From 15b3474f8721a3453e8732e264e0d20464e37288 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 30 Jul 2026 22:14:12 -0400 Subject: [PATCH 01/20] test(sccm): prepare distribution point corpus --- .../sccm/server/distribution_point/README.md | 18 + .../absent-dp/expected.json | 20 + .../absent-dp/manifest.json | 41 + .../current/DataTransferService.log | 1 + .../client-only-looking-request/expected.json | 30 + .../client-only-looking-request/manifest.json | 45 + .../dp-02/current/SMSDPProv.log | 3 + .../dp/current/SMSDPProv.log | 3 + .../site/current/PkgXferMgr.log | 2 + .../site/current/distmgr.log | 4 + .../site/dp-02/current/PkgXferMgr.log | 1 + .../site/dp-02/current/distmgr.log | 2 + .../content-version-mismatch/expected.json | 83 + .../content-version-mismatch/manifest.json | 134 ++ .../site/current/distmgr.log | 2 + .../distribution-failure/expected.json | 35 + .../distribution-failure/manifest.json | 29 + .../dp/current/SMSDPProv.log | 3 + .../site/current/PkgXferMgr.log | 1 + .../site/current/distmgr.log | 2 + .../healthy-package/expected.json | 61 + .../healthy-package/manifest.json | 77 + .../site/current/distmgr.log | 2 + .../incomplete/expected.json | 40 + .../incomplete/manifest.json | 61 + .../dp/malformed/SMSDPProv.log | 1 + .../site/current/distmgr.log | 1 + .../site/lo_/distmgr.log | 1 + .../rotation-boundary/expected.json | 25 + .../rotation-boundary/manifest.json | 69 + .../dp/current/SMSDPProv.log | 2 + .../site/current/PkgXferMgr.log | 1 + .../site/current/distmgr.log | 2 + .../server-dp-serve/dp/current/SMSdpmon.log | 1 + .../serve-observed/expected.json | 42 + .../serve-observed/manifest.json | 89 + .../site/current/PkgXferMgr.log | 1 + .../site/current/distmgr.log | 2 + .../transfer-retry/expected.json | 37 + .../transfer-retry/manifest.json | 49 + .../dp/current/SMSDPProv.log | 1 + .../site/current/PkgXferMgr.log | 1 + .../site/current/distmgr.log | 2 + .../validation-failure/expected.json | 39 + .../validation-failure/manifest.json | 69 + ...ver_distribution_point_fixture_contract.rs | 1645 +++++++++++++++++ .../issue-329-distribution-point-corpus.md | 151 ++ 47 files changed, 2931 insertions(+) create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/expected.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/manifest.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/evidence/client-content-control/current/DataTransferService.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/expected.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/manifest.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/dp-02/current/SMSDPProv.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/dp/current/SMSDPProv.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/PkgXferMgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/distmgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/PkgXferMgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/distmgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/expected.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/manifest.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/evidence/server-dp-distribution/site/current/distmgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/expected.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/manifest.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/dp/current/SMSDPProv.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/site/current/PkgXferMgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/site/current/distmgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/expected.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/manifest.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/evidence/server-dp-distribution/site/current/distmgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/expected.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/manifest.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/evidence/server-dp-distribution/dp/malformed/SMSDPProv.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/evidence/server-dp-distribution/site/current/distmgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/evidence/server-dp-distribution/site/lo_/distmgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/expected.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/manifest.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/dp/current/SMSDPProv.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/site/current/PkgXferMgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/site/current/distmgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-serve/dp/current/SMSdpmon.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/expected.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/manifest.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/evidence/server-dp-distribution/site/current/PkgXferMgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/evidence/server-dp-distribution/site/current/distmgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/expected.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/manifest.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/dp/current/SMSDPProv.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/site/current/PkgXferMgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/site/current/distmgr.log create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/expected.json create mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/manifest.json create mode 100644 crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs create mode 100644 docs/sccm/preparation/issue-329-distribution-point-corpus.md diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md new file mode 100644 index 000000000..1ef11fb2e --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md @@ -0,0 +1,18 @@ +# Synthetic Distribution Point fixture corpus + +This directory is test-only input for Issue `#329`. + +- Every evidence file is authored synthetic CCM text and contains the literal + `SYNTHETIC FIXTURE` marker. +- `manifest.json` records physical producer, workflow subject, coverage, + rotation, bounded path, encoding, and exact byte-count provenance. +- `expected.json` is a preparation label, not a frozen production API. +- Exact package/content/version/DP/profile keys keep versions and DPs + independent. +- Missing, denied, malformed, capped, or split evidence is coverage only. +- Client records and timestamps alone never establish a DP transaction or + cross-side cause. + +The focused Rust contract resolves every manifest path, runs captured CCM +files through the existing SCCM logical-record envelope, verifies normalized +timestamp/line provenance, and rejects adversarial mutations. diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/expected.json new file mode 100644 index 000000000..cebefbf55 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/expected.json @@ -0,0 +1,20 @@ +{ + "contractState": "proposedPendingReviewed318And335", + "workflow": "distributionPoint", + "scenario": "absent-dp", + "stateChain": ["receiveContent", "distribute", "transfer", "validate", "makeAvailable", "serveOrReport"], + "analysisContract": {"independentReducer": true, "consumesClientOutput": false, "crossSideCorrelationPerformed": false}, + "extractionProfile": {"selectionState": "selectedSynthetic", "profileId": "dp-server-5.00.test-v1", "validatedRole": "distributionPoint"}, + "roleAssessment": {"distributionPointObserved": true, "roleAbsentInferred": false, "missingDefaultPathInterpretation": "sourceCoverageOnly"}, + "coverage": [ + {"artifactId": "dp-absent-01-distmgr", "state": "absent"}, + {"artifactId": "dp-absent-02-provider", "state": "absent"} + ], + "transactions": [], + "sourceLocalObservations": [], + "artifactRequests": [ + {"sourceId": "server-dp-distribution", "reasonCode": "coverageAbsent"} + ], + "clientCausalClaims": [], + "correlationHandoff": {"issue": "#333", "performed": false, "timeOnlyEligible": false} +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/manifest.json new file mode 100644 index 000000000..e4d1cc788 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/manifest.json @@ -0,0 +1,41 @@ +{ + "sccmManifestVersion": 1, + "proposalOnly": true, + "syntheticFixture": true, + "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "artifacts": [ + { + "artifactId": "dp-absent-01-distmgr", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://default-site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:absent-distmgr", + "rotation": {"kind": "current", "lineageId": "absent-distmgr"}, + "captureState": "absent", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z" + }, + { + "artifactId": "dp-absent-02-provider", + "sourceId": "server-dp-distribution", + "producerRole": "distributionPoint", + "producerHostHandle": "safe:dp:lab-dp-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "SMSDPProv.log", + "sanitizedSourcePath": "SYNTHETIC://default-dp-root/Logs/SMSDPProv.log", + "pathFingerprint": "synthetic:absent-provider", + "rotation": {"kind": "current", "lineageId": "absent-provider"}, + "captureState": "absent", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z" + } + ] +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/evidence/client-content-control/current/DataTransferService.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/evidence/client-content-control/current/DataTransferService.log new file mode 100644 index 000000000..aa97c1886 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/evidence/client-content-control/current/DataTransferService.log @@ -0,0 +1 @@ + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/expected.json new file mode 100644 index 000000000..4cacb4db2 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/expected.json @@ -0,0 +1,30 @@ +{ + "contractState": "proposedPendingReviewed318And335", + "workflow": "distributionPoint", + "scenario": "client-only-looking-request", + "stateChain": ["receiveContent", "distribute", "transfer", "validate", "makeAvailable", "serveOrReport"], + "analysisContract": {"independentReducer": true, "consumesClientOutput": false, "crossSideCorrelationPerformed": false}, + "extractionProfile": {"selectionState": "selectedSynthetic", "profileId": "dp-server-5.00.test-v1", "validatedRole": "distributionPoint"}, + "roleAssessment": {"distributionPointObserved": false, "roleAbsentInferred": false, "missingDefaultPathInterpretation": "sourceCoverageOnly"}, + "coverage": [ + {"artifactId": "dp-client-control-01-data-transfer", "state": "captured"}, + {"artifactId": "dp-client-control-02-server-absent", "state": "absent"} + ], + "transactions": [], + "sourceLocalObservations": [ + { + "observationId": "client-control-01", + "classification": "ignoredClientEvidence", + "confidence": "low", + "confidenceCeiling": "low", + "correlationEligible": false, + "artifactIds": ["dp-client-control-01-data-transfer"], + "evidence": [{"artifactId": "dp-client-control-01-data-transfer", "startLine": 1, "endLine": 1}] + } + ], + "artifactRequests": [ + {"sourceId": "server-dp-distribution", "reasonCode": "coverageAbsent"} + ], + "clientCausalClaims": [], + "correlationHandoff": {"issue": "#333", "performed": false, "timeOnlyEligible": false} +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/manifest.json new file mode 100644 index 000000000..33055c5f4 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/manifest.json @@ -0,0 +1,45 @@ +{ + "sccmManifestVersion": 1, + "proposalOnly": true, + "syntheticFixture": true, + "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["siteServer"]}, + "artifacts": [ + { + "artifactId": "dp-client-control-01-data-transfer", + "sourceId": "client-content-control", + "producerRole": "client", + "producerHostHandle": "safe:client:lab-client-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "DataTransferService.log", + "sanitizedSourcePath": "SYNTHETIC://client-control/Logs/DataTransferService.log", + "pathFingerprint": "synthetic:client-only-data-transfer", + "rotation": {"kind": "current", "lineageId": "client-only-data-transfer", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 386, + "relativePath": "evidence/client-content-control/current/DataTransferService.log" + }, + { + "artifactId": "dp-client-control-02-server-absent", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:client-only-server-absent", + "rotation": {"kind": "current", "lineageId": "client-only-server-absent"}, + "captureState": "absent", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z" + } + ] +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/dp-02/current/SMSDPProv.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/dp-02/current/SMSDPProv.log new file mode 100644 index 000000000..2db3daa9d --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/dp-02/current/SMSDPProv.log @@ -0,0 +1,3 @@ + + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/dp/current/SMSDPProv.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/dp/current/SMSDPProv.log new file mode 100644 index 000000000..b926b006a --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/dp/current/SMSDPProv.log @@ -0,0 +1,3 @@ + + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/PkgXferMgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/PkgXferMgr.log new file mode 100644 index 000000000..df2c54a87 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/PkgXferMgr.log @@ -0,0 +1,2 @@ + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/distmgr.log new file mode 100644 index 000000000..4089684ff --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/distmgr.log @@ -0,0 +1,4 @@ + + + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/PkgXferMgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/PkgXferMgr.log new file mode 100644 index 000000000..1ec58d395 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/PkgXferMgr.log @@ -0,0 +1 @@ + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/distmgr.log new file mode 100644 index 000000000..72c42e73a --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/distmgr.log @@ -0,0 +1,2 @@ + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/expected.json new file mode 100644 index 000000000..a37868c5b --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/expected.json @@ -0,0 +1,83 @@ +{ + "contractState": "proposedPendingReviewed318And335", + "workflow": "distributionPoint", + "scenario": "content-version-mismatch", + "stateChain": ["receiveContent", "distribute", "transfer", "validate", "makeAvailable", "serveOrReport"], + "analysisContract": {"independentReducer": true, "consumesClientOutput": false, "crossSideCorrelationPerformed": false}, + "extractionProfile": {"selectionState": "selectedSynthetic", "profileId": "dp-server-5.00.test-v1", "validatedRole": "distributionPoint"}, + "roleAssessment": {"distributionPointObserved": true, "roleAbsentInferred": false, "missingDefaultPathInterpretation": "sourceCoverageOnly"}, + "coverage": [ + {"artifactId": "dp-version-01-distmgr", "state": "captured"}, + {"artifactId": "dp-version-02-pkgxfer", "state": "captured"}, + {"artifactId": "dp-version-03-provider", "state": "captured"}, + {"artifactId": "dp-version-04-distmgr-dp02", "state": "captured"}, + {"artifactId": "dp-version-05-pkgxfer-dp02", "state": "captured"}, + {"artifactId": "dp-version-06-provider-dp02", "state": "captured"} + ], + "transactions": [ + { + "transactionId": "dp:LAB00005:content-epsilon:v1:safe:dp:lab-dp-01", + "key": {"packageId": "LAB00005", "contentId": "content-epsilon", "contentVersion": 1, "siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "confidence": "exact", "extractionProfileId": "dp-server-5.00.test-v1"}, + "topologyCompatibility": "exact", + "correlationEligible": true, + "state": "succeeded", + "classification": "success", + "confidence": "high", + "confidenceCeiling": "high", + "lastSuccessfulPhase": "serveOrReport", + "nextSourceId": null, + "coverageGapArtifactIds": [], + "observations": [ + {"observationId": "01-v1-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-01-distmgr", "startLine": 1, "endLine": 1}]}, + {"observationId": "02-v1-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-01-distmgr", "startLine": 2, "endLine": 2}]}, + {"observationId": "03-v1-transfer", "phase": "transfer", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-02-pkgxfer", "startLine": 1, "endLine": 1}]}, + {"observationId": "04-v1-validate", "phase": "validate", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-03-provider", "startLine": 1, "endLine": 1}]}, + {"observationId": "05-v1-available", "phase": "makeAvailable", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-03-provider", "startLine": 2, "endLine": 2}]}, + {"observationId": "06-v1-report", "phase": "serveOrReport", "disposition": "succeeded", "terminal": true, "evidence": [{"artifactId": "dp-version-03-provider", "startLine": 3, "endLine": 3}]} + ] + }, + { + "transactionId": "dp:LAB00005:content-epsilon:v1:safe:dp:lab-dp-02", + "key": {"packageId": "LAB00005", "contentId": "content-epsilon", "contentVersion": 1, "siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-02", "confidence": "exact", "extractionProfileId": "dp-server-5.00.test-v1"}, + "topologyCompatibility": "exact", + "correlationEligible": true, + "state": "succeeded", + "classification": "success", + "confidence": "high", + "confidenceCeiling": "high", + "lastSuccessfulPhase": "serveOrReport", + "nextSourceId": null, + "coverageGapArtifactIds": [], + "observations": [ + {"observationId": "01-dp02-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-04-distmgr-dp02", "startLine": 1, "endLine": 1}]}, + {"observationId": "02-dp02-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-04-distmgr-dp02", "startLine": 2, "endLine": 2}]}, + {"observationId": "03-dp02-transfer", "phase": "transfer", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-05-pkgxfer-dp02", "startLine": 1, "endLine": 1}]}, + {"observationId": "04-dp02-validate", "phase": "validate", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-06-provider-dp02", "startLine": 1, "endLine": 1}]}, + {"observationId": "05-dp02-available", "phase": "makeAvailable", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-06-provider-dp02", "startLine": 2, "endLine": 2}]}, + {"observationId": "06-dp02-report", "phase": "serveOrReport", "disposition": "succeeded", "terminal": true, "evidence": [{"artifactId": "dp-version-06-provider-dp02", "startLine": 3, "endLine": 3}]} + ] + }, + { + "transactionId": "dp:LAB00005:content-epsilon:v2:safe:dp:lab-dp-01", + "key": {"packageId": "LAB00005", "contentId": "content-epsilon", "contentVersion": 2, "siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "confidence": "exact", "extractionProfileId": "dp-server-5.00.test-v1"}, + "topologyCompatibility": "exact", + "correlationEligible": true, + "state": "deferred", + "classification": "blockedOrDeferred", + "confidence": "medium", + "confidenceCeiling": "medium", + "lastSuccessfulPhase": "distribute", + "nextSourceId": null, + "coverageGapArtifactIds": [], + "observations": [ + {"observationId": "01-v2-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-01-distmgr", "startLine": 3, "endLine": 3}]}, + {"observationId": "02-v2-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-01-distmgr", "startLine": 4, "endLine": 4}]}, + {"observationId": "03-v2-transfer-retry", "phase": "transfer", "disposition": "retrying", "terminal": false, "evidence": [{"artifactId": "dp-version-02-pkgxfer", "startLine": 2, "endLine": 2}]} + ] + } + ], + "sourceLocalObservations": [], + "artifactRequests": [], + "clientCausalClaims": [], + "correlationHandoff": {"issue": "#333", "performed": false, "timeOnlyEligible": false} +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/manifest.json new file mode 100644 index 000000000..278ef27b9 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/manifest.json @@ -0,0 +1,134 @@ +{ + "sccmManifestVersion": 1, + "proposalOnly": true, + "syntheticFixture": true, + "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, + "topology": { + "siteCode": "LAB", + "distributionPointHandle": "safe:dp:lab-dp-01", + "distributionPointHandles": ["safe:dp:lab-dp-01", "safe:dp:lab-dp-02"], + "rolesObserved": ["distributionPoint", "siteServer"] + }, + "artifacts": [ + { + "artifactId": "dp-version-01-distmgr", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:version-distmgr", + "rotation": {"kind": "current", "lineageId": "version-distmgr", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 1404, + "relativePath": "evidence/server-dp-distribution/site/current/distmgr.log" + }, + { + "artifactId": "dp-version-02-pkgxfer", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "PkgXferMgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/PkgXferMgr.log", + "pathFingerprint": "synthetic:version-pkgxfer", + "rotation": {"kind": "current", "lineageId": "version-pkgxfer", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 705, + "relativePath": "evidence/server-dp-distribution/site/current/PkgXferMgr.log" + }, + { + "artifactId": "dp-version-03-provider", + "sourceId": "server-dp-distribution", + "producerRole": "distributionPoint", + "producerHostHandle": "safe:dp:lab-dp-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "SMSDPProv.log", + "sanitizedSourcePath": "SYNTHETIC://dp-root/Logs/SMSDPProv.log", + "pathFingerprint": "synthetic:version-provider", + "rotation": {"kind": "current", "lineageId": "version-provider", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 1062, + "relativePath": "evidence/server-dp-distribution/dp/current/SMSDPProv.log" + }, + { + "artifactId": "dp-version-04-distmgr-dp02", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-02", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:version-distmgr-dp02", + "rotation": {"kind": "current", "lineageId": "version-distmgr-dp02", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 702, + "relativePath": "evidence/server-dp-distribution/site/dp-02/current/distmgr.log" + }, + { + "artifactId": "dp-version-05-pkgxfer-dp02", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-02", + "sourceKind": "ccmLog", + "originalBasename": "PkgXferMgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/PkgXferMgr.log", + "pathFingerprint": "synthetic:version-pkgxfer-dp02", + "rotation": {"kind": "current", "lineageId": "version-pkgxfer-dp02", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 353, + "relativePath": "evidence/server-dp-distribution/site/dp-02/current/PkgXferMgr.log" + }, + { + "artifactId": "dp-version-06-provider-dp02", + "sourceId": "server-dp-distribution", + "producerRole": "distributionPoint", + "producerHostHandle": "safe:dp:lab-dp-02", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-02", + "sourceKind": "ccmLog", + "originalBasename": "SMSDPProv.log", + "sanitizedSourcePath": "SYNTHETIC://dp-02-root/Logs/SMSDPProv.log", + "pathFingerprint": "synthetic:version-provider-dp02", + "rotation": {"kind": "current", "lineageId": "version-provider-dp02", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 1062, + "relativePath": "evidence/server-dp-distribution/dp-02/current/SMSDPProv.log" + } + ] +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/evidence/server-dp-distribution/site/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/evidence/server-dp-distribution/site/current/distmgr.log new file mode 100644 index 000000000..0b7aa917f --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/evidence/server-dp-distribution/site/current/distmgr.log @@ -0,0 +1,2 @@ + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/expected.json new file mode 100644 index 000000000..6db59d4f3 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/expected.json @@ -0,0 +1,35 @@ +{ + "contractState": "proposedPendingReviewed318And335", + "workflow": "distributionPoint", + "scenario": "distribution-failure", + "stateChain": ["receiveContent", "distribute", "transfer", "validate", "makeAvailable", "serveOrReport"], + "analysisContract": {"independentReducer": true, "consumesClientOutput": false, "crossSideCorrelationPerformed": false}, + "extractionProfile": {"selectionState": "selectedSynthetic", "profileId": "dp-server-5.00.test-v1", "validatedRole": "distributionPoint"}, + "roleAssessment": {"distributionPointObserved": true, "roleAbsentInferred": false, "missingDefaultPathInterpretation": "sourceCoverageOnly"}, + "coverage": [ + {"artifactId": "dp-distribution-failure-01-distmgr", "state": "captured"} + ], + "transactions": [ + { + "transactionId": "dp:LAB00002:content-beta:v1:safe:dp:lab-dp-01", + "key": {"packageId": "LAB00002", "contentId": "content-beta", "contentVersion": 1, "siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "confidence": "exact", "extractionProfileId": "dp-server-5.00.test-v1"}, + "topologyCompatibility": "exact", + "correlationEligible": true, + "state": "failed", + "classification": "confirmedFailure", + "confidence": "high", + "confidenceCeiling": "high", + "lastSuccessfulPhase": "receiveContent", + "nextSourceId": null, + "coverageGapArtifactIds": [], + "observations": [ + {"observationId": "01-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-distribution-failure-01-distmgr", "startLine": 1, "endLine": 1}]}, + {"observationId": "02-distribute-failed", "phase": "distribute", "disposition": "failed", "terminal": true, "evidence": [{"artifactId": "dp-distribution-failure-01-distmgr", "startLine": 2, "endLine": 2}]} + ] + } + ], + "sourceLocalObservations": [], + "artifactRequests": [], + "clientCausalClaims": [], + "correlationHandoff": {"issue": "#333", "performed": false, "timeOnlyEligible": false} +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/manifest.json new file mode 100644 index 000000000..5ad7144f4 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/manifest.json @@ -0,0 +1,29 @@ +{ + "sccmManifestVersion": 1, + "proposalOnly": true, + "syntheticFixture": true, + "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "artifacts": [ + { + "artifactId": "dp-distribution-failure-01-distmgr", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:distribution-failure", + "rotation": {"kind": "current", "lineageId": "distribution-failure", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 692, + "relativePath": "evidence/server-dp-distribution/site/current/distmgr.log" + } + ] +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/dp/current/SMSDPProv.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/dp/current/SMSDPProv.log new file mode 100644 index 000000000..0474c8b44 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/dp/current/SMSDPProv.log @@ -0,0 +1,3 @@ + + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/site/current/PkgXferMgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/site/current/PkgXferMgr.log new file mode 100644 index 000000000..85ad5dd18 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/site/current/PkgXferMgr.log @@ -0,0 +1 @@ + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/site/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/site/current/distmgr.log new file mode 100644 index 000000000..697091415 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/evidence/server-dp-distribution/site/current/distmgr.log @@ -0,0 +1,2 @@ + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/expected.json new file mode 100644 index 000000000..5eacf62f1 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/expected.json @@ -0,0 +1,61 @@ +{ + "contractState": "proposedPendingReviewed318And335", + "workflow": "distributionPoint", + "scenario": "healthy-package", + "stateChain": ["receiveContent", "distribute", "transfer", "validate", "makeAvailable", "serveOrReport"], + "analysisContract": { + "independentReducer": true, + "consumesClientOutput": false, + "crossSideCorrelationPerformed": false + }, + "extractionProfile": { + "selectionState": "selectedSynthetic", + "profileId": "dp-server-5.00.test-v1", + "validatedRole": "distributionPoint" + }, + "roleAssessment": { + "distributionPointObserved": true, + "roleAbsentInferred": false, + "missingDefaultPathInterpretation": "sourceCoverageOnly" + }, + "coverage": [ + {"artifactId": "dp-healthy-01-distmgr", "state": "captured"}, + {"artifactId": "dp-healthy-02-pkgxfer", "state": "captured"}, + {"artifactId": "dp-healthy-03-provider", "state": "captured"} + ], + "transactions": [ + { + "transactionId": "dp:LAB00001:content-alpha:v1:safe:dp:lab-dp-01", + "key": { + "packageId": "LAB00001", + "contentId": "content-alpha", + "contentVersion": 1, + "siteCode": "LAB", + "distributionPointHandle": "safe:dp:lab-dp-01", + "confidence": "exact", + "extractionProfileId": "dp-server-5.00.test-v1" + }, + "topologyCompatibility": "exact", + "correlationEligible": true, + "state": "succeeded", + "classification": "success", + "confidence": "high", + "confidenceCeiling": "high", + "lastSuccessfulPhase": "serveOrReport", + "nextSourceId": null, + "coverageGapArtifactIds": [], + "observations": [ + {"observationId": "01-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-healthy-01-distmgr", "startLine": 1, "endLine": 1}]}, + {"observationId": "02-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-healthy-01-distmgr", "startLine": 2, "endLine": 2}]}, + {"observationId": "03-transfer", "phase": "transfer", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-healthy-02-pkgxfer", "startLine": 1, "endLine": 1}]}, + {"observationId": "04-validate", "phase": "validate", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-healthy-03-provider", "startLine": 1, "endLine": 1}]}, + {"observationId": "05-available", "phase": "makeAvailable", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-healthy-03-provider", "startLine": 2, "endLine": 2}]}, + {"observationId": "06-report", "phase": "serveOrReport", "disposition": "succeeded", "terminal": true, "evidence": [{"artifactId": "dp-healthy-03-provider", "startLine": 3, "endLine": 3}]} + ] + } + ], + "sourceLocalObservations": [], + "artifactRequests": [], + "clientCausalClaims": [], + "correlationHandoff": {"issue": "#333", "performed": false, "timeOnlyEligible": false} +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/manifest.json new file mode 100644 index 000000000..8f6a931e5 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/manifest.json @@ -0,0 +1,77 @@ +{ + "sccmManifestVersion": 1, + "proposalOnly": true, + "syntheticFixture": true, + "bundle": { + "bundleRole": "server", + "workflow": "distributionPoint", + "capturedUtc": "2026-07-30T12:30:00Z" + }, + "topology": { + "siteCode": "LAB", + "distributionPointHandle": "safe:dp:lab-dp-01", + "rolesObserved": ["distributionPoint", "siteServer"] + }, + "artifacts": [ + { + "artifactId": "dp-healthy-01-distmgr", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:healthy-distmgr", + "rotation": {"kind": "current", "lineageId": "healthy-distmgr", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 698, + "relativePath": "evidence/server-dp-distribution/site/current/distmgr.log" + }, + { + "artifactId": "dp-healthy-02-pkgxfer", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "PkgXferMgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/PkgXferMgr.log", + "pathFingerprint": "synthetic:healthy-pkgxfer", + "rotation": {"kind": "current", "lineageId": "healthy-pkgxfer", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 351, + "relativePath": "evidence/server-dp-distribution/site/current/PkgXferMgr.log" + }, + { + "artifactId": "dp-healthy-03-provider", + "sourceId": "server-dp-distribution", + "producerRole": "distributionPoint", + "producerHostHandle": "safe:dp:lab-dp-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "SMSDPProv.log", + "sanitizedSourcePath": "SYNTHETIC://dp-root/Logs/SMSDPProv.log", + "pathFingerprint": "synthetic:healthy-provider", + "rotation": {"kind": "current", "lineageId": "healthy-provider", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 1056, + "relativePath": "evidence/server-dp-distribution/dp/current/SMSDPProv.log" + } + ] +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/evidence/server-dp-distribution/site/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/evidence/server-dp-distribution/site/current/distmgr.log new file mode 100644 index 000000000..e333a9589 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/evidence/server-dp-distribution/site/current/distmgr.log @@ -0,0 +1,2 @@ + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/expected.json new file mode 100644 index 000000000..f0629e642 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/expected.json @@ -0,0 +1,40 @@ +{ + "contractState": "proposedPendingReviewed318And335", + "workflow": "distributionPoint", + "scenario": "incomplete", + "stateChain": ["receiveContent", "distribute", "transfer", "validate", "makeAvailable", "serveOrReport"], + "analysisContract": {"independentReducer": true, "consumesClientOutput": false, "crossSideCorrelationPerformed": false}, + "extractionProfile": {"selectionState": "selectedSynthetic", "profileId": "dp-server-5.00.test-v1", "validatedRole": "distributionPoint"}, + "roleAssessment": {"distributionPointObserved": true, "roleAbsentInferred": false, "missingDefaultPathInterpretation": "sourceCoverageOnly"}, + "coverage": [ + {"artifactId": "dp-incomplete-01-distmgr", "state": "captured"}, + {"artifactId": "dp-incomplete-02-pkgxfer-denied", "state": "accessDenied"}, + {"artifactId": "dp-incomplete-03-provider-absent", "state": "absent"} + ], + "transactions": [ + { + "transactionId": "dp:LAB00007:content-eta:v1:safe:dp:lab-dp-01", + "key": {"packageId": "LAB00007", "contentId": "content-eta", "contentVersion": 1, "siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "confidence": "exact", "extractionProfileId": "dp-server-5.00.test-v1"}, + "topologyCompatibility": "exact", + "correlationEligible": true, + "state": "incomplete", + "classification": "insufficientEvidence", + "confidence": "low", + "confidenceCeiling": "low", + "lastSuccessfulPhase": "distribute", + "nextSourceId": "server-dp-distribution", + "coverageGapArtifactIds": ["dp-incomplete-02-pkgxfer-denied", "dp-incomplete-03-provider-absent"], + "observations": [ + {"observationId": "01-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-incomplete-01-distmgr", "startLine": 1, "endLine": 1}]}, + {"observationId": "02-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-incomplete-01-distmgr", "startLine": 2, "endLine": 2}]} + ] + } + ], + "sourceLocalObservations": [], + "artifactRequests": [ + {"sourceId": "server-dp-distribution", "reasonCode": "coverageAbsent"}, + {"sourceId": "server-dp-distribution", "reasonCode": "coverageAccessDenied"} + ], + "clientCausalClaims": [], + "correlationHandoff": {"issue": "#333", "performed": false, "timeOnlyEligible": false} +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/manifest.json new file mode 100644 index 000000000..ae905b3aa --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/manifest.json @@ -0,0 +1,61 @@ +{ + "sccmManifestVersion": 1, + "proposalOnly": true, + "syntheticFixture": true, + "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "artifacts": [ + { + "artifactId": "dp-incomplete-01-distmgr", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:incomplete-distmgr", + "rotation": {"kind": "current", "lineageId": "incomplete-distmgr", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 694, + "relativePath": "evidence/server-dp-distribution/site/current/distmgr.log" + }, + { + "artifactId": "dp-incomplete-02-pkgxfer-denied", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "PkgXferMgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/PkgXferMgr.log", + "pathFingerprint": "synthetic:incomplete-pkgxfer", + "rotation": {"kind": "current", "lineageId": "incomplete-pkgxfer"}, + "captureState": "accessDenied", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z" + }, + { + "artifactId": "dp-incomplete-03-provider-absent", + "sourceId": "server-dp-distribution", + "producerRole": "distributionPoint", + "producerHostHandle": "safe:dp:lab-dp-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "SMSDPProv.log", + "sanitizedSourcePath": "SYNTHETIC://dp-root/Logs/SMSDPProv.log", + "pathFingerprint": "synthetic:incomplete-provider", + "rotation": {"kind": "current", "lineageId": "incomplete-provider"}, + "captureState": "absent", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z" + } + ] +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/evidence/server-dp-distribution/dp/malformed/SMSDPProv.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/evidence/server-dp-distribution/dp/malformed/SMSDPProv.log new file mode 100644 index 000000000..520e8a220 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/evidence/server-dp-distribution/dp/malformed/SMSDPProv.log @@ -0,0 +1 @@ +SYNTHETIC FIXTURE MALFORMED CCM RECORD WITHOUT ATTRIBUTES diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/evidence/server-dp-distribution/site/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/evidence/server-dp-distribution/site/current/distmgr.log new file mode 100644 index 000000000..a01c29afd --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/evidence/server-dp-distribution/site/current/distmgr.log @@ -0,0 +1 @@ +SYNTHETIC FIXTURE CURRENT FRAGMENT ONLY diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/expected.json new file mode 100644 index 000000000..1afd42488 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/expected.json @@ -0,0 +1,25 @@ +{ + "contractState": "proposedPendingReviewed318And335", + "workflow": "distributionPoint", + "scenario": "rotation-boundary", + "stateChain": ["receiveContent", "distribute", "transfer", "validate", "makeAvailable", "serveOrReport"], + "analysisContract": {"independentReducer": true, "consumesClientOutput": false, "crossSideCorrelationPerformed": false}, + "extractionProfile": {"selectionState": "selectedSynthetic", "profileId": "dp-server-5.00.test-v1", "validatedRole": "distributionPoint"}, + "roleAssessment": {"distributionPointObserved": true, "roleAbsentInferred": false, "missingDefaultPathInterpretation": "sourceCoverageOnly"}, + "coverage": [ + {"artifactId": "dp-rotation-01-current-fragment", "state": "captured"}, + {"artifactId": "dp-rotation-02-lo-fragment", "state": "captured"}, + {"artifactId": "dp-rotation-03-malformed", "state": "parseFailed"} + ], + "transactions": [], + "sourceLocalObservations": [ + {"observationId": "rotation-01-split", "classification": "rotationSplit", "confidence": "low", "confidenceCeiling": "low", "correlationEligible": false, "artifactIds": ["dp-rotation-01-current-fragment", "dp-rotation-02-lo-fragment"], "evidence": []}, + {"observationId": "rotation-02-malformed", "classification": "malformedEvidence", "confidence": "low", "confidenceCeiling": "low", "correlationEligible": false, "artifactIds": ["dp-rotation-03-malformed"], "evidence": []} + ], + "artifactRequests": [ + {"sourceId": "server-dp-distribution", "reasonCode": "coverageMalformed"}, + {"sourceId": "server-dp-distribution", "reasonCode": "coverageRotationSplit"} + ], + "clientCausalClaims": [], + "correlationHandoff": {"issue": "#333", "performed": false, "timeOnlyEligible": false} +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/manifest.json new file mode 100644 index 000000000..844006abb --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/manifest.json @@ -0,0 +1,69 @@ +{ + "sccmManifestVersion": 1, + "proposalOnly": true, + "syntheticFixture": true, + "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "artifacts": [ + { + "artifactId": "dp-rotation-01-current-fragment", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:rotation-current", + "rotation": {"kind": "current", "lineageId": "rotation-distmgr", "fragmentComplete": false}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 88, + "relativePath": "evidence/server-dp-distribution/site/current/distmgr.log" + }, + { + "artifactId": "dp-rotation-02-lo-fragment", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.lo_", + "pathFingerprint": "synthetic:rotation-lo", + "rotation": {"kind": "lo_", "lineageId": "rotation-distmgr", "fragmentComplete": false}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 84, + "relativePath": "evidence/server-dp-distribution/site/lo_/distmgr.log" + }, + { + "artifactId": "dp-rotation-03-malformed", + "sourceId": "server-dp-distribution", + "producerRole": "distributionPoint", + "producerHostHandle": "safe:dp:lab-dp-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "SMSDPProv.log", + "sanitizedSourcePath": "SYNTHETIC://dp-root/Logs/SMSDPProv.log", + "pathFingerprint": "synthetic:rotation-malformed", + "rotation": {"kind": "current", "lineageId": "rotation-provider", "fragmentComplete": true}, + "captureState": "parseFailed", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 58, + "relativePath": "evidence/server-dp-distribution/dp/malformed/SMSDPProv.log" + } + ] +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/dp/current/SMSDPProv.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/dp/current/SMSDPProv.log new file mode 100644 index 000000000..5ff2d1528 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/dp/current/SMSDPProv.log @@ -0,0 +1,2 @@ + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/site/current/PkgXferMgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/site/current/PkgXferMgr.log new file mode 100644 index 000000000..e7df5a40b --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/site/current/PkgXferMgr.log @@ -0,0 +1 @@ + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/site/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/site/current/distmgr.log new file mode 100644 index 000000000..26acda749 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-distribution/site/current/distmgr.log @@ -0,0 +1,2 @@ + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-serve/dp/current/SMSdpmon.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-serve/dp/current/SMSdpmon.log new file mode 100644 index 000000000..8a93861fc --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/evidence/server-dp-serve/dp/current/SMSdpmon.log @@ -0,0 +1 @@ + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/expected.json new file mode 100644 index 000000000..80e87d824 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/expected.json @@ -0,0 +1,42 @@ +{ + "contractState": "proposedPendingReviewed318And335", + "workflow": "distributionPoint", + "scenario": "serve-observed", + "stateChain": ["receiveContent", "distribute", "transfer", "validate", "makeAvailable", "serveOrReport"], + "analysisContract": {"independentReducer": true, "consumesClientOutput": false, "crossSideCorrelationPerformed": false}, + "extractionProfile": {"selectionState": "selectedSynthetic", "profileId": "dp-server-5.00.test-v1", "validatedRole": "distributionPoint"}, + "roleAssessment": {"distributionPointObserved": true, "roleAbsentInferred": false, "missingDefaultPathInterpretation": "sourceCoverageOnly"}, + "coverage": [ + {"artifactId": "dp-serve-01-distmgr", "state": "captured"}, + {"artifactId": "dp-serve-02-pkgxfer", "state": "captured"}, + {"artifactId": "dp-serve-03-provider", "state": "captured"}, + {"artifactId": "dp-serve-04-status", "state": "captured"} + ], + "transactions": [ + { + "transactionId": "dp:LAB00006:content-zeta:v1:safe:dp:lab-dp-01", + "key": {"packageId": "LAB00006", "contentId": "content-zeta", "contentVersion": 1, "siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "confidence": "exact", "extractionProfileId": "dp-server-5.00.test-v1"}, + "topologyCompatibility": "exact", + "correlationEligible": true, + "state": "succeeded", + "classification": "success", + "confidence": "high", + "confidenceCeiling": "high", + "lastSuccessfulPhase": "serveOrReport", + "nextSourceId": null, + "coverageGapArtifactIds": [], + "observations": [ + {"observationId": "01-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-serve-01-distmgr", "startLine": 1, "endLine": 1}]}, + {"observationId": "02-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-serve-01-distmgr", "startLine": 2, "endLine": 2}]}, + {"observationId": "03-transfer", "phase": "transfer", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-serve-02-pkgxfer", "startLine": 1, "endLine": 1}]}, + {"observationId": "04-validate", "phase": "validate", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-serve-03-provider", "startLine": 1, "endLine": 1}]}, + {"observationId": "05-available", "phase": "makeAvailable", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-serve-03-provider", "startLine": 2, "endLine": 2}]}, + {"observationId": "06-serve-observed", "phase": "serveOrReport", "disposition": "succeeded", "terminal": true, "evidence": [{"artifactId": "dp-serve-04-status", "startLine": 1, "endLine": 1}]} + ] + } + ], + "sourceLocalObservations": [], + "artifactRequests": [], + "clientCausalClaims": [], + "correlationHandoff": {"issue": "#333", "performed": false, "timeOnlyEligible": false} +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/manifest.json new file mode 100644 index 000000000..494aea4a1 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/manifest.json @@ -0,0 +1,89 @@ +{ + "sccmManifestVersion": 1, + "proposalOnly": true, + "syntheticFixture": true, + "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "artifacts": [ + { + "artifactId": "dp-serve-01-distmgr", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:serve-distmgr", + "rotation": {"kind": "current", "lineageId": "serve-distmgr", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 696, + "relativePath": "evidence/server-dp-distribution/site/current/distmgr.log" + }, + { + "artifactId": "dp-serve-02-pkgxfer", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "PkgXferMgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/PkgXferMgr.log", + "pathFingerprint": "synthetic:serve-pkgxfer", + "rotation": {"kind": "current", "lineageId": "serve-pkgxfer", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 350, + "relativePath": "evidence/server-dp-distribution/site/current/PkgXferMgr.log" + }, + { + "artifactId": "dp-serve-03-provider", + "sourceId": "server-dp-distribution", + "producerRole": "distributionPoint", + "producerHostHandle": "safe:dp:lab-dp-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "SMSDPProv.log", + "sanitizedSourcePath": "SYNTHETIC://dp-root/Logs/SMSDPProv.log", + "pathFingerprint": "synthetic:serve-provider", + "rotation": {"kind": "current", "lineageId": "serve-provider", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 701, + "relativePath": "evidence/server-dp-distribution/dp/current/SMSDPProv.log" + }, + { + "artifactId": "dp-serve-04-status", + "sourceId": "server-dp-serve", + "producerRole": "distributionPoint", + "producerHostHandle": "safe:dp:lab-dp-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "SMSdpmon.log", + "sanitizedSourcePath": "SYNTHETIC://dp-root/Logs/SMSdpmon.log", + "pathFingerprint": "synthetic:serve-status", + "rotation": {"kind": "current", "lineageId": "serve-status", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 350, + "relativePath": "evidence/server-dp-serve/dp/current/SMSdpmon.log" + } + ] +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/evidence/server-dp-distribution/site/current/PkgXferMgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/evidence/server-dp-distribution/site/current/PkgXferMgr.log new file mode 100644 index 000000000..154901bed --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/evidence/server-dp-distribution/site/current/PkgXferMgr.log @@ -0,0 +1 @@ + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/evidence/server-dp-distribution/site/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/evidence/server-dp-distribution/site/current/distmgr.log new file mode 100644 index 000000000..b0d629f5c --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/evidence/server-dp-distribution/site/current/distmgr.log @@ -0,0 +1,2 @@ + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/expected.json new file mode 100644 index 000000000..428e529ba --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/expected.json @@ -0,0 +1,37 @@ +{ + "contractState": "proposedPendingReviewed318And335", + "workflow": "distributionPoint", + "scenario": "transfer-retry", + "stateChain": ["receiveContent", "distribute", "transfer", "validate", "makeAvailable", "serveOrReport"], + "analysisContract": {"independentReducer": true, "consumesClientOutput": false, "crossSideCorrelationPerformed": false}, + "extractionProfile": {"selectionState": "selectedSynthetic", "profileId": "dp-server-5.00.test-v1", "validatedRole": "distributionPoint"}, + "roleAssessment": {"distributionPointObserved": true, "roleAbsentInferred": false, "missingDefaultPathInterpretation": "sourceCoverageOnly"}, + "coverage": [ + {"artifactId": "dp-transfer-retry-01-distmgr", "state": "captured"}, + {"artifactId": "dp-transfer-retry-02-pkgxfer", "state": "captured"} + ], + "transactions": [ + { + "transactionId": "dp:LAB00003:content-gamma:v1:safe:dp:lab-dp-01", + "key": {"packageId": "LAB00003", "contentId": "content-gamma", "contentVersion": 1, "siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "confidence": "exact", "extractionProfileId": "dp-server-5.00.test-v1"}, + "topologyCompatibility": "exact", + "correlationEligible": true, + "state": "deferred", + "classification": "blockedOrDeferred", + "confidence": "medium", + "confidenceCeiling": "medium", + "lastSuccessfulPhase": "distribute", + "nextSourceId": null, + "coverageGapArtifactIds": [], + "observations": [ + {"observationId": "01-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-transfer-retry-01-distmgr", "startLine": 1, "endLine": 1}]}, + {"observationId": "02-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-transfer-retry-01-distmgr", "startLine": 2, "endLine": 2}]}, + {"observationId": "03-transfer-retry", "phase": "transfer", "disposition": "retrying", "terminal": false, "evidence": [{"artifactId": "dp-transfer-retry-02-pkgxfer", "startLine": 1, "endLine": 1}]} + ] + } + ], + "sourceLocalObservations": [], + "artifactRequests": [], + "clientCausalClaims": [], + "correlationHandoff": {"issue": "#333", "performed": false, "timeOnlyEligible": false} +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/manifest.json new file mode 100644 index 000000000..f7773f185 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/manifest.json @@ -0,0 +1,49 @@ +{ + "sccmManifestVersion": 1, + "proposalOnly": true, + "syntheticFixture": true, + "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "artifacts": [ + { + "artifactId": "dp-transfer-retry-01-distmgr", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:retry-distmgr", + "rotation": {"kind": "current", "lineageId": "retry-distmgr", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 698, + "relativePath": "evidence/server-dp-distribution/site/current/distmgr.log" + }, + { + "artifactId": "dp-transfer-retry-02-pkgxfer", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "PkgXferMgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/PkgXferMgr.log", + "pathFingerprint": "synthetic:retry-pkgxfer", + "rotation": {"kind": "current", "lineageId": "retry-pkgxfer", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 350, + "relativePath": "evidence/server-dp-distribution/site/current/PkgXferMgr.log" + } + ] +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/dp/current/SMSDPProv.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/dp/current/SMSDPProv.log new file mode 100644 index 000000000..65223a49b --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/dp/current/SMSDPProv.log @@ -0,0 +1 @@ + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/site/current/PkgXferMgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/site/current/PkgXferMgr.log new file mode 100644 index 000000000..4b42203ad --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/site/current/PkgXferMgr.log @@ -0,0 +1 @@ + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/site/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/site/current/distmgr.log new file mode 100644 index 000000000..c80962a06 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/evidence/server-dp-distribution/site/current/distmgr.log @@ -0,0 +1,2 @@ + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/expected.json new file mode 100644 index 000000000..dc8309516 --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/expected.json @@ -0,0 +1,39 @@ +{ + "contractState": "proposedPendingReviewed318And335", + "workflow": "distributionPoint", + "scenario": "validation-failure", + "stateChain": ["receiveContent", "distribute", "transfer", "validate", "makeAvailable", "serveOrReport"], + "analysisContract": {"independentReducer": true, "consumesClientOutput": false, "crossSideCorrelationPerformed": false}, + "extractionProfile": {"selectionState": "selectedSynthetic", "profileId": "dp-server-5.00.test-v1", "validatedRole": "distributionPoint"}, + "roleAssessment": {"distributionPointObserved": true, "roleAbsentInferred": false, "missingDefaultPathInterpretation": "sourceCoverageOnly"}, + "coverage": [ + {"artifactId": "dp-validation-failure-01-distmgr", "state": "captured"}, + {"artifactId": "dp-validation-failure-02-pkgxfer", "state": "captured"}, + {"artifactId": "dp-validation-failure-03-provider", "state": "captured"} + ], + "transactions": [ + { + "transactionId": "dp:LAB00004:content-delta:v1:safe:dp:lab-dp-01", + "key": {"packageId": "LAB00004", "contentId": "content-delta", "contentVersion": 1, "siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "confidence": "exact", "extractionProfileId": "dp-server-5.00.test-v1"}, + "topologyCompatibility": "exact", + "correlationEligible": true, + "state": "failed", + "classification": "confirmedFailure", + "confidence": "high", + "confidenceCeiling": "high", + "lastSuccessfulPhase": "transfer", + "nextSourceId": null, + "coverageGapArtifactIds": [], + "observations": [ + {"observationId": "01-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-validation-failure-01-distmgr", "startLine": 1, "endLine": 1}]}, + {"observationId": "02-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-validation-failure-01-distmgr", "startLine": 2, "endLine": 2}]}, + {"observationId": "03-transfer", "phase": "transfer", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-validation-failure-02-pkgxfer", "startLine": 1, "endLine": 1}]}, + {"observationId": "04-validate-failed", "phase": "validate", "disposition": "failed", "terminal": true, "evidence": [{"artifactId": "dp-validation-failure-03-provider", "startLine": 1, "endLine": 1}]} + ] + } + ], + "sourceLocalObservations": [], + "artifactRequests": [], + "clientCausalClaims": [], + "correlationHandoff": {"issue": "#333", "performed": false, "timeOnlyEligible": false} +} diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/manifest.json new file mode 100644 index 000000000..a69dd593e --- /dev/null +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/manifest.json @@ -0,0 +1,69 @@ +{ + "sccmManifestVersion": 1, + "proposalOnly": true, + "syntheticFixture": true, + "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "artifacts": [ + { + "artifactId": "dp-validation-failure-01-distmgr", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "distmgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", + "pathFingerprint": "synthetic:validation-distmgr", + "rotation": {"kind": "current", "lineageId": "validation-distmgr", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 698, + "relativePath": "evidence/server-dp-distribution/site/current/distmgr.log" + }, + { + "artifactId": "dp-validation-failure-02-pkgxfer", + "sourceId": "server-dp-distribution", + "producerRole": "siteServer", + "producerHostHandle": "safe:server:lab-pri-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "PkgXferMgr.log", + "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/PkgXferMgr.log", + "pathFingerprint": "synthetic:validation-pkgxfer", + "rotation": {"kind": "current", "lineageId": "validation-pkgxfer", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 351, + "relativePath": "evidence/server-dp-distribution/site/current/PkgXferMgr.log" + }, + { + "artifactId": "dp-validation-failure-03-provider", + "sourceId": "server-dp-distribution", + "producerRole": "distributionPoint", + "producerHostHandle": "safe:dp:lab-dp-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "SMSDPProv.log", + "sanitizedSourcePath": "SYNTHETIC://dp-root/Logs/SMSDPProv.log", + "pathFingerprint": "synthetic:validation-provider", + "rotation": {"kind": "current", "lineageId": "validation-provider", "fragmentComplete": true}, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:20:00Z", + "encoding": "utf-8", + "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, + "bytesCopied": 345, + "relativePath": "evidence/server-dp-distribution/dp/current/SMSDPProv.log" + } + ] +} diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs new file mode 100644 index 000000000..58cb4752e --- /dev/null +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -0,0 +1,1645 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use chrono::DateTime; +use cmtraceopen_parser::sccm::{ + normalize_ccm_artifact, SccmArtifact, SccmCoverageState, SccmEvidence, SccmRole, SccmRotation, + SccmTimeOrderingState, +}; +use serde_json::{json, Value}; + +const SCENARIOS: &[&str] = &[ + "absent-dp", + "client-only-looking-request", + "content-version-mismatch", + "distribution-failure", + "healthy-package", + "incomplete", + "rotation-boundary", + "serve-observed", + "transfer-retry", + "validation-failure", +]; + +const STATE_CHAIN: &[&str] = &[ + "receiveContent", + "distribute", + "transfer", + "validate", + "makeAvailable", + "serveOrReport", +]; + +const EXACT_PROFILE: &str = "dp-server-5.00.test-v1"; +const EXACT_SITE: &str = "LAB"; +const EXACT_DP: &str = "safe:dp:lab-dp-01"; + +fn corpus_root() -> std::path::PathBuf { + std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/sccm/server/distribution_point") +} + +fn read_json(scenario: &str, filename: &str) -> Result { + let path = corpus_root().join(scenario).join(filename); + let contents = std::fs::read_to_string(&path) + .map_err(|error| format!("{} is readable: {error}", path.display()))?; + serde_json::from_str(&contents) + .map_err(|error| format!("{} contains valid JSON: {error}", path.display())) +} + +fn required_string<'a>(value: &'a Value, field: &str, context: &str) -> Result<&'a str, String> { + value[field] + .as_str() + .ok_or_else(|| format!("{context}.{field} must be a string")) +} + +fn required_array<'a>(value: &'a Value, field: &str, context: &str) -> Result<&'a [Value], String> { + value[field] + .as_array() + .map(Vec::as_slice) + .ok_or_else(|| format!("{context}.{field} must be an array")) +} + +fn required_bool(value: &Value, field: &str, context: &str) -> Result { + value[field] + .as_bool() + .ok_or_else(|| format!("{context}.{field} must be a boolean")) +} + +fn reject_unknown_fields( + value: &Value, + allowed: &[&str], + context: &str, + failures: &mut Vec, +) { + let Some(object) = value.as_object() else { + failures.push(format!("{context} must be an object")); + return; + }; + for field in object.keys() { + if !allowed.contains(&field.as_str()) { + failures.push(format!("{context} contains unsupported field {field}")); + } + } +} + +fn role_from_manifest(role: &str) -> Result { + match role { + "client" => Ok(SccmRole::Client), + "siteServer" => Ok(SccmRole::SiteServer), + "distributionPoint" => Ok(SccmRole::DistributionPoint), + other => Err(format!("unsupported fixture producer role {other}")), + } +} + +fn coverage_from_manifest(state: &str) -> Result { + match state { + "captured" => Ok(SccmCoverageState::Captured), + "absent" => Ok(SccmCoverageState::Absent), + "accessDenied" => Ok(SccmCoverageState::AccessDenied), + "capped" => Ok(SccmCoverageState::Capped), + "skipped" => Ok(SccmCoverageState::Skipped), + "unsupported" => Ok(SccmCoverageState::Unsupported), + "parseFailed" => Ok(SccmCoverageState::ParseFailed), + other => Err(format!("unsupported fixture capture state {other}")), + } +} + +fn rotation_from_manifest(rotation: &Value) -> Result { + match required_string(rotation, "kind", "rotation")? { + "current" => Ok(SccmRotation::Current), + "lo_" => Ok(SccmRotation::LoUnderscore), + "numbered" => rotation["value"] + .as_u64() + .and_then(|value| u32::try_from(value).ok()) + .map(SccmRotation::Numbered) + .ok_or_else(|| "numbered rotation requires a u32 value".to_owned()), + "timestamped" => required_string(rotation, "value", "rotation") + .map(str::to_owned) + .map(SccmRotation::Timestamped), + other => Err(format!("unsupported fixture rotation {other}")), + } +} + +fn allowed_source(source_id: &str, role: &str, basename: &str) -> bool { + matches!( + (source_id, role, basename), + ("server-dp-distribution", "siteServer", "distmgr.log") + | ("server-dp-distribution", "siteServer", "PkgXferMgr.log") + | ( + "server-dp-distribution", + "distributionPoint", + "SMSDPProv.log" + ) + | ("server-dp-distribution", "distributionPoint", "PullDP.log") + | ("server-dp-serve", "distributionPoint", "SMSdpmon.log") + | ( + "client-content-control", + "client", + "DataTransferService.log" + ) + ) +} + +fn phase_allowed_for_artifact(artifact: &ParsedArtifact, phase: &str) -> bool { + matches!( + ( + artifact.source_id.as_str(), + artifact.basename.as_str(), + phase + ), + ( + "server-dp-distribution", + "distmgr.log", + "receiveContent" | "distribute" + ) | ("server-dp-distribution", "PkgXferMgr.log", "transfer") + | ( + "server-dp-distribution", + "PullDP.log", + "receiveContent" | "transfer" + ) + | ( + "server-dp-distribution", + "SMSDPProv.log", + "validate" | "makeAvailable" | "serveOrReport" + ) + | ("server-dp-serve", "SMSdpmon.log", "serveOrReport") + ) +} + +#[derive(Debug)] +struct ParsedArtifact { + state: String, + source_id: String, + role: String, + basename: String, + fragment_complete: Option, +} + +#[derive(Debug)] +struct ParsedScenario { + artifacts: BTreeMap, + evidence: BTreeMap<(String, u32, u32), SccmEvidence>, + distribution_point_handles: BTreeSet, +} + +fn parse_fixture_fields(message: &str) -> Result, String> { + let message = message + .strip_prefix("[sccm-public-message-v1] ") + .ok_or_else(|| "normalized evidence lacks the public projection profile".to_owned())?; + let mut segments = message.split(';').map(str::trim); + if segments.next() != Some("SYNTHETIC FIXTURE") { + return Err("CCM evidence lacks the semantic SYNTHETIC FIXTURE marker".to_owned()); + } + + let allowed = [ + "Phase", + "Disposition", + "Terminal", + "PackageId", + "ContentId", + "ContentVersion", + "SiteCode", + "DpHandle", + "ProfileId", + "ClientHandle", + "RequestId", + ]; + let mut fields = BTreeMap::new(); + for segment in segments { + let (name, value) = segment + .split_once('=') + .ok_or_else(|| format!("fixture field is not Name=Value: {segment}"))?; + if !allowed.contains(&name) { + return Err(format!("unsupported fixture field {name}")); + } + if value.is_empty() { + return Err(format!("fixture field {name} is empty")); + } + if !value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b':' | b'_' | b'-')) + { + return Err(format!("fixture field {name} contains unsupported syntax")); + } + if fields.insert(name.to_owned(), value.to_owned()).is_some() { + return Err(format!("duplicate fixture field {name}")); + } + } + Ok(fields) +} + +fn source_path_is_bounded(relative_path: &str, basename: &str) -> bool { + !relative_path.is_empty() + && relative_path.starts_with("evidence/") + && !relative_path.starts_with('/') + && !relative_path.contains('\\') + && !relative_path.split('/').any(|segment| segment == "..") + && relative_path + .rsplit('/') + .next() + .is_some_and(|candidate| candidate == basename) +} + +fn validate_manifest( + scenario_root: &std::path::Path, + manifest: &Value, +) -> Result> { + let mut failures = Vec::new(); + reject_unknown_fields( + manifest, + &[ + "sccmManifestVersion", + "proposalOnly", + "syntheticFixture", + "bundle", + "topology", + "artifacts", + ], + "manifest", + &mut failures, + ); + reject_unknown_fields( + &manifest["bundle"], + &["bundleRole", "workflow", "capturedUtc"], + "bundle", + &mut failures, + ); + reject_unknown_fields( + &manifest["topology"], + &[ + "siteCode", + "distributionPointHandle", + "distributionPointHandles", + "rolesObserved", + ], + "topology", + &mut failures, + ); + if manifest["sccmManifestVersion"] != 1 + || manifest["proposalOnly"] != true + || manifest["syntheticFixture"] != true + || manifest["bundle"]["bundleRole"] != "server" + || manifest["bundle"]["workflow"] != "distributionPoint" + { + failures + .push("manifest does not retain the versioned synthetic server boundary".to_owned()); + } + if manifest["topology"]["siteCode"] != EXACT_SITE + || manifest["topology"]["distributionPointHandle"] != EXACT_DP + { + failures.push("manifest topology is not the exact synthetic LAB DP".to_owned()); + } + let mut distribution_point_handles = manifest["topology"]["distributionPointHandles"] + .as_array() + .map(|values| { + values + .iter() + .filter_map(Value::as_str) + .map(str::to_owned) + .collect::>() + }) + .unwrap_or_else(|| vec![EXACT_DP.to_owned()]); + let original_handle_order = distribution_point_handles.clone(); + distribution_point_handles.sort(); + distribution_point_handles.dedup(); + if distribution_point_handles != original_handle_order + || !distribution_point_handles + .iter() + .any(|handle| handle == EXACT_DP) + || distribution_point_handles + .iter() + .any(|handle| !handle.starts_with("safe:dp:")) + { + failures.push( + "distributionPointHandles must be sorted, unique, opaque, and include the primary DP" + .to_owned(), + ); + } + let distribution_point_handles = distribution_point_handles + .into_iter() + .collect::>(); + + let roles = manifest["topology"]["rolesObserved"] + .as_array() + .map(|values| values.iter().filter_map(Value::as_str).collect::>()) + .unwrap_or_default(); + let mut sorted_roles = roles.clone(); + sorted_roles.sort_unstable(); + sorted_roles.dedup(); + if roles != sorted_roles || !roles.contains(&"siteServer") { + failures + .push("rolesObserved must be sorted, unique, and retain the site server".to_owned()); + } + + let captured_utc = + match required_string(&manifest["bundle"], "capturedUtc", "bundle").and_then(|value| { + DateTime::parse_from_rfc3339(value) + .map(|parsed| parsed.timestamp_millis()) + .map_err(|error| format!("bundle.capturedUtc is RFC3339: {error}")) + }) { + Ok(value) => value, + Err(error) => { + failures.push(error); + i64::MAX + } + }; + + let artifacts = match required_array(manifest, "artifacts", "manifest") { + Ok(artifacts) => artifacts, + Err(error) => { + failures.push(error); + return Err(failures); + } + }; + let artifact_order = artifacts + .iter() + .filter_map(|artifact| artifact["artifactId"].as_str()) + .collect::>(); + let mut sorted_artifact_order = artifact_order.clone(); + sorted_artifact_order.sort_unstable(); + if artifact_order != sorted_artifact_order { + failures + .push("manifest artifacts are not deterministically sorted by artifactId".to_owned()); + } + + let mut parsed_artifacts = BTreeMap::new(); + let mut evidence_by_reference = BTreeMap::new(); + let mut relative_paths = BTreeSet::new(); + let mut path_fingerprints = BTreeSet::new(); + for artifact in artifacts { + let artifact_id = match required_string(artifact, "artifactId", "artifact") { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + let context = format!("artifact {artifact_id}"); + let source_id = match required_string(artifact, "sourceId", &context) { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + let role = match required_string(artifact, "producerRole", &context) { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + let basename = match required_string(artifact, "originalBasename", &context) { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + let state = match required_string(artifact, "captureState", &context) { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + reject_unknown_fields( + artifact, + &[ + "artifactId", + "sourceId", + "producerRole", + "producerHostHandle", + "workflowSubjectRole", + "workflowSubjectHandle", + "sourceKind", + "originalBasename", + "sanitizedSourcePath", + "pathFingerprint", + "rotation", + "captureState", + "sourceVersion", + "collectedUtc", + "encoding", + "collectionLimit", + "bytesCopied", + "relativePath", + ], + &context, + &mut failures, + ); + reject_unknown_fields( + &artifact["rotation"], + &["kind", "value", "lineageId", "fragmentComplete"], + &format!("{context}.rotation"), + &mut failures, + ); + if artifact.get("collectionLimit").is_some() { + reject_unknown_fields( + &artifact["collectionLimit"], + &["byteLimit", "limitApplied"], + &format!("{context}.collectionLimit"), + &mut failures, + ); + } + if !allowed_source(source_id, role, basename) { + failures.push(format!( + "{artifact_id} has an uncatalogued source/producer/basename combination" + )); + } + if artifact["workflowSubjectRole"] != "distributionPoint" + || !artifact["workflowSubjectHandle"] + .as_str() + .is_some_and(|handle| distribution_point_handles.contains(handle)) + { + failures.push(format!( + "{artifact_id} loses the distribution-point workflow subject" + )); + } + if !artifact["producerHostHandle"] + .as_str() + .is_some_and(|value| value.starts_with("safe:")) + { + failures.push(format!("{artifact_id} lacks an opaque producer handle")); + } + if role == "distributionPoint" + && artifact["producerHostHandle"] != artifact["workflowSubjectHandle"] + { + failures.push(format!( + "{artifact_id} DP producer does not match its exact workflow subject" + )); + } + if !artifact["pathFingerprint"] + .as_str() + .is_some_and(|value| value.starts_with("synthetic:")) + || !artifact["sanitizedSourcePath"] + .as_str() + .is_some_and(|value| value.starts_with("SYNTHETIC://")) + { + failures.push(format!("{artifact_id} leaks or omits path provenance")); + } + if artifact["pathFingerprint"] + .as_str() + .is_some_and(|value| !path_fingerprints.insert(value.to_owned())) + { + failures.push(format!( + "{artifact_id} collapses a path fingerprint collision" + )); + } + if artifact["sourceKind"] != "ccmLog" + || !artifact["sourceVersion"] + .as_str() + .is_some_and(|value| value.starts_with("5.00.TEST.")) + { + failures.push(format!( + "{artifact_id} is outside the synthetic CCM/profile source boundary" + )); + } + + let role_model = match role_from_manifest(role) { + Ok(value) => value, + Err(error) => { + failures.push(format!("{artifact_id}: {error}")); + continue; + } + }; + let coverage_model = match coverage_from_manifest(state) { + Ok(value) => value, + Err(error) => { + failures.push(format!("{artifact_id}: {error}")); + continue; + } + }; + let rotation_model = match rotation_from_manifest(&artifact["rotation"]) { + Ok(value) => value, + Err(error) => { + failures.push(format!("{artifact_id}: {error}")); + continue; + } + }; + + if matches!(state, "captured" | "capped" | "parseFailed") { + let relative_path = match required_string(artifact, "relativePath", &context) { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + if !source_path_is_bounded(relative_path, basename) { + failures.push(format!( + "{artifact_id} has an unsafe or mismatched evidence path" + )); + } + if !relative_paths.insert(relative_path.to_owned()) { + failures.push(format!( + "{artifact_id} collides with another physical evidence destination" + )); + } + let fixture_path = scenario_root.join(relative_path); + let bytes = match std::fs::read(&fixture_path) { + Ok(value) => value, + Err(error) => { + failures.push(format!( + "{} is readable for {artifact_id}: {error}", + fixture_path.display() + )); + continue; + } + }; + if artifact["bytesCopied"].as_u64() != Some(bytes.len() as u64) { + failures.push(format!( + "{artifact_id}.bytesCopied does not match its physical fixture" + )); + } + let byte_limit = artifact["collectionLimit"]["byteLimit"].as_u64(); + let limit_applied = artifact["collectionLimit"]["limitApplied"].as_bool(); + if byte_limit.is_none() + || limit_applied.is_none() + || (state == "capped" + && (limit_applied != Some(true) || byte_limit != Some(bytes.len() as u64))) + || (state != "capped" + && (limit_applied != Some(false) + || byte_limit.is_some_and(|limit| limit < bytes.len() as u64))) + { + failures.push(format!( + "{artifact_id} has incoherent raw-byte collection-limit provenance" + )); + } + if !String::from_utf8_lossy(&bytes).contains("SYNTHETIC FIXTURE") { + failures.push(format!("{artifact_id} lacks a synthetic fixture marker")); + } + if matches!(state, "captured" | "capped") { + let content = String::from_utf8_lossy(&bytes); + let artifact_model = SccmArtifact { + artifact_id: artifact_id.to_owned(), + display_name: basename.to_owned(), + original_path: None, + host: artifact["producerHostHandle"].as_str().map(str::to_owned), + role: role_model.clone(), + configmgr_version: artifact["sourceVersion"].as_str().map(str::to_owned), + collected_at_utc: Some( + required_string(artifact, "collectedUtc", &context) + .unwrap_or_default() + .to_owned(), + ), + rotation: rotation_model.clone(), + coverage: coverage_model.clone(), + encoding: artifact["encoding"].as_str().map(str::to_owned), + }; + let normalized = normalize_ccm_artifact(artifact_model, &content); + if artifact["rotation"]["fragmentComplete"] == false && !normalized.is_empty() { + failures.push(format!( + "{artifact_id} exposes a logical record from an incomplete rotation fragment" + )); + } + for record in &normalized { + if record.role != role_model { + failures.push(format!("{artifact_id} loses producer-role provenance")); + } + if record.timestamp.ordering_state != SccmTimeOrderingState::NormalizedUtc + || record.timestamp.offset_minutes != Some(0) + || record.timestamp.utc_millis.is_none() + { + failures.push(format!( + "{artifact_id} has unusable timestamp provenance in a transaction-capable record" + )); + } else if record + .timestamp + .utc_millis + .is_some_and(|value| value > captured_utc) + { + failures.push(format!( + "{artifact_id} cites evidence later than the canonical bundle capture" + )); + } + let artifact_collected_utc = artifact["collectedUtc"] + .as_str() + .and_then(|value| DateTime::parse_from_rfc3339(value).ok()) + .map(|value| value.timestamp_millis()); + if artifact_collected_utc.is_none() + || artifact_collected_utc.is_some_and(|value| value > captured_utc) + || record + .timestamp + .utc_millis + .zip(artifact_collected_utc) + .is_some_and(|(evidence_utc, collected_utc)| { + evidence_utc > collected_utc + }) + { + failures.push(format!( + "{artifact_id} has incoherent evidence/artifact/bundle chronology" + )); + } + if record + .ccm_source_file + .as_deref() + .is_none_or(|value| !value.contains(".cpp:")) + { + failures.push(format!( + "{artifact_id} loses distinct CCM code-origin provenance" + )); + } + if let Err(error) = parse_fixture_fields(&record.message) { + failures.push(format!("{artifact_id}: {error}")); + } + let Some(line_start) = record.reference.line_start else { + failures.push(format!("{artifact_id} evidence lacks lineStart")); + continue; + }; + let Some(line_end) = record.reference.line_end else { + failures.push(format!("{artifact_id} evidence lacks lineEnd")); + continue; + }; + let key = (artifact_id.to_owned(), line_start, line_end); + if evidence_by_reference.insert(key, record.clone()).is_some() { + failures.push(format!("{artifact_id} has duplicate line-range evidence")); + } + } + } + } else if artifact.get("relativePath").is_some() + || artifact.get("bytesCopied").is_some() + || artifact.get("encoding").is_some() + || artifact["rotation"].get("fragmentComplete").is_some() + { + failures.push(format!( + "{artifact_id} invents physical capture facts for state {state}" + )); + } + + if parsed_artifacts + .insert( + artifact_id.to_owned(), + ParsedArtifact { + state: state.to_owned(), + source_id: source_id.to_owned(), + role: role.to_owned(), + basename: basename.to_owned(), + fragment_complete: artifact["rotation"]["fragmentComplete"].as_bool(), + }, + ) + .is_some() + { + failures.push(format!("duplicate artifactId {artifact_id}")); + } + } + + if failures.is_empty() { + Ok(ParsedScenario { + artifacts: parsed_artifacts, + evidence: evidence_by_reference, + distribution_point_handles, + }) + } else { + Err(failures) + } +} + +fn evidence_for<'a>( + parsed: &'a ParsedScenario, + reference: &Value, + context: &str, +) -> Result<&'a SccmEvidence, String> { + let artifact_id = required_string(reference, "artifactId", context)?; + let line_start = reference["startLine"] + .as_u64() + .and_then(|value| u32::try_from(value).ok()) + .ok_or_else(|| format!("{context}.startLine must be a u32"))?; + let line_end = reference["endLine"] + .as_u64() + .and_then(|value| u32::try_from(value).ok()) + .ok_or_else(|| format!("{context}.endLine must be a u32"))?; + parsed + .evidence + .get(&(artifact_id.to_owned(), line_start, line_end)) + .ok_or_else(|| { + format!( + "{context} does not cite a physical logical record: {artifact_id}:{line_start}-{line_end}" + ) + }) +} + +fn exact_key_fields( + key: &Value, + context: &str, + distribution_point_handles: &BTreeSet, +) -> Result, String> { + let mut fields = BTreeMap::new(); + for (json_field, record_field) in [ + ("packageId", "PackageId"), + ("contentId", "ContentId"), + ("contentVersion", "ContentVersion"), + ("siteCode", "SiteCode"), + ("distributionPointHandle", "DpHandle"), + ("extractionProfileId", "ProfileId"), + ] { + let value = if json_field == "contentVersion" { + key[json_field] + .as_u64() + .map(|value| value.to_string()) + .ok_or_else(|| format!("{context}.{json_field} must be a u64"))? + } else { + required_string(key, json_field, context)?.to_owned() + }; + fields.insert(record_field, value); + } + if fields["SiteCode"] != EXACT_SITE + || !distribution_point_handles.contains(&fields["DpHandle"]) + || fields["ProfileId"] != EXACT_PROFILE + || key["confidence"] != "exact" + { + return Err(format!( + "{context} is outside the exact synthetic key profile" + )); + } + Ok(fields) +} + +fn validate_expected( + scenario: &str, + manifest: &Value, + expected: &Value, + parsed: &ParsedScenario, +) -> Result<(), Vec> { + let mut failures = Vec::new(); + reject_unknown_fields( + expected, + &[ + "contractState", + "workflow", + "scenario", + "stateChain", + "analysisContract", + "extractionProfile", + "roleAssessment", + "coverage", + "transactions", + "sourceLocalObservations", + "artifactRequests", + "clientCausalClaims", + "correlationHandoff", + ], + "expected", + &mut failures, + ); + reject_unknown_fields( + &expected["analysisContract"], + &[ + "independentReducer", + "consumesClientOutput", + "crossSideCorrelationPerformed", + ], + "analysisContract", + &mut failures, + ); + reject_unknown_fields( + &expected["extractionProfile"], + &["selectionState", "profileId", "validatedRole"], + "extractionProfile", + &mut failures, + ); + reject_unknown_fields( + &expected["roleAssessment"], + &[ + "distributionPointObserved", + "roleAbsentInferred", + "missingDefaultPathInterpretation", + ], + "roleAssessment", + &mut failures, + ); + reject_unknown_fields( + &expected["correlationHandoff"], + &["issue", "performed", "timeOnlyEligible"], + "correlationHandoff", + &mut failures, + ); + if expected["contractState"] != "proposedPendingReviewed318And335" + || expected["workflow"] != "distributionPoint" + || expected["scenario"] != scenario + || expected["analysisContract"]["independentReducer"] != true + || expected["analysisContract"]["consumesClientOutput"] != false + || expected["analysisContract"]["crossSideCorrelationPerformed"] != false + { + failures.push("expected output loses the preparation/dependency boundary".to_owned()); + } + if expected["stateChain"] + .as_array() + .map(|values| values.iter().filter_map(Value::as_str).collect::>()) + .as_deref() + != Some(STATE_CHAIN) + { + failures.push("expected state chain does not match Task 5".to_owned()); + } + if expected["extractionProfile"]["profileId"] != EXACT_PROFILE + || expected["extractionProfile"]["selectionState"] != "selectedSynthetic" + || expected["extractionProfile"]["validatedRole"] != "distributionPoint" + { + failures + .push("expected output lacks the versioned synthetic extraction profile".to_owned()); + } + if expected["roleAssessment"]["roleAbsentInferred"] != false + || expected["roleAssessment"]["missingDefaultPathInterpretation"] != "sourceCoverageOnly" + { + failures.push("expected output infers role state from source coverage".to_owned()); + } + + let expected_coverage = parsed + .artifacts + .iter() + .map(|(artifact_id, artifact)| (artifact_id.clone(), artifact.state.clone())) + .collect::>(); + let mut declared_coverage = BTreeMap::new(); + let mut coverage_order = Vec::new(); + match required_array(expected, "coverage", "expected") { + Ok(rows) => { + for row in rows { + reject_unknown_fields(row, &["artifactId", "state"], "coverage row", &mut failures); + let Ok(artifact_id) = required_string(row, "artifactId", "coverage row") else { + failures.push("coverage row lacks artifactId".to_owned()); + continue; + }; + let Ok(state) = required_string(row, "state", "coverage row") else { + failures.push(format!("{artifact_id} coverage row lacks state")); + continue; + }; + coverage_order.push(artifact_id.to_owned()); + if declared_coverage + .insert(artifact_id.to_owned(), state.to_owned()) + .is_some() + { + failures.push(format!("duplicate coverage row {artifact_id}")); + } + } + } + Err(error) => failures.push(error), + } + let mut sorted_coverage = coverage_order.clone(); + sorted_coverage.sort(); + if coverage_order != sorted_coverage { + failures.push("coverage rows are not deterministically sorted".to_owned()); + } + if declared_coverage != expected_coverage { + failures.push(format!( + "coverage is not the exact physical manifest projection: {declared_coverage:?} != {expected_coverage:?}" + )); + } + + let transactions = match required_array(expected, "transactions", "expected") { + Ok(value) => value, + Err(error) => { + failures.push(error); + &[] + } + }; + let transaction_order = transactions + .iter() + .filter_map(|transaction| transaction["transactionId"].as_str()) + .collect::>(); + let mut sorted_transaction_order = transaction_order.clone(); + sorted_transaction_order.sort_unstable(); + if transaction_order != sorted_transaction_order { + failures.push("transactions are not deterministically sorted".to_owned()); + } + + let mut seen_transaction_ids = BTreeSet::new(); + for transaction in transactions { + let transaction_id = match required_string(transaction, "transactionId", "transaction") { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + if !seen_transaction_ids.insert(transaction_id) { + failures.push(format!("duplicate transactionId {transaction_id}")); + } + reject_unknown_fields( + transaction, + &[ + "transactionId", + "key", + "topologyCompatibility", + "correlationEligible", + "state", + "classification", + "confidence", + "confidenceCeiling", + "lastSuccessfulPhase", + "nextSourceId", + "coverageGapArtifactIds", + "observations", + ], + transaction_id, + &mut failures, + ); + reject_unknown_fields( + &transaction["key"], + &[ + "packageId", + "contentId", + "contentVersion", + "siteCode", + "distributionPointHandle", + "confidence", + "extractionProfileId", + ], + &format!("{transaction_id}.key"), + &mut failures, + ); + let key_fields = match exact_key_fields( + &transaction["key"], + &format!("{transaction_id}.key"), + &parsed.distribution_point_handles, + ) { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + let expected_id = format!( + "dp:{}:{}:v{}:{}", + key_fields["PackageId"], + key_fields["ContentId"], + key_fields["ContentVersion"], + key_fields["DpHandle"] + ); + if transaction_id != expected_id { + failures.push(format!( + "{transaction_id} is not derived from its exact immutable key" + )); + } + if transaction["topologyCompatibility"] != "exact" + || transaction["correlationEligible"] != true + { + failures.push(format!("{transaction_id} is not exact/topology-gated")); + } + + let observations = match required_array(transaction, "observations", transaction_id) { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + let observation_order = observations + .iter() + .filter_map(|observation| observation["observationId"].as_str()) + .collect::>(); + let mut sorted_observation_order = observation_order.clone(); + sorted_observation_order.sort_unstable(); + if observation_order != sorted_observation_order { + failures.push(format!( + "{transaction_id} observations are not deterministically sorted" + )); + } + + let mut latest_success: Option = None; + let mut terminal_success = false; + let mut terminal_failure = false; + let mut terminal_deferred = false; + let mut previous_utc = i64::MIN; + let mut previous_phase = 0usize; + for observation in observations { + let observation_id = + required_string(observation, "observationId", transaction_id).unwrap_or("invalid"); + let phase = required_string(observation, "phase", observation_id).unwrap_or("invalid"); + let disposition = + required_string(observation, "disposition", observation_id).unwrap_or("invalid"); + let terminal = required_bool(observation, "terminal", observation_id).unwrap_or(false); + reject_unknown_fields( + observation, + &[ + "observationId", + "phase", + "disposition", + "terminal", + "evidence", + ], + observation_id, + &mut failures, + ); + let phase_index = STATE_CHAIN.iter().position(|candidate| *candidate == phase); + if phase_index.is_none() { + failures.push(format!("{observation_id} uses unsupported phase {phase}")); + } + if phase_index.is_some_and(|index| index < previous_phase) { + failures.push(format!( + "{transaction_id} phases move backward despite increasing evidence time" + )); + } + if let Some(index) = phase_index { + previous_phase = index; + } + let references = match required_array(observation, "evidence", observation_id) { + Ok(value) if !value.is_empty() => value, + Ok(_) => { + failures.push(format!("{observation_id} has no cited evidence")); + continue; + } + Err(error) => { + failures.push(error); + continue; + } + }; + for reference in references { + reject_unknown_fields( + reference, + &["artifactId", "startLine", "endLine"], + &format!("{observation_id}.evidence"), + &mut failures, + ); + let cited_artifact_id = + required_string(reference, "artifactId", observation_id).unwrap_or("invalid"); + match parsed.artifacts.get(cited_artifact_id) { + Some(artifact) + if artifact.role != "client" + && phase_allowed_for_artifact(artifact, phase) => {} + _ => failures.push(format!( + "{observation_id} cites an artifact that cannot own phase {phase}" + )), + } + let record = match evidence_for(parsed, reference, observation_id) { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + let fields = match parse_fixture_fields(&record.message) { + Ok(value) => value, + Err(error) => { + failures.push(format!("{observation_id}: {error}")); + continue; + } + }; + for (field, expected_value) in &key_fields { + if fields.get(*field) != Some(expected_value) { + failures.push(format!( + "{observation_id} evidence does not repeat exact {field}={expected_value}" + )); + } + } + if fields.get("Phase").map(String::as_str) != Some(phase) + || fields.get("Disposition").map(String::as_str) != Some(disposition) + || fields.get("Terminal").map(String::as_str) + != Some(if terminal { "true" } else { "false" }) + { + failures.push(format!( + "{observation_id} phase/disposition/terminal is not cited exactly" + )); + } + let utc = record.timestamp.utc_millis.unwrap_or(i64::MIN); + if utc < previous_utc { + failures.push(format!( + "{transaction_id} evidence is not ordered by normalized UTC provenance" + )); + } + previous_utc = utc; + } + match (disposition, terminal) { + ("succeeded", true) => { + latest_success = latest_success.max(phase_index); + terminal_success = true; + } + ("succeeded", false) => latest_success = latest_success.max(phase_index), + ("failed", true) => terminal_failure = true, + ("deferred" | "retrying", false) => terminal_deferred = true, + _ => failures.push(format!( + "{observation_id} uses an incoherent disposition/terminal pair" + )), + } + } + + let computed_last_success = latest_success.map(|index| STATE_CHAIN[index]); + if transaction["lastSuccessfulPhase"].as_str() != computed_last_success + || (computed_last_success.is_none() && !transaction["lastSuccessfulPhase"].is_null()) + { + failures.push(format!( + "{transaction_id}.lastSuccessfulPhase is not evidence-derived" + )); + } + let state = required_string(transaction, "state", transaction_id).unwrap_or("invalid"); + let classification = + required_string(transaction, "classification", transaction_id).unwrap_or("invalid"); + let confidence = + required_string(transaction, "confidence", transaction_id).unwrap_or("invalid"); + let confidence_ceiling = + required_string(transaction, "confidenceCeiling", transaction_id).unwrap_or("invalid"); + match (state, classification) { + ("succeeded", "success") + if terminal_success + && computed_last_success == Some("serveOrReport") + && !terminal_failure + && confidence == "high" + && confidence_ceiling == "high" => {} + ("failed", "confirmedFailure") + if terminal_failure + && !terminal_success + && confidence == "high" + && confidence_ceiling == "high" => {} + ("deferred", "blockedOrDeferred") + if terminal_deferred + && !terminal_failure + && !terminal_success + && confidence == "medium" + && confidence_ceiling == "medium" => {} + ("incomplete", "insufficientEvidence") + if !terminal_failure + && !terminal_success + && confidence == "low" + && confidence_ceiling == "low" => {} + _ => failures.push(format!( + "{transaction_id} state/classification lacks the required terminal evidence" + )), + } + + let gap_ids = transaction["coverageGapArtifactIds"] + .as_array() + .map(|values| values.iter().filter_map(Value::as_str).collect::>()) + .unwrap_or_default(); + let mut sorted_gap_ids = gap_ids.clone(); + sorted_gap_ids.sort_unstable(); + if gap_ids != sorted_gap_ids { + failures.push(format!("{transaction_id} coverage gaps are not sorted")); + } + for artifact_id in gap_ids { + match parsed.artifacts.get(artifact_id) { + Some(artifact) if artifact.state != "captured" => {} + _ => failures.push(format!( + "{transaction_id} coverage gap {artifact_id} is absent or complete" + )), + } + } + if state == "incomplete" { + let next_source = transaction["nextSourceId"].as_str(); + if next_source.is_none() + || !parsed.artifacts.values().any(|artifact| { + Some(artifact.source_id.as_str()) == next_source && artifact.state != "captured" + }) + { + failures.push(format!( + "{transaction_id} incomplete state lacks a bounded noncomplete next source" + )); + } + } else if !transaction["nextSourceId"].is_null() { + failures.push(format!( + "{transaction_id} terminal/deferred state invents a next source" + )); + } + } + + let source_local = match required_array(expected, "sourceLocalObservations", "expected") { + Ok(value) => value, + Err(error) => { + failures.push(error); + &[] + } + }; + let source_local_order = source_local + .iter() + .filter_map(|observation| observation["observationId"].as_str()) + .collect::>(); + let mut sorted_source_local_order = source_local_order.clone(); + sorted_source_local_order.sort_unstable(); + if source_local_order != sorted_source_local_order { + failures.push("source-local observations are not deterministically sorted".to_owned()); + } + for observation in source_local { + let observation_id = + required_string(observation, "observationId", "sourceLocalObservation") + .unwrap_or("invalid"); + reject_unknown_fields( + observation, + &[ + "observationId", + "classification", + "confidence", + "confidenceCeiling", + "correlationEligible", + "artifactIds", + "evidence", + ], + observation_id, + &mut failures, + ); + if !matches!( + observation["classification"].as_str(), + Some("ignoredClientEvidence" | "rotationSplit" | "malformedEvidence") + ) || observation.get("key").is_some() + || observation["correlationEligible"] != false + || observation["confidence"] != "low" + || observation["confidenceCeiling"] != "low" + { + failures.push(format!( + "{observation_id} is not an explicitly noncorrelatable source-local observation" + )); + } + let artifact_ids = observation["artifactIds"] + .as_array() + .map(|values| values.iter().filter_map(Value::as_str).collect::>()) + .unwrap_or_default(); + let mut sorted_artifact_ids = artifact_ids.clone(); + sorted_artifact_ids.sort_unstable(); + if artifact_ids.is_empty() || artifact_ids != sorted_artifact_ids { + failures.push(format!( + "{observation_id} lacks sorted physical artifact provenance" + )); + } + for artifact_id in artifact_ids { + if !parsed.artifacts.contains_key(artifact_id) { + failures.push(format!( + "{observation_id} cites unknown physical artifact {artifact_id}" + )); + } + } + for reference in observation["evidence"] + .as_array() + .map(Vec::as_slice) + .unwrap_or_default() + { + reject_unknown_fields( + reference, + &["artifactId", "startLine", "endLine"], + &format!("{observation_id}.evidence"), + &mut failures, + ); + if let Err(error) = evidence_for(parsed, reference, observation_id) { + failures.push(error); + } + } + } + + let requests = match required_array(expected, "artifactRequests", "expected") { + Ok(value) => value, + Err(error) => { + failures.push(error); + &[] + } + }; + let mut request_order = Vec::new(); + for request in requests { + let source_id = + required_string(request, "sourceId", "artifactRequest").unwrap_or("invalid"); + let reason_code = + required_string(request, "reasonCode", "artifactRequest").unwrap_or("invalid"); + reject_unknown_fields( + request, + &["sourceId", "reasonCode"], + "artifactRequest", + &mut failures, + ); + request_order.push((source_id, reason_code)); + if !matches!(source_id, "server-dp-distribution" | "server-dp-serve") + || !matches!( + reason_code, + "coverageAbsent" + | "coverageAccessDenied" + | "coverageCapped" + | "coverageMalformed" + | "coverageRotationSplit" + ) + || request.get("reason").is_some() + { + failures.push(format!( + "artifact request is not a bounded versioned source/reason code: {source_id}/{reason_code}" + )); + } + let matching_coverage = parsed.artifacts.values().any(|artifact| { + artifact.source_id == source_id + && match reason_code { + "coverageAbsent" => artifact.state == "absent", + "coverageAccessDenied" => artifact.state == "accessDenied", + "coverageCapped" => artifact.state == "capped", + "coverageMalformed" => artifact.state == "parseFailed", + "coverageRotationSplit" => { + matches!(artifact.state.as_str(), "captured" | "capped") + && artifact.fragment_complete == Some(false) + } + _ => false, + } + }); + if !matching_coverage { + failures.push(format!( + "artifact request {source_id}/{reason_code} lacks matching noncomplete coverage" + )); + } + } + let mut sorted_request_order = request_order.clone(); + sorted_request_order.sort_unstable(); + if request_order != sorted_request_order { + failures.push("artifact requests are not deterministically sorted".to_owned()); + } + + if expected["clientCausalClaims"] != json!([]) + || expected["correlationHandoff"]["issue"] != "#333" + || expected["correlationHandoff"]["performed"] != false + || expected["correlationHandoff"]["timeOnlyEligible"] != false + { + failures.push( + "expected output makes or enables a premature cross-side causal claim".to_owned(), + ); + } + + if scenario == "absent-dp" + && (expected["roleAssessment"]["distributionPointObserved"] != true + || !transactions.is_empty()) + { + failures.push("absent-dp must retain the observed role without a diagnosis".to_owned()); + } + if scenario == "client-only-looking-request" + && (!transactions.is_empty() + || !parsed + .artifacts + .values() + .any(|artifact| artifact.role == "client")) + { + failures.push("client-only evidence entered a DP transaction".to_owned()); + } + if scenario == "rotation-boundary" && !transactions.is_empty() { + failures.push("rotation fragments formed a DP transaction".to_owned()); + } + if scenario == "content-version-mismatch" { + let versions = transactions + .iter() + .filter_map(|transaction| transaction["key"]["contentVersion"].as_u64()) + .collect::>(); + let dp_handles = transactions + .iter() + .filter_map(|transaction| { + transaction["key"]["distributionPointHandle"] + .as_str() + .map(str::to_owned) + }) + .collect::>(); + if versions != BTreeSet::from([1, 2]) + || dp_handles + != BTreeSet::from([ + "safe:dp:lab-dp-01".to_owned(), + "safe:dp:lab-dp-02".to_owned(), + ]) + || transactions.len() != 3 + { + failures.push( + "content/version/DP topology did not remain three exact transactions".to_owned(), + ); + } + } + + if manifest["topology"]["rolesObserved"] + .as_array() + .is_some_and(|roles| roles.iter().any(|role| role == "distributionPoint")) + != expected["roleAssessment"]["distributionPointObserved"] + .as_bool() + .unwrap_or(false) + { + failures.push("role assessment is not an exact topology projection".to_owned()); + } + + if failures.is_empty() { + Ok(()) + } else { + Err(failures) + } +} + +fn validate_scenario_values( + scenario: &str, + manifest: &Value, + expected: &Value, +) -> Result<(), Vec> { + let scenario_root = corpus_root().join(scenario); + let parsed = validate_manifest(&scenario_root, manifest)?; + validate_expected(scenario, manifest, expected, &parsed) +} + +#[test] +fn distribution_point_scenario_matrix_is_complete_and_loadable() { + let root = corpus_root(); + let mut actual = std::fs::read_dir(&root) + .unwrap_or_else(|error| panic!("{} is readable: {error}", root.display())) + .filter_map(|entry| { + let path = entry.ok()?.path(); + path.is_dir().then(|| { + path.file_name() + .expect("scenario directory has a name") + .to_string_lossy() + .into_owned() + }) + }) + .collect::>(); + actual.sort(); + + assert_eq!(actual, SCENARIOS, "the Task 5 scenario matrix changed"); + for scenario in SCENARIOS { + let manifest = read_json(scenario, "manifest.json") + .unwrap_or_else(|error| panic!("{scenario}: {error}")); + let expected = read_json(scenario, "expected.json") + .unwrap_or_else(|error| panic!("{scenario}: {error}")); + validate_scenario_values(scenario, &manifest, &expected) + .unwrap_or_else(|failures| panic!("{scenario}:\n{}", failures.join("\n"))); + } +} + +fn mutation_was_accepted(scenario: &str, manifest: &Value, expected: &Value) -> bool { + validate_scenario_values(scenario, manifest, expected).is_ok() +} + +#[test] +fn exact_content_version_dp_topology_and_terminal_evidence_fail_closed() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut version_alias = healthy_expected.clone(); + version_alias["transactions"][0]["key"]["contentVersion"] = json!(2); + if mutation_was_accepted("healthy-package", &healthy_manifest, &version_alias) { + accepted.push("transaction content version diverged from cited evidence"); + } + + let mut dp_alias = healthy_expected.clone(); + dp_alias["transactions"][0]["key"]["distributionPointHandle"] = json!("safe:dp:lab-dp-02"); + if mutation_was_accepted("healthy-package", &healthy_manifest, &dp_alias) { + accepted.push("transaction DP topology diverged from cited evidence"); + } + + let mut time_only_cause = healthy_expected.clone(); + time_only_cause["clientCausalClaims"] = + json!(["A same-time client request proves the DP caused the failure."]); + if mutation_was_accepted("healthy-package", &healthy_manifest, &time_only_cause) { + accepted.push("time-only client/DP causality was admitted"); + } + + let failure_manifest = + read_json("distribution-failure", "manifest.json").expect("manifest loads"); + let mut failure_expected = + read_json("distribution-failure", "expected.json").expect("expected loads"); + failure_expected["transactions"][0]["observations"][1]["terminal"] = json!(false); + if mutation_was_accepted("distribution-failure", &failure_manifest, &failure_expected) { + accepted.push("confirmed failure survived without cited terminal evidence"); + } + + assert!( + accepted.is_empty(), + "exact key/causality/terminal mutations were accepted: {accepted:?}" + ); +} + +#[test] +fn coverage_role_and_rotation_states_fail_closed() { + let absent_manifest = read_json("absent-dp", "manifest.json").expect("manifest loads"); + let absent_expected = read_json("absent-dp", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut promoted_coverage = absent_expected.clone(); + promoted_coverage["coverage"][0]["state"] = json!("captured"); + if mutation_was_accepted("absent-dp", &absent_manifest, &promoted_coverage) { + accepted.push("absent source coverage was promoted to captured"); + } + + let mut missing_role = absent_expected.clone(); + missing_role["roleAssessment"]["distributionPointObserved"] = json!(false); + missing_role["roleAssessment"]["roleAbsentInferred"] = json!(true); + if mutation_was_accepted("absent-dp", &absent_manifest, &missing_role) { + accepted.push("missing source path was promoted to missing DP role"); + } + + let mut role_alias_manifest = absent_manifest.clone(); + role_alias_manifest["artifacts"][0]["producerRole"] = json!("distributionPoint"); + if mutation_was_accepted("absent-dp", &role_alias_manifest, &absent_expected) { + accepted.push("basename reclassified the site-server producer as a DP"); + } + + let rotation_manifest = + read_json("rotation-boundary", "manifest.json").expect("manifest loads"); + let rotation_expected = + read_json("rotation-boundary", "expected.json").expect("expected loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut invented_transaction = rotation_expected.clone(); + invented_transaction["transactions"] = healthy_expected["transactions"].clone(); + if mutation_was_accepted( + "rotation-boundary", + &rotation_manifest, + &invented_transaction, + ) { + accepted.push("split rotation fragments formed a transaction"); + } + + assert!( + accepted.is_empty(), + "coverage/role/rotation mutations were accepted: {accepted:?}" + ); +} + +#[test] +fn client_only_and_version_mismatch_controls_stay_independent() { + let client_manifest = + read_json("client-only-looking-request", "manifest.json").expect("manifest loads"); + let client_expected = + read_json("client-only-looking-request", "expected.json").expect("expected loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut server_transaction = client_expected.clone(); + server_transaction["transactions"] = healthy_expected["transactions"].clone(); + if mutation_was_accepted( + "client-only-looking-request", + &client_manifest, + &server_transaction, + ) { + accepted.push("client-only record entered the server DP reducer"); + } + + let mismatch_manifest = + read_json("content-version-mismatch", "manifest.json").expect("manifest loads"); + let mismatch_expected = + read_json("content-version-mismatch", "expected.json").expect("expected loads"); + let mut merged_versions = mismatch_expected.clone(); + merged_versions["transactions"] + .as_array_mut() + .expect("transactions are mutable") + .pop(); + if mutation_was_accepted( + "content-version-mismatch", + &mismatch_manifest, + &merged_versions, + ) { + accepted.push("same content across two versions collapsed into one transaction"); + } + + assert!( + accepted.is_empty(), + "client/version separation mutations were accepted: {accepted:?}" + ); +} + +#[test] +fn structured_fixture_fields_are_unique_closed_and_record_local() { + let valid = "[sccm-public-message-v1] SYNTHETIC FIXTURE; Phase=transfer; Disposition=succeeded; Terminal=false; PackageId=LAB00001; ContentId=content-alpha; ContentVersion=1; SiteCode=LAB; DpHandle=safe:dp:lab-dp-01; ProfileId=dp-server-5.00.test-v1"; + assert!(parse_fixture_fields(valid).is_ok()); + + for invalid in [ + "[sccm-public-message-v1] SYNTHETIC FIXTURE; Phase=transfer; Phase=validate; Disposition=succeeded; Terminal=false", + "[sccm-public-message-v1] SYNTHETIC FIXTURE; Phase=transfer; Disposition=succeeded; Terminal=false; Terminal=true", + "[sccm-public-message-v1] SYNTHETIC FIXTURE; Phase=transfer; Disposition=succeeded; Terminal=false; ServerCause=network", + "[sccm-public-message-v1] SYNTHETIC FIXTURE; Phase=transfer]LOG]!>; Disposition=succeeded; Terminal=false", + ] { + assert!( + parse_fixture_fields(invalid).is_err(), + "ambiguous or unsupported fields were accepted: {invalid}" + ); + } +} + +#[test] +fn unknown_semantics_collisions_and_output_reordering_fail_closed() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut injected_cause = healthy_expected.clone(); + injected_cause["serverCause"] = json!("The DP triggered the client failure."); + if mutation_was_accepted("healthy-package", &healthy_manifest, &injected_cause) { + accepted.push("unknown server-cause field"); + } + + let mut reversed_observations = healthy_expected.clone(); + reversed_observations["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are mutable") + .reverse(); + if mutation_was_accepted("healthy-package", &healthy_manifest, &reversed_observations) { + accepted.push("reversed observation output"); + } + + let mismatch_manifest = + read_json("content-version-mismatch", "manifest.json").expect("manifest loads"); + let mut mismatch_expected = + read_json("content-version-mismatch", "expected.json").expect("expected loads"); + mismatch_expected["transactions"] + .as_array_mut() + .expect("transactions are mutable") + .reverse(); + if mutation_was_accepted( + "content-version-mismatch", + &mismatch_manifest, + &mismatch_expected, + ) { + accepted.push("reversed transaction output"); + } + + let mut collided_manifest = healthy_manifest.clone(); + collided_manifest["artifacts"][1]["relativePath"] = + collided_manifest["artifacts"][0]["relativePath"].clone(); + collided_manifest["artifacts"][1]["originalBasename"] = + collided_manifest["artifacts"][0]["originalBasename"].clone(); + if mutation_was_accepted("healthy-package", &collided_manifest, &healthy_expected) { + accepted.push("colliding physical evidence destination"); + } + + assert!( + accepted.is_empty(), + "closed-schema/collision/order mutations were accepted: {accepted:?}" + ); +} diff --git a/docs/sccm/preparation/issue-329-distribution-point-corpus.md b/docs/sccm/preparation/issue-329-distribution-point-corpus.md new file mode 100644 index 000000000..00c443f32 --- /dev/null +++ b/docs/sccm/preparation/issue-329-distribution-point-corpus.md @@ -0,0 +1,151 @@ +# Issue #329 Distribution Point/content corpus preparation + +## Scope and dependency boundary + +This slice prepares the role-local source and behavior contract for Issue +`#329`. It intentionally contains only synthetic CCM evidence, versioned +manifest/expected-output labels, and a focused Rust fixture-contract test. +It does not add a production reducer, native collector, parser family, public +wire type, database dependency, or cross-side correlator. + +The preparation contract is `proposedPendingReviewed318And335`: + +- the reviewed #318 artifact, logical-record, evidence, timestamp, key, + coverage, redaction, signal, and finding contracts are the implementation + boundary; +- #335 supplies the producer-role, workflow-subject, configured-path, + physical identity, rotation, and coverage handoff; +- #322 remains independently callable and does not feed this role-local + preparation corpus; and +- #333 may later consume exact #322/#329 counterpart facts, but this slice + performs no correlation and makes no client-impact or causal claim. + +All `.log` files remain raw CCM transport. The corpus calls the existing +`normalize_ccm_artifact` logical-record path and does not introduce +`ParserKind::Sccm` or a second CCM parser. + +## Producer and workflow-subject contract + +A physical producer is not inferred from the workflow it describes. + +| Source ID | Basename | Allowed producer role | Workflow subject | Use | +| --- | --- | --- | --- | --- | +| `server-dp-distribution` | `distmgr.log` | `siteServer` | exact DP handle | Receive and distribute | +| `server-dp-distribution` | `PkgXferMgr.log` | `siteServer` | exact DP handle | Transfer and retry | +| `server-dp-distribution` | `SMSDPProv.log` | `distributionPoint` | same exact DP handle | Validate and make available | +| `server-dp-distribution` | `PullDP.log` | `distributionPoint` | same exact pull-DP handle | Pull transfer when a reviewed fixture proves it | +| `server-dp-serve` | `SMSdpmon.log` | `distributionPoint` | same exact DP handle | Optional, explicitly catalogued serving/status evidence | +| `client-content-control` | `DataTransferService.log` | `client` | selected DP only as an ignored control | Must never enter the server reducer | + +`server-dp-serve` is supplemental and bounded. It is not permission to scan +an IIS tree, content library, filesystem root, or arbitrary DP directory. +The existing IIS W3C parser may later support an explicitly catalogued +artifact, but this corpus neither requires nor fabricates one. + +Each manifest preserves: + +- a synthetic site code and one or more approved opaque DP handles; +- producer role and producer handle separately from workflow-subject role and + handle; +- source ID, exact basename, source grammar, synthetic version, path + fingerprint, and `SYNTHETIC://` provenance; +- rotation kind, lineage, and fragment completeness; +- capture state, collection timestamp, encoding, byte policy, exact copied + byte count, and bounded relative evidence path; and +- deterministic artifact identity and ordering. + +The two DPs in `content-version-mismatch` are separate subjects even though +the site-server basenames and package/content identifiers overlap. + +## State and exact-key contract + +The proposed role-local state chain is: + +```text +ReceiveContent -> Distribute -> Transfer -> Validate -> MakeAvailable -> ServeOrReport +``` + +A transaction is admitted only when every cited logical record repeats the +same exact profile-valid tuple: + +```text +packageId ++ contentId ++ contentVersion ++ siteCode ++ distributionPointHandle ++ extractionProfileId +``` + +The synthetic profile is `dp-server-5.00.test-v1`, limited to +`5.00.TEST.*` fixture evidence. It is not a claim that a real ConfigMgr build +has been validated. + +The focused contract parses semicolon-delimited synthetic fields as unique +`Name=Value` pairs. Substring lookalikes, duplicate fields, missing fields, +case aliases, a changed version, or a changed DP handle cannot satisfy an +exact transaction. Observation order uses the additive normalized SCCM +timestamp provenance, not the legacy public `LogEntry.timezone_offset`. +Evidence later than the canonical bundle capture is rejected. + +The outcome rules are conservative: + +- success requires a cited terminal successful `ServeOrReport`; +- confirmed failure requires cited source-specific terminal failure evidence; +- retry remains `blockedOrDeferred`; +- incomplete coverage remains `insufficientEvidence` with exact physical gap + IDs and a bounded source ID; +- rotation fragments and malformed evidence remain noncorrelatable + source-local observations; and +- a client-only download record cannot become a DP transaction or DP failure. + +## Coverage and request contract + +`captured`, `absent`, `accessDenied`, `capped`, `skipped`, `unsupported`, and +`parseFailed` remain distinct physical manifest states. The expected coverage +array is an exact, sorted projection of physical artifact IDs and states. + +Artifact requests contain only a catalogued source ID and one versioned reason +code: + +- `coverageAbsent` +- `coverageAccessDenied` +- `coverageCapped` +- `coverageMalformed` +- `coverageRotationSplit` + +There is no free-form collection request in the preparation labels. A reason +code must have matching noncomplete physical coverage. An absent default path +is a source gap; it cannot change an observed DP role to absent, broken, +uninstalled, unavailable, healthy, or failed. + +## Scenario matrix + +| Scenario | Required behavior | +| --- | --- | +| `healthy-package` | Exact six-phase successful distribution | +| `distribution-failure` | Terminal distribution failure with Receive as the last success | +| `transfer-retry` | Retry/backlog remains deferred, not failed | +| `validation-failure` | Terminal provider validation failure after exact transfer evidence | +| `content-version-mismatch` | Same package/content stays separate across versions and two DPs | +| `serve-observed` | Optional bounded serving source supplies the terminal observed outcome | +| `client-only-looking-request` | Same-time client content failure remains ignored server-side evidence | +| `rotation-boundary` | Current/`.lo_` fragments and malformed provider bytes form no transaction | +| `absent-dp` | Missing source candidates do not erase or diagnose an observed DP role | +| `incomplete` | Exact early phases survive while absent/denied downstream coverage requests the bounded source | + +The contract test also mutates exact versions, DP topology, terminal evidence, +coverage states, role provenance, causal fields, rotations, and transaction +cardinality. Each mutation must fail closed. + +## Deferred implementation and validation + +After the #318 API gate and required restack/review, the production reducer may +map these labels onto the reviewed public contracts. It must remain pure Rust +and wasm32-compatible. Native capture remains a separate Windows adapter and +must retain configured paths, producer/subject topology, rotation, byte caps, +access results, and collision-safe identities. + +No committed fixture contains customer data, real hostnames, raw filesystem +paths, credentials, or live SCCM evidence. No live Windows or SCCM Server +acceptance is claimed by this preparation slice. From 5e43b6104c537711637737ed96a89f5cc06fde34 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 30 Jul 2026 22:31:07 -0400 Subject: [PATCH 02/20] test(sccm): harden distribution point corpus --- .../sccm/server/distribution_point/README.md | 4 + .../site/current/PkgXferMgr.log | 1 + .../site/current/distmgr.log | 2 + .../site/dp-02/current/PkgXferMgr.log | 1 - .../site/dp-02/current/distmgr.log | 2 - .../content-version-mismatch/expected.json | 22 +- .../content-version-mismatch/manifest.json | 50 +--- ...ver_distribution_point_fixture_contract.rs | 271 ++++++++++++++++-- .../issue-329-distribution-point-corpus.md | 18 +- 9 files changed, 278 insertions(+), 93 deletions(-) delete mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/PkgXferMgr.log delete mode 100644 crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/distmgr.log diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md index 1ef11fb2e..088756cce 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md @@ -2,6 +2,10 @@ This directory is test-only input for Issue `#329`. +Site-server logs are physical captures, not per-DP projections. The multi-DP +scenario therefore stores `distmgr.log` and `PkgXferMgr.log` once and binds +exact DP/content/version identity from each normalized logical CCM record. + - Every evidence file is authored synthetic CCM text and contains the literal `SYNTHETIC FIXTURE` marker. - `manifest.json` records physical producer, workflow subject, coverage, diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/PkgXferMgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/PkgXferMgr.log index df2c54a87..4557e6487 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/PkgXferMgr.log +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/PkgXferMgr.log @@ -1,2 +1,3 @@ + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/distmgr.log index 4089684ff..24a75c73e 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/distmgr.log +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/current/distmgr.log @@ -1,4 +1,6 @@ + + diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/PkgXferMgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/PkgXferMgr.log deleted file mode 100644 index 1ec58d395..000000000 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/PkgXferMgr.log +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/distmgr.log b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/distmgr.log deleted file mode 100644 index 72c42e73a..000000000 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/evidence/server-dp-distribution/site/dp-02/current/distmgr.log +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/expected.json index a37868c5b..5d690be0b 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/expected.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/expected.json @@ -10,9 +10,7 @@ {"artifactId": "dp-version-01-distmgr", "state": "captured"}, {"artifactId": "dp-version-02-pkgxfer", "state": "captured"}, {"artifactId": "dp-version-03-provider", "state": "captured"}, - {"artifactId": "dp-version-04-distmgr-dp02", "state": "captured"}, - {"artifactId": "dp-version-05-pkgxfer-dp02", "state": "captured"}, - {"artifactId": "dp-version-06-provider-dp02", "state": "captured"} + {"artifactId": "dp-version-04-provider-dp02", "state": "captured"} ], "transactions": [ { @@ -49,12 +47,12 @@ "nextSourceId": null, "coverageGapArtifactIds": [], "observations": [ - {"observationId": "01-dp02-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-04-distmgr-dp02", "startLine": 1, "endLine": 1}]}, - {"observationId": "02-dp02-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-04-distmgr-dp02", "startLine": 2, "endLine": 2}]}, - {"observationId": "03-dp02-transfer", "phase": "transfer", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-05-pkgxfer-dp02", "startLine": 1, "endLine": 1}]}, - {"observationId": "04-dp02-validate", "phase": "validate", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-06-provider-dp02", "startLine": 1, "endLine": 1}]}, - {"observationId": "05-dp02-available", "phase": "makeAvailable", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-06-provider-dp02", "startLine": 2, "endLine": 2}]}, - {"observationId": "06-dp02-report", "phase": "serveOrReport", "disposition": "succeeded", "terminal": true, "evidence": [{"artifactId": "dp-version-06-provider-dp02", "startLine": 3, "endLine": 3}]} + {"observationId": "01-dp02-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-01-distmgr", "startLine": 3, "endLine": 3}]}, + {"observationId": "02-dp02-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-01-distmgr", "startLine": 4, "endLine": 4}]}, + {"observationId": "03-dp02-transfer", "phase": "transfer", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-02-pkgxfer", "startLine": 2, "endLine": 2}]}, + {"observationId": "04-dp02-validate", "phase": "validate", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-04-provider-dp02", "startLine": 1, "endLine": 1}]}, + {"observationId": "05-dp02-available", "phase": "makeAvailable", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-04-provider-dp02", "startLine": 2, "endLine": 2}]}, + {"observationId": "06-dp02-report", "phase": "serveOrReport", "disposition": "succeeded", "terminal": true, "evidence": [{"artifactId": "dp-version-04-provider-dp02", "startLine": 3, "endLine": 3}]} ] }, { @@ -70,9 +68,9 @@ "nextSourceId": null, "coverageGapArtifactIds": [], "observations": [ - {"observationId": "01-v2-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-01-distmgr", "startLine": 3, "endLine": 3}]}, - {"observationId": "02-v2-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-01-distmgr", "startLine": 4, "endLine": 4}]}, - {"observationId": "03-v2-transfer-retry", "phase": "transfer", "disposition": "retrying", "terminal": false, "evidence": [{"artifactId": "dp-version-02-pkgxfer", "startLine": 2, "endLine": 2}]} + {"observationId": "01-v2-receive", "phase": "receiveContent", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-01-distmgr", "startLine": 5, "endLine": 5}]}, + {"observationId": "02-v2-distribute", "phase": "distribute", "disposition": "succeeded", "terminal": false, "evidence": [{"artifactId": "dp-version-01-distmgr", "startLine": 6, "endLine": 6}]}, + {"observationId": "03-v2-transfer-retry", "phase": "transfer", "disposition": "retrying", "terminal": false, "evidence": [{"artifactId": "dp-version-02-pkgxfer", "startLine": 3, "endLine": 3}]} ] } ], diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/manifest.json index 278ef27b9..6b8af19f0 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/manifest.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/content-version-mismatch/manifest.json @@ -16,7 +16,7 @@ "producerRole": "siteServer", "producerHostHandle": "safe:server:lab-pri-01", "workflowSubjectRole": "distributionPoint", - "workflowSubjectHandle": "safe:dp:lab-dp-01", + "workflowSubjectBasis": "manifestTopology", "sourceKind": "ccmLog", "originalBasename": "distmgr.log", "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", @@ -27,7 +27,7 @@ "collectedUtc": "2026-07-30T12:20:00Z", "encoding": "utf-8", "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, - "bytesCopied": 1404, + "bytesCopied": 2106, "relativePath": "evidence/server-dp-distribution/site/current/distmgr.log" }, { @@ -36,7 +36,7 @@ "producerRole": "siteServer", "producerHostHandle": "safe:server:lab-pri-01", "workflowSubjectRole": "distributionPoint", - "workflowSubjectHandle": "safe:dp:lab-dp-01", + "workflowSubjectBasis": "manifestTopology", "sourceKind": "ccmLog", "originalBasename": "PkgXferMgr.log", "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/PkgXferMgr.log", @@ -47,7 +47,7 @@ "collectedUtc": "2026-07-30T12:20:00Z", "encoding": "utf-8", "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, - "bytesCopied": 705, + "bytesCopied": 1058, "relativePath": "evidence/server-dp-distribution/site/current/PkgXferMgr.log" }, { @@ -71,47 +71,7 @@ "relativePath": "evidence/server-dp-distribution/dp/current/SMSDPProv.log" }, { - "artifactId": "dp-version-04-distmgr-dp02", - "sourceId": "server-dp-distribution", - "producerRole": "siteServer", - "producerHostHandle": "safe:server:lab-pri-01", - "workflowSubjectRole": "distributionPoint", - "workflowSubjectHandle": "safe:dp:lab-dp-02", - "sourceKind": "ccmLog", - "originalBasename": "distmgr.log", - "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/distmgr.log", - "pathFingerprint": "synthetic:version-distmgr-dp02", - "rotation": {"kind": "current", "lineageId": "version-distmgr-dp02", "fragmentComplete": true}, - "captureState": "captured", - "sourceVersion": "5.00.TEST.0001", - "collectedUtc": "2026-07-30T12:20:00Z", - "encoding": "utf-8", - "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, - "bytesCopied": 702, - "relativePath": "evidence/server-dp-distribution/site/dp-02/current/distmgr.log" - }, - { - "artifactId": "dp-version-05-pkgxfer-dp02", - "sourceId": "server-dp-distribution", - "producerRole": "siteServer", - "producerHostHandle": "safe:server:lab-pri-01", - "workflowSubjectRole": "distributionPoint", - "workflowSubjectHandle": "safe:dp:lab-dp-02", - "sourceKind": "ccmLog", - "originalBasename": "PkgXferMgr.log", - "sanitizedSourcePath": "SYNTHETIC://site-root/Logs/PkgXferMgr.log", - "pathFingerprint": "synthetic:version-pkgxfer-dp02", - "rotation": {"kind": "current", "lineageId": "version-pkgxfer-dp02", "fragmentComplete": true}, - "captureState": "captured", - "sourceVersion": "5.00.TEST.0001", - "collectedUtc": "2026-07-30T12:20:00Z", - "encoding": "utf-8", - "collectionLimit": {"byteLimit": 4096, "limitApplied": false}, - "bytesCopied": 353, - "relativePath": "evidence/server-dp-distribution/site/dp-02/current/PkgXferMgr.log" - }, - { - "artifactId": "dp-version-06-provider-dp02", + "artifactId": "dp-version-04-provider-dp02", "sourceId": "server-dp-distribution", "producerRole": "distributionPoint", "producerHostHandle": "safe:dp:lab-dp-02", diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 58cb4752e..3a5a2890a 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -172,6 +172,9 @@ struct ParsedArtifact { source_id: String, role: String, basename: String, + workflow_subject_handle: Option, + rotation_kind: String, + rotation_lineage: String, fragment_complete: Option, } @@ -365,7 +368,7 @@ fn validate_manifest( let mut parsed_artifacts = BTreeMap::new(); let mut evidence_by_reference = BTreeMap::new(); let mut relative_paths = BTreeSet::new(); - let mut path_fingerprints = BTreeSet::new(); + let mut physical_source_identities = BTreeSet::new(); for artifact in artifacts { let artifact_id = match required_string(artifact, "artifactId", "artifact") { Ok(value) => value, @@ -412,6 +415,7 @@ fn validate_manifest( "producerHostHandle", "workflowSubjectRole", "workflowSubjectHandle", + "workflowSubjectBasis", "sourceKind", "originalBasename", "sanitizedSourcePath", @@ -447,10 +451,16 @@ fn validate_manifest( "{artifact_id} has an uncatalogued source/producer/basename combination" )); } + let workflow_subject_handle = artifact["workflowSubjectHandle"].as_str(); + let workflow_subject_basis = artifact["workflowSubjectBasis"].as_str(); if artifact["workflowSubjectRole"] != "distributionPoint" - || !artifact["workflowSubjectHandle"] - .as_str() - .is_some_and(|handle| distribution_point_handles.contains(handle)) + || match (workflow_subject_handle, workflow_subject_basis) { + (Some(handle), None) => !distribution_point_handles.contains(handle), + (None, Some("manifestTopology")) => { + role != "siteServer" || distribution_point_handles.len() < 2 + } + _ => true, + } { failures.push(format!( "{artifact_id} loses the distribution-point workflow subject" @@ -463,28 +473,47 @@ fn validate_manifest( failures.push(format!("{artifact_id} lacks an opaque producer handle")); } if role == "distributionPoint" - && artifact["producerHostHandle"] != artifact["workflowSubjectHandle"] + && (workflow_subject_handle.is_none() + || artifact["producerHostHandle"] != artifact["workflowSubjectHandle"]) { failures.push(format!( "{artifact_id} DP producer does not match its exact workflow subject" )); } - if !artifact["pathFingerprint"] - .as_str() - .is_some_and(|value| value.starts_with("synthetic:")) - || !artifact["sanitizedSourcePath"] - .as_str() - .is_some_and(|value| value.starts_with("SYNTHETIC://")) + let path_fingerprint = artifact["pathFingerprint"].as_str(); + let sanitized_source_path = artifact["sanitizedSourcePath"].as_str(); + if !path_fingerprint.is_some_and(|value| value.starts_with("synthetic:")) + || !sanitized_source_path.is_some_and(|value| value.starts_with("SYNTHETIC://")) { failures.push(format!("{artifact_id} leaks or omits path provenance")); } - if artifact["pathFingerprint"] + let rotation_kind = artifact["rotation"]["kind"].as_str(); + let rotation_value = artifact["rotation"]["value"] .as_str() - .is_some_and(|value| !path_fingerprints.insert(value.to_owned())) - { - failures.push(format!( - "{artifact_id} collapses a path fingerprint collision" - )); + .map(str::to_owned) + .or_else(|| { + artifact["rotation"]["value"] + .as_u64() + .map(|value| value.to_string()) + }) + .unwrap_or_default(); + if let (Some(producer), Some(source_path), Some(rotation_kind)) = ( + artifact["producerHostHandle"].as_str(), + sanitized_source_path, + rotation_kind, + ) { + let physical_identity = ( + producer.to_owned(), + source_path.to_owned(), + basename.to_owned(), + rotation_kind.to_owned(), + rotation_value, + ); + if !physical_source_identities.insert(physical_identity) { + failures.push(format!( + "{artifact_id} duplicates one physical source for another workflow subject" + )); + } } if artifact["sourceKind"] != "ccmLog" || !artifact["sourceVersion"] @@ -640,8 +669,22 @@ fn validate_manifest( "{artifact_id} loses distinct CCM code-origin provenance" )); } - if let Err(error) = parse_fixture_fields(&record.message) { - failures.push(format!("{artifact_id}: {error}")); + match parse_fixture_fields(&record.message) { + Ok(fields) => { + let record_dp_handle = fields.get("DpHandle").map(String::as_str); + if workflow_subject_handle + .is_some_and(|handle| record_dp_handle != Some(handle)) + || workflow_subject_basis == Some("manifestTopology") + && record_dp_handle.is_none_or(|handle| { + !distribution_point_handles.contains(handle) + }) + { + failures.push(format!( + "{artifact_id} record escapes its declared workflow-subject scope" + )); + } + } + Err(error) => failures.push(format!("{artifact_id}: {error}")), } let Some(line_start) = record.reference.line_start else { failures.push(format!("{artifact_id} evidence lacks lineStart")); @@ -675,6 +718,15 @@ fn validate_manifest( source_id: source_id.to_owned(), role: role.to_owned(), basename: basename.to_owned(), + workflow_subject_handle: workflow_subject_handle.map(str::to_owned), + rotation_kind: artifact["rotation"]["kind"] + .as_str() + .unwrap_or_default() + .to_owned(), + rotation_lineage: artifact["rotation"]["lineageId"] + .as_str() + .unwrap_or_default() + .to_owned(), fragment_complete: artifact["rotation"]["fragmentComplete"].as_bool(), }, ) @@ -1055,7 +1107,11 @@ fn validate_expected( match parsed.artifacts.get(cited_artifact_id) { Some(artifact) if artifact.role != "client" - && phase_allowed_for_artifact(artifact, phase) => {} + && phase_allowed_for_artifact(artifact, phase) + && artifact + .workflow_subject_handle + .as_deref() + .is_none_or(|handle| handle == key_fields["DpHandle"]) => {} _ => failures.push(format!( "{observation_id} cites an artifact that cannot own phase {phase}" )), @@ -1224,8 +1280,9 @@ fn validate_expected( observation_id, &mut failures, ); + let classification = observation["classification"].as_str(); if !matches!( - observation["classification"].as_str(), + classification, Some("ignoredClientEvidence" | "rotationSplit" | "malformedEvidence") ) || observation.get("key").is_some() || observation["correlationEligible"] != false @@ -1242,33 +1299,103 @@ fn validate_expected( .unwrap_or_default(); let mut sorted_artifact_ids = artifact_ids.clone(); sorted_artifact_ids.sort_unstable(); - if artifact_ids.is_empty() || artifact_ids != sorted_artifact_ids { + let unique_artifact_ids = artifact_ids.iter().copied().collect::>(); + if artifact_ids.is_empty() + || artifact_ids != sorted_artifact_ids + || unique_artifact_ids.len() != artifact_ids.len() + { failures.push(format!( "{observation_id} lacks sorted physical artifact provenance" )); } - for artifact_id in artifact_ids { - if !parsed.artifacts.contains_key(artifact_id) { + for artifact_id in &artifact_ids { + if !parsed.artifacts.contains_key(*artifact_id) { failures.push(format!( "{observation_id} cites unknown physical artifact {artifact_id}" )); } } - for reference in observation["evidence"] + let references = observation["evidence"] .as_array() .map(Vec::as_slice) - .unwrap_or_default() - { + .unwrap_or_default(); + let mut cited_artifact_ids = BTreeSet::new(); + for reference in references { reject_unknown_fields( reference, &["artifactId", "startLine", "endLine"], &format!("{observation_id}.evidence"), &mut failures, ); + if let Ok(artifact_id) = required_string( + reference, + "artifactId", + &format!("{observation_id}.evidence"), + ) { + cited_artifact_ids.insert(artifact_id); + if !unique_artifact_ids.contains(artifact_id) { + failures.push(format!( + "{observation_id} cites evidence outside its physical artifact set" + )); + } + } if let Err(error) = evidence_for(parsed, reference, observation_id) { failures.push(error); } } + let artifacts = artifact_ids + .iter() + .filter_map(|artifact_id| parsed.artifacts.get(*artifact_id)) + .collect::>(); + let semantic_match = match classification { + Some("ignoredClientEvidence") => { + !references.is_empty() + && cited_artifact_ids == unique_artifact_ids + && artifacts.iter().all(|artifact| { + artifact.role == "client" + && artifact.source_id == "client-content-control" + && matches!(artifact.state.as_str(), "captured" | "capped") + }) + } + Some("rotationSplit") => { + let source_ids = artifacts + .iter() + .map(|artifact| artifact.source_id.as_str()) + .collect::>(); + let lineages = artifacts + .iter() + .map(|artifact| artifact.rotation_lineage.as_str()) + .collect::>(); + let rotation_kinds = artifacts + .iter() + .map(|artifact| artifact.rotation_kind.as_str()) + .collect::>(); + references.is_empty() + && artifacts.len() >= 2 + && source_ids.len() == 1 + && lineages.len() == 1 + && lineages.first().is_some_and(|lineage| !lineage.is_empty()) + && rotation_kinds.len() >= 2 + && artifacts.iter().all(|artifact| { + artifact.role != "client" + && matches!(artifact.state.as_str(), "captured" | "capped") + && artifact.fragment_complete == Some(false) + }) + } + Some("malformedEvidence") => { + references.is_empty() + && !artifacts.is_empty() + && artifacts.iter().all(|artifact| { + artifact.role != "client" && artifact.state == "parseFailed" + }) + } + _ => false, + }; + if !semantic_match { + failures.push(format!( + "{observation_id} classification is detached from exact physical coverage semantics" + )); + } } let requests = match required_array(expected, "artifactRequests", "expected") { @@ -1643,3 +1770,93 @@ fn unknown_semantics_collisions_and_output_reordering_fail_closed() { "closed-schema/collision/order mutations were accepted: {accepted:?}" ); } + +#[test] +fn one_physical_site_log_is_not_duplicated_per_distribution_point_subject() { + let manifest = read_json("content-version-mismatch", "manifest.json").expect("manifest loads"); + let mut physical_sources = BTreeSet::new(); + let mut duplicates = Vec::new(); + + for artifact in manifest["artifacts"] + .as_array() + .expect("artifacts are an array") + { + let identity = ( + artifact["producerHostHandle"] + .as_str() + .expect("producer handle"), + artifact["sanitizedSourcePath"] + .as_str() + .expect("sanitized source path"), + artifact["originalBasename"].as_str().expect("basename"), + artifact["rotation"]["kind"] + .as_str() + .expect("rotation kind"), + ); + if !physical_sources.insert(identity) { + duplicates.push(identity); + } + } + + assert!( + duplicates.is_empty(), + "one physical capture was duplicated to attach multiple workflow subjects: {duplicates:?}" + ); + + let mut falsely_narrowed = manifest.clone(); + falsely_narrowed["artifacts"][0] + .as_object_mut() + .expect("artifact is an object") + .remove("workflowSubjectBasis"); + falsely_narrowed["artifacts"][0]["workflowSubjectHandle"] = json!(EXACT_DP); + assert!( + validate_manifest( + &corpus_root().join("content-version-mismatch"), + &falsely_narrowed + ) + .is_err(), + "a shared physical site log was falsely narrowed to one DP despite containing another" + ); +} + +#[test] +fn source_local_classifications_are_bound_to_physical_coverage_semantics() { + let client_manifest = + read_json("client-only-looking-request", "manifest.json").expect("manifest loads"); + let client_expected = + read_json("client-only-looking-request", "expected.json").expect("expected loads"); + let rotation_manifest = + read_json("rotation-boundary", "manifest.json").expect("manifest loads"); + let rotation_expected = + read_json("rotation-boundary", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut client_as_malformed = client_expected.clone(); + client_as_malformed["sourceLocalObservations"][0]["classification"] = + json!("malformedEvidence"); + if mutation_was_accepted( + "client-only-looking-request", + &client_manifest, + &client_as_malformed, + ) { + accepted.push("captured client evidence relabeled as malformed server evidence"); + } + + let mut split_as_client = rotation_expected.clone(); + split_as_client["sourceLocalObservations"][0]["classification"] = + json!("ignoredClientEvidence"); + if mutation_was_accepted("rotation-boundary", &rotation_manifest, &split_as_client) { + accepted.push("split server rotation relabeled as ignored client evidence"); + } + + let mut malformed_as_split = rotation_expected.clone(); + malformed_as_split["sourceLocalObservations"][1]["classification"] = json!("rotationSplit"); + if mutation_was_accepted("rotation-boundary", &rotation_manifest, &malformed_as_split) { + accepted.push("parse-failed source relabeled as a rotation split"); + } + + assert!( + accepted.is_empty(), + "source-local classifications were detached from physical coverage: {accepted:?}" + ); +} diff --git a/docs/sccm/preparation/issue-329-distribution-point-corpus.md b/docs/sccm/preparation/issue-329-distribution-point-corpus.md index 00c443f32..aeac2a4a6 100644 --- a/docs/sccm/preparation/issue-329-distribution-point-corpus.md +++ b/docs/sccm/preparation/issue-329-distribution-point-corpus.md @@ -30,8 +30,8 @@ A physical producer is not inferred from the workflow it describes. | Source ID | Basename | Allowed producer role | Workflow subject | Use | | --- | --- | --- | --- | --- | -| `server-dp-distribution` | `distmgr.log` | `siteServer` | exact DP handle | Receive and distribute | -| `server-dp-distribution` | `PkgXferMgr.log` | `siteServer` | exact DP handle | Transfer and retry | +| `server-dp-distribution` | `distmgr.log` | `siteServer` | DP role scope; exact handle on each record | Receive and distribute | +| `server-dp-distribution` | `PkgXferMgr.log` | `siteServer` | DP role scope; exact handle on each record | Transfer and retry | | `server-dp-distribution` | `SMSDPProv.log` | `distributionPoint` | same exact DP handle | Validate and make available | | `server-dp-distribution` | `PullDP.log` | `distributionPoint` | same exact pull-DP handle | Pull transfer when a reviewed fixture proves it | | `server-dp-serve` | `SMSdpmon.log` | `distributionPoint` | same exact DP handle | Optional, explicitly catalogued serving/status evidence | @@ -46,7 +46,8 @@ Each manifest preserves: - a synthetic site code and one or more approved opaque DP handles; - producer role and producer handle separately from workflow-subject role and - handle; + either an exact handle or the bounded `manifestTopology` basis used by one + site-server file that contains records for multiple declared DPs; - source ID, exact basename, source grammar, synthetic version, path fingerprint, and `SYNTHETIC://` provenance; - rotation kind, lineage, and fragment completeness; @@ -54,8 +55,12 @@ Each manifest preserves: byte count, and bounded relative evidence path; and - deterministic artifact identity and ordering. -The two DPs in `content-version-mismatch` are separate subjects even though -the site-server basenames and package/content identifiers overlap. +The two DPs in `content-version-mismatch` remain separate transaction subjects +even though one physical `distmgr.log` and one physical `PkgXferMgr.log` +contain records for both. A physical site-server source is captured once; +changing its path fingerprint or destination cannot duplicate it merely to +attach another workflow-subject handle. Each admitted logical record must +carry an exact DP handle from the bounded manifest topology. ## State and exact-key contract @@ -96,7 +101,8 @@ The outcome rules are conservative: - incomplete coverage remains `insufficientEvidence` with exact physical gap IDs and a bounded source ID; - rotation fragments and malformed evidence remain noncorrelatable - source-local observations; and + source-local observations whose classification is bound to exact physical + role, capture state, lineage, rotation kind, and fragment completeness; and - a client-only download record cannot become a DP transaction or DP failure. ## Coverage and request contract From d07ab1575d2d5a4360dc0f606064b8dbc6525170 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 30 Jul 2026 22:33:15 -0400 Subject: [PATCH 03/20] test(sccm): enforce DP producer provenance --- ...ver_distribution_point_fixture_contract.rs | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 3a5a2890a..fe58b8f12 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -32,6 +32,8 @@ const STATE_CHAIN: &[&str] = &[ const EXACT_PROFILE: &str = "dp-server-5.00.test-v1"; const EXACT_SITE: &str = "LAB"; const EXACT_DP: &str = "safe:dp:lab-dp-01"; +const EXACT_SITE_SERVER: &str = "safe:server:lab-pri-01"; +const EXACT_CLIENT: &str = "safe:client:lab-client-01"; fn corpus_root() -> std::path::PathBuf { std::path::Path::new(env!("CARGO_MANIFEST_DIR")) @@ -466,11 +468,19 @@ fn validate_manifest( "{artifact_id} loses the distribution-point workflow subject" )); } - if !artifact["producerHostHandle"] - .as_str() - .is_some_and(|value| value.starts_with("safe:")) - { - failures.push(format!("{artifact_id} lacks an opaque producer handle")); + let producer_host_handle = artifact["producerHostHandle"].as_str(); + let producer_matches_role = match role { + "siteServer" => producer_host_handle == Some(EXACT_SITE_SERVER), + "client" => producer_host_handle == Some(EXACT_CLIENT), + "distributionPoint" => { + producer_host_handle.is_some_and(|value| distribution_point_handles.contains(value)) + } + _ => false, + }; + if !producer_matches_role { + failures.push(format!( + "{artifact_id} producer handle is not in the exact role-specific namespace" + )); } if role == "distributionPoint" && (workflow_subject_handle.is_none() @@ -703,6 +713,7 @@ fn validate_manifest( } else if artifact.get("relativePath").is_some() || artifact.get("bytesCopied").is_some() || artifact.get("encoding").is_some() + || artifact.get("collectionLimit").is_some() || artifact["rotation"].get("fragmentComplete").is_some() { failures.push(format!( @@ -1638,6 +1649,19 @@ fn coverage_role_and_rotation_states_fail_closed() { accepted.push("basename reclassified the site-server producer as a DP"); } + let mut host_alias_manifest = absent_manifest.clone(); + host_alias_manifest["artifacts"][0]["producerHostHandle"] = json!(EXACT_DP); + if mutation_was_accepted("absent-dp", &host_alias_manifest, &absent_expected) { + accepted.push("site-server producer host collapsed onto its DP workflow subject"); + } + + let mut nonphysical_limit_manifest = absent_manifest.clone(); + nonphysical_limit_manifest["artifacts"][0]["collectionLimit"] = + json!({"byteLimit": 4096, "limitApplied": false}); + if mutation_was_accepted("absent-dp", &nonphysical_limit_manifest, &absent_expected) { + accepted.push("absent artifact invented a physical collection-limit policy"); + } + let rotation_manifest = read_json("rotation-boundary", "manifest.json").expect("manifest loads"); let rotation_expected = From 98a72fe8db7315497935d7382a5572e1f63a3f61 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 01:41:08 -0400 Subject: [PATCH 04/20] test(sccm): harden DP executable probe --- ...ver_distribution_point_fixture_contract.rs | 317 ++++++++++++++++-- .../issue-329-distribution-point-corpus.md | 19 +- 2 files changed, 295 insertions(+), 41 deletions(-) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index fe58b8f12..daca035ed 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -30,6 +30,7 @@ const STATE_CHAIN: &[&str] = &[ ]; const EXACT_PROFILE: &str = "dp-server-5.00.test-v1"; +const EXACT_SOURCE_VERSION: &str = "5.00.TEST.0001"; const EXACT_SITE: &str = "LAB"; const EXACT_DP: &str = "safe:dp:lab-dp-01"; const EXACT_SITE_SERVER: &str = "safe:server:lab-pri-01"; @@ -108,16 +109,20 @@ fn coverage_from_manifest(state: &str) -> Result { fn rotation_from_manifest(rotation: &Value) -> Result { match required_string(rotation, "kind", "rotation")? { - "current" => Ok(SccmRotation::Current), - "lo_" => Ok(SccmRotation::LoUnderscore), - "numbered" => rotation["value"] - .as_u64() - .and_then(|value| u32::try_from(value).ok()) - .map(SccmRotation::Numbered) - .ok_or_else(|| "numbered rotation requires a u32 value".to_owned()), - "timestamped" => required_string(rotation, "value", "rotation") - .map(str::to_owned) - .map(SccmRotation::Timestamped), + "current" if rotation.get("value").is_none() => Ok(SccmRotation::Current), + "current" => Err("current rotation must not contain a value".to_owned()), + "lo_" if rotation.get("value").is_none() => Ok(SccmRotation::LoUnderscore), + "lo_" => Err("lo_ rotation must not contain a value".to_owned()), + "numbered" => serde_json::from_value(json!({ + "kind": "numbered", + "value": rotation["value"].clone(), + })) + .map_err(|error| format!("numbered rotation is noncanonical: {error}")), + "timestamped" => serde_json::from_value(json!({ + "kind": "timestamped", + "value": rotation["value"].clone(), + })) + .map_err(|error| format!("timestamped rotation is noncanonical: {error}")), other => Err(format!("unsupported fixture rotation {other}")), } } @@ -233,18 +238,45 @@ fn parse_fixture_fields(message: &str) -> Result, Strin Ok(fields) } +fn path_segment_is_safe(segment: &str) -> bool { + !segment.is_empty() + && !matches!(segment, "." | "..") + && segment + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-')) +} + +fn segmented_path_is_safe(path: &str) -> bool { + !path.is_empty() && !path.contains('\\') && path.split('/').all(path_segment_is_safe) +} + fn source_path_is_bounded(relative_path: &str, basename: &str) -> bool { - !relative_path.is_empty() - && relative_path.starts_with("evidence/") - && !relative_path.starts_with('/') - && !relative_path.contains('\\') - && !relative_path.split('/').any(|segment| segment == "..") + relative_path + .strip_prefix("evidence/") + .is_some_and(segmented_path_is_safe) && relative_path .rsplit('/') .next() .is_some_and(|candidate| candidate == basename) } +fn sanitized_source_path_is_bounded(source_path: &str) -> bool { + source_path + .strip_prefix("SYNTHETIC://") + .is_some_and(segmented_path_is_safe) +} + +fn path_fingerprint_is_safe(path_fingerprint: &str) -> bool { + path_fingerprint + .strip_prefix("synthetic:") + .is_some_and(|suffix| { + !suffix.is_empty() + && suffix + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-')) + }) +} + fn validate_manifest( scenario_root: &std::path::Path, manifest: &Value, @@ -324,10 +356,25 @@ fn validate_manifest( .into_iter() .collect::>(); - let roles = manifest["topology"]["rolesObserved"] - .as_array() - .map(|values| values.iter().filter_map(Value::as_str).collect::>()) - .unwrap_or_default(); + let mut roles = Vec::new(); + match required_array(&manifest["topology"], "rolesObserved", "topology") { + Ok(values) => { + for value in values { + match value.as_str() { + Some(role) if matches!(role, "distributionPoint" | "siteServer") => { + roles.push(role); + } + Some(role) => { + failures.push(format!("rolesObserved contains unsupported role {role}")) + } + None => failures.push( + "rolesObserved entries must be strings in the allowed role set".to_owned(), + ), + } + } + } + Err(error) => failures.push(error), + } let mut sorted_roles = roles.clone(); sorted_roles.sort_unstable(); sorted_roles.dedup(); @@ -371,6 +418,7 @@ fn validate_manifest( let mut evidence_by_reference = BTreeMap::new(); let mut relative_paths = BTreeSet::new(); let mut physical_source_identities = BTreeSet::new(); + let mut path_fingerprints = BTreeSet::new(); for artifact in artifacts { let artifact_id = match required_string(artifact, "artifactId", "artifact") { Ok(value) => value, @@ -492,11 +540,16 @@ fn validate_manifest( } let path_fingerprint = artifact["pathFingerprint"].as_str(); let sanitized_source_path = artifact["sanitizedSourcePath"].as_str(); - if !path_fingerprint.is_some_and(|value| value.starts_with("synthetic:")) - || !sanitized_source_path.is_some_and(|value| value.starts_with("SYNTHETIC://")) + if !path_fingerprint.is_some_and(path_fingerprint_is_safe) + || !sanitized_source_path.is_some_and(sanitized_source_path_is_bounded) { failures.push(format!("{artifact_id} leaks or omits path provenance")); } + if path_fingerprint.is_some_and(|value| !path_fingerprints.insert(value.to_owned())) { + failures.push(format!( + "{artifact_id} duplicates another physical path fingerprint" + )); + } let rotation_kind = artifact["rotation"]["kind"].as_str(); let rotation_value = artifact["rotation"]["value"] .as_str() @@ -526,9 +579,7 @@ fn validate_manifest( } } if artifact["sourceKind"] != "ccmLog" - || !artifact["sourceVersion"] - .as_str() - .is_some_and(|value| value.starts_with("5.00.TEST.")) + || artifact["sourceVersion"].as_str() != Some(EXACT_SOURCE_VERSION) { failures.push(format!( "{artifact_id} is outside the synthetic CCM/profile source boundary" @@ -763,6 +814,16 @@ fn evidence_for<'a>( reference: &Value, context: &str, ) -> Result<&'a SccmEvidence, String> { + let key = evidence_reference_key(reference, context)?; + parsed.evidence.get(&key).ok_or_else(|| { + format!( + "{context} does not cite a physical logical record: {}:{}-{}", + key.0, key.1, key.2 + ) + }) +} + +fn evidence_reference_key(reference: &Value, context: &str) -> Result<(String, u32, u32), String> { let artifact_id = required_string(reference, "artifactId", context)?; let line_start = reference["startLine"] .as_u64() @@ -772,14 +833,7 @@ fn evidence_for<'a>( .as_u64() .and_then(|value| u32::try_from(value).ok()) .ok_or_else(|| format!("{context}.endLine must be a u32"))?; - parsed - .evidence - .get(&(artifact_id.to_owned(), line_start, line_end)) - .ok_or_else(|| { - format!( - "{context} does not cite a physical logical record: {artifact_id}:{line_start}-{line_end}" - ) - }) + Ok((artifact_id.to_owned(), line_start, line_end)) } fn exact_key_fields( @@ -1064,9 +1118,22 @@ fn validate_expected( let mut terminal_deferred = false; let mut previous_utc = i64::MIN; let mut previous_phase = 0usize; + let mut seen_observation_ids = BTreeSet::new(); + let mut consumed_evidence = BTreeSet::new(); for observation in observations { - let observation_id = - required_string(observation, "observationId", transaction_id).unwrap_or("invalid"); + let observation_id = match required_string(observation, "observationId", transaction_id) + { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + if !seen_observation_ids.insert(observation_id) { + failures.push(format!( + "{transaction_id} contains duplicate observationId {observation_id}" + )); + } let phase = required_string(observation, "phase", observation_id).unwrap_or("invalid"); let disposition = required_string(observation, "disposition", observation_id).unwrap_or("invalid"); @@ -1113,6 +1180,13 @@ fn validate_expected( &format!("{observation_id}.evidence"), &mut failures, ); + if evidence_reference_key(reference, observation_id) + .is_ok_and(|key| !consumed_evidence.insert(key)) + { + failures.push(format!( + "{transaction_id} consumes one physical evidence reference more than once" + )); + } let cited_artifact_id = required_string(reference, "artifactId", observation_id).unwrap_or("invalid"); match parsed.artifacts.get(cited_artifact_id) { @@ -1884,3 +1958,178 @@ fn source_local_classifications_are_bound_to_physical_coverage_semantics() { "source-local classifications were detached from physical coverage: {accepted:?}" ); } + +#[test] +fn path_provenance_aliases_fail_closed() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut duplicate_fingerprint = healthy_manifest.clone(); + duplicate_fingerprint["artifacts"][1]["pathFingerprint"] = + duplicate_fingerprint["artifacts"][0]["pathFingerprint"].clone(); + if mutation_was_accepted("healthy-package", &duplicate_fingerprint, &healthy_expected) { + accepted.push("duplicate path fingerprint"); + } + + let mut dot_segment_alias = healthy_manifest.clone(); + dot_segment_alias["artifacts"][1]["relativePath"] = + json!("evidence/server-dp-distribution/site/current/./PkgXferMgr.log"); + if mutation_was_accepted("healthy-package", &dot_segment_alias, &healthy_expected) { + accepted.push("dot-segment physical evidence alias"); + } + + let mut unsafe_source_path = healthy_manifest.clone(); + unsafe_source_path["artifacts"][2]["sanitizedSourcePath"] = + json!("SYNTHETIC://../../Users/RealUser/SMSDPProv.log"); + if mutation_was_accepted("healthy-package", &unsafe_source_path, &healthy_expected) { + accepted.push("unsafe sanitized source path"); + } + + assert!( + accepted.is_empty(), + "unsafe or colliding path provenance was accepted: {accepted:?}" + ); +} + +#[test] +fn exact_profile_requires_the_pinned_synthetic_source_version() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut missing_version = healthy_manifest.clone(); + missing_version["artifacts"][0] + .as_object_mut() + .expect("artifact is an object") + .remove("sourceVersion"); + if mutation_was_accepted("healthy-package", &missing_version, &healthy_expected) { + accepted.push("missing source version retained Exact"); + } + + for (label, version) in [ + ("unknown source version retained Exact", "5.00.TEST.UNKNOWN"), + ("malformed source version retained Exact", "5.00.TEST."), + ( + "prefix-collision source version retained Exact", + "5.00.TEST.0001-extra", + ), + ] { + let mut mutated = healthy_manifest.clone(); + mutated["artifacts"][0]["sourceVersion"] = json!(version); + if mutation_was_accepted("healthy-package", &mutated, &healthy_expected) { + accepted.push(label); + } + } + + assert!( + accepted.is_empty(), + "unvalidated source versions selected the Exact profile: {accepted:?}" + ); +} + +#[test] +fn topology_roles_are_typed_and_known() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut non_string_role = healthy_manifest.clone(); + non_string_role["topology"]["rolesObserved"] + .as_array_mut() + .expect("roles are an array") + .push(json!(7)); + if mutation_was_accepted("healthy-package", &non_string_role, &healthy_expected) { + accepted.push("non-string observed role"); + } + + let mut unknown_role = healthy_manifest.clone(); + unknown_role["topology"]["rolesObserved"] + .as_array_mut() + .expect("roles are an array") + .push(json!("unknownRole")); + if mutation_was_accepted("healthy-package", &unknown_role, &healthy_expected) { + accepted.push("unknown observed role"); + } + + assert!( + accepted.is_empty(), + "malformed role topology was accepted: {accepted:?}" + ); +} + +#[test] +fn rotation_shapes_match_the_shared_canonical_contract() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let rotation_manifest = + read_json("rotation-boundary", "manifest.json").expect("manifest loads"); + let rotation_expected = + read_json("rotation-boundary", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut current_with_value = healthy_manifest.clone(); + current_with_value["artifacts"][0]["rotation"]["value"] = json!("unexpected"); + if mutation_was_accepted("healthy-package", ¤t_with_value, &healthy_expected) { + accepted.push("current rotation with value"); + } + + let mut lo_with_value = rotation_manifest.clone(); + lo_with_value["artifacts"][1]["rotation"]["value"] = json!("unexpected"); + if mutation_was_accepted("rotation-boundary", &lo_with_value, &rotation_expected) { + accepted.push("lo_ rotation with value"); + } + + let mut numbered_zero = healthy_manifest.clone(); + numbered_zero["artifacts"][0]["rotation"]["kind"] = json!("numbered"); + numbered_zero["artifacts"][0]["rotation"]["value"] = json!(0); + if mutation_was_accepted("healthy-package", &numbered_zero, &healthy_expected) { + accepted.push("numbered rotation with zero value"); + } + + let mut malformed_timestamp = healthy_manifest.clone(); + malformed_timestamp["artifacts"][0]["rotation"]["kind"] = json!("timestamped"); + malformed_timestamp["artifacts"][0]["rotation"]["value"] = json!("20260730_122000"); + if mutation_was_accepted("healthy-package", &malformed_timestamp, &healthy_expected) { + accepted.push("timestamped rotation with noncanonical value"); + } + + assert!( + accepted.is_empty(), + "noncanonical rotation shapes were accepted: {accepted:?}" + ); +} + +#[test] +fn transaction_observation_ids_and_evidence_are_single_use() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut duplicate_observation_id = healthy_expected.clone(); + duplicate_observation_id["transactions"][0]["observations"][1]["observationId"] = + json!("01-receive"); + if mutation_was_accepted( + "healthy-package", + &healthy_manifest, + &duplicate_observation_id, + ) { + accepted.push("duplicate observation ID"); + } + + let mut reused_evidence = healthy_expected.clone(); + let mut repeated = reused_evidence["transactions"][0]["observations"][5].clone(); + repeated["observationId"] = json!("07-report-copy"); + reused_evidence["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are an array") + .push(repeated); + if mutation_was_accepted("healthy-package", &healthy_manifest, &reused_evidence) { + accepted.push("one physical evidence reference consumed twice"); + } + + assert!( + accepted.is_empty(), + "duplicate observations or reused evidence were accepted: {accepted:?}" + ); +} diff --git a/docs/sccm/preparation/issue-329-distribution-point-corpus.md b/docs/sccm/preparation/issue-329-distribution-point-corpus.md index aeac2a4a6..42f181215 100644 --- a/docs/sccm/preparation/issue-329-distribution-point-corpus.md +++ b/docs/sccm/preparation/issue-329-distribution-point-corpus.md @@ -82,16 +82,19 @@ packageId + extractionProfileId ``` -The synthetic profile is `dp-server-5.00.test-v1`, limited to -`5.00.TEST.*` fixture evidence. It is not a claim that a real ConfigMgr build -has been validated. +The synthetic profile is `dp-server-5.00.test-v1`, pinned to the exact +`5.00.TEST.0001` fixture version. Missing, malformed, unknown, or +prefix-collision versions cannot retain the exact profile. This is not a claim +that a real ConfigMgr build has been validated. The focused contract parses semicolon-delimited synthetic fields as unique `Name=Value` pairs. Substring lookalikes, duplicate fields, missing fields, case aliases, a changed version, or a changed DP handle cannot satisfy an exact transaction. Observation order uses the additive normalized SCCM timestamp provenance, not the legacy public `LogEntry.timezone_offset`. -Evidence later than the canonical bundle capture is rejected. +Evidence later than the canonical bundle capture is rejected. Observation IDs +are unique within a transaction, and one physical +`(artifactId, startLine, endLine)` reference can be consumed only once. The outcome rules are conservative: @@ -140,9 +143,11 @@ uninstalled, unavailable, healthy, or failed. | `absent-dp` | Missing source candidates do not erase or diagnose an observed DP role | | `incomplete` | Exact early phases survive while absent/denied downstream coverage requests the bounded source | -The contract test also mutates exact versions, DP topology, terminal evidence, -coverage states, role provenance, causal fields, rotations, and transaction -cardinality. Each mutation must fail closed. +The contract test also mutates exact versions, typed role topology, terminal +evidence, coverage states, role provenance, causal fields, canonical rotation +shapes, transaction cardinality, path fingerprints, safe segmented source and +destination paths, observation IDs, and evidence consumption. Each mutation +must fail closed. ## Deferred implementation and validation From 0a915dc679e0847b01e7aa8b608ecefd1d967d29 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 02:19:01 -0400 Subject: [PATCH 05/20] test(sccm): harden DP corpus review boundaries --- .../sccm/server/distribution_point/README.md | 6 + ...ver_distribution_point_fixture_contract.rs | 431 ++++++++++++++++-- .../issue-329-distribution-point-corpus.md | 16 +- 3 files changed, 403 insertions(+), 50 deletions(-) diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md index 088756cce..634194a3a 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md @@ -13,6 +13,12 @@ exact DP/content/version identity from each normalized logical CCM record. - `expected.json` is a preparation label, not a frozen production API. - Exact package/content/version/DP/profile keys keep versions and DPs independent. +- Case-folded path fingerprints stay unique, sanitized roots and rotated + basenames stay synthetic, and topology arrays retain only typed declared + handles. +- Rotation lineage/fragment fields, observation IDs, evidence references, and + coverage-gap IDs fail closed on malformed, empty, duplicate, or reused + values. - Missing, denied, malformed, capped, or split evidence is coverage only. - Client records and timestamps alone never establish a DP transaction or cross-side cause. diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index daca035ed..da311a37b 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -33,6 +33,7 @@ const EXACT_PROFILE: &str = "dp-server-5.00.test-v1"; const EXACT_SOURCE_VERSION: &str = "5.00.TEST.0001"; const EXACT_SITE: &str = "LAB"; const EXACT_DP: &str = "safe:dp:lab-dp-01"; +const EXACT_DP_02: &str = "safe:dp:lab-dp-02"; const EXACT_SITE_SERVER: &str = "safe:server:lab-pri-01"; const EXACT_CLIENT: &str = "safe:client:lab-client-01"; @@ -55,6 +56,20 @@ fn required_string<'a>(value: &'a Value, field: &str, context: &str) -> Result<& .ok_or_else(|| format!("{context}.{field} must be a string")) } +fn required_nonempty_string<'a>( + value: &'a Value, + field: &str, + context: &str, +) -> Result<&'a str, String> { + required_string(value, field, context).and_then(|candidate| { + if candidate.is_empty() { + Err(format!("{context}.{field} must not be empty")) + } else { + Ok(candidate) + } + }) +} + fn required_array<'a>(value: &'a Value, field: &str, context: &str) -> Result<&'a [Value], String> { value[field] .as_array() @@ -260,10 +275,43 @@ fn source_path_is_bounded(relative_path: &str, basename: &str) -> bool { .is_some_and(|candidate| candidate == basename) } -fn sanitized_source_path_is_bounded(source_path: &str) -> bool { - source_path - .strip_prefix("SYNTHETIC://") - .is_some_and(segmented_path_is_safe) +fn sanitized_source_path_is_bounded(source_path: &str, basename: &str, rotation: &Value) -> bool { + let Some(suffix) = source_path.strip_prefix("SYNTHETIC://") else { + return false; + }; + if !segmented_path_is_safe(suffix) { + return false; + } + let segments = suffix.split('/').collect::>(); + if segments.len() != 3 + || !matches!( + segments[0], + "client-control" + | "default-dp-root" + | "default-site-root" + | "dp-02-root" + | "dp-root" + | "site-root" + ) + || segments[1] != "Logs" + { + return false; + } + + let expected_basename = match rotation["kind"].as_str() { + Some("current") => Some(basename.to_owned()), + Some("lo_") => basename + .strip_suffix(".log") + .map(|stem| format!("{stem}.lo_")), + Some("numbered") => rotation["value"] + .as_u64() + .map(|value| format!("{basename}.{value}")), + Some("timestamped") => rotation["value"] + .as_str() + .map(|value| format!("{basename}.{value}")), + _ => None, + }; + expected_basename.is_some_and(|expected| segments[2].eq_ignore_ascii_case(&expected)) } fn path_fingerprint_is_safe(path_fingerprint: &str) -> bool { @@ -326,16 +374,30 @@ fn validate_manifest( { failures.push("manifest topology is not the exact synthetic LAB DP".to_owned()); } - let mut distribution_point_handles = manifest["topology"]["distributionPointHandles"] - .as_array() - .map(|values| { - values - .iter() - .filter_map(Value::as_str) - .map(str::to_owned) - .collect::>() - }) - .unwrap_or_else(|| vec![EXACT_DP.to_owned()]); + let mut distribution_point_handles = Vec::new(); + match manifest["topology"].get("distributionPointHandles") { + None => distribution_point_handles.push(EXACT_DP.to_owned()), + Some(value) => match value.as_array() { + Some(values) => { + for value in values { + match value.as_str() { + Some(handle) + if !handle.is_empty() && matches!(handle, EXACT_DP | EXACT_DP_02) => + { + distribution_point_handles.push(handle.to_owned()); + } + Some(handle) => failures.push(format!( + "distributionPointHandles contains unknown handle {handle}" + )), + None => failures.push( + "distributionPointHandles entries must be nonempty strings".to_owned(), + ), + } + } + } + None => failures.push("distributionPointHandles must be an array".to_owned()), + }, + } let original_handle_order = distribution_point_handles.clone(); distribution_point_handles.sort(); distribution_point_handles.dedup(); @@ -538,19 +600,44 @@ fn validate_manifest( "{artifact_id} DP producer does not match its exact workflow subject" )); } + let rotation_kind = artifact["rotation"]["kind"].as_str(); + let rotation_lineage = + match required_nonempty_string(&artifact["rotation"], "lineageId", &context) { + Ok(value) => value.to_owned(), + Err(error) => { + failures.push(error); + String::new() + } + }; + let physical_capture = matches!(state, "captured" | "capped" | "parseFailed"); + let fragment_complete = if physical_capture { + match required_bool(&artifact["rotation"], "fragmentComplete", &context) { + Ok(value) => Some(value), + Err(error) => { + failures.push(error); + None + } + } + } else { + None + }; let path_fingerprint = artifact["pathFingerprint"].as_str(); let sanitized_source_path = artifact["sanitizedSourcePath"].as_str(); if !path_fingerprint.is_some_and(path_fingerprint_is_safe) - || !sanitized_source_path.is_some_and(sanitized_source_path_is_bounded) + || !sanitized_source_path.is_some_and(|value| { + sanitized_source_path_is_bounded(value, basename, &artifact["rotation"]) + }) { failures.push(format!("{artifact_id} leaks or omits path provenance")); } - if path_fingerprint.is_some_and(|value| !path_fingerprints.insert(value.to_owned())) { + if path_fingerprint + .map(str::to_ascii_lowercase) + .is_some_and(|value| !path_fingerprints.insert(value)) + { failures.push(format!( "{artifact_id} duplicates another physical path fingerprint" )); } - let rotation_kind = artifact["rotation"]["kind"].as_str(); let rotation_value = artifact["rotation"]["value"] .as_str() .map(str::to_owned) @@ -567,10 +654,10 @@ fn validate_manifest( ) { let physical_identity = ( producer.to_owned(), - source_path.to_owned(), - basename.to_owned(), + source_path.to_ascii_lowercase(), + basename.to_ascii_lowercase(), rotation_kind.to_owned(), - rotation_value, + rotation_value.to_ascii_lowercase(), ); if !physical_source_identities.insert(physical_identity) { failures.push(format!( @@ -608,7 +695,7 @@ fn validate_manifest( } }; - if matches!(state, "captured" | "capped" | "parseFailed") { + if physical_capture { let relative_path = match required_string(artifact, "relativePath", &context) { Ok(value) => value, Err(error) => { @@ -621,7 +708,7 @@ fn validate_manifest( "{artifact_id} has an unsafe or mismatched evidence path" )); } - if !relative_paths.insert(relative_path.to_owned()) { + if !relative_paths.insert(relative_path.to_ascii_lowercase()) { failures.push(format!( "{artifact_id} collides with another physical evidence destination" )); @@ -678,7 +765,7 @@ fn validate_manifest( encoding: artifact["encoding"].as_str().map(str::to_owned), }; let normalized = normalize_ccm_artifact(artifact_model, &content); - if artifact["rotation"]["fragmentComplete"] == false && !normalized.is_empty() { + if fragment_complete == Some(false) && !normalized.is_empty() { failures.push(format!( "{artifact_id} exposes a logical record from an incomplete rotation fragment" )); @@ -785,11 +872,8 @@ fn validate_manifest( .as_str() .unwrap_or_default() .to_owned(), - rotation_lineage: artifact["rotation"]["lineageId"] - .as_str() - .unwrap_or_default() - .to_owned(), - fragment_complete: artifact["rotation"]["fragmentComplete"].as_bool(), + rotation_lineage, + fragment_complete, }, ) .is_some() @@ -1020,6 +1104,8 @@ fn validate_expected( } let mut seen_transaction_ids = BTreeSet::new(); + let mut seen_observation_ids = BTreeSet::new(); + let mut consumed_evidence = BTreeSet::new(); for transaction in transactions { let transaction_id = match required_string(transaction, "transactionId", "transaction") { Ok(value) => value, @@ -1118,17 +1204,15 @@ fn validate_expected( let mut terminal_deferred = false; let mut previous_utc = i64::MIN; let mut previous_phase = 0usize; - let mut seen_observation_ids = BTreeSet::new(); - let mut consumed_evidence = BTreeSet::new(); for observation in observations { - let observation_id = match required_string(observation, "observationId", transaction_id) - { - Ok(value) => value, - Err(error) => { - failures.push(error); - continue; - } - }; + let observation_id = + match required_nonempty_string(observation, "observationId", transaction_id) { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; if !seen_observation_ids.insert(observation_id) { failures.push(format!( "{transaction_id} contains duplicate observationId {observation_id}" @@ -1296,14 +1380,30 @@ fn validate_expected( )), } - let gap_ids = transaction["coverageGapArtifactIds"] - .as_array() - .map(|values| values.iter().filter_map(Value::as_str).collect::>()) - .unwrap_or_default(); + let mut gap_ids = Vec::new(); + match required_array(transaction, "coverageGapArtifactIds", transaction_id) { + Ok(values) => { + for value in values { + match value.as_str() { + Some(artifact_id) if !artifact_id.is_empty() => gap_ids.push(artifact_id), + Some(_) => failures.push(format!( + "{transaction_id} coverage gap artifact ID must not be empty" + )), + None => failures.push(format!( + "{transaction_id} coverage gap artifact IDs must be strings" + )), + } + } + } + Err(error) => failures.push(error), + } let mut sorted_gap_ids = gap_ids.clone(); sorted_gap_ids.sort_unstable(); + sorted_gap_ids.dedup(); if gap_ids != sorted_gap_ids { - failures.push(format!("{transaction_id} coverage gaps are not sorted")); + failures.push(format!( + "{transaction_id} coverage gaps must be sorted and unique" + )); } for artifact_id in gap_ids { match parsed.artifacts.get(artifact_id) { @@ -1348,9 +1448,22 @@ fn validate_expected( failures.push("source-local observations are not deterministically sorted".to_owned()); } for observation in source_local { - let observation_id = - required_string(observation, "observationId", "sourceLocalObservation") - .unwrap_or("invalid"); + let observation_id = match required_nonempty_string( + observation, + "observationId", + "sourceLocalObservation", + ) { + Ok(value) => value, + Err(error) => { + failures.push(error); + continue; + } + }; + if !seen_observation_ids.insert(observation_id) { + failures.push(format!( + "source-local observations contain duplicate observationId {observation_id}" + )); + } reject_unknown_fields( observation, &[ @@ -1424,6 +1537,13 @@ fn validate_expected( )); } } + if evidence_reference_key(reference, observation_id) + .is_ok_and(|key| !consumed_evidence.insert(key)) + { + failures.push(format!( + "{observation_id} consumes one physical evidence reference more than once" + )); + } if let Err(error) = evidence_for(parsed, reference, observation_id) { failures.push(error); } @@ -2133,3 +2253,222 @@ fn transaction_observation_ids_and_evidence_are_single_use() { "duplicate observations or reused evidence were accepted: {accepted:?}" ); } + +#[test] +fn physical_path_provenance_is_case_folded_bounded_and_basename_bound() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut case_folded_fingerprint = healthy_manifest.clone(); + case_folded_fingerprint["artifacts"][1]["pathFingerprint"] = json!("synthetic:HEALTHY-DISTMGR"); + if mutation_was_accepted( + "healthy-package", + &case_folded_fingerprint, + &healthy_expected, + ) { + accepted.push("case-folded duplicate path fingerprint"); + } + + let mut basename_detached = healthy_manifest.clone(); + basename_detached["artifacts"][1]["sanitizedSourcePath"] = + basename_detached["artifacts"][0]["sanitizedSourcePath"].clone(); + if mutation_was_accepted("healthy-package", &basename_detached, &healthy_expected) { + accepted.push("sanitized source path detached from its original basename"); + } + + let mut identity_bearing_path = healthy_manifest.clone(); + identity_bearing_path["artifacts"][2]["sanitizedSourcePath"] = + json!("SYNTHETIC://Users/RealUser/SMSDPProv.log"); + if mutation_was_accepted("healthy-package", &identity_bearing_path, &healthy_expected) { + accepted.push("identity-bearing sanitized source root"); + } + + assert!( + accepted.is_empty(), + "unbounded or colliding physical path provenance was accepted: {accepted:?}" + ); +} + +#[test] +fn distribution_point_handles_are_typed_known_unique_and_complete() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut non_string_handle = healthy_manifest.clone(); + non_string_handle["topology"]["distributionPointHandles"] = json!([EXACT_DP, 7]); + if mutation_was_accepted("healthy-package", &non_string_handle, &healthy_expected) { + accepted.push("non-string distribution-point topology handle"); + } + + let mut unknown_handle = healthy_manifest.clone(); + unknown_handle["topology"]["distributionPointHandles"] = json!([EXACT_DP, "safe:dp:lab-dp-99"]); + if mutation_was_accepted("healthy-package", &unknown_handle, &healthy_expected) { + accepted.push("unknown distribution-point topology handle"); + } + + let mut duplicate_handle = healthy_manifest.clone(); + duplicate_handle["topology"]["distributionPointHandles"] = json!([EXACT_DP, EXACT_DP]); + if mutation_was_accepted("healthy-package", &duplicate_handle, &healthy_expected) { + accepted.push("duplicate distribution-point topology handle"); + } + + let mut missing_primary = healthy_manifest.clone(); + missing_primary["topology"]["distributionPointHandles"] = json!(["safe:dp:lab-dp-02"]); + if mutation_was_accepted("healthy-package", &missing_primary, &healthy_expected) { + accepted.push("distribution-point topology omitted its primary handle"); + } + + assert!( + accepted.is_empty(), + "malformed distribution-point topology handles were accepted: {accepted:?}" + ); +} + +#[test] +fn physical_rotation_provenance_is_typed_and_complete() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut non_boolean_fragment = healthy_manifest.clone(); + non_boolean_fragment["artifacts"][0]["rotation"]["fragmentComplete"] = json!("true"); + if mutation_was_accepted("healthy-package", &non_boolean_fragment, &healthy_expected) { + accepted.push("non-boolean physical fragment completeness"); + } + + let mut non_string_lineage = healthy_manifest.clone(); + non_string_lineage["artifacts"][0]["rotation"]["lineageId"] = json!(7); + if mutation_was_accepted("healthy-package", &non_string_lineage, &healthy_expected) { + accepted.push("non-string rotation lineage"); + } + + let mut missing_lineage = healthy_manifest.clone(); + missing_lineage["artifacts"][0]["rotation"] + .as_object_mut() + .expect("rotation is an object") + .remove("lineageId"); + if mutation_was_accepted("healthy-package", &missing_lineage, &healthy_expected) { + accepted.push("missing rotation lineage"); + } + + let mut missing_fragment = healthy_manifest.clone(); + missing_fragment["artifacts"][0]["rotation"] + .as_object_mut() + .expect("rotation is an object") + .remove("fragmentComplete"); + if mutation_was_accepted("healthy-package", &missing_fragment, &healthy_expected) { + accepted.push("missing physical fragment completeness"); + } + + assert_eq!( + rotation_from_manifest(&json!({"kind": "numbered", "value": 3})) + .expect("canonical numbered rotation"), + SccmRotation::Numbered(3) + ); + assert_eq!( + rotation_from_manifest(&json!({"kind": "timestamped", "value": "20260730-150000"})) + .expect("canonical timestamped rotation"), + SccmRotation::Timestamped("20260730-150000".to_owned()) + ); + + assert!( + accepted.is_empty(), + "malformed or incomplete rotation provenance was accepted: {accepted:?}" + ); +} + +#[test] +fn observation_ids_and_physical_evidence_are_unique_across_classes() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let rotation_manifest = + read_json("rotation-boundary", "manifest.json").expect("manifest loads"); + let rotation_expected = + read_json("rotation-boundary", "expected.json").expect("expected loads"); + let client_manifest = + read_json("client-only-looking-request", "manifest.json").expect("manifest loads"); + let client_expected = + read_json("client-only-looking-request", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut empty_transaction_observation = healthy_expected.clone(); + empty_transaction_observation["transactions"][0]["observations"][0]["observationId"] = + json!(""); + if mutation_was_accepted( + "healthy-package", + &healthy_manifest, + &empty_transaction_observation, + ) { + accepted.push("empty transaction observation ID"); + } + + let mut duplicate_source_local_id = rotation_expected.clone(); + duplicate_source_local_id["sourceLocalObservations"][1]["observationId"] = + duplicate_source_local_id["sourceLocalObservations"][0]["observationId"].clone(); + if mutation_was_accepted( + "rotation-boundary", + &rotation_manifest, + &duplicate_source_local_id, + ) { + accepted.push("duplicate source-local observation ID"); + } + + let mut reused_source_local_evidence = client_expected.clone(); + let repeated = + reused_source_local_evidence["sourceLocalObservations"][0]["evidence"][0].clone(); + reused_source_local_evidence["sourceLocalObservations"][0]["evidence"] + .as_array_mut() + .expect("source-local evidence is an array") + .push(repeated); + if mutation_was_accepted( + "client-only-looking-request", + &client_manifest, + &reused_source_local_evidence, + ) { + accepted.push("source-local physical evidence consumed twice"); + } + + assert!( + accepted.is_empty(), + "observation identity or evidence single-use violations were accepted: {accepted:?}" + ); +} + +#[test] +fn coverage_gap_ids_are_typed_nonempty_unique_and_physical() { + let incomplete_manifest = read_json("incomplete", "manifest.json").expect("manifest loads"); + let incomplete_expected = read_json("incomplete", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut non_string_gap = incomplete_expected.clone(); + non_string_gap["transactions"][0]["coverageGapArtifactIds"] + .as_array_mut() + .expect("coverage gaps are an array") + .push(json!(7)); + if mutation_was_accepted("incomplete", &incomplete_manifest, &non_string_gap) { + accepted.push("non-string coverage-gap artifact ID"); + } + + let mut empty_gap = incomplete_expected.clone(); + empty_gap["transactions"][0]["coverageGapArtifactIds"][0] = json!(""); + if mutation_was_accepted("incomplete", &incomplete_manifest, &empty_gap) { + accepted.push("empty coverage-gap artifact ID"); + } + + let mut duplicate_gap = incomplete_expected.clone(); + let repeated = duplicate_gap["transactions"][0]["coverageGapArtifactIds"][1].clone(); + duplicate_gap["transactions"][0]["coverageGapArtifactIds"] + .as_array_mut() + .expect("coverage gaps are an array") + .push(repeated); + if mutation_was_accepted("incomplete", &incomplete_manifest, &duplicate_gap) { + accepted.push("duplicate coverage-gap artifact ID"); + } + + assert!( + accepted.is_empty(), + "malformed coverage-gap artifact IDs were accepted: {accepted:?}" + ); +} diff --git a/docs/sccm/preparation/issue-329-distribution-point-corpus.md b/docs/sccm/preparation/issue-329-distribution-point-corpus.md index 42f181215..d398e27c6 100644 --- a/docs/sccm/preparation/issue-329-distribution-point-corpus.md +++ b/docs/sccm/preparation/issue-329-distribution-point-corpus.md @@ -49,8 +49,11 @@ Each manifest preserves: either an exact handle or the bounded `manifestTopology` basis used by one site-server file that contains records for multiple declared DPs; - source ID, exact basename, source grammar, synthetic version, path - fingerprint, and `SYNTHETIC://` provenance; -- rotation kind, lineage, and fragment completeness; + fingerprint, and `SYNTHETIC://` provenance; path identities are compared + with Windows case-folding, and sanitized paths must use a declared + synthetic root plus the rotation-correct basename; +- rotation kind, nonempty typed lineage, and typed fragment completeness for + physical captures; - capture state, collection timestamp, encoding, byte policy, exact copied byte count, and bounded relative evidence path; and - deterministic artifact identity and ordering. @@ -61,6 +64,8 @@ contain records for both. A physical site-server source is captured once; changing its path fingerprint or destination cannot duplicate it merely to attach another workflow-subject handle. Each admitted logical record must carry an exact DP handle from the bounded manifest topology. +An explicit multi-DP handle array is parsed element by element; malformed, +unknown, duplicate, or primary-omitting entries cannot be projected away. ## State and exact-key contract @@ -93,8 +98,9 @@ case aliases, a changed version, or a changed DP handle cannot satisfy an exact transaction. Observation order uses the additive normalized SCCM timestamp provenance, not the legacy public `LogEntry.timezone_offset`. Evidence later than the canonical bundle capture is rejected. Observation IDs -are unique within a transaction, and one physical -`(artifactId, startLine, endLine)` reference can be consumed only once. +are nonempty and unique across transaction and source-local output classes, +and one physical `(artifactId, startLine, endLine)` reference can be consumed +only once across the scenario. The outcome rules are conservative: @@ -113,6 +119,8 @@ The outcome rules are conservative: `captured`, `absent`, `accessDenied`, `capped`, `skipped`, `unsupported`, and `parseFailed` remain distinct physical manifest states. The expected coverage array is an exact, sorted projection of physical artifact IDs and states. +Coverage-gap artifact IDs are nonempty typed strings, sorted, unique, and +bound to a declared non-complete physical artifact. Artifact requests contain only a catalogued source ID and one versioned reason code: From 79e6c5a33450a3a9b6fb006d9726ac95496544e2 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 02:21:52 -0400 Subject: [PATCH 06/20] test(sccm): require explicit DP topology arrays --- .../absent-dp/manifest.json | 2 +- .../client-only-looking-request/manifest.json | 2 +- .../distribution-failure/manifest.json | 2 +- .../healthy-package/manifest.json | 1 + .../incomplete/manifest.json | 2 +- .../rotation-boundary/manifest.json | 2 +- .../serve-observed/manifest.json | 2 +- .../transfer-retry/manifest.json | 2 +- .../validation-failure/manifest.json | 2 +- ...ver_distribution_point_fixture_contract.rs | 48 +++++++++++-------- .../issue-329-distribution-point-corpus.md | 5 +- 11 files changed, 41 insertions(+), 29 deletions(-) diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/manifest.json index e4d1cc788..f4ab8b390 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/manifest.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/absent-dp/manifest.json @@ -3,7 +3,7 @@ "proposalOnly": true, "syntheticFixture": true, "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, - "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "distributionPointHandles": ["safe:dp:lab-dp-01"], "rolesObserved": ["distributionPoint", "siteServer"]}, "artifacts": [ { "artifactId": "dp-absent-01-distmgr", diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/manifest.json index 33055c5f4..fab490129 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/manifest.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/client-only-looking-request/manifest.json @@ -3,7 +3,7 @@ "proposalOnly": true, "syntheticFixture": true, "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, - "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["siteServer"]}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "distributionPointHandles": ["safe:dp:lab-dp-01"], "rolesObserved": ["siteServer"]}, "artifacts": [ { "artifactId": "dp-client-control-01-data-transfer", diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/manifest.json index 5ad7144f4..779a87c46 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/manifest.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/distribution-failure/manifest.json @@ -3,7 +3,7 @@ "proposalOnly": true, "syntheticFixture": true, "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, - "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "distributionPointHandles": ["safe:dp:lab-dp-01"], "rolesObserved": ["distributionPoint", "siteServer"]}, "artifacts": [ { "artifactId": "dp-distribution-failure-01-distmgr", diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/manifest.json index 8f6a931e5..03efabfb5 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/manifest.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/healthy-package/manifest.json @@ -10,6 +10,7 @@ "topology": { "siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", + "distributionPointHandles": ["safe:dp:lab-dp-01"], "rolesObserved": ["distributionPoint", "siteServer"] }, "artifacts": [ diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/manifest.json index ae905b3aa..47872848c 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/manifest.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/incomplete/manifest.json @@ -3,7 +3,7 @@ "proposalOnly": true, "syntheticFixture": true, "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, - "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "distributionPointHandles": ["safe:dp:lab-dp-01"], "rolesObserved": ["distributionPoint", "siteServer"]}, "artifacts": [ { "artifactId": "dp-incomplete-01-distmgr", diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/manifest.json index 844006abb..a4ad922e6 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/manifest.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/manifest.json @@ -3,7 +3,7 @@ "proposalOnly": true, "syntheticFixture": true, "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, - "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "distributionPointHandles": ["safe:dp:lab-dp-01"], "rolesObserved": ["distributionPoint", "siteServer"]}, "artifacts": [ { "artifactId": "dp-rotation-01-current-fragment", diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/manifest.json index 494aea4a1..3edd339dd 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/manifest.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/serve-observed/manifest.json @@ -3,7 +3,7 @@ "proposalOnly": true, "syntheticFixture": true, "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, - "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "distributionPointHandles": ["safe:dp:lab-dp-01"], "rolesObserved": ["distributionPoint", "siteServer"]}, "artifacts": [ { "artifactId": "dp-serve-01-distmgr", diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/manifest.json index f7773f185..923f68992 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/manifest.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/transfer-retry/manifest.json @@ -3,7 +3,7 @@ "proposalOnly": true, "syntheticFixture": true, "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, - "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "distributionPointHandles": ["safe:dp:lab-dp-01"], "rolesObserved": ["distributionPoint", "siteServer"]}, "artifacts": [ { "artifactId": "dp-transfer-retry-01-distmgr", diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/manifest.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/manifest.json index a69dd593e..4be8dc67c 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/manifest.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/validation-failure/manifest.json @@ -3,7 +3,7 @@ "proposalOnly": true, "syntheticFixture": true, "bundle": {"bundleRole": "server", "workflow": "distributionPoint", "capturedUtc": "2026-07-30T12:30:00Z"}, - "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "rolesObserved": ["distributionPoint", "siteServer"]}, + "topology": {"siteCode": "LAB", "distributionPointHandle": "safe:dp:lab-dp-01", "distributionPointHandles": ["safe:dp:lab-dp-01"], "rolesObserved": ["distributionPoint", "siteServer"]}, "artifacts": [ { "artifactId": "dp-validation-failure-01-distmgr", diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index da311a37b..9cbfc1181 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -375,28 +375,29 @@ fn validate_manifest( failures.push("manifest topology is not the exact synthetic LAB DP".to_owned()); } let mut distribution_point_handles = Vec::new(); - match manifest["topology"].get("distributionPointHandles") { - None => distribution_point_handles.push(EXACT_DP.to_owned()), - Some(value) => match value.as_array() { - Some(values) => { - for value in values { - match value.as_str() { - Some(handle) - if !handle.is_empty() && matches!(handle, EXACT_DP | EXACT_DP_02) => - { - distribution_point_handles.push(handle.to_owned()); - } - Some(handle) => failures.push(format!( - "distributionPointHandles contains unknown handle {handle}" - )), - None => failures.push( - "distributionPointHandles entries must be nonempty strings".to_owned(), - ), + match required_array( + &manifest["topology"], + "distributionPointHandles", + "topology", + ) { + Ok(values) => { + for value in values { + match value.as_str() { + Some(handle) + if !handle.is_empty() && matches!(handle, EXACT_DP | EXACT_DP_02) => + { + distribution_point_handles.push(handle.to_owned()); } + Some(handle) => failures.push(format!( + "distributionPointHandles contains unknown handle {handle}" + )), + None => failures.push( + "distributionPointHandles entries must be nonempty strings".to_owned(), + ), } } - None => failures.push("distributionPointHandles must be an array".to_owned()), - }, + } + Err(error) => failures.push(error), } let original_handle_order = distribution_point_handles.clone(); distribution_point_handles.sort(); @@ -2320,6 +2321,15 @@ fn distribution_point_handles_are_typed_known_unique_and_complete() { accepted.push("distribution-point topology omitted its primary handle"); } + let mut missing_handle_array = healthy_manifest.clone(); + missing_handle_array["topology"] + .as_object_mut() + .expect("topology is an object") + .remove("distributionPointHandles"); + if mutation_was_accepted("healthy-package", &missing_handle_array, &healthy_expected) { + accepted.push("distribution-point topology omitted its exact handle array"); + } + assert!( accepted.is_empty(), "malformed distribution-point topology handles were accepted: {accepted:?}" diff --git a/docs/sccm/preparation/issue-329-distribution-point-corpus.md b/docs/sccm/preparation/issue-329-distribution-point-corpus.md index d398e27c6..5ed8256aa 100644 --- a/docs/sccm/preparation/issue-329-distribution-point-corpus.md +++ b/docs/sccm/preparation/issue-329-distribution-point-corpus.md @@ -64,8 +64,9 @@ contain records for both. A physical site-server source is captured once; changing its path fingerprint or destination cannot duplicate it merely to attach another workflow-subject handle. Each admitted logical record must carry an exact DP handle from the bounded manifest topology. -An explicit multi-DP handle array is parsed element by element; malformed, -unknown, duplicate, or primary-omitting entries cannot be projected away. +Every manifest carries an explicit DP handle array parsed element by element; +missing, malformed, unknown, duplicate, or primary-omitting entries cannot be +projected away. ## State and exact-key contract From b22974170517ec62761a68868f42c34312f612a1 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 02:30:04 -0400 Subject: [PATCH 07/20] test(sccm): reject malformed source-local artifact ids --- ...ver_distribution_point_fixture_contract.rs | 98 ++++++++++++++++++- .../issue-329-distribution-point-corpus.md | 4 +- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 9cbfc1181..0c16214cc 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -1492,10 +1492,25 @@ fn validate_expected( "{observation_id} is not an explicitly noncorrelatable source-local observation" )); } - let artifact_ids = observation["artifactIds"] - .as_array() - .map(|values| values.iter().filter_map(Value::as_str).collect::>()) - .unwrap_or_default(); + let mut artifact_ids = Vec::new(); + match required_array(observation, "artifactIds", observation_id) { + Ok(values) => { + for value in values { + match value.as_str() { + Some(artifact_id) if !artifact_id.is_empty() => { + artifact_ids.push(artifact_id); + } + Some(_) => { + failures.push(format!("{observation_id} artifact ID must not be empty")) + } + None => { + failures.push(format!("{observation_id} artifact IDs must be strings")) + } + } + } + } + Err(error) => failures.push(error), + } let mut sorted_artifact_ids = artifact_ids.clone(); sorted_artifact_ids.sort_unstable(); let unique_artifact_ids = artifact_ids.iter().copied().collect::>(); @@ -2446,6 +2461,81 @@ fn observation_ids_and_physical_evidence_are_unique_across_classes() { ); } +#[test] +fn source_local_artifact_ids_are_strict_strings_across_classifications() { + let client_manifest = + read_json("client-only-looking-request", "manifest.json").expect("manifest loads"); + let client_expected = + read_json("client-only-looking-request", "expected.json").expect("expected loads"); + let rotation_manifest = + read_json("rotation-boundary", "manifest.json").expect("manifest loads"); + let rotation_expected = + read_json("rotation-boundary", "expected.json").expect("expected loads"); + let surfaces = [ + ( + "ignoredClientEvidence", + "client-only-looking-request", + &client_manifest, + &client_expected, + 0usize, + ), + ( + "rotationSplit", + "rotation-boundary", + &rotation_manifest, + &rotation_expected, + 0usize, + ), + ( + "malformedEvidence", + "rotation-boundary", + &rotation_manifest, + &rotation_expected, + 1usize, + ), + ]; + let invalid_entries = [ + ("numeric", json!(7)), + ("null", Value::Null), + ("boolean", json!(true)), + ("object", json!({"unexpected": "value"})), + ("empty string", json!("")), + ]; + let mut accepted = Vec::new(); + + for (surface, scenario, manifest, expected, observation_index) in surfaces { + for (shape, invalid_entry) in &invalid_entries { + let mut mutated = expected.clone(); + mutated["sourceLocalObservations"][observation_index]["artifactIds"] + .as_array_mut() + .expect("source-local artifact IDs are an array") + .push(invalid_entry.clone()); + if mutation_was_accepted(scenario, manifest, &mutated) { + accepted.push(format!("{surface} accepted appended {shape} artifact ID")); + } + } + + let mut mixed_array = expected.clone(); + mixed_array["sourceLocalObservations"][observation_index]["artifactIds"] + .as_array_mut() + .expect("source-local artifact IDs are an array") + .extend([ + json!(7), + Value::Null, + json!(true), + json!({"unexpected": "value"}), + ]); + if mutation_was_accepted(scenario, manifest, &mixed_array) { + accepted.push(format!("{surface} accepted a mixed-type artifact ID array")); + } + } + + assert!( + accepted.is_empty(), + "malformed source-local physical artifact IDs were accepted: {accepted:?}" + ); +} + #[test] fn coverage_gap_ids_are_typed_nonempty_unique_and_physical() { let incomplete_manifest = read_json("incomplete", "manifest.json").expect("manifest loads"); diff --git a/docs/sccm/preparation/issue-329-distribution-point-corpus.md b/docs/sccm/preparation/issue-329-distribution-point-corpus.md index 5ed8256aa..42169ec4a 100644 --- a/docs/sccm/preparation/issue-329-distribution-point-corpus.md +++ b/docs/sccm/preparation/issue-329-distribution-point-corpus.md @@ -112,7 +112,9 @@ The outcome rules are conservative: IDs and a bounded source ID; - rotation fragments and malformed evidence remain noncorrelatable source-local observations whose classification is bound to exact physical - role, capture state, lineage, rotation kind, and fragment completeness; and + role, capture state, lineage, rotation kind, and fragment completeness; + every source-local artifact ID is a nonempty typed string bound to that + physical manifest; and - a client-only download record cannot become a DP transaction or DP failure. ## Coverage and request contract From dfe32e94a79b55decb2be21dc92dcb79bc52206e Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 02:51:53 -0400 Subject: [PATCH 08/20] test(sccm): bind source-local evidence to physical lines --- .../sccm/server/distribution_point/README.md | 3 + .../rotation-boundary/expected.json | 4 +- ...ver_distribution_point_fixture_contract.rs | 202 +++++++++++++++++- .../issue-329-distribution-point-corpus.md | 5 +- 4 files changed, 200 insertions(+), 14 deletions(-) diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md index 634194a3a..f6a9d16e3 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/README.md @@ -19,6 +19,9 @@ exact DP/content/version identity from each normalized logical CCM record. - Rotation lineage/fragment fields, observation IDs, evidence references, and coverage-gap IDs fail closed on malformed, empty, duplicate, or reused values. +- Every source-local observation cites a nonempty closed array of exact raw + physical artifact/line ranges. Citing a fragment or malformed raw line does + not promote it to a logical transaction or make it correlation-eligible. - Missing, denied, malformed, capped, or split evidence is coverage only. - Client records and timestamps alone never establish a DP transaction or cross-side cause. diff --git a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/expected.json b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/expected.json index 1afd42488..0c081832e 100644 --- a/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/expected.json +++ b/crates/cmtraceopen-parser/tests/fixtures/sccm/server/distribution_point/rotation-boundary/expected.json @@ -13,8 +13,8 @@ ], "transactions": [], "sourceLocalObservations": [ - {"observationId": "rotation-01-split", "classification": "rotationSplit", "confidence": "low", "confidenceCeiling": "low", "correlationEligible": false, "artifactIds": ["dp-rotation-01-current-fragment", "dp-rotation-02-lo-fragment"], "evidence": []}, - {"observationId": "rotation-02-malformed", "classification": "malformedEvidence", "confidence": "low", "confidenceCeiling": "low", "correlationEligible": false, "artifactIds": ["dp-rotation-03-malformed"], "evidence": []} + {"observationId": "rotation-01-split", "classification": "rotationSplit", "confidence": "low", "confidenceCeiling": "low", "correlationEligible": false, "artifactIds": ["dp-rotation-01-current-fragment", "dp-rotation-02-lo-fragment"], "evidence": [{"artifactId": "dp-rotation-01-current-fragment", "startLine": 1, "endLine": 1}, {"artifactId": "dp-rotation-02-lo-fragment", "startLine": 1, "endLine": 1}]}, + {"observationId": "rotation-02-malformed", "classification": "malformedEvidence", "confidence": "low", "confidenceCeiling": "low", "correlationEligible": false, "artifactIds": ["dp-rotation-03-malformed"], "evidence": [{"artifactId": "dp-rotation-03-malformed", "startLine": 1, "endLine": 1}]} ], "artifactRequests": [ {"sourceId": "server-dp-distribution", "reasonCode": "coverageMalformed"}, diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 0c16214cc..f8fc009b7 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -204,6 +204,7 @@ struct ParsedArtifact { struct ParsedScenario { artifacts: BTreeMap, evidence: BTreeMap<(String, u32, u32), SccmEvidence>, + physical_evidence: BTreeSet<(String, u32, u32)>, distribution_point_handles: BTreeSet, } @@ -479,6 +480,7 @@ fn validate_manifest( let mut parsed_artifacts = BTreeMap::new(); let mut evidence_by_reference = BTreeMap::new(); + let mut physical_evidence_by_reference = BTreeSet::new(); let mut relative_paths = BTreeSet::new(); let mut physical_source_identities = BTreeSet::new(); let mut path_fingerprints = BTreeSet::new(); @@ -744,11 +746,24 @@ fn validate_manifest( "{artifact_id} has incoherent raw-byte collection-limit provenance" )); } - if !String::from_utf8_lossy(&bytes).contains("SYNTHETIC FIXTURE") { + let content = String::from_utf8_lossy(&bytes); + if !content.contains("SYNTHETIC FIXTURE") { failures.push(format!("{artifact_id} lacks a synthetic fixture marker")); } + for (line_index, _) in content.lines().enumerate() { + let Ok(line_number) = u32::try_from(line_index + 1) else { + failures.push(format!( + "{artifact_id} has more physical lines than an evidence reference can address" + )); + break; + }; + physical_evidence_by_reference.insert(( + artifact_id.to_owned(), + line_number, + line_number, + )); + } if matches!(state, "captured" | "capped") { - let content = String::from_utf8_lossy(&bytes); let artifact_model = SccmArtifact { artifact_id: artifact_id.to_owned(), display_name: basename.to_owned(), @@ -887,6 +902,7 @@ fn validate_manifest( Ok(ParsedScenario { artifacts: parsed_artifacts, evidence: evidence_by_reference, + physical_evidence: physical_evidence_by_reference, distribution_point_handles, }) } else { @@ -908,6 +924,22 @@ fn evidence_for<'a>( }) } +fn physical_evidence_for( + parsed: &ParsedScenario, + reference: &Value, + context: &str, +) -> Result<(), String> { + let key = evidence_reference_key(reference, context)?; + if parsed.physical_evidence.contains(&key) { + Ok(()) + } else { + Err(format!( + "{context} does not cite an exact physical line: {}:{}-{}", + key.0, key.1, key.2 + )) + } +} + fn evidence_reference_key(reference: &Value, context: &str) -> Result<(String, u32, u32), String> { let artifact_id = required_string(reference, "artifactId", context)?; let line_start = reference["startLine"] @@ -1529,10 +1561,19 @@ fn validate_expected( )); } } - let references = observation["evidence"] - .as_array() - .map(Vec::as_slice) - .unwrap_or_default(); + let references = match required_array(observation, "evidence", observation_id) { + Ok(values) if !values.is_empty() => values, + Ok(_) => { + failures.push(format!( + "{observation_id} has no cited physical source-local evidence" + )); + &[] + } + Err(error) => { + failures.push(error); + &[] + } + }; let mut cited_artifact_ids = BTreeSet::new(); for reference in references { reject_unknown_fields( @@ -1560,7 +1601,7 @@ fn validate_expected( "{observation_id} consumes one physical evidence reference more than once" )); } - if let Err(error) = evidence_for(parsed, reference, observation_id) { + if let Err(error) = physical_evidence_for(parsed, reference, observation_id) { failures.push(error); } } @@ -1570,8 +1611,7 @@ fn validate_expected( .collect::>(); let semantic_match = match classification { Some("ignoredClientEvidence") => { - !references.is_empty() - && cited_artifact_ids == unique_artifact_ids + cited_artifact_ids == unique_artifact_ids && artifacts.iter().all(|artifact| { artifact.role == "client" && artifact.source_id == "client-content-control" @@ -1591,7 +1631,7 @@ fn validate_expected( .iter() .map(|artifact| artifact.rotation_kind.as_str()) .collect::>(); - references.is_empty() + cited_artifact_ids == unique_artifact_ids && artifacts.len() >= 2 && source_ids.len() == 1 && lineages.len() == 1 @@ -1604,7 +1644,7 @@ fn validate_expected( }) } Some("malformedEvidence") => { - references.is_empty() + cited_artifact_ids == unique_artifact_ids && !artifacts.is_empty() && artifacts.iter().all(|artifact| { artifact.role != "client" && artifact.state == "parseFailed" @@ -2536,6 +2576,146 @@ fn source_local_artifact_ids_are_strict_strings_across_classifications() { ); } +#[test] +fn source_local_evidence_is_typed_nonempty_closed_and_physical() { + let client_manifest = + read_json("client-only-looking-request", "manifest.json").expect("manifest loads"); + let client_expected = + read_json("client-only-looking-request", "expected.json").expect("expected loads"); + let rotation_manifest = + read_json("rotation-boundary", "manifest.json").expect("manifest loads"); + let rotation_expected = + read_json("rotation-boundary", "expected.json").expect("expected loads"); + let surfaces = [ + ( + "ignoredClientEvidence", + "client-only-looking-request", + &client_manifest, + &client_expected, + 0usize, + json!([{ + "artifactId": "dp-client-control-01-data-transfer", + "startLine": 1, + "endLine": 1 + }]), + ), + ( + "rotationSplit", + "rotation-boundary", + &rotation_manifest, + &rotation_expected, + 0usize, + json!([ + { + "artifactId": "dp-rotation-01-current-fragment", + "startLine": 1, + "endLine": 1 + }, + { + "artifactId": "dp-rotation-02-lo-fragment", + "startLine": 1, + "endLine": 1 + } + ]), + ), + ( + "malformedEvidence", + "rotation-boundary", + &rotation_manifest, + &rotation_expected, + 1usize, + json!([{ + "artifactId": "dp-rotation-03-malformed", + "startLine": 1, + "endLine": 1 + }]), + ), + ]; + let non_array_shapes = [ + ("null", Value::Null), + ("boolean", json!(true)), + ("numeric", json!(7)), + ("string", json!("not-an-evidence-array")), + ("object", json!({"unexpected": "value"})), + ]; + let mut accepted = Vec::new(); + + for (surface, scenario, manifest, expected, observation_index, valid_evidence) in surfaces { + for (shape, invalid_evidence) in &non_array_shapes { + let mut mutated = expected.clone(); + mutated["sourceLocalObservations"][observation_index]["evidence"] = + invalid_evidence.clone(); + if mutation_was_accepted(scenario, manifest, &mutated) { + accepted.push(format!("{surface} accepted {shape} evidence")); + } + } + + let mut empty = expected.clone(); + empty["sourceLocalObservations"][observation_index]["evidence"] = json!([]); + if mutation_was_accepted(scenario, manifest, &empty) { + accepted.push(format!("{surface} accepted an empty evidence array")); + } + + let mut mixed = expected.clone(); + let mut mixed_entries = valid_evidence + .as_array() + .expect("valid source-local evidence is an array") + .clone(); + mixed_entries.extend([ + Value::Null, + json!(true), + json!(7), + json!("not-an-evidence-reference"), + json!({"unexpected": "value"}), + ]); + mixed["sourceLocalObservations"][observation_index]["evidence"] = + Value::Array(mixed_entries); + if mutation_was_accepted(scenario, manifest, &mixed) { + accepted.push(format!("{surface} accepted a mixed evidence array")); + } + + let mut open_reference = expected.clone(); + open_reference["sourceLocalObservations"][observation_index]["evidence"] = + valid_evidence.clone(); + open_reference["sourceLocalObservations"][observation_index]["evidence"][0]["unexpected"] = + json!("value"); + if mutation_was_accepted(scenario, manifest, &open_reference) { + accepted.push(format!( + "{surface} accepted an open evidence-reference object" + )); + } + + let mut missing_line = expected.clone(); + missing_line["sourceLocalObservations"][observation_index]["evidence"] = + valid_evidence.clone(); + missing_line["sourceLocalObservations"][observation_index]["evidence"][0] + .as_object_mut() + .expect("evidence reference is an object") + .remove("endLine"); + if mutation_was_accepted(scenario, manifest, &missing_line) { + accepted.push(format!( + "{surface} accepted an incomplete evidence reference" + )); + } + + let mut unbound_line = expected.clone(); + unbound_line["sourceLocalObservations"][observation_index]["evidence"] = + valid_evidence.clone(); + unbound_line["sourceLocalObservations"][observation_index]["evidence"][0]["startLine"] = + json!(99); + unbound_line["sourceLocalObservations"][observation_index]["evidence"][0]["endLine"] = + json!(99); + if mutation_was_accepted(scenario, manifest, &unbound_line) { + accepted.push(format!("{surface} accepted an unbound physical line")); + } + } + + assert!( + accepted.is_empty(), + "malformed source-local evidence was accepted: {accepted:?}" + ); +} + #[test] fn coverage_gap_ids_are_typed_nonempty_unique_and_physical() { let incomplete_manifest = read_json("incomplete", "manifest.json").expect("manifest loads"); diff --git a/docs/sccm/preparation/issue-329-distribution-point-corpus.md b/docs/sccm/preparation/issue-329-distribution-point-corpus.md index 42169ec4a..f62c3ab96 100644 --- a/docs/sccm/preparation/issue-329-distribution-point-corpus.md +++ b/docs/sccm/preparation/issue-329-distribution-point-corpus.md @@ -114,7 +114,10 @@ The outcome rules are conservative: source-local observations whose classification is bound to exact physical role, capture state, lineage, rotation kind, and fragment completeness; every source-local artifact ID is a nonempty typed string bound to that - physical manifest; and + physical manifest, and every source-local observation cites a nonempty + closed array of exact physical artifact/line ranges. A raw physical citation + does not make a fragment or malformed record transaction- or + correlation-eligible; and - a client-only download record cannot become a DP transaction or DP failure. ## Coverage and request contract From 71158c1757fc4f62e40ec82be57dae35571e00bb Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 03:24:10 -0400 Subject: [PATCH 09/20] test(sccm): close distribution point contract gaps --- ...ver_distribution_point_fixture_contract.rs | 603 +++++++++++++++++- 1 file changed, 568 insertions(+), 35 deletions(-) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index f8fc009b7..2cb962810 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -326,6 +326,31 @@ fn path_fingerprint_is_safe(path_fingerprint: &str) -> bool { }) } +fn artifact_id_is_safe(artifact_id: &str) -> bool { + artifact_id.len() <= 128 && path_segment_is_safe(artifact_id) +} + +fn request_id_is_safe(request_id: &str) -> bool { + request_id + .strip_prefix("client-request-") + .is_some_and(|suffix| suffix.len() <= 64 && path_segment_is_safe(suffix)) +} + +fn coverage_request_reason(artifact: &ParsedArtifact) -> Option<&'static str> { + match artifact.state.as_str() { + "absent" => Some("coverageAbsent"), + "accessDenied" => Some("coverageAccessDenied"), + "capped" => Some("coverageCapped"), + "parseFailed" => Some("coverageMalformed"), + _ if artifact.fragment_complete == Some(false) => Some("coverageRotationSplit"), + _ => None, + } +} + +fn artifact_has_incomplete_coverage(artifact: &ParsedArtifact) -> bool { + artifact.state != "captured" || artifact.fragment_complete == Some(false) +} + fn validate_manifest( scenario_root: &std::path::Path, manifest: &Value, @@ -485,13 +510,18 @@ fn validate_manifest( let mut physical_source_identities = BTreeSet::new(); let mut path_fingerprints = BTreeSet::new(); for artifact in artifacts { - let artifact_id = match required_string(artifact, "artifactId", "artifact") { + let artifact_id = match required_nonempty_string(artifact, "artifactId", "artifact") { Ok(value) => value, Err(error) => { failures.push(error); continue; } }; + if !artifact_id_is_safe(artifact_id) { + failures.push(format!( + "artifact {artifact_id} does not use a bounded stable artifact ID" + )); + } let context = format!("artifact {artifact_id}"); let source_id = match required_string(artifact, "sourceId", &context) { Ok(value) => value, @@ -613,6 +643,31 @@ fn validate_manifest( } }; let physical_capture = matches!(state, "captured" | "capped" | "parseFailed"); + let artifact_collected_utc = match required_string(artifact, "collectedUtc", &context) + .and_then(|value| { + DateTime::parse_from_rfc3339(value) + .map(|parsed| parsed.timestamp_millis()) + .map_err(|error| format!("{context}.collectedUtc is RFC3339: {error}")) + }) { + Ok(value) => { + if value > captured_utc { + failures.push(format!( + "{artifact_id} was collected after the canonical bundle capture" + )); + } + Some(value) + } + Err(error) => { + failures.push(error); + None + } + }; + let encoding = artifact["encoding"].as_str(); + if physical_capture && encoding.is_none_or(str::is_empty) { + failures.push(format!( + "{artifact_id} physical capture lacks encoding provenance" + )); + } let fragment_complete = if physical_capture { match required_bool(&artifact["rotation"], "fragmentComplete", &context) { Ok(value) => Some(value), @@ -778,7 +833,7 @@ fn validate_manifest( ), rotation: rotation_model.clone(), coverage: coverage_model.clone(), - encoding: artifact["encoding"].as_str().map(str::to_owned), + encoding: encoding.map(str::to_owned), }; let normalized = normalize_ccm_artifact(artifact_model, &content); if fragment_complete == Some(false) && !normalized.is_empty() { @@ -806,10 +861,6 @@ fn validate_manifest( "{artifact_id} cites evidence later than the canonical bundle capture" )); } - let artifact_collected_utc = artifact["collectedUtc"] - .as_str() - .and_then(|value| DateTime::parse_from_rfc3339(value).ok()) - .map(|value| value.timestamp_millis()); if artifact_collected_utc.is_none() || artifact_collected_utc.is_some_and(|value| value > captured_utc) || record @@ -847,6 +898,25 @@ fn validate_manifest( "{artifact_id} record escapes its declared workflow-subject scope" )); } + let identity_fields_are_safe = match role { + "client" => { + fields.get("ClientHandle").map(String::as_str) + == Some(EXACT_CLIENT) + && fields + .get("RequestId") + .is_some_and(|value| request_id_is_safe(value)) + } + "siteServer" | "distributionPoint" => { + !fields.contains_key("ClientHandle") + && !fields.contains_key("RequestId") + } + _ => false, + }; + if !identity_fields_are_safe { + failures.push(format!( + "{artifact_id} exposes identity-bearing fields outside the approved opaque role namespace" + )); + } } Err(error) => failures.push(format!("{artifact_id}: {error}")), } @@ -1059,7 +1129,7 @@ fn validate_expected( } if expected["stateChain"] .as_array() - .map(|values| values.iter().filter_map(Value::as_str).collect::>()) + .and_then(|values| values.iter().map(Value::as_str).collect::>>()) .as_deref() != Some(STATE_CHAIN) { @@ -1139,6 +1209,7 @@ fn validate_expected( let mut seen_transaction_ids = BTreeSet::new(); let mut seen_observation_ids = BTreeSet::new(); let mut consumed_evidence = BTreeSet::new(); + let mut required_incomplete_requests = BTreeSet::new(); for transaction in transactions { let transaction_id = match required_string(transaction, "transactionId", "transaction") { Ok(value) => value, @@ -1219,6 +1290,11 @@ fn validate_expected( continue; } }; + if observations.is_empty() { + failures.push(format!( + "{transaction_id} has an exact correlation-eligible key without cited logical records" + )); + } let observation_order = observations .iter() .filter_map(|observation| observation["observationId"].as_str()) @@ -1233,8 +1309,10 @@ fn validate_expected( let mut latest_success: Option = None; let mut terminal_success = false; + let mut terminal_success_phase = None; let mut terminal_failure = false; let mut terminal_deferred = false; + let mut cites_capped_evidence = false; let mut previous_utc = i64::MIN; let mut previous_phase = 0usize; for observation in observations { @@ -1313,7 +1391,10 @@ fn validate_expected( && artifact .workflow_subject_handle .as_deref() - .is_none_or(|handle| handle == key_fields["DpHandle"]) => {} + .is_none_or(|handle| handle == key_fields["DpHandle"]) => + { + cites_capped_evidence |= artifact.state == "capped"; + } _ => failures.push(format!( "{observation_id} cites an artifact that cannot own phase {phase}" )), @@ -1359,6 +1440,12 @@ fn validate_expected( match (disposition, terminal) { ("succeeded", true) => { latest_success = latest_success.max(phase_index); + if terminal_success_phase.is_some() { + failures.push(format!( + "{transaction_id} contains more than one terminal success" + )); + } + terminal_success_phase = phase_index; terminal_success = true; } ("succeeded", false) => latest_success = latest_success.max(phase_index), @@ -1388,19 +1475,26 @@ fn validate_expected( match (state, classification) { ("succeeded", "success") if terminal_success + && terminal_success_phase + == STATE_CHAIN + .iter() + .position(|phase| *phase == "serveOrReport") && computed_last_success == Some("serveOrReport") && !terminal_failure + && !cites_capped_evidence && confidence == "high" && confidence_ceiling == "high" => {} ("failed", "confirmedFailure") if terminal_failure && !terminal_success + && !cites_capped_evidence && confidence == "high" && confidence_ceiling == "high" => {} ("deferred", "blockedOrDeferred") if terminal_deferred && !terminal_failure && !terminal_success + && !cites_capped_evidence && confidence == "medium" && confidence_ceiling == "medium" => {} ("incomplete", "insufficientEvidence") @@ -1438,9 +1532,9 @@ fn validate_expected( "{transaction_id} coverage gaps must be sorted and unique" )); } - for artifact_id in gap_ids { - match parsed.artifacts.get(artifact_id) { - Some(artifact) if artifact.state != "captured" => {} + for artifact_id in &gap_ids { + match parsed.artifacts.get(*artifact_id) { + Some(artifact) if artifact_has_incomplete_coverage(artifact) => {} _ => failures.push(format!( "{transaction_id} coverage gap {artifact_id} is absent or complete" )), @@ -1457,6 +1551,35 @@ fn validate_expected( "{transaction_id} incomplete state lacks a bounded noncomplete next source" )); } + let declared_gap_ids = gap_ids.iter().copied().collect::>(); + let expected_gap_ids = parsed + .artifacts + .iter() + .filter(|(_, artifact)| { + Some(artifact.source_id.as_str()) == next_source + && artifact_has_incomplete_coverage(artifact) + }) + .map(|(artifact_id, _)| artifact_id.as_str()) + .collect::>(); + if declared_gap_ids.is_empty() || declared_gap_ids != expected_gap_ids { + failures.push(format!( + "{transaction_id} incomplete state lacks the exact physical coverage gaps" + )); + } + for artifact_id in declared_gap_ids { + let Some(artifact) = parsed.artifacts.get(artifact_id) else { + continue; + }; + match coverage_request_reason(artifact) { + Some(reason_code) => { + required_incomplete_requests + .insert((artifact.source_id.clone(), reason_code.to_owned())); + } + None => failures.push(format!( + "{transaction_id} gap {artifact_id} has no bounded artifact-request reason" + )), + } + } } else if !transaction["nextSourceId"].is_null() { failures.push(format!( "{transaction_id} terminal/deferred state invents a next source" @@ -1575,6 +1698,7 @@ fn validate_expected( } }; let mut cited_artifact_ids = BTreeSet::new(); + let mut reference_order = Vec::new(); for reference in references { reject_unknown_fields( reference, @@ -1594,17 +1718,25 @@ fn validate_expected( )); } } - if evidence_reference_key(reference, observation_id) - .is_ok_and(|key| !consumed_evidence.insert(key)) - { - failures.push(format!( - "{observation_id} consumes one physical evidence reference more than once" - )); + if let Ok(key) = evidence_reference_key(reference, observation_id) { + reference_order.push(key.clone()); + if !consumed_evidence.insert(key) { + failures.push(format!( + "{observation_id} consumes one physical evidence reference more than once" + )); + } } if let Err(error) = physical_evidence_for(parsed, reference, observation_id) { failures.push(error); } } + let mut sorted_reference_order = reference_order.clone(); + sorted_reference_order.sort(); + if reference_order != sorted_reference_order { + failures.push(format!( + "{observation_id} physical evidence is not canonically ordered" + )); + } let artifacts = artifact_ids .iter() .filter_map(|artifact_id| parsed.artifacts.get(*artifact_id)) @@ -1668,27 +1800,30 @@ fn validate_expected( }; let mut request_order = Vec::new(); for request in requests { - let source_id = - required_string(request, "sourceId", "artifactRequest").unwrap_or("invalid"); - let reason_code = - required_string(request, "reasonCode", "artifactRequest").unwrap_or("invalid"); + let source_id = required_string(request, "sourceId", "artifactRequest") + .unwrap_or("invalid") + .to_owned(); + let reason_code = required_string(request, "reasonCode", "artifactRequest") + .unwrap_or("invalid") + .to_owned(); reject_unknown_fields( request, &["sourceId", "reasonCode"], "artifactRequest", &mut failures, ); - request_order.push((source_id, reason_code)); - if !matches!(source_id, "server-dp-distribution" | "server-dp-serve") - || !matches!( - reason_code, - "coverageAbsent" - | "coverageAccessDenied" - | "coverageCapped" - | "coverageMalformed" - | "coverageRotationSplit" - ) - || request.get("reason").is_some() + request_order.push((source_id.clone(), reason_code.clone())); + if !matches!( + source_id.as_str(), + "server-dp-distribution" | "server-dp-serve" + ) || !matches!( + reason_code.as_str(), + "coverageAbsent" + | "coverageAccessDenied" + | "coverageCapped" + | "coverageMalformed" + | "coverageRotationSplit" + ) || request.get("reason").is_some() { failures.push(format!( "artifact request is not a bounded versioned source/reason code: {source_id}/{reason_code}" @@ -1696,7 +1831,7 @@ fn validate_expected( } let matching_coverage = parsed.artifacts.values().any(|artifact| { artifact.source_id == source_id - && match reason_code { + && match reason_code.as_str() { "coverageAbsent" => artifact.state == "absent", "coverageAccessDenied" => artifact.state == "accessDenied", "coverageCapped" => artifact.state == "capped", @@ -1716,8 +1851,27 @@ fn validate_expected( } let mut sorted_request_order = request_order.clone(); sorted_request_order.sort_unstable(); - if request_order != sorted_request_order { - failures.push("artifact requests are not deterministically sorted".to_owned()); + let declared_requests = request_order.iter().cloned().collect::>(); + if request_order != sorted_request_order || declared_requests.len() != request_order.len() { + failures.push("artifact requests are not sorted and unique".to_owned()); + } + let expected_requests = parsed + .artifacts + .values() + .filter_map(|artifact| { + coverage_request_reason(artifact) + .map(|reason| (artifact.source_id.clone(), reason.to_owned())) + }) + .collect::>(); + if declared_requests != expected_requests { + failures.push(format!( + "artifact requests are not the exact bounded coverage projection: {declared_requests:?} != {expected_requests:?}" + )); + } + if !required_incomplete_requests.is_subset(&declared_requests) { + failures.push( + "incomplete transactions lack requests matching their exact physical gaps".to_owned(), + ); } if expected["clientCausalClaims"] != json!([]) @@ -1834,6 +1988,84 @@ fn mutation_was_accepted(scenario: &str, manifest: &Value, expected: &Value) -> validate_scenario_values(scenario, manifest, expected).is_ok() } +struct TemporaryScenario { + root: std::path::PathBuf, +} + +impl Drop for TemporaryScenario { + fn drop(&mut self) { + let _ = std::fs::remove_dir_all(&self.root); + } +} + +fn copy_fixture_tree(source: &std::path::Path, destination: &std::path::Path) { + std::fs::create_dir_all(destination).expect("temporary fixture directory is created"); + for entry in std::fs::read_dir(source).expect("fixture directory is readable") { + let entry = entry.expect("fixture directory entry is readable"); + let source_path = entry.path(); + let destination_path = destination.join(entry.file_name()); + if source_path.is_dir() { + copy_fixture_tree(&source_path, &destination_path); + } else { + std::fs::copy(&source_path, &destination_path) + .expect("fixture file is copied into the temporary scenario"); + } + } +} + +fn temporary_scenario(scenario: &str) -> TemporaryScenario { + static NEXT_TEMP_SCENARIO: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); + let sequence = NEXT_TEMP_SCENARIO.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let root = std::env::temp_dir().join(format!( + "cmtraceopen-sccm-329-{}-{scenario}-{sequence}", + std::process::id() + )); + copy_fixture_tree(&corpus_root().join(scenario), &root); + TemporaryScenario { root } +} + +fn mutation_at_root_was_accepted( + scenario: &str, + scenario_root: &std::path::Path, + manifest: &Value, + expected: &Value, +) -> bool { + validate_manifest(scenario_root, manifest) + .and_then(|parsed| validate_expected(scenario, manifest, expected, &parsed)) + .is_ok() +} + +fn replace_fixture_text( + scenario_root: &std::path::Path, + relative_path: &str, + original: &str, + replacement: &str, +) { + let path = scenario_root.join(relative_path); + let contents = std::fs::read_to_string(&path).expect("temporary fixture is readable"); + assert_eq!( + contents.matches(original).count(), + 1, + "fixture mutation must identify exactly one raw marker" + ); + std::fs::write(&path, contents.replacen(original, replacement, 1)) + .expect("temporary fixture mutation is written"); +} + +fn refresh_artifact_bytes( + manifest: &mut Value, + artifact_index: usize, + scenario_root: &std::path::Path, +) { + let relative_path = manifest["artifacts"][artifact_index]["relativePath"] + .as_str() + .expect("physical artifact has a relative path"); + let byte_count = std::fs::metadata(scenario_root.join(relative_path)) + .expect("mutated physical artifact is readable") + .len(); + manifest["artifacts"][artifact_index]["bytesCopied"] = json!(byte_count); +} + #[test] fn exact_content_version_dp_topology_and_terminal_evidence_fail_closed() { let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); @@ -2752,3 +2984,304 @@ fn coverage_gap_ids_are_typed_nonempty_unique_and_physical() { "malformed coverage-gap artifact IDs were accepted: {accepted:?}" ); } + +#[test] +fn capped_transaction_evidence_cannot_retain_high_confidence_terminal_health() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + for (label, artifact_index) in [("earlier phase", 0usize), ("terminal phase", 2usize)] { + let mut manifest = healthy_manifest.clone(); + let mut expected = healthy_expected.clone(); + let artifact_id = manifest["artifacts"][artifact_index]["artifactId"].clone(); + let bytes_copied = manifest["artifacts"][artifact_index]["bytesCopied"].clone(); + manifest["artifacts"][artifact_index]["captureState"] = json!("capped"); + manifest["artifacts"][artifact_index]["collectionLimit"] = + json!({"byteLimit": bytes_copied, "limitApplied": true}); + expected["coverage"][artifact_index]["state"] = json!("capped"); + expected["transactions"][0]["coverageGapArtifactIds"] = json!([artifact_id]); + expected["artifactRequests"] = json!([{ + "sourceId": "server-dp-distribution", + "reasonCode": "coverageCapped" + }]); + + if mutation_was_accepted("healthy-package", &manifest, &expected) { + accepted.push(label); + } + } + + assert!( + accepted.is_empty(), + "capped transaction evidence retained high-confidence success: {accepted:?}" + ); +} + +#[test] +fn terminal_success_is_bound_to_the_cited_serve_or_report_record() { + let temporary = temporary_scenario("healthy-package"); + let mut manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let mut expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let relative_path = manifest["artifacts"][2]["relativePath"] + .as_str() + .expect("provider artifact has a path") + .to_owned(); + + replace_fixture_text( + &temporary.root, + &relative_path, + "Phase=validate; Disposition=succeeded; Terminal=false;", + "Phase=validate; Disposition=succeeded; Terminal=true;", + ); + replace_fixture_text( + &temporary.root, + &relative_path, + "Phase=serveOrReport; Disposition=succeeded; Terminal=true;", + "Phase=serveOrReport; Disposition=succeeded; Terminal=false;", + ); + refresh_artifact_bytes(&mut manifest, 2, &temporary.root); + expected["transactions"][0]["observations"][3]["terminal"] = json!(true); + expected["transactions"][0]["observations"][5]["terminal"] = json!(false); + + assert!( + !mutation_at_root_was_accepted("healthy-package", &temporary.root, &manifest, &expected,), + "an earlier terminal success survived later nonterminal ServeOrReport evidence" + ); +} + +#[test] +fn correlation_eligible_incomplete_output_requires_evidence_gaps_and_requests() { + let manifest = read_json("incomplete", "manifest.json").expect("manifest loads"); + let expected = read_json("incomplete", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut uncited_key = expected.clone(); + uncited_key["transactions"][0]["observations"] = json!([]); + uncited_key["transactions"][0]["lastSuccessfulPhase"] = Value::Null; + if mutation_was_accepted("incomplete", &manifest, &uncited_key) { + accepted.push("correlation-eligible exact key with zero cited logical records"); + } + + let mut no_gaps = expected.clone(); + no_gaps["transactions"][0]["coverageGapArtifactIds"] = json!([]); + if mutation_was_accepted("incomplete", &manifest, &no_gaps) { + accepted.push("insufficientEvidence transaction with no physical gaps"); + } + + let mut no_requests = expected.clone(); + no_requests["artifactRequests"] = json!([]); + if mutation_was_accepted("incomplete", &manifest, &no_requests) { + accepted.push("insufficientEvidence transaction with no bounded request"); + } + + assert!( + accepted.is_empty(), + "incomplete output escaped evidence-first coverage requirements: {accepted:?}" + ); +} + +#[test] +fn identity_bearing_fixture_fields_are_bounded_role_local_and_not_public() { + let client_expected = + read_json("client-only-looking-request", "expected.json").expect("expected loads"); + let public_json = serde_json::to_string(&client_expected).expect("public output serializes"); + for forbidden in ["ClientHandle", "RequestId", "RealUser", "RealRequest"] { + assert!( + !public_json.contains(forbidden), + "public expected JSON exposes raw identity marker {forbidden}" + ); + } + + let mut accepted = Vec::new(); + for (label, original, replacement) in [ + ( + "unbounded client handle", + "ClientHandle=safe:client:lab-client-01", + "ClientHandle=RealUser", + ), + ( + "unbounded request ID", + "RequestId=client-request-01", + "RequestId=RealRequest", + ), + ] { + let temporary = temporary_scenario("client-only-looking-request"); + let mut manifest = + read_json("client-only-looking-request", "manifest.json").expect("manifest loads"); + let relative_path = manifest["artifacts"][0]["relativePath"] + .as_str() + .expect("client artifact has a path") + .to_owned(); + replace_fixture_text(&temporary.root, &relative_path, original, replacement); + refresh_artifact_bytes(&mut manifest, 0, &temporary.root); + if mutation_at_root_was_accepted( + "client-only-looking-request", + &temporary.root, + &manifest, + &client_expected, + ) { + accepted.push(label); + } + } + + let temporary = temporary_scenario("healthy-package"); + let mut manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let relative_path = manifest["artifacts"][2]["relativePath"] + .as_str() + .expect("provider artifact has a path") + .to_owned(); + replace_fixture_text( + &temporary.root, + &relative_path, + "ProfileId=dp-server-5.00.test-v1]LOG]!> Date: Fri, 31 Jul 2026 03:32:38 -0400 Subject: [PATCH 10/20] test(sccm): align distribution point coverage requests --- ...ver_distribution_point_fixture_contract.rs | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 2cb962810..4ebda3987 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -1544,7 +1544,8 @@ fn validate_expected( let next_source = transaction["nextSourceId"].as_str(); if next_source.is_none() || !parsed.artifacts.values().any(|artifact| { - Some(artifact.source_id.as_str()) == next_source && artifact.state != "captured" + Some(artifact.source_id.as_str()) == next_source + && artifact_has_incomplete_coverage(artifact) }) { failures.push(format!( @@ -1858,6 +1859,7 @@ fn validate_expected( let expected_requests = parsed .artifacts .values() + .filter(|artifact| artifact.role != "client") .filter_map(|artifact| { coverage_request_reason(artifact) .map(|reason| (artifact.source_id.clone(), reason.to_owned())) @@ -3285,3 +3287,71 @@ fn source_local_evidence_and_artifact_requests_are_canonical_and_unique() { "nondeterministic source-local output was accepted: {accepted:?}" ); } + +#[test] +fn client_control_coverage_does_not_create_a_server_artifact_request() { + let mut manifest = + read_json("client-only-looking-request", "manifest.json").expect("manifest loads"); + let mut expected = + read_json("client-only-looking-request", "expected.json").expect("expected loads"); + let bytes_copied = manifest["artifacts"][0]["bytesCopied"].clone(); + manifest["artifacts"][0]["captureState"] = json!("capped"); + manifest["artifacts"][0]["collectionLimit"] = + json!({"byteLimit": bytes_copied, "limitApplied": true}); + expected["coverage"][0]["state"] = json!("capped"); + + let validation = validate_scenario_values("client-only-looking-request", &manifest, &expected); + assert!( + validation.is_ok(), + "ignored capped client control invented a server artifact request: {validation:?}" + ); +} + +#[test] +fn captured_incomplete_fragments_satisfy_the_bounded_next_source() { + let temporary = temporary_scenario("incomplete"); + let mut manifest = read_json("incomplete", "manifest.json").expect("manifest loads"); + let mut expected = read_json("incomplete", "expected.json").expect("expected loads"); + let fragments = [ + ( + 1usize, + "evidence/server-dp-distribution/site/current/PkgXferMgr.log", + "SYNTHETIC FIXTURE CURRENT FRAGMENT ONLY Date: Fri, 31 Jul 2026 05:39:59 -0400 Subject: [PATCH 11/20] test(sccm): reproduce DP content fail-open mutations --- ...ver_distribution_point_fixture_contract.rs | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 4ebda3987..3419dd14d 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -2068,6 +2068,19 @@ fn refresh_artifact_bytes( manifest["artifacts"][artifact_index]["bytesCopied"] = json!(byte_count); } +fn remove_physical_capture_fields(artifact: &mut Value) { + let artifact = artifact + .as_object_mut() + .expect("fixture artifact is an object"); + for field in ["encoding", "collectionLimit", "bytesCopied", "relativePath"] { + artifact.remove(field); + } + artifact["rotation"] + .as_object_mut() + .expect("fixture rotation is an object") + .remove("fragmentComplete"); +} + #[test] fn exact_content_version_dp_topology_and_terminal_evidence_fail_closed() { let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); @@ -3355,3 +3368,268 @@ fn captured_incomplete_fragments_satisfy_the_bounded_next_source() { "captured incomplete fragments were not usable as bounded coverage gaps: {validation:?}" ); } + +#[test] +fn unresolved_required_transfer_coverage_cannot_retain_high_success() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let healthy_expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let transfer_artifact_id = healthy_manifest["artifacts"][1]["artifactId"].clone(); + let mut accepted = Vec::new(); + + let mut capped_manifest = healthy_manifest.clone(); + let mut capped_expected = healthy_expected.clone(); + let bytes_copied = capped_manifest["artifacts"][1]["bytesCopied"].clone(); + capped_manifest["artifacts"][1]["captureState"] = json!("capped"); + capped_manifest["artifacts"][1]["collectionLimit"] = + json!({"byteLimit": bytes_copied, "limitApplied": true}); + capped_expected["coverage"][1]["state"] = json!("capped"); + capped_expected["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are an array") + .remove(2); + capped_expected["transactions"][0]["coverageGapArtifactIds"] = + json!([transfer_artifact_id.clone()]); + capped_expected["artifactRequests"] = json!([{ + "sourceId": "server-dp-distribution", + "reasonCode": "coverageCapped" + }]); + if mutation_was_accepted("healthy-package", &capped_manifest, &capped_expected) { + accepted.push("capped transfer evidence was removed from high success"); + } + + let mut denied_manifest = healthy_manifest.clone(); + let mut denied_expected = healthy_expected.clone(); + denied_manifest["artifacts"][1]["captureState"] = json!("accessDenied"); + remove_physical_capture_fields(&mut denied_manifest["artifacts"][1]); + denied_expected["coverage"][1]["state"] = json!("accessDenied"); + denied_expected["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are an array") + .remove(2); + denied_expected["transactions"][0]["coverageGapArtifactIds"] = + json!([transfer_artifact_id.clone()]); + denied_expected["artifactRequests"] = json!([{ + "sourceId": "server-dp-distribution", + "reasonCode": "coverageAccessDenied" + }]); + if mutation_was_accepted("healthy-package", &denied_manifest, &denied_expected) { + accepted.push("access-denied transfer evidence was removed from high success"); + } + + let temporary = temporary_scenario("healthy-package"); + let mut split_manifest = healthy_manifest; + let mut split_expected = healthy_expected; + let relative_path = split_manifest["artifacts"][1]["relativePath"] + .as_str() + .expect("transfer artifact has a path") + .to_owned(); + std::fs::write( + temporary.root.join(&relative_path), + "SYNTHETIC FIXTURE CURRENT FRAGMENT ONLY \n", + ); + std::fs::write(&fixture_path, contents).expect("later recovery evidence is written"); + refresh_artifact_bytes(&mut manifest, 2, &temporary.root); + expected["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are an array") + .push(json!({ + "observationId": "05-available", + "phase": "makeAvailable", + "disposition": "succeeded", + "terminal": false, + "evidence": [{ + "artifactId": "dp-validation-failure-03-provider", + "startLine": 2, + "endLine": 2 + }] + })); + expected["transactions"][0]["lastSuccessfulPhase"] = json!("makeAvailable"); + + assert!( + !mutation_at_root_was_accepted("validation-failure", &temporary.root, &manifest, &expected,), + "a later same-key success retained a stale high confirmed failure" + ); +} + +#[test] +fn incomplete_transaction_gaps_are_bound_to_the_exact_distribution_point() { + let mut manifest = read_json("incomplete", "manifest.json").expect("manifest loads"); + let expected = read_json("incomplete", "expected.json").expect("expected loads"); + manifest["topology"]["distributionPointHandles"] = json!([EXACT_DP, EXACT_DP_02]); + manifest["artifacts"][1]["workflowSubjectHandle"] = json!(EXACT_DP_02); + manifest["artifacts"][2]["producerHostHandle"] = json!(EXACT_DP_02); + manifest["artifacts"][2]["workflowSubjectHandle"] = json!(EXACT_DP_02); + manifest["artifacts"][2]["sanitizedSourcePath"] = + json!("SYNTHETIC://dp-02-root/Logs/SMSDPProv.log"); + + assert!( + !mutation_was_accepted("incomplete", &manifest, &expected), + "DP-02 gaps and requests satisfied an exact DP-01 transaction" + ); +} + +#[test] +fn rotation_split_requires_one_physical_log_family() { + let temporary = temporary_scenario("rotation-boundary"); + let mut manifest = read_json("rotation-boundary", "manifest.json").expect("manifest loads"); + let expected = read_json("rotation-boundary", "expected.json").expect("expected loads"); + let old_relative_path = manifest["artifacts"][1]["relativePath"] + .as_str() + .expect("lo_ artifact has a path") + .to_owned(); + let new_relative_path = "evidence/server-dp-distribution/site/lo_/PkgXferMgr.log".to_owned(); + let new_fixture_path = temporary.root.join(&new_relative_path); + std::fs::create_dir_all( + new_fixture_path + .parent() + .expect("temporary replacement has a parent"), + ) + .expect("temporary replacement parent is created"); + std::fs::copy(temporary.root.join(old_relative_path), &new_fixture_path) + .expect("lo_ fragment is copied into another physical log family"); + manifest["artifacts"][1]["originalBasename"] = json!("PkgXferMgr.log"); + manifest["artifacts"][1]["sanitizedSourcePath"] = + json!("SYNTHETIC://site-root/Logs/PkgXferMgr.lo_"); + manifest["artifacts"][1]["relativePath"] = json!(new_relative_path); + + assert!( + !mutation_at_root_was_accepted("rotation-boundary", &temporary.root, &manifest, &expected,), + "distmgr current plus PkgXferMgr lo_ formed one rotation split" + ); +} + +#[test] +fn parse_failed_raw_bytes_remain_synthetic_and_identity_free() { + let temporary = temporary_scenario("rotation-boundary"); + let mut manifest = read_json("rotation-boundary", "manifest.json").expect("manifest loads"); + let expected = read_json("rotation-boundary", "expected.json").expect("expected loads"); + let relative_path = manifest["artifacts"][2]["relativePath"] + .as_str() + .expect("malformed artifact has a path") + .to_owned(); + let fixture_path = temporary.root.join(&relative_path); + let mut contents = + std::fs::read_to_string(&fixture_path).expect("malformed fixture is readable"); + contents + .push_str("real.user@example.com C:\\Users\\RealUser\\SMSDPProv.log secret=RealSecret\n"); + std::fs::write(&fixture_path, contents).expect("identity-bearing malformed bytes are written"); + refresh_artifact_bytes(&mut manifest, 2, &temporary.root); + + assert!( + !mutation_at_root_was_accepted("rotation-boundary", &temporary.root, &manifest, &expected,), + "parse-failed raw bytes retained uncited identity, path, and secret markers" + ); +} + +#[test] +fn observation_ids_reject_identity_bearing_values_across_output_classes() { + let healthy_manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let mut healthy_expected = + read_json("healthy-package", "expected.json").expect("expected loads"); + healthy_expected["transactions"][0]["observations"][0]["observationId"] = + json!("01-C:\\Users\\RealUser"); + + let rotation_manifest = + read_json("rotation-boundary", "manifest.json").expect("manifest loads"); + let mut rotation_expected = + read_json("rotation-boundary", "expected.json").expect("expected loads"); + rotation_expected["sourceLocalObservations"][0]["observationId"] = + json!("rotation-01-C:\\Users\\RealUser"); + + let accepted = [ + ( + "transaction observation ID", + mutation_was_accepted("healthy-package", &healthy_manifest, &healthy_expected), + ), + ( + "source-local observation ID", + mutation_was_accepted("rotation-boundary", &rotation_manifest, &rotation_expected), + ), + ] + .into_iter() + .filter_map(|(label, was_accepted)| was_accepted.then_some(label)) + .collect::>(); + + assert!( + accepted.is_empty(), + "identity-bearing public observation IDs were accepted: {accepted:?}" + ); +} + +#[test] +fn rotation_lineage_rejects_identity_bearing_values() { + let mut manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let expected = read_json("healthy-package", "expected.json").expect("expected loads"); + manifest["artifacts"][0]["rotation"]["lineageId"] = json!("C:\\Users\\RealUser\\distmgr"); + + assert!( + !mutation_was_accepted("healthy-package", &manifest, &expected), + "identity-bearing rotation lineage was accepted" + ); +} + +#[test] +fn path_fingerprints_are_bounded_declared_synthetic_identities() { + let manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut identity_bearing = manifest.clone(); + identity_bearing["artifacts"][0]["pathFingerprint"] = json!("synthetic:real-user-hostname"); + if mutation_was_accepted("healthy-package", &identity_bearing, &expected) { + accepted.push("identity-bearing fingerprint"); + } + + let mut oversized = manifest; + oversized["artifacts"][0]["pathFingerprint"] = json!(format!("synthetic:{}", "a".repeat(256))); + if mutation_was_accepted("healthy-package", &oversized, &expected) { + accepted.push("oversized fingerprint"); + } + + assert!( + accepted.is_empty(), + "unbounded or undeclared path fingerprints were accepted: {accepted:?}" + ); +} From 0d940081f37a3d55c00d3730f81e5e54ef0b8932 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 07:05:34 -0400 Subject: [PATCH 12/20] fix(sccm): close DP content evidence gaps --- ...ver_distribution_point_fixture_contract.rs | 230 ++++++++++++++++-- 1 file changed, 213 insertions(+), 17 deletions(-) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 3419dd14d..c3b561a1f 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -36,6 +36,63 @@ const EXACT_DP: &str = "safe:dp:lab-dp-01"; const EXACT_DP_02: &str = "safe:dp:lab-dp-02"; const EXACT_SITE_SERVER: &str = "safe:server:lab-pri-01"; const EXACT_CLIENT: &str = "safe:client:lab-client-01"; +const APPROVED_PATH_FINGERPRINTS: &[&str] = &[ + "synthetic:absent-distmgr", + "synthetic:absent-provider", + "synthetic:client-only-data-transfer", + "synthetic:client-only-server-absent", + "synthetic:distribution-failure", + "synthetic:healthy-distmgr", + "synthetic:healthy-pkgxfer", + "synthetic:healthy-provider", + "synthetic:incomplete-distmgr", + "synthetic:incomplete-pkgxfer", + "synthetic:incomplete-provider", + "synthetic:retry-distmgr", + "synthetic:retry-pkgxfer", + "synthetic:rotation-current", + "synthetic:rotation-lo", + "synthetic:rotation-malformed", + "synthetic:serve-distmgr", + "synthetic:serve-pkgxfer", + "synthetic:serve-provider", + "synthetic:serve-status", + "synthetic:validation-distmgr", + "synthetic:validation-pkgxfer", + "synthetic:validation-provider", + "synthetic:version-distmgr", + "synthetic:version-pkgxfer", + "synthetic:version-provider", + "synthetic:version-provider-dp02", +]; +const APPROVED_ROTATION_LINEAGES: &[&str] = &[ + "absent-distmgr", + "absent-provider", + "client-only-data-transfer", + "client-only-server-absent", + "distribution-failure", + "healthy-distmgr", + "healthy-pkgxfer", + "healthy-provider", + "incomplete-distmgr", + "incomplete-pkgxfer", + "incomplete-provider", + "retry-distmgr", + "retry-pkgxfer", + "rotation-distmgr", + "rotation-provider", + "serve-distmgr", + "serve-pkgxfer", + "serve-provider", + "serve-status", + "validation-distmgr", + "validation-pkgxfer", + "validation-provider", + "version-distmgr", + "version-pkgxfer", + "version-provider", + "version-provider-dp02", +]; fn corpus_root() -> std::path::PathBuf { std::path::Path::new(env!("CARGO_MANIFEST_DIR")) @@ -193,8 +250,10 @@ struct ParsedArtifact { state: String, source_id: String, role: String, + producer_host_handle: String, basename: String, workflow_subject_handle: Option, + workflow_subject_basis: Option, rotation_kind: String, rotation_lineage: String, fragment_complete: Option, @@ -208,6 +267,16 @@ struct ParsedScenario { distribution_point_handles: BTreeSet, } +fn artifact_applies_to_distribution_point( + artifact: &ParsedArtifact, + distribution_point_handle: &str, +) -> bool { + artifact.workflow_subject_handle.as_deref() == Some(distribution_point_handle) + || (artifact.workflow_subject_handle.is_none() + && artifact.workflow_subject_basis.as_deref() == Some("manifestTopology") + && artifact.role == "siteServer") +} + fn parse_fixture_fields(message: &str) -> Result, String> { let message = message .strip_prefix("[sccm-public-message-v1] ") @@ -316,13 +385,60 @@ fn sanitized_source_path_is_bounded(source_path: &str, basename: &str, rotation: } fn path_fingerprint_is_safe(path_fingerprint: &str) -> bool { - path_fingerprint - .strip_prefix("synthetic:") - .is_some_and(|suffix| { + path_fingerprint.len() <= 64 && APPROVED_PATH_FINGERPRINTS.contains(&path_fingerprint) +} + +fn rotation_lineage_is_safe(rotation_lineage: &str) -> bool { + rotation_lineage.len() <= 64 && APPROVED_ROTATION_LINEAGES.contains(&rotation_lineage) +} + +fn transaction_observation_id_is_safe(observation_id: &str) -> bool { + observation_id.len() <= 64 + && observation_id + .as_bytes() + .get(..2) + .is_some_and(|prefix| prefix.iter().all(u8::is_ascii_digit)) + && observation_id.as_bytes().get(2) == Some(&b'-') + && observation_id.as_bytes().get(3..).is_some_and(|suffix| { !suffix.is_empty() && suffix - .bytes() - .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-')) + .iter() + .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || *byte == b'-') + }) +} + +fn source_local_observation_id_is_safe(observation_id: &str) -> bool { + observation_id.len() <= 64 + && (observation_id + .strip_prefix("client-control-") + .is_some_and(|suffix| { + suffix.len() == 2 && suffix.bytes().all(|byte| byte.is_ascii_digit()) + }) + || observation_id + .strip_prefix("rotation-") + .is_some_and(|suffix| { + suffix.split_once('-').is_some_and(|(ordinal, label)| { + ordinal.len() == 2 + && ordinal.bytes().all(|byte| byte.is_ascii_digit()) + && !label.is_empty() + && label.bytes().all(|byte| { + byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-' + }) + }) + })) +} + +fn raw_fixture_bytes_are_sanitized(content: &str) -> bool { + !content.is_empty() + && content.lines().all(|line| { + let lower = line.to_ascii_lowercase(); + !line.is_empty() + && line.contains("SYNTHETIC FIXTURE") + && !line.contains(['@', '\\']) + && !lower.contains("/users/") + && !lower.contains("secret=") + && !lower.contains("password=") + && !lower.contains("token=") }) } @@ -634,14 +750,24 @@ fn validate_manifest( )); } let rotation_kind = artifact["rotation"]["kind"].as_str(); - let rotation_lineage = - match required_nonempty_string(&artifact["rotation"], "lineageId", &context) { - Ok(value) => value.to_owned(), - Err(error) => { - failures.push(error); - String::new() + let rotation_lineage = match required_nonempty_string( + &artifact["rotation"], + "lineageId", + &context, + ) { + Ok(value) => { + if !rotation_lineage_is_safe(value) { + failures.push(format!( + "{artifact_id} rotation lineage is not a bounded declared synthetic identity" + )); } - }; + value.to_owned() + } + Err(error) => { + failures.push(error); + String::new() + } + }; let physical_capture = matches!(state, "captured" | "capped" | "parseFailed"); let artifact_collected_utc = match required_string(artifact, "collectedUtc", &context) .and_then(|value| { @@ -801,9 +927,19 @@ fn validate_manifest( "{artifact_id} has incoherent raw-byte collection-limit provenance" )); } - let content = String::from_utf8_lossy(&bytes); - if !content.contains("SYNTHETIC FIXTURE") { - failures.push(format!("{artifact_id} lacks a synthetic fixture marker")); + let content = match std::str::from_utf8(&bytes) { + Ok(value) => value, + Err(error) => { + failures.push(format!( + "{artifact_id} is not valid UTF-8 under its declared encoding: {error}" + )); + "" + } + }; + if !raw_fixture_bytes_are_sanitized(content) { + failures.push(format!( + "{artifact_id} contains non-synthetic or identity-bearing raw fixture bytes" + )); } for (line_index, _) in content.lines().enumerate() { let Ok(line_number) = u32::try_from(line_index + 1) else { @@ -835,7 +971,7 @@ fn validate_manifest( coverage: coverage_model.clone(), encoding: encoding.map(str::to_owned), }; - let normalized = normalize_ccm_artifact(artifact_model, &content); + let normalized = normalize_ccm_artifact(artifact_model, content); if fragment_complete == Some(false) && !normalized.is_empty() { failures.push(format!( "{artifact_id} exposes a logical record from an incomplete rotation fragment" @@ -952,8 +1088,10 @@ fn validate_manifest( state: state.to_owned(), source_id: source_id.to_owned(), role: role.to_owned(), + producer_host_handle: producer_host_handle.unwrap_or_default().to_owned(), basename: basename.to_owned(), workflow_subject_handle: workflow_subject_handle.map(str::to_owned), + workflow_subject_basis: workflow_subject_basis.map(str::to_owned), rotation_kind: artifact["rotation"]["kind"] .as_str() .unwrap_or_default() @@ -1265,6 +1403,17 @@ fn validate_expected( continue; } }; + let required_incomplete_artifact_ids = parsed + .artifacts + .iter() + .filter(|(_, artifact)| { + artifact.source_id == "server-dp-distribution" + && artifact_has_incomplete_coverage(artifact) + && artifact_applies_to_distribution_point(artifact, &key_fields["DpHandle"]) + }) + .map(|(artifact_id, _)| artifact_id.as_str()) + .collect::>(); + let has_unresolved_required_coverage = !required_incomplete_artifact_ids.is_empty(); let expected_id = format!( "dp:{}:{}:v{}:{}", key_fields["PackageId"], @@ -1312,6 +1461,7 @@ fn validate_expected( let mut terminal_success_phase = None; let mut terminal_failure = false; let mut terminal_deferred = false; + let mut observed_after_terminal_failure = false; let mut cites_capped_evidence = false; let mut previous_utc = i64::MIN; let mut previous_phase = 0usize; @@ -1329,6 +1479,11 @@ fn validate_expected( "{transaction_id} contains duplicate observationId {observation_id}" )); } + if !transaction_observation_id_is_safe(observation_id) { + failures.push(format!( + "{transaction_id} contains an unbounded or identity-bearing observationId" + )); + } let phase = required_string(observation, "phase", observation_id).unwrap_or("invalid"); let disposition = required_string(observation, "disposition", observation_id).unwrap_or("invalid"); @@ -1437,6 +1592,9 @@ fn validate_expected( } previous_utc = utc; } + if terminal_failure { + observed_after_terminal_failure = true; + } match (disposition, terminal) { ("succeeded", true) => { latest_success = latest_success.max(phase_index); @@ -1482,12 +1640,15 @@ fn validate_expected( && computed_last_success == Some("serveOrReport") && !terminal_failure && !cites_capped_evidence + && !has_unresolved_required_coverage && confidence == "high" && confidence_ceiling == "high" => {} ("failed", "confirmedFailure") if terminal_failure && !terminal_success + && !observed_after_terminal_failure && !cites_capped_evidence + && !has_unresolved_required_coverage && confidence == "high" && confidence_ceiling == "high" => {} ("deferred", "blockedOrDeferred") @@ -1495,6 +1656,7 @@ fn validate_expected( && !terminal_failure && !terminal_success && !cites_capped_evidence + && !has_unresolved_required_coverage && confidence == "medium" && confidence_ceiling == "medium" => {} ("incomplete", "insufficientEvidence") @@ -1532,6 +1694,12 @@ fn validate_expected( "{transaction_id} coverage gaps must be sorted and unique" )); } + let declared_gap_ids = gap_ids.iter().copied().collect::>(); + if declared_gap_ids != required_incomplete_artifact_ids { + failures.push(format!( + "{transaction_id} coverage gaps are not the exact required DP-bound source gaps" + )); + } for artifact_id in &gap_ids { match parsed.artifacts.get(*artifact_id) { Some(artifact) if artifact_has_incomplete_coverage(artifact) => {} @@ -1552,13 +1720,13 @@ fn validate_expected( "{transaction_id} incomplete state lacks a bounded noncomplete next source" )); } - let declared_gap_ids = gap_ids.iter().copied().collect::>(); let expected_gap_ids = parsed .artifacts .iter() .filter(|(_, artifact)| { Some(artifact.source_id.as_str()) == next_source && artifact_has_incomplete_coverage(artifact) + && artifact_applies_to_distribution_point(artifact, &key_fields["DpHandle"]) }) .map(|(artifact_id, _)| artifact_id.as_str()) .collect::>(); @@ -1621,6 +1789,9 @@ fn validate_expected( "source-local observations contain duplicate observationId {observation_id}" )); } + if !source_local_observation_id_is_safe(observation_id) { + failures.push("source-local observationId is unbounded or identity-bearing".to_owned()); + } reject_unknown_fields( observation, &[ @@ -1756,6 +1927,26 @@ fn validate_expected( .iter() .map(|artifact| artifact.source_id.as_str()) .collect::>(); + let roles = artifacts + .iter() + .map(|artifact| artifact.role.as_str()) + .collect::>(); + let producers = artifacts + .iter() + .map(|artifact| artifact.producer_host_handle.as_str()) + .collect::>(); + let basenames = artifacts + .iter() + .map(|artifact| artifact.basename.to_ascii_lowercase()) + .collect::>(); + let workflow_subject_handles = artifacts + .iter() + .map(|artifact| artifact.workflow_subject_handle.as_deref()) + .collect::>(); + let workflow_subject_bases = artifacts + .iter() + .map(|artifact| artifact.workflow_subject_basis.as_deref()) + .collect::>(); let lineages = artifacts .iter() .map(|artifact| artifact.rotation_lineage.as_str()) @@ -1767,6 +1958,11 @@ fn validate_expected( cited_artifact_ids == unique_artifact_ids && artifacts.len() >= 2 && source_ids.len() == 1 + && roles.len() == 1 + && producers.len() == 1 + && basenames.len() == 1 + && workflow_subject_handles.len() == 1 + && workflow_subject_bases.len() == 1 && lineages.len() == 1 && lineages.first().is_some_and(|lineage| !lineage.is_empty()) && rotation_kinds.len() >= 2 From eecb54cabd5173fbbe94c558f2a61995586e224c Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 08:43:43 -0400 Subject: [PATCH 13/20] test(sccm): require explicit DP terminal markers --- ..._server_distribution_point_fixture_contract.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index c3b561a1f..76c8dc64c 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -2317,6 +2317,21 @@ fn exact_content_version_dp_topology_and_terminal_evidence_fail_closed() { ); } +#[test] +fn every_observation_requires_an_explicit_typed_terminal_marker() { + let manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let mut expected = read_json("healthy-package", "expected.json").expect("expected loads"); + expected["transactions"][0]["observations"][0] + .as_object_mut() + .expect("nonterminal observation is an object") + .remove("terminal"); + + assert!( + !mutation_was_accepted("healthy-package", &manifest, &expected), + "nonterminal observation without terminal was accepted" + ); +} + #[test] fn coverage_role_and_rotation_states_fail_closed() { let absent_manifest = read_json("absent-dp", "manifest.json").expect("manifest loads"); From 9a63b6bd9494882c1006c17ec6b9e9af0bef91d9 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 08:44:10 -0400 Subject: [PATCH 14/20] fix(sccm): fail closed on missing DP terminal markers --- .../sccm_server_distribution_point_fixture_contract.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 76c8dc64c..dd6a7cb5e 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -1487,7 +1487,13 @@ fn validate_expected( let phase = required_string(observation, "phase", observation_id).unwrap_or("invalid"); let disposition = required_string(observation, "disposition", observation_id).unwrap_or("invalid"); - let terminal = required_bool(observation, "terminal", observation_id).unwrap_or(false); + let terminal = match required_bool(observation, "terminal", observation_id) { + Ok(value) => value, + Err(error) => { + failures.push(error); + false + } + }; reject_unknown_fields( observation, &[ From 3bcfdd03443b31b51ae4f7f915f7cd6bf984e2b4 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 09:04:01 -0400 Subject: [PATCH 15/20] test(sccm): reproduce DP semantic review gaps --- ...ver_distribution_point_fixture_contract.rs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index dd6a7cb5e..3ff222b70 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -3710,6 +3710,78 @@ fn later_same_key_success_prevents_stale_high_terminal_failure() { ); } +#[test] +fn uncited_later_same_key_terminal_failure_invalidates_high_success() { + let temporary = temporary_scenario("healthy-package"); + let mut manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let relative_path = manifest["artifacts"][2]["relativePath"] + .as_str() + .expect("provider artifact has a path") + .to_owned(); + let fixture_path = temporary.root.join(&relative_path); + let mut contents = + std::fs::read_to_string(&fixture_path).expect("provider fixture is readable"); + contents.push_str( + "\n", + ); + std::fs::write(&fixture_path, contents).expect("later terminal failure is written"); + refresh_artifact_bytes(&mut manifest, 2, &temporary.root); + + assert!( + !mutation_at_root_was_accepted("healthy-package", &temporary.root, &manifest, &expected,), + "an uncited later same-key terminal failure retained stale high success" + ); +} + +#[test] +fn admitted_distribution_point_producers_require_the_observed_role() { + let mut manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let mut expected = read_json("healthy-package", "expected.json").expect("expected loads"); + manifest["topology"]["rolesObserved"] = json!(["siteServer"]); + expected["roleAssessment"]["distributionPointObserved"] = json!(false); + + assert!( + !mutation_was_accepted("healthy-package", &manifest, &expected), + "DP-produced evidence retained high success while the DP role was not observed" + ); +} + +#[test] +fn distribution_point_observed_is_a_required_boolean() { + let manifest = + read_json("client-only-looking-request", "manifest.json").expect("manifest loads"); + let expected = + read_json("client-only-looking-request", "expected.json").expect("expected loads"); + let mut accepted = Vec::new(); + + let mut missing = expected.clone(); + missing["roleAssessment"] + .as_object_mut() + .expect("role assessment is an object") + .remove("distributionPointObserved"); + if mutation_was_accepted("client-only-looking-request", &manifest, &missing) { + accepted.push("missing"); + } + + for (shape, value) in [ + ("null", Value::Null), + ("string", json!("false")), + ("number", json!(0)), + ] { + let mut malformed = expected.clone(); + malformed["roleAssessment"]["distributionPointObserved"] = value; + if mutation_was_accepted("client-only-looking-request", &manifest, &malformed) { + accepted.push(shape); + } + } + + assert!( + accepted.is_empty(), + "distributionPointObserved accepted non-Boolean shapes: {accepted:?}" + ); +} + #[test] fn incomplete_transaction_gaps_are_bound_to_the_exact_distribution_point() { let mut manifest = read_json("incomplete", "manifest.json").expect("manifest loads"); From ee168ccfb05c926b95b8c0cef6d97bc53262da13 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 09:05:00 -0400 Subject: [PATCH 16/20] fix(sccm): close DP evidence and role gaps --- ...ver_distribution_point_fixture_contract.rs | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 3ff222b70..94e7c11c2 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -712,6 +712,11 @@ fn validate_manifest( "{artifact_id} has an uncatalogued source/producer/basename combination" )); } + if role != "client" && !roles.contains(&role) { + failures.push(format!( + "{artifact_id} producer role {role} is absent from rolesObserved" + )); + } let workflow_subject_handle = artifact["workflowSubjectHandle"].as_str(); let workflow_subject_basis = artifact["workflowSubjectBasis"].as_str(); if artifact["workflowSubjectRole"] != "distributionPoint" @@ -1285,6 +1290,17 @@ fn validate_expected( { failures.push("expected output infers role state from source coverage".to_owned()); } + let distribution_point_observed = match required_bool( + &expected["roleAssessment"], + "distributionPointObserved", + "roleAssessment", + ) { + Ok(value) => Some(value), + Err(error) => { + failures.push(error); + None + } + }; let expected_coverage = parsed .artifacts @@ -1994,6 +2010,17 @@ fn validate_expected( } } + let unconsumed_normalized_evidence = parsed + .evidence + .keys() + .filter(|key| !consumed_evidence.contains(*key)) + .collect::>(); + if !unconsumed_normalized_evidence.is_empty() { + failures.push(format!( + "normalized logical records lack an explicit transaction or source-local classification: {unconsumed_normalized_evidence:?}" + )); + } + let requests = match required_array(expected, "artifactRequests", "expected") { Ok(value) => value, Err(error) => { @@ -2089,8 +2116,7 @@ fn validate_expected( } if scenario == "absent-dp" - && (expected["roleAssessment"]["distributionPointObserved"] != true - || !transactions.is_empty()) + && (distribution_point_observed != Some(true) || !transactions.is_empty()) { failures.push("absent-dp must retain the observed role without a diagnosis".to_owned()); } @@ -2133,13 +2159,10 @@ fn validate_expected( } } - if manifest["topology"]["rolesObserved"] + let topology_distribution_point_observed = manifest["topology"]["rolesObserved"] .as_array() - .is_some_and(|roles| roles.iter().any(|role| role == "distributionPoint")) - != expected["roleAssessment"]["distributionPointObserved"] - .as_bool() - .unwrap_or(false) - { + .is_some_and(|roles| roles.iter().any(|role| role == "distributionPoint")); + if distribution_point_observed != Some(topology_distribution_point_observed) { failures.push("role assessment is not an exact topology projection".to_owned()); } From 44c2fad0b861afec06cf0ca753eece242fad0b36 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 09:30:44 -0400 Subject: [PATCH 17/20] test(sccm): reproduce stale DP outcome accepts --- ...ver_distribution_point_fixture_contract.rs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 94e7c11c2..67f94f72e 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -2279,6 +2279,17 @@ fn replace_fixture_text( .expect("temporary fixture mutation is written"); } +fn append_fixture_record(scenario_root: &std::path::Path, relative_path: &str, record: &str) { + let path = scenario_root.join(relative_path); + let mut contents = std::fs::read_to_string(&path).expect("temporary fixture is readable"); + if !contents.ends_with('\n') { + contents.push('\n'); + } + contents.push_str(record); + contents.push('\n'); + std::fs::write(&path, contents).expect("temporary fixture mutation is written"); +} + fn refresh_artifact_bytes( manifest: &mut Value, artifact_index: usize, @@ -3304,6 +3315,73 @@ fn terminal_success_is_bound_to_the_cited_serve_or_report_record() { ); } +#[test] +fn later_same_key_retry_invalidates_stale_high_success() { + let temporary = temporary_scenario("healthy-package"); + let mut manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let mut expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let relative_path = manifest["artifacts"][2]["relativePath"] + .as_str() + .expect("provider artifact has a path") + .to_owned(); + + append_fixture_record( + &temporary.root, + &relative_path, + r#""#, + ); + refresh_artifact_bytes(&mut manifest, 2, &temporary.root); + expected["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are an array") + .push(json!({ + "observationId": "07-report-retry", + "phase": "serveOrReport", + "disposition": "retrying", + "terminal": false, + "evidence": [{"artifactId": "dp-healthy-03-provider", "startLine": 4, "endLine": 4}] + })); + + assert!( + !mutation_at_root_was_accepted("healthy-package", &temporary.root, &manifest, &expected,), + "a later same-key retry retained stale high-confidence success" + ); +} + +#[test] +fn later_same_key_success_invalidates_stale_deferred_outcome() { + let temporary = temporary_scenario("transfer-retry"); + let mut manifest = read_json("transfer-retry", "manifest.json").expect("manifest loads"); + let mut expected = read_json("transfer-retry", "expected.json").expect("expected loads"); + let relative_path = manifest["artifacts"][1]["relativePath"] + .as_str() + .expect("transfer artifact has a path") + .to_owned(); + + append_fixture_record( + &temporary.root, + &relative_path, + r#""#, + ); + refresh_artifact_bytes(&mut manifest, 1, &temporary.root); + expected["transactions"][0]["lastSuccessfulPhase"] = json!("transfer"); + expected["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are an array") + .push(json!({ + "observationId": "04-transfer-succeeded", + "phase": "transfer", + "disposition": "succeeded", + "terminal": false, + "evidence": [{"artifactId": "dp-transfer-retry-02-pkgxfer", "startLine": 2, "endLine": 2}] + })); + + assert!( + !mutation_at_root_was_accepted("transfer-retry", &temporary.root, &manifest, &expected,), + "a later same-key success retained stale deferred classification" + ); +} + #[test] fn correlation_eligible_incomplete_output_requires_evidence_gaps_and_requests() { let manifest = read_json("incomplete", "manifest.json").expect("manifest loads"); From 4f8bd3b6e89ab208932446756ed6a29596f8da4c Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 09:34:53 -0400 Subject: [PATCH 18/20] fix(sccm): bind DP state to latest evidence --- .../tests/sccm_server_distribution_point_fixture_contract.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 67f94f72e..8262f383d 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -1473,6 +1473,7 @@ fn validate_expected( } let mut latest_success: Option = None; + let mut latest_outcome: Option<(&str, bool)> = None; let mut terminal_success = false; let mut terminal_success_phase = None; let mut terminal_failure = false; @@ -1635,6 +1636,7 @@ fn validate_expected( "{observation_id} uses an incoherent disposition/terminal pair" )), } + latest_outcome = Some((disposition, terminal)); } let computed_last_success = latest_success.map(|index| STATE_CHAIN[index]); @@ -1655,6 +1657,7 @@ fn validate_expected( match (state, classification) { ("succeeded", "success") if terminal_success + && latest_outcome == Some(("succeeded", true)) && terminal_success_phase == STATE_CHAIN .iter() @@ -1675,6 +1678,7 @@ fn validate_expected( && confidence_ceiling == "high" => {} ("deferred", "blockedOrDeferred") if terminal_deferred + && matches!(latest_outcome, Some(("deferred" | "retrying", false))) && !terminal_failure && !terminal_success && !cites_capped_evidence From 2e54a20faa244e5c429792127cc91d530d5155b7 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 10:01:24 -0400 Subject: [PATCH 19/20] test(sccm): reproduce equal-time DP ordering gaps --- ...ver_distribution_point_fixture_contract.rs | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index 8262f383d..dbb616e31 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -3386,6 +3386,225 @@ fn later_same_key_success_invalidates_stale_deferred_outcome() { ); } +#[test] +fn equal_utc_same_artifact_retry_cannot_be_reordered_before_terminal_success() { + let temporary = temporary_scenario("healthy-package"); + let mut manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let mut expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let relative_path = manifest["artifacts"][2]["relativePath"] + .as_str() + .expect("provider artifact has a path") + .to_owned(); + + append_fixture_record( + &temporary.root, + &relative_path, + r#""#, + ); + refresh_artifact_bytes(&mut manifest, 2, &temporary.root); + expected["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are an array") + .insert( + 5, + json!({ + "observationId": "05-report-retry", + "phase": "serveOrReport", + "disposition": "retrying", + "terminal": false, + "evidence": [{ + "artifactId": "dp-healthy-03-provider", + "startLine": 4, + "endLine": 4 + }] + }), + ); + + assert!( + !mutation_at_root_was_accepted("healthy-package", &temporary.root, &manifest, &expected), + "an observation ID reordered a later equal-UTC physical retry before stale high success" + ); +} + +#[test] +fn equal_utc_same_artifact_recovery_cannot_be_reordered_before_terminal_failure() { + let temporary = temporary_scenario("validation-failure"); + let mut manifest = read_json("validation-failure", "manifest.json").expect("manifest loads"); + let mut expected = read_json("validation-failure", "expected.json").expect("expected loads"); + let relative_path = manifest["artifacts"][2]["relativePath"] + .as_str() + .expect("provider artifact has a path") + .to_owned(); + + append_fixture_record( + &temporary.root, + &relative_path, + r#""#, + ); + refresh_artifact_bytes(&mut manifest, 2, &temporary.root); + expected["transactions"][0]["lastSuccessfulPhase"] = json!("validate"); + expected["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are an array") + .insert( + 3, + json!({ + "observationId": "03-validate-recovered", + "phase": "validate", + "disposition": "succeeded", + "terminal": false, + "evidence": [{ + "artifactId": "dp-validation-failure-03-provider", + "startLine": 2, + "endLine": 2 + }] + }), + ); + + assert!( + !mutation_at_root_was_accepted("validation-failure", &temporary.root, &manifest, &expected,), + "an observation ID reordered a later equal-UTC physical recovery before stale high failure" + ); +} + +#[test] +fn equal_utc_cross_artifact_outcomes_fail_closed_as_ambiguous() { + let temporary = temporary_scenario("serve-observed"); + let mut manifest = read_json("serve-observed", "manifest.json").expect("manifest loads"); + let mut expected = read_json("serve-observed", "expected.json").expect("expected loads"); + let relative_path = manifest["artifacts"][2]["relativePath"] + .as_str() + .expect("provider artifact has a path") + .to_owned(); + + append_fixture_record( + &temporary.root, + &relative_path, + r#""#, + ); + refresh_artifact_bytes(&mut manifest, 2, &temporary.root); + expected["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are an array") + .insert( + 5, + json!({ + "observationId": "05-serve-retry", + "phase": "serveOrReport", + "disposition": "retrying", + "terminal": false, + "evidence": [{ + "artifactId": "dp-serve-03-provider", + "startLine": 3, + "endLine": 3 + }] + }), + ); + + assert!( + !mutation_at_root_was_accepted("serve-observed", &temporary.root, &manifest, &expected), + "different artifacts at equal UTC used observation IDs to retain stale high success" + ); +} + +#[test] +fn equal_utc_cross_rotation_outcomes_fail_closed_as_ambiguous() { + let temporary = temporary_scenario("healthy-package"); + let mut manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let mut expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let current_relative_path = manifest["artifacts"][2]["relativePath"] + .as_str() + .expect("provider artifact has a path") + .to_owned(); + let rollback_relative_path = "evidence/server-dp-distribution/dp/lo_/SMSDPProv.log".to_owned(); + let rollback_fixture_path = temporary.root.join(&rollback_relative_path); + std::fs::create_dir_all( + rollback_fixture_path + .parent() + .expect("rollback fixture has a parent"), + ) + .expect("rollback fixture parent is created"); + std::fs::copy( + temporary.root.join(¤t_relative_path), + &rollback_fixture_path, + ) + .expect("current provider evidence is copied to the rollback artifact"); + manifest["artifacts"][2]["sanitizedSourcePath"] = + json!("SYNTHETIC://dp-root/Logs/SMSDPProv.lo_"); + manifest["artifacts"][2]["rotation"]["kind"] = json!("lo_"); + manifest["artifacts"][2]["relativePath"] = json!(rollback_relative_path); + refresh_artifact_bytes(&mut manifest, 2, &temporary.root); + + let current_record = r#""#; + std::fs::write( + temporary.root.join(¤t_relative_path), + format!("{current_record}\n"), + ) + .expect("current rotation retry fixture is written"); + let current_bytes = std::fs::metadata(temporary.root.join(¤t_relative_path)) + .expect("current rotation retry fixture is readable") + .len(); + manifest["artifacts"] + .as_array_mut() + .expect("artifacts are an array") + .push(json!({ + "artifactId": "dp-healthy-04-provider-current", + "sourceId": "server-dp-distribution", + "producerRole": "distributionPoint", + "producerHostHandle": "safe:dp:lab-dp-01", + "workflowSubjectRole": "distributionPoint", + "workflowSubjectHandle": "safe:dp:lab-dp-01", + "sourceKind": "ccmLog", + "originalBasename": "SMSDPProv.log", + "sanitizedSourcePath": "SYNTHETIC://dp-root/Logs/SMSDPProv.log", + "pathFingerprint": "synthetic:serve-provider", + "rotation": { + "kind": "current", + "lineageId": "healthy-provider", + "fragmentComplete": true + }, + "captureState": "captured", + "sourceVersion": "5.00.TEST.0001", + "collectedUtc": "2026-07-30T12:10:00Z", + "encoding": "utf-8", + "collectionLimit": { + "byteLimit": 4096, + "limitApplied": false + }, + "bytesCopied": current_bytes, + "relativePath": current_relative_path + })); + expected["coverage"] + .as_array_mut() + .expect("coverage is an array") + .push(json!({ + "artifactId": "dp-healthy-04-provider-current", + "state": "captured" + })); + expected["transactions"][0]["observations"] + .as_array_mut() + .expect("observations are an array") + .insert( + 5, + json!({ + "observationId": "05-report-retry", + "phase": "serveOrReport", + "disposition": "retrying", + "terminal": false, + "evidence": [{ + "artifactId": "dp-healthy-04-provider-current", + "startLine": 1, + "endLine": 1 + }] + }), + ); + + assert!( + !mutation_at_root_was_accepted("healthy-package", &temporary.root, &manifest, &expected), + "equal-UTC current/rollback outcomes used observation IDs to retain stale high success" + ); +} + #[test] fn correlation_eligible_incomplete_output_requires_evidence_gaps_and_requests() { let manifest = read_json("incomplete", "manifest.json").expect("manifest loads"); From b45c4b610d001f6f0540d722212643cdc66b0a4e Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 31 Jul 2026 10:03:24 -0400 Subject: [PATCH 20/20] fix(sccm): order equal-time DP evidence by provenance --- ...ver_distribution_point_fixture_contract.rs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs index dbb616e31..ae1f19957 100644 --- a/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs +++ b/crates/cmtraceopen-parser/tests/sccm_server_distribution_point_fixture_contract.rs @@ -1481,6 +1481,7 @@ fn validate_expected( let mut observed_after_terminal_failure = false; let mut cites_capped_evidence = false; let mut previous_utc = i64::MIN; + let mut previous_physical_order: Option<(String, u32)> = None; let mut previous_phase = 0usize; for observation in observations { let observation_id = @@ -1613,7 +1614,30 @@ fn validate_expected( "{transaction_id} evidence is not ordered by normalized UTC provenance" )); } + let current_physical_order = record + .reference + .line_start + .zip(record.reference.line_end) + .map(|(line_start, line_end)| { + (record.reference.artifact_id.as_str(), line_start, line_end) + }); + if utc == previous_utc + && previous_physical_order.as_ref().is_some_and( + |(previous_artifact_id, previous_line_end)| { + current_physical_order.is_none_or(|(artifact_id, line_start, _)| { + artifact_id != previous_artifact_id + || line_start <= *previous_line_end + }) + }, + ) + { + failures.push(format!( + "{transaction_id} equal-UTC evidence lacks immutable same-artifact line order" + )); + } previous_utc = utc; + previous_physical_order = current_physical_order + .map(|(artifact_id, _, line_end)| (artifact_id.to_owned(), line_end)); } if terminal_failure { observed_after_terminal_failure = true; @@ -3467,6 +3491,30 @@ fn equal_utc_same_artifact_recovery_cannot_be_reordered_before_terminal_failure( ); } +#[test] +fn equal_utc_same_artifact_forward_line_order_remains_usable() { + let temporary = temporary_scenario("healthy-package"); + let mut manifest = read_json("healthy-package", "manifest.json").expect("manifest loads"); + let expected = read_json("healthy-package", "expected.json").expect("expected loads"); + let relative_path = manifest["artifacts"][2]["relativePath"] + .as_str() + .expect("provider artifact has a path") + .to_owned(); + + replace_fixture_text( + &temporary.root, + &relative_path, + r#"