@starci/hfs 4.7.32 → 4.7.33

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 +9 -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 +54 -14
  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,125 @@
1
+ // brand-colour.mjs - the colour mathematics of the brand checks (brand.mjs): sRGB <-> linear <-> OKLab <-> oklch, colour
2
+ // parsing and WCAG contrast. Pure: nothing here reads a file, so a brand is never proven by a dependency that may not be installed.
3
+
4
+ const COLOUR_REGEX=Object.freeze({percent:new RegExp(['^[-+]?',String.raw`\d*`,String.raw`\.?`,String.raw`\d+%$`].join('')),number:new RegExp(['^[-+]?',String.raw`\d*`,String.raw`\.?`,String.raw`\d+`,String.raw`(?:e[-+]?\d+)?$`].join(''),'i'),important:new RegExp([String.raw`\s*`,'!important$'].join(''),'i'),call:new RegExp(['^(oklch|rgba?)',String.raw`\(`,String.raw`\s*`,'([^)]*)',String.raw`\)$`].join(''),'i')});
5
+
6
+ /** A brand token matches its source within half a unit of the x100 OKLab distance: below perception, wide enough for hex/oklch rounding. */
7
+ export const TOKEN_TOLERANCE=0.5;
8
+
9
+ /** `value` without a trailing `!important`. */
10
+ export const stripImportant=value=>value.replace(COLOUR_REGEX.important,'');
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Colour mathematics: sRGB <-> linear <-> OKLab <-> oklch, and WCAG contrast.
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const clamp01=value=>{if(value<0){return 0;}if(value>1){return 1;}return value;};
17
+ /** sRGB transfer function and its inverse; the piecewise form, not the 2.2 approximation. */
18
+ const srgbToLinear=channel=>channel<=0.04045?channel/12.92:((channel+0.055)/1.055)**2.4;
19
+ const linearToSrgb=channel=>channel<=0.0031308?channel*12.92:1.055*channel**(1/2.4)-0.055;
20
+
21
+ /** Linear-light sRGB (0..1 each) to OKLab. */
22
+ function linearRgbToOklab([red,green,blue]){
23
+ const long=Math.cbrt(0.4122214708*red+0.5363325363*green+0.0514459929*blue);
24
+ const medium=Math.cbrt(0.2119034982*red+0.6806995451*green+0.1073969566*blue);
25
+ const short=Math.cbrt(0.0883024619*red+0.2817188376*green+0.6299787005*blue);
26
+ return {L:0.2104542553*long+0.7936177850*medium-0.0040720468*short,
27
+ a:1.9779984951*long-2.4285922050*medium+0.4505937099*short,
28
+ b:0.0259040371*long+0.7827717662*medium-0.8086757660*short};
29
+ }
30
+
31
+ /** OKLab to linear-light sRGB (unclamped: a value outside 0..1 is outside the sRGB gamut). */
32
+ function oklabToLinearRgb({L,a,b}){
33
+ const long=(L+0.3963377774*a+0.2158037573*b)**3;
34
+ const medium=(L-0.1055613458*a-0.0638541728*b)**3;
35
+ const short=(L-0.0894841775*a-1.2914855480*b)**3;
36
+ return [4.0767416621*long-3.3077115913*medium+0.2309699292*short,
37
+ -1.2684380046*long+2.6097574011*medium-0.3413193965*short,
38
+ -0.0041960863*long-0.7034186147*medium+1.7076147010*short];
39
+ }
40
+
41
+ export const rgbToOklab=([red,green,blue])=>linearRgbToOklab([srgbToLinear(red/255),srgbToLinear(green/255),srgbToLinear(blue/255)]);
42
+ /** Out-of-gamut OKLab clips per channel; `clipped` says so rather than hiding it. */
43
+ export function oklabToRgb(lab){
44
+ const linear=oklabToLinearRgb(lab);
45
+ const clipped=linear.some(channel=>channel<-1e-6||channel>1+1e-6);
46
+ return {rgb:linear.map(channel=>Math.round(clamp01(linearToSrgb(clamp01(channel)))*255)),clipped};
47
+ }
48
+
49
+ export function oklabToOklch({L,a,b}){
50
+ const chroma=Math.hypot(a,b);
51
+ const hue=chroma<1e-7?0:(Math.atan2(b,a)*180/Math.PI+360)%360;
52
+ return {L,C:chroma,h:hue};
53
+ }
54
+ export const oklchToOklab=({L,C,h})=>({L,a:C*Math.cos(h*Math.PI/180),b:C*Math.sin(h*Math.PI/180)});
55
+ export const formatHex=rgb=>`#${rgb.map(channel=>Math.max(0,Math.min(255,Math.round(channel))).toString(16).padStart(2,'0')).join('')}`;
56
+
57
+ const number=(text,{percentOf=1}={})=>{
58
+ const value=String(text).trim();
59
+ if(COLOUR_REGEX.percent.test(value))return Number.parseFloat(value)/100*percentOf;
60
+ if(COLOUR_REGEX.number.test(value))return Number.parseFloat(value);
61
+ if(/^none$/i.test(value))return 0;
62
+ return null;
63
+ };
64
+
65
+ /**
66
+ * Parses the colour notations a brand record and a stylesheet actually use: `#rgb`/`#rgba`/`#rrggbb`/
67
+ * `#rrggbbaa`, `oklch(L C H[/a])` and `rgb()/rgba()`. Alpha is read and reported but never compared:
68
+ * a token's identity is its colour. Anything else - a named colour, `var()`, `color-mix()`, `calc()` -
69
+ * returns null so the caller fails loudly instead of guessing a value.
70
+ */
71
+ export function parseColor(input){
72
+ const value=String(input??'').trim().replace(COLOUR_REGEX.important,'');
73
+ if(!value)return null;
74
+ const hex=/^#([0-9a-f]{3,8})$/i.exec(value);
75
+ return hex?parseHexColor(hex[1],value):parseFunctionColor(value);
76
+ }
77
+
78
+ function parseHexColor(body,raw){
79
+ if(![3,4,6,8].includes(body.length))return null;
80
+ const pairs=body.length<=4?[...body].map(char=>char+char):body.match(/../g);
81
+ const [red,green,blue,alpha]=pairs.map(pair=>Number.parseInt(pair,16));
82
+ return color({notation:'hex',rgb:[red,green,blue],alpha:alpha===undefined?1:alpha/255,raw});
83
+ }
84
+
85
+ function parseFunctionColor(value){
86
+ const call=COLOUR_REGEX.call.exec(value);
87
+ if(!call)return null;
88
+ const kind=call[1].toLowerCase();
89
+ const [head,tail]=call[2].split('/');
90
+ const parts=head.trim().split(/[\s,]+/).filter(Boolean);
91
+ if(parts.length<3)return null;
92
+ const alpha=tail===undefined?1:number(tail,{percentOf:1});
93
+ return kind==='oklch'?parseOklchParts(parts,alpha,value):parseRgbParts(parts,alpha,value);
94
+ }
95
+
96
+ function parseOklchParts(parts,alpha,raw){
97
+ const lightness=number(parts[0],{percentOf:1});
98
+ const chroma=number(parts[1],{percentOf:0.4});
99
+ const hue=number(parts[2]==='none'?'0':String(parts[2]).replace(/deg$/i,''));
100
+ if([lightness,chroma,hue].includes(null))return null;
101
+ const lab=oklchToOklab({L:lightness,C:chroma,h:hue});
102
+ const {rgb,clipped}=oklabToRgb(lab);
103
+ return color({notation:'oklch',rgb,alpha:alpha??1,raw,lab,clipped});
104
+ }
105
+
106
+ function parseRgbParts(parts,alpha,raw){
107
+ const channels=parts.slice(0,3).map(part=>number(part,{percentOf:255}));
108
+ if(channels.includes(null))return null;
109
+ return color({notation:'rgb',rgb:channels.map(channel=>Math.round(channel)),alpha:alpha??1,raw});
110
+ }
111
+
112
+ function color({notation,rgb,alpha,raw,lab=null,clipped=false}){
113
+ const oklab=lab??rgbToOklab(rgb);
114
+ return {notation,raw,rgb,alpha,clipped,oklab,oklch:oklabToOklch(oklab),hex:formatHex(rgb)};
115
+ }
116
+
117
+ /** Euclidean OKLab distance on the x100 scale: black against white is 100. */
118
+ export const deltaEOk=(first,second)=>100*Math.hypot(first.oklab.L-second.oklab.L,first.oklab.a-second.oklab.a,first.oklab.b-second.oklab.b);
119
+ /** WCAG 2.x relative luminance of a parsed colour. */
120
+ const relativeLuminance=({rgb:[red,green,blue]})=>0.2126*srgbToLinear(red/255)+0.7152*srgbToLinear(green/255)+0.0722*srgbToLinear(blue/255);
121
+ /** WCAG 2.x contrast ratio: 21 for black against white, 1 for a colour against itself. */
122
+ export function contrastRatio(first,second){
123
+ const one=relativeLuminance(first),two=relativeLuminance(second);
124
+ return (Math.max(one,two)+0.05)/(Math.min(one,two)+0.05);
125
+ }
@@ -0,0 +1,167 @@
1
+ // brand-tokens.mjs - reading the real tokens out of the real source for the brand checks (brand.mjs): css custom
2
+ // properties by scope, JSON/YAML token files, and the lookup that follows a `var(--other)` through the declared sources.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import {parseYaml} from '../../../engine/yaml.mjs';
6
+ import {grammarDistRefusal} from '../../gates/grammar-dist.mjs';
7
+ import {slash} from '../../lib/path-key.mjs';
8
+ import {stripImportant} from './brand-colour.mjs';
9
+
10
+ const SOURCE_BYTES_LIMIT=4*1024*1024;
11
+ const REGEX=Object.freeze({declaration:new RegExp(['^(--[A-Za-z0-9_-]+)',String.raw`\s*:\s*`,String.raw`([\s\S]+)$`].join('')),semicolons:new RegExp([';+','$'].join(''))});
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Reading the real tokens out of the real source.
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /**
18
+ * Every `--name: value` declaration of a stylesheet, with the selector that carries it. Declarations are
19
+ * split by scope because a brand's `color.tokens[].value` is the default (light) value: a dark override is
20
+ * reported as context, never accepted as a match for it.
21
+ */
22
+ export function parseCssCustomProperties(text){
23
+ const source=String(text??'').replace(/\/\*[\s\S]*?\*\//g,' ');
24
+ const base=new Map(),dark=new Map(),all=[];
25
+ const stack=[];
26
+ let buffer='';
27
+ const flush=()=>{
28
+ const declaration=buffer.trim();
29
+ buffer='';
30
+ const match=REGEX.declaration.exec(declaration);
31
+ if(!match)return;
32
+ const selector=stack.filter(Boolean).join(' ');
33
+ const value=stripImportant(match[2].trim()).replace(REGEX.semicolons,'').trim();
34
+ // `:root:not([data-theme="light"])` inside a dark media query is a dark scope: a negated light theme is not a light one.
35
+ const asserted=selector.replace(/:not\([^)]*\)/g,' ');
36
+ const isDark=/dark/i.test(selector)&&!/data-theme\s*=\s*["']?light/i.test(asserted);
37
+ const entry={name:match[1],value,selector,scope:isDark?'dark':'base'};
38
+ all.push(entry);
39
+ const target=isDark?dark:base;
40
+ if(!target.has(entry.name))target.set(entry.name,entry);
41
+ };
42
+ for(const character of source){
43
+ if(character==='{'){stack.push(buffer.trim());buffer='';continue;}
44
+ if(character==='}'){flush();stack.pop();continue;}
45
+ if(character===';'){flush();continue;}
46
+ buffer+=character;
47
+ }
48
+ flush();
49
+ return {base,dark,all};
50
+ }
51
+
52
+ /**
53
+ * A JSON/YAML token file, flattened by key. Three authored shapes are accepted: a `tokens: [{name,value}]`
54
+ * list (the shape the grammar DNA uses), flat `--token: value` keys, and a nested object whose key path
55
+ * spells the token (`starci.core.primary` is `--starci-core-primary`).
56
+ */
57
+ export function parseTokenData(data){
58
+ const maps={exact:new Map(),derived:new Map()};
59
+ walkTokens(data,[],maps);
60
+ return maps;
61
+ }
62
+
63
+ const putToken=(map,name,value)=>{if(typeof name==='string'&&name&&!map.has(name))map.set(name,String(value));};
64
+ const isTokenLeaf=value=>typeof value==='string'||typeof value==='number';
65
+
66
+ function walkTokens(node,trail,maps){
67
+ if(Array.isArray(node)){
68
+ for(const item of node){
69
+ if(item&&typeof item==='object')walkTokenItem(item,trail,maps);
70
+ }
71
+ return;
72
+ }
73
+ if(node&&typeof node==='object')walkTokenEntries(node,trail,maps);
74
+ }
75
+
76
+ function walkTokenEntries(node,trail,maps){
77
+ for(const [key,value] of Object.entries(node)){
78
+ const next=[...trail,key];
79
+ if(!isTokenLeaf(value)){walkTokens(value,key.startsWith('--')?trail:next,maps);continue;}
80
+ if(key.startsWith('--'))putToken(maps.exact,key,value);
81
+ else {putToken(maps.derived,key,value);putToken(maps.derived,`--${next.join('-')}`,value);}
82
+ }
83
+ }
84
+
85
+ /** One object of a `tokens: [{name,value}]` list: a named leaf, else a nested structure. */
86
+ function walkTokenItem(item,trail,maps){
87
+ if(!isTokenLeaf(item.value)){walkTokens(item,trail,maps);return;}
88
+ const name=item.name??item.token;
89
+ if(typeof name==='string')putToken(name.startsWith('--')?maps.exact:maps.derived,name,item.value);
90
+ }
91
+
92
+ export const readText=file=>{
93
+ const stat=fs.lstatSync(file);
94
+ if(stat.isSymbolicLink()||!stat.isFile()||stat.size>SOURCE_BYTES_LIMIT)throw new Error('A brand source must be a bounded real file.');
95
+ return fs.readFileSync(file,'utf8');
96
+ };
97
+
98
+ const PATH_ESCAPES='the declared path escapes its repository root';
99
+ /** Why a declared source path cannot be read inside `sourceRoot`, or null. */
100
+ function sourcePathError(sourceRoot,declared){
101
+ if(!declared||path.isAbsolute(declared)||declared.split('/').includes('..'))return PATH_ESCAPES;
102
+ const file=path.resolve(sourceRoot,declared);
103
+ if(path.relative(path.resolve(sourceRoot),file).startsWith('..'))return PATH_ESCAPES;
104
+ return fs.existsSync(file)?null:'this repository does not carry the declared file';
105
+ }
106
+
107
+ /** The token lookup of a read source file: css custom properties, or a JSON/YAML token file. */
108
+ function parseSourceText(file,source,text,entry){
109
+ if(source.kind==='css'||path.extname(file).toLowerCase()==='.css'){
110
+ const parsed=parseCssCustomProperties(text);
111
+ return {...entry,found:true,declarations:parsed.all.length,lookup:{css:parsed}};
112
+ }
113
+ try{
114
+ const data=path.extname(file).toLowerCase()==='.json'?JSON.parse(text):parseYaml(text);
115
+ const parsed=parseTokenData(data);
116
+ return {...entry,found:true,declarations:parsed.exact.size+parsed.derived.size,lookup:{tokens:parsed}};
117
+ }catch(error){return {...entry,found:true,error:`unreadable token file: ${String(error.message??error)}`};}
118
+ }
119
+
120
+ /** One declared source file, read into a token lookup. Never throws: an unreadable source is reported. */
121
+ export function readSourceTokens(sourceRoot,source){
122
+ const declared=slash(source?.path??'');
123
+ const entry={repository:source?.repository??null,path:declared,kind:source?.kind??null,found:false,declarations:0,scope:null};
124
+ const pathError=sourcePathError(sourceRoot,declared);
125
+ if(pathError)return {...entry,error:pathError};
126
+ const file=path.resolve(sourceRoot,declared);
127
+ // A source inside a built @starci/grammar dist is read only when that dist is the build of its source:
128
+ // a stale dist would bind the brand to tokens the package no longer ships.
129
+ const refusal=grammarDistRefusal(file);
130
+ if(refusal)return {...entry,found:true,error:refusal.message,staleGrammarDist:{state:refusal.state,root:refusal.root,fix:refusal.fix}};
131
+ let text;
132
+ try{text=readText(file);}catch(error){return {...entry,found:true,error:String(error.message??error)};}
133
+ return parseSourceText(file,source,text,entry);
134
+ }
135
+
136
+ /** The first declaration of this token across the declared sources, preferring the default scope. */
137
+ export function lookupToken(sources,token,seen=new Set()){
138
+ if(seen.has(token))return null;
139
+ const nextSeen=new Set(seen).add(token);
140
+ for(const scope of ['base','dark']){
141
+ for(const source of sources){
142
+ const hit=lookupInSource(sources,source,token,scope,nextSeen);
143
+ if(hit)return hit;
144
+ }
145
+ }
146
+ return null;
147
+ }
148
+
149
+ /** A css declaration of the token in `scope`, its `var(--other)` followed through the declared sources. */
150
+ function resolveCssDeclaration(sources,source,found,scope,seen){
151
+ const reference=/^var\(\s*(--[A-Za-z0-9_-]+)\s*\)$/.exec(String(found.value).trim())?.[1]??null;
152
+ if(reference){
153
+ const resolved=lookupToken(sources,reference,seen);
154
+ if(resolved)return {...resolved,file:source.path,selector:found.selector,scope,declaredValue:found.value,resolvedFrom:reference};
155
+ }
156
+ return {value:found.value,file:source.path,selector:found.selector,scope};
157
+ }
158
+
159
+ /** The token as one source declares it in `scope` (a token file only answers the base scope), or null. */
160
+ function lookupInSource(sources,source,token,scope,seen){
161
+ const found=source.lookup?.css?.[scope].get(token);
162
+ if(found)return resolveCssDeclaration(sources,source,found,scope,seen);
163
+ const tokens=scope==='base'?source.lookup?.tokens:null;
164
+ if(!tokens)return null;
165
+ const value=tokens.exact.get(token)??tokens.derived.get(token)??tokens.derived.get(token.replace(/^--/,''));
166
+ return value===undefined?null:{value,file:source.path,selector:null,scope:'base'};
167
+ }
@@ -0,0 +1,106 @@
1
+ // brand-value-source.mjs - how a colour token the app has not written yet is traced (brand.mjs tokens-match-source). Two forms:
2
+ // {path, value, token?, line?, sha256?} the reference render the value was read from: the file is read, never written.
3
+ // {ruling, token, value} the answered ask that ruled the value, read from the ledger's recorded answer; the
4
+ // declaration the app still has to make is owed to interface.implement.
5
+ // Both pass at the decide stage and are refused at the verify stage while the app source lacks the token (planned-source-missing).
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import {sha256 as digest} from '../../../engine/digest.mjs';
9
+ import {slash} from '../../lib/path-key.mjs';
10
+ import {parseColor,deltaEOk,TOKEN_TOLERANCE} from './brand-colour.mjs';
11
+ import {readSourceTokens,lookupToken} from './brand-tokens.mjs';
12
+
13
+ const round=(value,places=4)=>Number.parseFloat(Number(value).toFixed(places));
14
+ const collapse=value=>String(value??'').trim().replace(/\s+/g,' ').toLowerCase();
15
+ const CUSTOM_PROPERTY=/^--[A-Za-z0-9_-]+$/;
16
+ const COLOUR_IN_TEXT=/#[0-9a-f]{3,8}\b|(?:oklch|rgba?)\([^)]*\)/gi;
17
+ /** The answerers whose recorded answer is a ruling: the owner, the owner's standing autopilot ruling, and the config's auto-accept of the recommended option. */
18
+ const RULING_ANSWERERS=Object.freeze(['owner','autopilot','auto-recommended']);
19
+ /** The op that writes the app theme, so the one that owes the declaration of a planned token. */
20
+ export const OWED_TO='interface.implement';
21
+
22
+ const invalid=why=>({status:'value-source-invalid',why});
23
+
24
+ /** The first recorded answer of `ruling` given by one of RULING_ANSWERERS, or the first refusal. */
25
+ function rulingAnswer({ruling,brandDir,findReceipt}){
26
+ if(typeof findReceipt!=='function')return {ok:false,why:'no ledger was given to read the ruling from'};
27
+ const attempts=RULING_ANSWERERS.map(answerer=>findReceipt({acceptedBy:ruling,brandDir,answerer}));
28
+ return attempts.find(attempt=>attempt.ok)??attempts[0];
29
+ }
30
+
31
+ /** Whether the answer's own words (the chosen option, the note, the picks) carry `value`, as the same text or as a colour equal to it. */
32
+ function answerCarries(answer,value){
33
+ const wanted=parseColor(value);
34
+ const texts=[answer.option,answer.note,...Object.values(answer.picks??{})].filter(text=>typeof text==='string');
35
+ const sameColour=text=>[...text.matchAll(COLOUR_IN_TEXT)].some(found=>{const colour=parseColor(found[0]);return Boolean(colour&&wanted&&deltaEOk(colour,wanted)<=TOKEN_TOLERANCE);});
36
+ return texts.some(text=>collapse(text).includes(collapse(value))||sameColour(text));
37
+ }
38
+
39
+ /** The shape of a ruling reference, or the refusal naming what is wrong with it. */
40
+ function rulingShapeProblem(reference){
41
+ if(typeof reference.ruling!=='string'||!reference.ruling.trim())return 'valueSource.ruling must be the dispatch id of the answered ask that ruled this value';
42
+ if(typeof reference.token!=='string'||!CUSTOM_PROPERTY.test(reference.token))return 'valueSource.token must be the custom property the app has to declare';
43
+ if(typeof reference.value!=='string'||!reference.value.trim())return 'valueSource.value must be the exact value the ruling carries';
44
+ if(['path','sha256','line'].some(key=>Object.hasOwn(reference,key)))return 'a ruling valueSource is {ruling, token, value}; a file reference names path and has no ruling';
45
+ return null;
46
+ }
47
+
48
+ /** A token traced to the ledger's recorded answer: the answer exists, was given by a ruling answerer, carries the value, and the value is the brand value. */
49
+ function checkRulingSource({token,reference,brandDir,findReceipt}){
50
+ const problem=rulingShapeProblem(reference);
51
+ if(problem)return invalid(problem);
52
+ if(reference.token!==token.token)return invalid(`valueSource.token ${reference.token} must be this brand token, ${token.token}: the app declares it under its brand token name`);
53
+ const evidence={ruling:reference.ruling,token:reference.token,value:reference.value,owedTo:OWED_TO};
54
+ const answer=rulingAnswer({ruling:reference.ruling,brandDir,findReceipt});
55
+ if(!answer.ok)return {...evidence,status:'ruling-unrecorded',why:answer.why};
56
+ const given={answeredBy:answer.answeredBy,provisional:answer.provisional,answeredAt:answer.at,receipt:answer.file};
57
+ if(!answerCarries(answer,reference.value))return {...evidence,...given,status:'ruling-differs',why:`the answer to ${reference.ruling} does not carry ${reference.value}`};
58
+ const expected=parseColor(token.value),ruled=parseColor(reference.value);
59
+ if(!expected||!ruled)return {...evidence,...given,status:'unparseable-reference-value',why:'the brand value or the ruled value is not a colour this runtime can parse'};
60
+ const delta=round(deltaEOk(expected,ruled),3);
61
+ if(delta>TOKEN_TOLERANCE)return {...evidence,...given,deltaE:delta,status:'ruling-differs',why:`the brand value ${token.value} is not the ruled value ${reference.value}`};
62
+ return {...evidence,...given,deltaE:delta,status:'planned-from-ruling'};
63
+ }
64
+
65
+ /**
66
+ * A token the app has not written yet may name the external render it was read from:
67
+ * `valueSource: {path, value, token?, line?, sha256?}`. `path` is relative to the same --source root as
68
+ * `sources[]` (for example `acme-fe/src/app/globals.css`) and is only ever read; `token` is the
69
+ * name the reference declares (default: the brand token's own name); `value` is the exact value it declares
70
+ * there. The reference passes when that declaration exists in the default (light) scope with exactly that
71
+ * text, its colour is the brand value, and a declared sha256 still names the file's bytes.
72
+ */
73
+ function checkFileSource({sourceRoot,token,reference}){
74
+ if(typeof reference.path!=='string'||!reference.path.trim())return invalid('valueSource.path must name the reference file, relative to the --source root');
75
+ if(typeof reference.value!=='string'||!reference.value.trim())return invalid('valueSource.value must be the exact value the reference declares');
76
+ const name=reference.token??token.token;
77
+ if(typeof name!=='string'||!CUSTOM_PROPERTY.test(name))return invalid('valueSource.token must be the custom property the reference declares');
78
+ const evidence={path:slash(reference.path),token:name,value:reference.value,line:reference.line??null,declaredSha256:reference.sha256??null};
79
+ const extension=path.extname(reference.path).toLowerCase();
80
+ const read=readSourceTokens(sourceRoot,{path:reference.path,kind:extension==='.css'?'css':'tokens'});
81
+ if(read.error)return {...evidence,status:'reference-unreadable',why:read.error};
82
+ if(reference.sha256!==undefined&&reference.sha256!==null){
83
+ const computed=digest(fs.readFileSync(path.resolve(sourceRoot,reference.path)));
84
+ if(String(reference.sha256).toLowerCase()!==computed)
85
+ return {...evidence,computedSha256:computed,status:'reference-digest-mismatch',why:'the reference file is no longer the bytes the brand read'};
86
+ }
87
+ const found=lookupToken([read],name);
88
+ if(!found)return {...evidence,status:'reference-absent',why:`the reference does not declare ${name}`};
89
+ if(found.scope==='dark')return {...evidence,actual:found.value,selector:found.selector,status:'reference-only-in-dark-scope',why:`the reference declares ${name} only in a dark scope`};
90
+ const declared=[found.value,found.declaredValue].filter(value=>value!==undefined).map(collapse);
91
+ if(!declared.includes(collapse(reference.value)))
92
+ return {...evidence,actual:found.declaredValue??found.value,selector:found.selector,status:'reference-differs',why:`the reference declares ${name}: ${found.declaredValue??found.value}, not ${reference.value}`};
93
+ const expected=parseColor(token.value),planned=parseColor(reference.value);
94
+ if(!expected||!planned)return {...evidence,status:'unparseable-reference-value',why:'the brand value or the reference value is not a colour this runtime can parse'};
95
+ const delta=round(deltaEOk(expected,planned),3);
96
+ if(delta>TOKEN_TOLERANCE)return {...evidence,deltaE:delta,status:'reference-differs',why:`the brand value ${token.value} is not the reference value ${reference.value}`};
97
+ return {...evidence,selector:found.selector,deltaE:delta,status:'planned-from-reference'};
98
+ }
99
+
100
+ /** One planned token's trace: its reference file or its ruling. `findReceipt` reads an answered ask from the ledger (brand.mjs findOwnerReceipt). */
101
+ export function checkValueSource({sourceRoot,token,brandDir=null,findReceipt=null}){
102
+ const reference=token?.valueSource;
103
+ if(!reference||typeof reference!=='object'||Array.isArray(reference))return invalid('valueSource must be an object {path, value, token?, line?, sha256?} or {ruling, token, value}');
104
+ if(Object.hasOwn(reference,'ruling'))return checkRulingSource({token,reference,brandDir,findReceipt});
105
+ return checkFileSource({sourceRoot,token,reference});
106
+ }