@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,193 @@
1
+ // platform.operate's own law over one branch, on top of the shared step check: the branch's closed
2
+ // effect, proof and capability sets; the approved plan hash and the approval that covers it; every
3
+ // desired resource inventoried under the same kind and inside the mutable ceiling; port claims owned
4
+ // by this operation and never freed by mutating their holder; every mutation inventoried first and
5
+ // inside the approved set; convergence agreeing with the mutation count; the complete proof set
6
+ // passed before an operated outcome; and no capability handle or credential-shaped token anywhere.
7
+ import { existsSync } from 'node:fs';
8
+ import { readFile } from 'node:fs/promises';
9
+ import path from 'node:path';
10
+ import process from 'node:process';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { validateStep } from '../../scripts/validate-step.mjs';
13
+ import { tableUnder } from '../../scripts/validate-response.mjs';
14
+
15
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
16
+
17
+ // The three service kinds are branches of one job. Each publishes its own closed effect set, its own
18
+ // required proof set, and the exact capabilities it needs; a cross-filed effect is how an unapproved
19
+ // change acquires the appearance of authority.
20
+ export const KIND_EFFECTS = {
21
+ observability: ['update-config', 'restart-service', 'upsert-dashboard', 'update-remote-write'],
22
+ sonar: ['create-project', 'assign-profile', 'assign-gate', 'enforce-setting'],
23
+ tunnel: ['create-tunnel', 'update-tunnel-route', 'upsert-proxied-dns'],
24
+ };
25
+ export const KIND_CHECKS = {
26
+ observability: ['service-health', 'target-boundary', 'label-boundary', 'remote-write-delivery', 'sample-ordering', 'retry-backoff', 'sensitive-data-filter'],
27
+ sonar: ['service-available', 'project-exists', 'source-revision', 'profile-assigned', 'gate-assigned', 'enforcement-active'],
28
+ tunnel: ['dns-target', 'tunnel-route', 'tls', 'public-https'],
29
+ };
30
+ export const KIND_CAPABILITIES = {
31
+ observability: ['metrics:remote-write'],
32
+ sonar: ['sonar:project-admin'],
33
+ tunnel: ['tunnel:write', 'dns:write'],
34
+ };
35
+
36
+ // A credential is resolved for use, never written down. Fingerprints and commit heads are legitimate
37
+ // long hex, so they are scrubbed before the unbroken-run heuristic runs.
38
+ export function credentialLeak(value) {
39
+ if (value.includes('capability://')) return true;
40
+ const scrubbed = value.replaceAll(/sha256:[0-9a-f]{64}/g, '').replaceAll(/\b[0-9a-f]{40}\b/g, '');
41
+ if (/(?:token|secret|password|api[_-]?key|bearer|authorization)\s*[:=]\s*\S/i.test(scrubbed)) return true;
42
+ return /[A-Za-z0-9+=]{32,}/.test(scrubbed);
43
+ }
44
+ function forEachString(value, visit, at = '$') {
45
+ if (typeof value === 'string') return visit(value, at);
46
+ if (Array.isArray(value)) { value.forEach((item, i) => forEachString(item, visit, `${at}[${i}]`)); return; }
47
+ if (value !== null && typeof value === 'object') for (const [k, child] of Object.entries(value)) forEachString(child, visit, `${at}.${k}`);
48
+ }
49
+ const empty = (v) => v === undefined || v === null || v === '' || v === '—';
50
+ const asList = (v) => (Array.isArray(v) ? v : []);
51
+
52
+ export async function validatePlatformStep(branchDir, root = ROOT) {
53
+ const base = await validateStep(root, branchDir);
54
+ const errors = [...base.errors];
55
+ const { response, request, requirements = {}, present = new Set() } = base;
56
+ if (!response || response.operatorId !== 'platform.operate') return { errors };
57
+ const has = (f) => existsSync(path.join(branchDir, f));
58
+ const read = (f) => readFile(path.join(branchDir, f), 'utf8');
59
+
60
+ const desired = requirements.desiredState ?? {};
61
+ const kind = String(desired.serviceKind ?? '');
62
+ const desiredEffects = asList(desired.effects);
63
+ const desiredResources = asList(desired.resourceRefs);
64
+ const mutable = new Set(asList(desired.mutableResourceRefs));
65
+ const observationOnly = new Set(asList(desired.observationOnlyResourceRefs));
66
+ const portClaims = asList(requirements.portClaims);
67
+ const service = requirements.service;
68
+
69
+ if (empty(requirements.approval)) errors.push('request.json: approval has no default; changing a shared runtime is never something an agent decides alone');
70
+ if (kind && !KIND_EFFECTS[kind]) errors.push(`request.json: ${kind} is not a service kind this operator operates`);
71
+ if (KIND_EFFECTS[kind]) {
72
+ if (new Set(desiredEffects).size !== desiredEffects.length) errors.push('request.json: desiredState.effects must not repeat an effect');
73
+ for (const e of desiredEffects) if (!KIND_EFFECTS[kind].includes(e)) errors.push(`request.json: requested effect ${e} does not belong to the ${kind} service kind`);
74
+ }
75
+ if (!empty(service) && mutable.size && !mutable.has(service)) errors.push('request.json: the operated service must be inside desiredState.mutableResourceRefs');
76
+ for (const r of mutable) if (observationOnly.has(r)) errors.push(`request.json: resource ${r} cannot be both mutable and observation-only`);
77
+ for (const r of desiredResources) if (mutable.size && !mutable.has(r)) errors.push(`request.json: desired resource ${r} lies outside the mutable ceiling`);
78
+ const claimedPorts = portClaims.map((c) => c.port);
79
+ if (new Set(claimedPorts).size !== claimedPorts.length) errors.push('request.json: portClaims must not claim the same port twice');
80
+ for (const c of portClaims) if (desiredResources.length && !desiredResources.includes(c.resourceRef)) errors.push(`request.json: port ${c.port} is claimed for ${c.resourceRef}, which this operation does not own`);
81
+ // The input contract refuses any string that carries credential material.
82
+ forEachString(request?.requirements ?? {}, (text, at) => { if (credentialLeak(text)) errors.push(`request.json: ${at} carries a credential value, which cannot enter the operator contract`); });
83
+
84
+ let delta = null;
85
+ if (present.has('delta') && has('response/data/delta.json')) { try { delta = JSON.parse(await read('response/data/delta.json')); } catch { delta = null; } }
86
+ let checks = null;
87
+ if (present.has('checks') && has('response/data/checks.json')) { try { checks = JSON.parse(await read('response/data/checks.json')); } catch { checks = null; } }
88
+
89
+ let inventoried = new Set();
90
+ if (delta) {
91
+ const refs = delta.inventoriedResources.map((r) => r.resourceRef);
92
+ inventoried = new Set(refs);
93
+ if (new Set(refs).size !== refs.length) errors.push('response/data/delta.json: inventoriedResources must not list a resource twice');
94
+ const ports = delta.observedPortHolders.map((h) => h.port);
95
+ if (new Set(ports).size !== ports.length) errors.push('response/data/delta.json: observedPortHolders must not list the same port twice');
96
+ if (!empty(service) && delta.serviceRef !== service) errors.push('response/data/delta.json: the delta operates a service the request did not name');
97
+ if (!empty(service) && !inventoried.has(service)) errors.push(`response/data/delta.json: service ${service} was not inventoried before the operation`);
98
+ if (kind && delta.serviceKind !== kind) errors.push(`response/data/delta.json: the delta names the ${delta.serviceKind} branch, not ${kind}`);
99
+ const byRef = new Map(delta.inventoriedResources.map((r) => [r.resourceRef, r]));
100
+ for (const r of desiredResources) {
101
+ const res = byRef.get(r);
102
+ if (!res) errors.push(`response/data/delta.json: desired resource ${r} is absent from the bound inventory`);
103
+ else if (res.kind !== kind) errors.push(`response/data/delta.json: resource ${r} belongs to the ${res.kind} branch and is outside this ${kind} operation`);
104
+ }
105
+ if (!empty(requirements.approval) && delta.approvalRef !== requirements.approval) errors.push('response/data/delta.json: the bound approval is not the one the request declared');
106
+ if (!empty(desired.planSha256) && delta.planSha256 !== desired.planSha256) errors.push('response/data/delta.json: planSha256 must equal the approved plan hash');
107
+ for (const e of delta.allowedEffects) if (KIND_EFFECTS[kind] && !KIND_EFFECTS[kind].includes(e)) errors.push(`response/data/delta.json: approved effect ${e} does not belong to the ${kind} service kind`);
108
+ for (const e of desiredEffects) if (!delta.allowedEffects.includes(e)) errors.push(`response/data/delta.json: requested effect ${e} is outside the approved effect set`);
109
+
110
+ const held = delta.capabilities.map((c) => c.capability);
111
+ if (new Set(held).size !== held.length) errors.push('response/data/delta.json: capabilities must not repeat a capability');
112
+ for (const c of held) if (KIND_CAPABILITIES[kind] && !KIND_CAPABILITIES[kind].includes(c)) errors.push(`response/data/delta.json: capability ${c} is not used by the ${kind} branch`);
113
+ for (const c of KIND_CAPABILITIES[kind] ?? []) if (!held.includes(c)) errors.push(`response/data/delta.json: the ${kind} branch requires the ${c} capability`);
114
+
115
+ const portHolderRefs = new Set(delta.observedPortHolders.map((h) => h.holderRef));
116
+ const applied = new Set(delta.appliedEffects);
117
+ const mutatedEffects = new Set();
118
+ for (const m of delta.mutations) {
119
+ if (KIND_EFFECTS[kind] && !KIND_EFFECTS[kind].includes(m.effect)) errors.push(`response/data/delta.json: effect ${m.effect} does not belong to the ${kind} service kind`);
120
+ if (!applied.has(m.effect)) errors.push(`response/data/delta.json: mutation effect ${m.effect} is absent from appliedEffects`);
121
+ mutatedEffects.add(m.effect);
122
+ if (!inventoried.has(m.resourceRef)) errors.push(`response/data/delta.json: resource ${m.resourceRef} was mutated without being inventoried first`);
123
+ // A port already in use is a coordination finding, never permission to reclaim it.
124
+ if (portHolderRefs.has(m.resourceRef)) errors.push(`response/data/delta.json: resource ${m.resourceRef} holds a claimed port and must never be mutated to free it`);
125
+ if (delta.mutableResourceRefs.length && !delta.mutableResourceRefs.includes(m.resourceRef)) errors.push(`response/data/delta.json: resource ${m.resourceRef} was mutated outside the mutable ceiling`);
126
+ }
127
+ for (const e of applied) if (!mutatedEffects.has(e)) errors.push(`response/data/delta.json: applied effect ${e} records no mutation`);
128
+ if (delta.convergence === 'already-converged' && delta.mutations.length > 0) errors.push('response/data/delta.json: an already-converged operation cannot report a mutation');
129
+ if (delta.convergence === 'converged' && delta.mutations.length === 0) errors.push('response/data/delta.json: a converged operation must report the mutation that converged it');
130
+ // The receipt is durable; a durable record of a capability is a leaked credential with a delay.
131
+ forEachString(delta, (text, at) => { if (credentialLeak(text)) errors.push(`response/data/delta.json: ${at} records a credential, which the receipt refuses`); });
132
+ } else if (response.status === 'done') errors.push('response/data/delta.json: a done branch needs the derived and applied delta');
133
+
134
+ let findingCount = 0;
135
+ if (checks) {
136
+ if (!empty(service) && checks.serviceRef !== service) errors.push('response/data/checks.json: the proof set names a service the request did not operate');
137
+ if (kind && checks.serviceKind !== kind) errors.push('response/data/checks.json: the proof set names another service kind');
138
+ // The required proof set is the whole set the branch publishes; the caller cannot ask for less.
139
+ const requested = new Set(checks.requiredCheckNames);
140
+ for (const n of KIND_CHECKS[kind] ?? []) if (!requested.has(n)) errors.push(`response/data/checks.json: the ${kind} branch must require the ${n} check`);
141
+ for (const n of requested) if (KIND_CHECKS[kind] && !KIND_CHECKS[kind].includes(n)) errors.push(`response/data/checks.json: check ${n} does not belong to the ${kind} service kind`);
142
+
143
+ const seen = new Set();
144
+ const proved = new Set();
145
+ for (const c of checks.checks) {
146
+ const key = `${c.name}|${c.resourceRef}`;
147
+ if (seen.has(key)) errors.push(`response/data/checks.json: check ${c.name} is recorded twice for ${c.resourceRef}`);
148
+ seen.add(key);
149
+ if (delta && !inventoried.has(c.resourceRef)) errors.push(`response/data/checks.json: check ${c.name} names uninventoried resource ${c.resourceRef}`);
150
+ if (c.status === 'passed') proved.add(c.name);
151
+ else if (response.status === 'done') errors.push(`response/data/checks.json: check ${c.name} failed, so the operation cannot be reported as operated`);
152
+ }
153
+ if (response.status === 'done') for (const n of KIND_CHECKS[kind] ?? []) if (!proved.has(n)) errors.push(`response/data/checks.json: the ${kind} branch cannot be proved without the ${n} check`);
154
+
155
+ findingCount = checks.findings.length;
156
+ for (const f of checks.findings) {
157
+ if (delta && !inventoried.has(f.resourceRef)) errors.push(`response/data/checks.json: finding on ${f.resourceRef} names an uninventoried resource`);
158
+ if (f.code !== 'PORT_COORDINATION_REQUIRED') continue;
159
+ if (f.port === null) errors.push('response/data/checks.json: a port coordination finding must name the port');
160
+ if (f.holderRef === null) errors.push('response/data/checks.json: a port coordination finding must name the process that already holds the port');
161
+ if (response.status === 'done') errors.push('response/data/checks.json: a port coordination finding cannot end in an operated outcome');
162
+ else if (response.stop !== 'PORT_CONFLICT') errors.push('response/response.json: a port coordination finding requires the PORT_CONFLICT failure');
163
+ }
164
+ forEachString(checks, (text, at) => { if (credentialLeak(text)) errors.push(`response/data/checks.json: ${at} records a credential, which the receipt refuses`); });
165
+ } else if (response.status === 'done') errors.push('response/data/checks.json: a done branch needs the proved check set');
166
+
167
+ if (present.has('platform-operation-receipt') && has('response/response.md')) {
168
+ const text = await read('response/response.md');
169
+ const binding = Object.fromEntries((tableUnder(text, '## Binding') ?? []).map(([k, v]) => [k, v]));
170
+ if (!empty(service) && binding.Service !== service) errors.push('response/response.md: Binding names a service the request did not operate');
171
+ if (kind && binding['Service kind'] !== kind) errors.push('response/response.md: Binding names another service kind');
172
+ if (!empty(desired.planSha256) && binding['Desired state'] !== desired.planSha256) errors.push('response/response.md: Binding carries a desired state the approval did not cover');
173
+ if (!empty(requirements.approval) && binding.Approval !== requirements.approval) errors.push('response/response.md: Binding names an approval the request did not declare');
174
+ const conv = Object.fromEntries((tableUnder(text, '## Convergence') ?? []).map(([k, v]) => [k, v]));
175
+ if (delta && conv.Convergence !== delta.convergence) errors.push(`response/response.md: Convergence says ${conv.Convergence} but the delta says ${delta.convergence}`);
176
+ const mdChecks = tableUnder(text, '## Checks') ?? [];
177
+ if (checks && mdChecks.length !== checks.checks.length) errors.push(`response/response.md: Checks has ${mdChecks.length} rows, the proof set has ${checks.checks.length}`);
178
+ const mdMutations = tableUnder(text, '## Mutations') ?? [];
179
+ if (delta && mdMutations.length !== delta.mutations.length) errors.push(`response/response.md: Mutations has ${mdMutations.length} rows, the delta has ${delta.mutations.length}`);
180
+ const mdFindings = tableUnder(text, '## Findings') ?? [];
181
+ if (checks && mdFindings.length !== findingCount) errors.push(`response/response.md: Findings has ${mdFindings.length} rows, the proof set has ${findingCount}`);
182
+ if (credentialLeak(text)) errors.push('response/response.md: a credential handle or secret-shaped token must never be recorded in the receipt');
183
+ } else if (response.status === 'done') errors.push('response/response.md: a done branch needs the operation receipt');
184
+
185
+ return { errors };
186
+ }
187
+
188
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
189
+ const target = process.argv[2];
190
+ if (!target) { process.stderr.write('usage: node validate.mjs <session>/step-N/parallel-M\n'); process.exit(2); }
191
+ const { errors } = await validatePlatformStep(path.resolve(target));
192
+ if (errors.length) { process.stderr.write(`${errors.join('\n')}\n`); process.exitCode = 1; } else process.stdout.write('valid platform.operate branch\n');
193
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "schemaVersion": 9,
3
+ "note": "Stop codes only quality.verify 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
+ "PREDECESSOR_MIXED": {
6
+ "domain": "caller",
7
+ "disposition": "terminate",
8
+ "meaning": {
9
+ "en": "Two predecessor receipts describe different source heads, so their union is a delivery nobody built.",
10
+ "vi": "Hai biên bản tiền nhiệm mô tả hai source head khác nhau, nên hợp của chúng là một delivery chẳng ai xây."
11
+ },
12
+ "resume": {
13
+ "en": "Supply one coherent predecessor set on one head.",
14
+ "vi": "Cấp một bộ tiền nhiệm nhất quán trên cùng một head."
15
+ }
16
+ },
17
+ "PREDECESSOR_STALE": {
18
+ "domain": "caller",
19
+ "disposition": "terminate",
20
+ "meaning": {
21
+ "en": "A predecessor fingerprint no longer matches the frozen source.",
22
+ "vi": "Fingerprint của một tiền nhiệm không còn khớp source đã đóng băng."
23
+ },
24
+ "resume": {
25
+ "en": "Bring a refreshed upstream receipt.",
26
+ "vi": "Mang biên bản thượng nguồn đã làm mới."
27
+ }
28
+ },
29
+ "GATE_UNAVAILABLE": {
30
+ "domain": "platform",
31
+ "disposition": "terminate",
32
+ "meaning": {
33
+ "en": "A required gate cannot be executed at all in this environment, and an unmeasurable gate is not a passed one.",
34
+ "vi": "Một cổng bắt buộc hoàn toàn không chạy được ở môi trường này, và cổng không đo được không phải cổng đã qua."
35
+ },
36
+ "resume": {
37
+ "en": "Provide a working gate environment.",
38
+ "vi": "Cấp một môi trường chạy cổng hoạt động được."
39
+ }
40
+ },
41
+ "DEBT_UNAPPROVED": {
42
+ "domain": "caller",
43
+ "disposition": "terminate",
44
+ "meaning": {
45
+ "en": "A declared debt has no live owner approval, or it covers a gate that passed or a boundary-drift failure.",
46
+ "vi": "Một khoản nợ đã khai không có phê duyệt còn sống của chủ, hoặc phủ lên một cổng đã pass hay một lỗi boundary-drift."
47
+ },
48
+ "resume": {
49
+ "en": "Supply the unexpired owner approval, or drop the debt.",
50
+ "vi": "Cấp phê duyệt của chủ còn hạn, hoặc bỏ khoản nợ."
51
+ }
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "schemaVersion": 9,
3
+ "id": "quality.verify",
4
+ "domain": "quality",
5
+ "job": "Verify one bounded delivery by running its declared gates against an unchanged predecessor receipt at one frozen head, and return the exact measured verdict, repairing nothing.",
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
+ }
19
+ }
20
+ }
@@ -0,0 +1,185 @@
1
+ # quality.verify
2
+
3
+ ## Job
4
+
5
+ Verify one bounded delivery by running its declared gates against an unchanged predecessor receipt
6
+ at one frozen head, and return the exact measured verdict, repairing nothing.
7
+
8
+ ## One delivery, one head, at least one producer receipt
9
+
10
+ The three Inputs are the three shapes a delivery arrives in: a backend implementation, a frontend
11
+ source application, and the `changes` record that names which paths moved and which gates and
12
+ surfaces they touch. Each is optional on its own and at least one must be present, because a
13
+ verification with no producer receipt has no head to freeze and no delivery to measure. Every
14
+ predecessor receipt must report the same source head, and that head must be the head
15
+ `request/request.json` froze; two predecessors on different heads describe two different deliveries,
16
+ and gating the union of them measures something nobody built. That is `PREDECESSOR_MIXED`, refused
17
+ before a single command runs rather than discovered later as a confusing gate failure. A predecessor
18
+ whose fingerprint no longer matches the frozen source is `PREDECESSOR_STALE`. What a predecessor
19
+ decided is consumed unchanged: this operator never re-plans the delivery, re-opens its boundary, or
20
+ forms an opinion about whether the change was a good one.
21
+
22
+ ## The head is confirmed inside the gate
23
+
24
+ There is no separate head-verification step, because a head confirmed anywhere but at the gate is a
25
+ head that could drift before the first command. The producer wrote its delivery on the session
26
+ branch `session/<sessionId>` of the routed checkout, in a git worktree prepared from the frozen head,
27
+ and committed it once. `request/request.json` therefore pins `@workspaces/be` or `@workspaces/fe` at
28
+ that exact commit sha in `contexts[].head`, and step 1 confirms the observed head equals it before
29
+ anything else happens; a difference is `SOURCE_DRIFT`. The predecessor receipt's own commit must
30
+ equal that same head, because a receipt describing a commit the gates are not standing on is
31
+ `PREDECESSOR_STALE`. Every gate runs inside that session branch worktree and never on the person's
32
+ checked-out branch, so a gate result names one commit somebody can check out again.
33
+
34
+ ## A red gate is a verdict, not a stop
35
+
36
+ Quality measures. It does not repair, redesign, reclassify, or negotiate. A failing gate produces a
37
+ red verdict naming the failure and its classification, and that verdict goes back to the owner who
38
+ can fix it; the branch is `done`, not `blocked`, because the operator did exactly what it was asked
39
+ to do. Only an inability to reach any verdict at all is a stop. The operator does not touch product
40
+ source, does not adjust a gate command or its configuration to change an outcome, and does not
41
+ substitute an easier check for a hard one.
42
+
43
+ ## A gate result is measured, never narrated
44
+
45
+ Every executed gate carries its command reference, its exit code, and its evidence, in its own file
46
+ under `response/data/gates/`. One file per gate is what makes a gate result quotable on its own: a
47
+ later reader opens `lint.json` and sees one command, one exit code, one classification, without
48
+ reading around a bundle. A pass means exit code zero with evidence beside it; a failure means a
49
+ non-zero exit code with evidence and a classification. The classification is read from the structured
50
+ diagnostics after the command ran, never chosen before it: `in-boundary` when the delivery owner can
51
+ fix it, `boundary-drift` when fixing it would change an approved boundary, `flaky` when identical
52
+ source and environment produced contradictory outcomes, and `external-blocker` when the environment
53
+ or a dependency prevented a verdict at all. A rerun exists to tell those four apart. It never exists
54
+ to convert an unexplained failure into a pass. No gate is skipped, suppressed, substituted, or moved
55
+ with `passWithNoTests`, and a zero-test run is not a pass. Every gate file records the same source
56
+ head, because two gates standing on two heads measured two deliveries.
57
+
58
+ ## Two facts about this codebase
59
+
60
+ Sonar measures new code only. The pinned gate is scoped to the change, so a green Sonar result is a
61
+ statement about the diff and not about the project, and a project may sit red beneath it. Under the
62
+ default `sonarScope` of `new-code`, a passing Sonar result is recorded together with a
63
+ `SONAR_NEW_CODE_ONLY` finding; without it a later reader takes a green gate for project health, which
64
+ is the exact misreading this operator exists to prevent.
65
+
66
+ End-to-end is never run unless a person asked for it in this invocation, which is why
67
+ `explicitE2eRequest` defaults to false. Otherwise the gate is recorded as `skipped-not-requested`
68
+ with an `E2E_NOT_REQUESTED` finding: no command, no exit code, no evidence, and no implication that
69
+ behaviour was proved. Planning the e2e gate without that request is invalid input.
70
+
71
+ ## Coverage carries four thresholds, not one
72
+
73
+ Statements, lines, functions and branches are each compared against their own threshold, and
74
+ branches carry an independent one because a branch threshold folded into the statement figure is how
75
+ an untested error path passes. The thresholds default to the four percentages the routed gate
76
+ configuration already pins, so a person who names none is measured against the project's own bar. A
77
+ metric under its threshold makes the unit gate a failure and records `COVERAGE_BELOW_THRESHOLD`; it
78
+ is never a note beside a green result.
79
+
80
+ ## Debt is explicit and owned
81
+
82
+ A gate stays red only when an owner-approved debt record covers it, naming the debt, the gate, the
83
+ approval, the owner and the expiry, and only when that approval is still live at the instant the
84
+ gate was measured. An expired approval is not a debt and a debt against a gate that passed is a
85
+ record of nothing; both are refused as `DEBT_UNAPPROVED`. A debt covers only an `in-boundary`
86
+ failure, the kind the delivery owner can fix; a `boundary-drift` failure belongs to whoever owns the
87
+ boundary and cannot be owed away here. `declaredDebts` defaults to the empty list, so carrying a red
88
+ gate is always something a person did on purpose.
89
+
90
+ ## The verdict
91
+
92
+ `pass` requires every required gate to have passed, or to have failed `in-boundary` under a declared
93
+ debt. Every other shape is `fail`, including a required gate the environment blocked: an unmeasurable
94
+ gate is not a passed one. A non-required gate that fails is recorded and does not by itself turn the
95
+ verdict red, which is the whole reason `required` exists, and it is the gate plan's declaration and
96
+ never this operator's judgement.
97
+
98
+ ## Boundary
99
+
100
+ Context is read-only. The operator writes only `response/` of its own branch: one `gate-result` file
101
+ per gate under `response/data/gates/`, `data/coverage.json`, `response.md` and `response.json`. It
102
+ does not modify product source, configuration, or a gate command; it does not redesign, repair, or
103
+ reclassify a measured failure into a pass; it does not run the end-to-end suite unasked; it does not
104
+ add, weaken, skip, suppress, or substitute a declared gate; it does not read a project-level Sonar
105
+ verdict out of a new-code quality gate; and it does not carry a debt no owner approved or whose
106
+ approval expired.
107
+
108
+ ## Context
109
+
110
+ | Alias | Bind | Required |
111
+ | --- | --- | --- |
112
+ | `@workspaces/<project>/<role>/gates` | the pinned gate commands, their configuration and the thresholds they carry; what "the same gate" means across runs | yes |
113
+ | `@workspaces/be` | the routed backend checkout at the pinned commit, the subject every gate measures when the delivery is a backend | no |
114
+ | `@workspaces/fe` | the routed frontend checkout at the pinned commit, the subject every gate measures when the delivery is a frontend | no |
115
+ | `@worktrees/debts` | owner-approved debt records and their expiry; a red gate is carried only from here | no |
116
+
117
+ ## Inputs
118
+
119
+ | Kind | From | Required |
120
+ | --- | --- | --- |
121
+ | `backend-source-application` | `backend.source.apply`, the backend delivery to verify | no |
122
+ | `frontend-source-application` | `frontend.source.apply`, the frontend delivery to verify | no |
123
+ | `changes` | `backend.source.apply` or `frontend.source.apply`, the paths that moved and the gates and surfaces they name | no |
124
+
125
+ ## Requirements
126
+
127
+ | Field | Type | Default | Ask |
128
+ | --- | --- | --- | --- |
129
+ | `gates` | list of `{gate, commandRef, configRef, required}` | the routed gate plan | Which pinned gates to run, once each, from format, lint, typecheck, build, unit-coverage, integration, e2e and sonar |
130
+ | `thresholds` | list of `{statements, lines, functions, branches}` | the four percentages the routed gate configuration pins | The percentage each coverage metric must meet, branches on its own |
131
+ | `explicitE2eRequest` | choice | false | false unless a person asked for the end-to-end suite in this invocation; true only then |
132
+ | `sonarScope` | choice | new-code | new-code or overall; it must agree with whether sonar is in the gate plan |
133
+ | `declaredDebts` | list of `{debtId, gate, approvalRef, ownerRef, expiresAt}` | [] | Owner-approved debts that let a named gate stay red |
134
+ | `resume` | token | null | The blocked branch's token when re-entering after a stop |
135
+
136
+ ## Steps
137
+
138
+ | # | Step | Params | Reads | Writes | Stops with |
139
+ | --- | --- | --- | --- | --- | --- |
140
+ | 1 | Validate the gate, confirm the frozen head and the resume | `resume` | `request/request.json`, @workspaces/be or @workspaces/fe at the commit the request pinned, @tools/git | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
141
+ | 2 | Consume the predecessors unchanged | — | inputs `backend-source-application`, `frontend-source-application` and `changes` at their fingerprints, and the commit each one recorded | — | `PREDECESSOR_MIXED`, `PREDECESSOR_STALE` |
142
+ | 3 | Run the gates in declared order | `gates`, `explicitE2eRequest`, `sonarScope` | @workspaces/<project>/<role>/gates, @workspaces/be or @workspaces/fe as the subject each gate measures, @tools/http | `response/data/gates/<gate>.json`, @tools/shell | `GATE_UNAVAILABLE` |
143
+ | 4 | Apply the coverage policy | `thresholds` | `response/data/gates/<gate>.json` of the unit gate | `response/data/coverage.json` | — |
144
+ | 5 | Classify each failure from its diagnostics | — | `response/data/gates/<gate>.json` of every red gate | — | — |
145
+ | 6 | Apply approved debt | `declaredDebts` | @worktrees/debts, `response/data/gates/<gate>.json` | — | `DEBT_UNAPPROVED` |
146
+ | 7 | Compute the verdict, write the receipt and emit | — | everything above | `response/response.md`, `response/response.json` | — |
147
+
148
+ A gate that could not be executed at all in this environment is `GATE_UNAVAILABLE` when it was
149
+ required; a non-required gate the environment blocked is recorded as `external-blocker` and the
150
+ verdict absorbs it. There is no repair code, because repair is not this operator's job: an
151
+ `in-boundary` failure returns as a red verdict to the owner who can fix it, and the fixed delivery
152
+ comes back as a new head with a new predecessor fingerprint. A resume reuses only unchanged
153
+ fingerprinted observations and consumes the exact delta; a resume that adds no predecessor, gate,
154
+ debt or source change is `NO_PROGRESS`, because the same fingerprint cannot yield a different answer.
155
+
156
+ ## Outputs
157
+
158
+ | Kind | File | Type | Required |
159
+ | --- | --- | --- | --- |
160
+ | `quality-verification` | `response/response.md` | md | yes |
161
+ | `gate-result` | `response/data/gates/<gate>.json` | data | yes |
162
+ | `coverage` | `response/data/coverage.json` | data | no |
163
+
164
+ ## Stops
165
+
166
+ | Code | Disposition |
167
+ | --- | --- |
168
+ | `INVALID_INPUT` | terminate |
169
+ | `SOURCE_DRIFT` | terminate |
170
+ | `NO_PROGRESS` | terminate |
171
+ | `PREDECESSOR_MIXED` | terminate |
172
+ | `PREDECESSOR_STALE` | terminate |
173
+ | `GATE_UNAVAILABLE` | terminate |
174
+ | `DEBT_UNAPPROVED` | terminate |
175
+
176
+ ## Next
177
+
178
+ | When | Operator |
179
+ | --- | --- |
180
+ | a backend gate failed in boundary and the backend owner must fix it | `backend.source.apply` |
181
+ | a frontend gate failed in boundary and the frontend owner must apply the fix | `frontend.source.apply` |
182
+ | the verdict is green and the delivery is ready to publish | `git.publish` |
183
+ | the verdict is green and the published head must reach an environment | `release.deploy` |
184
+ | the gates are green and the promise must be reconciled against the delivered source | `business.decide` |
185
+ | the gates are green and a person asked for the journey to be walked | `uat.verify` |
@@ -0,0 +1,177 @@
1
+ # quality.verify
2
+
3
+ ## Việc
4
+
5
+ Kiểm định một delivery có ranh giới bằng cách chạy các cổng đã khai trên một biên bản tiền nhiệm còn
6
+ nguyên vẹn tại một head đã đóng băng, rồi trả về đúng phán quyết đo được, không sửa gì cả.
7
+
8
+ ## Một delivery, một head, ít nhất một biên bản của người sản xuất
9
+
10
+ Ba Đầu vào là ba hình dạng mà một delivery có thể tới: một bản hiện thực backend, một bản áp source
11
+ frontend, và bản ghi `changes` nêu những path nào đã dịch chuyển cùng những cổng và bề mặt chúng chạm
12
+ tới. Từng cái đều không bắt buộc, nhưng phải có ít nhất một, vì một lần kiểm định không có biên bản
13
+ của người sản xuất thì chẳng có head nào để đóng băng và chẳng có delivery nào để đo. Mọi biên bản
14
+ tiền nhiệm phải báo cùng một source head, và head đó phải là head mà `request/request.json` đã đóng
15
+ băng; hai tiền nhiệm trên hai head khác nhau mô tả hai delivery khác nhau, và gác hợp của chúng là đo
16
+ một thứ không ai xây. Đó là `PREDECESSOR_MIXED`, bị từ chối trước khi có lệnh nào chạy thay vì lộ ra
17
+ về sau dưới dạng một lỗi cổng khó hiểu. Một tiền nhiệm có fingerprint không còn khớp source đã đóng
18
+ băng là `PREDECESSOR_STALE`. Điều tiền nhiệm đã quyết được tiêu thụ nguyên vẹn: operator này không
19
+ bao giờ lập lại kế hoạch delivery, mở lại ranh giới của nó, hay có ý kiến về việc thay đổi ấy hay dở.
20
+
21
+ ## Head được xác nhận ngay trong cổng vào
22
+
23
+ Không có bước xác nhận head riêng, vì một head được xác nhận ở chỗ nào khác cổng vào là một head còn
24
+ kịp trôi trước lệnh đầu tiên. Người sản xuất đã viết delivery trên nhánh phiên `session/<sessionId>`
25
+ của checkout đã route, trong một git worktree dựng từ head đã đóng băng, và commit đúng một lần. Vì
26
+ thế `request/request.json` ghim `@workspaces/be` hoặc `@workspaces/fe` tại đúng commit sha đó trong
27
+ `contexts[].head`, và bước 1 xác nhận head quan sát được bằng đúng nó trước khi bất kỳ điều gì khác
28
+ xảy ra; khác nhau là `SOURCE_DRIFT`. Commit mà biên bản tiền nhiệm ghi phải bằng chính head đó, vì
29
+ một biên bản mô tả một commit mà các cổng không đứng trên là `PREDECESSOR_STALE`. Mọi cổng chạy bên
30
+ trong worktree của nhánh phiên ấy, không bao giờ trên nhánh người ta đang checkout, nên một kết quả
31
+ cổng gọi tên đúng một commit mà ai đó có thể checkout lại.
32
+
33
+ ## Cổng đỏ là một phán quyết, không phải một mã dừng
34
+
35
+ Quality đo. Nó không sửa, không thiết kế lại, không phân loại lại, không mặc cả. Một cổng hỏng sinh
36
+ ra một phán quyết đỏ gọi tên cái hỏng và phân loại của nó, và phán quyết ấy quay về người chủ có thể
37
+ sửa; nhánh là `done`, không phải `blocked`, vì operator đã làm đúng việc được giao. Chỉ khi hoàn toàn
38
+ không đạt tới được phán quyết nào thì mới là một mã dừng. Operator không đụng vào source sản phẩm,
39
+ không chỉnh lệnh cổng hay cấu hình của nó để đổi kết quả, và không thay một kiểm khó bằng một kiểm dễ.
40
+
41
+ ## Kết quả cổng là đo được, không phải kể lại
42
+
43
+ Mọi cổng đã chạy mang theo tham chiếu lệnh, mã thoát và bằng chứng của nó, trong file riêng dưới
44
+ `response/data/gates/`. Một file cho một cổng là thứ khiến kết quả cổng trích dẫn được một mình: người
45
+ đọc sau mở `lint.json` và thấy một lệnh, một mã thoát, một phân loại, không phải đọc vòng quanh một
46
+ bó. Pass nghĩa là mã thoát bằng không kèm bằng chứng bên cạnh; fail nghĩa là mã thoát khác không kèm
47
+ bằng chứng và một phân loại. Phân loại được đọc từ chẩn đoán có cấu trúc sau khi lệnh đã chạy, không
48
+ bao giờ chọn trước: `in-boundary` khi chủ delivery sửa được, `boundary-drift` khi sửa nó là đổi một
49
+ ranh giới đã duyệt, `flaky` khi cùng source cùng môi trường cho hai kết quả mâu thuẫn, và
50
+ `external-blocker` khi môi trường hay một phụ thuộc chặn hẳn phán quyết. Chạy lại tồn tại để phân
51
+ biệt bốn thứ đó. Nó không bao giờ tồn tại để biến một lỗi chưa giải thích được thành pass. Không cổng
52
+ nào bị bỏ, bịt, thay hay dời bằng `passWithNoTests`, và một lần chạy không có test nào không phải là
53
+ pass. Mọi file cổng ghi cùng một source head, vì hai cổng đứng trên hai head là đã đo hai delivery.
54
+
55
+ ## Hai sự thật về codebase này
56
+
57
+ Sonar chỉ đo new code. Cổng đã ghim được thu hẹp theo thay đổi, nên một kết quả Sonar xanh là phát
58
+ biểu về cái diff chứ không phải về dự án, và dự án bên dưới vẫn có thể đỏ. Dưới mặc định `new-code`
59
+ của `sonarScope`, một kết quả Sonar pass được ghi kèm phát hiện `SONAR_NEW_CODE_ONLY`; thiếu nó thì
60
+ người đọc sau hiểu cổng xanh là sức khỏe dự án, đúng cái hiểu sai mà operator này sinh ra để chặn.
61
+
62
+ End-to-end không bao giờ chạy trừ khi có người yêu cầu trong chính lần gọi này, nên
63
+ `explicitE2eRequest` mặc định là false. Ngược lại cổng được ghi là `skipped-not-requested` kèm phát
64
+ hiện `E2E_NOT_REQUESTED`: không lệnh, không mã thoát, không bằng chứng, và không hàm ý nào rằng hành
65
+ vi đã được chứng minh. Lập kế hoạch cổng e2e mà không có yêu cầu ấy là đầu vào không hợp lệ.
66
+
67
+ ## Coverage mang bốn ngưỡng, không phải một
68
+
69
+ Statement, line, function và branch mỗi thứ so với ngưỡng riêng của nó, và branch mang một ngưỡng độc
70
+ lập vì gộp ngưỡng branch vào con số statement chính là cách một nhánh lỗi chưa test đi lọt. Ngưỡng
71
+ mặc định lấy bốn phần trăm mà cấu hình cổng của route đã ghim, nên người không nêu ngưỡng nào vẫn được
72
+ đo theo đúng thanh của dự án. Một chỉ số dưới ngưỡng của nó làm cổng unit thành fail và ghi
73
+ `COVERAGE_BELOW_THRESHOLD`; nó không bao giờ là một ghi chú bên cạnh một kết quả xanh.
74
+
75
+ ## Nợ phải tường minh và có chủ
76
+
77
+ Một cổng chỉ được ở lại màu đỏ khi có bản ghi nợ được chủ duyệt phủ lên nó, nêu khoản nợ, cổng, phê
78
+ duyệt, chủ và hạn, và chỉ khi phê duyệt ấy còn sống tại đúng thời điểm cổng được đo. Phê duyệt hết hạn
79
+ không phải là nợ, và một khoản nợ đặt lên cổng đã pass là bản ghi của hư không; cả hai bị từ chối bằng
80
+ `DEBT_UNAPPROVED`. Nợ chỉ phủ được lỗi `in-boundary`, loại mà chủ delivery sửa được; lỗi
81
+ `boundary-drift` thuộc về người sở hữu ranh giới và không thể khất ở đây. `declaredDebts` mặc định là
82
+ danh sách rỗng, nên mang một cổng đỏ luôn là việc có người cố ý làm.
83
+
84
+ ## Phán quyết
85
+
86
+ `pass` đòi mọi cổng bắt buộc đã pass, hoặc đã fail `in-boundary` dưới một khoản nợ đã khai. Mọi hình
87
+ dạng khác là `fail`, kể cả một cổng bắt buộc bị môi trường chặn: cổng không đo được không phải cổng đã
88
+ qua. Một cổng không bắt buộc mà fail thì được ghi lại và tự nó không làm phán quyết đỏ, đó chính là lý
89
+ do `required` tồn tại, và nó là khai báo của kế hoạch cổng chứ không bao giờ là phán đoán của operator.
90
+
91
+ ## Ranh giới
92
+
93
+ Context chỉ đọc. Operator chỉ ghi vào `response/` của nhánh mình: một file `gate-result` cho mỗi cổng
94
+ dưới `response/data/gates/`, `data/coverage.json`, `response.md` và `response.json`. Nó không sửa
95
+ source sản phẩm, cấu hình hay lệnh cổng; không thiết kế lại, sửa chữa hay phân loại một lỗi đã đo
96
+ thành pass; không chạy bộ end-to-end khi không được hỏi; không thêm, làm yếu, bỏ, bịt hay thay một
97
+ cổng đã khai; không đọc phán quyết mức dự án ra từ một quality gate chỉ đo new code; và không mang một
98
+ khoản nợ không ai duyệt hoặc đã hết hạn.
99
+
100
+ ## Context
101
+
102
+ | Alias | Bind | Bắt buộc |
103
+ | --- | --- | --- |
104
+ | `@workspaces/<project>/<role>/gates` | lệnh cổng đã ghim, cấu hình và ngưỡng chúng mang; là nghĩa của "cùng một cổng" qua các lần chạy | có |
105
+ | `@workspaces/be` | checkout backend đã route tại commit đã ghim, chủ thể mọi cổng đo khi delivery là backend | không |
106
+ | `@workspaces/fe` | checkout frontend đã route tại commit đã ghim, chủ thể mọi cổng đo khi delivery là frontend | không |
107
+ | `@worktrees/debts` | bản ghi nợ được chủ duyệt và hạn của chúng; một cổng đỏ chỉ được mang từ đây | không |
108
+
109
+ ## Đầu vào
110
+
111
+ | Kind | Từ đâu | Bắt buộc |
112
+ | --- | --- | --- |
113
+ | `backend-source-application` | `backend.source.apply`, delivery backend cần kiểm định | không |
114
+ | `frontend-source-application` | `frontend.source.apply`, delivery frontend cần kiểm định | không |
115
+ | `changes` | `backend.source.apply` hoặc `frontend.source.apply`, những path đã dịch chuyển cùng cổng và bề mặt chúng nêu | không |
116
+
117
+ ## Yêu cầu
118
+
119
+ | Field | Kiểu | Mặc định | Hỏi |
120
+ | --- | --- | --- | --- |
121
+ | `gates` | list of `{gate, commandRef, configRef, required}` | the routed gate plan | Chạy những cổng đã ghim nào, mỗi cổng một lần, trong format, lint, typecheck, build, unit-coverage, integration, e2e và sonar |
122
+ | `thresholds` | list of `{statements, lines, functions, branches}` | the four percentages the routed gate configuration pins | Mỗi chỉ số coverage phải đạt bao nhiêu phần trăm, branch đứng riêng |
123
+ | `explicitE2eRequest` | choice | false | false trừ khi có người yêu cầu bộ end-to-end trong chính lần gọi này; chỉ khi đó mới true |
124
+ | `sonarScope` | choice | new-code | new-code hoặc overall; phải khớp với việc sonar có nằm trong kế hoạch cổng hay không |
125
+ | `declaredDebts` | list of `{debtId, gate, approvalRef, ownerRef, expiresAt}` | [] | Những khoản nợ được chủ duyệt cho phép một cổng có tên ở lại màu đỏ |
126
+ | `resume` | token | null | Token của nhánh bị chặn khi vào lại sau một mã dừng |
127
+
128
+ ## Các bước
129
+
130
+ | # | Bước | Tham số | Đọc | Ghi | Dừng với |
131
+ | --- | --- | --- | --- | --- | --- |
132
+ | 1 | Kiểm cổng vào, xác nhận head đã đóng băng và resume | `resume` | `request/request.json`, @workspaces/be hoặc @workspaces/fe tại commit mà request đã ghim, @tools/git | — | `INVALID_INPUT`, `SOURCE_DRIFT`, `NO_PROGRESS` |
133
+ | 2 | Tiêu thụ tiền nhiệm nguyên vẹn | — | Đầu vào `backend-source-application`, `frontend-source-application` và `changes` tại fingerprint của chúng, cùng commit mà mỗi cái đã ghi | — | `PREDECESSOR_MIXED`, `PREDECESSOR_STALE` |
134
+ | 3 | Chạy các cổng theo thứ tự đã khai | `gates`, `explicitE2eRequest`, `sonarScope` | @workspaces/<project>/<role>/gates, @workspaces/be hoặc @workspaces/fe là chủ thể mỗi cổng đo, @tools/http | `response/data/gates/<gate>.json`, @tools/shell | `GATE_UNAVAILABLE` |
135
+ | 4 | Áp chính sách coverage | `thresholds` | `response/data/gates/<gate>.json` của cổng unit | `response/data/coverage.json` | — |
136
+ | 5 | Phân loại từng lỗi từ chẩn đoán của nó | — | `response/data/gates/<gate>.json` của mọi cổng đỏ | — | — |
137
+ | 6 | Áp nợ đã được duyệt | `declaredDebts` | @worktrees/debts, `response/data/gates/<gate>.json` | — | `DEBT_UNAPPROVED` |
138
+ | 7 | Tính phán quyết, viết biên bản và phát | — | mọi thứ ở trên | `response/response.md`, `response/response.json` | — |
139
+
140
+ Một cổng hoàn toàn không chạy được ở môi trường này là `GATE_UNAVAILABLE` khi nó bắt buộc; một cổng
141
+ không bắt buộc bị môi trường chặn được ghi là `external-blocker` và phán quyết hấp thụ nó. Không có mã
142
+ sửa chữa, vì sửa chữa không phải việc của operator này: một lỗi `in-boundary` quay về dưới dạng phán
143
+ quyết đỏ tới người chủ sửa được nó, và delivery đã sửa quay lại thành một head mới với fingerprint
144
+ tiền nhiệm mới. Một lần resume chỉ dùng lại quan sát có fingerprint không đổi và tiêu thụ đúng phần
145
+ delta; một lần resume không thêm tiền nhiệm, cổng, nợ hay thay đổi source nào là `NO_PROGRESS`, vì
146
+ cùng một fingerprint không thể cho một câu trả lời khác.
147
+
148
+ ## Đầu ra
149
+
150
+ | Kind | File | Kiểu | Bắt buộc |
151
+ | --- | --- | --- | --- |
152
+ | `quality-verification` | `response/response.md` | md | có |
153
+ | `gate-result` | `response/data/gates/<gate>.json` | data | có |
154
+ | `coverage` | `response/data/coverage.json` | data | không |
155
+
156
+ ## Dừng
157
+
158
+ | Code | Xử lý |
159
+ | --- | --- |
160
+ | `INVALID_INPUT` | terminate |
161
+ | `SOURCE_DRIFT` | terminate |
162
+ | `NO_PROGRESS` | terminate |
163
+ | `PREDECESSOR_MIXED` | terminate |
164
+ | `PREDECESSOR_STALE` | terminate |
165
+ | `GATE_UNAVAILABLE` | terminate |
166
+ | `DEBT_UNAPPROVED` | terminate |
167
+
168
+ ## Kế tiếp
169
+
170
+ | Khi | Operator |
171
+ | --- | --- |
172
+ | một cổng backend hỏng trong ranh giới và chủ backend phải sửa | `backend.source.apply` |
173
+ | một cổng frontend hỏng trong ranh giới và chủ frontend phải áp bản sửa | `frontend.source.apply` |
174
+ | phán quyết xanh và delivery sẵn sàng để publish | `git.publish` |
175
+ | phán quyết xanh và head đã publish phải tới được một môi trường | `release.deploy` |
176
+ | các gate xanh và lời hứa phải được đối chiếu với source đã giao | `business.decide` |
177
+ | các gate xanh và có người yêu cầu đi thử hành trình | `uat.verify` |