@cassiomc1/forgeloop 1.12.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 (249) hide show
  1. package/.github/copilot-instructions.md +1 -1
  2. package/AGENTS.md +1 -1
  3. package/AGENT_COMPATIBILITY.md +8 -0
  4. package/CLAUDE.md +1 -1
  5. package/CONTRIBUTING.md +90 -0
  6. package/DOCS_INDEX.md +46 -12
  7. package/ENG/c-development-eng.md +112 -0
  8. package/ENG/cpp-development-eng.md +109 -0
  9. package/ENG/dotnet-aspnetcore-development-eng.md +401 -0
  10. package/ENG/go-development-eng.md +103 -0
  11. package/ENG/java-development-eng.md +125 -0
  12. package/ENG/nodejs-backend-development-eng.md +605 -0
  13. package/ENG/php-development-eng.md +104 -0
  14. package/ENG/rust-development-eng.md +422 -0
  15. package/ENG/sec-code-eng.md +7 -7
  16. package/ENG/sql-development-eng.md +108 -0
  17. package/ENG/swift-development-eng.md +111 -0
  18. package/ENG/typescript-development-eng.md +108 -0
  19. package/EXECUTION_STATE.md +12 -0
  20. package/GUIDE_ROUTER.md +418 -9
  21. package/LOOP_ENGINEERING.md +28 -2
  22. package/ORCHESTRATOR_INTEGRATION.md +9 -5
  23. package/PROTOCOL_INTEGRATION.md +55 -2
  24. package/QUALITY_SCORECARD.md +1 -0
  25. package/README.md +78 -52
  26. package/TERMINOLOGY.md +2 -0
  27. package/THIRD_PARTY_NOTICES.md +19 -7
  28. package/THREAT_MODEL.md +140 -1
  29. package/completions/_forgeloop +22 -4
  30. package/completions/forgeloop.bash +40 -4
  31. package/completions/forgeloop.fish +130 -1
  32. package/docs/ADVISORY_CONTEXT.md +25 -0
  33. package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
  34. package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
  35. package/docs/AGENT_PROTOCOL_SUMMARY.md +81 -3
  36. package/docs/AGENT_SKILL.md +66 -0
  37. package/docs/ARTIFACT_REFERENCE.md +123 -0
  38. package/docs/AUDIT_UX.md +46 -0
  39. package/docs/BROWSER_VERIFICATION.md +136 -0
  40. package/docs/CLI_REFERENCE.md +392 -10
  41. package/docs/CODE_ATTESTATION.md +2 -2
  42. package/docs/DOCUMENTATION_GUIDE.md +34 -12
  43. package/docs/GETTING_STARTED.md +59 -0
  44. package/docs/JEV_BENCHMARKS.md +31 -0
  45. package/docs/MODEL_ROUTING.md +37 -0
  46. package/docs/OPENSRC_ADAPTER.md +241 -0
  47. package/docs/PACKAGE_CONTENTS.md +60 -19
  48. package/docs/PROVIDERS.md +126 -0
  49. package/docs/PROVIDER_ARCHITECTURE.md +199 -0
  50. package/docs/RECIPES.md +32 -0
  51. package/docs/RELEASE_CHECKLIST.md +66 -5
  52. package/docs/SECURITY_REVIEW.md +71 -0
  53. package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
  54. package/docs/TEST_INTELLIGENCE.md +29 -0
  55. package/docs/TEST_PRUNING.md +14 -0
  56. package/docs/TROUBLESHOOTING.md +298 -3
  57. package/docs/UNIVERSAL_INTEGRATION.md +31 -0
  58. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
  59. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
  60. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
  61. package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
  62. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  63. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
  64. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
  65. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
  66. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
  67. package/docs/diagrams/README.md +13 -9
  68. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
  69. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
  70. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
  71. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  72. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  73. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  74. package/docs/documentation-manifest.json +1397 -0
  75. package/docs/protocol-requirements.json +101 -0
  76. package/package.json +46 -4
  77. package/schemas/config.schema.json +14 -0
  78. package/schemas/context-plan.schema.json +18 -0
  79. package/schemas/routing-input.schema.json +1 -1
  80. package/schemas/semantic-decision.schema.json +46 -0
  81. package/schemas/test-utility.schema.json +44 -0
  82. package/scripts/CI_VALIDATORS.md +84 -11
  83. package/scripts/benchmark-jev.mjs +5 -0
  84. package/scripts/benchmark-test-intelligence.mjs +4 -0
  85. package/scripts/generate-agent-protocol-summary.mjs +40 -1
  86. package/scripts/generate-forgeloop-skill.mjs +133 -0
  87. package/scripts/jev-smoke.mjs +19 -0
  88. package/skills/forgeloop/README.md +9 -0
  89. package/skills/forgeloop/SKILL.md +77 -0
  90. package/skills/forgeloop/references/lifecycle.md +9 -0
  91. package/skills/forgeloop/references/recovery.md +7 -0
  92. package/skills/forgeloop/references/verification.md +7 -0
  93. package/src/adapters/agent-browser/assertions.js +47 -0
  94. package/src/adapters/agent-browser/commands.js +54 -0
  95. package/src/adapters/agent-browser/index.js +3 -0
  96. package/src/adapters/agent-browser/locator.js +40 -0
  97. package/src/adapters/agent-browser/process.js +215 -0
  98. package/src/adapters/agent-browser/provider.js +313 -0
  99. package/src/adapters/emulated-services/constants.js +24 -0
  100. package/src/adapters/emulated-services/index.js +7 -0
  101. package/src/adapters/emulated-services/process.js +162 -0
  102. package/src/adapters/emulated-services/provider.js +282 -0
  103. package/src/adapters/opensrc/normalize.js +90 -0
  104. package/src/adapters/opensrc/process.js +248 -0
  105. package/src/adapters/opensrc/provider.js +338 -0
  106. package/src/adapters/opensrc/search.js +264 -0
  107. package/src/adapters/typesafe/client.js +28 -0
  108. package/src/adapters/typesafe/engine.js +63 -0
  109. package/src/adapters/typesafe/normalize.js +41 -0
  110. package/src/cli.js +108 -0
  111. package/src/commands/checkpoint-revalidate.js +176 -0
  112. package/src/commands/context-plan.js +38 -0
  113. package/src/commands/contract-create.js +264 -0
  114. package/src/commands/contract-revise.js +236 -0
  115. package/src/commands/decision-show.js +14 -0
  116. package/src/commands/decision-status.js +22 -0
  117. package/src/commands/discover.js +41 -0
  118. package/src/commands/doctor.js +15 -0
  119. package/src/commands/gate-record.js +205 -0
  120. package/src/commands/gate-revalidate.js +137 -0
  121. package/src/commands/model-route.js +32 -0
  122. package/src/commands/next.js +19 -7
  123. package/src/commands/route.js +146 -18
  124. package/src/commands/semantic-plan.js +17 -0
  125. package/src/commands/task-abandon.js +224 -0
  126. package/src/commands/task-create.js +84 -25
  127. package/src/commands/task-list.js +22 -2
  128. package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
  129. package/src/commands/task-repair-contract-bootstrap.js +263 -0
  130. package/src/commands/test-inventory.js +5 -0
  131. package/src/commands/test-prune-plan.js +5 -0
  132. package/src/commands/test-prune-probe.js +5 -0
  133. package/src/commands/test-utility.js +5 -0
  134. package/src/commands/validate-protocol.js +10 -1
  135. package/src/config/guides.json +44 -0
  136. package/src/core/artifact-registry.js +24 -0
  137. package/src/core/audit-ux.js +514 -0
  138. package/src/core/browser-verification/constants.js +149 -0
  139. package/src/core/browser-verification/normalize.js +254 -0
  140. package/src/core/browser-verification/provider.js +519 -0
  141. package/src/core/browser-verification/service.js +115 -0
  142. package/src/core/build-script.js +151 -0
  143. package/src/core/c-cpp-project.js +143 -0
  144. package/src/core/checkpoint-revalidation.js +319 -0
  145. package/src/core/cli-command-definitions.js +249 -1
  146. package/src/core/command-executors.js +115 -3
  147. package/src/core/command-input.js +212 -102
  148. package/src/core/completion-artifacts.js +14 -5
  149. package/src/core/completion.js +4 -6
  150. package/src/core/config.js +3 -0
  151. package/src/core/context-compiler/budget.js +9 -0
  152. package/src/core/context-compiler/candidates.js +39 -0
  153. package/src/core/context-compiler/compiler.js +63 -0
  154. package/src/core/context-compiler/fingerprint.js +11 -0
  155. package/src/core/context-compiler/policy.js +13 -0
  156. package/src/core/context-compiler/result.js +23 -0
  157. package/src/core/contract-bootstrap-recovery.js +655 -0
  158. package/src/core/contract-presets.js +82 -0
  159. package/src/core/contract-revision.js +210 -0
  160. package/src/core/decision/artifact.js +69 -0
  161. package/src/core/decision/benchmarks.js +103 -0
  162. package/src/core/decision/cache.js +27 -0
  163. package/src/core/decision/constants.js +58 -0
  164. package/src/core/decision/cutover.js +34 -0
  165. package/src/core/decision/engine.js +22 -0
  166. package/src/core/decision/errors.js +68 -0
  167. package/src/core/decision/events.js +101 -0
  168. package/src/core/decision/freshness.js +19 -0
  169. package/src/core/decision/normalizers/index.js +115 -0
  170. package/src/core/decision/policy.js +18 -0
  171. package/src/core/decision/projection.js +16 -0
  172. package/src/core/decision/question-registry.js +201 -0
  173. package/src/core/decision/request.js +26 -0
  174. package/src/core/decision/resolver.js +130 -0
  175. package/src/core/decision/result.js +58 -0
  176. package/src/core/decision/service.js +156 -0
  177. package/src/core/decision/state-builder.js +65 -0
  178. package/src/core/decision/task-bindings.js +30 -0
  179. package/src/core/decision/test-provider.js +32 -0
  180. package/src/core/decision/thresholds.js +15 -0
  181. package/src/core/error-codes.js +281 -3
  182. package/src/core/events.js +226 -57
  183. package/src/core/evidence-readiness.js +9 -0
  184. package/src/core/execution-prerequisites.js +14 -0
  185. package/src/core/execution-profile.js +63 -38
  186. package/src/core/filesystem.js +1 -10
  187. package/src/core/gate-provenance.js +124 -0
  188. package/src/core/go-project.js +206 -0
  189. package/src/core/integration-invocation-policy.js +27 -4
  190. package/src/core/integration-resources.js +86 -61
  191. package/src/core/java-project.js +403 -0
  192. package/src/core/model-router/constants.js +10 -0
  193. package/src/core/model-router/policy.js +103 -0
  194. package/src/core/model-router/router.js +37 -0
  195. package/src/core/multi-language-project.js +117 -0
  196. package/src/core/next-action-model.js +58 -0
  197. package/src/core/next-action-phases.js +130 -42
  198. package/src/core/next-action-refresh.js +43 -9
  199. package/src/core/next-action-review-phase.js +7 -2
  200. package/src/core/next-action.js +35 -7
  201. package/src/core/next-explanation.js +63 -0
  202. package/src/core/phase.js +128 -10
  203. package/src/core/php-project.js +85 -0
  204. package/src/core/preflight-consistency.js +23 -9
  205. package/src/core/preflight-loaders.js +37 -5
  206. package/src/core/project-detection.js +1760 -52
  207. package/src/core/protocol-info.js +65 -0
  208. package/src/core/protocol.js +20 -0
  209. package/src/core/reconcile-closure.js +132 -53
  210. package/src/core/recovery-history.js +1 -0
  211. package/src/core/resumability.js +154 -44
  212. package/src/core/route-artifact.js +15 -1
  213. package/src/core/router.js +223 -4
  214. package/src/core/runtime-context.js +118 -61
  215. package/src/core/rust-project.js +400 -0
  216. package/src/core/schema-validation.js +3 -0
  217. package/src/core/security-review/constants.js +64 -0
  218. package/src/core/security-review/normalize.js +245 -0
  219. package/src/core/security-review/provider.js +204 -0
  220. package/src/core/security-review/service.js +134 -0
  221. package/src/core/semantic-planning/constants.js +19 -0
  222. package/src/core/semantic-planning/projection.js +94 -0
  223. package/src/core/semantic-planning/service.js +15 -0
  224. package/src/core/sources.js +37 -0
  225. package/src/core/sql-project.js +141 -0
  226. package/src/core/swift-project.js +200 -0
  227. package/src/core/task-claim-state.js +201 -1
  228. package/src/core/task-conflict-inspection.js +31 -5
  229. package/src/core/task-paths.js +13 -0
  230. package/src/core/task-recovery.js +1 -0
  231. package/src/core/templates.js +3 -0
  232. package/src/core/test-intelligence/benchmarks.js +68 -0
  233. package/src/core/test-intelligence/inventory.js +73 -0
  234. package/src/core/test-intelligence/prune.js +90 -0
  235. package/src/core/test-intelligence/semantic-state.js +15 -0
  236. package/src/core/test-intelligence/service.js +40 -0
  237. package/src/core/test-intelligence/utility.js +50 -0
  238. package/src/core/trace.js +11 -7
  239. package/src/core/transaction.js +1 -0
  240. package/src/core/typescript-project.js +349 -0
  241. package/src/core/xml-structure.js +123 -0
  242. package/src/integration.d.ts +492 -0
  243. package/src/integration.js +54 -0
  244. package/src/providers/README.md +47 -0
  245. package/src/providers/capabilities.js +46 -0
  246. package/src/providers/errors.js +15 -0
  247. package/src/providers/index.js +29 -0
  248. package/src/providers/json-snapshot.js +105 -0
  249. package/src/providers/registry.js +152 -0
@@ -9,6 +9,15 @@ import { assertSchema, readSchema } from "./schema-validation.js";
9
9
  import { assertSecretFree } from "./receipt.js";
10
10
  import { PROTOCOL_VERSION } from "./protocol.js";
11
11
  import { isRecoverableCompletionEvidenceCode } from "./completion-recovery.js";
12
+ import {
13
+ CONTRACT_BOOTSTRAP_REPAIR_EVENT,
14
+ ROUTE_CHECKPOINT_BOUND_EVENT,
15
+ assertContractBootstrapRepairDetails,
16
+ assertContractBootstrapRepairMigrationDetails,
17
+ isLegacyContractBootstrapRepairMarkerShape,
18
+ validateContractBootstrapRepairMigrations,
19
+ repairMarkerErrors,
20
+ } from "./contract-bootstrap-recovery.js";
12
21
 
13
22
  import { taskArtifactPath } from "./task-paths.js";
14
23
  import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
@@ -33,6 +42,32 @@ import {
33
42
  isLegacyRecoveryEventShape,
34
43
  legacyRecoveryMigrationId,
35
44
  } from "./task-recovery-migration.js";
45
+ import {
46
+ CHECKPOINT_REVALIDATED_EVENT,
47
+ assertCheckpointRevalidatedDetails,
48
+ validateCheckpointRevalidationCurrentBinding,
49
+ validateCheckpointRevalidationEventBindings,
50
+ } from "./checkpoint-revalidation.js";
51
+ import {
52
+ CONTRACT_REVISED_EVENT,
53
+ assertContractRevisedDetails,
54
+ validateContractRevisionCurrentBinding,
55
+ validateContractRevisionEventBindings,
56
+ } from "./contract-revision.js";
57
+ import {
58
+ GATE_SATISFIED_EVENT,
59
+ GATE_REVALIDATED_EVENT,
60
+ assertGateSatisfiedDetails,
61
+ assertGateRevalidatedDetails,
62
+ hasCurrentGateEvidence,
63
+ validateGateSatisfactionBindings,
64
+ } from "./gate-provenance.js";
65
+ import {
66
+ assertSemanticDecisionDetails,
67
+ validateSemanticDecisionArtifactBindings,
68
+ validateSemanticDecisionEventBindings,
69
+ } from "./decision/events.js";
70
+ import { SEMANTIC_DECISION_RECORDED_EVENT, SEMANTIC_DECISION_SUPERSEDED_EVENT } from "./decision/constants.js";
36
71
 
37
72
  const EVENT_SCHEMA_VERSION = 1;
38
73
  export const LIFECYCLE_MILESTONES = Object.freeze([
@@ -60,6 +95,48 @@ const REPEATABLE_MILESTONES = new Set([
60
95
  "PREFLIGHT_READY",
61
96
  ]);
62
97
 
98
+ export function isRevisionEpochPlanRepeat(events = [], index = 0, event = null) {
99
+ if (event?.event !== "PLAN_RECORDED") return false;
100
+ const previousPlan = events.slice(0, index).findLast((candidate) => candidate.event === "PLAN_RECORDED");
101
+ const revision = events.slice(0, index).findLast((candidate) => candidate.event === CONTRACT_REVISED_EVENT);
102
+ return Boolean(previousPlan && revision && previousPlan.seq < revision.seq);
103
+ }
104
+
105
+ function validateMilestoneChronology(events, index, event, milestoneCounts, lastMilestone, errors) {
106
+ const milestoneIndex = LIFECYCLE_MILESTONES.indexOf(event.event);
107
+ if (milestoneIndex < 0) return lastMilestone;
108
+ const count = (milestoneCounts.get(event.event) ?? 0) + 1;
109
+ milestoneCounts.set(event.event, count);
110
+ const revisionPlanRepeat = isRevisionEpochPlanRepeat(events, index, event);
111
+ if (count > 1 && !REPEATABLE_MILESTONES.has(event.event) && !revisionPlanRepeat) {
112
+ errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: `lifecycle milestone must not repeat: ${event.event}` });
113
+ }
114
+ if (milestoneIndex > lastMilestone + 1) {
115
+ errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: `${event.event} is missing prerequisite milestone: ${LIFECYCLE_MILESTONES[lastMilestone + 1]}` });
116
+ } else if (milestoneIndex < lastMilestone && event.event !== "VERIFICATION_STARTED" && event.event !== "PREFLIGHT_READY") {
117
+ errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: `${event.event} is out of lifecycle order` });
118
+ } else if (milestoneIndex === lastMilestone && !REPEATABLE_MILESTONES.has(event.event) && !revisionPlanRepeat) {
119
+ errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: `lifecycle milestone must not repeat: ${event.event}` });
120
+ }
121
+ if (milestoneIndex > lastMilestone) return milestoneIndex;
122
+ return lastMilestone;
123
+ }
124
+
125
+ function validateExecutionGateChronology(events, index, seen, errors) {
126
+ if (!seen.has("ROUTE_VALIDATED")) errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: "execution started before route validation" });
127
+ if (!seen.has("CONTRACT_VALIDATED")) errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: "execution started before contract validation" });
128
+ if (!seen.has("PREFLIGHT_READY")) errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: "execution started before preflight readiness" });
129
+ const preflight = events.slice(0, index).findLast((candidate) => candidate.event === "PREFLIGHT_READY");
130
+ const requiredGates = preflight?.details?.requiredGates ?? [];
131
+ const latestContractRevisionSeq = events.slice(0, index).findLast((candidate) => candidate.event === CONTRACT_REVISED_EVENT)?.seq ?? 0;
132
+ const beforeExecution = events.slice(0, index);
133
+ for (const gate of requiredGates) {
134
+ if (!hasCurrentGateEvidence(beforeExecution, events[index]?.taskId, gate, latestContractRevisionSeq)) {
135
+ errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: `execution started before gate satisfaction: ${gate}` });
136
+ }
137
+ }
138
+ }
139
+
63
140
  function eventIndexPath(eventsPath) {
64
141
  return `${eventsPath}.index.json`;
65
142
  }
@@ -125,6 +202,22 @@ export function validateKnownEventDetails(event) {
125
202
  case "CHECKPOINT_RECONCILED":
126
203
  assertReconcileClosureDetails(event.details);
127
204
  return;
205
+ case CHECKPOINT_REVALIDATED_EVENT:
206
+ assertCheckpointRevalidatedDetails(event.details);
207
+ return;
208
+ case CONTRACT_REVISED_EVENT:
209
+ assertContractRevisedDetails(event.details);
210
+ return;
211
+ case GATE_SATISFIED_EVENT:
212
+ assertGateSatisfiedDetails(event.details);
213
+ return;
214
+ case GATE_REVALIDATED_EVENT:
215
+ assertGateRevalidatedDetails(event.details);
216
+ return;
217
+ case SEMANTIC_DECISION_RECORDED_EVENT:
218
+ case SEMANTIC_DECISION_SUPERSEDED_EVENT:
219
+ assertSemanticDecisionDetails(event.details, event.event);
220
+ return;
128
221
  case "TASK_RECOVERY_RECORDED":
129
222
  assertRecoveryRecordedDetails(event.details);
130
223
  return;
@@ -135,18 +228,27 @@ export function validateKnownEventDetails(event) {
135
228
  if (!event.details?.recoveryId && isLegacyRecoveryDetailsShape(event.details)) return;
136
229
  assertRecoveryRecordedDetails(event.details);
137
230
  return;
231
+ case "TASK_ABANDONED":
232
+ assertTaskAbandonedDetails(event.details);
233
+ return;
138
234
  case "LEGACY_RECOVERY_MIGRATION_RECORDED":
139
235
  assertLegacyMigrationDetails(event.details);
140
236
  return;
237
+ case CONTRACT_BOOTSTRAP_REPAIR_EVENT:
238
+ case "CONTRACT_BOOTSTRAP_REPAIR_MIGRATION_RECORDED":
239
+ validateKnownContractBootstrapEvent(event);
240
+ return;
241
+ case "ROUTE_REBOUND":
242
+ assertRouteReboundDetails(event.details);
243
+ return;
244
+ case ROUTE_CHECKPOINT_BOUND_EVENT:
245
+ assertRouteCheckpointBoundDetails(event.details);
246
+ return;
141
247
  case "TASK_RECOVERY_RESUMED":
142
248
  assertRecoveryResumedDetails(event.details);
143
249
  return;
144
250
  case "TRAJECTORY_EVALUATED":
145
- if (!event.details || !/^eval-[A-Za-z0-9_-]+$/.test(event.details.evaluationId ?? "")
146
- || typeof event.details.scenarioId !== "string" || !/^[a-f0-9]{64}$/.test(event.details.evaluationFingerprint ?? "")
147
- || event.fingerprint !== event.details.evaluationFingerprint) {
148
- throw protocolError("E_EVENT_INVALID", "TRAJECTORY_EVALUATED requires a bound evaluationId, scenarioId, and fingerprint");
149
- }
251
+ assertTrajectoryEvaluatedDetails(event);
150
252
  return;
151
253
  case "WORKSPACE_BOUND":
152
254
  assertStructuredArtifactEvent(event, ["workspaceFingerprint"], "WORKSPACE_BOUND");
@@ -161,14 +263,7 @@ export function validateKnownEventDetails(event) {
161
263
  assertFingerprint(event.details.digest, "HANDOFF_CREATED details.digest");
162
264
  return;
163
265
  case "HANDOFF_ACCEPTED":
164
- if (!event.details || typeof event.details !== "object" || Array.isArray(event.details)
165
- || typeof event.details.handoffId !== "string" || !/^handoff-[A-Za-z0-9_-]+$/.test(event.details.handoffId)
166
- || typeof event.details.handoffDigest !== "string"
167
- || typeof event.details.consumerId !== "string" || !event.details.consumerId.trim()
168
- || (event.details.harness !== undefined && (typeof event.details.harness !== "string" || !event.details.harness.trim()))) {
169
- throw protocolError("E_EVENT_INVALID", "HANDOFF_ACCEPTED requires a valid handoffId, handoffDigest, and consumerId");
170
- }
171
- assertFingerprint(event.details.handoffDigest, "HANDOFF_ACCEPTED details.handoffDigest");
266
+ assertHandoffAcceptedDetails(event);
172
267
  return;
173
268
  case "RESPONSIBILITY_SET":
174
269
  assertStructuredArtifactEvent(event, ["responsibilityFingerprint"], "RESPONSIBILITY_SET");
@@ -207,6 +302,34 @@ export function validateKnownEventDetails(event) {
207
302
  }
208
303
  }
209
304
 
305
+ function validateKnownContractBootstrapEvent(event) {
306
+ if (event.event === CONTRACT_BOOTSTRAP_REPAIR_EVENT) {
307
+ if (isLegacyContractBootstrapRepairMarkerShape(event)) return;
308
+ assertContractBootstrapRepairDetails(event.details);
309
+ return;
310
+ }
311
+ assertContractBootstrapRepairMigrationDetails(event.details);
312
+ }
313
+
314
+ function assertTrajectoryEvaluatedDetails(event) {
315
+ if (!event.details || !/^eval-[A-Za-z0-9_-]+$/.test(event.details.evaluationId ?? "")
316
+ || typeof event.details.scenarioId !== "string" || !/^[a-f0-9]{64}$/.test(event.details.evaluationFingerprint ?? "")
317
+ || event.fingerprint !== event.details.evaluationFingerprint) {
318
+ throw protocolError("E_EVENT_INVALID", "TRAJECTORY_EVALUATED requires a bound evaluationId, scenarioId, and fingerprint");
319
+ }
320
+ }
321
+
322
+ function assertHandoffAcceptedDetails(event) {
323
+ if (!event.details || typeof event.details !== "object" || Array.isArray(event.details)
324
+ || typeof event.details.handoffId !== "string" || !/^handoff-[A-Za-z0-9_-]+$/.test(event.details.handoffId)
325
+ || typeof event.details.handoffDigest !== "string"
326
+ || typeof event.details.consumerId !== "string" || !event.details.consumerId.trim()
327
+ || (event.details.harness !== undefined && (typeof event.details.harness !== "string" || !event.details.harness.trim()))) {
328
+ throw protocolError("E_EVENT_INVALID", "HANDOFF_ACCEPTED requires a valid handoffId, handoffDigest, and consumerId");
329
+ }
330
+ assertFingerprint(event.details.handoffDigest, "HANDOFF_ACCEPTED details.handoffDigest");
331
+ }
332
+
210
333
  function assertStringList(value, label) {
211
334
  if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || !item)) {
212
335
  throw protocolError("E_EVENT_INVALID", `${label} must be an array of non-empty strings`);
@@ -234,6 +357,35 @@ function assertStructuredArtifactEvent(event, requiredDetails, label) {
234
357
  }
235
358
  }
236
359
 
360
+ function assertRouteReboundDetails(details) {
361
+ const keys = ["routeFingerprint", "contractFingerprint", "previousRouteFingerprint"];
362
+ if (!details || typeof details !== "object" || Array.isArray(details)
363
+ || Object.keys(details).length !== keys.length
364
+ || keys.some((key) => !Object.prototype.hasOwnProperty.call(details, key))) {
365
+ throw protocolError("E_EVENT_INVALID", "ROUTE_REBOUND requires exactly route, contract, and previous route fingerprints");
366
+ }
367
+ assertFingerprint(details.routeFingerprint, "ROUTE_REBOUND details.routeFingerprint");
368
+ assertFingerprint(details.contractFingerprint, "ROUTE_REBOUND details.contractFingerprint");
369
+ if (details.previousRouteFingerprint !== null) {
370
+ assertFingerprint(details.previousRouteFingerprint, "ROUTE_REBOUND details.previousRouteFingerprint");
371
+ }
372
+ }
373
+
374
+ function assertRouteCheckpointBoundDetails(details) {
375
+ const keys = ["routeFingerprint", "contractFingerprint", "selectedGuides"];
376
+ if (!details || typeof details !== "object" || Array.isArray(details)
377
+ || Object.keys(details).length !== keys.length
378
+ || keys.some((key) => !Object.prototype.hasOwnProperty.call(details, key))) {
379
+ throw protocolError("E_EVENT_INVALID", "ROUTE_CHECKPOINT_BOUND requires exactly route, contract, and selected guide identity");
380
+ }
381
+ assertFingerprint(details.routeFingerprint, "ROUTE_CHECKPOINT_BOUND details.routeFingerprint");
382
+ assertFingerprint(details.contractFingerprint, "ROUTE_CHECKPOINT_BOUND details.contractFingerprint");
383
+ assertStringList(details.selectedGuides, "ROUTE_CHECKPOINT_BOUND details.selectedGuides");
384
+ if (new Set(details.selectedGuides).size !== details.selectedGuides.length) {
385
+ throw protocolError("E_EVENT_INVALID", "ROUTE_CHECKPOINT_BOUND details.selectedGuides must not contain duplicates");
386
+ }
387
+ }
388
+
237
389
  function assertRecoveryRecordedDetails(details) {
238
390
  if (!details || typeof details !== "object" || Array.isArray(details)) {
239
391
  throw protocolError("E_EVENT_INVALID", "recovery event requires structured details");
@@ -256,6 +408,44 @@ function assertRecoveryRecordedDetails(details) {
256
408
  assertStringList(details.releasedClaims, "recovery event details.releasedClaims");
257
409
  }
258
410
 
411
+ function assertTaskAbandonedDetails(details) {
412
+ const keys = [
413
+ "recoveryId", "classification", "reasonCodes", "previousPhase", "previousRevision",
414
+ "previousHead", "previousBranch", "currentHead", "currentBranch",
415
+ "releasedClaims", "authorityKind",
416
+ ];
417
+ if (!details || typeof details !== "object" || Array.isArray(details)
418
+ || Object.keys(details).length !== keys.length
419
+ || keys.some((key) => !Object.prototype.hasOwnProperty.call(details, key))) {
420
+ throw protocolError("E_EVENT_INVALID", "TASK_ABANDONED requires the exact abandonment boundary details");
421
+ }
422
+ if (typeof details.recoveryId !== "string" || details.recoveryId.length === 0) {
423
+ throw protocolError("E_EVENT_INVALID", "TASK_ABANDONED details.recoveryId must be a non-empty string");
424
+ }
425
+ if (details.classification !== "ABANDONED") {
426
+ throw protocolError("E_EVENT_INVALID", "TASK_ABANDONED details.classification must be ABANDONED");
427
+ }
428
+ assertStringList(details.reasonCodes, "TASK_ABANDONED details.reasonCodes");
429
+ if (details.reasonCodes.length !== 1 || details.reasonCodes[0] !== "CALLER_ABANDONED") {
430
+ throw protocolError("E_EVENT_INVALID", "TASK_ABANDONED details.reasonCodes must be [CALLER_ABANDONED]");
431
+ }
432
+ if (typeof details.previousPhase !== "string" || details.previousPhase.length === 0) {
433
+ throw protocolError("E_EVENT_INVALID", "TASK_ABANDONED details.previousPhase must be a non-empty string");
434
+ }
435
+ if (!Number.isInteger(details.previousRevision) || details.previousRevision < 0) {
436
+ throw protocolError("E_EVENT_INVALID", "TASK_ABANDONED details.previousRevision must be a non-negative integer");
437
+ }
438
+ for (const key of ["previousHead", "previousBranch", "currentHead", "currentBranch"]) {
439
+ if (typeof details[key] !== "string" && details[key] !== null) {
440
+ throw protocolError("E_EVENT_INVALID", `TASK_ABANDONED details.${key} must be a string or null`);
441
+ }
442
+ }
443
+ assertStringList(details.releasedClaims, "TASK_ABANDONED details.releasedClaims");
444
+ if (details.authorityKind !== "CALLER_ACKNOWLEDGED") {
445
+ throw protocolError("E_EVENT_INVALID", "TASK_ABANDONED details.authorityKind must be CALLER_ACKNOWLEDGED");
446
+ }
447
+ }
448
+
259
449
  function assertRecoveryResumedDetails(details) {
260
450
  if (!details || typeof details !== "object" || Array.isArray(details)
261
451
  || typeof details.recoveryId !== "string" || !details.recoveryId) {
@@ -542,50 +732,10 @@ export async function validateEventLedger(target, packageRoot, options = {}) {
542
732
  } catch (err) {
543
733
  errors.push({ code: err.code ?? "E_EVENT_INVALID", message: `event ${event.seq} (${event.event}): ${err.message}` });
544
734
  }
545
- const milestoneIndex = LIFECYCLE_MILESTONES.indexOf(event.event);
546
- if (milestoneIndex >= 0) {
547
- const count = (milestoneCounts.get(event.event) ?? 0) + 1;
548
- milestoneCounts.set(event.event, count);
549
- if (count > 1 && !REPEATABLE_MILESTONES.has(event.event)) {
550
- errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: `lifecycle milestone must not repeat: ${event.event}` });
551
- }
552
- if (milestoneIndex > lastMilestone + 1) {
553
- errors.push({
554
- code: "E_PHASE_CHRONOLOGY_INVALID",
555
- message: `${event.event} is missing prerequisite milestone: ${LIFECYCLE_MILESTONES[lastMilestone + 1]}`,
556
- });
557
- } else if (milestoneIndex < lastMilestone && event.event !== "VERIFICATION_STARTED"
558
- && event.event !== "PREFLIGHT_READY") {
559
- // VERIFICATION_STARTED re-enters per verification cycle; PREFLIGHT_READY
560
- // may be refreshed mid-lifecycle (policy/contract evolution) after its
561
- // prerequisites were already satisfied by the earlier occurrence.
562
- errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: `${event.event} is out of lifecycle order` });
563
- } else if (milestoneIndex === lastMilestone && !REPEATABLE_MILESTONES.has(event.event)) {
564
- errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: `lifecycle milestone must not repeat: ${event.event}` });
565
- }
566
- if (milestoneIndex > lastMilestone) lastMilestone = milestoneIndex;
567
- }
735
+ lastMilestone = validateMilestoneChronology(events, index, event, milestoneCounts, lastMilestone, errors);
568
736
  seen.add(event.event);
569
- if (event.event === "EXECUTION_STARTED" && !seen.has("ROUTE_VALIDATED")) {
570
- errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: "execution started before route validation" });
571
- }
572
- if (event.event === "EXECUTION_STARTED" && !seen.has("CONTRACT_VALIDATED")) {
573
- errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: "execution started before contract validation" });
574
- }
575
- if (event.event === "EXECUTION_STARTED" && !seen.has("PREFLIGHT_READY")) {
576
- errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: "execution started before preflight readiness" });
577
- }
578
737
  if (event.event === "EXECUTION_STARTED") {
579
- const preflight = events.slice(0, index).findLast((candidate) => candidate.event === "PREFLIGHT_READY");
580
- const requiredGates = preflight?.details?.requiredGates ?? [];
581
- const satisfiedGates = new Set(events.slice(0, index)
582
- .filter((candidate) => candidate.event === "GATE_SATISFIED")
583
- .map((candidate) => candidate.details?.gate));
584
- for (const gate of requiredGates) {
585
- if (!satisfiedGates.has(gate)) {
586
- errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: `execution started before gate satisfaction: ${gate}` });
587
- }
588
- }
738
+ validateExecutionGateChronology(events, index, seen, errors);
589
739
  }
590
740
  if (event.event === "VERIFICATION_RECORDED" && !seen.has("VERIFICATION_STARTED")) {
591
741
  errors.push({ code: "E_PHASE_CHRONOLOGY_INVALID", message: "verification evidence recorded before verification started" });
@@ -620,11 +770,30 @@ export async function validateEventLedger(target, packageRoot, options = {}) {
620
770
  validateLegacyRecoveryMigrations(events, errors, {
621
771
  allowUnmigratedLegacyRecoveryEvents: options?.allowUnmigratedLegacyRecoveryEvents === true,
622
772
  });
623
- return { valid: errors.length === 0, events, errors };
773
+ errors.push(...validateCheckpointRevalidationEventBindings(events));
774
+ errors.push(...validateContractRevisionEventBindings(events));
775
+ errors.push(...validateGateSatisfactionBindings(events));
776
+ errors.push(...validateSemanticDecisionEventBindings(events));
777
+ errors.push(...await validateSemanticDecisionArtifactBindings(target, packageRoot, events));
778
+ const repairedErrors = validateContractBootstrapRepairLedger(events, errors, options);
779
+ return { valid: repairedErrors.length === 0, events, errors: repairedErrors };
780
+ }
781
+
782
+ function validateContractBootstrapRepairLedger(events, errors, options) {
783
+ const allowUnmigrated = options?.allowUnmigratedLegacyContractBootstrapRepairMarkers === true;
784
+ validateContractBootstrapRepairMigrations(events, errors, {
785
+ allowUnmigratedLegacyContractBootstrapRepairMarkers: allowUnmigrated,
786
+ });
787
+ return repairMarkerErrors(events, errors, {
788
+ allowUnmigratedLegacyContractBootstrapRepairMarkers: allowUnmigrated,
789
+ });
624
790
  }
625
791
 
626
792
  export function validateStateLedgerCoherence(state, events) {
627
- const errors = [];
793
+ const errors = [
794
+ ...validateCheckpointRevalidationCurrentBinding(state, events),
795
+ ...validateContractRevisionCurrentBinding(state, events),
796
+ ];
628
797
  if (!Number.isInteger(state.verificationCycle)) return errors;
629
798
  const taskEvents = events.filter((event) => event.taskId === state.taskId);
630
799
  const observed = new Set(taskEvents.map((event) => event.event));
@@ -164,6 +164,15 @@ export function terminalRequirements(requirements = []) {
164
164
  return normalizeRequirements(requirements).filter((requirement) => requirement.terminalOwned);
165
165
  }
166
166
 
167
+ export function terminalRequirementsForContract(contractValue = {}, { additionalEvidence = [] } = {}) {
168
+ const pool = [
169
+ ...(contractValue.verification ?? []),
170
+ ...(contractValue.successCriteria ?? []),
171
+ ...additionalEvidence,
172
+ ];
173
+ return terminalRequirements(pool);
174
+ }
175
+
167
176
  export function lifecycleRequirements(requirements = []) {
168
177
  return normalizeRequirements(requirements).filter((requirement) => requirement.type === "LIFECYCLE");
169
178
  }
@@ -6,6 +6,7 @@ import { readPersistedRoute } from "./route-artifact.js";
6
6
  import { stateIdentityErrors } from "./completion-relationships.js";
7
7
  import { classifyLoadedWorkState } from "./work-state.js";
8
8
  import { taskArtifactPath } from "./task-paths.js";
9
+ import { hasCurrentGateEvidence } from "./gate-provenance.js";
9
10
 
10
11
  const START_EXECUTION_EVENTS = Object.freeze([
11
12
  "CONTRACT_VALIDATED",
@@ -129,6 +130,19 @@ function prerequisiteLedgerErrors(ledger, taskId, preflight, route) {
129
130
  [ARTIFACT_PATHS.events, ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.route],
130
131
  ));
131
132
  }
133
+ const latestContractRevisionSeq = currentEvents.findLast((event) => event.event === "CONTRACT_REVISED")?.seq ?? 0;
134
+ const currentEpochSatisfiedGates = new Set(preflight.requiredGates.filter((gate) => (
135
+ hasCurrentGateEvidence(currentEvents, taskId, gate, latestContractRevisionSeq)
136
+ )));
137
+ for (const gate of preflight.requiredGates) {
138
+ if (!currentEpochSatisfiedGates.has(gate)) {
139
+ errors.push(issue(
140
+ "E_PHASE_CHRONOLOGY_INVALID",
141
+ `execution started before current-contract gate satisfaction: ${gate}`,
142
+ [ARTIFACT_PATHS.events, ARTIFACT_PATHS.preflight],
143
+ ));
144
+ }
145
+ }
132
146
  return errors;
133
147
  }
134
148
 
@@ -162,12 +162,63 @@ export function projectExecutionProfile(route) {
162
162
  return route.executionProfile?.resolved ?? LEGACY_EXECUTION_PROFILE;
163
163
  }
164
164
 
165
+ function routeProfileSignals(normalizedRoute) {
166
+ const fullReasons = [];
167
+ const balancedReasons = [];
168
+ if (FULL_WORK_TYPES.has(normalizedRoute.workType)) fullReasons.push(`WORK_${normalizedRoute.workType.toUpperCase().replaceAll("-", "_")}`);
169
+ if (BALANCED_WORK_TYPES.has(normalizedRoute.workType)) balancedReasons.push(`WORK_${normalizedRoute.workType.toUpperCase().replaceAll("-", "_")}`);
170
+ for (const surface of normalizedRoute.surfaces) {
171
+ if (FULL_SURFACES.has(surface)) fullReasons.push(`SURFACE_${surface.toUpperCase().replaceAll("-", "_")}`);
172
+ else if (BALANCED_SURFACES.has(surface)) balancedReasons.push(`SURFACE_${surface.toUpperCase().replaceAll("-", "_")}`);
173
+ }
174
+ for (const risk of normalizedRoute.risks) {
175
+ if (FULL_RISKS.has(risk)) fullReasons.push(`RISK_${risk.toUpperCase().replaceAll("-", "_")}`);
176
+ else if (BALANCED_RISKS.has(risk)) balancedReasons.push(`RISK_${risk.toUpperCase().replaceAll("-", "_")}`);
177
+ }
178
+ if (normalizedRoute.behaviorChange) balancedReasons.push("WORK_BEHAVIOR_CHANGE");
179
+ if (normalizedRoute.executableChange) balancedReasons.push("WORK_EXECUTABLE_CHANGE");
180
+ return { fullReasons, balancedReasons };
181
+ }
182
+
183
+ function contractProfileSignals(textSignals, normalizedRoute) {
184
+ const fullReasons = [];
185
+ if (textSignals.secrets) fullReasons.push("RISK_SECRETS");
186
+ if (textSignals.personalData) fullReasons.push("RISK_PERSONAL_DATA");
187
+ if (textSignals.publication) fullReasons.push("RISK_PUBLICATION");
188
+ if (textSignals.destructive) fullReasons.push("RISK_DESTRUCTIVE");
189
+ if (textSignals.migration && normalizedRoute.surfaces.includes("database")) fullReasons.push("RISK_MIGRATION");
190
+ if (textSignals.payment) fullReasons.push("RISK_PAYMENT");
191
+ if (textSignals.externalMutation) fullReasons.push("RISK_EXTERNAL_MUTATION");
192
+ if (textSignals.authoritySensitiveExternalMutation) fullReasons.push("RISK_AUTHORITY_SENSITIVE_EXTERNAL_MUTATION");
193
+ if (textSignals.broadProductionValidation) fullReasons.push("RISK_PRODUCTION_VALIDATION");
194
+ return fullReasons;
195
+ }
196
+
197
+ function scopeProfileSignals(scope) {
198
+ const balancedReasons = [];
199
+ if (scope.multiDeliverable) balancedReasons.push("SCOPE_MULTI_DELIVERABLE");
200
+ if (scope.multiClaim) balancedReasons.push("SCOPE_MULTI_CLAIM");
201
+ if (scope.broad || scope.multiSurfaceExecutableChange) balancedReasons.push("SCOPE_BROAD");
202
+ return balancedReasons;
203
+ }
204
+
205
+ function profileSignals({ normalizedRoute, contract, taskDescriptor }) {
206
+ const route = routeProfileSignals(normalizedRoute);
207
+ const scope = scopeSignals({ routeInput: normalizedRoute, contract, taskDescriptor });
208
+ return {
209
+ fullReasons: [...route.fullReasons, ...contractProfileSignals(contractSignals(contract), normalizedRoute)],
210
+ balancedReasons: [...route.balancedReasons, ...scopeProfileSignals(scope)],
211
+ scope,
212
+ };
213
+ }
214
+
165
215
  export function resolveExecutionProfile({
166
216
  routeInput = {},
167
217
  contract = null,
168
218
  taskDescriptor = null,
169
219
  configuredProfile = "auto",
170
220
  requestedProfile = null,
221
+ semanticRecommendation = null,
171
222
  } = {}) {
172
223
  const normalizedRoute = normalizedRouteInput(routeInput);
173
224
  const configured = normalizeRequestedProfile(configuredProfile, "configuredProfile");
@@ -181,43 +232,7 @@ export function resolveExecutionProfile({
181
232
  else reasons.push("PROFILE_DEFAULT_AUTO");
182
233
 
183
234
  let floor = "light";
184
- const fullReasons = [];
185
- const balancedReasons = [];
186
- const addFull = (reason) => fullReasons.push(reason);
187
- const addBalanced = (reason) => balancedReasons.push(reason);
188
-
189
- if (FULL_WORK_TYPES.has(normalizedRoute.workType)) {
190
- addFull(`WORK_${normalizedRoute.workType.toUpperCase().replaceAll("-", "_")}`);
191
- }
192
- if (BALANCED_WORK_TYPES.has(normalizedRoute.workType)) {
193
- addBalanced(`WORK_${normalizedRoute.workType.toUpperCase().replaceAll("-", "_")}`);
194
- }
195
- for (const surface of normalizedRoute.surfaces) {
196
- if (FULL_SURFACES.has(surface)) addFull(`SURFACE_${surface.toUpperCase().replaceAll("-", "_")}`);
197
- else if (BALANCED_SURFACES.has(surface)) addBalanced(`SURFACE_${surface.toUpperCase().replaceAll("-", "_")}`);
198
- }
199
- for (const risk of normalizedRoute.risks) {
200
- if (FULL_RISKS.has(risk)) addFull(`RISK_${risk.toUpperCase().replaceAll("-", "_")}`);
201
- else if (BALANCED_RISKS.has(risk)) addBalanced(`RISK_${risk.toUpperCase().replaceAll("-", "_")}`);
202
- }
203
- if (normalizedRoute.behaviorChange) addBalanced("WORK_BEHAVIOR_CHANGE");
204
- if (normalizedRoute.executableChange) addBalanced("WORK_EXECUTABLE_CHANGE");
205
-
206
- const textSignals = contractSignals(contract);
207
- if (textSignals.secrets) addFull("RISK_SECRETS");
208
- if (textSignals.personalData) addFull("RISK_PERSONAL_DATA");
209
- if (textSignals.publication) addFull("RISK_PUBLICATION");
210
- if (textSignals.destructive) addFull("RISK_DESTRUCTIVE");
211
- if (textSignals.migration && normalizedRoute.surfaces.includes("database")) addFull("RISK_MIGRATION");
212
- if (textSignals.payment) addFull("RISK_PAYMENT");
213
- if (textSignals.externalMutation) addFull("RISK_EXTERNAL_MUTATION");
214
- if (textSignals.authoritySensitiveExternalMutation) addFull("RISK_AUTHORITY_SENSITIVE_EXTERNAL_MUTATION");
215
- if (textSignals.broadProductionValidation) addFull("RISK_PRODUCTION_VALIDATION");
216
-
217
- const scope = scopeSignals({ routeInput: normalizedRoute, contract, taskDescriptor });
218
- if (scope.multiDeliverable) addBalanced("SCOPE_MULTI_DELIVERABLE");
219
- if (scope.multiClaim) addBalanced("SCOPE_MULTI_CLAIM");
220
- if (scope.broad || scope.multiSurfaceExecutableChange) addBalanced("SCOPE_BROAD");
235
+ const { fullReasons, balancedReasons, scope } = profileSignals({ normalizedRoute, contract, taskDescriptor });
221
236
 
222
237
  if (fullReasons.length > 0) {
223
238
  floor = "full";
@@ -247,7 +262,17 @@ export function resolveExecutionProfile({
247
262
  }
248
263
 
249
264
  const requestedRank = requested === "auto" ? PROFILE_RANK[floor] : PROFILE_RANK[requested];
250
- const resolved = EXECUTION_PROFILES.find((profile) => PROFILE_RANK[profile] === Math.max(requestedRank, PROFILE_RANK[floor]));
265
+ let resolved = EXECUTION_PROFILES.find((profile) => PROFILE_RANK[profile] === Math.max(requestedRank, PROFILE_RANK[floor]));
266
+ const semanticProfile = typeof semanticRecommendation === "string"
267
+ ? semanticRecommendation
268
+ : semanticRecommendation?.executionProfile?.recommendedProfile
269
+ ?? semanticRecommendation?.recommendedProfile
270
+ ?? null;
271
+ if (["light", "balanced", "full"].includes(semanticProfile)
272
+ && PROFILE_RANK[semanticProfile] > PROFILE_RANK[resolved]) {
273
+ resolved = semanticProfile;
274
+ reasons.push("PROFILE_ESCALATED_BY_JEV");
275
+ }
251
276
  const escalated = requested !== "auto" && PROFILE_RANK[requested] < PROFILE_RANK[floor];
252
277
  if (escalated) reasons.push("PROFILE_ESCALATED_BY_SAFETY");
253
278
 
@@ -129,7 +129,6 @@ export async function assertSafePath(root, relativePath) {
129
129
  }
130
130
 
131
131
  let existing = destination;
132
- let relativeExisting = path.normalize(relativePath);
133
132
  while (true) {
134
133
  try {
135
134
  const info = await lstatWithTransientWindowsRetry(existing);
@@ -137,14 +136,7 @@ export async function assertSafePath(root, relativePath) {
137
136
  throw new Error(`Path uses a symlink inside target directory: ${relativePath}`);
138
137
  }
139
138
  const resolvedRoot = await realpathWithTransientWindowsRetry(absoluteRoot);
140
- // Resolve the comparison path from the canonical root. Windows can
141
- // return different equivalent spellings (for example, short names or
142
- // extended-length prefixes) for the root and a child path when those
143
- // paths are canonicalized independently. Reconstructing the child from
144
- // the canonical root keeps the comparison in one namespace without
145
- // weakening the realpath containment check for junctions or symlinks.
146
- const canonicalExisting = path.resolve(resolvedRoot, relativeExisting);
147
- const resolvedExisting = await realpathWithTransientWindowsRetry(canonicalExisting);
139
+ const resolvedExisting = await realpathWithTransientWindowsRetry(existing);
148
140
  if (!isPathWithin(resolvedRoot, resolvedExisting)) {
149
141
  throw new Error(`Path escapes target directory: ${relativePath}`);
150
142
  }
@@ -154,7 +146,6 @@ export async function assertSafePath(root, relativePath) {
154
146
  const parent = path.dirname(existing);
155
147
  if (parent === existing) throw new Error(`Path does not resolve inside target directory: ${relativePath}`);
156
148
  existing = parent;
157
- relativeExisting = path.dirname(relativeExisting);
158
149
  }
159
150
  }
160
151
  }