@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,162 @@
1
+ # platform.operate
2
+
3
+ ## Job
4
+
5
+ Operate one bounded shared observability, Sonar, or tunnel service from exact evidence: inventory it,
6
+ converge only the approved delta, prove every check the bound knowledge requires, and stop at the
7
+ smallest owning gap instead of taking product deployment ownership.
8
+
9
+ ## Shared infrastructure, not product
10
+
11
+ This operator serves shared infrastructure and never takes product deployment ownership. That
12
+ boundary is not advice: a resource can only be changed if the bound inventory lists it under the same
13
+ service kind, and a product deployment target is never an observability, Sonar, or tunnel resource. A
14
+ plan that reaches for one is invalid input rather than a judgement call at execution time, and a
15
+ request to restart a product service to make room for a shared one leaves through a
16
+ `PRODUCT_DEPLOYMENT_DECLINED` finding rather than through a mutation.
17
+
18
+ ## One job, three branches
19
+
20
+ The service kind the inventory records selects the branch, and the three are branches of one job
21
+ rather than three operators.
22
+ Each branch publishes three closed sets, and each is enforced. Observability applies `update-config`,
23
+ `restart-service`, `upsert-dashboard` and `update-remote-write`, proves `service-health`,
24
+ `target-boundary`, `label-boundary`, `remote-write-delivery`, `sample-ordering`, `retry-backoff` and
25
+ `sensitive-data-filter`, and needs `metrics:remote-write`. Sonar applies `create-project`,
26
+ `assign-profile`, `assign-gate` and `enforce-setting`, proves `service-available`, `project-exists`,
27
+ `source-revision`, `profile-assigned`, `gate-assigned` and `enforcement-active`, and needs
28
+ `sonar:project-admin`. Tunnel applies `create-tunnel`, `update-tunnel-route` and `upsert-proxied-dns`,
29
+ proves `dns-target`, `tunnel-route`, `tls` and `public-https`, and needs `tunnel:write` and
30
+ `dns:write`. An effect or a check filed under the wrong branch is invalid input rather than a
31
+ warning, because a cross-filed effect is how an unapproved change acquires the appearance of
32
+ authority. The required proof set is the whole set the branch publishes: the caller cannot ask for
33
+ less, because a green dashboard alone never proved delivery, ordering, or redaction.
34
+
35
+ ## Inventory before change
36
+
37
+ A shared service is inventoried before it is changed. The inventory is bound by fingerprint, so the
38
+ receipt states exactly what the service was when the decision was made, and a concurrent revision
39
+ becomes visible as `INVENTORY_DRIFT` rather than being silently overwritten. The recheck happens
40
+ before any mutation, so a differing revision stops the invocation while nothing has changed yet.
41
+ Anything mutated appears in the inventory echo, so a change to a resource nobody looked at first
42
+ cannot be reported as an operation at all. An already-converged service is a proved no-op with no
43
+ mutation, not a failure and not a rewrite, and a converged operation that reports no mutation is
44
+ refused because one of its two statements is false. Application touches only effects inside the
45
+ approved set, one resource at a time, recording the before and after revision of each; a partial
46
+ application is reported as `PARTIAL_MUTATION` with exact revisions and is never hidden behind a
47
+ generic blocker.
48
+
49
+ ## A port in use is a coordination finding
50
+
51
+ A port already bound by another process is a fact about a shared machine, not permission to reclaim
52
+ it. The operation records `PORT_COORDINATION_REQUIRED` naming both the port and the process that
53
+ holds it, returns `PORT_CONFLICT`, and stops. It does not stop, kill, restart, or reconfigure the
54
+ holder, and no mutation may target a process observed holding a claimed port. Coordination is the
55
+ required next step and it belongs to the two owners, not to this invocation; `PORT_CONFLICT` is the
56
+ expected outcome on a busy shared machine, not a defect in the plan.
57
+
58
+ ## Credentials are resolved, never recorded
59
+
60
+ A capability is a handle and its custody evidence. The credential behind it is resolved for use at
61
+ the moment of the call and is never logged, echoed into evidence, or persisted. The receipt refuses
62
+ the handle as well as the value, because a receipt is durable and a durable record of a capability is
63
+ a leaked credential with a delay; a string carrying credential material anywhere in the request or
64
+ the response is refused as malformed.
65
+
66
+ ## The desired state is one approved declaration
67
+
68
+ `desiredState` is the whole of what the person asks for: the approved plan hash, the service kind the
69
+ plan was written against, the resources to converge, the effects to apply, and the two scope sets that
70
+ say which resources may change and which may only be observed. Keeping it as one declaration is what
71
+ makes the approval mean something: `approval` covers that declaration, hash and all, so a field
72
+ edited afterwards no longer matches the hash the approval named. `approval` has no default because
73
+ this is a runtime other sessions and other people share, and changing what a shared service does is
74
+ never something an agent decides alone. `portClaims` defaults to the empty list, because most
75
+ operations need no port at all and a claim nobody made cannot collide with anybody.
76
+
77
+ ## Boundary
78
+
79
+ Context is read-only apart from the approved delta. The operator applies only the approved effect
80
+ delta on the inventoried shared service, under an exclusive lease on
81
+ `@worktrees/sessions/central-runtime`, and writes only `response/` of its own branch:
82
+ `data/delta.json`, `data/checks.json`, `response.md` and `response.json`. It does not deploy,
83
+ restart, migrate, or otherwise take ownership of a product service; does not mutate a resource the
84
+ bound inventory does not list; does not emit an effect or a check the bound service kind does not
85
+ publish; does not free a port by stopping, killing, or reconfiguring the process that already holds
86
+ it; does not record a credential value, capability handle, or secret-shaped token anywhere in the
87
+ output; does not edit knowledge or grant its own approval; and does not claim an operated outcome
88
+ while any required check is absent or failed, nor any product readiness, release approval, or UAT
89
+ proof.
90
+
91
+ ## Context
92
+
93
+ | Alias | Bind | Required |
94
+ | --- | --- | --- |
95
+ | `@worktrees/sessions/central-runtime` | the shared runtime owner: inventory, generation and health, bound by fingerprint and generation, written only under an exclusive lease | yes |
96
+ | `@workspaces/ports/<project>` | the port projection the runtime binds to | yes |
97
+ | `@workspaces/device-state` | capability handles by name and their custody; values never appear | yes |
98
+ | `@workspaces/projects/<project>/<role>` | which projects the shared services serve | no |
99
+
100
+ ## Inputs
101
+
102
+ | Kind | From | Required |
103
+ | --- | --- | --- |
104
+
105
+ ## Requirements
106
+
107
+ | Field | Type | Default | Ask |
108
+ | --- | --- | --- | --- |
109
+ | `service` | id | — | The one shared service being operated |
110
+ | `desiredState` | `{planSha256, serviceKind, resourceRefs, effects, mutableResourceRefs, observationOnlyResourceRefs}` | — | The approved declaration: which plan, which branch, which resources, which effects, and what may change against what may only be observed |
111
+ | `portClaims` | list of `{port, resourceRef}` | [] | Which ports the desired state needs, and for which owned resource |
112
+ | `approval` | id | — | The approval that covers this desired state; changing a shared runtime always needs a person |
113
+ | `resume` | token | null | The blocked branch's token when re-entering after a stop |
114
+
115
+ ## Steps
116
+
117
+ | # | Step | Params | Reads | Writes | Stops with |
118
+ | --- | --- | --- | --- | --- | --- |
119
+ | 1 | Validate the gate and resume | `resume` | `request/request.json`, @worktrees/sessions/central-runtime at the frozen generation | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
120
+ | 2 | Bind the authority: the runtime, the device state, the projects and the approval | `service`, `approval` | @worktrees/sessions/central-runtime for the inventory fingerprint and generation, @workspaces/device-state for each capability handle with its custody evidence, @workspaces/projects/<project>/<role>, @tools/secrets | — | `AUTHORITY_DRIFT`, `CAPABILITY_MISSING` |
121
+ | 3 | Recheck the inventory once before anything changes | — | @worktrees/sessions/central-runtime, the declared resources re-observed once, @tools/git | — | `INVENTORY_DRIFT` |
122
+ | 4 | Resolve the port claims | `portClaims` | @workspaces/ports/<project> for the claimed ports, @worktrees/sessions/central-runtime for their observed holders | — | `PORT_CONFLICT` |
123
+ | 5 | Derive the delta between what is observed and what is desired | `desiredState` | @worktrees/sessions/central-runtime for the observed state, `request/request.json` for the desired state | `response/data/delta.json` | — |
124
+ | 6 | Apply the approved delta, one resource at a time, under an exclusive lease | — | @worktrees/sessions/central-runtime, @workspaces/device-state for the handles by name | @worktrees/sessions/central-runtime, `response/data/delta.json`, @tools/container, @tools/shell | `EFFECT_UNAUTHORIZED`, `SERVICE_UNAVAILABLE` |
125
+ | 7 | Prove every required check | — | @worktrees/sessions/central-runtime re-read against the branch's complete proof set, @tools/http | `response/data/checks.json` | `PROOF_FAILED` |
126
+ | 8 | Write the receipt and emit | — | everything above | `response/response.md`, `response/response.json` | — |
127
+
128
+ A resume begins again at validation, reuses only unchanged fingerprinted observations, and consumes
129
+ the exact delta; a resume that adds no authority, inventory, desired-state or scope change is
130
+ `NO_PROGRESS`, and a re-observed inventory must arrive as a new fingerprint because the same
131
+ fingerprint cannot yield a different answer.
132
+
133
+ ## Outputs
134
+
135
+ | Kind | File | Type | Required |
136
+ | --- | --- | --- | --- |
137
+ | `platform-operation-receipt` | `response/response.md` | md | yes |
138
+ | `delta` | `response/data/delta.json` | data | yes |
139
+ | `checks` | `response/data/checks.json` | data | yes |
140
+
141
+ ## Stops
142
+
143
+ | Code | Disposition |
144
+ | --- | --- |
145
+ | `INVALID_INPUT` | terminate |
146
+ | `SOURCE_DRIFT` | terminate |
147
+ | `NO_PROGRESS` | terminate |
148
+ | `AUTHORITY_DRIFT` | terminate |
149
+ | `CAPABILITY_MISSING` | terminate |
150
+ | `INVENTORY_DRIFT` | terminate |
151
+ | `PORT_CONFLICT` | terminate |
152
+ | `EFFECT_UNAUTHORIZED` | terminate |
153
+ | `SERVICE_UNAVAILABLE` | terminate |
154
+ | `PROOF_FAILED` | terminate |
155
+
156
+ ## Next
157
+
158
+ | When | Operator |
159
+ | --- | --- |
160
+ | the routed checkout or its head no longer matches the frozen binding | `workspace.bind` |
161
+ | the runtime a frontend surface must be audited against is now serving | `frontend.surface.audit` |
162
+ | the shared service is operated and the release that waited on it may continue | `release.deploy` |
@@ -0,0 +1,160 @@
1
+ # platform.operate
2
+
3
+ ## Việc
4
+
5
+ Vận hành một dịch vụ dùng chung có ranh giới, thuộc observability, Sonar hay tunnel, từ bằng chứng
6
+ chính xác: kiểm kê nó, hội tụ đúng phần delta đã duyệt, chứng minh mọi phép kiểm mà tri thức đã ràng
7
+ đòi hỏi, và dừng ở khoảng trống sở hữu nhỏ nhất thay vì nhận lấy quyền deploy sản phẩm.
8
+
9
+ ## Hạ tầng dùng chung, không phải sản phẩm
10
+
11
+ Operator này phục vụ hạ tầng dùng chung và không bao giờ nhận quyền deploy sản phẩm. Ranh giới đó
12
+ không phải lời khuyên: một tài nguyên chỉ đổi được nếu inventory đã ràng liệt kê nó dưới cùng một
13
+ service kind, và một đích deploy sản phẩm thì không bao giờ là tài nguyên observability, Sonar hay
14
+ tunnel. Một kế hoạch với tay sang đó là đầu vào không hợp lệ chứ không phải chuyện cân nhắc lúc chạy,
15
+ và yêu cầu restart một dịch vụ sản phẩm để dọn chỗ cho một dịch vụ dùng chung sẽ đi ra bằng phát hiện
16
+ `PRODUCT_DEPLOYMENT_DECLINED` chứ không bằng một lần mutate.
17
+
18
+ ## Một việc, ba nhánh
19
+
20
+ Kiểu dịch vụ mà inventory ghi lại chọn nhánh, và ba nhánh là ba nhánh của cùng một việc chứ không phải ba operator. Mỗi
21
+ nhánh công bố ba tập đóng, và cả ba đều được thi hành. Observability áp `update-config`,
22
+ `restart-service`, `upsert-dashboard` và `update-remote-write`, chứng minh `service-health`,
23
+ `target-boundary`, `label-boundary`, `remote-write-delivery`, `sample-ordering`, `retry-backoff` và
24
+ `sensitive-data-filter`, và cần `metrics:remote-write`. Sonar áp `create-project`, `assign-profile`,
25
+ `assign-gate` và `enforce-setting`, chứng minh `service-available`, `project-exists`,
26
+ `source-revision`, `profile-assigned`, `gate-assigned` và `enforcement-active`, và cần
27
+ `sonar:project-admin`. Tunnel áp `create-tunnel`, `update-tunnel-route` và `upsert-proxied-dns`,
28
+ chứng minh `dns-target`, `tunnel-route`, `tls` và `public-https`, và cần `tunnel:write` cùng
29
+ `dns:write`. Một effect hay một check nộp nhầm nhánh là đầu vào không hợp lệ chứ không phải cảnh báo,
30
+ vì effect nộp chéo chính là cách một thay đổi chưa duyệt khoác lên mình vẻ ngoài của thẩm quyền. Tập
31
+ chứng minh bắt buộc là trọn tập mà nhánh công bố: người gọi không được xin ít hơn, vì một dashboard
32
+ xanh tự nó chưa bao giờ chứng minh được delivery, ordering hay redaction.
33
+
34
+ ## Kiểm kê trước khi đổi
35
+
36
+ Một dịch vụ dùng chung được kiểm kê trước khi bị đổi. Inventory được ràng bằng fingerprint, nên biên
37
+ bản nói đúng dịch vụ là gì tại lúc quyết định, và một revision chạy song song hiện ra thành
38
+ `INVENTORY_DRIFT` thay vì bị ghi đè lặng lẽ. Lần kiểm lại xảy ra trước mọi mutation, nên một revision
39
+ khác đi sẽ dừng lượt chạy khi chưa có gì thay đổi. Mọi thứ bị mutate đều xuất hiện trong phần vọng
40
+ lại của inventory, nên một thay đổi lên tài nguyên chẳng ai nhìn trước thì không thể báo là một lần
41
+ vận hành. Một dịch vụ đã hội tụ sẵn là một no-op đã được chứng minh, không mutation nào, không phải
42
+ lỗi và cũng không phải viết lại; còn một lần vận hành báo là đã hội tụ mà không có mutation nào thì
43
+ bị từ chối vì một trong hai phát biểu của nó là sai. Việc áp chỉ chạm các effect nằm trong tập đã
44
+ duyệt, mỗi lần một tài nguyên, ghi lại revision trước và sau của từng cái; áp một phần được báo là
45
+ `PARTIAL_MUTATION` kèm đúng hai revision và không bao giờ bị giấu sau một mã chặn chung chung.
46
+
47
+ ## Một cổng đang bận là phát hiện cần phối hợp
48
+
49
+ Một cổng đã bị tiến trình khác chiếm là một sự thật về máy dùng chung, không phải giấy phép giành lại
50
+ nó. Lần vận hành ghi `PORT_COORDINATION_REQUIRED` nêu cả cổng lẫn tiến trình đang giữ nó, trả
51
+ `PORT_CONFLICT`, rồi dừng. Nó không dừng, không giết, không restart và không cấu hình lại kẻ đang
52
+ giữ, và không mutation nào được nhắm vào một tiến trình đã quan sát thấy đang giữ một cổng được
53
+ claim. Phối hợp là bước kế bắt buộc và nó thuộc về hai người chủ, không thuộc lượt chạy này;
54
+ `PORT_CONFLICT` là kết quả bình thường trên một máy dùng chung bận, không phải khiếm khuyết của kế
55
+ hoạch.
56
+
57
+ ## Credential được phân giải, không bao giờ được ghi lại
58
+
59
+ Một capability là một handle cùng bằng chứng custody của nó. Credential đứng sau nó được phân giải để
60
+ dùng đúng lúc gọi và không bao giờ được log, vọng vào evidence hay lưu lại. Biên bản từ chối cả cái
61
+ handle chứ không riêng giá trị, vì biên bản là thứ bền, và một bản ghi bền của một capability là một
62
+ credential rò rỉ có độ trễ; một chuỗi mang vật liệu credential ở bất cứ đâu trong request hay
63
+ response đều bị từ chối như dữ liệu sai dạng.
64
+
65
+ ## Trạng thái mong muốn là một khai báo đã duyệt
66
+
67
+ `desiredState` là toàn bộ những gì người ta xin: hash của kế hoạch đã duyệt, kiểu dịch vụ mà kế hoạch
68
+ được viết cho, các resource cần đưa về đúng trạng thái, các effect cần áp, và hai tập phạm vi nói
69
+ resource nào được đổi và resource nào chỉ được quan sát. Giữ nó thành một khai báo duy nhất chính là
70
+ thứ làm cho phê duyệt có nghĩa: `approval` phủ lên đúng khai báo đó, kèm cả hash, nên một field sửa
71
+ sau đó không còn khớp cái hash mà phê duyệt đã gọi tên. `approval` không có mặc định vì đây là một
72
+ runtime mà phiên khác và người khác dùng chung, và đổi việc một dịch vụ dùng chung đang làm không bao
73
+ giờ là chuyện một agent tự quyết. `portClaims` mặc định là danh sách rỗng, vì phần lớn thao tác không
74
+ cần cổng nào, và một claim không ai đặt thì không thể đụng ai.
75
+
76
+ ## Ranh giới ghi
77
+
78
+ Context chỉ đọc, trừ phần delta đã duyệt. Operator chỉ áp delta effect đã duyệt lên dịch vụ dùng chung
79
+ đã kiểm kê, dưới một lease độc quyền trên `@worktrees/sessions/central-runtime`, và chỉ ghi
80
+ `response/` của nhánh mình: `data/delta.json`, `data/checks.json`, `response.md` và `response.json`.
81
+ Nó không deploy, restart, migrate hay theo cách nào khác nhận quyền sở hữu một dịch vụ sản phẩm; không
82
+ làm đổi một resource mà inventory đã ràng không liệt kê; không phát một effect hay một check mà nhánh
83
+ dịch vụ đã ràng không công bố; không giải phóng một cổng bằng cách dừng, giết hay cấu hình lại tiến
84
+ trình đang giữ nó; không ghi giá trị credential, handle capability hay token dạng bí mật ở bất kỳ đâu
85
+ trong đầu ra; không sửa knowledge hay tự cấp phê duyệt cho mình; và không tuyên bố một kết quả đã vận
86
+ hành khi còn một check bắt buộc vắng mặt hay hỏng, cũng không tuyên bố readiness sản phẩm, phê duyệt
87
+ release hay bằng chứng UAT nào.
88
+
89
+ ## Context
90
+
91
+ | Alias | Bind | Bắt buộc |
92
+ | --- | --- | --- |
93
+ | `@worktrees/sessions/central-runtime` | chủ sở hữu runtime dùng chung: inventory, generation và health, ràng theo fingerprint và generation, chỉ ghi dưới một lease độc quyền | có |
94
+ | `@workspaces/ports/<project>` | phép chiếu cổng mà runtime ràng vào | có |
95
+ | `@workspaces/device-state` | handle capability theo tên và custody của chúng; giá trị không bao giờ xuất hiện | có |
96
+ | `@workspaces/projects/<project>/<role>` | những project mà các dịch vụ dùng chung phục vụ | không |
97
+
98
+ ## Đầu vào
99
+
100
+ | Kind | Từ đâu | Bắt buộc |
101
+ | --- | --- | --- |
102
+
103
+ ## Yêu cầu
104
+
105
+ | Field | Kiểu | Mặc định | Hỏi |
106
+ | --- | --- | --- | --- |
107
+ | `service` | id | — | Đúng một dịch vụ dùng chung đang được vận hành |
108
+ | `desiredState` | `{planSha256, serviceKind, resourceRefs, effects, mutableResourceRefs, observationOnlyResourceRefs}` | — | Khai báo đã duyệt: kế hoạch nào, nhánh nào, resource nào, effect nào, và cái gì được đổi so với cái gì chỉ được quan sát |
109
+ | `portClaims` | list of `{port, resourceRef}` | [] | Trạng thái mong muốn cần những cổng nào, và cho resource sở hữu nào |
110
+ | `approval` | id | — | Phê duyệt phủ lên trạng thái mong muốn này; đổi một runtime dùng chung luôn cần một con người |
111
+ | `resume` | token | null | Token của nhánh bị chặn khi vào lại sau một mã dừng |
112
+
113
+ ## Các bước
114
+
115
+ | # | Bước | Tham số | Đọc | Ghi | Dừng với |
116
+ | --- | --- | --- | --- | --- | --- |
117
+ | 1 | Kiểm cổng vào và resume | `resume` | `request/request.json`, @worktrees/sessions/central-runtime tại generation đã đóng băng | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
118
+ | 2 | Ràng thẩm quyền: runtime, device-state, projects và phê duyệt | `service`, `approval` | @worktrees/sessions/central-runtime cho fingerprint và generation của inventory, @workspaces/device-state cho từng handle capability kèm bằng chứng custody, @workspaces/projects/<project>/<role>, @tools/secrets | — | `AUTHORITY_DRIFT`, `CAPABILITY_MISSING` |
119
+ | 3 | Kiểm lại inventory một lần trước khi có gì thay đổi | — | @worktrees/sessions/central-runtime, các resource đã khai được quan sát lại một lần, @tools/git | — | `INVENTORY_DRIFT` |
120
+ | 4 | Phân giải các port claim | `portClaims` | @workspaces/ports/<project> cho các cổng được claim, @worktrees/sessions/central-runtime cho chủ giữ quan sát được của chúng | — | `PORT_CONFLICT` |
121
+ | 5 | Suy ra delta giữa cái quan sát được và cái mong muốn | `desiredState` | @worktrees/sessions/central-runtime cho trạng thái quan sát được, `request/request.json` cho trạng thái mong muốn | `response/data/delta.json` | — |
122
+ | 6 | Áp delta đã duyệt, từng resource một, dưới một lease độc quyền | — | @worktrees/sessions/central-runtime, @workspaces/device-state cho các handle theo tên | @worktrees/sessions/central-runtime, `response/data/delta.json`, @tools/container, @tools/shell | `EFFECT_UNAUTHORIZED`, `SERVICE_UNAVAILABLE` |
123
+ | 7 | Chứng minh mọi check bắt buộc | — | @worktrees/sessions/central-runtime đọc lại theo bộ chứng minh đầy đủ của nhánh, @tools/http | `response/data/checks.json` | `PROOF_FAILED` |
124
+ | 8 | Viết biên bản và phát | — | mọi thứ ở trên | `response/response.md`, `response/response.json` | — |
125
+
126
+ Một lần resume bắt đầu lại từ cổng vào, chỉ dùng lại quan sát có fingerprint không đổi, và tiêu thụ
127
+ đúng phần delta; một lần resume không thêm thẩm quyền, inventory, trạng thái mong muốn hay phạm vi nào
128
+ là `NO_PROGRESS`, và một inventory quan sát lại phải tới dưới dạng một fingerprint mới vì cùng một
129
+ fingerprint không thể cho một câu trả lời khác.
130
+
131
+ ## Đầu ra
132
+
133
+ | Kind | File | Kiểu | Bắt buộc |
134
+ | --- | --- | --- | --- |
135
+ | `platform-operation-receipt` | `response/response.md` | md | có |
136
+ | `delta` | `response/data/delta.json` | data | có |
137
+ | `checks` | `response/data/checks.json` | data | có |
138
+
139
+ ## Dừng
140
+
141
+ | Code | Xử lý |
142
+ | --- | --- |
143
+ | `INVALID_INPUT` | terminate |
144
+ | `SOURCE_DRIFT` | terminate |
145
+ | `NO_PROGRESS` | terminate |
146
+ | `AUTHORITY_DRIFT` | terminate |
147
+ | `CAPABILITY_MISSING` | terminate |
148
+ | `INVENTORY_DRIFT` | terminate |
149
+ | `PORT_CONFLICT` | terminate |
150
+ | `EFFECT_UNAUTHORIZED` | terminate |
151
+ | `SERVICE_UNAVAILABLE` | terminate |
152
+ | `PROOF_FAILED` | terminate |
153
+
154
+ ## Kế tiếp
155
+
156
+ | Khi | Operator |
157
+ | --- | --- |
158
+ | checkout đã route hay head của nó không còn khớp ràng buộc đã đóng băng | `workspace.bind` |
159
+ | runtime mà một bề mặt frontend phải được audit trên đó nay đã phục vụ | `frontend.surface.audit` |
160
+ | dịch vụ dùng chung đã vận hành xong và release đang chờ nó có thể chạy tiếp | `release.deploy` |
@@ -0,0 +1,202 @@
1
+ // Proves validate.mjs on a synthetic session branch: one converged observability operation, one
2
+ // already-converged no-op, one blocked on a port conflict, and one mutation per law, each of which
3
+ // 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 { validatePlatformStep, KIND_CHECKS } from './validate.mjs';
9
+
10
+ const PLAN = `sha256:${'0'.repeat(64)}`;
11
+ const FP = `sha256:${'1'.repeat(64)}`;
12
+ const SERVICE = 'prometheus/shared';
13
+ const OWNER = 'platform-team';
14
+ const APPROVAL = '@worktrees/debts/be.md#metrics-approval';
15
+ const EFFECTS = ['update-config', 'update-remote-write'];
16
+ const CAPABILITIES = [{ capability: 'metrics:remote-write', custodyEvidenceRef: 'custody/metrics.json' }];
17
+
18
+ const resource = (ref = SERVICE, kind = 'observability', revision = 'r-14') => ({ resourceRef: ref, kind, revision, ownerRef: OWNER });
19
+ const mutation = (effect, over = {}) => ({ effect, resourceRef: SERVICE, beforeRevision: 'r-14', afterRevision: 'r-15', ...over });
20
+ const check = (name, status = 'passed') => ({ name, resourceRef: SERVICE, status, evidenceRef: `logs/${name}.txt` });
21
+ const checkList = (names = KIND_CHECKS.observability, status = 'passed') => names.map((n) => check(n, status));
22
+
23
+ function delta({
24
+ convergence = 'converged', mutations = EFFECTS.map((e) => mutation(e)), appliedEffects, allowedEffects = EFFECTS,
25
+ capabilities = CAPABILITIES, resources = [resource()], portHolders = [], portClaims = [], serviceKind = 'observability',
26
+ planSha256 = PLAN, approvalRef = APPROVAL, mutableResourceRefs = [SERVICE], serviceRef = SERVICE,
27
+ } = {}) {
28
+ return {
29
+ serviceRef, serviceKind, ownerRef: OWNER, approvalRef, planSha256,
30
+ inventoryFingerprint: FP, generation: 7, observedAt: '2026-01-10T00:00:00.000Z',
31
+ inventoriedResources: resources, observedPortHolders: portHolders, portClaims,
32
+ mutableResourceRefs, observationOnlyResourceRefs: [],
33
+ allowedEffects, appliedEffects: appliedEffects ?? [...new Set(mutations.map((m) => m.effect))],
34
+ capabilities, convergence, mutations,
35
+ };
36
+ }
37
+
38
+ function checksJson({ serviceKind = 'observability', required = KIND_CHECKS.observability, list = checkList(), findings, serviceRef = SERVICE } = {}) {
39
+ return {
40
+ serviceRef, serviceKind, requiredCheckNames: required, checks: list,
41
+ findings: findings ?? [{ code: 'SHARED_SERVICE_INVENTORIED', resourceRef: SERVICE, port: null, holderRef: null, statement: 'the service was inventoried before anything changed' }],
42
+ };
43
+ }
44
+
45
+ function responseMd({ convergence = 'converged', mutations = EFFECTS.map((e) => mutation(e)), list = checkList(), findings = [['SHARED_SERVICE_INVENTORIED', SERVICE, '—', '—', 'the service was inventoried before anything changed']], service = SERVICE, kind = 'observability', plan = PLAN, approval = APPROVAL, resources = [resource()], portHolders = [] } = {}) {
46
+ return `# platform-operation-receipt — ${kind} ${service}
47
+
48
+ The shared service was inventoried, the approved delta was applied, and the branch's complete proof
49
+ set was proved on its own evidence.
50
+
51
+ ## Binding
52
+
53
+ | Field | Value |
54
+ | --- | --- |
55
+ | Operator | \`platform.operate\` |
56
+ | Step | \`step-1/parallel-1\` |
57
+ | Project | \`starci-academy\` |
58
+ | Service | ${service} |
59
+ | Service kind | ${kind} |
60
+ | Owner | \`${OWNER}\` |
61
+ | Approval | ${approval} |
62
+ | Desired state | \`${plan}\` |
63
+ | Inventory fingerprint | \`${FP}\` |
64
+
65
+ ## Convergence
66
+
67
+ | Field | Value |
68
+ | --- | --- |
69
+ | Convergence | ${convergence} |
70
+
71
+ ## Inventoried resources
72
+
73
+ | Resource | Kind | Revision | Owner |
74
+ | --- | --- | --- | --- |
75
+ ${resources.map((r) => `| \`${r.resourceRef}\` | ${r.kind} | ${r.revision} | \`${r.ownerRef}\` |`).join('\n')}
76
+
77
+ ## Port holders
78
+
79
+ | Port | Holder | Evidence |
80
+ | --- | --- | --- |
81
+ ${portHolders.map((h) => `| ${h.port} | \`${h.holderRef}\` | \`${h.evidenceRef}\` |`).join('\n')}
82
+
83
+ ## Mutations
84
+
85
+ | Effect | Resource | Before | After |
86
+ | --- | --- | --- | --- |
87
+ ${mutations.map((m) => `| \`${m.effect}\` | \`${m.resourceRef}\` | ${m.beforeRevision ?? '—'} | ${m.afterRevision} |`).join('\n')}
88
+
89
+ ## Checks
90
+
91
+ | Check | Resource | Status | Evidence |
92
+ | --- | --- | --- | --- |
93
+ ${list.map((c) => `| \`${c.name}\` | \`${c.resourceRef}\` | ${c.status} | \`${c.evidenceRef}\` |`).join('\n')}
94
+
95
+ ## Findings
96
+
97
+ | Code | Resource | Port | Holder | Statement |
98
+ | --- | --- | --- | --- | --- |
99
+ ${findings.map(([code, res, port, holder, statement]) => `| \`${code}\` | \`${res}\` | ${port} | ${holder} | ${statement} |`).join('\n')}
100
+ `;
101
+ }
102
+
103
+ const requestJson = ({ kind = 'observability', effects = EFFECTS, resourceRefs = [SERVICE], mutableResourceRefs = [SERVICE], observationOnlyResourceRefs = [], portClaims = [], plan = PLAN, service = SERVICE, approval = APPROVAL, extra = {} } = {}) => ({
104
+ schemaVersion: 9, operatorId: 'platform.operate', step: 1, parallel: 1, sessionId: 's-test',
105
+ contexts: [{ alias: '@worktrees/sessions/central-runtime', head: null }],
106
+ requirements: {
107
+ service, approval, portClaims, resume: null,
108
+ desiredState: { planSha256: plan, serviceKind: kind, resourceRefs, effects, mutableResourceRefs, observationOnlyResourceRefs },
109
+ ...extra,
110
+ },
111
+ inputs: {}, resume: null,
112
+ });
113
+
114
+ const responseJson = ({ status = 'done', stop, next = ['release.deploy'] } = {}) => ({
115
+ schemaVersion: 9, operatorId: 'platform.operate', step: 1, parallel: 1, status, ...(stop ? { stop } : {}),
116
+ fallbacks: [],
117
+ fields: status === 'blocked' ? {} : { 'platform-operation-receipt': 'response/response.md', delta: 'response/data/delta.json', checks: 'response/data/checks.json' },
118
+ commits: [], next,
119
+ });
120
+
121
+ function writeBranch(files) {
122
+ const session = mkdtempSync(path.join(tmpdir(), 'platform-session-'));
123
+ const branch = path.join(session, 'step-1', 'parallel-1');
124
+ for (const d of ['request', 'response/data', 'response/artifacts']) mkdirSync(path.join(branch, d), { recursive: true });
125
+ writeFileSync(path.join(session, 'state.json'), JSON.stringify({ id: 's-test', chain: [['1/1']], steps: { '1/1': 'platform.operate' }, current: '1/1', status: 'running' }));
126
+ for (const [name, content] of Object.entries(files)) {
127
+ if (content === null) continue;
128
+ writeFileSync(path.join(branch, name), typeof content === 'string' ? content : JSON.stringify(content, null, 2));
129
+ }
130
+ return { branch, session };
131
+ }
132
+
133
+ const baseline = (over = {}) => ({
134
+ 'request/request.json': requestJson(),
135
+ 'response/response.json': responseJson(),
136
+ 'response/response.md': responseMd(),
137
+ 'response/data/delta.json': delta(),
138
+ 'response/data/checks.json': checksJson(),
139
+ ...over,
140
+ });
141
+
142
+ const alreadyConverged = () => baseline({
143
+ 'request/request.json': requestJson({ effects: [] }),
144
+ 'response/data/delta.json': delta({ convergence: 'already-converged', mutations: [] }),
145
+ 'response/data/checks.json': checksJson({ findings: [{ code: 'ALREADY_CONVERGED', resourceRef: SERVICE, port: null, holderRef: null, statement: 'the service already matched the approved plan' }] }),
146
+ 'response/response.md': responseMd({ convergence: 'already-converged', mutations: [], findings: [['ALREADY_CONVERGED', SERVICE, '—', '—', 'the service already matched the approved plan']] }),
147
+ });
148
+
149
+ async function expectValid(files, label) {
150
+ const { branch, session } = writeBranch(files);
151
+ const { errors } = await validatePlatformStep(branch);
152
+ rmSync(session, { recursive: true, force: true });
153
+ assert.deepEqual(errors, [], `${label} should be valid`);
154
+ }
155
+ async function expectError(files, needle, label) {
156
+ const { branch, session } = writeBranch(files);
157
+ const { errors } = await validatePlatformStep(branch);
158
+ rmSync(session, { recursive: true, force: true });
159
+ assert.ok(errors.some((e) => e.includes(needle)), `${label}: expected an error containing "${needle}", got:\n${errors.join('\n') || '(none)'}`);
160
+ }
161
+
162
+ await expectValid(baseline(), 'a converged observability operation with the complete proof set');
163
+ await expectValid(alreadyConverged(), 'an already-converged service as a proved no-op');
164
+ await expectValid({
165
+ 'request/request.json': requestJson(),
166
+ 'response/response.json': responseJson({ status: 'blocked', stop: 'PORT_CONFLICT', next: [] }),
167
+ 'response/response.md': null, 'response/data/delta.json': null, 'response/data/checks.json': null,
168
+ }, 'blocked on a port conflict before anything changed');
169
+
170
+ await expectError(baseline({ 'response/response.json': { ...responseJson(), stop: 'PROOF_FAILED' } }), 'only a blocked response carries a stop', 'done with a stop');
171
+ await expectError(baseline({ 'response/response.json': responseJson({ status: 'blocked', stop: 'PORT_SEIZED', next: [] }) }), 'not a registered code', 'unknown stop code');
172
+ await expectError(baseline({ 'request/request.json': requestJson({ extra: { credential: 'token: abcdefghijklmnop' } }) }), 'requirements.credential is not a field', 'a credential has nowhere to go in the request');
173
+ await expectError(baseline({ 'request/request.json': requestJson({ approval: null }) }), 'required field approval has no value', 'a shared runtime change nobody approved');
174
+ await expectError(baseline({ 'request/request.json': requestJson({ extra: { serviceKind: 'observability' } }) }), 'requirements.serviceKind is not a field', 'a field the operator no longer declares');
175
+ await expectError(baseline({ 'request/request.json': requestJson({ effects: ['assign-gate'] }) }), 'does not belong to the observability service kind', 'a Sonar effect filed under observability');
176
+ await expectError(baseline({ 'request/request.json': requestJson({ mutableResourceRefs: ['other/service'], resourceRefs: [SERVICE] }) }), 'must be inside desiredState.mutableResourceRefs', 'the service outside its own mutable ceiling');
177
+ await expectError(baseline({ 'request/request.json': requestJson({ observationOnlyResourceRefs: [SERVICE] }) }), 'cannot be both mutable and observation-only', 'a resource in both scope sets');
178
+ await expectError(baseline({ 'request/request.json': requestJson({ portClaims: [{ port: 9090, resourceRef: 'product/web' }] }) }), 'which this operation does not own', 'a port claimed for a foreign resource');
179
+ await expectError(baseline({ 'request/request.json': requestJson({ portClaims: [{ port: 9090, resourceRef: SERVICE }, { port: 9090, resourceRef: SERVICE }] }) }), 'must not claim the same port twice', 'the same port claimed twice');
180
+ await expectError(baseline({ 'response/data/delta.json': delta({ resources: [resource('other/service')] }) }), 'was not inventoried before the operation', 'the operated service never inventoried');
181
+ await expectError(baseline({ 'response/data/delta.json': delta({ resources: [resource(SERVICE, 'sonar')] }) }), 'belongs to the sonar branch and is outside this observability operation', 'a resource from another branch');
182
+ await expectError(baseline({ 'response/data/delta.json': delta({ planSha256: `sha256:${'9'.repeat(64)}` }) }), 'planSha256 must equal the approved plan hash', 'a plan hash nobody approved');
183
+ await expectError(baseline({ 'response/data/delta.json': delta({ approvalRef: '@worktrees/debts/be.md#other' }) }), 'the bound approval is not the one the request declared', 'an approval borrowed from another plan');
184
+ await expectError(baseline({ 'request/request.json': requestJson({ effects: ['update-config', 'restart-service'] }), 'response/data/delta.json': delta({ allowedEffects: EFFECTS }) }), 'is outside the approved effect set', 'an effect outside the approval');
185
+ await expectError(baseline({ 'response/data/delta.json': delta({ capabilities: [] }) }), 'requires the metrics:remote-write capability', 'a branch running without its capability');
186
+ await expectError(baseline({ 'response/data/checks.json': checksJson({ required: KIND_CHECKS.observability.slice(0, 3), list: checkList(KIND_CHECKS.observability.slice(0, 3)) }) }), 'must require the remote-write-delivery check', 'a narrowed proof set');
187
+ await expectError(baseline({ 'response/data/checks.json': checksJson({ list: checkList(KIND_CHECKS.observability.slice(0, 6)) }), 'response/response.md': responseMd({ list: checkList(KIND_CHECKS.observability.slice(0, 6)) }) }), 'cannot be proved without the sensitive-data-filter check', 'an operated outcome missing a check');
188
+ await expectError(baseline({ 'response/data/checks.json': checksJson({ list: checkList(KIND_CHECKS.observability, 'failed') }), 'response/response.md': responseMd({ list: checkList(KIND_CHECKS.observability, 'failed') }) }), 'so the operation cannot be reported as operated', 'an operated outcome over a failed check');
189
+ await expectError(baseline({ 'response/data/delta.json': delta({ mutations: [mutation('update-config'), mutation('update-remote-write', { resourceRef: 'ghost/service' })] }) }), 'was mutated without being inventoried first', 'a mutation on a resource nobody inventoried');
190
+ await expectError(baseline({ 'response/data/delta.json': delta({ portHolders: [{ port: 9090, holderRef: SERVICE, evidenceRef: 'ss.txt' }] }) }), 'holds a claimed port and must never be mutated to free it', 'a port freed by mutating its holder');
191
+ await expectError(baseline({ 'response/data/delta.json': delta({ appliedEffects: [...EFFECTS, 'restart-service'] }) }), 'records no mutation', 'an applied effect with no mutation');
192
+ await expectError(baseline({ 'response/data/delta.json': delta({ convergence: 'already-converged' }) }), 'already-converged operation cannot report a mutation', 'a no-op that mutated');
193
+ await expectError(baseline({ 'response/data/delta.json': delta({ convergence: 'converged', mutations: [] }), 'response/response.md': responseMd({ mutations: [] }) }), 'converged operation must report the mutation that converged it', 'convergence with nothing applied');
194
+ await expectError(baseline({ 'response/data/checks.json': checksJson({ findings: [{ code: 'PORT_COORDINATION_REQUIRED', resourceRef: SERVICE, port: 9090, holderRef: 'product/web', statement: 'the port is held elsewhere' }] }), 'response/response.md': responseMd({ findings: [['PORT_COORDINATION_REQUIRED', SERVICE, '9090', '`product/web`', 'the port is held elsewhere']] }) }), 'cannot end in an operated outcome', 'a port coordination finding on an operated receipt');
195
+ await expectError(baseline({ 'response/data/checks.json': checksJson({ findings: [{ code: 'SHARED_SERVICE_INVENTORIED', resourceRef: 'ghost/service', port: null, holderRef: null, statement: 'x' }] }) }), 'names an uninventoried resource', 'a finding on a resource nobody inventoried');
196
+ await expectError(baseline({ 'response/data/delta.json': delta({ capabilities: [{ capability: 'metrics:remote-write', custodyEvidenceRef: 'capability://metrics/remote-write' }] }) }), 'records a credential, which the receipt refuses', 'a capability handle inside the delta');
197
+ await expectError(baseline({ 'response/response.md': responseMd({ service: 'other/service' }) }), 'Binding names a service the request did not operate', 'a receipt for another service');
198
+ await expectError(baseline({ 'response/response.md': responseMd().replace('## Checks', '## Proofs') }), 'missing section ^## Checks$', 'receipt section renamed');
199
+ await expectError(baseline({ 'response/data/delta.json': { ...delta(), inventoryFingerprint: 'nope' } }), 'inventoryFingerprint', 'delta schema');
200
+ await expectError(baseline({ 'response/response.json': (() => { const o = responseJson(); delete o.fields.checks; return o; })() }), 'required output checks is not in fields', 'missing required output');
201
+
202
+ process.stdout.write('platform.operate self-test: 3 valid branches, 28 rejected mutations\n');