@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
@@ -15,7 +15,7 @@ export const SECOND_LANGUAGE_LETTER = /[À-ÃÈ-ÊÌÍÒ-ÕÙÚÝà-ãè-êìí
15
15
  export const hasSecondLanguage = (text) => typeof text === 'string' && SECOND_LANGUAGE_LETTER.test(text.normalize('NFC'));
16
16
 
17
17
  /** Every line of `text` with a Vietnamese letter: `[{ line, column }]`, both 1-based. */
18
- export function secondLanguageHits(text) {
18
+ function secondLanguageHits(text) {
19
19
  const hits = [];
20
20
  String(text).split(/\r?\n/).forEach((raw, index) => {
21
21
  const match = SECOND_LANGUAGE_LETTER.exec(raw.normalize('NFC'));
@@ -24,6 +24,99 @@ export function secondLanguageHits(text) {
24
24
  return hits;
25
25
  }
26
26
 
27
+ /** The named bindings of an import from the backend message catalog contract. */
28
+ function messageCatalogBindings(ts, statement) {
29
+ if (!ts.isImportDeclaration(statement) || statement.moduleSpecifier.text !== '@modules/platform/i18n') return null;
30
+ return statement.importClause?.namedBindings ?? null;
31
+ }
32
+
33
+ /** The local type names imported from the backend message catalog contract. */
34
+ function messageBundleImports(ts, source) {
35
+ const named = new Set();
36
+ const namespaces = new Set();
37
+ for (const statement of source.statements) {
38
+ const bindings = messageCatalogBindings(ts, statement);
39
+ if (!bindings) continue;
40
+ if (ts.isNamespaceImport(bindings)) namespaces.add(bindings.name.text);
41
+ for (const entry of ts.isNamedImports(bindings) ? bindings.elements : []) {
42
+ if ((entry.propertyName ?? entry.name).text === 'MessageBundle') named.add(entry.name.text);
43
+ }
44
+ }
45
+ return { named, namespaces };
46
+ }
47
+
48
+ /** A declaration that introduces a same-named local type or namespace. */
49
+ function declaresTypeName(ts, statement, name) {
50
+ if (statement.name?.text !== name) return false;
51
+ return [ts.isTypeAliasDeclaration, ts.isInterfaceDeclaration, ts.isClassDeclaration, ts.isEnumDeclaration, ts.isModuleDeclaration]
52
+ .some(predicate => predicate(statement));
53
+ }
54
+
55
+ /** A type reference bound to the imported MessageBundle contract, including import aliases. */
56
+ function isMessageBundleType(ts, type, imports) {
57
+ if (!type || !ts.isTypeReferenceNode(type)) return false;
58
+ const name = type.typeName;
59
+ if (ts.isIdentifier(name)) return imports.named.has(name.text) && !shadowsImportedType(ts, type, name.text);
60
+ return ts.isQualifiedName(name) && ts.isIdentifier(name.left)
61
+ && imports.namespaces.has(name.left.text) && name.right.text === 'MessageBundle' && !shadowsImportedType(ts, type, name.left.text);
62
+ }
63
+
64
+ /** A local declaration or generic type parameter takes precedence over a same-named import. */
65
+ function shadowsImportedType(ts, node, name) {
66
+ for (let scope = node.parent; scope; scope = scope.parent) {
67
+ if (scope.typeParameters?.some(parameter => parameter.name.text === name)) return true;
68
+ if (scope.statements?.some(statement => declaresTypeName(ts, statement, name))) return true;
69
+ }
70
+ return false;
71
+ }
72
+
73
+ /** The literal object supplied by a variable typed with the imported MessageBundle contract. */
74
+ function messageCatalogObject(ts, node, imports) {
75
+ if (!ts.isVariableDeclaration(node) || !node.initializer) return null;
76
+ let value = node.initializer;
77
+ const type = ts.isSatisfiesExpression(value) ? value.type : node.type;
78
+ if (ts.isSatisfiesExpression(value)) value = value.expression;
79
+ return isMessageBundleType(ts, type, imports) && ts.isObjectLiteralExpression(value) ? value : null;
80
+ }
81
+
82
+ /** Literal string values in the Vietnamese locale of one message catalog. */
83
+ function vietnameseValueRanges(ts, catalog, source) {
84
+ const ranges = [];
85
+ for (const locale of catalog.properties) {
86
+ if (!ts.isPropertyAssignment(locale) || locale.name.text !== 'vi' || !ts.isObjectLiteralExpression(locale.initializer)) continue;
87
+ for (const message of locale.initializer.properties) {
88
+ if (ts.isPropertyAssignment(message) && ts.isStringLiteralLike(message.initializer))
89
+ ranges.push({ start: message.initializer.getStart(source), end: message.initializer.end });
90
+ }
91
+ }
92
+ return ranges;
93
+ }
94
+
95
+ /** Literal Vietnamese message values of typed catalogs; keys, comments, English values and expressions remain source. */
96
+ function messageBundleValueRanges(ts, source) {
97
+ const imports = messageBundleImports(ts, source);
98
+ const ranges = [];
99
+ const visit = (node) => {
100
+ const catalog = messageCatalogObject(ts, node, imports);
101
+ if (catalog) ranges.push(...vietnameseValueRanges(ts, catalog, source));
102
+ ts.forEachChild(node, visit);
103
+ };
104
+ visit(source);
105
+ return ranges;
106
+ }
107
+
108
+ /** Source-language hits, treating only literal vi values of typed backend MessageBundle objects as translated product data. */
109
+ export function sourceLanguageHits(rel, text, ts) {
110
+ if (!hasSecondLanguage(text)) return [];
111
+ if (!ts || !/\.[cm]?tsx?$/.test(rel)) return secondLanguageHits(text);
112
+ const source = ts.createSourceFile(rel, text, ts.ScriptTarget.Latest, true);
113
+ let sourceText = text;
114
+ for (const range of messageBundleValueRanges(ts, source).sort((left, right) => right.start - left.start)) {
115
+ sourceText = sourceText.slice(0, range.start) + sourceText.slice(range.start, range.end).replace(/[^\r\n]/g, ' ') + sourceText.slice(range.end);
116
+ }
117
+ return secondLanguageHits(sourceText);
118
+ }
119
+
27
120
  /**
28
121
  * The Vietnamese fields of one failure-code entry: the operator text the owner mandated for the failure-code catalog
29
122
  * (scripts/checks/check-failure-codes.mjs types the entry with exactly these). causes_vi is the list companion of the three scalar fields.
@@ -0,0 +1,44 @@
1
+ // loosening-budgets.mjs - the numeric budgets of the canon, pinned in knowledge/hfs/design-truths.yaml (`budgets`), judged on the tree as it stands.
2
+ // A budget is a ceiling (a file, a function, a feature may not be bigger) or a floor (a duplicate block is at least this large to count): raising a
3
+ // ceiling or lowering a floor in the slot manifests loosens the gate, and so does a budget that is gone. The pin names the file, the ruleParams node
4
+ // and the numbers, and the per-slot `budget` maps; the check reads each file's YAML and compares.
5
+ import { parseYaml } from '../../engine/yaml.mjs';
6
+
7
+ const at = (node, dotted) => (dotted ? dotted.split('.').reduce((value, key) => value?.[key], node) : node);
8
+ const isNumber = (value) => typeof value === 'number';
9
+
10
+ function paramRecords(doc, params) {
11
+ const base = at(doc, params.under);
12
+ const found = [];
13
+ for (const [key, max] of Object.entries(params.ceilings ?? {})) {
14
+ const value = at(base, key);
15
+ if (!isNumber(value) || value > max) found.push(`${key} ${value ?? 'missing'}, the ceiling is ${max}`);
16
+ }
17
+ for (const [key, min] of Object.entries(params.floors ?? {})) {
18
+ const value = at(base, key);
19
+ if (!isNumber(value) || value < min) found.push(`${key} ${value ?? 'missing'}, the floor is ${min}`);
20
+ }
21
+ for (const [key, want] of Object.entries(params.equals ?? {})) {
22
+ if (at(base, key) !== want) found.push(`${key} ${at(base, key) ?? 'missing'}, the canon says ${want}`);
23
+ }
24
+ return found;
25
+ }
26
+
27
+ function slotRecords(doc, slots) {
28
+ const found = [];
29
+ for (const [id, ceilings] of Object.entries(slots ?? {})) {
30
+ const slot = (doc.slots ?? []).find((entry) => entry.id === id);
31
+ for (const [key, max] of Object.entries(ceilings)) {
32
+ const value = slot?.budget?.[key];
33
+ if (!isNumber(value) || value > max) found.push(`slot ${id} budget ${key} ${value ?? 'missing'}, the ceiling is ${max}`);
34
+ }
35
+ }
36
+ return found;
37
+ }
38
+
39
+ /** What the YAML `text` of a pinned file holds against its pin: the details of every budget raised, lowered or gone (an unreadable file is one). */
40
+ export function budgetRecords(text, pin) {
41
+ let doc;
42
+ try { doc = parseYaml(text); } catch { return ['the file is not readable YAML, so its budgets cannot be judged']; }
43
+ return [...(pin.params ? paramRecords(doc, pin.params) : []), ...slotRecords(doc, pin.slots)];
44
+ }
@@ -0,0 +1,361 @@
1
+ // loosening-config.mjs - reads the gate configuration files of a tree as the lists and numbers that could relax a gate.
2
+ // A config file is one of a fixed set of kinds (codecov, sonar, jest, eslint, stylelint, tsconfig, ignore file, stack declaration, hfs declaration,
3
+ // exception file); for each, this module returns what the file states, as plain records, and judges nothing: scripts/lib/loosening-state.mjs
4
+ // compares the records with the canon and the design-truths manifest. YAML is parsed (anchors, flow style and quoted keys resolve), a
5
+ // .properties file is read by the Java rules (continuations; =, : or a blank as the separator), a tsconfig is found by its content.
6
+ // lists { list, key, item, line } an entry of an ignore or exclude list; `list` is the class the manifest allows entries by
7
+ // numbers { key, value, line } a coverage target or threshold
8
+ // settings { key, value, line } a yes/no switch that can relax a gate
9
+ // rulesOff { rule, line } an ESLint or stylelint rule turned off or down
10
+ // strictness { key, value, line } a tsconfig strictness option turned off, or a type-check skip turned on
11
+ // budgets { key, line } a numeric budget a config states itself (the canon owns every budget)
12
+ // scope { key, line } a key that narrows what a tool reads or runs (the canon configs state none)
13
+ import { parseYaml } from '../../engine/yaml.mjs';
14
+ import { langOf, lexSource, splitLines } from './js-lex.mjs';
15
+ import { splitList } from './list.mjs';
16
+
17
+ export const LIST = Object.freeze({
18
+ coverage: 'coverage-exclusions',
19
+ sonar: 'sonar-exclusions',
20
+ cpd: 'cpd-exclusions',
21
+ scope: 'sonar-scope',
22
+ paths: 'path-ignores',
23
+ jest: 'jest-patterns',
24
+ issues: 'issue-ignores',
25
+ });
26
+
27
+ const JEST_PRESET = 'packages/jest-preset/index.cjs';
28
+ const KINDS = [
29
+ ['codecov', /(?:^|\/)\.?codecov\.ya?ml$/],
30
+ ['sonar', /(?:^|\/)sonar[\w.-]*\.properties$/],
31
+ ['jest', /(?:^|\/)jest\.config\.(?:[cm]?[jt]s|json)$|^packages\/jest-preset\/index\.cjs$/],
32
+ ['eslint', /(?:^|\/)(?:eslint\.config\.[cm]?[jt]s|\.eslintrc(?:\.\w+)?)$|^packages\/eslint\/[^/]+\/lib\/config\.mjs$/],
33
+ ['stylelint', /(?:^|\/)(?:stylelint\.config\.[cm]?[jt]s|\.stylelintrc(?:\.\w+)?)$/],
34
+ ['tsconfig', /(?:^|\/)tsconfig[\w.-]*\.json$|^packages\/tsconfig\/[\w-]+\.json$/],
35
+ ['ignorefile', /(?:^|\/)\.?(?:prettier|eslint|stylelint)ignore$/],
36
+ ['stack', /(?:^|\/)application-stacks\.ya?ml$/],
37
+ ['declaration', /(?:^|\/)hfs\.json$/],
38
+ ];
39
+
40
+ /** The kind of gate configuration `file` is, or null. A JSON file that holds compilerOptions is a tsconfig whatever it is called (it may be extended). */
41
+ export function configKindOf(file, text = '') {
42
+ const named = KINDS.find(([, pattern]) => pattern.test(file))?.[0];
43
+ if (named) return named;
44
+ return file.endsWith('.json') && !file.endsWith('package.json') && /"compilerOptions"\s*:/.test(text) ? 'tsconfig' : null;
45
+ }
46
+
47
+ /** Whether `file` may be a tsconfig by its extension: the check has to read it to know. */
48
+ export const maybeTsconfig = (file) => file.endsWith('.json') && !file.endsWith('package.json') && !file.endsWith('package-lock.json');
49
+
50
+ const EXCEPTION_WORDS = new Set(['allowlist', 'approval', 'approvals', 'exception', 'exceptions', 'exempt', 'exemption', 'exemptions', 'waiver', 'waivers',
51
+ 'suppression', 'suppressions', 'baseline', 'accepted', 'permit', 'permits', 'ignored', 'skiplist']);
52
+ const EXCEPTION_PAIRS = ['allow-list', 'known-issues', 'known-findings', 'ignore-list', 'skip-list', 'accepted-risk', 'accepted-risks'];
53
+ const EXCEPTION_EXTENSIONS = ['.yaml', '.yml', '.json', '.txt', '.csv'];
54
+
55
+ /** Files whose name says, anywhere in it, that they hold exceptions, approvals or a baseline of findings; a schema file only describes one. */
56
+ export function isExceptionFile(file) {
57
+ const base = file.slice(file.lastIndexOf('/') + 1).toLowerCase();
58
+ const extension = EXCEPTION_EXTENSIONS.find((suffix) => base.endsWith(suffix));
59
+ if (!extension) return false;
60
+ const stem = base.slice(0, -extension.length);
61
+ if (stem.endsWith('.schema')) return false;
62
+ return stem.split(/[._-]/).some((word) => EXCEPTION_WORDS.has(word)) || EXCEPTION_PAIRS.some((pair) => stem.includes(pair));
63
+ }
64
+
65
+ const METADATA_KEYS = new Set(['schema', 'version', 'id', 'title', 'purpose', 'description']);
66
+
67
+ /** The entries a parsed exception document holds: the size of every array or map section, one for any other value. */
68
+ function sectionSize(value) {
69
+ if (Array.isArray(value)) return value.length;
70
+ if (value !== null && typeof value === 'object') return Object.keys(value).length;
71
+ return value === null || value === undefined || value === '' ? 0 : 1;
72
+ }
73
+
74
+ /** How many entries an exception file holds (parsed: a flow-style list counts like a block-style one; a text list counts its lines). */
75
+ export function exceptionEntries(file, text) {
76
+ if (/\.(?:ya?ml|json)$/i.test(file)) {
77
+ let doc;
78
+ try { doc = file.endsWith('.json') ? JSON.parse(text) : parseYaml(text); } catch { return 1; }
79
+ if (Array.isArray(doc)) return doc.length;
80
+ return Object.entries(doc ?? {}).filter(([key]) => !METADATA_KEYS.has(key)).reduce((sum, [, value]) => sum + sectionSize(value), 0);
81
+ }
82
+ return splitLines(text).filter((line) => line.trim() && !line.trimStart().startsWith('#')).length;
83
+ }
84
+
85
+ const PLACEHOLDER = /\{\{|\}\}/;
86
+ const record = (list, key, item, line) => ({ list, key, item: String(item).trim(), line });
87
+ const unquote = (value) => String(value).trim().replace(/^["']|["']$/g, '');
88
+ const lineOfText = (text, needle) => Math.max(1, splitLines(text).findIndex((line) => line.includes(needle)) + 1);
89
+
90
+ /** A .properties file as [{key, value, line}]: logical lines (a trailing backslash continues), key and value split at the first =, : or blank. */
91
+ function propertyRows(text) {
92
+ const rows = [];
93
+ const lines = splitLines(text);
94
+ for (let index = 0; index < lines.length; index += 1) {
95
+ let logical = lines[index];
96
+ const line = index + 1;
97
+ while (/(?:^|[^\\])(?:\\\\)*\\$/.test(logical) && index + 1 < lines.length) { logical = `${logical.slice(0, -1)}${lines[index + 1].trimStart()}`; index += 1; }
98
+ const trimmed = logical.trimStart();
99
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('!')) continue;
100
+ const split = trimmed.search(/[=:\s]/);
101
+ if (split < 1) continue;
102
+ rows.push({ key: trimmed.slice(0, split), value: trimmed.slice(split).replace(/^\s*[=:]?\s*/, '').trim(), line });
103
+ }
104
+ return rows;
105
+ }
106
+
107
+ const SONAR_LIST_KEY = /^sonar\.(?:[a-z]+\.)?(?:global\.|test\.|coverage\.|cpd\.)?exclusions$/;
108
+ const SONAR_SCOPE_KEYS = new Set(['sonar.inclusions', 'sonar.sources']);
109
+
110
+ function sonarListOf(key) {
111
+ if (key.includes('coverage.')) return LIST.coverage;
112
+ return key.includes('cpd.') ? LIST.cpd : LIST.sonar;
113
+ }
114
+
115
+ function sonarRecords(text) {
116
+ const lists = [];
117
+ const settings = [];
118
+ for (const { key, value, line } of propertyRows(text)) {
119
+ const kind = SONAR_LIST_KEY.test(key) ? sonarListOf(key) : (SONAR_SCOPE_KEYS.has(key) && LIST.scope);
120
+ if (kind) lists.push(...splitList(value, { sep: ',' }).filter((item) => !PLACEHOLDER.test(item)).map((item) => record(kind, key, item, line)));
121
+ else if (/^sonar\.(?:issue\.(?:ignore|enforce)\.|skip$)/.test(key)) lists.push(record(LIST.issues, key, `${key}=${value}`, line));
122
+ else if (key === 'sonar.qualitygate.wait') settings.push({ key, value, line });
123
+ }
124
+ return { lists, settings };
125
+ }
126
+
127
+ const SETTING_KEYS = new Set(['require_ci_to_pass', 'informational', 'if_ci_failed']);
128
+ const percentOf = (value) => (typeof value === 'number' ? value : Number.parseFloat(String(value).replace('%', '')));
129
+
130
+ /** Every `{key, value}` under a `status` mapping, however deep (anchors and flow style are already resolved by the parser). */
131
+ function walkCodecov(node, found, path = []) {
132
+ if (node === null || typeof node !== 'object') return;
133
+ for (const [key, value] of Object.entries(node)) {
134
+ if ((key === 'target' || key === 'threshold') && (typeof value !== 'object' || value === null)) {
135
+ const number = key === 'target' && String(value).trim() === 'auto' ? 0 : percentOf(value);
136
+ found.numbers.push({ key, value: Number.isNaN(number) ? 0 : number, line: 1 });
137
+ } else if (SETTING_KEYS.has(key)) found.settings.push({ key, value: String(value), line: 1 });
138
+ else if ((key === 'project' || key === 'patch' || key === 'changes') && path.includes('status') && (value === false || String(value) === 'off')) found.settings.push({ key: 'status', value: 'off', line: 1 });
139
+ walkCodecov(value, found, [...path, key]);
140
+ }
141
+ }
142
+
143
+ function codecovParsed(text) {
144
+ const doc = parseYaml(text);
145
+ const found = { lists: [], numbers: [], settings: [] };
146
+ for (const item of Array.isArray(doc?.ignore) ? doc.ignore : []) found.lists.push(record(LIST.coverage, 'ignore', unquote(item), lineOfText(text, String(item))));
147
+ walkCodecov(doc, found);
148
+ return found;
149
+ }
150
+
151
+ function listItemOf(line) {
152
+ const text = line.trim();
153
+ if (!text.startsWith('- ')) return null;
154
+ const note = text.indexOf(' #');
155
+ return (note < 0 ? text : text.slice(0, note)).slice(2).trim() || null;
156
+ }
157
+
158
+ /** The line-shaped reading of a codecov file the YAML parser cannot read (a template with placeholders). */
159
+ function codecovLines(text) {
160
+ const lines = splitLines(text);
161
+ const found = { lists: [], numbers: [], settings: [] };
162
+ lines.forEach((raw, index) => {
163
+ const number = /^\s*(target|threshold)\s*:\s*["']?(-?\d+(?:\.\d+)?)%?["']?\s*(?:#.*)?$/.exec(raw);
164
+ if (number) found.numbers.push({ key: number[1], value: Number(number[2]), line: index + 1 });
165
+ const setting = /^\s*(require_ci_to_pass|informational|if_ci_failed)\s*:\s*(\S+)/.exec(raw);
166
+ if (setting) found.settings.push({ key: setting[1], value: unquote(setting[2]), line: index + 1 });
167
+ });
168
+ const start = lines.findIndex((line) => /^ignore\s*:/.test(line));
169
+ for (let at = start + 1; start >= 0 && at < lines.length && !/^\S/.test(lines[at]); at += 1) {
170
+ const item = listItemOf(lines[at]);
171
+ if (item && !PLACEHOLDER.test(item)) found.lists.push(record(LIST.coverage, 'ignore', unquote(item), at + 1));
172
+ }
173
+ return found;
174
+ }
175
+
176
+ /** A YAML anchor or alias outside a comment: the codecov file of the canon states every value itself, so a reused node can hide an entry. */
177
+ const ANCHOR_WORD = /^[&*][\w-]+$/;
178
+ /** A line without its comment and without what sits in quotes. */
179
+ function bareLine(line) {
180
+ let quote = '';
181
+ let out = '';
182
+ for (const char of line) {
183
+ if (quote) { if (char === quote) quote = ''; } else if (char === '"' || char === "'") quote = char;
184
+ else if (char === '#') break;
185
+ else out += char;
186
+ }
187
+ return out;
188
+ }
189
+ const hasAlias = (text) => splitLines(text).some((line) => bareLine(line).split(/[\s[\]{},:]+/).some((word) => ANCHOR_WORD.test(word)));
190
+
191
+ function codecovRecords(text) {
192
+ const aliased = hasAlias(text) ? [{ key: 'yaml-alias', value: 'true', line: lineOfText(text, '&') }] : [];
193
+ let found;
194
+ try { found = codecovParsed(text); } catch { found = codecovLines(text); }
195
+ return { ...found, settings: [...found.settings, ...aliased] };
196
+ }
197
+
198
+ /** The offsets of the bracket pair that opens at `open` in the blanked `code`. */
199
+ function closeOf(code, open) {
200
+ let depth = 0;
201
+ for (let at = open; at < code.length; at += 1) {
202
+ if (code[at] === '[') depth += 1;
203
+ else if (code[at] === ']') { depth -= 1; if (depth === 0) return at; }
204
+ }
205
+ return code.length;
206
+ }
207
+
208
+ const ARRAY_START = /[:=(]\s*(?:Object\.freeze\(\s*)?\[/g;
209
+ const NAME_CHAR = /[\w$]/;
210
+
211
+ /** The identifier that ends right before `end` in the blanked `code`, past a closing quote and blanks (a key, a constant or a callee). */
212
+ function nameBefore(code, end) {
213
+ let stop = end;
214
+ while (stop > 0 && (code[stop - 1] === ' ' || code[stop - 1] === '\t' || code[stop - 1] === '"' || code[stop - 1] === "'")) stop -= 1;
215
+ let start = stop;
216
+ while (start > 0 && NAME_CHAR.test(code[start - 1])) start -= 1;
217
+ return code.slice(start, stop);
218
+ }
219
+
220
+ /** The string elements of every array a key, a constant or a call named by `accept(name)` holds. */
221
+ function arrayItems(lexed, accept) {
222
+ const found = [];
223
+ for (const match of lexed.code.matchAll(ARRAY_START)) {
224
+ const name = nameBefore(lexed.code, match.index);
225
+ if (!name || !accept(name)) continue;
226
+ const open = match.index + match[0].length - 1;
227
+ const close = closeOf(lexed.code, open);
228
+ for (const piece of lexed.strings) if (piece.at > open && piece.at < close) found.push({ name, item: piece.text, line: piece.line });
229
+ }
230
+ return found;
231
+ }
232
+
233
+ const JEST_ARRAYS = new Map([
234
+ ['coveragePathIgnorePatterns', LIST.coverage], ['excludes', LIST.coverage],
235
+ ['testPathIgnorePatterns', LIST.jest], ['modulePathIgnorePatterns', LIST.jest], ['watchPathIgnorePatterns', LIST.jest],
236
+ ]);
237
+ const JEST_SCOPE_KEYS = ['testMatch', 'testRegex', 'roots', 'projects', 'collectCoverageFrom', 'coverageThreshold', 'coverageProvider', 'rootDir', 'testPathPattern'];
238
+ const lineAt = (text, index) => splitLines(text.slice(0, index)).length;
239
+
240
+ /** The ranges of the `coverage: { ... }` objects of a jest config: the rendered coverage options (roots, roles, excludes) are the canon's, not a scope of the run. */
241
+ function coverageRanges(code) {
242
+ const ranges = [];
243
+ for (const match of code.matchAll(/\bcoverage\s*:\s*\{/g)) {
244
+ let depth = 0;
245
+ for (let at = match.index + match[0].length - 1; at < code.length; at += 1) {
246
+ if (code[at] === '{') depth += 1;
247
+ else if (code[at] === '}') { depth -= 1; if (depth === 0) { ranges.push([match.index, at]); break; } }
248
+ }
249
+ }
250
+ return ranges;
251
+ }
252
+
253
+ function jestRecords(text, file) {
254
+ const lexed = lexSource(text, langOf(file));
255
+ const source = file.endsWith('.json') ? { ...lexed, code: text, strings: [...text.matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((m) => ({ at: m.index + 1, line: lineAt(text, m.index), text: m[1] })) } : lexed;
256
+ const lists = arrayItems(source, (name) => JEST_ARRAYS.has(name)).map((entry) => record(JEST_ARRAYS.get(entry.name), entry.name, entry.item, entry.line));
257
+ const numbers = [...text.matchAll(/["']?\b(lines|branches|functions|statements)\b["']?\s*:\s*(-?\d+(?:\.\d+)?)/g)].map((match) => ({ key: match[1], value: Number(match[2]), line: lineAt(text, match.index) }));
258
+ const settings = [...text.matchAll(/["']?\bpassWithNoTests\b["']?\s*:\s*(true)\b/g)].map((match) => ({ key: 'passWithNoTests', value: match[1], line: lineAt(text, match.index) }));
259
+ const inside = coverageRanges(source.code);
260
+ const scope = file === JEST_PRESET ? [] : JEST_SCOPE_KEYS.flatMap((key) => [...source.code.matchAll(new RegExp(String.raw`["']?\b${key}\b["']?\s*:`, 'g'))]
261
+ .filter((match) => !inside.some(([from, to]) => match.index > from && match.index < to)).map((match) => ({ key, line: lineAt(text, match.index) })));
262
+ return { lists, numbers, settings, scope };
263
+ }
264
+
265
+ const IGNORE_KEYS = new Set(['ignores', 'ignoreFiles', 'ignorePatterns', 'globalIgnores']);
266
+ const IGNORE_SUFFIXES = ['IGNORED', 'IGNORES', 'EXCLUDE', 'EXCLUDES'];
267
+ const isIgnoreName = (name) => IGNORE_KEYS.has(name) || (name === name.toUpperCase() && (name.startsWith('OUTSIDE_THE_') || IGNORE_SUFFIXES.some((suffix) => name.endsWith(suffix))));
268
+ const LEVEL = /["']([@\w/.-]+)["']\s*:\s*(?:["'](?:off|warn)["']|\[\s*["'](?:off|warn)["']|[01])/g;
269
+ const NULL_RULE = /["']([@\w/.-]+)["']\s*:\s*null\b/g;
270
+ const BUDGET_KEYS = /"(ruleParams|budget|budgets|fileLines|functionLines|featureFiles|duplicateBlock)"\s*:/g;
271
+ const LINT_BUDGETS = /["']?(max-lines|max-lines-per-function|max-params|max-depth|max-statements|max-nested-callbacks|complexity)["']?\s*:/g;
272
+
273
+ /** Every budget key `pattern` finds in `text`, with its line. */
274
+ function budgetKeys(text, pattern) {
275
+ return [...text.matchAll(pattern)].map((match) => ({ key: match[1], line: lineAt(text, match.index) }));
276
+ }
277
+
278
+ function lintRecords(text, file, kind) {
279
+ const lexed = lexSource(text, langOf(file));
280
+ const lists = arrayItems(lexed, isIgnoreName).map((entry) => record(LIST.paths, entry.name, entry.item, entry.line));
281
+ const rulesOff = [];
282
+ const pattern = kind === 'eslint' ? LEVEL : NULL_RULE;
283
+ splitLines(text).forEach((line, index) => { for (const match of line.matchAll(pattern)) rulesOff.push({ rule: match[1], line: index + 1 }); });
284
+ return { lists, rulesOff, budgets: kind === 'eslint' ? budgetKeys(text, LINT_BUDGETS) : [] };
285
+ }
286
+
287
+ const STRICT = Object.freeze({
288
+ strict: true, noImplicitAny: true, strictNullChecks: true, strictFunctionTypes: true, strictBindCallApply: true, strictPropertyInitialization: true,
289
+ noImplicitThis: true, useUnknownInCatchVariables: true, alwaysStrict: true, noImplicitOverride: true, noFallthroughCasesInSwitch: true,
290
+ noImplicitReturns: true, noUncheckedIndexedAccess: true, exactOptionalPropertyTypes: true, noUnusedLocals: true, noUnusedParameters: true,
291
+ allowUnusedLabels: false, allowUnreachableCode: false, forceConsistentCasingInFileNames: true,
292
+ });
293
+ const RELAXING_ON = Object.freeze(['skipLibCheck', 'skipDefaultLibCheck', 'suppressImplicitAnyIndexErrors', 'suppressExcessPropertyErrors', 'noStrictGenericChecks', 'noImplicitUseStrict', 'noCheck']);
294
+ const isRelaxed = (key, value) => (Object.hasOwn(STRICT, key) && (value === 'true') !== STRICT[key]) || (RELAXING_ON.includes(key) && value === 'true');
295
+
296
+ function tsconfigRecords(text) {
297
+ const strictness = [];
298
+ const lists = [];
299
+ splitLines(text).forEach((raw, index) => {
300
+ for (const option of raw.matchAll(/"(\w+)"\s*:\s*(true|false)\b/g)) {
301
+ if (isRelaxed(option[1], option[2])) strictness.push({ key: option[1], value: option[2], line: index + 1 });
302
+ }
303
+ });
304
+ for (const match of text.matchAll(/"exclude"\s*:\s*\[([^\]]*)\]/g)) {
305
+ const line = lineAt(text, match.index);
306
+ for (const item of match[1].matchAll(/"([^"]+)"/g)) lists.push(record(LIST.paths, 'exclude', item[1], line));
307
+ }
308
+ return { lists, strictness };
309
+ }
310
+
311
+ function ignoreFileRecords(text) {
312
+ const lists = [];
313
+ splitLines(text).forEach((raw, index) => {
314
+ const line = raw.trim();
315
+ if (line && !line.startsWith('#') && !line.startsWith('!') && !PLACEHOLDER.test(line)) lists.push(record(LIST.paths, 'ignore', line, index + 1));
316
+ });
317
+ return { lists };
318
+ }
319
+
320
+ function stackRecords(text) {
321
+ const settings = [];
322
+ splitLines(text).forEach((raw, index) => {
323
+ const setting = /^\s*(requireCi)\s*:\s*(true|false)\b/.exec(raw);
324
+ if (setting) settings.push({ key: setting[1], value: setting[2], line: index + 1 });
325
+ });
326
+ return { settings };
327
+ }
328
+
329
+ const EXTRACT = {
330
+ codecov: (text) => codecovRecords(text),
331
+ sonar: (text) => sonarRecords(text),
332
+ jest: jestRecords,
333
+ eslint: (text, file) => lintRecords(text, file, 'eslint'),
334
+ stylelint: (text, file) => lintRecords(text, file, 'stylelint'),
335
+ tsconfig: (text) => tsconfigRecords(text),
336
+ ignorefile: (text) => ignoreFileRecords(text),
337
+ stack: (text) => stackRecords(text),
338
+ declaration: (text) => ({ budgets: budgetKeys(text, BUDGET_KEYS) }),
339
+ };
340
+
341
+ /** What the file of config `kind` states: { lists, numbers, settings, rulesOff, strictness, budgets, scope } (each possibly empty). */
342
+ export function configRecords(kind, text, file) {
343
+ return { lists: [], numbers: [], settings: [], rulesOff: [], strictness: [], budgets: [], scope: [], ...EXTRACT[kind](text, file) };
344
+ }
345
+
346
+ /** `item` as a path core: no quotes, no leading dot-slash, globstar or slash, no trailing slash or star segment, so a folder spelled three ways is one entry. */
347
+ export function coreOf(item) {
348
+ let text = String(item).trim();
349
+ if (text.startsWith('"') || text.startsWith("'")) text = text.slice(1);
350
+ if (text.endsWith('"') || text.endsWith("'")) text = text.slice(0, -1);
351
+ for (const lead of ['!', './']) if (text.startsWith(lead)) text = text.slice(lead.length);
352
+ while (text.startsWith('**/')) text = text.slice(3);
353
+ if (text.startsWith('/')) text = text.slice(1);
354
+ for (const tail of ['/**', '/*', '/']) {
355
+ while (text.endsWith(tail)) text = text.slice(0, -tail.length);
356
+ }
357
+ return text;
358
+ }
359
+
360
+ /** Whether the item was spelled as a path at any depth (`**` first), which a rooted allowance does not cover. */
361
+ export const isAnyDepth = (item) => String(item).trim().replace(/^["'!]+/, '').startsWith('**/');