harness-everything 0.36.3 → 0.36.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +10 -0
  4. package/ci/fixtures/doc-obligations/read-boundary/boundary-controls.json +211 -0
  5. package/ci/fixtures/doc-obligations/read-boundary/boundary-policy.json +325 -0
  6. package/ci/fixtures/doc-obligations/read-boundary/cases.json +14058 -0
  7. package/ci/fixtures/doc-obligations/read-boundary/expected.json +1 -0
  8. package/ci/fixtures/doc-obligations/read-boundary/identity-cases.json +119 -0
  9. package/ci/fixtures/doc-obligations/read-boundary/identity-expected.json +1 -0
  10. package/ci/fixtures/doc-obligations/read-boundary/scope-cases.json +208 -0
  11. package/ci/fixtures/doc-obligations/read-boundary/scope-expected.json +1 -0
  12. package/ci/fixtures/doc-obligations/reconciliation-prep/cases.json +2751 -0
  13. package/ci/fixtures/doc-obligations/reconciliation-prep/flood-entrypoint.js +5 -0
  14. package/ci/fixtures/doc-obligations/reconciliation-prep/hang-entrypoint.js +5 -0
  15. package/ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json +648 -0
  16. package/ci/fixtures/doc-obligations/reconciliation-prep/provider-contract.json +100 -0
  17. package/ci/fixtures/doc-obligations/reconciliation-prep/trust-contract.json +188 -0
  18. package/ci/lib/doc-evidence-boundary-check.js +1240 -0
  19. package/ci/lib/doc-evidence-fixtures.js +459 -0
  20. package/ci/lib/doc-evidence-trace.js +171 -0
  21. package/ci/lib/doc-reconciliation-prep-fixtures.js +502 -0
  22. package/ci/lib/snapshot-stdin.js +27 -0
  23. package/ci/mechanism-325-3b-preparation.test.js +382 -0
  24. package/ci/mechanism-341-boundary-enforcement.test.js +660 -0
  25. package/ci/mechanism-341-capture-module.test.js +357 -0
  26. package/ci/mechanism-341-mutation-runner.test.js +413 -0
  27. package/ci/mechanism-341-read-boundary.test.js +388 -0
  28. package/ci/mechanism-341-snapshot-stdin.test.js +40 -0
  29. package/ci/mutation-check.js +805 -45
  30. package/ci/mutation-loader.js +544 -57
  31. package/ci/prep/doc-reconciliation-red.js +407 -0
  32. package/ci/stress-341-boundary-control.js +85 -0
  33. package/docs/doc-evidence-baseline-binding.md +105 -19
  34. package/docs/doc-evidence-boundary.md +135 -0
  35. package/docs/doc-workflow-reconciliation.md +224 -0
  36. package/docs/document-lifecycle.md +8 -1
  37. package/harness-everything/SKILL.md +1 -1
  38. package/harness-everything/scripts/doc-evidence-capture.js +331 -0
  39. package/harness-everything/scripts/doc-evidence.js +72 -223
  40. package/opencode-plugin/plugin.json +1 -1
  41. package/package.json +5 -3
  42. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  43. package/plugins/harness-everything/plugin.json +1 -1
  44. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  45. package/plugins/harness-everything/skills/harness-everything/scripts/doc-evidence-capture.js +331 -0
  46. package/plugins/harness-everything/skills/harness-everything/scripts/doc-evidence.js +72 -223
  47. package/sbom.cdx.json +43 -7
@@ -0,0 +1,357 @@
1
+ 'use strict';
2
+
3
+ // #341 Phase 0B (B1p scaffold): new-file byte and load parity for the private capture module, detachment
4
+ // detectors for its returned owned views, and pins that keep the 0A oracle unchanged.
5
+ //
6
+ // B1 has landed, so a missing capture module or packaged copy is always a FAIL, never a SKIP (#341 E0 requires
7
+ // the module checks unconditionally).
8
+ //
9
+ // Nothing here is evidence that the module is correct. B1 binds the detachment flow to the landed signatures
10
+ // and runs it; this file only fixes the checks and the failure modes.
11
+
12
+ const assert = require('assert');
13
+ const crypto = require('crypto');
14
+ const fs = require('fs');
15
+ const os = require('os');
16
+ const path = require('path');
17
+ const { types } = require('util');
18
+ const fx = require('./lib/doc-evidence-fixtures');
19
+
20
+ const ROOT = path.resolve(__dirname, '..');
21
+ const CANONICAL_SCRIPTS = path.join(ROOT, 'harness-everything', 'scripts');
22
+ const PACKAGED_SCRIPTS = path.join(ROOT, 'plugins', 'harness-everything', 'skills', 'harness-everything', 'scripts');
23
+ const CANONICAL_EVAL = path.join(CANONICAL_SCRIPTS, 'doc-evidence.js');
24
+ const PACKAGED_EVAL = path.join(PACKAGED_SCRIPTS, 'doc-evidence.js');
25
+ const CANONICAL_CAPTURE = path.join(CANONICAL_SCRIPTS, 'doc-evidence-capture.js');
26
+ const PACKAGED_CAPTURE = path.join(PACKAGED_SCRIPTS, 'doc-evidence-capture.js');
27
+ const CORPUS = path.join(__dirname, 'fixtures', 'doc-obligations', 'read-boundary');
28
+ const REQUIRED = true;
29
+
30
+ // Pins from b0-baseline-rebased.txt. They were re-pinned after the reviewed 0A head 0f475ab (owner review of #344)
31
+ // changed the corpus: RB102-105 second-invocation trace/counter sections, RB103 recipe, and the new RB106 case.
32
+ // The 0B refactor itself must not change these bytes again; the 0A-era pins in b0-baseline.md are superseded.
33
+ const ORACLE_PINS = [
34
+ ['cases.json', path.join(CORPUS, 'cases.json'), '623cae95b9f9e9c243d89ec2f845ded10e9dd3d32fd9e36deb6f081b03983acb'],
35
+ ['expected.json', path.join(CORPUS, 'expected.json'), 'd1c16f18b49223387a89edaa8491cf10a003ce31b1cbefd44a0cdd30f0cfdc87']
36
+ ];
37
+ // Every retained transparency and in-call/post-return control that B1 must keep.
38
+ const RETAINED_CASES = ['RB36', 'RB38', 'RB39', 'RB40', 'RB41', 'RB102', 'RB103', 'RB104', 'RB105'];
39
+
40
+ let passed = 0;
41
+ let skipped = 0;
42
+ let failed = 0;
43
+
44
+ class Skip extends Error {}
45
+
46
+ function sha256(file) {
47
+ return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
48
+ }
49
+
50
+ function checkCase(name, run) {
51
+ try {
52
+ run();
53
+ passed += 1;
54
+ console.log(`PASS ${name}`);
55
+ } catch (error) {
56
+ if (error instanceof Skip) {
57
+ if (REQUIRED) {
58
+ failed += 1;
59
+ console.error(`FAIL ${name}: ${error.message} (capture module is required)`);
60
+ return;
61
+ }
62
+ skipped += 1;
63
+ console.log(`SKIP ${name}: ${error.message}`);
64
+ return;
65
+ }
66
+ failed += 1;
67
+ console.error(`FAIL ${name}: ${String(error.message).split('\n')[0]}`);
68
+ }
69
+ }
70
+
71
+ function requirePresent(files, label) {
72
+ for (const file of files) {
73
+ if (!fs.existsSync(file)) {
74
+ throw new Skip(`${label} absent until B1 lands (${path.relative(ROOT, file)})`);
75
+ }
76
+ }
77
+ }
78
+
79
+ // Detects a returned view that is not owned. Owned views are scalars, plain or null-prototype objects, and
80
+ // arrays, with only data properties. Caller Proxies and caller-input identities are rejected through the
81
+ // factory's identity registry. Descriptors are read from views the module allocated, so no caller getter runs.
82
+ function ownedViewViolations(value, recorder) {
83
+ const violations = [];
84
+ const seen = new Set();
85
+ const visit = (node, at) => {
86
+ if (node === null || ['string', 'number', 'boolean', 'undefined'].includes(typeof node)) return;
87
+ if (typeof node !== 'object') {
88
+ violations.push(`${at}: ${typeof node} is not a scalar`);
89
+ return;
90
+ }
91
+ if (types.isProxy(node)) {
92
+ violations.push(`${at}: is a Proxy`);
93
+ return;
94
+ }
95
+ if (recorder && recorder.idOf(node) !== undefined) {
96
+ violations.push(`${at}: references a caller input object`);
97
+ return;
98
+ }
99
+ if (seen.has(node)) return;
100
+ seen.add(node);
101
+ const proto = Object.getPrototypeOf(node);
102
+ if (proto !== Object.prototype && proto !== Array.prototype && proto !== null) {
103
+ violations.push(`${at}: prototype is not a plain, null or array prototype`);
104
+ return;
105
+ }
106
+ for (const key of Reflect.ownKeys(node)) {
107
+ if (typeof key === 'symbol') {
108
+ violations.push(`${at}: symbol key`);
109
+ continue;
110
+ }
111
+ const descriptor = Object.getOwnPropertyDescriptor(node, key);
112
+ if (!('value' in descriptor)) {
113
+ violations.push(`${at}.${key}: accessor descriptor`);
114
+ continue;
115
+ }
116
+ visit(descriptor.value, `${at}.${key}`);
117
+ }
118
+ };
119
+ visit(value, 'view');
120
+ return violations;
121
+ }
122
+
123
+ // A tiny factory recipe for detector self-tests: one artifacts entry with a digest.
124
+ const SELF_TEST_RECIPE = {
125
+ nodes: { entry: { kind: 'object', props: { digest: 'a'.repeat(64) } } },
126
+ input: { artifacts: { $ref: 'entry' }, obligations: { kind: 'array', items: [] } }
127
+ };
128
+
129
+ console.log('=== #341 0B B1p: capture module scaffold (new-file parity, detachment detectors, oracle pins) ===');
130
+
131
+ // Detector self-tests. They run now, so the detachment harness is validated before B1 relies on it.
132
+ checkCase('detector accepts owned scalars, records, null-prototype records and arrays', () => {
133
+ const nullRecord = Object.assign(Object.create(null), { digest: 'b'.repeat(64) });
134
+ const owned = { kind: 'present', value: 'x', nested: [{ kind: 'absent' }, nullRecord, [1, 'two', undefined, null]] };
135
+ assert.deepStrictEqual(ownedViewViolations(owned, null), []);
136
+ });
137
+
138
+ checkCase('detector rejects a caller Proxy inside a returned view', () => {
139
+ const built = fx.buildCase(SELF_TEST_RECIPE, { traced: true });
140
+ const violations = ownedViewViolations({ leaked: built.input.artifacts }, built.recorder);
141
+ assert.ok(violations.some(item => item.includes('is a Proxy')), violations.join('; '));
142
+ });
143
+
144
+ checkCase('detector rejects a reference to a caller input object', () => {
145
+ const built = fx.buildCase(SELF_TEST_RECIPE, { traced: false });
146
+ const violations = ownedViewViolations({ leaked: built.input.artifacts }, built.recorder);
147
+ assert.ok(violations.some(item => item.includes('caller input object')), violations.join('; '));
148
+ });
149
+
150
+ checkCase('detector rejects an accessor descriptor, a function and a symbol key', () => {
151
+ const accessor = {};
152
+ Object.defineProperty(accessor, 'value', { enumerable: true, get() { return 1; } });
153
+ assert.ok(ownedViewViolations(accessor, null).some(item => item.includes('accessor descriptor')));
154
+ assert.ok(ownedViewViolations({ f: () => 1 }, null).some(item => item.includes('function is not a scalar')));
155
+ assert.ok(ownedViewViolations({ [Symbol('tag')]: 1 }, null).some(item => item.includes('symbol key')));
156
+ });
157
+
158
+ // Oracle pins. The 0A corpus and expectations must stay byte-identical through 0B.
159
+ for (const [label, file, expected] of ORACLE_PINS) {
160
+ checkCase(`oracle pin: ${label} is byte-identical to the B0 baseline`, () => {
161
+ assert.strictEqual(sha256(file), expected, `${label} changed`);
162
+ });
163
+ }
164
+
165
+ checkCase('oracle pin: canonical and packaged evaluators are byte-identical (B4 state)', () => {
166
+ assert.strictEqual(sha256(CANONICAL_EVAL), sha256(PACKAGED_EVAL));
167
+ });
168
+
169
+ checkCase('retained controls: every RB36/RB38-41 and RB102-105 case is still in the corpus', () => {
170
+ const corpus = JSON.parse(fs.readFileSync(path.join(CORPUS, 'cases.json'), 'utf8'));
171
+ const ids = new Set(corpus.cases.map(item => item.caseId));
172
+ const missing = RETAINED_CASES.filter(id => !ids.has(id));
173
+ assert.deepStrictEqual(missing, []);
174
+ });
175
+
176
+ // Module-dependent checks. Each one is skipped, not passed, until its files exist.
177
+ checkCase('byte parity: canonical and packaged capture modules exist', () => {
178
+ requirePresent([CANONICAL_CAPTURE, PACKAGED_CAPTURE], 'capture module');
179
+ });
180
+
181
+ checkCase('byte parity: canonical and packaged capture modules have identical bytes', () => {
182
+ requirePresent([CANONICAL_CAPTURE, PACKAGED_CAPTURE], 'capture module');
183
+ assert.strictEqual(sha256(CANONICAL_CAPTURE), sha256(PACKAGED_CAPTURE));
184
+ });
185
+
186
+ // Loads one evaluator from a temporary copy beside its capture module, and reports which capture file loaded.
187
+ // The copy is removed afterwards, and the module cache entries it created are dropped. Node keys the cache by
188
+ // realpath, so the directory is resolved first: on macOS os.tmpdir() (/var/...) is a symlink to /private/var/....
189
+ function loadEvaluatorCopy(evaluatorFile, captureFile) {
190
+ const dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'b1p-load-')));
191
+ const evaluatorCopy = path.join(dir, 'doc-evidence.js');
192
+ const captureCopy = path.join(dir, 'doc-evidence-capture.js');
193
+ try {
194
+ fs.copyFileSync(evaluatorFile, evaluatorCopy);
195
+ fs.copyFileSync(captureFile, captureCopy);
196
+ const mod = require(evaluatorCopy);
197
+ const loaded = Object.keys(require.cache).filter(key => key.startsWith(dir));
198
+ return { mod, loaded, captureCopy, dir };
199
+ } finally {
200
+ for (const key of Object.keys(require.cache)) {
201
+ if (key.startsWith(dir)) delete require.cache[key];
202
+ }
203
+ fs.rmSync(dir, { recursive: true, force: true });
204
+ }
205
+ }
206
+
207
+ checkCase('load parity: the packaged evaluator loads its adjacent packaged capture module, never a canonical fallback', () => {
208
+ requirePresent([PACKAGED_EVAL, PACKAGED_CAPTURE], 'packaged capture module');
209
+ const { mod, loaded, captureCopy } = loadEvaluatorCopy(PACKAGED_EVAL, PACKAGED_CAPTURE);
210
+ assert.strictEqual(typeof mod.checkDocObligations, 'function', 'public export is unchanged');
211
+ assert.ok(loaded.includes(captureCopy), `the adjacent capture module loaded (${loaded.length} copy-local modules)`);
212
+ assert.ok(!loaded.includes(fs.realpathSync(CANONICAL_CAPTURE)), 'no canonical capture module was loaded');
213
+ });
214
+
215
+ checkCase('load parity: the canonical evaluator loads its adjacent canonical capture module', () => {
216
+ requirePresent([CANONICAL_EVAL, CANONICAL_CAPTURE], 'canonical capture module');
217
+ const mod = require(CANONICAL_EVAL);
218
+ assert.strictEqual(typeof mod.checkDocObligations, 'function', 'public export is unchanged');
219
+ assert.ok(Object.keys(require.cache).includes(CANONICAL_CAPTURE), 'the canonical capture module loaded');
220
+ assert.ok(!Object.keys(require.cache).includes(PACKAGED_CAPTURE), 'no packaged capture module was loaded');
221
+ });
222
+
223
+ checkCase('module contract: the capture module exports only createInvocationCapture', () => {
224
+ requirePresent([CANONICAL_CAPTURE], 'capture module');
225
+ const exported = require(CANONICAL_CAPTURE);
226
+ assert.deepStrictEqual(Object.keys(exported), ['createInvocationCapture']);
227
+ assert.strictEqual(typeof exported.createInvocationCapture, 'function');
228
+ });
229
+
230
+ // Detachment proof (plan §3, B04). Each contract is an assertion with its own event id, so the mutation runner
231
+ // credits a kill only to the contract the mutant breaks. Every contract runs over both distributions' modules.
232
+ const EVENT_FILE = process.env.HARNESS_ASSERTION_EVENT_FILE;
233
+ const SELECTED_ASSERTION = process.env.HARNESS_ASSERTION_ID;
234
+ const DETACHMENT_CASE = 'CAPTURE-B04';
235
+ const detachmentFailures = [];
236
+
237
+ function emit(event) {
238
+ if (!EVENT_FILE) return;
239
+ fs.appendFileSync(EVENT_FILE, `${JSON.stringify({ schemaVersion: 1, ...event })}\n`, 'utf8');
240
+ }
241
+
242
+ function assertion(contract, run) {
243
+ const assertionId = `${DETACHMENT_CASE}.${contract}`;
244
+ if (SELECTED_ASSERTION && SELECTED_ASSERTION !== assertionId) return;
245
+ try {
246
+ run();
247
+ } catch (error) {
248
+ // Only an assertion-library comparison is an assertion failure; anything else is infrastructure and propagates.
249
+ if (!(error instanceof assert.AssertionError)) throw error;
250
+ detachmentFailures.push(assertionId);
251
+ emit({ event: 'assertion-failure', caseId: DETACHMENT_CASE, assertionId, errorCode: 'ERR_ASSERTION', message: String(error.message).split('\n')[0] });
252
+ console.error(`FAIL ${assertionId}: ${String(error.message).split('\n')[0]}`);
253
+ return;
254
+ }
255
+ emit({ event: 'assertion-pass', caseId: DETACHMENT_CASE, assertionId });
256
+ console.log(`PASS ${assertionId}`);
257
+ }
258
+
259
+ // A caller input for one obligation: one deleted entry with a trusted baseline digest and a candidate artifact.
260
+ function detachmentInput() {
261
+ return {
262
+ entryOld: { path: 'docs/adr/OLD.md', reason: 'deleted' },
263
+ baselineArtifacts: { 'docs/adr/OLD.md': 'e'.repeat(64) },
264
+ artifacts: { 'docs/adr/NEW.md': { digest: '3'.repeat(64) } }
265
+ };
266
+ }
267
+
268
+ // Every object reachable from the caller input. A returned view that holds one of these leaks caller state.
269
+ function inputObjects(input) {
270
+ const seen = new Set();
271
+ const walk = value => {
272
+ if (value === null || typeof value !== 'object' || seen.has(value)) return;
273
+ seen.add(value);
274
+ for (const child of Object.values(value)) walk(child);
275
+ };
276
+ walk(input);
277
+ return seen;
278
+ }
279
+
280
+ // Runs the capture schedule for one obligation and returns the views the evaluator would receive.
281
+ function captureViews(createInvocationCapture, input) {
282
+ const baseline = { digest: 'b'.repeat(64), artifacts: input.baselineArtifacts };
283
+ const capture = createInvocationCapture({ artifacts: input.artifacts, baseline });
284
+ capture.initializeArtifactFallback();
285
+ capture.captureBaselineIdentity();
286
+ const obligation = capture.beginObligation('applicable');
287
+ obligation.prepareBaselineContainer();
288
+ const entry = obligation.captureEntry(input.entryOld);
289
+ const absence = obligation.captureSourceAbsence(entry.token, input.entryOld.path);
290
+ const baselineRead = obligation.captureBaselinePath(input.entryOld.path);
291
+ obligation.acceptBaselineEntry({
292
+ entryToken: entry.token,
293
+ path: input.entryOld.path,
294
+ reason: input.entryOld.reason,
295
+ trustedBaselineDigest: baselineRead.value
296
+ });
297
+ return {
298
+ obligation,
299
+ entry,
300
+ absence,
301
+ baselineRead,
302
+ entries: obligation.baselineEntries(),
303
+ pairs: obligation.baselineAuditPairs()
304
+ };
305
+ }
306
+
307
+ function eachModule(run) {
308
+ for (const [name, file] of [['canonical', CANONICAL_CAPTURE], ['packaged', PACKAGED_CAPTURE]]) {
309
+ run(name, require(file));
310
+ }
311
+ }
312
+
313
+ function viewsOf(views) {
314
+ return [views.entry, views.absence, views.baselineRead, ...views.entries, ...views.pairs];
315
+ }
316
+
317
+ assertion('detachment.no-input-references', () => eachModule((name, mod) => {
318
+ const input = detachmentInput();
319
+ const views = captureViews(mod.createInvocationCapture, input);
320
+ const inputs = inputObjects(input);
321
+ const recorder = { idOf: value => (inputs.has(value) ? 'caller-input' : undefined) };
322
+ const violations = viewsOf(views).flatMap(view => ownedViewViolations(view, recorder));
323
+ assert.deepStrictEqual(violations, [], `${name}: a returned view is not owned or references caller input`);
324
+ }));
325
+
326
+ assertion('detachment.views-fresh-copies', () => eachModule((name, mod) => {
327
+ const { obligation } = captureViews(mod.createInvocationCapture, detachmentInput());
328
+ const first = obligation.baselineEntries();
329
+ const second = obligation.baselineEntries();
330
+ assert.strictEqual(first.length, 1, `${name}: the accepted entry is returned`);
331
+ assert.notStrictEqual(first, second, `${name}: each call returns a new array`);
332
+ assert.notStrictEqual(first[0], second[0], `${name}: each call returns fresh copies, not one shared internal view`);
333
+ }));
334
+
335
+ assertion('detachment.views-unchanged-after-mutation', () => eachModule((name, mod) => {
336
+ const input = detachmentInput();
337
+ const views = captureViews(mod.createInvocationCapture, input);
338
+ const before = JSON.stringify(viewsOf(views));
339
+ input.baselineArtifacts['docs/adr/OLD.md'] = 'f'.repeat(64);
340
+ input.entryOld.path = 'docs/adr/OTHER.md';
341
+ input.artifacts['docs/adr/NEW.md'].digest = '4'.repeat(64);
342
+ assert.strictEqual(JSON.stringify(viewsOf(views)), before, `${name}: a returned view changed after the caller mutated its input`);
343
+ }));
344
+
345
+ assertion('detachment.fresh-invocation-sees-new-data', () => eachModule((name, mod) => {
346
+ const input = detachmentInput();
347
+ captureViews(mod.createInvocationCapture, input);
348
+ input.baselineArtifacts['docs/adr/OLD.md'] = 'f'.repeat(64);
349
+ const fresh = captureViews(mod.createInvocationCapture, input);
350
+ assert.strictEqual(fresh.baselineRead.value, 'f'.repeat(64), `${name}: a fresh invocation reads the mutated baseline value`);
351
+ }));
352
+
353
+ console.log(`\n[341 capture-module] ${passed} passed, ${skipped} skipped, ${failed + detachmentFailures.length} failed`);
354
+ if (skipped > 0 && !REQUIRED) {
355
+ console.log('Skipped checks are not evidence. They run once B1 lands the module and its packaged copy.');
356
+ }
357
+ process.exitCode = failed || detachmentFailures.length ? 1 : 0;