@optitransfer/proofline 0.0.0-stage → 0.2.0-next.1
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/AGENTS.md +32 -0
- package/CHANGELOG.md +56 -0
- package/CONTRIBUTING.md +29 -0
- package/LICENSE +154 -0
- package/NOTICE +10 -0
- package/PATENTS-NONASSERT.md +1 -0
- package/README.md +285 -2
- package/bin/proofline.mjs +292 -0
- package/docs/00-index.md +43 -0
- package/docs/01-scope.md +70 -0
- package/docs/02-requirements.md +64 -0
- package/docs/03-domain-model.md +43 -0
- package/docs/04-architecture.md +79 -0
- package/docs/05-lifecycle.md +47 -0
- package/docs/06-verification.md +57 -0
- package/docs/07-evidence.md +69 -0
- package/docs/08-adapters.md +61 -0
- package/docs/09-security.md +64 -0
- package/docs/10-conformance.md +59 -0
- package/docs/11-development-pipeline.md +50 -0
- package/docs/12-roadmap.md +104 -0
- package/docs/13-interdependencies.md +53 -0
- package/docs/14-metrics.md +40 -0
- package/docs/adr/0001-product-boundary.md +28 -0
- package/docs/adr/0002-evidence-and-gates.md +22 -0
- package/docs/adr/0003-standalone-first.md +18 -0
- package/docs/receipt.md +25 -0
- package/package.json +60 -4
- package/src/cache.mjs +81 -0
- package/src/checks.mjs +276 -0
- package/src/claims.mjs +60 -0
- package/src/contract.mjs +404 -0
- package/src/engine.mjs +378 -0
- package/src/evidence.mjs +45 -0
- package/src/forbidden.mjs +57 -0
- package/src/guided.mjs +240 -0
- package/src/hook-stop.mjs +105 -0
- package/src/index.mjs +431 -0
- package/src/infer.mjs +173 -0
- package/src/inference.json +20 -0
- package/src/isolate.mjs +123 -0
- package/src/mcp.mjs +70 -0
- package/src/receipt.mjs +17 -0
- package/src/signature.mjs +110 -0
- package/src/util.mjs +92 -0
- package/src/vault.mjs +109 -0
- package/src/verify-receipt.mjs +109 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Proofline CLI. Exit codes: 0 PASS/ok, 1 FAIL, 2 BLOCKED, 3 usage error.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import * as P from '../src/index.mjs';
|
|
6
|
+
import { verifyChain, readLog } from '../src/evidence.mjs';
|
|
7
|
+
import { verifyReceipt } from '../src/verify-receipt.mjs';
|
|
8
|
+
import { buildReceipt } from '../src/receipt.mjs';
|
|
9
|
+
import { connectClaudeCode, hookStop } from '../src/hook-stop.mjs';
|
|
10
|
+
import * as G from '../src/guided.mjs';
|
|
11
|
+
import * as S from '../src/signature.mjs';
|
|
12
|
+
|
|
13
|
+
const argv = process.argv.slice(2);
|
|
14
|
+
const flags = {}; const pos = [];
|
|
15
|
+
const SWITCHES = ['fresh']; // never take a value: `verify --fresh T` keeps T as the task
|
|
16
|
+
for (let i = 0; i < argv.length; i++) {
|
|
17
|
+
const a = argv[i];
|
|
18
|
+
if (a.startsWith('--')) { const [k, v] = a.slice(2).split('='); if (v !== undefined) flags[k] = v; else if (!SWITCHES.includes(k) && argv[i + 1] && !argv[i + 1].startsWith('--')) flags[k] = argv[++i]; else flags[k] = true; }
|
|
19
|
+
else pos.push(a);
|
|
20
|
+
}
|
|
21
|
+
const root = flags.root ? flags.root : P.findRoot();
|
|
22
|
+
const json = !!flags.json;
|
|
23
|
+
const print = (obj, text) => { process.stdout.write((json || text === undefined ? JSON.stringify(obj, null, 2) : text) + '\n'); };
|
|
24
|
+
const code = (d) => (d === 'PASS' ? 0 : d === 'BLOCKED' ? 2 : 1);
|
|
25
|
+
|
|
26
|
+
function ownerGate(root) {
|
|
27
|
+
const r = S.verifyContractSignature(root);
|
|
28
|
+
if (!r.armed || r.ok) return true;
|
|
29
|
+
console.error(`proofline: contract not owner-signed\n${r.detail}`);
|
|
30
|
+
process.exitCode = 2;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const owner = () => { if (flags.owner === true) throw new Error('--owner needs a name'); return flags.owner; };
|
|
34
|
+
const base = () => { if (flags.base === true) throw new Error('--base needs a revision'); return flags.base; };
|
|
35
|
+
const claims = () => flags['claims-file'] ? fs.readFileSync(flags['claims-file'], 'utf8') : flags.claims === true ? fs.readFileSync(0, 'utf8') : flags.claims;
|
|
36
|
+
|
|
37
|
+
const HELP = `proofline: independent V&V and delivery controller
|
|
38
|
+
setup [--agent claude-code|none] init, hooks, the owner key, approvals at .proofline/approvals
|
|
39
|
+
task "<plain words>" write a proposal from those words and print its hash
|
|
40
|
+
contract approve [<hash>] show the diff; on y write the marker and apply the bytes
|
|
41
|
+
contract sign write proofline.json.sig with the owner key
|
|
42
|
+
contract verify-sig check the owner signature
|
|
43
|
+
contract propose write proposals/<sha256>.diff and print the hash
|
|
44
|
+
watch [TASK] print the active task until the gate is PASS
|
|
45
|
+
init [--no-hooks] [--approvals <dir>] create proofline.json, .proofline/, install git hooks;
|
|
46
|
+
--approvals sets the directory of owner markers (re-runnable)
|
|
47
|
+
contract create --from spec.json add a task contract (or --id --title [--phase] [--owner n] [--depends a,b] [--scope g1,g2])
|
|
48
|
+
contract scaffold --title ... --scope a,b [--kind feature|docs|release] [--id TASK]
|
|
49
|
+
print a task with the standard criteria and stop rules
|
|
50
|
+
contract merge <fragment.json> [--owners a=b,c=d]
|
|
51
|
+
append tasks and new owner rules; prints the sha256
|
|
52
|
+
contract coverage <REQUIREMENTS.md> [--contracts a.json,b.json]
|
|
53
|
+
one line per requirement id, or MISSING
|
|
54
|
+
contract validate | show validate also warns when a grep already matches on the base revision
|
|
55
|
+
next [--owner <name>] [--base <rev>] the single next task with its full brief (marks it active);
|
|
56
|
+
--owner limits the lane; --base re-pins the base
|
|
57
|
+
brief <TASK> [--owner <name>] [--base <rev>] brief for a task, with the base verify will use
|
|
58
|
+
(refuses another owner's task)
|
|
59
|
+
verify <TASK> [--claims "line"] [--base <rev>] [--fresh] run every check independently; prints corrective prompt
|
|
60
|
+
gate <TASK>|--all [--claims ...] [--base <rev>] [--fresh] release decision PASS/FAIL/BLOCKED + report (.proofline/reports)
|
|
61
|
+
--base: the revision the task started from (needed in a fresh clone
|
|
62
|
+
unless \`next\` pinned it or HEAD carries a Proofline-Base: trailer)
|
|
63
|
+
--fresh: run every check again, ignoring PASS results reused under
|
|
64
|
+
defaults.reuse_results
|
|
65
|
+
report <TASK> latest gate report
|
|
66
|
+
status [--owner <name>] task + phase status, evidence chain
|
|
67
|
+
evidence record <type> --data '{..}' | evidence verify | evidence log
|
|
68
|
+
verify-receipt <envelope> [--keys <dir>] [--sigstore] [--recompute --base <sha>]
|
|
69
|
+
exit 0 verified, 1 failed or differs, 2 unknown key, 3 usage
|
|
70
|
+
receipt [--role <name>] [--base <rev>] [--vault]
|
|
71
|
+
one receipt for the revision; --vault writes the note when a daemon answers
|
|
72
|
+
hook pre-commit|pre-push (used by git hooks)
|
|
73
|
+
hook stop read a Stop hook payload on stdin; one receipt for new commits, or silence
|
|
74
|
+
connect claude-code merge a Stop hook into ~/.claude/settings.json, after any hook already there
|
|
75
|
+
mcp MCP server on stdio
|
|
76
|
+
Global: --json --root <dir> Exit: 0 PASS, 1 FAIL, 2 BLOCKED, 3 usage`;
|
|
77
|
+
|
|
78
|
+
const GLOBAL_FLAGS = ['json', 'root'];
|
|
79
|
+
const ACCEPTED_FLAGS = {
|
|
80
|
+
setup: ['agent'],
|
|
81
|
+
task: [],
|
|
82
|
+
init: ['approvals', 'no-hooks'],
|
|
83
|
+
'contract create': ['from', 'id', 'title', 'phase', 'owner', 'description', 'depends', 'scope'],
|
|
84
|
+
'contract merge': ['owners'],
|
|
85
|
+
'contract coverage': ['contracts'],
|
|
86
|
+
'contract scaffold': ['title', 'scope', 'kind', 'id'],
|
|
87
|
+
'contract approve': [],
|
|
88
|
+
'contract sign': [],
|
|
89
|
+
'contract verify-sig': [],
|
|
90
|
+
'contract propose': [],
|
|
91
|
+
'contract validate': ['base'],
|
|
92
|
+
'contract show': [],
|
|
93
|
+
next: ['owner', 'base'],
|
|
94
|
+
brief: ['owner', 'base'],
|
|
95
|
+
verify: ['claims', 'claims-file', 'base', 'fresh'],
|
|
96
|
+
gate: ['claims', 'claims-file', 'base', 'fresh', 'all', 'ci', 'skip-pushed'],
|
|
97
|
+
report: [],
|
|
98
|
+
status: ['owner'],
|
|
99
|
+
'evidence record': ['data'],
|
|
100
|
+
'evidence verify': [],
|
|
101
|
+
'evidence log': [],
|
|
102
|
+
'verify-receipt': ['keys', 'sigstore', 'recompute', 'base'],
|
|
103
|
+
receipt: ['base', 'role', 'task', 'vault'],
|
|
104
|
+
hook: [],
|
|
105
|
+
watch: [],
|
|
106
|
+
connect: [],
|
|
107
|
+
mcp: [],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
function refuseUnknownFlags(cmd, sub) {
|
|
111
|
+
if (!cmd) return;
|
|
112
|
+
const key = sub ? `${cmd} ${sub}` : cmd;
|
|
113
|
+
const accepted = ACCEPTED_FLAGS[key] ?? ACCEPTED_FLAGS[cmd];
|
|
114
|
+
if (!accepted) return;
|
|
115
|
+
const allow = new Set([...GLOBAL_FLAGS, ...accepted]);
|
|
116
|
+
const bad = Object.keys(flags).filter((k) => !allow.has(k));
|
|
117
|
+
if (!bad.length) return;
|
|
118
|
+
const named = bad.map((k) => `--${k}`).join(', ');
|
|
119
|
+
const ok = [...allow].sort().map((k) => `--${k}`).join(', ');
|
|
120
|
+
console.error(`proofline: unknown flag ${named}\naccepted: ${ok}`);
|
|
121
|
+
process.exit(2);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const [cmd, sub] = pos;
|
|
126
|
+
refuseUnknownFlags(cmd, sub);
|
|
127
|
+
switch (cmd) {
|
|
128
|
+
case 'setup': {
|
|
129
|
+
const agent = flags.agent === undefined ? 'none' : flags.agent;
|
|
130
|
+
if (agent === true) throw new Error('unknown agent\nNext step: proofline setup --agent none');
|
|
131
|
+
process.stdout.write(G.setup(root, { agent: String(agent) }));
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case 'task': {
|
|
135
|
+
process.stdout.write(G.task(root, pos.slice(1).join(' ')));
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'init': {
|
|
139
|
+
if (flags.approvals === true) throw new Error('--approvals needs a directory');
|
|
140
|
+
const r = P.init(root, { hooks: !flags['no-hooks'], approvals: flags.approvals });
|
|
141
|
+
print(r, `initialised ${root}${r.made.map((m) => `\n ${m}`).join('')}`);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'contract': {
|
|
145
|
+
if (sub === 'create') {
|
|
146
|
+
const spec = flags.from ? JSON.parse(fs.readFileSync(flags.from, 'utf8')) : { id: flags.id, title: flags.title, phase: flags.phase, owner: owner(), description: flags.description || '', depends_on: flags.depends ? String(flags.depends).split(',') : [], scope: flags.scope ? String(flags.scope).split(',') : undefined, criteria: [] };
|
|
147
|
+
const specs = Array.isArray(spec) ? spec : spec.tasks || [spec];
|
|
148
|
+
const out = specs.map((s) => P.contractCreate(root, s));
|
|
149
|
+
print(out, out.map((o) => `${o.task.id}: ${o.valid ? 'valid' : 'INCOMPLETE — ' + o.problems.join('; ')}`).join('\n'));
|
|
150
|
+
process.exitCode = out.every((o) => o.valid) ? 0 : 1;
|
|
151
|
+
} else if (sub === 'validate') {
|
|
152
|
+
const loaded = P.C.load(root);
|
|
153
|
+
const { errors, warnings } = P.C.lint(loaded, { root, base: base() || P.C.defaultBase(loaded) });
|
|
154
|
+
const lines = [...errors, ...warnings.map((w) => `warning: ${w}`)];
|
|
155
|
+
print({ valid: !errors.length, problems: errors, warnings }, lines.length ? lines.join('\n') : 'contract valid');
|
|
156
|
+
process.exitCode = errors.length ? 1 : 0;
|
|
157
|
+
} else if (sub === 'merge') {
|
|
158
|
+
const frag = pos[2];
|
|
159
|
+
if (!frag) throw new Error('contract merge needs a fragment file');
|
|
160
|
+
const owners = flags.owners && flags.owners !== true ? String(flags.owners) : '';
|
|
161
|
+
const out = P.C.mergeContract(root, path.resolve(root, frag), { owners });
|
|
162
|
+
print(out, out.sha256);
|
|
163
|
+
} else if (sub === 'coverage') {
|
|
164
|
+
const req = pos[2];
|
|
165
|
+
if (!req) throw new Error('contract coverage needs a requirements file');
|
|
166
|
+
const contracts = flags.contracts && flags.contracts !== true ? String(flags.contracts).split(',').map((s) => s.trim()).filter(Boolean) : [];
|
|
167
|
+
const out = P.C.coverageReport(root, path.resolve(root, req), { contracts: contracts.map((f) => path.resolve(root, f)) });
|
|
168
|
+
print(out, out.lines.join('\n'));
|
|
169
|
+
process.exitCode = out.ok ? 0 : 1;
|
|
170
|
+
} else if (sub === 'scaffold') {
|
|
171
|
+
if (typeof flags.title !== 'string') throw new Error('scaffold needs --title');
|
|
172
|
+
if (typeof flags.scope !== 'string') throw new Error('scaffold needs --scope');
|
|
173
|
+
const kind = flags.kind && flags.kind !== true ? String(flags.kind) : 'feature';
|
|
174
|
+
const id = typeof flags.id === 'string' ? flags.id : 'TASK';
|
|
175
|
+
const task = P.C.scaffoldTask({ title: flags.title, scope: flags.scope, kind, id });
|
|
176
|
+
print(task, JSON.stringify(task, null, 2));
|
|
177
|
+
}
|
|
178
|
+
else if (sub === 'sign') process.stdout.write(S.signContract(root));
|
|
179
|
+
else if (sub === 'verify-sig') {
|
|
180
|
+
const r = S.verifyContractSignature(root);
|
|
181
|
+
if (!r.ok) { console.error(`proofline: contract not owner-signed\n${r.detail}`); process.exitCode = 2; }
|
|
182
|
+
else process.stdout.write(`${r.detail}\n`);
|
|
183
|
+
}
|
|
184
|
+
else if (sub === 'propose') process.stdout.write(S.proposeContract(root));
|
|
185
|
+
else if (sub === 'approve') {
|
|
186
|
+
process.stdout.write(G.approve(root, pos[2]));
|
|
187
|
+
}
|
|
188
|
+
else if (sub === 'show') print(P.C.load(root));
|
|
189
|
+
else { console.error(HELP); process.exitCode = 3; }
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
case 'next': {
|
|
193
|
+
if (!ownerGate(root)) break;
|
|
194
|
+
const n = P.taskNext(root, { owner: owner(), ...(flags.base === undefined ? {} : { base: base() }) }); print(n, n.brief || n.message); break;
|
|
195
|
+
}
|
|
196
|
+
case 'brief': {
|
|
197
|
+
if (!ownerGate(root)) break;
|
|
198
|
+
const b = P.taskBrief(root, sub, { owner: owner(), base: base() }); print({ brief: b }, b); break;
|
|
199
|
+
}
|
|
200
|
+
case 'verify': case 'gate': {
|
|
201
|
+
if (!ownerGate(root)) break;
|
|
202
|
+
const requested = sub;
|
|
203
|
+
const settled = P.settleLanded(root);
|
|
204
|
+
if (settled && !flags.all && !flags.ci && (!requested || requested === settled.task)) {
|
|
205
|
+
print({ landed: true, task: settled.task }, settled.message);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
const c = P.C.load(root);
|
|
209
|
+
const ids = flags.all ? c.tasks.map((t) => t.id) : flags.ci ? (c.ci?.tasks || []) : [requested || P.activeTask(root)].filter(Boolean);
|
|
210
|
+
if (!ids.length) {
|
|
211
|
+
if (flags.ci) {
|
|
212
|
+
// Clearing ci.tasks is still a contract change. Refuse it before the empty-list short-circuit.
|
|
213
|
+
const refusal = P.E.unapprovedContractChange(root, base());
|
|
214
|
+
if (refusal) { print({ decision: 'BLOCKED', blocked: [refusal] }, `${refusal}\n\nDECISION: BLOCKED`); process.exitCode = 2; break; }
|
|
215
|
+
const e = P.C.validate(c); print({ decision: e.length ? 'FAIL' : 'PASS', problems: e }, e.length ? e.join('\n') : 'contract valid; no ci.tasks'); process.exitCode = e.length ? 1 : 0; break;
|
|
216
|
+
}
|
|
217
|
+
throw new Error('which task? (proofline next)');
|
|
218
|
+
}
|
|
219
|
+
const outs = ids.map((id) => P.E.gate(root, id, { claims: claims(), base: base(), fresh: flags.fresh === true, skipTypes: flags['skip-pushed'] || flags.ci ? ['pushed'] : [] }));
|
|
220
|
+
const worst = outs.some((o) => o.decision === 'BLOCKED') ? 'BLOCKED' : outs.some((o) => o.decision === 'FAIL') ? 'FAIL' : 'PASS';
|
|
221
|
+
const note = settled ? `${settled.message}\n\n` : '';
|
|
222
|
+
print(outs.length === 1 ? outs[0] : { decision: worst, gates: outs.map((o) => ({ task: o.task, decision: o.decision, failing: o.failing, blocked: o.blocked })) },
|
|
223
|
+
note + outs.map((o) => `== ${o.task}: ${o.decision}\n base: ${o.base_ref} = ${o.base?.slice(0, 8) || 'not found'} (${o.base_source})\n${o.results.map((r) => ` ${r.status.toUpperCase().padEnd(5)} ${r.id} [${r.type}] ${r.detail.split('\n')[0]}`).join('\n')}${o.forbidden.length ? '\n forbidden: ' + o.forbidden.map((f) => `${f.rule}@${f.file}`).join(', ') : ''}\n report: ${o.report}\n\n${o.corrective_prompt}`).join('\n\n') + `\n\nDECISION: ${worst}`);
|
|
224
|
+
process.exitCode = code(worst);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case 'report': { const r = P.report(root, sub); print(r, r.markdown || r.error); process.exitCode = r.error ? 1 : 0; break; }
|
|
228
|
+
case 'status': { const s = P.status(root, { owner: owner() }); print(s, Object.entries(s.tasks).map(([k, v]) => `${k.padEnd(14)} ${v.status}`).join('\n') + '\n\n' + s.phases.map((p) => `${p.phase.padEnd(10)} ${p.verified}/${p.tasks}${p.open ? ' (open)' : ''}`).join('\n') + `\n\nevidence chain: ${s.chain.ok ? 'intact' : 'BROKEN'} (${s.chain.entries ?? '?'} entries); active: ${s.active || '-'}`); break; }
|
|
229
|
+
case 'evidence': {
|
|
230
|
+
if (sub === 'record') print(P.evidenceRecord(root, pos[2] || 'note', flags.data ? JSON.parse(flags.data) : {}));
|
|
231
|
+
else if (sub === 'verify') { const v = verifyChain(root); print(v, v.ok ? `chain intact: ${v.entries} entries, head ${v.head.slice(0, 16)}` : `CHAIN BROKEN at ${v.at}: ${v.reason}`); process.exitCode = v.ok ? 0 : 2; }
|
|
232
|
+
else if (sub === 'log') print(readLog(root));
|
|
233
|
+
else { console.error(HELP); process.exitCode = 3; }
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case 'verify-receipt': {
|
|
237
|
+
if (!sub) { console.error(HELP); process.exitCode = 3; break; }
|
|
238
|
+
const r = await verifyReceipt(sub, {
|
|
239
|
+
root, keys: flags.keys === true ? undefined : flags.keys, sigstore: flags.sigstore === true,
|
|
240
|
+
recompute: flags.recompute === true, base: flags.base === true ? undefined : flags.base,
|
|
241
|
+
});
|
|
242
|
+
print(r, r.message);
|
|
243
|
+
process.exitCode = r.exitCode;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case 'receipt': {
|
|
247
|
+
const r = await buildReceipt(root, {
|
|
248
|
+
base: flags.base === true ? undefined : flags.base,
|
|
249
|
+
role: flags.role === true ? undefined : flags.role,
|
|
250
|
+
task: flags.task === true ? undefined : flags.task,
|
|
251
|
+
vault: flags.vault === true,
|
|
252
|
+
});
|
|
253
|
+
print(r);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case 'hook': {
|
|
257
|
+
if ((sub === 'pre-commit' || sub === 'pre-push') && !ownerGate(root)) break;
|
|
258
|
+
if (sub === 'stop') {
|
|
259
|
+
let payload = {};
|
|
260
|
+
try {
|
|
261
|
+
const raw = fs.readFileSync(0, 'utf8');
|
|
262
|
+
if (raw.trim()) payload = JSON.parse(raw);
|
|
263
|
+
} catch { break; }
|
|
264
|
+
const where = flags.root ? root : (payload.cwd || root);
|
|
265
|
+
const out = await hookStop(where, payload);
|
|
266
|
+
if (out.receipts.length === 1) print(out.receipts[0]);
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
let lines;
|
|
270
|
+
if (sub === 'pre-push' && !process.stdin.isTTY) lines = fs.readFileSync(0, 'utf8').split('\n').filter((l) => l.trim());
|
|
271
|
+
const pushUrl = typeof pos[3] === 'string' ? pos[3] : undefined;
|
|
272
|
+
const r = sub === 'pre-commit' ? P.hookPreCommit(root) : sub === 'pre-push' ? P.hookPrePush(root, { lines, pushUrl }) : null;
|
|
273
|
+
if (!r) { process.exitCode = 3; break; }
|
|
274
|
+
if (!r.ok) { console.error(`proofline ${sub}: REFUSED\n${r.msg}\n(There is no bypass. Fix the cause.)`); process.exitCode = 1; }
|
|
275
|
+
else if (r.msg) console.error(r.msg);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case 'watch': {
|
|
279
|
+
process.exitCode = G.watch(root, sub);
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case 'connect': {
|
|
283
|
+
if (sub !== 'claude-code') { console.error(HELP); process.exitCode = 3; break; }
|
|
284
|
+
const r = connectClaudeCode();
|
|
285
|
+
if (!r.ok) { console.error(`proofline connect: ${r.reason}`); process.exitCode = 1; break; }
|
|
286
|
+
print(r, `hook installed in ${r.file}`);
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case 'mcp': await import('../src/mcp.mjs').then((m) => m.serve(root)); break;
|
|
290
|
+
default: console.log(HELP); process.exitCode = cmd ? 3 : 0;
|
|
291
|
+
}
|
|
292
|
+
} catch (e) { console.error(`proofline: ${e.message}`); process.exitCode = 3; }
|
package/docs/00-index.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Documentation index
|
|
2
|
+
|
|
3
|
+
This is the source of truth for the Proofline design. Requirements are normative where marked **MUST**; roadmap dates and implementation details remain provisional until explicitly accepted.
|
|
4
|
+
|
|
5
|
+
## Product and design
|
|
6
|
+
|
|
7
|
+
- [01 — Scope and product brief](01-scope.md)
|
|
8
|
+
- [02 — Requirements](02-requirements.md)
|
|
9
|
+
- [03 — Domain model and terminology](03-domain-model.md)
|
|
10
|
+
- [04 — Architecture](04-architecture.md)
|
|
11
|
+
- [05 — Task lifecycle and state machine](05-lifecycle.md)
|
|
12
|
+
- [06 — Verification and reconciliation](06-verification.md)
|
|
13
|
+
- [07 — Evidence model](07-evidence.md)
|
|
14
|
+
- [08 — Agent adapters](08-adapters.md)
|
|
15
|
+
- [09 — Security and trust boundaries](09-security.md)
|
|
16
|
+
|
|
17
|
+
## Delivery and validation
|
|
18
|
+
|
|
19
|
+
- [10 — Test and conformance strategy](10-conformance.md)
|
|
20
|
+
- [11 — Development and release pipeline](11-development-pipeline.md)
|
|
21
|
+
- [12 — Roadmap](12-roadmap.md)
|
|
22
|
+
- [13 — Interdependencies](13-interdependencies.md)
|
|
23
|
+
- [14 — Metrics and evaluation](14-metrics.md)
|
|
24
|
+
|
|
25
|
+
## Decisions and examples
|
|
26
|
+
|
|
27
|
+
- [Architecture decision records](adr/)
|
|
28
|
+
- [Example task contract](../examples/demo-contract/proofline.json)
|
|
29
|
+
- [Change log](../CHANGELOG.md)
|
|
30
|
+
|
|
31
|
+
## Status vocabulary
|
|
32
|
+
|
|
33
|
+
| Status | Meaning |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `planned` | Defined but not implemented |
|
|
36
|
+
| `in_progress` | Active implementation; not yet accepted |
|
|
37
|
+
| `implemented` | Code exists; tests may still be incomplete |
|
|
38
|
+
| `verified` | Required checks passed and evidence is available for the identified revision |
|
|
39
|
+
| `blocked` | A mandatory gate or dependency prevents progress |
|
|
40
|
+
| `partial` | Some requirements are met; remaining gaps are explicit |
|
|
41
|
+
| `unverified` | Evidence is absent, stale, inconclusive, or insufficient |
|
|
42
|
+
|
|
43
|
+
A status label is not a substitute for evidence. In particular, `implemented` is not equivalent to `verified`.
|
package/docs/01-scope.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# 01 — Scope and product brief
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
AI agents can complete a plausible-looking task while omitting requirements, changing behaviour outside the requested scope, skipping mandatory checks, relying on stale assumptions, or overstating what was validated. The same agent that performs the work is not a sufficient independent authority on whether the work meets its contract.
|
|
6
|
+
|
|
7
|
+
## Product statement
|
|
8
|
+
|
|
9
|
+
**Proofline is an independent verification and delivery gate for AI-agent work.** It keeps the task contract separate from implementation, records evidence, tests the implementation against the contract, reconciles discrepancies, and reports what can legitimately be claimed at delivery.
|
|
10
|
+
|
|
11
|
+
## Target user
|
|
12
|
+
|
|
13
|
+
A developer, technical lead, researcher, or team using coding agents and needing reliable control over scope, validation, and delivery—not just more generated code.
|
|
14
|
+
|
|
15
|
+
## Primary jobs to be done
|
|
16
|
+
|
|
17
|
+
1. Turn a request into a reviewable contract with explicit acceptance criteria.
|
|
18
|
+
2. Observe work without assuming that narrated progress is accurate.
|
|
19
|
+
3. Map each requirement to a change, test, or explicit exception.
|
|
20
|
+
4. Execute or independently evaluate required checks under controlled policy.
|
|
21
|
+
5. Detect missing coverage, scope drift, contradictory evidence, and unsupported claims.
|
|
22
|
+
6. Provide bounded, specific corrective feedback.
|
|
23
|
+
7. Produce an auditable delivery decision and portable report.
|
|
24
|
+
|
|
25
|
+
## Core workflow
|
|
26
|
+
|
|
27
|
+
1. **Contract:** establish requirements, constraints, exclusions, risk tier, required evidence, and approval rules.
|
|
28
|
+
2. **Observe:** capture permitted lifecycle events, commands, diffs, and revision identifiers.
|
|
29
|
+
3. **Verify:** run policy-approved checks and independent challenges.
|
|
30
|
+
4. **Reconcile:** compare requested, implemented, tested, and reported outcomes.
|
|
31
|
+
5. **Correct or escalate:** return specific gaps, retry within configured limits, or request human judgement.
|
|
32
|
+
6. **Deliver:** produce status, residual risks, exceptions, and evidence references.
|
|
33
|
+
|
|
34
|
+
## MVP — in scope
|
|
35
|
+
|
|
36
|
+
- Local CLI and local task/session state.
|
|
37
|
+
- Versioned task-contract schema.
|
|
38
|
+
- One deeply supported agent adapter, chosen after a spike.
|
|
39
|
+
- Event ingestion with schema validation.
|
|
40
|
+
- Evidence ledger with immutable record IDs and revision/environment context.
|
|
41
|
+
- Configurable command/check runner with allowlists and timeouts.
|
|
42
|
+
- Requirement-to-evidence traceability.
|
|
43
|
+
- Reconciliation rules and explicit terminal states.
|
|
44
|
+
- Bounded corrective feedback and loop detection.
|
|
45
|
+
- Markdown and JSON delivery reports.
|
|
46
|
+
- Unit, integration, adapter-contract, and end-to-end tests.
|
|
47
|
+
- A standalone mode with no required hosted service or HiveVault dependency.
|
|
48
|
+
|
|
49
|
+
## Explicitly out of scope for MVP
|
|
50
|
+
|
|
51
|
+
- A general-purpose multi-agent orchestration platform.
|
|
52
|
+
- A hosted SaaS control plane or central telemetry service.
|
|
53
|
+
- A mandatory HiveVault integration.
|
|
54
|
+
- Automatic approval of production deployment, financial actions, or other irreversible changes.
|
|
55
|
+
- A claim that arbitrary natural-language work can be fully proven correct.
|
|
56
|
+
- A dashboard before the CLI and evidence model are validated.
|
|
57
|
+
- A universal policy pack for every programming language and domain.
|
|
58
|
+
- An agent marketplace or broad adapter matrix before one adapter is reliable.
|
|
59
|
+
|
|
60
|
+
## Product boundaries
|
|
61
|
+
|
|
62
|
+
- The implementing agent proposes and changes; Proofline evaluates against the accepted contract.
|
|
63
|
+
- The user/team owns the contract and can explicitly approve changes to it.
|
|
64
|
+
- The verifier may identify a likely defect, but domain-specific correctness still depends on the available oracle and evidence.
|
|
65
|
+
- An evidence record establishes what was recorded and under what conditions; it does not automatically establish that the chosen test was sufficient.
|
|
66
|
+
- HiveVault may provide prior decisions, but prior context cannot satisfy a current-run verification gate by itself.
|
|
67
|
+
|
|
68
|
+
## Success criteria for the first pilot
|
|
69
|
+
|
|
70
|
+
The MVP is worth continuing only if a real workflow shows that it catches meaningful omissions or false completion claims while keeping false blocks, latency, and interruptions tolerable. See [metrics](14-metrics.md).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# 02 — Requirements
|
|
2
|
+
|
|
3
|
+
This is the initial normative requirements baseline. IDs are stable references for issues, tests, reports, and changes. `MUST` is mandatory for MVP unless explicitly re-scoped through an ADR and changelog entry.
|
|
4
|
+
|
|
5
|
+
## Functional requirements
|
|
6
|
+
|
|
7
|
+
| ID | Priority | Requirement | Acceptance evidence |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| REQ-001 | MUST | The system MUST create or load a versioned task contract before enforcement. | Contract schema tests; invalid-contract rejection |
|
|
10
|
+
| REQ-002 | MUST | The contract MUST express acceptance criteria, exclusions, constraints, required checks, and risk/approval policy. | Schema and contract round-trip tests |
|
|
11
|
+
| REQ-003 | MUST | The system MUST preserve the accepted contract separately from agent-authored task notes. | Mutation/tampering tests |
|
|
12
|
+
| REQ-004 | MUST | The observer MUST ingest supported adapter events and validate event schema/version. | Adapter conformance suite |
|
|
13
|
+
| REQ-005 | MUST | Every evidence record MUST identify its source, timestamp, task/run, revision where applicable, and result state. | Evidence schema tests |
|
|
14
|
+
| REQ-006 | MUST | The system MUST distinguish `passed`, `failed`, `skipped`, `not_run`, and `inconclusive`. | State-transition tests |
|
|
15
|
+
| REQ-007 | MUST | Each acceptance criterion MUST resolve to evidence, an explicit exception, or an unresolved gap. | Traceability report tests |
|
|
16
|
+
| REQ-008 | MUST | Verification MUST be able to run configured checks independently of the implementing agent's claims. | End-to-end test with deliberately false claim |
|
|
17
|
+
| REQ-009 | MUST | The system MUST compare requested scope, observed changes, checks, and delivery claims. | Scope-drift and reconciliation fixtures |
|
|
18
|
+
| REQ-010 | MUST | Delivery status MUST distinguish `verified`, `partial`, `blocked`, and `unverified`. | Decision matrix tests |
|
|
19
|
+
| REQ-011 | MUST | Mandatory failed, skipped, stale, or absent evidence MUST NOT be reported as verified. | Negative delivery-gate tests |
|
|
20
|
+
| REQ-012 | MUST | Corrective feedback MUST cite the gap, requirement ID, supporting evidence, and next action. | Feedback contract tests |
|
|
21
|
+
| REQ-013 | MUST | Corrective retries MUST be bounded and repeated-loop detection MUST exist. | Retry-limit and loop fixtures |
|
|
22
|
+
| REQ-014 | MUST | The system MUST emit human-readable and machine-readable reports. | Snapshot/schema tests |
|
|
23
|
+
| REQ-015 | MUST | The system MUST work without any external context store or network connection for local workflows. | Offline integration test |
|
|
24
|
+
| REQ-016 | MUST | The system MUST record human-approved exceptions with actor, rationale, scope, and timestamp. | Exception audit tests |
|
|
25
|
+
| REQ-017 | SHOULD | The system SHOULD support baseline and final revision comparison. | Git integration tests |
|
|
26
|
+
| REQ-018 | SHOULD | The system SHOULD support report export and verification replay. | Replay integration test |
|
|
27
|
+
| REQ-019 | SHOULD | The system SHOULD expose a stable adapter contract for future agent integrations. | Contract test harness |
|
|
28
|
+
| REQ-020 | COULD | The system COULD integrate with an external context store for prior decisions and accepted exceptions. | Optional integration tests |
|
|
29
|
+
|
|
30
|
+
## Non-functional requirements
|
|
31
|
+
|
|
32
|
+
| ID | Requirement | Initial acceptance target |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| NFR-001 | Local-first | Core contract, evidence, and report path works offline. |
|
|
35
|
+
| NFR-002 | Deterministic decisions | Same contract, policy, evidence set, and verifier version produce the same gate decision. |
|
|
36
|
+
| NFR-003 | Explainability | Every block identifies one or more unmet gates and the evidence behind the decision. |
|
|
37
|
+
| NFR-004 | Performance | Establish a measured baseline; avoid arbitrary latency promises before pilot data. |
|
|
38
|
+
| NFR-005 | Safety | Commands run only through explicit policy; timeout and output-size limits exist. |
|
|
39
|
+
| NFR-006 | Privacy | No telemetry by default; evidence redaction and local storage documented. |
|
|
40
|
+
| NFR-007 | Portability | Reports are plain text/JSON with schema versions and no hosted-service dependency. |
|
|
41
|
+
| NFR-008 | Recoverability | Interrupted runs are recoverable without silently promoting status. |
|
|
42
|
+
| NFR-009 | Compatibility | Adapter and report schemas are versioned and migration policy documented. |
|
|
43
|
+
| NFR-010 | Testability | Core state machine and policy decisions are testable without live AI-provider calls. |
|
|
44
|
+
|
|
45
|
+
## MVP acceptance criteria
|
|
46
|
+
|
|
47
|
+
The MVP cannot be declared complete until all of the following are demonstrated:
|
|
48
|
+
|
|
49
|
+
- [ ] A contract can be created, validated, frozen for a run, and versioned.
|
|
50
|
+
- [ ] One supported agent can be observed through a real task lifecycle.
|
|
51
|
+
- [ ] The changed revision and permitted verification commands are captured.
|
|
52
|
+
- [ ] A deliberately omitted requirement is detected.
|
|
53
|
+
- [ ] A deliberately false “all tests passed” claim is rejected when evidence disagrees or is absent.
|
|
54
|
+
- [ ] A skipped mandatory check cannot produce `verified`.
|
|
55
|
+
- [ ] Scope drift is reported with changed paths and a contract reference.
|
|
56
|
+
- [ ] Retry limits prevent unbounded corrective loops.
|
|
57
|
+
- [ ] Human exceptions are explicit and visible in the final report.
|
|
58
|
+
- [ ] Reports can be read without Proofline running.
|
|
59
|
+
- [ ] Offline tests and end-to-end tests pass in CI.
|
|
60
|
+
- [ ] Security review covers command execution, secrets, path handling, and adapter trust.
|
|
61
|
+
|
|
62
|
+
## Change control
|
|
63
|
+
|
|
64
|
+
A requirement may be changed only with a recorded rationale, impact assessment, updated acceptance evidence, and (for architectural or trust-boundary changes) an ADR. The implementing agent cannot silently weaken the contract it is being evaluated against.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 03 — Domain model and terminology
|
|
2
|
+
|
|
3
|
+
## Core entities
|
|
4
|
+
|
|
5
|
+
- **Task contract:** accepted statement of scope, acceptance criteria, exclusions, constraints, required checks, risk tier, and approval rules.
|
|
6
|
+
- **Run:** one bounded attempt to fulfil one frozen contract against a repository/workspace revision.
|
|
7
|
+
- **Event:** timestamped observation from an adapter, runner, repository inspector, or human action.
|
|
8
|
+
- **Evidence record:** normalized, attributable record of a check, diff, claim, approval, or observation.
|
|
9
|
+
- **Requirement link:** mapping from a requirement to one or more implementation and verification records.
|
|
10
|
+
- **Finding:** a discrepancy, risk, failure, omission, or ambiguity produced by a verifier.
|
|
11
|
+
- **Exception:** explicit, attributable acceptance of a known gap or policy deviation.
|
|
12
|
+
- **Gate decision:** a deterministic decision derived from contract, policy, evidence freshness, and findings.
|
|
13
|
+
- **Delivery report:** portable summary of the decision, evidence, requirements coverage, exceptions, and residual risks.
|
|
14
|
+
|
|
15
|
+
## Trust distinctions
|
|
16
|
+
|
|
17
|
+
| Item | What it can establish | What it cannot establish alone |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| Agent statement | The agent asserted something | That the assertion is true |
|
|
20
|
+
| Exit code | A process returned a status | That the command tested the right property |
|
|
21
|
+
| Test output | A test produced the recorded output | That test coverage is sufficient |
|
|
22
|
+
| Git diff | Files changed between revisions | That all changes are justified |
|
|
23
|
+
| Hash/signature | Record integrity/authorship under key assumptions | Semantic correctness of the underlying work |
|
|
24
|
+
| Human approval | A named actor accepted a stated risk/scope | That the risk no longer exists |
|
|
25
|
+
|
|
26
|
+
## Required result vocabulary
|
|
27
|
+
|
|
28
|
+
- `passed`: the configured check ran and met its explicit success condition.
|
|
29
|
+
- `failed`: the configured check ran and did not meet its success condition.
|
|
30
|
+
- `skipped`: the check was deliberately not run; reason required.
|
|
31
|
+
- `not_run`: no execution evidence exists for this check.
|
|
32
|
+
- `inconclusive`: the check ran but could not establish pass or fail.
|
|
33
|
+
- `stale`: evidence applies to a different or superseded revision/context.
|
|
34
|
+
- `waived`: an authorized actor explicitly accepted a named gap; never equivalent to passed.
|
|
35
|
+
|
|
36
|
+
## Terminal delivery states
|
|
37
|
+
|
|
38
|
+
- `verified`: all mandatory requirements and gates have fresh sufficient evidence; no unresolved mandatory finding remains.
|
|
39
|
+
- `partial`: useful work is delivered, but one or more requirements remain incomplete or waived and are clearly reported.
|
|
40
|
+
- `blocked`: a mandatory gate, policy, or dependency prevents delivery.
|
|
41
|
+
- `unverified`: there is insufficient evidence to determine whether the contract was met.
|
|
42
|
+
|
|
43
|
+
The system must never infer `verified` merely because the agent stopped or returned a success message.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# 04 — Architecture
|
|
2
|
+
|
|
3
|
+
## Architectural rule
|
|
4
|
+
|
|
5
|
+
Keep the policy and decision engine independent from the agent adapter. Agent integrations translate events and actions; they must not own the truth of the delivery decision.
|
|
6
|
+
|
|
7
|
+
## Component view
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart TB
|
|
11
|
+
CLI[CLI / local API] --> CONTRACT[Contract loader and validator]
|
|
12
|
+
CLI --> RUN[Run coordinator]
|
|
13
|
+
CONTRACT --> POLICY[Policy and gate engine]
|
|
14
|
+
RUN --> ADAPTER[Agent adapter]
|
|
15
|
+
RUN --> OBS[Event observer]
|
|
16
|
+
ADAPTER --> OBS
|
|
17
|
+
OBS --> LEDGER[Evidence ledger]
|
|
18
|
+
GIT[Workspace / Git inspector] --> LEDGER
|
|
19
|
+
CHECKS[Policy-controlled check runner] --> LEDGER
|
|
20
|
+
LEDGER --> TRACE[Requirement traceability]
|
|
21
|
+
TRACE --> VERIFY[Independent verifier]
|
|
22
|
+
POLICY --> VERIFY
|
|
23
|
+
VERIFY --> RECON[Reconciliation engine]
|
|
24
|
+
RECON --> DECISION[Delivery decision]
|
|
25
|
+
DECISION --> REPORT[JSON + Markdown report]
|
|
26
|
+
RECON --> FEEDBACK[Bounded corrective feedback]
|
|
27
|
+
FEEDBACK --> RUN
|
|
28
|
+
HUMAN[Human approval] --> POLICY
|
|
29
|
+
HV[Optional HiveVault connector] -. prior context only .-> CONTRACT
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Logical modules
|
|
33
|
+
|
|
34
|
+
1. **Contract** — schema, validation, freeze/version, approved amendments.
|
|
35
|
+
2. **Run coordinator** — run lifecycle, state transitions, timeout and retry budgets.
|
|
36
|
+
3. **Adapters** — vendor-specific lifecycle event and action translation.
|
|
37
|
+
4. **Observer** — normalized event stream and workspace/revision observations.
|
|
38
|
+
5. **Evidence ledger** — append-oriented records, references, hashes, redaction metadata.
|
|
39
|
+
6. **Check runner** — allowlisted commands, controlled environment, captured outputs and exit status.
|
|
40
|
+
7. **Traceability engine** — requirement-to-change-to-test/evidence graph.
|
|
41
|
+
8. **Verifier** — requirement coverage, freshness, regression challenges, claim checks, policy checks.
|
|
42
|
+
9. **Reconciler** — compares contract, actual diff, check outcomes, exceptions, and agent delivery claims.
|
|
43
|
+
10. **Gate engine** — deterministic terminal status and policy enforcement.
|
|
44
|
+
11. **Reporter** — portable Markdown and JSON output.
|
|
45
|
+
12. **Optional context connector** — retrieves prior decisions from HiveVault without elevating them to current-run proof.
|
|
46
|
+
|
|
47
|
+
## Dependency direction
|
|
48
|
+
|
|
49
|
+
`adapters -> normalized events -> ledger -> verifier -> reconciler -> gate/report`
|
|
50
|
+
|
|
51
|
+
The verifier must not depend on a particular agent SDK. The gate engine must not depend on network access, model calls, or HiveVault. Model-assisted review may be an optional signal, but it must be labelled as such and cannot override hard evidence rules.
|
|
52
|
+
|
|
53
|
+
## Proposed storage
|
|
54
|
+
|
|
55
|
+
MVP: local run directory with versioned JSON records and report artifacts; a small embedded database may be considered only if evidence querying proves awkward. Avoid committing secrets or large raw outputs by default. Large outputs may be stored as bounded artifacts referenced by digest and path.
|
|
56
|
+
|
|
57
|
+
Suggested local layout:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
.proofline/
|
|
61
|
+
config.yaml
|
|
62
|
+
contracts/
|
|
63
|
+
runs/
|
|
64
|
+
<run-id>/
|
|
65
|
+
run.json
|
|
66
|
+
events.jsonl
|
|
67
|
+
evidence/
|
|
68
|
+
findings.json
|
|
69
|
+
report.json
|
|
70
|
+
report.md
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Architectural constraints
|
|
74
|
+
|
|
75
|
+
- Evidence is append-oriented; corrections supersede earlier records.
|
|
76
|
+
- Every decision records verifier/policy/schema versions.
|
|
77
|
+
- Any model-generated finding is distinguishable from deterministic checks.
|
|
78
|
+
- A policy change during a run is versioned and must not silently rewrite the earlier decision.
|
|
79
|
+
- The final report names the workspace revision it describes.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 05 — Task lifecycle and state machine
|
|
2
|
+
|
|
3
|
+
## Lifecycle
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
stateDiagram-v2
|
|
7
|
+
[*] --> Draft
|
|
8
|
+
Draft --> Contracted: validate + accept contract
|
|
9
|
+
Contracted --> Running: start run
|
|
10
|
+
Running --> Observed: task stops / checkpoint
|
|
11
|
+
Observed --> Verifying: evidence snapshot
|
|
12
|
+
Verifying --> Reconciling: checks complete
|
|
13
|
+
Reconciling --> Correcting: actionable gap + retry budget
|
|
14
|
+
Correcting --> Running: resume agent
|
|
15
|
+
Reconciling --> AwaitingApproval: explicit exception / high-risk action
|
|
16
|
+
AwaitingApproval --> Reconciling: decision recorded
|
|
17
|
+
Reconciling --> Verified: mandatory gates satisfied
|
|
18
|
+
Reconciling --> Partial: known gaps accepted / incomplete
|
|
19
|
+
Reconciling --> Blocked: mandatory failure or policy denial
|
|
20
|
+
Reconciling --> Unverified: evidence insufficient
|
|
21
|
+
Verified --> [*]
|
|
22
|
+
Partial --> [*]
|
|
23
|
+
Blocked --> [*]
|
|
24
|
+
Unverified --> [*]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## State transition rules
|
|
28
|
+
|
|
29
|
+
- A contract must be validated before a run can enter `Running`.
|
|
30
|
+
- A run is bound to a contract version and initial workspace revision.
|
|
31
|
+
- Verification uses a defined evidence snapshot; new changes invalidate evidence where relevant.
|
|
32
|
+
- Any material change after verification triggers re-verification of affected gates.
|
|
33
|
+
- `Verified` is reachable only when all mandatory gates pass with fresh evidence.
|
|
34
|
+
- A waived requirement remains visible as waived and normally results in `Partial`, unless the contract explicitly defines it as non-mandatory before the run.
|
|
35
|
+
- A policy denial cannot be converted to a pass by agent retry.
|
|
36
|
+
- Retry exhaustion moves the run to `Blocked` or `Unverified` with a specific reason.
|
|
37
|
+
- Human approval is scoped to a specific action or exception; it does not grant blanket future approval.
|
|
38
|
+
|
|
39
|
+
## Suggested bounded retry defaults
|
|
40
|
+
|
|
41
|
+
Defaults must be configurable and validated through the pilot; initial design proposal:
|
|
42
|
+
- Maximum corrective cycles per run: 3.
|
|
43
|
+
- Maximum repeat of the same normalized finding without meaningful evidence change: 1.
|
|
44
|
+
- Every retry must identify which finding it attempts to resolve.
|
|
45
|
+
- A retry does not erase prior failures; it appends new evidence and re-evaluates affected gates.
|
|
46
|
+
|
|
47
|
+
These are starting defaults, not validated performance claims.
|