harness-everything 0.44.0 → 0.45.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/CHANGELOG.md +10 -0
- package/ci/fixtures/doc-obligations/reconciliation-s2a/harness.js +19 -1
- package/ci/mechanism-325-f2-doc-craft.test.js +2 -2
- package/ci/mechanism-325-f3-derived-views.test.js +5 -2
- package/ci/mechanism-pr-a-conflict-check.test.js +507 -0
- package/docs/doc-workflow-reconciliation.md +2 -1
- package/docs/platform-capabilities.md +4 -0
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/doc-check.js +84 -6
- package/harness-everything/scripts/doc-lifecycle.js +51 -6
- package/hooks/scripts/lib/doc-reconciliation-events.js +17 -0
- package/hooks/scripts/lib/doc-reconciliation-pilot.js +3 -0
- package/hooks/scripts/lib/doc-reconciliation-provider.js +80 -7
- 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/hooks/scripts/lib/doc-reconciliation-events.js +17 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-pilot.js +3 -0
- package/plugins/harness-everything/hooks/scripts/lib/doc-reconciliation-provider.js +80 -7
- 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-check.js +84 -6
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +51 -6
- 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.
|
|
13
|
+
"version": "0.45.0",
|
|
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.
|
|
4
|
+
"version": "0.45.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Miya Daniel"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.45.0](https://github.com/dyphn1/Harness-everything/compare/v0.44.0...v0.45.0) (2026-10-27)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* **doc-lifecycle:** produce conflict-check from a full-corpus assessment ([ec596e0](https://github.com/dyphn1/Harness-everything/commit/ec596e02de554f14778f7790551b6990a50dd0b6)), closes [#325](https://github.com/dyphn1/Harness-everything/issues/325)
|
|
10
|
+
|
|
5
11
|
## [0.44.0](https://github.com/dyphn1/Harness-everything/compare/v0.43.1...v0.44.0) (2026-10-27)
|
|
6
12
|
|
|
7
13
|
### Features
|
|
@@ -16,6 +22,10 @@ All notable changes to this project are documented in this file.
|
|
|
16
22
|
|
|
17
23
|
## [Unreleased]
|
|
18
24
|
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
* **doc-lifecycle:** add complete full-corpus conflict-check evidence with snapshot-bound scope and corpus identity.
|
|
28
|
+
|
|
19
29
|
### Fixed
|
|
20
30
|
|
|
21
31
|
- **doc-governance:** allow regular governed-content revisions and expand base-owned governed/derived globs against Git trees, while retaining review for membership, structural, policy, trust-surface, fixture, and derived-output changes; run the guard in its own CI job.
|
|
@@ -400,7 +400,7 @@ function buildEvents(snapshot, repo, vec) {
|
|
|
400
400
|
originRef: `fake-origin:event:${ref}`
|
|
401
401
|
};
|
|
402
402
|
if (kind === 'execution') {
|
|
403
|
-
|
|
403
|
+
const event = {
|
|
404
404
|
...common,
|
|
405
405
|
kind: 'execution',
|
|
406
406
|
principalId: PRINCIPALS.executor,
|
|
@@ -415,6 +415,24 @@ function buildEvents(snapshot, repo, vec) {
|
|
|
415
415
|
resultRef: `result-${capability}`,
|
|
416
416
|
artifactDigests: clone(artifactDigests)
|
|
417
417
|
};
|
|
418
|
+
if (capability === 'conflict-check') {
|
|
419
|
+
const { documentCorpusPaths } = require(path.join(ROOT, 'harness-everything', 'scripts', 'doc-lifecycle.js'));
|
|
420
|
+
const configSource = snapshot.config.capture.sources.find(item => item.path === CONFIG_REL && item.presence === 'present');
|
|
421
|
+
const configBytes = Buffer.isBuffer(configSource?.bytes) ? configSource.bytes : Buffer.from(configSource?.bytes?.data || []);
|
|
422
|
+
const corpusPaths = documentCorpusPaths(snapshot.candidate.records, snapshot.config.resolution.config);
|
|
423
|
+
event.corpusBinding = {
|
|
424
|
+
complete: true,
|
|
425
|
+
paths: corpusPaths,
|
|
426
|
+
digests: Object.fromEntries(corpusPaths.map(relative => [relative,
|
|
427
|
+
snapshot.candidate.records.find(item => item.path === relative && item.presence === 'present')?.digest])),
|
|
428
|
+
config: { path: CONFIG_REL, digest: sha256(configBytes) },
|
|
429
|
+
baseCommit: snapshot.originContext.baseline.commit,
|
|
430
|
+
baselineDigest: snapshot.identity.baselineDigest,
|
|
431
|
+
candidateDigest: snapshot.identity.candidateDigest,
|
|
432
|
+
configIdentityDigest: snapshot.identity.configDigest,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
events[ref] = event;
|
|
418
436
|
} else {
|
|
419
437
|
events[ref] = {
|
|
420
438
|
...common,
|
|
@@ -502,8 +502,8 @@ function mp2Apply() {
|
|
|
502
502
|
const dir = scriptCopy('f2 doc craft mp2 ');
|
|
503
503
|
const file = path.join(dir, MODULE);
|
|
504
504
|
const original = fs.readFileSync(file, 'utf8');
|
|
505
|
-
const emitMatches = [...original.matchAll(/return emit\(result
|
|
506
|
-
if (emitMatches.length !== 1) throw new ProbeApplyError(`could not uniquely locate the
|
|
505
|
+
const emitMatches = [...original.matchAll(/return emit\(result, lintExit === 1 \|\| broken\.length \? 1 : 0\);/g)];
|
|
506
|
+
if (emitMatches.length !== 1) throw new ProbeApplyError(`could not uniquely locate the plain-check exit mapping (${emitMatches.length} candidates); update this probe for the green shape`);
|
|
507
507
|
const injected = `if (result.reminders && result.reminders.length) return emit(result, 1);\n ${emitMatches[0][0]}`;
|
|
508
508
|
fs.writeFileSync(file, original.replace(emitMatches[0][0], injected));
|
|
509
509
|
return dir;
|
|
@@ -356,8 +356,11 @@ function caseD13(leg) {
|
|
|
356
356
|
const candidate = path.join(dir, 'candidate.md');
|
|
357
357
|
fs.writeFileSync(candidate, Buffer.from(TARGET_BYTES.toString('utf8').replace(/\n/g, '\r\n'), 'utf8'));
|
|
358
358
|
const result = runCompare(leg.dir, { workspace: ws, config: CONFIG, view: 'docs/index.md', candidate, status: 'ok' });
|
|
359
|
-
|
|
360
|
-
|
|
359
|
+
let comparison;
|
|
360
|
+
try { comparison = JSON.parse(result.stdout); } catch { comparison = null; }
|
|
361
|
+
const detail = `compare-derived JSON: ${JSON.stringify(comparison)}; stdout: ${truncate(result.stdout)}; stderr: ${truncate(result.stderr)}`;
|
|
362
|
+
same(result.status, 1, `exit; ${detail}`);
|
|
363
|
+
if (!comparison || !JSON.stringify(comparison).includes('stale')) throw new Error(`CRLF candidate accepted; ${detail}`);
|
|
361
364
|
}
|
|
362
365
|
assertPerLeg('case-d13:crlf-candidate-not-normalized', leg => caseD13(leg));
|
|
363
366
|
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// PR-A RED/GREEN contract for the full-corpus conflict-check producer.
|
|
5
|
+
const assert = require('node:assert/strict');
|
|
6
|
+
const crypto = require('node:crypto');
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const os = require('node:os');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const { execFileSync, spawnSync } = require('node:child_process');
|
|
11
|
+
|
|
12
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
13
|
+
const DOC_CHECK = path.join(ROOT, 'harness-everything', 'scripts', 'doc-check.js');
|
|
14
|
+
const PROVIDER = require(path.join(ROOT, 'hooks', 'scripts', 'lib', 'doc-reconciliation-provider.js'));
|
|
15
|
+
const PILOT = require(path.join(ROOT, 'hooks', 'scripts', 'lib', 'doc-reconciliation-pilot.js'));
|
|
16
|
+
const PRODUCER = require(path.join(ROOT, 'hooks', 'scripts', 'lib', 'doc-reconciliation-producer.js'));
|
|
17
|
+
const EVENTS = require(path.join(ROOT, 'hooks', 'scripts', 'lib', 'doc-reconciliation-events.js'));
|
|
18
|
+
const EVIDENCE = require(path.join(ROOT, 'harness-everything', 'scripts', 'doc-evidence.js'));
|
|
19
|
+
const LIFECYCLE = require(path.join(ROOT, 'harness-everything', 'scripts', 'doc-lifecycle.js'));
|
|
20
|
+
const REQUIREMENTS = [
|
|
21
|
+
{ id: 'req-docs', stepType: 'doc-change', summary: 'Update the ADR', acceptance: 'the normative change is reviewed' },
|
|
22
|
+
{ id: 'req-verify', stepType: 'verification', summary: 'Verify the ADR', acceptance: 'full-corpus conflict evidence is recorded' },
|
|
23
|
+
];
|
|
24
|
+
const PILOT_CONFIG_TEXT = JSON.stringify({
|
|
25
|
+
governed: [{ path: 'docs/current/**', role: 'adr' }], legacy: [], domains: ['GRPH'],
|
|
26
|
+
}, null, 2) + '\n';
|
|
27
|
+
const CONFIG = '.agents/harness-everything/project-docs.json';
|
|
28
|
+
const CONFIG_TEXT = JSON.stringify({
|
|
29
|
+
governed: [{ path: 'docs/current/**', role: 'reference' }],
|
|
30
|
+
legacy: ['docs/legacy/**'],
|
|
31
|
+
domains: ['GRPH', 'LEG'],
|
|
32
|
+
}, null, 2) + '\n';
|
|
33
|
+
|
|
34
|
+
const roots = [];
|
|
35
|
+
function write(root, relative, text) {
|
|
36
|
+
const file = path.join(root, relative);
|
|
37
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
38
|
+
fs.writeFileSync(file, text);
|
|
39
|
+
}
|
|
40
|
+
function git(root, args) {
|
|
41
|
+
const env = { ...process.env, GIT_CONFIG_NOSYSTEM: '1' };
|
|
42
|
+
for (const key of Object.keys(env)) if (key.startsWith('GIT_')) delete env[key];
|
|
43
|
+
return execFileSync('git', ['-C', root, ...args], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
44
|
+
}
|
|
45
|
+
function reference(id, extra = '') {
|
|
46
|
+
return `---\nrole: reference\nstatus: active\nid: ${id}\n${extra}---\n\n# ${id}\n\nA governed document.\n`;
|
|
47
|
+
}
|
|
48
|
+
function adr(id, body) {
|
|
49
|
+
return `---\nid: ${id}\nrole: adr\nstatus: accepted\n---\n\n# ${id}\n\n## Decision\n\n${body}\n`;
|
|
50
|
+
}
|
|
51
|
+
function workspace(files, configText = CONFIG_TEXT) {
|
|
52
|
+
const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'pra-conflict-check-')));
|
|
53
|
+
roots.push(root);
|
|
54
|
+
write(root, CONFIG, configText);
|
|
55
|
+
for (const [relative, contents] of Object.entries(files)) write(root, relative, contents);
|
|
56
|
+
git(root, ['init', '-q']);
|
|
57
|
+
git(root, ['add', '-A']);
|
|
58
|
+
git(root, ['-c', 'user.name=Harness Test', '-c', 'user.email=harness@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '-q', '-m', 'baseline']);
|
|
59
|
+
return { root, base: git(root, ['rev-parse', 'HEAD']) };
|
|
60
|
+
}
|
|
61
|
+
function runCheck(root, base, changed = ['docs/current/a.md'], extra = []) {
|
|
62
|
+
return spawnSync(process.execPath, [DOC_CHECK, 'check', '--workspace', root, '--config', CONFIG,
|
|
63
|
+
...changed.flatMap(item => ['--changed-path', item]), '--base', base, ...extra],
|
|
64
|
+
{ cwd: root, encoding: 'utf8' });
|
|
65
|
+
}
|
|
66
|
+
function report(result) {
|
|
67
|
+
try { return JSON.parse(result.stdout); } catch { return null; }
|
|
68
|
+
}
|
|
69
|
+
function sha(text) { return crypto.createHash('sha256').update(text).digest('hex'); }
|
|
70
|
+
|
|
71
|
+
function seedPilotScenario() {
|
|
72
|
+
const baselineDoc = adr('GRPH-001', 'Checkout retries once.');
|
|
73
|
+
const { root, base } = workspace({
|
|
74
|
+
'docs/current/a.md': baselineDoc,
|
|
75
|
+
'docs/current/b.md': adr('GRPH-002', 'The receipt is durable.'),
|
|
76
|
+
}, PILOT_CONFIG_TEXT);
|
|
77
|
+
write(root, 'docs/current/a.md', adr('GRPH-001', 'Checkout retries twice.'));
|
|
78
|
+
const sessionId = 'pra-session';
|
|
79
|
+
const workflowId = 'pra-workflow';
|
|
80
|
+
const sessionDir = path.join(root, '.harness-session');
|
|
81
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
82
|
+
const plan = { strategySelection: 'selected', tier: 'tier2', strategy: 'iterative-single' };
|
|
83
|
+
const context = {
|
|
84
|
+
sessionId, sessionDir, root,
|
|
85
|
+
workflow: { workflowId, revision: 1, state: 'pending', strategy: 'iterative-single', workflowPlan: plan },
|
|
86
|
+
};
|
|
87
|
+
const obligations = require(path.join(ROOT, 'hooks', 'scripts', 'lib', 'workflow-obligations.js'));
|
|
88
|
+
obligations.initializePlanningContract(context, plan);
|
|
89
|
+
obligations.recordPlanning(context, JSON.stringify(REQUIREMENTS), plan, 'iterative-single', 'one bounded documentation verification path');
|
|
90
|
+
obligations.materializeExecutionObligations(context, plan);
|
|
91
|
+
const loaded = obligations.loadObligationSet(context, plan);
|
|
92
|
+
if (!loaded.set) throw new Error(`pilot fixture failed to load obligations: ${loaded.error}`);
|
|
93
|
+
const previousPilot = process.env.HARNESS_DOC_RECONCILIATION_PILOT;
|
|
94
|
+
process.env.HARNESS_DOC_RECONCILIATION_PILOT = '1';
|
|
95
|
+
try { PILOT.captureBaseline(context); }
|
|
96
|
+
finally {
|
|
97
|
+
if (previousPilot === undefined) delete process.env.HARNESS_DOC_RECONCILIATION_PILOT;
|
|
98
|
+
else process.env.HARNESS_DOC_RECONCILIATION_PILOT = previousPilot;
|
|
99
|
+
}
|
|
100
|
+
write(sessionDir, 'workflow-run.json', JSON.stringify({ workflowId, revision: 1, docReconciliationBaseline: context.workflow.docReconciliationBaseline }));
|
|
101
|
+
return { root, base, context, plan, set: loaded.set, sessionId, workflowId, sessionDir };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function addHostCapture(scenario, payload, eventName = 'PostToolUse') {
|
|
105
|
+
const capture = PROVIDER.buildCaptureRecord(payload, eventName, scenario.root);
|
|
106
|
+
if (!capture) throw new Error(`capture builder did not accept ${eventName}`);
|
|
107
|
+
capture.observedIdentity = PROVIDER.observedIdentityFor(scenario.sessionDir);
|
|
108
|
+
PROVIDER.writeImmutableEvent(scenario.sessionDir, capture);
|
|
109
|
+
PROVIDER.rebuildJournalView(scenario.sessionDir);
|
|
110
|
+
return capture;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function addAssessmentCapture(scenario, changed = ['docs/current/a.md'], beforeCapture = null) {
|
|
114
|
+
const args = [DOC_CHECK, 'check', '--workspace', scenario.root, '--config', CONFIG,
|
|
115
|
+
...changed.flatMap(item => ['--changed-path', item]), '--base', scenario.base, '--conflict-assessment'];
|
|
116
|
+
const result = spawnSync(process.execPath, args, { cwd: scenario.root, encoding: 'utf8' });
|
|
117
|
+
if (result.status !== 0) throw new Error(`fixture assessment failed with ${result.status}: ${report(result)?.conflictAssessment?.reason || result.stderr}`);
|
|
118
|
+
if (beforeCapture) beforeCapture();
|
|
119
|
+
const command = ['node', ...args].join(' ');
|
|
120
|
+
return { result, capture: addHostCapture(scenario, {
|
|
121
|
+
cwd: scenario.root, session_id: scenario.sessionId, tool_name: 'Bash', tool_use_id: 'check-1',
|
|
122
|
+
tool_input: { command }, tool_response: { stdout: result.stdout, interrupted: false },
|
|
123
|
+
}) };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function addReviewCapture(scenario) {
|
|
127
|
+
const reviewer = 'independent-reviewer';
|
|
128
|
+
addHostCapture(scenario, {
|
|
129
|
+
cwd: scenario.root, session_id: scenario.sessionId, tool_name: 'Task', tool_use_id: 'review-task',
|
|
130
|
+
tool_input: { description: 'Read-only ADR review' }, tool_response: { agentId: reviewer, interrupted: false },
|
|
131
|
+
});
|
|
132
|
+
const digest = sha(fs.readFileSync(path.join(scenario.root, 'docs/current/a.md')));
|
|
133
|
+
addHostCapture(scenario, {
|
|
134
|
+
cwd: scenario.root, session_id: scenario.sessionId, tool_use_id: 'review-stop', agent_id: reviewer, agent_type: 'general-purpose',
|
|
135
|
+
last_assistant_message: `Review verdict: approve\nThe updated ADR is clear.\nReviewed artifact: docs/current/a.md sha256:${digest}`,
|
|
136
|
+
}, 'SubagentStop');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function testOrigin(scenario, captures = PROVIDER.readCaptureView(scenario.sessionDir).records) {
|
|
140
|
+
return PROVIDER.createClaudeOriginProvider({
|
|
141
|
+
hostId: 'claude-code', sessionId: scenario.sessionId, workspaceRoot: scenario.root,
|
|
142
|
+
workflowId: scenario.workflowId, workflowRevision: 1,
|
|
143
|
+
taskId: `task-${REQUIREMENTS.map(item => item.id).join('+')}`,
|
|
144
|
+
principals: { author: scenario.sessionId, executors: [scenario.sessionId] },
|
|
145
|
+
baseline: scenario.context.workflow.docReconciliationBaseline,
|
|
146
|
+
captures,
|
|
147
|
+
qualification: { source: 'fake-test-fixture', faked: true, ref: 'pra-fixture', toolUseIdPopulated: true, agentIdPopulated: true },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function providerIdentity(scenario) {
|
|
152
|
+
return {
|
|
153
|
+
workspaceId: 'pra-workspace', sessionId: scenario.sessionId, workflowId: scenario.workflowId,
|
|
154
|
+
workflowRevision: 1, taskId: `task-${REQUIREMENTS.map(item => item.id).join('+')}`,
|
|
155
|
+
taskScopeDigest: '1'.repeat(64), baselineDigest: '2'.repeat(64), candidateDigest: '3'.repeat(64),
|
|
156
|
+
configDigest: '4'.repeat(64), prdRoot: 'docs/prd/',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function produceSnapshot(scenario, origin = testOrigin(scenario)) {
|
|
161
|
+
const result = PRODUCER.produceDocSnapshot({
|
|
162
|
+
workspaceRoot: scenario.root, baseline: scenario.context.workflow.docReconciliationBaseline,
|
|
163
|
+
workflow: { workflowId: scenario.workflowId, workflowRevision: 1, sessionId: scenario.sessionId },
|
|
164
|
+
task: { taskId: `task-${REQUIREMENTS.map(item => item.id).join('+')}`, requirements: REQUIREMENTS },
|
|
165
|
+
scope: { prdRoot: 'docs/prd/' }, activeHost: 'claude-code', prdRoot: 'docs/prd/',
|
|
166
|
+
}, { originProvider: origin });
|
|
167
|
+
if (result.state !== 'ready' || !result.snapshot) throw new Error(`pilot snapshot unavailable: ${JSON.stringify(result.diagnostics)}`);
|
|
168
|
+
return result.snapshot;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let passed = 0;
|
|
172
|
+
let failed = 0;
|
|
173
|
+
function check(id, fn) {
|
|
174
|
+
try { fn(); passed += 1; console.log(`PASS ${id}`); }
|
|
175
|
+
catch (error) { failed += 1; console.error(`FAIL ${id} — ${error.message.split('\n')[0]}`); }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
check('F1 full clean corpus emits a bound conflict-check projection', () => {
|
|
180
|
+
const docs = {
|
|
181
|
+
'docs/current/a.md': reference('GRPH-001'),
|
|
182
|
+
'docs/current/b.md': reference('GRPH-002'),
|
|
183
|
+
'docs/legacy/LEG-001.md': reference('LEG-001'),
|
|
184
|
+
};
|
|
185
|
+
const { root, base } = workspace(docs);
|
|
186
|
+
const result = runCheck(root, base, ['docs/current/a.md'], ['--conflict-assessment']);
|
|
187
|
+
const out = report(result);
|
|
188
|
+
assert.equal(result.status, 0, `expected clean assessment exit 0, got ${result.status}: ${out?.lint?.error || result.stderr}`);
|
|
189
|
+
assert.equal(out.mode, 'conflict-assessment');
|
|
190
|
+
assert.equal(out.capabilities['conflict-check'].state, 'pass');
|
|
191
|
+
assert.deepEqual(out.capabilities['conflict-check'].checkedPaths, ['docs/current/a.md']);
|
|
192
|
+
assert.deepEqual(out.capabilities['conflict-check'].corpus.paths, Object.keys(docs).sort());
|
|
193
|
+
assert.deepEqual(out.capabilities['conflict-check'].corpus.digests, Object.fromEntries(Object.entries(docs).map(([p, text]) => [p, sha(text)])));
|
|
194
|
+
assert.equal(out.capabilities['conflict-check'].corpus.config.path, CONFIG);
|
|
195
|
+
assert.equal(out.capabilities['conflict-check'].corpus.config.digest, sha(fs.readFileSync(path.join(root, CONFIG))));
|
|
196
|
+
assert.equal(out.capabilities['conflict-check'].corpus.baseCommit, base);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
check('F2 plain changed-path check cannot produce conflict-check credit for unchanged conflicts', () => {
|
|
200
|
+
const { root, base } = workspace({
|
|
201
|
+
'docs/current/a.md': reference('GRPH-001', 'conflicts_with: [GRPH-002]\n'),
|
|
202
|
+
'docs/current/b.md': reference('GRPH-002'),
|
|
203
|
+
'docs/current/probe.md': reference('GRPH-003'),
|
|
204
|
+
});
|
|
205
|
+
const result = runCheck(root, base, ['docs/current/probe.md']);
|
|
206
|
+
const out = report(result);
|
|
207
|
+
assert.equal(result.status, 0, `unchanged conflict must stay a warning in plain check, got ${result.status}`);
|
|
208
|
+
assert.equal(out.capabilities['doc-lint'].state, 'pass');
|
|
209
|
+
assert.equal(out.capabilities['link-check'].state, 'pass');
|
|
210
|
+
assert.equal(out.capabilities['conflict-check'], undefined);
|
|
211
|
+
const command = `node ${DOC_CHECK} check --workspace ${root} --config ${CONFIG} --changed-path docs/current/probe.md --base ${base}`;
|
|
212
|
+
const invocation = PROVIDER.resolveDocCheckInvocation(command, root);
|
|
213
|
+
assert.equal(invocation.kind, 'accepted');
|
|
214
|
+
assert.notEqual(invocation.conflictAssessment, true);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
check('F3 full assessment rejects recorded conflicts on unchanged files', () => {
|
|
218
|
+
const { root, base } = workspace({
|
|
219
|
+
'docs/current/a.md': reference('GRPH-001', 'conflicts_with: [GRPH-002]\n'),
|
|
220
|
+
'docs/current/b.md': reference('GRPH-002'),
|
|
221
|
+
'docs/current/probe.md': reference('GRPH-003'),
|
|
222
|
+
});
|
|
223
|
+
const result = runCheck(root, base, ['docs/current/probe.md'], ['--conflict-assessment']);
|
|
224
|
+
const out = report(result);
|
|
225
|
+
assert.equal(result.status, 1, `full lint should reject recorded conflict, got ${result.status}`);
|
|
226
|
+
assert.ok(out.lint.diagnostics.some(item => item.code === 'recorded-conflict'));
|
|
227
|
+
assert.notEqual(out.capabilities?.['conflict-check']?.state, 'pass');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
check('assessment flag is structurally parsed and accepted by the provider', () => {
|
|
231
|
+
const { root, base } = workspace({ 'docs/current/a.md': reference('GRPH-001') });
|
|
232
|
+
const command = `node ${DOC_CHECK} check --workspace ${root} --config ${CONFIG} --changed-path docs/current/a.md --base ${base} --conflict-assessment`;
|
|
233
|
+
const invocation = PROVIDER.resolveDocCheckInvocation(command, root);
|
|
234
|
+
assert.equal(invocation.kind, 'accepted', `provider rejected structured assessment argv: ${invocation.reason}`);
|
|
235
|
+
assert.equal(invocation.conflictAssessment, true);
|
|
236
|
+
assert.equal(invocation.base, base);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
check('duplicate assessment flag is rejected as a duplicate', () => {
|
|
240
|
+
const { root, base } = workspace({ 'docs/current/a.md': reference('GRPH-001') });
|
|
241
|
+
const result = runCheck(root, base, ['docs/current/a.md'], ['--conflict-assessment', '--conflict-assessment']);
|
|
242
|
+
const out = report(result);
|
|
243
|
+
assert.equal(result.status, 2);
|
|
244
|
+
assert.match(out.lint.error, /repeated argument: --conflict-assessment/);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
check('assessment mode requires its baseline pin', () => {
|
|
248
|
+
const { root, base } = workspace({ 'docs/current/a.md': reference('GRPH-001') });
|
|
249
|
+
const args = [DOC_CHECK, 'check', '--workspace', root, '--config', CONFIG, '--changed-path', 'docs/current/a.md', '--conflict-assessment'];
|
|
250
|
+
const result = spawnSync(process.execPath, args, { cwd: root, encoding: 'utf8' });
|
|
251
|
+
assert.equal(result.status, 2);
|
|
252
|
+
assert.match(report(result).lint.error, /--conflict-assessment requires --base/);
|
|
253
|
+
const command = `node ${DOC_CHECK} check --workspace ${root} --config ${CONFIG} --changed-path docs/current/a.md --conflict-assessment`;
|
|
254
|
+
assert.equal(PROVIDER.resolveDocCheckInvocation(command, root).reason, 'conflict-assessment-base-required');
|
|
255
|
+
const unpinned = runCheck(root, 'HEAD', ['docs/current/a.md'], ['--conflict-assessment']);
|
|
256
|
+
assert.equal(report(unpinned).conflictAssessment.state, 'unavailable');
|
|
257
|
+
assert.equal(report(unpinned).conflictAssessment.reason, 'base-not-pinned');
|
|
258
|
+
const movingBaseCommand = `node ${DOC_CHECK} check --workspace ${root} --config ${CONFIG} --changed-path docs/current/a.md --base HEAD --conflict-assessment`;
|
|
259
|
+
assert.equal(PROVIDER.resolveDocCheckInvocation(movingBaseCommand, root).reason, 'conflict-assessment-base-unpinned');
|
|
260
|
+
assert.ok(base);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
check('F4 full graph resolves cross-domain legacy targets and rejects unresolved references', () => {
|
|
264
|
+
const crossDomain = workspace({
|
|
265
|
+
'docs/current/a.md': reference('GRPH-001', 'conflicts_with: [legacy:LEG-001]\n'),
|
|
266
|
+
'docs/legacy/LEG-001.md': reference('LEG-001'),
|
|
267
|
+
'docs/current/probe.md': reference('GRPH-003'),
|
|
268
|
+
});
|
|
269
|
+
const crossResult = runCheck(crossDomain.root, crossDomain.base, ['docs/current/probe.md'], ['--conflict-assessment']);
|
|
270
|
+
const crossOut = report(crossResult);
|
|
271
|
+
assert.equal(crossResult.status, 1);
|
|
272
|
+
assert.ok(crossOut.lint.diagnostics.some(item => item.code === 'recorded-conflict' && item.path === 'docs/current/a.md'), JSON.stringify(crossOut.lint.diagnostics));
|
|
273
|
+
assert.notEqual(crossOut.capabilities?.['conflict-check']?.state, 'pass');
|
|
274
|
+
|
|
275
|
+
const unresolved = workspace({
|
|
276
|
+
'docs/current/a.md': reference('GRPH-001', 'conflicts_with: [LEG-404]\n'),
|
|
277
|
+
'docs/current/probe.md': reference('GRPH-003'),
|
|
278
|
+
});
|
|
279
|
+
const unresolvedResult = runCheck(unresolved.root, unresolved.base, ['docs/current/probe.md'], ['--conflict-assessment']);
|
|
280
|
+
const unresolvedOut = report(unresolvedResult);
|
|
281
|
+
assert.equal(unresolvedResult.status, 1);
|
|
282
|
+
assert.ok(unresolvedOut.lint.diagnostics.some(item => item.code === 'unresolved-ref'), JSON.stringify(unresolvedOut.lint.diagnostics));
|
|
283
|
+
assert.notEqual(unresolvedOut.capabilities?.['conflict-check']?.state, 'pass');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
check('omitted corpus and stale base stay unavailable', () => {
|
|
287
|
+
const config = JSON.stringify({ governed: [
|
|
288
|
+
{ path: 'docs/current/a.md', role: 'reference' },
|
|
289
|
+
{ path: 'docs/current/missing.md', role: 'reference' },
|
|
290
|
+
], legacy: [], domains: ['GRPH'] }, null, 2) + '\n';
|
|
291
|
+
const omitted = workspace({ 'docs/current/a.md': reference('GRPH-001') }, config);
|
|
292
|
+
const incomplete = report(runCheck(omitted.root, omitted.base, ['docs/current/a.md'], ['--conflict-assessment']));
|
|
293
|
+
assert.equal(incomplete.conflictAssessment.state, 'unavailable');
|
|
294
|
+
assert.equal(incomplete.conflictAssessment.reason, 'corpus-incomplete');
|
|
295
|
+
assert.notEqual(incomplete.capabilities?.['conflict-check']?.state, 'pass');
|
|
296
|
+
|
|
297
|
+
const clean = workspace({ 'docs/current/a.md': reference('GRPH-001') });
|
|
298
|
+
const stale = report(runCheck(clean.root, 'deadbeef', ['docs/current/a.md'], ['--conflict-assessment']));
|
|
299
|
+
assert.notEqual(stale?.capabilities?.['conflict-check']?.state, 'pass');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
check('mode-looking argument text is not parsed as an assessment flag', () => {
|
|
303
|
+
const { root, base } = workspace({ 'docs/current/a.md': reference('GRPH-001') });
|
|
304
|
+
const command = `node ${DOC_CHECK} check --workspace ${root} --config ${CONFIG} --changed-path "docs/current/a.md --conflict-assessment" --base ${base}`;
|
|
305
|
+
const invocation = PROVIDER.resolveDocCheckInvocation(command, root);
|
|
306
|
+
assert.equal(invocation.kind, 'accepted');
|
|
307
|
+
assert.notEqual(invocation.conflictAssessment, true);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
check('end-to-end pilot discovers conflict-check from real capture records', () => {
|
|
311
|
+
const previous = process.env.HARNESS_DOC_RECONCILIATION_PILOT;
|
|
312
|
+
process.env.HARNESS_DOC_RECONCILIATION_PILOT = '1';
|
|
313
|
+
try {
|
|
314
|
+
const scenario = seedPilotScenario();
|
|
315
|
+
const { capture } = addAssessmentCapture(scenario);
|
|
316
|
+
addReviewCapture(scenario);
|
|
317
|
+
const result = PILOT.assess(scenario.context, scenario.set, { originProvider: testOrigin(scenario) });
|
|
318
|
+
assert.equal(result.withhold, false, JSON.stringify(result.report));
|
|
319
|
+
assert.equal(result.report.pilotCredit, true);
|
|
320
|
+
assert.ok(result.report.creditedCapabilities.includes('conflict-check'), JSON.stringify(result.report));
|
|
321
|
+
assert.ok(result.report.acceptedRefs.includes(`${capture.ref}#conflict-check`), JSON.stringify(result.report.acceptedRefs));
|
|
322
|
+
} finally {
|
|
323
|
+
if (previous === undefined) delete process.env.HARNESS_DOC_RECONCILIATION_PILOT;
|
|
324
|
+
else process.env.HARNESS_DOC_RECONCILIATION_PILOT = previous;
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
check('capture rejects corpus or config changes between check and PostToolUse', () => {
|
|
329
|
+
const corpusChanged = seedPilotScenario();
|
|
330
|
+
const changedRun = addAssessmentCapture(corpusChanged, ['docs/current/a.md'], () => {
|
|
331
|
+
write(corpusChanged.root, 'docs/current/b.md', adr('GRPH-002', 'The receipt is now archived.'));
|
|
332
|
+
});
|
|
333
|
+
const corpusOrigin = testOrigin(corpusChanged);
|
|
334
|
+
const corpusResult = corpusOrigin.resolveEvent({
|
|
335
|
+
reference: `${changedRun.capture.ref}#conflict-check`, identity: providerIdentity(corpusChanged),
|
|
336
|
+
});
|
|
337
|
+
assert.equal(corpusResult.status, 'rejected');
|
|
338
|
+
assert.ok(corpusResult.diagnostics.some(item => item.code === 'corpus-changed-after-check'));
|
|
339
|
+
|
|
340
|
+
const configChanged = seedPilotScenario();
|
|
341
|
+
const configRun = addAssessmentCapture(configChanged, ['docs/current/a.md'], () => {
|
|
342
|
+
fs.appendFileSync(path.join(configChanged.root, CONFIG), '\n');
|
|
343
|
+
});
|
|
344
|
+
const configOrigin = testOrigin(configChanged);
|
|
345
|
+
const configResult = configOrigin.resolveEvent({
|
|
346
|
+
reference: `${configRun.capture.ref}#conflict-check`, identity: providerIdentity(configChanged),
|
|
347
|
+
});
|
|
348
|
+
assert.equal(configResult.status, 'rejected');
|
|
349
|
+
assert.ok(configResult.diagnostics.some(item => item.code === 'config-changed-after-check'));
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
check('corpus changes after capture are rejected against the later snapshot', () => {
|
|
353
|
+
const scenario = seedPilotScenario();
|
|
354
|
+
const { capture } = addAssessmentCapture(scenario);
|
|
355
|
+
const origin = testOrigin(scenario);
|
|
356
|
+
write(scenario.root, 'docs/current/b.md', adr('GRPH-002', 'The receipt is now archived.'));
|
|
357
|
+
const snapshot = produceSnapshot(scenario, origin);
|
|
358
|
+
const accepted = EVENTS.acceptDocEvents({ snapshot, eventRefs: [`${capture.ref}#conflict-check`] }, { originProvider: origin });
|
|
359
|
+
assert.equal(accepted.state, 'unresolved');
|
|
360
|
+
assert.ok(accepted.rejected.some(item => item.code === 'corpus-mismatch'));
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
check('symlink skips and unreadable corpus files mark the full collection incomplete', () => {
|
|
364
|
+
const { root } = workspace({ 'docs/current/a.md': reference('GRPH-001') });
|
|
365
|
+
const external = path.join(os.tmpdir(), `pra-outside-${process.pid}.md`);
|
|
366
|
+
write(root, 'docs/current/skipped.md', reference('GRPH-002'));
|
|
367
|
+
fs.writeFileSync(external, reference('GRPH-003'));
|
|
368
|
+
let linked = false;
|
|
369
|
+
try {
|
|
370
|
+
fs.rmSync(path.join(root, 'docs/current/skipped.md'));
|
|
371
|
+
fs.symlinkSync(external, path.join(root, 'docs/current/skipped.md'));
|
|
372
|
+
linked = true;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (process.platform !== 'win32' || !['EPERM', 'EACCES', 'ENOSYS'].includes(error.code)) throw error;
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
if (linked) {
|
|
378
|
+
const result = report(runCheck(root, git(root, ['rev-parse', 'HEAD']), ['docs/current/a.md'], ['--conflict-assessment']));
|
|
379
|
+
assert.equal(result.conflictAssessment.state, 'unavailable');
|
|
380
|
+
assert.equal(result.conflictAssessment.reason, 'corpus-incomplete');
|
|
381
|
+
}
|
|
382
|
+
} finally {
|
|
383
|
+
fs.rmSync(external, { force: true });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const target = path.join(root, 'docs/current/a.md');
|
|
387
|
+
const originalRead = fs.readFileSync;
|
|
388
|
+
const coverage = {};
|
|
389
|
+
fs.readFileSync = function (file, ...args) {
|
|
390
|
+
if (typeof file === 'string' && path.resolve(file) === target) {
|
|
391
|
+
const error = new Error('fixture unreadable');
|
|
392
|
+
error.code = 'EACCES';
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
return originalRead.call(this, file, ...args);
|
|
396
|
+
};
|
|
397
|
+
try {
|
|
398
|
+
LIFECYCLE.collectDocs(root, { governed: [{ path: 'docs/current/**', role: 'reference' }], legacy: [] }, coverage);
|
|
399
|
+
} finally { fs.readFileSync = originalRead; }
|
|
400
|
+
assert.equal(coverage.complete, false);
|
|
401
|
+
assert.ok(coverage.skipped.some(item => item.path === 'docs/current/a.md' && item.reason === 'unreadable-file'));
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
check('provider requires the assessment stdout schema as well as the argv mode', () => {
|
|
405
|
+
const scenario = seedPilotScenario();
|
|
406
|
+
const command = `node ${DOC_CHECK} check --workspace ${scenario.root} --config ${CONFIG} --changed-path docs/current/a.md --base ${scenario.base} --conflict-assessment`;
|
|
407
|
+
const capture = addHostCapture(scenario, {
|
|
408
|
+
cwd: scenario.root, session_id: scenario.sessionId, tool_name: 'Bash', tool_use_id: 'forged-mode',
|
|
409
|
+
tool_input: { command }, tool_response: { interrupted: false, stdout: JSON.stringify({
|
|
410
|
+
schema: 'forged', mode: 'conflict-assessment', lint: { exit: 0 },
|
|
411
|
+
capabilities: { 'conflict-check': { state: 'pass', checkedPaths: ['docs/current/a.md'] } },
|
|
412
|
+
}) },
|
|
413
|
+
});
|
|
414
|
+
const response = testOrigin(scenario).resolveEvent({
|
|
415
|
+
reference: `${capture.ref}#conflict-check`, identity: providerIdentity(scenario),
|
|
416
|
+
});
|
|
417
|
+
assert.equal(response.status, 'rejected');
|
|
418
|
+
assert.ok(response.diagnostics.some(item => item.code === 'check-not-passed'));
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
check('snapshot adapter rejects wrong scope, corpus, config, and base bindings', () => {
|
|
422
|
+
const scenario = seedPilotScenario();
|
|
423
|
+
const { capture } = addAssessmentCapture(scenario);
|
|
424
|
+
const origin = testOrigin(scenario);
|
|
425
|
+
const snapshot = produceSnapshot(scenario, origin);
|
|
426
|
+
const reference = `${capture.ref}#conflict-check`;
|
|
427
|
+
const accept = mutate => EVENTS.acceptDocEvents({ snapshot, eventRefs: [reference] }, {
|
|
428
|
+
originProvider: {
|
|
429
|
+
resolveContext: request => origin.resolveContext(request),
|
|
430
|
+
resolveEvent: request => {
|
|
431
|
+
const response = origin.resolveEvent(request);
|
|
432
|
+
if (response.status !== 'accepted') return response;
|
|
433
|
+
const event = JSON.parse(JSON.stringify(response.event));
|
|
434
|
+
mutate(event);
|
|
435
|
+
return { ...response, event };
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const good = EVENTS.acceptDocEvents({ snapshot, eventRefs: [reference] }, { originProvider: origin });
|
|
441
|
+
assert.equal(good.state, 'ready', JSON.stringify(good.rejected));
|
|
442
|
+
for (const [name, mutate, expected] of [
|
|
443
|
+
['scope', event => { event.paths = ['docs/current/b.md']; }, 'scope-mismatch'],
|
|
444
|
+
['corpus', event => { event.corpusBinding.paths.pop(); }, 'corpus-mismatch'],
|
|
445
|
+
['config', event => { event.corpusBinding.config.digest = '0'.repeat(64); }, 'config-capture-mismatch'],
|
|
446
|
+
['base', event => { event.corpusBinding.baseCommit = '0'.repeat(40); }, 'snapshot-changed'],
|
|
447
|
+
]) {
|
|
448
|
+
const output = accept(mutate);
|
|
449
|
+
assert.equal(output.state, 'unresolved', `${name}: ${JSON.stringify(output.rejected)}`);
|
|
450
|
+
assert.ok(output.rejected.some(item => item.code === expected), `${name}: ${JSON.stringify(output.rejected)}`);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
check('provider keeps replay, interruption, failure, and digest-pin controls', () => {
|
|
455
|
+
const scenario = seedPilotScenario();
|
|
456
|
+
const { capture } = addAssessmentCapture(scenario);
|
|
457
|
+
const identity = providerIdentity(scenario);
|
|
458
|
+
const duplicate = JSON.parse(JSON.stringify(capture));
|
|
459
|
+
duplicate.ref = 'replayed-capture';
|
|
460
|
+
const replay = testOrigin(scenario, [capture, duplicate]).resolveEvent({
|
|
461
|
+
reference: 'replayed-capture#conflict-check', identity,
|
|
462
|
+
});
|
|
463
|
+
assert.ok(replay.diagnostics.some(item => item.code === 'replayed-record'));
|
|
464
|
+
|
|
465
|
+
const interruptedCapture = { ...capture, interrupted: true };
|
|
466
|
+
const interrupted = testOrigin(scenario, [interruptedCapture]).resolveEvent({
|
|
467
|
+
reference: `${capture.ref}#conflict-check`, identity,
|
|
468
|
+
});
|
|
469
|
+
assert.ok(interrupted.diagnostics.some(item => item.code === 'interrupted-not-credited'));
|
|
470
|
+
|
|
471
|
+
addHostCapture(scenario, {
|
|
472
|
+
cwd: scenario.root, session_id: scenario.sessionId, tool_name: 'Bash', tool_use_id: capture.tool_use_id,
|
|
473
|
+
tool_input: { command: capture.command }, error: 'Exit code 1',
|
|
474
|
+
}, 'PostToolUseFailure');
|
|
475
|
+
const failedInvocation = testOrigin(scenario).resolveEvent({
|
|
476
|
+
reference: `${capture.ref}#conflict-check`, identity,
|
|
477
|
+
});
|
|
478
|
+
assert.ok(failedInvocation.diagnostics.some(item => item.code === 'failure-invalidates-mapping'));
|
|
479
|
+
|
|
480
|
+
const stalePinCapture = { ...capture, implementationDigest: '0'.repeat(64) };
|
|
481
|
+
const stalePin = testOrigin(scenario, [stalePinCapture]).resolveEvent({
|
|
482
|
+
reference: `${capture.ref}#conflict-check`, identity,
|
|
483
|
+
});
|
|
484
|
+
assert.ok(stalePin.diagnostics.some(item => item.code === 'implementation-digest-mismatch'));
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
check('a waiver without a successful complete assessment cannot PASS conflict-check', () => {
|
|
488
|
+
const scope = ['docs/current/a.md'];
|
|
489
|
+
const digest = 'a'.repeat(64);
|
|
490
|
+
const baselineDigest = 'b'.repeat(64);
|
|
491
|
+
const output = EVIDENCE.checkDocObligations({
|
|
492
|
+
obligations: [{ id: 'conflict-check', capability: 'conflict-check', strength: 'MUST', applicability: 'applicable', scope: { paths: scope } }],
|
|
493
|
+
artifacts: { [scope[0]]: { digest } }, baseline: { digest: baselineDigest, artifacts: {} }, executionRecords: [],
|
|
494
|
+
reviewRecords: [{ id: 'waiver-1', obligationId: 'conflict-check', kind: 'waiver', reason: 'assessment was complete',
|
|
495
|
+
citedRecordIds: ['missing-assessment'], paths: scope, artifactDigests: { [scope[0]]: digest }, baseDigest: baselineDigest }],
|
|
496
|
+
});
|
|
497
|
+
const result = output.results.find(item => item.obligationId === 'conflict-check');
|
|
498
|
+
assert.ok(result);
|
|
499
|
+
assert.notEqual(result.outcome, 'pass');
|
|
500
|
+
assert.ok(output.diagnostics.some(item => item.code === 'unsupported-waiver'));
|
|
501
|
+
});
|
|
502
|
+
} finally {
|
|
503
|
+
for (const root of roots) fs.rmSync(root, { recursive: true, force: true });
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
console.log(`\nPR-A conflict-check summary: ${passed}/${passed + failed} passed, ${failed} failed`);
|
|
507
|
+
process.exitCode = failed ? 1 : 0;
|
|
@@ -1171,7 +1171,8 @@ Candidate classification follows command roles, not arbitrary text matches. A di
|
|
|
1171
1171
|
| `link-check` | A separate projection of the same invocation, scoped to the expected paths: no broken, skipped, unreadable or unsupported link syntax |
|
|
1172
1172
|
| `derived-regenerate` | An observed successful generation **and** an observed successful pure comparison, bound to the same source and config identity and to the same generated bytes. `--generation-status ok` is caller-reported and cannot establish generation alone |
|
|
1173
1173
|
| `quality-review` | A completed review from a correlated host `agent_id` distinct from the author session and every executor. Executors are the author session and the principals of registered doc-check runs in the task, which are the execution evidence a doc obligation uses (see the rationale below). An Agent/Task result must name the same `agent_id`. The verdict needs an explicit approve line, a non-empty summary and one `Reviewed artifact: <path> sha256:<hex>` line per reviewed path, which must match the candidate. SubagentStop alone is never approval |
|
|
1174
|
-
| `conflict-check
|
|
1174
|
+
| `conflict-check` | A registered `check --conflict-assessment` runs lifecycle lint with `--all` and requires an exact `--base` commit SHA; the corpus collection must be complete and lint must have no error diagnostics. The event keeps the exact obligation `normativePaths` as its scope and binds the full corpus paths/digests, config bytes and base commit separately to the workflow snapshot. This checks recorded and structural conflicts only; prose contradictions stay with independent `quality-review`. Plain `check` earns no conflict credit |
|
|
1175
|
+
| `requirement-sync` | Stay unresolved. F4 gives it no credit |
|
|
1175
1176
|
| SHOULD reminders | Never earn credit, and their absence never withholds it |
|
|
1176
1177
|
|
|
1177
1178
|
### Live capture gate
|
|
@@ -39,6 +39,10 @@ permissions stay authoritative; an explicit required `actionGate` hint still gat
|
|
|
39
39
|
This is a deterministic mechanism change, not live-host proof, and does not
|
|
40
40
|
upgrade any platform row's live-host status.
|
|
41
41
|
|
|
42
|
+
### Full-corpus conflict-check (mechanism only)
|
|
43
|
+
|
|
44
|
+
The registered `doc-check check --conflict-assessment` mode runs document lifecycle lint across the full governed and legacy corpus and requires `--base` as an exact commit SHA. It emits `conflict-check` only when collection is complete and there are no lifecycle errors. Its event preserves the exact obligation scope separately from full-corpus paths and digests, config bytes and base commit, then checks those bindings against the workflow snapshot. The acceptance is limited to recorded and structural conflicts; prose contradictions remain with independent `quality-review`. Plain `check` never earns this credit, and OpenCode still refuses the capability. Deterministic mechanism coverage does not establish live-host behavior or change the platform matrix's live-verification status.
|
|
45
|
+
|
|
42
46
|
## Current capability summary
|
|
43
47
|
|
|
44
48
|
| Platform surface | Standalone skills | Plugin support | Hooks/lifecycle | Project scope | Global scope | Current evidence boundary |
|