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,1090 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { artifactOutputId, computeArtifactContentHash, readArtifact, verifyArtifact } from "../domain/artifacts.js";
4
+ import { createExternalBinding, externalPublishTarget } from "../domain/external-receipt.js";
5
+ import { computeWorkspaceSnapshot, computeWorkspaceTreeDigest, sha256 } from "../domain/digests.js";
6
+ import { matchesRepositoryPath, resolveRepositoryRegularFile } from "../domain/repository-path.js";
7
+ import { transitionStage, transitionWorkItem } from "../domain/states.js";
8
+ import { deriveInitialStages } from "./initial-stages.js";
9
+ import { applyProfileDecision } from "./profile.js";
10
+ import { prepareArtifactApproval } from "../engine/approvals.js";
11
+ import { parseLoopStepInstanceId, projectArtifactValues } from "../engine/control/loop.js";
12
+ import { failRetry, isRetryableStepFailure, isStepFailureCode, retryFailureProblem, stepFailureProblem, } from "../engine/control/retry.js";
13
+ import { mutateControlPlane, readIdempotentControlPlaneResult } from "../engine/scheduler.js";
14
+ import { evaluateSubmitProfileDecision } from "../engine/results.js";
15
+ import { validateGitCommitApproval } from "../registry/connectors/git-commit.js";
16
+ import { canonicalRequirementText } from "../registry/connectors/local-requirement.js";
17
+ import { validateKnowledgePublishTarget } from "../registry/connectors/knowledge-publish.js";
18
+ import { validateIssueWriteAction } from "../registry/connectors/issue.js";
19
+ import { validate } from "../schemas/index.js";
20
+ import { recoverControlPlane } from "../storage/control-plane.js";
21
+ import { readEvents } from "../storage/events.js";
22
+ import { recordGreenEvidenceDetails } from "../engine/tdd/green-gate.js";
23
+ import { recordRedEvidence } from "../engine/tdd/red-gate.js";
24
+ import { isTddVerificationCode, VerificationError } from "../engine/tdd/types.js";
25
+ import { assertImplementHasTrustedRed, evaluateReviewFixEvidence, evidenceProjectionKey, evidenceRecordHash, fixedTestGateForState, tddCycleEvidenceKey, tddGreenEvidenceKey, tddRedEvidenceKey, } from "../engine/verification.js";
26
+ import { acquireNextLocked } from "./acquire.js";
27
+ import { loadApplicationState, selectedProfile } from "./state.js";
28
+ import { assertCurrentExternalActionContext, executeExternalAction, ExternalActionError, externalActionApprovalSummary, externalActionRejectionKey, prepareExternalAction, } from "./external-action.js";
29
+ import { canonicalDigest } from "../engine/external-effects/idempotency.js";
30
+ import { evaluateExternalDelivery } from "../engine/external-effects/reconciliation.js";
31
+ export class ApplicationSubmitError extends Error {
32
+ code;
33
+ constructor(code, message) {
34
+ super(`${code}: ${message}`);
35
+ this.code = code;
36
+ this.name = "ApplicationSubmitError";
37
+ }
38
+ }
39
+ function ownProjection(values, key) {
40
+ return Object.hasOwn(values, key) ? values[key] : undefined;
41
+ }
42
+ function record(value) {
43
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
44
+ }
45
+ function externalWriteIntent(result) {
46
+ if (result.externalWrites.length !== 1) {
47
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_INTENT_INVALID", "external-write Step 必须精确提交一个外部动作意图。 ");
48
+ }
49
+ const value = record(result.externalWrites[0]);
50
+ const target = record(value?.target);
51
+ const allowed = ["kind", "provider", "action", "target", "payload", "sideEffects"];
52
+ if (value?.kind !== "external-action" || Object.keys(value).some((key) => !allowed.includes(key))
53
+ || typeof value.provider !== "string" || !/^[a-z][a-z0-9-]{0,62}$/u.test(value.provider)
54
+ || !["git.commit", "issue.update", "knowledge.publish", "issue.close"].includes(value.action)
55
+ || target === undefined || Object.keys(target).some((key) => !["kind", "stableId"].includes(key))
56
+ || !["repository", "issue", "knowledge"].includes(target.kind) || typeof target.stableId !== "string" || target.stableId === ""
57
+ || !Array.isArray(value.sideEffects) || value.sideEffects.length === 0
58
+ || value.sideEffects.some((item) => typeof item !== "string" || item === "") || value.payload === undefined) {
59
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_INTENT_INVALID", "外部动作意图字段不完整或包含未知字段。 ");
60
+ }
61
+ return {
62
+ kind: "external-action",
63
+ provider: value.provider,
64
+ action: value.action,
65
+ target: { kind: target.kind, stableId: target.stableId },
66
+ payload: value.payload,
67
+ sideEffects: [...value.sideEffects],
68
+ };
69
+ }
70
+ function governedActionStep(step) {
71
+ return step.securityClass === "external-write"
72
+ || (step.securityClass === "local-write" && step.action === "git.commit");
73
+ }
74
+ function validatedGitApproval(intent, worktree) {
75
+ let approval;
76
+ try {
77
+ approval = validateGitCommitApproval(intent.payload);
78
+ }
79
+ catch {
80
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_BINDING_INVALID", "Git commit approval 无效。");
81
+ }
82
+ if (intent.provider !== "git-native" || intent.action !== "git.commit" || intent.target.kind !== "repository"
83
+ || intent.target.stableId !== approval.repositoryCommonDir || approval.repositoryRoot !== worktree
84
+ || canonicalDigest(approval) !== canonicalDigest(intent.payload)) {
85
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_BINDING_INVALID", "Git commit approval 未精确绑定当前 Work Item 仓库。");
86
+ }
87
+ return approval;
88
+ }
89
+ function externalBlocked(code, message, retryable = false) {
90
+ return { action: "blocked", problems: [{ code, message, retryable }] };
91
+ }
92
+ function externalApprovalAction(workItemId, state) {
93
+ const summary = externalActionApprovalSummary(state.request);
94
+ return {
95
+ action: "await_approval",
96
+ approval: {
97
+ kind: "external_action",
98
+ requestId: state.request.requestId,
99
+ workItemId: workItemId,
100
+ title: `${summary.provider} ${summary.action} ${summary.target.stableId}`,
101
+ digest: state.request.requestDigest,
102
+ provider: summary.provider,
103
+ action: summary.action,
104
+ target: summary.target,
105
+ sideEffects: summary.sideEffects,
106
+ },
107
+ };
108
+ }
109
+ function executionTarget(profile, projection, stepInstanceId) {
110
+ const topLevel = profile.steps.find((candidate) => candidate.id === stepInstanceId);
111
+ if (topLevel !== undefined)
112
+ return { stageId: topLevel.id, stepInstanceId, step: topLevel, internal: false };
113
+ const parsed = parseLoopStepInstanceId(stepInstanceId);
114
+ if (parsed === undefined)
115
+ throw new ApplicationSubmitError("WSSPEC_STAGE_NOT_FOUND", `找不到 Step ${stepInstanceId}。 `);
116
+ const loopStep = profile.steps.find((candidate) => candidate.id === parsed.loopId && candidate.uses === "control.loop");
117
+ const loop = ownProjection(projection.loops, parsed.loopId);
118
+ const child = loopStep?.steps.find((candidate) => candidate.id === parsed.stepId);
119
+ if (loopStep === undefined || child === undefined || loop?.status !== "running" || loop.iteration !== parsed.iteration) {
120
+ throw new ApplicationSubmitError("WSSPEC_ATTEMPT_NOT_ACTIVE", "Attempt 所属循环轮次已失效。 ");
121
+ }
122
+ return { stageId: loopStep.id, stepInstanceId, step: child, internal: true };
123
+ }
124
+ function internalPath(workItemId, filename) {
125
+ return filename === `.wsspec/work-items/${workItemId}` || filename.startsWith(`.wsspec/work-items/${workItemId}/`);
126
+ }
127
+ async function actualChangedFiles(state, before) {
128
+ const after = await computeWorkspaceSnapshot(state.worktree);
129
+ const identity = (entry) => JSON.stringify([
130
+ entry.type,
131
+ entry.mode,
132
+ entry.digest ?? null,
133
+ entry.target ?? null,
134
+ ]);
135
+ const beforeByPath = new Map(before.map((entry) => [entry.path, identity(entry)]));
136
+ const afterByPath = new Map(after.map((entry) => [entry.path, identity(entry)]));
137
+ return [...new Set([...beforeByPath.keys(), ...afterByPath.keys()].filter((file) => beforeByPath.get(file) !== afterByPath.get(file)))]
138
+ .filter((file) => !internalPath(state.item.workItemId, file))
139
+ .sort((left, right) => left.localeCompare(right));
140
+ }
141
+ function permitted(file, allowedPaths) {
142
+ return allowedPaths.some((allowed) => matchesRepositoryPath(allowed, file));
143
+ }
144
+ function sameStrings(left, right) {
145
+ const normalizedLeft = [...new Set(left)].sort((a, b) => a.localeCompare(b));
146
+ const normalizedRight = [...new Set(right)].sort((a, b) => a.localeCompare(b));
147
+ return JSON.stringify(normalizedLeft) === JSON.stringify(normalizedRight);
148
+ }
149
+ async function verifySubmittedArtifact(state, step, attemptId, reference) {
150
+ if (reference.artifactType === "requirement-source") {
151
+ const source = state.snapshot.source;
152
+ const fields = ["artifactType", "outputId", "schemaVersion", "artifactId", "path", "revision", "contentHash", "mediaType", "contentLevel"];
153
+ if (!fields.every((field) => reference[field] === source[field])) {
154
+ throw new ApplicationSubmitError("WSSPEC_ARTIFACT_REFERENCE_INVALID", "Requirement Source Artifact 与不可变快照不一致。 ");
155
+ }
156
+ return;
157
+ }
158
+ if (reference.path === undefined || reference.contentHash === undefined || reference.revision === undefined) {
159
+ throw new ApplicationSubmitError("WSSPEC_ARTIFACT_REFERENCE_INVALID", `Artifact ${reference.artifactType} 缺少路径、revision 或摘要。 `);
160
+ }
161
+ const normalized = reference.path.replaceAll("\\", "/");
162
+ if (path.isAbsolute(normalized) || normalized.split("/").includes("..")) {
163
+ throw new ApplicationSubmitError("WSSPEC_ARTIFACT_REFERENCE_INVALID", "Artifact 路径必须位于工作区内。 ");
164
+ }
165
+ const filename = path.join(state.worktree, normalized);
166
+ const verified = await verifyArtifact(filename, {
167
+ repositoryRoot: state.worktree,
168
+ artifactType: reference.artifactType,
169
+ workItemId: state.item.workItemId,
170
+ stageId: step.id,
171
+ attemptId,
172
+ }, { allowUnregisteredType: true });
173
+ if (verified.artifactType !== reference.artifactType
174
+ || verified.outputId !== reference.outputId
175
+ || verified.schemaVersion !== reference.schemaVersion
176
+ || verified.path !== normalized
177
+ || verified.revision !== reference.revision
178
+ || verified.contentHash !== reference.contentHash
179
+ || (reference.mediaType !== undefined && verified.mediaType !== reference.mediaType)) {
180
+ throw new ApplicationSubmitError("WSSPEC_ARTIFACT_REFERENCE_INVALID", `Artifact ${reference.artifactType} 身份与提交引用不一致。 `);
181
+ }
182
+ const canonicalPath = `.wsspec/work-items/${state.item.workItemId}/artifacts/${reference.artifactType}/${reference.contentHash.slice("sha256:".length)}.md`;
183
+ const artifactDigest = sha256(await readFile(filename));
184
+ const authored = (await readEvents(state.projection.controlPlane)).some((event) => {
185
+ if (event.eventType !== "artifact.authored" || event.stageId !== step.id || event.attemptId !== attemptId)
186
+ return false;
187
+ const value = record(event.result.value);
188
+ return value?.artifactType === reference.artifactType
189
+ && value.outputId === reference.outputId
190
+ && value.schemaVersion === reference.schemaVersion
191
+ && value.revision === reference.revision
192
+ && value.contentHash === reference.contentHash
193
+ && (reference.mediaType === undefined || value.mediaType === reference.mediaType)
194
+ && value.contentLevel === reference.contentLevel
195
+ && value.artifactDigest === artifactDigest;
196
+ });
197
+ if (normalized !== canonicalPath || !authored) {
198
+ throw new ApplicationSubmitError("WSSPEC_ARTIFACT_REFERENCE_INVALID", `Artifact ${reference.artifactType} 缺少匹配的受治理 authoring 事件。`);
199
+ }
200
+ }
201
+ async function verifyPublicationArtifacts(state, artifacts) {
202
+ const bodies = [];
203
+ for (const reference of artifacts) {
204
+ try {
205
+ if (reference.path === undefined || reference.contentHash === undefined || reference.revision === undefined)
206
+ throw new Error("incomplete reference");
207
+ const normalized = reference.path.replaceAll("\\", "/");
208
+ const filename = await resolveRepositoryRegularFile(state.worktree, normalized);
209
+ const artifact = await readArtifact(filename);
210
+ const { contentHash: storedHash, ...metadataWithoutHash } = artifact.metadata;
211
+ const actualHash = computeArtifactContentHash(metadataWithoutHash, artifact.body);
212
+ if (artifact.metadata.workItemId !== state.item.workItemId
213
+ || artifact.metadata.artifactType !== reference.artifactType
214
+ || artifact.metadata.revision !== reference.revision
215
+ || storedHash !== actualHash
216
+ || reference.contentHash !== actualHash)
217
+ throw new Error("artifact content mismatch");
218
+ bodies.push(artifact.body);
219
+ }
220
+ catch {
221
+ throw new ApplicationSubmitError("WSSPEC_ARTIFACT_REFERENCE_INVALID", `External publish Artifact ${reference.artifactType} 的实际内容与输入引用不一致。 `);
222
+ }
223
+ }
224
+ return bodies;
225
+ }
226
+ function knowledgePublishedContentDigest(bodies) {
227
+ if (bodies.length !== 1) {
228
+ throw new ApplicationSubmitError("WSSPEC_ARTIFACT_REFERENCE_INVALID", "Knowledge publish 必须精确绑定一个已验证 Artifact 正文。 ");
229
+ }
230
+ try {
231
+ return sha256(canonicalRequirementText(bodies[0]));
232
+ }
233
+ catch {
234
+ throw new ApplicationSubmitError("WSSPEC_ARTIFACT_REFERENCE_INVALID", "Knowledge publish Artifact 正文无法规范化为有界 Markdown。 ");
235
+ }
236
+ }
237
+ function engineTddStep(step, internal = false, hasCycle = false) {
238
+ return (step.id === "verify-red" && step.action === "quality.test")
239
+ || (step.id === "verify-green" && step.action === "quality.test")
240
+ || (internal && hasCycle && step.id === "verify" && step.action === "quality.verify");
241
+ }
242
+ export function tddFailureDisposition(input, code) {
243
+ switch (code) {
244
+ case "WSSPEC_TDD_RED_NOT_OBSERVED":
245
+ case "WSSPEC_TDD_RED_SCOPE_INVALID":
246
+ return input.phase === "red" ? "restart-red" : "fail-closed";
247
+ case "WSSPEC_TDD_RED_SYNTAX_FAILURE":
248
+ case "WSSPEC_TDD_REPORT_INVALID":
249
+ return input.phase === "red" ? "restart-red" : "retry";
250
+ case "WSSPEC_TDD_GREEN_NOT_OBSERVED":
251
+ return input.phase === "red" ? "fail-closed" : input.internal ? "retry" : "restart-implementation";
252
+ case "WSSPEC_TDD_GATE_EXECUTION_FAILED":
253
+ case "WSSPEC_TDD_RED_INFRASTRUCTURE_FAILURE":
254
+ case "WSSPEC_TDD_RED_TIMEOUT":
255
+ return "retry";
256
+ case "WSSPEC_TDD_EVIDENCE_INVALIDATED":
257
+ case "WSSPEC_TDD_GATE_CONFIGURATION_INVALID":
258
+ case "WSSPEC_TDD_RED_REQUIRED":
259
+ case "WSSPEC_TDD_REPORTER_UNSUPPORTED":
260
+ case "WSSPEC_TDD_STEP_INVALID":
261
+ case "WSSPEC_TDD_TEST_PATH_INVALID":
262
+ return "fail-closed";
263
+ }
264
+ }
265
+ function failedTddResult(input, error, retryable) {
266
+ return {
267
+ ...input.result,
268
+ status: "failed",
269
+ failureCode: retryable ? "WSSPEC_STEP_FAILED" : "WSSPEC_STEP_INPUT_INVALID",
270
+ summary: error.message,
271
+ artifacts: [],
272
+ commands: [],
273
+ evidence: [],
274
+ externalWrites: [],
275
+ remainingRisks: [],
276
+ };
277
+ }
278
+ function restartTddCycle(projection, workItemId) {
279
+ const resetIds = new Set(["write-tests", "verify-red", "implement", "verify-green", "review-fix"]);
280
+ const stages = { ...projection.stages };
281
+ for (const stepId of resetIds) {
282
+ if (stepId === "write-tests" || stages[stepId] !== undefined)
283
+ stages[stepId] = { status: stepId === "write-tests" ? "ready" : "pending" };
284
+ }
285
+ const outsideCycle = ([key]) => !resetIds.has(key) && !key.startsWith("review-fix:");
286
+ const loops = { ...projection.loops };
287
+ delete loops["review-fix"];
288
+ const evidence = Object.fromEntries(Object.entries(projection.evidence).filter(([key]) => !key.startsWith(`tdd:${workItemId}:`)));
289
+ delete evidence[evidenceProjectionKey("verify-green", "test")];
290
+ return {
291
+ ...projection,
292
+ stages,
293
+ claims: Object.fromEntries(Object.entries(projection.claims).filter(outsideCycle)),
294
+ contexts: Object.fromEntries(Object.entries(projection.contexts).filter(outsideCycle)),
295
+ retries: Object.fromEntries(Object.entries(projection.retries).filter(outsideCycle)),
296
+ loops,
297
+ evidence,
298
+ };
299
+ }
300
+ function restartTddImplementation(projection, workItemId) {
301
+ const resetIds = new Set(["implement", "verify-green", "review-fix"]);
302
+ const stages = { ...projection.stages };
303
+ for (const stepId of resetIds) {
304
+ if (stepId === "implement" || stages[stepId] !== undefined)
305
+ stages[stepId] = { status: stepId === "implement" ? "ready" : "pending" };
306
+ }
307
+ const outsideReset = ([key]) => !resetIds.has(key) && !key.startsWith("review-fix:");
308
+ const loops = { ...projection.loops };
309
+ delete loops["review-fix"];
310
+ const evidence = Object.fromEntries(Object.entries(projection.evidence).filter(([key]) => !key.startsWith(`tdd:${workItemId}:green:`) && key !== tddCycleEvidenceKey(workItemId)));
311
+ delete evidence[evidenceProjectionKey("verify-green", "test")];
312
+ return {
313
+ ...projection,
314
+ stages,
315
+ claims: Object.fromEntries(Object.entries(projection.claims).filter(outsideReset)),
316
+ contexts: Object.fromEntries(Object.entries(projection.contexts).filter(outsideReset)),
317
+ retries: Object.fromEntries(Object.entries(projection.retries).filter(outsideReset)),
318
+ loops,
319
+ evidence,
320
+ };
321
+ }
322
+ async function validateResult(input, state, target, projection, now) {
323
+ const claim = projection.claims[target.stageId];
324
+ const context = projection.contexts[target.stageId];
325
+ if (claim?.stageId !== input.stepId || claim.attemptId !== input.attemptId || claim.claimToken !== input.leaseToken || context?.workPackage.stepId !== input.stepId || context.workPackage.attemptId !== input.attemptId || context.workPackage.lease.token !== input.leaseToken) {
326
+ throw new ApplicationSubmitError("WSSPEC_ATTEMPT_NOT_ACTIVE", "Attempt 或 Lease 已失效。 ");
327
+ }
328
+ if (new Date(claim.expiresAt) <= now) {
329
+ throw new ApplicationSubmitError("WSSPEC_ATTEMPT_NOT_ACTIVE", "Attempt Lease 已过期。 ");
330
+ }
331
+ const changed = await actualChangedFiles(state, claim.workspaceSnapshot);
332
+ if (state.snapshot.changePolicy.kind === "documentation-only" && changed.some((file) => !permitted(file, state.snapshot.changePolicy.allowedPaths))) {
333
+ throw new ApplicationSubmitError("WSSPEC_DOCUMENTATION_SCOPE_VIOLATION", "实际 Git diff 越出 documentation-only 路径边界。 ");
334
+ }
335
+ if (!sameStrings(changed, input.result.modifiedFiles)) {
336
+ throw new ApplicationSubmitError("WSSPEC_MODIFIED_FILES_MISMATCH", "提交结果的 modifiedFiles 与实际 Git diff 不一致。 ");
337
+ }
338
+ const hasCycle = projection.evidence[tddCycleEvidenceKey(state.item.workItemId)] !== undefined;
339
+ if (engineTddStep(target.step, target.internal, hasCycle) && changed.length !== 0) {
340
+ throw new VerificationError("WSSPEC_TDD_EVIDENCE_INVALIDATED", "引擎管理的 TDD 验证 Attempt 不允许修改 workspace。 ");
341
+ }
342
+ const undeclared = input.result.artifacts.find((artifact) => !target.step.outputs.some((output) => {
343
+ return artifact.artifactType === output.artifact && artifactOutputId(artifact) === output.outputId;
344
+ }));
345
+ if (undeclared !== undefined) {
346
+ throw new ApplicationSubmitError("WSSPEC_UNDECLARED_ARTIFACT", `Artifact output ${artifactOutputId(undeclared) ?? "<missing>"} 未在 Step outputs 中声明。 `);
347
+ }
348
+ const expectedOutputs = context.workPackage.requiredOutputs;
349
+ for (const artifact of input.result.artifacts)
350
+ await verifySubmittedArtifact(state, { ...target.step, id: input.stepId }, input.attemptId, artifact);
351
+ if (input.result.status === "completed" && !engineTddStep(target.step, target.internal, hasCycle)) {
352
+ for (const output of expectedOutputs) {
353
+ const outputId = output.outputId ?? (output.artifactType === "requirement-source" ? "requirement-source" : undefined);
354
+ if (outputId === undefined || !input.result.artifacts.some((artifact) => {
355
+ return artifact.artifactType === output.artifactType && artifactOutputId(artifact) === outputId;
356
+ })) {
357
+ throw new ApplicationSubmitError("WSSPEC_REQUIRED_ARTIFACT_MISSING", `缺少必需 Artifact output ${output.outputId ?? output.artifactType}。 `);
358
+ }
359
+ }
360
+ }
361
+ return changed;
362
+ }
363
+ function approvalAction(state, request) {
364
+ return {
365
+ action: "await_approval",
366
+ approval: {
367
+ kind: "step",
368
+ requestId: request.requestId,
369
+ workItemId: state.item.workItemId,
370
+ title: `审批 ${request.stageId}`,
371
+ digest: request.contentHash,
372
+ },
373
+ };
374
+ }
375
+ function trustedSubmitResult(submitted, validated) {
376
+ if (validated.status !== submitted.status) {
377
+ throw new ApplicationSubmitError("WSSPEC_STEP_FAILURE_CLASSIFICATION_INVALID", "Executor 返回的状态与 SubmitResult 不一致。 ");
378
+ }
379
+ if (submitted.status === "failed") {
380
+ if (!isStepFailureCode(validated.failureCode)) {
381
+ throw new ApplicationSubmitError("WSSPEC_STEP_FAILURE_CLASSIFICATION_INVALID", "Executor 未返回受支持的稳定失败码。 ");
382
+ }
383
+ return { ...submitted, failureCode: validated.failureCode };
384
+ }
385
+ if (validated.failureCode !== undefined) {
386
+ throw new ApplicationSubmitError("WSSPEC_STEP_FAILURE_CLASSIFICATION_INVALID", "成功结果不能携带失败码。 ");
387
+ }
388
+ return submitted;
389
+ }
390
+ function changedProfileSteps(state, decision) {
391
+ if (decision.previous === decision.selected)
392
+ return [];
393
+ const previous = state.snapshot.profiles[decision.previous];
394
+ const selected = state.snapshot.profiles[decision.selected];
395
+ const previousById = new Map(previous.steps.map((step) => [step.id, step]));
396
+ return selected.steps
397
+ .filter((step) => JSON.stringify(previousById.get(step.id)) !== JSON.stringify(step))
398
+ .map(({ id }) => id);
399
+ }
400
+ function activateSelectedProfile(state, projection, decision, options = {}) {
401
+ const invalidatedStepIds = [...new Set([...decision.invalidatedStepIds, ...changedProfileSteps(state, decision)])].sort();
402
+ const applied = applyProfileDecision(projection, { ...decision, invalidatedStepIds }, options);
403
+ const profile = state.snapshot.profiles[decision.selected];
404
+ const initial = deriveInitialStages(profile);
405
+ const stages = { ...applied.stages };
406
+ for (const stepId of invalidatedStepIds) {
407
+ if (stages[stepId]?.status === "invalidated" && initial[stepId] !== undefined)
408
+ stages[stepId] = initial[stepId];
409
+ }
410
+ const loops = { ...applied.loops };
411
+ if (options.preserveCurrentStep !== undefined && loops[options.preserveCurrentStep] !== undefined) {
412
+ const selectedStep = profile.steps.find(({ id }) => id === options.preserveCurrentStep);
413
+ if (selectedStep?.maxIterations !== undefined) {
414
+ loops[options.preserveCurrentStep] = { ...loops[options.preserveCurrentStep], maxIterations: selectedStep.maxIterations };
415
+ }
416
+ }
417
+ const activated = { ...applied, stages, loops };
418
+ return {
419
+ state: {
420
+ ...state,
421
+ projection: activated,
422
+ snapshot: { ...state.snapshot, selectedProfile: decision.selected, changePolicy: profile.changePolicy },
423
+ },
424
+ projection: activated,
425
+ invalidatedStepIds,
426
+ };
427
+ }
428
+ function discoveryTarget(projection, kind, workItemId) {
429
+ const bindings = record(projection.evidence.bindings);
430
+ const binding = record(bindings?.[kind]);
431
+ if (binding?.exists !== true || typeof binding.stableId !== "string" || binding.stableId === ""
432
+ || binding.externalWorkItemId !== workItemId) {
433
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_BINDING_INVALID", `外部 ${kind} 目标缺少当前 Work Item 的稳定发现绑定。 `);
434
+ }
435
+ return {
436
+ exists: true,
437
+ stableId: binding.stableId,
438
+ externalWorkItemId: workItemId,
439
+ ...(typeof binding.provider === "string" ? { provider: binding.provider } : {}),
440
+ };
441
+ }
442
+ function normalizedProvider(value) {
443
+ return { github: "github-cli", gitlab: "gitlab-cli", feishu: "lark-cli" }[value] ?? value;
444
+ }
445
+ function assertKnowledgePayloadContract(input) {
446
+ const payload = record(input.payload);
447
+ if (payload === undefined || Object.keys(payload).sort().join("\0") !== "binding\0target") {
448
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_BINDING_INVALID", "Knowledge payload 必须精确携带目标和 Application binding。 ");
449
+ }
450
+ let target;
451
+ try {
452
+ target = validateKnowledgePublishTarget(payload.target);
453
+ }
454
+ catch {
455
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_BINDING_INVALID", "Knowledge payload 目标无法规范化。 ");
456
+ }
457
+ if (target.stableId !== input.stableId || sha256(target.markdown) !== input.contentDigest
458
+ || canonicalDigest(payload.binding, "WSSPEC_EXTERNAL_BINDING_INVALID")
459
+ !== canonicalDigest(input.applicationBinding, "WSSPEC_EXTERNAL_BINDING_INVALID")) {
460
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_BINDING_INVALID", "Knowledge payload、Application binding 与已验证 Artifact 正文不一致。 ");
461
+ }
462
+ }
463
+ function previousVerifiedIssueUpdate(projection, stableId) {
464
+ return Object.values(projection.externalActions)
465
+ .filter((state) => state.status === "verified" && state.request.action === "issue.update" && state.request.target.stableId === stableId)
466
+ .sort((left, right) => left.receipt.verifiedAt.localeCompare(right.receipt.verifiedAt))
467
+ .at(-1)?.receipt;
468
+ }
469
+ function knowledgeDeliveryStatus(projection, profile) {
470
+ const actions = Object.values(projection.externalActions)
471
+ .filter((state) => state.request.action === "knowledge.publish")
472
+ .sort((left, right) => left.request.createdAt.localeCompare(right.request.createdAt));
473
+ const latest = actions.at(-1);
474
+ if (latest !== undefined) {
475
+ if (latest.status === "failed") {
476
+ const warning = projection.evidence[`external-warning:${latest.request.requestId}`];
477
+ if (warning?.code === "WSSPEC_OPTIONAL_KNOWLEDGE_FAILED")
478
+ return "warning";
479
+ }
480
+ return latest.status;
481
+ }
482
+ const steps = profile.steps.filter((step) => step.action === "knowledge.publish");
483
+ if (steps.length === 0)
484
+ return "absent";
485
+ if (steps.every((step) => projection.stages[step.id]?.status === "skipped"))
486
+ return "skipped";
487
+ return "missing";
488
+ }
489
+ async function governExternalSubmit(input, state, dependencies) {
490
+ if (input.result.externalWrites.length === 0)
491
+ return { input };
492
+ const profile = selectedProfile(state.snapshot);
493
+ const target = executionTarget(profile, state.projection, input.stepId);
494
+ if (!governedActionStep(target.step)) {
495
+ if (input.result.externalWrites.length !== 0) {
496
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_INTENT_INVALID", "非受治理写入 Step 不能携带外部写入记录。");
497
+ }
498
+ return { input };
499
+ }
500
+ if (target.internal)
501
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_INTENT_INVALID", "循环内部 Step 不支持外部写入。 ");
502
+ if (input.result.status === "failed") {
503
+ if (input.result.externalWrites.length !== 0)
504
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_INTENT_INVALID", "失败结果不能携带外部写入意图。 ");
505
+ return { input };
506
+ }
507
+ const intent = externalWriteIntent(input.result);
508
+ const gitCommit = intent.action === "git.commit";
509
+ if (intent.action !== target.step.action
510
+ || (gitCommit && intent.target.kind !== "repository")
511
+ || (!gitCommit && (intent.action === "knowledge.publish") !== (intent.target.kind === "knowledge"))
512
+ || (!gitCommit && intent.target.kind === "repository")) {
513
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_INTENT_INVALID", "外部动作意图与编译后的 Step 动作或目标类别不一致。 ");
514
+ }
515
+ let externalEffectKind;
516
+ if (intent.action === "issue.update" || intent.action === "issue.close") {
517
+ const issueAction = validateIssueWriteAction(record(intent.payload)?.action);
518
+ if ((intent.action === "issue.close") !== (issueAction.type === "issue.close")) {
519
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_INTENT_INVALID", "Issue payload 动作与编译后的 Step 动作不一致。 ");
520
+ }
521
+ if (issueAction.type === "comment")
522
+ externalEffectKind = "issue.comment";
523
+ }
524
+ const payloadDigest = canonicalDigest(intent.payload);
525
+ const prior = Object.values(state.projection.externalActions).find((candidate) => candidate.request.workItemId === input.workItemId
526
+ && candidate.request.stepId === input.stepId
527
+ && candidate.request.attemptId === input.attemptId
528
+ && candidate.request.provider === intent.provider
529
+ && candidate.request.action === intent.action
530
+ && candidate.request.target.kind === intent.target.kind
531
+ && candidate.request.target.stableId === intent.target.stableId);
532
+ if (prior !== undefined) {
533
+ const rejection = state.projection.evidence[externalActionRejectionKey(prior.request.requestId)];
534
+ if (rejection !== undefined) {
535
+ if (rejection.requestId !== prior.request.requestId || rejection.requestDigest !== prior.request.requestDigest
536
+ || rejection.actor === "" || !Number.isFinite(Date.parse(rejection.rejectedAt))) {
537
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_REJECTION_INVALID", "外部动作拒绝证据未绑定当前请求。");
538
+ }
539
+ return { input, action: externalBlocked("WSSPEC_EXTERNAL_ACTION_REJECTED", "外部动作未获授权。") };
540
+ }
541
+ if (prior.request.payloadDigest !== payloadDigest
542
+ || JSON.stringify(prior.request.sideEffects) !== JSON.stringify(intent.sideEffects)) {
543
+ throw new ExternalActionError("WSSPEC_EXTERNAL_IDEMPOTENCY_CONFLICT", "同一 Attempt 的外部动作已绑定不同请求内容。");
544
+ }
545
+ }
546
+ const active = state.projection.contexts[target.stageId];
547
+ let bindingDigest;
548
+ let inputDigest;
549
+ let expectedContentDigest = payloadDigest;
550
+ if (gitCommit) {
551
+ const approval = validatedGitApproval(intent, state.worktree);
552
+ expectedContentDigest = approval.diffDigest;
553
+ bindingDigest = canonicalDigest({
554
+ kind: "repository",
555
+ repositoryRoot: approval.repositoryRoot,
556
+ repositoryCommonDir: approval.repositoryCommonDir,
557
+ baselineRevision: approval.baselineRevision,
558
+ }, "WSSPEC_EXTERNAL_BINDING_INVALID");
559
+ inputDigest = canonicalDigest({
560
+ baselineRevision: approval.baselineRevision,
561
+ files: approval.files,
562
+ message: approval.message,
563
+ diffDigest: approval.diffDigest,
564
+ }, "WSSPEC_EXTERNAL_BINDING_INVALID");
565
+ }
566
+ else {
567
+ if (intent.target.kind === "repository") {
568
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_INTENT_INVALID", "外部动作意图与目标类别不一致。 ");
569
+ }
570
+ const publicationBodies = await verifyPublicationArtifacts(state, active.workPackage.artifacts.filter(({ artifactType }) => artifactType !== "requirement-source"));
571
+ const discovered = discoveryTarget(state.projection, intent.target.kind, state.item.workItemId);
572
+ if (discovered.stableId !== intent.target.stableId) {
573
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_BINDING_INVALID", "外部动作意图的稳定目标与发现绑定不一致。 ");
574
+ }
575
+ if (discovered.provider !== undefined && normalizedProvider(discovered.provider) !== normalizedProvider(intent.provider)) {
576
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_BINDING_INVALID", "外部动作 Provider 与生产发现绑定不一致。 ");
577
+ }
578
+ if (intent.action === "issue.close") {
579
+ const previous = previousVerifiedIssueUpdate(state.projection, discovered.stableId);
580
+ if (previous === undefined) {
581
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_ORDER_INVALID", "Issue Close 前缺少已验证的 Issue Update Receipt。 ");
582
+ }
583
+ const delivery = evaluateExternalDelivery({
584
+ issueUpdate: "verified",
585
+ knowledge: knowledgeDeliveryStatus(state.projection, profile),
586
+ knowledgeRequired: profile.publishing.knowledgeRequired,
587
+ issueClose: "verified",
588
+ });
589
+ if (!delivery.allowed) {
590
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_ORDER_INVALID", "Issue Close 前的 Knowledge 发布状态尚未满足 Profile 约束。 ");
591
+ }
592
+ bindingDigest = canonicalDigest({ kind: "issue", ...discovered }, "WSSPEC_EXTERNAL_BINDING_INVALID");
593
+ inputDigest = previous.readBackContentDigest;
594
+ }
595
+ else {
596
+ const publishTarget = intent.action === "knowledge.publish" ? "knowledge" : "issue";
597
+ if (publishTarget === "knowledge")
598
+ expectedContentDigest = knowledgePublishedContentDigest(publicationBodies);
599
+ const binding = createExternalBinding({
600
+ target: publishTarget,
601
+ workPackage: active.workPackage,
602
+ discoveryBinding: discovered,
603
+ ...(publishTarget === "knowledge" ? { expectedPublishedContentDigest: expectedContentDigest } : {}),
604
+ });
605
+ if (publishTarget === "knowledge") {
606
+ assertKnowledgePayloadContract({
607
+ payload: intent.payload,
608
+ applicationBinding: binding,
609
+ stableId: discovered.stableId,
610
+ contentDigest: expectedContentDigest,
611
+ });
612
+ }
613
+ bindingDigest = canonicalDigest(binding, "WSSPEC_EXTERNAL_BINDING_INVALID");
614
+ inputDigest = binding.publishInputDigest;
615
+ }
616
+ }
617
+ const artifactDigests = active.workPackage.artifacts.map((artifact) => (artifact.contentHash ?? canonicalDigest(artifact))).sort();
618
+ if (prior !== undefined) {
619
+ const sameLogicalRequest = prior.request.payloadDigest === payloadDigest
620
+ && prior.request.expectedContentDigest === expectedContentDigest
621
+ && prior.request.bindingDigest === bindingDigest
622
+ && prior.request.inputDigest === inputDigest
623
+ && prior.request.externalEffectKind === externalEffectKind
624
+ && JSON.stringify(prior.request.artifactDigests) === JSON.stringify(artifactDigests)
625
+ && JSON.stringify(prior.request.sideEffects) === JSON.stringify(intent.sideEffects);
626
+ if (!sameLogicalRequest) {
627
+ throw new ExternalActionError("WSSPEC_EXTERNAL_IDEMPOTENCY_CONFLICT", "同一 Attempt 的外部动作已绑定不同请求内容。");
628
+ }
629
+ if (prior.status === "verified") {
630
+ await assertCurrentExternalActionContext(state, state.projection, prior.request);
631
+ }
632
+ }
633
+ await validateResult(input, state, target, state.projection, dependencies.now());
634
+ const executorStep = { ...target.step, id: target.stageId };
635
+ trustedSubmitResult(input.result, await dependencies.executors.assertStep(executorStep).validate(executorStep, input.result, state.projection));
636
+ let governed;
637
+ if (prior !== undefined) {
638
+ governed = prior;
639
+ }
640
+ else {
641
+ governed = await prepareExternalAction({
642
+ root: input.root,
643
+ workItemId: input.workItemId,
644
+ stepId: input.stepId,
645
+ attemptId: input.attemptId,
646
+ provider: intent.provider,
647
+ action: intent.action,
648
+ securityClass: gitCommit ? "local-write" : "external-write",
649
+ target: intent.target,
650
+ ...(externalEffectKind === undefined ? {} : { externalEffectKind }),
651
+ payload: intent.payload,
652
+ expectedContentDigest,
653
+ bindingDigest,
654
+ inputDigest,
655
+ artifactDigests,
656
+ sideEffects: intent.sideEffects,
657
+ createdAt: dependencies.now().toISOString(),
658
+ expiresAt: active.workPackage.lease.expiresAt,
659
+ actor: state.projection.claims[target.stageId].actor,
660
+ });
661
+ }
662
+ if (governed.status === "prepared")
663
+ return { input, action: externalApprovalAction(input.workItemId, governed) };
664
+ if (governed.status === "reconciliation_required"
665
+ || (governed.status === "executing" && governed.dispatch === "sent_or_unknown")) {
666
+ return { input, action: externalBlocked("WSSPEC_EXTERNAL_RECONCILIATION_REQUIRED", "外部动作结果未知,禁止自动重发。") };
667
+ }
668
+ if (governed.status === "failed") {
669
+ return { input, action: externalBlocked("WSSPEC_EXTERNAL_RECONCILIATION_FAILED", governed.reason) };
670
+ }
671
+ if (governed.status !== "verified") {
672
+ governed = await executeExternalAction({
673
+ root: input.root,
674
+ workItemId: input.workItemId,
675
+ requestId: governed.request.requestId,
676
+ payload: intent.payload,
677
+ executor: dependencies.externalExecutor(intent.provider, intent.action),
678
+ now: dependencies.now().toISOString(),
679
+ });
680
+ }
681
+ if (governed.status === "reconciliation_required") {
682
+ return { input, action: externalBlocked("WSSPEC_EXTERNAL_RECONCILIATION_REQUIRED", "外部动作结果未知,必须先完成只读协调回查。") };
683
+ }
684
+ if (governed.status === "executing") {
685
+ return { input, action: externalBlocked("WSSPEC_EXTERNAL_EXECUTION_IN_PROGRESS", "外部动作已有执行 owner,请稍后重试。", true) };
686
+ }
687
+ return {
688
+ input: { ...input, result: { ...input.result, externalWrites: [{ ...governed.receipt }] } },
689
+ };
690
+ }
691
+ function governedExternalReceipt(result) {
692
+ const candidate = result.externalWrites.length === 1 ? result.externalWrites[0] : undefined;
693
+ try {
694
+ return validate("builtin.external-write-receipt.v1", candidate);
695
+ }
696
+ catch {
697
+ return undefined;
698
+ }
699
+ }
700
+ function withLegacyExternalEvidence(projection, receipt) {
701
+ if (receipt.action === "git.commit" || receipt.action === "issue.close")
702
+ return projection;
703
+ const target = receipt.target.kind;
704
+ const binding = {
705
+ version: 1,
706
+ kind: "external-binding",
707
+ target,
708
+ exists: true,
709
+ stableId: receipt.target.stableId,
710
+ externalWorkItemId: receipt.workItemId,
711
+ publishStepId: receipt.stepId,
712
+ publishAttemptId: receipt.attemptId,
713
+ publishInputDigest: receipt.inputDigest,
714
+ expectedPublishedContentDigest: receipt.expectedContentDigest,
715
+ };
716
+ const legacyReceipt = {
717
+ version: 1,
718
+ kind: "external-receipt",
719
+ target,
720
+ stableId: receipt.target.stableId,
721
+ ...(receipt.externalEffectKind === undefined ? {} : { externalEffectKind: receipt.externalEffectKind }),
722
+ ...(receipt.externalEffectId === undefined ? {} : { externalEffectId: receipt.externalEffectId }),
723
+ externalWorkItemId: receipt.workItemId,
724
+ publishStepId: receipt.stepId,
725
+ publishAttemptId: receipt.attemptId,
726
+ publishInputDigest: receipt.inputDigest,
727
+ publishedContentDigest: receipt.publishedContentDigest,
728
+ readBackContentDigest: receipt.readBackContentDigest,
729
+ status: "confirmed",
730
+ readBackStatus: "confirmed",
731
+ };
732
+ return {
733
+ ...projection,
734
+ evidence: {
735
+ ...projection.evidence,
736
+ [`external-binding:${target}`]: binding,
737
+ [`external-receipt:${target}`]: legacyReceipt,
738
+ },
739
+ };
740
+ }
741
+ export async function submitApplication(input, dependencies) {
742
+ validate("builtin.application-submit-input.v1", input);
743
+ validate("builtin.submit-result.v1", input.result);
744
+ const { root: _root, ...operationInput } = input;
745
+ const completed = await readIdempotentControlPlaneResult({
746
+ cwd: input.root,
747
+ workItemId: input.workItemId,
748
+ idempotencyKey: `submit:${input.attemptId}`,
749
+ operationInput,
750
+ });
751
+ if (completed !== undefined)
752
+ return completed;
753
+ const state = await loadApplicationState(input.root, input.workItemId);
754
+ const governed = await governExternalSubmit(input, state, dependencies);
755
+ if (governed.action !== undefined)
756
+ return governed.action;
757
+ input = governed.input;
758
+ let profileEvent;
759
+ let profileInvalidatedStepIds = [];
760
+ const action = await mutateControlPlane({
761
+ cwd: input.root,
762
+ workItemId: input.workItemId,
763
+ eventType: (value) => value.action === "completed" && value.summary.status === "closed"
764
+ ? "work-item.closed"
765
+ : profileEvent ?? "attempt.submitted",
766
+ idempotencyKey: `submit:${input.attemptId}`,
767
+ stageId: input.stepId,
768
+ attemptId: input.attemptId,
769
+ operationInput,
770
+ eventDetails: () => profileInvalidatedStepIds.length === 0 ? {} : { invalidatedStepIds: profileInvalidatedStepIds },
771
+ mutate: async (current) => {
772
+ let runtimeState = state;
773
+ if (runtimeState.snapshot.selectedProfile !== current.profile.selected) {
774
+ const selected = runtimeState.snapshot.profiles[current.profile.selected];
775
+ runtimeState = {
776
+ ...runtimeState,
777
+ projection: current,
778
+ snapshot: { ...runtimeState.snapshot, selectedProfile: current.profile.selected, changePolicy: selected.changePolicy },
779
+ };
780
+ }
781
+ const profile = selectedProfile(runtimeState.snapshot);
782
+ const target = executionTarget(profile, current, input.stepId);
783
+ if (governedActionStep(target.step) && input.result.status === "completed"
784
+ && input.result.externalWrites.length === 0) {
785
+ throw new ApplicationSubmitError("WSSPEC_EXTERNAL_INTENT_INVALID", "external-write Step 必须提交经过授权和回读验证的外部动作 Receipt。 ");
786
+ }
787
+ let changed;
788
+ try {
789
+ changed = await validateResult(input, runtimeState, target, current, dependencies.now());
790
+ }
791
+ catch (error) {
792
+ if (!(error instanceof VerificationError) || !engineTddStep(target.step, target.internal, current.evidence[tddCycleEvidenceKey(runtimeState.item.workItemId)] !== undefined))
793
+ throw error;
794
+ const result = failedTddResult(input, error, false);
795
+ const actor = current.claims[target.stageId].actor;
796
+ const active = current.contexts[target.stageId];
797
+ const projection = {
798
+ ...current,
799
+ stages: { ...current.stages, [target.stageId]: { status: "failed" } },
800
+ claims: { ...current.claims },
801
+ contexts: { ...current.contexts, [target.stageId]: { ...active, actor, stepInstanceId: target.stepInstanceId, artifactValues: {}, result } },
802
+ retries: { ...current.retries },
803
+ };
804
+ delete projection.claims[target.stageId];
805
+ delete projection.retries[target.stepInstanceId];
806
+ return { projection, value: { action: "blocked", problems: [stepFailureProblem("WSSPEC_STEP_INPUT_INVALID", error.message)] } };
807
+ }
808
+ const executorStep = { ...target.step, id: target.stageId };
809
+ const hasCycle = current.evidence[tddCycleEvidenceKey(runtimeState.item.workItemId)] !== undefined;
810
+ const engineManagedTdd = engineTddStep(target.step, target.internal, hasCycle);
811
+ let gate;
812
+ let gateFailure;
813
+ if (engineManagedTdd || target.step.id === "implement") {
814
+ try {
815
+ gate = await fixedTestGateForState(runtimeState);
816
+ }
817
+ catch (error) {
818
+ if (!(error instanceof VerificationError) || !engineManagedTdd)
819
+ throw error;
820
+ gateFailure = error;
821
+ }
822
+ }
823
+ if (target.step.id === "implement") {
824
+ await assertImplementHasTrustedRed({
825
+ taskId: runtimeState.item.workItemId,
826
+ commandId: gate.commandId,
827
+ gate: gate,
828
+ worktree: runtimeState.worktree,
829
+ redEvidence: current.evidence[tddRedEvidenceKey(runtimeState.item.workItemId)],
830
+ });
831
+ }
832
+ let trustedTddEvidence;
833
+ let trustedTddCycle;
834
+ let executionProjection = current;
835
+ const publishTarget = externalPublishTarget(target.step.action);
836
+ const externalReceipt = governedExternalReceipt(input.result);
837
+ if (externalReceipt !== undefined) {
838
+ executionProjection = withLegacyExternalEvidence(current, externalReceipt);
839
+ }
840
+ else if (publishTarget !== undefined) {
841
+ const active = current.contexts[target.stageId];
842
+ const publicationBodies = await verifyPublicationArtifacts(runtimeState, active.workPackage.artifacts);
843
+ const bindings = current.evidence.bindings;
844
+ const binding = createExternalBinding({
845
+ target: publishTarget,
846
+ workPackage: active.workPackage,
847
+ discoveryBinding: bindings?.[publishTarget],
848
+ ...(publishTarget === "knowledge" ? { expectedPublishedContentDigest: knowledgePublishedContentDigest(publicationBodies) } : {}),
849
+ });
850
+ const evidence = { ...current.evidence, [`external-binding:${publishTarget}`]: binding };
851
+ delete evidence[`external-receipt:${publishTarget}`];
852
+ executionProjection = { ...current, evidence };
853
+ }
854
+ let result;
855
+ if (target.step.id === "verify-red") {
856
+ const writeTests = current.contexts["write-tests"];
857
+ if (gateFailure !== undefined) {
858
+ result = failedTddResult(input, gateFailure, false);
859
+ }
860
+ else
861
+ try {
862
+ trustedTddEvidence = await recordRedEvidence({
863
+ taskId: runtimeState.item.workItemId,
864
+ step: target.step,
865
+ gate: gate,
866
+ worktree: runtimeState.worktree,
867
+ workspaceDigest: await computeWorkspaceTreeDigest(runtimeState.worktree),
868
+ modifiedFiles: writeTests?.result?.modifiedFiles ?? [],
869
+ testPaths: writeTests?.result?.modifiedFiles ?? [],
870
+ });
871
+ result = { ...input.result, status: "completed", summary: "引擎已记录可信 Red Evidence。", artifacts: [], commands: [], evidence: [] };
872
+ }
873
+ catch (error) {
874
+ if (!(error instanceof VerificationError))
875
+ throw error;
876
+ const disposition = isTddVerificationCode(error.code)
877
+ ? tddFailureDisposition({ phase: "red", internal: false }, error.code)
878
+ : "fail-closed";
879
+ if (disposition === "restart-red") {
880
+ const restarted = restartTddCycle(current, runtimeState.item.workItemId);
881
+ const next = await acquireNextLocked({ state: runtimeState, projection: restarted, actor: current.claims[target.stageId].actor, root: input.root, dependencies });
882
+ return { projection: next.projection, value: next.action };
883
+ }
884
+ result = failedTddResult(input, error, disposition === "retry");
885
+ }
886
+ }
887
+ else if (target.step.id === "verify-green" || (target.internal && hasCycle && target.step.id === "verify" && target.step.action === "quality.verify")) {
888
+ const redEvidence = current.evidence[tddRedEvidenceKey(runtimeState.item.workItemId)];
889
+ if (redEvidence === undefined)
890
+ gateFailure = new VerificationError("WSSPEC_TDD_RED_REQUIRED", "verify-green 缺少可信 Red Evidence。 ");
891
+ const previousCycle = current.evidence[tddCycleEvidenceKey(runtimeState.item.workItemId)];
892
+ if (gateFailure !== undefined) {
893
+ result = failedTddResult(input, gateFailure, false);
894
+ }
895
+ else
896
+ try {
897
+ const green = await recordGreenEvidenceDetails({
898
+ taskId: runtimeState.item.workItemId,
899
+ step: target.step,
900
+ gate: gate,
901
+ worktree: runtimeState.worktree,
902
+ workspaceDigest: await computeWorkspaceTreeDigest(runtimeState.worktree),
903
+ redEvidence: redEvidence,
904
+ ...(target.internal && previousCycle !== undefined ? { previousCycle } : {}),
905
+ });
906
+ trustedTddEvidence = green.evidence;
907
+ trustedTddCycle = green.cycle;
908
+ result = { ...input.result, status: "completed", summary: "引擎已记录可信 Green Evidence。", artifacts: [], commands: [], evidence: [] };
909
+ }
910
+ catch (error) {
911
+ if (!(error instanceof VerificationError))
912
+ throw error;
913
+ const disposition = isTddVerificationCode(error.code)
914
+ ? tddFailureDisposition({ phase: "green", internal: target.internal }, error.code)
915
+ : "fail-closed";
916
+ if (disposition === "restart-implementation") {
917
+ const restarted = restartTddImplementation(current, runtimeState.item.workItemId);
918
+ const next = await acquireNextLocked({ state: runtimeState, projection: restarted, actor: current.claims[target.stageId].actor, root: input.root, dependencies });
919
+ return { projection: next.projection, value: next.action };
920
+ }
921
+ result = failedTddResult(input, error, disposition === "retry");
922
+ }
923
+ }
924
+ else {
925
+ result = trustedSubmitResult(input.result, await dependencies.executors.assertStep(executorStep).validate(executorStep, input.result, executionProjection));
926
+ }
927
+ if (target.internal && target.step.approval) {
928
+ throw new ApplicationSubmitError("WSSPEC_LOOP_STEP_APPROVAL_UNSUPPORTED", "循环内部 Step 暂不支持审批。 ");
929
+ }
930
+ const actor = current.claims[target.stageId].actor;
931
+ const active = current.contexts[target.stageId];
932
+ const artifactValues = await projectArtifactValues(runtimeState.worktree, result.artifacts);
933
+ const record = {
934
+ workPackage: active.workPackage,
935
+ retryCount: active.retryCount,
936
+ actor,
937
+ stepInstanceId: target.stepInstanceId,
938
+ artifactValues,
939
+ result,
940
+ };
941
+ let projection = {
942
+ ...executionProjection,
943
+ stages: { ...current.stages },
944
+ claims: { ...current.claims },
945
+ contexts: { ...current.contexts, [target.stageId]: record },
946
+ retries: { ...current.retries },
947
+ };
948
+ if (target.step.id === "verify-red" && trustedTddEvidence !== undefined) {
949
+ projection.evidence = { ...projection.evidence, [tddRedEvidenceKey(runtimeState.item.workItemId)]: trustedTddEvidence };
950
+ }
951
+ else if ((target.step.id === "verify-green" || (target.internal && target.step.id === "verify")) && trustedTddEvidence !== undefined && trustedTddCycle !== undefined) {
952
+ const verifyGreen = target.step.id === "verify-green"
953
+ ? active.workPackage
954
+ : projection.contexts["verify-green"]?.workPackage;
955
+ if (verifyGreen === undefined)
956
+ throw new ApplicationSubmitError("WSSPEC_TDD_EVIDENCE_INVALIDATED", "Review-Fix 复验缺少原始 verify-green Attempt。 ");
957
+ const unsignedGate = {
958
+ evidenceId: trustedTddEvidence.evidenceId,
959
+ level: "trusted",
960
+ gateId: "test",
961
+ codeRevision: runtimeState.item.execution.baselineRevision,
962
+ baselineTreeDigest: runtimeState.item.execution.baselineTreeDigest,
963
+ workspaceTreeDigest: trustedTddEvidence.workspaceDigest,
964
+ configDigest: runtimeState.item.execution.configDigest,
965
+ attemptId: verifyGreen.attemptId,
966
+ result: "passed",
967
+ };
968
+ const gateEvidence = { ...unsignedGate, recordHash: evidenceRecordHash(unsignedGate) };
969
+ projection.evidence = {
970
+ ...projection.evidence,
971
+ [tddCycleEvidenceKey(runtimeState.item.workItemId)]: trustedTddCycle,
972
+ [tddGreenEvidenceKey(runtimeState.item.workItemId, trustedTddEvidence.evidenceId)]: trustedTddEvidence,
973
+ [evidenceProjectionKey("verify-green", "test")]: gateEvidence,
974
+ };
975
+ }
976
+ if (target.internal)
977
+ projection.contexts[target.stepInstanceId] = record;
978
+ delete projection.claims[target.stageId];
979
+ const currentRetry = result.status === "failed"
980
+ ? ownProjection(projection.retries, target.stepInstanceId)
981
+ : undefined;
982
+ if (result.status === "failed" && currentRetry === undefined) {
983
+ throw new ApplicationSubmitError("WSSPEC_RETRY_PROJECTION_INVALID", `步骤 ${target.stepInstanceId} 缺少重试投影。 `);
984
+ }
985
+ const profileEvaluation = evaluateSubmitProfileDecision({
986
+ projection,
987
+ result,
988
+ stepId: target.stageId,
989
+ workflow: runtimeState.snapshot.changePolicy.kind,
990
+ });
991
+ projection.profile = profileEvaluation.profile;
992
+ const decision = profileEvaluation.decision;
993
+ let invalidatesTarget = false;
994
+ if (decision !== undefined) {
995
+ const selectedChanged = decision.selected !== decision.previous;
996
+ const activated = activateSelectedProfile(runtimeState, projection, decision, result.status === "failed" ? { preserveCurrentStep: target.stageId } : {});
997
+ projection = activated.projection;
998
+ runtimeState = activated.state;
999
+ profileInvalidatedStepIds = activated.invalidatedStepIds;
1000
+ profileEvent = selectedChanged ? "profile.upgraded" : "profile.selected";
1001
+ invalidatesTarget = activated.invalidatedStepIds.includes(target.stageId);
1002
+ }
1003
+ if (result.status === "failed") {
1004
+ const running = transitionStage(current.stages[target.stageId], { type: "transition", to: "running" });
1005
+ projection.stages[target.stageId] = transitionStage(running, { type: "transition", to: "failed" });
1006
+ const failureCode = result.failureCode;
1007
+ if (!isRetryableStepFailure(failureCode)) {
1008
+ delete projection.retries[target.stepInstanceId];
1009
+ return {
1010
+ projection,
1011
+ value: { action: "blocked", problems: [stepFailureProblem(failureCode, result.summary)] },
1012
+ };
1013
+ }
1014
+ const failedRetry = failRetry(currentRetry);
1015
+ projection.retries[target.stepInstanceId] = failedRetry;
1016
+ return {
1017
+ projection,
1018
+ value: { action: "blocked", problems: [retryFailureProblem(failedRetry, result.summary)] },
1019
+ };
1020
+ }
1021
+ delete projection.retries[target.stepInstanceId];
1022
+ if (target.internal && target.step.id === "fix") {
1023
+ const cycle = projection.evidence[tddCycleEvidenceKey(runtimeState.item.workItemId)];
1024
+ if (cycle !== undefined) {
1025
+ let restart = evaluateReviewFixEvidence({ modifiedFiles: changed, cycle }).action === "restart-cycle";
1026
+ if (!restart) {
1027
+ try {
1028
+ await assertImplementHasTrustedRed({
1029
+ taskId: runtimeState.item.workItemId,
1030
+ commandId: cycle.commandId,
1031
+ worktree: runtimeState.worktree,
1032
+ redEvidence: projection.evidence[tddRedEvidenceKey(runtimeState.item.workItemId)],
1033
+ });
1034
+ }
1035
+ catch (error) {
1036
+ if (!(error instanceof VerificationError) || error.code !== "WSSPEC_TDD_EVIDENCE_INVALIDATED")
1037
+ throw error;
1038
+ restart = true;
1039
+ }
1040
+ }
1041
+ if (restart) {
1042
+ projection = restartTddCycle(projection, runtimeState.item.workItemId);
1043
+ const next = await acquireNextLocked({ state: runtimeState, projection, actor, root: input.root, dependencies });
1044
+ return { projection: next.projection, value: next.action };
1045
+ }
1046
+ }
1047
+ }
1048
+ if (invalidatesTarget) {
1049
+ const next = await acquireNextLocked({ state: runtimeState, projection, actor, root: input.root, dependencies });
1050
+ return { projection: next.projection, value: next.action };
1051
+ }
1052
+ if (target.internal) {
1053
+ projection.stages[target.stageId] = transitionStage(projection.stages[target.stageId], { type: "transition", to: "ready" });
1054
+ delete projection.contexts[target.stageId];
1055
+ const next = await acquireNextLocked({ state: runtimeState, projection, actor, root: input.root, dependencies });
1056
+ return { projection: next.projection, value: next.action };
1057
+ }
1058
+ const running = transitionStage(projection.stages[target.stageId], { type: "transition", to: "running" });
1059
+ const validating = transitionStage(running, { type: "transition", to: "validating" });
1060
+ projection.stages[target.stageId] = validating;
1061
+ if (target.step.approval && !governedActionStep(target.step)) {
1062
+ const artifacts = result.artifacts.filter((candidate) => candidate.path !== undefined);
1063
+ const request = await prepareArtifactApproval({
1064
+ cwd: input.root,
1065
+ workItemId: input.workItemId,
1066
+ stageId: target.stageId,
1067
+ attemptId: input.attemptId,
1068
+ artifacts,
1069
+ actor,
1070
+ now: dependencies.now(),
1071
+ });
1072
+ projection = {
1073
+ ...projection,
1074
+ workItem: transitionWorkItem(projection.workItem, { type: "transition", to: "awaiting_approval" }),
1075
+ stages: { ...projection.stages, [target.stageId]: transitionStage(validating, { type: "transition", to: "awaiting_approval" }) },
1076
+ approvals: { ...projection.approvals, [request.requestId]: request },
1077
+ };
1078
+ return { projection, value: approvalAction(runtimeState, request) };
1079
+ }
1080
+ projection.stages[target.stageId] = transitionStage(validating, { type: "transition", to: "succeeded" });
1081
+ const next = await acquireNextLocked({ state: runtimeState, projection, actor, root: input.root, dependencies });
1082
+ return { projection: next.projection, value: next.action };
1083
+ },
1084
+ });
1085
+ if (action.action === "completed" && action.summary.status === "closed") {
1086
+ await recoverControlPlane({ cwd: input.root, workItemId: input.workItemId });
1087
+ }
1088
+ return action;
1089
+ }
1090
+ //# sourceMappingURL=submit.js.map