ws-spec-kit 0.1.0-beta.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 (353) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +79 -0
  3. package/dist/adapters/cli/output.d.ts +15 -0
  4. package/dist/adapters/cli/output.js +21 -0
  5. package/dist/adapters/cli/output.js.map +1 -0
  6. package/dist/adapters/cli/workflow.d.ts +25 -0
  7. package/dist/adapters/cli/workflow.js +131 -0
  8. package/dist/adapters/cli/workflow.js.map +1 -0
  9. package/dist/adapters/connectors/git-native.d.ts +23 -0
  10. package/dist/adapters/connectors/git-native.js +521 -0
  11. package/dist/adapters/connectors/git-native.js.map +1 -0
  12. package/dist/adapters/connectors/github-cli.d.ts +23 -0
  13. package/dist/adapters/connectors/github-cli.js +213 -0
  14. package/dist/adapters/connectors/github-cli.js.map +1 -0
  15. package/dist/adapters/connectors/gitlab-cli.d.ts +23 -0
  16. package/dist/adapters/connectors/gitlab-cli.js +199 -0
  17. package/dist/adapters/connectors/gitlab-cli.js.map +1 -0
  18. package/dist/adapters/connectors/lark-cli.d.ts +24 -0
  19. package/dist/adapters/connectors/lark-cli.js +296 -0
  20. package/dist/adapters/connectors/lark-cli.js.map +1 -0
  21. package/dist/adapters/process/redaction.d.ts +2 -0
  22. package/dist/adapters/process/redaction.js +51 -0
  23. package/dist/adapters/process/redaction.js.map +1 -0
  24. package/dist/adapters/process/spawn-json.d.ts +37 -0
  25. package/dist/adapters/process/spawn-json.js +312 -0
  26. package/dist/adapters/process/spawn-json.js.map +1 -0
  27. package/dist/adapters/skills/claude.d.ts +1 -0
  28. package/dist/adapters/skills/claude.js +3 -0
  29. package/dist/adapters/skills/claude.js.map +1 -0
  30. package/dist/adapters/skills/codex.d.ts +1 -0
  31. package/dist/adapters/skills/codex.js +3 -0
  32. package/dist/adapters/skills/codex.js.map +1 -0
  33. package/dist/adapters/skills/cursor.d.ts +1 -0
  34. package/dist/adapters/skills/cursor.js +3 -0
  35. package/dist/adapters/skills/cursor.js.map +1 -0
  36. package/dist/adapters/skills/generic.d.ts +1 -0
  37. package/dist/adapters/skills/generic.js +2 -0
  38. package/dist/adapters/skills/generic.js.map +1 -0
  39. package/dist/adapters/skills/install.d.ts +28 -0
  40. package/dist/adapters/skills/install.js +571 -0
  41. package/dist/adapters/skills/install.js.map +1 -0
  42. package/dist/application/acquire.d.ts +60 -0
  43. package/dist/application/acquire.js +964 -0
  44. package/dist/application/acquire.js.map +1 -0
  45. package/dist/application/application.d.ts +21 -0
  46. package/dist/application/application.js +31 -0
  47. package/dist/application/application.js.map +1 -0
  48. package/dist/application/artifact-reader.d.ts +1 -0
  49. package/dist/application/artifact-reader.js +150 -0
  50. package/dist/application/artifact-reader.js.map +1 -0
  51. package/dist/application/artifact-writer.d.ts +1 -0
  52. package/dist/application/artifact-writer.js +268 -0
  53. package/dist/application/artifact-writer.js.map +1 -0
  54. package/dist/application/artifact.d.ts +28 -0
  55. package/dist/application/artifact.js +523 -0
  56. package/dist/application/artifact.js.map +1 -0
  57. package/dist/application/decide.d.ts +12 -0
  58. package/dist/application/decide.js +168 -0
  59. package/dist/application/decide.js.map +1 -0
  60. package/dist/application/doctor-connectors.d.ts +34 -0
  61. package/dist/application/doctor-connectors.js +99 -0
  62. package/dist/application/doctor-connectors.js.map +1 -0
  63. package/dist/application/external-action.d.ts +164 -0
  64. package/dist/application/external-action.js +670 -0
  65. package/dist/application/external-action.js.map +1 -0
  66. package/dist/application/initial-stages.d.ts +3 -0
  67. package/dist/application/initial-stages.js +7 -0
  68. package/dist/application/initial-stages.js.map +1 -0
  69. package/dist/application/inspect.d.ts +2 -0
  70. package/dist/application/inspect.js +31 -0
  71. package/dist/application/inspect.js.map +1 -0
  72. package/dist/application/profile.d.ts +32 -0
  73. package/dist/application/profile.js +70 -0
  74. package/dist/application/profile.js.map +1 -0
  75. package/dist/application/snapshot.d.ts +75 -0
  76. package/dist/application/snapshot.js +338 -0
  77. package/dist/application/snapshot.js.map +1 -0
  78. package/dist/application/start.d.ts +43 -0
  79. package/dist/application/start.js +360 -0
  80. package/dist/application/start.js.map +1 -0
  81. package/dist/application/state.d.ts +17 -0
  82. package/dist/application/state.js +94 -0
  83. package/dist/application/state.js.map +1 -0
  84. package/dist/application/submit.d.ts +18 -0
  85. package/dist/application/submit.js +1090 -0
  86. package/dist/application/submit.js.map +1 -0
  87. package/dist/cli/commands/core.d.ts +2 -0
  88. package/dist/cli/commands/core.js +205 -0
  89. package/dist/cli/commands/core.js.map +1 -0
  90. package/dist/cli/commands/public-contract.d.ts +12 -0
  91. package/dist/cli/commands/public-contract.js +47 -0
  92. package/dist/cli/commands/public-contract.js.map +1 -0
  93. package/dist/cli/main.d.ts +2 -0
  94. package/dist/cli/main.js +23 -0
  95. package/dist/cli/main.js.map +1 -0
  96. package/dist/domain/artifacts.d.ts +52 -0
  97. package/dist/domain/artifacts.js +241 -0
  98. package/dist/domain/artifacts.js.map +1 -0
  99. package/dist/domain/digests.d.ts +11 -0
  100. package/dist/domain/digests.js +106 -0
  101. package/dist/domain/digests.js.map +1 -0
  102. package/dist/domain/external-receipt.d.ts +48 -0
  103. package/dist/domain/external-receipt.js +138 -0
  104. package/dist/domain/external-receipt.js.map +1 -0
  105. package/dist/domain/ids.d.ts +7 -0
  106. package/dist/domain/ids.js +5 -0
  107. package/dist/domain/ids.js.map +1 -0
  108. package/dist/domain/repository-path.d.ts +3 -0
  109. package/dist/domain/repository-path.js +87 -0
  110. package/dist/domain/repository-path.js.map +1 -0
  111. package/dist/domain/states.d.ts +38 -0
  112. package/dist/domain/states.js +69 -0
  113. package/dist/domain/states.js.map +1 -0
  114. package/dist/domain/work-package-identity.d.ts +2 -0
  115. package/dist/domain/work-package-identity.js +10 -0
  116. package/dist/domain/work-package-identity.js.map +1 -0
  117. package/dist/domain/workflow.d.ts +58 -0
  118. package/dist/domain/workflow.js +2 -0
  119. package/dist/domain/workflow.js.map +1 -0
  120. package/dist/engine/actor-roles.d.ts +15 -0
  121. package/dist/engine/actor-roles.js +53 -0
  122. package/dist/engine/actor-roles.js.map +1 -0
  123. package/dist/engine/approvals.d.ts +44 -0
  124. package/dist/engine/approvals.js +239 -0
  125. package/dist/engine/approvals.js.map +1 -0
  126. package/dist/engine/archive.d.ts +24 -0
  127. package/dist/engine/archive.js +442 -0
  128. package/dist/engine/archive.js.map +1 -0
  129. package/dist/engine/compiler.d.ts +25 -0
  130. package/dist/engine/compiler.js +828 -0
  131. package/dist/engine/compiler.js.map +1 -0
  132. package/dist/engine/control/condition.d.ts +10 -0
  133. package/dist/engine/control/condition.js +49 -0
  134. package/dist/engine/control/condition.js.map +1 -0
  135. package/dist/engine/control/loop.d.ts +23 -0
  136. package/dist/engine/control/loop.js +96 -0
  137. package/dist/engine/control/loop.js.map +1 -0
  138. package/dist/engine/control/retry.d.ts +15 -0
  139. package/dist/engine/control/retry.js +54 -0
  140. package/dist/engine/control/retry.js.map +1 -0
  141. package/dist/engine/docs-integrity.d.ts +13 -0
  142. package/dist/engine/docs-integrity.js +34 -0
  143. package/dist/engine/docs-integrity.js.map +1 -0
  144. package/dist/engine/expressions/ast.d.ts +18 -0
  145. package/dist/engine/expressions/ast.js +9 -0
  146. package/dist/engine/expressions/ast.js.map +1 -0
  147. package/dist/engine/expressions/evaluate.d.ts +7 -0
  148. package/dist/engine/expressions/evaluate.js +47 -0
  149. package/dist/engine/expressions/evaluate.js.map +1 -0
  150. package/dist/engine/expressions/parser.d.ts +2 -0
  151. package/dist/engine/expressions/parser.js +173 -0
  152. package/dist/engine/expressions/parser.js.map +1 -0
  153. package/dist/engine/external-effects/authorization.d.ts +146 -0
  154. package/dist/engine/external-effects/authorization.js +222 -0
  155. package/dist/engine/external-effects/authorization.js.map +1 -0
  156. package/dist/engine/external-effects/idempotency.d.ts +14 -0
  157. package/dist/engine/external-effects/idempotency.js +26 -0
  158. package/dist/engine/external-effects/idempotency.js.map +1 -0
  159. package/dist/engine/external-effects/reconciliation.d.ts +11 -0
  160. package/dist/engine/external-effects/reconciliation.js +18 -0
  161. package/dist/engine/external-effects/reconciliation.js.map +1 -0
  162. package/dist/engine/results.d.ts +14 -0
  163. package/dist/engine/results.js +88 -0
  164. package/dist/engine/results.js.map +1 -0
  165. package/dist/engine/scheduler.d.ts +60 -0
  166. package/dist/engine/scheduler.js +225 -0
  167. package/dist/engine/scheduler.js.map +1 -0
  168. package/dist/engine/tdd/green-gate.d.ts +6 -0
  169. package/dist/engine/tdd/green-gate.js +72 -0
  170. package/dist/engine/tdd/green-gate.js.map +1 -0
  171. package/dist/engine/tdd/red-gate.d.ts +30 -0
  172. package/dist/engine/tdd/red-gate.js +531 -0
  173. package/dist/engine/tdd/red-gate.js.map +1 -0
  174. package/dist/engine/tdd/types.d.ts +89 -0
  175. package/dist/engine/tdd/types.js +38 -0
  176. package/dist/engine/tdd/types.js.map +1 -0
  177. package/dist/engine/verification.d.ts +56 -0
  178. package/dist/engine/verification.js +196 -0
  179. package/dist/engine/verification.js.map +1 -0
  180. package/dist/policy/profile.d.ts +24 -0
  181. package/dist/policy/profile.js +23 -0
  182. package/dist/policy/profile.js.map +1 -0
  183. package/dist/policy/risk.d.ts +33 -0
  184. package/dist/policy/risk.js +138 -0
  185. package/dist/policy/risk.js.map +1 -0
  186. package/dist/protocol/application.d.ts +196 -0
  187. package/dist/protocol/application.js +2 -0
  188. package/dist/protocol/application.js.map +1 -0
  189. package/dist/protocol/public-contract.d.ts +81 -0
  190. package/dist/protocol/public-contract.js +231 -0
  191. package/dist/protocol/public-contract.js.map +1 -0
  192. package/dist/protocol/work-package.d.ts +55 -0
  193. package/dist/protocol/work-package.js +2 -0
  194. package/dist/protocol/work-package.js.map +1 -0
  195. package/dist/registry/connectors/external-executor.d.ts +4 -0
  196. package/dist/registry/connectors/external-executor.js +249 -0
  197. package/dist/registry/connectors/external-executor.js.map +1 -0
  198. package/dist/registry/connectors/feishu-document.d.ts +21 -0
  199. package/dist/registry/connectors/feishu-document.js +197 -0
  200. package/dist/registry/connectors/feishu-document.js.map +1 -0
  201. package/dist/registry/connectors/git-commit.d.ts +32 -0
  202. package/dist/registry/connectors/git-commit.js +122 -0
  203. package/dist/registry/connectors/git-commit.js.map +1 -0
  204. package/dist/registry/connectors/issue.d.ts +68 -0
  205. package/dist/registry/connectors/issue.js +301 -0
  206. package/dist/registry/connectors/issue.js.map +1 -0
  207. package/dist/registry/connectors/knowledge-publish.d.ts +30 -0
  208. package/dist/registry/connectors/knowledge-publish.js +157 -0
  209. package/dist/registry/connectors/knowledge-publish.js.map +1 -0
  210. package/dist/registry/connectors/local-requirement.d.ts +19 -0
  211. package/dist/registry/connectors/local-requirement.js +177 -0
  212. package/dist/registry/connectors/local-requirement.js.map +1 -0
  213. package/dist/registry/connectors/manifest.d.ts +6 -0
  214. package/dist/registry/connectors/manifest.js +164 -0
  215. package/dist/registry/connectors/manifest.js.map +1 -0
  216. package/dist/registry/connectors/registry.d.ts +11 -0
  217. package/dist/registry/connectors/registry.js +34 -0
  218. package/dist/registry/connectors/registry.js.map +1 -0
  219. package/dist/registry/connectors/requirement-source.d.ts +46 -0
  220. package/dist/registry/connectors/requirement-source.js +513 -0
  221. package/dist/registry/connectors/requirement-source.js.map +1 -0
  222. package/dist/registry/connectors/runtime.d.ts +38 -0
  223. package/dist/registry/connectors/runtime.js +127 -0
  224. package/dist/registry/connectors/runtime.js.map +1 -0
  225. package/dist/registry/connectors/secret-detector.d.ts +19 -0
  226. package/dist/registry/connectors/secret-detector.js +85 -0
  227. package/dist/registry/connectors/secret-detector.js.map +1 -0
  228. package/dist/registry/connectors/semver.d.ts +8 -0
  229. package/dist/registry/connectors/semver.js +57 -0
  230. package/dist/registry/connectors/semver.js.map +1 -0
  231. package/dist/registry/connectors/types.d.ts +45 -0
  232. package/dist/registry/connectors/types.js +2 -0
  233. package/dist/registry/connectors/types.js.map +1 -0
  234. package/dist/registry/executors/registry.d.ts +15 -0
  235. package/dist/registry/executors/registry.js +68 -0
  236. package/dist/registry/executors/registry.js.map +1 -0
  237. package/dist/registry/executors/types.d.ts +19 -0
  238. package/dist/registry/executors/types.js +2 -0
  239. package/dist/registry/executors/types.js.map +1 -0
  240. package/dist/registry/skills/lock.d.ts +3 -0
  241. package/dist/registry/skills/lock.js +278 -0
  242. package/dist/registry/skills/lock.js.map +1 -0
  243. package/dist/registry/skills/resolver.d.ts +16 -0
  244. package/dist/registry/skills/resolver.js +353 -0
  245. package/dist/registry/skills/resolver.js.map +1 -0
  246. package/dist/registry/skills/types.d.ts +75 -0
  247. package/dist/registry/skills/types.js +9 -0
  248. package/dist/registry/skills/types.js.map +1 -0
  249. package/dist/resources/catalog.d.ts +20 -0
  250. package/dist/resources/catalog.js +111 -0
  251. package/dist/resources/catalog.js.map +1 -0
  252. package/dist/schemas/definitions.d.ts +5 -0
  253. package/dist/schemas/definitions.js +958 -0
  254. package/dist/schemas/definitions.js.map +1 -0
  255. package/dist/schemas/generate.d.ts +1 -0
  256. package/dist/schemas/generate.js +26 -0
  257. package/dist/schemas/generate.js.map +1 -0
  258. package/dist/schemas/index.d.ts +11 -0
  259. package/dist/schemas/index.js +54 -0
  260. package/dist/schemas/index.js.map +1 -0
  261. package/dist/storage/control-plane.d.ts +136 -0
  262. package/dist/storage/control-plane.js +688 -0
  263. package/dist/storage/control-plane.js.map +1 -0
  264. package/dist/storage/events.d.ts +36 -0
  265. package/dist/storage/events.js +185 -0
  266. package/dist/storage/events.js.map +1 -0
  267. package/dist/storage/external-action-payload.d.ts +33 -0
  268. package/dist/storage/external-action-payload.js +190 -0
  269. package/dist/storage/external-action-payload.js.map +1 -0
  270. package/dist/storage/files.d.ts +13 -0
  271. package/dist/storage/files.js +45 -0
  272. package/dist/storage/files.js.map +1 -0
  273. package/dist/storage/git.d.ts +4 -0
  274. package/dist/storage/git.js +21 -0
  275. package/dist/storage/git.js.map +1 -0
  276. package/dist/storage/project-config.d.ts +11 -0
  277. package/dist/storage/project-config.js +60 -0
  278. package/dist/storage/project-config.js.map +1 -0
  279. package/dist/storage/repository.d.ts +14 -0
  280. package/dist/storage/repository.js +151 -0
  281. package/dist/storage/repository.js.map +1 -0
  282. package/dist/storage/work-items.d.ts +77 -0
  283. package/dist/storage/work-items.js +373 -0
  284. package/dist/storage/work-items.js.map +1 -0
  285. package/dist/storage/workflow-trust.d.ts +38 -0
  286. package/dist/storage/workflow-trust.js +310 -0
  287. package/dist/storage/workflow-trust.js.map +1 -0
  288. package/dist/workflow-package/loader.d.ts +12 -0
  289. package/dist/workflow-package/loader.js +249 -0
  290. package/dist/workflow-package/loader.js.map +1 -0
  291. package/dist/workflow-package/lock.d.ts +6 -0
  292. package/dist/workflow-package/lock.js +12 -0
  293. package/dist/workflow-package/lock.js.map +1 -0
  294. package/dist/workflow-package/path-boundary.d.ts +5 -0
  295. package/dist/workflow-package/path-boundary.js +38 -0
  296. package/dist/workflow-package/path-boundary.js.map +1 -0
  297. package/dist/workflow-package/trust.d.ts +27 -0
  298. package/dist/workflow-package/trust.js +61 -0
  299. package/dist/workflow-package/trust.js.map +1 -0
  300. package/dist/workflow-package/types.d.ts +176 -0
  301. package/dist/workflow-package/types.js +9 -0
  302. package/dist/workflow-package/types.js.map +1 -0
  303. package/dist/workflow-package/workflow-v1.d.ts +3 -0
  304. package/dist/workflow-package/workflow-v1.js +274 -0
  305. package/dist/workflow-package/workflow-v1.js.map +1 -0
  306. package/package.json +40 -0
  307. package/resources/catalog.yaml +37 -0
  308. package/resources/connectors/github-cli.yaml +24 -0
  309. package/resources/connectors/gitlab-cli.yaml +24 -0
  310. package/resources/connectors/lark-cli.yaml +28 -0
  311. package/resources/skills/bug-fixing/SKILL.md +16 -0
  312. package/resources/skills/code-review/SKILL.md +16 -0
  313. package/resources/skills/documentation-editing/SKILL.md +16 -0
  314. package/resources/skills/documentation-exploration/SKILL.md +16 -0
  315. package/resources/skills/documentation-review/SKILL.md +16 -0
  316. package/resources/skills/requirement-exploration/SKILL.md +16 -0
  317. package/resources/skills/solution-design/SKILL.md +16 -0
  318. package/resources/skills/task-planning/SKILL.md +16 -0
  319. package/resources/skills/tdd-implementation/SKILL.md +16 -0
  320. package/resources/workflows/documentation-delivery/manifest.yaml +8 -0
  321. package/resources/workflows/documentation-delivery/profiles/governed.yaml +23 -0
  322. package/resources/workflows/documentation-delivery/profiles/quick.yaml +23 -0
  323. package/resources/workflows/documentation-delivery/profiles/standard.yaml +21 -0
  324. package/resources/workflows/documentation-delivery/workflow.yaml +109 -0
  325. package/resources/workflows/feature-delivery/manifest.yaml +8 -0
  326. package/resources/workflows/feature-delivery/profiles/governed.yaml +27 -0
  327. package/resources/workflows/feature-delivery/profiles/quick.yaml +25 -0
  328. package/resources/workflows/feature-delivery/profiles/standard.yaml +26 -0
  329. package/resources/workflows/feature-delivery/workflow.yaml +150 -0
  330. package/schemas/builtin-agent-action-v1.schema.json +509 -0
  331. package/schemas/builtin-application-acquire-input-v1.schema.json +25 -0
  332. package/schemas/builtin-application-artifact-create-input-v1.schema.json +49 -0
  333. package/schemas/builtin-application-decision-input-v1.schema.json +284 -0
  334. package/schemas/builtin-application-inspect-input-v1.schema.json +20 -0
  335. package/schemas/builtin-application-project-config-snapshot-v1.schema.json +289 -0
  336. package/schemas/builtin-application-project-config-v1.schema.json +293 -0
  337. package/schemas/builtin-application-start-input-v1.schema.json +92 -0
  338. package/schemas/builtin-application-submit-input-v1.schema.json +145 -0
  339. package/schemas/builtin-artifact-v1.schema.json +48 -0
  340. package/schemas/builtin-evidence-v1.schema.json +65 -0
  341. package/schemas/builtin-external-action-grant-v1.schema.json +298 -0
  342. package/schemas/builtin-external-action-request-v1.schema.json +288 -0
  343. package/schemas/builtin-external-binding-v1.schema.json +59 -0
  344. package/schemas/builtin-external-receipt-v1.schema.json +109 -0
  345. package/schemas/builtin-external-write-receipt-v1.schema.json +253 -0
  346. package/schemas/builtin-source-artifact-v1.schema.json +293 -0
  347. package/schemas/builtin-submit-result-v1.schema.json +111 -0
  348. package/schemas/builtin-tdd-cycle-evidence-v1.schema.json +119 -0
  349. package/schemas/builtin-tdd-node-test-report-v1.schema.json +103 -0
  350. package/schemas/builtin-tdd-trusted-evidence-v1.schema.json +184 -0
  351. package/schemas/builtin-work-item-v1.schema.json +201 -0
  352. package/schemas/builtin-work-package-v1.schema.json +254 -0
  353. package/schemas/builtin-workflow-selection-v1.schema.json +40 -0
@@ -0,0 +1,964 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { computeArtifactTreeDigest, computeWorkspaceSnapshot, computeWorkspaceTreeDigest, sha256 } from "../domain/digests.js";
3
+ import { transitionStage, transitionWorkItem } from "../domain/states.js";
4
+ import { revalidateGlobalSkillLock } from "../registry/skills/resolver.js";
5
+ import { validate } from "../schemas/index.js";
6
+ import { applicationCloseEvidenceKey, recoverControlPlane } from "../storage/control-plane.js";
7
+ import { mutateControlPlane } from "../engine/scheduler.js";
8
+ import { closeChecklistForWorktree } from "../engine/archive.js";
9
+ import { conditionScope, evaluateCondition } from "../engine/control/condition.js";
10
+ import { advanceLoop, blockLoop, loopLimitProblem, loopStepInstanceId, startLoop, succeedLoop, } from "../engine/control/loop.js";
11
+ import { implementationActors } from "../engine/actor-roles.js";
12
+ import { acquireRetry, interruptedRetry, isRetryableStepFailure, isStepFailureCode, retryExhaustedProblem, retryLimit, stepFailureProblem, } from "../engine/control/retry.js";
13
+ import { rebindAdditionalGlobalRoots } from "../storage/project-config.js";
14
+ import { assertImplementHasTrustedRed, fixedTestGateForState, tddRedEvidenceKey } from "../engine/verification.js";
15
+ import { loadApplicationState, selectedProfile } from "./state.js";
16
+ import { externalActionApprovalSummary, externalActionRejectionKey } from "./external-action.js";
17
+ import { readEvents } from "../storage/events.js";
18
+ import { workPackageIdentityDigest } from "../domain/work-package-identity.js";
19
+ export class ApplicationAcquireError extends Error {
20
+ code;
21
+ constructor(code, message) {
22
+ super(`${code}: ${message}`);
23
+ this.code = code;
24
+ this.name = "ApplicationAcquireError";
25
+ }
26
+ }
27
+ function attemptRecord(value) {
28
+ if (value === null || typeof value !== "object" || Array.isArray(value))
29
+ return undefined;
30
+ const record = value;
31
+ return record.workPackage === undefined ? undefined : record;
32
+ }
33
+ function skippedRecord(value) {
34
+ if (value === null || typeof value !== "object" || Array.isArray(value))
35
+ return undefined;
36
+ const record = value;
37
+ return record.skipped === true && typeof record.stepInstanceId === "string"
38
+ ? record
39
+ : undefined;
40
+ }
41
+ function ownProjection(values, key) {
42
+ return Object.hasOwn(values, key) ? values[key] : undefined;
43
+ }
44
+ function objectRecord(value) {
45
+ return value !== null && typeof value === "object" && !Array.isArray(value)
46
+ ? value
47
+ : undefined;
48
+ }
49
+ function sameStrings(left, right) {
50
+ return left.length === right.length && left.every((value, index) => value === right[index]);
51
+ }
52
+ function workspaceSnapshotDigest(snapshot) {
53
+ const entries = snapshot.map((entry) => {
54
+ if (entry.type === "file")
55
+ return { path: entry.path, type: entry.type, mode: entry.mode, digest: entry.digest };
56
+ if (entry.type === "symlink")
57
+ return { path: entry.path, type: entry.type, mode: entry.mode, target: entry.target };
58
+ return { path: entry.path, type: entry.type, mode: entry.mode };
59
+ });
60
+ return sha256(`${JSON.stringify({ version: 1, entries })}\n`);
61
+ }
62
+ function claimedSnapshotStep(input) {
63
+ const root = input.profile.steps.find(({ id }) => id === input.stageId);
64
+ if (root === undefined)
65
+ return undefined;
66
+ if (input.claim.stageId === input.stageId)
67
+ return root;
68
+ const loop = ownProjection(input.projection.loops, input.stageId);
69
+ if (loop === undefined || root.uses !== "control.loop")
70
+ return undefined;
71
+ return root.steps.find((step) => loopStepInstanceId(root.id, loop.iteration, step.id) === input.claim.stageId);
72
+ }
73
+ async function authoritativeActiveBinding(input) {
74
+ const events = await readEvents(input.projection.controlPlane);
75
+ const tip = events.at(-1);
76
+ if (tip === undefined || input.projection.lastSequence !== tip.sequence || input.projection.lastEventHash !== tip.eventHash) {
77
+ throw new Error("runtime projection is not anchored to the event tip");
78
+ }
79
+ for (let index = events.length - 1; index >= 0; index -= 1) {
80
+ const result = objectRecord(events[index]?.result);
81
+ const projection = objectRecord(result?.projection);
82
+ if (projection === undefined)
83
+ continue;
84
+ const claims = objectRecord(projection?.claims);
85
+ const contexts = objectRecord(projection?.contexts);
86
+ if (claims === undefined || contexts === undefined || !Object.hasOwn(claims, input.stageId)
87
+ || !Object.hasOwn(contexts, input.stageId)) {
88
+ throw new Error("latest authoritative projection does not contain the active binding");
89
+ }
90
+ return { claim: claims[input.stageId], context: contexts[input.stageId] };
91
+ }
92
+ throw new Error("active Claim has no authoritative event projection");
93
+ }
94
+ async function activeClaimContext(input) {
95
+ try {
96
+ const rawContext = input.projection.contexts[input.stageId];
97
+ const context = attemptRecord(rawContext);
98
+ const rawClaim = input.claim;
99
+ const authoritative = await authoritativeActiveBinding(input);
100
+ if (!isDeepStrictEqual(rawClaim, authoritative.claim) || !isDeepStrictEqual(rawContext, authoritative.context)) {
101
+ throw new Error("active binding differs from the event projection");
102
+ }
103
+ if (context === undefined || typeof context.stepInstanceId !== "string"
104
+ || typeof rawClaim.stageId !== "string" || typeof rawClaim.attemptId !== "string"
105
+ || typeof rawClaim.claimToken !== "string" || typeof rawClaim.actor !== "string"
106
+ || typeof rawClaim.claimedAt !== "string" || typeof rawClaim.expiresAt !== "string"
107
+ || typeof rawClaim.inputWorkspaceTreeDigest !== "string" || !Array.isArray(rawClaim.allowedPaths)
108
+ || !rawClaim.allowedPaths.every((value) => typeof value === "string") || !Array.isArray(rawClaim.workspaceSnapshot)) {
109
+ throw new Error("active Claim or Context shape is invalid");
110
+ }
111
+ const workPackage = validate("builtin.work-package.v1", context.workPackage);
112
+ const step = claimedSnapshotStep(input);
113
+ const stageStatus = input.projection.stages[input.stageId]?.status;
114
+ const claimedAt = Date.parse(rawClaim.claimedAt);
115
+ const expiresAt = Date.parse(rawClaim.expiresAt);
116
+ const valid = step !== undefined
117
+ && (stageStatus === "claimed" || stageStatus === "awaiting_approval")
118
+ && rawClaim.stageId !== ""
119
+ && rawClaim.attemptId !== ""
120
+ && context.stepInstanceId === rawClaim.stageId
121
+ && Number.isFinite(claimedAt) && claimedAt <= input.now.getTime()
122
+ && Number.isFinite(expiresAt) && expiresAt > input.now.getTime()
123
+ && rawClaim.inputWorkspaceTreeDigest === workspaceSnapshotDigest(rawClaim.workspaceSnapshot)
124
+ && sameStrings(rawClaim.allowedPaths, input.state.snapshot.changePolicy.allowedPaths)
125
+ && workPackage.workItemId === input.state.item.workItemId
126
+ && workPackage.stepId === rawClaim.stageId
127
+ && workPackage.attemptId === rawClaim.attemptId
128
+ && workPackage.lease.token === rawClaim.claimToken
129
+ && workPackage.lease.expiresAt === rawClaim.expiresAt
130
+ && rawClaim.workPackageDigest === workPackageIdentityDigest(workPackage)
131
+ && sameStrings(workPackage.constraints.allowedPaths, rawClaim.allowedPaths);
132
+ if (!valid || step === undefined)
133
+ throw new Error("active Claim binding is invalid");
134
+ return { context: { ...context, workPackage }, step };
135
+ }
136
+ catch {
137
+ throw new ApplicationAcquireError("WSSPEC_ACTIVE_CLAIM_INVALID", "活动 Claim 与当前 Application、Attempt 或 Work Package 绑定不一致。");
138
+ }
139
+ }
140
+ async function reacquireActiveClaim(input) {
141
+ const { context, step } = input.active;
142
+ const additionalGlobalRoots = await rebindAdditionalGlobalRoots({
143
+ root: input.root,
144
+ rootIds: input.state.snapshot.skillResolution.additionalGlobalRootIds,
145
+ });
146
+ await revalidateGlobalSkillLock({
147
+ lock: input.state.snapshot.skillLock,
148
+ provider: input.state.snapshot.skillResolution.provider,
149
+ projectRoot: input.state.worktree,
150
+ home: input.dependencies.home,
151
+ additionalGlobalRoots,
152
+ });
153
+ const token = crypto.randomUUID();
154
+ const expiresAt = new Date(input.now.getTime() + input.state.snapshot.leaseTtlSeconds * 1000).toISOString();
155
+ const workPackage = {
156
+ ...context.workPackage,
157
+ lease: { token, expiresAt },
158
+ };
159
+ const claim = {
160
+ ...input.claim,
161
+ claimToken: token,
162
+ claimedAt: input.now.toISOString(),
163
+ expiresAt,
164
+ workPackageDigest: workPackageIdentityDigest(workPackage),
165
+ };
166
+ input.dependencies.executors.assertStep(step);
167
+ const projection = {
168
+ ...input.projection,
169
+ claims: { ...input.projection.claims, [input.stageId]: claim },
170
+ contexts: {
171
+ ...input.projection.contexts,
172
+ [input.stageId]: { ...context, workPackage },
173
+ },
174
+ };
175
+ return {
176
+ projection,
177
+ action: { action: "execute", workPackage },
178
+ reacquired: {
179
+ stageId: input.claim.stageId,
180
+ attemptId: input.claim.attemptId,
181
+ previousLeaseDigest: sha256(input.claim.claimToken),
182
+ leaseDigest: sha256(token),
183
+ },
184
+ };
185
+ }
186
+ function completedResults(projection, stageId) {
187
+ const direct = attemptRecord(projection.contexts[stageId])?.result;
188
+ const loop = ownProjection(projection.loops, stageId);
189
+ const nested = loop === undefined
190
+ ? []
191
+ : Object.entries(projection.contexts)
192
+ .filter(([key]) => key.startsWith(`${stageId}:${loop.iteration}:`))
193
+ .map(([, value]) => attemptRecord(value)?.result)
194
+ .filter((result) => result !== undefined);
195
+ return [...(direct === undefined ? [] : [direct]), ...nested];
196
+ }
197
+ function promoteReady(projection, profile) {
198
+ let next = { ...projection, stages: { ...projection.stages } };
199
+ const skippedStepIds = [];
200
+ let changed = true;
201
+ while (changed) {
202
+ changed = false;
203
+ for (const step of profile.steps) {
204
+ const state = next.stages[step.id];
205
+ if (state?.status !== "pending")
206
+ continue;
207
+ const dependenciesComplete = step.needs.every((dependency) => {
208
+ const status = next.stages[dependency]?.status;
209
+ return status === "succeeded" || status === "succeeded_with_warnings" || status === "skipped";
210
+ });
211
+ if (dependenciesComplete) {
212
+ const skipped = !step.enabled || !evaluateCondition(step.when, conditionScope(next));
213
+ next.stages[step.id] = transitionStage(state, { type: "transition", to: skipped ? "skipped" : "ready" });
214
+ if (step.enabled && skipped)
215
+ skippedStepIds.push(step.id);
216
+ changed = true;
217
+ }
218
+ }
219
+ }
220
+ return { projection: next, skippedStepIds };
221
+ }
222
+ function pendingApproval(projection, workItemId) {
223
+ const approval = Object.values(projection.approvals).find((candidate) => candidate.status === "pending");
224
+ if (approval === undefined)
225
+ return undefined;
226
+ return {
227
+ action: "await_approval",
228
+ approval: {
229
+ kind: "step",
230
+ requestId: approval.requestId,
231
+ workItemId: workItemId,
232
+ title: `审批 ${approval.stageId}`,
233
+ digest: approval.contentHash,
234
+ },
235
+ };
236
+ }
237
+ function pendingExternalAction(projection, workItemId, now) {
238
+ const candidates = Object.values(projection.externalActions)
239
+ .sort((left, right) => left.request.createdAt.localeCompare(right.request.createdAt));
240
+ for (const candidate of candidates) {
241
+ const rejection = projection.evidence[externalActionRejectionKey(candidate.request.requestId)];
242
+ if (rejection !== undefined) {
243
+ if (rejection.requestId !== candidate.request.requestId || rejection.requestDigest !== candidate.request.requestDigest
244
+ || rejection.actor === "" || !Number.isFinite(Date.parse(rejection.rejectedAt))) {
245
+ throw new ApplicationAcquireError("WSSPEC_EXTERNAL_REJECTION_INVALID", "外部动作拒绝证据未绑定当前请求。");
246
+ }
247
+ return { action: "blocked", problems: [{ code: "WSSPEC_EXTERNAL_ACTION_REJECTED", message: "外部动作未获授权。", retryable: false }] };
248
+ }
249
+ const claim = projection.claims[candidate.request.stepId];
250
+ const context = attemptRecord(projection.contexts[candidate.request.stepId]);
251
+ const active = claim?.stageId === candidate.request.stepId
252
+ && claim.attemptId === candidate.request.attemptId
253
+ && new Date(claim.expiresAt) > now
254
+ && context?.workPackage.stepId === candidate.request.stepId
255
+ && context.workPackage.attemptId === candidate.request.attemptId
256
+ && context.workPackage.lease.token === claim.claimToken;
257
+ if (candidate.status === "prepared") {
258
+ if (!active) {
259
+ return { action: "blocked", problems: [{ code: "WSSPEC_EXTERNAL_ATTEMPT_MISMATCH", message: "外部动作审批绑定的 Attempt/Lease 已失效。", retryable: false }] };
260
+ }
261
+ const summary = externalActionApprovalSummary(candidate.request);
262
+ return {
263
+ action: "await_approval",
264
+ approval: {
265
+ kind: "external_action",
266
+ requestId: candidate.request.requestId,
267
+ workItemId: workItemId,
268
+ title: `${summary.provider} ${summary.action} ${summary.target.stableId}`,
269
+ digest: candidate.request.requestDigest,
270
+ provider: summary.provider,
271
+ action: summary.action,
272
+ target: summary.target,
273
+ sideEffects: summary.sideEffects,
274
+ },
275
+ };
276
+ }
277
+ if (candidate.status === "reconciliation_required"
278
+ || (candidate.status === "executing" && candidate.dispatch === "sent_or_unknown")) {
279
+ return { action: "blocked", problems: [{ code: "WSSPEC_EXTERNAL_RECONCILIATION_REQUIRED", message: "外部动作结果未知,必须先完成只读协调回查。", retryable: false }] };
280
+ }
281
+ if (candidate.status === "failed") {
282
+ if (projection.evidence[`external-warning:${candidate.request.requestId}`] !== undefined)
283
+ continue;
284
+ return { action: "blocked", problems: [{ code: "WSSPEC_EXTERNAL_RECONCILIATION_FAILED", message: candidate.reason, retryable: false }] };
285
+ }
286
+ if (candidate.status === "approved" || candidate.status === "executing" || candidate.status === "verified") {
287
+ if (!active) {
288
+ if (candidate.status === "verified")
289
+ continue;
290
+ return { action: "blocked", problems: [{ code: "WSSPEC_EXTERNAL_ATTEMPT_MISMATCH", message: "外部动作 Grant 绑定的 Attempt/Lease 已失效。", retryable: false }] };
291
+ }
292
+ return { action: "execute", workPackage: context.workPackage };
293
+ }
294
+ }
295
+ return undefined;
296
+ }
297
+ function settleOptionalKnowledgeFailures(projection, profile) {
298
+ if (profile.publishing.knowledgeRequired)
299
+ return projection;
300
+ let next = projection;
301
+ for (const candidate of Object.values(projection.externalActions)) {
302
+ if (candidate.status !== "failed" || candidate.request.action !== "knowledge.publish")
303
+ continue;
304
+ const warningKey = `external-warning:${candidate.request.requestId}`;
305
+ if (next.evidence[warningKey] !== undefined)
306
+ continue;
307
+ const stage = next.stages[candidate.request.stepId];
308
+ const claim = next.claims[candidate.request.stepId];
309
+ const context = attemptRecord(next.contexts[candidate.request.stepId]);
310
+ if (stage?.status !== "claimed" || claim?.attemptId !== candidate.request.attemptId
311
+ || context?.workPackage.attemptId !== candidate.request.attemptId) {
312
+ throw new ApplicationAcquireError("WSSPEC_EXTERNAL_ATTEMPT_MISMATCH", "可选 Knowledge 失败与活动 Attempt 投影不一致。");
313
+ }
314
+ const running = transitionStage(stage, { type: "transition", to: "running" });
315
+ const validating = transitionStage(running, { type: "transition", to: "validating" });
316
+ const claims = { ...next.claims };
317
+ const retries = { ...next.retries };
318
+ delete claims[candidate.request.stepId];
319
+ delete retries[candidate.request.stepId];
320
+ const result = {
321
+ version: 1,
322
+ status: "completed",
323
+ summary: "可选 Knowledge 发布未确认,已记录 warning。",
324
+ modifiedFiles: [],
325
+ artifacts: [],
326
+ commands: [],
327
+ evidence: [],
328
+ externalWrites: [],
329
+ remainingRisks: [{ code: "WSSPEC_OPTIONAL_KNOWLEDGE_FAILED", reason: candidate.reason }],
330
+ };
331
+ next = {
332
+ ...next,
333
+ stages: { ...next.stages, [candidate.request.stepId]: transitionStage(validating, { type: "transition", to: "succeeded_with_warnings" }) },
334
+ claims,
335
+ contexts: { ...next.contexts, [candidate.request.stepId]: { ...context, result } },
336
+ retries,
337
+ evidence: {
338
+ ...next.evidence,
339
+ [warningKey]: { code: "WSSPEC_OPTIONAL_KNOWLEDGE_FAILED", reason: candidate.reason },
340
+ },
341
+ };
342
+ }
343
+ return next;
344
+ }
345
+ function dependencyClosure(stepId, profile) {
346
+ const byId = new Map(profile.steps.map((step) => [step.id, step]));
347
+ const result = new Set();
348
+ const visit = (candidate) => {
349
+ for (const dependency of byId.get(candidate)?.needs ?? []) {
350
+ if (result.has(dependency))
351
+ continue;
352
+ result.add(dependency);
353
+ visit(dependency);
354
+ }
355
+ };
356
+ visit(stepId);
357
+ return result;
358
+ }
359
+ function inputArtifacts(input) {
360
+ const ancestors = dependencyClosure(input.step.id, input.profile);
361
+ const order = new Map(input.profile.order.map((stepId, index) => [stepId, index]));
362
+ const selected = [];
363
+ const seen = new Set();
364
+ for (const requirement of input.step.inputs) {
365
+ if (seen.has(requirement.outputId))
366
+ continue;
367
+ seen.add(requirement.outputId);
368
+ if (requirement.outputId === "red-evidence") {
369
+ const red = input.projection.evidence[tddRedEvidenceKey(input.projection.workItemId)];
370
+ if (red?.level === "trusted" && red.phase === "red" && red.taskId === input.projection.workItemId)
371
+ continue;
372
+ }
373
+ if (requirement.outputId === "red-test-result" && input.step.id === "verify-red") {
374
+ const writeTests = input.projection.contexts["write-tests"];
375
+ const modifiedFiles = writeTests?.result?.modifiedFiles;
376
+ if (Array.isArray(modifiedFiles) && modifiedFiles.length > 0 && modifiedFiles.every((filename) => typeof filename === "string"))
377
+ continue;
378
+ }
379
+ let artifact;
380
+ if (requirement.outputId === "requirement-source") {
381
+ artifact = input.snapshot.source;
382
+ }
383
+ else {
384
+ const candidates = [...ancestors].flatMap((stepId) => {
385
+ const stage = input.projection.stages[stepId];
386
+ if (stage?.status !== "succeeded" && stage?.status !== "succeeded_with_warnings")
387
+ return [];
388
+ return completedResults(input.projection, stepId).flatMap((result) => result.status !== "completed" ? [] : result.artifacts
389
+ .map((candidate, artifactIndex) => ({ candidate, artifactIndex, stepIndex: order.get(stepId) ?? -1 }))
390
+ .filter(({ candidate }) => candidate.outputId === requirement.outputId));
391
+ });
392
+ candidates.sort((left, right) => (right.candidate.revision ?? 0) - (left.candidate.revision ?? 0)
393
+ || right.stepIndex - left.stepIndex
394
+ || right.artifactIndex - left.artifactIndex);
395
+ artifact = candidates[0]?.candidate;
396
+ }
397
+ if (artifact !== undefined)
398
+ selected.push(artifact);
399
+ else if (requirement.required) {
400
+ throw new ApplicationAcquireError("WSSPEC_REQUIRED_INPUT_ARTIFACT_MISSING", `步骤 ${input.step.id} 缺少必需输入 Artifact output ${requirement.outputId}。`);
401
+ }
402
+ }
403
+ return selected;
404
+ }
405
+ function workPackageFor(input) {
406
+ const gatesById = new Map(input.snapshot.gates.map((gate) => [gate.id, gate]));
407
+ const requiredOutputs = input.step.outputs.filter((output) => output.required).map((output) => {
408
+ return {
409
+ artifactType: output.artifact,
410
+ ...(output.artifact === "requirement-source" ? {} : { outputId: output.outputId }),
411
+ schemaVersion: 1,
412
+ ...(output.contentLevel === undefined ? {} : { contentLevel: output.contentLevel }),
413
+ };
414
+ });
415
+ const value = {
416
+ version: 1,
417
+ workItemId: input.workItemId,
418
+ stepId: input.stepInstanceId ?? input.step.id,
419
+ attemptId: input.attemptId,
420
+ lease: { token: input.token, expiresAt: input.expiresAt },
421
+ objective: input.step.objective ?? `${input.step.uses}${input.step.action === undefined ? "" : `/${input.step.action}`}`,
422
+ ...(input.step.artifactLevel === undefined ? {} : { artifactLevel: input.step.artifactLevel }),
423
+ skills: input.step.skills.map((skill) => ({ ...skill })),
424
+ artifacts: input.artifacts ?? inputArtifacts(input),
425
+ constraints: {
426
+ allowedPaths: [...input.snapshot.changePolicy.allowedPaths],
427
+ forbiddenActions: ["push", "merge", "release", "unapproved-external-write"],
428
+ },
429
+ requiredOutputs,
430
+ artifactAuthoring: {
431
+ version: 1,
432
+ maxContentBytes: 1_048_576,
433
+ draftRoots: [".acceptance", `.wsspec/work-items/${input.workItemId}/drafts`],
434
+ },
435
+ gates: input.step.gates.map((id) => ({ id, evidence: gatesById.get(id)?.evidence ?? "trusted", required: true })),
436
+ resultSchema: "builtin.submit-result.v1",
437
+ };
438
+ return validate("builtin.work-package.v1", value);
439
+ }
440
+ function completed(workItemId, status, message) {
441
+ return { action: "completed", summary: { workItemId: workItemId, status, message } };
442
+ }
443
+ function loopScope(projection, loopId, iteration, steps) {
444
+ const iterationPrefix = `${loopId}:${iteration}:`;
445
+ const loopPrefix = `${loopId}:`;
446
+ const contextKeys = Object.keys(projection.contexts).filter((key) => {
447
+ return key !== loopId && (!key.startsWith(loopPrefix) || key.startsWith(iterationPrefix));
448
+ });
449
+ const statuses = {};
450
+ for (const step of steps) {
451
+ const stepInstanceId = loopStepInstanceId(loopId, iteration, step.id);
452
+ const attempt = attemptRecord(projection.contexts[stepInstanceId]);
453
+ if (attempt?.result?.status === "completed")
454
+ statuses[step.id] = { status: "succeeded" };
455
+ else if (attempt?.result?.status === "failed")
456
+ statuses[step.id] = { status: "failed" };
457
+ else if (skippedRecord(projection.contexts[stepInstanceId]) !== undefined)
458
+ statuses[step.id] = { status: "skipped" };
459
+ }
460
+ return conditionScope(projection, { contextKeys, steps: statuses });
461
+ }
462
+ function loopInputArtifacts(input) {
463
+ const artifacts = new Map(inputArtifacts({
464
+ step: input.loop,
465
+ profile: input.profile,
466
+ projection: input.projection,
467
+ snapshot: input.snapshot,
468
+ }).map((artifact) => [artifact.artifactType, artifact]));
469
+ for (const child of input.loop.steps) {
470
+ const context = attemptRecord(input.projection.contexts[loopStepInstanceId(input.loop.id, input.iteration, child.id)]);
471
+ if (context?.result?.status !== "completed")
472
+ continue;
473
+ for (const artifact of context.result.artifacts) {
474
+ const current = artifacts.get(artifact.artifactType);
475
+ if ((artifact.revision ?? 0) >= (current?.revision ?? 0))
476
+ artifacts.set(artifact.artifactType, artifact);
477
+ }
478
+ }
479
+ return [...artifacts.values()];
480
+ }
481
+ function loopDependenciesComplete(projection, loopId, iteration, step) {
482
+ return step.needs.every((dependency) => {
483
+ const context = projection.contexts[loopStepInstanceId(loopId, iteration, dependency)];
484
+ return attemptRecord(context)?.result?.status === "completed" || skippedRecord(context) !== undefined;
485
+ });
486
+ }
487
+ function exhaustedRetryInstanceId(projection, stageId) {
488
+ const direct = attemptRecord(projection.contexts[stageId])?.stepInstanceId ?? stageId;
489
+ if (ownProjection(projection.retries, direct)?.status === "exhausted")
490
+ return direct;
491
+ const loop = ownProjection(projection.loops, stageId);
492
+ if (loop?.status !== "running")
493
+ return undefined;
494
+ const prefix = `${stageId}:${loop.iteration}:`;
495
+ return Object.keys(projection.retries)
496
+ .filter((stepInstanceId) => stepInstanceId.startsWith(prefix))
497
+ .sort((left, right) => left.localeCompare(right))
498
+ .find((stepInstanceId) => ownProjection(projection.retries, stepInstanceId)?.status === "exhausted");
499
+ }
500
+ function permanentFailureProblem(projection, stageId) {
501
+ const result = attemptRecord(projection.contexts[stageId])?.result;
502
+ return result?.status === "failed"
503
+ && isStepFailureCode(result.failureCode)
504
+ && !isRetryableStepFailure(result.failureCode)
505
+ ? stepFailureProblem(result.failureCode, result.summary)
506
+ : undefined;
507
+ }
508
+ async function acquireExecutableStep(input) {
509
+ const maxAttempts = retryLimit(input.step.retry?.maxAttempts, input.state.snapshot.maxStageRetries);
510
+ const retry = acquireRetry(ownProjection(input.projection.retries, input.stepInstanceId), input.stepInstanceId, maxAttempts);
511
+ const attemptId = `attempt-${crypto.randomUUID()}`;
512
+ const token = crypto.randomUUID();
513
+ const expiresAt = new Date(input.now.getTime() + input.state.snapshot.leaseTtlSeconds * 1000).toISOString();
514
+ const workPackage = workPackageFor({
515
+ workItemId: input.state.item.workItemId,
516
+ step: input.step,
517
+ attemptId,
518
+ token,
519
+ expiresAt,
520
+ projection: input.projection,
521
+ snapshot: input.state.snapshot,
522
+ profile: input.profile,
523
+ stepInstanceId: input.stepInstanceId,
524
+ ...(input.artifacts === undefined ? {} : { artifacts: input.artifacts }),
525
+ });
526
+ const claim = {
527
+ stageId: input.stepInstanceId,
528
+ attemptId,
529
+ claimToken: token,
530
+ actor: input.actor,
531
+ claimedAt: input.now.toISOString(),
532
+ expiresAt,
533
+ inputWorkspaceTreeDigest: await computeWorkspaceTreeDigest(input.state.worktree),
534
+ allowedPaths: [...input.state.snapshot.changePolicy.allowedPaths],
535
+ workspaceSnapshot: await computeWorkspaceSnapshot(input.state.worktree),
536
+ workPackageDigest: workPackageIdentityDigest(workPackage),
537
+ };
538
+ let projection = {
539
+ ...input.projection,
540
+ stages: {
541
+ ...input.projection.stages,
542
+ [input.stageId]: transitionStage(input.projection.stages[input.stageId], { type: "transition", to: "claimed" }),
543
+ },
544
+ claims: { ...input.projection.claims, [input.stageId]: claim },
545
+ contexts: {
546
+ ...input.projection.contexts,
547
+ [input.stageId]: {
548
+ workPackage,
549
+ retryCount: retry.attemptsUsed - 1,
550
+ stepInstanceId: input.stepInstanceId,
551
+ },
552
+ },
553
+ retries: { ...input.projection.retries, [input.stepInstanceId]: retry },
554
+ };
555
+ const executorStep = { ...input.step, id: input.stageId };
556
+ const executor = input.dependencies.executors.assertStep(executorStep);
557
+ const action = await executor.acquire(executorStep, projection);
558
+ return { projection, action };
559
+ }
560
+ async function acquireLoopStep(input) {
561
+ if (input.step.until === undefined || input.step.maxIterations === undefined || input.step.steps.length === 0) {
562
+ throw new ApplicationAcquireError("WSSPEC_LOOP_CONFIGURATION_INVALID", `循环 ${input.step.id} 缺少 until、maxIterations 或内部 Step。`);
563
+ }
564
+ let projection = {
565
+ ...input.projection,
566
+ loops: { ...input.projection.loops },
567
+ retries: { ...input.projection.retries },
568
+ contexts: { ...input.projection.contexts },
569
+ stages: { ...input.projection.stages },
570
+ };
571
+ let loop = ownProjection(projection.loops, input.step.id) ?? startLoop(input.step.id, input.step.maxIterations);
572
+ if (loop.maxIterations !== input.step.maxIterations || loop.loopId !== input.step.id) {
573
+ throw new ApplicationAcquireError("WSSPEC_LOOP_PROJECTION_INVALID", `循环 ${input.step.id} 的投影与 Application 快照不一致。`);
574
+ }
575
+ projection.loops[input.step.id] = loop;
576
+ const skippedStepIds = [...input.skippedStepIds];
577
+ while (true) {
578
+ const scope = loopScope(projection, loop.loopId, loop.iteration, input.step.steps);
579
+ const iterationPrefix = `${loop.loopId}:${loop.iteration}:`;
580
+ const hasCompletedStep = Object.entries(projection.contexts).some(([key, value]) => {
581
+ return key.startsWith(iterationPrefix)
582
+ && (attemptRecord(value)?.result?.status === "completed" || skippedRecord(value) !== undefined);
583
+ });
584
+ if (hasCompletedStep && evaluateCondition(input.step.until, scope)) {
585
+ loop = succeedLoop(loop);
586
+ let stage = transitionStage(projection.stages[input.step.id], { type: "transition", to: "running" });
587
+ stage = transitionStage(stage, { type: "transition", to: "validating" });
588
+ stage = transitionStage(stage, { type: "transition", to: "succeeded" });
589
+ projection = {
590
+ ...projection,
591
+ loops: { ...projection.loops, [loop.loopId]: loop },
592
+ stages: { ...projection.stages, [input.step.id]: stage },
593
+ };
594
+ return acquireNextLocked({
595
+ state: input.state,
596
+ projection,
597
+ actor: input.actor,
598
+ root: input.root,
599
+ dependencies: input.dependencies,
600
+ });
601
+ }
602
+ let selected;
603
+ let skippedThisPass = false;
604
+ for (const child of input.step.steps) {
605
+ const stepInstanceId = loopStepInstanceId(loop.loopId, loop.iteration, child.id);
606
+ const attempt = attemptRecord(projection.contexts[stepInstanceId]);
607
+ if (attempt?.result?.status === "completed" || skippedRecord(projection.contexts[stepInstanceId]) !== undefined)
608
+ continue;
609
+ if (!loopDependenciesComplete(projection, loop.loopId, loop.iteration, child))
610
+ continue;
611
+ if (attempt?.result?.status === "failed") {
612
+ const retry = ownProjection(projection.retries, stepInstanceId);
613
+ if (retry?.status === "exhausted") {
614
+ return { projection, action: { action: "blocked", problems: [retryExhaustedProblem(stepInstanceId)] }, skippedStepIds };
615
+ }
616
+ selected = { step: child, stepInstanceId };
617
+ break;
618
+ }
619
+ if (!evaluateCondition(child.when, scope)) {
620
+ projection.contexts[stepInstanceId] = { stepInstanceId, skipped: true };
621
+ skippedStepIds.push(stepInstanceId);
622
+ skippedThisPass = true;
623
+ continue;
624
+ }
625
+ selected = { step: child, stepInstanceId };
626
+ break;
627
+ }
628
+ if (selected !== undefined) {
629
+ if (input.step.independentReviewActor === true && selected.step.actorRole === "review") {
630
+ const actors = implementationActors({ profile: input.profile, projection: input.projection, loopId: loop.loopId, iteration: loop.iteration });
631
+ if (actors === undefined || actors.has(input.actor)) {
632
+ return {
633
+ projection,
634
+ action: {
635
+ action: "blocked",
636
+ problems: [{
637
+ code: "WSSPEC_INDEPENDENT_REVIEW_REQUIRED",
638
+ message: "Governed Review 必须由不同于实现者的独立 Actor 执行。",
639
+ retryable: true,
640
+ }],
641
+ },
642
+ skippedStepIds,
643
+ };
644
+ }
645
+ }
646
+ const acquired = await acquireExecutableStep({
647
+ state: input.state,
648
+ projection,
649
+ actor: input.actor,
650
+ stageId: input.step.id,
651
+ stepInstanceId: selected.stepInstanceId,
652
+ step: selected.step,
653
+ profile: input.profile,
654
+ root: input.root,
655
+ dependencies: input.dependencies,
656
+ now: input.now,
657
+ artifacts: loopInputArtifacts({
658
+ loop: input.step,
659
+ iteration: loop.iteration,
660
+ profile: input.profile,
661
+ projection,
662
+ snapshot: input.state.snapshot,
663
+ }),
664
+ });
665
+ return { projection: acquired.projection, action: acquired.action, skippedStepIds };
666
+ }
667
+ if (skippedThisPass)
668
+ continue;
669
+ if (loop.iteration >= loop.maxIterations) {
670
+ loop = blockLoop(loop);
671
+ let stage = transitionStage(projection.stages[input.step.id], { type: "transition", to: "running" });
672
+ stage = transitionStage(stage, { type: "transition", to: "failed" });
673
+ projection = {
674
+ ...projection,
675
+ loops: { ...projection.loops, [loop.loopId]: loop },
676
+ stages: { ...projection.stages, [input.step.id]: stage },
677
+ };
678
+ return { projection, action: { action: "blocked", problems: [loopLimitProblem(loop)] }, skippedStepIds };
679
+ }
680
+ loop = advanceLoop(loop);
681
+ projection.loops[loop.loopId] = loop;
682
+ }
683
+ }
684
+ export async function acquireNextLocked(input) {
685
+ const { state, actor, dependencies } = input;
686
+ let projection = {
687
+ ...input.projection,
688
+ stages: { ...input.projection.stages },
689
+ claims: { ...input.projection.claims },
690
+ contexts: { ...input.projection.contexts },
691
+ loops: { ...input.projection.loops },
692
+ retries: { ...input.projection.retries },
693
+ };
694
+ if (projection.workItem.status === "closed" || projection.workItem.status === "cancelled") {
695
+ return { projection, action: completed(state.item.workItemId, projection.workItem.status, "Workflow 已结束。"), skippedStepIds: [] };
696
+ }
697
+ const profile = selectedProfile(state.snapshot);
698
+ const now = dependencies.now();
699
+ const activeClaims = new Map();
700
+ for (const [stepId, claim] of Object.entries(projection.claims)) {
701
+ if (new Date(claim.expiresAt) > now) {
702
+ activeClaims.set(stepId, await activeClaimContext({ state, projection, profile, stageId: stepId, claim, now }));
703
+ }
704
+ }
705
+ projection = settleOptionalKnowledgeFailures(projection, profile);
706
+ const externalAction = pendingExternalAction(projection, state.item.workItemId, now);
707
+ if (externalAction !== undefined)
708
+ return { projection, action: externalAction, skippedStepIds: [] };
709
+ const approval = pendingApproval(projection, state.item.workItemId);
710
+ if (approval !== undefined)
711
+ return { projection, action: approval, skippedStepIds: [] };
712
+ for (const [stepId, claim] of Object.entries(projection.claims)) {
713
+ if (new Date(claim.expiresAt) > now) {
714
+ const active = activeClaims.get(stepId);
715
+ if (active === undefined || projection.stages[stepId]?.status !== "claimed") {
716
+ throw new ApplicationAcquireError("WSSPEC_ACTIVE_CLAIM_INVALID", "活动 Claim 与当前 Application、Attempt 或 Work Package 绑定不一致。");
717
+ }
718
+ if (claim.actor === actor) {
719
+ const reacquired = await reacquireActiveClaim({
720
+ state,
721
+ projection,
722
+ profile,
723
+ actor,
724
+ stageId: stepId,
725
+ claim,
726
+ root: input.root,
727
+ dependencies,
728
+ now,
729
+ active,
730
+ });
731
+ return { ...reacquired, skippedStepIds: [] };
732
+ }
733
+ return {
734
+ projection,
735
+ action: { action: "blocked", problems: [{ code: "WSSPEC_STAGE_ALREADY_CLAIMED", message: `步骤 ${claim.stageId} 已有活动 Lease。`, retryable: true }] },
736
+ skippedStepIds: [],
737
+ };
738
+ }
739
+ const retry = ownProjection(projection.retries, claim.stageId);
740
+ if (retry !== undefined)
741
+ projection.retries[claim.stageId] = interruptedRetry(retry);
742
+ const current = projection.stages[stepId];
743
+ if (current?.status === "claimed") {
744
+ projection.stages[stepId] = ownProjection(projection.retries, claim.stageId)?.status === "exhausted"
745
+ ? transitionStage(transitionStage(current, { type: "transition", to: "running" }), { type: "transition", to: "failed" })
746
+ : transitionStage(current, { type: "transition", to: "ready" });
747
+ }
748
+ delete projection.claims[stepId];
749
+ delete projection.contexts[stepId];
750
+ }
751
+ for (const [stepId, stateValue] of Object.entries(projection.stages)) {
752
+ if (stateValue.status === "revision_required") {
753
+ projection.stages[stepId] = transitionStage(stateValue, { type: "transition", to: "ready" });
754
+ }
755
+ else if (stateValue.status === "failed") {
756
+ const stepInstanceId = attemptRecord(projection.contexts[stepId])?.stepInstanceId ?? stepId;
757
+ if (ownProjection(projection.retries, stepInstanceId)?.status === "ready") {
758
+ const retrying = transitionStage(stateValue, { type: "transition", to: "retrying" });
759
+ projection.stages[stepId] = transitionStage(retrying, { type: "transition", to: "ready" });
760
+ }
761
+ }
762
+ }
763
+ const promoted = promoteReady(projection, profile);
764
+ projection = promoted.projection;
765
+ const step = profile.steps.find((candidate) => projection.stages[candidate.id]?.status === "ready");
766
+ if (step === undefined) {
767
+ const blocked = profile.steps.map((candidate) => {
768
+ if (projection.stages[candidate.id]?.status !== "failed")
769
+ return false;
770
+ const loop = ownProjection(projection.loops, candidate.id);
771
+ if (loop?.status === "blocked")
772
+ return { step: candidate, problem: loopLimitProblem(loop) };
773
+ const stepInstanceId = exhaustedRetryInstanceId(projection, candidate.id);
774
+ if (stepInstanceId !== undefined)
775
+ return { step: candidate, problem: retryExhaustedProblem(stepInstanceId) };
776
+ const problem = permanentFailureProblem(projection, candidate.id);
777
+ return problem === undefined ? false : { step: candidate, problem };
778
+ }).find((candidate) => candidate !== false);
779
+ if (blocked !== undefined) {
780
+ return {
781
+ projection,
782
+ action: { action: "blocked", problems: [blocked.problem] },
783
+ skippedStepIds: promoted.skippedStepIds,
784
+ };
785
+ }
786
+ const unfinished = Object.values(projection.stages).some(({ status }) => !["succeeded", "succeeded_with_warnings", "skipped", "cancelled"].includes(status));
787
+ return {
788
+ projection,
789
+ action: unfinished
790
+ ? { action: "blocked", problems: [{ code: "WSSPEC_WORKFLOW_BLOCKED", message: "没有可执行的步骤。", retryable: true }] }
791
+ : completed(state.item.workItemId, "closed", "Workflow 已完成。"),
792
+ skippedStepIds: promoted.skippedStepIds,
793
+ };
794
+ }
795
+ if (step.id === "implement") {
796
+ const gate = await fixedTestGateForState(state);
797
+ await assertImplementHasTrustedRed({
798
+ taskId: state.item.workItemId,
799
+ commandId: gate.commandId,
800
+ gate,
801
+ worktree: state.worktree,
802
+ redEvidence: projection.evidence[tddRedEvidenceKey(state.item.workItemId)],
803
+ requireWorkspaceMatch: true,
804
+ });
805
+ }
806
+ const additionalGlobalRoots = await rebindAdditionalGlobalRoots({
807
+ root: input.root,
808
+ rootIds: state.snapshot.skillResolution.additionalGlobalRootIds,
809
+ });
810
+ await revalidateGlobalSkillLock({
811
+ lock: state.snapshot.skillLock,
812
+ provider: state.snapshot.skillResolution.provider,
813
+ projectRoot: state.worktree,
814
+ home: dependencies.home,
815
+ additionalGlobalRoots,
816
+ });
817
+ dependencies.executors.assertStep(step);
818
+ if (step.uses === "control.loop") {
819
+ return acquireLoopStep({
820
+ state,
821
+ projection,
822
+ actor,
823
+ root: input.root,
824
+ dependencies,
825
+ profile,
826
+ step,
827
+ now,
828
+ skippedStepIds: promoted.skippedStepIds,
829
+ });
830
+ }
831
+ if (step.uses === "control.close") {
832
+ const [workspaceTreeDigest, artifactTreeDigest] = await Promise.all([
833
+ computeWorkspaceTreeDigest(state.worktree),
834
+ computeArtifactTreeDigest(state.worktree),
835
+ ]);
836
+ const decision = await closeChecklistForWorktree({
837
+ profile,
838
+ projection,
839
+ gatePolicy: state.snapshot.gatePolicy,
840
+ gates: state.snapshot.gates,
841
+ workspaceTreeDigest,
842
+ configDigest: state.item.execution.configDigest,
843
+ worktree: state.worktree,
844
+ source: state.snapshot.source,
845
+ });
846
+ if (!decision.allowed) {
847
+ let workItem = transitionWorkItem(projection.workItem, { type: "transition", to: "verifying" });
848
+ workItem = transitionWorkItem(workItem, { type: "transition", to: "blocked" });
849
+ projection = {
850
+ ...projection,
851
+ workItem,
852
+ evidence: {
853
+ ...projection.evidence,
854
+ "application.close-checklist": {
855
+ checkedAt: now.toISOString(),
856
+ workspaceTreeDigest,
857
+ decision,
858
+ },
859
+ },
860
+ };
861
+ const missing = decision.missing.map(({ category, id }) => `${category}:${id}`).join(", ");
862
+ return {
863
+ projection,
864
+ action: {
865
+ action: "blocked",
866
+ problems: [{
867
+ code: "WSSPEC_CLOSE_CHECKLIST_INCOMPLETE",
868
+ message: `Close checklist 未满足:${missing}`,
869
+ retryable: true,
870
+ }],
871
+ },
872
+ skippedStepIds: promoted.skippedStepIds,
873
+ closeDecision: decision,
874
+ };
875
+ }
876
+ let stage = transitionStage(projection.stages[step.id], { type: "transition", to: "running" });
877
+ stage = transitionStage(stage, { type: "transition", to: "validating" });
878
+ stage = transitionStage(stage, { type: "transition", to: "succeeded" });
879
+ let workItem = transitionWorkItem(projection.workItem, { type: "transition", to: "verifying" });
880
+ workItem = transitionWorkItem(workItem, { type: "transition", to: "verified" });
881
+ workItem = transitionWorkItem(workItem, { type: "transition", to: "closed" });
882
+ const applicationClose = {
883
+ closedAt: now.toISOString(),
884
+ workspaceTreeDigest,
885
+ artifactTreeDigest,
886
+ };
887
+ projection = {
888
+ ...projection,
889
+ stages: { ...projection.stages, [step.id]: stage },
890
+ workItem,
891
+ evidence: { ...projection.evidence, [applicationCloseEvidenceKey]: applicationClose },
892
+ readOnly: true,
893
+ };
894
+ return {
895
+ projection,
896
+ action: completed(state.item.workItemId, "closed", "Workflow 已完成。"),
897
+ skippedStepIds: promoted.skippedStepIds,
898
+ closeDecision: decision,
899
+ };
900
+ }
901
+ const acquired = await acquireExecutableStep({
902
+ state,
903
+ projection,
904
+ actor,
905
+ stageId: step.id,
906
+ stepInstanceId: step.id,
907
+ step,
908
+ profile,
909
+ root: input.root,
910
+ dependencies,
911
+ now,
912
+ });
913
+ return { projection: acquired.projection, action: acquired.action, skippedStepIds: promoted.skippedStepIds };
914
+ }
915
+ export async function acquireApplication(input, dependencies) {
916
+ validate("builtin.application-acquire-input.v1", input);
917
+ const state = await loadApplicationState(input.root, input.workItemId);
918
+ if (state.projection.workItem.status === "closed" || state.projection.workItem.status === "cancelled") {
919
+ return completed(state.item.workItemId, state.projection.workItem.status, "Workflow 已结束。");
920
+ }
921
+ const action = await mutateControlPlane({
922
+ cwd: input.root,
923
+ workItemId: input.workItemId,
924
+ eventType: (value) => {
925
+ if (value.action.action === "completed" && value.action.summary.status === "closed")
926
+ return "work-item.closed";
927
+ if (value.closeDecision !== undefined)
928
+ return "evidence.recorded";
929
+ if (value.reacquired !== undefined)
930
+ return "attempt.reacquired";
931
+ return value.skippedStepIds.length > 0 ? "step.skipped" : "attempt.acquired";
932
+ },
933
+ stageId: (value) => value.reacquired?.stageId ?? value.skippedStepIds[0],
934
+ attemptId: (value) => value.reacquired?.attemptId,
935
+ idempotencyKey: `acquire:${crypto.randomUUID()}`,
936
+ actor: input.actor,
937
+ operationInput: { actor: input.actor, at: dependencies.now().toISOString() },
938
+ eventDetails: (value) => ({
939
+ ...(value.skippedStepIds.length === 0 ? {} : { skippedStepIds: value.skippedStepIds }),
940
+ ...(value.closeDecision === undefined ? {} : { closeDecision: value.closeDecision }),
941
+ ...(value.reacquired === undefined ? {} : {
942
+ previousLeaseDigest: value.reacquired.previousLeaseDigest,
943
+ leaseDigest: value.reacquired.leaseDigest,
944
+ }),
945
+ }),
946
+ mutate: async (projection) => {
947
+ const acquired = await acquireNextLocked({ state, projection, actor: input.actor, root: input.root, dependencies });
948
+ return {
949
+ projection: acquired.projection,
950
+ value: {
951
+ action: acquired.action,
952
+ skippedStepIds: acquired.skippedStepIds,
953
+ ...(acquired.closeDecision === undefined ? {} : { closeDecision: acquired.closeDecision }),
954
+ ...(acquired.reacquired === undefined ? {} : { reacquired: acquired.reacquired }),
955
+ },
956
+ };
957
+ },
958
+ });
959
+ if (action.action.action === "completed" && action.action.summary.status === "closed") {
960
+ await recoverControlPlane({ cwd: input.root, workItemId: input.workItemId });
961
+ }
962
+ return action.action;
963
+ }
964
+ //# sourceMappingURL=acquire.js.map