harness-everything 0.38.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +79 -63
  4. package/CHANGELOG.md +32 -0
  5. package/benchmarks/results/reviews/pr353-r6-2026-10-22/README.md +76 -0
  6. package/benchmarks/results/reviews/pr353-r6-2026-10-22/a-reviews-b.md +191 -0
  7. package/benchmarks/results/reviews/pr353-r6-2026-10-22/b-reviews-a.md +100 -0
  8. package/benchmarks/results/reviews/pr353-r6-2026-10-22/baseline-probes.log +47 -0
  9. package/benchmarks/results/reviews/pr353-r6-2026-10-22/bom-red.log +85 -0
  10. package/benchmarks/results/reviews/pr353-r6-2026-10-22/bom-transition.txt +25 -0
  11. package/benchmarks/results/reviews/pr353-r6-2026-10-22/coordinator-probes.txt +84 -0
  12. package/benchmarks/results/reviews/pr353-r6-2026-10-22/cross-suite.txt +18 -0
  13. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-bom.log +3 -0
  14. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-consistency.log.gz +0 -0
  15. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-core.log +198 -0
  16. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-probes.log +27 -0
  17. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-producer.log +83 -0
  18. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-review.md +89 -0
  19. package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-transitions.log +5 -0
  20. package/benchmarks/results/reviews/pr353-r6-2026-10-22/implementation-a.md +166 -0
  21. package/benchmarks/results/reviews/pr353-r6-2026-10-22/implementation-b.md +264 -0
  22. package/benchmarks/results/reviews/pr353-r6-2026-10-22/logs.sha256 +15 -0
  23. package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-a-probe.log +62 -0
  24. package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-a-probe.txt +313 -0
  25. package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-b-probe.log +51 -0
  26. package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-b-probe.txt +94 -0
  27. package/benchmarks/results/reviews/pr353-r6-2026-10-22/semantic-transitions.txt +25 -0
  28. package/benchmarks/results/reviews/pr353-r6-2026-10-22/source.sha256 +9 -0
  29. package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-a-on-b-core.log +201 -0
  30. package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-a-on-b-producer.log +82 -0
  31. package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-b-on-a-core.log +197 -0
  32. package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-b-on-a-producer.log +82 -0
  33. package/benchmarks/results/reviews/pr353-r6-2026-10-22/verify-all.log.gz +0 -0
  34. package/benchmarks/results/reviews/pr353-r6-2026-10-22/verify-gate-summary.json +22 -0
  35. package/ci/fixtures/doc-obligations/reconciliation-s2a/harness.js +489 -0
  36. package/ci/fixtures/doc-obligations/reconciliation-s2a/vectors.json +617 -0
  37. package/ci/mechanism-325-3b-s2a-core.test.js +840 -0
  38. package/ci/mechanism-325-3b-s2a-producer.test.js +564 -0
  39. package/code-review/SKILL.md +3 -3
  40. package/code-review/references/review-playbook.md +4 -0
  41. package/code-review/templates/finding.template.md +6 -0
  42. package/docs/doc-workflow-reconciliation.md +357 -0
  43. package/evals/zoom-out/tasks/negative-first-edge-case-review.yaml +16 -0
  44. package/evals/zoom-out/tasks/positive-repair-loop.yaml +15 -0
  45. package/harness-everything/SKILL.md +1 -1
  46. package/harness-everything/scripts/project-docs-resolver.js +42 -8
  47. package/harness-everything/scripts/routing-keywords.json +4 -0
  48. package/hooks/scripts/lib/doc-reconciliation-core.js +280 -0
  49. package/hooks/scripts/lib/doc-reconciliation-events.js +141 -0
  50. package/hooks/scripts/lib/doc-reconciliation-producer.js +206 -0
  51. package/multi-agent-workspace/SKILL.md +1 -1
  52. package/multi-agent-workspace/scripts/project-docs-resolver.js +42 -8
  53. package/opencode-plugin/plugin.json +1 -1
  54. package/package.json +2 -1
  55. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  56. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-core.js +280 -0
  57. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-events.js +141 -0
  58. package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-producer.js +206 -0
  59. package/plugins/harness-everything/plugin.json +1 -1
  60. package/plugins/harness-everything/skills/code-review/SKILL.md +3 -3
  61. package/plugins/harness-everything/skills/code-review/references/review-playbook.md +4 -0
  62. package/plugins/harness-everything/skills/code-review/templates/finding.template.md +6 -0
  63. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  64. package/plugins/harness-everything/skills/harness-everything/scripts/project-docs-resolver.js +42 -8
  65. package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +4 -0
  66. package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +1 -1
  67. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/project-docs-resolver.js +42 -8
  68. package/plugins/harness-everything/skills/to-spec/SKILL.md +1 -1
  69. package/plugins/harness-everything/skills/to-spec/scripts/project-docs-resolver.js +42 -8
  70. package/plugins/harness-everything/skills/zoom-out/SKILL.md +4 -3
  71. package/plugins/harness-everything/skills/zoom-out/references/circuit-breaker.md +7 -0
  72. package/plugins/harness-everything/skills/zoom-out/templates/zoom-out-report.template.md +4 -2
  73. package/sbom.cdx.json +7 -7
  74. package/scripts/errors-sync.js +90 -0
  75. package/to-spec/SKILL.md +1 -1
  76. package/to-spec/scripts/project-docs-resolver.js +42 -8
  77. package/zoom-out/SKILL.md +4 -3
  78. package/zoom-out/references/circuit-breaker.md +7 -0
  79. package/zoom-out/templates/zoom-out-report.template.md +4 -2
@@ -0,0 +1,83 @@
1
+ PASS snapshot:required-fields-present [source]
2
+ PASS snapshot:required-fields-present [packaged]
3
+ PASS snapshot:identity-tuple-types-and-digest-equalities [source]
4
+ PASS snapshot:identity-tuple-types-and-digest-equalities [packaged]
5
+ PASS snapshot:obligations-equal-unchanged-derive-api-on-captured-bytes [source]
6
+ PASS snapshot:obligations-equal-unchanged-derive-api-on-captured-bytes [packaged]
7
+ PASS snapshot:derivation-reasons-cover-every-obligation [source]
8
+ PASS snapshot:derivation-reasons-cover-every-obligation [packaged]
9
+ PASS A04:docs-prd-root-positive-produces-ready-snapshot [source]
10
+ PASS A04:docs-prd-root-positive-produces-ready-snapshot [packaged]
11
+ PASS A04:custom-prd-root-unavailable-with-no-fallback [source]
12
+ PASS A04:custom-prd-root-unavailable-with-no-fallback [packaged]
13
+ PASS A05:whole-candidate-identity-is-not-the-prd-set-digest [source]
14
+ PASS A05:whole-candidate-identity-is-not-the-prd-set-digest [packaged]
15
+ PASS A05:deletion-and-rename-endpoints-are-both-bound [source]
16
+ PASS A05:deletion-and-rename-endpoints-are-both-bound [packaged]
17
+ PASS A05:deleted-tombstones-omitted-from-checker-artifacts [source]
18
+ PASS A05:deleted-tombstones-omitted-from-checker-artifacts [packaged]
19
+ PASS A05:deleted-path-differs-from-empty-file-in-candidate-digest [source]
20
+ PASS A05:deleted-path-differs-from-empty-file-in-candidate-digest [packaged]
21
+ PASS framing:path-boundary-concatenation-does-not-collide [source]
22
+ PASS framing:path-boundary-concatenation-does-not-collide [packaged]
23
+ PASS framing:mode-drift-changes-candidate-digest [source]
24
+ PASS framing:mode-drift-changes-candidate-digest [packaged]
25
+ PASS A09:pinned-baseline-remains-after-fixture-commit [source]
26
+ PASS A09:pinned-baseline-remains-after-fixture-commit [packaged]
27
+ PASS A09:head-anchored-capture-has-a-different-baseline-identity [source]
28
+ PASS A09:head-anchored-capture-has-a-different-baseline-identity [packaged]
29
+ PASS A09:rename-endpoints-stable-across-repeated-captures [source]
30
+ PASS A09:rename-endpoints-stable-across-repeated-captures [packaged]
31
+ PASS R2d-P1:reference-only-provider-context-is-baseline-unavailable [source]
32
+ PASS R2d-P1:reference-only-provider-context-is-baseline-unavailable [packaged]
33
+ PASS R2d-P1:input-baseline-re-anchored-to-moved-head-is-never-ready [source]
34
+ PASS R2d-P1:input-baseline-re-anchored-to-moved-head-is-never-ready [packaged]
35
+ PASS R2d-P1:input-baseline-equal-to-the-provider-pin-stays-ready-after-head-moved [source]
36
+ PASS R2d-P1:input-baseline-equal-to-the-provider-pin-stays-ready-after-head-moved [packaged]
37
+ PASS R2d-P2a:nested-scope-policy-status-is-a-semantic-change [source]
38
+ PASS R2d-P2a:nested-scope-policy-status-is-a-semantic-change [packaged]
39
+ PASS R2d-P2a:nested-requirement-and-binding-semantics-change-the-digest [source]
40
+ PASS R2d-P2a:nested-requirement-and-binding-semantics-change-the-digest [packaged]
41
+ PASS A10:missing-baseline-commit-yields-baseline-unavailable [source]
42
+ PASS A10:missing-baseline-commit-yields-baseline-unavailable [packaged]
43
+ PASS A10:non-repository-baseline-root-yields-baseline-unavailable [source]
44
+ PASS A10:non-repository-baseline-root-yields-baseline-unavailable [packaged]
45
+ PASS A10:normal-git-capture-succeeds [source]
46
+ PASS A10:normal-git-capture-succeeds [packaged]
47
+ PASS A11:ignored-governed-path-included-or-unavailable-never-silent [source]
48
+ PASS A11:ignored-governed-path-included-or-unavailable-never-silent [packaged]
49
+ PASS A11:index-removed-ignored-file-included-or-unavailable [source]
50
+ PASS A11:index-removed-ignored-file-included-or-unavailable [packaged]
51
+ PASS A11:ignored-irrelevant-control-has-no-new-must-and-a-justified-exclusion [source]
52
+ PASS A11:ignored-irrelevant-control-has-no-new-must-and-a-justified-exclusion [packaged]
53
+ PASS A03:captured-resolution-equals-existing-resolver-project-config [source]
54
+ PASS A03:captured-resolution-equals-existing-resolver-project-config [packaged]
55
+ PASS A03:project-overrides-host-positive-equals-existing-resolver [source]
56
+ PASS A03:project-overrides-host-positive-equals-existing-resolver [packaged]
57
+ PASS A03:equal-host-positive-equals-existing-resolver [source]
58
+ PASS A03:equal-host-positive-equals-existing-resolver [packaged]
59
+ PASS A03:capture-binds-exact-config-bytes-and-selection [source]
60
+ PASS A03:capture-binds-exact-config-bytes-and-selection [packaged]
61
+ PASS A03:absent-project-config-yields-config-unavailable [source]
62
+ PASS A03:absent-project-config-yields-config-unavailable [packaged]
63
+ PASS purity:producer-capture-writes-nothing-spawns-only-git [source]
64
+ PASS purity:producer-capture-writes-nothing-spawns-only-git [packaged]
65
+ PASS R3-P2:unchanged-binary-asset-captures-without-changing-applicability [source]
66
+ PASS R3-P2:unchanged-binary-asset-captures-without-changing-applicability [packaged]
67
+ PASS R3-P2:changed-binary-asset-changes-the-candidate-digest-without-changing-applicability [source]
68
+ PASS R3-P2:changed-binary-asset-changes-the-candidate-digest-without-changing-applicability [packaged]
69
+ PASS R3-P2:undecodable-governed-document-record-fails-closed [source]
70
+ PASS R3-P2:undecodable-governed-document-record-fails-closed [packaged]
71
+ PASS R3-P2:undecodable-derived-output-record-fails-closed [source]
72
+ PASS R3-P2:undecodable-derived-output-record-fails-closed [packaged]
73
+ PASS R3-P2:undecodable-code-input-record-fails-closed [source]
74
+ PASS R3-P2:undecodable-code-input-record-fails-closed [packaged]
75
+ PASS R3-P2:undecodable-test-input-record-fails-closed [source]
76
+ PASS R3-P2:undecodable-test-input-record-fails-closed [packaged]
77
+ PASS R3-P2:undecodable-frontmatter-plain-fails-closed [source]
78
+ PASS R3-P2:undecodable-frontmatter-plain-fails-closed [packaged]
79
+ PASS R3-P2:undecodable-frontmatter-bom-fails-closed [source]
80
+ PASS R3-P2:undecodable-frontmatter-bom-fails-closed [packaged]
81
+
82
+ S2a producer summary: 80 PASS, 0 FAIL, 0 INFRA across 80 assertion-legs
83
+ S2a producer suite: all assertions PASS.
@@ -0,0 +1,89 @@
1
+ # Final integration review — PR #353 (selected tree)
2
+
3
+ Scope: **read-only**, tightly scoped to the selected tree `/Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/fix-353`
4
+ compared against frozen A `/Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-a`.
5
+ No file was edited, no test suite or gate was executed, no background command was launched, no scope was widened.
6
+
7
+ ## Verdict
8
+
9
+ **ACCEPT — no blocker found.** The only functional delta is the BOM-corrected frontmatter classifier, it
10
+ agrees with the fatal `TextDecoder` leading-BOM semantics, and the new producer regressions pin it.
11
+ The two known non-blocking items (malformed-record diagnostic drift, mirrored-rules maintenance risk)
12
+ are unchanged and remain documented. **No PR-readiness claim is made here; the owner decides.**
13
+
14
+ ## What was compared (read-only)
15
+
16
+ `diff -u` of every file in the two worktrees plus `git status --porcelain -uall` and `shasum -a 256`:
17
+
18
+ | File | Delta vs frozen A |
19
+ | --- | --- |
20
+ | `hooks/scripts/lib/doc-reconciliation-core.js` | `opensFrontmatter` only, plus the rule comment: `subarray(0,8).toString('latin1')` → `subarray(0,11).toString('utf8').replace(/^\uFEFF/,'')`; comment gains "Match TextDecoder's BOM handling" |
21
+ | `hooks/scripts/lib/doc-reconciliation-core.js` (rule comment) | wording tightened + `// shortcut: mirrors derive path rules, update them together until a shared classifier is exposed.` |
22
+ | `hooks/scripts/lib/doc-reconciliation-producer.js` | **identical** to frozen A |
23
+ | `hooks/scripts/lib/doc-reconciliation-events.js` | **identical** to frozen A |
24
+ | `plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-{core,producer}.js` | regenerated copies of the same deltas; SHA-256 identical to the canonical sources (`b4345651…`, `f8f5683f…`); events copy identical (`5fd2808a…`) |
25
+ | `ci/mechanism-325-3b-s2a-producer.test.js` | +2 assertions: `R3-P2:undecodable-frontmatter-plain-fails-closed`, `R3-P2:undecodable-frontmatter-bom-fails-closed` (loop over prefix `''` / `'\uFEFF'`, writing `assets/role.dat` = prefix + `---\nrole: prd\n---\n` + binary bytes) |
26
+ | `ci/mechanism-325-3b-s2a-core.test.js`, `ci/fixtures/.../harness.js`, `CHANGELOG.md` | **identical** to frozen A |
27
+ | `docs/doc-workflow-reconciliation.md` | wording only: adds "(including one after a leading UTF-8 BOM)"; replaces "a binary asset never changes document applicability" with "irrelevant binary assets remain usable" |
28
+
29
+ No public derive/checker API, identity/digest schema, S1 runtime, workflow wiring, version or dependency
30
+ changed. Scope is exactly the stated delta — **source-traced**.
31
+
32
+ ## Findings
33
+
34
+ 1. **The BOM classifier agrees with the fatal `TextDecoder` leading-BOM semantics** — *source-traced*.
35
+ The derive contract reads a file through `TextDecoder('utf-8', {fatal:true})`, whose WHATWG UTF-8 decoder
36
+ removes a leading `EF BB BF` from the byte stream, so the decoded text starts at the byte after the BOM.
37
+ The classifier reproduces exactly that: `Buffer.toString('utf8')` keeps U+FEFF as a character (it does
38
+ **not** strip the BOM), so the explicit `replace(/^\uFEFF/, '')` restores the decoder's behaviour; a
39
+ leading `EF BB BF` is by definition the BOM, so the classifier never strips something the decoder would
40
+ keep. The 11-byte window covers the longest relevant first line — BOM(3) + `---`(3) + CRLF(2) = 8 bytes
41
+ — and a longer first line cannot be `---`. A truncated BOM (`EF`, `EF BB`) decodes to U+FFFD in both
42
+ decoders (neither skips bytes), so the classifier's `false` matches the decoder's "no frontmatter".
43
+ The trailing-`\r` strip mirrors `parseFrontmatter`'s own rule exactly.
44
+
45
+ 2. **The new tests pin the fix, and the RED arithmetic confirms the RED was the BOM case** — *source-traced
46
+ + coordinator-executed*. `assets/role.dat` is at a path no config glob classifies, so only the frontmatter
47
+ rule can catch it; the frozen latin1 classifier saw `'\u00EF\u00BB\u00BF---'` and returned `false`, i.e.
48
+ it dropped a document-semantic record — exactly the defect the coordinator's independent BOM transition
49
+ probe reported for both candidates. The frozen tree's plain variant passed and only the BOM variant
50
+ failed, which is what the reported **78 PASS / 2 FAIL** (one failing assertion-leg per leg) and
51
+ **80 / 0** GREEN over 80 legs imply; the final classifier returns `true` for both variants
52
+ (`'---'` and `'\uFEFF---\n…'` → stripped → `'---'`). The tests fail loudly if the BOM handling regresses
53
+ (the producer would become `ready` and the assertion `undecodable frontmatter must not disappear from
54
+ semantic inputs` would fire).
55
+
56
+ 3. **The change is confined to the classifier; no behaviour elsewhere moved** — *source-traced*.
57
+ `semanticRelevance`, `deriveFiles`, `originBound`, `configBytesBound`, the identity framing and the
58
+ producer/event modules are untouched, so the previously accepted P1 binding, the binary byte identity
59
+ (`candidateDigest`/`baselineDigest` framing), producer/core parity and the fail-closed reason codes are
60
+ unchanged. Packaged copies are byte-identical to the canonical files.
61
+
62
+ 4. **The docs delta is wording-only and removes an over-claim** — *source-traced*. The frozen sentence
63
+ "a binary asset never changes document applicability" was inaccurate (a *relevant* binary asset does
64
+ change applicability — it fails closed); the replacement "irrelevant binary assets remain usable while
65
+ the identity still covers their bytes" is the accurate statement, and the BOM case is now named in the
66
+ contract text. No normative claim was weakened.
67
+
68
+ 5. **The shortcut comment follows the repository convention** — *source-traced*. It states the limit
69
+ (the mirrored derive-contract path rules) and the upgrade condition ("until a shared classifier is
70
+ exposed"), matching the `shortcut: <limit>, <when to upgrade>` form. It also names the real risk my
71
+ earlier cross-review raised, so the drift exposure is now visible in the code itself.
72
+
73
+ 6. **The two known non-blocking items are unchanged and remain non-blocking** — *source-traced*.
74
+ (a) *Malformed-record diagnostic drift*: the core catch still maps the plain `Error('inventory-unsupported')`
75
+ thrown by the shared `bytes()` helper to `inventory-unsupported` where the base reported `invalid-input`;
76
+ credit is withheld either way because any diagnostic makes `reconcileDocResults` return before the
77
+ eligibility branch. (b) *Mirrored-rules maintenance risk*: `CODE_EXTENSION`/`TEST_PATH`/glob semantics are
78
+ still mirrored inside the reconciliation module; the drift guard remains a follow-up, now documented
79
+ in-code. Neither is a blocker for this change, which does not touch that surface.
80
+
81
+ ## Not assessed in this review
82
+
83
+ The coordinator's executed evidence was taken as given (32 independent probes pass; `verify:all`
84
+ 13 PASS / 0 FAIL / 3 UNCHECKED; consistency rerun PASS; producer RED 78/2 → GREEN 80/0; core 195/0).
85
+ This review executed no tests, gates or suites, made no file edits, and did not examine live-host
86
+ provenance, the S1 runtime, workflow wiring or E1 readiness — all outside S2a. PR readiness is not
87
+ certified here.
88
+
89
+ STATUS: DONE
@@ -0,0 +1,5 @@
1
+ PASS source frontmatter unavailable
2
+ PASS source req-test unavailable
3
+ PASS packaged frontmatter unavailable
4
+ PASS packaged req-test unavailable
5
+ {"passed":4,"failed":0}
@@ -0,0 +1,166 @@
1
+ # PR #353 — Leg A implementation report
2
+
3
+ Worktree: `/Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-a`
4
+ Base: `59421f41a8ce2b00250c96634a4a43ae0ba782bf`
5
+ Node: v24.14.1 (`/Users/daniel.chang/.nvm/versions/node/v24.14.1/bin/node`), TMPDIR isolated to
6
+ `/tmp/harness-353-r6-a/legA/tmp`.
7
+ Evidence dir: `/tmp/harness-353-r6-a/legA` (logs in `legA/logs`).
8
+ No git/gh write commands were run; the coordinator owns git, packaging upload and PR readiness.
9
+
10
+ ## Changed files (write-set only)
11
+
12
+ | File | Change |
13
+ | --- | --- |
14
+ | `hooks/scripts/lib/doc-reconciliation-core.js` | P1: `originBound()` binds the captured origin identity to `snapshot.identity`, the captured plan and the selected active host; `configBytesBound()` binds the captured config bytes to the captured inventory. P2: `deriveFiles()` converts only decodable records and fails closed for records the derive contract can observe; `semanticRelevance()` + `opensFrontmatter()`; coded error mapping in `validateSnapshot`. |
15
+ | `hooks/scripts/lib/doc-reconciliation-producer.js` | P2: the producer uses the shared `U.deriveFiles()` with the captured config (identical handling to the core re-derivation). |
16
+ | `plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-{core,producer}.js` | Generated by `npm run plugin:sync` (byte-identical to the canonical files). |
17
+ | `ci/mechanism-325-3b-s2a-core.test.js` | P1 identity/plan/host/config-link regressions; P2 binary-record regressions. |
18
+ | `ci/mechanism-325-3b-s2a-producer.test.js` | P2 binary capture and fail-closed regressions. |
19
+ | `ci/fixtures/doc-obligations/reconciliation-s2a/harness.js` | Fixture-only binary support: `writeBinary()`, `makeRepo(base, binary)`, `BINARY_BYTES`, `BINARY_BYTES_CHANGED`. |
20
+ | `docs/doc-workflow-reconciliation.md` | S2a contract text for the origin-identity binding and the binary-record rule. |
21
+ | `CHANGELOG.md` | Two new topmost `[Unreleased]` entries. |
22
+
23
+ `hooks/scripts/lib/doc-reconciliation-events.js` was **not** changed: it already compares the provider
24
+ context with the captured origin context and calls `validateSnapshot`, so it inherits the P1 binding.
25
+ No public derive/checker API, identity/digest schema, S1 runtime, workflow wiring, version or dependency
26
+ changed. No scratch `.js` file was left in the repo (`git status` shows no untracked files).
27
+
28
+ ## Design choices
29
+
30
+ ### P1 — the captured origin identity must be the snapshot identity
31
+
32
+ `validateSnapshot()` now calls `originBound(snapshot)` (new, pure):
33
+
34
+ 1. `originContext.{workspaceId,sessionId,workflowId,workflowRevision,taskId}` must equal
35
+ `identity.{...}` — the five fields of the origin-correlated identity.
36
+ 2. The captured plan must agree with that identity: `taskSemantics.workflow.{workflowId,
37
+ workflowRevision,sessionId}`, `taskSemantics.task.taskId`, `taskSemantics.prdRoot`,
38
+ `taskSemantics.configDigest`.
39
+ 3. The captured `originContext.activeHost` must equal the host the captured config was selected for
40
+ (`config.capture.activeHost`); the comparison is skipped only when neither side represents a host.
41
+
42
+ This closes the disconnect: events compare provider↔captured origin context and event identity↔
43
+ `snapshot.identity`, so binding the captured origin context to the identity connects the two halves. A
44
+ joint substitution of the captured origin context and the provider context now yields
45
+ `identity-mismatch` in **both** `verification` and `review` mode, in the fresh-acceptance path and in the
46
+ cached-acceptance path (the cached acceptance is re-validated against the snapshot on every core call, so
47
+ it cannot bypass the invariant).
48
+
49
+ Adjacent link closed in the same class: `configBytesBound()` requires the captured config source bytes to
50
+ be the bytes the captured inventory holds for the same path — the producer's own rule
51
+ (`doc-reconciliation-producer.js`), which the core previously did not re-check. A substituted config
52
+ digest is `config-capture-mismatch`.
53
+
54
+ ### P2 — a whole-project inventory may contain binary records
55
+
56
+ * Stored bytes are untouched: every record's bytes stay in the `candidateDigest`/`baselineDigest`
57
+ framing, so a changed binary asset changes `candidateDigest`.
58
+ * Semantic derivation needs decoded text. A record whose bytes are not valid UTF-8 is **omitted** from
59
+ derivation only when the derive contract cannot observe it, and **fails closed**
60
+ (`inventory-unsupported`) when it can.
61
+ * Relevance is taken from the derive contract's own surface, not from a binary-extension allowlist:
62
+ the configured `governed`/`legacy` globs, the `derived` output/source globs, the derive contract's
63
+ code-extension and REQ-test path rules (`doc-obligations.js` `CODE_EXTENSION`, `TEST_PATH`), plus any
64
+ record that opens a frontmatter block (the derive contract reads the frontmatter of every captured
65
+ file and treats a frontmatter `role` as governed).
66
+ * Producer and core re-derivation share one implementation (`_internals.deriveFiles`), so both handle
67
+ binary records identically.
68
+ * The lifecycle reader, its byte-conversion rule (`workingStoredBytes`: a NUL byte means no text
69
+ conversion) and the producer's explicit `MAX_FILES`/`MAX_BYTES` budgets were not altered; no public
70
+ lifecycle helper behavior changed.
71
+
72
+ ## RED / GREEN evidence
73
+
74
+ | Stage | Command | Result | Log |
75
+ | --- | --- | --- | --- |
76
+ | RED core (tests first, production unchanged) | `node ci/mechanism-325-3b-s2a-core.test.js` | **175 PASS, 18 FAIL, 0 INFRA** / 193 assertion-legs | `legA/logs/red-core.log` |
77
+ | RED producer | `node ci/mechanism-325-3b-s2a-producer.test.js` | **72 PASS, 4 FAIL, 0 INFRA** / 76 assertion-legs | `legA/logs/red-producer.log` |
78
+ | GREEN core (run 1) | same | **195 PASS, 0 FAIL, 0 INFRA** / 195 assertion-legs | `legA/logs/final-core-run1.log` |
79
+ | GREEN core (run 2, identical input) | same | **195 PASS, 0 FAIL, 0 INFRA**, assertion lines byte-identical to run 1 | `legA/logs/final-core-run2.log` |
80
+ | GREEN producer (run 1) | same | **76 PASS, 0 FAIL, 0 INFRA** / 76 assertion-legs | `legA/logs/final-producer-run1.log` |
81
+ | GREEN producer (run 2, identical input) | same | **76 PASS, 0 FAIL, 0 INFRA**, assertion lines byte-identical to run 1 | `legA/logs/final-producer-run2.log` |
82
+
83
+ Actual RED failure reasons (from the unchanged production code):
84
+
85
+ * Core — `12 × verification: a substituted origin identity must not earn credit`
86
+ (joint substitution of `workspaceId`, `sessionId`, `workflowId`, `workflowRevision`, `taskId` and
87
+ `activeHost` in the captured origin context **and** the provider context earned credit).
88
+ * Core — `2 × verification: a substituted plan and identity must not earn credit`.
89
+ * Core — `2 × capture with an irrelevant binary record: no decision` (the producer was unavailable for a
90
+ tracked binary asset).
91
+ * Core — `2 × the core must fail closed on a relevant undecodable record: [{"code":"invalid-input",…}]`
92
+ (the core had no relevance-specific reason).
93
+ * Producer — `4 × capture with an unchanged binary asset: state unavailable with codes
94
+ ["inventory-unsupported"]`.
95
+
96
+ Non-vacuity probe for the added config↔inventory link (the check was temporarily disabled in the
97
+ production file, then restored byte-for-byte): the new assertion failed with
98
+ `config bytes the captured inventory contradicts must not earn credit` on both legs
99
+ (`legA/logs/red-config-link-3.log`), i.e. the substituted-config snapshot was **eligible** without the
100
+ new check. After restoring the file: 195 PASS / 0 FAIL (`legA/logs/green-core-3.log`).
101
+
102
+ ## Tests executed
103
+
104
+ * `node ci/mechanism-325-3b-s2a-core.test.js` — both legs (canonical + packaged), twice.
105
+ * `node ci/mechanism-325-3b-s2a-producer.test.js` — both legs (canonical + packaged), twice.
106
+ * `npm run plugin:sync` — regenerated `plugins/harness-everything/hooks/scripts/lib/`; the packaged core
107
+ and producer are byte-identical to the canonical files (sha256 verified).
108
+ * `npm run test:consistency` — PASS (`legA/logs/final-gate-consistency.log`).
109
+ * `npm run test:plugin:openai` — PASS (`legA/logs/final-gate-plugin-openai.log`).
110
+ * `npm run test:portability` — PASS (`legA/logs/final-gate-portability.log`).
111
+
112
+ Not run (coordinator owns the full gates): `npm test`, `npm run verify:all`,
113
+ `ci/mechanism-test.js`, `npm run test:mechanism`.
114
+
115
+ ## Source-anchored findings
116
+
117
+ * **executed** — On base `59421f4`, a joint substitution of the captured origin context and the provider
118
+ context (identity, events and cached acceptance left at the original tuple) earned
119
+ `eligible` credit in verification mode: the provider half and the event half of the trust contract were
120
+ disconnected (`legA/logs/red-core.log`).
121
+ * **executed** — On base `59421f4`, a normal tracked binary asset (`assets/logo.png`, invalid UTF-8)
122
+ made the producer return `inventory-unsupported`, so the whole-project capture was unusable for an
123
+ ordinary repository (`legA/logs/red-producer.log`).
124
+ * **executed** — With the P2 fix, a changed binary asset changes `candidateDigest` while the MUST set
125
+ stays the normative set; an unchanged binary asset leaves the capture usable and the core's
126
+ re-derivation accepts the snapshot.
127
+ * **source-traced** — The derive contract's applicability surface is governed/legacy globs, derived
128
+ output/source globs, `CODE_EXTENSION`, `TEST_PATH` and frontmatter `role`
129
+ (`harness-everything/scripts/doc-obligations.js`: `CODE_EXTENSION`/`TEST_PATH` lines 17-18,
130
+ `governedEntry`/`matchesAny` lines 92-95, `roleAt`/`isGoverned` lines 1055-1068,
131
+ `derivedEntryAffected` lines 1313-1322). These are the paths whose bytes can change applicability and
132
+ therefore the relevance surface used by the fix.
133
+ * **source-traced** — The producer already required the captured config bytes to equal the captured
134
+ inventory bytes for the same path (`doc-reconciliation-producer.js`, config-source loop); the core
135
+ re-checks the same rule now.
136
+ * **source-traced** — The lifecycle reader's conversion rule and the producer's explicit capture budgets
137
+ (`MAX_FILES`, `MAX_BYTES`) are unchanged; no public lifecycle helper was touched.
138
+ * **inconclusive** — Whether a production origin provider context always carries an `activeHost` is not
139
+ observable from this repository (the S2a fake provider carries one). The host check is skipped only
140
+ when neither the captured context nor the captured config represents a host.
141
+
142
+ ## Remaining risks
143
+
144
+ 1. The relevance rules mirror the derive contract's path rules inside the reconciliation module, because
145
+ `doc-obligations.js` is outside this write-set. A future change to the derive contract's code/test
146
+ path rules or glob semantics must be mirrored here. The duplication is two regexes plus the glob
147
+ semantics; the behaviour is pinned by the new tests.
148
+ 2. A record whose first line is exactly `---` fails closed even when it is an irrelevant binary asset.
149
+ This is deliberate (the derive contract observes such a file's frontmatter) but can turn an exotic
150
+ binary asset into `inventory-unsupported`.
151
+ 3. In the core, a relevant undecodable record is reported as `inventory-unsupported` and the decision is
152
+ `unresolved` (not `unavailable`). Credit is withheld either way; the reason code differs from the
153
+ producer's `state: "unavailable"`.
154
+ 4. The producer's whole-project walk still traverses `node_modules` and is bounded by the pre-existing
155
+ `MAX_FILES`/`MAX_BYTES` limits. That is unchanged pre-existing behavior and out of scope here.
156
+ 5. Evidence is deterministic mechanism evidence only: the origin provider is an explicit test fake, so
157
+ nothing here proves live-host provenance, reviewer independence in deployment, host loading or E1
158
+ readiness.
159
+
160
+ ## Handover
161
+
162
+ Leg A is complete and self-contained in its worktree. Nothing was committed, staged or pushed; the
163
+ coordinator owns git and PR readiness. This implementation is independent of leg B: it was developed
164
+ without reading the other worktree or its artifacts.
165
+
166
+ STATUS: DONE
@@ -0,0 +1,264 @@
1
+ # PR #353 — Implementation report (leg B)
2
+
3
+ Base: `59421f41a8ce2b00250c96634a4a43ae0ba782bf` (read-only `git show`/`git log` only; no git/gh write
4
+ commands were run, no commits, no worktree mutation, no remote messages).
5
+
6
+ Worktree: `/Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-b`
7
+ Evidence dir: `/tmp/harness-353-r6-b` (logs under `logs/`, scratch probes under `probe/`, both outside the repo)
8
+
9
+ ## Round 2 — coordinator defect (executed omission defect) and its fix
10
+
11
+ **Defect (executed, coordinator-found).** The coordinator's transition probe
12
+ (`/tmp/harness-353-r6-coordinator/semantic-transitions.js`, log `b-transitions.log`) failed all four
13
+ source/packaged cases. My first implementation probed relevance with
14
+ `base.undecodable[path] → same text + "\n"` only: it read the **baseline** record's lossy text and never the
15
+ candidate's. A capture whose baseline record is nonsemantic while the candidate record's bytes carry a
16
+ `role: prd` frontmatter or a REQ-citing test therefore looked irrelevant, and the candidate's document
17
+ meaning was silently dropped from the derivation. Two further weaknesses: a same-text modification cannot
18
+ show new/deleted test identity, and no addition/deletion transition was probed at all.
19
+
20
+ **Fix (minimal, in the shared helper).** `deriveCapturedFacts` now probes relevance through the unchanged
21
+ derivation with each undecodable record's **own** bytes and three transition witnesses:
22
+
23
+ | Witness | Baseline side | Candidate side | Shows |
24
+ | --- | --- | --- | --- |
25
+ | actual | the record's baseline lossy text (if present) | the record's candidate lossy text (if present) | the real transition of this capture, carrying the candidate's own semantics |
26
+ | addition | the record absent | the record's lossy text | new document / new REQ-citing test identity |
27
+ | deletion | the record's lossy text | the record absent | deleted document / deleted test identity |
28
+
29
+ If any witness changes the derived obligations (applicability **and** scope included), the record is
30
+ semantically relevant and the capture fails closed as `inventory-unsupported`; otherwise it is omitted from
31
+ the derivation inputs. The stored bytes of every record stay in the whole-project hash, so the inventory
32
+ byte identity is intact (`candidateDigest` still covers binary bytes; `baselineDigest` unchanged).
33
+
34
+ **Honest scope of the claim.** These witnesses are lossy-text probes of the unchanged derivation, not a
35
+ proof about the true bytes of an undecodable file. They establish that the unchanged derivation finds no
36
+ document meaning for the record under the actual, addition and deletion transitions; they do **not**
37
+ establish full decision equivalence for every possible undecodable byte sequence. Where the witnesses
38
+ disagree the implementation fails closed, which is the safe branch.
39
+
40
+ ### Round-2 evidence
41
+
42
+ | Suite | RED (v1 implementation, new tests) | GREEN (fixed) | Log |
43
+ | --- | --- | --- | --- |
44
+ | S2a core | **191 PASS, 2 FAIL, 0 INFRA** / 193 assertion-legs | **193 PASS, 0 FAIL, 0 INFRA** | `logs/RED2-core.log` → `logs/GREEN-core.log` |
45
+ | S2a producer | **74 PASS, 4 FAIL, 0 INFRA** / 78 assertion-legs | **78 PASS, 0 FAIL, 0 INFRA** | `logs/RED2-producer.log` → `logs/GREEN-producer.log` |
46
+
47
+ RED2 failure reason, identical on the `source` and `packaged` legs and equal to the coordinator's finding:
48
+
49
+ ```
50
+ FAIL R2h-P2:prd-frontmatter-binary-semantics-are-never-omitted — the candidate semantics of the binary record at assets/role.dat were omitted
51
+ FAIL R2h-P2:req-citing-test-binary-semantics-are-never-omitted — the candidate semantics of the binary record at tests/fixture.dat were omitted
52
+ FAIL R2h-P2:candidate-binary-semantics-never-earn-credit-while-omitted — the candidate semantics of the binary record at assets/role.dat were omitted
53
+ ```
54
+
55
+ Shipped regression tests (run on both legs by the S2a runner):
56
+
57
+ - `ci/mechanism-325-3b-s2a-producer.test.js` — `R2h-P2:prd-frontmatter-binary-semantics-are-never-omitted`
58
+ and `R2h-P2:req-citing-test-binary-semantics-are-never-omitted`: a capture is acceptable only if it fails
59
+ closed with `inventory-unsupported`, or if the derived obligations still carry the candidate's semantics
60
+ (`role: prd` in a MUST scope / a non-N/A `requirement-sync`).
61
+ - `ci/mechanism-325-3b-s2a-core.test.js` — `R2h-P2:candidate-binary-semantics-never-earn-credit-while-omitted`:
62
+ the same property through the full produce → accept → decide flow, and no credit without accepted evidence.
63
+
64
+ Coordinator probes re-executed read-only against the fixed implementation:
65
+
66
+ ```
67
+ node /tmp/harness-353-r6-coordinator/semantic-transitions.js <worktree> → 4 passed, 0 failed (all four now FAIL-CLOSED: state "unavailable")
68
+ node /tmp/harness-353-r6-coordinator/probes.js <worktree> → 26 passed, 0 failed
69
+ ```
70
+
71
+ Logs: `logs/GATE-coordinator-transitions.log`, `logs/GATE-coordinator-probes.log`.
72
+
73
+ ## Changed files (write-set respected)
74
+
75
+ | File | Change |
76
+ | --- | --- |
77
+ | `hooks/scripts/lib/doc-reconciliation-core.js` | P1 origin↔identity binding in `validateSnapshot`; P2 binary-safe shared derivation (`readRecords`, `decodeUtf8`, `deriveCapturedFacts`); `inventory-unsupported` mapped out of the validation catch |
78
+ | `hooks/scripts/lib/doc-reconciliation-producer.js` | Producer now derives through the shared `U.deriveCapturedFacts`; local `filesOf` and the now-unused derive import removed |
79
+ | `plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-core.js` | generated copy (`npm run plugin:sync`), byte-identical to source |
80
+ | `plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-producer.js` | generated copy (`npm run plugin:sync`), byte-identical to source |
81
+ | `ci/mechanism-325-3b-s2a-core.test.js` | 8 new P1/P2 assertions (7 round 1 + 1 round 2); `withVector` now returns the callback value |
82
+ | `ci/mechanism-325-3b-s2a-producer.test.js` | 7 new P2 assertions (5 round 1 + 2 round 2) |
83
+ | `ci/fixtures/doc-obligations/reconciliation-s2a/harness.js` | shared `BINARY_ASSET` / `BINARY_ASSET_CHANGED` byte fixtures (raw bytes, invalid UTF-8) |
84
+ | `docs/doc-workflow-reconciliation.md` | S2a contract: origin-identity binding paragraph; binary-record derivation paragraph |
85
+ | `CHANGELOG.md` | topmost `[Unreleased]` entry only |
86
+
87
+ Not changed (deliberately): `doc-reconciliation-events.js` needed no change — it already compares the
88
+ provider context with `snapshot.originContext` and calls `validateSnapshot`, so it inherits both fixes.
89
+ No public derive/checker API, identity/digest schema, S1 runtime, workflow wiring, version or dependency
90
+ was touched. No scratch `.js` file was left inside the repo (`git status --porcelain -uall` shows only the
91
+ nine modified files above).
92
+
93
+ ## P1 — the whole captured origin identity must agree with the hashed identity
94
+
95
+ **Finding (source-traced → executed).** `validateSnapshot` bound only the baseline pin
96
+ (`context.baseline.commit` / `context.baseline.root`); `acceptDocEvents` compared the provider context with
97
+ `snapshot.originContext`, and the event's `identity` was compared with `snapshot.identity`. The
98
+ `originContext ↔ identity` link was missing, so the two halves never met. `snapshot.originContext` is not
99
+ hashed by anything (`snapshotDigest` covers baseline/coverage/records, `configDigest` covers the config,
100
+ `taskScopeDigest` covers the plan), so it was an unbound field.
101
+
102
+ **Fix.** In `validateSnapshot` (pure, no I/O):
103
+
104
+ - every origin-correlated identity field — `workspaceId`, `sessionId`, `workflowId`, `workflowRevision`,
105
+ `taskId` — must equal the same field in `snapshot.identity`; a disagreement is `identity-mismatch`;
106
+ - the captured `activeHost` must equal the selected config capture's `activeHost`
107
+ (`snapshot.config.capture.activeHost`), which is the selection context already hashed into
108
+ `configDigest`.
109
+
110
+ Because `validateSnapshot` runs in `acceptDocEvents` **and** in `reconcileDocResults`, the binding holds at
111
+ event acceptance, in `verification` mode, in `review` mode, and for a cached `accepted` report replayed
112
+ against the tampered snapshot. The plan side was already bound: `taskScopeDigest` covers task id, workflow
113
+ id/revision/session, scope, coverage and the PRD root, so a joint plan/digest change is caught by the event
114
+ identity check (pinned as a regression, it already passed on the RED base).
115
+
116
+ Represented tuple links inspected (not just two examples): identity↔originContext (5 fields),
117
+ identity↔activeHost, identity↔plan (via `taskScopeDigest`), identity↔baseline pin, identity↔baseline/
118
+ candidate digests, identity↔config digest, identity↔PRD root.
119
+
120
+ ## P2 — a binary asset must not make the whole-project capture unsupported
121
+
122
+ **Finding (source-traced → executed).** `filesOf` decoded every captured record with
123
+ `new TextDecoder('utf-8', {fatal: true})`, so one ordinary tracked asset (`assets/logo.png`) made the whole
124
+ producer `inventory-unsupported` — fatal decoding was applied to a whole-project inventory.
125
+
126
+ **Relevance is decided by the unchanged derivation, not by an extension allowlist.** The relevance rule is
127
+ the derivation's own path classification (governed/legacy globs, derived paths and sources, code
128
+ extensions, test paths), and the unchanged `deriveDocFacts`/`deriveDocObligations` decide it: an
129
+ undecodable record is **probed** through the derivation with its own bytes, under the actual, addition and
130
+ deletion transitions (round 2), and the derived obligations (applicability *and* scope) must stay
131
+ identical. Only then is the record omitted from the derivation inputs; otherwise the record is semantically
132
+ relevant and the capture fails closed as `inventory-unsupported`. The stored bytes of every record remain
133
+ in the whole-project hash, so `candidateDigest` still changes when a binary asset changes.
134
+
135
+ Consequences, verified: `assets/logo.png` (irrelevant) → capture stays ready, MUST set unchanged,
136
+ `candidateDigest` changes; `docs/prd/logo.png` (governed), `docs/roadmap/index.md` (derived output) and
137
+ `src/logo.js` (code) → `inventory-unsupported`; a candidate binary carrying a `role: prd` frontmatter or a
138
+ REQ-citing test → `inventory-unsupported` (round 2, coordinator defect).
139
+
140
+ The bounded readers are untouched: the producer's `MAX_BYTES`, the lifecycle's bounded blob read and the
141
+ public lifecycle helpers (`parseFrontmatter`, `scanWorkspaceFiles`, `analyzeDocs`) are unchanged; only the
142
+ byte-to-text step in the S2a modules changed. Producer and core re-derivation call the same shared
143
+ `deriveCapturedFacts`, so a captured snapshot and its re-derivation always agree (asserted by comparing the
144
+ derived facts and obligations of the same repository with and without the asset).
145
+
146
+ ## RED → GREEN evidence (deterministic mechanism only)
147
+
148
+ Logs: `/tmp/harness-353-r6-b/logs/`. RED was produced by restoring the two production modules (and their
149
+ packaged copies) from `HEAD` with read-only `git show`, running the **final** test files, then restoring the
150
+ implementation. Every RED failure below is the stated reason, not an artefact.
151
+
152
+ | Suite | RED (base code, final tests) | GREEN (implementation) | Log |
153
+ | --- | --- | --- | --- |
154
+ | S2a core | **177 PASS, 14 FAIL, 0 INFRA** / 191 assertion-legs | **193 PASS, 0 FAIL, 0 INFRA** / 193 | `logs/RED-core.log` → `logs/GREEN-core.log` |
155
+ | S2a producer | **70 PASS, 4 FAIL, 0 INFRA** / 74 assertion-legs | **78 PASS, 0 FAIL, 0 INFRA** / 78 | `logs/RED-producer.log` → `logs/GREEN-producer.log` |
156
+
157
+ Round 2 (after the coordinator defect) is reported in its own section above: `logs/RED2-*.log` →
158
+ `logs/GREEN-*.log`.
159
+
160
+ RED failure reasons (identical on the `source` and `packaged` legs):
161
+
162
+ ```
163
+ FAIL R2g-P1:joint-origin-and-provider-workspaceId-substitution-rejected — expected one of ["identity-mismatch"] in []
164
+ FAIL R2g-P1:joint-origin-and-provider-sessionId-substitution-rejected — expected one of ["identity-mismatch"] in []
165
+ FAIL R2g-P1:joint-origin-and-provider-workflowId-substitution-rejected — expected one of ["identity-mismatch"] in []
166
+ FAIL R2g-P1:joint-origin-and-provider-workflowRevision-substitution-rejected — expected one of ["identity-mismatch"] in []
167
+ FAIL R2g-P1:joint-origin-and-provider-taskId-substitution-rejected — expected one of ["identity-mismatch"] in []
168
+ FAIL R2g-P1:joint-origin-and-provider-active-host-substitution-rejected — expected one of ["identity-mismatch"] in []
169
+ FAIL R2g-P2:binary-asset-capture-stays-eligible-and-derives-as-without-the-asset — no decision (producer unavailable: inventory-unsupported)
170
+ FAIL R2g-P2:unchanged-binary-asset-captures-and-keeps-the-document-must-set — state unavailable with codes ["inventory-unsupported"]
171
+ FAIL R2g-P2:changed-binary-asset-changes-the-candidate-digest-and-keeps-the-must-set — state unavailable with codes ["inventory-unsupported"]
172
+ ```
173
+
174
+ i.e. on the base code the joint origin/provider substitution produced **no** diagnostic at all and stayed
175
+ eligible, and a binary asset made the capture unavailable.
176
+
177
+ Repeat-identical-input evidence: the two suites were run twice after the fix and the per-assertion
178
+ PASS/FAIL lines are identical (`logs/GREEN-core-run2.log`, `logs/GREEN-producer-run2.log`; `diff` clean).
179
+
180
+ ## Tests executed
181
+
182
+ ```
183
+ node ci/mechanism-325-3b-s2a-core.test.js PASS (193/193) logs/GREEN-core.log
184
+ node ci/mechanism-325-3b-s2a-producer.test.js PASS (78/78) logs/GREEN-producer.log
185
+ node ci/mechanism-325-3b-s1.test.js PASS logs/GATE-mechanism-325-3b-s1.log
186
+ node ci/mechanism-325-3b-s1-review.test.js PASS logs/GATE-mechanism-325-3b-s1-review.log
187
+ node ci/mechanism-325-3b-preparation.test.js PASS logs/GATE-mechanism-325-3b-preparation.log
188
+ node ci/mechanism-2ax-doc-lifecycle.test.js PASS logs/GATE-mechanism-2ax-doc-lifecycle.log
189
+ npm run test:consistency PASS logs/GATE-consistency3.log (and 1, 2)
190
+ npm run test:plugin:openai PASS logs/GATE-plugin-openai.log
191
+ npm run test:portability PASS logs/GATE-portability.log
192
+ npm run test:repo-contract PASS logs/GATE-repo-contract.log
193
+ npm run test:docs:capabilities PASS logs/GATE-docs-capabilities.log
194
+ npm run plugin:sync generated copies synced
195
+ ```
196
+
197
+ Round-2 additions: the coordinator's `semantic-transitions.js` (4/4 PASS) and `probes.js` (26/26 PASS),
198
+ both re-executed read-only — `logs/GATE-coordinator-transitions.log`, `logs/GATE-coordinator-probes.log`.
199
+ Source and packaged copies of all three S2a modules are byte-identical (SHA-256 checked).
200
+
201
+ Not run by instruction: `npm test`, `npm run verify:all`, `ci/mechanism-test.js` (the coordinator runs the
202
+ full gates once).
203
+
204
+ ## Findings by evidence layer
205
+
206
+ - **executed** — P1 joint substitution of every origin-correlated identity field and of `activeHost` is
207
+ rejected at event acceptance, in `verification`, in `review`, and against a cached accepted report; the
208
+ unchanged-context control stays eligible.
209
+ - **executed** — a tracked binary asset no longer makes the producer unavailable; `candidateDigest` still
210
+ changes when the asset changes; the document MUST set is unchanged; producer and core re-derivation agree.
211
+ - **executed** — a binary record at a governed path, a derived output path or a code path fails closed with
212
+ `inventory-unsupported`; the unchanged-binary and changed-binary variants are both covered.
213
+ - **executed (round 2)** — a candidate binary record whose bytes carry a `role: prd` frontmatter or a
214
+ REQ-citing test is no longer omitted: the capture fails closed, on both the source and the packaged leg.
215
+ The coordinator's four transition cases now pass; its 26-case probe suite still passes.
216
+ - **source-traced** — the plan side of the tuple (`taskId`, workflow id/revision/session, scope, coverage,
217
+ PRD root) is bound by `taskScopeDigest`; a joint plan/digest change is rejected by the event identity
218
+ check. The regression test for it already passed on the RED base (it is a pin, not a new RED).
219
+ - **source-traced** — the root half of the workspace binding stays out of scope, exactly as the S2a
220
+ contract already states: root metadata is not in the hashed identity and external worktree
221
+ authentication remains outside this guarantee (`docs/doc-workflow-reconciliation.md`).
222
+ - **inconclusive** — the completeness of the relevance witnesses. They are lossy-text probes of the
223
+ unchanged derivation under the actual, addition and deletion transitions. They show that the derivation
224
+ finds no document meaning for the record under those transitions; they do **not** prove decision
225
+ equivalence for every possible undecodable byte sequence, and they are not a proof about the true bytes
226
+ of an undecodable file.
227
+ - **inconclusive** — live-host provenance. Every origin in these suites is an explicit fake provider
228
+ (`H.FakeOrigin`). The suites prove the acceptance/decision algorithm only; they prove nothing about
229
+ authentic host provenance, reviewer independence in deployment, live-host loading or E1 readiness.
230
+ - **inconclusive** — platform coverage. The suites ran on macOS only. `test:portability` passed (no
231
+ Windows-hostile paths in the scanned test sources), but the Windows lane and the Node 22 runtime floor
232
+ were not executed locally; CI owns them.
233
+
234
+ ## Remaining risks
235
+
236
+ 1. **Binary relevance is decided by lossy-text witnesses of the unchanged derivation** (round 2). A record
237
+ whose bytes the witnesses cannot read as meaningful document structure is omitted, so
238
+ `facts.changedPaths` and `facts.documentAnalysis` do not mention it. Applicability and scope are
239
+ unchanged under every probed transition, but the informational facts no longer list the omitted path.
240
+ Where the witnesses disagree the capture fails closed, which is the safe branch. Documented in
241
+ `docs/doc-workflow-reconciliation.md`.
242
+ 2. **The witnesses cost up to three extra `deriveDocFacts` calls** when a capture contains undecodable
243
+ records (batched over all such records, short-circuiting on the first difference). No extra call for
244
+ text-only repositories.
245
+ 3. **`workspaceRoot` is still not hashed into the identity.** The joint provider/context pin-root
246
+ replacement remains detectable only for snapshot-internal consistency, as the contract already states.
247
+ Closing it needs a root-bound identity, which would change the identity schema — out of scope here.
248
+ 4. **`activeHost` comparison treats "absent" on both sides as equal**, because the producer's own check
249
+ requires the provider's and the input's `activeHost` to be strictly equal, and an undefined input host
250
+ is captured as `null` by the resolver. A snapshot with an absent host on one side and a present host on
251
+ the other is rejected.
252
+ 5. **Cross-leg coordination.** This leg edits `ci/fixtures/doc-obligations/reconciliation-s2a/harness.js`
253
+ (shared constants). If leg A edited the same fixture, the coordinator's selection must take one harness,
254
+ not merge both.
255
+ 6. **The coordinator's transition probe asserts "fail closed or semantics retained"**, not which of the two
256
+ happens. This implementation always fails closed on those inputs. If a later revision keeps such a
257
+ capture ready, the shipped regression tests still require the semantics to be present in the obligations.
258
+
259
+ ## Not certified
260
+
261
+ PR readiness is not certified here. The owner decides. The coordinator still owes the full `npm test` /
262
+ `npm run verify:all` run (including the Windows and Node 22 lanes) and the cross-validation against leg A.
263
+
264
+ STATUS: DONE
@@ -0,0 +1,15 @@
1
+ 220657f085fd148dc8e5fc0523a3d01070b2ae8a1a3eae04ed9da17f34e031d7 baseline-probes.log
2
+ 90349aceb2bdb4df1444d89f80494754c3a4d1e323afca5d02cacf7a3c930147 bom-red.log
3
+ 7209e2448ea0b9ab78aa0df98d6be76facf023a50a5bc0c0825cd678f8035ec5 final-bom.log
4
+ d83f299638acec8476ec0801aa3cf58db98f466a226e587fa16c0a3b8e482674 final-consistency.log.gz
5
+ 7bea7e1ff27064f04d2f5268c1e65cae547716b4970a606c2ac7316ec2d5a896 final-core.log
6
+ ac922d20de98c4c247d764d77b2dd4164200885e83a8cff7ef5588a9ef9a0dc2 final-probes.log
7
+ 5b6de979d4342827c2bd0ef49867c60c60fc0e017b3ced9eb5a88b4db4f9ea78 final-producer.log
8
+ 279830701f9804c77c778bf11d3e858c5cb20dfa8ab75288a47655bebd87855e final-transitions.log
9
+ 67d2e42ca0a5258a7f497c1a0bb1ab9c4b743de451c57b89645339b67536d7be peer-a-probe.log
10
+ 8ed56e3da122ebc3cacf3550b9cc2e0b71df895968cddfd30e8047f485db24f7 peer-b-probe.log
11
+ 1c7068c0f0aa12e2eb6e4e6f59945b7b934bb38f12764380d6a8837c211c5a28 tests-a-on-b-core.log
12
+ 30f403cd24e448bec96767f0ff42efede99d74fc2f25a59ff8e4112f9a9b9f6f tests-a-on-b-producer.log
13
+ 7fc384d85d9f48175b7920c292772a6607f73bd458f8b3a8a20767e56c2879d1 tests-b-on-a-core.log
14
+ dacd3b67443223b400ce283883c20831cce1063211a3c22868477b5a28bcaec7 tests-b-on-a-producer.log
15
+ 71d59834355033a53db6311ed53fd735a948f53fc9ac914ae76fc6a9f022cf46 verify-all.log.gz