@starci/skills 1.1.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 (321) hide show
  1. package/INDEX.md +74 -0
  2. package/INDEX.vi.md +75 -0
  3. package/README.md +44 -0
  4. package/README.vi.md +43 -0
  5. package/SKILL.md +135 -0
  6. package/SKILL.vi.md +128 -0
  7. package/alias/INDEX.md +104 -0
  8. package/alias/INDEX.vi.md +104 -0
  9. package/alias/alias.json +297 -0
  10. package/bin/starci-skills.mjs +219 -0
  11. package/knowledge/INDEX.md +22 -0
  12. package/knowledge/INDEX.vi.md +22 -0
  13. package/knowledge/grammars/starci/DNA.md +146 -0
  14. package/knowledge/grammars/starci/DNA.vi.md +146 -0
  15. package/knowledge/grammars/starci/INDEX.md +25 -0
  16. package/knowledge/grammars/starci/INDEX.vi.md +25 -0
  17. package/knowledge/grammars/starci/family.md +50 -0
  18. package/knowledge/grammars/starci/family.vi.md +50 -0
  19. package/knowledge/grammars/starci/idioms.md +120 -0
  20. package/knowledge/grammars/starci/idioms.vi.md +121 -0
  21. package/knowledge/grammars/starci/playbook.md +36 -0
  22. package/knowledge/grammars/starci/playbook.vi.md +35 -0
  23. package/knowledge/patterns/be/INDEX.md +29 -0
  24. package/knowledge/patterns/be/INDEX.vi.md +29 -0
  25. package/knowledge/patterns/be/comment.md +80 -0
  26. package/knowledge/patterns/be/comment.vi.md +80 -0
  27. package/knowledge/patterns/be/error.md +86 -0
  28. package/knowledge/patterns/be/error.vi.md +86 -0
  29. package/knowledge/patterns/be/folder.md +87 -0
  30. package/knowledge/patterns/be/folder.vi.md +86 -0
  31. package/knowledge/patterns/be/function.md +80 -0
  32. package/knowledge/patterns/be/function.vi.md +80 -0
  33. package/knowledge/patterns/be/imports.md +79 -0
  34. package/knowledge/patterns/be/imports.vi.md +79 -0
  35. package/knowledge/patterns/be/naming.md +87 -0
  36. package/knowledge/patterns/be/naming.vi.md +87 -0
  37. package/knowledge/patterns/be/test.md +79 -0
  38. package/knowledge/patterns/be/test.vi.md +79 -0
  39. package/knowledge/patterns/be/typing.md +73 -0
  40. package/knowledge/patterns/be/typing.vi.md +73 -0
  41. package/knowledge/patterns/fe/INDEX.md +29 -0
  42. package/knowledge/patterns/fe/INDEX.vi.md +29 -0
  43. package/knowledge/patterns/fe/comment.md +70 -0
  44. package/knowledge/patterns/fe/comment.vi.md +70 -0
  45. package/knowledge/patterns/fe/error.md +61 -0
  46. package/knowledge/patterns/fe/error.vi.md +61 -0
  47. package/knowledge/patterns/fe/folder.md +98 -0
  48. package/knowledge/patterns/fe/folder.vi.md +95 -0
  49. package/knowledge/patterns/fe/function.md +70 -0
  50. package/knowledge/patterns/fe/function.vi.md +70 -0
  51. package/knowledge/patterns/fe/imports.md +87 -0
  52. package/knowledge/patterns/fe/imports.vi.md +87 -0
  53. package/knowledge/patterns/fe/naming.md +77 -0
  54. package/knowledge/patterns/fe/naming.vi.md +77 -0
  55. package/knowledge/patterns/fe/test.md +73 -0
  56. package/knowledge/patterns/fe/test.vi.md +73 -0
  57. package/knowledge/patterns/fe/typing.md +67 -0
  58. package/knowledge/patterns/fe/typing.vi.md +67 -0
  59. package/knowledge/ui/INDEX.md +101 -0
  60. package/knowledge/ui/INDEX.vi.md +101 -0
  61. package/knowledge/ui/composition/INDEX.md +60 -0
  62. package/knowledge/ui/composition/INDEX.vi.md +63 -0
  63. package/knowledge/ui/composition/accent.md +73 -0
  64. package/knowledge/ui/composition/accent.vi.md +74 -0
  65. package/knowledge/ui/composition/action.md +55 -0
  66. package/knowledge/ui/composition/action.vi.md +56 -0
  67. package/knowledge/ui/composition/coverage.md +37 -0
  68. package/knowledge/ui/composition/coverage.vi.md +37 -0
  69. package/knowledge/ui/composition/cta.md +79 -0
  70. package/knowledge/ui/composition/cta.vi.md +79 -0
  71. package/knowledge/ui/composition/feedback.md +55 -0
  72. package/knowledge/ui/composition/feedback.vi.md +56 -0
  73. package/knowledge/ui/composition/hierarchy.md +83 -0
  74. package/knowledge/ui/composition/hierarchy.vi.md +82 -0
  75. package/knowledge/ui/composition/layout.md +91 -0
  76. package/knowledge/ui/composition/layout.vi.md +93 -0
  77. package/knowledge/ui/composition/responsive.md +67 -0
  78. package/knowledge/ui/composition/responsive.vi.md +67 -0
  79. package/knowledge/ui/composition/state.md +105 -0
  80. package/knowledge/ui/composition/state.vi.md +106 -0
  81. package/knowledge/ui/presentation/INDEX.md +94 -0
  82. package/knowledge/ui/presentation/INDEX.vi.md +69 -0
  83. package/knowledge/ui/presentation/boundary.md +170 -0
  84. package/knowledge/ui/presentation/boundary.vi.md +169 -0
  85. package/knowledge/ui/presentation/font.md +155 -0
  86. package/knowledge/ui/presentation/font.vi.md +156 -0
  87. package/knowledge/ui/presentation/gap.md +181 -0
  88. package/knowledge/ui/presentation/gap.vi.md +181 -0
  89. package/knowledge/ui/presentation/margin.md +168 -0
  90. package/knowledge/ui/presentation/margin.vi.md +171 -0
  91. package/knowledge/ui/presentation/measure.md +174 -0
  92. package/knowledge/ui/presentation/measure.vi.md +178 -0
  93. package/knowledge/ui/presentation/overflow.md +142 -0
  94. package/knowledge/ui/presentation/overflow.vi.md +142 -0
  95. package/knowledge/ui/presentation/padding.md +293 -0
  96. package/knowledge/ui/presentation/padding.vi.md +292 -0
  97. package/knowledge/ui/presentation/surface.md +166 -0
  98. package/knowledge/ui/presentation/surface.vi.md +166 -0
  99. package/knowledge/ui/presentation/text-flow.md +123 -0
  100. package/knowledge/ui/presentation/text-flow.vi.md +123 -0
  101. package/knowledge/ui/presentation/tone.md +114 -0
  102. package/knowledge/ui/presentation/tone.vi.md +113 -0
  103. package/knowledge/ui/proof/INDEX.md +55 -0
  104. package/knowledge/ui/proof/INDEX.vi.md +55 -0
  105. package/knowledge/ui/proof/accessibility.md +67 -0
  106. package/knowledge/ui/proof/accessibility.vi.md +68 -0
  107. package/knowledge/ui/proof/contrast.md +58 -0
  108. package/knowledge/ui/proof/contrast.vi.md +57 -0
  109. package/knowledge/ui/proof/focus.md +76 -0
  110. package/knowledge/ui/proof/focus.vi.md +76 -0
  111. package/knowledge/ui/proof/motion.md +63 -0
  112. package/knowledge/ui/proof/motion.vi.md +64 -0
  113. package/knowledge/ui/proof/render-truth.md +63 -0
  114. package/knowledge/ui/proof/render-truth.vi.md +63 -0
  115. package/operators/INDEX.md +199 -0
  116. package/operators/INDEX.vi.md +199 -0
  117. package/operators/architecture-decide/errors.json +115 -0
  118. package/operators/architecture-decide/operator.json +20 -0
  119. package/operators/architecture-decide/operator.md +133 -0
  120. package/operators/architecture-decide/operator.vi.md +130 -0
  121. package/operators/architecture-decide/self-test.mjs +221 -0
  122. package/operators/architecture-decide/validate.mjs +112 -0
  123. package/operators/backend-source-apply/errors.json +66 -0
  124. package/operators/backend-source-apply/operator.json +20 -0
  125. package/operators/backend-source-apply/operator.md +189 -0
  126. package/operators/backend-source-apply/operator.vi.md +185 -0
  127. package/operators/backend-source-apply/self-test.mjs +257 -0
  128. package/operators/backend-source-apply/validate.mjs +240 -0
  129. package/operators/business-decide/errors.json +78 -0
  130. package/operators/business-decide/operator.json +20 -0
  131. package/operators/business-decide/operator.md +176 -0
  132. package/operators/business-decide/operator.vi.md +174 -0
  133. package/operators/business-decide/self-test.mjs +225 -0
  134. package/operators/business-decide/validate.mjs +277 -0
  135. package/operators/content-generate/errors.json +106 -0
  136. package/operators/content-generate/operator.json +21 -0
  137. package/operators/content-generate/operator.md +155 -0
  138. package/operators/content-generate/operator.vi.md +155 -0
  139. package/operators/content-generate/self-test.mjs +288 -0
  140. package/operators/content-generate/validate.mjs +196 -0
  141. package/operators/errors.json +178 -0
  142. package/operators/frontend-direction-decide/errors.json +135 -0
  143. package/operators/frontend-direction-decide/operator.json +21 -0
  144. package/operators/frontend-direction-decide/operator.md +167 -0
  145. package/operators/frontend-direction-decide/operator.vi.md +167 -0
  146. package/operators/frontend-direction-decide/self-test.mjs +226 -0
  147. package/operators/frontend-direction-decide/validate.mjs +146 -0
  148. package/operators/frontend-presentation-resolve/errors.json +42 -0
  149. package/operators/frontend-presentation-resolve/operator.json +19 -0
  150. package/operators/frontend-presentation-resolve/operator.md +142 -0
  151. package/operators/frontend-presentation-resolve/operator.vi.md +140 -0
  152. package/operators/frontend-presentation-resolve/self-test.mjs +163 -0
  153. package/operators/frontend-presentation-resolve/validate.mjs +143 -0
  154. package/operators/frontend-source-apply/errors.json +30 -0
  155. package/operators/frontend-source-apply/operator.json +20 -0
  156. package/operators/frontend-source-apply/operator.md +127 -0
  157. package/operators/frontend-source-apply/operator.vi.md +126 -0
  158. package/operators/frontend-source-apply/self-test.mjs +214 -0
  159. package/operators/frontend-source-apply/validate.mjs +133 -0
  160. package/operators/frontend-surface-audit/errors.json +5 -0
  161. package/operators/frontend-surface-audit/operator.json +22 -0
  162. package/operators/frontend-surface-audit/operator.md +121 -0
  163. package/operators/frontend-surface-audit/operator.vi.md +121 -0
  164. package/operators/frontend-surface-audit/self-test.mjs +185 -0
  165. package/operators/frontend-surface-audit/validate.mjs +127 -0
  166. package/operators/git-publish/errors.json +54 -0
  167. package/operators/git-publish/operator.json +20 -0
  168. package/operators/git-publish/operator.md +180 -0
  169. package/operators/git-publish/operator.vi.md +178 -0
  170. package/operators/git-publish/self-test.mjs +177 -0
  171. package/operators/git-publish/validate.mjs +106 -0
  172. package/operators/platform-operate/errors.json +90 -0
  173. package/operators/platform-operate/operator.json +22 -0
  174. package/operators/platform-operate/operator.md +162 -0
  175. package/operators/platform-operate/operator.vi.md +160 -0
  176. package/operators/platform-operate/self-test.mjs +202 -0
  177. package/operators/platform-operate/validate.mjs +193 -0
  178. package/operators/quality-verify/errors.json +54 -0
  179. package/operators/quality-verify/operator.json +20 -0
  180. package/operators/quality-verify/operator.md +185 -0
  181. package/operators/quality-verify/operator.vi.md +177 -0
  182. package/operators/quality-verify/self-test.mjs +210 -0
  183. package/operators/quality-verify/validate.mjs +197 -0
  184. package/operators/release-deploy/errors.json +158 -0
  185. package/operators/release-deploy/operator.json +23 -0
  186. package/operators/release-deploy/operator.md +183 -0
  187. package/operators/release-deploy/operator.vi.md +181 -0
  188. package/operators/release-deploy/self-test.mjs +235 -0
  189. package/operators/release-deploy/validate.mjs +129 -0
  190. package/operators/uat-verify/errors.json +78 -0
  191. package/operators/uat-verify/operator.json +25 -0
  192. package/operators/uat-verify/operator.md +162 -0
  193. package/operators/uat-verify/operator.vi.md +161 -0
  194. package/operators/uat-verify/self-test.mjs +270 -0
  195. package/operators/uat-verify/validate.mjs +202 -0
  196. package/operators/workspace-bind/errors.json +90 -0
  197. package/operators/workspace-bind/operator.json +21 -0
  198. package/operators/workspace-bind/operator.md +148 -0
  199. package/operators/workspace-bind/operator.vi.md +148 -0
  200. package/operators/workspace-bind/self-test.mjs +205 -0
  201. package/operators/workspace-bind/validate.mjs +152 -0
  202. package/package.json +50 -0
  203. package/readiness/initialization/workspaces/commit-policy.json +91 -0
  204. package/readiness/initialization/workspaces/config.schema.json +35 -0
  205. package/readiness/initialization/workspaces/device-state.schema.json +54 -0
  206. package/readiness/initialization/workspaces/local-route.schema.json +206 -0
  207. package/readiness/initialization/workspaces/portable-route.schema.json +200 -0
  208. package/resources/INDEX.md +96 -0
  209. package/resources/INDEX.vi.md +99 -0
  210. package/resources/agents/profiles/claude.json +131 -0
  211. package/resources/agents/profiles/openai.json +131 -0
  212. package/resources/orchestrator.json +71 -0
  213. package/resources/tools.json +85 -0
  214. package/routing.json +272 -0
  215. package/scripts/alias-registry.mjs +31 -0
  216. package/scripts/device-state.mjs +497 -0
  217. package/scripts/device-state.spec.mjs +18 -0
  218. package/scripts/errors-registry.mjs +68 -0
  219. package/scripts/generate-alias-doc.mjs +66 -0
  220. package/scripts/generate-grammar-dna.mjs +387 -0
  221. package/scripts/generate-operators-index.mjs +95 -0
  222. package/scripts/generate-presentation-owned.mjs +681 -0
  223. package/scripts/install-cli.spec.mjs +74 -0
  224. package/scripts/json-schema.mjs +94 -0
  225. package/scripts/operator-md.mjs +96 -0
  226. package/scripts/run-operator-self-tests.mjs +36 -0
  227. package/scripts/validate-alias.mjs +165 -0
  228. package/scripts/validate-defaults.mjs +72 -0
  229. package/scripts/validate-knowledge-citations.mjs +90 -0
  230. package/scripts/validate-operator.mjs +125 -0
  231. package/scripts/validate-request.mjs +80 -0
  232. package/scripts/validate-resources.mjs +117 -0
  233. package/scripts/validate-response.mjs +141 -0
  234. package/scripts/validate-routing.mjs +91 -0
  235. package/scripts/validate-step.mjs +50 -0
  236. package/scripts/validate-templates.mjs +226 -0
  237. package/scripts/validate-templates.spec.mjs +144 -0
  238. package/scripts/validate-workflows.mjs +106 -0
  239. package/scripts/workspace-portable.mjs +389 -0
  240. package/scripts/workspace-portable.spec.mjs +246 -0
  241. package/templates/README.md +47 -0
  242. package/templates/README.vi.md +45 -0
  243. package/templates/changes.example.md +27 -0
  244. package/templates/grammars.template.md +34 -0
  245. package/templates/kinds/architecture-decision.contract.json +14 -0
  246. package/templates/kinds/architecture-decision.skeleton.md +52 -0
  247. package/templates/kinds/backend-source-application.contract.json +10 -0
  248. package/templates/kinds/backend-source-application.skeleton.md +34 -0
  249. package/templates/kinds/business-promise-authority.contract.json +13 -0
  250. package/templates/kinds/business-promise-authority.skeleton.md +67 -0
  251. package/templates/kinds/capture.schema.json +33 -0
  252. package/templates/kinds/changes.contract.json +10 -0
  253. package/templates/kinds/changes.skeleton.md +26 -0
  254. package/templates/kinds/checks.schema.json +44 -0
  255. package/templates/kinds/claims.schema.json +165 -0
  256. package/templates/kinds/conformance.schema.json +15 -0
  257. package/templates/kinds/content-brief.contract.json +12 -0
  258. package/templates/kinds/content-brief.skeleton.md +45 -0
  259. package/templates/kinds/content-generation-receipt.contract.json +11 -0
  260. package/templates/kinds/content-generation-receipt.skeleton.md +40 -0
  261. package/templates/kinds/content-review.contract.json +11 -0
  262. package/templates/kinds/content-review.skeleton.md +45 -0
  263. package/templates/kinds/contract.schema.json +28 -0
  264. package/templates/kinds/coverage-matrix.schema.json +86 -0
  265. package/templates/kinds/coverage.schema.json +62 -0
  266. package/templates/kinds/current-state.schema.json +40 -0
  267. package/templates/kinds/delta.schema.json +95 -0
  268. package/templates/kinds/e2e.schema.json +63 -0
  269. package/templates/kinds/frontend-direction-decision.contract.json +93 -0
  270. package/templates/kinds/frontend-direction-decision.skeleton.md +68 -0
  271. package/templates/kinds/frontend-presentation-resolution.contract.json +11 -0
  272. package/templates/kinds/frontend-presentation-resolution.skeleton.md +32 -0
  273. package/templates/kinds/frontend-source-application.contract.json +10 -0
  274. package/templates/kinds/frontend-source-application.skeleton.md +30 -0
  275. package/templates/kinds/frontend-surface-audit.contract.json +52 -0
  276. package/templates/kinds/frontend-surface-audit.skeleton.md +32 -0
  277. package/templates/kinds/gate-result.schema.json +64 -0
  278. package/templates/kinds/git-publication.contract.json +82 -0
  279. package/templates/kinds/git-publication.skeleton.md +61 -0
  280. package/templates/kinds/independent-critique.contract.json +9 -0
  281. package/templates/kinds/independent-critique.skeleton.md +28 -0
  282. package/templates/kinds/inventory.schema.json +35 -0
  283. package/templates/kinds/model.schema.json +99 -0
  284. package/templates/kinds/mutations.schema.json +65 -0
  285. package/templates/kinds/platform-operation-receipt.contract.json +76 -0
  286. package/templates/kinds/platform-operation-receipt.skeleton.md +54 -0
  287. package/templates/kinds/probes.schema.json +130 -0
  288. package/templates/kinds/proof.schema.json +17 -0
  289. package/templates/kinds/quality-verification.contract.json +87 -0
  290. package/templates/kinds/quality-verification.skeleton.md +58 -0
  291. package/templates/kinds/release-deployment.contract.json +85 -0
  292. package/templates/kinds/release-deployment.skeleton.md +67 -0
  293. package/templates/kinds/route.schema.json +293 -0
  294. package/templates/kinds/stack-model.schema.json +90 -0
  295. package/templates/kinds/uat-capture.schema.json +35 -0
  296. package/templates/kinds/uat-flow-verification.contract.json +11 -0
  297. package/templates/kinds/uat-flow-verification.skeleton.md +47 -0
  298. package/templates/kinds/uat-snapshot.schema.json +96 -0
  299. package/templates/kinds/uat-verdicts.schema.json +41 -0
  300. package/templates/kinds/ui-coverage.schema.json +77 -0
  301. package/templates/kinds/verdicts.schema.json +39 -0
  302. package/templates/kinds/workspace-route-binding.contract.json +12 -0
  303. package/templates/kinds/workspace-route-binding.skeleton.md +60 -0
  304. package/templates/kinds/writes.schema.json +37 -0
  305. package/templates/operator.template.md +73 -0
  306. package/templates/patterns.template.md +31 -0
  307. package/templates/step/request.schema.json +42 -0
  308. package/templates/step/response.schema.json +144 -0
  309. package/templates/ui-composition.template.md +36 -0
  310. package/templates/ui-presentation.template.md +57 -0
  311. package/templates/ui-proof.template.md +34 -0
  312. package/workflows/README.md +37 -0
  313. package/workflows/README.vi.md +37 -0
  314. package/workflows/backend-feature.json +59 -0
  315. package/workflows/content-unit.json +19 -0
  316. package/workflows/frontend-new-surface.json +81 -0
  317. package/workflows/frontend-reconstruct.json +67 -0
  318. package/workflows/frontend-refine.json +67 -0
  319. package/workflows/frontend-with-uat.json +78 -0
  320. package/workflows/full-feature.json +104 -0
  321. package/workflows/release.json +29 -0
@@ -0,0 +1,178 @@
1
+ # git.publish
2
+
3
+ ## Việc
4
+
5
+ Publish một ranh giới Git đã được duyệt từ đúng commit mà quality đã kiểm định, theo ngữ nghĩa
6
+ non-force và chỉ fast-forward, và dừng bằng một lỗi có kiểu thay vì với tay sang một đường tắt.
7
+
8
+ ## Nó không quyết gì về bản thân thay đổi
9
+
10
+ Việc thay đổi có đúng hay không đã được quyết bởi các cổng sinh ra biên bản `quality-verification`,
11
+ còn việc nó có được publish hay không đã được quyết bởi phê duyệt. Operator này chỉ thực hiện thao
12
+ tác ghi, hoặc báo chính xác vì sao nó đã không ghi. Biên bản nó để lại chứng minh rằng đúng commit
13
+ này đã tới đúng ref này dưới đúng những hook này; nó không mang phán quyết, không mang điểm, và
14
+ không mang khẳng định nào rằng có thứ gì đã pass.
15
+
16
+ ## Route được đọc, không bao giờ được tự tìm lại
17
+
18
+ Operator này không tự phân giải một project ra thành một checkout. `workspace.bind` làm việc đó, và
19
+ biên bản của nó tới đây đã ràng sẵn. Một lần publish tự phân giải đường dẫn của mình có thể publish
20
+ từ một checkout không ai kiểm, đúng cái hỏng mà sự tách bạch này sinh ra để chặn. Một biên bản route
21
+ có trạng thái khác `bound`, hoặc gọi tên một project khác, là `ROUTE_UNVERIFIED`.
22
+
23
+ ## Phê duyệt luôn là một con người
24
+
25
+ Publish đẩy công việc ra khỏi phiên và vào một nơi người khác pull về, nên `approval` không có mặc
26
+ định: một hành động hướng ra ngoài luôn là thứ có người nói đồng ý. Bằng chứng hoàn thành ghi lại
27
+ những cổng mà ranh giới đã qua; nó là bằng chứng công việc đã xong chứ không bao giờ là bằng chứng nó
28
+ được phép publish. Đúng một phê duyệt phải gọi tên ranh giới này. Một phê duyệt cấp cho ranh giới khác
29
+ là một phê duyệt thật của người khác, và đó chính là cách thay đổi chưa duyệt đi ké theo thay đổi đã
30
+ duyệt; đó là `APPROVAL_MISSING`.
31
+
32
+ ## Commit được publish là commit đã kiểm định
33
+
34
+ Đầu vào `quality-verification` đã đo đúng một commit. Chính commit đó, và không commit nào khác, là
35
+ thứ lần publish này đẩy đi: một head nhỉnh hơn commit đã kiểm định một nhịp mang theo một thay đổi
36
+ không cổng nào từng thấy. Biên bản ghi commit đã kiểm định cạnh head đã publish để sau này so được
37
+ hai thứ mà không phải chạy lại gì.
38
+
39
+ ## Không force là chuyện cấu trúc, không phải lời khuyên
40
+
41
+ Force push, viết lại lịch sử, `reset --hard`, `clean`, `stash`, xóa nhánh và bỏ qua hook không phải
42
+ là những field có mặc định; chúng vắng mặt khỏi từ vựng của operator này, nên không request nào, ở
43
+ bất kỳ tổ hợp và biện minh nào, xin được một cái. Lý do là mỗi thứ trong số đó cám dỗ nhất đúng vào
44
+ lúc một lần publish vừa hỏng: một push bị từ chối, một hook đỏ và một file bẩn bất ngờ đều có một câu
45
+ trả lời một lệnh rất hiển nhiên, và nó phá công việc hoặc bằng chứng của người khác. `reset --hard`
46
+ phá công việc chưa commit mà không biên bản nào ghi, `clean` phá file chưa track chưa ai xem, còn
47
+ `stash` giấu một ranh giới bẩn thay vì giải quyết nó. Làm cho lời xin không diễn đạt được là gỡ quyết
48
+ định ra khỏi đúng khoảnh khắc nó sẽ bị quyết sai. Chế độ publish luôn là chỉ fast-forward, và mọi lần
49
+ publish đều ghi rằng nó đã không bị force.
50
+
51
+ ## Nhánh phiên được merge, không bao giờ bị rebase
52
+
53
+ Người sản xuất đã không viết trên nhánh mà người ta đang checkout. Nó viết trên nhánh phiên
54
+ `session/<sessionId>` của checkout đã route, trong một git worktree dựng từ head đã đóng băng, và
55
+ commit tập ghi của mình đúng một lần. Operator này merge nhánh phiên ấy vào nhánh đích trước khi
56
+ push. Khi nhánh đích chưa dịch chuyển kể từ base của phiên, merge là fast-forward và không có gì mới
57
+ sinh ra. Khi nhánh đích đã dịch chuyển, một merge commit chỉ được phép dưới hai điều kiện đồng thời:
58
+ merge không sinh xung đột, và những cổng mà bản kiểm định đã nêu được chạy lại trên kết quả merge và
59
+ đều pass. Xung đột là `NON_FAST_FORWARD`, nó terminate, và một con người giải quyết; operator không
60
+ bao giờ rebase, không bao giờ force, và không bao giờ chạy với hook bị tắt.
61
+
62
+ ## Hook chặn là một kết quả
63
+
64
+ Hook luôn được thi hành, và `pre-push` là cổng cuối trước remote. Một hook hỏng sinh ra `HOOK_BLOCKED`
65
+ gọi tên hook đó, và phần delta gỡ được nó là một ranh giới đã sửa cùng một head mới. Nó không bao giờ
66
+ là lý do để chạy lại push với hook bị tắt, để dời thay đổi sang một nhánh có hook nhẹ hơn, hay để
67
+ commit chính cấu hình hook ra khỏi đường. Một lần publish mang theo kết quả hook hỏng, hoặc thiếu hẳn
68
+ kết quả `pre-push`, đều bị từ chối.
69
+
70
+ ## Push bị từ chối là một kết quả
71
+
72
+ Khi remote mang những commit mà ref cục bộ không có, push không phải fast-forward. Operator trả về
73
+ `NON_FAST_FORWARD` gọi tên remote head nó quan sát được. Nó không rebase lên đó, không amend một
74
+ commit cho push áp được, không squash phần phân kỳ đi, không force, không lease-force. Hòa giải lịch
75
+ sử phân kỳ là đổi thứ người khác đã pull về, nên nó thuộc về người sở hữu nhánh, và người đó không
76
+ phải operator này.
77
+
78
+ ## Ranh giới là chính xác
79
+
80
+ Ranh giới là toàn bộ những gì lần publish này sở hữu, và Đầu vào `changes` nêu các path nằm trong đó.
81
+ Bất cứ thứ gì bẩn ở ngoài nó là công việc ranh giới này không sở hữu, và một lần publish mang nó theo
82
+ là publish thay đổi chưa duyệt của người khác; đó là `DIRTY_OUTSIDE_BOUNDARY`. Dưới chính sách cấm
83
+ nhánh worktree, head được publish nằm trên nhánh mutation đã route, và một head trên nhánh khác là
84
+ `BRANCH_POLICY_VIOLATION`. Một lần publish không đẩy được gì lên không phải là publish: head đã publish
85
+ đi trước upstream của nó và ref thực sự đã dịch chuyển.
86
+
87
+ ## Tag là thứ được hỏi, hoặc không có
88
+
89
+ `tag` mặc định null, nên một lần publish chỉ mang tag tiếp nối khi có người nêu tên nó, và tag ấy là
90
+ annotated và trỏ vào một head mà chính lần publish này đã đẩy. Một tag trên head mà lần chạy này
91
+ không đẩy là một cái nhãn mà commit của người khác đang phải mang.
92
+
93
+ ## Dọn dẹp là một phần của publish
94
+
95
+ Sau khi push thành công, worktree phiên và nhánh phiên được xóa cùng thư mục phiên, vì bằng chứng
96
+ chúng giữ vừa trở thành một commit đã publish. Một phiên bị chặn giữ lại cả hai: bằng chứng về việc
97
+ đã thử làm gì nằm ở đó cho tới khi có người đọc.
98
+
99
+ ## Ranh giới ghi
100
+
101
+ Context chỉ đọc, trừ merge và push. Operator chỉ ghi vào `response/` của nhánh mình, nhánh đích của
102
+ checkout đã route, và cú push tới `@remote/git/<project>/<role>`: head đã duyệt trên ref đã route, và
103
+ tối đa một tag tiếp nối annotated trỏ vào một head mà chính lần publish này đã đẩy. Nó không force
104
+ push, không lease-force push, không viết lại lịch sử đã publish; không chạy reset, clean hay stash;
105
+ không xóa nhánh nào ngoài nhánh phiên nó đang dọn; không bỏ qua, bỏ sót hay tắt một Git hook; không
106
+ amend, rebase hay squash một commit để một push bị từ chối đi lọt; không publish một head ngoài ranh
107
+ giới đã duyệt; và không publish khi chưa có route đã kiểm và một phê duyệt ràng vào đúng ranh giới này.
108
+
109
+ ## Context
110
+
111
+ | Alias | Bind | Bắt buộc |
112
+ | --- | --- | --- |
113
+ | `@workspaces/local/routes/<project>/<role>` | checkout, nhánh phiên và nhánh đích của nó, đọc tại head đã đóng băng | có |
114
+ | `@workspaces/<project>/<role>/husky` | `pre-commit` và `pre-push`, luôn chạy | có |
115
+ | `@remote/git/<project>/<role>` | đích publish và remote head quan sát được lúc gọi | có |
116
+
117
+ ## Đầu vào
118
+
119
+ | Kind | Từ đâu | Bắt buộc |
120
+ | --- | --- | --- |
121
+ | `workspace-route-binding` | `workspace.bind`; một lần publish không bao giờ tự phân giải checkout của mình | có |
122
+ | `changes` | `backend.source.apply` hoặc `frontend.source.apply`, đúng tập file mà lần publish này mang | có |
123
+ | `quality-verification` | `quality.verify`, biên bản có commit đã đo chính là commit lần publish này đẩy | có |
124
+
125
+ ## Yêu cầu
126
+
127
+ | Field | Kiểu | Mặc định | Hỏi |
128
+ | --- | --- | --- | --- |
129
+ | `boundary` | id | — | Đúng một ranh giới được publish, đúng như phê duyệt gọi tên nó |
130
+ | `approval` | id | — | Bản ghi phê duyệt phủ lên ranh giới này; hoàn thành không phải phê duyệt |
131
+ | `tag` | `{name, message}` | null | Một tag tiếp nối annotated trên head mà lần publish này đẩy, hoặc không có |
132
+ | `resume` | token | null | Token của nhánh bị chặn khi vào lại sau một mã dừng |
133
+
134
+ ## Các bước
135
+
136
+ | # | Bước | Tham số | Đọc | Ghi | Dừng với |
137
+ | --- | --- | --- | --- | --- | --- |
138
+ | 1 | Kiểm cổng vào và resume | `resume` | `request/request.json`, @workspaces/local/routes/<project>/<role> tại head đã đóng băng, @remote/git/<project>/<role> như quan sát được | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
139
+ | 2 | Ràng route | — | Đầu vào `workspace-route-binding`: checkout đã kiểm, head và chính sách đã route của nó, cùng @workspaces/local/routes/<project>/<role> | — | `ROUTE_UNVERIFIED` |
140
+ | 3 | Ràng phê duyệt vào đúng ranh giới này | `boundary`, `approval` | phần requirements của `request/request.json`, Đầu vào `changes` là tập file, Đầu vào `quality-verification` là commit đã đo | — | `APPROVAL_MISSING` |
141
+ | 4 | Kiểm cây: bẩn ngoài ranh giới, chính sách nhánh | — | @workspaces/local/routes/<project>/<role>, các path bẩn, mọi nhánh, chính sách đã route | — | `DIRTY_OUTSIDE_BOUNDARY`, `BRANCH_POLICY_VIOLATION` |
142
+ | 5 | Chạy hook | — | @workspaces/<project>/<role>/husky: các hook đã cài, trong đó có `pre-push` | @tools/shell | `HOOK_BLOCKED` |
143
+ | 6 | Merge nhánh phiên vào nhánh đích | — | @workspaces/local/routes/<project>/<role> cho head đích, base phiên và head phiên | @workspaces/local/routes/<project>/<role>, nhánh đích của checkout đó, @tools/git | `NON_FAST_FORWARD` |
144
+ | 7 | Push non-force, chỉ fast-forward | — | @workspaces/local/routes/<project>/<role> cho head đã duyệt, @remote/git/<project>/<role> tại remote head quan sát được, @tools/ci | @remote/git/<project>/<role>, @tools/git | `NON_FAST_FORWARD` |
145
+ | 8 | Push tag tiếp nối | `tag` | @workspaces/local/routes/<project>/<role> cho head mà lần publish này đã đẩy | @remote/git/<project>/<role>, @tools/git | — |
146
+ | 9 | Xóa worktree và nhánh phiên, viết biên bản và phát | — | mọi thứ ở trên | @workspaces/local/routes/<project>/<role>, `response/response.md`, `response/response.json`, @tools/git | — |
147
+
148
+ Tạo một remote ref và fast-forward một remote ref là hai hành động khác nhau với hai người duyệt khác
149
+ nhau, nên head đã publish ghi lại nó đã làm cái nào. Một lần resume bắt đầu lại từ cổng vào, chỉ dùng
150
+ lại quan sát có fingerprint không đổi, và tiêu thụ đúng phần delta; một lần resume không thêm head,
151
+ phê duyệt, hook hay thay đổi remote nào là `NO_PROGRESS`, và một remote đã quan sát lại phải tới dưới
152
+ dạng một remote head mới vì cùng một quan sát không thể cho một kết quả khác.
153
+
154
+ ## Đầu ra
155
+
156
+ | Kind | File | Kiểu | Bắt buộc |
157
+ | --- | --- | --- | --- |
158
+ | `git-publication` | `response/response.md` | md | có |
159
+
160
+ ## Dừng
161
+
162
+ | Code | Xử lý |
163
+ | --- | --- |
164
+ | `INVALID_INPUT` | terminate |
165
+ | `SOURCE_DRIFT` | terminate |
166
+ | `NO_PROGRESS` | terminate |
167
+ | `ROUTE_UNVERIFIED` | terminate |
168
+ | `APPROVAL_MISSING` | terminate |
169
+ | `BRANCH_POLICY_VIOLATION` | terminate |
170
+ | `DIRTY_OUTSIDE_BOUNDARY` | terminate |
171
+ | `HOOK_BLOCKED` | terminate |
172
+ | `NON_FAST_FORWARD` | terminate |
173
+
174
+ ## Kế tiếp
175
+
176
+ | Khi | Operator |
177
+ | --- | --- |
178
+ | ranh giới đã publish và head phải tới được một môi trường | `release.deploy` |
@@ -0,0 +1,177 @@
1
+ // Proves validate.mjs on a synthetic session branch: one fast-forward publication, one merge-commit
2
+ // publication, one that carries the continuation tag it was asked for, one blocked on a rejected
3
+ // push, and one mutation per law, each of which must fail with a line that names the defect.
4
+ import assert from 'node:assert/strict';
5
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
6
+ import { tmpdir } from 'node:os';
7
+ import path from 'node:path';
8
+ import { validateGitPublishStep } from './validate.mjs';
9
+
10
+ const HEAD = '1'.repeat(40);
11
+ const REMOTE = '2'.repeat(40);
12
+ const OTHER = '3'.repeat(40);
13
+ const BOUNDARY = 'api.core';
14
+ const APPROVAL = '@worktrees/businesses/features/api-core/model.json#approval';
15
+ const SESSION = 'session/s-test';
16
+ const TAG = { name: 'v1.4.0', message: 'continuation' };
17
+
18
+ function responseMd({
19
+ boundary = BOUNDARY, approval = APPROVAL, mode = 'fast-forward-only', forced = 'no', merge = 'fast-forward',
20
+ verified = HEAD, heads = [['`@workspaces/be`', '`mtp`', HEAD, REMOTE, '4']], hooks = [['`pre-push`', '`.husky/pre-push`', 'passed']],
21
+ tag = null, cleanup = 'worktree and session branch removed',
22
+ findings = [['HOOK_ENFORCED', '`pre-push`', 'the hook ran and was not bypassed'], ['REMOTE_FAST_FORWARDED', '`@workspaces/be`', 'the ref advanced from the remote head it carried']],
23
+ } = {}) {
24
+ return `# git-publication — ${boundary}
25
+
26
+ The boundary was published from the commit quality verified onto the routed ref, under enforced
27
+ hooks, without force.
28
+
29
+ ## Binding
30
+
31
+ | Field | Value |
32
+ | --- | --- |
33
+ | Operator | \`git.publish\` |
34
+ | Step | \`step-1/parallel-1\` |
35
+ | Project | \`starci-academy\` |
36
+ | Boundary | ${boundary} |
37
+ | Approval | ${approval} |
38
+ | Route receipt | \`step-1/parallel-1/response/response.md\` |
39
+ | Worktree branches | forbidden |
40
+ | Mutation branch | \`mtp\` |
41
+ | Frozen head | \`${HEAD}\` |
42
+
43
+ ## Publication
44
+
45
+ | Field | Value |
46
+ | --- | --- |
47
+ | Remote | origin |
48
+ | Ref | \`refs/heads/mtp\` |
49
+ | Mode | ${mode} |
50
+ | Forced | ${forced} |
51
+ | Session branch | \`${SESSION}\` |
52
+ | Target branch | \`mtp\` |
53
+ | Merge | ${merge} |
54
+ | Verified commit | \`${verified}\` |
55
+ | Cleanup | ${cleanup} |
56
+
57
+ ## Published heads
58
+
59
+ | Checkout | Branch | Head | Previous remote head | Commits |
60
+ | --- | --- | --- | --- | --- |
61
+ ${heads.map((h) => `| ${h[0]} | ${h[1]} | \`${h[2]}\` | \`${h[3]}\` | ${h[4]} |`).join('\n')}
62
+
63
+ ## Hooks
64
+
65
+ | Hook | Reference | Outcome |
66
+ | --- | --- | --- |
67
+ ${hooks.map((h) => `| ${h[0]} | ${h[1]} | ${h[2]} |`).join('\n')}
68
+
69
+ ## Continuation tag
70
+
71
+ | Field | Value |
72
+ | --- | --- |
73
+ | Tag | ${tag ? `\`${tag.name}\`` : '—'} |
74
+ | Ref | ${tag ? `\`refs/tags/${tag.name}\`` : '—'} |
75
+ | Head | ${tag ? `\`${tag.head}\`` : '—'} |
76
+
77
+ ## Findings
78
+
79
+ | Code | Subject | Statement |
80
+ | --- | --- | --- |
81
+ ${findings.map(([code, subject, statement]) => `| \`${code}\` | ${subject} | ${statement} |`).join('\n')}
82
+ `;
83
+ }
84
+
85
+ const requestJson = ({ boundary = BOUNDARY, approval = APPROVAL, tag = null, inputs = {
86
+ 'workspace-route-binding': 'step-1/parallel-1/response/response.md',
87
+ changes: 'step-1/parallel-1/response/changes.md',
88
+ 'quality-verification': 'step-1/parallel-1/response/quality.md',
89
+ }, extra = {} } = {}) => ({
90
+ schemaVersion: 9, operatorId: 'git.publish', step: 1, parallel: 1, sessionId: 's-test',
91
+ contexts: [{ alias: '@workspaces/local/routes/starci-academy/be', head: HEAD }],
92
+ requirements: { boundary, approval, tag, resume: null, ...extra },
93
+ inputs, resume: null,
94
+ });
95
+
96
+ const responseJson = ({ status = 'done', stop, commits = [HEAD], next = ['release.deploy'] } = {}) => ({
97
+ schemaVersion: 9, operatorId: 'git.publish', step: 1, parallel: 1, status, ...(stop ? { stop } : {}),
98
+ fallbacks: [], fields: status === 'blocked' ? {} : { 'git-publication': 'response/response.md' }, commits, next,
99
+ });
100
+
101
+ function writeBranch(files) {
102
+ const session = mkdtempSync(path.join(tmpdir(), 'publish-session-'));
103
+ const branch = path.join(session, 'step-1', 'parallel-1');
104
+ for (const d of ['request', 'response/data', 'response/artifacts']) mkdirSync(path.join(branch, d), { recursive: true });
105
+ writeFileSync(path.join(session, 'state.json'), JSON.stringify({ id: 's-test', chain: [['1/1']], steps: { '1/1': 'git.publish' }, current: '1/1', status: 'running' }));
106
+ for (const name of ['response.md', 'changes.md', 'quality.md']) writeFileSync(path.join(branch, 'response', name), '# placeholder\n');
107
+ for (const [name, content] of Object.entries(files)) {
108
+ if (content === null) continue;
109
+ writeFileSync(path.join(branch, name), typeof content === 'string' ? content : JSON.stringify(content, null, 2));
110
+ }
111
+ return { branch, session };
112
+ }
113
+
114
+ const baseline = (over = {}) => ({
115
+ 'request/request.json': requestJson(),
116
+ 'response/response.json': responseJson(),
117
+ 'response/response.md': responseMd(),
118
+ ...over,
119
+ });
120
+
121
+ async function expectValid(files, label) {
122
+ const { branch, session } = writeBranch(files);
123
+ const { errors } = await validateGitPublishStep(branch);
124
+ rmSync(session, { recursive: true, force: true });
125
+ assert.deepEqual(errors, [], `${label} should be valid`);
126
+ }
127
+ async function expectError(files, needle, label) {
128
+ const { branch, session } = writeBranch(files);
129
+ const { errors } = await validateGitPublishStep(branch);
130
+ rmSync(session, { recursive: true, force: true });
131
+ assert.ok(errors.some((e) => e.includes(needle)), `${label}: expected an error containing "${needle}", got:\n${errors.join('\n') || '(none)'}`);
132
+ }
133
+
134
+ await expectValid(baseline(), 'a fast-forward publication of the verified commit');
135
+ await expectValid(baseline({ 'response/response.md': responseMd({ merge: 'merge-commit' }) }), 'a merge commit onto a target that moved');
136
+ await expectValid(baseline({
137
+ 'request/request.json': requestJson({ tag: TAG }),
138
+ 'response/response.md': responseMd({
139
+ tag: { ...TAG, head: HEAD },
140
+ findings: [['HOOK_ENFORCED', '`pre-push`', 'the hook ran'], ['CONTINUATION_TAG_PUBLISHED', '`v1.4.0`', 'the tag points at the head this run pushed']],
141
+ }),
142
+ }), 'a continuation tag the person asked for');
143
+ await expectValid({
144
+ 'request/request.json': requestJson(),
145
+ 'response/response.json': responseJson({ status: 'blocked', stop: 'NON_FAST_FORWARD', commits: [], next: [] }),
146
+ 'response/response.md': null,
147
+ }, 'blocked because the remote had moved');
148
+
149
+ await expectError(baseline({ 'response/response.json': { ...responseJson(), stop: 'HOOK_BLOCKED' } }), 'only a blocked response carries a stop', 'done with a stop');
150
+ await expectError(baseline({ 'response/response.json': responseJson({ status: 'blocked', stop: 'PUSH_REJECTED', commits: [], next: [] }) }), 'not a registered code', 'unknown stop code');
151
+ await expectError(baseline({ 'request/request.json': requestJson({ extra: { force: true } }) }), 'requirements.force is not a field', 'force asked for in the request');
152
+ await expectError(baseline({ 'request/request.json': requestJson({ extra: { writeRoots: ['src'] } }) }), 'requirements.writeRoots is not a field', 'a field the operator no longer declares');
153
+ await expectError(baseline({ 'request/request.json': requestJson({ approval: null }) }), 'required field approval has no value', 'a publish with nobody approving it');
154
+ await expectError(baseline({ 'response/response.md': responseMd({ boundary: 'other.boundary' }) }), 'but the request bound', 'a receipt for another boundary');
155
+ await expectError(baseline({ 'response/response.md': responseMd({ approval: '`someone-elses-approval`' }) }), 'names an approval the request did not bind', 'an approval borrowed from another boundary');
156
+ await expectError(baseline({ 'response/response.md': responseMd({ mode: 'force' }) }), 'the publication mode is always fast-forward only', 'a forced publication mode');
157
+ await expectError(baseline({ 'response/response.md': responseMd({ forced: 'yes' }) }), 'a forced push rewrites', 'a push that was forced');
158
+ await expectError(baseline({ 'response/response.md': responseMd({ merge: 'rebase' }) }), 'merged, never rebased', 'a session branch rebased onto the target');
159
+ await expectError(baseline({ 'response/response.md': responseMd({ verified: OTHER }) }), 'but quality verified', 'a head past the commit the gates measured');
160
+ await expectError(baseline({ 'response/response.md': responseMd({ verified: 'not-a-sha' }) }), 'Verified commit is not a commit sha', 'a receipt that never says what was verified');
161
+ await expectError(baseline({ 'response/response.md': responseMd({ heads: [['`@workspaces/be`', '`mtp`', HEAD, HEAD, '4']] }) }), 'equals the remote head it claims to advance', 'a publication that advanced nothing');
162
+ await expectError(baseline({ 'response/response.md': responseMd({ hooks: [['`pre-commit`', '`.husky/pre-commit`', 'passed']] }) }), 'lacks the pre-push result', 'a publish with no pre-push result');
163
+ await expectError(baseline({ 'response/response.md': responseMd({ hooks: [['`pre-push`', '`.husky/pre-push`', 'failed']] }) }), 'is HOOK_BLOCKED, not a receipt', 'a receipt over a failed hook');
164
+ await expectError(baseline({ 'response/response.md': responseMd({ findings: [['BOUNDARY_CLEAN', '`api.core`', 'nothing dirty lay outside the boundary']] }) }), 'records no HOOK_ENFORCED finding', 'hooks run but never recorded');
165
+ await expectError(baseline({ 'request/request.json': requestJson({ tag: TAG }) }), 'asked for a continuation tag and the receipt records none', 'a tag asked for and never pushed');
166
+ await expectError(baseline({ 'response/response.md': responseMd({ tag: { ...TAG, head: HEAD }, findings: [['HOOK_ENFORCED', '`pre-push`', 'ran'], ['CONTINUATION_TAG_PUBLISHED', '`v1.4.0`', 'pushed']] }) }), 'without the request asking for one', 'a tag nobody asked for');
167
+ await expectError(baseline({
168
+ 'request/request.json': requestJson({ tag: TAG }),
169
+ 'response/response.md': responseMd({ tag: { ...TAG, head: OTHER }, findings: [['HOOK_ENFORCED', '`pre-push`', 'ran'], ['CONTINUATION_TAG_PUBLISHED', '`v1.4.0`', 'pushed']] }),
170
+ }), 'which this publication did not push', 'a tag on somebody else\'s commit');
171
+ await expectError(baseline({ 'response/response.md': responseMd({ cleanup: 'worktree kept for inspection' }) }), 'removes the worktree and the session branch', 'a published branch that never cleaned up');
172
+ await expectError(baseline({ 'response/response.json': responseJson({ commits: [] }) }), 'commits does not register the published head', 'a receipt whose commit the response hides');
173
+ await expectError(baseline({ 'response/response.json': responseJson({ commits: [HEAD, OTHER] }) }), 'which the receipt did not publish', 'a commit the receipt never published');
174
+ await expectError(baseline({ 'request/request.json': requestJson({ inputs: { 'workspace-route-binding': 'step-1/parallel-1/response/response.md', changes: 'step-1/parallel-1/response/changes.md' } }) }), 'required input quality-verification is absent', 'a publish with nothing verified');
175
+ await expectError(baseline({ 'response/response.md': responseMd().replace('## Hooks', '## Checks') }), 'missing section ^## Hooks$', 'receipt section renamed');
176
+
177
+ process.stdout.write('git.publish self-test: 4 valid branches, 22 rejected mutations\n');
@@ -0,0 +1,106 @@
1
+ // git.publish's own law over one branch, on top of the shared step check: the receipt names the
2
+ // boundary and the approval the request bound; the published head is the commit the
3
+ // quality-verification input measured; the mode is fast-forward only and nothing was forced; the
4
+ // session branch was merged, never rebased, and a merge commit exists only when the target moved;
5
+ // every hook ran and passed, `pre-push` among them; the head actually advanced; a continuation tag
6
+ // exists exactly when the request asked for one and points at the head this run pushed; and the
7
+ // worktree and the session branch were cleaned up on a done branch.
8
+ import { existsSync } from 'node:fs';
9
+ import { readFile } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import process from 'node:process';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { validateStep } from '../../scripts/validate-step.mjs';
14
+ import { tableUnder } from '../../scripts/validate-response.mjs';
15
+
16
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
17
+ const SHA = /^[0-9a-f]{40}$/;
18
+ const empty = (v) => v === undefined || v === null || v === '' || v === '—';
19
+ const rows = (text, heading) => tableUnder(text, heading) ?? [];
20
+ const fields = (text, heading) => Object.fromEntries(rows(text, heading).map(([k, v]) => [k, v]));
21
+
22
+ export async function validateGitPublishStep(branchDir, root = ROOT, { verifiedCommit = null } = {}) {
23
+ const base = await validateStep(root, branchDir);
24
+ const errors = [...base.errors];
25
+ const { response, request, requirements = {}, present = new Set() } = base;
26
+ if (!response || response.operatorId !== 'git.publish') return { errors };
27
+ const read = (f) => readFile(path.join(branchDir, f), 'utf8');
28
+
29
+ // Nothing destructive can be asked for, because no field can carry it.
30
+ for (const forbidden of ['force', 'forceWithLease', 'noVerify', 'rebase', 'amend', 'reset', 'clean', 'stash', 'deleteBranch']) {
31
+ if (requirements[forbidden] !== undefined) errors.push(`request.json: ${forbidden} is not a field git.publish declares; a destructive act must stay unrepresentable`);
32
+ }
33
+ if (empty(requirements.approval)) errors.push('request.json: approval has no default; publishing outward is always something a person said yes to');
34
+ const wantsTag = !empty(requirements.tag);
35
+
36
+ if (!(present.has('git-publication') && existsSync(path.join(branchDir, 'response/response.md')))) {
37
+ if (response.status === 'done') errors.push('response/response.md: a done branch needs the publication receipt');
38
+ return { errors };
39
+ }
40
+ const text = await read('response/response.md');
41
+ const binding = fields(text, '## Binding');
42
+ const publication = fields(text, '## Publication');
43
+ const heads = rows(text, '## Published heads');
44
+ const hooks = rows(text, '## Hooks');
45
+ const tag = fields(text, '## Continuation tag');
46
+ const findings = new Set(rows(text, '## Findings').map(([code]) => code));
47
+
48
+ if (!empty(requirements.boundary) && binding.Boundary !== requirements.boundary) errors.push(`response/response.md: Binding publishes ${binding.Boundary} but the request bound ${requirements.boundary}`);
49
+ if (!empty(requirements.approval) && binding.Approval !== requirements.approval) errors.push('response/response.md: Binding names an approval the request did not bind; an approval for another boundary is somebody else\'s');
50
+
51
+ if (publication.Mode !== 'fast-forward-only') errors.push(`response/response.md: Mode is ${publication.Mode}; the publication mode is always fast-forward only`);
52
+ if (publication.Forced !== 'no') errors.push('response/response.md: Forced is not no; a forced push rewrites what other people have already pulled');
53
+ if (!/^session\//.test(publication['Session branch'] ?? '')) errors.push('response/response.md: the producer wrote on a session branch, and the receipt must name it');
54
+ if (!['fast-forward', 'merge-commit'].includes(publication.Merge)) errors.push(`response/response.md: Merge is ${publication.Merge}; the session branch is merged, never rebased or squashed`);
55
+
56
+ // The published head is the commit quality measured, not one commit further on.
57
+ const verified = verifiedCommit ?? (publication['Verified commit'] ?? '');
58
+ if (!SHA.test(verified)) errors.push('response/response.md: Verified commit is not a commit sha; the receipt must say which commit the gates measured');
59
+ if (heads.length === 0) errors.push('response/response.md: Published heads is empty; a publication that advances nothing is not a publication');
60
+ for (const [checkout, branch, head, previous, commits] of heads) {
61
+ const at = `response/response.md: published head ${checkout}`;
62
+ if (SHA.test(verified) && head !== verified) errors.push(`${at} is ${head} but quality verified ${verified}; a head past the verified commit carries change no gate saw`);
63
+ if (head === previous) errors.push(`${at} equals the remote head it claims to advance`);
64
+ if (!/^[1-9][0-9]*$/.test(commits)) errors.push(`${at} publishes ${commits} commits`);
65
+ if (publication.Merge === 'fast-forward' && previous !== '—' && !SHA.test(previous)) errors.push(`${at} fast-forwarded from a previous head that is not a sha`);
66
+ if (branch === '—') errors.push(`${at} names no branch`);
67
+ }
68
+
69
+ // Hooks are enforced, always, and pre-push is the last gate before the remote.
70
+ const hookNames = hooks.map(([hook]) => hook);
71
+ if (!hookNames.includes('pre-push')) errors.push('response/response.md: Hooks lacks the pre-push result; the last gate before the remote cannot be missing');
72
+ for (const [hook, , outcome] of hooks) if (outcome !== 'passed') errors.push(`response/response.md: hook ${hook} is ${outcome}; a publication carrying a failed hook is HOOK_BLOCKED, not a receipt`);
73
+ if (hooks.length && !findings.has('HOOK_ENFORCED')) errors.push('response/response.md: the hooks ran but the receipt records no HOOK_ENFORCED finding');
74
+
75
+ // A tag exists exactly when the person asked for one, and it points at a head this run pushed.
76
+ const pushedHeads = new Set(heads.map(([, , head]) => head));
77
+ if (wantsTag) {
78
+ if (tag.Tag === '—') errors.push('response/response.md: the request asked for a continuation tag and the receipt records none');
79
+ else {
80
+ const name = (requirements.tag?.name ?? '').toString();
81
+ if (name && tag.Tag !== name) errors.push(`response/response.md: the tag is ${tag.Tag} but the request named ${name}`);
82
+ if (!/^refs\/tags\//.test(tag.Ref ?? '')) errors.push('response/response.md: a continuation tag is annotated and lives under refs/tags/');
83
+ if (!pushedHeads.has(tag.Head)) errors.push(`response/response.md: the tag points at ${tag.Head}, which this publication did not push`);
84
+ if (!findings.has('CONTINUATION_TAG_PUBLISHED')) errors.push('response/response.md: a published tag must record CONTINUATION_TAG_PUBLISHED');
85
+ }
86
+ } else if (tag.Tag !== '—') errors.push('response/response.md: a continuation tag was published without the request asking for one');
87
+
88
+ // Cleanup is part of the publish, and only on a done branch.
89
+ if (response.status === 'done' && !/removed/i.test(publication.Cleanup ?? '')) errors.push('response/response.md: a published branch removes the worktree and the session branch');
90
+
91
+ // The commit the response registers is the commit the receipt published.
92
+ const commits = new Set(response.commits ?? []);
93
+ for (const head of pushedHeads) if (!commits.has(head)) errors.push(`response/response.json: commits does not register the published head ${head}`);
94
+ for (const c of commits) if (!pushedHeads.has(c)) errors.push(`response/response.json: commits carries ${c}, which the receipt did not publish`);
95
+
96
+ // The verified commit comes from the quality receipt, so the input must be there to compare against.
97
+ if (response.status === 'done' && !(request?.inputs ?? {})['quality-verification']) errors.push('request.json: a publish needs the quality-verification input whose commit it pushes');
98
+ return { errors };
99
+ }
100
+
101
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
102
+ const target = process.argv[2];
103
+ if (!target) { process.stderr.write('usage: node validate.mjs <session>/step-N/parallel-M\n'); process.exit(2); }
104
+ const { errors } = await validateGitPublishStep(path.resolve(target));
105
+ if (errors.length) { process.stderr.write(`${errors.join('\n')}\n`); process.exitCode = 1; } else process.stdout.write('valid git.publish branch\n');
106
+ }
@@ -0,0 +1,90 @@
1
+ {
2
+ "schemaVersion": 9,
3
+ "note": "Stop codes only platform.operate emits. Same entry shape as operators/errors.json, scope implicit. Shared codes (INVALID_INPUT, SOURCE_DRIFT, NO_PROGRESS) come from operators/errors.json.",
4
+ "codes": {
5
+ "AUTHORITY_DRIFT": {
6
+ "domain": "caller",
7
+ "disposition": "terminate",
8
+ "meaning": {
9
+ "en": "The approval, the plan hash, or the allowed effect set no longer matches the requested delta.",
10
+ "vi": "Phê duyệt, hash kế hoạch hay tập effect được phép không còn khớp phần delta được yêu cầu."
11
+ },
12
+ "resume": {
13
+ "en": "Bring a fresh approval for this exact plan.",
14
+ "vi": "Mang một phê duyệt mới cho đúng kế hoạch này."
15
+ }
16
+ },
17
+ "CAPABILITY_MISSING": {
18
+ "domain": "caller",
19
+ "disposition": "terminate",
20
+ "meaning": {
21
+ "en": "The capability the service kind requires is absent or names no custody evidence.",
22
+ "vi": "Capability mà service kind đòi hỏi đang thiếu, hoặc không nêu bằng chứng custody nào."
23
+ },
24
+ "resume": {
25
+ "en": "Supply the missing capability handle with its custody.",
26
+ "vi": "Cấp handle capability còn thiếu cùng custody của nó."
27
+ }
28
+ },
29
+ "INVENTORY_DRIFT": {
30
+ "domain": "platform",
31
+ "disposition": "terminate",
32
+ "meaning": {
33
+ "en": "A declared resource moved since the inventory was bound, so the plan describes a service that no longer exists.",
34
+ "vi": "Một tài nguyên đã khai đã đổi kể từ khi inventory được ràng, nên kế hoạch mô tả một dịch vụ không còn tồn tại."
35
+ },
36
+ "resume": {
37
+ "en": "Re-observe the inventory; it must arrive with a new fingerprint.",
38
+ "vi": "Quan sát lại inventory; nó phải tới với một fingerprint mới."
39
+ }
40
+ },
41
+ "PORT_CONFLICT": {
42
+ "domain": "product",
43
+ "disposition": "terminate",
44
+ "meaning": {
45
+ "en": "A claimed port is already held by another declared process, and holding it is not permission to reclaim it.",
46
+ "vi": "Một cổng được claim đang bị một tiến trình đã khai khác giữ, và việc nó bị giữ không phải giấy phép giành lại."
47
+ },
48
+ "resume": {
49
+ "en": "Agree a port, or the holder's owner releases it.",
50
+ "vi": "Thoả thuận một cổng khác, hoặc chủ của kẻ đang giữ nhả nó ra."
51
+ }
52
+ },
53
+ "EFFECT_UNAUTHORIZED": {
54
+ "domain": "caller",
55
+ "disposition": "terminate",
56
+ "meaning": {
57
+ "en": "A required effect lies outside the approved effect set or outside the branch.",
58
+ "vi": "Một effect cần thiết nằm ngoài tập effect đã duyệt hoặc ngoài nhánh."
59
+ },
60
+ "resume": {
61
+ "en": "Approve the effect, or bring a narrower plan.",
62
+ "vi": "Duyệt effect đó, hoặc mang một kế hoạch hẹp hơn."
63
+ }
64
+ },
65
+ "SERVICE_UNAVAILABLE": {
66
+ "domain": "provider",
67
+ "disposition": "terminate",
68
+ "meaning": {
69
+ "en": "The shared service or its provider cannot be reached.",
70
+ "vi": "Dịch vụ dùng chung hoặc provider của nó không tới được."
71
+ },
72
+ "resume": {
73
+ "en": "Restore the provider.",
74
+ "vi": "Khôi phục provider."
75
+ }
76
+ },
77
+ "PROOF_FAILED": {
78
+ "domain": "platform",
79
+ "disposition": "terminate",
80
+ "meaning": {
81
+ "en": "A required check is missing, unreadable, or failed after apply, and an unproved operation is not an operated one.",
82
+ "vi": "Một phép kiểm bắt buộc thiếu, không đọc được, hoặc hỏng sau khi áp, và một lần vận hành chưa chứng minh được thì chưa phải đã vận hành."
83
+ },
84
+ "resume": {
85
+ "en": "Repair the service, then invoke again.",
86
+ "vi": "Sửa dịch vụ, rồi gọi lại."
87
+ }
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "schemaVersion": 9,
3
+ "id": "platform.operate",
4
+ "domain": "platform",
5
+ "job": "Operate one bounded shared observability, Sonar, or tunnel service from exact evidence: inventory it, converge only the approved delta, prove every check the bound knowledge requires, and stop at the smallest owning gap instead of taking product deployment ownership.",
6
+ "package": "operator.md",
7
+ "errors": "errors.json",
8
+ "validator": "validate.mjs",
9
+ "selfTest": "self-test.mjs",
10
+ "resources": {
11
+ "profile": "luna",
12
+ "grammarBound": false,
13
+ "tools": {
14
+ "@tools/fileread": "context-aliases",
15
+ "@tools/git": "read",
16
+ "@tools/shell": "declared-commands",
17
+ "@tools/http": "probe",
18
+ "@tools/container": "operate",
19
+ "@tools/secrets": "resolve-by-name"
20
+ }
21
+ }
22
+ }