@cassiomc1/forgeloop 1.13.0 → 1.14.0

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 (211) hide show
  1. package/AGENT_COMPATIBILITY.md +8 -0
  2. package/DOCS_INDEX.md +35 -3
  3. package/ENG/nodejs-backend-development-eng.md +2 -2
  4. package/ENG/sec-code-eng.md +7 -7
  5. package/EXECUTION_STATE.md +12 -0
  6. package/LOOP_ENGINEERING.md +28 -2
  7. package/ORCHESTRATOR_INTEGRATION.md +9 -5
  8. package/PROTOCOL_INTEGRATION.md +55 -2
  9. package/README.md +40 -25
  10. package/TERMINOLOGY.md +2 -0
  11. package/THREAT_MODEL.md +140 -1
  12. package/completions/_forgeloop +19 -1
  13. package/completions/forgeloop.bash +37 -1
  14. package/completions/forgeloop.fish +123 -1
  15. package/docs/ADVISORY_CONTEXT.md +25 -0
  16. package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
  17. package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
  18. package/docs/AGENT_PROTOCOL_SUMMARY.md +27 -2
  19. package/docs/AGENT_SKILL.md +66 -0
  20. package/docs/ARTIFACT_REFERENCE.md +123 -0
  21. package/docs/AUDIT_UX.md +46 -0
  22. package/docs/BROWSER_VERIFICATION.md +136 -0
  23. package/docs/CLI_REFERENCE.md +366 -6
  24. package/docs/CODE_ATTESTATION.md +2 -2
  25. package/docs/DOCUMENTATION_GUIDE.md +32 -11
  26. package/docs/JEV_BENCHMARKS.md +31 -0
  27. package/docs/MODEL_ROUTING.md +37 -0
  28. package/docs/OPENSRC_ADAPTER.md +241 -0
  29. package/docs/PACKAGE_CONTENTS.md +35 -8
  30. package/docs/PROVIDERS.md +126 -0
  31. package/docs/PROVIDER_ARCHITECTURE.md +199 -0
  32. package/docs/RECIPES.md +9 -0
  33. package/docs/RELEASE_CHECKLIST.md +38 -5
  34. package/docs/SECURITY_REVIEW.md +71 -0
  35. package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
  36. package/docs/TEST_INTELLIGENCE.md +29 -0
  37. package/docs/TEST_PRUNING.md +14 -0
  38. package/docs/TROUBLESHOOTING.md +198 -1
  39. package/docs/UNIVERSAL_INTEGRATION.md +31 -0
  40. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
  41. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
  42. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
  43. package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
  44. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  45. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
  46. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
  47. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
  48. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
  49. package/docs/diagrams/README.md +13 -9
  50. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
  51. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
  52. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
  53. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  54. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  55. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  56. package/docs/documentation-manifest.json +750 -5
  57. package/docs/protocol-requirements.json +24 -0
  58. package/package.json +30 -3
  59. package/schemas/config.schema.json +14 -0
  60. package/schemas/context-plan.schema.json +18 -0
  61. package/schemas/semantic-decision.schema.json +46 -0
  62. package/schemas/test-utility.schema.json +44 -0
  63. package/scripts/CI_VALIDATORS.md +6 -6
  64. package/scripts/benchmark-jev.mjs +5 -0
  65. package/scripts/benchmark-test-intelligence.mjs +4 -0
  66. package/scripts/generate-agent-protocol-summary.mjs +4 -1
  67. package/scripts/generate-forgeloop-skill.mjs +133 -0
  68. package/scripts/jev-smoke.mjs +19 -0
  69. package/skills/forgeloop/README.md +9 -0
  70. package/skills/forgeloop/SKILL.md +77 -0
  71. package/skills/forgeloop/references/lifecycle.md +9 -0
  72. package/skills/forgeloop/references/recovery.md +7 -0
  73. package/skills/forgeloop/references/verification.md +7 -0
  74. package/src/adapters/agent-browser/assertions.js +47 -0
  75. package/src/adapters/agent-browser/commands.js +54 -0
  76. package/src/adapters/agent-browser/index.js +3 -0
  77. package/src/adapters/agent-browser/locator.js +40 -0
  78. package/src/adapters/agent-browser/process.js +215 -0
  79. package/src/adapters/agent-browser/provider.js +313 -0
  80. package/src/adapters/emulated-services/constants.js +24 -0
  81. package/src/adapters/emulated-services/index.js +7 -0
  82. package/src/adapters/emulated-services/process.js +162 -0
  83. package/src/adapters/emulated-services/provider.js +282 -0
  84. package/src/adapters/opensrc/normalize.js +90 -0
  85. package/src/adapters/opensrc/process.js +248 -0
  86. package/src/adapters/opensrc/provider.js +338 -0
  87. package/src/adapters/opensrc/search.js +264 -0
  88. package/src/adapters/typesafe/client.js +28 -0
  89. package/src/adapters/typesafe/engine.js +63 -0
  90. package/src/adapters/typesafe/normalize.js +41 -0
  91. package/src/cli.js +108 -0
  92. package/src/commands/checkpoint-revalidate.js +176 -0
  93. package/src/commands/context-plan.js +38 -0
  94. package/src/commands/contract-create.js +264 -0
  95. package/src/commands/contract-revise.js +236 -0
  96. package/src/commands/decision-show.js +14 -0
  97. package/src/commands/decision-status.js +22 -0
  98. package/src/commands/discover.js +41 -0
  99. package/src/commands/doctor.js +15 -0
  100. package/src/commands/gate-record.js +205 -0
  101. package/src/commands/gate-revalidate.js +137 -0
  102. package/src/commands/model-route.js +32 -0
  103. package/src/commands/route.js +146 -18
  104. package/src/commands/semantic-plan.js +17 -0
  105. package/src/commands/task-abandon.js +224 -0
  106. package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
  107. package/src/commands/task-repair-contract-bootstrap.js +263 -0
  108. package/src/commands/test-inventory.js +5 -0
  109. package/src/commands/test-prune-plan.js +5 -0
  110. package/src/commands/test-prune-probe.js +5 -0
  111. package/src/commands/test-utility.js +5 -0
  112. package/src/commands/validate-protocol.js +10 -1
  113. package/src/core/artifact-registry.js +24 -0
  114. package/src/core/audit-ux.js +514 -0
  115. package/src/core/browser-verification/constants.js +149 -0
  116. package/src/core/browser-verification/normalize.js +254 -0
  117. package/src/core/browser-verification/provider.js +519 -0
  118. package/src/core/browser-verification/service.js +115 -0
  119. package/src/core/checkpoint-revalidation.js +319 -0
  120. package/src/core/cli-command-definitions.js +241 -0
  121. package/src/core/command-executors.js +110 -0
  122. package/src/core/command-input.js +115 -43
  123. package/src/core/completion-artifacts.js +14 -5
  124. package/src/core/completion.js +4 -6
  125. package/src/core/config.js +3 -0
  126. package/src/core/context-compiler/budget.js +9 -0
  127. package/src/core/context-compiler/candidates.js +39 -0
  128. package/src/core/context-compiler/compiler.js +63 -0
  129. package/src/core/context-compiler/fingerprint.js +11 -0
  130. package/src/core/context-compiler/policy.js +13 -0
  131. package/src/core/context-compiler/result.js +23 -0
  132. package/src/core/contract-bootstrap-recovery.js +655 -0
  133. package/src/core/contract-revision.js +210 -0
  134. package/src/core/decision/artifact.js +69 -0
  135. package/src/core/decision/benchmarks.js +103 -0
  136. package/src/core/decision/cache.js +27 -0
  137. package/src/core/decision/constants.js +58 -0
  138. package/src/core/decision/cutover.js +34 -0
  139. package/src/core/decision/engine.js +22 -0
  140. package/src/core/decision/errors.js +68 -0
  141. package/src/core/decision/events.js +101 -0
  142. package/src/core/decision/freshness.js +19 -0
  143. package/src/core/decision/normalizers/index.js +115 -0
  144. package/src/core/decision/policy.js +18 -0
  145. package/src/core/decision/projection.js +16 -0
  146. package/src/core/decision/question-registry.js +201 -0
  147. package/src/core/decision/request.js +26 -0
  148. package/src/core/decision/resolver.js +130 -0
  149. package/src/core/decision/result.js +58 -0
  150. package/src/core/decision/service.js +156 -0
  151. package/src/core/decision/state-builder.js +65 -0
  152. package/src/core/decision/task-bindings.js +30 -0
  153. package/src/core/decision/test-provider.js +32 -0
  154. package/src/core/decision/thresholds.js +15 -0
  155. package/src/core/error-codes.js +278 -0
  156. package/src/core/events.js +226 -57
  157. package/src/core/evidence-readiness.js +9 -0
  158. package/src/core/execution-prerequisites.js +14 -0
  159. package/src/core/execution-profile.js +63 -38
  160. package/src/core/gate-provenance.js +124 -0
  161. package/src/core/integration-invocation-policy.js +27 -4
  162. package/src/core/integration-resources.js +86 -61
  163. package/src/core/model-router/constants.js +10 -0
  164. package/src/core/model-router/policy.js +103 -0
  165. package/src/core/model-router/router.js +37 -0
  166. package/src/core/next-action-model.js +58 -0
  167. package/src/core/next-action-phases.js +130 -42
  168. package/src/core/next-action-refresh.js +43 -9
  169. package/src/core/next-action-review-phase.js +7 -2
  170. package/src/core/next-action.js +35 -7
  171. package/src/core/phase.js +128 -10
  172. package/src/core/preflight-consistency.js +23 -9
  173. package/src/core/preflight-loaders.js +37 -5
  174. package/src/core/protocol-info.js +65 -0
  175. package/src/core/protocol.js +20 -0
  176. package/src/core/reconcile-closure.js +128 -52
  177. package/src/core/recovery-history.js +1 -0
  178. package/src/core/resumability.js +154 -44
  179. package/src/core/route-artifact.js +15 -1
  180. package/src/core/router.js +67 -1
  181. package/src/core/runtime-context.js +118 -61
  182. package/src/core/schema-validation.js +3 -0
  183. package/src/core/security-review/constants.js +64 -0
  184. package/src/core/security-review/normalize.js +245 -0
  185. package/src/core/security-review/provider.js +204 -0
  186. package/src/core/security-review/service.js +134 -0
  187. package/src/core/semantic-planning/constants.js +19 -0
  188. package/src/core/semantic-planning/projection.js +94 -0
  189. package/src/core/semantic-planning/service.js +15 -0
  190. package/src/core/sources.js +37 -0
  191. package/src/core/task-claim-state.js +201 -1
  192. package/src/core/task-conflict-inspection.js +31 -5
  193. package/src/core/task-paths.js +13 -0
  194. package/src/core/task-recovery.js +1 -0
  195. package/src/core/templates.js +3 -0
  196. package/src/core/test-intelligence/benchmarks.js +68 -0
  197. package/src/core/test-intelligence/inventory.js +73 -0
  198. package/src/core/test-intelligence/prune.js +90 -0
  199. package/src/core/test-intelligence/semantic-state.js +15 -0
  200. package/src/core/test-intelligence/service.js +40 -0
  201. package/src/core/test-intelligence/utility.js +50 -0
  202. package/src/core/trace.js +11 -7
  203. package/src/core/transaction.js +1 -0
  204. package/src/integration.d.ts +492 -0
  205. package/src/integration.js +54 -0
  206. package/src/providers/README.md +47 -0
  207. package/src/providers/capabilities.js +46 -0
  208. package/src/providers/errors.js +15 -0
  209. package/src/providers/index.js +29 -0
  210. package/src/providers/json-snapshot.js +105 -0
  211. package/src/providers/registry.js +152 -0
@@ -80,6 +80,18 @@ function sameReadyPreflightEvent(event, result) {
80
80
  && sameStringSet(event.details?.satisfiedGates, result.satisfiedGates);
81
81
  }
82
82
 
83
+ function latestContractRevisionSeq(events) {
84
+ return events.findLast((event) => event.event === "CONTRACT_REVISED")?.seq ?? 0;
85
+ }
86
+
87
+ function hasCurrentRevisionGate(events, taskId, gate) {
88
+ const revisionSeq = latestContractRevisionSeq(events);
89
+ return events.some((event) => event.event === "GATE_SATISFIED"
90
+ && event.taskId === taskId
91
+ && event.details?.gate === gate
92
+ && event.seq > revisionSeq);
93
+ }
94
+
83
95
  export async function validateReadyProtocolConsistency({
84
96
  target,
85
97
  packageRoot,
@@ -141,9 +153,7 @@ export async function validateReadyProtocolConsistency({
141
153
  }
142
154
  }
143
155
  for (const gate of persisted.satisfiedGates ?? []) {
144
- if (!events.some((event) => event.event === "GATE_SATISFIED"
145
- && event.taskId === persisted.taskId
146
- && event.details?.gate === gate)) {
156
+ if (!hasCurrentRevisionGate(events, persisted.taskId, gate)) {
147
157
  errors.push(issue("E_PREFLIGHT_GATE_EVENT_MISSING", `READY preflight is missing lifecycle gate event: ${gate}`, [eventsRel, `${gatesRel}/${gate}.json`]));
148
158
  }
149
159
  }
@@ -189,9 +199,12 @@ export function assertExistingReadyLifecycleCompatibility(ledger, result) {
189
199
  const existingReady = events.findLast((event) => event.event === "PREFLIGHT_READY" && event.taskId === result.taskId);
190
200
  if (!existingReady) return;
191
201
  const latestOutcome = latestPreflightOutcomeEvent(events, result.taskId);
202
+ const revisedAfterReady = events.some((event) => event.event === "CONTRACT_REVISED"
203
+ && event.taskId === result.taskId
204
+ && event.seq > existingReady.seq);
192
205
  // A READY outcome superseded by a later BLOCKED outcome may be replaced by a
193
206
  // fresh READY with different details once the blocked preflight is resolved.
194
- if (latestOutcome?.event === "PREFLIGHT_BLOCKED") return;
207
+ if (latestOutcome?.event === "PREFLIGHT_BLOCKED" || revisedAfterReady) return;
195
208
  if (!sameReadyPreflightEvent(existingReady, result)) {
196
209
  throw preflightError(
197
210
  "E_PHASE_CHRONOLOGY_INVALID",
@@ -217,9 +230,7 @@ export async function appendActivationEvents(target, packageRoot, ledger, result
217
230
  await append({ taskId: result.taskId, event: "ROUTE_VALIDATED", fingerprint: result.fingerprints.routing });
218
231
  }
219
232
  for (const gate of result.satisfiedGates) {
220
- if (!events.some((event) => event.event === "GATE_SATISFIED"
221
- && event.taskId === result.taskId
222
- && event.details?.gate === gate)) {
233
+ if (!hasCurrentRevisionGate(events, result.taskId, gate)) {
223
234
  await append({ taskId: result.taskId, event: "GATE_SATISFIED", details: { gate } });
224
235
  }
225
236
  }
@@ -227,15 +238,18 @@ export async function appendActivationEvents(target, packageRoot, ledger, result
227
238
  const existingReady = events.findLast((event) => event.event === "PREFLIGHT_READY" && event.taskId === result.taskId);
228
239
  const latestOutcome = latestPreflightOutcomeEvent(events, result.taskId);
229
240
  const readySupersededByBlocked = existingReady && latestOutcome?.event === "PREFLIGHT_BLOCKED";
241
+ const readySupersededByContractRevision = existingReady && events.some((event) => event.event === "CONTRACT_REVISED"
242
+ && event.taskId === result.taskId
243
+ && event.seq > existingReady.seq);
230
244
  if (result.status === "READY") {
231
- if (existingReady && !readySupersededByBlocked && !sameReadyPreflightEvent(existingReady, result)) {
245
+ if (existingReady && !readySupersededByBlocked && !readySupersededByContractRevision && !sameReadyPreflightEvent(existingReady, result)) {
232
246
  throw preflightError(
233
247
  "E_PHASE_CHRONOLOGY_INVALID",
234
248
  "PREFLIGHT_READY already exists with different READY preflight details; repair the contract, route, or gate lifecycle before refreshing preflight",
235
249
  [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.events, ARTIFACT_PATHS.contract, ARTIFACT_PATHS.route, ARTIFACT_PATHS.gates],
236
250
  );
237
251
  }
238
- if (!existingReady || readySupersededByBlocked) {
252
+ if (!existingReady || readySupersededByBlocked || readySupersededByContractRevision) {
239
253
  await append({
240
254
  taskId: result.taskId,
241
255
  event: "PREFLIGHT_READY",
@@ -2,13 +2,15 @@ import { ARTIFACT_PATHS, readJsonArtifact } from "./artifacts.js";
2
2
  import { readContract } from "./contract.js";
3
3
  import { readGateIfPresent, validateGateArtifacts } from "./gate-artifact.js";
4
4
  import { requiredGatesForGuides } from "./guide-metadata.js";
5
- import { assertSourceProvenance } from "./sources.js";
5
+ import { assertContractPresetRefs, assertContractSourceProvenance, externalContractSourceRefs } from "./sources.js";
6
6
  import { readPersistedRoute } from "./route-artifact.js";
7
7
  import { ensureWithin, readBytes } from "./filesystem.js";
8
8
  import { sha256 } from "./manifest.js";
9
9
  import { findProfilePath } from "./profile.js";
10
10
  import { issue } from "./preflight-model.js";
11
11
  import { taskGatePath, taskArtifactPath } from "./task-paths.js";
12
+ import { readEvents } from "./events.js";
13
+ import { hasCurrentGateEvidence } from "./gate-provenance.js";
12
14
 
13
15
  export async function readProfile(target) {
14
16
  const relativePath = await findProfilePath(target);
@@ -62,15 +64,25 @@ export async function loadRoute(target, packageRoot, errors, options = {}) {
62
64
 
63
65
  export async function loadSources(target, contract, packageRoot, errors) {
64
66
  if (!contract?.value?.sourceRefs?.length) return null;
65
- let registry;
67
+ const refs = contract.value.sourceRefs;
66
68
  try {
67
- registry = (await readJsonArtifact(target, ARTIFACT_PATHS.sources, "source-registry", packageRoot)).value;
69
+ assertContractPresetRefs(refs);
68
70
  } catch (error) {
69
- errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_PROFILE_SOURCE_MISSING" : "E_PROFILE_SOURCE_UNKNOWN", error.message, [ARTIFACT_PATHS.sources]));
71
+ errors.push(issue(error.code ?? "E_PROFILE_SOURCE_UNKNOWN", error.message, [ARTIFACT_PATHS.sources]));
70
72
  return null;
71
73
  }
74
+ const externalRefs = externalContractSourceRefs(refs);
75
+ let registry;
76
+ if (externalRefs.length > 0) {
77
+ try {
78
+ registry = (await readJsonArtifact(target, ARTIFACT_PATHS.sources, "source-registry", packageRoot)).value;
79
+ } catch (error) {
80
+ errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_PROFILE_SOURCE_MISSING" : "E_PROFILE_SOURCE_UNKNOWN", error.message, [ARTIFACT_PATHS.sources]));
81
+ return null;
82
+ }
83
+ }
72
84
  try {
73
- assertSourceProvenance(registry, contract.value.sourceRefs);
85
+ assertContractSourceProvenance(registry, refs);
74
86
  } catch (error) {
75
87
  errors.push(issue(error.code ?? "E_PROFILE_SOURCE_UNKNOWN", error.message, [ARTIFACT_PATHS.sources]));
76
88
  }
@@ -84,6 +96,15 @@ export async function inspectGates(target, contract, route, packageRoot, errors,
84
96
  const satisfied = [];
85
97
  const records = {};
86
98
  const taskId = options.taskId ?? null;
99
+ let events = [];
100
+ if (taskId) {
101
+ try {
102
+ events = await readEvents(target, packageRoot, { taskId });
103
+ } catch {
104
+ events = [];
105
+ }
106
+ }
107
+ const latestContractRevisionSeq = events.findLast((event) => event.event === "CONTRACT_REVISED")?.seq ?? 0;
87
108
  for (const gate of required) {
88
109
  let artifact;
89
110
  const defaultGateRel = taskId ? taskGatePath(taskId, gate) : `${ARTIFACT_PATHS.gates}/${gate}.json`;
@@ -111,6 +132,17 @@ export async function inspectGates(target, contract, route, packageRoot, errors,
111
132
  errors.push(issue("E_GATE_STALE", `Gate ${gate} references stale artifacts`, [artifact.path], { gate, stale }));
112
133
  continue;
113
134
  }
135
+ const currentEpochSatisfied = latestContractRevisionSeq === 0
136
+ || hasCurrentGateEvidence(events, taskId, gate, latestContractRevisionSeq);
137
+ if (!currentEpochSatisfied) {
138
+ errors.push(issue(
139
+ "E_GATE_UNVERIFIED",
140
+ `Required gate ${gate} was satisfied before the current contract revision`,
141
+ [defaultGateRel],
142
+ { gate },
143
+ ));
144
+ continue;
145
+ }
114
146
  satisfied.push(gate);
115
147
  }
116
148
  return { required, satisfied: satisfied.sort(), records };
@@ -4,6 +4,8 @@ import { PUBLIC_ERROR_REGISTRY } from "./error-codes.js";
4
4
  import { GUIDE_REGISTRY } from "./guide-registry.js";
5
5
  import { PROTOCOL_VERSION, WORK_PHASES, WORK_TRANSITIONS } from "./protocol.js";
6
6
  import { VERIFICATION_ISOLATION_MODES } from "./verification-execution.js";
7
+ import { PROVIDER_KINDS } from "../providers/capabilities.js";
8
+ import { DECISION_DEFAULT_POLICY, DECISION_ENGINE_ID, PINNED_JEV_MODEL } from "./decision/constants.js";
7
9
 
8
10
  export const SCHEMA_COMPATIBILITY_POLICY = Object.freeze({
9
11
  protocolVersion: PROTOCOL_VERSION,
@@ -36,6 +38,42 @@ export function protocolInfo({ packageVersion = null } = {}) {
36
38
  writesSchemaVersions: schemaVersions,
37
39
  compatibility: SCHEMA_COMPATIBILITY_POLICY,
38
40
  features: {
41
+ semanticDecisionPlane: {
42
+ version: 1,
43
+ supported: true,
44
+ requiredForNewSemanticDecisions: true,
45
+ engine: DECISION_ENGINE_ID,
46
+ model: PINNED_JEV_MODEL,
47
+ policyVersion: DECISION_DEFAULT_POLICY.policyVersion,
48
+ authority: "SEMANTIC_DECISION",
49
+ evidenceAuthority: "NONE",
50
+ lifecycleAuthority: false,
51
+ completionAuthority: false,
52
+ ownershipAuthority: false,
53
+ installationAuthority: false,
54
+ commands: ["decision-status", "decision-show", "context-plan", "model-route", "semantic-plan", "test-inventory", "test-utility", "test-prune-plan", "test-prune-probe"],
55
+ resource: "task/decisions",
56
+ contextPlanResource: "task/context-plan",
57
+ modelRouteResource: "task/model-route",
58
+ testUtilityResource: "task/test-utility",
59
+ testPruneResource: "task/test-utility",
60
+ offlineInspection: true,
61
+ completePerformsLiveRequest: false,
62
+ modelRouting: {
63
+ version: 1,
64
+ tiers: ["NONE", "FAST", "STANDARD", "PRIMARY"],
65
+ deterministicFloor: true,
66
+ advisoryEscalationOnly: true,
67
+ vendorSelection: false,
68
+ },
69
+ semanticPlanning: {
70
+ version: 1,
71
+ questionSets: ["intake-v1", "contract-v1", "route-v1", "context-v1", "model-route-v1", "failure-v1", "diagnosis-v1", "review-v1", "task-overlap-v1", "test-utility-v1", "test-prune-v1"],
72
+ authority: "SEMANTIC_DECISION",
73
+ evidenceAuthority: "NONE",
74
+ lifecycleAuthority: false,
75
+ },
76
+ },
39
77
  taskClaimRecovery: {
40
78
  version: 1,
41
79
  durableRecoveryState: true,
@@ -153,6 +191,18 @@ export function protocolInfo({ packageVersion = null } = {}) {
153
191
  commands: ["next", "task-show"],
154
192
  preservesDefaultOutput: true,
155
193
  },
194
+ auditUx: {
195
+ version: 1,
196
+ supported: true,
197
+ readOnly: true,
198
+ resource: "task/audit-view",
199
+ timeline: true,
200
+ lifecycleAuthority: false,
201
+ evidenceAuthority: false,
202
+ completionAuthority: false,
203
+ mutationAuthority: false,
204
+ externalExecution: false,
205
+ },
156
206
  usageTelemetry: {
157
207
  version: 1,
158
208
  supported: true,
@@ -205,6 +255,21 @@ export function protocolInfo({ packageVersion = null } = {}) {
205
255
  evidenceAuthority: false,
206
256
  executable: false,
207
257
  },
258
+ providerExtensions: {
259
+ version: 1,
260
+ supported: true,
261
+ providerNeutral: true,
262
+ maturity: "experimental",
263
+ publicRegistryApi: false,
264
+ packageSubpathExported: false,
265
+ autoInstall: false,
266
+ lifecycleAuthority: false,
267
+ completionAuthority: false,
268
+ evidenceAuthority: false,
269
+ providerKinds: [...PROVIDER_KINDS],
270
+ resultBoundary: "STRICT_JSON_SNAPSHOT",
271
+ cancellation: "COOPERATIVE_ABORT_SIGNAL",
272
+ },
208
273
  responsibilityConstraints: {
209
274
  version: 1,
210
275
  supported: true,
@@ -30,6 +30,9 @@ export const FAILURE_CODES = Object.freeze([
30
30
  "E_GATE_REQUIRED",
31
31
  "E_GATE_UNVERIFIED",
32
32
  "E_GATE_STALE",
33
+ "E_GATE_NOT_REQUIRED",
34
+ "E_GATE_INVALID",
35
+ "E_PHASE_FREEZE",
33
36
  "E_PHASE_TRANSITION_INVALID",
34
37
  "E_PHASE_PREREQUISITE_MISSING",
35
38
  "E_PHASE_CHRONOLOGY_INVALID",
@@ -113,6 +116,23 @@ export const FAILURE_CODES = Object.freeze([
113
116
  "E_INTERVENTION_HYPOTHESIS_MISSING",
114
117
  "E_INTERVENTION_REFERENCE_INVALID",
115
118
  "E_STRATEGY_OSCILLATION",
119
+ "E_DECISION_ENGINE_UNAVAILABLE",
120
+ "E_DECISION_ENGINE_AUTH_REQUIRED",
121
+ "E_DECISION_ENGINE_AUTH_INVALID",
122
+ "E_DECISION_MODEL_UNSUPPORTED",
123
+ "E_DECISION_REQUEST_INVALID",
124
+ "E_DECISION_RESULT_INVALID",
125
+ "E_DECISION_TIMEOUT",
126
+ "E_DECISION_RATE_LIMITED",
127
+ "E_DECISION_STATE_UNSAFE",
128
+ "E_DECISION_STATE_LIMIT",
129
+ "E_DECISION_LOW_CONFIDENCE",
130
+ "E_DECISION_STALE",
131
+ "E_DECISION_POLICY_INVALID",
132
+ "E_DECISION_CACHE_INVALID",
133
+ "E_DECISION_QUESTION_SET_UNKNOWN",
134
+ "E_DECISION_QUESTION_SET_STALE",
135
+ "E_DECISION_REQUIRED",
116
136
  "E_DECISION_CRITERION_INVALID",
117
137
  "E_DECISION_NOT_UNRESOLVED",
118
138
  ]);
@@ -1,18 +1,17 @@
1
1
  import { readContract } from "./contract.js";
2
2
  import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
3
- import { appendProtocolEvent, validateEventLedger } from "./events.js";
3
+ import { appendProtocolEvent, validateCompletionRecoveryAuthorization, validateEventLedger } from "./events.js";
4
4
  import { authorizeCompletionRecoveryOrRebind } from "./completion-recovery-rebind.js";
5
5
  import { runCommandExecution } from "./execution.js";
6
6
  import { createReceipt } from "./receipt.js";
7
7
  import { currentRepositoryFingerprint } from "./repository.js";
8
8
  import { taskArtifactPath } from "./task-paths.js";
9
+ import { resolveTaskClaimState } from "./task-claim-state.js";
9
10
  import { classifyLoadedWorkState, readWorkState, mutateWorkState } from "./work-state.js";
10
11
  import { classifyRequirement } from "./evidence-readiness.js";
11
12
 
12
13
  export const RECONCILE_EVENT = "CHECKPOINT_RECONCILED";
13
14
 
14
- const RECONCILABLE_DRIFT = new Set(["REPOSITORY_CHANGED"]);
15
-
16
15
  const RECONCILABLE_PHASES = new Set(["EXECUTING", "VERIFYING", "REVIEWING"]);
17
16
 
18
17
  function reconcileError(code, message, artifacts = []) {
@@ -22,14 +21,126 @@ function reconcileError(code, message, artifacts = []) {
22
21
  return error;
23
22
  }
24
23
 
24
+ function assertRepositoryOnlyFreshness(freshness, stateRel, contractRel) {
25
+ if (freshness.status !== "REVALIDATION_REQUIRED") {
26
+ throw reconcileError(
27
+ "E_RECONCILE_NOT_STALE",
28
+ `work-state checkpoint is ${freshness.status === "FRESH" ? "fresh" : "not revalidation-required"}; no reconciliation required`,
29
+ [stateRel, contractRel],
30
+ );
31
+ }
32
+ if (freshness.reasons.length === 1 && freshness.reasons[0] === "REPOSITORY_CHANGED") return;
33
+ throw reconcileError(
34
+ "E_RECONCILE_UNSUPPORTED_DRIFT",
35
+ `reconcile-closure only reconciles repository fingerprint drift; unresolved drift: ${freshness.reasons.join(", ")}`,
36
+ [stateRel, contractRel],
37
+ );
38
+ }
39
+
40
+ async function assertFreshReviewingRecoveryIsAuthorized({
41
+ target,
42
+ packageRoot,
43
+ taskId,
44
+ state,
45
+ eventsRel,
46
+ receiptRel,
47
+ }) {
48
+ if (state.phase !== "REVIEWING" || !Object.prototype.hasOwnProperty.call(state, "lastCompletionAttempt")) return;
49
+ const ledger = await validateEventLedger(target, packageRoot, { taskId });
50
+ if (!ledger.valid) return;
51
+ let receipt = null;
52
+ try {
53
+ receipt = (await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot)).value;
54
+ } catch (error) {
55
+ if (error.code !== "ARTIFACT_MISSING") throw error;
56
+ }
57
+ const recoveryAuth = validateCompletionRecoveryAuthorization({ state, receipt, events: ledger.events });
58
+ if (!recoveryAuth.authorized) {
59
+ const first = recoveryAuth.errors?.[0] ?? {};
60
+ throw reconcileError(
61
+ first.code ?? "E_COMPLETION_RECOVERY_UNAUTHORIZED",
62
+ `REVIEWING reconciliation requires authorized completion recovery: ${first.message ?? "unauthorized"}`,
63
+ [eventsRel, receiptRel],
64
+ );
65
+ }
66
+ }
67
+
68
+ async function validateReconciliationCheckpoint({
69
+ target,
70
+ packageRoot,
71
+ taskId,
72
+ state,
73
+ stateRel,
74
+ contractRel,
75
+ eventsRel,
76
+ receiptRel,
77
+ authorityContext,
78
+ runtimeContext,
79
+ }) {
80
+ const freshness = await classifyLoadedWorkState({ target, state, contractFile: contractRel });
81
+ if (freshness.status !== "REVALIDATION_REQUIRED") {
82
+ await assertFreshReviewingRecoveryIsAuthorized({
83
+ target,
84
+ packageRoot,
85
+ taskId,
86
+ state,
87
+ eventsRel,
88
+ receiptRel,
89
+ });
90
+ }
91
+ assertRepositoryOnlyFreshness(freshness, stateRel, contractRel);
92
+
93
+ const ledger = await validateEventLedger(target, packageRoot, { taskId });
94
+ if (!ledger.valid) {
95
+ const first = ledger.errors[0];
96
+ throw reconcileError(
97
+ "E_RECONCILE_LEDGER_INVALID",
98
+ `append-only event ledger must be valid before reconciliation: ${first?.message ?? "invalid ledger"}`,
99
+ [eventsRel],
100
+ );
101
+ }
102
+
103
+ const ownership = await resolveTaskClaimState(target, { taskId, packageRoot });
104
+ if (!ownership.ownershipValid || !ownership.mutationAllowed || ownership.claimState !== "ACTIVE") {
105
+ const first = ownership.ownershipErrors?.[0] ?? ownership.errors?.[0] ?? {};
106
+ throw reconcileError(
107
+ first.code ?? "E_TASK_CLAIM_OWNERSHIP_INCONSISTENT",
108
+ `reconcile-closure requires active, valid task claim ownership: ${first.message ?? ownership.claimState}`,
109
+ [stateRel, eventsRel],
110
+ );
111
+ }
112
+
113
+ if (state.phase !== "REVIEWING" || !Object.prototype.hasOwnProperty.call(state, "lastCompletionAttempt")) {
114
+ return state;
115
+ }
116
+
117
+ const recovery = await authorizeCompletionRecoveryOrRebind({
118
+ target,
119
+ packageRoot,
120
+ taskId,
121
+ authorityContext,
122
+ runtimeContext,
123
+ });
124
+ if (!recovery.recoveryAuth.authorized) {
125
+ const first = recovery.recoveryAuth.errors?.[0] ?? {};
126
+ throw reconcileError(
127
+ first.code ?? "E_COMPLETION_RECOVERY_UNAUTHORIZED",
128
+ `REVIEWING reconciliation requires authorized completion recovery: ${first.message ?? "unauthorized"}`,
129
+ [stateRel, receiptRel],
130
+ );
131
+ }
132
+ return recovery.rebound ? recovery.state : state;
133
+ }
134
+
25
135
  /**
26
136
  * Canonical recovery for an EXECUTING, VERIFYING, or REVIEWING task whose
27
137
  * objective is already satisfied in the current repository but whose
28
138
  * work-state checkpoint is stale because the repository fingerprint moved.
29
139
  *
30
140
  * The command refreshes the checkpoint repository fingerprint only after:
31
- * - the task is EXECUTING, VERIFYING, or (with authorized completion
32
- * recovery) REVIEWING,
141
+ * - the task is EXECUTING, VERIFYING, or REVIEWING (with either a
142
+ * persisted completion rejection or the narrow repository-only bootstrap
143
+ * path),
33
144
  * - classification requires revalidation and the only drift is
34
145
  * REPOSITORY_CHANGED,
35
146
  * - the append-only event ledger is valid,
@@ -81,53 +192,18 @@ export async function runReconcileClosure({
81
192
  [stateRel],
82
193
  );
83
194
  }
84
- if (state.phase === "REVIEWING") {
85
- const recovery = await authorizeCompletionRecoveryOrRebind({
86
- target,
87
- packageRoot,
88
- taskId,
89
- authorityContext,
90
- runtimeContext,
91
- });
92
- if (!recovery.recoveryAuth.authorized) {
93
- const first = recovery.recoveryAuth.errors?.[0] ?? {};
94
- throw reconcileError(
95
- first.code ?? "E_COMPLETION_RECOVERY_UNAUTHORIZED",
96
- `REVIEWING reconciliation requires authorized completion recovery: ${first.message ?? "unauthorized"}`,
97
- [stateRel, receiptRel],
98
- );
99
- }
100
- if (recovery.rebound) {
101
- state = recovery.state;
102
- }
103
- }
104
-
105
- const freshness = await classifyLoadedWorkState({ target, state, contractFile: contractRel });
106
- if (freshness.status !== "REVALIDATION_REQUIRED" || !freshness.reasons.includes("REPOSITORY_CHANGED")) {
107
- throw reconcileError(
108
- "E_RECONCILE_NOT_STALE",
109
- `work-state checkpoint is ${freshness.status === "FRESH" ? "fresh" : "not revalidation-required"}; no reconciliation required`,
110
- [stateRel, contractRel],
111
- );
112
- }
113
- const unsupported = freshness.reasons.filter((reason) => !RECONCILABLE_DRIFT.has(reason));
114
- if (unsupported.length > 0) {
115
- throw reconcileError(
116
- "E_RECONCILE_UNSUPPORTED_DRIFT",
117
- `reconcile-closure only reconciles repository fingerprint drift; unresolved drift: ${unsupported.join(", ")}`,
118
- [stateRel, contractRel],
119
- );
120
- }
121
-
122
- const ledger = await validateEventLedger(target, packageRoot, { taskId });
123
- if (!ledger.valid) {
124
- const first = ledger.errors[0];
125
- throw reconcileError(
126
- "E_RECONCILE_LEDGER_INVALID",
127
- `append-only event ledger must be valid before reconciliation: ${first?.message ?? "invalid ledger"}`,
128
- [eventsRel],
129
- );
130
- }
195
+ state = await validateReconciliationCheckpoint({
196
+ target,
197
+ packageRoot,
198
+ taskId,
199
+ state,
200
+ stateRel,
201
+ contractRel,
202
+ eventsRel,
203
+ receiptRel,
204
+ authorityContext,
205
+ runtimeContext,
206
+ });
131
207
 
132
208
  const contract = await readContract(target, packageRoot, { taskId });
133
209
  const verificationItem = (contract.value.verification ?? []).find((item) => {
@@ -6,6 +6,7 @@ import {
6
6
  const RECOVERY_EVENT_TYPES = new Set([
7
7
  "TASK_RECOVERY_RECORDED",
8
8
  "OPERATOR_RECOVERY_RECORDED",
9
+ "TASK_ABANDONED",
9
10
  LEGACY_RECOVERY_MIGRATION_EVENT,
10
11
  ]);
11
12