@cassiomc1/forgeloop 0.1.1 → 0.1.3

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.
Files changed (103) hide show
  1. package/.cursor/rules/project-loop.mdc +12 -1
  2. package/.github/copilot-instructions.md +11 -0
  3. package/AGENTS.md +13 -1
  4. package/AGENT_COMPATIBILITY.md +14 -1
  5. package/CLAUDE.md +11 -1
  6. package/ENG/accessibility-eng.md +5 -0
  7. package/ENG/clean-code-eng.md +1 -0
  8. package/ENG/design-code-eng.md +5 -0
  9. package/ENG/games-code-design-web-eng.md +6 -0
  10. package/ENG/perf-code-eng.md +3 -0
  11. package/ENG/premium-sites-studio-eng.md +9 -0
  12. package/ENG/sec-code-eng.md +5 -0
  13. package/ENG/test-code-eng.md +3 -0
  14. package/GUIDE_ROUTER.md +16 -0
  15. package/LOOP_ENGINEERING.md +163 -4
  16. package/ORCHESTRATOR_INTEGRATION.md +15 -0
  17. package/QUALITY_SCORECARD.md +16 -0
  18. package/README.md +64 -1
  19. package/THREAT_MODEL.md +6 -0
  20. package/conformance/README.md +47 -0
  21. package/conformance/backend-auth/EXPECTED_ROUTE.json +7 -0
  22. package/conformance/backend-auth/REQUEST.md +4 -0
  23. package/conformance/backend-auth/REQUIRED_EVIDENCE.json +3 -0
  24. package/conformance/backend-auth/REQUIRED_GATES.json +3 -0
  25. package/conformance/blind-premium-website/EXPECTED_ROUTE.json +7 -0
  26. package/conformance/blind-premium-website/REQUEST.md +6 -0
  27. package/conformance/blind-premium-website/REQUIRED_EVIDENCE.json +14 -0
  28. package/conformance/blind-premium-website/REQUIRED_GATES.json +3 -0
  29. package/conformance/complete-website/EXPECTED_ROUTE.json +7 -0
  30. package/conformance/complete-website/REQUEST.md +6 -0
  31. package/conformance/complete-website/REQUIRED_EVIDENCE.json +3 -0
  32. package/conformance/complete-website/REQUIRED_GATES.json +3 -0
  33. package/conformance/docs-only/EXPECTED_ROUTE.json +7 -0
  34. package/conformance/docs-only/REQUEST.md +4 -0
  35. package/conformance/docs-only/REQUIRED_EVIDENCE.json +3 -0
  36. package/conformance/docs-only/REQUIRED_GATES.json +3 -0
  37. package/conformance/runs/2026-08-11-codex-first-live.md +98 -0
  38. package/conformance/runs/2026-08-11-codex-second-live.md +87 -0
  39. package/conformance/simple-bug/EXPECTED_ROUTE.json +7 -0
  40. package/conformance/simple-bug/REQUEST.md +4 -0
  41. package/conformance/simple-bug/REQUIRED_EVIDENCE.json +3 -0
  42. package/conformance/simple-bug/REQUIRED_GATES.json +3 -0
  43. package/package.json +4 -3
  44. package/schemas/activation.schema.json +15 -0
  45. package/schemas/check.schema.json +23 -0
  46. package/schemas/config.schema.json +16 -0
  47. package/schemas/current-contract.schema.json +50 -0
  48. package/schemas/event.schema.json +20 -0
  49. package/schemas/evidence-coverage.schema.json +17 -0
  50. package/schemas/execution-receipt.schema.json +10 -0
  51. package/schemas/gate.schema.json +32 -0
  52. package/schemas/policy.schema.json +19 -0
  53. package/schemas/preflight.schema.json +23 -0
  54. package/schemas/routing-result.schema.json +1 -0
  55. package/schemas/source-registry.schema.json +29 -0
  56. package/schemas/task-bundle.schema.json +14 -0
  57. package/schemas/work-state.schema.json +7 -1
  58. package/src/cli.js +249 -9
  59. package/src/commands/activate.js +11 -0
  60. package/src/commands/advance.js +12 -0
  61. package/src/commands/audit.js +12 -0
  62. package/src/commands/bundle.js +10 -0
  63. package/src/commands/complete.js +18 -0
  64. package/src/commands/next.js +29 -0
  65. package/src/commands/policy.js +19 -0
  66. package/src/commands/preflight.js +12 -0
  67. package/src/commands/prepare-completion.js +18 -0
  68. package/src/commands/record-check.js +20 -0
  69. package/src/commands/report.js +10 -0
  70. package/src/commands/route.js +14 -2
  71. package/src/commands/validate-receipt.js +7 -6
  72. package/src/core/activation.js +16 -0
  73. package/src/core/artifacts.js +132 -0
  74. package/src/core/audit.js +88 -0
  75. package/src/core/bundles.js +120 -0
  76. package/src/core/checks.js +115 -0
  77. package/src/core/completion-artifacts.js +321 -0
  78. package/src/core/completion-relationships.js +141 -0
  79. package/src/core/completion.js +266 -0
  80. package/src/core/config.js +35 -0
  81. package/src/core/conformance.js +7 -0
  82. package/src/core/contract.js +141 -0
  83. package/src/core/coverage.js +89 -0
  84. package/src/core/events.js +158 -0
  85. package/src/core/execution-prerequisites.js +200 -0
  86. package/src/core/gate-artifact.js +54 -0
  87. package/src/core/gates.js +55 -0
  88. package/src/core/guide-metadata.js +62 -0
  89. package/src/core/next-action.js +709 -0
  90. package/src/core/phase.js +212 -0
  91. package/src/core/policies.js +69 -0
  92. package/src/core/preflight.js +409 -0
  93. package/src/core/profile.js +48 -0
  94. package/src/core/protocol.js +34 -0
  95. package/src/core/receipt.js +40 -1
  96. package/src/core/report.js +49 -0
  97. package/src/core/repository.js +18 -0
  98. package/src/core/route-artifact.js +33 -0
  99. package/src/core/router.js +16 -8
  100. package/src/core/schema-validation.js +11 -0
  101. package/src/core/sources.js +86 -0
  102. package/src/core/templates.js +11 -0
  103. package/src/core/work-state.js +41 -13
@@ -0,0 +1,321 @@
1
+ import {
2
+ ARTIFACT_PATHS,
3
+ canonicalFingerprint,
4
+ readJsonArtifact,
5
+ writeJsonArtifact,
6
+ } from "./artifacts.js";
7
+ import { readContract } from "./contract.js";
8
+ import { appendProtocolEvent, validateEventLedger } from "./events.js";
9
+ import { completionEvidenceForGuides } from "./guide-metadata.js";
10
+ import { createCheck } from "./checks.js";
11
+ import { createEvidence } from "./evidence.js";
12
+ import { coverageForRequirements } from "./coverage.js";
13
+ import { assertCompletionRelationships } from "./completion-relationships.js";
14
+ import { evaluatePreflight } from "./preflight.js";
15
+ import { currentChangedPaths } from "./repository.js";
16
+ import { readPersistedRoute } from "./route-artifact.js";
17
+ import { createReceipt, validateReceipt } from "./receipt.js";
18
+ import { readWorkState, writeWorkState } from "./work-state.js";
19
+ import { assertExecutionPrerequisites, hasExecutionStarted } from "./execution-prerequisites.js";
20
+
21
+ function artifactError(code, message, artifacts = []) {
22
+ const error = new Error(message);
23
+ error.code = code;
24
+ error.artifacts = artifacts;
25
+ return error;
26
+ }
27
+
28
+ function requiredString(value, label) {
29
+ if (typeof value !== "string" || value.trim() === "") {
30
+ throw artifactError("E_CHECK_INVALID", `${label} must be a non-empty string`);
31
+ }
32
+ return value;
33
+ }
34
+
35
+ async function readCurrentReceipt(target, packageRoot) {
36
+ try {
37
+ return await readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot);
38
+ } catch (error) {
39
+ if (error.code === "ARTIFACT_MISSING") {
40
+ throw artifactError(
41
+ "E_RECEIPT_MISSING",
42
+ "Execution receipt is missing; run forgeloop prepare-completion first",
43
+ [ARTIFACT_PATHS.receipt],
44
+ );
45
+ }
46
+ throw error;
47
+ }
48
+ }
49
+
50
+ async function readOptionalConfig(target, packageRoot) {
51
+ try {
52
+ return (await readJsonArtifact(target, ARTIFACT_PATHS.config, "config", packageRoot)).value;
53
+ } catch (error) {
54
+ if (error.code === "ARTIFACT_MISSING") return {};
55
+ throw error;
56
+ }
57
+ }
58
+
59
+ export async function requiredEvidenceForTarget({ target, contract, route, packageRoot, additionalEvidence = [] }) {
60
+ const config = await readOptionalConfig(target, packageRoot);
61
+ const guideEvidence = await completionEvidenceForGuides(route.value.guides, packageRoot);
62
+ return [...new Set([
63
+ ...(contract.value.successCriteria ?? []),
64
+ ...guideEvidence,
65
+ ...(config.requiredEvidence ?? []),
66
+ ...additionalEvidence,
67
+ ])].sort();
68
+ }
69
+
70
+ export async function prepareCompletion({ target, packageRoot }) {
71
+ const contract = await readContract(target, packageRoot);
72
+ const route = await readPersistedRoute(target, packageRoot);
73
+ const state = await readWorkState(target, packageRoot);
74
+ if (!state) {
75
+ throw artifactError("E_STATE_MISSING", "Work state is required before preparing completion", [ARTIFACT_PATHS.state]);
76
+ }
77
+ if (hasExecutionStarted(state.phase)) {
78
+ await assertExecutionPrerequisites({ target, state, packageRoot });
79
+ }
80
+
81
+ let existing = null;
82
+ try {
83
+ existing = await readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot);
84
+ await validateReceipt(existing.value, packageRoot);
85
+ } catch (error) {
86
+ if (error.code !== "ARTIFACT_MISSING") throw error;
87
+ }
88
+
89
+ const preflight = await evaluatePreflight({ target, packageRoot });
90
+ const requiredEvidence = await requiredEvidenceForTarget({
91
+ target,
92
+ contract,
93
+ route,
94
+ packageRoot,
95
+ additionalEvidence: preflight.policy?.requiredEvidence ?? [],
96
+ });
97
+ const existingValue = existing?.value ?? {};
98
+ assertCompletionRelationships({
99
+ contract,
100
+ route,
101
+ state,
102
+ receipt: existingValue.taskId ? existingValue : null,
103
+ requiredEvidence,
104
+ requireRequiredChecks: false,
105
+ });
106
+ const changedPaths = existing
107
+ ? [...(existingValue.changedPaths ?? [])]
108
+ : (await currentChangedPaths(target) ?? []);
109
+ const checks = existing ? [...existingValue.checks] : [...state.checks];
110
+ const evidence = existing ? [...(existingValue.evidence ?? [])] : [...state.verificationEvidence];
111
+ const receipt = await createReceipt({
112
+ ...existingValue,
113
+ taskId: contract.value.taskId,
114
+ contractFingerprint: contract.fingerprint,
115
+ routeFingerprint: route.fingerprint,
116
+ stateFingerprint: canonicalFingerprint(state),
117
+ status: existingValue.status ?? "in-progress",
118
+ taskStatus: existingValue.taskStatus ?? "in-progress",
119
+ verificationStatus: existingValue.verificationStatus ?? "not-verified",
120
+ publicationStatus: existingValue.publicationStatus ?? "local-only",
121
+ productionReadiness: existingValue.productionReadiness ?? "not-verified",
122
+ selectedGuides: [...route.value.guides],
123
+ changedPaths,
124
+ checks,
125
+ evidence,
126
+ evidenceCoverage: coverageForRequirements(requiredEvidence, checks),
127
+ review: existingValue.review ?? { status: "not-run", independent: false },
128
+ limitations: [...(existingValue.limitations ?? [])],
129
+ publication: existingValue.publication ?? {
130
+ committed: false,
131
+ pushed: false,
132
+ pullRequest: null,
133
+ deployed: false,
134
+ },
135
+ }, packageRoot);
136
+ const written = await writeJsonArtifact(
137
+ target,
138
+ ARTIFACT_PATHS.receipt,
139
+ receipt,
140
+ "execution-receipt",
141
+ packageRoot,
142
+ );
143
+ return {
144
+ path: written.path,
145
+ receipt: written.value,
146
+ requiredEvidence,
147
+ changedPaths,
148
+ };
149
+ }
150
+
151
+ function mergeByCheckId(checks, nextCheck) {
152
+ const next = [...checks];
153
+ const index = next.findIndex((item) => item?.schemaVersion === 1 && item.id === nextCheck.id);
154
+ if (index >= 0) next[index] = nextCheck;
155
+ else next.push(nextCheck);
156
+ return next;
157
+ }
158
+
159
+ function appendUniqueEvidence(evidence, nextEvidence) {
160
+ const exists = evidence.some((item) => item.kind === nextEvidence.kind
161
+ && item.source === nextEvidence.source
162
+ && item.result === nextEvidence.result
163
+ && JSON.stringify(item.details ?? null) === JSON.stringify(nextEvidence.details ?? null));
164
+ return exists ? [...evidence] : [...evidence, nextEvidence];
165
+ }
166
+
167
+ export async function recordCheck({
168
+ target,
169
+ packageRoot,
170
+ id,
171
+ kind = "command",
172
+ requirement,
173
+ status,
174
+ evidenceKind,
175
+ command,
176
+ result,
177
+ exitCode,
178
+ details,
179
+ }) {
180
+ requiredString(id, "check id");
181
+ requiredString(kind, "check kind");
182
+ requiredString(requirement, "check requirement");
183
+ requiredString(status, "check status");
184
+ requiredString(evidenceKind, "evidence kind");
185
+ if (command !== undefined && typeof command !== "string") {
186
+ throw artifactError("E_CHECK_INVALID", "command must be a string when supplied");
187
+ }
188
+ if (result !== undefined && typeof result !== "string") {
189
+ throw artifactError("E_CHECK_INVALID", "result must be a string when supplied");
190
+ }
191
+ if (details !== undefined && (!details || typeof details !== "object" || Array.isArray(details))) {
192
+ throw artifactError("E_CHECK_INVALID", "check details must be a JSON object");
193
+ }
194
+ if ((typeof command !== "string" || command.trim() === "")
195
+ && (typeof result !== "string" || result.trim() === "")) {
196
+ throw artifactError("E_CHECK_INVALID", "record-check requires --command or --result");
197
+ }
198
+
199
+ const state = await readWorkState(target, packageRoot);
200
+ if (!state) throw artifactError("E_STATE_MISSING", "Work state is required before recording a check", [ARTIFACT_PATHS.state]);
201
+ if (["COMPLETE", "BLOCKED"].includes(state.phase)) {
202
+ throw artifactError("E_PHASE_TRANSITION_INVALID", `Cannot record a check in ${state.phase}`, [ARTIFACT_PATHS.state]);
203
+ }
204
+ if (state.phase !== "VERIFYING") {
205
+ throw artifactError(
206
+ "E_PHASE_PREREQUISITE_MISSING",
207
+ `record-check requires VERIFYING before review; found ${state.phase}`,
208
+ [ARTIFACT_PATHS.state],
209
+ );
210
+ }
211
+ await assertExecutionPrerequisites({ target, state, packageRoot });
212
+
213
+ const existingReceipt = await readCurrentReceipt(target, packageRoot);
214
+ await validateReceipt(existingReceipt.value, packageRoot);
215
+ const source = command?.trim() || `check:${id}`;
216
+ const recordedResult = result?.trim() || `recorded command: ${command.trim()}`;
217
+ const check = createCheck({
218
+ id,
219
+ kind,
220
+ requirement,
221
+ status,
222
+ evidenceKind,
223
+ source,
224
+ ...(exitCode === undefined ? {} : { exitCode }),
225
+ details: {
226
+ ...(command === undefined ? {} : { command }),
227
+ ...(result === undefined ? {} : { result }),
228
+ ...(details === undefined ? {} : details),
229
+ },
230
+ });
231
+ const evidence = createEvidence({
232
+ kind: evidenceKind,
233
+ source,
234
+ result: recordedResult,
235
+ ...(details === undefined ? {} : { details: structuredClone(details) }),
236
+ });
237
+
238
+ const checks = mergeByCheckId(existingReceipt.value.checks ?? [], check);
239
+ const evidenceList = appendUniqueEvidence(existingReceipt.value.evidence ?? [], evidence);
240
+ const contract = await readContract(target, packageRoot);
241
+ const route = await readPersistedRoute(target, packageRoot);
242
+ const preflight = await evaluatePreflight({ target, packageRoot });
243
+ const requiredEvidence = await requiredEvidenceForTarget({
244
+ target,
245
+ contract,
246
+ route,
247
+ packageRoot,
248
+ additionalEvidence: preflight.policy?.requiredEvidence ?? [],
249
+ });
250
+ assertCompletionRelationships({
251
+ contract,
252
+ route,
253
+ state,
254
+ receipt: existingReceipt.value,
255
+ requiredEvidence,
256
+ requireRequiredChecks: false,
257
+ });
258
+ const ledger = await validateEventLedger(target, packageRoot);
259
+ if (!ledger.valid) {
260
+ const first = ledger.errors[0];
261
+ throw artifactError(first.code, first.message, [ARTIFACT_PATHS.events]);
262
+ }
263
+ if (!ledger.events.some((event) => event.taskId === state.taskId && event.event === "VERIFICATION_STARTED")) {
264
+ throw artifactError(
265
+ "E_PHASE_CHRONOLOGY_INVALID",
266
+ "record-check requires VERIFICATION_STARTED in the current task ledger",
267
+ [ARTIFACT_PATHS.events],
268
+ );
269
+ }
270
+ const coverage = coverageForRequirements(requiredEvidence, checks);
271
+ const nextState = {
272
+ ...state,
273
+ checks,
274
+ verificationEvidence: appendUniqueEvidence(state.verificationEvidence ?? [], evidence),
275
+ evidenceCoverage: coverage,
276
+ lastUpdated: new Date().toISOString(),
277
+ };
278
+ const nextReceipt = await createReceipt({
279
+ ...existingReceipt.value,
280
+ checks,
281
+ evidence: evidenceList,
282
+ evidenceCoverage: coverage,
283
+ stateFingerprint: canonicalFingerprint(nextState),
284
+ }, packageRoot);
285
+
286
+ assertCompletionRelationships({
287
+ contract,
288
+ route,
289
+ state: nextState,
290
+ receipt: nextReceipt,
291
+ requiredEvidence,
292
+ requireRequiredChecks: false,
293
+ });
294
+
295
+ await writeWorkState(target, nextState, { packageRoot });
296
+ const written = await writeJsonArtifact(
297
+ target,
298
+ ARTIFACT_PATHS.receipt,
299
+ nextReceipt,
300
+ "execution-receipt",
301
+ packageRoot,
302
+ );
303
+ const event = await appendProtocolEvent(target, {
304
+ taskId: state.taskId,
305
+ event: "VERIFICATION_RECORDED",
306
+ details: {
307
+ checkId: check.id,
308
+ requirement: check.requirement,
309
+ status: check.status,
310
+ evidenceKind: check.evidenceKind,
311
+ },
312
+ }, packageRoot);
313
+ return {
314
+ path: written.path,
315
+ receipt: written.value,
316
+ check,
317
+ evidence,
318
+ coverage,
319
+ event,
320
+ };
321
+ }
@@ -0,0 +1,141 @@
1
+ import { ARTIFACT_PATHS, canonicalFingerprint } from "./artifacts.js";
2
+ import { assertCheckList, requiredChecksSatisfied } from "./checks.js";
3
+ import { assertCoverageList, coverageForRequirements } from "./coverage.js";
4
+ import { assertEvidenceList } from "./evidence.js";
5
+
6
+ function issue(code, message, artifacts = []) {
7
+ return { code, message, artifacts };
8
+ }
9
+
10
+ function sameValue(left, right) {
11
+ return canonicalFingerprint(left) === canonicalFingerprint(right);
12
+ }
13
+
14
+ function addAssertion(errors, assertion, code, artifacts) {
15
+ try {
16
+ assertion();
17
+ } catch (error) {
18
+ errors.push(issue(code, error.message, artifacts));
19
+ }
20
+ }
21
+
22
+ export function stateIdentityErrors({
23
+ contract,
24
+ route,
25
+ state,
26
+ } = {}) {
27
+ const errors = [];
28
+ const contractValue = contract?.value ?? contract;
29
+ const contractFingerprint = contract?.fingerprint;
30
+ if (contractValue && state && contractValue.taskId !== state.taskId) {
31
+ errors.push(issue("E_STATE_TASK_MISMATCH", "Work state does not belong to the current contract task", [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.state]));
32
+ }
33
+ if (contractFingerprint && state && state.contractFingerprint !== contractFingerprint) {
34
+ errors.push(issue("E_CONTRACT_STALE", "Work state does not match the current contract", [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.state]));
35
+ }
36
+ if (contractFingerprint && route && route.value.contractFingerprint !== contractFingerprint) {
37
+ errors.push(issue("E_ROUTE_STALE", "Routing result does not match the current contract", [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.route]));
38
+ }
39
+ if (route && state && route.value.contractFingerprint !== undefined
40
+ && route.value.contractFingerprint !== state.contractFingerprint) {
41
+ errors.push(issue("E_ROUTE_STALE", "Routing result does not match the work state contract", [ARTIFACT_PATHS.route, ARTIFACT_PATHS.state]));
42
+ }
43
+ if (route && state && (state.routeFingerprint !== route.fingerprint || !sameValue(state.selectedGuides, route.value.guides))) {
44
+ errors.push(issue("E_ROUTE_GUIDE_MISMATCH", "Work state does not match the persisted route identity", [ARTIFACT_PATHS.route, ARTIFACT_PATHS.state]));
45
+ }
46
+ return errors;
47
+ }
48
+
49
+ export function assertStateIdentity(input) {
50
+ const errors = stateIdentityErrors(input);
51
+ if (errors.length === 0) return;
52
+ const first = errors[0];
53
+ const error = new Error(first.message);
54
+ error.code = first.code;
55
+ error.artifacts = first.artifacts;
56
+ throw error;
57
+ }
58
+
59
+ export function completionRelationshipErrors({
60
+ contract,
61
+ route,
62
+ state,
63
+ receipt,
64
+ requiredEvidence = [],
65
+ requireReceiptStateFingerprint = true,
66
+ requireRequiredChecks = true,
67
+ } = {}) {
68
+ const errors = stateIdentityErrors({ contract, route, state });
69
+ const contractValue = contract?.value ?? contract;
70
+ const contractFingerprint = contract?.fingerprint;
71
+ if (contractValue && receipt && contractValue.taskId !== receipt.taskId) {
72
+ errors.push(issue("E_RECEIPT_TASK_MISMATCH", "Execution receipt does not belong to the current contract task", [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.receipt]));
73
+ }
74
+ if (route && receipt && (receipt.routeFingerprint !== route.fingerprint || !sameValue(receipt.selectedGuides, route.value.guides))) {
75
+ errors.push(issue("E_ROUTE_GUIDE_MISMATCH", "Execution receipt does not match the persisted route identity", [ARTIFACT_PATHS.route, ARTIFACT_PATHS.receipt]));
76
+ }
77
+ if (contractFingerprint && receipt && receipt.contractFingerprint !== contractFingerprint) {
78
+ errors.push(issue("E_RECEIPT_CONTRACT_MISMATCH", "Execution receipt does not match the current contract", [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.receipt]));
79
+ }
80
+
81
+ if (state) {
82
+ addAssertion(errors, () => assertCheckList(state.checks, "work-state.checks"), "E_CHECK_INVALID", [ARTIFACT_PATHS.state]);
83
+ addAssertion(errors, () => assertEvidenceList(state.verificationEvidence, "work-state.verificationEvidence"), "E_EVIDENCE_INVALID", [ARTIFACT_PATHS.state]);
84
+ }
85
+ if (receipt) {
86
+ addAssertion(errors, () => assertCheckList(receipt.checks, "receipt.checks"), "E_CHECK_INVALID", [ARTIFACT_PATHS.receipt]);
87
+ addAssertion(errors, () => assertEvidenceList(receipt.evidence ?? [], "receipt.evidence"), "E_EVIDENCE_INVALID", [ARTIFACT_PATHS.receipt]);
88
+ if (requireRequiredChecks) {
89
+ for (const error of requiredChecksSatisfied(receipt.checks, requiredEvidence)) {
90
+ errors.push(issue(error.code, error.message, [ARTIFACT_PATHS.receipt]));
91
+ }
92
+ }
93
+ }
94
+
95
+ const expectedCoverage = state ? coverageForRequirements(requiredEvidence, state.checks) : [];
96
+ if (state?.evidenceCoverage !== undefined) {
97
+ addAssertion(errors, () => assertCoverageList(state.evidenceCoverage, "work-state.evidenceCoverage"), "E_EVIDENCE_COVERAGE_INVALID", [ARTIFACT_PATHS.state]);
98
+ if (!sameValue(state.evidenceCoverage, expectedCoverage)) {
99
+ errors.push(issue("E_EVIDENCE_COVERAGE_INVALID", "Work state evidence coverage does not match its checks", [ARTIFACT_PATHS.state]));
100
+ }
101
+ }
102
+ if (receipt) {
103
+ if (receipt.evidenceCoverage === undefined) {
104
+ errors.push(issue("E_EVIDENCE_COVERAGE_INVALID", "Execution receipt requires evidence coverage", [ARTIFACT_PATHS.receipt]));
105
+ } else {
106
+ addAssertion(errors, () => assertCoverageList(receipt.evidenceCoverage, "receipt.evidenceCoverage"), "E_EVIDENCE_COVERAGE_INVALID", [ARTIFACT_PATHS.receipt]);
107
+ if (!sameValue(receipt.evidenceCoverage, expectedCoverage)) {
108
+ errors.push(issue("E_EVIDENCE_COVERAGE_INVALID", "Execution receipt evidence coverage does not match current checks", [ARTIFACT_PATHS.receipt]));
109
+ }
110
+ }
111
+ }
112
+
113
+ if (state && receipt) {
114
+ if (requireReceiptStateFingerprint && receipt.stateFingerprint === undefined) {
115
+ errors.push(issue("E_RECEIPT_STATE_MISMATCH", "Execution receipt requires the current work-state fingerprint", [ARTIFACT_PATHS.state, ARTIFACT_PATHS.receipt]));
116
+ } else if (receipt.stateFingerprint !== undefined && receipt.stateFingerprint !== canonicalFingerprint(state)) {
117
+ errors.push(issue("E_RECEIPT_STATE_MISMATCH", "Execution receipt does not match the recorded work state", [ARTIFACT_PATHS.state, ARTIFACT_PATHS.receipt]));
118
+ }
119
+ if (!sameValue(state.checks, receipt.checks)) {
120
+ errors.push(issue("E_RECEIPT_STATE_MISMATCH", "Work state and execution receipt checks diverge", [ARTIFACT_PATHS.state, ARTIFACT_PATHS.receipt]));
121
+ }
122
+ if (!sameValue(state.verificationEvidence, receipt.evidence ?? [])) {
123
+ errors.push(issue("E_RECEIPT_STATE_MISMATCH", "Work state and execution receipt evidence diverge", [ARTIFACT_PATHS.state, ARTIFACT_PATHS.receipt]));
124
+ }
125
+ if (state.evidenceCoverage !== undefined && receipt.evidenceCoverage !== undefined
126
+ && !sameValue(state.evidenceCoverage, receipt.evidenceCoverage)) {
127
+ errors.push(issue("E_RECEIPT_STATE_MISMATCH", "Work state and execution receipt coverage diverge", [ARTIFACT_PATHS.state, ARTIFACT_PATHS.receipt]));
128
+ }
129
+ }
130
+ return errors;
131
+ }
132
+
133
+ export function assertCompletionRelationships(input) {
134
+ const errors = completionRelationshipErrors(input);
135
+ if (errors.length === 0) return;
136
+ const first = errors[0];
137
+ const error = new Error(first.message);
138
+ error.code = first.code;
139
+ error.artifacts = first.artifacts;
140
+ throw error;
141
+ }