harness-everything 0.45.0 → 0.46.1
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.
- package/.agents/harness-everything/project-docs.json +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +24 -0
- package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/README.md +75 -0
- package/benchmarks/results/live-host/claude-341-outline-dispatch-2026-10-28/capture.sanitized.jsonl +32 -0
- package/ci/fixtures/doc-obligations/read-boundary/boundary-policy.json +2 -2
- package/ci/mechanism-2ax-doc-lifecycle.test.js +2 -1
- package/ci/mechanism-325-3b-s2b-capture.test.js +6 -1
- package/ci/mechanism-325-f4-projections.test.js +11 -1
- package/ci/mechanism-prb-outline-review.test.js +1449 -0
- package/docs/adr/DOCS-001-project-docs-config.md +32 -0
- package/docs/adr/DOCS-002-adr-identity-supersession-index.md +34 -0
- package/docs/adr/DOCS-003-prd-revision-contract.md +29 -0
- package/docs/adr/DOCS-004-doc-change-step-type.md +28 -0
- package/docs/adr/DOCS-005-doc-obligations-evidence-producer.md +29 -0
- package/docs/adr/DOCS-006-reconciliation-pilot.md +50 -0
- package/docs/adr/DOCS-007-shared-doc-craft-derived-views.md +31 -0
- package/docs/adr/DOCS-008-obligation-abstraction-bounded-consumer.md +30 -0
- package/docs/doc-workflow-reconciliation.md +47 -1
- package/docs/governed-index.md +8 -0
- package/docs/platform-capabilities.md +2 -0
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/doc-evidence.js +55 -0
- package/hooks/hooks.json +1 -1
- package/hooks/scripts/lib/doc-reconciliation-events.js +121 -9
- package/hooks/scripts/lib/doc-reconciliation-pilot.js +128 -15
- package/hooks/scripts/lib/doc-reconciliation-provider.js +976 -8
- package/hooks/scripts/state-persist.js +10 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-events.js +121 -9
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +128 -15
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-provider.js +976 -8
- package/plugins/harness-everything/hooks/scripts/state-persist.js +10 -0
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-evidence.js +55 -0
- package/sbom.cdx.json +7 -7
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DOCS-001
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DOCS-001 — Project docs config resolution (D5)
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
- The committed, host-neutral `.agents/harness-everything/project-docs.json` wholly overrides differing host `projectDocs` values.
|
|
11
|
+
- `AGENTS.md` is never a configuration source.
|
|
12
|
+
- When the project file overrides a differing host value, the resolver reports `project-overrides-host`.
|
|
13
|
+
|
|
14
|
+
## Context
|
|
15
|
+
|
|
16
|
+
| Issue | Subject | State |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| #325 | Documentation family, PRD/ADR governance and doc-change workflow | OPEN |
|
|
19
|
+
|
|
20
|
+
- Contract: [document-lifecycle.md § Configuration (D5)](../document-lifecycle.md#configuration-d5).
|
|
21
|
+
|
|
22
|
+
## Evidence
|
|
23
|
+
|
|
24
|
+
- Implementation: [project-docs-resolver.js](../../harness-everything/scripts/project-docs-resolver.js) (`project-overrides-host`, around line 390), [doc-check.js](../../harness-everything/scripts/doc-check.js).
|
|
25
|
+
- Test: [mechanism-2aw-project-docs-config.test.js](../../ci/mechanism-2aw-project-docs-config.test.js).
|
|
26
|
+
- Repo-wide gate, not row-specific: [reference-check.js](../../ci/reference-check.js).
|
|
27
|
+
- `AGENTS.md` exclusion is stated in [document-lifecycle.md](../document-lifecycle.md) (configuration table). The resolver has no `AGENTS.md` read path.
|
|
28
|
+
- Config in this repo: [project-docs.json](../../.agents/harness-everything/project-docs.json).
|
|
29
|
+
|
|
30
|
+
## Open gates
|
|
31
|
+
|
|
32
|
+
- #325 is OPEN as the documentation-family umbrella. Shipped slices do not close it.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DOCS-002
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DOCS-002 — ADR/REQ identity, supersession and generated index (D6.1–D6.4)
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
- ADR IDs are `<DOMAIN>-NNN` (domain 2–10 uppercase alphanumerics, at least 3 digits). The domain must be declared in `domains`.
|
|
11
|
+
- `status` is required and uses `proposed` / `accepted` / `rejected` / `superseded`.
|
|
12
|
+
- Aliases and `legacy:` references resolve to one document. Self-aliases and collisions are diagnosed.
|
|
13
|
+
- A `supersedes` edge takes effect only from an `accepted` ADR to a target already `superseded`. Cycles are diagnosed.
|
|
14
|
+
- `conflicts_with` stays a recorded conflict until an effective `supersedes` edge exists.
|
|
15
|
+
- The index in `docs/governed-index.md` is generated, not hand-written.
|
|
16
|
+
|
|
17
|
+
## Context
|
|
18
|
+
|
|
19
|
+
| Issue | Subject | State |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| #325 | Documentation family, PRD/ADR governance and doc-change workflow | OPEN |
|
|
22
|
+
|
|
23
|
+
- Contract: [document-lifecycle.md § IDs](../document-lifecycle.md#ids-aliases-and-references-d62-d68), [§ Supersession](../document-lifecycle.md#supersession-d63-d64).
|
|
24
|
+
|
|
25
|
+
## Evidence
|
|
26
|
+
|
|
27
|
+
- Implementation: [doc-lifecycle.js](../../harness-everything/scripts/doc-lifecycle.js) (`ADR_ID` at line 20; `unknown-domain` at line 1078; `supersede-cycle` at line 1220).
|
|
28
|
+
- Test: [mechanism-2ax-doc-lifecycle.test.js](../../ci/mechanism-2ax-doc-lifecycle.test.js).
|
|
29
|
+
- Repo-wide gate, not row-specific: [reference-check.js](../../ci/reference-check.js).
|
|
30
|
+
- Generator: [governed-index.md](../governed-index.md), produced by `doc-lifecycle.js index` (see `derived` in [project-docs.json](../../.agents/harness-everything/project-docs.json)).
|
|
31
|
+
|
|
32
|
+
## Open gates
|
|
33
|
+
|
|
34
|
+
- #325 is OPEN as the documentation-family umbrella. Shipped slices do not close it.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DOCS-003
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DOCS-003 — Contract-style PRD revision chain (D6.9)
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
- An effective PRD is a frozen revision chain with an approval record.
|
|
11
|
+
- A change creates a new complete revision copied from the current effective revision. The effective snapshot is never edited in place.
|
|
12
|
+
- This replaces the D6.1 living-PRD model for PRDs only. ADRs, tickets and other roles are unchanged.
|
|
13
|
+
|
|
14
|
+
## Context
|
|
15
|
+
|
|
16
|
+
| Issue | Subject | State |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| #325 | Documentation family, PRD/ADR governance and doc-change workflow | OPEN |
|
|
19
|
+
|
|
20
|
+
- Contract: [document-lifecycle.md § Contract-style PRD revisions (D6.9)](../document-lifecycle.md#contract-style-prd-revisions-d69).
|
|
21
|
+
|
|
22
|
+
## Evidence
|
|
23
|
+
|
|
24
|
+
- Implementation: [doc-lifecycle.js](../../harness-everything/scripts/doc-lifecycle.js) (revision identity, approval, immutability and `immutable-revision-modified` diagnostics).
|
|
25
|
+
- Test: [mechanism-2ay-prd-revisions.test.js](../../ci/mechanism-2ay-prd-revisions.test.js). Its header cites the D6.9 contract section.
|
|
26
|
+
|
|
27
|
+
## Open gates
|
|
28
|
+
|
|
29
|
+
- #325 is OPEN as the documentation-family umbrella. Shipped slices do not close it.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DOCS-004
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DOCS-004 — `doc-change` step type
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
- `doc-change` is an explicit documentation-edit step type.
|
|
11
|
+
- It has no implicit TDD binding. Step types are declared in a registry, not inferred.
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
| Issue | Subject | State |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| #326 | Extract step-type registry and add doc-change step (split from #320 for #325) | CLOSED |
|
|
18
|
+
| #328 | Declarative step-type registry with declared-only doc-change (#326) | MERGED |
|
|
19
|
+
|
|
20
|
+
## Evidence
|
|
21
|
+
|
|
22
|
+
- Implementation: [step-types.json](../../hooks/scripts/lib/step-types.json) (`"id": "doc-change"`), [workflow-obligations.js](../../hooks/scripts/lib/workflow-obligations.js).
|
|
23
|
+
- Test: [mechanism-2az-step-type-registry.test.js](../../ci/mechanism-2az-step-type-registry.test.js).
|
|
24
|
+
|
|
25
|
+
## Open gates
|
|
26
|
+
|
|
27
|
+
- None for #326 and #328.
|
|
28
|
+
- #325 is OPEN as the documentation-family umbrella (see DOCS-001).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DOCS-005
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DOCS-005 — Doc obligations and trusted evidence producer (phase 3a)
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
- Documentation MUST obligations fail closed: missing or untrusted evidence yields a failing result, never a pass.
|
|
11
|
+
- Evidence is produced by a trusted producer. Baseline-bound evidence is refused when the baseline has no whole-baseline digest.
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
| Issue | Subject | State |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| #334 | Phase 3a doc-change obligation derivation and evidence producer (#325) | MERGED |
|
|
18
|
+
| #335 | Bind deleted and renamed paths to baseline evidence | CLOSED |
|
|
19
|
+
| #342 | Read baseline and destination identities exactly once (fix for #335) | MERGED |
|
|
20
|
+
|
|
21
|
+
## Evidence
|
|
22
|
+
|
|
23
|
+
- Implementation: [doc-obligations.js](../../harness-everything/scripts/doc-obligations.js), [doc-evidence.js](../../harness-everything/scripts/doc-evidence.js) (digest refusal near line 165), [doc-evidence-capture.js](../../harness-everything/scripts/doc-evidence-capture.js).
|
|
24
|
+
- Tests: [mechanism-325-3a-doc-obligations.test.js](../../ci/mechanism-325-3a-doc-obligations.test.js), [mechanism-325-3a-doc-evidence.test.js](../../ci/mechanism-325-3a-doc-evidence.test.js), [mechanism-335-baseline-evidence.test.js](../../ci/mechanism-335-baseline-evidence.test.js).
|
|
25
|
+
|
|
26
|
+
## Open gates
|
|
27
|
+
|
|
28
|
+
- None specific to #334, #335 or #342.
|
|
29
|
+
- #325 is OPEN as the documentation-family umbrella (see DOCS-001).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DOCS-006
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DOCS-006 — Doc reconciliation pilot (S1 default-OFF, F4 limited credit)
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
- The S1 runtime is default OFF. It runs only when `HARNESS_DOC_RECONCILIATION_PILOT=1`.
|
|
11
|
+
- Fail-closed: evidence that is not trusted is withheld, not credited.
|
|
12
|
+
- F4 gives positive credit for `doc-lint` and `link-check`, from a host-captured run of the installed doc-check. The contract limits positive credit to Claude Code ([F4 contract](../doc-workflow-reconciliation.md#f4-credited-reconciliation-claude-only-pilot-on), line 1158).
|
|
13
|
+
- F4 also gives credit for `quality-review` from an independent host reviewer (`SubagentStop`).
|
|
14
|
+
- `conflict-check` (PR #385): `doc-check.js check --conflict-assessment` requires an exact `--base` SHA and runs lifecycle lint with `--all`. It credits only a complete corpus with no lint error diagnostics, and checks recorded and structural conflicts only. Prose contradictions stay with `quality-review`. Plain `check` earns no conflict credit.
|
|
15
|
+
- The observed live runs (Claude Code 2.1.309, runs 4–5) credited only `doc-lint` and `link-check`. `quality-review` and `conflict-check` have mechanism tests only.
|
|
16
|
+
- Other capabilities stay withheld. Expansion beyond the limited pilot is gated on #341 E1.
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
flowchart LR
|
|
20
|
+
E["HARNESS_DOC_RECONCILIATION_PILOT=1"] --> P["doc-reconciliation-pilot.js"]
|
|
21
|
+
P -->|"host-captured doc-check"| L["doc-lint + link-check: credited"]
|
|
22
|
+
P -->|"independent SubagentStop reviewer"| Q["quality-review: credited (mechanism tests)"]
|
|
23
|
+
P -->|"check --conflict-assessment, complete corpus"| C["conflict-check: credited (mechanism tests)"]
|
|
24
|
+
P -. "all other capabilities" .-> W["withheld"]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Context
|
|
28
|
+
|
|
29
|
+
| Issue | Subject | State |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| #341 | Phased snapshot boundary, conformance suite, obligation contract | OPEN |
|
|
32
|
+
| #350 | Docuvia pilot limitations (code-to-doc dependency edge, derivation hygiene) | OPEN |
|
|
33
|
+
| #358 | Credit host-captured doc evidence on Claude Code (#325) | MERGED |
|
|
34
|
+
| #375 | Retain adjudicated E1 blockers and Phase 3c handoff | MERGED |
|
|
35
|
+
| #385 | Conflict-check producer from a full-corpus assessment | MERGED |
|
|
36
|
+
|
|
37
|
+
- #375 records zero qualifying E1 cycles, so expansion stays gated.
|
|
38
|
+
- Contract: [doc-workflow-reconciliation.md § S1 switch](../doc-workflow-reconciliation.md#s1--switch-trusted-scope-and-fail-closed-no-credit-gate).
|
|
39
|
+
|
|
40
|
+
## Evidence
|
|
41
|
+
|
|
42
|
+
- Implementation: [doc-reconciliation-pilot.js](../../hooks/scripts/lib/doc-reconciliation-pilot.js) (env gate at line 8; `doc-lint` / `link-check` credit at line 246; `quality-review` collection from `SubagentStop` at lines 244 and 254; `conflict-check` collection at lines 251–252), [doc-check.js](../../harness-everything/scripts/doc-check.js) (flag parse at lines 86–88; `--base` required at line 102; `--all` lint at line 108; assessment at lines 468–498; credit at line 501), [doc-reconciliation-provider.js](../../hooks/scripts/lib/doc-reconciliation-provider.js) (corpus binding check at lines 763–785), [doc-reconciliation-core.js](../../hooks/scripts/lib/doc-reconciliation-core.js), [doc-reconciliation-producer.js](../../hooks/scripts/lib/doc-reconciliation-producer.js), [doc-reconciliation-events.js](../../hooks/scripts/lib/doc-reconciliation-events.js).
|
|
43
|
+
- F4 contract: [doc-workflow-reconciliation.md § F4](../doc-workflow-reconciliation.md#f4-credited-reconciliation-claude-only-pilot-on). `conflict-check` row: [doc-workflow-reconciliation.md](../doc-workflow-reconciliation.md) line 1174.
|
|
44
|
+
- Tests: [mechanism-pr-a-conflict-check.test.js](../../ci/mechanism-pr-a-conflict-check.test.js) (17/17 PASS at this SHA; mechanism only, no live-host run), [mechanism-325-3b-preparation.test.js](../../ci/mechanism-325-3b-preparation.test.js), [mechanism-325-3b-s1-review.test.js](../../ci/mechanism-325-3b-s1-review.test.js), [mechanism-325-3b-s2a-core.test.js](../../ci/mechanism-325-3b-s2a-core.test.js), [mechanism-325-3b-s2b-wiring.test.js](../../ci/mechanism-325-3b-s2b-wiring.test.js), [mechanism-341-opencode-lane.test.js](../../ci/mechanism-341-opencode-lane.test.js).
|
|
45
|
+
|
|
46
|
+
## Open gates
|
|
47
|
+
|
|
48
|
+
- #341 OPEN: E1 GO / REVISE / NO-GO gates any expansion beyond the limited pilot.
|
|
49
|
+
- #350 OPEN: Docuvia pilot limitations.
|
|
50
|
+
- #325 OPEN as the documentation-family umbrella (see DOCS-001).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DOCS-007
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DOCS-007 — Shared doc craft and derived-view regeneration (F2/F3)
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
- Each declared derived view in `derived` MUST be regenerated and must match its source when its inputs change (`derived-regenerate`).
|
|
11
|
+
- Doc-skill craft guidance is shared from one place under `references/documentation/_shared/`, not copied into each skill.
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
| Issue | Subject | State |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| #356 | Share documentation craft across doc skills (#325) | MERGED |
|
|
18
|
+
| #357 | Check derived views with regenerate-equals (#325) | MERGED |
|
|
19
|
+
|
|
20
|
+
- #325 is OPEN as the documentation-family umbrella (see DOCS-001). It is the commit-title parent of both merged PRs.
|
|
21
|
+
|
|
22
|
+
## Evidence
|
|
23
|
+
|
|
24
|
+
- Implementation: [doc-obligations.js](../../harness-everything/scripts/doc-obligations.js) (`derived-regenerate` capability at line 10), [writing-craft.md](../../harness-everything/references/documentation/_shared/writing-craft.md), [maintenance.md](../../harness-everything/references/documentation/_shared/maintenance.md).
|
|
25
|
+
- Tests: [mechanism-325-f3-derived-views.test.js](../../ci/mechanism-325-f3-derived-views.test.js), [mechanism-325-f2-doc-craft.test.js](../../ci/mechanism-325-f2-doc-craft.test.js).
|
|
26
|
+
- Repo-wide gate, not row-specific: [reference-check.js](../../ci/reference-check.js).
|
|
27
|
+
|
|
28
|
+
## Open gates
|
|
29
|
+
|
|
30
|
+
- None specific to #356 or #357.
|
|
31
|
+
- #325 is OPEN as the documentation-family umbrella (see DOCS-001).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DOCS-008
|
|
3
|
+
status: accepted
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DOCS-008 — #320 obligation abstraction as a bounded doc consumer
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
|
|
10
|
+
- The doc workflow reconciliation consumes the workflow obligation abstraction in a bounded way: the documentation path only, under the DOCS-006 gate.
|
|
11
|
+
- Broad promotion of the abstraction to generic workflow use is not approved. It is gated on the Phase 3c H1–H4 paired-task evidence and owner sign-off.
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
|
|
15
|
+
| Issue | Subject | State |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| #320 | Compile workflow/skill metadata into a dynamic decision graph | OPEN |
|
|
18
|
+
|
|
19
|
+
- Phase 3c is planned, not shipped: [document-lifecycle.md § Phase 3c](../document-lifecycle.md#phase-3c--architecture-promotion-gate-for-320-planned-not-shipped).
|
|
20
|
+
|
|
21
|
+
## Evidence
|
|
22
|
+
|
|
23
|
+
- Implementation: [doc-reconciliation-core.js](../../hooks/scripts/lib/doc-reconciliation-core.js), [doc-obligations.js](../../harness-everything/scripts/doc-obligations.js) (the obligation table consumed by the doc path).
|
|
24
|
+
- Consumer tests (exercise `doc-reconciliation-core.js` consuming `deriveDocObligations`): [mechanism-325-3b-s2a-core.test.js](../../ci/mechanism-325-3b-s2a-core.test.js), [mechanism-325-3b-s2b-wiring.test.js](../../ci/mechanism-325-3b-s2b-wiring.test.js).
|
|
25
|
+
- Fail-closed controls (not evidence of the consumer): [mechanism-325-3b-preparation.test.js](../../ci/mechanism-325-3b-preparation.test.js), [mechanism-325-3b-s1-review.test.js](../../ci/mechanism-325-3b-s1-review.test.js), [mechanism-341-opencode-lane.test.js](../../ci/mechanism-341-opencode-lane.test.js).
|
|
26
|
+
|
|
27
|
+
## Open gates
|
|
28
|
+
|
|
29
|
+
- #320 OPEN. Its own promotion gate is unmet: H1–H4 paired-task evidence has not been collected.
|
|
30
|
+
- Pilot gate (#341 E1) applies, see DOCS-006.
|
|
@@ -966,7 +966,7 @@ fields through sanitization; a transcript alone, synthetic payload or screenshot
|
|
|
966
966
|
this hook-capture evidence.
|
|
967
967
|
|
|
968
968
|
The origin provider's qualification check MUST resolve that retained evidence reference (or its
|
|
969
|
-
verified export), validate captured host/event kinds, nonempty tool/subagent IDs,
|
|
969
|
+
verified export), validate captured host/event kinds, nonempty tool/subagent IDs, session/tool-use
|
|
970
970
|
correlation and recorded digests, and match the supported host/capture implementation being used.
|
|
971
971
|
Missing, malformed, mismatched or unresolvable qualification evidence returns unavailable and no
|
|
972
972
|
credit. Do not enable qualification from a caller `trusted` flag, environment toggle, populated
|
|
@@ -976,6 +976,22 @@ host capture path exposed the fields; every later event still needs current orig
|
|
|
976
976
|
A retained capture does not authenticate arbitrary local journals or imply E1 GO. Record the live
|
|
977
977
|
gate as unmet until this check actually passes; do not label the docs or deterministic tests as live.
|
|
978
978
|
|
|
979
|
+
For PR-B outline dispatches, the versioned mapping contract is
|
|
980
|
+
`claude-outline-dispatch-mapping/2`. A Task/Agent `PreToolUse` anchor requires `session_id` plus
|
|
981
|
+
`tool_use_id`, one valid marker in `tool_input.prompt`, explicit
|
|
982
|
+
`tool_input.run_in_background === false`, and no `tool_input.isolation` property. Exactly one
|
|
983
|
+
Task/Agent `PostToolUse` with that key must report `tool_response.status === 'completed'` and a
|
|
984
|
+
nonempty `tool_response.agentId`; that observed ID must match exactly one `SubagentStart` and one
|
|
985
|
+
`SubagentStop` with the same session. Journal order requires PreToolUse before SubagentStart before
|
|
986
|
+
SubagentStop; the completed PostToolUse may appear before or after SubagentStop. The Start and Stop
|
|
987
|
+
must each report a canonical `cwd` equal to the marker's worker root. `prompt_id` is shared across
|
|
988
|
+
dispatches, and neither `parent_tool_use_id` nor `interrupted` is an outline dispatch key or
|
|
989
|
+
completion signal. Captured records retain `tool_response.status` as `tool_response_status`, the
|
|
990
|
+
explicit `run_in_background` value, and whether `isolation` was present; absent status or background
|
|
991
|
+
fields remain absent. Async launches and ambiguous or contradictory matches stay unresolved.
|
|
992
|
+
The retained qualification export still has no outline mapping, so production remains
|
|
993
|
+
`outline-live-gate-unmet` and this contract does not enable positive credit.
|
|
994
|
+
|
|
979
995
|
### Workflow credit, review progression and freshness
|
|
980
996
|
|
|
981
997
|
Every ON + supported credit attempt MUST invoke the following current chain, with the same
|
|
@@ -1213,3 +1229,33 @@ claude -p "<fixed capture prompt, containing the exact check command>" \
|
|
|
1213
1229
|
- **The live gate is a repository-pinned field-availability check, not a running-host compatibility check (F10).** The qualification export records Claude Code `2.1.308`, but the retained Claude hook payloads expose no host-version field for the runtime to compare. The export and sibling sanitized capture validate the recorded payload kinds and correlations for that host version; they do not establish compatibility with the current session. Editing both repository artifacts and updating the capture digest can reopen the gate without a new live capture.
|
|
1214
1230
|
- **The unsupported-host check is vacuous in production (F11).** The only production caller asserts the Claude host id. OpenCode and Codex stay unavailable because their plugins do not mount `state-persist.js`, not because the host is detected.
|
|
1215
1231
|
- **Event files are not tamper-proof (F13).** Event files are created exclusively, and the journal is checked against them. The local agent can still rewrite event files and rebuild the journal. This is the owner's bounded claim, not a guarantee of tamper resistance.
|
|
1232
|
+
|
|
1233
|
+
## PR-B: dedicated pre-draft outline confirmation
|
|
1234
|
+
|
|
1235
|
+
Status: **implemented behind the existing pilot switch; mechanism and package checks pass; no positive live-host credit is enabled.** This capability follows owner decisions 9 and 11: an independent reviewer approves exact O before drafting; the outline cites one row from the pinned inventory; final D is a strict descendant of O; a coordinator handoff records review-complete → fresh-draft-dispatch. Owner approval is provenance only and never a runtime reviewer principal.
|
|
1236
|
+
|
|
1237
|
+
```mermaid
|
|
1238
|
+
flowchart LR
|
|
1239
|
+
B["Original baseline B<br/>task and config pinned"] --> O["Proposed outline O<br/>new target absent at B"]
|
|
1240
|
+
I["Coordinator-pinned inventory"] --> R["Independent review<br/>exact O + cited row"]
|
|
1241
|
+
O --> R
|
|
1242
|
+
R --> H["Immutable handoff +<br/>exclusive writer lease"]
|
|
1243
|
+
H --> P["Task/Agent PreToolUse<br/>exact prompt marker"]
|
|
1244
|
+
P --> S["Synchronous seam<br/>clean worker root at O<br/>one-use token consumed"]
|
|
1245
|
+
S --> W["Correlated worker start/result"]
|
|
1246
|
+
W --> D["Final candidate D<br/>strict descendant of O"]
|
|
1247
|
+
D --> A["Outline result and<br/>aggregate B→D MUSTs"]
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
| Evidence | Required binding |
|
|
1251
|
+
|---|---|
|
|
1252
|
+
| B and task | Keep the workflow-start B commit/root/tree and ordered requirement IDs for every attempt. Config scope must still match B. An attempt cannot reset B to O. |
|
|
1253
|
+
| Inventory | The coordinator supplies exact bytes and `inventory-pin.json` outside the candidate tree. The pinned source is `report-inv.md`, SHA-256 `2741bd4514d2cd68f57d93a2b3ce94cbe597ca62d30e26f0ca58e882df5b03c6`, run `e1-inv-1`, input SHA `0ef0541c6cae550e8269ba712e03797e0e987dfb`, approval `#325` comment `6436207729`, decision 1. The provider rehashes the bytes and compares every pin field before resolving one row and domain. Missing, stale, duplicate, ambiguous, wrong-target or unavailable evidence is unresolved with no fallback. Synthetic test pins prove parser/mechanism binding only. |
|
|
1254
|
+
| O and review | O is bound to its commit, tree, target blobs and stored-byte digests. Its `proposed` ADR frontmatter cites one `Inventory row: <id>`, target and domain, and contains no completed Decision text. A host-captured `SubagentStop` review must approve those exact O bytes and the pinned inventory, with a distinct host `agent_id` and correlated Agent/Task result. |
|
|
1255
|
+
| Handoff and dispatch | One immutable attempt handoff binds B, O, scope, inventory, accepted review, coordinator principal, lease and one-use token. Agent/Task dispatch accepts exactly one `HARNESS_OUTLINE_DISPATCH_V1` block in `tool_input.prompt`, whose token must equal the handoff token; it does not search free-form text or use another field or cwd as a fallback. The outline anchor also requires explicit `run_in_background === false` and absence of any `isolation` key, including null or empty values. Before worker start, PreToolUse verifies the handoff, review, pin, exclusive lease, canonical root, HEAD/tree at O and clean index/worktree including untracked files; it creates a write-once handoff-digest seal, stores validation digests/reasons, then consumes the token by exclusive creation. Every predecessor seal, seam and token remains bound to the original handoff bytes, so an overwritten predecessor invalidates the chain. |
|
|
1256
|
+
| Worker and D | The dispatch key is `(session_id, tool_use_id)`. Exactly one matching Task/Agent result must carry `tool_response.status === 'completed'`; its observed `tool_response.agentId` must map to exactly one `SubagentStart` and one `SubagentStop` with the same `session_id` and `agent_id`. Require PreToolUse < SubagentStart < SubagentStop in journal order, but do not order the completed result against Stop. Both worker events need exact canonical `cwd === workerRoot`. `prompt_id` and `parent_tool_use_id` are not correlation keys; `interrupted` is not completion proof. Async launches, failures, cancellation, missing/duplicate events, replayed IDs, worktree isolation and root mismatch stay unresolved. D is checked against the current candidate separately from O. O must be a strict ancestor of D. A squash may retain the cycle only when a coordinator correspondence record proves identical target blobs between retained pre-merge D and the merged tree; changed bytes need fresh final quality review. |
|
|
1257
|
+
| Checker outcome | `outline-confirmation` has its own pass/unresolved lane. Generic command success and waivers never satisfy it. No D is incomplete/unresolved. Its result is separate from the aggregate result over every MUST derived from original B→D; an outline pass never waives lint, conflict, quality review, requirement-sync or other MUSTs. |
|
|
1258
|
+
|
|
1259
|
+
Capture and validation failures stay fail-open to host tools and withhold evidence credit. Unclassified shell commands and writes outside the correlated worker/root remain unresolved. Retries use a fresh attempt, token, clean worktree and immutable handoff; only one non-superseded leaf is current. An identical same-tool-ID/input callback may reuse its stored seam, but repeated PreToolUse records remain ambiguous for evidence credit; changed input or token reuse is unresolved. OpenCode and Codex remain unsupported for positive outline credit.
|
|
1260
|
+
|
|
1261
|
+
The current retained Claude qualification does not establish the versioned `claude-outline-dispatch-mapping/2` contract. The outline provider therefore grants no positive live-host credit until a retained Claude capture validates the prompt, coordinator, result status/agent ID, explicit non-background boundary, isolation absence and exact worker-root mapping. Deterministic fixtures exercise the real hook/provider/adapter/checker/pilot path with a synthetic inventory pin; they are mechanism evidence, not live-host behavior or E1 readiness.
|
package/docs/governed-index.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
- `(no id)` — `docs/architecture.md` (role: reference; status: active)
|
|
6
6
|
- `(no id)` — `docs/doc-workflow-reconciliation.md` (role: reference; status: active)
|
|
7
|
+
- `DOCS-001` — `docs/adr/DOCS-001-project-docs-config.md` (role: adr; status: accepted)
|
|
8
|
+
- `DOCS-002` — `docs/adr/DOCS-002-adr-identity-supersession-index.md` (role: adr; status: accepted)
|
|
9
|
+
- `DOCS-003` — `docs/adr/DOCS-003-prd-revision-contract.md` (role: adr; status: accepted)
|
|
10
|
+
- `DOCS-004` — `docs/adr/DOCS-004-doc-change-step-type.md` (role: adr; status: accepted)
|
|
11
|
+
- `DOCS-005` — `docs/adr/DOCS-005-doc-obligations-evidence-producer.md` (role: adr; status: accepted)
|
|
12
|
+
- `DOCS-006` — `docs/adr/DOCS-006-reconciliation-pilot.md` (role: adr; status: accepted)
|
|
13
|
+
- `DOCS-007` — `docs/adr/DOCS-007-shared-doc-craft-derived-views.md` (role: adr; status: accepted)
|
|
14
|
+
- `DOCS-008` — `docs/adr/DOCS-008-obligation-abstraction-bounded-consumer.md` (role: adr; status: accepted)
|
|
7
15
|
|
|
8
16
|
## PRD revision chains
|
|
9
17
|
|
|
@@ -210,6 +210,8 @@ The editorial doc check (`harness-everything/scripts/doc-check.js`, F1) is reach
|
|
|
210
210
|
|
|
211
211
|
Credited reconciliation for F4 (Claude Code only, pilot ON) is **implemented, with mechanism and package evidence and one live field-availability capture**. Credit works for `doc-lint` and `link-check` through a registered `doc-check` run, and for an independent `quality-review` with bound path digests. The capture under `benchmarks/results/live-host/claude-325-3b-s2b/` proves field availability on Claude Code 2.1.308 for one session. It is not live-host credit, and no live-host credit run exists yet. Deterministic mechanism tests (replay through the real hook entry points, with a CI-only qualification preloader) prove the chain.
|
|
212
212
|
|
|
213
|
+
PR-B adds a dedicated `outline-confirmation` evidence lane for Claude captures with separate B/O/D checks, a coordinator-sealed pre-dispatch handoff, pinned-inventory row binding, and an independent exact-O review. It is **mechanism and package evidence only**. The retained qualification does not yet establish `tool_input.prompt`, the coordinator identity, synchronous PreToolUse boundary or worker-root mapping, so positive live-host outline credit remains disabled. OpenCode and Codex receive no outline credit; missing or ambiguous capture stays unresolved and host tools remain fail-open. The outline result does not replace the aggregate B→D MUST result or the existing exact-D `quality-review` contract.
|
|
214
|
+
|
|
213
215
|
### Own-repository governance guard (PR #376)
|
|
214
216
|
|
|
215
217
|
On `pull_request`, a standalone `doc-governance` job extracts `scripts/doc-governance-guard.js` from the event base commit and runs those bytes against the exact event base/head SHAs. Its nonzero result fails that job; `self-regression` and the other CI jobs run independently. The ordinary test suite may still run on GitHub's PR checkout SHA, which can be a synthetic merge commit; it is reported separately. The guard never falls back to the candidate copy. PR #376's reviewed base lacked the verifier; that historical bootstrap reported BOOTSTRAP_UNCHECKED without executing candidate bytes. Bases containing the guard (including main at 0ef0541c) execute the base-owned verifier. If a selected PR base lacks the guard, the wrapper retains the warning/step-summary-only bootstrap path with exit 0; this is unverified governance, not PASS. Every nonzero exit from an extracted verifier, including FAIL and BOOTSTRAP_UNCHECKED, fails the governance job. Other CI jobs run independently.
|
|
@@ -433,6 +433,61 @@ function evaluateObligation(obligation, context) {
|
|
|
433
433
|
return entry ? baselineBound(item, entry) : candidateBound(item, path);
|
|
434
434
|
};
|
|
435
435
|
|
|
436
|
+
if (capability === 'outline-confirmation') {
|
|
437
|
+
const candidates = matching.filter(item => reviewList.includes(item) && item.kind === 'outline-confirmation');
|
|
438
|
+
if (candidates.length !== 1) {
|
|
439
|
+
return result('unresolved', 'none',
|
|
440
|
+
'outline-confirmation requires one validated pre-draft O/review/handoff/dispatch record and a final D; generic execution and waivers do not qualify',
|
|
441
|
+
candidates.map(item => item.id));
|
|
442
|
+
}
|
|
443
|
+
const item = candidates[0];
|
|
444
|
+
const evidence = isPlainObject(item.outlineEvidence) ? item.outlineEvidence : null;
|
|
445
|
+
const sha = value => isSha256(value);
|
|
446
|
+
const digestsMatch = map => isPlainObject(map)
|
|
447
|
+
&& Object.keys(map).sort(compareText).join('\0') === scopePaths.slice().sort(compareText).join('\0')
|
|
448
|
+
&& scopePaths.every(path => sha(map[path]));
|
|
449
|
+
const identityValid = Boolean(evidence)
|
|
450
|
+
&& evidence.schema === 'harness.outline-confirmation.v1' && evidence.state === 'validated'
|
|
451
|
+
&& nonEmptyString(evidence.taskId) && nonEmptyString(evidence.attemptId)
|
|
452
|
+
&& /^\d{1,2}$/.test(String(evidence.inventoryRowId || ''))
|
|
453
|
+
&& /^[A-Z][A-Z0-9]{1,9}$/.test(String(evidence.domain || ''))
|
|
454
|
+
&& sha(evidence.inventoryPinDigest) && sha(evidence.inventoryArtifactDigest)
|
|
455
|
+
&& /^[0-9a-f]{40,64}$/.test(String(evidence.baselineCommit || ''))
|
|
456
|
+
&& /^[0-9a-f]{40,64}$/.test(String(evidence.outlineCommit || ''))
|
|
457
|
+
&& /^[0-9a-f]{40,64}$/.test(String(evidence.draftCommit || ''))
|
|
458
|
+
&& /^[0-9a-f]{40,64}$/.test(String(evidence.mergedCommit || ''))
|
|
459
|
+
&& evidence.outlineCommit !== evidence.draftCommit
|
|
460
|
+
&& evidence.ancestry === 'strict-descendant'
|
|
461
|
+
&& sha(evidence.handoffDigest) && sha(evidence.reviewCaptureDigest) && sha(evidence.reviewDigest)
|
|
462
|
+
&& sha(evidence.seamDigest) && sha(evidence.dispatchInputDigest) && sha(evidence.writerLeaseDigest)
|
|
463
|
+
&& nonEmptyString(evidence.dispatchToolUseId) && nonEmptyString(evidence.coordinatorPrincipal)
|
|
464
|
+
&& nonEmptyString(evidence.reviewerPrincipal) && nonEmptyString(evidence.workerPrincipal)
|
|
465
|
+
&& evidence.reviewerPrincipal !== evidence.coordinatorPrincipal
|
|
466
|
+
&& evidence.reviewerPrincipal !== evidence.workerPrincipal
|
|
467
|
+
&& Array.isArray(evidence.targetPaths)
|
|
468
|
+
&& evidence.targetPaths.slice().sort(compareText).join('\0') === scopePaths.slice().sort(compareText).join('\0')
|
|
469
|
+
&& digestsMatch(evidence.outlineArtifactDigests) && digestsMatch(evidence.draftArtifactDigests)
|
|
470
|
+
&& digestsMatch(item.artifactDigests)
|
|
471
|
+
&& scopePaths.every(path => evidence.draftArtifactDigests[path] === item.artifactDigests[path]
|
|
472
|
+
&& candidateBound(item, path));
|
|
473
|
+
const mergeValid = evidence && (evidence.mergeCorrespondence === null
|
|
474
|
+
|| (isPlainObject(evidence.mergeCorrespondence)
|
|
475
|
+
&& ['accepted', 'changed'].includes(evidence.mergeCorrespondence.state)
|
|
476
|
+
&& Array.isArray(evidence.mergeCorrespondence.targetPaths)
|
|
477
|
+
&& evidence.mergeCorrespondence.targetPaths.slice().sort(compareText).join('\0')
|
|
478
|
+
=== scopePaths.slice().sort(compareText).join('\0')));
|
|
479
|
+
const outlineProblems = [];
|
|
480
|
+
if (!identityValid) outlineProblems.push('envelope-or-current-D-binding');
|
|
481
|
+
if (!mergeValid) outlineProblems.push('merge-correspondence');
|
|
482
|
+
if (item.baseDigest !== baselineDigest) outlineProblems.push('original-B-digest');
|
|
483
|
+
if (outlineProblems.length) {
|
|
484
|
+
diagnostics.push({ code: 'outline-evidence-unresolved', obligationId,
|
|
485
|
+
message: `the outline evidence is incomplete or inconsistent: ${outlineProblems.join(', ')}` });
|
|
486
|
+
return result('unresolved', 'none', 'outline evidence is incomplete or unbound to the original B and final D', [item.id]);
|
|
487
|
+
}
|
|
488
|
+
return result('observed', 'pass', 'validated independent review of exact O, coordinator handoff, clean one-use dispatch, pinned inventory, and strict O-to-D evidence', [item.id]);
|
|
489
|
+
}
|
|
490
|
+
|
|
436
491
|
for (const item of matching) {
|
|
437
492
|
if (executionList.includes(item) && !completeExecution(item)) {
|
|
438
493
|
diagnostics.push({
|
package/hooks/hooks.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const
|
|
4
|
+
const { execFileSync } = require('child_process');
|
|
5
5
|
const { _internals: U } = require('./doc-reconciliation-core');
|
|
6
|
+
const { key } = require('./workflow-isolation');
|
|
6
7
|
|
|
7
8
|
// Directory identity as in the producer: native realpath (Windows 8.3 names), case-insensitive on Windows.
|
|
8
9
|
function samePath(a, b) {
|
|
9
10
|
try {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return
|
|
11
|
+
fs.statSync(a);
|
|
12
|
+
fs.statSync(b);
|
|
13
|
+
return key(a) === key(b);
|
|
13
14
|
} catch { return false; }
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -19,6 +20,109 @@ function artifactsFor(records) {
|
|
|
19
20
|
function prdDigest(artifacts) {
|
|
20
21
|
return U.sha(JSON.stringify(Object.keys(artifacts).filter(p => p.startsWith('docs/prd/')).sort().map(p => [p, artifacts[p].digest])));
|
|
21
22
|
}
|
|
23
|
+
|
|
24
|
+
function git(root, args, binary = false) {
|
|
25
|
+
const env = { ...process.env };
|
|
26
|
+
for (const key of Object.keys(env)) if (key.startsWith('GIT_')) delete env[key];
|
|
27
|
+
return execFileSync('git', ['-C', root, ...args], { env, encoding: binary ? null : 'utf8',
|
|
28
|
+
timeout: 10000, maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function shaBytes(value) { return U.sha(Buffer.from(value)); }
|
|
32
|
+
|
|
33
|
+
function outlineEventValid({ event, snapshot, paths, artifacts, baselineArtifacts } = {}) {
|
|
34
|
+
let stage = 'envelope';
|
|
35
|
+
try {
|
|
36
|
+
const evidence = event?.outlineEvidence;
|
|
37
|
+
const root = snapshot.originContext.workspaceRoot;
|
|
38
|
+
const baseline = snapshot.originContext.baseline;
|
|
39
|
+
if (snapshot.originContext.activeHost !== 'claude-code' || !U.record(evidence)
|
|
40
|
+
|| evidence.schema !== 'harness.outline-confirmation.v1' || evidence.state !== 'validated'
|
|
41
|
+
|| evidence.baselineCommit !== baseline?.commit || evidence.taskId !== snapshot.identity.taskId
|
|
42
|
+
|| !/^[0-9a-f]{40,64}$/.test(String(evidence.outlineCommit || ''))
|
|
43
|
+
|| !/^[0-9a-f]{40,64}$/.test(String(evidence.draftCommit || ''))
|
|
44
|
+
|| !/^[0-9a-f]{40,64}$/.test(String(evidence.mergedCommit || ''))
|
|
45
|
+
|| !/^[0-9a-f]{64}$/.test(String(evidence.handoffDigest || ''))
|
|
46
|
+
|| !/^[0-9a-f]{64}$/.test(String(evidence.reviewCaptureDigest || ''))
|
|
47
|
+
|| !/^[0-9a-f]{64}$/.test(String(evidence.reviewDigest || ''))
|
|
48
|
+
|| !/^[0-9a-f]{64}$/.test(String(evidence.seamDigest || ''))
|
|
49
|
+
|| !/^[0-9a-f]{64}$/.test(String(evidence.dispatchInputDigest || ''))
|
|
50
|
+
|| !/^[0-9a-f]{64}$/.test(String(evidence.writerLeaseDigest || ''))
|
|
51
|
+
|| !/^[0-9a-f]{64}$/.test(String(evidence.inventoryPinDigest || ''))
|
|
52
|
+
|| !/^[0-9a-f]{64}$/.test(String(evidence.inventoryArtifactDigest || ''))
|
|
53
|
+
|| !Array.isArray(evidence.targetPaths) || U.stable(evidence.targetPaths) !== U.stable(paths)
|
|
54
|
+
|| !U.record(evidence.outlineArtifactDigests) || !U.record(evidence.draftArtifactDigests)
|
|
55
|
+
|| !U.record(evidence.outlineBlobOids) || !U.record(evidence.draftBlobOids)
|
|
56
|
+
|| U.stable(Object.keys(evidence.outlineArtifactDigests).sort()) !== U.stable(paths)
|
|
57
|
+
|| U.stable(Object.keys(evidence.draftArtifactDigests).sort()) !== U.stable(paths)
|
|
58
|
+
|| U.stable(Object.keys(evidence.outlineBlobOids).sort()) !== U.stable(paths)
|
|
59
|
+
|| U.stable(Object.keys(evidence.draftBlobOids).sort()) !== U.stable(paths)
|
|
60
|
+
|| !U.record(event.draftArtifactDigests) || U.stable(Object.keys(event.draftArtifactDigests).sort()) !== U.stable(paths)
|
|
61
|
+
|| U.stable(event.draftArtifactDigests) !== U.stable(evidence.draftArtifactDigests)
|
|
62
|
+
|| !U.record(event.reviewedArtifactDigests)
|
|
63
|
+
|| !paths.every(p => /^[0-9a-f]{64}$/.test(String(evidence.outlineArtifactDigests[p] || ''))
|
|
64
|
+
&& /^[0-9a-f]{64}$/.test(String(evidence.draftArtifactDigests[p] || ''))
|
|
65
|
+
&& /^[0-9a-f]{40,64}$/.test(String(evidence.outlineBlobOids[p] || ''))
|
|
66
|
+
&& /^[0-9a-f]{40,64}$/.test(String(evidence.draftBlobOids[p] || ''))
|
|
67
|
+
&& artifacts[p]?.digest === evidence.draftArtifactDigests[p])
|
|
68
|
+
|| !/^[0-9a-f]{40,64}$/.test(String(evidence.baselineCommit || ''))
|
|
69
|
+
|| !/^[0-9a-f]{40,64}$/.test(String(evidence.outlineTree || ''))
|
|
70
|
+
|| !/^[0-9a-f]{40,64}$/.test(String(evidence.draftTree || ''))
|
|
71
|
+
|| !U.text(evidence.coordinatorPrincipal) || !U.text(evidence.reviewerPrincipal) || !U.text(evidence.workerPrincipal)
|
|
72
|
+
|| evidence.reviewerPrincipal === evidence.coordinatorPrincipal || evidence.reviewerPrincipal === evidence.workerPrincipal
|
|
73
|
+
|| evidence.ancestry !== 'strict-descendant' || evidence.outlineCommit === evidence.draftCommit) return stage;
|
|
74
|
+
|
|
75
|
+
stage = 'reviewed-object-binding';
|
|
76
|
+
const pinPath = Object.keys(event.reviewedArtifactDigests).filter(p => p.startsWith('doc-reconciliation/outline-confirmation/inventory/'));
|
|
77
|
+
if (pinPath.length !== 1 || event.reviewedArtifactDigests[pinPath[0]] !== evidence.inventoryArtifactDigest
|
|
78
|
+
|| Object.keys(event.reviewedArtifactDigests).length !== paths.length + 1
|
|
79
|
+
|| !paths.every(p => event.reviewedArtifactDigests[p] === evidence.outlineArtifactDigests[p])) return stage;
|
|
80
|
+
|
|
81
|
+
stage = 'current-commit-binding';
|
|
82
|
+
const head = git(root, ['rev-parse', '--verify', 'HEAD^{commit}']).trim();
|
|
83
|
+
const tree = git(root, ['rev-parse', '--verify', 'HEAD^{tree}']).trim();
|
|
84
|
+
if (head !== evidence.mergedCommit || tree !== evidence.draftTree) return stage;
|
|
85
|
+
const verified = commit => git(root, ['rev-parse', '--verify', `${commit}^{commit}`]).trim() === commit;
|
|
86
|
+
if (![evidence.baselineCommit, evidence.outlineCommit, evidence.draftCommit, evidence.mergedCommit].every(verified)) return 'git-object-missing';
|
|
87
|
+
if (evidence.baselineCommit === evidence.outlineCommit) return 'B-to-O-ancestry';
|
|
88
|
+
stage = 'B-to-O-ancestry';
|
|
89
|
+
try { git(root, ['merge-base', '--is-ancestor', evidence.baselineCommit, evidence.outlineCommit]); }
|
|
90
|
+
catch { return stage; }
|
|
91
|
+
stage = 'O-to-D-ancestry';
|
|
92
|
+
try { git(root, ['merge-base', '--is-ancestor', evidence.outlineCommit, evidence.draftCommit]); }
|
|
93
|
+
catch { return stage; }
|
|
94
|
+
stage = 'merge-correspondence';
|
|
95
|
+
if (evidence.mergeCorrespondence === null) {
|
|
96
|
+
if (evidence.draftCommit !== evidence.mergedCommit) return stage;
|
|
97
|
+
} else {
|
|
98
|
+
const merge = evidence.mergeCorrespondence;
|
|
99
|
+
if (!U.record(merge) || !['accepted', 'changed'].includes(merge.state)
|
|
100
|
+
|| merge.draftCommit !== evidence.draftCommit || merge.mergedCommit !== evidence.mergedCommit
|
|
101
|
+
|| U.stable(merge.targetPaths) !== U.stable(paths) || !U.record(merge.targetBlobs)) return stage;
|
|
102
|
+
for (const p of paths) {
|
|
103
|
+
const draftOid = git(root, ['rev-parse', `${evidence.draftCommit}:${p}`]).trim();
|
|
104
|
+
const mergedOid = git(root, ['rev-parse', `${evidence.mergedCommit}:${p}`]).trim();
|
|
105
|
+
if (merge.targetBlobs[p]?.draft !== draftOid || merge.targetBlobs[p]?.merged !== mergedOid
|
|
106
|
+
|| (merge.state === 'accepted' && draftOid !== mergedOid)
|
|
107
|
+
|| (merge.state === 'changed' && draftOid === mergedOid)) return stage;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
stage = 'target-object-binding';
|
|
111
|
+
for (const p of paths) {
|
|
112
|
+
const atBase = git(root, ['ls-tree', '-r', '--name-only', evidence.baselineCommit, '--', p]).trim().split('\n').filter(Boolean);
|
|
113
|
+
if (atBase.includes(p)) return 'target-existed-at-B';
|
|
114
|
+
if (git(root, ['rev-parse', `${evidence.outlineCommit}:${p}`]).trim() !== evidence.outlineBlobOids[p]
|
|
115
|
+
|| git(root, ['rev-parse', `${evidence.mergedCommit}:${p}`]).trim() !== evidence.draftBlobOids[p]) return stage;
|
|
116
|
+
const outlineBytes = git(root, ['show', `${evidence.outlineCommit}:${p}`], true);
|
|
117
|
+
const draftBytes = git(root, ['show', `${evidence.mergedCommit}:${p}`], true);
|
|
118
|
+
if (shaBytes(outlineBytes) !== evidence.outlineArtifactDigests[p]
|
|
119
|
+
|| shaBytes(draftBytes) !== evidence.draftArtifactDigests[p]
|
|
120
|
+
|| shaBytes(draftBytes) !== artifacts[p]?.digest) return 'candidate-artifact-binding';
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
} catch { return 'git-verification-failed'; }
|
|
124
|
+
}
|
|
125
|
+
|
|
22
126
|
function acceptDocEvents(input = {}, dependencies = {}) {
|
|
23
127
|
const { snapshot, eventRefs } = U.record(input) ? input : {};
|
|
24
128
|
const originProvider = dependencies?.originProvider;
|
|
@@ -91,8 +195,12 @@ function acceptDocEvents(input = {}, dependencies = {}) {
|
|
|
91
195
|
seenCapabilities.add(event?.capability);
|
|
92
196
|
const paths = obligation?.scope?.paths || [];
|
|
93
197
|
if (!Array.isArray(event?.paths) || U.stable([...event.paths].sort()) !== U.stable([...paths].sort())) reject('scope-mismatch');
|
|
198
|
+
const outlineCapability = event?.capability === 'outline-confirmation';
|
|
94
199
|
const digests = event?.kind === 'review' ? event.reviewedArtifactDigests : event?.artifactDigests;
|
|
95
|
-
if (
|
|
200
|
+
if (outlineCapability) {
|
|
201
|
+
const outlineValidation = outlineEventValid({ event, snapshot, paths, artifacts, baselineArtifacts });
|
|
202
|
+
if (outlineValidation !== true) reject(`outline-${outlineValidation || 'evidence-invalid'}`);
|
|
203
|
+
} else if (!U.record(digests)) reject('artifact-mismatch');
|
|
96
204
|
else {
|
|
97
205
|
for (const p of paths) {
|
|
98
206
|
if (artifacts[p] && digests[p] !== artifacts[p].digest) reject('artifact-mismatch');
|
|
@@ -135,10 +243,13 @@ function acceptDocEvents(input = {}, dependencies = {}) {
|
|
|
135
243
|
if (!context?.principals?.executors?.includes(event?.principalId)) reject('origin-untrusted');
|
|
136
244
|
}
|
|
137
245
|
if (problems.length) { rejected.push(...problems); continue; }
|
|
138
|
-
const adapted = { id: reference, obligationId,
|
|
246
|
+
const adapted = { id: reference, obligationId,
|
|
247
|
+
kind: outlineCapability ? 'outline-confirmation' : event.kind === 'review' ? 'review' : 'execution',
|
|
139
248
|
paths: [...paths], provenance: event.provenance, baseDigest: snapshot.identity.baselineDigest,
|
|
140
|
-
artifactDigests: U.clone(
|
|
141
|
-
|
|
249
|
+
artifactDigests: U.clone(outlineCapability ? event.draftArtifactDigests : digests),
|
|
250
|
+
...(event.baselineArtifacts ? { baselineArtifacts: U.clone(event.baselineArtifacts) } : {}) };
|
|
251
|
+
if (outlineCapability) Object.assign(adapted, { outlineEvidence: U.clone(event.outlineEvidence) });
|
|
252
|
+
else if (event.kind === 'review') Object.assign(adapted, { verdict: event.verdict, summary: event.summary });
|
|
142
253
|
else Object.assign(adapted, { command: event.command, exitCode: event.exitCode });
|
|
143
254
|
if (event.capability === 'conflict-check') {
|
|
144
255
|
adapted.candidatePrdSet = { digest: prdDigest(artifacts), complete: true, paths: Object.keys(artifacts).filter(p => p.startsWith('docs/prd/')).sort() };
|
|
@@ -146,7 +257,8 @@ function acceptDocEvents(input = {}, dependencies = {}) {
|
|
|
146
257
|
(event.kind === 'review' ? checkerInputs.reviewRecords : checkerInputs.executionRecords).push(adapted);
|
|
147
258
|
acceptedIndex[reference] = { reference, eventId: event.eventId, obligationId, capability: event.capability,
|
|
148
259
|
paths: [...paths], identity: U.clone(event.identity), originRef: response.originRef, artifactBinding: snapshot.identity.candidateDigest,
|
|
149
|
-
artifactDigests: U.clone(digests),
|
|
260
|
+
artifactDigests: U.clone(outlineCapability ? event.draftArtifactDigests : digests),
|
|
261
|
+
...(outlineCapability ? { reviewedArtifactDigests: U.clone(event.reviewedArtifactDigests) } : {}), event };
|
|
150
262
|
}
|
|
151
263
|
if (diagnostics.length || rejected.length) out.state = 'unresolved';
|
|
152
264
|
return out;
|