@cassiomc1/forgeloop 0.1.13 → 0.1.14
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/LOOP_ENGINEERING.md +33 -0
- package/PROTOCOL_INTEGRATION.md +71 -0
- package/README.md +16 -9
- package/THREAT_MODEL.md +4 -0
- package/package.json +1 -1
- package/schemas/authority.schema.json +34 -0
- package/src/commands/inspect.js +2 -0
- package/src/commands/prepare-completion.js +2 -2
- package/src/core/audit.js +2 -2
- package/src/core/checks.js +12 -5
- package/src/core/completion-artifacts.js +122 -23
- package/src/core/completion-recovery.js +4 -0
- package/src/core/completion-relationships.js +39 -4
- package/src/core/completion.js +29 -7
- package/src/core/coverage.js +15 -2
- package/src/core/evidence-readiness.js +32 -6
- package/src/core/inspect.js +3 -1
- package/src/core/next-action.js +87 -9
- package/src/core/phase.js +29 -6
- package/src/core/protocol.js +5 -0
- package/src/core/receipt.js +6 -6
- package/src/core/runtime-context.js +80 -0
- package/src/core/trusted-authority.js +296 -0
- package/src/core/verification-capability.js +499 -0
|
@@ -3,6 +3,7 @@ import { assertCheckList } from "./checks.js";
|
|
|
3
3
|
import { assertCoverageList, coverageForRequirements } from "./coverage.js";
|
|
4
4
|
import { assertEvidenceList } from "./evidence.js";
|
|
5
5
|
import { evaluateRequiredEvidence } from "./evidence-readiness.js";
|
|
6
|
+
import { validateVerificationAuthority } from "./verification-capability.js";
|
|
6
7
|
|
|
7
8
|
function issue(code, message, artifacts = [], details = {}) {
|
|
8
9
|
return { code, message, artifacts, ...details };
|
|
@@ -65,10 +66,23 @@ export function completionRelationshipErrors({
|
|
|
65
66
|
requiredEvidence = [],
|
|
66
67
|
requireReceiptStateFingerprint = true,
|
|
67
68
|
requireRequiredChecks = true,
|
|
69
|
+
target,
|
|
70
|
+
taskId,
|
|
71
|
+
authorities,
|
|
72
|
+
authorityContext,
|
|
73
|
+
runtimeContext,
|
|
68
74
|
} = {}) {
|
|
69
75
|
const errors = stateIdentityErrors({ contract, route, state });
|
|
70
76
|
const contractValue = contract?.value ?? contract;
|
|
71
77
|
const contractFingerprint = contract?.fingerprint;
|
|
78
|
+
const effectiveTaskId = taskId ?? contractValue?.taskId ?? state?.taskId ?? receipt?.taskId;
|
|
79
|
+
const authOptions = {
|
|
80
|
+
...(target ? { target } : {}),
|
|
81
|
+
...(effectiveTaskId ? { taskId: effectiveTaskId } : {}),
|
|
82
|
+
...(authorities ? { authorities } : {}),
|
|
83
|
+
...(authorityContext ? { authorityContext } : {}),
|
|
84
|
+
...(runtimeContext ? { runtimeContext } : {}),
|
|
85
|
+
};
|
|
72
86
|
if (contractValue && receipt && contractValue.taskId !== receipt.taskId) {
|
|
73
87
|
errors.push(issue("E_RECEIPT_TASK_MISMATCH", "Execution receipt does not belong to the current contract task", [ARTIFACT_PATHS.contract, ARTIFACT_PATHS.receipt]));
|
|
74
88
|
}
|
|
@@ -80,14 +94,30 @@ export function completionRelationshipErrors({
|
|
|
80
94
|
}
|
|
81
95
|
|
|
82
96
|
if (state) {
|
|
83
|
-
addAssertion(errors, () => assertCheckList(state.checks, "work-state.checks"), "E_CHECK_INVALID", [ARTIFACT_PATHS.state]);
|
|
97
|
+
addAssertion(errors, () => assertCheckList(state.checks, "work-state.checks", authOptions), "E_CHECK_INVALID", [ARTIFACT_PATHS.state]);
|
|
84
98
|
addAssertion(errors, () => assertEvidenceList(state.verificationEvidence, "work-state.verificationEvidence"), "E_EVIDENCE_INVALID", [ARTIFACT_PATHS.state]);
|
|
99
|
+
for (const check of state.checks ?? []) {
|
|
100
|
+
if (check.status === "passed") {
|
|
101
|
+
const auth = validateVerificationAuthority(check, authOptions);
|
|
102
|
+
if (!auth.valid) {
|
|
103
|
+
errors.push(issue(auth.error.code, auth.error.message, [ARTIFACT_PATHS.state]));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
85
107
|
}
|
|
86
108
|
if (receipt) {
|
|
87
|
-
addAssertion(errors, () => assertCheckList(receipt.checks, "receipt.checks"), "E_CHECK_INVALID", [ARTIFACT_PATHS.receipt]);
|
|
109
|
+
addAssertion(errors, () => assertCheckList(receipt.checks, "receipt.checks", authOptions), "E_CHECK_INVALID", [ARTIFACT_PATHS.receipt]);
|
|
88
110
|
addAssertion(errors, () => assertEvidenceList(receipt.evidence ?? [], "receipt.evidence"), "E_EVIDENCE_INVALID", [ARTIFACT_PATHS.receipt]);
|
|
111
|
+
for (const check of receipt.checks ?? []) {
|
|
112
|
+
if (check.status === "passed") {
|
|
113
|
+
const auth = validateVerificationAuthority(check, authOptions);
|
|
114
|
+
if (!auth.valid) {
|
|
115
|
+
errors.push(issue(auth.error.code, auth.error.message, [ARTIFACT_PATHS.receipt]));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
89
119
|
if (requireRequiredChecks) {
|
|
90
|
-
const readiness = evaluateRequiredEvidence({ requirements: requiredEvidence, checks: receipt.checks });
|
|
120
|
+
const readiness = evaluateRequiredEvidence({ requirements: requiredEvidence, checks: receipt.checks, options: authOptions });
|
|
91
121
|
for (const requirement of readiness.missing) {
|
|
92
122
|
errors.push(issue("E_EVIDENCE_REQUIRED", `Required check is missing: ${requirement.text}`, [ARTIFACT_PATHS.receipt], { requirementId: requirement.id }));
|
|
93
123
|
}
|
|
@@ -100,7 +130,12 @@ export function completionRelationshipErrors({
|
|
|
100
130
|
}
|
|
101
131
|
}
|
|
102
132
|
|
|
103
|
-
const expectedCoverage = state ? coverageForRequirements(requiredEvidence, state.checks
|
|
133
|
+
const expectedCoverage = state ? coverageForRequirements(requiredEvidence, state.checks, {
|
|
134
|
+
target,
|
|
135
|
+
taskId: effectiveTaskId,
|
|
136
|
+
authorities,
|
|
137
|
+
options: authOptions,
|
|
138
|
+
}) : [];
|
|
104
139
|
if (state?.evidenceCoverage !== undefined) {
|
|
105
140
|
addAssertion(errors, () => assertCoverageList(state.evidenceCoverage, "work-state.evidenceCoverage"), "E_EVIDENCE_COVERAGE_INVALID", [ARTIFACT_PATHS.state]);
|
|
106
141
|
if (!sameValue(state.evidenceCoverage, expectedCoverage)) {
|
package/src/core/completion.js
CHANGED
|
@@ -77,6 +77,13 @@ function repairNext(error) {
|
|
|
77
77
|
return "Satisfy or refresh the named gate, then rerun forgeloop preflight.";
|
|
78
78
|
case "E_PROFILE_UNVERIFIED":
|
|
79
79
|
return "Use Standard mode for a fresh target, or verify PROJECT_PROFILE.md before Strict completion.";
|
|
80
|
+
case "E_INSTALLATION_AUTHORITY_REQUIRED":
|
|
81
|
+
case "E_AUTHORITY_INVALID":
|
|
82
|
+
case "E_AUTHORITY_SCOPE_MISMATCH":
|
|
83
|
+
case "E_AUTHORITY_UNTRUSTED_SOURCE":
|
|
84
|
+
return "Do not execute installation-capable verification commands without explicit scoped installation authority; use local equivalents or record NOT_VERIFIED.";
|
|
85
|
+
case "E_VERIFICATION_TOOL_UNAVAILABLE":
|
|
86
|
+
return "Use an available local verifier, an existing equivalent, or record NOT_VERIFIED if installation was not authorized.";
|
|
80
87
|
default:
|
|
81
88
|
return "Resolve this validator finding in the named artifact before retrying completion.";
|
|
82
89
|
}
|
|
@@ -151,7 +158,7 @@ async function validateLedger(target, taskId, state, errors, packageRoot) {
|
|
|
151
158
|
return ledger;
|
|
152
159
|
}
|
|
153
160
|
|
|
154
|
-
export async function evaluateCompletion({ target, packageRoot, strict = false } = {}) {
|
|
161
|
+
export async function evaluateCompletion({ target, packageRoot, strict = false, authorityContext, runtimeContext } = {}) {
|
|
155
162
|
const errors = [];
|
|
156
163
|
const preflight = await evaluatePreflight({ target, packageRoot, strict });
|
|
157
164
|
errors.push(...preflight.errors);
|
|
@@ -198,7 +205,12 @@ export async function evaluateCompletion({ target, packageRoot, strict = false }
|
|
|
198
205
|
|
|
199
206
|
if (receipt) {
|
|
200
207
|
try {
|
|
201
|
-
await validateReceipt(receipt.value, packageRoot
|
|
208
|
+
await validateReceipt(receipt.value, packageRoot, {
|
|
209
|
+
target,
|
|
210
|
+
taskId: contract?.value?.taskId,
|
|
211
|
+
authorityContext,
|
|
212
|
+
runtimeContext,
|
|
213
|
+
});
|
|
202
214
|
} catch (error) {
|
|
203
215
|
errors.push(issue(error.code ?? "E_RECEIPT_INVALID", `Execution receipt is invalid: ${error.message}`, [ARTIFACT_PATHS.receipt]));
|
|
204
216
|
}
|
|
@@ -223,6 +235,10 @@ export async function evaluateCompletion({ target, packageRoot, strict = false }
|
|
|
223
235
|
state,
|
|
224
236
|
receipt: receipt?.value,
|
|
225
237
|
requiredEvidence,
|
|
238
|
+
target,
|
|
239
|
+
taskId: contract?.value?.taskId,
|
|
240
|
+
authorityContext,
|
|
241
|
+
runtimeContext,
|
|
226
242
|
});
|
|
227
243
|
errors.push(...relationshipErrors);
|
|
228
244
|
coverage = receipt?.value?.evidenceCoverage ?? [];
|
|
@@ -290,12 +306,18 @@ export async function evaluateCompletion({ target, packageRoot, strict = false }
|
|
|
290
306
|
};
|
|
291
307
|
}
|
|
292
308
|
|
|
293
|
-
export async function runComplete({ target, packageRoot, strict = false, persist = true } = {}) {
|
|
294
|
-
const result = await evaluateCompletion({ target, packageRoot, strict });
|
|
309
|
+
export async function runComplete({ target, packageRoot, strict = false, persist = true, authorityContext, runtimeContext } = {}) {
|
|
310
|
+
const result = await evaluateCompletion({ target, packageRoot, strict, authorityContext, runtimeContext });
|
|
295
311
|
const rejectionCodes = [...new Set(result.errors.map((error) => error.code))].sort();
|
|
296
312
|
const evidenceOnlyRejection = rejectionCodes.length > 0
|
|
297
313
|
&& rejectionCodes.every(isRecoverableCompletionEvidenceCode);
|
|
298
|
-
|
|
314
|
+
const authorityRejection = rejectionCodes.some((code) => [
|
|
315
|
+
"E_INSTALLATION_AUTHORITY_REQUIRED",
|
|
316
|
+
"E_AUTHORITY_INVALID",
|
|
317
|
+
"E_AUTHORITY_SCOPE_MISMATCH",
|
|
318
|
+
"E_AUTHORITY_UNTRUSTED_SOURCE",
|
|
319
|
+
].includes(code));
|
|
320
|
+
if (persist && result.status === "REJECTED" && evidenceOnlyRejection && !authorityRejection) {
|
|
299
321
|
const state = await readWorkState(target, packageRoot);
|
|
300
322
|
if (state?.phase === "REVIEWING") {
|
|
301
323
|
const reasonCodes = rejectionCodes;
|
|
@@ -335,7 +357,7 @@ export async function runComplete({ target, packageRoot, strict = false, persist
|
|
|
335
357
|
...receipt.value,
|
|
336
358
|
stateFingerprint: canonicalFingerprint(next),
|
|
337
359
|
verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
|
|
338
|
-
}, packageRoot);
|
|
360
|
+
}, packageRoot, { target, taskId: state.taskId, authorityContext, runtimeContext });
|
|
339
361
|
await writeJsonArtifact(target, ARTIFACT_PATHS.receipt, nextReceipt, "execution-receipt", packageRoot);
|
|
340
362
|
}
|
|
341
363
|
const ledger = await validateEventLedger(target, packageRoot);
|
|
@@ -380,7 +402,7 @@ export async function runComplete({ target, packageRoot, strict = false, persist
|
|
|
380
402
|
...receipt.value,
|
|
381
403
|
stateFingerprint: canonicalFingerprint(next),
|
|
382
404
|
verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
|
|
383
|
-
}, packageRoot);
|
|
405
|
+
}, packageRoot, { target, taskId: state.taskId, authorityContext, runtimeContext });
|
|
384
406
|
await writeWorkState(target, next, { packageRoot });
|
|
385
407
|
await writeJsonArtifact(target, ARTIFACT_PATHS.receipt, nextReceipt, "execution-receipt", packageRoot);
|
|
386
408
|
}
|
package/src/core/coverage.js
CHANGED
|
@@ -73,10 +73,23 @@ export function assertCoverageList(value, label = "evidenceCoverage") {
|
|
|
73
73
|
return value;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export function coverageForRequirements(requirements, checks, {
|
|
76
|
+
export function coverageForRequirements(requirements, checks, {
|
|
77
|
+
blockedIds = [],
|
|
78
|
+
target,
|
|
79
|
+
taskId,
|
|
80
|
+
authorities,
|
|
81
|
+
options = {},
|
|
82
|
+
} = {}) {
|
|
77
83
|
const normalizedRequirements = normalizeRequirements(requirements ?? []);
|
|
78
84
|
const normalizedChecks = Array.isArray(checks) ? checks : [];
|
|
79
|
-
const readiness = evaluateRequiredEvidence({
|
|
85
|
+
const readiness = evaluateRequiredEvidence({
|
|
86
|
+
requirements: normalizedRequirements,
|
|
87
|
+
checks: normalizedChecks,
|
|
88
|
+
target,
|
|
89
|
+
taskId,
|
|
90
|
+
authorities,
|
|
91
|
+
options,
|
|
92
|
+
});
|
|
80
93
|
const covered = new Set(readiness.covered.map((item) => item.id));
|
|
81
94
|
const partial = new Set(readiness.partial.map((item) => item.id));
|
|
82
95
|
const invalid = new Set(readiness.invalid.map((item) => item.id));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sha256 } from "./manifest.js";
|
|
2
|
+
import { validateVerificationAuthority } from "./verification-capability.js";
|
|
2
3
|
|
|
3
4
|
export const REQUIREMENT_TYPES = Object.freeze([
|
|
4
5
|
"PRODUCT",
|
|
@@ -246,7 +247,7 @@ export function authoritativeChecksForRequirements({ requirements = [], checks =
|
|
|
246
247
|
});
|
|
247
248
|
}
|
|
248
249
|
|
|
249
|
-
function componentStatus(check, requirement, allChecks = []) {
|
|
250
|
+
function componentStatus(check, requirement, allChecks = [], options = {}) {
|
|
250
251
|
if (requirement.operator !== "ALL" || !requirement.requirements?.length) return null;
|
|
251
252
|
const components = check?.details?.components;
|
|
252
253
|
const statuses = requirement.requirements.map((child) => {
|
|
@@ -254,10 +255,19 @@ function componentStatus(check, requirement, allChecks = []) {
|
|
|
254
255
|
const matchingComp = components.filter((item) => (
|
|
255
256
|
item?.requirementId === child.id || item?.requirement === child.text
|
|
256
257
|
)).at(-1);
|
|
257
|
-
if (matchingComp)
|
|
258
|
+
if (matchingComp) {
|
|
259
|
+
const auth = validateVerificationAuthority(matchingComp, options);
|
|
260
|
+
if (!auth.valid) return { ...matchingComp, status: "failed", reasonCode: auth.error.code };
|
|
261
|
+
return matchingComp;
|
|
262
|
+
}
|
|
258
263
|
}
|
|
259
264
|
const childCandidates = allChecks.filter((candidate) => matchesRequirement(candidate, child));
|
|
260
|
-
|
|
265
|
+
const childCheck = latestAuthoritativeCheck(childCandidates);
|
|
266
|
+
if (childCheck) {
|
|
267
|
+
const auth = validateVerificationAuthority(childCheck, options);
|
|
268
|
+
if (!auth.valid) return { ...childCheck, status: "failed", reasonCode: auth.error.code };
|
|
269
|
+
}
|
|
270
|
+
return childCheck;
|
|
261
271
|
});
|
|
262
272
|
if (statuses.some((item) => !item)) return "MISSING";
|
|
263
273
|
if (statuses.some((item) => item.status === "failed")) return "INVALID";
|
|
@@ -265,7 +275,20 @@ function componentStatus(check, requirement, allChecks = []) {
|
|
|
265
275
|
return "COVERED";
|
|
266
276
|
}
|
|
267
277
|
|
|
268
|
-
export function evaluateRequiredEvidence({
|
|
278
|
+
export function evaluateRequiredEvidence({
|
|
279
|
+
requirements = [],
|
|
280
|
+
checks = [],
|
|
281
|
+
target,
|
|
282
|
+
taskId,
|
|
283
|
+
authorities,
|
|
284
|
+
options = {},
|
|
285
|
+
} = {}) {
|
|
286
|
+
const authOptions = {
|
|
287
|
+
...(target ? { target } : {}),
|
|
288
|
+
...(taskId ? { taskId } : {}),
|
|
289
|
+
...(authorities ? { authorities } : {}),
|
|
290
|
+
...options,
|
|
291
|
+
};
|
|
269
292
|
const normalized = normalizeRequirements(requirements);
|
|
270
293
|
const result = {
|
|
271
294
|
ready: true,
|
|
@@ -291,8 +314,11 @@ export function evaluateRequiredEvidence({ requirements = [], checks = [] } = {}
|
|
|
291
314
|
}
|
|
292
315
|
const candidates = checks.filter((check) => matchesRequirement(check, requirement));
|
|
293
316
|
const check = latestAuthoritativeCheck(candidates);
|
|
294
|
-
const
|
|
295
|
-
|
|
317
|
+
const auth = check ? validateVerificationAuthority(check, authOptions) : { valid: true };
|
|
318
|
+
const compound = componentStatus(check, requirement, checks, authOptions);
|
|
319
|
+
if (!auth.valid) {
|
|
320
|
+
result.invalid.push({ ...requirement, reasonCode: auth.error.code });
|
|
321
|
+
} else if (compound === "INVALID" || check?.status === "failed") {
|
|
296
322
|
result.invalid.push(requirement);
|
|
297
323
|
} else if (compound === "PARTIAL") {
|
|
298
324
|
result.partial.push(requirement);
|
package/src/core/inspect.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createEvidence } from "./evidence.js";
|
|
|
8
8
|
import { runDoctor } from "../commands/doctor.js";
|
|
9
9
|
import { findProfilePath } from "./profile.js";
|
|
10
10
|
import { FORGELOOP_KIT_DIR } from "./target-layout.js";
|
|
11
|
+
import { trustedAuthorityConfiguration } from "./trusted-authority.js";
|
|
11
12
|
|
|
12
13
|
function profileMetadata(bytes) {
|
|
13
14
|
const text = bytes.toString("utf8");
|
|
@@ -17,7 +18,7 @@ function profileMetadata(bytes) {
|
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export async function inspectTarget({ target, packageRoot, contractFile = null }) {
|
|
21
|
+
export async function inspectTarget({ target, packageRoot, contractFile = null, authorityContext, runtimeContext }) {
|
|
21
22
|
let manifest = null;
|
|
22
23
|
let manifestError = null;
|
|
23
24
|
try {
|
|
@@ -91,6 +92,7 @@ export async function inspectTarget({ target, packageRoot, contractFile = null }
|
|
|
91
92
|
];
|
|
92
93
|
return {
|
|
93
94
|
target: { path: target },
|
|
95
|
+
authority: trustedAuthorityConfiguration({ target, authorityContext, runtimeContext }),
|
|
94
96
|
manifest: {
|
|
95
97
|
present: manifest !== null,
|
|
96
98
|
status: manifestError ? "invalid" : manifest ? "ready" : "missing",
|
package/src/core/next-action.js
CHANGED
|
@@ -236,7 +236,16 @@ async function loadArtifact(loader, fallback) {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
async function requirementsAndCoverage({
|
|
239
|
+
async function requirementsAndCoverage({
|
|
240
|
+
target,
|
|
241
|
+
packageRoot,
|
|
242
|
+
contract,
|
|
243
|
+
route,
|
|
244
|
+
checks,
|
|
245
|
+
additionalEvidence = [],
|
|
246
|
+
authorityContext,
|
|
247
|
+
runtimeContext,
|
|
248
|
+
}) {
|
|
240
249
|
const requirements = await requiredEvidenceForTarget({
|
|
241
250
|
target,
|
|
242
251
|
contract,
|
|
@@ -244,14 +253,21 @@ async function requirementsAndCoverage({ target, packageRoot, contract, route, c
|
|
|
244
253
|
packageRoot,
|
|
245
254
|
additionalEvidence,
|
|
246
255
|
});
|
|
247
|
-
return {
|
|
256
|
+
return {
|
|
257
|
+
requirements,
|
|
258
|
+
coverage: coverageForRequirements(requirements, checks, {
|
|
259
|
+
target,
|
|
260
|
+
taskId: contract?.value?.taskId,
|
|
261
|
+
options: { authorityContext, runtimeContext },
|
|
262
|
+
}),
|
|
263
|
+
};
|
|
248
264
|
}
|
|
249
265
|
|
|
250
266
|
export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
251
267
|
const normalized = typeof targetOrOptions === "string"
|
|
252
268
|
? { target: targetOrOptions, packageRoot: packageRootOption }
|
|
253
269
|
: targetOrOptions;
|
|
254
|
-
const { target, packageRoot } = normalized ?? {};
|
|
270
|
+
const { target, packageRoot, authorityContext, runtimeContext } = normalized ?? {};
|
|
255
271
|
const workState = await loadArtifact(
|
|
256
272
|
() => readWorkState(target, packageRoot),
|
|
257
273
|
ARTIFACT_PATHS.state,
|
|
@@ -544,6 +560,8 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
544
560
|
route,
|
|
545
561
|
checks: state.checks,
|
|
546
562
|
additionalEvidence: preflight.policy?.requiredEvidence ?? [],
|
|
563
|
+
authorityContext,
|
|
564
|
+
runtimeContext,
|
|
547
565
|
});
|
|
548
566
|
const authoritative = authoritativeChecksForRequirements({
|
|
549
567
|
requirements: ordinaryLeafRequirements(evidence.requirements),
|
|
@@ -587,7 +605,12 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
587
605
|
);
|
|
588
606
|
}
|
|
589
607
|
try {
|
|
590
|
-
await validateReceipt(receipt.value.value, packageRoot
|
|
608
|
+
await validateReceipt(receipt.value.value, packageRoot, {
|
|
609
|
+
target,
|
|
610
|
+
taskId: contract?.value?.taskId,
|
|
611
|
+
authorityContext,
|
|
612
|
+
runtimeContext,
|
|
613
|
+
});
|
|
591
614
|
} catch (error) {
|
|
592
615
|
return decision(
|
|
593
616
|
context,
|
|
@@ -596,7 +619,13 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
596
619
|
[...requiredArtifacts, ARTIFACT_PATHS.receipt],
|
|
597
620
|
);
|
|
598
621
|
}
|
|
599
|
-
const readiness = evaluateRequiredEvidence({
|
|
622
|
+
const readiness = evaluateRequiredEvidence({
|
|
623
|
+
requirements: evidence.requirements,
|
|
624
|
+
checks: state.checks,
|
|
625
|
+
target,
|
|
626
|
+
taskId: contract?.value?.taskId,
|
|
627
|
+
options: { authorityContext, runtimeContext },
|
|
628
|
+
});
|
|
600
629
|
const receiptRelationships = completionRelationshipErrors({
|
|
601
630
|
contract,
|
|
602
631
|
route,
|
|
@@ -604,8 +633,26 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
604
633
|
receipt: receipt.value.value,
|
|
605
634
|
requiredEvidence: evidence.requirements,
|
|
606
635
|
requireRequiredChecks: false,
|
|
636
|
+
target,
|
|
637
|
+
taskId: contract?.value?.taskId,
|
|
638
|
+
authorityContext,
|
|
639
|
+
runtimeContext,
|
|
607
640
|
});
|
|
608
641
|
if (receiptRelationships.length > 0) {
|
|
642
|
+
if (receiptRelationships.some((err) => (
|
|
643
|
+
err.code === "E_RECEIPT_STATE_MISMATCH"
|
|
644
|
+
|| err.code === "E_RECEIPT_CYCLE_MISMATCH"
|
|
645
|
+
|| err.code === "E_RECEIPT_CONTRACT_MISMATCH"
|
|
646
|
+
|| err.code === "E_ROUTE_GUIDE_MISMATCH"
|
|
647
|
+
|| err.code === "E_EVIDENCE_COVERAGE_INVALID"
|
|
648
|
+
))) {
|
|
649
|
+
return decision(
|
|
650
|
+
context,
|
|
651
|
+
NEXT_ACTIONS.PREPARE_COMPLETION,
|
|
652
|
+
artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [ARTIFACT_PATHS.receipt]),
|
|
653
|
+
[...requiredArtifacts, ARTIFACT_PATHS.receipt],
|
|
654
|
+
);
|
|
655
|
+
}
|
|
609
656
|
return result({
|
|
610
657
|
...context,
|
|
611
658
|
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
@@ -670,8 +717,16 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
670
717
|
route,
|
|
671
718
|
checks: state.checks,
|
|
672
719
|
additionalEvidence: preflight.policy?.requiredEvidence ?? [],
|
|
720
|
+
authorityContext,
|
|
721
|
+
runtimeContext,
|
|
722
|
+
});
|
|
723
|
+
const readiness = evaluateRequiredEvidence({
|
|
724
|
+
requirements: evidence.requirements,
|
|
725
|
+
checks: state.checks,
|
|
726
|
+
target,
|
|
727
|
+
taskId: contract?.value?.taskId,
|
|
728
|
+
options: { authorityContext, runtimeContext },
|
|
673
729
|
});
|
|
674
|
-
const readiness = evaluateRequiredEvidence({ requirements: evidence.requirements, checks: state.checks });
|
|
675
730
|
if (!readiness.ready) {
|
|
676
731
|
let recoveryAuthorized = false;
|
|
677
732
|
if (state.lastCompletionAttempt?.status === "REJECTED") {
|
|
@@ -737,7 +792,12 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
737
792
|
);
|
|
738
793
|
}
|
|
739
794
|
try {
|
|
740
|
-
await validateReceipt(receipt.value.value, packageRoot
|
|
795
|
+
await validateReceipt(receipt.value.value, packageRoot, {
|
|
796
|
+
target,
|
|
797
|
+
taskId: contract?.value?.taskId,
|
|
798
|
+
authorityContext,
|
|
799
|
+
runtimeContext,
|
|
800
|
+
});
|
|
741
801
|
} catch (error) {
|
|
742
802
|
return decision(
|
|
743
803
|
context,
|
|
@@ -752,8 +812,26 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
752
812
|
state,
|
|
753
813
|
receipt: receipt.value.value,
|
|
754
814
|
requiredEvidence: evidence.requirements,
|
|
815
|
+
target,
|
|
816
|
+
taskId: contract?.value?.taskId,
|
|
817
|
+
authorityContext,
|
|
818
|
+
runtimeContext,
|
|
755
819
|
});
|
|
756
820
|
if (receiptRelationships.length > 0) {
|
|
821
|
+
if (receiptRelationships.some((err) => (
|
|
822
|
+
err.code === "E_RECEIPT_STATE_MISMATCH"
|
|
823
|
+
|| err.code === "E_RECEIPT_CYCLE_MISMATCH"
|
|
824
|
+
|| err.code === "E_RECEIPT_CONTRACT_MISMATCH"
|
|
825
|
+
|| err.code === "E_ROUTE_GUIDE_MISMATCH"
|
|
826
|
+
|| err.code === "E_EVIDENCE_COVERAGE_INVALID"
|
|
827
|
+
))) {
|
|
828
|
+
return decision(
|
|
829
|
+
context,
|
|
830
|
+
NEXT_ACTIONS.PREPARE_COMPLETION,
|
|
831
|
+
artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [ARTIFACT_PATHS.receipt]),
|
|
832
|
+
[...requiredArtifacts, ARTIFACT_PATHS.receipt],
|
|
833
|
+
);
|
|
834
|
+
}
|
|
757
835
|
return result({
|
|
758
836
|
...context,
|
|
759
837
|
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
@@ -761,7 +839,7 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
761
839
|
requiredArtifacts: [...requiredArtifacts, ARTIFACT_PATHS.receipt],
|
|
762
840
|
});
|
|
763
841
|
}
|
|
764
|
-
const completion = await evaluateCompletion({ target, packageRoot });
|
|
842
|
+
const completion = await evaluateCompletion({ target, packageRoot, authorityContext, runtimeContext });
|
|
765
843
|
if (completion.status !== "VALID") {
|
|
766
844
|
const terminalPendingErrors = completion.errors.filter((err) => (
|
|
767
845
|
err.code === "E_PUBLICATION_REQUIREMENT_PENDING" || err.code === "E_PRODUCTION_REQUIREMENT_PENDING"
|
|
@@ -793,7 +871,7 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
793
871
|
return decision(context, NEXT_ACTIONS.RUN_COMPLETE, artifactError("COMPLETION_READY", "Completion artifacts and cross-artifact validation are valid"));
|
|
794
872
|
}
|
|
795
873
|
if (state.phase === "COMPLETE") {
|
|
796
|
-
const completion = await evaluateCompletion({ target, packageRoot });
|
|
874
|
+
const completion = await evaluateCompletion({ target, packageRoot, authorityContext, runtimeContext });
|
|
797
875
|
if (completion.status === "VALID") {
|
|
798
876
|
return decision(context, NEXT_ACTIONS.NONE, artifactError("PHASE_COMPLETE", "Completion is validator-backed and terminal"));
|
|
799
877
|
}
|
package/src/core/phase.js
CHANGED
|
@@ -101,7 +101,7 @@ function reconcileImplementationStep(state, toPhase) {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
async function assertPhasePrerequisites(target, state, toPhase, packageRoot) {
|
|
104
|
+
async function assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext) {
|
|
105
105
|
if (toPhase === "CONTRACT_READY" || toPhase === "ROUTED" || toPhase === "EXECUTING") {
|
|
106
106
|
try {
|
|
107
107
|
await readContract(target, packageRoot);
|
|
@@ -127,7 +127,7 @@ async function assertPhasePrerequisites(target, state, toPhase, packageRoot) {
|
|
|
127
127
|
throw phaseError("E_PHASE_EVIDENCE_MISSING", "COMPLETE requires verification evidence");
|
|
128
128
|
}
|
|
129
129
|
if (toPhase === "COMPLETE") {
|
|
130
|
-
const completion = await evaluateCompletion({ target, packageRoot, persist: false });
|
|
130
|
+
const completion = await evaluateCompletion({ target, packageRoot, persist: false, authorityContext, runtimeContext });
|
|
131
131
|
if (completion.status !== "VALID") {
|
|
132
132
|
throw phaseError("E_COMPLETION_REJECTED", "COMPLETE requires a valid completion audit", completion.errors.flatMap((error) => error.artifacts ?? []));
|
|
133
133
|
}
|
|
@@ -136,11 +136,16 @@ async function assertPhasePrerequisites(target, state, toPhase, packageRoot) {
|
|
|
136
136
|
|
|
137
137
|
export async function advanceWorkState(target, toPhase, options = {}) {
|
|
138
138
|
const normalizedOptions = typeof options === "string" ? { packageRoot: options } : options;
|
|
139
|
-
const {
|
|
139
|
+
const {
|
|
140
|
+
packageRoot,
|
|
141
|
+
now = new Date().toISOString(),
|
|
142
|
+
authorityContext,
|
|
143
|
+
runtimeContext,
|
|
144
|
+
} = normalizedOptions;
|
|
140
145
|
assertWorkPhase(toPhase);
|
|
141
146
|
const state = await readWorkState(target, packageRoot);
|
|
142
147
|
if (!state) throw phaseError("E_PHASE_PREREQUISITE_MISSING", "Cannot advance without work state", [ARTIFACT_PATHS.state]);
|
|
143
|
-
await assertPhasePrerequisites(target, state, toPhase, packageRoot);
|
|
148
|
+
await assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext);
|
|
144
149
|
await assertPersistedStateIdentity(target, state, toPhase, packageRoot);
|
|
145
150
|
if (!isValidTransition(state.phase, toPhase)) {
|
|
146
151
|
throw phaseError("E_PHASE_TRANSITION_INVALID", `Invalid work-state transition: ${state.phase} -> ${toPhase}`);
|
|
@@ -231,7 +236,12 @@ export async function advanceWorkState(target, toPhase, options = {}) {
|
|
|
231
236
|
packageRoot,
|
|
232
237
|
additionalEvidence: preflight.policy?.requiredEvidence ?? [],
|
|
233
238
|
});
|
|
234
|
-
await validateReceipt(receipt.value, packageRoot
|
|
239
|
+
await validateReceipt(receipt.value, packageRoot, {
|
|
240
|
+
target,
|
|
241
|
+
taskId: contract?.value?.taskId,
|
|
242
|
+
authorityContext,
|
|
243
|
+
runtimeContext,
|
|
244
|
+
});
|
|
235
245
|
assertCompletionRelationships({
|
|
236
246
|
contract,
|
|
237
247
|
route,
|
|
@@ -240,12 +250,21 @@ export async function advanceWorkState(target, toPhase, options = {}) {
|
|
|
240
250
|
requiredEvidence,
|
|
241
251
|
requireRequiredChecks: false,
|
|
242
252
|
requireReceiptStateFingerprint: false,
|
|
253
|
+
target,
|
|
254
|
+
taskId: contract?.value?.taskId,
|
|
255
|
+
authorityContext,
|
|
256
|
+
runtimeContext,
|
|
243
257
|
});
|
|
244
258
|
nextReceipt = await createReceipt({
|
|
245
259
|
...receipt.value,
|
|
246
260
|
stateFingerprint: canonicalFingerprint(next),
|
|
247
261
|
verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
|
|
248
|
-
}, packageRoot
|
|
262
|
+
}, packageRoot, {
|
|
263
|
+
target,
|
|
264
|
+
taskId: contract?.value?.taskId,
|
|
265
|
+
authorityContext,
|
|
266
|
+
runtimeContext,
|
|
267
|
+
});
|
|
249
268
|
assertCompletionRelationships({
|
|
250
269
|
contract,
|
|
251
270
|
route,
|
|
@@ -253,6 +272,10 @@ export async function advanceWorkState(target, toPhase, options = {}) {
|
|
|
253
272
|
receipt: nextReceipt,
|
|
254
273
|
requiredEvidence,
|
|
255
274
|
requireRequiredChecks: false,
|
|
275
|
+
target,
|
|
276
|
+
taskId: contract?.value?.taskId,
|
|
277
|
+
authorityContext,
|
|
278
|
+
runtimeContext,
|
|
256
279
|
});
|
|
257
280
|
} catch (error) {
|
|
258
281
|
if (error.code !== "ARTIFACT_MISSING") throw error;
|
package/src/core/protocol.js
CHANGED
|
@@ -81,6 +81,11 @@ export const FAILURE_CODES = Object.freeze([
|
|
|
81
81
|
"E_TERMINAL_REQUIREMENT_NOT_TERMINAL",
|
|
82
82
|
"E_TERMINAL_REQUIREMENT_TYPE_MISMATCH",
|
|
83
83
|
"E_TERMINAL_STATUS_REGRESSION",
|
|
84
|
+
"E_INSTALLATION_AUTHORITY_REQUIRED",
|
|
85
|
+
"E_AUTHORITY_INVALID",
|
|
86
|
+
"E_AUTHORITY_SCOPE_MISMATCH",
|
|
87
|
+
"E_AUTHORITY_UNTRUSTED_SOURCE",
|
|
88
|
+
"E_VERIFICATION_TOOL_UNAVAILABLE",
|
|
84
89
|
]);
|
|
85
90
|
|
|
86
91
|
export const WORK_PHASES = Object.freeze([
|
package/src/core/receipt.js
CHANGED
|
@@ -56,7 +56,7 @@ function assertKnownGuides(guides) {
|
|
|
56
56
|
if (new Set(guides).size !== guides.length) throw new Error("Receipt selectedGuides must not contain duplicates");
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export function assertReceiptSemantics(receipt) {
|
|
59
|
+
export function assertReceiptSemantics(receipt, options = {}) {
|
|
60
60
|
const evidence = receipt.evidence ?? [];
|
|
61
61
|
if (receipt.verificationCycle !== undefined
|
|
62
62
|
&& (!Number.isInteger(receipt.verificationCycle) || receipt.verificationCycle < 1)) {
|
|
@@ -66,7 +66,7 @@ export function assertReceiptSemantics(receipt) {
|
|
|
66
66
|
|
|
67
67
|
for (const [index, check] of receipt.checks.entries()) {
|
|
68
68
|
if (check?.schemaVersion !== undefined || check?.id !== undefined || check?.evidenceKind !== undefined) {
|
|
69
|
-
assertCheck(check, `receipt.checks[${index}]
|
|
69
|
+
assertCheck(check, `receipt.checks[${index}]`, options);
|
|
70
70
|
continue;
|
|
71
71
|
}
|
|
72
72
|
if (check?.status === "passed") {
|
|
@@ -132,15 +132,15 @@ export function assertReceiptSemantics(receipt) {
|
|
|
132
132
|
return receipt;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
export async function validateReceipt(receipt, packageRoot) {
|
|
135
|
+
export async function validateReceipt(receipt, packageRoot, options = {}) {
|
|
136
136
|
assertSecretFree(receipt);
|
|
137
137
|
const schema = await readSchema("execution-receipt", packageRoot);
|
|
138
138
|
assertSchema(receipt, schema, "execution receipt");
|
|
139
139
|
assertKnownGuides(receipt.selectedGuides);
|
|
140
|
-
return assertReceiptSemantics(receipt);
|
|
140
|
+
return assertReceiptSemantics(receipt, options);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
export async function createReceipt(input, packageRoot) {
|
|
143
|
+
export async function createReceipt(input, packageRoot, options = {}) {
|
|
144
144
|
assertSecretFree(input);
|
|
145
145
|
const receipt = {
|
|
146
146
|
schemaVersion: RECEIPT_SCHEMA_VERSION,
|
|
@@ -169,5 +169,5 @@ export async function createReceipt(input, packageRoot) {
|
|
|
169
169
|
deployed: false,
|
|
170
170
|
},
|
|
171
171
|
};
|
|
172
|
-
return validateReceipt(receipt, packageRoot);
|
|
172
|
+
return validateReceipt(receipt, packageRoot, options);
|
|
173
173
|
}
|