@cassiomc1/forgeloop 1.10.0 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/DOCS_INDEX.md +9 -0
  2. package/LOOP_ENGINEERING.md +4 -4
  3. package/LOOP_SYSTEM_DESIGN.md +23 -0
  4. package/QUALITY_SCORECARD.md +2 -2
  5. package/README.md +13 -0
  6. package/docs/AGENT_PROTOCOL_SUMMARY.md +1 -1
  7. package/docs/ARTIFACT_REFERENCE.md +15 -0
  8. package/docs/CODE_ATTESTATION.md +9 -0
  9. package/docs/DOCUMENTATION_GUIDE.md +4 -4
  10. package/docs/EXECUTION_PROFILE_BENCHMARKS.md +10 -0
  11. package/docs/MCP.md +13 -1
  12. package/docs/PACKAGE_CONTENTS.md +83 -0
  13. package/docs/RELEASE_CHECKLIST.md +8 -0
  14. package/docs/REVISION_PROVIDERS.md +9 -0
  15. package/docs/TROUBLESHOOTING.md +12 -0
  16. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +13 -2
  17. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +6 -6
  18. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +10 -1
  19. package/docs/assets/diagrams/forgeloop-engineering-flow.html +22 -11
  20. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  21. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +17 -8
  22. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +14 -3
  23. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +6 -6
  24. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +10 -1
  25. package/docs/diagrams/README.md +17 -0
  26. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +383 -57
  27. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +374 -55
  28. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +328 -47
  29. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  30. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  31. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  32. package/package.json +13 -4
  33. package/scripts/CI_VALIDATORS.md +24 -0
  34. package/scripts/check-critical-coverage.mjs +9 -0
  35. package/src/commands/doctor.js +11 -10
  36. package/src/core/actions.js +2 -2
  37. package/src/core/approvals.js +2 -2
  38. package/src/core/artifacts.js +3 -3
  39. package/src/core/checks.js +0 -33
  40. package/src/core/completion.js +2 -2
  41. package/src/core/events.js +5 -5
  42. package/src/core/execution-profile.js +18 -5
  43. package/src/core/next-action-pending-actions.js +255 -0
  44. package/src/core/next-action-phases.js +26 -764
  45. package/src/core/next-action-planned-phase.js +51 -0
  46. package/src/core/next-action-quality-guidance.js +19 -0
  47. package/src/core/next-action-recovery-phases.js +97 -0
  48. package/src/core/next-action-refresh.js +20 -0
  49. package/src/core/next-action-review-phase.js +189 -0
  50. package/src/core/next-action-verification-phase.js +192 -0
  51. package/src/core/task-recovery.js +2 -2
  52. package/src/core/transaction-maintenance.js +70 -0
  53. package/src/core/transaction.js +31 -10
  54. package/src/core/work-state.js +5 -5
  55. package/src/integration.d.ts +19 -3
  56. package/src/integration.js +1 -0
  57. package/src/core/cli-metadata.js +0 -23
  58. package/src/core/decision-classification.js +0 -55
  59. package/src/core/gates.js +0 -57
  60. package/src/core/workflow-compatibility.js +0 -151
@@ -5,7 +5,7 @@ import { assertSecretFree } from "./receipt.js";
5
5
  import { assertJsonBytes, assertJsonLimits } from "./json-safety.js";
6
6
  import { assertSchema, readSchema } from "./schema-validation.js";
7
7
  import { getPackageRoot } from "./templates.js";
8
- import { getActiveTaskTransaction, withTaskTransaction } from "./transaction.js";
8
+ import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
9
9
 
10
10
  export const ARTIFACT_PATHS = Object.freeze({
11
11
  contract: ".forgeloop/current-contract.json",
@@ -76,7 +76,7 @@ export async function readJsonArtifact(
76
76
  }
77
77
 
78
78
  const artifactPath = ensureWithin(target, relativePath);
79
- const transaction = getActiveTaskTransaction();
79
+ const transaction = (await getTaskTransaction(target));
80
80
  const stagedText = transaction ? await transaction.readText(relativePath) : null;
81
81
  if (stagedText === null && !(await fileExists(artifactPath))) {
82
82
  throw new ArtifactError(
@@ -124,7 +124,7 @@ export async function writeJsonArtifact(
124
124
  } catch (error) {
125
125
  throw artifactError("ARTIFACT_PATH_INVALID", relativePath, error);
126
126
  }
127
- const activeTransaction = getActiveTaskTransaction();
127
+ const activeTransaction = (await getTaskTransaction(target));
128
128
  if (!activeTransaction && taskId && !dryRun) {
129
129
  return withTaskTransaction({ target, taskId, operation, packageRoot }, async () => (
130
130
  writeJsonArtifact(target, relativePath, value, schemaName, packageRoot, { dryRun, taskId, operation })
@@ -134,36 +134,3 @@ export function assertCheckList(value, label = "checks", options = {}) {
134
134
  });
135
135
  return value;
136
136
  }
137
-
138
- function requiredChecksSatisfiedBy(checks, requiredValues, selector, { allowInferred = false } = {}) {
139
- assertCheckList(checks);
140
- if (!Array.isArray(requiredValues)) throw checkError("E_CHECK_INVALID", "required values must be an array");
141
- const errors = [];
142
- for (const value of requiredValues) {
143
- const candidates = checks.filter((check) => selector(check) === value);
144
- const check = candidates.find((candidate) => candidate.status === "passed"
145
- && (allowInferred || candidate.evidenceKind === "OBSERVED"))
146
- ?? candidates.find((candidate) => candidate.status === "passed")
147
- ?? candidates[0];
148
- if (!check) {
149
- errors.push(checkError("E_EVIDENCE_REQUIRED", `Required check is missing: ${value}`, [value]));
150
- continue;
151
- }
152
- if (check.status !== "passed") {
153
- errors.push(checkError("E_EVIDENCE_REQUIRED", `Required check is not passed: ${value}`, [value]));
154
- continue;
155
- }
156
- if (!allowInferred && check.evidenceKind !== "OBSERVED") {
157
- errors.push(checkError("E_EVIDENCE_KIND_INVALID", `Required check must be observed: ${value}`, [value]));
158
- }
159
- }
160
- return errors;
161
- }
162
-
163
- export function requiredChecksSatisfied(checks, requiredIds, options = {}) {
164
- return requiredChecksSatisfiedBy(checks, requiredIds, (check) => check.id, options);
165
- }
166
-
167
- export function requiredChecksSatisfiedForRequirements(checks, requiredRequirements, options = {}) {
168
- return requiredChecksSatisfiedBy(checks, requiredRequirements, (check) => check.requirement, options);
169
- }
@@ -17,7 +17,7 @@ import { listActions } from "./actions.js";
17
17
  import { readConfig } from "./config.js";
18
18
  import { resolveResponsibilityStatus } from "./responsibility.js";
19
19
  import { createCodeManifest, readCodeManifest, validateCodeManifestBindings, writeCodeManifest } from "./code-manifest.js";
20
- import { getActiveTaskTransaction, withTaskTransaction } from "./transaction.js";
20
+ import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
21
21
  import { validateStructuralQualityCheckProvenance } from "./structural-quality/service.js";
22
22
 
23
23
  async function attestationConfiguration(target, packageRoot, errors) {
@@ -856,7 +856,7 @@ async function runCompleteInternal({
856
856
 
857
857
  export async function runComplete(options = {}) {
858
858
  const taskId = options.taskId ?? null;
859
- if (options.persist !== false && taskId && !getActiveTaskTransaction()) {
859
+ if (options.persist !== false && taskId && !(await getTaskTransaction(options.target))) {
860
860
  return withTaskTransaction({
861
861
  target: options.target,
862
862
  taskId,
@@ -11,7 +11,7 @@ import { PROTOCOL_VERSION } from "./protocol.js";
11
11
  import { isRecoverableCompletionEvidenceCode } from "./completion-recovery.js";
12
12
 
13
13
  import { taskArtifactPath } from "./task-paths.js";
14
- import { getActiveTaskTransaction, withTaskTransaction } from "./transaction.js";
14
+ import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
15
15
 
16
16
  import { assertDiagnosisDetails } from "./diagnosis-model.js";
17
17
  import {
@@ -311,7 +311,7 @@ export function buildProtocolEvent(input, { checkpoint } = {}) {
311
311
  }
312
312
 
313
313
  export async function previewProtocolEvent(target, input, packageRoot, options = {}) {
314
- const activeTransaction = getActiveTaskTransaction();
314
+ const activeTransaction = (await getTaskTransaction(target));
315
315
  if (!activeTransaction) {
316
316
  return withTaskTransaction({
317
317
  target,
@@ -337,7 +337,7 @@ export async function readEvents(target, packageRoot, options = {}) {
337
337
  const relPath = options?.eventsPath ?? options?.relativePath ?? (options?.taskId ? taskArtifactPath(options.taskId, "events") : ARTIFACT_PATHS.events);
338
338
  await assertSafePath(target, relPath);
339
339
  const eventsPath = ensureWithin(target, relPath);
340
- const transaction = getActiveTaskTransaction();
340
+ const transaction = (await getTaskTransaction(target));
341
341
  if (transaction) {
342
342
  const stagedText = await transaction.readText(relPath);
343
343
  if (stagedText !== null) return parseEventsText(stagedText, relPath, packageRoot);
@@ -355,7 +355,7 @@ export async function readEventTail(target, packageRoot, options = {}) {
355
355
  const relPath = options?.eventsPath ?? options?.relativePath ?? (options?.taskId ? taskArtifactPath(options.taskId, "events") : ARTIFACT_PATHS.events);
356
356
  await assertSafePath(target, relPath);
357
357
  const eventsPath = ensureWithin(target, relPath);
358
- const transaction = getActiveTaskTransaction();
358
+ const transaction = (await getTaskTransaction(target));
359
359
  if (transaction) {
360
360
  const stagedText = await transaction.readText(relPath);
361
361
  if (stagedText !== null) {
@@ -411,7 +411,7 @@ async function parseEventsText(text, relPath, packageRoot) {
411
411
  }
412
412
 
413
413
  export async function appendProtocolEvent(target, input, packageRoot, options = {}) {
414
- const activeTransaction = getActiveTaskTransaction();
414
+ const activeTransaction = (await getTaskTransaction(target));
415
415
  if (typeof input?.taskId !== "string" || !input.taskId) throw protocolError("E_EVENT_INVALID", "event taskId is required");
416
416
  if (typeof input?.event !== "string" || !input.event) throw protocolError("E_EVENT_INVALID", "event type is required");
417
417
  const relPath = options?.eventsPath ?? options?.relativePath ?? (options?.taskId ? taskArtifactPath(options.taskId, "events") : ARTIFACT_PATHS.events);
@@ -66,24 +66,37 @@ function contractCollections(contract) {
66
66
 
67
67
  function contractSignals(contract) {
68
68
  const collections = contractCollections(contract);
69
+ const obligations = [];
70
+ const types = new Set();
71
+ function collect(requirement) {
72
+ if (typeof requirement === "string") obligations.push(requirement);
73
+ else if (requirement && typeof requirement === "object") {
74
+ if (typeof requirement.text === "string") obligations.push(requirement.text);
75
+ types.add(requirement.type);
76
+ if (Array.isArray(requirement.requirements)) requirement.requirements.forEach(collect);
77
+ }
78
+ }
79
+ collections.successCriteria.forEach(collect);
80
+ if (Array.isArray(contract?.verification)) contract.verification.forEach(collect);
69
81
  const riskText = collections.risks.join(" ").toLowerCase();
82
+ // Constraints and stop conditions describe boundaries, not obligations.
83
+ // Declared route risks remain authoritative even when a constraint excludes
84
+ // an operation; do not try to interpret prose negation as authorization.
70
85
  const allText = [
71
86
  ...collections.deliverables,
72
- ...collections.successCriteria,
73
- ...collections.constraints,
74
- ...collections.stopConditions,
87
+ ...obligations,
75
88
  ].join(" ").toLowerCase();
76
89
  const combinedText = `${riskText} ${allText}`;
77
90
  const signals = {
78
91
  secrets: /\bsecrets?\b|\bcredentials?\b|\bpasswords?\b|\bprivate keys?\b/.test(combinedText),
79
92
  personalData: /\bpersonal[- ]data\b|\bpii\b|\bsensitive data\b/.test(combinedText),
80
- publication: /\bpublication\b|\bpublish(?:ing|ed)?\b|\bdeploy(?:ment|ed)?\b/.test(combinedText),
93
+ publication: types.has("PUBLICATION") || /\bpublication\b|\bpublish(?:ing|ed)?\b|\bdeploy(?:ment|ed)?\b/.test(combinedText),
81
94
  destructive: /\bdestructive\b|\birreversible\b|\bdrop database\b|\bdelete production\b/.test(combinedText),
82
95
  migration: /\bmigration\b|\bmigrate\b|\bschema change\b|\birreversible persistence\b/.test(combinedText),
83
96
  payment: /\bpayments?\b|\bcheckout\b|\bbilling\b/.test(combinedText),
84
97
  externalMutation: /\bexternal mutation\b|\bmutate(?:s|d)? external\b|\bpublish(?:ing|ed)?\b/.test(combinedText),
85
98
  authoritySensitiveExternalMutation: /\bauthority[- ]sensitive\b|\bhost[- ]authorized external\b|\bexternal mutation requiring authority\b/.test(combinedText),
86
- broadProductionValidation: /\bbroad production validation\b|\bproduction validation\b|\bvalidate in production\b/.test(combinedText),
99
+ broadProductionValidation: types.has("PRODUCTION_READINESS") || /\bbroad production validation\b|\bproduction validation\b|\bvalidate in production\b/.test(combinedText),
87
100
  };
88
101
  return signals;
89
102
  }
@@ -0,0 +1,255 @@
1
+ import { taskArtifactPath } from "./task-paths.js";
2
+ import { NEXT_ACTIONS, result } from "./next-action-model.js";
3
+ import { artifactError } from "./next-action-artifacts.js";
4
+ import { listActions } from "./actions.js";
5
+ import { listApprovals } from "./approvals.js";
6
+ import { loadPolicyIdentity } from "./policy-engine.js";
7
+
8
+ export async function resolvePendingActionGuidance({ target, packageRoot, state, context, eventsRel, authorityContext, runtimeContext, helpers }) {
9
+ const {
10
+ capabilityDecisionMetadata,
11
+ actionApprovalGuidance,
12
+ actionAuthorityGuidance,
13
+ actionApprovalResolutionGuidance,
14
+ actionDeniedGuidance,
15
+ actionPolicyEpochGuidance,
16
+ authorizeActionGuidance,
17
+ evaluateProposedActionCapability,
18
+ findApplicablePendingApproval,
19
+ } = helpers;
20
+ let actionsForApproval;
21
+ try {
22
+ actionsForApproval = await listActions(target, { packageRoot, taskId: state.taskId });
23
+ } catch (error) {
24
+ if (error.code === "E_ACTION_INVALID") {
25
+ return result({
26
+ ...context,
27
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
28
+ commands: [],
29
+ reasons: [artifactError(
30
+ error.code,
31
+ `A durable action artifact is invalid and cannot be offered for authorization: ${error.message}`,
32
+ [taskArtifactPath(state.taskId, "actions"), eventsRel],
33
+ )],
34
+ requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel],
35
+ });
36
+ }
37
+ throw error;
38
+ }
39
+ const ambiguousAction = actionsForApproval.find((action) => action.state === "COMMIT_UNKNOWN");
40
+ if (ambiguousAction) {
41
+ return result({ ...context, nextAction: NEXT_ACTIONS.RECONCILE_ACTION,
42
+ commands: [`forgeloop action-reconcile --task ${state.taskId} --action ${ambiguousAction.actionId} --outcome UNKNOWN`],
43
+ reasons: [artifactError("E_ACTION_RECONCILIATION_REQUIRED",
44
+ `Action ${ambiguousAction.actionId} has an unknown external commit outcome; retry is forbidden until reconciliation.`)],
45
+ reconciliationAuthorityRequired: {
46
+ outcomesRequiringTrust: ["COMMITTED", "NOT_COMMITTED"],
47
+ reason: "Recording an UNKNOWN observation is safe from any surface; settling COMMITTED or NOT_COMMITTED requires a trusted host boundary and evidence.",
48
+ },
49
+ requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel] });
50
+ }
51
+ // A PROPOSED required action ready for authorization should use the
52
+ // canonical authorization surface; host/approval blockers remain structured.
53
+ const authorizableAction = actionsForApproval.find((action) => action.requiredForCompletion
54
+ && action.state === "PROPOSED");
55
+ if (authorizableAction) {
56
+ let policyIdentity;
57
+ try {
58
+ policyIdentity = await loadPolicyIdentity(target, packageRoot, state.taskId);
59
+ } catch (error) {
60
+ return actionPolicyEpochGuidance({
61
+ context,
62
+ state,
63
+ action: authorizableAction,
64
+ policyIdentity: {
65
+ code: error.code ?? "E_POLICY_INVALID",
66
+ message: `Capability policy epoch validation failed for required action ${authorizableAction.actionId}: ${error.message}`,
67
+ },
68
+ eventsRel,
69
+ });
70
+ }
71
+ if (policyIdentity.status !== "VALID") {
72
+ return actionPolicyEpochGuidance({
73
+ context,
74
+ state,
75
+ action: authorizableAction,
76
+ policyIdentity,
77
+ eventsRel,
78
+ });
79
+ }
80
+
81
+ let capability;
82
+ try {
83
+ capability = await evaluateProposedActionCapability({
84
+ target,
85
+ packageRoot,
86
+ action: authorizableAction,
87
+ authorityContext,
88
+ });
89
+ } catch (error) {
90
+ return result({
91
+ ...context,
92
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
93
+ commands: [],
94
+ reasons: [artifactError(
95
+ error.code ?? "E_ACTION_CAPABILITY_INVALID",
96
+ `Capability policy evaluation failed for required action ${authorizableAction.actionId}: ${error.message}`,
97
+ [taskArtifactPath(state.taskId, "actions"), eventsRel],
98
+ )],
99
+ capabilityDecision: {
100
+ capability: authorizableAction.capability,
101
+ decision: "DENY",
102
+ reasonCode: error.code ?? "E_ACTION_CAPABILITY_INVALID",
103
+ policyFingerprint: null,
104
+ },
105
+ requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel],
106
+ });
107
+ }
108
+ if (capability.decision === "REQUIRE_AUTHORITY") {
109
+ return actionAuthorityGuidance({
110
+ context,
111
+ state,
112
+ action: authorizableAction,
113
+ capability,
114
+ eventsRel,
115
+ });
116
+ }
117
+ if (capability.allowed) {
118
+ return authorizeActionGuidance({
119
+ context,
120
+ state,
121
+ action: authorizableAction,
122
+ capability,
123
+ eventsRel,
124
+ });
125
+ }
126
+ if (capability.decision === "REQUIRE_APPROVAL") {
127
+ let approvals;
128
+ try {
129
+ approvals = await listApprovals(target, { packageRoot, taskId: state.taskId });
130
+ } catch (error) {
131
+ return result({
132
+ ...context,
133
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
134
+ commands: [],
135
+ reasons: [artifactError(
136
+ error.code ?? "E_APPROVAL_INVALID",
137
+ `Approval artifact inspection failed for required action ${authorizableAction.actionId}: ${error.message}`,
138
+ [taskArtifactPath(state.taskId, "approvals"), eventsRel],
139
+ )],
140
+ capabilityDecision: capabilityDecisionMetadata(authorizableAction, capability),
141
+ requiredArtifacts: [taskArtifactPath(state.taskId, "approvals"), eventsRel],
142
+ });
143
+ }
144
+ try {
145
+ capability = await evaluateProposedActionCapability({
146
+ target,
147
+ packageRoot,
148
+ action: authorizableAction,
149
+ approvals,
150
+ authorityContext,
151
+ });
152
+ } catch (error) {
153
+ return result({
154
+ ...context,
155
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
156
+ commands: [],
157
+ reasons: [artifactError(
158
+ error.code ?? "E_ACTION_CAPABILITY_INVALID",
159
+ `Capability policy evaluation failed for required action ${authorizableAction.actionId}: ${error.message}`,
160
+ [taskArtifactPath(state.taskId, "actions"), eventsRel],
161
+ )],
162
+ capabilityDecision: {
163
+ capability: authorizableAction.capability,
164
+ decision: "DENY",
165
+ reasonCode: error.code ?? "E_ACTION_CAPABILITY_INVALID",
166
+ policyFingerprint: null,
167
+ },
168
+ requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel],
169
+ });
170
+ }
171
+ if (capability.allowed) {
172
+ return authorizeActionGuidance({
173
+ context,
174
+ state,
175
+ action: authorizableAction,
176
+ capability,
177
+ eventsRel,
178
+ });
179
+ }
180
+ if (capability.decision !== "REQUIRE_APPROVAL") {
181
+ if (capability.decision === "REQUIRE_AUTHORITY") {
182
+ return actionAuthorityGuidance({
183
+ context,
184
+ state,
185
+ action: authorizableAction,
186
+ capability,
187
+ eventsRel,
188
+ });
189
+ }
190
+ if (capability.allowed) {
191
+ return authorizeActionGuidance({
192
+ context,
193
+ state,
194
+ action: authorizableAction,
195
+ capability,
196
+ eventsRel,
197
+ });
198
+ }
199
+ return actionDeniedGuidance({
200
+ context,
201
+ state,
202
+ action: authorizableAction,
203
+ capability,
204
+ eventsRel,
205
+ });
206
+ }
207
+ const pendingApproval = await findApplicablePendingApproval({
208
+ target,
209
+ packageRoot,
210
+ taskId: state.taskId,
211
+ action: authorizableAction,
212
+ approvals,
213
+ });
214
+ if (pendingApproval) {
215
+ return actionApprovalResolutionGuidance({
216
+ context,
217
+ state,
218
+ action: authorizableAction,
219
+ approval: pendingApproval,
220
+ eventsRel,
221
+ });
222
+ }
223
+ return actionApprovalGuidance({
224
+ context,
225
+ state,
226
+ action: authorizableAction,
227
+ capability,
228
+ eventsRel,
229
+ });
230
+ }
231
+ return actionDeniedGuidance({
232
+ context,
233
+ state,
234
+ action: authorizableAction,
235
+ capability,
236
+ eventsRel,
237
+ });
238
+ }
239
+ // Committed-but-unverified required action needs canonical postcondition
240
+ // evidence before it can satisfy completion.
241
+ const committedActions = actionsForApproval;
242
+ const unverifiedRequired = committedActions.find((action) => action.requiredForCompletion
243
+ && action.state === "COMMITTED");
244
+ if (unverifiedRequired) {
245
+ return result({ ...context, nextAction: NEXT_ACTIONS.VERIFY_EXTERNAL_ACTION,
246
+ commands: [
247
+ `forgeloop run-check --task ${state.taskId} --id check-postcondition --requirement <requirement> -- <independent verification argv>`,
248
+ `forgeloop action-verify --task ${state.taskId} --action ${unverifiedRequired.actionId} --evidence <execution-ref>`,
249
+ ],
250
+ reasons: [artifactError("E_ACTION_VERIFICATION_REQUIRED",
251
+ `Committed action ${unverifiedRequired.actionId} requires independent canonical postcondition evidence; exit code 0 alone is not verification.`)],
252
+ requiredArtifacts: [taskArtifactPath(state.taskId, "actions"), eventsRel] });
253
+ }
254
+ return null;
255
+ }