@starci/hfs 4.7.32 → 4.7.34

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 (473) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +13 -6
  3. package/emit/contracts.mjs +4 -3
  4. package/emit/form-bounds-reader.ts.partial +58 -0
  5. package/emit/form-bounds.mjs +155 -0
  6. package/emit/graphql-input-bounds.mjs +97 -0
  7. package/emit/input-bounds.mjs +44 -0
  8. package/emit/operation-schemas.mjs +137 -0
  9. package/emit/operations-worker.mjs +2 -2
  10. package/emit/operations.mjs +52 -16
  11. package/emit/schema-worker.mjs +17 -4
  12. package/emit/type-schema.mjs +17 -5
  13. package/package.json +9 -2
  14. package/release/gate.mjs +18 -23
  15. package/release/index.mjs +1 -1
  16. package/release/push.mjs +4 -5
  17. package/runtime/engine/admission.mjs +331 -0
  18. package/runtime/engine/canonical-json.mjs +14 -0
  19. package/runtime/engine/db/blob.mjs +380 -0
  20. package/runtime/engine/db/event-compact.mjs +73 -0
  21. package/runtime/engine/db/ledger-open.mjs +38 -0
  22. package/runtime/engine/db/ledger-paths.mjs +87 -0
  23. package/runtime/engine/db/ledger.mjs +1068 -0
  24. package/runtime/engine/db/machine-connection.mjs +150 -0
  25. package/runtime/engine/db/machine-open.mjs +60 -0
  26. package/runtime/engine/db/machine-schema.mjs +105 -0
  27. package/runtime/engine/db/machine.mjs +1317 -0
  28. package/runtime/engine/db/provider-reservations.mjs +127 -0
  29. package/runtime/engine/db/provision-authority.mjs +76 -0
  30. package/runtime/engine/db/provision-lock.mjs +29 -0
  31. package/runtime/engine/db/provision-operations.mjs +146 -0
  32. package/runtime/engine/db/ref-value.mjs +67 -0
  33. package/runtime/engine/db/schema/machine.sql +920 -0
  34. package/runtime/engine/db/schema/runtime.sql +1113 -0
  35. package/runtime/engine/refuse.mjs +11 -0
  36. package/runtime/knowledge/hfs/canon-pins.yaml +65 -9
  37. package/runtime/knowledge/hfs/design-truths.yaml +149 -0
  38. package/runtime/knowledge/hfs/fe-stack.yaml +23 -5
  39. package/runtime/knowledge/hfs/rules.yaml +269 -48
  40. package/runtime/knowledge/hfs/slots.yaml +113 -53
  41. package/runtime/knowledge/patterns/be/api.yaml +46 -20
  42. package/runtime/knowledge/patterns/be/event-bus.yaml +19 -12
  43. package/runtime/knowledge/patterns/be/idempotency.yaml +265 -0
  44. package/runtime/knowledge/patterns/be/jobs.yaml +17 -12
  45. package/runtime/knowledge/patterns/be/projections.yaml +2 -2
  46. package/runtime/knowledge/patterns/be/queues.yaml +57 -22
  47. package/runtime/knowledge/patterns/be/reactors.yaml +1 -1
  48. package/runtime/knowledge/patterns/be/realtime.yaml +326 -27
  49. package/runtime/knowledge/patterns/be/saga.yaml +2 -2
  50. package/runtime/knowledge/sonar-gate.yaml +1 -1
  51. package/runtime/modules/kernel/command-policy.yaml +249 -0
  52. package/runtime/modules/kernel/failure-codes.yaml +211 -52
  53. package/runtime/modules/kernel/hook-input.yaml +6 -0
  54. package/runtime/modules/kernel/protected-zone.yaml +81 -0
  55. package/runtime/modules/kernel/removed-vocabulary.yaml +62 -0
  56. package/runtime/modules/kernel/roles.yaml +405 -0
  57. package/runtime/modules/models/registry.yaml +3 -1
  58. package/runtime/modules/models/runtimes.yaml +2 -0
  59. package/runtime/modules/schemas/application-stacks.schema.yaml +10 -2
  60. package/runtime/packages/cli/src/catalog.generated.mjs +46 -59
  61. package/runtime/packages/grammar/scripts/build-stamp.mjs +98 -0
  62. package/runtime/scripts/agent/codex-app-server.mjs +79 -0
  63. package/runtime/scripts/agent/codex-mirror-source.mjs +34 -0
  64. package/runtime/scripts/agent/host-agents.mjs +80 -0
  65. package/runtime/scripts/agent/launch-trust-policy.mjs +53 -0
  66. package/runtime/scripts/agent/trust-launch.mjs +191 -0
  67. package/runtime/scripts/agent/trust.mjs +523 -0
  68. package/runtime/scripts/api/fs/rename-over.mjs +21 -0
  69. package/runtime/scripts/api/fs/temp-path.mjs +8 -0
  70. package/runtime/scripts/api/fs/write-json-file.mjs +10 -0
  71. package/runtime/scripts/api/git/diff-names.mjs +8 -0
  72. package/runtime/scripts/api/git/head-time.mjs +10 -0
  73. package/runtime/scripts/api/git/index-lock.mjs +78 -0
  74. package/runtime/scripts/api/git/is-ancestor.mjs +5 -0
  75. package/runtime/scripts/api/git/log.mjs +5 -0
  76. package/runtime/scripts/api/git/merge-base-query.mjs +5 -0
  77. package/runtime/scripts/api/git/merge-tree.mjs +5 -0
  78. package/runtime/scripts/api/git/rev-list.mjs +5 -0
  79. package/runtime/scripts/api/git/rev-parse.mjs +13 -0
  80. package/runtime/scripts/api/git/status-query.mjs +5 -0
  81. package/runtime/scripts/api/git/worktree-list-query.mjs +5 -0
  82. package/runtime/scripts/api/node/spawn-node.mjs +6 -0
  83. package/runtime/scripts/api/npm/dist-tag.mjs +8 -0
  84. package/runtime/scripts/api/npm/lib.mjs +32 -0
  85. package/runtime/scripts/api/npm/pack-dry-run.mjs +9 -0
  86. package/runtime/scripts/api/npm/run-npm.mjs +6 -0
  87. package/runtime/scripts/api/npm/view.mjs +30 -0
  88. package/runtime/scripts/api/npm/whoami.mjs +8 -0
  89. package/runtime/scripts/api/process/hide-child-windows.mjs +31 -0
  90. package/runtime/scripts/api/process/kill-tree.mjs +22 -0
  91. package/runtime/scripts/api/process/lib.mjs +125 -0
  92. package/runtime/scripts/api/process/probe-guard-command.mjs +74 -0
  93. package/runtime/scripts/api/process/process-list.mjs +28 -0
  94. package/runtime/scripts/api/process/system-tool.mjs +37 -0
  95. package/runtime/scripts/gates/canon-digest.mjs +81 -0
  96. package/runtime/scripts/gates/canon-pins.mjs +16 -0
  97. package/runtime/scripts/gates/commit-proof.mjs +53 -0
  98. package/runtime/scripts/gates/gate-input.mjs +98 -0
  99. package/runtime/scripts/gates/gate.mjs +669 -0
  100. package/runtime/scripts/gates/grammar-dist.mjs +206 -0
  101. package/runtime/scripts/gates/runtime-package-selection.mjs +27 -0
  102. package/runtime/scripts/gates/test-world-run.mjs +254 -0
  103. package/runtime/scripts/gates/type-impact.mjs +277 -0
  104. package/runtime/scripts/gates/type-program.mjs +113 -0
  105. package/runtime/scripts/guards/assigned-command.mjs +12 -0
  106. package/runtime/scripts/guards/call-rights.mjs +47 -0
  107. package/runtime/scripts/guards/command-guard.mjs +468 -0
  108. package/runtime/scripts/guards/command-policy.mjs +342 -0
  109. package/runtime/scripts/guards/critic-reach.mjs +82 -0
  110. package/runtime/scripts/guards/deps-guard.mjs +150 -0
  111. package/runtime/scripts/guards/env-dump-verdict.mjs +89 -0
  112. package/runtime/scripts/guards/git-pathspec.mjs +225 -0
  113. package/runtime/scripts/guards/git-policy.mjs +355 -0
  114. package/runtime/scripts/guards/guards-root.mjs +27 -0
  115. package/runtime/scripts/guards/hook-install.mjs +343 -0
  116. package/runtime/scripts/guards/hook-io.mjs +76 -0
  117. package/runtime/scripts/guards/hook-verdict.mjs +12 -0
  118. package/runtime/scripts/guards/install-verdict.mjs +41 -0
  119. package/runtime/scripts/guards/kernel-seat.mjs +63 -0
  120. package/runtime/scripts/guards/launch-verdict.mjs +74 -0
  121. package/runtime/scripts/guards/op-starci.mjs +16 -0
  122. package/runtime/scripts/guards/op-write-scope.mjs +27 -0
  123. package/runtime/scripts/guards/orca-self-lifecycle.mjs +39 -0
  124. package/runtime/scripts/guards/process-kill-verdict.mjs +106 -0
  125. package/runtime/scripts/guards/protected-zone.mjs +56 -0
  126. package/runtime/scripts/guards/refusals.mjs +27 -0
  127. package/runtime/scripts/guards/release-definition.mjs +185 -0
  128. package/runtime/scripts/guards/release-push-gate.mjs +72 -0
  129. package/runtime/scripts/guards/release-record.mjs +58 -0
  130. package/runtime/scripts/guards/release-suite-mode.mjs +15 -0
  131. package/runtime/scripts/guards/rights.mjs +268 -0
  132. package/runtime/scripts/guards/seat-tools.mjs +47 -0
  133. package/runtime/scripts/guards/shell-commands.mjs +328 -0
  134. package/runtime/scripts/guards/supervisor-seat.mjs +80 -0
  135. package/runtime/scripts/guards/verdict-process.mjs +81 -0
  136. package/runtime/scripts/hfs/architecture/dead-exports.mjs +10 -10
  137. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +1 -1
  138. package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +53 -0
  139. package/runtime/scripts/hfs/architecture/reachability.mjs +1 -1
  140. package/runtime/scripts/hfs/architecture/sql-owner.mjs +17 -6
  141. package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +105 -0
  142. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +38 -52
  143. package/runtime/scripts/hfs/architecture/supabase-keys.mjs +261 -0
  144. package/runtime/scripts/hfs/architecture/transport-owner.mjs +49 -3
  145. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -1
  146. package/runtime/scripts/hfs/check.mjs +6 -4
  147. package/runtime/scripts/hfs/coverage-scope.mjs +28 -1
  148. package/runtime/scripts/hfs/db-restamp-verb.mjs +56 -16
  149. package/runtime/scripts/hfs/declaration-shape.mjs +4 -4
  150. package/runtime/scripts/hfs/declaration-slots.mjs +14 -3
  151. package/runtime/scripts/hfs/manifest-shape.mjs +10 -6
  152. package/runtime/scripts/hfs/rule-params-shape.mjs +18 -6
  153. package/runtime/scripts/hfs/rules/a15-findings.mjs +4 -1
  154. package/runtime/scripts/hfs/rules/bypass-markers.mjs +22 -7
  155. package/runtime/scripts/hfs/rules/database-migrations.mjs +69 -14
  156. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +9 -7
  157. package/runtime/scripts/hfs/rules/database-queues.mjs +68 -18
  158. package/runtime/scripts/hfs/rules/edition.mjs +56 -12
  159. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -10
  160. package/runtime/scripts/hfs/rules/fe-stack.mjs +12 -7
  161. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +1 -1
  162. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +75 -9
  163. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +1 -1
  164. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +15 -4
  165. package/runtime/scripts/hfs/rules/text-integrity.mjs +73 -0
  166. package/runtime/scripts/hfs/runtime-rules/hook-shape.mjs +74 -0
  167. package/runtime/scripts/hfs/runtime-rules/release-notes.mjs +44 -0
  168. package/runtime/scripts/hfs/sealed-members.mjs +3 -3
  169. package/runtime/scripts/hfs/slot-side-problems.mjs +3 -3
  170. package/runtime/scripts/hfs/slots.mjs +2 -1
  171. package/runtime/scripts/hfs/stack-deploy-config.mjs +22 -5
  172. package/runtime/scripts/hfs/stacks-gate.mjs +28 -7
  173. package/runtime/scripts/hfs/view.mjs +2 -1
  174. package/runtime/scripts/lib/app-data.mjs +11 -0
  175. package/runtime/scripts/lib/bypass-markers.mjs +272 -75
  176. package/runtime/scripts/lib/check-format.mjs +7 -0
  177. package/runtime/scripts/lib/cli-arg.mjs +120 -0
  178. package/runtime/scripts/lib/css-rules.mjs +220 -0
  179. package/runtime/scripts/lib/dom-tree.mjs +4 -0
  180. package/runtime/scripts/lib/escape.mjs +16 -0
  181. package/runtime/scripts/lib/event-items.mjs +22 -0
  182. package/runtime/scripts/lib/failure-code-template-errors.mjs +161 -0
  183. package/runtime/scripts/lib/git-dir.mjs +120 -0
  184. package/runtime/scripts/lib/grammar-vendor-parity.mjs +377 -0
  185. package/runtime/scripts/lib/guard-command.mjs +24 -0
  186. package/runtime/scripts/lib/hook-limits.mjs +5 -0
  187. package/runtime/scripts/lib/interrupt-cleanup.mjs +38 -0
  188. package/runtime/scripts/lib/js-lex.mjs +200 -0
  189. package/runtime/scripts/lib/json-formats.mjs +0 -1
  190. package/runtime/scripts/lib/language.mjs +94 -1
  191. package/runtime/scripts/lib/loosening-budgets.mjs +44 -0
  192. package/runtime/scripts/lib/loosening-config.mjs +361 -0
  193. package/runtime/scripts/lib/loosening-state.mjs +223 -0
  194. package/runtime/scripts/lib/menu-parts.mjs +11 -0
  195. package/runtime/scripts/lib/normalize.mjs +25 -0
  196. package/runtime/scripts/lib/op-gate.mjs +15 -0
  197. package/runtime/scripts/lib/redact.mjs +208 -0
  198. package/runtime/scripts/lib/runtime-location.mjs +21 -0
  199. package/runtime/scripts/lib/runtime-package-inventory.mjs +21 -0
  200. package/runtime/scripts/lib/runtime-package-manifests.mjs +84 -0
  201. package/runtime/scripts/lib/runtime-publication-tags.mjs +99 -0
  202. package/runtime/scripts/lib/seat-env.mjs +43 -0
  203. package/runtime/scripts/lib/secret-patterns.mjs +58 -9
  204. package/runtime/scripts/lib/shell-source.mjs +4 -0
  205. package/runtime/scripts/lib/source-phrases.mjs +34 -0
  206. package/runtime/scripts/lib/sqlite.mjs +21 -0
  207. package/runtime/scripts/lib/swarm-stack.mjs +2 -1
  208. package/runtime/scripts/lib/tar-files.mjs +39 -0
  209. package/runtime/scripts/lib/ts-analysis.mjs +10 -0
  210. package/runtime/scripts/lib/worktree-exclude.mjs +35 -0
  211. package/runtime/scripts/lib/worktree-record.mjs +14 -0
  212. package/runtime/scripts/machine/artifact-hold.mjs +52 -0
  213. package/runtime/scripts/machine/ask-receipts.mjs +113 -0
  214. package/runtime/scripts/machine/evidence-store.mjs +223 -0
  215. package/runtime/scripts/machine/home.mjs +216 -0
  216. package/runtime/scripts/machine/host-lock.mjs +184 -0
  217. package/runtime/scripts/machine/lock-recovery.mjs +55 -0
  218. package/runtime/scripts/machine/roles-contract.mjs +90 -0
  219. package/runtime/scripts/machine/runtime-change.mjs +20 -0
  220. package/runtime/scripts/machine/workflow-tree.mjs +122 -0
  221. package/runtime/scripts/reconciler/checklist-items.mjs +7 -0
  222. package/runtime/scripts/reconciler/guard-row.mjs +25 -0
  223. package/runtime/scripts/work/asset-slot.mjs +284 -0
  224. package/runtime/scripts/work/brand/brand-colour.mjs +125 -0
  225. package/runtime/scripts/work/brand/brand-tokens.mjs +167 -0
  226. package/runtime/scripts/work/brand/brand-value-source.mjs +106 -0
  227. package/runtime/scripts/work/brand/brand.mjs +892 -0
  228. package/runtime/scripts/work/draw/draw-dna-anatomy.mjs +60 -0
  229. package/runtime/scripts/work/draw/draw-dna-findings.mjs +256 -0
  230. package/runtime/scripts/work/draw/draw-dna-selector.mjs +38 -0
  231. package/runtime/scripts/work/draw/draw-dna.mjs +483 -0
  232. package/runtime/scripts/work/draw/draw-layer.mjs +305 -0
  233. package/runtime/scripts/work/draw/draw-rationale-page.mjs +237 -0
  234. package/runtime/scripts/work/draw/draw-rationale-ref.mjs +14 -0
  235. package/runtime/scripts/work/draw/draw-rationale.mjs +353 -0
  236. package/runtime/scripts/work/draw/draw-source.mjs +412 -0
  237. package/runtime/scripts/work/draw/draw-taste.mjs +278 -0
  238. package/runtime/scripts/work/draw/draw-typecheck.mjs +72 -0
  239. package/runtime/scripts/work/draw/rationale-findings.mjs +205 -0
  240. package/runtime/scripts/work/draw-grammar.mjs +175 -0
  241. package/runtime/scripts/work/draw-render-page.mjs +188 -0
  242. package/runtime/scripts/work/draw-render.mjs +626 -0
  243. package/runtime/scripts/work/grammar-proposal.mjs +279 -0
  244. package/runtime/scripts/work/png.mjs +237 -0
  245. package/runtime/scripts/work/render-tools.mjs +64 -0
  246. package/runtime/scripts/work/work-io.mjs +119 -0
  247. package/scaffold/add-inbox-lite.mjs +2 -2
  248. package/scaffold/add-plan.mjs +40 -13
  249. package/scaffold/add-queue-lite.mjs +372 -0
  250. package/scaffold/add.mjs +5 -2
  251. package/scaffold/app.mjs +1 -2
  252. package/scaffold/edition-gate.mjs +11 -7
  253. package/scaffold/lite-exports.mjs +10 -0
  254. package/scaffold/pattern-file-slots.mjs +33 -0
  255. package/scaffold/test-world.mjs +1 -1
  256. package/scaffold/yaml-block-edit.mjs +117 -0
  257. package/src/main.mjs +5 -4
  258. package/stack/deploy.mjs +7 -3
  259. package/stack/post-deploy.mjs +48 -0
  260. package/sync/index.mjs +10 -0
  261. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +1 -1
  262. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +1 -1
  263. package/templates/app/codegen-graphql.mjs.partial +59 -12
  264. package/templates/app/hooks/husky/pre-commit +2 -2
  265. package/templates/app/hooks/husky/pre-push +10 -3
  266. package/templates/app/package-scripts/redis-dev.json +3 -0
  267. package/templates/app/package-scripts-lite/package.json +1 -0
  268. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +36 -8
  269. package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
  270. package/templates/app/skeleton/.starcistacks/production/README.md +10 -9
  271. package/templates/app/skeleton/.starcistacks/production/infra/swarm/postgres-init.sh +21 -0
  272. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +1 -1
  273. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +1 -0
  274. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +47 -5
  275. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +5 -3
  276. package/templates/app/skeleton/README.md +2 -0
  277. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +5 -0
  278. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +2 -2
  279. package/templates/app/skeleton-lite/.starcistacks/production/README.md +1 -1
  280. package/templates/app/skeleton-lite/README.md +1 -1
  281. package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +18 -0
  282. package/templates/be/inbox/platform/event-bus/errors/event-bus.error.ts.tpl +16 -0
  283. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +10 -0
  284. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +1 -1
  285. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +15 -3
  286. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +11 -3
  287. package/templates/be/inbox/platform/event-bus/index.ts.tpl +2 -0
  288. package/templates/be/inbox/platform/event-bus/messages/event-bus.messages.ts.tpl +7 -0
  289. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +2 -2
  290. package/templates/be/inbox/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
  291. package/templates/be/inbox/platform/idempotency/index.ts.tpl +2 -0
  292. package/templates/be/inbox/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
  293. package/templates/be/inbox/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
  294. package/templates/be/inbox/platform/inbox/client-operation.policy.ts.tpl +17 -0
  295. package/templates/be/inbox/platform/inbox/errors/inbox.error.ts.tpl +22 -0
  296. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +21 -0
  297. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +334 -11
  298. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +55 -4
  299. package/templates/be/inbox/platform/inbox/index.ts.tpl +2 -0
  300. package/templates/be/inbox/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
  301. package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
  302. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +26 -0
  303. package/templates/be/inbox/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
  304. package/templates/be/inbox/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
  305. package/templates/be/inbox/platform/migration.sql.tpl +1 -0
  306. package/templates/be/inbox/service.spec.ts.tpl +4 -4
  307. package/templates/be/inbox/service.ts.tpl +3 -1
  308. package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -2
  309. package/templates/be/inbox/webhook-door.ts.tpl +3 -2
  310. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +3 -0
  311. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +10 -0
  312. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +2 -2
  313. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +16 -3
  314. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +11 -3
  315. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +139 -50
  316. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +25 -2
  317. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +139 -78
  318. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +10 -6
  319. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +262 -159
  320. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +32 -5
  321. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +2 -0
  322. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +5 -1
  323. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +2 -0
  324. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -4
  325. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +1 -0
  326. package/templates/be/patterns/idempotency/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
  327. package/templates/be/patterns/idempotency/platform/idempotency/index.ts.tpl +2 -0
  328. package/templates/be/patterns/idempotency/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
  329. package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
  330. package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.ts.tpl +17 -0
  331. package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.spec.ts.tpl +66 -0
  332. package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.ts.tpl +24 -0
  333. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.spec.ts.tpl +60 -0
  334. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.ts.tpl +12 -0
  335. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.spec.ts.tpl +49 -0
  336. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.ts.tpl +12 -0
  337. package/templates/be/patterns/idempotency/platform/inbox/effectful.decorators.ts.tpl +7 -0
  338. package/templates/be/patterns/idempotency/platform/inbox/errors/inbox.error.ts.tpl +22 -0
  339. package/templates/be/patterns/idempotency/platform/inbox/inbox.decorators.ts.tpl +17 -0
  340. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +7 -0
  341. package/templates/be/patterns/idempotency/platform/inbox/inbox.module.ts.tpl +28 -0
  342. package/templates/be/patterns/idempotency/platform/inbox/inbox.options.ts.tpl +7 -0
  343. package/templates/be/patterns/idempotency/platform/inbox/inbox.port.ts.tpl +44 -0
  344. package/templates/be/patterns/idempotency/platform/inbox/inbox.service.spec.ts.tpl +388 -0
  345. package/templates/be/patterns/idempotency/platform/inbox/inbox.service.ts.tpl +77 -0
  346. package/templates/be/patterns/idempotency/platform/inbox/index.ts.tpl +10 -0
  347. package/templates/be/patterns/idempotency/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
  348. package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +9 -0
  349. package/templates/be/patterns/idempotency/platform/inbox/persistence/entities/inbox-claim.entity.ts.tpl +25 -0
  350. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
  351. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +39 -0
  352. package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1789800005000-create-inbox-claims.ts.tpl +21 -0
  353. package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1792800000000-client-operation-outcomes.ts.tpl +21 -0
  354. package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
  355. package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
  356. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +1 -0
  357. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +4 -3
  358. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +2 -0
  359. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +20 -2
  360. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +1 -0
  361. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +4 -0
  362. package/templates/be/patterns/jobs/platform-lite/jobs.index.ts.tpl +10 -0
  363. package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +44 -0
  364. package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +33 -0
  365. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +203 -18
  366. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +78 -22
  367. package/templates/be/patterns/queues/platform/errors/queue.error.ts.tpl +19 -0
  368. package/templates/be/patterns/queues/platform/messages/queue.messages.ts.tpl +13 -0
  369. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +1 -0
  370. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +4 -0
  371. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +2 -0
  372. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -4
  373. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +14 -2
  374. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +5 -2
  375. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +72 -74
  376. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +1 -1
  377. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +23 -8
  378. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +29 -8
  379. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +7 -3
  380. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +5 -0
  381. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +2 -0
  382. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +4 -0
  383. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +2 -1
  384. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +1 -1
  385. package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.spec.ts.tpl +417 -0
  386. package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.ts.tpl +172 -0
  387. package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +12 -0
  388. package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +32 -0
  389. package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +148 -0
  390. package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +51 -0
  391. package/templates/be/patterns/queues/platform-lite/queue-transport.port.ts.tpl +163 -0
  392. package/templates/be/patterns/queues/platform-lite/queue.config.ts.tpl +17 -0
  393. package/templates/be/patterns/queues/platform-lite/queue.error.ts.tpl +25 -0
  394. package/templates/be/patterns/queues/platform-lite/queue.index.ts.tpl +8 -0
  395. package/templates/be/patterns/queues/platform-lite/queue.messages.ts.tpl +21 -0
  396. package/templates/be/patterns/queues/platform-lite/queue.module.ts.tpl +74 -0
  397. package/templates/be/patterns/queues/platform-lite/queue.options.ts.tpl +38 -0
  398. package/templates/be/patterns/queues/queue.queue.ts.tpl +2 -2
  399. package/templates/be/patterns/reactors/application.contracts.ts.tpl +2 -0
  400. package/templates/be/patterns/reactors/consumer.ts.tpl +5 -1
  401. package/templates/be/platform/graphql/graphql.contracts.ts +2 -1
  402. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +1 -1
  403. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +6 -1
  404. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -4
  405. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +4 -0
  406. package/templates/be/skeleton/apps/cli/src/main.ts +7 -4
  407. package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -1
  408. package/templates/be/skeleton/src/modules/platform/http/errors/http.error.ts +22 -0
  409. package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.spec.ts +235 -0
  410. package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.ts +85 -0
  411. package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.spec.ts +92 -0
  412. package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.ts +52 -0
  413. package/templates/be/skeleton/src/modules/platform/http/http.decorators.ts +9 -0
  414. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +7 -0
  415. package/templates/be/skeleton/src/modules/platform/http/http.module.ts +19 -0
  416. package/templates/be/skeleton/src/modules/platform/http/http.options.ts +2 -0
  417. package/templates/be/skeleton/src/modules/platform/http/http.port.ts +40 -0
  418. package/templates/be/skeleton/src/modules/platform/http/index.ts +7 -0
  419. package/templates/be/skeleton/src/modules/platform/http/messages/http.messages.ts +15 -0
  420. package/templates/be/skeleton/src/modules/platform/ids/ids.decorators.ts +9 -0
  421. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +7 -0
  422. package/templates/be/skeleton/src/modules/platform/ids/ids.module.ts +19 -0
  423. package/templates/be/skeleton/src/modules/platform/ids/ids.options.ts +2 -0
  424. package/templates/be/skeleton/src/modules/platform/ids/ids.port.ts +5 -0
  425. package/templates/be/skeleton/src/modules/platform/ids/index.ts +4 -0
  426. package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.spec.ts +15 -0
  427. package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.ts +10 -0
  428. package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.spec.ts +59 -0
  429. package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.ts +21 -0
  430. package/templates/be/skeleton/src/modules/platform/logging/correlation.contracts.ts +16 -0
  431. package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.spec.ts +150 -0
  432. package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.ts +42 -0
  433. package/templates/be/skeleton/src/modules/platform/logging/correlation.port.ts +15 -0
  434. package/templates/be/skeleton/src/modules/platform/logging/correlation.service.spec.ts +23 -0
  435. package/templates/be/skeleton/src/modules/platform/logging/correlation.service.ts +28 -0
  436. package/templates/be/skeleton/src/modules/platform/logging/index.ts +14 -1
  437. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +39 -6
  438. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +15 -3
  439. package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +17 -2
  440. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +13 -1
  441. package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +13 -3
  442. package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.spec.ts +69 -0
  443. package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.ts +48 -0
  444. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  445. package/templates/be/skeleton/src/modules/platform/primitives/record.policy.spec.ts +12 -0
  446. package/templates/be/skeleton/src/modules/platform/primitives/record.policy.ts +3 -0
  447. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +6 -1
  448. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +4 -0
  449. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  450. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +14 -1
  451. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +12 -0
  452. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  453. package/templates/be/test-world/world/test-apps.options.ts.tpl +2 -0
  454. package/templates/be/test-world/world/test-world.config.ts.tpl +1 -0
  455. package/upgrade/check.mjs +12 -135
  456. package/upgrade/file-state.mjs +1 -16
  457. package/upgrade/index.mjs +34 -361
  458. package/runtime/scripts/lib/check-code-loosening.mjs +0 -128
  459. package/runtime/scripts/lib/gate-loosening.mjs +0 -292
  460. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +0 -89
  461. package/templates/app/upgrade-full/.starcistacks/dev/README.md +0 -16
  462. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +0 -13
  463. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +0 -3
  464. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +0 -12
  465. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +0 -7
  466. package/templates/be/skeleton-lite/apps/api/src/main.ts +0 -26
  467. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +0 -12
  468. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +0 -7
  469. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +0 -5
  470. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +0 -9
  471. package/templates/be/upgrade-full/apps/cli/src/main.ts +0 -29
  472. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +0 -7
  473. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +0 -23
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env node
2
+ // draw-rationale.mjs — owner ruling 2026-09-27 (draw-rationale-evidence): the owner critiques a drawing from
3
+ // EVIDENCE, never from taste. Every interface.draw shape ships, next to its render source, a rationale.json - an
4
+ // array of decisions
5
+ //
6
+ // {id, selector, kind: element|layout|spacing|type|radius|colour|art, decision, value, because, rules[],
7
+ // alternativesRejected[{option, why}], source}
8
+ //
9
+ // and every visible element and region of the render source carries data-why="<id>[ <id>...]" naming its
10
+ // decisions. draw-render.mjs measures the render itself (measureRationale: getComputedStyle over every visible
11
+ // element) and captures an annotated redline render beside each part (<base>.redline.png: spacing brackets with the
12
+ // value and the rule id, DNA labels at component roots). DRAW_RATIONALE_MISSING refuses, per render source:
13
+ // - no rationale file (<source stem>.rationale.json, else rationale.json beside it), or an entry that is malformed
14
+ // (a field missing, an unknown kind, a duplicate id);
15
+ // - a visible element or region without data-why, or a data-why naming no entry; a region whose decision is not a
16
+ // layout decision; no layout decision stating the region order (its value names the region count);
17
+ // - a grid whose layout decision does not state its column count at a viewport;
18
+ // - a distinct gap/padding/inset, radius, font-size, font-weight or line-height the RENDER uses (measured at every
19
+ // viewport) that no decision of the matching kind states in its value (spacing|layout, radius, type);
20
+ // - a colour the render paints (background, text, border) that no colour decision names (hex, or a --token that
21
+ // resolves to it in the render);
22
+ // - a DNA component or a closed variant value the render uses that no decision names;
23
+ // - an art slot (img, picture, data-asset-slot, data-artwork-slot, a background image) whose decision is not art;
24
+ // - a rules[] entry that resolves to nothing: its first token must be a knowledge id (knowledge/**: GAP-5,
25
+ // PADDING-9 case-1, ui.presentation.gap), a DNA component or closed value (dna:Button.variant=secondary), a
26
+ // brand.direction principle/rubric id (direction:P2, rubric:H3), or an owner ruling (owner:<id> of
27
+ // modules/kernel/owner-rulings.yaml, a ui.review.feedback note id, an answer receipt);
28
+ // - an empty `because`, or one that cites no FR, content or user job;
29
+ // - a part with no measured render (re-render it with draw-render) or no redline render beside it.
30
+ //
31
+ // starci work draw-rationale <render.html> [--rationale <file>] [--ui <ui-record-dir>]
32
+ // [--records <draw-render.json>[,<...>]] [--json]
33
+ import fs from 'node:fs';
34
+ import path from 'node:path';
35
+ import { fileURLToPath } from 'node:url';
36
+ import { parseYaml } from '../../../engine/yaml.mjs';
37
+ import { COMPONENT_ATTR, loadDna, walkElements } from './draw-dna.mjs';
38
+ import { findRationaleIssues } from './rationale-findings.mjs';
39
+ import { bareRuleToken, parseRuleRef } from './draw-rationale-ref.mjs';
40
+ import { list } from '../../lib/list.mjs';
41
+ import { isFile } from '../../lib/fs-kind.mjs'; import { isMain } from '../../lib/is-main.mjs';
42
+
43
+ export { drawRedlines, measureRationale } from './draw-rationale-page.mjs';
44
+
45
+ export const DRAW_RATIONALE_MISSING = 'DRAW_RATIONALE_MISSING';
46
+ const RATIONALE_KINDS = Object.freeze(['element', 'layout', 'spacing', 'type', 'radius', 'colour', 'art']);
47
+ export const WHY_ATTR = 'data-why';
48
+ export const REDLINE_ATTR = 'data-draw-redline';
49
+ export const MEASURE_SCHEMA = 'starci/draw-rationale-measure@1';
50
+ const REQUIRED_FIELDS = Object.freeze(['id', 'selector', 'kind', 'decision', 'value', 'because', 'rules', 'alternativesRejected', 'source']);
51
+ /** Components too small to label in the redline (they are named by their region's label). */
52
+ export const REDLINE_LEAF_COMPONENTS = Object.freeze(['Text', 'Heading', 'Icon', 'Label', 'Description', 'Kbd', 'Badge', 'Chip', 'IconTile', 'TagGroup', 'Tag', 'StateMark', 'MediaFrame', 'Avatar']);
53
+
54
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
55
+ const KNOWLEDGE = path.join(ROOT, 'knowledge');
56
+ const OWNER_RULINGS = path.join(ROOT, 'modules', 'kernel', 'owner-rulings.yaml');
57
+ const readJson = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch { return undefined; } };
58
+ const readYamlOr = (f) => { try { return parseYaml(fs.readFileSync(f, 'utf8')); } catch { return null; } };
59
+ const str = (v) => { if (v == null) { return ''; } if (typeof v === 'string') { return v; } if (typeof v === 'number') { return String(v); } return JSON.stringify(v); };
60
+
61
+ // ---------------------------------------------------------------------------------------------------------
62
+ // The rationale file
63
+ // ---------------------------------------------------------------------------------------------------------
64
+
65
+ /** The rationale file of a render source: <stem>.rationale.json beside it, else rationale.json in its directory. */
66
+ export function rationaleFileOf(htmlFile) {
67
+ if (!htmlFile) return null;
68
+ const own = String(htmlFile).replace(/\.html?$/i, '.rationale.json');
69
+ if (isFile(own)) return own;
70
+ const shared = path.join(path.dirname(htmlFile), 'rationale.json');
71
+ return isFile(shared) ? shared : null;
72
+ }
73
+
74
+ /** Read and validate a rationale file: {entries, errors:[string]}. A missing or unparseable file is one error. */
75
+ export function loadRationale(file) {
76
+ if (!file || !isFile(file)) return { entries: [], errors: ['no rationale.json beside the render source'] };
77
+ const doc = readJson(file);
78
+ if (doc === undefined) return { entries: [], errors: [`${path.basename(file)} is not JSON`] };
79
+ const entries = Array.isArray(doc) ? doc : null;
80
+ if (!entries) return { entries: [], errors: [`${path.basename(file)} is not an array of decisions`] };
81
+ if (!entries.length) return { entries: [], errors: [`${path.basename(file)} holds no decision`] };
82
+ const errors = [];
83
+ const seen = new Set();
84
+ entries.forEach((e, i) => {
85
+ const at = `decision ${e?.id ?? '#' + (i + 1)}`;
86
+ if (!e || typeof e !== 'object' || Array.isArray(e)) { errors.push(`#${i + 1} is not an object`); return; }
87
+ const missing = REQUIRED_FIELDS.filter((k) => e[k] == null || (typeof e[k] === 'string' && !e[k].trim()));
88
+ if (missing.length) errors.push(`${at} lacks ${missing.join(', ')}`);
89
+ if (e.id != null) { if (seen.has(String(e.id))) { errors.push(`${at}: the id is used twice`); } seen.add(String(e.id)); }
90
+ if (e.kind != null && !RATIONALE_KINDS.includes(e.kind)) errors.push(`${at}: kind "${e.kind}" is none of ${RATIONALE_KINDS.join('|')}`);
91
+ if (e.rules != null && (!Array.isArray(e.rules) || !e.rules.length)) errors.push(`${at}: rules[] must cite at least one rule id`);
92
+ if (e.alternativesRejected != null && (!Array.isArray(e.alternativesRejected) || e.alternativesRejected.some((a) => !a || typeof a !== 'object' || !str(a.option).trim() || !str(a.why).trim()))) {
93
+ errors.push(`${at}: alternativesRejected is [{option, why}]`);
94
+ }
95
+ });
96
+ return { entries: entries.filter((e) => e && typeof e === 'object' && !Array.isArray(e)), errors };
97
+ }
98
+
99
+ /** The labels the redline shows per decision: {id: {kind, value, rule}} - rule is the first rule's id token. */
100
+ export function redlineLabelsOf(entries) {
101
+ const out = {};
102
+ for (const e of list(entries)) {
103
+ if (e?.id == null) continue;
104
+ out[String(e.id)] = { kind: e.kind ?? null, value: str(e.value).slice(0, 40), rule: ruleTokenOf(list(e.rules)[0]) };
105
+ }
106
+ return out;
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------------------------------------
110
+ // Rule id resolution
111
+ // ---------------------------------------------------------------------------------------------------------
112
+
113
+ const NAMESPACES = ['knowledge', 'dna', 'grammar', 'direction', 'rubric', 'owner'];
114
+
115
+ /** The id token of a rules[] entry (namespace dropped), for labels. */
116
+ function ruleTokenOf(ref) {
117
+ const m = parseRuleRef(str(ref));
118
+ if (!m) return '';
119
+ return `${m[2]}${m[3] ? ' ' + m[3] : ''}`;
120
+ }
121
+
122
+ let knowledgeCache = null;
123
+ /** Every id knowledge/** declares: {ids:Set, cases:Map(ruleId -> Set(case ids))}. */
124
+ function addKnowledgeRecord(node, ids, cases) {
125
+ if (typeof node.id !== 'string' || !node.id.trim()) return;
126
+ const id = node.id.trim();
127
+ if (!/^case-\d+$/i.test(id)) ids.add(id);
128
+ if (!Array.isArray(node.cases)) return;
129
+ const set = cases.get(id) ?? new Set();
130
+ for (const item of node.cases) if (typeof item?.id === 'string') set.add(item.id.trim());
131
+ cases.set(id, set);
132
+ }
133
+ function visitKnowledgeNode(node, ids, cases) {
134
+ if (Array.isArray(node)) {
135
+ for (const item of node) visitKnowledgeNode(item, ids, cases);
136
+ return;
137
+ }
138
+ if (!node || typeof node !== 'object') return;
139
+ addKnowledgeRecord(node, ids, cases);
140
+ for (const value of Object.values(node)) if (value && typeof value === 'object') visitKnowledgeNode(value, ids, cases);
141
+ }
142
+ function knowledgeIndex(root = KNOWLEDGE) {
143
+ if (knowledgeCache?.root === root) return knowledgeCache;
144
+ const ids = new Set(), cases = new Map();
145
+ const walk = (dir) => {
146
+ let names = [];
147
+ try { names = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
148
+ for (const d of names) {
149
+ if (d.name.startsWith('.')) continue;
150
+ const p = path.join(dir, d.name);
151
+ if (d.isDirectory()) walk(p);
152
+ else if (/\.ya?ml$/i.test(d.name)) visitKnowledgeNode(readYamlOr(p), ids, cases);
153
+ }
154
+ };
155
+ walk(root);
156
+ knowledgeCache = { root, ids, cases };
157
+ return knowledgeCache;
158
+ }
159
+
160
+ const idsUnder = (node, out = new Set()) => {
161
+ if (Array.isArray(node)) { for (const n of node) { idsUnder(n, out); } return out; }
162
+ if (!node || typeof node !== 'object') return out;
163
+ if (typeof node.id === 'string' && node.id.trim()) out.add(node.id.trim());
164
+ for (const v of Object.values(node)) if (v && typeof v === 'object') idsUnder(v, out);
165
+ return out;
166
+ };
167
+
168
+ /** The product's brand.direction: {all:Set, rubric:Set} of its ids. */
169
+ function directionIds(workRoot) {
170
+ const doc = workRoot ? readYamlOr(path.join(workRoot, 'brand', 'index.yaml')) : null;
171
+ const direction = doc?.brand?.direction ?? doc?.direction ?? null;
172
+ return { all: idsUnder(direction), rubric: new Set(list(direction?.rubric?.checks).map((c) => str(c?.id).trim()).filter(Boolean)) };
173
+ }
174
+
175
+ /** Current owner rulings, product feedback notes and actual receipts. */
176
+ function ownerIds({ record = null, workRoot = null, rulingsFile = OWNER_RULINGS } = {}) {
177
+ const ids = new Set();
178
+ for (const r of list(readYamlOr(rulingsFile)?.rulings)) if (r?.id) ids.add(str(r.id).trim());
179
+ const review = record?.ui?.review ?? {};
180
+ for (const n of idsUnder(review.feedback)) ids.add(n);
181
+ for (const r of list(review.feedback?.rounds)) if (r?.receipt) ids.add(str(r.receipt));
182
+ if (review.owner?.receipt) ids.add(str(review.owner.receipt));
183
+ if (review.owner?.dispatchId) ids.add(str(review.owner.dispatchId));
184
+ const doc = workRoot ? readYamlOr(path.join(workRoot, 'brand', 'index.yaml')) : null;
185
+ for (const l of list((doc?.brand?.direction ?? doc?.direction)?.learned)) if (l?.id) ids.add(str(l.id).trim());
186
+ return ids;
187
+ }
188
+
189
+ const kebab = (s) => String(s).replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/([A-Z])([A-Z][a-z])/g, '$1-$2').toLowerCase();
190
+
191
+ /** Whether `token` names a DNA component, one of its parts, props or closed values (Button, Button.variant=secondary). */
192
+ function dnaResolves(dna, token) {
193
+ if (!dna) return false;
194
+ const [head, rest] = String(token).split(/\.(.+)/);
195
+ const spec = dna.components.get(head);
196
+ if (!spec) return false;
197
+ if (!rest) return true;
198
+ const [prop, value] = rest.split('=');
199
+ if (spec.parts.has(prop) || spec.parts.has(kebab(prop))) return value == null;
200
+ const closed = spec.closed.get(prop);
201
+ if (!closed) return false;
202
+ if (value == null) return true;
203
+ return closed.values ? closed.values.includes(value) : true;
204
+ }
205
+
206
+ /**
207
+ * A resolver of rules[] entries: (ref) -> {ok, via, id, why?}. Context: {workRoot, record, dna, knowledge,
208
+ * rulingsFile, repoRoot}.
209
+ */
210
+ export function ruleResolver({ workRoot = null, record = null, dna = loadDna(), knowledge = knowledgeIndex(), rulingsFile = OWNER_RULINGS, repoRoot = null } = {}) {
211
+ const direction = directionIds(workRoot);
212
+ const owner = ownerIds({ record, workRoot, rulingsFile });
213
+ const receiptOk = (token) => {
214
+ for (const base of [repoRoot, workRoot ? path.dirname(workRoot) : null, workRoot].filter(Boolean)) {
215
+ const f = path.resolve(base, token);
216
+ if (isFile(f) && readJson(f)?.schema === 'starci/ask-answer@1') return true;
217
+ }
218
+ return false;
219
+ };
220
+ const inKnowledge = (id, kase) => {
221
+ if (!knowledge.ids.has(id)) return { ok: false, why: `${id} is no id in knowledge/` };
222
+ if (kase && !(knowledge.cases.get(id)?.has(kase))) return { ok: false, why: `${id} has no ${kase}` };
223
+ return { ok: true };
224
+ };
225
+ const resolveNamespace = (namespace, token, kase) => {
226
+ if (namespace === 'knowledge') {
227
+ const result = inKnowledge(token, kase);
228
+ return result.ok ? { ok: true, via: 'knowledge', id: token } : { via: 'knowledge', why: result.why };
229
+ }
230
+ if (namespace === 'dna' && dnaResolves(dna, token)) return { ok: true, via: 'dna', id: token };
231
+ if (namespace === 'direction' && direction.all.has(token)) return { ok: true, via: 'direction', id: token };
232
+ if (namespace === 'rubric' && direction.rubric.has(token)) return { ok: true, via: 'rubric', id: token };
233
+ if (namespace === 'owner' && (owner.has(token) || receiptOk(token))) return { ok: true, via: 'owner', id: token };
234
+ return null;
235
+ };
236
+ return (ref) => {
237
+ const text = str(ref);
238
+ const m = parseRuleRef(text);
239
+ if (!m?.[2]) return { ok: false, id: text, why: 'empty rule reference' };
240
+ const ns = m[1] ? m[1].toLowerCase() : null;
241
+ const token = bareRuleToken(m[2]);
242
+ const kase = m[3] ?? null;
243
+ if (ns && !NAMESPACES.includes(ns)) return { ok: false, id: token, why: `unknown namespace ${ns}: (one of ${NAMESPACES.join(', ')})` };
244
+ const tries = ns ? [ns === 'grammar' ? 'dna' : ns] : ['knowledge', 'dna', 'direction', 'owner'];
245
+ let why = null;
246
+ for (const t of tries) {
247
+ const result = resolveNamespace(t, token, kase);
248
+ if (result?.ok) return result;
249
+ if (result?.via === 'knowledge') why ??= result.why;
250
+ }
251
+ return { ok: false, id: token, why: ns ? `${ns}:${token} resolves to nothing` : (why ?? `${token} resolves to no knowledge id, DNA name, brand.direction id or owner ruling`) };
252
+ };
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------------------------------------
256
+ // Numbers stated in a decision
257
+ // ---------------------------------------------------------------------------------------------------------
258
+
259
+ /** The numbers a decision states, in px where a unit says so (1.5rem -> 24); pill/full/circle as words. */
260
+ export function statedValues(text) {
261
+ const s = str(text);
262
+ const nums = new Set();
263
+ for (const m of s.matchAll(/(\d+(?:\.\d+)?)\s*(rem|em|px|%)?/gi)) {
264
+ const n = Number(m[1]);
265
+ if (!Number.isFinite(n)) continue;
266
+ const unit = (m[2] ?? '').toLowerCase();
267
+ if (unit === 'rem' || unit === 'em') nums.add(round(n * 16));
268
+ else nums.add(round(n));
269
+ }
270
+ return { nums, pill: /\b(pill|full|9999|rounded-full|capsule)\b/i.test(s), circle: /\b(circle|50%)\b/i.test(s) };
271
+ }
272
+ const round = (n) => Math.round(Number(n) * 100) / 100;
273
+
274
+ const statesValue = (entry, v) => {
275
+ const st = statedValues(`${str(entry.value)}`);
276
+ if (typeof v === 'string') {
277
+ if (v === 'pill') return st.pill;
278
+ if (v === 'circle') return st.circle;
279
+ return str(entry.value).toLowerCase().includes(v.toLowerCase());
280
+ }
281
+ return [...st.nums].some((n) => Math.abs(n - v) < 0.26);
282
+ };
283
+
284
+ /** The id-and-reference test of `because`: a record ref, an FR/NFR/BR id, the content or a user job. */
285
+ export function becauseCites(because, refs = []) {
286
+ const s = str(because);
287
+ if (!s.trim()) return false;
288
+ if (refs.some((r) => r && s.includes(r))) return true;
289
+ return [
290
+ /\b(?:N?FR|BR|UJ|JTBD|US)[-.]?[a-z0-9]*\d/i,
291
+ /\bfr\.[a-z0-9][\w.-]*/i,
292
+ /\b\d{2}-CONTENT\b/i,
293
+ /\bcontent\b/i,
294
+ /\buser(?:'s)? job\b/i,
295
+ /\bjob[- ]to[- ]be[- ]done\b/i,
296
+ /\bjob:/i,
297
+ ].some((pattern) => pattern.test(s));
298
+ }
299
+
300
+ /** The region container of a render: the PageContainer root, else <main>, else <body>. */
301
+ export function regionContainerOf(tree) {
302
+ const all = walkElements(tree);
303
+ return all.find((e) => str(e.attrs[COMPONENT_ATTR]).trim() === 'PageContainer') ?? all.find((e) => e.tag === 'main') ?? all.find((e) => e.tag === 'body') ?? tree;
304
+ }
305
+
306
+ /**
307
+ * DRAW_RATIONALE_MISSING findings of one render source. `html` its text; `entries`/`errors` from loadRationale;
308
+ * `measures` the draw-render measurements (measureRationale) of every viewport it was rendered at; `resolve` a
309
+ * ruleResolver; `record` the ui record (its refs); `redlines` [{viewport, ok}] when the caller checks the redline
310
+ * renders. Returns [{code, kind, detail, count, examples}].
311
+ */
312
+ export function rationaleFindings({ html, entries = [], errors = [], measures = [], resolve = ruleResolver(), record = null, dna = loadDna(), label = 'the render', redlines = null } = {}) {
313
+ return findRationaleIssues({ html, entries, errors, measures, resolve, record, dna, label, redlines }, {
314
+ str, statesValue, round, statedValues, becauseCites, kebab, regionContainerOf,
315
+ code: DRAW_RATIONALE_MISSING, measureSchema: MEASURE_SCHEMA, whyAttr: WHY_ATTR, redlineAttr: REDLINE_ATTR,
316
+ });
317
+ }
318
+ /** The measures of a render source's parts: the rationale block of each starci/draw-render@1 record given. */
319
+ export const measuresOf = (records) => list(records).map((r) => r?.rationale ?? null).filter(Boolean);
320
+
321
+ /** A one-line summary of a rationale file for the owner's ask: {decisions, byKind}. */
322
+ export function rationaleSummary(file) {
323
+ const { entries } = loadRationale(file);
324
+ const byKind = {};
325
+ for (const e of entries) byKind[e.kind] = (byKind[e.kind] ?? 0) + 1;
326
+ return { decisions: entries.length, byKind };
327
+ }
328
+
329
+ // ---------------------------------------------------------------------------------------------------------
330
+ // CLI
331
+ // ---------------------------------------------------------------------------------------------------------
332
+
333
+ async function main(argv) {
334
+ const file = argv.find((a) => !a.startsWith('--') && /\.html?$/i.test(a));
335
+ const at = (n) => { const i = argv.indexOf(n); return i >= 0 ? argv[i + 1] : null; };
336
+ if (!file) { process.stderr.write('use: starci work draw-rationale <render.html> [--rationale <file>] [--ui <ui-record-dir>] [--records <draw-render.json>[,...]] [--json]\n'); return 2; }
337
+ const html = path.resolve(file);
338
+ const rationale = at('--rationale') ? path.resolve(at('--rationale')) : rationaleFileOf(html);
339
+ const { entries, errors } = loadRationale(rationale);
340
+ const uiDir = at('--ui') ? path.resolve(at('--ui')) : null;
341
+ const record = uiDir ? readYamlOr(path.join(uiDir, 'index.yaml')) : null;
342
+ let workRoot = null;
343
+ for (let d = uiDir ?? path.dirname(html); ; d = path.dirname(d)) { if (path.basename(d) === '.starciwork') { workRoot = d; break; } if (path.dirname(d) === d) break; }
344
+ const records = (at('--records') ? at('--records').split(',') : []).map((f) => readJson(path.resolve(f)));
345
+ const findings = rationaleFindings({ html: fs.readFileSync(html, 'utf8'), entries, errors, measures: measuresOf(records), resolve: ruleResolver({ workRoot, record, repoRoot: process.cwd() }), record, label: path.basename(html) });
346
+ if (argv.includes('--json')) process.stdout.write(`${JSON.stringify({ ok: !findings.length, rationale, decisions: entries.length, findings }, null, 2)}\n`);
347
+ else process.stdout.write(`${findings.length ? 'REFUSED' : 'ok'}: ${entries.length} decision(s), ${findings.length} finding group(s)\n${findings.map((f) => ' [' + f.code + '] ' + f.detail).join('\n')}\n`);
348
+ return findings.length ? 1 : 0;
349
+ }
350
+
351
+ if (isMain(import.meta.url)) {
352
+ process.exitCode = await main(process.argv.slice(2));
353
+ }