@cassiomc1/forgeloop 1.5.0 → 1.6.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 (133) hide show
  1. package/CONTRACT_COVERAGE.md +1 -0
  2. package/DOCS_INDEX.md +20 -10
  3. package/EXECUTION_STATE.md +20 -0
  4. package/LOOP_ENGINEERING.md +103 -1
  5. package/LOOP_SYSTEM_DESIGN.md +32 -0
  6. package/PROTOCOL_INTEGRATION.md +90 -0
  7. package/QUALITY_SCORECARD.md +2 -0
  8. package/README.md +47 -9
  9. package/THIRD_PARTY_NOTICES.md +15 -0
  10. package/THREAT_MODEL.md +41 -1
  11. package/docs/ARTIFACT_REFERENCE.md +159 -0
  12. package/docs/CLI_REFERENCE.md +294 -3
  13. package/docs/DIAGNOSTIC_MODEL.md +181 -0
  14. package/docs/DOCUMENTATION_GUIDE.md +22 -13
  15. package/docs/EXECUTION_TRACE.md +76 -0
  16. package/docs/MCP.md +34 -1
  17. package/docs/RECIPES.md +67 -0
  18. package/docs/RELEASE_CHECKLIST_1_6_1.md +121 -0
  19. package/docs/TROUBLESHOOTING.md +133 -2
  20. package/docs/assets/diagrams/forgeloop-engineering-flow.html +13797 -0
  21. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +37 -0
  22. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +5002 -0
  23. package/docs/diagrams/README.md +55 -0
  24. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +122 -0
  25. package/docs/diagrams/manifest.json +42 -0
  26. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +20 -0
  27. package/package.json +13 -8
  28. package/schemas/action.schema.json +100 -0
  29. package/schemas/approval.schema.json +51 -0
  30. package/schemas/capability-policy.schema.json +41 -0
  31. package/schemas/diagnostic-case.schema.json +85 -0
  32. package/schemas/execution-receipt.schema.json +16 -0
  33. package/schemas/execution.schema.json +15 -0
  34. package/schemas/hypothesis-disposition.schema.json +16 -0
  35. package/schemas/intervention.schema.json +27 -0
  36. package/schemas/policy-lock.schema.json +1 -0
  37. package/schemas/policy-snapshot.schema.json +2 -0
  38. package/schemas/trajectory-evaluation.schema.json +64 -0
  39. package/schemas/trajectory-scenario.schema.json +42 -0
  40. package/src/cli.js +94 -0
  41. package/src/commands/action-authorize.js +41 -0
  42. package/src/commands/action-propose.js +10 -0
  43. package/src/commands/action-reconcile.js +10 -0
  44. package/src/commands/action-record.js +47 -0
  45. package/src/commands/action-show.js +10 -0
  46. package/src/commands/action-verify.js +10 -0
  47. package/src/commands/advance.js +7 -2
  48. package/src/commands/approval-request.js +64 -0
  49. package/src/commands/approval-resolve.js +10 -0
  50. package/src/commands/baseline.js +3 -3
  51. package/src/commands/eval.js +6 -0
  52. package/src/commands/history.js +18 -0
  53. package/src/commands/init.js +2 -2
  54. package/src/commands/inspect.js +49 -0
  55. package/src/commands/metrics.js +7 -0
  56. package/src/commands/next.js +8 -2
  57. package/src/commands/policy-discover.js +2 -2
  58. package/src/commands/record-diagnosis.js +37 -1
  59. package/src/commands/record-hypothesis-disposition.js +45 -0
  60. package/src/commands/record-intervention.js +35 -0
  61. package/src/commands/reflect.js +38 -0
  62. package/src/commands/report.js +9 -1
  63. package/src/commands/run-action.js +18 -0
  64. package/src/commands/run-check.js +1 -0
  65. package/src/commands/trace.js +34 -0
  66. package/src/commands/validate-protocol.js +21 -13
  67. package/src/core/action-authorization.js +106 -0
  68. package/src/core/action-constants.js +86 -0
  69. package/src/core/action-execution.js +106 -0
  70. package/src/core/action-ledger-projection.js +302 -0
  71. package/src/core/action-model.js +581 -0
  72. package/src/core/action-readiness.js +141 -0
  73. package/src/core/action-reconciliation-policy.js +49 -0
  74. package/src/core/action-reconciliation.js +66 -0
  75. package/src/core/action-verification.js +111 -0
  76. package/src/core/actions.js +462 -0
  77. package/src/core/approvals.js +405 -0
  78. package/src/core/artifact-registry.js +48 -0
  79. package/src/core/audit.js +25 -0
  80. package/src/core/bundles.js +15 -0
  81. package/src/core/capability-policy.js +226 -0
  82. package/src/core/cli-command-definitions.js +210 -1
  83. package/src/core/command-executors.js +171 -15
  84. package/src/core/command-runtime.js +12 -1
  85. package/src/core/completion-artifacts.js +71 -14
  86. package/src/core/completion-recovery-rebind.js +194 -0
  87. package/src/core/completion.js +70 -0
  88. package/src/core/continuity-reconciliation.js +24 -5
  89. package/src/core/diagnostic-model.js +396 -0
  90. package/src/core/diagnostic-projection.js +51 -0
  91. package/src/core/diagnostic-record.js +360 -0
  92. package/src/core/error-codes.js +363 -0
  93. package/src/core/events.js +41 -1
  94. package/src/core/execution-prerequisites.js +4 -1
  95. package/src/core/execution.js +29 -188
  96. package/src/core/failure-signature.js +70 -0
  97. package/src/core/failure-surface.js +57 -0
  98. package/src/core/history.js +110 -0
  99. package/src/core/hypothesis-projection.js +85 -0
  100. package/src/core/information-gain-projection.js +283 -0
  101. package/src/core/information-gain.js +138 -0
  102. package/src/core/inspect.js +105 -7
  103. package/src/core/integration-invocation-policy.js +55 -0
  104. package/src/core/integration-resources.js +51 -0
  105. package/src/core/next-action-model.js +35 -1
  106. package/src/core/next-action.js +459 -3
  107. package/src/core/phase.js +40 -21
  108. package/src/core/policy-engine.js +113 -6
  109. package/src/core/preflight-consistency.js +31 -5
  110. package/src/core/preflight.js +19 -2
  111. package/src/core/prepared-execution.js +256 -0
  112. package/src/core/progress.js +41 -4
  113. package/src/core/protocol-info.js +56 -0
  114. package/src/core/protocol.js +14 -0
  115. package/src/core/receipt.js +1 -0
  116. package/src/core/reconcile-closure.js +15 -12
  117. package/src/core/reflection.js +305 -0
  118. package/src/core/resumability.js +57 -3
  119. package/src/core/runtime-context.js +19 -1
  120. package/src/core/schema-validation.js +8 -0
  121. package/src/core/strategy-analysis.js +97 -0
  122. package/src/core/task-paths.js +28 -0
  123. package/src/core/task-snapshot.js +53 -0
  124. package/src/core/templates.js +8 -0
  125. package/src/core/trace.js +548 -0
  126. package/src/core/trajectory-evaluation.js +71 -0
  127. package/src/core/trajectory-metrics.js +80 -0
  128. package/src/core/transaction.js +8 -0
  129. package/src/core/verification-execution.js +257 -0
  130. package/src/core/work-state.js +10 -5
  131. package/src/integration.js +8 -0
  132. package/docs/assets/forgeloop-flow.svg +0 -1
  133. package/docs/forgeloop-flow.mmd +0 -51
@@ -0,0 +1,71 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { canonicalFingerprint, writeJsonArtifact } from "./artifacts.js";
5
+ import { buildTaskTrace } from "./trace.js";
6
+ import { buildTrajectoryMetrics } from "./trajectory-metrics.js";
7
+ import { assertSchema, readSchema } from "./schema-validation.js";
8
+ import { taskEvaluationPath } from "./task-paths.js";
9
+ import { appendProtocolEvent } from "./events.js";
10
+ import { assertSafePath, ensureWithin } from "./filesystem.js";
11
+ import { withTaskTransaction } from "./transaction.js";
12
+
13
+ function evaluationError(code, message) { const error = new Error(message); error.code = code; return error; }
14
+
15
+ export async function evaluateTrajectory({ target, packageRoot, taskId, scenarioPath, evaluationId = `eval-${randomUUID()}` }) {
16
+ if (typeof scenarioPath !== "string" || !scenarioPath || path.isAbsolute(scenarioPath) || scenarioPath.includes("..")) {
17
+ throw evaluationError("E_TRAJECTORY_SCENARIO_INVALID", "scenario path must be a relative project-local file");
18
+ }
19
+ let scenario;
20
+ try {
21
+ await assertSafePath(target, scenarioPath);
22
+ scenario = JSON.parse(await readFile(ensureWithin(target, scenarioPath), "utf8"));
23
+ } catch (error) {
24
+ throw evaluationError("E_TRAJECTORY_SCENARIO_INVALID", `unable to read scenario safely: ${error.message}`);
25
+ }
26
+ assertSchema(scenario, await readSchema("trajectory-scenario", packageRoot), "trajectory scenario");
27
+ if (!/^eval-[A-Za-z0-9_-]+$/.test(evaluationId)) throw evaluationError("E_TRAJECTORY_SCENARIO_INVALID", "evaluationId is invalid");
28
+ const trace = await buildTaskTrace({ target, packageRoot, taskId });
29
+ const metrics = await buildTrajectoryMetrics({ target, packageRoot, taskId });
30
+ const milestoneSet = new Set(trace.events.map((event) => event.type));
31
+ const missingMilestones = (scenario.requiredMilestones ?? []).filter((milestone) => !milestoneSet.has(milestone));
32
+ const completionEvent = trace.events.find((event) => event.type === "COMPLETION_VALIDATED");
33
+ const verificationEvent = trace.events.find((event) => event.type === "VERIFICATION_STARTED" || event.type === "VERIFICATION_RECORDED");
34
+ const completionBeforeVerification = Boolean(completionEvent && (!verificationEvent || completionEvent.sequence < verificationEvent.sequence));
35
+ // Canonical truth: ambiguous actions or unresolved trusted-readiness items
36
+ // block safety. A raw VERIFIED label alone never resolves a required action.
37
+ const unresolvedRequiredAction = (trace.actions?.ambiguous ?? 0) > 0
38
+ || ((metrics.actions.unresolvedRequired ?? null) !== null
39
+ ? metrics.actions.unresolvedRequired > 0
40
+ : (trace.actions?.required ?? 0) > (trace.actions?.verified ?? 0));
41
+ const limit = (name, actual, max) => ({ actual, ...(max === undefined ? {} : { max, pass: actual <= max }) });
42
+ const limits = {
43
+ verificationCycles: limit("verificationCycles", metrics.trajectory.verificationCycles, scenario.limits?.maxVerificationCycles),
44
+ nonInformativeInterventions: limit("nonInformativeInterventions", metrics.trajectory.interventions.nonInformative, scenario.limits?.maxNonInformativeInterventions),
45
+ ambiguousActions: limit("ambiguousActions", metrics.actions.ambiguous, scenario.limits?.maxAmbiguousActions),
46
+ };
47
+ const safetyValid = !(scenario.forbidden?.completionBeforeVerification && completionBeforeVerification)
48
+ && !(scenario.forbidden?.unresolvedRequiredAction && unresolvedRequiredAction);
49
+ const completionValid = metrics.completion.validated && missingMilestones.length === 0;
50
+ const limitsValid = Object.values(limits).every((item) => item.pass !== false);
51
+ // Comparable steps have exactly one owner: buildTrajectoryMetrics.
52
+ const efficiency = Number.isInteger(scenario.reference?.comparableSteps) && scenario.reference.comparableSteps > 0
53
+ ? { referenceComparableSteps: scenario.reference.comparableSteps, actualComparableSteps: metrics.comparableSteps,
54
+ ratio: scenario.reference.comparableSteps / Math.max(1, metrics.comparableSteps) }
55
+ : null;
56
+ const base = {
57
+ schemaVersion: 1, evaluationId, scenarioId: scenario.scenarioId,
58
+ scenarioFingerprint: canonicalFingerprint(scenario), taskId,
59
+ result: completionValid && safetyValid && limitsValid ? "PASS" : "FAIL",
60
+ completionValid, safetyValid, missingMilestones, limits, efficiency,
61
+ computedAt: new Date().toISOString(), source: "PROJECT_LOCAL_REFERENCE",
62
+ };
63
+ const evaluation = { ...base, evaluationFingerprint: canonicalFingerprint(base) };
64
+ assertSchema(evaluation, await readSchema("trajectory-evaluation", packageRoot), "trajectory evaluation");
65
+ await withTaskTransaction({ target, taskId, operation: "trajectory-evaluated" }, async () => {
66
+ await writeJsonArtifact(target, taskEvaluationPath(taskId, evaluationId), evaluation, "trajectory-evaluation", packageRoot);
67
+ await appendProtocolEvent(target, { taskId, event: "TRAJECTORY_EVALUATED", fingerprint: evaluation.evaluationFingerprint,
68
+ details: { evaluationId, scenarioId: scenario.scenarioId, evaluationFingerprint: evaluation.evaluationFingerprint } }, packageRoot, { taskId });
69
+ });
70
+ return evaluation;
71
+ }
@@ -0,0 +1,80 @@
1
+ import { buildTaskTrace } from "./trace.js";
2
+ import { buildTaskReflection } from "./reflection.js";
3
+
4
+ export const COMPARABLE_WORK_EVENTS = new Set([
5
+ "EXECUTION_STARTED", "VERIFICATION_RECORDED", "DIAGNOSTIC_CASE_RECORDED",
6
+ "INTERVENTION_RECORDED", "ACTION_STARTED", "ACTION_RECONCILED", "REVIEW_STARTED",
7
+ ]);
8
+
9
+ export async function buildTrajectoryMetrics({ target, packageRoot, taskId }) {
10
+ const trace = await buildTaskTrace({ target, packageRoot, taskId });
11
+ const reflection = await buildTaskReflection({ target, packageRoot, taskId });
12
+ const events = trace.events;
13
+ const verificationCycles = new Set(events.filter((event) => event.type === "VERIFICATION_STARTED")
14
+ .map((event) => event.data?.verificationCycle).filter(Number.isInteger));
15
+ const diagnosticCycles = new Set(trace.diagnostics.cases.map((item) => item.verificationCycle));
16
+ const firstEventAt = events.find((event) => event.timestampQuality === "authoritative")?.timestamp ?? null;
17
+ const lastEventAt = [...events].reverse().find((event) => event.timestampQuality === "authoritative")?.timestamp ?? null;
18
+ const firstMs = firstEventAt ? Date.parse(firstEventAt) : NaN;
19
+ const lastMs = lastEventAt ? Date.parse(lastEventAt) : NaN;
20
+ const interventions = reflection.interventions ?? { count: 0, informative: 0, nonInformative: 0 };
21
+ return {
22
+ schemaVersion: 1,
23
+ taskId,
24
+ completion: {
25
+ validated: Boolean(trace.completion.validatedAt),
26
+ phase: trace.task.phase,
27
+ },
28
+ trajectory: {
29
+ events: events.length,
30
+ verificationCycles: verificationCycles.size,
31
+ diagnosticCycles: diagnosticCycles.size,
32
+ strategyChanges: Math.max(0, (reflection.strategies?.length ?? 0) - 1),
33
+ oscillationDetected: Boolean(reflection.oscillation?.detected),
34
+ noEffectiveInformationGainCycles: reflection.informationGain?.cyclesWithoutEffectiveGain?.length ?? 0,
35
+ interventions: {
36
+ total: interventions.count ?? 0,
37
+ informative: interventions.informative ?? 0,
38
+ nonInformative: interventions.nonInformative ?? 0,
39
+ },
40
+ },
41
+ actions: {
42
+ total: trace.actions?.total ?? 0,
43
+ verified: trace.actions?.verified ?? 0,
44
+ // Canonical trust counts come from the action-readiness projection;
45
+ // raw VERIFIED labels are observability only.
46
+ trustedSatisfied: await (async () => {
47
+ try {
48
+ const { evaluateRequiredActionReadiness } = await import("./action-readiness.js");
49
+ const readiness = await evaluateRequiredActionReadiness({ target, packageRoot, taskId });
50
+ return readiness.satisfied;
51
+ } catch {
52
+ return null;
53
+ }
54
+ })(),
55
+ unresolvedRequired: await (async () => {
56
+ try {
57
+ const { evaluateRequiredActionReadiness } = await import("./action-readiness.js");
58
+ const readiness = await evaluateRequiredActionReadiness({ target, packageRoot, taskId });
59
+ return readiness.unresolved;
60
+ } catch {
61
+ return null;
62
+ }
63
+ })(),
64
+ failed: trace.actions?.failed ?? 0,
65
+ ambiguous: trace.actions?.ambiguous ?? 0,
66
+ reconciliations: trace.actions?.reconciliationCount ?? 0,
67
+ },
68
+ executions: {
69
+ observedCommands: trace.executions.length,
70
+ failedCommands: trace.executions.filter((execution) => execution.status === "failed").length,
71
+ },
72
+ timing: {
73
+ firstEventAt,
74
+ lastEventAt,
75
+ wallClockMs: Number.isFinite(firstMs) && Number.isFinite(lastMs) ? Math.max(0, lastMs - firstMs) : null,
76
+ },
77
+ usage: { tokens: null, costUsd: null, source: "UNKNOWN" },
78
+ comparableSteps: events.filter((event) => COMPARABLE_WORK_EVENTS.has(event.type)).length,
79
+ };
80
+ }
@@ -103,6 +103,13 @@ export async function withTaskTransaction({
103
103
  recordCommitEvent = false,
104
104
  } = {}, callback) {
105
105
  if (!target || !taskId) throw new Error("target and taskId are required for a task transaction");
106
+ const activeTransaction = getActiveTaskTransaction();
107
+ if (activeTransaction) {
108
+ if (activeTransaction.taskId !== taskId) {
109
+ throw new Error(`cannot nest task transaction for ${taskId} inside ${activeTransaction.taskId}`);
110
+ }
111
+ return callback(activeTransaction);
112
+ }
106
113
  const started = Date.now();
107
114
  const runWithLock = async () => withTaskLock(target, lockTaskId, operation, async (lock) => {
108
115
  const transactionId = `txn-${randomUUID()}`;
@@ -115,6 +122,7 @@ export async function withTaskTransaction({
115
122
  await writeManifest(target, manifestPath, manifest);
116
123
  const tx = {
117
124
  transactionId,
125
+ taskId,
118
126
  lock,
119
127
  async readText(relativePath) {
120
128
  await assertSafePath(target, relativePath);
@@ -0,0 +1,257 @@
1
+ export const VERIFICATION_ISOLATION_MODES = Object.freeze([
2
+ "NATIVE_PROJECT",
3
+ "PROJECT_ISOLATED",
4
+ "SYSTEM_ISOLATED",
5
+ ]);
6
+
7
+ export const VERIFICATION_EXECUTION_POLICY_MODES = Object.freeze([
8
+ "NONE",
9
+ ...VERIFICATION_ISOLATION_MODES,
10
+ ]);
11
+
12
+ export const E_VERIFICATION_ISOLATION_UNAVAILABLE = "E_VERIFICATION_ISOLATION_UNAVAILABLE";
13
+ export const E_VERIFICATION_EXECUTION_INVALID = "E_VERIFICATION_EXECUTION_INVALID";
14
+
15
+ const ISOLATION_RANK = Object.freeze({
16
+ NATIVE_PROJECT: 0,
17
+ PROJECT_ISOLATED: 1,
18
+ SYSTEM_ISOLATED: 2,
19
+ });
20
+
21
+ function isRecord(value) {
22
+ return value !== null && typeof value === "object" && !Array.isArray(value);
23
+ }
24
+
25
+ function executionError(code, message) {
26
+ const error = new Error(message);
27
+ error.code = code;
28
+ return error;
29
+ }
30
+
31
+ function requiredString(value, field) {
32
+ if (typeof value !== "string" || value.trim() === "") {
33
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, `Verification execution ${field} must be a non-empty string`);
34
+ }
35
+ return value;
36
+ }
37
+
38
+ function nullableString(value, field) {
39
+ if (value !== null && typeof value !== "string") {
40
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, `Verification execution ${field} must be a string or null`);
41
+ }
42
+ return value;
43
+ }
44
+
45
+ function outputBytes(value, field) {
46
+ if (value === undefined || value === null) return Buffer.alloc(0);
47
+ if (typeof value === "string") return Buffer.from(value, "utf8");
48
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) return Buffer.from(value);
49
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, `Verification execution ${field} must be text or bytes`);
50
+ }
51
+
52
+ function nonNegativeInteger(value, field, fallback) {
53
+ const resolved = value === undefined ? fallback : value;
54
+ if (!Number.isInteger(resolved) || resolved < 0) {
55
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, `Verification execution ${field} must be a non-negative integer`);
56
+ }
57
+ return resolved;
58
+ }
59
+
60
+ export function normalizeVerificationExecutionPolicy(policy = {}) {
61
+ if (policy === undefined || policy === null) return Object.freeze({ requiredIsolation: "NONE" });
62
+ if (!isRecord(policy)) {
63
+ throw executionError(E_VERIFICATION_ISOLATION_UNAVAILABLE, "Verification execution isolation policy is invalid");
64
+ }
65
+ const requiredIsolation = policy.requiredIsolation ?? "NONE";
66
+ if (!VERIFICATION_EXECUTION_POLICY_MODES.includes(requiredIsolation)) {
67
+ throw executionError(E_VERIFICATION_ISOLATION_UNAVAILABLE, "Verification execution isolation policy is unsupported");
68
+ }
69
+ return Object.freeze({ requiredIsolation });
70
+ }
71
+
72
+ export function isVerificationExecutionAdapter(value) {
73
+ return isRecord(value) && typeof value.execute === "function";
74
+ }
75
+
76
+ function readAdapter(runtimeContext) {
77
+ const value = isRecord(runtimeContext) ? runtimeContext.verificationExecutionAdapter : undefined;
78
+ if (value === undefined || value === null) return null;
79
+ if (!isVerificationExecutionAdapter(value)) {
80
+ throw executionError(E_VERIFICATION_ISOLATION_UNAVAILABLE, "Verification execution adapter is unavailable or invalid");
81
+ }
82
+ return value;
83
+ }
84
+
85
+ function assertIsolationMetadataConsistency(isolation) {
86
+ if (isolation.mode === "NATIVE_PROJECT") {
87
+ if (
88
+ isolation.isolated !== false
89
+ || isolation.liveProjectWritable !== true
90
+ ) {
91
+ throw executionError(
92
+ E_VERIFICATION_EXECUTION_INVALID,
93
+ "NATIVE_PROJECT must report isolated=false and liveProjectWritable=true",
94
+ );
95
+ }
96
+ return;
97
+ }
98
+
99
+ if (
100
+ isolation.isolated !== true
101
+ || isolation.liveProjectWritable !== false
102
+ ) {
103
+ throw executionError(
104
+ E_VERIFICATION_EXECUTION_INVALID,
105
+ `${isolation.mode} must report isolated=true and liveProjectWritable=false`,
106
+ );
107
+ }
108
+
109
+ if (
110
+ isolation.mode === "SYSTEM_ISOLATED"
111
+ && isolation.networkPolicy !== "DENIED"
112
+ ) {
113
+ throw executionError(
114
+ E_VERIFICATION_EXECUTION_INVALID,
115
+ "SYSTEM_ISOLATED must report networkPolicy=DENIED",
116
+ );
117
+ }
118
+ }
119
+
120
+ function normalizeIsolation(value) {
121
+ if (!isRecord(value)) {
122
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution isolation metadata is missing");
123
+ }
124
+ const mode = value.mode;
125
+ if (!VERIFICATION_ISOLATION_MODES.includes(mode)) {
126
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution isolation mode is unsupported");
127
+ }
128
+ if (typeof value.isolated !== "boolean" || typeof value.liveProjectWritable !== "boolean") {
129
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution isolation booleans are invalid");
130
+ }
131
+ const isolation = {
132
+ mode,
133
+ isolated: value.isolated,
134
+ liveProjectWritable: value.liveProjectWritable,
135
+ networkPolicy: requiredString(value.networkPolicy, "networkPolicy"),
136
+ environmentPolicy: requiredString(value.environmentPolicy, "environmentPolicy"),
137
+ };
138
+ assertIsolationMetadataConsistency(isolation);
139
+ return isolation;
140
+ }
141
+
142
+ export function normalizeVerificationExecutionResult(value, { defaultCwd = null } = {}) {
143
+ if (!isRecord(value)) {
144
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution adapter returned an invalid result");
145
+ }
146
+ const cwd = value.cwd ?? defaultCwd;
147
+ requiredString(cwd, "cwd");
148
+ if (value.exitCode !== null && !Number.isInteger(value.exitCode)) {
149
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution exitCode must be an integer or null");
150
+ }
151
+ const signal = nullableString(value.signal ?? null, "signal");
152
+ if (typeof value.timedOut !== "boolean") {
153
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution timedOut must be boolean");
154
+ }
155
+ if (typeof value.outputTruncated !== "boolean") {
156
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution outputTruncated must be boolean");
157
+ }
158
+ const stdout = outputBytes(value.stdout, "stdout");
159
+ const stderr = outputBytes(value.stderr, "stderr");
160
+ return {
161
+ exitCode: value.exitCode,
162
+ signal,
163
+ timedOut: value.timedOut,
164
+ stdout,
165
+ stderr,
166
+ stdoutBytes: nonNegativeInteger(value.stdoutBytes, "stdoutBytes", stdout.length),
167
+ stderrBytes: nonNegativeInteger(value.stderrBytes, "stderrBytes", stderr.length),
168
+ outputTruncated: value.outputTruncated,
169
+ spawnError: Boolean(value.spawnError),
170
+ cwd,
171
+ isolation: normalizeIsolation(value.isolation),
172
+ };
173
+ }
174
+
175
+ function satisfiesIsolationPolicy(isolation, requiredIsolation) {
176
+ if (requiredIsolation === "NONE") return true;
177
+ if (ISOLATION_RANK[isolation.mode] < ISOLATION_RANK[requiredIsolation]) return false;
178
+ if (requiredIsolation === "NATIVE_PROJECT") return true;
179
+ return isolation.isolated === true
180
+ && isolation.liveProjectWritable === false
181
+ && (requiredIsolation !== "SYSTEM_ISOLATED" || isolation.networkPolicy === "DENIED");
182
+ }
183
+
184
+ export function assertVerificationIsolation(result, policy) {
185
+ const requiredIsolation = normalizeVerificationExecutionPolicy(policy).requiredIsolation;
186
+ if (!satisfiesIsolationPolicy(result.isolation, requiredIsolation)) {
187
+ throw executionError(
188
+ E_VERIFICATION_ISOLATION_UNAVAILABLE,
189
+ `Verification execution did not satisfy the required ${requiredIsolation} isolation boundary`,
190
+ );
191
+ }
192
+ return result;
193
+ }
194
+
195
+ export async function executeVerificationProcess({
196
+ argv,
197
+ protocolProjectRoot,
198
+ taskId,
199
+ checkId,
200
+ requirement,
201
+ resolution,
202
+ timeoutMs,
203
+ runtimeContext,
204
+ nativeExecute,
205
+ }) {
206
+ const policy = normalizeVerificationExecutionPolicy(runtimeContext?.verificationExecutionPolicy);
207
+ const adapter = readAdapter(runtimeContext);
208
+ let rawResult;
209
+ if (adapter) {
210
+ try {
211
+ rawResult = await adapter.execute(Object.freeze({
212
+ argv: [...argv],
213
+ protocolProjectRoot,
214
+ taskId,
215
+ checkId,
216
+ requirement,
217
+ timeoutMs,
218
+ resolution,
219
+ }));
220
+ } catch (error) {
221
+ if (error?.code === E_VERIFICATION_ISOLATION_UNAVAILABLE) throw error;
222
+ throw executionError(E_VERIFICATION_EXECUTION_INVALID, "Verification execution adapter failed");
223
+ }
224
+ } else {
225
+ if (policy.requiredIsolation !== "NONE") {
226
+ throw executionError(
227
+ E_VERIFICATION_ISOLATION_UNAVAILABLE,
228
+ `Verification execution requires ${policy.requiredIsolation}, but no trusted adapter was supplied`,
229
+ );
230
+ }
231
+ const nativeResult = await nativeExecute(argv, protocolProjectRoot, { timeoutMs });
232
+ rawResult = {
233
+ ...nativeResult,
234
+ cwd: protocolProjectRoot,
235
+ isolation: {
236
+ mode: "NATIVE_PROJECT",
237
+ isolated: false,
238
+ liveProjectWritable: true,
239
+ networkPolicy: "INHERITED",
240
+ environmentPolicy: "INHERITED",
241
+ },
242
+ };
243
+ }
244
+
245
+ const normalized = normalizeVerificationExecutionResult(rawResult, { defaultCwd: protocolProjectRoot });
246
+ if (
247
+ policy.requiredIsolation !== "NONE"
248
+ && normalized.isolation.mode !== "NATIVE_PROJECT"
249
+ && normalized.cwd === protocolProjectRoot
250
+ ) {
251
+ throw executionError(
252
+ E_VERIFICATION_EXECUTION_INVALID,
253
+ "Isolated verification execution must use a cwd separate from the protocol project root",
254
+ );
255
+ }
256
+ return assertVerificationIsolation(normalized, policy);
257
+ }
@@ -538,13 +538,18 @@ export async function classifyLoadedWorkState({ target, state, contractFile = nu
538
538
  };
539
539
  }
540
540
 
541
- export async function readAndClassifyWorkState({ target, packageRoot = getPackageRoot(), contractFile = null, maxAgeMs } = {}) {
541
+ export async function readAndClassifyWorkState({ target, packageRoot = getPackageRoot(), contractFile = null, maxAgeMs, taskId = null, stateFile = null, statePath = null } = {}) {
542
+ const effectiveStateRel = stateFile ?? statePath ?? (taskId ? taskArtifactPath(taskId, "state") : WORK_STATE_PATH);
542
543
  let state = null;
543
544
  try {
544
- state = await readWorkState(target, packageRoot);
545
+ state = await readWorkState(target, {
546
+ packageRoot,
547
+ ...(taskId ? { taskId } : {}),
548
+ ...(stateFile || statePath ? { statePath: effectiveStateRel } : {}),
549
+ });
545
550
  } catch (error) {
546
551
  return {
547
- path: WORK_STATE_PATH,
552
+ path: effectiveStateRel,
548
553
  present: true,
549
554
  status: "INVALID",
550
555
  reasons: ["STATE_INVALID"],
@@ -565,7 +570,7 @@ export async function readAndClassifyWorkState({ target, packageRoot = getPackag
565
570
  if (!state) {
566
571
  const repository = await currentRepositoryFingerprint(target);
567
572
  return {
568
- path: WORK_STATE_PATH,
573
+ path: effectiveStateRel,
569
574
  present: false,
570
575
  status: "ABSENT",
571
576
  reasons: ["NO_CHECKPOINT"],
@@ -589,7 +594,7 @@ export async function readAndClassifyWorkState({ target, packageRoot = getPackag
589
594
 
590
595
  const classification = await classifyLoadedWorkState({ target, state, contractFile, maxAgeMs });
591
596
  return {
592
- path: WORK_STATE_PATH,
597
+ path: effectiveStateRel,
593
598
  present: true,
594
599
  ...classification,
595
600
  phase: state.phase,
@@ -44,4 +44,12 @@ export {
44
44
  CLI_COMMAND_DEFINITIONS,
45
45
  };
46
46
 
47
+ export { createForgeLoopContext } from "./core/runtime-context.js";
48
+ export {
49
+ E_VERIFICATION_EXECUTION_INVALID,
50
+ E_VERIFICATION_ISOLATION_UNAVAILABLE,
51
+ VERIFICATION_EXECUTION_POLICY_MODES,
52
+ VERIFICATION_ISOLATION_MODES,
53
+ } from "./core/verification-execution.js";
54
+
47
55
  export const FORGELOOP_INTEGRATION_API_VERSION = FORGELOOP_INTEGRATION_RUNTIME_VERSION;