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
@@ -517,6 +517,363 @@ S1 proves only its tested no-credit mechanism. It does not prove positive reconc
517
517
  independent producer provenance, live-host loading, H1–H4 effectiveness or E1 GO. Registry empty
518
518
  is a later strict-prep gate, not proof of any of those evidence layers.
519
519
 
520
+ ## S2a — producer and reconciliation core
521
+
522
+ **Docs-stage contract for the deterministic core; not workflow integration or deployed origin
523
+ proof.** S2a is stacked on S1 at `8903435`, under the
524
+ [approved S2 scope](https://github.com/dyphn1/Harness-everything/issues/325#issuecomment-6308606462).
525
+ S1 remains default OFF and no-credit; S2a does not change disposition/review/Stop wiring, write a
526
+ credit-bearing session report or retire the ten prep gaps. Tests inject explicitly fake origin
527
+ providers. A successful fake-origin vector proves the acceptance/decision algorithm only, never
528
+ authentic host provenance, reviewer independence in deployment, live-host loading or E1 readiness.
529
+
530
+ ### Private core interfaces
531
+
532
+ Use pilot-local modules under `hooks/scripts/lib/`, generated into the installed package by
533
+ `plugin:sync`. These are module contracts for independent RED tests, not a new public CLI,
534
+ workflow strategy or generic provider registry. No discovery call executes a verifier, generator
535
+ or network operation. Invalid/unavailable inputs return structured diagnostics and no eligibility,
536
+ never a fabricated empty expected set.
537
+
538
+ ```text
539
+ # doc-reconciliation-producer.js
540
+ produceDocSnapshot({ workspaceRoot, baseline, workflow, task, scope, activeHost,
541
+ prdRoot = "docs/prd/" }, { originProvider })
542
+ -> { state: "ready" | "unavailable", snapshot: Snapshot | null, diagnostics: [...] }
543
+
544
+ # doc-reconciliation-events.js
545
+ acceptDocEvents({ snapshot, eventRefs }, { originProvider })
546
+ -> { state: "ready" | "unavailable" | "unresolved", checkerInputs,
547
+ acceptedIndex, rejected: [...], diagnostics: [...] }
548
+
549
+ # doc-reconciliation-core.js
550
+ checkAcceptedDocEvidence({ snapshot, accepted, mode = "verification" })
551
+ -> { decision: "eligible" | "legacy" | "unresolved" | "unavailable",
552
+ eligible: boolean, expectedMustIds: [...], checkedMustIds: [...],
553
+ results: [...], unsatisfied: [...], diagnostics: [...] }
554
+
555
+ reconcileDocResults({ snapshot, accepted, checkerOutput, mode = "verification" })
556
+ -> same decision shape
557
+ ```
558
+
559
+ `checkAcceptedDocEvidence` calls unchanged `checkDocObligations()` with the producer-derived
560
+ obligations and adapted accepted records, then delegates to `reconcileDocResults`. The latter
561
+ is the pure decision seam for missing/duplicate/conflicting-result RED vectors. Caller result
562
+ IDs do not define the expected set. Results are retained unchanged; side indexes establish
563
+ identity/scope/origin, rather than adding trusted fields to checker results. `eligible: true`
564
+ means only that this core evaluation met its declared mode's requirements. It never persists
565
+ PASS, sets runtime `pilotCredit`, or authorizes a workflow transition in S2a.
566
+
567
+ `Snapshot` has these required fields: `identity`; `baseline` with `commit`, `treeRef`, `records`,
568
+ `digest`; `candidate` with `records`, `coverage`, `digest`; `config` with `resolution`, `capture`,
569
+ `digest`; `originContext`; `taskSemantics`; `facts`; `obligations`; and `derivationReasons`.
570
+ `records` retain canonical path, presence, type, mode, stored bytes and their per-path digest.
571
+ `derivationReasons[id]` contains `applicability`, a nonempty `reason` and `factRefs` identifying
572
+ captured facts; it is derived alongside, not added to, the unchanged obligation schema. Baseline,
573
+ candidate and config digest fields equal their corresponding identity tuple fields. This detached
574
+ capture contains the full inventory and immutable baseline references, not path names for later rereads. Byte buffers and scalar metadata must come from the same capture; later
575
+ reads of caller objects or paths cannot silently change that capture. The producer correlates
576
+ workspace/worktree, pinned baseline and task/workflow context through the injected origin
577
+ provider; command-line IDs, environment hints and copied context JSON alone are not origin.
578
+ Capture after start's workflow revision increment. Reject a replaced baseline record that does
579
+ not match the provider's correlated workflow-start reference; never substitute current HEAD.
580
+ The provider context MUST carry the concrete pinned `baseline: { commit, root }` (a bare `baselineReference`
581
+ is not enough); a missing pin or any mismatch with the input baseline is `baseline-unavailable`, so a
582
+ baseline re-anchored to a moved HEAD can never return `ready` (PR #353 review P1).
583
+ Capture canonicalizes the pinned root and workspace root to the same native filesystem identity.
584
+ The detached snapshot MUST retain a pin whose commit equals the hashed baseline commit and whose
585
+ canonical root equals the captured workspace root. Snapshot validation reports these violations as `baseline-unavailable`; an invalid snapshot stops
586
+ event adaptation, and both reconciliation modes withhold credit. The commit check rejects joint
587
+ provider/captured-pin replacement against an unchanged hashed baseline. Root comparison proves only
588
+ snapshot-internal consistency: jointly replacing the pin root, captured workspace root and provider
589
+ root is not detected by this detached check. Root metadata is not included in the hashed identity;
590
+ external worktree authentication remains outside this S2a guarantee. The core performs no filesystem
591
+ reads to establish the invariant. Provider-only pin changes remain diagnostic and block credit at
592
+ reconciliation; they do not short-circuit event adaptation.
593
+ The captured origin context is the snapshot's only origin-correlated identity. Snapshot validation
594
+ compares its `workspaceId`, `sessionId`, `workflowId`, `workflowRevision` and `taskId` with the claimed
595
+ `identity`, compares the captured plan (`workflowId`, `workflowRevision`, `sessionId`, `taskId`,
596
+ `prdRoot`, `configDigest`) with that identity, and compares the captured `activeHost` with the host the
597
+ captured config was selected for. A joint substitution of the captured origin context and the provider
598
+ context therefore cannot pass validation: the provider half and the event half of the trust contract stay
599
+ connected through the identity. Both reconciliation modes withhold credit for such a snapshot, and a
600
+ cached acceptance produced for the substituted tuple does not bypass the same check (PR #353 review P1).
601
+ The captured config bytes must also be the bytes the captured inventory holds for the same path; a config
602
+ digest that binds bytes the whole-project capture contradicts is `config-capture-mismatch`.
603
+ Progress-field exclusions in `taskScopeDigest` apply only at their defined schema locations (per-step and
604
+ per-binding status/evidence records), never to every nested key with the same name; nested scope,
605
+ requirement or binding semantics always change the digest (PR #353 review P2).
606
+ In verification mode only rejected evidence for an expected applicable/unknown MUST, or a global
607
+ origin/identity fault, blocks eligibility; a rejected SHOULD or N/A event stays a visible diagnostic and
608
+ never gates (PR #353 review P2).
609
+
610
+ `checkerInputs` has exactly the existing checker inputs: `obligations`, `artifacts`, `baseline`,
611
+ `executionRecords`, `reviewRecords`. `artifacts[path].digest` and
612
+ `baseline.{digest,artifacts[path].digest}` come from the captured bytes. The checker PRD-set
613
+ envelope is distinct from the whole-candidate identity. `acceptedIndex` maps each checker
614
+ record reference back to accepted event identity, capability, scope, artifacts and origin;
615
+ `rejected` retains targeted invalid/contradictory records so a failing applicable record cannot
616
+ be filtered away to leave a passing subset. Missing acceptance produces empty record lists,
617
+ not accepted records with invented principals or zero exits.
618
+
619
+ Decision diagnostics use `{ code, message, ...context }`; unsatisfied items use the existing
620
+ `{ obligationId, state, reasonCode }` states pending/unknown/unavailable/failed. Unavailable
621
+ collection/origin support yields `unavailable`; a known unmet or unknown MUST yields
622
+ `unresolved`. Only complete trusted derivation with no applicable/unknown document MUST yields
623
+ `legacy`, `eligible: false`,
624
+ never verified credit. SHOULD results remain visible and never gate.
625
+
626
+ ### Snapshot identity and completeness
627
+
628
+ Every comparison uses all ten fields from the existing trust contract:
629
+
630
+ | Field | S2a binding |
631
+ | --- | --- |
632
+ | `workspaceId`, `sessionId` | Origin-provider-correlated actual repository/worktree and host session; validate against the collected root, not a supplied label alone |
633
+ | `workflowId`, `workflowRevision` | Correlated persisted workflow and nonnegative integer revision, collected after start |
634
+ | `taskId` | Actual active task identity from correlated context; any workflow-local mapping must be explicit and validated against the host task lifecycle |
635
+ | `taskScopeDigest` | Stable plan semantics plus effective task/config/diff coverage and PRD root; include requirement IDs/order, summaries, acceptance, dependencies, binding declarations/locators and scope changes; exclude progress statuses, loaded flags, timestamps and derived reports |
636
+ | `baselineDigest` | Whole pinned tree/capture identity and per-path SHA256 from the same stored blob bytes, retaining path/type/mode information |
637
+ | `candidateDigest` | Whole effective candidate capture, including new/untracked/ignored-relevant content, deletions, rename endpoints and type/mode changes; not merely the PRD-set digest |
638
+ | `configDigest` | Exact captured bytes actually parsed/validated plus resolver selection context, described below |
639
+ | `prdRoot` | Exactly `docs/prd/`; another requested root is unavailable, not silently redirected or generalized |
640
+
641
+ Define byte framing once: `F(bytes) = ASCII(decimal byteLength) + ":" + bytes`. Strings use
642
+ UTF-8; digests use lowercase SHA256 hex. Snapshot hashing is domain-separated and framed:
643
+ `SHA256(F("doc-snapshot-v1") || F(kind) || F(baselineCommit) || F(baselineTreeRef) ||
644
+ F(coverageDigest) || F(recordCount) || records)`, where kind is baseline or candidate and the
645
+ pinned commit/tree remain unchanged for both and recordCount is the decimal ASCII count.
646
+ Each sorted record contributes
647
+ `F(path) || F(presence) || F(type) || F(mode) || F(storedBytes)`; sort canonical repository-relative
648
+ UTF-8 paths by byte order. Candidate records cover the union of baseline and current effective
649
+ inventory, with explicit deleted tombstones (empty type/mode/content); new paths have present
650
+ records. Presence is exactly `present` or `deleted`; supported regular-file type is `file` with
651
+ Git-normalized mode `100644` or `100755`; unsupported types/modes fail unavailable. Rename
652
+ source/destination must both be covered. Deleted tombstones participate only in identity framing:
653
+ **omit deleted paths entirely from candidate checker artifacts**, retaining their baseline artifact
654
+ and the paired rename destination where applicable. An empty candidate artifact is not absence.
655
+ Per-path content digests hash those same owned byte buffers, not a second file read. This framing distinguishes concatenation
656
+ collisions, path boundaries, deletion vs empty files and mode/type changes.
657
+
658
+ `coverageDigest` and `taskScopeDigest` use framed canonical structured semantics: recursively
659
+ sort object keys, retain semantically ordered arrays, sort declared sets, and hash the resulting
660
+ UTF-8 representation: `coverageDigest = SHA256(F("doc-coverage-v1") || F(canonicalCoverage))`
661
+ and `taskScopeDigest = SHA256(F("doc-task-scope-v1") || F(canonicalTaskSemantics))`.
662
+ Excluding mutable progress means passing
663
+ review does not stale its own evidence; a changed requirement/acceptance/binding/scope still does.
664
+ No invented task ID or timestamp can replace correlated identity. The checker-compatible
665
+ PRD-set digest remains SHA256 of its existing JSON sorted `[path, artifactDigest]` pairs under
666
+ `docs/prd/` (`doc-evidence-capture.js:62–67`), computed separately and never substituted for
667
+ `candidateDigest`.
668
+
669
+ Reuse lifecycle stored-byte handling; collect bytes before UTF-8 conversion for fact derivation.
670
+ A whole-project inventory contains binary records. Every stored byte stays in the identity framing, so a
671
+ changed binary asset changes `candidateDigest`, but semantic derivation needs decoded text. A record whose
672
+ bytes are not valid UTF-8 is omitted from derivation only when the derive contract cannot observe it:
673
+ outside the configured governed/legacy/derived globs, outside the derive contract's code and REQ-test path
674
+ rules, and without a frontmatter block (including one after a leading UTF-8 BOM). Otherwise it fails closed as `inventory-unsupported`, so a
675
+ governed, derived, code or test record is never silently dropped and applicability is never weakened.
676
+ Producer and core re-derivation apply the same rule, so irrelevant binary assets remain usable
677
+ while the identity still covers their bytes (PR #353 review P2).
678
+ Inventory includes trusted task scope, governed/legacy documents, derived sources/outputs,
679
+ config-selection sources and any code/REQ-test paths affecting applicability. Default to a full
680
+ project capture apart from Git administrative storage. A narrower effective inventory requires
681
+ an explicit justified scope/completeness proof and identity binding; an authored doc step alone
682
+ cannot narrow it. Never call a governed-Markdown-only inventory a whole project snapshot.
683
+ Unsupported encodings/filters, symlinks/submodules, path/case collisions, read failures or
684
+ oversized traversal that prevent faithful identity yield unavailable. Bound resources by refusing
685
+ unsupported capture, not silently omitting paths. Detect mutation/mixed reads during capture;
686
+ a capture whose before/after context or inventories disagree is unusable, not current evidence.
687
+
688
+ **F4 is a producer requirement, not an inherited S1 exemption.** Include ignored paths intersecting
689
+ governed/derived/task scope, including parent-directory ignore rules, ignored rename destinations,
690
+ config and relevant code/REQ facts. A file removed from the index is not therefore absent. Include
691
+ its bytes in candidate identity or return inventory unsupported/unavailable. Even ignored paths
692
+ outside document globs must be covered if they affect whole-candidate completeness/applicability.
693
+ An ignored irrelevant-path control must show either inclusion with no new MUST or a justified
694
+ excluded scope; uncertainty cannot establish non-document status. No silent residual re-acceptance.
695
+
696
+ ### Captured-config resolver seam
697
+
698
+ Add only `resolveDocsConfigCaptured(workspaceRoot, options = {})` alongside the existing resolver:
699
+
700
+ ```text
701
+ resolveDocsConfigCaptured(root, { activeHost, ...existingOptions })
702
+ -> { resolution, capture: { activeHost, sources: [...], selectionInputs: [...] } }
703
+ ```
704
+
705
+ `resolution` is byte-for-byte-equivalent in serialized values, shape, ordering, diagnostics and
706
+ precedence to existing `resolveDocsConfig(root, options)`. Existing callers keep their old result
707
+ shape and behavior. Both entry points share the same internal captured-read selection logic;
708
+ do not resolve first and later reread/hash filenames. Preserve project-overrides-host, known
709
+ active-host selection, equal-host handling, invalid/absent/ambiguous behavior and host normalization.
710
+ An unknown host cannot arbitrarily choose differing manifests. Keep canonical resolver copies
711
+ under `to-spec/scripts/` and `multi-agent-workspace/scripts/` synchronized in implementation.
712
+
713
+ Each capture source retains its canonical path, presence/read disposition, exact owned bytes
714
+ actually parsed, byte SHA256 and selected normalized value when applicable. Host manifests bind
715
+ the complete parsed manifest bytes as well as selected `projectDocs` and its normalization.
716
+ `selectionInputs` records selection-affecting path presence/absence, selection status/source paths
717
+ and active-host context. Define `canonicalSelection` as canonical JSON of that selection metadata,
718
+ including selected normalized config. Compute `configDigest = SHA256(F("doc-config-v1") ||
719
+ F(canonicalSelection) || F(sourceCount) || sourceRecords)`, where sourceCount is decimal ASCII
720
+ and each path-sorted source record is `F(path) || F(presence) || F(exactBytes)`; absence has empty
721
+ bytes and a distinct presence marker. Never presence-check a digest or hash only normalized config. Equivalent parsed configs with changed bytes or host selection
722
+ must differ. If trustworthy capture cannot be established, do not return a usable producer identity.
723
+ Source replacement between parse and hash cannot bind B's bytes to A's validated value; mutation
724
+ beyond the captured snapshot is caught by a later capture, not silently promoted as freshness.
725
+
726
+ ### Injected origin and accepted-event boundary
727
+
728
+ The origin provider is an injected dependency, not an object loaded from a journal or a record's
729
+ `trusted` field. S2a tests explicitly name their fake. Define this narrow interface:
730
+
731
+ ```text
732
+ originProvider.resolveContext({ workspaceRoot, workflowId, sessionId })
733
+ -> { status: "accepted", context, originRef } | { status: "unavailable" | "rejected", diagnostics }
734
+ originProvider.resolveEvent({ reference, identity })
735
+ -> { status: "accepted", event, originRef, correlation } |
736
+ { status: "unavailable" | "rejected", diagnostics }
737
+ ```
738
+
739
+ An accepted context binds actual workspace/session/task, author and relevant executor principals,
740
+ active-host context and the workflow-start baseline reference. An accepted event binds the common
741
+ trust-contract fields `eventId`, `kind`, `producerId`, `originRef`, full `identity`, `principalId`,
742
+ `paths`, `provenance`. Execution/verification also retain exact authorized command/argv, cwd,
743
+ integer exit, signal, tool/event correlation ID, actual capability/scope, start/end identity captures
744
+ and immutable result reference. Missing/null/noninteger exit is never converted to zero; nonzero
745
+ exit/signal or a command with unrelated semantics cannot satisfy a document check.
746
+
747
+ Review requires approve verdict, nonempty summary, reviewed artifact digests, current identity/scope
748
+ and independently correlated reviewer provenance. The reviewer differs from the current author
749
+ and all relevant executors; `independentOf` strings, distinct filenames/session labels or different
750
+ actor strings alone are insufficient. These principals and role relationships also come from the
751
+ provider. Provider acceptance must be invoked for current references, not inferred from an old
752
+ pilot report, copied local journal, filesystem permissions, hash agreement or self-asserted JSON.
753
+ Missing/expired origins remain unavailable; conflicting/replayed/stale records never earn credit.
754
+
755
+ Adapt only accepted records into the unchanged checker schema. Keep a separate binding from
756
+ checker record IDs/`recordRefs` to the accepted origin index; every contributing reference must
757
+ resolve and cover its expected scope/current tuple/artifacts, including baseline-only and paired
758
+ rename endpoints. Retain relevant rejection/conflict diagnostics; do not discard observed failures
759
+ and submit only passing records. An unrelated extra event cannot replace a missing MUST; an
760
+ extra/misattributed result cannot invent one. Fake providers may simulate acceptance/rejection for
761
+ these deterministic tests; there is no environment/CLI fixture trust switch, and production code
762
+ must not read `prep-host-events` as an authority.
763
+
764
+ ### Expected-set and review-mode decisions
765
+
766
+ The binding reconciliation rule (A2 §1.5) is:
767
+
768
+ > Reconcile by expected ID/scope/identity, not `results.every(...)`: every applicable MUST needs exactly one unambiguous matching observed/pass decision backed by accepted records covering its scope. Missing, duplicate/conflicting, extra/misattributed, stale or untrusted evidence cannot provide credit; unknown remains unresolved; N/A requires trusted derivation; SHOULD never gates. Extra evidence is not a substitute for a missing MUST. No document MUSTs means legacy non-document handling, not fabricated `verified` credit.
769
+
770
+ Retain all producer-derived obligations, including unknown, N/A derivation reasons and SHOULD
771
+ reminders. The full expected credit set comes from applicable/unknown MUSTs, with or without an
772
+ authored doc step. Every applicable MUST needs exactly one matching result with
773
+ `observation: observed`, `outcome: pass`, current scope/artifact binding and accepted supporting
774
+ references. Never use a passing returned subset or empty list as completion. Unknown MUSTs are
775
+ unresolved even with a passing-looking result; N/A without trusted derivation is unresolved,
776
+ not an excuse to shrink the set. Duplicate/conflicting decisions or extra/misattributed result
777
+ IDs cannot provide credit. Known SHOULD outcomes remain diagnostic-only and do not gate.
778
+
779
+ `mode: verification` checks the full expected set. `mode: review` evaluates only the current
780
+ quality-review MUST for the governed normative review, preserves the full set in expectedMustIds,
781
+ and reports its selected subset in checkedMustIds. It requires independent current review, not
782
+ later execution/verification evidence; missing or failed later verifier records do not gate review
783
+ eligibility, though their diagnostics remain visible. If no review MUST is required, use legacy review behavior
784
+ without granting full verification credit. No authored step is advanced by either core call.
785
+ This provides a testable review → missing verifier → later valid retry progression without wiring
786
+ updateStep in S2a, matching P02e/P03's intended S2b transition.
787
+
788
+ Reason codes below are deterministic core diagnostics, not extensions to checker observation or
789
+ outcome enums; report all applicable failures rather than hiding one behind a successful result:
790
+
791
+ | Code | Trigger / no-credit interpretation |
792
+ | --- | --- |
793
+ | `invalid-input` | Malformed core/provider/record shape; no inferred empty successful set |
794
+ | `baseline-unavailable` | Missing/tampered/unreadable pinned baseline or Git failure; retain S1 F5 meaning |
795
+ | `inventory-incomplete`, `inventory-unsupported` | Missing new/deleted/rename/ignored scope, read failure or unsupported traversal/type/filter semantics |
796
+ | `snapshot-changed` | Mixed capture or changed current snapshot/identity across a core evaluation boundary |
797
+ | `config-unavailable`, `config-capture-mismatch` | Invalid/ambiguous/absent config or capture bytes/selection not bound to parsed value |
798
+ | `unsupported-prd-root` | Requested root differs from supported docs/prd/ |
799
+ | `identity-mismatch` | Any tuple field/task/plan/scope substitution or wrong baseline/candidate identity |
800
+ | `origin-unavailable`, `origin-untrusted` | Missing/expired/unsupported authentic origin, or rejected copied/forged/self-asserted evidence |
801
+ | `event-stale`, `event-duplicate`, `event-conflict` | Replay, duplicate/conflicting relevant event or stale review |
802
+ | `event-invalid`, `capability-mismatch`, `scope-mismatch`, `artifact-mismatch` | Incomplete command/exit/signal/verdict/summary, unrelated command capability, path coverage or stored-byte/destination digest mismatch |
803
+ | `review-not-independent` | Reviewer provenance missing or principal equals author/relevant executor |
804
+ | `checker-unavailable` | Checker evaluation cannot complete; do not fabricate results |
805
+ | `result-missing`, `result-duplicate`, `result-misattributed`, `result-not-pass`, `result-untrusted` | Expected-set result absent, repeated/conflicting, assigned to another obligation, not observed/pass or not backed by accepted refs |
806
+ | `unknown-applicability`, `na-reason-missing` | Unknown MUST remains unresolved, or N/A lacks trusted derivation |
807
+ | `no-document-musts` | Diagnostic for legacy decision only; never a verified result |
808
+
809
+ ### Exact S2a RED handoff
810
+
811
+ All positive origins here are **explicit test fakes**; run real read-only collector/resolver fixtures
812
+ alongside pure adapter/reconciliation tests. No ten-entry prep retirement or CLI completion is
813
+ claimed by these core assertions.
814
+
815
+ | ID | Positive control and strict negative pair reachable in S2a |
816
+ | --- | --- |
817
+ | A01 | Complete normative lint/link/review/conflict vector: exact producer-derived IDs, actual unchanged checker call/results, current identity, accepted refs, eligible core decision; nonnormative positive proves review is not globally forced |
818
+ | A02 | Same evidence/task succeeds; task A→B, narrowed scope, changed requirements/acceptance/bindings or workflow revision rejects. Same bytes alone never prove same task |
819
+ | A03 | Bound config capture succeeds; parse A then replace with B, same parsed value/different source bytes, or changed active-host selection rejects/stales. Preserve project-overrides-host and equal-host positives |
820
+ | A04 | docs/prd/ positive; otherwise-identical custom-root request is unavailable with no fallback |
821
+ | A05 | Whole baseline/candidate and paired deletion/rename bytes agree; another tree's path bytes, omitted new/deleted paths, wrong destination, mode/type drift or PRD-set digest substituted for whole candidate rejects |
822
+ | A06 | Independent current review eligible before any verifier event; verification stays unresolved; later accepted execution for same tuple makes core retry eligible. Self-review/unknown independent provenance rejects without prose matching |
823
+ | A07 | With/without authored doc-change derives the same MUSTs; omit one/all/subset checker results and lose eligibility even when every returned result passes |
824
+ | A08 | Previously non-document snapshot is legacy; new governed scope produces current MUSTs and rejects old tuple/results, retaining supplied authored history. S2b alone must prove persisted Stop revocation/exit 0 |
825
+ | A09 | Pinned-baseline capture remains pinned after fixture commit; rename old/new endpoints and repeated captures remain bound. Re-anchoring to current HEAD loses the intended governed obligations and must be detected |
826
+ | A10 | Git timeout/corruption/command failure retains baseline-unavailable and zero eligibility; normal Git capture succeeds |
827
+ | A11 | New ignored normative PRD/ADR, nonignored→ignored rename, index removal with file remaining, ignored config/derived source, tracked files matched by ignore patterns, unreadable ignored subtree: include faithfully or unavailable. Include justified ignored-irrelevant control |
828
+ | A12 | Fake context/event provider accepts valid correlation and independent principal; identical copied journal/tuple/actor labels without current provider acceptance reject. Missing, expired and forged origins do not become trusted via flags |
829
+ | A13 | Applicable/unknown/N/A/SHOULD matrix: unknown unresolved, N/A needs reason, SHOULD never gates; no applicable/unknown document MUST => legacy, never verified |
830
+ | A14 | Current captures match; mutate bytes/config/task during capture or modeled before/after-check boundary and reject mixed/stale identity. Status/timestamp-only progress does not stale its own review |
831
+
832
+ Flip exactly one property from a previously eligible vector for each of: every tuple field; source
833
+ bytes/selection; author/reviewer identity; exit type/null/nonzero/signal; actual capability mapping;
834
+ path coverage; baseline/destination digest; approve verdict/summary; duplicate/conflicting event or
835
+ result; stale review; expired/missing origin; forged copied journal/report; omitted MUST. Each test
836
+ must name the failed field/reason and demonstrate loss of the prior valid path. An arbitrary
837
+ passing command cannot satisfy lint/link/REQ synchronization. If shared checker decision/read
838
+ behavior is wrong, return it to #341 fixture-first; do not patch that API in this consumer.
839
+
840
+ Non-vacuity mutations must kill: disable origin validation, skip reviewer independence, iterate
841
+ only returned results, trust a previous core report/PASS, re-anchor baseline to HEAD, hash only
842
+ PRDs, drop config-byte binding, and exclude ignored governed paths. A mutation may not count as
843
+ killed merely because the untouched S1 runtime still refuses everything. The completion-recollection
844
+ mutation and actual after-satisfied freshness/persist race belong to S2b, where completion is wired.
845
+ Core modeled stale-snapshot mutations remain S2a requirements.
846
+
847
+ ### S2b dependency and close-out boundary
848
+
849
+ S2b MUST name at least one actual supported host capture/export path, retain command/task/session
850
+ correlation and independent author/executor/reviewer evidence, and reject copied/altered local
851
+ records against that path. Existing timestamps, counters, append-only-looking JSON, permissions
852
+ and hashes do not by themselves establish origin. If any required origin/input is unavailable for
853
+ a host, that host retains S1 withholding and reports positive capability unavailable. Reviewer
854
+ independence is not weakened to make a host pass.
855
+
856
+ S2b, not S2a, wires review/verification/direct aggregate/completion and recollects every tuple and
857
+ accepted binding before credit, including persisted satisfied state. It must bracket authorized
858
+ evidence operations and recheck after checker evaluation before persistence, rejecting concurrent
859
+ mutation rather than introducing a lock. S2a supplies coherent captures and deterministic decisions;
860
+ it cannot prove that the real host collects them at the required boundaries.
861
+
862
+ S2b RED additionally proves P02e review refusal/pending/Stop 0, P03 actual review→verify→retry→
863
+ satisfied completion (restore its S1-staged final running assertion), initial P05 positive verify/
864
+ completion followed by its separate stale-credit/ON→OFF→ON negatives, actual task/host substitution,
865
+ old typed PASS acquiring governed scope, and genuinely active correlated Fable/unsupported-untyped
866
+ reporting independent of generic completion. Preserve OFF/P06/P07u/P02d, required bindings,
867
+ source/package parity, exact intentional-refusal exits and both Stop surfaces exiting 0. No Fable
868
+ credit or automatic generator/verifier is introduced. Retire the ten registry entries only in S2b
869
+ when corresponding strict assertions genuinely pass; recapture new runtime-module fingerprints
870
+ and review actual/desired/reasons deliberately. Registry empty is a mechanism gate, not authentic
871
+ host loading or E1 GO. E1 begins only after a creditable S2.
872
+
873
+ Outside this contract: attestation service/PKI, a new command runner/authorization boundary, forced
874
+ review provider, PRD-root/schema generalization, automatic verifier/generator, code→doc graph
875
+ (#350) and new locks. S2a does not change the public derive/check APIs or weaken reviewer independence.
876
+
520
877
  ## Implementation and acceptance after E0
521
878
 
522
879
  S1 ships the default-OFF switch, narrow disposition/completion no-credit integration and runtime
@@ -0,0 +1,16 @@
1
+ id: negative-first-edge-case-review-001
2
+ name: A first review finding an edge case is ordinary review
3
+ description: One edge case in the first review does not establish repeated repair rounds.
4
+ tags:
5
+ - negative-trigger
6
+ inputs:
7
+ prompt: "Review this PR for the first time. The reviewer found one edge case in an unchanged state reader. Assess the finding against the acceptance clause and trace the affected consumers. No earlier review or corrective round has taken place."
8
+ expected:
9
+ should_trigger: false
10
+ graders:
11
+ - type: text
12
+ name: not-contains-keywords
13
+ config:
14
+ contains:
15
+ - "circuit breaker tripped"
16
+ - "reflection report"
@@ -0,0 +1,15 @@
1
+ id: positive-repair-loop-001
2
+ name: Successful review commands still expose a repair loop
3
+ description: "Reflect-first circuit breaker - after 3 failures, stop edits, rebuild the full picture with read-only tools, write a fact-check report, then resume or escalate. USE FOR: \"you are stuck in a loop\", \"the same error keeps failing\", \"rethink your assumptions\". DO NOT USE FOR: \"routine single-failure debugging\", \"greenfield planning without failures\"."
4
+ tags:
5
+ - positive-trigger
6
+ inputs:
7
+ prompt: "Every review round finds a new edge case. In two consecutive review/fix rounds for the same feature, each review found a new defect in the same acceptance decision: whether the checker is trusted. The first fix covered invocation; the next review found an unchanged state reader making that decision differently. All commands succeeded. Review both findings and the affected consumers before choosing the next correction."
8
+ expected:
9
+ should_trigger: true
10
+ graders:
11
+ - type: text
12
+ name: contains-keywords
13
+ config:
14
+ contains:
15
+ - "zoom-out"
@@ -4,7 +4,7 @@ description: "Route software/project work through Harness: classify tier/topolog
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.38.0
7
+ version: 0.40.0
8
8
  ---
9
9
 
10
10
  # Harness Everything
@@ -122,15 +122,17 @@ function readHostProjectDocsConfigs(workspaceRoot) {
122
122
  return manifests;
123
123
  }
124
124
 
125
- function inspectHostProjectDocs(workspaceRoot) {
125
+ function inspectHostProjectDocs(workspaceRoot, readSource = null) {
126
126
  const entries = [];
127
127
  for (const home of PLATFORM_HOMES) {
128
128
  const file = path.join(workspaceRoot, home, 'harness-everything', 'manifest.json');
129
- if (!fs.existsSync(file)) continue;
129
+ const captured = readSource ? readSource(file) : null;
130
+ if (captured ? captured.presence === 'absent' : !fs.existsSync(file)) continue;
130
131
  const entry = { file, home, projectDocs: null, warnings: [], diagnostics: [], hasProjectDocs: false };
131
132
  let manifest;
132
133
  try {
133
- manifest = JSON.parse(fs.readFileSync(file, 'utf8'));
134
+ if (captured && captured.presence !== 'present') throw new Error('unreadable source');
135
+ manifest = JSON.parse(captured ? captured.bytes.toString('utf8') : fs.readFileSync(file, 'utf8'));
134
136
  } catch {
135
137
  entry.diagnostics.push({
136
138
  code: 'invalid-host-config',
@@ -349,16 +351,18 @@ function configSource(workspaceRoot, file) {
349
351
  return relativePath(workspaceRoot, file);
350
352
  }
351
353
 
352
- function resolveDocsConfig(workspaceRoot, options = {}) {
354
+ function resolveDocsConfigSelection(workspaceRoot, options, readSource) {
353
355
  const resolvedRoot = workspaceRoot || getWorkspaceRoot();
354
356
  if (!resolvedRoot) throw new Error('cannot resolve project docs outside a git workspace; pass a workspace root explicitly');
355
357
  const root = path.resolve(resolvedRoot);
356
358
  const projectFile = projectDocsConfigPath(root);
357
- if (fs.existsSync(projectFile)) {
359
+ const capturedProject = readSource(projectFile);
360
+ if (capturedProject.presence !== 'absent') {
358
361
  const source = configSource(root, projectFile);
359
362
  let config;
360
363
  try {
361
- config = JSON.parse(fs.readFileSync(projectFile, 'utf8'));
364
+ if (capturedProject.presence !== 'present') throw new Error('unreadable source');
365
+ config = JSON.parse(capturedProject.bytes.toString('utf8'));
362
366
  } catch {
363
367
  return {
364
368
  status: 'invalid',
@@ -376,7 +380,7 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
376
380
  return { status: 'invalid', sources: [source], config: null, warnings, diagnostics };
377
381
  }
378
382
 
379
- const hosts = inspectHostProjectDocs(root);
383
+ const hosts = inspectHostProjectDocs(root, readSource);
380
384
  const hostDiagnostics = hosts.flatMap(entry => entry.diagnostics);
381
385
  const hostWarnings = hosts.flatMap(entry => entry.warnings);
382
386
  const validHosts = hosts.filter(entry => entry.projectDocs);
@@ -393,7 +397,7 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
393
397
  return { status: 'project', sources: [source], config, warnings: [...warnings, ...hostWarnings], diagnostics: [...diagnostics, ...hostDiagnostics] };
394
398
  }
395
399
 
396
- const hosts = inspectHostProjectDocs(root);
400
+ const hosts = inspectHostProjectDocs(root, readSource);
397
401
  const hostDiagnostics = hosts.flatMap(entry => entry.diagnostics);
398
402
  const hostWarnings = hosts.flatMap(entry => entry.warnings);
399
403
  const activeHome = normalizedHostHome(options.activeHost);
@@ -457,6 +461,35 @@ function resolveDocsConfig(workspaceRoot, options = {}) {
457
461
  };
458
462
  }
459
463
 
464
+ function resolveDocsConfigCaptured(workspaceRoot, options = {}) {
465
+ const resolvedRoot = workspaceRoot || getWorkspaceRoot();
466
+ if (!resolvedRoot) throw new Error('cannot resolve project docs outside a git workspace; pass a workspace root explicitly');
467
+ const root = path.resolve(resolvedRoot);
468
+ const records = new Map();
469
+ const readSource = file => {
470
+ const key = configSource(root, file);
471
+ if (!records.has(key)) {
472
+ let bytes = Buffer.alloc(0);
473
+ let presence = 'present';
474
+ try { bytes = Buffer.from(fs.readFileSync(file)); }
475
+ catch (error) { presence = error.code === 'ENOENT' ? 'absent' : 'unreadable'; }
476
+ records.set(key, { path: key, presence, bytes, digest: crypto.createHash('sha256').update(bytes).digest('hex') });
477
+ }
478
+ return records.get(key);
479
+ };
480
+ const resolution = resolveDocsConfigSelection(root, options, readSource);
481
+ let activeHost = null;
482
+ try { activeHost = JSON.parse(JSON.stringify(options.activeHost)); } catch { /* no serializable host context */ }
483
+ const sources = [...records.values()].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
484
+ const selectionInputs = sources.map(({ path: sourcePath, presence }) => ({ path: sourcePath, presence }));
485
+ selectionInputs.push({ status: resolution.status, sources: [...resolution.sources], activeHost, config: resolution.config });
486
+ return { resolution, capture: { activeHost, sources, selectionInputs } };
487
+ }
488
+
489
+ function resolveDocsConfig(workspaceRoot, options = {}) {
490
+ return resolveDocsConfigCaptured(workspaceRoot, options).resolution;
491
+ }
492
+
460
493
  function cleanConfiguredPath(value) {
461
494
  if (typeof value !== 'string') return null;
462
495
  const trimmed = value.trim();
@@ -769,6 +802,7 @@ module.exports = {
769
802
  getRepoManifestHomes,
770
803
  readProjectDocs,
771
804
  resolveDocsConfig,
805
+ resolveDocsConfigCaptured,
772
806
  validateDocsConfig,
773
807
  normalizeHostProjectDocs,
774
808
  parseContextMap,
@@ -598,6 +598,10 @@
598
598
  "cease fire",
599
599
  "rule-of-3",
600
600
  "rule of 3",
601
+ "every round finds a new edge case",
602
+ "every review round finds a new edge case",
603
+ "又陷入反覆修正",
604
+ "反覆測試地獄",
601
605
  "卡住",
602
606
  "失敗",
603
607
  "重複",