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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +10 -0
- package/ci/fixtures/doc-obligations/read-boundary/boundary-controls.json +211 -0
- package/ci/fixtures/doc-obligations/read-boundary/boundary-policy.json +325 -0
- package/ci/fixtures/doc-obligations/read-boundary/cases.json +14058 -0
- package/ci/fixtures/doc-obligations/read-boundary/expected.json +1 -0
- package/ci/fixtures/doc-obligations/read-boundary/identity-cases.json +119 -0
- package/ci/fixtures/doc-obligations/read-boundary/identity-expected.json +1 -0
- package/ci/fixtures/doc-obligations/read-boundary/scope-cases.json +208 -0
- package/ci/fixtures/doc-obligations/read-boundary/scope-expected.json +1 -0
- package/ci/fixtures/doc-obligations/reconciliation-prep/cases.json +2751 -0
- package/ci/fixtures/doc-obligations/reconciliation-prep/flood-entrypoint.js +5 -0
- package/ci/fixtures/doc-obligations/reconciliation-prep/hang-entrypoint.js +5 -0
- package/ci/fixtures/doc-obligations/reconciliation-prep/pending-red.json +648 -0
- package/ci/fixtures/doc-obligations/reconciliation-prep/provider-contract.json +100 -0
- package/ci/fixtures/doc-obligations/reconciliation-prep/trust-contract.json +188 -0
- package/ci/lib/doc-evidence-boundary-check.js +1240 -0
- package/ci/lib/doc-evidence-fixtures.js +459 -0
- package/ci/lib/doc-evidence-trace.js +171 -0
- package/ci/lib/doc-reconciliation-prep-fixtures.js +502 -0
- package/ci/lib/snapshot-stdin.js +27 -0
- package/ci/mechanism-325-3b-preparation.test.js +382 -0
- package/ci/mechanism-341-boundary-enforcement.test.js +660 -0
- package/ci/mechanism-341-capture-module.test.js +357 -0
- package/ci/mechanism-341-mutation-runner.test.js +413 -0
- package/ci/mechanism-341-read-boundary.test.js +388 -0
- package/ci/mechanism-341-snapshot-stdin.test.js +40 -0
- package/ci/mutation-check.js +805 -45
- package/ci/mutation-loader.js +544 -57
- package/ci/prep/doc-reconciliation-red.js +407 -0
- package/ci/stress-341-boundary-control.js +85 -0
- package/docs/doc-evidence-baseline-binding.md +105 -19
- package/docs/doc-evidence-boundary.md +135 -0
- package/docs/doc-workflow-reconciliation.md +224 -0
- package/docs/document-lifecycle.md +8 -1
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/doc-evidence-capture.js +331 -0
- package/harness-everything/scripts/doc-evidence.js +72 -223
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +5 -3
- 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-capture.js +331 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-evidence.js +72 -223
- package/sbom.cdx.json +43 -7
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
function isPlainObject(value) {
|
|
6
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// #335 P3: the candidate inventory provenance check is deliberately stricter than the shared
|
|
10
|
+
// `isPlainObject` helper. Baseline-only evidence requires a real plain object or a
|
|
11
|
+
// null-prototype dictionary; object-like values (Map, Date, RegExp, class instances) must not
|
|
12
|
+
// masquerade as an empty inventory. The shared normalizer and `artifactMap` fallback remain
|
|
13
|
+
// unchanged for non-baseline callers.
|
|
14
|
+
// A Proxy (or host object) whose `getPrototypeOf` trap throws cannot be proven to be a plain
|
|
15
|
+
// object, so it is classified as unknown inventory rather than aborting the whole check.
|
|
16
|
+
function isCandidateInventory(value) {
|
|
17
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
18
|
+
let prototype;
|
|
19
|
+
try {
|
|
20
|
+
prototype = Object.getPrototypeOf(value);
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return prototype === Object.prototype || prototype === null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function nonEmptyString(value) {
|
|
28
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// #335 P2: per-path baseline and rename evidence must be carried explicitly. Inherited
|
|
32
|
+
// properties (including prototype-injected digests) are not declared evidence, so every
|
|
33
|
+
// trust-boundary map lookup uses own-key membership rather than bracket lookup alone.
|
|
34
|
+
function hasOwnEntry(map, key) {
|
|
35
|
+
return Boolean(map) && typeof map === 'object' && typeof key === 'string'
|
|
36
|
+
&& Object.prototype.hasOwnProperty.call(map, key);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function artifact(entry) {
|
|
40
|
+
return entry && typeof entry.digest === 'string' && entry.digest.length > 0 ? entry.digest : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// #335 P2: a baseline-bound rename destination must declare its own digest. An inherited
|
|
44
|
+
// `digest` (for example Object.create({ digest })) is not explicit candidate evidence.
|
|
45
|
+
// P2(r4321077631): reads the own digest exactly once. `artifact()` inspects `entry.digest` up
|
|
46
|
+
// to three times (typeof, length, value), so a stateful accessor could validate one value and
|
|
47
|
+
// have the captured destination snapshot carry another. Only the applicable rename-destination
|
|
48
|
+
// snapshot uses this helper; non-baseline and inapplicable lanes keep the legacy `artifact()`
|
|
49
|
+
// reads for their audit identity. A throwing digest getter propagates to the caller's guarded
|
|
50
|
+
// block and fails the inspection closed there. This is the single authoritative own-digest
|
|
51
|
+
// helper; nothing else in this module reads a destination's own digest.
|
|
52
|
+
function ownDigestOnce(entry) {
|
|
53
|
+
if (!isPlainObject(entry)) return '';
|
|
54
|
+
if (!hasOwnEntry(entry, 'digest')) return '';
|
|
55
|
+
const digest = entry.digest;
|
|
56
|
+
return typeof digest === 'string' && digest.length > 0 ? digest : '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// F2 (§11): the candidate PRD-set identity is the digest over the sorted [path, digest] pairs
|
|
60
|
+
// of the artifacts under docs/prd/. The envelope must carry this exact digest to attest that
|
|
61
|
+
// the inventory describes the candidate being checked.
|
|
62
|
+
function prdSetDigest(artifactMap) {
|
|
63
|
+
const pairs = Object.keys(artifactMap)
|
|
64
|
+
.filter(path => path.startsWith('docs/prd/'))
|
|
65
|
+
.sort()
|
|
66
|
+
.map(path => [path, artifact(artifactMap[path])]);
|
|
67
|
+
return crypto.createHash('sha256').update(JSON.stringify(pairs)).digest('hex');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createInvocationCapture({ artifacts, baseline }) {
|
|
71
|
+
let artifactMap;
|
|
72
|
+
let baselineIdentityCaptured = false;
|
|
73
|
+
let baselineDigest = '';
|
|
74
|
+
let candidateInventoryInitialized = false;
|
|
75
|
+
let candidateInventory = false;
|
|
76
|
+
|
|
77
|
+
return Object.freeze({
|
|
78
|
+
// This is the original E:147 plain-object fallback. The caller retains no raw map reference;
|
|
79
|
+
// all legacy candidate operations below stay inside this closure.
|
|
80
|
+
initializeArtifactFallback() {
|
|
81
|
+
artifactMap = isPlainObject(artifacts) ? artifacts : {};
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// R1: preserve the original E:155–168 guarded baseline.digest read and cache its scalar.
|
|
85
|
+
captureBaselineIdentity() {
|
|
86
|
+
if (!baselineIdentityCaptured) {
|
|
87
|
+
baselineIdentityCaptured = true;
|
|
88
|
+
if (isPlainObject(baseline)) {
|
|
89
|
+
try {
|
|
90
|
+
const declaredBaselineDigest = baseline.digest;
|
|
91
|
+
if (nonEmptyString(declaredBaselineDigest)) baselineDigest = declaredBaselineDigest;
|
|
92
|
+
} catch {
|
|
93
|
+
baselineDigest = '';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return baselineDigest;
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// R2: remain lazy at E:456–458 and cache both true and false exactly once per invocation.
|
|
101
|
+
candidateInventoryKnown() {
|
|
102
|
+
if (!candidateInventoryInitialized) {
|
|
103
|
+
candidateInventory = isCandidateInventory(artifacts);
|
|
104
|
+
candidateInventoryInitialized = true;
|
|
105
|
+
}
|
|
106
|
+
return candidateInventory;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
beginObligation(applicability) {
|
|
110
|
+
let baselineArtifactsMap = null;
|
|
111
|
+
const entryFacts = new Map();
|
|
112
|
+
const baselinePathReads = new Map();
|
|
113
|
+
const acceptedBaselineEntries = new Map();
|
|
114
|
+
const validatedBaselineDigests = new Map();
|
|
115
|
+
const validatedDestinationDigestMap = new Map();
|
|
116
|
+
const failedDestinationDigests = new Map();
|
|
117
|
+
const candidateDigestCache = new Map();
|
|
118
|
+
|
|
119
|
+
const candidateDigestForAuditOrStale = (path) => {
|
|
120
|
+
if (failedDestinationDigests.has(path)) return '';
|
|
121
|
+
if (candidateDigestCache.has(path)) return candidateDigestCache.get(path);
|
|
122
|
+
if (applicability === 'applicable' && acceptedBaselineEntries.has(path)) return '';
|
|
123
|
+
try {
|
|
124
|
+
return artifact(artifactMap[path]);
|
|
125
|
+
} catch {
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const obligationCapture = {
|
|
131
|
+
prepareBaselineContainer() {
|
|
132
|
+
baselineArtifactsMap = isPlainObject(baseline) && isPlainObject(baseline.artifacts)
|
|
133
|
+
? baseline.artifacts : null;
|
|
134
|
+
return Boolean(baselineArtifactsMap);
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
captureEntry(rawEntry) {
|
|
138
|
+
const token = Object.freeze(Object.create(null));
|
|
139
|
+
if (!isPlainObject(rawEntry)) {
|
|
140
|
+
entryFacts.set(token, { kind: 'not-object' });
|
|
141
|
+
return Object.freeze({ kind: 'not-object', token });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let snapshot = null;
|
|
145
|
+
try {
|
|
146
|
+
snapshot = {
|
|
147
|
+
path: rawEntry.path,
|
|
148
|
+
reason: rawEntry.reason,
|
|
149
|
+
hasOwnRenameTo: Object.prototype.hasOwnProperty.call(rawEntry, 'renameTo'),
|
|
150
|
+
renameTo: rawEntry.renameTo
|
|
151
|
+
};
|
|
152
|
+
} catch {
|
|
153
|
+
snapshot = null;
|
|
154
|
+
}
|
|
155
|
+
if (!snapshot) {
|
|
156
|
+
entryFacts.set(token, { kind: 'inspection-failed' });
|
|
157
|
+
return Object.freeze({ kind: 'inspection-failed', token });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
entryFacts.set(token, {
|
|
161
|
+
kind: 'captured',
|
|
162
|
+
rawReason: snapshot.reason,
|
|
163
|
+
rawRenameTo: snapshot.renameTo,
|
|
164
|
+
sourceAbsence: undefined
|
|
165
|
+
});
|
|
166
|
+
return Object.freeze({
|
|
167
|
+
kind: 'captured',
|
|
168
|
+
token,
|
|
169
|
+
path: typeof snapshot.path === 'string' ? snapshot.path : undefined,
|
|
170
|
+
reason: typeof snapshot.reason === 'string' ? snapshot.reason : undefined,
|
|
171
|
+
hasOwnRenameTo: snapshot.hasOwnRenameTo,
|
|
172
|
+
renameTo: typeof snapshot.renameTo === 'string' ? snapshot.renameTo : undefined
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
formatInvalidReason(entryToken) {
|
|
177
|
+
return JSON.stringify(entryFacts.get(entryToken).rawReason);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
formatInvalidRenameTo(entryToken) {
|
|
181
|
+
return JSON.stringify(entryFacts.get(entryToken).rawRenameTo);
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
captureSourceAbsence(entryToken, path) {
|
|
185
|
+
const present = hasOwnEntry(artifactMap, path);
|
|
186
|
+
const result = Object.freeze({ kind: present ? 'present' : 'absent' });
|
|
187
|
+
const entry = entryFacts.get(entryToken);
|
|
188
|
+
if (entry && entry.kind === 'captured') entry.sourceAbsence = result;
|
|
189
|
+
return result;
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
isDestinationPoisoned(path) {
|
|
193
|
+
return failedDestinationDigests.has(path);
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
poisonDestination(path) {
|
|
197
|
+
failedDestinationDigests.set(path, '');
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
captureDestination(entryToken, path) {
|
|
201
|
+
void entryToken;
|
|
202
|
+
let digest = '';
|
|
203
|
+
try {
|
|
204
|
+
if (hasOwnEntry(artifactMap, path)) digest = ownDigestOnce(artifactMap[path]);
|
|
205
|
+
} catch {
|
|
206
|
+
return Object.freeze({ kind: 'inspection-failed' });
|
|
207
|
+
}
|
|
208
|
+
if (!nonEmptyString(digest)) return Object.freeze({ kind: 'unusable' });
|
|
209
|
+
|
|
210
|
+
const sharedDigest = validatedDestinationDigestMap.get(path);
|
|
211
|
+
if (sharedDigest === undefined) {
|
|
212
|
+
validatedDestinationDigestMap.set(path, digest);
|
|
213
|
+
} else if (sharedDigest !== digest) {
|
|
214
|
+
return Object.freeze({ kind: 'conflict', digest });
|
|
215
|
+
}
|
|
216
|
+
return Object.freeze({ kind: 'accepted', digest });
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
captureBaselinePath(path) {
|
|
220
|
+
if (baselinePathReads.has(path)) return Object.freeze({ ...baselinePathReads.get(path) });
|
|
221
|
+
let result;
|
|
222
|
+
try {
|
|
223
|
+
if (!hasOwnEntry(baselineArtifactsMap, path)) {
|
|
224
|
+
result = Object.freeze({ kind: 'absent' });
|
|
225
|
+
} else {
|
|
226
|
+
const value = baselineArtifactsMap[path];
|
|
227
|
+
result = value === undefined
|
|
228
|
+
? Object.freeze({ kind: 'present', value: undefined })
|
|
229
|
+
: typeof value === 'string'
|
|
230
|
+
? Object.freeze({ kind: 'present', value })
|
|
231
|
+
: Object.freeze({ kind: 'present', invalidValue: true });
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
result = Object.freeze({ kind: 'unknown', reason: 'inspection' });
|
|
235
|
+
}
|
|
236
|
+
baselinePathReads.set(path, result);
|
|
237
|
+
return Object.freeze({ ...result });
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
acceptBaselineEntry(detachedEntry) {
|
|
241
|
+
const entryFact = entryFacts.get(detachedEntry.entryToken);
|
|
242
|
+
const sourceAbsence = entryFact && entryFact.sourceAbsence;
|
|
243
|
+
const trustedBaselineDigest = typeof detachedEntry.trustedBaselineDigest === 'string'
|
|
244
|
+
? detachedEntry.trustedBaselineDigest : undefined;
|
|
245
|
+
const acceptedEntry = Object.freeze({
|
|
246
|
+
path: detachedEntry.path,
|
|
247
|
+
reason: detachedEntry.reason,
|
|
248
|
+
candidatePathPresent: !sourceAbsence || sourceAbsence.kind !== 'absent',
|
|
249
|
+
trustedBaselineDigest,
|
|
250
|
+
...(detachedEntry.reason === 'rename-from' ? {
|
|
251
|
+
renameTo: detachedEntry.renameTo,
|
|
252
|
+
renameDestinationDigest: detachedEntry.renameDestinationDigest
|
|
253
|
+
} : {})
|
|
254
|
+
});
|
|
255
|
+
acceptedBaselineEntries.set(acceptedEntry.path, acceptedEntry);
|
|
256
|
+
if (trustedBaselineDigest !== undefined) {
|
|
257
|
+
validatedBaselineDigests.set(acceptedEntry.path, acceptedEntry.trustedBaselineDigest);
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
captureOrdinaryCandidates(detachedScopePaths) {
|
|
262
|
+
for (const entry of acceptedBaselineEntries.values()) {
|
|
263
|
+
if (entry.reason === 'rename-from' && nonEmptyString(entry.renameDestinationDigest)
|
|
264
|
+
&& !candidateDigestCache.has(entry.renameTo)) {
|
|
265
|
+
candidateDigestCache.set(entry.renameTo, entry.renameDestinationDigest);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
let inspectionFailed = false;
|
|
269
|
+
for (const path of detachedScopePaths) {
|
|
270
|
+
if (acceptedBaselineEntries.has(path) || candidateDigestCache.has(path)
|
|
271
|
+
|| failedDestinationDigests.has(path)) continue;
|
|
272
|
+
try {
|
|
273
|
+
candidateDigestCache.set(path, artifact(artifactMap[path]));
|
|
274
|
+
} catch {
|
|
275
|
+
inspectionFailed = true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return inspectionFailed;
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
capturedCandidate(path) {
|
|
282
|
+
if (!candidateDigestCache.has(path)) return Object.freeze({ kind: 'uncaptured' });
|
|
283
|
+
const digest = candidateDigestCache.get(path);
|
|
284
|
+
return typeof digest === 'string'
|
|
285
|
+
? Object.freeze({ kind: 'captured', digest })
|
|
286
|
+
: Object.freeze({ kind: 'unusable' });
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
candidateAuditValue(path) {
|
|
290
|
+
return `${candidateDigestForAuditOrStale(path) || 'missing'}`;
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
compatibilityCandidateBound(record, path, baselineDigestValue) {
|
|
294
|
+
if (!nonEmptyString(record.baseDigest) || record.baseDigest !== baselineDigestValue) return false;
|
|
295
|
+
if (!isPlainObject(record.artifactDigests)) return false;
|
|
296
|
+
if (candidateDigestCache.has(path)) {
|
|
297
|
+
const digest = candidateDigestCache.get(path);
|
|
298
|
+
return digest.length > 0
|
|
299
|
+
&& record.artifactDigests[path] === digest
|
|
300
|
+
&& Array.isArray(record.paths) && record.paths.includes(path);
|
|
301
|
+
}
|
|
302
|
+
return artifact(artifactMap[path]).length > 0
|
|
303
|
+
&& record.artifactDigests[path] === artifactMap[path].digest
|
|
304
|
+
&& Array.isArray(record.paths) && record.paths.includes(path);
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
compatibilityStaleArtifactMismatch(record, path) {
|
|
308
|
+
return candidateDigestForAuditOrStale(path).length > 0
|
|
309
|
+
&& nonEmptyString(record.artifactDigests[path])
|
|
310
|
+
&& record.artifactDigests[path] !== candidateDigestForAuditOrStale(path);
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
baselineEntries() {
|
|
314
|
+
return [...acceptedBaselineEntries.values()].map(entry => Object.freeze({ ...entry }));
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
baselineAuditPairs() {
|
|
318
|
+
return [...validatedBaselineDigests].map(([path, digest]) => Object.freeze([path, digest]));
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
return Object.freeze(obligationCapture);
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
compatibilityPrdSetDigest() {
|
|
326
|
+
return prdSetDigest(artifactMap);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
module.exports = { createInvocationCapture };
|