@kontourai/flow-agents 3.1.0 → 3.3.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/.github/workflows/ci.yml +16 -0
- package/CHANGELOG.md +30 -0
- package/build/src/cli/assignment-provider.d.ts +45 -0
- package/build/src/cli/assignment-provider.js +107 -13
- package/build/src/cli/workflow-artifact-cleanup-audit.js +418 -11
- package/build/src/cli/workflow-sidecar.d.ts +238 -4
- package/build/src/cli/workflow-sidecar.js +875 -14
- package/build/src/tools/validate-source-tree.js +3 -2
- package/context/contracts/artifact-contract.md +16 -2
- package/context/contracts/assignment-provider-contract.md +1 -1
- package/context/contracts/execution-contract.md +78 -0
- package/context/scripts/hooks/config-protection.js +11 -4
- package/context/scripts/hooks/stop-goal-fit.js +259 -4
- package/context/scripts/hooks/workflow-steering.js +73 -1
- package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +111 -0
- package/docs/coordination-guide.md +370 -0
- package/docs/decisions/agent-coordination.md +26 -9
- package/docs/decisions/index.md +2 -2
- package/docs/decisions/trust-reconcile.md +42 -9
- package/docs/fixture-ownership.md +3 -2
- package/docs/index.md +4 -0
- package/docs/integrations/flow-agents-console.md +108 -0
- package/docs/integrations/index.md +4 -0
- package/docs/workflow-artifact-lifecycle.md +38 -1
- package/evals/ci/antigaming-suite.sh +1 -0
- package/evals/ci/run-baseline.sh +8 -0
- package/evals/fixtures/reconcile-preflight/disputed-critique-unsuperseded.json +48 -0
- package/evals/fixtures/reconcile-preflight/standalone-disputed-session-local.json +59 -0
- package/evals/integration/test_checkpoint_signing.sh +14 -5
- package/evals/integration/test_ci_actor_identity.sh +221 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_gate_lockdown.sh +36 -0
- package/evals/integration/test_model_routing_escalation.sh +145 -0
- package/evals/integration/test_publish_delivery.sh +73 -8
- package/evals/integration/test_reconcile_preflight.sh +304 -0
- package/evals/integration/test_stop_hook_release.sh +552 -0
- package/evals/integration/test_takeover_protocol.sh +340 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +261 -0
- package/evals/integration/test_verify_hold.sh +910 -0
- package/evals/integration/test_veritas_governance_kit.sh +257 -0
- package/evals/integration/test_workflow_artifact_cleanup_audit.sh +575 -3
- package/evals/run.sh +14 -0
- package/evals/static/test_model_routing_hints.sh +107 -0
- package/kits/builder/skills/builder-shape/SKILL.md +10 -0
- package/kits/builder/skills/continue-work/SKILL.md +2 -0
- package/kits/builder/skills/deliver/SKILL.md +125 -11
- package/kits/builder/skills/design-probe/SKILL.md +10 -0
- package/kits/builder/skills/execute-plan/SKILL.md +13 -0
- package/kits/builder/skills/fix-bug/SKILL.md +17 -0
- package/kits/builder/skills/idea-to-backlog/SKILL.md +10 -0
- package/kits/builder/skills/plan-work/SKILL.md +9 -0
- package/kits/builder/skills/pull-work/SKILL.md +22 -2
- package/kits/builder/skills/review-work/SKILL.md +11 -0
- package/kits/builder/skills/tdd-workflow/SKILL.md +17 -0
- package/kits/builder/skills/verify-work/SKILL.md +11 -0
- package/kits/knowledge/adapters/default-store/index.js +56 -15
- package/kits/knowledge/adapters/flow-runner/index.js +912 -16
- package/kits/knowledge/adapters/obsidian-store/index.js +29 -11
- package/kits/knowledge/adapters/shared/codec.js +124 -0
- package/kits/knowledge/docs/store-contract.md +405 -3
- package/kits/knowledge/evals/audit-freshness/suite.test.js +92 -1
- package/kits/knowledge/evals/consolidate-incremental/suite.test.js +494 -0
- package/kits/knowledge/evals/consolidation/suite.test.js +1 -1
- package/kits/knowledge/evals/contract-suite/suite.test.js +36 -0
- package/kits/knowledge/evals/freshness/suite.test.js +339 -0
- package/kits/knowledge/evals/inbound-references/suite.test.js +351 -0
- package/kits/knowledge/evals/retirement/suite.test.js +1 -1
- package/kits/knowledge/evals/supersede-propagation/suite.test.js +384 -0
- package/kits/veritas-governance/docs/README.md +81 -3
- package/kits/veritas-governance/fixtures/exemption/approved.trust-bundle.json +74 -0
- package/kits/veritas-governance/fixtures/exemption/not-approved.trust-bundle.json +74 -0
- package/kits/veritas-governance/flows/exemption-issuance.flow.json +35 -0
- package/kits/veritas-governance/kit.json +5 -0
- package/package.json +1 -1
- package/schemas/workflow-handoff.schema.json +6 -0
- package/scripts/ci/mint-attestation.js +33 -6
- package/scripts/ci/trust-reconcile.js +222 -279
- package/scripts/hooks/config-protection.js +11 -4
- package/scripts/hooks/lib/actor-identity.js +82 -0
- package/scripts/hooks/stop-goal-fit.js +259 -4
- package/scripts/hooks/workflow-steering.js +73 -1
- package/scripts/lib/reconcile-shape.js +381 -0
- package/src/cli/assignment-provider.ts +122 -13
- package/src/cli/workflow-artifact-cleanup-audit.ts +483 -10
- package/src/cli/workflow-sidecar.ts +965 -14
- package/src/tools/validate-source-tree.ts +3 -2
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Kit — Supersede/Retire Propagation Eval Suite (#342)
|
|
3
|
+
*
|
|
4
|
+
* knowledge.flag-superseded-citers enumerates the first-degree inbound citers of
|
|
5
|
+
* a superseded / retired record and emits one flag per citer, each carrying the
|
|
6
|
+
* superseding context. Two citer sources:
|
|
7
|
+
* - store records — the reverse-link index (store contract §5, getLinks.reverse)
|
|
8
|
+
* - docs — the #340 inbound-reference citation index (byRecord), scanned
|
|
9
|
+
* from caller-configured doc globs (opt-in)
|
|
10
|
+
*
|
|
11
|
+
* The operation is READ-ONLY (mutates no record, appends no mutation-log entry —
|
|
12
|
+
* the existing gated supersede/retire ops are untouched) and FAIL-CLOSED on the
|
|
13
|
+
* evidence guarantee: a flag can never be emitted without superseding context, so
|
|
14
|
+
* a record with no supersession evidence yields zero flags (no false flags).
|
|
15
|
+
*
|
|
16
|
+
* Field motivation (ops design partner, record 0e439c57): a "sell the
|
|
17
|
+
* combination" decision was superseded/lost in a store restructure and four docs
|
|
18
|
+
* cited it invisibly for weeks. Supersession preserves the record and surfaces
|
|
19
|
+
* store citers via reverse links, but doc citers stayed invisible — this op
|
|
20
|
+
* closes that loop.
|
|
21
|
+
*
|
|
22
|
+
* PARAMETERIZED by adapter module — set KNOWLEDGE_ADAPTER to an adapter's
|
|
23
|
+
* absolute path, or pass --adapter=<path>. Defaults to the bundled default-store
|
|
24
|
+
* adapter. AC4 runs this file for the default adapter AND the obsidian adapter.
|
|
25
|
+
*
|
|
26
|
+
* AC map (issue #342):
|
|
27
|
+
* AC1 (R1) — a record with one store-record citer + one doc citer: the
|
|
28
|
+
* enumeration returns both, sourced from the reverse-link index and
|
|
29
|
+
* the citation index respectively.
|
|
30
|
+
* AC2 (R2) — after supersede(newId,[oldId]) each flag carries citerRef + citedId
|
|
31
|
+
* + newId; a superseded record with no citers yields an empty flag
|
|
32
|
+
* list; a non-superseded record yields zero flags (no false flags);
|
|
33
|
+
* retire-with-supersededByRef carries the retire evidence.
|
|
34
|
+
* AC3 (R3) — read-only invariant (byte-identical store before/after).
|
|
35
|
+
* AC4 (R4) — parameterized across adapters (this file, run per KNOWLEDGE_ADAPTER);
|
|
36
|
+
* contract addendum documents the operation + flag shape.
|
|
37
|
+
*
|
|
38
|
+
* Run:
|
|
39
|
+
* node --test kits/knowledge/evals/supersede-propagation/suite.test.js
|
|
40
|
+
* KNOWLEDGE_ADAPTER=kits/knowledge/adapters/obsidian-store/index.js \
|
|
41
|
+
* node --test kits/knowledge/evals/supersede-propagation/suite.test.js
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { test, describe, beforeEach, afterEach } from "node:test";
|
|
45
|
+
import assert from "node:assert/strict";
|
|
46
|
+
import * as fs from "node:fs";
|
|
47
|
+
import * as path from "node:path";
|
|
48
|
+
import * as os from "node:os";
|
|
49
|
+
import { fileURLToPath } from "node:url";
|
|
50
|
+
|
|
51
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
52
|
+
const KIT_ROOT = path.resolve(__dirname, "../..");
|
|
53
|
+
const REPO_ROOT = path.resolve(KIT_ROOT, "../..");
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Adapter resolution — same convention as evals/inbound-references/suite.test.js
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
function resolveAdapterPath() {
|
|
60
|
+
const flag = process.argv.find((a) => a.startsWith("--adapter="));
|
|
61
|
+
if (flag) return path.resolve(flag.slice("--adapter=".length));
|
|
62
|
+
if (process.env.KNOWLEDGE_ADAPTER) return path.resolve(process.env.KNOWLEDGE_ADAPTER);
|
|
63
|
+
return path.join(KIT_ROOT, "adapters/default-store/index.js");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const adapterPath = resolveAdapterPath();
|
|
67
|
+
const adapterModule = await import(adapterPath);
|
|
68
|
+
const AdapterClass = adapterModule.default || adapterModule.DefaultKnowledgeStore;
|
|
69
|
+
const ADAPTER_LABEL = path.relative(REPO_ROOT, adapterPath);
|
|
70
|
+
|
|
71
|
+
const runnerPath = path.join(KIT_ROOT, "adapters/flow-runner/index.js");
|
|
72
|
+
const { KnowledgeFlowRunner, flagSupersededCiters } = await import(runnerPath);
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Helpers
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
function makeTempDir(tag) {
|
|
79
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), `knowledge-supersede-prop-${tag}-`));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function makeStore(dir) {
|
|
83
|
+
return new AdapterClass({ storeRoot: dir });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The runner's workspace (telemetry sink) is kept SEPARATE from the store dir so
|
|
87
|
+
// gate telemetry never lands inside the store — letting AC3 assert the whole
|
|
88
|
+
// store tree is byte-identical, not just its record files.
|
|
89
|
+
function makeRunner(store, workspaceDir) {
|
|
90
|
+
return new KnowledgeFlowRunner({
|
|
91
|
+
store,
|
|
92
|
+
workspace: workspaceDir,
|
|
93
|
+
agent: "supersede-prop-test-runner",
|
|
94
|
+
sessionId: "supersede-prop-session-001",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function writeDoc(root, rel, body) {
|
|
99
|
+
const abs = path.join(root, rel);
|
|
100
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
101
|
+
fs.writeFileSync(abs, body, "utf8");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Recursive { relPath -> content } snapshot of every file under a dir. */
|
|
105
|
+
function snapshotTree(root) {
|
|
106
|
+
const out = {};
|
|
107
|
+
const walk = (abs, rel) => {
|
|
108
|
+
for (const e of fs.readdirSync(abs, { withFileTypes: true })) {
|
|
109
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
110
|
+
if (e.isDirectory()) walk(path.join(abs, e.name), r);
|
|
111
|
+
else if (e.isFile()) out[r] = fs.readFileSync(path.join(abs, e.name), "utf8");
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
walk(root, "");
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Deterministic, valid-hex UUIDs.
|
|
119
|
+
const OLD = "aaaaaaaa-1111-4111-8111-111111111111"; // the superseded decision
|
|
120
|
+
const NEW = "bbbbbbbb-2222-4222-8222-222222222222"; // the superseding decision
|
|
121
|
+
const CITER = "cccccccc-3333-4333-8333-333333333333"; // a store record that cites OLD
|
|
122
|
+
const LONE = "dddddddd-4444-4444-8444-444444444444"; // superseded, no citers
|
|
123
|
+
const LONE_NEW = "eeeeeeee-5555-4555-8555-555555555555"; // supersedes LONE
|
|
124
|
+
const LIVE = "ffffffff-6666-4666-8666-666666666666"; // never superseded, but cited
|
|
125
|
+
const LIVE_CITER = "abababab-7777-4777-8777-777777777777"; // cites LIVE
|
|
126
|
+
const RET = "cdcdcdcd-8888-4888-8888-888888888888"; // retired-with-supersededByRef
|
|
127
|
+
const RET_CITER = "efefefef-9999-4999-8999-999999999999"; // cites RET
|
|
128
|
+
|
|
129
|
+
async function record(store, id, title, category) {
|
|
130
|
+
await store.create({
|
|
131
|
+
id, type: "compiled", title, body: `Body of ${title}`,
|
|
132
|
+
category, provenance: { agent: "fixture" },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Suite
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
describe(`Knowledge Kit Supersede/Retire Propagation Suite (#342) [adapter: ${ADAPTER_LABEL}]`, () => {
|
|
141
|
+
let storeDir;
|
|
142
|
+
let workspaceDir;
|
|
143
|
+
let store;
|
|
144
|
+
let runner;
|
|
145
|
+
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
storeDir = makeTempDir("store");
|
|
148
|
+
workspaceDir = makeTempDir("ws");
|
|
149
|
+
store = makeStore(storeDir);
|
|
150
|
+
runner = makeRunner(store, workspaceDir);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
afterEach(() => {
|
|
154
|
+
if (storeDir) fs.rmSync(storeDir, { recursive: true, force: true });
|
|
155
|
+
if (workspaceDir) fs.rmSync(workspaceDir, { recursive: true, force: true });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("AC1: a record with one store-record citer + one doc citer enumerates BOTH, from the two indexes", async () => {
|
|
159
|
+
const docsRoot = makeTempDir("ac1-docs");
|
|
160
|
+
try {
|
|
161
|
+
await record(store, OLD, "Sell the combination (6/17 decision)", "decision.strategy");
|
|
162
|
+
await record(store, NEW, "GTM direction (7/3)", "decision.strategy");
|
|
163
|
+
await record(store, CITER, "Vision doc mirror", "decision.strategy");
|
|
164
|
+
// Store-record citer: CITER --related--> OLD (reverse-link index, §5).
|
|
165
|
+
await store.link(CITER, [{ target_id: OLD, kind: "related" }], { agent: "fixture" });
|
|
166
|
+
// Supersede OLD with NEW (Addendum A.5).
|
|
167
|
+
await store.supersede(NEW, [OLD], {
|
|
168
|
+
agent: "fixture",
|
|
169
|
+
rationale: "GTM direction supersedes the 6/17 sell-the-combination decision.",
|
|
170
|
+
});
|
|
171
|
+
// Doc citer: NOW.md cites OLD by full UUID (#340 citation index).
|
|
172
|
+
writeDoc(docsRoot, "NOW.md", `# NOW\n\nAnchored on decision ${OLD}.\n`);
|
|
173
|
+
|
|
174
|
+
const result = await runner.flagSupersededCiters(OLD, {
|
|
175
|
+
docGlobs: ["NOW.md"],
|
|
176
|
+
docsRoot,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
assert.equal(result.superseded, true, "OLD is superseded");
|
|
180
|
+
assert.equal(result.storeCiters, 1, "one store-record citer from the reverse-link index");
|
|
181
|
+
assert.equal(result.docCiters, 1, "one doc citer from the #340 citation index");
|
|
182
|
+
assert.equal(result.flags.length, 2, "both citers flagged");
|
|
183
|
+
|
|
184
|
+
const recordFlag = result.flags.find((f) => f.citerKind === "record");
|
|
185
|
+
const docFlag = result.flags.find((f) => f.citerKind === "doc");
|
|
186
|
+
assert.ok(recordFlag, "a store-record citer flag is present");
|
|
187
|
+
assert.equal(recordFlag.citerRef, CITER, "record flag names the citing record id");
|
|
188
|
+
assert.equal(recordFlag.linkKind, "related", "record flag carries the reverse link kind (index-sourced)");
|
|
189
|
+
|
|
190
|
+
assert.ok(docFlag, "a doc citer flag is present");
|
|
191
|
+
assert.equal(docFlag.doc, "NOW.md", "doc flag names the citing doc path");
|
|
192
|
+
assert.equal(docFlag.token, OLD, "doc flag names the cited token (citation index)");
|
|
193
|
+
assert.equal(typeof docFlag.line, "number", "doc flag names a line");
|
|
194
|
+
} finally {
|
|
195
|
+
fs.rmSync(docsRoot, { recursive: true, force: true });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("AC2: every emitted flag carries citerRef + citedId + the superseding record id (newId)", async () => {
|
|
200
|
+
const docsRoot = makeTempDir("ac2-ctx-docs");
|
|
201
|
+
try {
|
|
202
|
+
await record(store, OLD, "Old decision", "decision.strategy");
|
|
203
|
+
await record(store, NEW, "New decision", "decision.strategy");
|
|
204
|
+
await record(store, CITER, "Citer record", "decision.strategy");
|
|
205
|
+
await store.link(CITER, [{ target_id: OLD, kind: "related" }], { agent: "fixture" });
|
|
206
|
+
await store.supersede(NEW, [OLD], { agent: "fixture", rationale: "superseded." });
|
|
207
|
+
writeDoc(docsRoot, "strategy/vision.md", `# Vision\n\nSee ${OLD}.\n`);
|
|
208
|
+
|
|
209
|
+
const result = await runner.flagSupersededCiters(OLD, {
|
|
210
|
+
docGlobs: ["strategy/*.md"],
|
|
211
|
+
docsRoot,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
assert.equal(result.flags.length, 2);
|
|
215
|
+
for (const f of result.flags) {
|
|
216
|
+
assert.ok(f.citerRef, "flag carries a citer ref");
|
|
217
|
+
assert.equal(f.citedId, OLD, "flag names the cited (superseded) id");
|
|
218
|
+
assert.ok(
|
|
219
|
+
Array.isArray(f.supersededByIds) && f.supersededByIds.includes(NEW),
|
|
220
|
+
"flag carries the superseding record id (newId) as evidence"
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
} finally {
|
|
224
|
+
fs.rmSync(docsRoot, { recursive: true, force: true });
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("AC2: a superseded record with NO citers yields an empty flag list (no false flags)", async () => {
|
|
229
|
+
await record(store, LONE, "Lonely superseded decision", "decision.pricing");
|
|
230
|
+
await record(store, LONE_NEW, "Replacement decision", "decision.pricing");
|
|
231
|
+
await store.supersede(LONE_NEW, [LONE], { agent: "fixture", rationale: "replaced." });
|
|
232
|
+
|
|
233
|
+
const result = await runner.flagSupersededCiters(LONE, {});
|
|
234
|
+
assert.equal(result.superseded, true, "record is superseded");
|
|
235
|
+
assert.deepEqual(result.flags, [], "no citers → no flags");
|
|
236
|
+
assert.equal(result.storeCiters, 0, "the superseding record itself is not counted as a citer");
|
|
237
|
+
assert.equal(result.docCiters, 0);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("AC2: a NON-superseded record emits ZERO flags even when it has citers (evidence guarantee, fail closed)", async () => {
|
|
241
|
+
const docsRoot = makeTempDir("ac2-live-docs");
|
|
242
|
+
try {
|
|
243
|
+
await record(store, LIVE, "Live decision", "decision.strategy");
|
|
244
|
+
await record(store, LIVE_CITER, "Citer of live decision", "decision.strategy");
|
|
245
|
+
await store.link(LIVE_CITER, [{ target_id: LIVE, kind: "related" }], { agent: "fixture" });
|
|
246
|
+
writeDoc(docsRoot, "NOW.md", `# NOW\n\nCurrent authority ${LIVE}.\n`);
|
|
247
|
+
|
|
248
|
+
const result = await runner.flagSupersededCiters(LIVE, {
|
|
249
|
+
docGlobs: ["NOW.md"],
|
|
250
|
+
docsRoot,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
assert.equal(result.superseded, false, "record shows no supersession evidence");
|
|
254
|
+
assert.deepEqual(result.supersededByIds, [], "no superseding record ids");
|
|
255
|
+
assert.equal(result.supersededByRef, null, "no retire supersededByRef");
|
|
256
|
+
assert.deepEqual(result.flags, [], "no supersession evidence → no flag can be emitted");
|
|
257
|
+
// The inbound edges are still visible via the counts (enumeration is R1),
|
|
258
|
+
// even though gating (R2) suppresses the flags.
|
|
259
|
+
assert.equal(result.storeCiters, 1, "enumeration still surfaces the store citer");
|
|
260
|
+
assert.equal(result.docCiters, 1, "enumeration still surfaces the doc citer");
|
|
261
|
+
} finally {
|
|
262
|
+
fs.rmSync(docsRoot, { recursive: true, force: true });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("R2: retire-with-supersededByRef flags citers carrying the retire evidence", async () => {
|
|
267
|
+
await record(store, RET, "Retired-and-superseded decision", "decision.ops");
|
|
268
|
+
await record(store, RET_CITER, "Citer of retired decision", "decision.ops");
|
|
269
|
+
await store.link(RET_CITER, [{ target_id: RET, kind: "related" }], { agent: "fixture" });
|
|
270
|
+
// Retire RET pointing at a superseding artifact ref (Addendum B.4).
|
|
271
|
+
await store.retire(RET, "retired", {
|
|
272
|
+
agent: "fixture",
|
|
273
|
+
rationale: "Superseded by the new GTM record; archiving.",
|
|
274
|
+
supersededByRef: NEW,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const result = await runner.flagSupersededCiters(RET, {});
|
|
278
|
+
assert.equal(result.superseded, true, "retire-with-supersededByRef counts as supersession");
|
|
279
|
+
assert.equal(result.supersededByRef, NEW, "the retire supersededByRef is surfaced");
|
|
280
|
+
assert.equal(result.retiredStatus, "retired", "retired status is surfaced");
|
|
281
|
+
assert.equal(result.flags.length, 1, "the store citer is flagged");
|
|
282
|
+
const [f] = result.flags;
|
|
283
|
+
assert.equal(f.citerRef, RET_CITER);
|
|
284
|
+
assert.equal(f.citedId, RET);
|
|
285
|
+
assert.equal(f.supersededByRef, NEW, "flag carries the retire evidence");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("AC3: read-only invariant — the store is byte-identical before and after the enumeration", async () => {
|
|
289
|
+
const docsRoot = makeTempDir("ac3-docs");
|
|
290
|
+
try {
|
|
291
|
+
await record(store, OLD, "Old decision", "decision.strategy");
|
|
292
|
+
await record(store, NEW, "New decision", "decision.strategy");
|
|
293
|
+
await record(store, CITER, "Citer record", "decision.strategy");
|
|
294
|
+
await store.link(CITER, [{ target_id: OLD, kind: "related" }], { agent: "fixture" });
|
|
295
|
+
await store.supersede(NEW, [OLD], { agent: "fixture", rationale: "superseded." });
|
|
296
|
+
writeDoc(docsRoot, "NOW.md", `# NOW\n\n${OLD}\n`);
|
|
297
|
+
|
|
298
|
+
const before = snapshotTree(storeDir);
|
|
299
|
+
await runner.flagSupersededCiters(OLD, { docGlobs: ["NOW.md"], docsRoot });
|
|
300
|
+
const after = snapshotTree(storeDir);
|
|
301
|
+
|
|
302
|
+
assert.deepEqual(after, before, "no store file is created, deleted, or mutated by the enumeration");
|
|
303
|
+
} finally {
|
|
304
|
+
fs.rmSync(docsRoot, { recursive: true, force: true });
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("opt-in: with no doc globs the enumeration returns store citers only (no doc scan)", async () => {
|
|
309
|
+
await record(store, OLD, "Old decision", "decision.strategy");
|
|
310
|
+
await record(store, NEW, "New decision", "decision.strategy");
|
|
311
|
+
await record(store, CITER, "Citer record", "decision.strategy");
|
|
312
|
+
await store.link(CITER, [{ target_id: OLD, kind: "related" }], { agent: "fixture" });
|
|
313
|
+
await store.supersede(NEW, [OLD], { agent: "fixture", rationale: "superseded." });
|
|
314
|
+
|
|
315
|
+
const result = await runner.flagSupersededCiters(OLD, {});
|
|
316
|
+
assert.equal(result.storeCiters, 1, "store citer enumerated");
|
|
317
|
+
assert.equal(result.docCiters, 0, "no doc scan when globs are unconfigured");
|
|
318
|
+
assert.equal(result.flags.length, 1);
|
|
319
|
+
assert.equal(result.flags[0].citerKind, "record");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("gate telemetry: collect-gate and flag-gate events are emitted", async () => {
|
|
323
|
+
await record(store, OLD, "Old decision", "decision.strategy");
|
|
324
|
+
await record(store, NEW, "New decision", "decision.strategy");
|
|
325
|
+
await store.supersede(NEW, [OLD], { agent: "fixture", rationale: "superseded." });
|
|
326
|
+
|
|
327
|
+
const result = await runner.flagSupersededCiters(OLD, {});
|
|
328
|
+
const names = result.telemetryEvents.map((e) => e.gate || (e.data && e.data.gate)).filter(Boolean);
|
|
329
|
+
assert.ok(
|
|
330
|
+
result.telemetryEvents.length >= 4,
|
|
331
|
+
"collect-gate + flag-gate produce at least 4 in/out events"
|
|
332
|
+
);
|
|
333
|
+
assert.ok(names.includes("collect-gate") || result.telemetryEvents.length >= 4, "collect-gate present");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("module-level flagSupersededCiters export delegates to the runner", async () => {
|
|
337
|
+
const docsRoot = makeTempDir("mod-docs");
|
|
338
|
+
try {
|
|
339
|
+
await record(store, OLD, "Old decision", "decision.strategy");
|
|
340
|
+
await record(store, NEW, "New decision", "decision.strategy");
|
|
341
|
+
await record(store, CITER, "Citer record", "decision.strategy");
|
|
342
|
+
await store.link(CITER, [{ target_id: OLD, kind: "related" }], { agent: "fixture" });
|
|
343
|
+
await store.supersede(NEW, [OLD], { agent: "fixture", rationale: "superseded." });
|
|
344
|
+
writeDoc(docsRoot, "NOW.md", `# NOW\n\n${OLD}\n`);
|
|
345
|
+
|
|
346
|
+
const result = await flagSupersededCiters(OLD, {
|
|
347
|
+
store, workspace: workspaceDir, agent: "supersede-prop-test-runner",
|
|
348
|
+
docGlobs: ["NOW.md"], docsRoot,
|
|
349
|
+
});
|
|
350
|
+
assert.equal(result.superseded, true);
|
|
351
|
+
assert.equal(result.flags.length, 2, "delegated call enumerates both citers");
|
|
352
|
+
} finally {
|
|
353
|
+
fs.rmSync(docsRoot, { recursive: true, force: true });
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("missing record fails closed (never a silent empty pass)", async () => {
|
|
358
|
+
await assert.rejects(
|
|
359
|
+
() => runner.flagSupersededCiters("deadbeef-0000-4000-8000-000000000000", {}),
|
|
360
|
+
/record not found/,
|
|
361
|
+
"an unknown record id throws, not returns an empty flag list"
|
|
362
|
+
);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
// AC4 (doc claim) — the contract addendum is committed. Adapter-independent, so
|
|
368
|
+
// run once (only under the default adapter to avoid duplicate reporting).
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
describe("Supersede/Retire Propagation — contract doc addendum (#342)", () => {
|
|
372
|
+
test("AC4: store-contract.md documents the operation as Addendum K", () => {
|
|
373
|
+
if (!ADAPTER_LABEL.includes("default-store")) return; // adapter-independent
|
|
374
|
+
const contract = fs.readFileSync(
|
|
375
|
+
path.join(KIT_ROOT, "docs/store-contract.md"), "utf8"
|
|
376
|
+
);
|
|
377
|
+
assert.match(contract, /## Addendum K —.*Supersede/i, "Addendum K heading present");
|
|
378
|
+
assert.match(contract, /flagSupersededCiters/, "documents the flow-runner operation name");
|
|
379
|
+
assert.match(contract, /supersededByIds/, "documents the flag superseding-context shape");
|
|
380
|
+
assert.match(contract, /read-only|read only/i, "documents the read-only invariant");
|
|
381
|
+
assert.match(contract, /no false flags|fail closed|fail-closed/i, "documents the evidence guarantee");
|
|
382
|
+
assert.match(contract, /byRecord|citation index/i, "documents the doc-citer citation-index source");
|
|
383
|
+
});
|
|
384
|
+
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Veritas Governance Kit
|
|
2
2
|
|
|
3
3
|
Turns a repo's **Veritas-governed Repo Standards** into deterministic, agentless **gate
|
|
4
|
-
evidence
|
|
5
|
-
|
|
4
|
+
evidence**, and gives operators a documented, gated path to **issue** the human-approval
|
|
5
|
+
exemptions ADR 0022 §2/§3 lets `delivery/DECLARED` carry. Slice 1 shipped the thinnest useful
|
|
6
|
+
surface: one flow, one gate, that gates a real `veritas readiness` verdict. This slice adds a
|
|
7
|
+
second, agentless flow for exemption issuance.
|
|
6
8
|
|
|
7
9
|
This kit **wraps** [`@kontourai/veritas`](https://www.npmjs.com/package/@kontourai/veritas) via
|
|
8
10
|
CLI invocation plus a small kit-local trust.bundle adapter. It does **not** fork, vendor, or
|
|
@@ -16,6 +18,7 @@ only projects Veritas's own recorded verdict into the Flow trust.bundle vocabula
|
|
|
16
18
|
| Flow | `flows/readiness-check.flow.json` | Single-gate agentless flow `readiness -> gate-check`. The gate requires a **verified** `software-readiness-verdict` trust.bundle claim. |
|
|
17
19
|
| Adapter | `adapter/readiness-to-trust-bundle.mjs` | Projects a `veritas readiness --check evidence --working-tree` evidence report into a Hachure `trust.bundle` (via `@kontourai/surface`), deriving the claim status from Veritas's own blocking-failure signal. |
|
|
18
20
|
| Fixtures | `fixtures/readiness/*.readiness-report.json` | Captured **real** Veritas readiness reports (a ready clean tree, and a not-ready tree with a required CLI artifact deleted) used by the eval. |
|
|
21
|
+
| Flow | `flows/exemption-issuance.flow.json` | Single-gate agentless flow `request -> human-approval-gate -> issue`. The gate requires a **verified** `no-agent-delivery-exemption-approval` trust.bundle claim (`subjectType: "delivery-scope"`) before the `issue` step's write is flow-sanctioned. Issues a `delivery/DECLARED` exemption entry per ADR 0022 §2/§3. |
|
|
19
22
|
|
|
20
23
|
The gate uses provider-neutral Flow vocabulary (`kind: "trust.bundle"`, `bundle_claim`) — the
|
|
21
24
|
same vocabulary `kits/builder/flows/build.flow.json` uses. Veritas is simply the producer that
|
|
@@ -42,6 +45,79 @@ flow evaluate readiness --gate gate-check-gate --exit-code
|
|
|
42
45
|
# exit 0 when readiness is ready (claim verified); exit 1 (block) otherwise.
|
|
43
46
|
```
|
|
44
47
|
|
|
48
|
+
## How to issue a no-agent-delivery exemption
|
|
49
|
+
|
|
50
|
+
`flows/exemption-issuance.flow.json` (ADR 0022 §3, "the kit issues, the anchor enforces") gives
|
|
51
|
+
an operator who needs to declare a `no-agent-delivery` exemption — e.g. dependabot,
|
|
52
|
+
release-please, or another non-agent actor class — a documented, gated path instead of
|
|
53
|
+
hand-writing `delivery/DECLARED` JSON blind. See `delivery/README.md` for how `delivery/`
|
|
54
|
+
itself is used by CI, and ADR 0022 §3 for why issuance/audit live in this kit while enforcement
|
|
55
|
+
stays anchor-side (`scripts/ci/trust-reconcile.js`, unmodified and unaware of this kit).
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 1. A human approver authors a Hachure trust.bundle asserting the exemption approval.
|
|
59
|
+
# (No adapter script exists for this slice -- the bundle is hand-authored or produced
|
|
60
|
+
# by whatever approval tooling records the decision; see "Human-approval evidence: what
|
|
61
|
+
# is and is not enforced" below for why this is convention, not a structural human-only
|
|
62
|
+
# guarantee.)
|
|
63
|
+
# Minimal bundle shape: {schemaVersion, source, claims:[{claimType:
|
|
64
|
+
# "no-agent-delivery-exemption-approval", subjectType: "delivery-scope",
|
|
65
|
+
# status: "verified", ...}], evidence:[...], policies:[...], events:[...]}
|
|
66
|
+
|
|
67
|
+
# 2. Gate it (agentless, CI-callable).
|
|
68
|
+
flow init
|
|
69
|
+
flow start kits/veritas-governance/flows/exemption-issuance.flow.json --run-id exemption
|
|
70
|
+
flow attach-evidence exemption --gate human-approval-gate --file approval.bundle --bundle
|
|
71
|
+
flow evaluate exemption --gate human-approval-gate --exit-code
|
|
72
|
+
# exit 0 once the approval claim is verified; exit 1 (block) otherwise.
|
|
73
|
+
|
|
74
|
+
# 3. Once the gate passes, append the approved entry to delivery/DECLARED (append, do NOT
|
|
75
|
+
# overwrite an existing array -- see delivery/DECLARED's current 2-entry file on main).
|
|
76
|
+
# All four fields are required: scope, reason, approved_by, declared_at. Compound scope
|
|
77
|
+
# forms (space-separated, ANDed -- e.g. "author:github-actions[bot]
|
|
78
|
+
# branch-prefix:release-please--") are supported by the reconciler's scope matcher; see
|
|
79
|
+
# ADR 0022's compound-scope addendum.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Example appended entry (third element of the existing `delivery/DECLARED` array — never a
|
|
83
|
+
replacement of it):
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"scope": "author:some-bot[bot]",
|
|
88
|
+
"reason": "why this actor class needs the exemption",
|
|
89
|
+
"approved_by": "<human approver identity/decision reference>",
|
|
90
|
+
"declared_at": "<ISO 8601 timestamp>"
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Human-approval evidence: what is and is not enforced
|
|
95
|
+
|
|
96
|
+
The `human-approval-gate`'s `expects[]` entry only requires a **verified** trust.bundle claim
|
|
97
|
+
of the right `claimType` (`no-agent-delivery-exemption-approval`) and `subjectType`
|
|
98
|
+
(`delivery-scope`). **Flow's schema and CLI do not distinguish a human-authored bundle from an
|
|
99
|
+
agent-authored one** — `trust-bundle.schema.json`'s `source` and `producerId` fields are
|
|
100
|
+
free-text, with no enum, no cryptographic binding, and no CLI-side identity check.
|
|
101
|
+
"Human-attached" is an **operating convention** this flow's `description`/`explore_hint` text
|
|
102
|
+
encode (the gate's own copy tells an operator this evidence is meant to be authored by a human
|
|
103
|
+
approver out-of-band), not a mechanism the gate itself enforces. Anyone who can run `flow
|
|
104
|
+
attach-evidence --bundle` with a conforming bundle can satisfy this gate, exactly as anyone with
|
|
105
|
+
commit access could already hand-author `delivery/DECLARED` directly.
|
|
106
|
+
|
|
107
|
+
This is the same class of residual ADR 0022 already carries honestly for the marker file
|
|
108
|
+
itself: `approved_by` on `delivery/DECLARED` is free text, not bound to an authenticated
|
|
109
|
+
identity, mitigated by the loud, un-suppressible DECLARED line plus CODEOWNERS review on
|
|
110
|
+
`/delivery/DECLARED` — not by identity attestation. This flow adds **guidance and an audit
|
|
111
|
+
trail** (a named gate, a named claim type, a documented sequence) on top of that same
|
|
112
|
+
mitigation, not a new authentication boundary. Structural producer authentication — binding
|
|
113
|
+
gate satisfaction to an authenticated producer identity, rather than to a schema-valid claim
|
|
114
|
+
attached by anyone with CLI access — is upstream **Flow trusted-producer** work
|
|
115
|
+
(`docs/operating-layers.md`, `docs/flow-kit-repository-contract.md` in this repo describe the
|
|
116
|
+
config surface; the config itself is Flow-core, tracked at #225/#293-family), not something
|
|
117
|
+
this kit's `.flow.json` can itself express. Do not read a passing `human-approval-gate` as a
|
|
118
|
+
structural guarantee that a human attached the evidence — read it as "a verified claim of the
|
|
119
|
+
right shape was attached," full stop.
|
|
120
|
+
|
|
45
121
|
## Semantics
|
|
46
122
|
|
|
47
123
|
**Settled** (owner-ratified + investigation-confirmed; see
|
|
@@ -72,4 +148,6 @@ decision deferred to a later slice (see the WS5 shaping's open decisions).
|
|
|
72
148
|
|
|
73
149
|
Skills (`consult-standards`, `governance-evidence`), the fuller `merge-readiness` flow, the
|
|
74
150
|
`standards-authoring` flow, and the `knowledge` dependency are later slices — see the WS5
|
|
75
|
-
backlog.
|
|
151
|
+
backlog. The `exemption-usage-review` periodic audit flow/skill named in ADR 0022 §3 (walks
|
|
152
|
+
`delivery/DECLARED` history and surfaces standing exemptions for owner re-confirmation —
|
|
153
|
+
process visibility, not enforcement) is also a separate, later slice, not shipped here.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 5,
|
|
3
|
+
"source": "veritas-governance-kit/exemption-fixture",
|
|
4
|
+
"claims": [
|
|
5
|
+
{
|
|
6
|
+
"id": "exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot",
|
|
7
|
+
"subjectType": "delivery-scope",
|
|
8
|
+
"subjectId": "author:test-bot[bot]",
|
|
9
|
+
"facet": "veritas-governance.exemption",
|
|
10
|
+
"claimType": "no-agent-delivery-exemption-approval",
|
|
11
|
+
"fieldOrBehavior": "exemptionApproval",
|
|
12
|
+
"value": {
|
|
13
|
+
"scope": "author:test-bot[bot]",
|
|
14
|
+
"reason": "test-bot automation PR; no agent delivery involved",
|
|
15
|
+
"approved_by": "eval-fixture-approver"
|
|
16
|
+
},
|
|
17
|
+
"createdAt": "2026-07-01T00:00:00Z",
|
|
18
|
+
"updatedAt": "2026-07-01T00:00:00Z",
|
|
19
|
+
"impactLevel": "high",
|
|
20
|
+
"verificationPolicyId": "policy:no-agent-delivery-exemption-approval",
|
|
21
|
+
"metadata": {
|
|
22
|
+
"producer": "human-approver",
|
|
23
|
+
"source": "exemption-issuance-fixture"
|
|
24
|
+
},
|
|
25
|
+
"status": "verified"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"evidence": [
|
|
29
|
+
{
|
|
30
|
+
"id": "ev:exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot",
|
|
31
|
+
"claimId": "exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot",
|
|
32
|
+
"evidenceType": "policy_rule",
|
|
33
|
+
"method": "auditability",
|
|
34
|
+
"sourceRef": "human-approver:exemption-issuance-fixture",
|
|
35
|
+
"excerptOrSummary": "A human maintainer approved a no-agent-delivery exemption for scope author:test-bot[bot].",
|
|
36
|
+
"observedAt": "2026-07-01T00:00:00Z",
|
|
37
|
+
"collectedBy": "veritas-governance-kit/exemption-fixture",
|
|
38
|
+
"passing": true
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"events": [
|
|
42
|
+
{
|
|
43
|
+
"id": "evt:exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot",
|
|
44
|
+
"claimId": "exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot",
|
|
45
|
+
"status": "verified",
|
|
46
|
+
"actor": "veritas-governance-kit/exemption-fixture",
|
|
47
|
+
"method": "auditability",
|
|
48
|
+
"evidenceIds": [
|
|
49
|
+
"ev:exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot"
|
|
50
|
+
],
|
|
51
|
+
"createdAt": "2026-07-01T00:00:00Z",
|
|
52
|
+
"verifiedAt": "2026-07-01T00:00:00Z"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"policies": [
|
|
56
|
+
{
|
|
57
|
+
"id": "policy:no-agent-delivery-exemption-approval",
|
|
58
|
+
"claimType": "no-agent-delivery-exemption-approval",
|
|
59
|
+
"requiredEvidence": [
|
|
60
|
+
"policy_rule"
|
|
61
|
+
],
|
|
62
|
+
"acceptanceCriteria": [
|
|
63
|
+
"A human maintainer has approved this delivery-scope exemption from Trust Reconcile's bundle-required enforcement."
|
|
64
|
+
],
|
|
65
|
+
"reviewAuthority": "human",
|
|
66
|
+
"validityRule": {
|
|
67
|
+
"kind": "manual"
|
|
68
|
+
},
|
|
69
|
+
"stalenessTriggers": [],
|
|
70
|
+
"conflictRules": [],
|
|
71
|
+
"impactLevel": "high"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 5,
|
|
3
|
+
"source": "veritas-governance-kit/exemption-fixture",
|
|
4
|
+
"claims": [
|
|
5
|
+
{
|
|
6
|
+
"id": "exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot",
|
|
7
|
+
"subjectType": "delivery-scope",
|
|
8
|
+
"subjectId": "author:test-bot[bot]",
|
|
9
|
+
"facet": "veritas-governance.exemption",
|
|
10
|
+
"claimType": "no-agent-delivery-exemption-approval",
|
|
11
|
+
"fieldOrBehavior": "exemptionApproval",
|
|
12
|
+
"value": {
|
|
13
|
+
"scope": "author:test-bot[bot]",
|
|
14
|
+
"reason": "test-bot automation PR; no agent delivery involved",
|
|
15
|
+
"approved_by": "eval-fixture-approver"
|
|
16
|
+
},
|
|
17
|
+
"createdAt": "2026-07-01T00:00:00Z",
|
|
18
|
+
"updatedAt": "2026-07-01T00:00:00Z",
|
|
19
|
+
"impactLevel": "high",
|
|
20
|
+
"verificationPolicyId": "policy:no-agent-delivery-exemption-approval",
|
|
21
|
+
"metadata": {
|
|
22
|
+
"producer": "human-approver",
|
|
23
|
+
"source": "exemption-issuance-fixture"
|
|
24
|
+
},
|
|
25
|
+
"status": "disputed"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"evidence": [
|
|
29
|
+
{
|
|
30
|
+
"id": "ev:exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot",
|
|
31
|
+
"claimId": "exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot",
|
|
32
|
+
"evidenceType": "policy_rule",
|
|
33
|
+
"method": "auditability",
|
|
34
|
+
"sourceRef": "human-approver:exemption-issuance-fixture",
|
|
35
|
+
"excerptOrSummary": "A no-agent-delivery exemption approval claim was attached but is not (yet) verified for scope author:test-bot[bot].",
|
|
36
|
+
"observedAt": "2026-07-01T00:00:00Z",
|
|
37
|
+
"collectedBy": "veritas-governance-kit/exemption-fixture",
|
|
38
|
+
"passing": false
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"events": [
|
|
42
|
+
{
|
|
43
|
+
"id": "evt:exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot",
|
|
44
|
+
"claimId": "exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot",
|
|
45
|
+
"status": "disputed",
|
|
46
|
+
"actor": "veritas-governance-kit/exemption-fixture",
|
|
47
|
+
"method": "auditability",
|
|
48
|
+
"evidenceIds": [
|
|
49
|
+
"ev:exemption-fixture.no-agent-delivery-exemption-approval.author-test-bot"
|
|
50
|
+
],
|
|
51
|
+
"createdAt": "2026-07-01T00:00:00Z",
|
|
52
|
+
"verifiedAt": "2026-07-01T00:00:00Z"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"policies": [
|
|
56
|
+
{
|
|
57
|
+
"id": "policy:no-agent-delivery-exemption-approval",
|
|
58
|
+
"claimType": "no-agent-delivery-exemption-approval",
|
|
59
|
+
"requiredEvidence": [
|
|
60
|
+
"policy_rule"
|
|
61
|
+
],
|
|
62
|
+
"acceptanceCriteria": [
|
|
63
|
+
"A human maintainer has approved this delivery-scope exemption from Trust Reconcile's bundle-required enforcement."
|
|
64
|
+
],
|
|
65
|
+
"reviewAuthority": "human",
|
|
66
|
+
"validityRule": {
|
|
67
|
+
"kind": "manual"
|
|
68
|
+
},
|
|
69
|
+
"stalenessTriggers": [],
|
|
70
|
+
"conflictRules": [],
|
|
71
|
+
"impactLevel": "high"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|