@cassiomc1/forgeloop 0.1.12 → 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.
@@ -11,7 +11,7 @@ import { completionEvidenceForGuides } from "./guide-metadata.js";
11
11
  import { createCheck } from "./checks.js";
12
12
  import { createEvidence } from "./evidence.js";
13
13
  import { coverageForRequirements } from "./coverage.js";
14
- import { assertCompletionRelationships } from "./completion-relationships.js";
14
+ import { assertCompletionRelationships, assertStateIdentity } from "./completion-relationships.js";
15
15
  import { evaluatePreflight } from "./preflight.js";
16
16
  import { currentChangedPaths } from "./repository.js";
17
17
  import { readPersistedRoute } from "./route-artifact.js";
@@ -19,6 +19,7 @@ import { createReceipt, validateReceipt } from "./receipt.js";
19
19
  import { readWorkState, writeWorkState } from "./work-state.js";
20
20
  import { assertExecutionPrerequisites, hasExecutionStarted } from "./execution-prerequisites.js";
21
21
  import { normalizeRequirements, classifyRequirement } from "./evidence-readiness.js";
22
+ import { classifyCommandResolution, validateVerificationAuthority } from "./verification-capability.js";
22
23
 
23
24
  function artifactError(code, message, artifacts = []) {
24
25
  const error = new Error(message);
@@ -69,7 +70,7 @@ export async function requiredEvidenceForTarget({ target, contract, route, packa
69
70
  ])].sort();
70
71
  }
71
72
 
72
- export async function prepareCompletion({ target, packageRoot }) {
73
+ export async function prepareCompletion({ target, packageRoot, authorityContext, runtimeContext }) {
73
74
  const contract = await readContract(target, packageRoot);
74
75
  const route = await readPersistedRoute(target, packageRoot);
75
76
  const state = await readWorkState(target, packageRoot);
@@ -83,7 +84,12 @@ export async function prepareCompletion({ target, packageRoot }) {
83
84
  let existing = null;
84
85
  try {
85
86
  existing = await readJsonArtifact(target, ARTIFACT_PATHS.receipt, "execution-receipt", packageRoot);
86
- await validateReceipt(existing.value, packageRoot);
87
+ await validateReceipt(existing.value, packageRoot, {
88
+ target,
89
+ taskId: contract?.value?.taskId,
90
+ authorityContext,
91
+ runtimeContext,
92
+ });
87
93
  } catch (error) {
88
94
  if (error.code !== "ARTIFACT_MISSING") throw error;
89
95
  }
@@ -97,17 +103,19 @@ export async function prepareCompletion({ target, packageRoot }) {
97
103
  additionalEvidence: preflight.policy?.requiredEvidence ?? [],
98
104
  });
99
105
  const existingValue = existing?.value ?? {};
100
- assertCompletionRelationships({
101
- contract,
102
- route,
103
- state,
104
- receipt: existingValue.taskId ? existingValue : null,
105
- requiredEvidence,
106
- requireRequiredChecks: false,
107
- });
108
- const changedPaths = existing
109
- ? [...(existingValue.changedPaths ?? [])]
110
- : (await currentChangedPaths(target) ?? []);
106
+ if (existingValue.taskId && existingValue.taskId !== contract.value.taskId) {
107
+ throw artifactError("E_RECEIPT_TASK_MISMATCH", "Execution receipt does not belong to the current contract task", [ARTIFACT_PATHS.receipt]);
108
+ }
109
+ if (existing && existingValue.stateFingerprint === undefined) {
110
+ throw artifactError("E_RECEIPT_STATE_MISMATCH", "Execution receipt requires the current work-state fingerprint", [ARTIFACT_PATHS.receipt]);
111
+ }
112
+ assertStateIdentity({ contract, route, state });
113
+ const observedPaths = await currentChangedPaths(target);
114
+ const changedPaths = observedPaths !== null
115
+ ? [...observedPaths]
116
+ : existing
117
+ ? [...(existingValue.changedPaths ?? [])]
118
+ : [];
111
119
  const checks = existing ? [...existingValue.checks] : [...state.checks];
112
120
  const evidence = existing ? [...(existingValue.evidence ?? [])] : [...state.verificationEvidence];
113
121
  const receipt = await createReceipt({
@@ -126,7 +134,11 @@ export async function prepareCompletion({ target, packageRoot }) {
126
134
  changedPaths,
127
135
  checks,
128
136
  evidence,
129
- evidenceCoverage: coverageForRequirements(requiredEvidence, checks),
137
+ evidenceCoverage: coverageForRequirements(requiredEvidence, checks, {
138
+ target,
139
+ taskId: contract.value.taskId,
140
+ options: { authorityContext, runtimeContext },
141
+ }),
130
142
  review: existingValue.review ?? { status: "not-run", independent: false },
131
143
  limitations: [...(existingValue.limitations ?? [])],
132
144
  publication: existingValue.publication ?? {
@@ -135,7 +147,24 @@ export async function prepareCompletion({ target, packageRoot }) {
135
147
  pullRequest: null,
136
148
  deployed: false,
137
149
  },
138
- }, packageRoot);
150
+ }, packageRoot, {
151
+ target,
152
+ taskId: contract.value.taskId,
153
+ authorityContext,
154
+ runtimeContext,
155
+ });
156
+ assertCompletionRelationships({
157
+ contract,
158
+ route,
159
+ state,
160
+ receipt,
161
+ requiredEvidence,
162
+ requireRequiredChecks: false,
163
+ target,
164
+ taskId: contract.value.taskId,
165
+ authorityContext,
166
+ runtimeContext,
167
+ });
139
168
  const written = await writeJsonArtifact(
140
169
  target,
141
170
  ARTIFACT_PATHS.receipt,
@@ -179,6 +208,8 @@ export async function recordCheck({
179
208
  result,
180
209
  exitCode,
181
210
  details,
211
+ authorityContext,
212
+ runtimeContext,
182
213
  }) {
183
214
  requiredString(id, "check id");
184
215
  requiredString(kind, "check kind");
@@ -199,6 +230,8 @@ export async function recordCheck({
199
230
  throw artifactError("E_CHECK_INVALID", "record-check requires --command or --result");
200
231
  }
201
232
 
233
+ const commandSpec = typeof command === "string" && command.trim() !== "" ? command.trim() : undefined;
234
+
202
235
  const state = await readWorkState(target, packageRoot);
203
236
  if (!state) throw artifactError("E_STATE_MISSING", "Work state is required before recording a check", [ARTIFACT_PATHS.state]);
204
237
  if (["COMPLETE", "BLOCKED"].includes(state.phase)) {
@@ -235,9 +268,20 @@ export async function recordCheck({
235
268
  }
236
269
 
237
270
  const existingReceipt = await readCurrentReceipt(target, packageRoot);
238
- await validateReceipt(existingReceipt.value, packageRoot);
239
- const source = command?.trim() || `check:${id}`;
240
- const recordedResult = result?.trim() || `recorded command: ${command.trim()}`;
271
+ await validateReceipt(existingReceipt.value, packageRoot, {
272
+ target,
273
+ taskId: contract.value.taskId,
274
+ authorityContext,
275
+ runtimeContext,
276
+ });
277
+ const source = commandSpec || `check:${id}`;
278
+ const recordedResult = result?.trim() || `recorded command: ${commandSpec || source}`;
279
+ const classification = commandSpec !== undefined ? classifyCommandResolution(commandSpec) : null;
280
+ const installationAuthorized = Boolean(
281
+ details?.installationAuthorized
282
+ || details?.authority?.softwareInstallation === "AUTHORIZED"
283
+ || details?.execution?.installationAuthorized
284
+ );
241
285
  const check = createCheck({
242
286
  id,
243
287
  kind,
@@ -252,7 +296,20 @@ export async function recordCheck({
252
296
  ...(result === undefined ? {} : { result }),
253
297
  ...(details === undefined ? {} : details),
254
298
  verificationCycle: state.verificationCycle ?? 1,
299
+ ...(classification ? {
300
+ execution: {
301
+ resolutionMode: classification.resolutionMode,
302
+ mayInstall: classification.mayInstall,
303
+ installationAuthorized,
304
+ },
305
+ } : {}),
255
306
  },
307
+ }, {
308
+ target,
309
+ taskId: contract.value.taskId,
310
+ packageRoot,
311
+ authorityContext,
312
+ runtimeContext,
256
313
  });
257
314
  const evidence = createEvidence({
258
315
  kind: evidenceKind,
@@ -265,6 +322,19 @@ export async function recordCheck({
265
322
  },
266
323
  });
267
324
 
325
+ if (status === "passed") {
326
+ const auth = validateVerificationAuthority(check, {
327
+ target,
328
+ taskId: contract.value.taskId,
329
+ packageRoot,
330
+ authorityContext,
331
+ runtimeContext,
332
+ });
333
+ if (!auth.valid) {
334
+ throw artifactError(auth.error.code, auth.error.message, [ARTIFACT_PATHS.receipt]);
335
+ }
336
+ }
337
+
268
338
  const checks = mergeByCheckId(existingReceipt.value.checks ?? [], check);
269
339
  const evidenceList = appendUniqueEvidence(existingReceipt.value.evidence ?? [], evidence);
270
340
  assertCompletionRelationships({
@@ -274,6 +344,10 @@ export async function recordCheck({
274
344
  receipt: existingReceipt.value,
275
345
  requiredEvidence,
276
346
  requireRequiredChecks: false,
347
+ target,
348
+ taskId: contract.value.taskId,
349
+ authorityContext,
350
+ runtimeContext,
277
351
  });
278
352
  const ledger = await validateEventLedger(target, packageRoot);
279
353
  if (!ledger.valid) {
@@ -287,7 +361,11 @@ export async function recordCheck({
287
361
  [ARTIFACT_PATHS.events],
288
362
  );
289
363
  }
290
- const coverage = coverageForRequirements(requiredEvidence, checks);
364
+ const coverage = coverageForRequirements(requiredEvidence, checks, {
365
+ target,
366
+ taskId: contract.value.taskId,
367
+ options: { authorityContext, runtimeContext },
368
+ });
291
369
  const nextState = {
292
370
  ...state,
293
371
  checks,
@@ -302,7 +380,12 @@ export async function recordCheck({
302
380
  evidenceCoverage: coverage,
303
381
  stateFingerprint: canonicalFingerprint(nextState),
304
382
  verificationCycle: state.verificationCycle ?? 1,
305
- }, packageRoot);
383
+ }, packageRoot, {
384
+ target,
385
+ taskId: contract.value.taskId,
386
+ authorityContext,
387
+ runtimeContext,
388
+ });
306
389
 
307
390
  assertCompletionRelationships({
308
391
  contract,
@@ -311,6 +394,10 @@ export async function recordCheck({
311
394
  receipt: nextReceipt,
312
395
  requiredEvidence,
313
396
  requireRequiredChecks: false,
397
+ target,
398
+ taskId: contract.value.taskId,
399
+ authorityContext,
400
+ runtimeContext,
314
401
  });
315
402
 
316
403
  await writeWorkState(target, nextState, { packageRoot });
@@ -351,6 +438,8 @@ export async function recordTerminalResult({
351
438
  source,
352
439
  result,
353
440
  details = {},
441
+ authorityContext,
442
+ runtimeContext,
354
443
  } = {}) {
355
444
  if (!target || !requirement || !type || !status || !source || !result) {
356
445
  throw artifactError("E_CHECK_INVALID", "record-terminal-result requires target, requirement, type, status, source, and result", [ARTIFACT_PATHS.state]);
@@ -413,7 +502,12 @@ export async function recordTerminalResult({
413
502
  }
414
503
 
415
504
  const existingReceipt = await readCurrentReceipt(target, packageRoot);
416
- await validateReceipt(existingReceipt.value, packageRoot);
505
+ await validateReceipt(existingReceipt.value, packageRoot, {
506
+ target,
507
+ taskId: contract?.value?.taskId,
508
+ authorityContext,
509
+ runtimeContext,
510
+ });
417
511
 
418
512
  if (type === "PUBLICATION") {
419
513
  const rank = {
@@ -540,7 +634,12 @@ export async function recordTerminalResult({
540
634
  const nextReceipt = await createReceipt({
541
635
  ...receiptUpdates,
542
636
  stateFingerprint: canonicalFingerprint(nextState),
543
- }, packageRoot);
637
+ }, packageRoot, {
638
+ target,
639
+ taskId: contract.value.taskId,
640
+ authorityContext,
641
+ runtimeContext,
642
+ });
544
643
 
545
644
  await writeWorkState(target, nextState, { packageRoot });
546
645
  await writeJsonArtifact(
@@ -8,6 +8,10 @@ export const RECOVERABLE_COMPLETION_EVIDENCE_CODES = Object.freeze([
8
8
  "E_VERIFICATION_CHECK_REQUIRED",
9
9
  "E_CHECK_REQUIRED",
10
10
  "E_CHECK_INVALID",
11
+ "E_INSTALLATION_AUTHORITY_REQUIRED",
12
+ "E_AUTHORITY_INVALID",
13
+ "E_AUTHORITY_SCOPE_MISMATCH",
14
+ "E_AUTHORITY_UNTRUSTED_SOURCE",
11
15
  ]);
12
16
 
13
17
  export function isRecoverableCompletionEvidenceCode(code) {
@@ -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)) {
@@ -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
- if (persist && result.status === "REJECTED" && evidenceOnlyRejection) {
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
  }
@@ -23,6 +23,24 @@ function sortErrors(errors) {
23
23
  || left.message.localeCompare(right.message));
24
24
  }
25
25
 
26
+ export function delegationIsInScope({
27
+ state = null,
28
+ receipt = null,
29
+ events = [],
30
+ taskBriefs = [],
31
+ delegatedResults = [],
32
+ } = {}) {
33
+ if (taskBriefs && taskBriefs.length > 0) return true;
34
+ if (delegatedResults && delegatedResults.length > 0) return true;
35
+ if (state?.delegatedTasks && state.delegatedTasks.length > 0) return true;
36
+ if (state?.delegatedTaskIds && state.delegatedTaskIds.length > 0) return true;
37
+ if (receipt?.delegatedTasks && receipt.delegatedTasks.length > 0) return true;
38
+ if (Array.isArray(events) && events.some((event) => typeof event?.type === "string" && event.type.toLowerCase().includes("delegat"))) {
39
+ return true;
40
+ }
41
+ return false;
42
+ }
43
+
26
44
  export function validateTaskArtifactSet({
27
45
  route = null,
28
46
  state = null,
@@ -30,6 +48,7 @@ export function validateTaskArtifactSet({
30
48
  receipt = null,
31
49
  taskBriefs = [],
32
50
  delegatedResults = [],
51
+ events = [],
33
52
  } = {}) {
34
53
  const errors = [];
35
54
  const incomplete = [];
@@ -62,6 +81,7 @@ export function validateTaskArtifactSet({
62
81
  errors.push(error("STATE_RECEIPT_GUIDES_MISMATCH", "execution-receipt.selectedGuides must equal work-state.selectedGuides", ["state", "receipt"]));
63
82
  }
64
83
 
84
+ const delegationActive = delegationIsInScope({ state, receipt, events, taskBriefs, delegatedResults });
65
85
  const briefIds = new Set();
66
86
  for (const brief of taskBriefs) {
67
87
  if (!brief?.taskId) continue;
@@ -86,13 +106,18 @@ export function validateTaskArtifactSet({
86
106
  }
87
107
  }
88
108
 
89
- if (taskBriefs.length > 0) {
90
- for (const taskId of [...briefIds].sort()) {
91
- if (!delegatedIds.has(taskId)) incomplete.push(`missing delegated result: ${taskId}`);
109
+ if (delegationActive) {
110
+ if (taskBriefs.length > 0) {
111
+ for (const taskId of [...briefIds].sort()) {
112
+ if (!delegatedIds.has(taskId)) incomplete.push(`missing delegated result: ${taskId}`);
113
+ }
114
+ } else if (delegatedResults.length > 0) {
115
+ incomplete.push("task briefs are required when delegated results are supplied");
116
+ } else {
117
+ incomplete.push("task briefs and delegated results were not supplied for delegated task");
92
118
  }
93
- } else if (delegatedResults.length === 0) {
94
- incomplete.push("task briefs and delegated results were not supplied");
95
119
  }
120
+
96
121
  if (!route || !state || !receipt) incomplete.push("route, state, and receipt are all required for a complete artifact set");
97
122
 
98
123
  const sortedErrors = sortErrors(errors);
@@ -117,6 +142,22 @@ export function validateTaskArtifactSet({
117
142
  }
118
143
  : null;
119
144
 
145
+ const delegation = delegationActive
146
+ ? {
147
+ status: sortedErrors.some((e) => e.code.includes("DELEGAT") || e.code.includes("TASK"))
148
+ ? "INCONSISTENT"
149
+ : incomplete.some((i) => i.includes("delegat") || i.includes("brief"))
150
+ ? "INCOMPLETE"
151
+ : "VALID",
152
+ required: true,
153
+ errors: sortedErrors.filter((e) => e.code.includes("DELEGAT") || e.code.includes("TASK")),
154
+ }
155
+ : {
156
+ status: "NOT_APPLICABLE",
157
+ required: false,
158
+ errors: [],
159
+ };
160
+
120
161
  const evidenceKind = status === "VALID"
121
162
  ? "OBSERVED"
122
163
  : status === "INCOMPLETE"
@@ -131,6 +172,7 @@ export function validateTaskArtifactSet({
131
172
  errors: sortedErrors,
132
173
  incomplete: [...new Set(incomplete)].sort(),
133
174
  stale,
175
+ delegation,
134
176
  evidence: [createEvidence({
135
177
  kind: evidenceKind,
136
178
  source: "ForgeLoop protocol conformance",
@@ -73,10 +73,23 @@ export function assertCoverageList(value, label = "evidenceCoverage") {
73
73
  return value;
74
74
  }
75
75
 
76
- export function coverageForRequirements(requirements, checks, { blockedIds = [] } = {}) {
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({ requirements: normalizedRequirements, checks: normalizedChecks });
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) return 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
- return latestAuthoritativeCheck(childCandidates);
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({ requirements = [], checks = [] } = {}) {
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 compound = componentStatus(check, requirement, checks);
295
- if (compound === "INVALID" || check?.status === "failed") {
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);
@@ -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",