@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.
@@ -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
  }
@@ -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",
@@ -236,7 +236,16 @@ async function loadArtifact(loader, fallback) {
236
236
  }
237
237
  }
238
238
 
239
- async function requirementsAndCoverage({ target, packageRoot, contract, route, checks, additionalEvidence = [] }) {
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 { requirements, coverage: coverageForRequirements(requirements, checks) };
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({ requirements: evidence.requirements, checks: state.checks });
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 { packageRoot, now = new Date().toISOString() } = normalizedOptions;
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;
@@ -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([
@@ -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
  }