harness-everything 0.38.0 → 0.40.0
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/AGENTS.md +79 -63
- package/CHANGELOG.md +32 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/README.md +76 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/a-reviews-b.md +191 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/b-reviews-a.md +100 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/baseline-probes.log +47 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/bom-red.log +85 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/bom-transition.txt +25 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/coordinator-probes.txt +84 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/cross-suite.txt +18 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-bom.log +3 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-consistency.log.gz +0 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-core.log +198 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-probes.log +27 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-producer.log +83 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-review.md +89 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/final-transitions.log +5 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/implementation-a.md +166 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/implementation-b.md +264 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/logs.sha256 +15 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-a-probe.log +62 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-a-probe.txt +313 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-b-probe.log +51 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/peer-b-probe.txt +94 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/semantic-transitions.txt +25 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/source.sha256 +9 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-a-on-b-core.log +201 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-a-on-b-producer.log +82 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-b-on-a-core.log +197 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/tests-b-on-a-producer.log +82 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/verify-all.log.gz +0 -0
- package/benchmarks/results/reviews/pr353-r6-2026-10-22/verify-gate-summary.json +22 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2a/harness.js +489 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2a/vectors.json +617 -0
- package/ci/mechanism-325-3b-s2a-core.test.js +840 -0
- package/ci/mechanism-325-3b-s2a-producer.test.js +564 -0
- package/code-review/SKILL.md +3 -3
- package/code-review/references/review-playbook.md +4 -0
- package/code-review/templates/finding.template.md +6 -0
- package/docs/doc-workflow-reconciliation.md +357 -0
- package/evals/zoom-out/tasks/negative-first-edge-case-review.yaml +16 -0
- package/evals/zoom-out/tasks/positive-repair-loop.yaml +15 -0
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/project-docs-resolver.js +42 -8
- package/harness-everything/scripts/routing-keywords.json +4 -0
- package/hooks/scripts/lib/doc-reconciliation-core.js +280 -0
- package/hooks/scripts/lib/doc-reconciliation-events.js +141 -0
- package/hooks/scripts/lib/doc-reconciliation-producer.js +206 -0
- package/multi-agent-workspace/SKILL.md +1 -1
- package/multi-agent-workspace/scripts/project-docs-resolver.js +42 -8
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +2 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-core.js +280 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-events.js +141 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-producer.js +206 -0
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/code-review/SKILL.md +3 -3
- package/plugins/harness-everything/skills/code-review/references/review-playbook.md +4 -0
- package/plugins/harness-everything/skills/code-review/templates/finding.template.md +6 -0
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/project-docs-resolver.js +42 -8
- package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +4 -0
- package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +1 -1
- package/plugins/harness-everything/skills/multi-agent-workspace/scripts/project-docs-resolver.js +42 -8
- package/plugins/harness-everything/skills/to-spec/SKILL.md +1 -1
- package/plugins/harness-everything/skills/to-spec/scripts/project-docs-resolver.js +42 -8
- package/plugins/harness-everything/skills/zoom-out/SKILL.md +4 -3
- package/plugins/harness-everything/skills/zoom-out/references/circuit-breaker.md +7 -0
- package/plugins/harness-everything/skills/zoom-out/templates/zoom-out-report.template.md +4 -2
- package/sbom.cdx.json +7 -7
- package/scripts/errors-sync.js +90 -0
- package/to-spec/SKILL.md +1 -1
- package/to-spec/scripts/project-docs-resolver.js +42 -8
- package/zoom-out/SKILL.md +4 -3
- package/zoom-out/references/circuit-breaker.md +7 -0
- package/zoom-out/templates/zoom-out-report.template.md +4 -2
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# worktree /Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-b
|
|
2
|
+
PASS p1-control-eligible — expected eligible, got eligible
|
|
3
|
+
PASS p1-joint-origin-task-substitution — expected rejected, got rejected (decision unresolved, codes identity-mismatch / cached unresolved)
|
|
4
|
+
DIFF p1-plan-only-substitution — expected rejected, got accepted (decision eligible, codes )
|
|
5
|
+
DIFF p1-config-bytes-substitution — expected rejected, got accepted (decision eligible, codes )
|
|
6
|
+
PASS p2-asset-irrelevant — expected ready, got ready (ready)
|
|
7
|
+
PASS p2-governed-path-unchanged — expected unavailable, got unavailable (inventory-unsupported)
|
|
8
|
+
PASS p2-role-frontmatter-asset — expected unavailable, got unavailable (inventory-unsupported)
|
|
9
|
+
DIFF p2-masked-frontmatter-asset — expected unavailable, got ready (ready)
|
|
10
|
+
# summary: 5 match, 3 diverge of 8
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# worktree /Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-a
|
|
14
|
+
PASS p1-control-eligible — expected eligible, got eligible
|
|
15
|
+
PASS p1-joint-origin-task-substitution — expected rejected, got rejected (decision unresolved, codes identity-mismatch / cached unresolved)
|
|
16
|
+
PASS p1-plan-only-substitution — expected rejected, got rejected (decision unresolved, codes identity-mismatch)
|
|
17
|
+
PASS p1-config-bytes-substitution — expected rejected, got rejected (decision unresolved, codes config-capture-mismatch)
|
|
18
|
+
PASS p2-asset-irrelevant — expected ready, got ready (ready)
|
|
19
|
+
PASS p2-governed-path-unchanged — expected unavailable, got unavailable (inventory-unsupported)
|
|
20
|
+
PASS p2-role-frontmatter-asset — expected unavailable, got unavailable (inventory-unsupported)
|
|
21
|
+
PASS p2-masked-frontmatter-asset — expected unavailable, got unavailable (inventory-unsupported)
|
|
22
|
+
# summary: 8 match, 0 diverge of 8
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# worktree /Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-a
|
|
26
|
+
PASS p2-asset-irrelevant — expected ready, got ready (ready)
|
|
27
|
+
PASS p2-governed-path-unchanged — expected unavailable, got unavailable (inventory-unsupported)
|
|
28
|
+
PASS p2-role-frontmatter-asset — expected unavailable, got unavailable (inventory-unsupported)
|
|
29
|
+
PASS p2-masked-frontmatter-asset — expected unavailable, got unavailable (inventory-unsupported)
|
|
30
|
+
PASS p2-new-binary-governed-record — expected unavailable, got unavailable (inventory-unsupported)
|
|
31
|
+
PASS p2-deleted-binary-governed-record — expected unavailable, got unavailable (inventory-unsupported)
|
|
32
|
+
PASS p2-binary-bytes-covered-by-both-digests — expected differ, got differ
|
|
33
|
+
PASS p2-producer-core-parity — expected no-errors, got no-errors
|
|
34
|
+
PASS p2-malformed-record-rejected — expected invalid-input, got invalid-input
|
|
35
|
+
# summary: 9 match, 0 diverge of 9
|
|
36
|
+
# worktree /Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-b
|
|
37
|
+
PASS p2-asset-irrelevant — expected ready, got ready (ready)
|
|
38
|
+
PASS p2-governed-path-unchanged — expected unavailable, got unavailable (inventory-unsupported)
|
|
39
|
+
PASS p2-role-frontmatter-asset — expected unavailable, got unavailable (inventory-unsupported)
|
|
40
|
+
DIFF p2-masked-frontmatter-asset — expected unavailable, got ready (ready)
|
|
41
|
+
PASS p2-new-binary-governed-record — expected unavailable, got unavailable (inventory-unsupported)
|
|
42
|
+
PASS p2-deleted-binary-governed-record — expected unavailable, got unavailable (inventory-unsupported)
|
|
43
|
+
PASS p2-binary-bytes-covered-by-both-digests — expected differ, got differ
|
|
44
|
+
PASS p2-producer-core-parity — expected no-errors, got no-errors
|
|
45
|
+
PASS p2-malformed-record-rejected — expected invalid-input, got invalid-input
|
|
46
|
+
# summary: 8 match, 1 diverge of 9
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# worktree /Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-a
|
|
50
|
+
PASS p1-control-eligible — expected eligible, got eligible
|
|
51
|
+
PASS p1-joint-origin-task-substitution — expected rejected, got rejected (decision unresolved, codes identity-mismatch / cached unresolved)
|
|
52
|
+
PASS p1-plan-only-substitution — expected rejected, got rejected (decision unresolved, codes identity-mismatch)
|
|
53
|
+
PASS p1-config-bytes-substitution — expected rejected, got rejected (decision unresolved, codes config-capture-mismatch)
|
|
54
|
+
DIFF p1-fully-joint-self-consistent-substitution — expected rejected, got accepted (decision eligible, codes )
|
|
55
|
+
# summary: 4 match, 1 diverge of 5
|
|
56
|
+
# worktree /Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-b
|
|
57
|
+
PASS p1-control-eligible — expected eligible, got eligible
|
|
58
|
+
PASS p1-joint-origin-task-substitution — expected rejected, got rejected (decision unresolved, codes identity-mismatch / cached unresolved)
|
|
59
|
+
DIFF p1-plan-only-substitution — expected rejected, got accepted (decision eligible, codes )
|
|
60
|
+
DIFF p1-config-bytes-substitution — expected rejected, got accepted (decision eligible, codes )
|
|
61
|
+
DIFF p1-fully-joint-self-consistent-substitution — expected rejected, got accepted (decision eligible, codes )
|
|
62
|
+
# summary: 2 match, 3 diverge of 5
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Independent cross-review probe (scratch, outside every repository).
|
|
5
|
+
// Usage: node cross-review-probe.js <worktree-root> [case]
|
|
6
|
+
// Cases: p1 | p2 | all
|
|
7
|
+
// It builds one normative Git repository per case, then exercises the S2a producer/events/core of the
|
|
8
|
+
// worktree under review. It prints a machine-readable verdict per case; it never writes into a repository.
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const crypto = require('crypto');
|
|
14
|
+
const { spawnSync } = require('child_process');
|
|
15
|
+
|
|
16
|
+
const ROOT = path.resolve(process.argv[2] || '.');
|
|
17
|
+
const ONLY = process.argv[3] || 'all';
|
|
18
|
+
const TMP = process.env.PROBE_TMPDIR || os.tmpdir();
|
|
19
|
+
const DERIVE_CONFIG = JSON.parse(fs.readFileSync(path.join(ROOT, 'ci/fixtures/doc-obligations/derive/config.json'), 'utf8'));
|
|
20
|
+
const FIXTURE = path.join(ROOT, 'ci/fixtures/doc-obligations/derive/missing-approval');
|
|
21
|
+
|
|
22
|
+
const sha256 = value => crypto.createHash('sha256').update(value).digest('hex');
|
|
23
|
+
const clone = value => JSON.parse(JSON.stringify(value));
|
|
24
|
+
const stable = value => JSON.stringify(value, (key, item) =>
|
|
25
|
+
(item && typeof item === 'object' && !Array.isArray(item)
|
|
26
|
+
? Object.fromEntries(Object.keys(item).sort().map(k => [k, item[k]])) : item));
|
|
27
|
+
|
|
28
|
+
function git(cwd, args) {
|
|
29
|
+
const env = { ...process.env };
|
|
30
|
+
for (const key of Object.keys(env)) if (key.startsWith('GIT_')) delete env[key];
|
|
31
|
+
env.GIT_AUTHOR_DATE = '2026-01-01T00:00:00Z';
|
|
32
|
+
env.GIT_COMMITTER_DATE = '2026-01-01T00:00:00Z';
|
|
33
|
+
const out = spawnSync('git', args, { cwd, encoding: 'utf8', env, timeout: 20000 });
|
|
34
|
+
if (out.status !== 0) throw new Error(`git ${args[0]} failed: ${out.stderr}`);
|
|
35
|
+
return out.stdout;
|
|
36
|
+
}
|
|
37
|
+
function writeFiles(root, files) {
|
|
38
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
39
|
+
const target = path.join(root, rel);
|
|
40
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
41
|
+
fs.writeFileSync(target, Buffer.from(content));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function makeRepo(baseFiles, binaryFiles = {}) {
|
|
45
|
+
const root = fs.realpathSync(fs.mkdtempSync(path.join(TMP, 'probe-')));
|
|
46
|
+
git(root, ['init', '-q']);
|
|
47
|
+
writeFiles(root, { '.agents/harness-everything/project-docs.json': JSON.stringify(DERIVE_CONFIG, null, 2) + '\n', ...baseFiles });
|
|
48
|
+
writeFiles(root, binaryFiles);
|
|
49
|
+
git(root, ['add', '-A']);
|
|
50
|
+
git(root, ['-c', 'user.name=probe', '-c', 'user.email=probe@invalid', '-c', 'commit.gpgsign=false', 'commit', '-q', '--no-verify', '-m', 'baseline']);
|
|
51
|
+
return { root, commit: git(root, ['rev-parse', 'HEAD']).trim() };
|
|
52
|
+
}
|
|
53
|
+
function readTree(dir) {
|
|
54
|
+
const files = {};
|
|
55
|
+
const walk = current => {
|
|
56
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
57
|
+
const full = path.join(current, entry.name);
|
|
58
|
+
if (entry.isDirectory()) walk(full);
|
|
59
|
+
else files[path.relative(dir, full).split(path.sep).join('/')] = fs.readFileSync(full);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
if (fs.existsSync(dir)) walk(dir);
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class FakeOrigin {
|
|
67
|
+
constructor(context) { this.context = clone(context); this.refs = new Map(); }
|
|
68
|
+
issueEvent(reference, event) { this.refs.set(reference, clone(event)); }
|
|
69
|
+
resolveContext() { return { status: 'accepted', context: clone(this.context), originRef: 'fake-origin:context' }; }
|
|
70
|
+
resolveEvent({ reference }) {
|
|
71
|
+
if (!this.refs.has(reference)) return { status: 'rejected', diagnostics: [{ code: 'origin-untrusted', message: 'not issued' }] };
|
|
72
|
+
const event = clone(this.refs.get(reference));
|
|
73
|
+
return { status: 'accepted', event, originRef: event.originRef, correlation: { toolCorrelationId: event.toolCorrelationId || null } };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const CONTEXT = repo => ({
|
|
77
|
+
baseline: { root: repo.root, commit: repo.commit },
|
|
78
|
+
workspaceId: 'fake-workspace', sessionId: 'session-s2a-001', workflowId: 'wf-s2a-001', workflowRevision: 2,
|
|
79
|
+
taskId: 'task-s2a-001', principals: { author: 'author-principal', executors: ['executor-principal'] },
|
|
80
|
+
activeHost: { hostId: 'fake-host' }, baselineReference: 'fake-origin:workflow-start-baseline'
|
|
81
|
+
});
|
|
82
|
+
const TASK = {
|
|
83
|
+
taskId: 'task-s2a-001',
|
|
84
|
+
requirements: [
|
|
85
|
+
{ id: 'req-docs', stepType: 'doc-change', summary: 'Update the PRD REQ text', acceptance: 'normative PRD text is reviewed', bindings: [] },
|
|
86
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify the PRD change', acceptance: 'objective doc checks are recorded', bindings: [{ id: 'verification-loop' }] }
|
|
87
|
+
]
|
|
88
|
+
};
|
|
89
|
+
function producerInput(repo) {
|
|
90
|
+
return {
|
|
91
|
+
workspaceRoot: repo.root, baseline: { root: repo.root, commit: repo.commit },
|
|
92
|
+
workflow: { workflowId: 'wf-s2a-001', workflowRevision: 2, sessionId: 'session-s2a-001' },
|
|
93
|
+
task: clone(TASK), scope: { prdRoot: 'docs/prd/' }, activeHost: { hostId: 'fake-host' }, prdRoot: 'docs/prd/'
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function buildEvents(snapshot, repo) {
|
|
97
|
+
const events = {};
|
|
98
|
+
const digests = {};
|
|
99
|
+
for (const obligation of snapshot.obligations || []) {
|
|
100
|
+
for (const rel of (obligation.scope && obligation.scope.paths) || []) {
|
|
101
|
+
const abs = path.join(repo.root, rel);
|
|
102
|
+
digests[rel] = fs.existsSync(abs) ? sha256(fs.readFileSync(abs)) : null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const obligation of snapshot.obligations || []) {
|
|
106
|
+
if (obligation.strength !== 'MUST' || obligation.applicability === 'N/A') continue;
|
|
107
|
+
const capability = obligation.capability;
|
|
108
|
+
const ref = `ref-${capability}`;
|
|
109
|
+
const common = {
|
|
110
|
+
eventId: `evt-${capability}`, producerId: 'fake-producer', identity: clone(snapshot.identity),
|
|
111
|
+
paths: [...(obligation.scope.paths || [])], provenance: 'fake-origin:fixture', capability,
|
|
112
|
+
originRef: `fake-origin:event:${ref}`
|
|
113
|
+
};
|
|
114
|
+
if (capability === 'quality-review') {
|
|
115
|
+
events[ref] = { ...common, kind: 'review', principalId: 'reviewer-principal', verdict: 'approve',
|
|
116
|
+
summary: `fixture review for ${capability}: approve`, reviewedArtifactDigests: clone(digests),
|
|
117
|
+
independentOf: ['author-principal', 'executor-principal'] };
|
|
118
|
+
} else {
|
|
119
|
+
events[ref] = { ...common, kind: 'execution', principalId: 'executor-principal', command: `fixture-check ${capability}`,
|
|
120
|
+
argv: ['fixture-check', capability], cwd: '.', exitCode: 0, signal: null, toolCorrelationId: `tool-${capability}`,
|
|
121
|
+
startIdentity: clone(snapshot.identity), endIdentity: clone(snapshot.identity), resultRef: `result-${capability}`,
|
|
122
|
+
artifactDigests: clone(digests) };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return events;
|
|
126
|
+
}
|
|
127
|
+
function codesOf(...results) {
|
|
128
|
+
const codes = new Set();
|
|
129
|
+
for (const result of results) {
|
|
130
|
+
for (const item of [...(result && result.diagnostics || []), ...(result && result.rejected || []), ...(result && result.unsatisfied || [])]) {
|
|
131
|
+
if (item && typeof item.code === 'string') codes.add(item.code);
|
|
132
|
+
if (item && typeof item.reasonCode === 'string') codes.add(item.reasonCode);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return [...codes].sort();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const CORE = path.join(ROOT, 'hooks/scripts/lib/doc-reconciliation-core.js');
|
|
139
|
+
const PRODUCER = path.join(ROOT, 'hooks/scripts/lib/doc-reconciliation-producer.js');
|
|
140
|
+
const EVENTS = path.join(ROOT, 'hooks/scripts/lib/doc-reconciliation-events.js');
|
|
141
|
+
function load() {
|
|
142
|
+
for (const file of [CORE, PRODUCER, EVENTS]) delete require.cache[require.resolve(file)];
|
|
143
|
+
return { core: require(CORE), producer: require(PRODUCER), events: require(EVENTS) };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const results = [];
|
|
147
|
+
function record(id, expected, actual, detail) {
|
|
148
|
+
results.push({ id, expected, actual, pass: expected === actual, detail });
|
|
149
|
+
console.log(`${expected === actual ? 'PASS' : 'DIFF'} ${id} — expected ${expected}, got ${actual}${detail ? ` (${detail})` : ''}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---- P1 probes -----------------------------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
function flow(mods, repo) {
|
|
155
|
+
const origin = new FakeOrigin(CONTEXT(repo));
|
|
156
|
+
const produced = mods.producer.produceDocSnapshot(producerInput(repo), { originProvider: origin });
|
|
157
|
+
if (produced.state !== 'ready' || !produced.snapshot) return { produced, codes: codesOf(produced) };
|
|
158
|
+
const snapshot = produced.snapshot;
|
|
159
|
+
const events = buildEvents(snapshot, repo);
|
|
160
|
+
for (const [ref, event] of Object.entries(events)) origin.issueEvent(ref, event);
|
|
161
|
+
const accepted = mods.events.acceptDocEvents({ snapshot, eventRefs: Object.keys(events) }, { originProvider: origin });
|
|
162
|
+
const decision = mods.core.checkAcceptedDocEvidence({ snapshot, accepted, mode: 'verification' });
|
|
163
|
+
return { produced, snapshot, events, accepted, decision, origin, codes: codesOf(produced, accepted, decision) };
|
|
164
|
+
}
|
|
165
|
+
function rewriteCached(accepted, snapshot) {
|
|
166
|
+
const out = clone(accepted);
|
|
167
|
+
out.identity = clone(snapshot.identity);
|
|
168
|
+
out.originContext = clone(snapshot.originContext);
|
|
169
|
+
for (const key of Object.keys(out.acceptedIndex || {})) out.acceptedIndex[key].identity = clone(snapshot.identity);
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function p1Probes(mods) {
|
|
174
|
+
const files = { base: readTree(path.join(FIXTURE, 'base')), candidate: readTree(path.join(FIXTURE, 'candidate')) };
|
|
175
|
+
const repo = makeRepo(files.base);
|
|
176
|
+
try {
|
|
177
|
+
writeFiles(repo.root, files.candidate);
|
|
178
|
+
const base = flow(mods, repo);
|
|
179
|
+
if (!base.snapshot) { record('p1-control-eligible', 'eligible', 'no-snapshot', base.codes.join(',')); return; }
|
|
180
|
+
record('p1-control-eligible', 'eligible', base.decision.eligible ? 'eligible' : 'not-eligible', base.codes.join(','));
|
|
181
|
+
|
|
182
|
+
// (1) Joint captured-origin + provider task substitution (the packet's named attack).
|
|
183
|
+
{
|
|
184
|
+
const snapshot = clone(base.snapshot);
|
|
185
|
+
snapshot.originContext.taskId = 'task-s2a-002';
|
|
186
|
+
base.origin.context.taskId = 'task-s2a-002';
|
|
187
|
+
const accepted = mods.events.acceptDocEvents({ snapshot, eventRefs: Object.keys(base.events) }, { originProvider: base.origin });
|
|
188
|
+
const decision = mods.core.checkAcceptedDocEvidence({ snapshot, accepted, mode: 'verification' });
|
|
189
|
+
const cached = clone(base.accepted); cached.originContext = clone(snapshot.originContext);
|
|
190
|
+
const replay = mods.core.checkAcceptedDocEvidence({ snapshot, accepted: cached, mode: 'verification' });
|
|
191
|
+
record('p1-joint-origin-task-substitution', 'rejected', (decision.eligible || replay.eligible) ? 'accepted' : 'rejected',
|
|
192
|
+
`decision ${decision.decision}, codes ${codesOf(accepted, decision).join(',')} / cached ${replay.decision}`);
|
|
193
|
+
}
|
|
194
|
+
// (2) Plan-only substitution: the captured plan names another workflow, the plan digest is recomputed,
|
|
195
|
+
// the identity and the origin context keep the original tuple.
|
|
196
|
+
{
|
|
197
|
+
const snapshot = clone(base.snapshot);
|
|
198
|
+
snapshot.taskSemantics.workflow.workflowId = 'wf-substituted';
|
|
199
|
+
snapshot.identity.taskScopeDigest = mods.core._internals.structuredDigest('doc-task-scope-v1', snapshot.taskSemantics);
|
|
200
|
+
const cached = rewriteCached(base.accepted, snapshot);
|
|
201
|
+
const decision = mods.core.checkAcceptedDocEvidence({ snapshot, accepted: cached, mode: 'verification' });
|
|
202
|
+
record('p1-plan-only-substitution', 'rejected', decision.eligible ? 'accepted' : 'rejected',
|
|
203
|
+
`decision ${decision.decision}, codes ${codesOf(decision).join(',')}`);
|
|
204
|
+
}
|
|
205
|
+
// (3) Config-bytes substitution: the captured config bytes are replaced, every digest that names them is
|
|
206
|
+
// recomputed, and the cached acceptance is restated to match.
|
|
207
|
+
{
|
|
208
|
+
const snapshot = clone(base.snapshot);
|
|
209
|
+
const source = snapshot.config.capture.sources.find(item => item.presence === 'present');
|
|
210
|
+
source.bytes = Buffer.from('{"governed":[]}\n');
|
|
211
|
+
source.digest = sha256(source.bytes);
|
|
212
|
+
snapshot.config.digest = mods.core._internals.configDigest(snapshot.config);
|
|
213
|
+
snapshot.identity.configDigest = snapshot.config.digest;
|
|
214
|
+
snapshot.taskSemantics.configDigest = snapshot.config.digest;
|
|
215
|
+
snapshot.identity.taskScopeDigest = mods.core._internals.structuredDigest('doc-task-scope-v1', snapshot.taskSemantics);
|
|
216
|
+
const cached = rewriteCached(base.accepted, snapshot);
|
|
217
|
+
const decision = mods.core.checkAcceptedDocEvidence({ snapshot, accepted: cached, mode: 'verification' });
|
|
218
|
+
record('p1-config-bytes-substitution', 'rejected', decision.eligible ? 'accepted' : 'rejected',
|
|
219
|
+
`decision ${decision.decision}, codes ${codesOf(decision).join(',')}`);
|
|
220
|
+
}
|
|
221
|
+
// (4) Fully joint, self-consistent substitution: captured origin context, provider context, identity,
|
|
222
|
+
// captured plan and every digest that names them, plus the cached acceptance, all restated together.
|
|
223
|
+
{
|
|
224
|
+
const snapshot = clone(base.snapshot);
|
|
225
|
+
snapshot.originContext.taskId = 'task-s2a-002';
|
|
226
|
+
base.origin.context.taskId = 'task-s2a-002';
|
|
227
|
+
snapshot.identity.taskId = 'task-s2a-002';
|
|
228
|
+
snapshot.taskSemantics.task.taskId = 'task-s2a-002';
|
|
229
|
+
snapshot.identity.taskScopeDigest = mods.core._internals.structuredDigest('doc-task-scope-v1', snapshot.taskSemantics);
|
|
230
|
+
const cached = rewriteCached(base.accepted, snapshot);
|
|
231
|
+
const decision = mods.core.checkAcceptedDocEvidence({ snapshot, accepted: cached, mode: 'verification' });
|
|
232
|
+
record('p1-fully-joint-self-consistent-substitution', 'rejected', decision.eligible ? 'accepted' : 'rejected',
|
|
233
|
+
`decision ${decision.decision}, codes ${codesOf(decision).join(',')}`);
|
|
234
|
+
}
|
|
235
|
+
} finally { fs.rmSync(repo.root, { recursive: true, force: true }); }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ---- P2 probes -----------------------------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
const BINARY = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0a, 0x1a, 0x0a, 0x00, 0xff, 0xfe, 0x80, 0x81]);
|
|
241
|
+
const ROLE_FRONT = Buffer.from('---\nrole: prd\nid: PRD-GRPH-999\nrevision: 1\nstatus: draft\n---\n### REQ-GRPH-999\n\nbinary requirement\n\nAcceptance: evaluate it.\n');
|
|
242
|
+
const MASKED_FRONT = Buffer.concat([Buffer.from('---\nrole: prd\nid: PRD-GRPH-999\n'), Buffer.from([0xff]), Buffer.from('\n---\nbody\n')]);
|
|
243
|
+
|
|
244
|
+
function p2Probes(mods) {
|
|
245
|
+
const files = { base: readTree(path.join(FIXTURE, 'base')), candidate: readTree(path.join(FIXTURE, 'candidate')) };
|
|
246
|
+
const cases = [
|
|
247
|
+
['p2-asset-irrelevant', 'assets/logo.png', BINARY, 'ready'],
|
|
248
|
+
['p2-governed-path-unchanged', 'docs/prd/diagram.png', BINARY, 'unavailable'],
|
|
249
|
+
['p2-role-frontmatter-asset', 'assets/role.dat', Buffer.concat([ROLE_FRONT, BINARY]), 'unavailable'],
|
|
250
|
+
['p2-masked-frontmatter-asset', 'assets/masked.dat', MASKED_FRONT, 'unavailable']
|
|
251
|
+
];
|
|
252
|
+
for (const [id, rel, bytes, expected] of cases) {
|
|
253
|
+
const repo = makeRepo(files.base, { [rel]: bytes });
|
|
254
|
+
try {
|
|
255
|
+
writeFiles(repo.root, { ...files.candidate, [rel]: bytes });
|
|
256
|
+
const origin = new FakeOrigin(CONTEXT(repo));
|
|
257
|
+
const produced = mods.producer.produceDocSnapshot(producerInput(repo), { originProvider: origin });
|
|
258
|
+
const actual = produced.state === 'ready' ? 'ready' : 'unavailable';
|
|
259
|
+
record(id, expected, actual, codesOf(produced).join(',') || produced.state);
|
|
260
|
+
} finally { fs.rmSync(repo.root, { recursive: true, force: true }); }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// New and deleted record identity: a binary record that only exists on one side of the capture.
|
|
264
|
+
for (const [id, rel, mode, expected] of [
|
|
265
|
+
['p2-new-binary-governed-record', 'docs/prd/new.bin', 'new', 'unavailable'],
|
|
266
|
+
['p2-deleted-binary-governed-record', 'docs/prd/old.bin', 'deleted', 'unavailable']
|
|
267
|
+
]) {
|
|
268
|
+
const repo = makeRepo(mode === 'new' ? files.base : { ...files.base, [rel]: BINARY });
|
|
269
|
+
try {
|
|
270
|
+
if (mode === 'new') writeFiles(repo.root, { ...files.candidate, [rel]: BINARY });
|
|
271
|
+
else writeFiles(repo.root, files.candidate);
|
|
272
|
+
const origin = new FakeOrigin(CONTEXT(repo));
|
|
273
|
+
const produced = mods.producer.produceDocSnapshot(producerInput(repo), { originProvider: origin });
|
|
274
|
+
const actual = produced.state === 'ready' ? 'ready' : 'unavailable';
|
|
275
|
+
record(id, expected, actual, codesOf(produced).join(',') || produced.state);
|
|
276
|
+
} finally { fs.rmSync(repo.root, { recursive: true, force: true }); }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Binary bytes stay in the whole-project hash, and producer/core re-derivation agree on the same snapshot.
|
|
280
|
+
{
|
|
281
|
+
const plain = makeRepo(files.base);
|
|
282
|
+
const withAsset = makeRepo(files.base, { 'assets/logo.png': BINARY });
|
|
283
|
+
try {
|
|
284
|
+
writeFiles(plain.root, files.candidate);
|
|
285
|
+
writeFiles(withAsset.root, { ...files.candidate, 'assets/logo.png': BINARY });
|
|
286
|
+
const plainOrigin = new FakeOrigin(CONTEXT(plain));
|
|
287
|
+
const assetOrigin = new FakeOrigin(CONTEXT(withAsset));
|
|
288
|
+
const plainSnapshot = mods.producer.produceDocSnapshot(producerInput(plain), { originProvider: plainOrigin });
|
|
289
|
+
const assetSnapshot = mods.producer.produceDocSnapshot(producerInput(withAsset), { originProvider: assetOrigin });
|
|
290
|
+
const digestsDiffer = plainSnapshot.snapshot.identity.candidateDigest !== assetSnapshot.snapshot.identity.candidateDigest
|
|
291
|
+
&& plainSnapshot.snapshot.identity.baselineDigest !== assetSnapshot.snapshot.identity.baselineDigest;
|
|
292
|
+
record('p2-binary-bytes-covered-by-both-digests', 'differ', digestsDiffer ? 'differ' : 'same');
|
|
293
|
+
const errors = mods.core._internals.validateSnapshot(assetSnapshot.snapshot);
|
|
294
|
+
record('p2-producer-core-parity', 'no-errors', errors.length ? JSON.stringify(errors) : 'no-errors');
|
|
295
|
+
// A record whose stored bytes do not match its digest must not verify.
|
|
296
|
+
const tampered = clone(assetSnapshot.snapshot);
|
|
297
|
+
const record0 = tampered.candidate.records.find(item => item.path === 'assets/logo.png');
|
|
298
|
+
record0.digest = '0'.repeat(64);
|
|
299
|
+
const tamperedErrors = mods.core._internals.validateSnapshot(tampered);
|
|
300
|
+
record('p2-malformed-record-rejected', 'invalid-input',
|
|
301
|
+
tamperedErrors.some(item => item.code === 'invalid-input') ? 'invalid-input' : JSON.stringify(tamperedErrors));
|
|
302
|
+
} finally {
|
|
303
|
+
fs.rmSync(plain.root, { recursive: true, force: true });
|
|
304
|
+
fs.rmSync(withAsset.root, { recursive: true, force: true });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const mods = load();
|
|
310
|
+
console.log(`# worktree ${ROOT}`);
|
|
311
|
+
if (ONLY === 'p1' || ONLY === 'all') p1Probes(mods);
|
|
312
|
+
if (ONLY === 'p2' || ONLY === 'all') p2Probes(mods);
|
|
313
|
+
console.log(`# summary: ${results.filter(r => r.pass).length} match, ${results.filter(r => !r.pass).length} diverge of ${results.length}`);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
== 353-r6-a (/Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-a) ==
|
|
2
|
+
{"name":"frontmatter-role-nonclassified","state":"unavailable","codes":["inventory-unsupported"]}
|
|
3
|
+
{"name":"req-citing-test","state":"unavailable","codes":["inventory-unsupported"]}
|
|
4
|
+
{"name":"req-block-nonclassified","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
5
|
+
{"name":"deleted-req-block","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
6
|
+
{"name":"irrelevant-asset-control","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
7
|
+
---
|
|
8
|
+
== 353-r6-b (/Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-b) ==
|
|
9
|
+
{"name":"frontmatter-role-nonclassified","state":"unavailable","codes":["inventory-unsupported"]}
|
|
10
|
+
{"name":"req-citing-test","state":"unavailable","codes":["inventory-unsupported"]}
|
|
11
|
+
{"name":"req-block-nonclassified","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
12
|
+
{"name":"deleted-req-block","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
13
|
+
{"name":"irrelevant-asset-control","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
== 353-r6-a (/Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-a) ==
|
|
17
|
+
{"name":"frontmatter-role-nonclassified","state":"unavailable","codes":["inventory-unsupported"]}
|
|
18
|
+
{"name":"req-citing-test","state":"unavailable","codes":["inventory-unsupported"]}
|
|
19
|
+
{"name":"req-block-nonclassified","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
20
|
+
{"name":"deleted-req-block","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
21
|
+
{"name":"req-citation-scope","state":"ready","musts":["doc-change:requirement-sync"],"requirementSync":{"applicability":"unknown","scope":["src/lint.js"],"citedReqs":[]}}
|
|
22
|
+
{"name":"irrelevant-asset-control","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
23
|
+
---
|
|
24
|
+
== 353-r6-b (/Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-b) ==
|
|
25
|
+
{"name":"frontmatter-role-nonclassified","state":"unavailable","codes":["inventory-unsupported"]}
|
|
26
|
+
{"name":"req-citing-test","state":"unavailable","codes":["inventory-unsupported"]}
|
|
27
|
+
{"name":"req-block-nonclassified","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
28
|
+
{"name":"deleted-req-block","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
29
|
+
{"name":"req-citation-scope","state":"ready","musts":["doc-change:requirement-sync"],"requirementSync":{"applicability":"unknown","scope":["src/lint.js"],"citedReqs":[]}}
|
|
30
|
+
{"name":"irrelevant-asset-control","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
== 353-r6-a (/Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-a) ==
|
|
34
|
+
{"name":"frontmatter-role-nonclassified","state":"unavailable","codes":["inventory-unsupported"]}
|
|
35
|
+
{"name":"req-citing-test","state":"unavailable","codes":["inventory-unsupported"]}
|
|
36
|
+
{"name":"req-block-nonclassified","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
37
|
+
{"name":"deleted-req-block","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
38
|
+
{"name":"req-citation-scope","state":"ready","musts":["doc-change:requirement-sync"],"requirementSync":{"applicability":"unknown","scope":["src/lint.js"],"citedReqs":[]}}
|
|
39
|
+
{"name":"irrelevant-asset-control","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
40
|
+
{"name":"undecodable-test-path-no-req","state":"unavailable","codes":["inventory-unsupported"]}
|
|
41
|
+
{"name":"undecodable-code-path-no-req","state":"unavailable","codes":["inventory-unsupported"]}
|
|
42
|
+
---
|
|
43
|
+
== 353-r6-b (/Users/daniel.chang/Desktop/Skills/Harness-everything/.worktrees/353-r6-b) ==
|
|
44
|
+
{"name":"frontmatter-role-nonclassified","state":"unavailable","codes":["inventory-unsupported"]}
|
|
45
|
+
{"name":"req-citing-test","state":"unavailable","codes":["inventory-unsupported"]}
|
|
46
|
+
{"name":"req-block-nonclassified","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
47
|
+
{"name":"deleted-req-block","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
48
|
+
{"name":"req-citation-scope","state":"ready","musts":["doc-change:requirement-sync"],"requirementSync":{"applicability":"unknown","scope":["src/lint.js"],"citedReqs":[]}}
|
|
49
|
+
{"name":"irrelevant-asset-control","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
50
|
+
{"name":"undecodable-test-path-no-req","state":"ready","musts":[],"requirementSync":{"applicability":"N/A","scope":[],"citedReqs":[]}}
|
|
51
|
+
{"name":"undecodable-code-path-no-req","state":"unavailable","codes":["inventory-unsupported"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Independent cross-review probe (leg B as reviewer). Read-only against both implementation worktrees.
|
|
3
|
+
// Uses only the harness API common to both legs (makeRepo/writeFiles with Buffer content).
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const BINARY = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0a, 0x1a, 0x0a, 0x00, 0xff, 0xfe, 0x80, 0x81]);
|
|
8
|
+
const PRD = '---\nid: PRD-GRPH-001\nrevision: 1\nstatus: draft\n---\n### REQ-GRPH-001\n\nThe rule.\n\nAcceptance: it holds.\n';
|
|
9
|
+
|
|
10
|
+
function load(root) {
|
|
11
|
+
const H = require(path.join(root, 'ci/fixtures/doc-obligations/reconciliation-s2a/harness.js'));
|
|
12
|
+
const P = require(path.join(root, 'hooks/scripts/lib/doc-reconciliation-producer.js'));
|
|
13
|
+
return { H, P };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function describe(snapshot) {
|
|
17
|
+
if (!snapshot) return null;
|
|
18
|
+
const req = (snapshot.obligations || []).find(o => o.capability === 'requirement-sync');
|
|
19
|
+
const musts = (snapshot.obligations || []).filter(o => o.strength === 'MUST' && o.applicability !== 'N/A').map(o => o.id).sort();
|
|
20
|
+
return {
|
|
21
|
+
musts,
|
|
22
|
+
requirementSync: req ? { applicability: req.applicability, scope: (req.scope.paths || []).slice(), citedReqs: req.citedReqs || [] } : null
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runCase(root, name, build) {
|
|
27
|
+
const { H, P } = load(root);
|
|
28
|
+
const files = build();
|
|
29
|
+
const repo = H.makeRepo(files.base);
|
|
30
|
+
try {
|
|
31
|
+
H.writeFiles(repo.root, files.candidate);
|
|
32
|
+
const origin = new H.FakeOrigin({ context: H.fakeContext({}, repo) });
|
|
33
|
+
const produced = P.produceDocSnapshot(H.producerInput(repo), { originProvider: origin });
|
|
34
|
+
if (produced.state !== 'ready') {
|
|
35
|
+
return { name, state: produced.state, codes: H.codesOf(produced) };
|
|
36
|
+
}
|
|
37
|
+
return { name, state: 'ready', ...describe(produced.snapshot) };
|
|
38
|
+
} finally { H.removeRepo(repo.root); }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const CASES = {
|
|
42
|
+
// A candidate record whose bytes carry a role: prd frontmatter, at a path no config glob classifies.
|
|
43
|
+
'frontmatter-role-nonclassified': () => ({
|
|
44
|
+
base: { 'docs/prd/graph.md': PRD, 'assets/role.dat': BINARY },
|
|
45
|
+
candidate: { 'docs/prd/graph.md': PRD, 'assets/role.dat': Buffer.concat([Buffer.from('---\nrole: prd\nid: PRD-GRPH-999\nrevision: 1\nstatus: draft\n---\n### REQ-GRPH-999\n\nBinary requirement.\n'), BINARY]) }
|
|
46
|
+
}),
|
|
47
|
+
// A candidate record whose bytes are a REQ-citing test, at a test path.
|
|
48
|
+
'req-citing-test': () => ({
|
|
49
|
+
base: { 'docs/prd/graph.md': PRD, 'tests/fixture.dat': BINARY },
|
|
50
|
+
candidate: { 'docs/prd/graph.md': PRD, 'tests/fixture.dat': Buffer.concat([Buffer.from('// REQ-GRPH-001\nit("requires the rule", () => { expect(true).toBe(true); });\n'), BINARY]) }
|
|
51
|
+
}),
|
|
52
|
+
// A candidate record whose bytes declare a REQ block at a path no config glob classifies, with a changed
|
|
53
|
+
// test that cites that REQ id.
|
|
54
|
+
'req-block-nonclassified': () => ({
|
|
55
|
+
base: { 'docs/prd/graph.md': PRD, 'tests/req.test.js': '// REQ-GRPH-001\nit("a", () => {});\n', 'assets/req.md': BINARY },
|
|
56
|
+
candidate: { 'docs/prd/graph.md': PRD, 'tests/req.test.js': '// REQ-GRPH-001\nit("b", () => {});\n', 'assets/req.md': Buffer.concat([Buffer.from('### REQ-GRPH-001\n\nThe binary declares the requirement.\n'), BINARY]) }
|
|
57
|
+
}),
|
|
58
|
+
// The same, with the record deleted in the candidate (new/deleted record identity).
|
|
59
|
+
'deleted-req-block': () => ({
|
|
60
|
+
base: { 'docs/prd/graph.md': PRD, 'tests/req.test.js': '// REQ-GRPH-001\nit("a", () => {});\n', 'assets/req.md': Buffer.concat([Buffer.from('### REQ-GRPH-001\n\nThe binary declares the requirement.\n'), BINARY]) },
|
|
61
|
+
candidate: { 'docs/prd/graph.md': PRD, 'tests/req.test.js': '// REQ-GRPH-001\nit("b", () => {});\n', 'assets/req.md': null }
|
|
62
|
+
}),
|
|
63
|
+
// A live requirement-sync surface (changed code + changed REQ-citing test) plus a candidate record whose
|
|
64
|
+
// bytes declare the cited REQ at a path no config glob classifies.
|
|
65
|
+
'req-citation-scope': () => ({
|
|
66
|
+
base: { 'src/lint.js': 'exports.run = () => 1;\n', 'tests/req.test.js': '// REQ-GRPH-001\nit("a", () => {});\n', 'assets/req.md': BINARY },
|
|
67
|
+
candidate: { 'src/lint.js': 'exports.run = () => 2;\n', 'tests/req.test.js': '// REQ-GRPH-001\nit("b", () => {});\n', 'assets/req.md': Buffer.concat([Buffer.from('### REQ-GRPH-001\n\nThe binary declares the requirement.\n'), BINARY]) }
|
|
68
|
+
}),
|
|
69
|
+
// Control: an ordinary irrelevant binary asset must not change the document obligations.
|
|
70
|
+
'irrelevant-asset-control': () => ({
|
|
71
|
+
base: { 'docs/prd/graph.md': PRD, 'assets/logo.png': BINARY },
|
|
72
|
+
candidate: { 'docs/prd/graph.md': PRD, 'assets/logo.png': Buffer.concat([BINARY, Buffer.from([1])]) }
|
|
73
|
+
}),
|
|
74
|
+
// An undecodable record at a REQ-test path with no REQ id: the packet forbids silently dropping a test
|
|
75
|
+
// input, so this case separates "fails closed" from "omitted".
|
|
76
|
+
'undecodable-test-path-no-req': () => ({
|
|
77
|
+
base: { 'docs/prd/graph.md': PRD, 'tests/plain.dat': BINARY },
|
|
78
|
+
candidate: { 'docs/prd/graph.md': PRD, 'tests/plain.dat': Buffer.concat([BINARY, Buffer.from([1])]) }
|
|
79
|
+
}),
|
|
80
|
+
// An undecodable record at a code path with no REQ id.
|
|
81
|
+
'undecodable-code-path-no-req': () => ({
|
|
82
|
+
base: { 'docs/prd/graph.md': PRD, 'src/plain.js': BINARY },
|
|
83
|
+
candidate: { 'docs/prd/graph.md': PRD, 'src/plain.js': Buffer.concat([BINARY, Buffer.from([1])]) }
|
|
84
|
+
})
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const root = path.resolve(process.argv[2]);
|
|
88
|
+
console.log(`== ${path.basename(root)} (${root}) ==`);
|
|
89
|
+
for (const [name, build] of Object.entries(CASES)) {
|
|
90
|
+
let outcome;
|
|
91
|
+
try { outcome = runCase(root, name, build); }
|
|
92
|
+
catch (error) { outcome = { name, state: 'ERROR', detail: error.message.split('\n')[0] }; }
|
|
93
|
+
console.log(JSON.stringify(outcome));
|
|
94
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs=require('fs'),path=require('path'),assert=require('assert/strict');
|
|
3
|
+
const root=path.resolve(process.argv[2]);
|
|
4
|
+
const H=require(path.join(root,'ci/fixtures/doc-obligations/reconciliation-s2a/harness.js'));
|
|
5
|
+
const binary=Buffer.from([0x89,0,0xff]);let passed=0,failed=0;
|
|
6
|
+
for(const leg of H.LEGS) for(const vector of ['frontmatter','req-test']) {
|
|
7
|
+
const P=require(path.join(leg.lib,'doc-reconciliation-producer.js'));
|
|
8
|
+
const files=H.vectorFiles('normative');const name=vector==='frontmatter'?'assets/role.dat':'tests/fixture.dat';
|
|
9
|
+
const repo=H.makeRepo({...files.base,[name]:binary});
|
|
10
|
+
try {
|
|
11
|
+
H.writeFiles(repo.root,files.candidate);
|
|
12
|
+
const content=vector==='frontmatter'?'---\nrole: prd\nid: PRD-GRPH-999\nrevision: 1\nstatus: draft\n---\n### REQ-GRPH-999\n\nThe binary candidate carries a requirement.\n\nAcceptance: evaluate this requirement.\n':'// REQ-GRPH-001\nit("requires the rule", () => { expect(true).toBe(true); });\n';
|
|
13
|
+
fs.writeFileSync(path.join(repo.root,name),Buffer.concat([Buffer.from(content),binary]));
|
|
14
|
+
const provider=new H.FakeOrigin({context:H.fakeContext({},repo)});
|
|
15
|
+
const produced=P.produceDocSnapshot(H.producerInput(repo),{originProvider:provider});
|
|
16
|
+
if(produced.state==='ready') {
|
|
17
|
+
const obligations=produced.snapshot.obligations;
|
|
18
|
+
const relevant=vector==='frontmatter'?obligations.some(o=>o.strength==='MUST'&&o.applicability!=='N/A'&&o.scope.paths.includes(name)):obligations.some(o=>o.capability==='requirement-sync'&&o.applicability!=='N/A');
|
|
19
|
+
assert(relevant,'candidate semantic content was omitted after probing only baseline bytes');
|
|
20
|
+
}
|
|
21
|
+
passed++;console.log('PASS',leg.name,vector,produced.state);
|
|
22
|
+
} catch(e) { failed++; console.error('FAIL',leg.name,vector,e.message); }
|
|
23
|
+
finally{H.removeRepo(repo.root);}
|
|
24
|
+
}
|
|
25
|
+
console.log(JSON.stringify({passed,failed}));process.exitCode=failed?1:0;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
5c3bcb7b7be96eee74e42885c436fdcb71da7c20e51cd372362f6bfc9cb08dc6 CHANGELOG.md
|
|
2
|
+
58a327f0c4ee3f97f321ecfae1a3e05e398b3073c8b891da03bd7e6962d5b09a ci/fixtures/doc-obligations/reconciliation-s2a/harness.js
|
|
3
|
+
0080edd1826c70d141a61b513d2a3be9c42d060e979f34ac74dd6519f2a5dbf0 ci/mechanism-325-3b-s2a-core.test.js
|
|
4
|
+
d4f4ec39e56f31cc15797cf79b9342edb3037c887057f81c5b7eb80bdc82323e ci/mechanism-325-3b-s2a-producer.test.js
|
|
5
|
+
10f2cb94c45a765d5ec7aa8edd8f9d2e5821c25119cae1c3446309e9f8a958d5 docs/doc-workflow-reconciliation.md
|
|
6
|
+
b4345651686c7b7d8e4ed66e416137aa3b42e17fa5c42bbf03c69b4eb8dad471 hooks/scripts/lib/doc-reconciliation-core.js
|
|
7
|
+
f8f5683fba9ea071e0cec83fdd7ca88c85a178f7ab9dabc98bb4b57b62b11599 hooks/scripts/lib/doc-reconciliation-producer.js
|
|
8
|
+
b4345651686c7b7d8e4ed66e416137aa3b42e17fa5c42bbf03c69b4eb8dad471 plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-core.js
|
|
9
|
+
f8f5683fba9ea071e0cec83fdd7ca88c85a178f7ab9dabc98bb4b57b62b11599 plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-producer.js
|