harness-everything 0.36.0 → 0.36.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +27 -0
- package/ci/mechanism-335-baseline-evidence.test.js +2690 -0
- package/docs/doc-evidence-baseline-binding.md +249 -0
- package/docs/document-lifecycle.md +2 -2
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/doc-evidence.js +452 -19
- package/harness-everything/scripts/doc-obligations.js +32 -5
- 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/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 +452 -19
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-obligations.js +32 -5
- package/sbom.cdx.json +7 -7
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Baseline-bound document evidence (#335)
|
|
2
|
+
|
|
3
|
+
Additive evidence contract for deleted files and the source endpoint of a rename. This extends
|
|
4
|
+
the phase 3a `doc-change` / `requirement-sync` producer in
|
|
5
|
+
[`document-lifecycle.md`](document-lifecycle.md). It does not reconcile evidence with workflow
|
|
6
|
+
disposition (#325 phase 3b/3c), add a lock, or add a router/provider path.
|
|
7
|
+
|
|
8
|
+
## Data shape
|
|
9
|
+
|
|
10
|
+
The trusted baseline keeps its existing whole-tree identity and may add per-path SHA256 values:
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
baseline = {
|
|
14
|
+
digest: '<whole-baseline identity>',
|
|
15
|
+
artifacts: {
|
|
16
|
+
'docs/adr/GRPH-008.md': '<sha256 of the trusted baseline file>'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`digest` is still required for every record, including baseline-only evidence. `artifacts` is
|
|
22
|
+
optional; when a scope declares baseline-only paths, the matching per-path values are required.
|
|
23
|
+
|
|
24
|
+
Derivation keeps every original `scope.paths` entry and adds an annotation for paths that cannot
|
|
25
|
+
have a candidate artifact:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
scope = {
|
|
29
|
+
paths: ['docs/reference/guide.md', 'docs/reference/old-guide.md'],
|
|
30
|
+
baselinePaths: [
|
|
31
|
+
{ path: 'docs/reference/old-guide.md', reason: 'rename-from', renameTo: 'docs/reference/guide.md' }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `reason: 'deleted'` describes a path present in the trusted baseline and absent from the
|
|
37
|
+
candidate. A deleted entry must not carry `renameTo` at all; any own `renameTo` property,
|
|
38
|
+
including an explicitly present `undefined`, is malformed and fails the obligation closed.
|
|
39
|
+
- `reason: 'rename-from'` describes the old endpoint of an identified content-identical move.
|
|
40
|
+
The destination is named by `renameTo` and must itself be a member of the same
|
|
41
|
+
`scope.paths`. A `renameTo` outside the scope is malformed and fails the obligation closed.
|
|
42
|
+
- Every `baselinePaths` entry's fields (`path`, `reason`, and the own-key `renameTo` presence) are
|
|
43
|
+
snapshotted through one guarded inspection, and each field result is carried exactly once. An
|
|
44
|
+
entry whose field getter or descriptor trap throws is a metadata error (`baseline-path-invalid`)
|
|
45
|
+
and fails the entry closed instead of escaping the checker; a field accessor that throws only on
|
|
46
|
+
a later read cannot abort the audit mirror; the mirror is built from the metadata-validated
|
|
47
|
+
snapshot (validated path and captured digest) and is never a second read of the raw entry
|
|
48
|
+
- A path without a `baselinePaths` entry remains a normal candidate path and keeps the existing
|
|
49
|
+
exact candidate-digest requirement.
|
|
50
|
+
- `scope.paths` is never filtered to hide a baseline-only path. If the annotation is absent, the
|
|
51
|
+
old fail-closed behavior remains: the path still needs candidate evidence and cannot pass.
|
|
52
|
+
|
|
53
|
+
## Record binding
|
|
54
|
+
|
|
55
|
+
Records keep `paths`, `artifactDigests`, and `baseDigest`. They may add
|
|
56
|
+
`baselineArtifacts`:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
{
|
|
60
|
+
id, obligationId, command, exitCode, provenance,
|
|
61
|
+
baseDigest: '<whole-baseline identity>',
|
|
62
|
+
paths: ['docs/reference/old-guide.md', 'docs/reference/guide.md'],
|
|
63
|
+
artifactDigests: { 'docs/reference/guide.md': '<candidate sha256>' },
|
|
64
|
+
baselineArtifacts: { 'docs/reference/old-guide.md': '<trusted baseline sha256>' }
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
A deleted path is satisfied only when one observed record:
|
|
69
|
+
|
|
70
|
+
1. carries the exact whole-baseline `baseDigest`;
|
|
71
|
+
2. lists the path in `paths`;
|
|
72
|
+
3. carries the exact trusted `baseline.artifacts[path]` value as an **own-key** entry in
|
|
73
|
+
`baselineArtifacts`;
|
|
74
|
+
4. carries **no** `artifactDigests` entry for that path, and the candidate artifact map has **no
|
|
75
|
+
own-key entry** for that path.
|
|
76
|
+
|
|
77
|
+
The trusted per-path value is validated once during metadata validation and then **captured**.
|
|
78
|
+
The audit mirror, the stale-baseline diagnostics, and baseline binding compare against that
|
|
79
|
+
captured value and never re-read the caller-owned `baseline.artifacts` map during evidence
|
|
80
|
+
evaluation: a stateful own getter cannot satisfy a record with a value it started returning only
|
|
81
|
+
after validation, cannot mute the stale-baseline diagnostic, and cannot drift the audit mirror.
|
|
82
|
+
Its first read must therefore be the value every consumer sees; a getter that starts returning a
|
|
83
|
+
different or unusable value later is factored out entirely. A baseline per-path inspection that
|
|
84
|
+
throws during validation is a `baseline-artifact-invalid` metadata error and fails the path
|
|
85
|
+
closed. The audit mirror is likewise keyed by the **validated** path snapshot rather than a
|
|
86
|
+
re-read of `baselinePaths[].path`, so a stateful `path` accessor is read exactly once and a
|
|
87
|
+
passing result always describes the baseline identity that validated and bound the evidence.
|
|
88
|
+
closed instead of escaping the checker.
|
|
89
|
+
|
|
90
|
+
Per-path evidence must be carried explicitly. Inherited properties in `baseline.artifacts` or a
|
|
91
|
+
record's `baselineArtifacts` are not own-key evidence and never satisfy a path; the trusted
|
|
92
|
+
baseline entry itself must also be an own-key entry. Null-prototype dictionaries remain valid
|
|
93
|
+
when they carry own entries.
|
|
94
|
+
|
|
95
|
+
Presence is own-key membership, not digest truthiness: a candidate map entry such as `{}`,
|
|
96
|
+
`{ digest: '' }`, or `null` still means the path is present, so it is contradictory path state,
|
|
97
|
+
not a deletion. Equally, a record with an otherwise correct `baselineArtifacts` entry that also
|
|
98
|
+
carries an `artifactDigests` entry for the deleted path (a real, empty, or fake digest) is
|
|
99
|
+
contradictory and does not bind that path; it is reported as `baseline-path-candidate-digest`.
|
|
100
|
+
A valid alternate record may still satisfy the obligation.
|
|
101
|
+
|
|
102
|
+
A rename source is satisfied only by a **single** record that also lists the current
|
|
103
|
+
`renameTo` path, carries its current candidate digest as an **own-key** entry in
|
|
104
|
+
`artifactDigests`, carries no `artifactDigests` entry for the old path, and has no own-key
|
|
105
|
+
candidate artifact map entry for the old path. The candidate destination must itself be an
|
|
106
|
+
own-key entry in the candidate artifact map whose artifact entry declares its own `digest`; an
|
|
107
|
+
inherited nested digest is not explicit evidence. The own digest validated by that metadata check
|
|
108
|
+
is carried into the captured snapshot, and the record binds the destination against that captured
|
|
109
|
+
candidate digest — the same one used by ordinary candidate binding, the audit identity, and the
|
|
110
|
+
stale diagnostics. The destination is therefore never read again through the candidate map during
|
|
111
|
+
capture or binding: a nested `digest` that throws or changes after validation cannot make a stale
|
|
112
|
+
destination pair bind while the audit/stale verdicts still describe the captured value. The same
|
|
113
|
+
record can cover both endpoints. Destination-only evidence, source-only tombstones, split endpoint
|
|
114
|
+
records, and stale destination digests do not satisfy the source. A deletion tombstone therefore
|
|
115
|
+
cannot stand in for a known rename. This destination validation and the snapshot it produces are
|
|
116
|
+
**applicability-gated**: `N/A` and `unknown` (and obligations without an applicability) never bind
|
|
117
|
+
evidence, so they do not consume the destination digest getter during metadata validation; their
|
|
118
|
+
audit identity keeps the un-gated legacy candidate read, and a stateful accessor therefore reports
|
|
119
|
+
the same first legacy value to them that it would report without baseline binding.
|
|
120
|
+
|
|
121
|
+
Metadata validation is the single candidate snapshot for a baseline-only path. Once a declared
|
|
122
|
+
baseline path has been confirmed to have **no** own-key candidate entry, that validated absence is
|
|
123
|
+
carried on the entry; binding and the `artifactDigest` audit identity reuse it instead of
|
|
124
|
+
re-inspecting the candidate map, so a `Proxy` that answers own-property or `get` inspection
|
|
125
|
+
consistently during validation cannot later abort the check or change the audit key. A
|
|
126
|
+
baseline-only path therefore contributes `path=missing` to `artifactDigest` from the validated
|
|
127
|
+
proof rather than from a fresh candidate read. This shortcut applies only to **applicable**
|
|
128
|
+
obligations. `N/A` and `unknown` obligations never bind evidence and keep the legacy candidate read
|
|
129
|
+
for their audit identity (so a candidate `get` that supplies a digest is still reflected there);
|
|
130
|
+
they remain `outcome: 'none'` and do not emit `candidate-artifacts-invalid` from this applicability-gated check.
|
|
131
|
+
|
|
132
|
+
A rename destination's validated own digest is carried too. When more than one accepted
|
|
133
|
+
`rename-from` entry shares one `renameTo`, every entry's validated own digest must agree; a
|
|
134
|
+
conflicting validated digest is a contradictory shared-destination snapshot and fails the whole
|
|
135
|
+
obligation closed (metadata error, `outcome: 'none'`) before any binding. The carried digest must
|
|
136
|
+
be a non-empty string: a stateful `digest` accessor whose value is not a usable string is not
|
|
137
|
+
carried or seeded. Only **applicable** obligations validate and carry this destination snapshot;
|
|
138
|
+
`N/A` and `unknown` obligations keep the legacy candidate read for their audit identity and do not
|
|
139
|
+
consume the destination getter during metadata validation.
|
|
140
|
+
|
|
141
|
+
The binding applies to execution and review records alike. Existing review approval, waiver,
|
|
142
|
+
failing-command, requirement-sync authority, and applicability rules are unchanged. A correctly
|
|
143
|
+
bound command that fails still produces an observed failure, not a pass.
|
|
144
|
+
|
|
145
|
+
## Trust limits
|
|
146
|
+
|
|
147
|
+
- `baseline.artifacts` is trusted caller/VCS input. The checker proves that evidence agrees with
|
|
148
|
+
the declared baseline values; it does not read Git or recompute those bytes. A validated
|
|
149
|
+
per-path value is read and captured once, so the agreement proof is against a single validated
|
|
150
|
+
snapshot rather than the value a stateful getter returns at later moments.
|
|
151
|
+
- The whole-baseline `digest` remains the identity anchor. A per-path digest without the exact
|
|
152
|
+
whole-baseline identity is insufficient.
|
|
153
|
+
- Candidate evidence remains authoritative for every present, new, or edited path. A baseline
|
|
154
|
+
digest alone never satisfies such a path.
|
|
155
|
+
- Path membership is explicit. A record cannot bind a path only by carrying its digest.
|
|
156
|
+
- Candidate presence is own-key membership in the candidate artifact map. A present entry whose
|
|
157
|
+
digest is absent, empty, or not a usable value is still present; it is never silently
|
|
158
|
+
reinterpreted as a deletion or as a rename source.
|
|
159
|
+
- For an applicable obligation that declares baseline-only paths, the top-level candidate
|
|
160
|
+
`artifacts` argument must itself be a non-null object, not an array, whose prototype is
|
|
161
|
+
`Object.prototype` or `null`. An omitted, `undefined`, `null`, array, string, `Map`, `Date`,
|
|
162
|
+
`RegExp`, class instance, custom-prototype object, or other malformed top-level value is
|
|
163
|
+
**unknown inventory** and fails closed with `candidate-artifacts-invalid`; an explicit `{}` or
|
|
164
|
+
an empty null-prototype dictionary remains the legitimate empty deletion inventory. This
|
|
165
|
+
provenance check is limited to obligations that actually declare baseline paths, and it does
|
|
166
|
+
not change the shared `isPlainObject` normalizer used by callers without
|
|
167
|
+
`scope.baselinePaths`. The prototype is read only for those applicable baseline-only
|
|
168
|
+
obligations; a value whose prototype inspection throws (for example a `Proxy` with a throwing
|
|
169
|
+
`getPrototypeOf` trap) is likewise unknown inventory, and unrelated non-baseline or
|
|
170
|
+
inapplicable obligations never trigger the read. The same boundary is exception-safe for
|
|
171
|
+
own-property inspection: a throwing property-descriptor trap or a throwing nested `digest`
|
|
172
|
+
accessor encountered during metadata validation or the pre-binding capture is classified as
|
|
173
|
+
unknown inventory (`candidate-artifacts-invalid` for applicable baseline-only obligations)
|
|
174
|
+
instead of escaping the checker. Inspecting a `baselinePaths` entry itself is guarded the same
|
|
175
|
+
way: an entry whose `path`, `reason`, or `renameTo` field getter or descriptor trap throws is a
|
|
176
|
+
`baseline-path-invalid` metadata error, not an escape; the audit mirror is built from the
|
|
177
|
+
validated snapshot and contains no second read of the raw entry, so an accessor that only
|
|
178
|
+
misbehaves after the guarded snapshot is never reached. Inspection is not repeated after
|
|
179
|
+
validation for a validated baseline-only path or a rename destination, so a trap that only
|
|
180
|
+
starts throwing on a later re-inspection is never reached. `N/A` and `unknown` obligations never
|
|
181
|
+
emit that diagnostic.
|
|
182
|
+
- A mixed scope can combine baseline-only paths with ordinary candidate paths (for example one
|
|
183
|
+
document deleted and another edited in the same change set). For an applicable obligation
|
|
184
|
+
that declares baseline paths, every ordinary scoped candidate digest is captured once before
|
|
185
|
+
evidence binding; binding, the `artifactDigest` audit identity, and the stale-digest
|
|
186
|
+
diagnostics all use that same captured snapshot, so a nested digest accessor whose later read
|
|
187
|
+
throws or returns a different value cannot make the audit key or a stale verdict disagree with
|
|
188
|
+
the digest that bound evidence. This includes the rename destination: after metadata proves the
|
|
189
|
+
destination is an own-key inventory entry with its own digest, the captured snapshot is seeded
|
|
190
|
+
with that validated own digest and the destination candidate map is not read again during
|
|
191
|
+
capture, so a stale pair plus a destination-only record cannot jointly pass. Declared
|
|
192
|
+
baseline-only paths were validated absent during metadata and are not read again either. A
|
|
193
|
+
throwing accessor on one of the remaining ordinary paths during capture also fails the candidate
|
|
194
|
+
inventory closed. Callers without `scope.baselinePaths` keep the legacy candidate-binding
|
|
195
|
+
behavior unchanged, including the legacy `candidateBound` read path.
|
|
196
|
+
|
|
197
|
+
## Fail-closed cases
|
|
198
|
+
|
|
199
|
+
The result is not a pass when any of the following is observed:
|
|
200
|
+
|
|
201
|
+
- absent whole-baseline identity, absent `baseline.artifacts`, or an invalid/missing per-path
|
|
202
|
+
baseline value for a declared baseline-only path;
|
|
203
|
+
- a baseline-only entry that is missing from `scope.paths`, is duplicated, has an unknown reason,
|
|
204
|
+
has a malformed `renameTo` (empty, equal to the source, outside `scope.paths`, or — for an
|
|
205
|
+
applicable obligation — without an own-key candidate destination artifact that declares its own
|
|
206
|
+
`digest`), or whose field inspection fails (a `path`, `reason`, or `renameTo` getter or
|
|
207
|
+
descriptor trap that throws during the guarded entry snapshot);
|
|
208
|
+
- a deleted entry that carries a `renameTo` property at all, including an explicitly present
|
|
209
|
+
`undefined`;
|
|
210
|
+
- a present `scope.baselinePaths` annotation that is not an array, including an explicitly
|
|
211
|
+
present `undefined`; absence of the property remains backward-compatible;
|
|
212
|
+
- an applicable obligation that declares baseline-only paths but receives an absent or malformed
|
|
213
|
+
top-level candidate `artifacts` map (omitted, `undefined`, `null`, array, string, or an
|
|
214
|
+
object-like value that is not a plain object or null-prototype dictionary, including `Map`,
|
|
215
|
+
`Date`, `RegExp`, class instances, objects whose prototype cannot be inspected, and inventories
|
|
216
|
+
whose own-property descriptors or nested digest accessors throw); an explicit `{}` or
|
|
217
|
+
null-prototype dictionary is valid;
|
|
218
|
+
- a mixed baseline+ordinary scope whose ordinary candidate digest accessor throws while the
|
|
219
|
+
candidate inventory is captured before binding;
|
|
220
|
+
- a rename pair whose destination digest differs from the captured destination candidate digest,
|
|
221
|
+
including a stale pair that a separate destination-only record would otherwise leave jointly
|
|
222
|
+
passing;
|
|
223
|
+
- a rename destination whose validated own digest conflicts with another accepted entry that
|
|
224
|
+
shares the same `renameTo`, or whose captured own value is not a usable non-empty string;
|
|
225
|
+
- a baseline-only path with an own-key candidate artifact map entry, even when that entry has no
|
|
226
|
+
usable digest (`{}`, `{ digest: '' }`, or `null`);
|
|
227
|
+
- a record that carries an `artifactDigests` entry (real, empty, or fake) for a baseline-only
|
|
228
|
+
path;
|
|
229
|
+
- a rename destination with no candidate artifact (applicable obligations only; `N/A` and
|
|
230
|
+
`unknown` obligations keep the legacy candidate read for their audit identity);
|
|
231
|
+
- a record with the wrong baseline identity, the wrong per-path baseline digest, an absent or
|
|
232
|
+
empty `baselineArtifacts` entry, or stale/missing endpoint evidence — where "wrong" is judged
|
|
233
|
+
against the validated baseline value captured during metadata, not a live re-read;
|
|
234
|
+
- a baseline per-path digest whose inspection throws (a getter or descriptor trap), which is a
|
|
235
|
+
`baseline-artifact-invalid` metadata error rather than an escape;
|
|
236
|
+
- a stateful `baselinePaths[].path` accessor whose later read names a different path: the audit
|
|
237
|
+
mirror is keyed by the validated path snapshot, so a pass can never describe a different
|
|
238
|
+
baseline identity than the one that validated the evidence;
|
|
239
|
+
- an unsupported review kind, an unapproved review, or an incomplete execution record.
|
|
240
|
+
|
|
241
|
+
These cases are visible in diagnostics and remain `outcome: 'none'` unless the failure is a
|
|
242
|
+
correctly bound command, which remains `outcome: 'fail'` as before.
|
|
243
|
+
|
|
244
|
+
## Audit output
|
|
245
|
+
|
|
246
|
+
Results keep the existing `artifactDigest`, `baseDigest`, `provenance`, and `recordRefs` fields.
|
|
247
|
+
Obligations that declare baseline paths additionally expose `baselineArtifacts`, mapping each
|
|
248
|
+
validated baseline-only path to the trusted baseline value used for the check. The field is
|
|
249
|
+
additive; callers that do not use baseline binding see unchanged output.
|
|
@@ -11,8 +11,8 @@ Documentation is a **consumer** of the #320 workflow/obligation abstractions. No
|
|
|
11
11
|
| 0 — contract + RED fixtures | This file; fixtures under `ci/fixtures/doc-lifecycle/` |
|
|
12
12
|
| 1a — `project-docs.json` loader | `multi-agent-workspace/scripts/project-docs-resolver.js` |
|
|
13
13
|
| 2 — roles, IDs, aliases, legacy, supersession, index, D6.9 PRD revisions | `harness-everything/scripts/doc-lifecycle.js` |
|
|
14
|
-
| 3a — `doc-change` / `requirement-sync` pure derivation and evidence producer | `harness-everything/scripts/doc-obligations.js` (facts and obligations), `harness-everything/scripts/doc-evidence.js` (`checkDocObligations`). Pilot-local: not wired into workflow disposition yet. H3 and H4 have deterministic tests; H1 and H2 are deferred to 3c. Known limits: the attested-envelope fields are caller-asserted (`configDigest` is checked for presence only, not against the parsed config); `candidateDigest` covers only `docs/prd/`, so custom PRD roots and task-scope binding are not yet verified; deleted and
|
|
15
|
-
| 1b, 3b–3c (provider suggestions, reconciliation, H1–H2 evaluation) | Not started: blocked by #320 family metadata, D1 obligation schema and D3 evidence states.
|
|
14
|
+
| 3a — `doc-change` / `requirement-sync` pure derivation and evidence producer | `harness-everything/scripts/doc-obligations.js` (facts and obligations), `harness-everything/scripts/doc-evidence.js` (`checkDocObligations`). Pilot-local: not wired into workflow disposition yet. H3 and H4 have deterministic tests; H1 and H2 are deferred to 3c. Known limits: the attested-envelope fields are caller-asserted (`configDigest` is checked for presence only, not against the parsed config); `candidateDigest` covers only `docs/prd/`, so custom PRD roots and task-scope binding are not yet verified; deleted and old rename paths are bound to trusted per-path baseline evidence ([doc-evidence-baseline-binding.md](doc-evidence-baseline-binding.md), #335), with reconciliation left out of scope |
|
|
15
|
+
| 1b, 3b–3c (provider suggestions, reconciliation, H1–H2 evaluation) | Not started: blocked by #320 family metadata, D1 obligation schema and D3 evidence states. Deleted and old rename paths now use trusted baseline-bound evidence (#335); reconciliation with workflow disposition remains open |
|
|
16
16
|
| 4 — derived-view generators, `describes:` drift | Not started |
|
|
17
17
|
|
|
18
18
|
## Configuration (D5)
|