harness-everything 0.36.1 → 0.36.2
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 +10 -0
- package/ci/mechanism-335-baseline-evidence.test.js +355 -7
- package/docs/doc-evidence-baseline-binding.md +28 -7
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/doc-evidence.js +69 -9
- 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 +69 -9
- package/sbom.cdx.json +7 -7
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "harness-everything",
|
|
11
11
|
"source": "./",
|
|
12
12
|
"description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
|
|
13
|
-
"version": "0.36.
|
|
13
|
+
"version": "0.36.2",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Miya Daniel"
|
|
16
16
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-everything",
|
|
3
3
|
"description": "Harness OS: an orchestrated agent operating system — tier routing, circuit breaker guardrails, verification gates, WAL session memory, and self-evolving workspace rules.",
|
|
4
|
-
"version": "0.36.
|
|
4
|
+
"version": "0.36.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Miya Daniel"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.36.2](https://github.com/dyphn1/Harness-everything/compare/v0.36.1...v0.36.2) (2026-10-20)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
* **doc-lifecycle:** honor poisoned shared destinations inside metadata ([2c69247](https://github.com/dyphn1/Harness-everything/commit/2c69247c3a2e108df945fc91c68fb060ac04e058)), closes [#340](https://github.com/dyphn1/Harness-everything/issues/340)
|
|
10
|
+
* **doc-lifecycle:** keep rejected destination snapshots single-read ([f49b253](https://github.com/dyphn1/Harness-everything/commit/f49b25318f4022d241d91051e1daa51ef61cdff2)), closes [#340](https://github.com/dyphn1/Harness-everything/issues/340)
|
|
11
|
+
* **doc-lifecycle:** single-read the whole-baseline and destination digests ([063a00b](https://github.com/dyphn1/Harness-everything/commit/063a00b4f97c147299a63d7b9b82fc2cbae87bc0)), closes [#340](https://github.com/dyphn1/Harness-everything/issues/340)
|
|
12
|
+
|
|
5
13
|
## [0.36.1](https://github.com/dyphn1/Harness-everything/compare/v0.36.0...v0.36.1) (2026-10-20)
|
|
6
14
|
|
|
7
15
|
### Bug Fixes
|
|
@@ -66,10 +74,12 @@ All notable changes to this project are documented in this file.
|
|
|
66
74
|
* **doc-evidence:** defer the candidate-inventory prototype check to applicable baseline-only obligations and treat a throwing prototype trap as unknown inventory, so unrelated obligations are never aborted
|
|
67
75
|
* **doc-evidence:** bind baseline-annotated mixed scopes and rename destinations to the captured candidate snapshot for evidence binding, the audit identity, and stale diagnostics, so a later-throwing or later-changing digest accessor cannot disagree with the digest that bound evidence
|
|
68
76
|
* **doc-evidence:** carry the metadata-validated rename destination digest and baseline-path source-absence proof through capture and binding, so a candidate inventory that answers consistently during validation is never re-inspected and cannot abort the check or drift the audit key
|
|
77
|
+
* **doc-evidence:** keep rejected rename destination snapshots single-read, so a throwing or unusable destination digest getter is never re-read and its later values never reach the capture or audit identity, including shared destinations where a poisoned destination is honored inside metadata validation and wins in the audit
|
|
69
78
|
* **doc-evidence:** restrict the validated source-absence audit shortcut to applicable obligations and require every accepted entry sharing a rename destination to agree on the validated own digest, so inapplicable audit identity keeps the legacy read and contradictory shared-destination snapshots fail closed
|
|
70
79
|
* **doc-evidence:** gate the rename destination snapshot on applicability and guard baselinePaths entry inspection, so inapplicable audit identity reports the first legacy digest value and a throwing entry getter or descriptor trap fails the entry closed instead of escaping the checker
|
|
71
80
|
* **doc-evidence:** capture the validated per-path baseline digest during metadata and drive the audit mirror, stale-baseline diagnostics, and binding from that snapshot, so a stateful baseline getter cannot bind a record against a value it started returning only after validation and a throwing inspection is a metadata error instead of an escape
|
|
72
81
|
* **doc-evidence:** key the baseline audit mirror by the metadata-validated path snapshot instead of re-reading `baselinePaths[].path`, so a stateful path accessor read during validation can never make a passing result describe a different baseline identity
|
|
82
|
+
* **doc-evidence:** read the whole-baseline `baseline.digest` and the rename destination's own digest exactly once and validate that captured scalar, so a stateful getter cannot pass validation as one value while evidence binds its later value; identity and destination inspection failures fail closed as structured metadata errors instead of escaping the checker
|
|
73
83
|
|
|
74
84
|
## [0.34.0](https://github.com/dyphn1/Harness-everything/compare/v0.33.0...v0.34.0) (2026-10-17)
|
|
75
85
|
|
|
@@ -1877,7 +1877,9 @@ checkCase('335 P3 the metadata-validated rename destination digest is carried wi
|
|
|
1877
1877
|
for (const target of [modules, packageModules]) {
|
|
1878
1878
|
const label = target === modules ? 'canonical' : 'packaged';
|
|
1879
1879
|
const ctx = prepare('rename', target);
|
|
1880
|
-
|
|
1880
|
+
// P3(r4321077631): the destination snapshot reads its own digest exactly once, so a getter
|
|
1881
|
+
// that throws after that single valid read is never reached and the captured B still binds.
|
|
1882
|
+
const counted = countingDigestEntry(RENAME_CAPTURED, { validReads: 1, later: 'throw' });
|
|
1881
1883
|
const artifacts = { ...ctx.artifacts, [RENAME_TO]: counted.entry };
|
|
1882
1884
|
const record = execRecord(ctx, 'pair-run', 'doc-change:doc-lint', {
|
|
1883
1885
|
paths: [RENAME_FROM, RENAME_TO], candidatePaths: [], baselinePaths: [RENAME_FROM],
|
|
@@ -1892,12 +1894,74 @@ checkCase('335 P3 the metadata-validated rename destination digest is carried wi
|
|
|
1892
1894
|
if (!result.artifactDigest.includes(`${RENAME_TO}=${RENAME_CAPTURED}`)) {
|
|
1893
1895
|
failures.push(`${label}: audit=${result.artifactDigest}`);
|
|
1894
1896
|
}
|
|
1895
|
-
if (counted.reads() !==
|
|
1897
|
+
if (counted.reads() !== 1) failures.push(`${label}: destination reads=${counted.reads()}`);
|
|
1896
1898
|
}
|
|
1897
1899
|
assert.deepStrictEqual(failures, [],
|
|
1898
1900
|
`the validated destination digest must be carried: ${failures.join('; ')}`);
|
|
1899
1901
|
});
|
|
1900
1902
|
|
|
1903
|
+
// #342 review P3: a destination snapshot rejected during metadata (throwing or unusable own
|
|
1904
|
+
// digest) is still a visited snapshot. The capture and audit lanes must never re-read it: the
|
|
1905
|
+
// single metadata read stays the only digest read, and a stateful getter cannot ship a later
|
|
1906
|
+
// value into the audit identity even though its entry failed.
|
|
1907
|
+
checkCase('#342 P3 a rejected destination snapshot is read exactly once even when its digest getter throws', () => {
|
|
1908
|
+
const failures = [];
|
|
1909
|
+
for (const target of [modules, packageModules]) {
|
|
1910
|
+
const label = target === modules ? 'canonical' : 'packaged';
|
|
1911
|
+
const ctx = prepare('rename', target);
|
|
1912
|
+
const counted = countingDigestEntry(RENAME_CAPTURED, { validReads: 0, later: 'throw' });
|
|
1913
|
+
const artifacts = { ...ctx.artifacts, [RENAME_TO]: counted.entry };
|
|
1914
|
+
const record = execRecord(ctx, 'move-run', 'doc-change:doc-lint', {
|
|
1915
|
+
paths: [RENAME_FROM, RENAME_TO], candidatePaths: [RENAME_TO], baselinePaths: [RENAME_FROM]
|
|
1916
|
+
});
|
|
1917
|
+
let out;
|
|
1918
|
+
try {
|
|
1919
|
+
out = runDocLint(ctx, target, { artifacts, records: [record] });
|
|
1920
|
+
} catch (error) { failures.push(`${label}: escaped: ${error.message}`); continue; }
|
|
1921
|
+
const result = resultOf(out, 'doc-change:doc-lint');
|
|
1922
|
+
if (result.outcome !== 'none') failures.push(`${label}: outcome=${result.outcome}`);
|
|
1923
|
+
if (!diagnosticCodes(out).includes('candidate-artifacts-invalid')) {
|
|
1924
|
+
failures.push(`${label}: diagnostics=${diagnosticCodes(out)}`);
|
|
1925
|
+
}
|
|
1926
|
+
if (counted.reads() !== 1) failures.push(`${label}: destination reads=${counted.reads()}`);
|
|
1927
|
+
if (!result.artifactDigest.includes(`${RENAME_TO}=missing`)) {
|
|
1928
|
+
failures.push(`${label}: audit=${result.artifactDigest}`);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
assert.deepStrictEqual(failures, [],
|
|
1932
|
+
`a rejected throwing destination must be read once and never again: ${failures.join('; ')}`);
|
|
1933
|
+
});
|
|
1934
|
+
|
|
1935
|
+
checkCase('#342 P3 an unusable first destination read never ships a later non-empty value into the audit', () => {
|
|
1936
|
+
const failures = [];
|
|
1937
|
+
for (const target of [modules, packageModules]) {
|
|
1938
|
+
const label = target === modules ? 'canonical' : 'packaged';
|
|
1939
|
+
const ctx = prepare('rename', target);
|
|
1940
|
+
// First read: unusable ''. Any later read (which must not happen) returns non-empty
|
|
1941
|
+
// RENAME_LATER, so the audit would describe it if the rejected snapshot were re-read.
|
|
1942
|
+
const counted = countingDigestEntry('', { validReads: 1, later: 'value', laterValue: RENAME_LATER });
|
|
1943
|
+
const artifacts = { ...ctx.artifacts, [RENAME_TO]: counted.entry };
|
|
1944
|
+
const record = execRecord(ctx, 'move-run', 'doc-change:doc-lint', {
|
|
1945
|
+
paths: [RENAME_FROM, RENAME_TO], candidatePaths: [RENAME_TO], baselinePaths: [RENAME_FROM]
|
|
1946
|
+
});
|
|
1947
|
+
let out;
|
|
1948
|
+
try {
|
|
1949
|
+
out = runDocLint(ctx, target, { artifacts, records: [record] });
|
|
1950
|
+
} catch (error) { failures.push(`${label}: escaped: ${error.message}`); continue; }
|
|
1951
|
+
const result = resultOf(out, 'doc-change:doc-lint');
|
|
1952
|
+
if (result.outcome !== 'none') failures.push(`${label}: outcome=${result.outcome}`);
|
|
1953
|
+
if (counted.reads() !== 1) failures.push(`${label}: destination reads=${counted.reads()}`);
|
|
1954
|
+
if (result.artifactDigest.includes(RENAME_LATER)) {
|
|
1955
|
+
failures.push(`${label}: audit describes a later value: ${result.artifactDigest}`);
|
|
1956
|
+
}
|
|
1957
|
+
if (!result.artifactDigest.includes(`${RENAME_TO}=missing`)) {
|
|
1958
|
+
failures.push(`${label}: audit=${result.artifactDigest}`);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
assert.deepStrictEqual(failures, [],
|
|
1962
|
+
`an unusable destination read must stay single-read and 'missing': ${failures.join('; ')}`);
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1901
1965
|
checkCase('335 P3 a stale pair plus a destination-only record stays none under descriptor patience', () => {
|
|
1902
1966
|
const failures = [];
|
|
1903
1967
|
for (const target of [modules, packageModules]) {
|
|
@@ -2116,10 +2180,12 @@ checkCase('335 P3 two accepted entries sharing a rename destination must agree o
|
|
|
2116
2180
|
}
|
|
2117
2181
|
}
|
|
2118
2182
|
|
|
2119
|
-
// Dynamic shared destination: D1 for the first entry's
|
|
2183
|
+
// Dynamic shared destination: D1 for the first entry's single-read snapshot, D2 for the
|
|
2184
|
+
// second (the per-entry read advances the getter). P3(r4321077631) made each entry's read
|
|
2185
|
+
// exactly one, so the fixture flips after one valid read.
|
|
2120
2186
|
{
|
|
2121
2187
|
const { ctx } = sharedDestinationContext(target);
|
|
2122
|
-
const dynamic = countingDigestEntry(RENAME_CAPTURED, { validReads:
|
|
2188
|
+
const dynamic = countingDigestEntry(RENAME_CAPTURED, { validReads: 1, later: 'value', laterValue: RENAME_LATER });
|
|
2123
2189
|
const artifacts = { ...ctx.artifacts, [RENAME_TO]: dynamic.entry };
|
|
2124
2190
|
const out = run(artifacts, [
|
|
2125
2191
|
sharedPairRecord(ctx, 'pair-run', RENAME_CAPTURED),
|
|
@@ -2177,6 +2243,69 @@ checkCase('335 P3 two accepted entries sharing a rename destination must agree o
|
|
|
2177
2243
|
`shared rename destinations must agree on the validated digest: ${failures.join('; ')}`);
|
|
2178
2244
|
});
|
|
2179
2245
|
|
|
2246
|
+
// #342 review P3: a shared rename destination whose snapshot is rejected must stay honored.
|
|
2247
|
+
// invalid→valid: the first declaring entry fails its own snapshot; a later declaring entry must
|
|
2248
|
+
// fail closed without reading the destination again (one read total, audit 'missing').
|
|
2249
|
+
// valid→throw: the first entry carries a validated success; the second declaration's read
|
|
2250
|
+
// throws, and the poisoned visited state wins in the audit over the previously seeded success
|
|
2251
|
+
// (one read per declaring entry, two total).
|
|
2252
|
+
checkCase('#342 P3 a rejected shared destination is never read again and keeps audit precedence', () => {
|
|
2253
|
+
const failures = [];
|
|
2254
|
+
for (const target of [modules, packageModules]) {
|
|
2255
|
+
const label = target === modules ? 'canonical' : 'packaged';
|
|
2256
|
+
|
|
2257
|
+
// invalid→valid: first own read unusable, later reads (which must not happen) valid.
|
|
2258
|
+
{
|
|
2259
|
+
const { ctx, obligations, baseline } = sharedDestinationContext(target);
|
|
2260
|
+
const counted = countingDigestEntry('', { validReads: 1, later: 'value', laterValue: RENAME_CAPTURED });
|
|
2261
|
+
// Read counts are scoped to the obligation under test; unrelated obligations keep the
|
|
2262
|
+
// designed legacy candidate read, exactly as runDocLint's single-obligation runs do.
|
|
2263
|
+
const out = target.checkDocObligations({
|
|
2264
|
+
obligations: obligations.filter(item => item.id === 'doc-change:doc-lint'),
|
|
2265
|
+
artifacts: { ...ctx.artifacts, [RENAME_TO]: counted.entry }, baseline,
|
|
2266
|
+
executionRecords: [sharedPairRecord(ctx, 'pair-run', RENAME_CAPTURED)], reviewRecords: []
|
|
2267
|
+
});
|
|
2268
|
+
const result = resultOf(out, 'doc-change:doc-lint');
|
|
2269
|
+
if (result.outcome !== 'none') failures.push(`${label}/invalid-to-valid: outcome=${result.outcome}`);
|
|
2270
|
+
if (!diagnosticCodes(out).includes('baseline-path-invalid')) {
|
|
2271
|
+
failures.push(`${label}/invalid-to-valid: diagnostics=${diagnosticCodes(out)}`);
|
|
2272
|
+
}
|
|
2273
|
+
if (counted.reads() !== 1) failures.push(`${label}/invalid-to-valid: destination reads=${counted.reads()}`);
|
|
2274
|
+
if (result.artifactDigest.includes(RENAME_CAPTURED)) {
|
|
2275
|
+
failures.push(`${label}/invalid-to-valid: audit describes a later value: ${result.artifactDigest}`);
|
|
2276
|
+
}
|
|
2277
|
+
if (!result.artifactDigest.includes(`${RENAME_TO}=missing`)) {
|
|
2278
|
+
failures.push(`${label}/invalid-to-valid: audit=${result.artifactDigest}`);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// valid→throw: successful first snapshot, then a throwing own read on the second entry.
|
|
2283
|
+
{
|
|
2284
|
+
const { ctx, obligations, baseline } = sharedDestinationContext(target);
|
|
2285
|
+
const counted = countingDigestEntry(RENAME_CAPTURED, { validReads: 1, later: 'throw' });
|
|
2286
|
+
const out = target.checkDocObligations({
|
|
2287
|
+
obligations: obligations.filter(item => item.id === 'doc-change:doc-lint'),
|
|
2288
|
+
artifacts: { ...ctx.artifacts, [RENAME_TO]: counted.entry }, baseline,
|
|
2289
|
+
executionRecords: [sharedPairRecord(ctx, 'pair-run', RENAME_CAPTURED)], reviewRecords: []
|
|
2290
|
+
});
|
|
2291
|
+
const result = resultOf(out, 'doc-change:doc-lint');
|
|
2292
|
+
if (result.outcome !== 'none') failures.push(`${label}/valid-to-throw: outcome=${result.outcome}`);
|
|
2293
|
+
if (!diagnosticCodes(out).includes('candidate-artifacts-invalid')) {
|
|
2294
|
+
failures.push(`${label}/valid-to-throw: diagnostics=${diagnosticCodes(out)}`);
|
|
2295
|
+
}
|
|
2296
|
+
if (counted.reads() !== 2) failures.push(`${label}/valid-to-throw: destination reads=${counted.reads()}`);
|
|
2297
|
+
if (result.artifactDigest.includes(RENAME_CAPTURED)) {
|
|
2298
|
+
failures.push(`${label}/valid-to-throw: audit describes a rejected-state value: ${result.artifactDigest}`);
|
|
2299
|
+
}
|
|
2300
|
+
if (!result.artifactDigest.includes(`${RENAME_TO}=missing`)) {
|
|
2301
|
+
failures.push(`${label}/valid-to-throw: audit=${result.artifactDigest}`);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
assert.deepStrictEqual(failures, [],
|
|
2306
|
+
`a rejected shared destination must stay single-read and audited as 'missing': ${failures.join('; ')}`);
|
|
2307
|
+
});
|
|
2308
|
+
|
|
2180
2309
|
// ---------------------------------------------------------------------------
|
|
2181
2310
|
// P2 applicability-gated destination snapshot (review r4318520818 at 56f3094):
|
|
2182
2311
|
// N/A and unknown obligations never bind evidence, so their destination digest
|
|
@@ -2220,10 +2349,11 @@ checkCase('335 P2 an inapplicable rename never consumes the destination snapshot
|
|
|
2220
2349
|
}
|
|
2221
2350
|
|
|
2222
2351
|
// Positive control: the applicable obligation still captures the destination snapshot
|
|
2223
|
-
// once during metadata and binds against it.
|
|
2352
|
+
// once during metadata and binds against it. P3(r4321077631) made that capture a
|
|
2353
|
+
// single guarded read, so the getter is never consumed beyond its first valid read.
|
|
2224
2354
|
{
|
|
2225
2355
|
const ctx = prepare('rename', target);
|
|
2226
|
-
const counted = countingDigestEntry(RENAME_CAPTURED, { validReads:
|
|
2356
|
+
const counted = countingDigestEntry(RENAME_CAPTURED, { validReads: 1, later: 'throw' });
|
|
2227
2357
|
const artifacts = { ...ctx.artifacts, [RENAME_TO]: counted.entry };
|
|
2228
2358
|
const record = {
|
|
2229
2359
|
...execRecord(ctx, 'pair-run', 'doc-change:doc-lint', {
|
|
@@ -2235,7 +2365,7 @@ checkCase('335 P2 an inapplicable rename never consumes the destination snapshot
|
|
|
2235
2365
|
const out = checkArtifacts(ctx, artifacts, { obligations: [obligation], executionRecords: [record] }, target);
|
|
2236
2366
|
const result = resultOf(out, 'doc-change:doc-lint');
|
|
2237
2367
|
if (result.outcome !== 'pass') failures.push(`${label}/applicable: outcome=${result.outcome}`);
|
|
2238
|
-
if (counted.reads() !==
|
|
2368
|
+
if (counted.reads() !== 1) failures.push(`${label}/applicable: destination getter reads=${counted.reads()}`);
|
|
2239
2369
|
}
|
|
2240
2370
|
}
|
|
2241
2371
|
assert.deepStrictEqual(failures, [],
|
|
@@ -2687,4 +2817,222 @@ checkCase('335 P2 the audit mirror reports the metadata-validated path snapshot,
|
|
|
2687
2817
|
`the audit mirror must use the validated path snapshot: ${failures.join('; ')}`);
|
|
2688
2818
|
});
|
|
2689
2819
|
|
|
2820
|
+
//----------------------------------------------------------------------------
|
|
2821
|
+
// P2(r4321077631/r4321077638) at 7217b3a: whole-baseline identity and rename
|
|
2822
|
+
// destination digests must each be read exactly once — the same scalar is
|
|
2823
|
+
// validated and captured, so a stateful getter cannot pass validation as one
|
|
2824
|
+
// value while evidence binds its later value.
|
|
2825
|
+
//----------------------------------------------------------------------------
|
|
2826
|
+
|
|
2827
|
+
const RENAME_OLD = 'docs/adr/old.md';
|
|
2828
|
+
const RENAME_NEW = 'docs/adr/new.md';
|
|
2829
|
+
const RENAME_BASELINE_DIGEST = sha256('rename whole baseline');
|
|
2830
|
+
const RENAME_OLD_BASELINE = sha256('old document baseline content');
|
|
2831
|
+
const RENAME_DEST_B = sha256('destination content B');
|
|
2832
|
+
const RENAME_DEST_C = sha256('destination content C');
|
|
2833
|
+
|
|
2834
|
+
checkCase('335 P2 the whole-baseline digest is validated and captured from one read', () => {
|
|
2835
|
+
const failures = [];
|
|
2836
|
+
for (const target of [modules, packageModules]) {
|
|
2837
|
+
const label = target === modules ? 'canonical' : 'packaged';
|
|
2838
|
+
const PATH = BASELINE_EXAMPLE;
|
|
2839
|
+
|
|
2840
|
+
function runWith(baseline, recordBaseDigest) {
|
|
2841
|
+
return target.checkDocObligations({
|
|
2842
|
+
obligations: [{
|
|
2843
|
+
id: 'doc-change:doc-lint',
|
|
2844
|
+
capability: 'doc-lint',
|
|
2845
|
+
level: 'MUST',
|
|
2846
|
+
applicability: 'applicable',
|
|
2847
|
+
scope: { paths: [PATH], baselinePaths: [{ path: PATH, reason: 'deleted' }] }
|
|
2848
|
+
}],
|
|
2849
|
+
artifacts: {},
|
|
2850
|
+
baseline,
|
|
2851
|
+
executionRecords: [{
|
|
2852
|
+
id: 'tomb-run',
|
|
2853
|
+
obligationId: 'doc-change:doc-lint',
|
|
2854
|
+
command: 'node ci/mechanism-335-baseline-evidence.test.js # tomb-run',
|
|
2855
|
+
exitCode: 0,
|
|
2856
|
+
provenance: 'baseline identity snapshot regression',
|
|
2857
|
+
baseDigest: recordBaseDigest,
|
|
2858
|
+
paths: [PATH],
|
|
2859
|
+
artifactDigests: {},
|
|
2860
|
+
baselineArtifacts: { [PATH]: BASELINE_EXAMPLE_VALUE }
|
|
2861
|
+
}],
|
|
2862
|
+
reviewRecords: []
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
// Stateful identity getter: validation must use the first read; a record attesting to the
|
|
2867
|
+
// later value cannot pass.
|
|
2868
|
+
{
|
|
2869
|
+
let identityReads = 0;
|
|
2870
|
+
const baseline = {
|
|
2871
|
+
get digest() {
|
|
2872
|
+
identityReads += 1;
|
|
2873
|
+
return identityReads === 1 ? 'baseline-A' : 'baseline-B';
|
|
2874
|
+
},
|
|
2875
|
+
artifacts: { [PATH]: BASELINE_EXAMPLE_VALUE }
|
|
2876
|
+
};
|
|
2877
|
+
let out;
|
|
2878
|
+
try {
|
|
2879
|
+
out = runWith(baseline, 'baseline-B');
|
|
2880
|
+
} catch (error) {
|
|
2881
|
+
failures.push(`${label}/stateful: escaped: ${error.message}`);
|
|
2882
|
+
continue;
|
|
2883
|
+
}
|
|
2884
|
+
const result = out.results.find(item => item.obligationId === 'doc-change:doc-lint');
|
|
2885
|
+
if (identityReads !== 1) failures.push(`${label}/stateful: identity getter reads=${identityReads}`);
|
|
2886
|
+
if (result.outcome === 'pass') failures.push(`${label}/stateful: a record bound to the later value passed`);
|
|
2887
|
+
if (!out.diagnostics.some(item => item.code === 'stale-base-digest')
|
|
2888
|
+
&& !out.diagnostics.some(item => item.code === 'baseline-identity-missing')) {
|
|
2889
|
+
failures.push(`${label}/stateful: diagnostics=${out.diagnostics.map(item => item.code)}`);
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
// Stable controls: the validated identity binds; a different identity does not.
|
|
2894
|
+
{
|
|
2895
|
+
const passing = runWith(
|
|
2896
|
+
{ digest: 'baseline-A', artifacts: { [PATH]: BASELINE_EXAMPLE_VALUE } }, 'baseline-A');
|
|
2897
|
+
if (passing.results.find(item => item.obligationId === 'doc-change:doc-lint').outcome !== 'pass') {
|
|
2898
|
+
failures.push(`${label}/stable: the validated identity did not pass`);
|
|
2899
|
+
}
|
|
2900
|
+
const stale = runWith(
|
|
2901
|
+
{ digest: 'baseline-B', artifacts: { [PATH]: BASELINE_EXAMPLE_VALUE } }, 'baseline-A');
|
|
2902
|
+
const staleResult = stale.results.find(item => item.obligationId === 'doc-change:doc-lint');
|
|
2903
|
+
if (staleResult.outcome !== 'none') failures.push(`${label}/stable: outcome=${staleResult.outcome}`);
|
|
2904
|
+
if (!stale.diagnostics.some(item => item.code === 'stale-base-digest')) {
|
|
2905
|
+
failures.push(`${label}/stable: diagnostics=${stale.diagnostics.map(item => item.code)}`);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
// A throwing identity getter fails closed as a metadata error instead of escaping.
|
|
2910
|
+
{
|
|
2911
|
+
const baseline = {
|
|
2912
|
+
get digest() {
|
|
2913
|
+
throw new Error('baseline identity getter');
|
|
2914
|
+
},
|
|
2915
|
+
artifacts: { [PATH]: BASELINE_EXAMPLE_VALUE }
|
|
2916
|
+
};
|
|
2917
|
+
let out;
|
|
2918
|
+
try {
|
|
2919
|
+
out = runWith(baseline, 'baseline-A');
|
|
2920
|
+
} catch (error) {
|
|
2921
|
+
failures.push(`${label}/throwing: escaped: ${error.message}`);
|
|
2922
|
+
continue;
|
|
2923
|
+
}
|
|
2924
|
+
const result = out.results.find(item => item.obligationId === 'doc-change:doc-lint');
|
|
2925
|
+
if (result.outcome === 'pass') failures.push(`${label}/throwing: a throwing identity produced a pass`);
|
|
2926
|
+
if (!out.diagnostics.some(item => item.code === 'baseline-identity-missing')) {
|
|
2927
|
+
failures.push(`${label}/throwing: diagnostics=${out.diagnostics.map(item => item.code)}`);
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
assert.deepStrictEqual(failures, [],
|
|
2932
|
+
`the whole-baseline identity must be captured once: ${failures.join('; ')}`);
|
|
2933
|
+
});
|
|
2934
|
+
|
|
2935
|
+
checkCase('335 P2 the rename destination digest is validated and captured from one read', () => {
|
|
2936
|
+
const failures = [];
|
|
2937
|
+
for (const target of [modules, packageModules]) {
|
|
2938
|
+
const label = target === modules ? 'canonical' : 'packaged';
|
|
2939
|
+
const obligation = {
|
|
2940
|
+
id: 'doc-change:doc-lint',
|
|
2941
|
+
capability: 'doc-lint',
|
|
2942
|
+
level: 'MUST',
|
|
2943
|
+
applicability: 'applicable',
|
|
2944
|
+
scope: {
|
|
2945
|
+
paths: [RENAME_OLD, RENAME_NEW],
|
|
2946
|
+
baselinePaths: [{ path: RENAME_OLD, reason: 'rename-from', renameTo: RENAME_NEW }]
|
|
2947
|
+
}
|
|
2948
|
+
};
|
|
2949
|
+
const baseline = {
|
|
2950
|
+
digest: RENAME_BASELINE_DIGEST,
|
|
2951
|
+
artifacts: { [RENAME_OLD]: RENAME_OLD_BASELINE }
|
|
2952
|
+
};
|
|
2953
|
+
|
|
2954
|
+
function runWith(destinationEntry, recordDestinationDigest) {
|
|
2955
|
+
return target.checkDocObligations({
|
|
2956
|
+
obligations: [obligation],
|
|
2957
|
+
artifacts: { [RENAME_NEW]: destinationEntry },
|
|
2958
|
+
baseline,
|
|
2959
|
+
executionRecords: [{
|
|
2960
|
+
id: 'rename-run',
|
|
2961
|
+
obligationId: 'doc-change:doc-lint',
|
|
2962
|
+
command: 'node ci/mechanism-335-baseline-evidence.test.js # rename-run',
|
|
2963
|
+
exitCode: 0,
|
|
2964
|
+
provenance: 'rename destination snapshot regression',
|
|
2965
|
+
baseDigest: RENAME_BASELINE_DIGEST,
|
|
2966
|
+
paths: [RENAME_OLD, RENAME_NEW],
|
|
2967
|
+
artifactDigests: { [RENAME_NEW]: recordDestinationDigest },
|
|
2968
|
+
baselineArtifacts: { [RENAME_OLD]: RENAME_OLD_BASELINE }
|
|
2969
|
+
}],
|
|
2970
|
+
reviewRecords: []
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
// Stateful destination digest getter: validation must use the first read B; a record
|
|
2975
|
+
// carrying only the later C cannot pass.
|
|
2976
|
+
{
|
|
2977
|
+
let destinationReads = 0;
|
|
2978
|
+
const statefulDestination = {
|
|
2979
|
+
get digest() {
|
|
2980
|
+
destinationReads += 1;
|
|
2981
|
+
return destinationReads === 1 ? RENAME_DEST_B : RENAME_DEST_C;
|
|
2982
|
+
}
|
|
2983
|
+
};
|
|
2984
|
+
let out;
|
|
2985
|
+
try {
|
|
2986
|
+
out = runWith(statefulDestination, RENAME_DEST_C);
|
|
2987
|
+
} catch (error) {
|
|
2988
|
+
failures.push(`${label}/stateful: escaped: ${error.message}`);
|
|
2989
|
+
continue;
|
|
2990
|
+
}
|
|
2991
|
+
const result = out.results.find(item => item.obligationId === 'doc-change:doc-lint');
|
|
2992
|
+
if (destinationReads !== 1) failures.push(`${label}/stateful: destination digest reads=${destinationReads}`);
|
|
2993
|
+
if (result.outcome === 'pass') failures.push(`${label}/stateful: a record bound to the later value passed`);
|
|
2994
|
+
if (!out.diagnostics.some(item => item.code === 'stale-artifact-digest')) {
|
|
2995
|
+
failures.push(`${label}/stateful: diagnostics=${out.diagnostics.map(item => item.code)}`);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
// Stable controls: the validated destination binds its own value; a different value is stale.
|
|
3000
|
+
{
|
|
3001
|
+
const passing = runWith({ digest: RENAME_DEST_B }, RENAME_DEST_B);
|
|
3002
|
+
if (passing.results.find(item => item.obligationId === 'doc-change:doc-lint').outcome !== 'pass') {
|
|
3003
|
+
failures.push(`${label}/stable: the validated destination did not pass; diagnostics=${passing.diagnostics.map(item => item.code)}`);
|
|
3004
|
+
}
|
|
3005
|
+
const stale = runWith({ digest: RENAME_DEST_B }, RENAME_DEST_C);
|
|
3006
|
+
const staleResult = stale.results.find(item => item.obligationId === 'doc-change:doc-lint');
|
|
3007
|
+
if (staleResult.outcome !== 'none') failures.push(`${label}/stable: outcome=${staleResult.outcome}`);
|
|
3008
|
+
if (!stale.diagnostics.some(item => item.code === 'stale-artifact-digest')) {
|
|
3009
|
+
failures.push(`${label}/stable: diagnostics=${stale.diagnostics.map(item => item.code)}`);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
// A throwing destination digest getter fails closed instead of escaping.
|
|
3014
|
+
{
|
|
3015
|
+
const throwingDestination = {
|
|
3016
|
+
get digest() {
|
|
3017
|
+
throw new Error('destination digest getter');
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
let out;
|
|
3021
|
+
try {
|
|
3022
|
+
out = runWith(throwingDestination, RENAME_DEST_B);
|
|
3023
|
+
} catch (error) {
|
|
3024
|
+
failures.push(`${label}/throwing: escaped: ${error.message}`);
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
const result = out.results.find(item => item.obligationId === 'doc-change:doc-lint');
|
|
3028
|
+
if (result.outcome === 'pass') failures.push(`${label}/throwing: a throwing destination produced a pass`);
|
|
3029
|
+
if (!out.diagnostics.some(item => item.code === 'candidate-artifacts-invalid')) {
|
|
3030
|
+
failures.push(`${label}/throwing: diagnostics=${out.diagnostics.map(item => item.code)}`);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
assert.deepStrictEqual(failures, [],
|
|
3035
|
+
`the rename destination digest must be captured once: ${failures.join('; ')}`);
|
|
3036
|
+
});
|
|
3037
|
+
|
|
2690
3038
|
helper.finish();
|
|
@@ -82,10 +82,16 @@ after validation, cannot mute the stale-baseline diagnostic, and cannot drift th
|
|
|
82
82
|
Its first read must therefore be the value every consumer sees; a getter that starts returning a
|
|
83
83
|
different or unusable value later is factored out entirely. A baseline per-path inspection that
|
|
84
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**
|
|
86
|
-
re-read of `baselinePaths[].path`, so a stateful `path` accessor is
|
|
87
|
-
passing result always describes the baseline identity that validated and
|
|
88
|
-
|
|
85
|
+
closed instead of escaping the checker. The audit mirror is likewise keyed by the **validated**
|
|
86
|
+
path snapshot rather than a re-read of `baselinePaths[].path`, so a stateful `path` accessor is
|
|
87
|
+
read exactly once and a passing result always describes the baseline identity that validated and
|
|
88
|
+
bound the evidence.
|
|
89
|
+
|
|
90
|
+
The whole-baseline identity `baseline.digest` follows the same discipline: it is read exactly
|
|
91
|
+
once and that same scalar is validated, so a stateful value cannot be inspected as A while every
|
|
92
|
+
evidence comparison uses B. The rename destination's own digest is also read exactly once during
|
|
93
|
+
its validated snapshot, and the captured value drives the metadata cache, binding, the stale
|
|
94
|
+
diagnostics, and the audit identity.
|
|
89
95
|
|
|
90
96
|
Per-path evidence must be carried explicitly. Inherited properties in `baseline.artifacts` or a
|
|
91
97
|
record's `baselineArtifacts` are not own-key evidence and never satisfy a path; the trusted
|
|
@@ -110,6 +116,11 @@ candidate digest — the same one used by ordinary candidate binding, the audit
|
|
|
110
116
|
stale diagnostics. The destination is therefore never read again through the candidate map during
|
|
111
117
|
capture or binding: a nested `digest` that throws or changes after validation cannot make a stale
|
|
112
118
|
destination pair bind while the audit/stale verdicts still describe the captured value. The same
|
|
119
|
+
read-once guarantee extends to rejected destination snapshots: when the destination's own digest
|
|
120
|
+
read throws or returns an unusable value, that failed snapshot ('') is recorded and all later
|
|
121
|
+
reads for that destination path are suppressed as well — the entry's single metadata read remains
|
|
122
|
+
its only digest read, and the failed path contributes `path=missing` to `artifactDigest` instead
|
|
123
|
+
of a later, different value. The same
|
|
113
124
|
record can cover both endpoints. Destination-only evidence, source-only tombstones, split endpoint
|
|
114
125
|
records, and stale destination digests do not satisfy the source. A deletion tombstone therefore
|
|
115
126
|
cannot stand in for a known rename. This destination validation and the snapshot it produces are
|
|
@@ -129,14 +140,24 @@ obligations. `N/A` and `unknown` obligations never bind evidence and keep the le
|
|
|
129
140
|
for their audit identity (so a candidate `get` that supplies a digest is still reflected there);
|
|
130
141
|
they remain `outcome: 'none'` and do not emit `candidate-artifacts-invalid` from this applicability-gated check.
|
|
131
142
|
|
|
143
|
+
When several `rename-from` entries declare one shared destination, the read-once promise is scoped
|
|
144
|
+
**per declaring entry**: each entry's metadata validation reads the shared destination once (their
|
|
145
|
+
agreement of validated values is what detects a contradictory shared-destination snapshot). A
|
|
146
|
+
poisoned destination — one whose snapshot was rejected — is honored inside the metadata loop: a
|
|
147
|
+
later declaring entry fails closed without reading the destination again, and the failed snapshot
|
|
148
|
+
wins in the audit, overriding any previously validated successful snapshot that an earlier entry
|
|
149
|
+
carried, rather than silently describing a value the getter shipped after the failure.
|
|
150
|
+
|
|
132
151
|
A rename destination's validated own digest is carried too. When more than one accepted
|
|
133
152
|
`rename-from` entry shares one `renameTo`, every entry's validated own digest must agree; a
|
|
134
153
|
conflicting validated digest is a contradictory shared-destination snapshot and fails the whole
|
|
135
154
|
obligation closed (metadata error, `outcome: 'none'`) before any binding. The carried digest must
|
|
136
155
|
be a non-empty string: a stateful `digest` accessor whose value is not a usable string is not
|
|
137
|
-
carried or seeded
|
|
138
|
-
|
|
139
|
-
|
|
156
|
+
carried or seeded — instead the destination path is recorded as a visited failure, and neither the
|
|
157
|
+
capture loop nor the audit identity reads the destination again. Only **applicable** obligations
|
|
158
|
+
validate and carry this destination snapshot; `N/A` and `unknown` obligations keep the legacy
|
|
159
|
+
candidate read for their audit identity and do not consume the destination getter during metadata
|
|
160
|
+
validation.
|
|
140
161
|
|
|
141
162
|
The binding applies to execution and review records alike. Existing review approval, waiver,
|
|
142
163
|
failing-command, requirement-sync authority, and applicability rules are unchanged. A correctly
|
|
@@ -72,8 +72,18 @@ function artifact(entry) {
|
|
|
72
72
|
|
|
73
73
|
// #335 P2: a baseline-bound rename destination must declare its own digest. An inherited
|
|
74
74
|
// `digest` (for example Object.create({ digest })) is not explicit candidate evidence.
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// P2(r4321077631): reads the own digest exactly once. `artifact()` inspects `entry.digest` up
|
|
76
|
+
// to three times (typeof, length, value), so a stateful accessor could validate one value and
|
|
77
|
+
// have the captured destination snapshot carry another. Only the applicable rename-destination
|
|
78
|
+
// snapshot uses this helper; non-baseline and inapplicable lanes keep the legacy `artifact()`
|
|
79
|
+
// reads for their audit identity. A throwing digest getter propagates to the caller's guarded
|
|
80
|
+
// block and fails the inspection closed there. This is the single authoritative own-digest
|
|
81
|
+
// helper; nothing else in this module reads a destination's own digest.
|
|
82
|
+
function ownDigestOnce(entry) {
|
|
83
|
+
if (!isPlainObject(entry)) return '';
|
|
84
|
+
if (!hasOwnEntry(entry, 'digest')) return '';
|
|
85
|
+
const digest = entry.digest;
|
|
86
|
+
return typeof digest === 'string' && digest.length > 0 ? digest : '';
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
function provenanceText(record) {
|
|
@@ -142,7 +152,20 @@ function checkDocObligations({ obligations, artifacts, baseline, executionRecord
|
|
|
142
152
|
}
|
|
143
153
|
return candidateInventoryKnown;
|
|
144
154
|
};
|
|
145
|
-
|
|
155
|
+
// P2(r4321077638): the whole-baseline identity is read exactly once and that same scalar is
|
|
156
|
+
// validated, so a stateful getter cannot pass validation as A and have every later comparison
|
|
157
|
+
// use B. A baseline identity whose inspection throws is unusable input: the captured digest
|
|
158
|
+
// stays '', so baseline-bound evaluations fail closed through `baseline-identity-missing`
|
|
159
|
+
// instead of escaping the checker, and non-baseline callers keep the legacy '' shape.
|
|
160
|
+
let baselineDigest = '';
|
|
161
|
+
if (isPlainObject(baseline)) {
|
|
162
|
+
try {
|
|
163
|
+
const declaredBaselineDigest = baseline.digest;
|
|
164
|
+
if (nonEmptyString(declaredBaselineDigest)) baselineDigest = declaredBaselineDigest;
|
|
165
|
+
} catch {
|
|
166
|
+
baselineDigest = '';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
146
169
|
const executionList = Array.isArray(executionRecords) ? executionRecords.filter(isRecord) : [];
|
|
147
170
|
const reviewList = Array.isArray(reviewRecords) ? reviewRecords.filter(isRecord) : [];
|
|
148
171
|
|
|
@@ -184,6 +207,16 @@ function evaluateObligation(obligation, context) {
|
|
|
184
207
|
// #340: every accepted entry sharing a rename destination must validate the same own digest,
|
|
185
208
|
// or the shared-destination snapshot is contradictory and the obligation fails closed.
|
|
186
209
|
const validatedDestinationDigestMap = new Map();
|
|
210
|
+
// #342 P3: a rename destination whose own-digest read throws or returns an unusable value is
|
|
211
|
+
// still a visited snapshot. Its captured ('') value is recorded here so the ordinary capture
|
|
212
|
+
// and the audit identity never re-read that destination entry: the single metadata read stays
|
|
213
|
+
// the only digest read for that declaring entry, and a stateful getter cannot ship a later
|
|
214
|
+
// non-empty value into the audit key even though its entry was rejected. A poisoned
|
|
215
|
+
// destination is honored inside the metadata loop (a later declaring entry fails closed
|
|
216
|
+
// without reading it) and keeps precedence over a previously seeded successful snapshot;
|
|
217
|
+
// multiple accepted entries still read the destination once each by design, because their
|
|
218
|
+
// agreement check detects shared-destination contradictions.
|
|
219
|
+
const failedDestinationDigests = new Map();
|
|
187
220
|
// #340 P2(r4320818749): the validated per-path baseline digest is captured once during
|
|
188
221
|
// metadata validation. The audit mirror, the stale-baseline diagnostics, and baselineBound()
|
|
189
222
|
// reuse that captured value instead of re-reading the caller map, so a stateful own getter
|
|
@@ -289,19 +322,38 @@ function evaluateObligation(obligation, context) {
|
|
|
289
322
|
// keeps the un-gated legacy candidate read; only applicable obligations capture and
|
|
290
323
|
// validate the destination snapshot.
|
|
291
324
|
if (applicability === 'applicable') {
|
|
325
|
+
// #342 review P3: a destination poisoned by an earlier entry's failed snapshot is
|
|
326
|
+
// honored inside the metadata loop; the failed visited snapshot is never read again
|
|
327
|
+
// and this entry fails closed with it, so a stateful getter's later successful
|
|
328
|
+
// value cannot repair a shared destination or enter the audit.
|
|
329
|
+
if (failedDestinationDigests.has(entryRenameTo)) {
|
|
330
|
+
invalid(entryPath, `its rename destination ${JSON.stringify(entryRenameTo)} failed an earlier snapshot validation for this obligation; the failed destination is not read again and this entry fails closed with it`);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
292
333
|
let destinationDigest = '';
|
|
293
334
|
try {
|
|
294
335
|
if (hasOwnEntry(artifactMap, entryRenameTo)) {
|
|
295
|
-
|
|
336
|
+
// P2(r4321077631): read the destination's own digest exactly once; the captured
|
|
337
|
+
// value is the one validated below and the one that flows to the metadata cache,
|
|
338
|
+
// binding, the stale diagnostics, and the audit identity. A stateful destination
|
|
339
|
+
// getter cannot be validated as B while a record carrying only its later C passes.
|
|
340
|
+
destinationDigest = ownDigestOnce(artifactMap[entryRenameTo]);
|
|
296
341
|
}
|
|
297
342
|
} catch {
|
|
298
343
|
candidateInventoryInspectionFailed = true;
|
|
344
|
+
// #342 P3: the destination is a visited snapshot even when its digest read threw;
|
|
345
|
+
// record it so the capture and audit lanes never re-read it.
|
|
346
|
+
failedDestinationDigests.set(entryRenameTo, '');
|
|
299
347
|
continue;
|
|
300
348
|
}
|
|
301
|
-
// #340: the captured own value must be a usable non-empty string;
|
|
302
|
-
//
|
|
349
|
+
// #340: the captured own value must be a usable non-empty string; an unusable result
|
|
350
|
+
// is not carried or seeded.
|
|
303
351
|
if (!nonEmptyString(destinationDigest)) {
|
|
304
352
|
invalid(entryPath, `its rename destination ${JSON.stringify(entryRenameTo)} has no candidate artifact`);
|
|
353
|
+
// #342 P3: the destination is a visited snapshot even when its own digest is
|
|
354
|
+
// unusable; record it so the capture and audit lanes never re-read it and cannot
|
|
355
|
+
// describe a stateful getter's later non-empty value.
|
|
356
|
+
failedDestinationDigests.set(entryRenameTo, '');
|
|
305
357
|
continue;
|
|
306
358
|
}
|
|
307
359
|
// #340: entries that share one rename destination must agree on the validated own
|
|
@@ -377,7 +429,9 @@ function evaluateObligation(obligation, context) {
|
|
|
377
429
|
// its own digest during metadata. Seed the captured snapshot from that validated value and
|
|
378
430
|
// never read the destination through the raw candidate map again, so a destination accessor
|
|
379
431
|
// that throws or changes after validation cannot make capture/binding/audit disagree. The
|
|
380
|
-
// seeded value is the validated own digest, never a caller-asserted rawEntry field.
|
|
432
|
+
// seeded value is the validated own digest, never a caller-asserted rawEntry field. A
|
|
433
|
+
// rejected destination is still visited: its failed capture (see failedDestinationDigests)
|
|
434
|
+
// suppresses the ordinary read below, so no destination entry is ever read twice.
|
|
381
435
|
for (const entry of baselineEntries.values()) {
|
|
382
436
|
if (entry.reason === 'rename-from' && nonEmptyString(entry.renameDestinationDigest)
|
|
383
437
|
&& !candidateDigestCache.has(entry.renameTo)) {
|
|
@@ -385,7 +439,8 @@ function evaluateObligation(obligation, context) {
|
|
|
385
439
|
}
|
|
386
440
|
}
|
|
387
441
|
for (const path of scopePaths) {
|
|
388
|
-
if (baselineEntries.has(path) || candidateDigestCache.has(path)
|
|
442
|
+
if (baselineEntries.has(path) || candidateDigestCache.has(path)
|
|
443
|
+
|| failedDestinationDigests.has(path)) continue;
|
|
389
444
|
try {
|
|
390
445
|
candidateDigestCache.set(path, artifact(artifactMap[path]));
|
|
391
446
|
} catch {
|
|
@@ -428,6 +483,11 @@ function evaluateObligation(obligation, context) {
|
|
|
428
483
|
// stale verdict disagree with the digest that actually bound evidence. Untouched paths and
|
|
429
484
|
// unannotated callers keep the legacy read.
|
|
430
485
|
const candidateDigestOf = (path) => {
|
|
486
|
+
// #342 P3: poisoned state takes precedence over the capture cache. A rejected rename
|
|
487
|
+
// destination keeps its visited '' capture even when an earlier accepted entry seeded a
|
|
488
|
+
// successful value, so the audit never describes a value the getter shipped after a failed
|
|
489
|
+
// snapshot made the obligation fail closed; the caller map is never read for it again.
|
|
490
|
+
if (failedDestinationDigests.has(path)) return '';
|
|
431
491
|
if (candidateDigestCache.has(path)) return candidateDigestCache.get(path);
|
|
432
492
|
// #340: only an applicable baseline-annotated obligation validated the source absence as
|
|
433
493
|
// part of its captured snapshot. `N/A` and `unknown` obligations never bind evidence, so they
|
|
@@ -509,7 +569,7 @@ function evaluateObligation(obligation, context) {
|
|
|
509
569
|
// candidate lane, the audit identity, and the stale diagnostics use, so a stateful nested
|
|
510
570
|
// accessor cannot make binding disagree with them. Metadata validation already required the
|
|
511
571
|
// destination to be an own-key candidate inventory entry carrying its own digest
|
|
512
|
-
// (
|
|
572
|
+
// (ownDigestOnce), so a non-empty captured digest is required here and the accessor is
|
|
513
573
|
// never re-read; a missing destination skips capture and lands on this guard.
|
|
514
574
|
const capturedDestination = candidateDigestCache.get(entry.renameTo);
|
|
515
575
|
if (!nonEmptyString(capturedDestination)) return false;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "harness-everything",
|
|
4
|
-
"version": "0.36.
|
|
4
|
+
"version": "0.36.2",
|
|
5
5
|
"description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Miya Daniel",
|
|
@@ -72,8 +72,18 @@ function artifact(entry) {
|
|
|
72
72
|
|
|
73
73
|
// #335 P2: a baseline-bound rename destination must declare its own digest. An inherited
|
|
74
74
|
// `digest` (for example Object.create({ digest })) is not explicit candidate evidence.
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// P2(r4321077631): reads the own digest exactly once. `artifact()` inspects `entry.digest` up
|
|
76
|
+
// to three times (typeof, length, value), so a stateful accessor could validate one value and
|
|
77
|
+
// have the captured destination snapshot carry another. Only the applicable rename-destination
|
|
78
|
+
// snapshot uses this helper; non-baseline and inapplicable lanes keep the legacy `artifact()`
|
|
79
|
+
// reads for their audit identity. A throwing digest getter propagates to the caller's guarded
|
|
80
|
+
// block and fails the inspection closed there. This is the single authoritative own-digest
|
|
81
|
+
// helper; nothing else in this module reads a destination's own digest.
|
|
82
|
+
function ownDigestOnce(entry) {
|
|
83
|
+
if (!isPlainObject(entry)) return '';
|
|
84
|
+
if (!hasOwnEntry(entry, 'digest')) return '';
|
|
85
|
+
const digest = entry.digest;
|
|
86
|
+
return typeof digest === 'string' && digest.length > 0 ? digest : '';
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
function provenanceText(record) {
|
|
@@ -142,7 +152,20 @@ function checkDocObligations({ obligations, artifacts, baseline, executionRecord
|
|
|
142
152
|
}
|
|
143
153
|
return candidateInventoryKnown;
|
|
144
154
|
};
|
|
145
|
-
|
|
155
|
+
// P2(r4321077638): the whole-baseline identity is read exactly once and that same scalar is
|
|
156
|
+
// validated, so a stateful getter cannot pass validation as A and have every later comparison
|
|
157
|
+
// use B. A baseline identity whose inspection throws is unusable input: the captured digest
|
|
158
|
+
// stays '', so baseline-bound evaluations fail closed through `baseline-identity-missing`
|
|
159
|
+
// instead of escaping the checker, and non-baseline callers keep the legacy '' shape.
|
|
160
|
+
let baselineDigest = '';
|
|
161
|
+
if (isPlainObject(baseline)) {
|
|
162
|
+
try {
|
|
163
|
+
const declaredBaselineDigest = baseline.digest;
|
|
164
|
+
if (nonEmptyString(declaredBaselineDigest)) baselineDigest = declaredBaselineDigest;
|
|
165
|
+
} catch {
|
|
166
|
+
baselineDigest = '';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
146
169
|
const executionList = Array.isArray(executionRecords) ? executionRecords.filter(isRecord) : [];
|
|
147
170
|
const reviewList = Array.isArray(reviewRecords) ? reviewRecords.filter(isRecord) : [];
|
|
148
171
|
|
|
@@ -184,6 +207,16 @@ function evaluateObligation(obligation, context) {
|
|
|
184
207
|
// #340: every accepted entry sharing a rename destination must validate the same own digest,
|
|
185
208
|
// or the shared-destination snapshot is contradictory and the obligation fails closed.
|
|
186
209
|
const validatedDestinationDigestMap = new Map();
|
|
210
|
+
// #342 P3: a rename destination whose own-digest read throws or returns an unusable value is
|
|
211
|
+
// still a visited snapshot. Its captured ('') value is recorded here so the ordinary capture
|
|
212
|
+
// and the audit identity never re-read that destination entry: the single metadata read stays
|
|
213
|
+
// the only digest read for that declaring entry, and a stateful getter cannot ship a later
|
|
214
|
+
// non-empty value into the audit key even though its entry was rejected. A poisoned
|
|
215
|
+
// destination is honored inside the metadata loop (a later declaring entry fails closed
|
|
216
|
+
// without reading it) and keeps precedence over a previously seeded successful snapshot;
|
|
217
|
+
// multiple accepted entries still read the destination once each by design, because their
|
|
218
|
+
// agreement check detects shared-destination contradictions.
|
|
219
|
+
const failedDestinationDigests = new Map();
|
|
187
220
|
// #340 P2(r4320818749): the validated per-path baseline digest is captured once during
|
|
188
221
|
// metadata validation. The audit mirror, the stale-baseline diagnostics, and baselineBound()
|
|
189
222
|
// reuse that captured value instead of re-reading the caller map, so a stateful own getter
|
|
@@ -289,19 +322,38 @@ function evaluateObligation(obligation, context) {
|
|
|
289
322
|
// keeps the un-gated legacy candidate read; only applicable obligations capture and
|
|
290
323
|
// validate the destination snapshot.
|
|
291
324
|
if (applicability === 'applicable') {
|
|
325
|
+
// #342 review P3: a destination poisoned by an earlier entry's failed snapshot is
|
|
326
|
+
// honored inside the metadata loop; the failed visited snapshot is never read again
|
|
327
|
+
// and this entry fails closed with it, so a stateful getter's later successful
|
|
328
|
+
// value cannot repair a shared destination or enter the audit.
|
|
329
|
+
if (failedDestinationDigests.has(entryRenameTo)) {
|
|
330
|
+
invalid(entryPath, `its rename destination ${JSON.stringify(entryRenameTo)} failed an earlier snapshot validation for this obligation; the failed destination is not read again and this entry fails closed with it`);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
292
333
|
let destinationDigest = '';
|
|
293
334
|
try {
|
|
294
335
|
if (hasOwnEntry(artifactMap, entryRenameTo)) {
|
|
295
|
-
|
|
336
|
+
// P2(r4321077631): read the destination's own digest exactly once; the captured
|
|
337
|
+
// value is the one validated below and the one that flows to the metadata cache,
|
|
338
|
+
// binding, the stale diagnostics, and the audit identity. A stateful destination
|
|
339
|
+
// getter cannot be validated as B while a record carrying only its later C passes.
|
|
340
|
+
destinationDigest = ownDigestOnce(artifactMap[entryRenameTo]);
|
|
296
341
|
}
|
|
297
342
|
} catch {
|
|
298
343
|
candidateInventoryInspectionFailed = true;
|
|
344
|
+
// #342 P3: the destination is a visited snapshot even when its digest read threw;
|
|
345
|
+
// record it so the capture and audit lanes never re-read it.
|
|
346
|
+
failedDestinationDigests.set(entryRenameTo, '');
|
|
299
347
|
continue;
|
|
300
348
|
}
|
|
301
|
-
// #340: the captured own value must be a usable non-empty string;
|
|
302
|
-
//
|
|
349
|
+
// #340: the captured own value must be a usable non-empty string; an unusable result
|
|
350
|
+
// is not carried or seeded.
|
|
303
351
|
if (!nonEmptyString(destinationDigest)) {
|
|
304
352
|
invalid(entryPath, `its rename destination ${JSON.stringify(entryRenameTo)} has no candidate artifact`);
|
|
353
|
+
// #342 P3: the destination is a visited snapshot even when its own digest is
|
|
354
|
+
// unusable; record it so the capture and audit lanes never re-read it and cannot
|
|
355
|
+
// describe a stateful getter's later non-empty value.
|
|
356
|
+
failedDestinationDigests.set(entryRenameTo, '');
|
|
305
357
|
continue;
|
|
306
358
|
}
|
|
307
359
|
// #340: entries that share one rename destination must agree on the validated own
|
|
@@ -377,7 +429,9 @@ function evaluateObligation(obligation, context) {
|
|
|
377
429
|
// its own digest during metadata. Seed the captured snapshot from that validated value and
|
|
378
430
|
// never read the destination through the raw candidate map again, so a destination accessor
|
|
379
431
|
// that throws or changes after validation cannot make capture/binding/audit disagree. The
|
|
380
|
-
// seeded value is the validated own digest, never a caller-asserted rawEntry field.
|
|
432
|
+
// seeded value is the validated own digest, never a caller-asserted rawEntry field. A
|
|
433
|
+
// rejected destination is still visited: its failed capture (see failedDestinationDigests)
|
|
434
|
+
// suppresses the ordinary read below, so no destination entry is ever read twice.
|
|
381
435
|
for (const entry of baselineEntries.values()) {
|
|
382
436
|
if (entry.reason === 'rename-from' && nonEmptyString(entry.renameDestinationDigest)
|
|
383
437
|
&& !candidateDigestCache.has(entry.renameTo)) {
|
|
@@ -385,7 +439,8 @@ function evaluateObligation(obligation, context) {
|
|
|
385
439
|
}
|
|
386
440
|
}
|
|
387
441
|
for (const path of scopePaths) {
|
|
388
|
-
if (baselineEntries.has(path) || candidateDigestCache.has(path)
|
|
442
|
+
if (baselineEntries.has(path) || candidateDigestCache.has(path)
|
|
443
|
+
|| failedDestinationDigests.has(path)) continue;
|
|
389
444
|
try {
|
|
390
445
|
candidateDigestCache.set(path, artifact(artifactMap[path]));
|
|
391
446
|
} catch {
|
|
@@ -428,6 +483,11 @@ function evaluateObligation(obligation, context) {
|
|
|
428
483
|
// stale verdict disagree with the digest that actually bound evidence. Untouched paths and
|
|
429
484
|
// unannotated callers keep the legacy read.
|
|
430
485
|
const candidateDigestOf = (path) => {
|
|
486
|
+
// #342 P3: poisoned state takes precedence over the capture cache. A rejected rename
|
|
487
|
+
// destination keeps its visited '' capture even when an earlier accepted entry seeded a
|
|
488
|
+
// successful value, so the audit never describes a value the getter shipped after a failed
|
|
489
|
+
// snapshot made the obligation fail closed; the caller map is never read for it again.
|
|
490
|
+
if (failedDestinationDigests.has(path)) return '';
|
|
431
491
|
if (candidateDigestCache.has(path)) return candidateDigestCache.get(path);
|
|
432
492
|
// #340: only an applicable baseline-annotated obligation validated the source absence as
|
|
433
493
|
// part of its captured snapshot. `N/A` and `unknown` obligations never bind evidence, so they
|
|
@@ -509,7 +569,7 @@ function evaluateObligation(obligation, context) {
|
|
|
509
569
|
// candidate lane, the audit identity, and the stale diagnostics use, so a stateful nested
|
|
510
570
|
// accessor cannot make binding disagree with them. Metadata validation already required the
|
|
511
571
|
// destination to be an own-key candidate inventory entry carrying its own digest
|
|
512
|
-
// (
|
|
572
|
+
// (ownDigestOnce), so a non-empty captured digest is required here and the accessor is
|
|
513
573
|
// never re-read; a missing destination skips capture and lands on this guard.
|
|
514
574
|
const capturedDestination = candidateDigestCache.get(entry.renameTo);
|
|
515
575
|
if (!nonEmptyString(capturedDestination)) return false;
|
package/sbom.cdx.json
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
"$schema": "https://cyclonedx.org/schema/bom-1.7.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.7",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:d784e4c6-dc73-508d-9e04-a38908e96486",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-10-
|
|
8
|
+
"timestamp": "2026-10-20T05:31:16.491Z",
|
|
9
9
|
"tools": [
|
|
10
10
|
{
|
|
11
11
|
"vendor": "Harness Everything",
|
|
12
12
|
"name": "generate-sbom.js",
|
|
13
|
-
"version": "0.36.
|
|
13
|
+
"version": "0.36.2"
|
|
14
14
|
}
|
|
15
15
|
],
|
|
16
16
|
"component": {
|
|
17
17
|
"type": "application",
|
|
18
|
-
"bom-ref": "pkg:npm/harness-everything@0.36.
|
|
18
|
+
"bom-ref": "pkg:npm/harness-everything@0.36.2",
|
|
19
19
|
"name": "harness-everything",
|
|
20
|
-
"version": "0.36.
|
|
21
|
-
"purl": "pkg:npm/harness-everything@0.36.
|
|
20
|
+
"version": "0.36.2",
|
|
21
|
+
"purl": "pkg:npm/harness-everything@0.36.2"
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"components": [
|
|
@@ -1999,7 +1999,7 @@
|
|
|
1999
1999
|
]
|
|
2000
2000
|
},
|
|
2001
2001
|
{
|
|
2002
|
-
"ref": "pkg:npm/harness-everything@0.36.
|
|
2002
|
+
"ref": "pkg:npm/harness-everything@0.36.2",
|
|
2003
2003
|
"dependsOn": [
|
|
2004
2004
|
"pkg:npm/%40commitlint/cli@19.8.1",
|
|
2005
2005
|
"pkg:npm/%40commitlint/config-conventional@19.8.1",
|