@starci/hfs 4.7.32 → 4.7.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (473) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +13 -6
  3. package/emit/contracts.mjs +4 -3
  4. package/emit/form-bounds-reader.ts.partial +58 -0
  5. package/emit/form-bounds.mjs +155 -0
  6. package/emit/graphql-input-bounds.mjs +97 -0
  7. package/emit/input-bounds.mjs +44 -0
  8. package/emit/operation-schemas.mjs +137 -0
  9. package/emit/operations-worker.mjs +2 -2
  10. package/emit/operations.mjs +52 -16
  11. package/emit/schema-worker.mjs +17 -4
  12. package/emit/type-schema.mjs +17 -5
  13. package/package.json +9 -2
  14. package/release/gate.mjs +18 -23
  15. package/release/index.mjs +1 -1
  16. package/release/push.mjs +4 -5
  17. package/runtime/engine/admission.mjs +331 -0
  18. package/runtime/engine/canonical-json.mjs +14 -0
  19. package/runtime/engine/db/blob.mjs +380 -0
  20. package/runtime/engine/db/event-compact.mjs +73 -0
  21. package/runtime/engine/db/ledger-open.mjs +38 -0
  22. package/runtime/engine/db/ledger-paths.mjs +87 -0
  23. package/runtime/engine/db/ledger.mjs +1068 -0
  24. package/runtime/engine/db/machine-connection.mjs +150 -0
  25. package/runtime/engine/db/machine-open.mjs +60 -0
  26. package/runtime/engine/db/machine-schema.mjs +105 -0
  27. package/runtime/engine/db/machine.mjs +1317 -0
  28. package/runtime/engine/db/provider-reservations.mjs +127 -0
  29. package/runtime/engine/db/provision-authority.mjs +76 -0
  30. package/runtime/engine/db/provision-lock.mjs +29 -0
  31. package/runtime/engine/db/provision-operations.mjs +146 -0
  32. package/runtime/engine/db/ref-value.mjs +67 -0
  33. package/runtime/engine/db/schema/machine.sql +920 -0
  34. package/runtime/engine/db/schema/runtime.sql +1113 -0
  35. package/runtime/engine/refuse.mjs +11 -0
  36. package/runtime/knowledge/hfs/canon-pins.yaml +65 -9
  37. package/runtime/knowledge/hfs/design-truths.yaml +149 -0
  38. package/runtime/knowledge/hfs/fe-stack.yaml +23 -5
  39. package/runtime/knowledge/hfs/rules.yaml +269 -48
  40. package/runtime/knowledge/hfs/slots.yaml +113 -53
  41. package/runtime/knowledge/patterns/be/api.yaml +46 -20
  42. package/runtime/knowledge/patterns/be/event-bus.yaml +19 -12
  43. package/runtime/knowledge/patterns/be/idempotency.yaml +265 -0
  44. package/runtime/knowledge/patterns/be/jobs.yaml +17 -12
  45. package/runtime/knowledge/patterns/be/projections.yaml +2 -2
  46. package/runtime/knowledge/patterns/be/queues.yaml +57 -22
  47. package/runtime/knowledge/patterns/be/reactors.yaml +1 -1
  48. package/runtime/knowledge/patterns/be/realtime.yaml +326 -27
  49. package/runtime/knowledge/patterns/be/saga.yaml +2 -2
  50. package/runtime/knowledge/sonar-gate.yaml +1 -1
  51. package/runtime/modules/kernel/command-policy.yaml +249 -0
  52. package/runtime/modules/kernel/failure-codes.yaml +211 -52
  53. package/runtime/modules/kernel/hook-input.yaml +6 -0
  54. package/runtime/modules/kernel/protected-zone.yaml +81 -0
  55. package/runtime/modules/kernel/removed-vocabulary.yaml +62 -0
  56. package/runtime/modules/kernel/roles.yaml +405 -0
  57. package/runtime/modules/models/registry.yaml +3 -1
  58. package/runtime/modules/models/runtimes.yaml +2 -0
  59. package/runtime/modules/schemas/application-stacks.schema.yaml +10 -2
  60. package/runtime/packages/cli/src/catalog.generated.mjs +46 -59
  61. package/runtime/packages/grammar/scripts/build-stamp.mjs +98 -0
  62. package/runtime/scripts/agent/codex-app-server.mjs +79 -0
  63. package/runtime/scripts/agent/codex-mirror-source.mjs +34 -0
  64. package/runtime/scripts/agent/host-agents.mjs +80 -0
  65. package/runtime/scripts/agent/launch-trust-policy.mjs +53 -0
  66. package/runtime/scripts/agent/trust-launch.mjs +191 -0
  67. package/runtime/scripts/agent/trust.mjs +523 -0
  68. package/runtime/scripts/api/fs/rename-over.mjs +21 -0
  69. package/runtime/scripts/api/fs/temp-path.mjs +8 -0
  70. package/runtime/scripts/api/fs/write-json-file.mjs +10 -0
  71. package/runtime/scripts/api/git/diff-names.mjs +8 -0
  72. package/runtime/scripts/api/git/head-time.mjs +10 -0
  73. package/runtime/scripts/api/git/index-lock.mjs +78 -0
  74. package/runtime/scripts/api/git/is-ancestor.mjs +5 -0
  75. package/runtime/scripts/api/git/log.mjs +5 -0
  76. package/runtime/scripts/api/git/merge-base-query.mjs +5 -0
  77. package/runtime/scripts/api/git/merge-tree.mjs +5 -0
  78. package/runtime/scripts/api/git/rev-list.mjs +5 -0
  79. package/runtime/scripts/api/git/rev-parse.mjs +13 -0
  80. package/runtime/scripts/api/git/status-query.mjs +5 -0
  81. package/runtime/scripts/api/git/worktree-list-query.mjs +5 -0
  82. package/runtime/scripts/api/node/spawn-node.mjs +6 -0
  83. package/runtime/scripts/api/npm/dist-tag.mjs +8 -0
  84. package/runtime/scripts/api/npm/lib.mjs +32 -0
  85. package/runtime/scripts/api/npm/pack-dry-run.mjs +9 -0
  86. package/runtime/scripts/api/npm/run-npm.mjs +6 -0
  87. package/runtime/scripts/api/npm/view.mjs +30 -0
  88. package/runtime/scripts/api/npm/whoami.mjs +8 -0
  89. package/runtime/scripts/api/process/hide-child-windows.mjs +31 -0
  90. package/runtime/scripts/api/process/kill-tree.mjs +22 -0
  91. package/runtime/scripts/api/process/lib.mjs +125 -0
  92. package/runtime/scripts/api/process/probe-guard-command.mjs +74 -0
  93. package/runtime/scripts/api/process/process-list.mjs +28 -0
  94. package/runtime/scripts/api/process/system-tool.mjs +37 -0
  95. package/runtime/scripts/gates/canon-digest.mjs +81 -0
  96. package/runtime/scripts/gates/canon-pins.mjs +16 -0
  97. package/runtime/scripts/gates/commit-proof.mjs +53 -0
  98. package/runtime/scripts/gates/gate-input.mjs +98 -0
  99. package/runtime/scripts/gates/gate.mjs +669 -0
  100. package/runtime/scripts/gates/grammar-dist.mjs +206 -0
  101. package/runtime/scripts/gates/runtime-package-selection.mjs +27 -0
  102. package/runtime/scripts/gates/test-world-run.mjs +254 -0
  103. package/runtime/scripts/gates/type-impact.mjs +277 -0
  104. package/runtime/scripts/gates/type-program.mjs +113 -0
  105. package/runtime/scripts/guards/assigned-command.mjs +12 -0
  106. package/runtime/scripts/guards/call-rights.mjs +47 -0
  107. package/runtime/scripts/guards/command-guard.mjs +468 -0
  108. package/runtime/scripts/guards/command-policy.mjs +342 -0
  109. package/runtime/scripts/guards/critic-reach.mjs +82 -0
  110. package/runtime/scripts/guards/deps-guard.mjs +150 -0
  111. package/runtime/scripts/guards/env-dump-verdict.mjs +89 -0
  112. package/runtime/scripts/guards/git-pathspec.mjs +225 -0
  113. package/runtime/scripts/guards/git-policy.mjs +355 -0
  114. package/runtime/scripts/guards/guards-root.mjs +27 -0
  115. package/runtime/scripts/guards/hook-install.mjs +343 -0
  116. package/runtime/scripts/guards/hook-io.mjs +76 -0
  117. package/runtime/scripts/guards/hook-verdict.mjs +12 -0
  118. package/runtime/scripts/guards/install-verdict.mjs +41 -0
  119. package/runtime/scripts/guards/kernel-seat.mjs +63 -0
  120. package/runtime/scripts/guards/launch-verdict.mjs +74 -0
  121. package/runtime/scripts/guards/op-starci.mjs +16 -0
  122. package/runtime/scripts/guards/op-write-scope.mjs +27 -0
  123. package/runtime/scripts/guards/orca-self-lifecycle.mjs +39 -0
  124. package/runtime/scripts/guards/process-kill-verdict.mjs +106 -0
  125. package/runtime/scripts/guards/protected-zone.mjs +56 -0
  126. package/runtime/scripts/guards/refusals.mjs +27 -0
  127. package/runtime/scripts/guards/release-definition.mjs +185 -0
  128. package/runtime/scripts/guards/release-push-gate.mjs +72 -0
  129. package/runtime/scripts/guards/release-record.mjs +58 -0
  130. package/runtime/scripts/guards/release-suite-mode.mjs +15 -0
  131. package/runtime/scripts/guards/rights.mjs +268 -0
  132. package/runtime/scripts/guards/seat-tools.mjs +47 -0
  133. package/runtime/scripts/guards/shell-commands.mjs +328 -0
  134. package/runtime/scripts/guards/supervisor-seat.mjs +80 -0
  135. package/runtime/scripts/guards/verdict-process.mjs +81 -0
  136. package/runtime/scripts/hfs/architecture/dead-exports.mjs +10 -10
  137. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +1 -1
  138. package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +53 -0
  139. package/runtime/scripts/hfs/architecture/reachability.mjs +1 -1
  140. package/runtime/scripts/hfs/architecture/sql-owner.mjs +17 -6
  141. package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +105 -0
  142. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +38 -52
  143. package/runtime/scripts/hfs/architecture/supabase-keys.mjs +261 -0
  144. package/runtime/scripts/hfs/architecture/transport-owner.mjs +49 -3
  145. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -1
  146. package/runtime/scripts/hfs/check.mjs +6 -4
  147. package/runtime/scripts/hfs/coverage-scope.mjs +28 -1
  148. package/runtime/scripts/hfs/db-restamp-verb.mjs +56 -16
  149. package/runtime/scripts/hfs/declaration-shape.mjs +4 -4
  150. package/runtime/scripts/hfs/declaration-slots.mjs +14 -3
  151. package/runtime/scripts/hfs/manifest-shape.mjs +10 -6
  152. package/runtime/scripts/hfs/rule-params-shape.mjs +18 -6
  153. package/runtime/scripts/hfs/rules/a15-findings.mjs +4 -1
  154. package/runtime/scripts/hfs/rules/bypass-markers.mjs +22 -7
  155. package/runtime/scripts/hfs/rules/database-migrations.mjs +69 -14
  156. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +9 -7
  157. package/runtime/scripts/hfs/rules/database-queues.mjs +68 -18
  158. package/runtime/scripts/hfs/rules/edition.mjs +56 -12
  159. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -10
  160. package/runtime/scripts/hfs/rules/fe-stack.mjs +12 -7
  161. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +1 -1
  162. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +75 -9
  163. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +1 -1
  164. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +15 -4
  165. package/runtime/scripts/hfs/rules/text-integrity.mjs +73 -0
  166. package/runtime/scripts/hfs/runtime-rules/hook-shape.mjs +74 -0
  167. package/runtime/scripts/hfs/runtime-rules/release-notes.mjs +44 -0
  168. package/runtime/scripts/hfs/sealed-members.mjs +3 -3
  169. package/runtime/scripts/hfs/slot-side-problems.mjs +3 -3
  170. package/runtime/scripts/hfs/slots.mjs +2 -1
  171. package/runtime/scripts/hfs/stack-deploy-config.mjs +22 -5
  172. package/runtime/scripts/hfs/stacks-gate.mjs +28 -7
  173. package/runtime/scripts/hfs/view.mjs +2 -1
  174. package/runtime/scripts/lib/app-data.mjs +11 -0
  175. package/runtime/scripts/lib/bypass-markers.mjs +272 -75
  176. package/runtime/scripts/lib/check-format.mjs +7 -0
  177. package/runtime/scripts/lib/cli-arg.mjs +120 -0
  178. package/runtime/scripts/lib/css-rules.mjs +220 -0
  179. package/runtime/scripts/lib/dom-tree.mjs +4 -0
  180. package/runtime/scripts/lib/escape.mjs +16 -0
  181. package/runtime/scripts/lib/event-items.mjs +22 -0
  182. package/runtime/scripts/lib/failure-code-template-errors.mjs +161 -0
  183. package/runtime/scripts/lib/git-dir.mjs +120 -0
  184. package/runtime/scripts/lib/grammar-vendor-parity.mjs +377 -0
  185. package/runtime/scripts/lib/guard-command.mjs +24 -0
  186. package/runtime/scripts/lib/hook-limits.mjs +5 -0
  187. package/runtime/scripts/lib/interrupt-cleanup.mjs +38 -0
  188. package/runtime/scripts/lib/js-lex.mjs +200 -0
  189. package/runtime/scripts/lib/json-formats.mjs +0 -1
  190. package/runtime/scripts/lib/language.mjs +94 -1
  191. package/runtime/scripts/lib/loosening-budgets.mjs +44 -0
  192. package/runtime/scripts/lib/loosening-config.mjs +361 -0
  193. package/runtime/scripts/lib/loosening-state.mjs +223 -0
  194. package/runtime/scripts/lib/menu-parts.mjs +11 -0
  195. package/runtime/scripts/lib/normalize.mjs +25 -0
  196. package/runtime/scripts/lib/op-gate.mjs +15 -0
  197. package/runtime/scripts/lib/redact.mjs +208 -0
  198. package/runtime/scripts/lib/runtime-location.mjs +21 -0
  199. package/runtime/scripts/lib/runtime-package-inventory.mjs +21 -0
  200. package/runtime/scripts/lib/runtime-package-manifests.mjs +84 -0
  201. package/runtime/scripts/lib/runtime-publication-tags.mjs +99 -0
  202. package/runtime/scripts/lib/seat-env.mjs +43 -0
  203. package/runtime/scripts/lib/secret-patterns.mjs +58 -9
  204. package/runtime/scripts/lib/shell-source.mjs +4 -0
  205. package/runtime/scripts/lib/source-phrases.mjs +34 -0
  206. package/runtime/scripts/lib/sqlite.mjs +21 -0
  207. package/runtime/scripts/lib/swarm-stack.mjs +2 -1
  208. package/runtime/scripts/lib/tar-files.mjs +39 -0
  209. package/runtime/scripts/lib/ts-analysis.mjs +10 -0
  210. package/runtime/scripts/lib/worktree-exclude.mjs +35 -0
  211. package/runtime/scripts/lib/worktree-record.mjs +14 -0
  212. package/runtime/scripts/machine/artifact-hold.mjs +52 -0
  213. package/runtime/scripts/machine/ask-receipts.mjs +113 -0
  214. package/runtime/scripts/machine/evidence-store.mjs +223 -0
  215. package/runtime/scripts/machine/home.mjs +216 -0
  216. package/runtime/scripts/machine/host-lock.mjs +184 -0
  217. package/runtime/scripts/machine/lock-recovery.mjs +55 -0
  218. package/runtime/scripts/machine/roles-contract.mjs +90 -0
  219. package/runtime/scripts/machine/runtime-change.mjs +20 -0
  220. package/runtime/scripts/machine/workflow-tree.mjs +122 -0
  221. package/runtime/scripts/reconciler/checklist-items.mjs +7 -0
  222. package/runtime/scripts/reconciler/guard-row.mjs +25 -0
  223. package/runtime/scripts/work/asset-slot.mjs +284 -0
  224. package/runtime/scripts/work/brand/brand-colour.mjs +125 -0
  225. package/runtime/scripts/work/brand/brand-tokens.mjs +167 -0
  226. package/runtime/scripts/work/brand/brand-value-source.mjs +106 -0
  227. package/runtime/scripts/work/brand/brand.mjs +892 -0
  228. package/runtime/scripts/work/draw/draw-dna-anatomy.mjs +60 -0
  229. package/runtime/scripts/work/draw/draw-dna-findings.mjs +256 -0
  230. package/runtime/scripts/work/draw/draw-dna-selector.mjs +38 -0
  231. package/runtime/scripts/work/draw/draw-dna.mjs +483 -0
  232. package/runtime/scripts/work/draw/draw-layer.mjs +305 -0
  233. package/runtime/scripts/work/draw/draw-rationale-page.mjs +237 -0
  234. package/runtime/scripts/work/draw/draw-rationale-ref.mjs +14 -0
  235. package/runtime/scripts/work/draw/draw-rationale.mjs +353 -0
  236. package/runtime/scripts/work/draw/draw-source.mjs +412 -0
  237. package/runtime/scripts/work/draw/draw-taste.mjs +278 -0
  238. package/runtime/scripts/work/draw/draw-typecheck.mjs +72 -0
  239. package/runtime/scripts/work/draw/rationale-findings.mjs +205 -0
  240. package/runtime/scripts/work/draw-grammar.mjs +175 -0
  241. package/runtime/scripts/work/draw-render-page.mjs +188 -0
  242. package/runtime/scripts/work/draw-render.mjs +626 -0
  243. package/runtime/scripts/work/grammar-proposal.mjs +279 -0
  244. package/runtime/scripts/work/png.mjs +237 -0
  245. package/runtime/scripts/work/render-tools.mjs +64 -0
  246. package/runtime/scripts/work/work-io.mjs +119 -0
  247. package/scaffold/add-inbox-lite.mjs +2 -2
  248. package/scaffold/add-plan.mjs +40 -13
  249. package/scaffold/add-queue-lite.mjs +372 -0
  250. package/scaffold/add.mjs +5 -2
  251. package/scaffold/app.mjs +1 -2
  252. package/scaffold/edition-gate.mjs +11 -7
  253. package/scaffold/lite-exports.mjs +10 -0
  254. package/scaffold/pattern-file-slots.mjs +33 -0
  255. package/scaffold/test-world.mjs +1 -1
  256. package/scaffold/yaml-block-edit.mjs +117 -0
  257. package/src/main.mjs +5 -4
  258. package/stack/deploy.mjs +7 -3
  259. package/stack/post-deploy.mjs +48 -0
  260. package/sync/index.mjs +10 -0
  261. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +1 -1
  262. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +1 -1
  263. package/templates/app/codegen-graphql.mjs.partial +59 -12
  264. package/templates/app/hooks/husky/pre-commit +2 -2
  265. package/templates/app/hooks/husky/pre-push +10 -3
  266. package/templates/app/package-scripts/redis-dev.json +3 -0
  267. package/templates/app/package-scripts-lite/package.json +1 -0
  268. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +36 -8
  269. package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
  270. package/templates/app/skeleton/.starcistacks/production/README.md +10 -9
  271. package/templates/app/skeleton/.starcistacks/production/infra/swarm/postgres-init.sh +21 -0
  272. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +1 -1
  273. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +1 -0
  274. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +47 -5
  275. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +5 -3
  276. package/templates/app/skeleton/README.md +2 -0
  277. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +5 -0
  278. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +2 -2
  279. package/templates/app/skeleton-lite/.starcistacks/production/README.md +1 -1
  280. package/templates/app/skeleton-lite/README.md +1 -1
  281. package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +18 -0
  282. package/templates/be/inbox/platform/event-bus/errors/event-bus.error.ts.tpl +16 -0
  283. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +10 -0
  284. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +1 -1
  285. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +15 -3
  286. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +11 -3
  287. package/templates/be/inbox/platform/event-bus/index.ts.tpl +2 -0
  288. package/templates/be/inbox/platform/event-bus/messages/event-bus.messages.ts.tpl +7 -0
  289. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +2 -2
  290. package/templates/be/inbox/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
  291. package/templates/be/inbox/platform/idempotency/index.ts.tpl +2 -0
  292. package/templates/be/inbox/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
  293. package/templates/be/inbox/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
  294. package/templates/be/inbox/platform/inbox/client-operation.policy.ts.tpl +17 -0
  295. package/templates/be/inbox/platform/inbox/errors/inbox.error.ts.tpl +22 -0
  296. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +21 -0
  297. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +334 -11
  298. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +55 -4
  299. package/templates/be/inbox/platform/inbox/index.ts.tpl +2 -0
  300. package/templates/be/inbox/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
  301. package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
  302. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +26 -0
  303. package/templates/be/inbox/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
  304. package/templates/be/inbox/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
  305. package/templates/be/inbox/platform/migration.sql.tpl +1 -0
  306. package/templates/be/inbox/service.spec.ts.tpl +4 -4
  307. package/templates/be/inbox/service.ts.tpl +3 -1
  308. package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -2
  309. package/templates/be/inbox/webhook-door.ts.tpl +3 -2
  310. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +3 -0
  311. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +10 -0
  312. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +2 -2
  313. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +16 -3
  314. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +11 -3
  315. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +139 -50
  316. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +25 -2
  317. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +139 -78
  318. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +10 -6
  319. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +262 -159
  320. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +32 -5
  321. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +2 -0
  322. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +5 -1
  323. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +2 -0
  324. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -4
  325. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +1 -0
  326. package/templates/be/patterns/idempotency/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
  327. package/templates/be/patterns/idempotency/platform/idempotency/index.ts.tpl +2 -0
  328. package/templates/be/patterns/idempotency/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
  329. package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
  330. package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.ts.tpl +17 -0
  331. package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.spec.ts.tpl +66 -0
  332. package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.ts.tpl +24 -0
  333. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.spec.ts.tpl +60 -0
  334. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.ts.tpl +12 -0
  335. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.spec.ts.tpl +49 -0
  336. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.ts.tpl +12 -0
  337. package/templates/be/patterns/idempotency/platform/inbox/effectful.decorators.ts.tpl +7 -0
  338. package/templates/be/patterns/idempotency/platform/inbox/errors/inbox.error.ts.tpl +22 -0
  339. package/templates/be/patterns/idempotency/platform/inbox/inbox.decorators.ts.tpl +17 -0
  340. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +7 -0
  341. package/templates/be/patterns/idempotency/platform/inbox/inbox.module.ts.tpl +28 -0
  342. package/templates/be/patterns/idempotency/platform/inbox/inbox.options.ts.tpl +7 -0
  343. package/templates/be/patterns/idempotency/platform/inbox/inbox.port.ts.tpl +44 -0
  344. package/templates/be/patterns/idempotency/platform/inbox/inbox.service.spec.ts.tpl +388 -0
  345. package/templates/be/patterns/idempotency/platform/inbox/inbox.service.ts.tpl +77 -0
  346. package/templates/be/patterns/idempotency/platform/inbox/index.ts.tpl +10 -0
  347. package/templates/be/patterns/idempotency/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
  348. package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +9 -0
  349. package/templates/be/patterns/idempotency/platform/inbox/persistence/entities/inbox-claim.entity.ts.tpl +25 -0
  350. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
  351. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +39 -0
  352. package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1789800005000-create-inbox-claims.ts.tpl +21 -0
  353. package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1792800000000-client-operation-outcomes.ts.tpl +21 -0
  354. package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
  355. package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
  356. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +1 -0
  357. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +4 -3
  358. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +2 -0
  359. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +20 -2
  360. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +1 -0
  361. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +4 -0
  362. package/templates/be/patterns/jobs/platform-lite/jobs.index.ts.tpl +10 -0
  363. package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +44 -0
  364. package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +33 -0
  365. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +203 -18
  366. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +78 -22
  367. package/templates/be/patterns/queues/platform/errors/queue.error.ts.tpl +19 -0
  368. package/templates/be/patterns/queues/platform/messages/queue.messages.ts.tpl +13 -0
  369. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +1 -0
  370. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +4 -0
  371. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +2 -0
  372. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -4
  373. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +14 -2
  374. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +5 -2
  375. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +72 -74
  376. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +1 -1
  377. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +23 -8
  378. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +29 -8
  379. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +7 -3
  380. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +5 -0
  381. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +2 -0
  382. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +4 -0
  383. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +2 -1
  384. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +1 -1
  385. package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.spec.ts.tpl +417 -0
  386. package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.ts.tpl +172 -0
  387. package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +12 -0
  388. package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +32 -0
  389. package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +148 -0
  390. package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +51 -0
  391. package/templates/be/patterns/queues/platform-lite/queue-transport.port.ts.tpl +163 -0
  392. package/templates/be/patterns/queues/platform-lite/queue.config.ts.tpl +17 -0
  393. package/templates/be/patterns/queues/platform-lite/queue.error.ts.tpl +25 -0
  394. package/templates/be/patterns/queues/platform-lite/queue.index.ts.tpl +8 -0
  395. package/templates/be/patterns/queues/platform-lite/queue.messages.ts.tpl +21 -0
  396. package/templates/be/patterns/queues/platform-lite/queue.module.ts.tpl +74 -0
  397. package/templates/be/patterns/queues/platform-lite/queue.options.ts.tpl +38 -0
  398. package/templates/be/patterns/queues/queue.queue.ts.tpl +2 -2
  399. package/templates/be/patterns/reactors/application.contracts.ts.tpl +2 -0
  400. package/templates/be/patterns/reactors/consumer.ts.tpl +5 -1
  401. package/templates/be/platform/graphql/graphql.contracts.ts +2 -1
  402. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +1 -1
  403. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +6 -1
  404. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -4
  405. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +4 -0
  406. package/templates/be/skeleton/apps/cli/src/main.ts +7 -4
  407. package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -1
  408. package/templates/be/skeleton/src/modules/platform/http/errors/http.error.ts +22 -0
  409. package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.spec.ts +235 -0
  410. package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.ts +85 -0
  411. package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.spec.ts +92 -0
  412. package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.ts +52 -0
  413. package/templates/be/skeleton/src/modules/platform/http/http.decorators.ts +9 -0
  414. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +7 -0
  415. package/templates/be/skeleton/src/modules/platform/http/http.module.ts +19 -0
  416. package/templates/be/skeleton/src/modules/platform/http/http.options.ts +2 -0
  417. package/templates/be/skeleton/src/modules/platform/http/http.port.ts +40 -0
  418. package/templates/be/skeleton/src/modules/platform/http/index.ts +7 -0
  419. package/templates/be/skeleton/src/modules/platform/http/messages/http.messages.ts +15 -0
  420. package/templates/be/skeleton/src/modules/platform/ids/ids.decorators.ts +9 -0
  421. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +7 -0
  422. package/templates/be/skeleton/src/modules/platform/ids/ids.module.ts +19 -0
  423. package/templates/be/skeleton/src/modules/platform/ids/ids.options.ts +2 -0
  424. package/templates/be/skeleton/src/modules/platform/ids/ids.port.ts +5 -0
  425. package/templates/be/skeleton/src/modules/platform/ids/index.ts +4 -0
  426. package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.spec.ts +15 -0
  427. package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.ts +10 -0
  428. package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.spec.ts +59 -0
  429. package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.ts +21 -0
  430. package/templates/be/skeleton/src/modules/platform/logging/correlation.contracts.ts +16 -0
  431. package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.spec.ts +150 -0
  432. package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.ts +42 -0
  433. package/templates/be/skeleton/src/modules/platform/logging/correlation.port.ts +15 -0
  434. package/templates/be/skeleton/src/modules/platform/logging/correlation.service.spec.ts +23 -0
  435. package/templates/be/skeleton/src/modules/platform/logging/correlation.service.ts +28 -0
  436. package/templates/be/skeleton/src/modules/platform/logging/index.ts +14 -1
  437. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +39 -6
  438. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +15 -3
  439. package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +17 -2
  440. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +13 -1
  441. package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +13 -3
  442. package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.spec.ts +69 -0
  443. package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.ts +48 -0
  444. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  445. package/templates/be/skeleton/src/modules/platform/primitives/record.policy.spec.ts +12 -0
  446. package/templates/be/skeleton/src/modules/platform/primitives/record.policy.ts +3 -0
  447. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +6 -1
  448. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +4 -0
  449. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  450. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +14 -1
  451. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +12 -0
  452. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  453. package/templates/be/test-world/world/test-apps.options.ts.tpl +2 -0
  454. package/templates/be/test-world/world/test-world.config.ts.tpl +1 -0
  455. package/upgrade/check.mjs +12 -135
  456. package/upgrade/file-state.mjs +1 -16
  457. package/upgrade/index.mjs +34 -361
  458. package/runtime/scripts/lib/check-code-loosening.mjs +0 -128
  459. package/runtime/scripts/lib/gate-loosening.mjs +0 -292
  460. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +0 -89
  461. package/templates/app/upgrade-full/.starcistacks/dev/README.md +0 -16
  462. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +0 -13
  463. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +0 -3
  464. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +0 -12
  465. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +0 -7
  466. package/templates/be/skeleton-lite/apps/api/src/main.ts +0 -26
  467. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +0 -12
  468. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +0 -7
  469. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +0 -5
  470. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +0 -9
  471. package/templates/be/upgrade-full/apps/cli/src/main.ts +0 -29
  472. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +0 -7
  473. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +0 -23
@@ -0,0 +1,127 @@
1
+ // Provider receipt SQL participates in the machine writer's existing fenced transactions.
2
+
3
+ // The host restarted after this receipt's last update, so no launch of that update can still run: the proof is bound to the receipt's own
4
+ // update instant (a later progress observation refuses it) and, when the receipt names a host request, to that request's absence in the current runtime.
5
+ const hostRestartedEnd = (row, proof) => proof?.kind === 'host-restarted' && proof.confirmed === true && !row.handle && !row.pid
6
+ && proof.receiptUpdatedAt === row.updated_at && Number.isFinite(proof.restartedAt) && proof.restartedAt > row.updated_at
7
+ && (!row.host_request_id || (proof.hostRequestId === row.host_request_id && proof.requestState === 'absent'));
8
+
9
+ /** The Dispatch states in which the worker has ended (Orca's own terminal states of a Dispatch). */
10
+ export const ENDED_DISPATCH_STATES = Object.freeze(['failed', 'completed', 'stopped', 'released', 'cancelled']);
11
+
12
+ // The host request of this receipt resolved to a Dispatch Orca reports ended, whose terminal (when it has one) is gone or disconnected and carries no process.
13
+ const dispatchEndedEnd = (row, proof) => proof?.kind === 'dispatch-ended' && proof.confirmed === true && !row.handle && !row.pid
14
+ && Boolean(row.host_request_id) && proof.hostRequestId === row.host_request_id
15
+ && typeof proof.dispatchId === 'string' && proof.dispatchId.length > 0 && ENDED_DISPATCH_STATES.includes(proof.dispatchStatus)
16
+ && proof.processVerdict === 'none' && (!proof.handle || ['gone', 'disconnected'].includes(proof.terminalProof));
17
+
18
+ export function providerReservationMethods({ need, parse, toJson, hex }) {
19
+ // Provider/account slots are shared by all five agent roles. A clock timeout is not exit evidence.
20
+ const providerReservationRow = (row) => row ? { id: row.id, fence: row.fence, attemptId: row.attempt_id,
21
+ provider: row.provider, account: row.account, model: row.model, role: row.role, state: row.state,
22
+ slots: row.slots, amountUnit: 'concurrent-launch', maxParallel: row.max_parallel,
23
+ handle: row.handle, pid: row.pid, launchIdentity: row.launch_identity, hostRequestId: row.host_request_id, createdAt: row.created_at, updatedAt: row.updated_at,
24
+ scope: parse(row.scope_json), estimate: parse(row.estimate_json), quota: parse(row.quota_json),
25
+ override: parse(row.override_json), releasedAt: row.released_at, proof: parse(row.proof_json) } : null;
26
+ // `tier` keeps the rows whose scope names that tier; `limit` returns the newest rows first (the tier's recent picks).
27
+ function providerReservations(m, { provider = null, account = null, activeOnly = false, tier = null, limit = null } = {}) {
28
+ const where = [], args = [];
29
+ if (tier !== null) { where.push("json_extract(scope_json,'$.tier')=?"); args.push(tier); }
30
+ if (provider !== null) { where.push('provider=?'); args.push(provider); }
31
+ if (account !== null) { where.push('account=?'); args.push(account); }
32
+ if (activeOnly) where.push("state<>'released'");
33
+ const clause = where.length ? ` WHERE ${where.join(' AND ')}` : '';
34
+ const order = limit === null ? 'ORDER BY fence' : `ORDER BY fence DESC LIMIT ${Math.trunc(Number(limit)) || 0}`;
35
+ return m.db.prepare(`SELECT * FROM provider_reservations${clause} ${order}`).all(...args).map(providerReservationRow);
36
+ }
37
+ function providerReservationUsage(m, { provider, account = 'default' } = {}) {
38
+ const reservations = providerReservations(m, { provider, account, activeOnly: true });
39
+ return { running: reservations.length, reservations, observed: true };
40
+ }
41
+ /** Atomic last-slot admission. Replays preserve the original receipt; changed attempt meaning refuses. */
42
+ function reserveProvider(m, { provider, account = 'default', attemptId, role, model, maxParallel, scope = null, quota = null, estimate = null, override = null } = {}) {
43
+ need([provider, account, attemptId, model].every((value) => typeof value === 'string' && value.trim()), 'provider reservation needs provider, account, attemptId and concrete model');
44
+ need(typeof role === 'string' && role.trim(), 'provider reservation needs one agent role'); // The DDL owns the allowed role domain.
45
+ need(Number.isInteger(maxParallel) && maxParallel >= 0, 'provider reservation needs nonnegative integer maxParallel');
46
+ return m.transaction((db) => {
47
+ const existing = db.prepare('SELECT * FROM provider_reservations WHERE attempt_id=?').get(attemptId);
48
+ if (existing) {
49
+ if (existing.provider !== provider || existing.account !== account || existing.model !== model || existing.role !== role || existing.scope_json !== toJson(scope))
50
+ return { ok: false, reason: 'attempt-conflict', reservation: providerReservationRow(existing) };
51
+ return { ok: existing.state !== 'released', reason: existing.state === 'released' ? 'attempt-released' : 'reserved', reused: true, reservation: providerReservationRow(existing) };
52
+ }
53
+ const running = Number(db.prepare("SELECT count(*) n FROM provider_reservations WHERE provider=? AND account=? AND state<>'released'").get(provider, account).n);
54
+ if (running >= maxParallel) return { ok: false, reason: 'capacity', running, maxParallel };
55
+ const id = hex(16), at = m.now();
56
+ const result = db.prepare("INSERT INTO provider_reservations(id,attempt_id,provider,account,model,role,scope_json,state,max_parallel,quota_json,estimate_json,override_json,created_at,updated_at) VALUES(?,?,?,?,?,?,?,'reserved',?,?,?,?,?,?)")
57
+ .run(id, attemptId, provider, account, model, role, toJson(scope), maxParallel, toJson(quota), toJson(estimate), toJson(override), at, at);
58
+ db.prepare("INSERT INTO provider_reservation_events(reservation_id,at,from_state,to_state) VALUES(?,?,NULL,'reserved')").run(id, at);
59
+ return { ok: true, reason: 'reserved', reused: false, reservation: providerReservationRow(db.prepare('SELECT * FROM provider_reservations WHERE fence=?').get(result.lastInsertRowid)) };
60
+ });
61
+ }
62
+ const fencedProviderRow = (db, { id, fence, attemptId } = {}) => {
63
+ const row = db.prepare('SELECT * FROM provider_reservations WHERE id=? AND fence=?').get(id ?? '', fence ?? -1);
64
+ return row && (attemptId === undefined || row.attempt_id === attemptId) ? row : null;
65
+ };
66
+ /** Observe launch progress under its original fence; unknown still occupies its slot. */
67
+ function markProviderReservation(m, { id, fence, attemptId, provider, account, model, role, state, launchIdentity = null, hostRequestId = null, handle = null, pid = null } = {}) {
68
+ need(['launching', 'live', 'unknown'].includes(state), 'provider reservation observation needs launching, live or unknown');
69
+ return m.transaction((db) => {
70
+ const row = fencedProviderRow(db, { id, fence, attemptId });
71
+ if (!row) return { ok: false, reason: 'fenced' };
72
+ if (Object.entries({ provider, account, model, role }).some(([key, value]) => value !== undefined && row[key] !== value)) return { ok: false, reason: 'identity-conflict' };
73
+ if (row.state === 'released') return { ok: false, reason: 'released' };
74
+ if (state === 'launching' && (typeof launchIdentity !== 'string' || !launchIdentity.trim())) return { ok: false, reason: 'launch-identity-required' };
75
+ if (launchIdentity !== null && row.launch_identity && row.launch_identity !== launchIdentity) return { ok: false, reason: 'launch-identity-conflict', reservation: providerReservationRow(row) };
76
+ if (hostRequestId !== null && row.host_request_id && row.host_request_id !== hostRequestId) return { ok: false, reason: 'host-request-conflict', reservation: providerReservationRow(row) };
77
+ if (state === 'launching' && row.state !== 'reserved' && row.state !== 'launching') return { ok: false, reason: 'state-regression' };
78
+ if ((row.handle && handle && row.handle !== handle) || (row.pid && pid && row.pid !== pid)) return { ok: false, reason: 'identity-conflict' };
79
+ const at = m.now();
80
+ db.prepare('UPDATE provider_reservations SET state=?,launch_identity=coalesce(launch_identity,?),host_request_id=coalesce(host_request_id,?),handle=coalesce(handle,?),pid=coalesce(pid,?),updated_at=? WHERE id=? AND fence=?')
81
+ .run(state, launchIdentity, hostRequestId, handle, pid, at, id, fence);
82
+ if (row.state !== state) db.prepare('INSERT INTO provider_reservation_events(reservation_id,at,from_state,to_state) VALUES(?,?,?,?)').run(id, at, row.state, state);
83
+ return { ok: true, reservation: providerReservationRow(db.prepare('SELECT * FROM provider_reservations WHERE id=?').get(id)) };
84
+ });
85
+ }
86
+ /** Only the launcher/reconciler's definitive matching exit/no-effect receipt releases capacity. */
87
+ function releaseProviderReservation(m, { id, fence, attemptId, proof } = {}) {
88
+ return m.transaction((db) => {
89
+ const row = fencedProviderRow(db, { id, fence, attemptId });
90
+ if (!row) return { ok: false, reason: 'fenced' };
91
+ if (row.state === 'released') return { ok: true, reused: true, reservation: providerReservationRow(row) };
92
+ const affirmation = proof?.hostAffirmation;
93
+ const reply = affirmation?.receipt, result = reply?.result;
94
+ const residual = result?.residualResources ?? result?.worker?.residualResources;
95
+ const identities = [result, result?.worker, result?.terminal].filter((value) => value && typeof value === 'object');
96
+ const hostNoEffect = reply && typeof reply === 'object' && !['unknown', 'outcome_unknown'].includes(result?.state)
97
+ && !['unknown', 'outcome_unknown'].includes(result?.worker?.state)
98
+ && !(residual && typeof residual === 'object' && Object.keys(residual).length > 0)
99
+ && !identities.some((value) => ['agentTerminalHandle', 'terminalHandle', 'handle', 'pid', 'processId', 'dispatchId'].some((key) => value[key] !== undefined && value[key] !== null && value[key] !== ''))
100
+ && (result?.failedStage !== undefined || result?.stage !== undefined || typeof reply.error?.code === 'string' && reply.error.code.trim());
101
+ const resolvedUnknown = row.state === 'unknown' && row.launch_identity && proof?.launchIdentity === row.launch_identity
102
+ && row.host_request_id && affirmation?.requestId === row.host_request_id && affirmation.kind === 'worker-start-replay'
103
+ && affirmation.replayed === true && affirmation.outcome === 'failed' && affirmation.effectState === 'none'
104
+ && hostNoEffect;
105
+ const noEffect = proof?.kind === 'failed-before-launch' && proof.confirmed === true && !row.handle && !row.pid
106
+ && (['reserved', 'launching'].includes(row.state) || resolvedUnknown);
107
+ const terminalEnded = row.handle && proof?.kind === 'closed' && proof.handle === row.handle
108
+ && ['gone', 'disconnected'].includes(proof.terminalProof) && ['none', 'stopped'].includes(proof.processVerdict);
109
+ const processEnded = !row.handle && row.pid && proof?.kind === 'process-exited' && proof.pid === row.pid;
110
+ const ended = proof?.confirmed === true && (terminalEnded || processEnded)
111
+ && (!row.pid || proof.pid == null || proof.pid === row.pid);
112
+ // A kernel reconcile settled this receipt's dispatch to no effect and recorded it durably
113
+ // (job result + dispatch-reconciled event): the proof carries that dispatch's identity and the
114
+ // receipt's own attempt scope, so the release is bound to the launch the receipt reserved.
115
+ const reconciledNoEffect = row.state === 'unknown' && proof?.kind === 'reconciled-no-effect'
116
+ && proof.confirmed === true && proof.effectState === 'none'
117
+ && typeof proof.dispatchId === 'string' && proof.dispatchId.trim()
118
+ && parse(row.scope_json)?.scopeId === proof.scopeId;
119
+ if (!noEffect && !ended && !reconciledNoEffect && !hostRestartedEnd(row, proof) && !dispatchEndedEnd(row, proof)) return { ok: false, reason: 'exit-unproven', reservation: providerReservationRow(row) };
120
+ const at = m.now();
121
+ db.prepare("UPDATE provider_reservations SET state='released',released_at=?,updated_at=?,proof_json=? WHERE id=? AND fence=?").run(at, at, toJson(proof), id, fence);
122
+ db.prepare("INSERT INTO provider_reservation_events(reservation_id,at,from_state,to_state,proof_json) VALUES(?,?,?,'released',?)").run(id, at, row.state, toJson(proof));
123
+ return { ok: true, reused: false, reservation: providerReservationRow(db.prepare('SELECT * FROM provider_reservations WHERE id=?').get(id)) };
124
+ });
125
+ }
126
+ return { reserveProvider, providerReservations, providerReservationUsage, markProviderReservation, releaseProviderReservation };
127
+ }
@@ -0,0 +1,76 @@
1
+ // Read-only joins of a native dispatch and its accepted goal grant; no prompt text grants authority.
2
+ import { canonicalJSON } from '../canonical-json.mjs';
3
+ import { sha256 } from '../digest.mjs';
4
+ import { byCodeUnit } from '../by-code-unit.mjs';
5
+ import { pathKey } from '../../scripts/lib/path-key.mjs';
6
+
7
+ const denied = reason => ({ ok: false, reason });
8
+ const parse = text => { try { return JSON.parse(text); } catch { return null; } };
9
+
10
+ /**
11
+ * Verify a provider-plan grant against the exact live job, latest attempt, filed admission and
12
+ * current owner-approved goal. The native caller-binding owner supplies `caller`; argv does not.
13
+ * This verifies retained conversation acceptance, not cryptographic human identity.
14
+ */
15
+ export function provisionGoalGrant(db, { caller, operationId, planSha, ledgerId, repoRoot, worktree }) {
16
+ const guard = caller?.guard;
17
+ if (!guard || caller.seat || guard.schema !== 'starci/op-guard@1' || guard.role !== 'op'
18
+ || caller.classification?.role !== 'op' || caller.classification.jobId !== guard.jobId
19
+ || caller.classification.workflowId !== guard.workflowId
20
+ || guard.workflowId === 'supervisor' || guard.terminal !== caller.terminal
21
+ || !guard.jobId || !guard.op || !guard.ledgerRepo || !guard.workflowWorktree
22
+ || pathKey(guard.ledgerRepo) !== pathKey(repoRoot) || pathKey(guard.workflowWorktree) !== pathKey(worktree)
23
+ || (caller.ledgerId !== undefined && caller.ledgerId !== ledgerId)
24
+ || (caller.workflowId !== undefined && caller.workflowId !== guard.workflowId))
25
+ return denied('provision-native-caller-unverified');
26
+ const job = db.prepare('SELECT * FROM jobs WHERE job_id=?').get(guard.jobId);
27
+ const attempt = db.prepare('SELECT * FROM op_attempts WHERE job_id=? ORDER BY attempt_id DESC LIMIT 1').get(guard.jobId);
28
+ const attemptId = attempt?.attempt_id;
29
+ if (!Number.isSafeInteger(attemptId) || attemptId < 1
30
+ || (caller.attemptId !== undefined && Number(caller.attemptId) !== attemptId)
31
+ || (caller.dispatchId !== undefined && caller.dispatchId !== attempt.dispatch_id)) return denied('provision-native-attempt-unverified');
32
+ if (job?.kind !== 'op' || job.status !== 'running' || job.workflow_id !== guard.workflowId || job.op_id !== guard.op
33
+ || !attempt || attempt.job_id !== guard.jobId || attempt.workflow_id !== guard.workflowId
34
+ || attempt.op_id !== guard.op || !attempt.dispatch_id || attempt.terminal_handle !== caller.terminal
35
+ || attempt.managed !== 1 || attempt.started_at === null || attempt.attested_at === null
36
+ || attempt.reported_at !== null || attempt.settled_at !== null || attempt.released_at !== null || attempt.end_state !== null
37
+ || !attempt.worktree_path || pathKey(attempt.worktree_path) !== pathKey(worktree))
38
+ return denied('provision-native-attempt-stale');
39
+ const workflow = db.prepare('SELECT * FROM workflows WHERE workflow_id=?').get(guard.workflowId);
40
+ const goal = db.prepare('SELECT * FROM goals WHERE workflow_id=? ORDER BY revision DESC LIMIT 1').get(guard.workflowId);
41
+ const unit = db.prepare('SELECT * FROM work_units WHERE workflow_id=? AND unit_id=?').get(guard.workflowId, job.unit_id);
42
+ const goalJson = parse(goal?.json), payload = parse(job.payload_json);
43
+ if (workflow?.phase !== 'running' || workflow.archived_at !== null || goal?.approved_by !== 'owner'
44
+ || !goal.markdown?.trim() || !goal.goal_identity || workflow.goal_identity !== goal.goal_identity || !goalJson || goalJson.provisional === true
45
+ || !unit || unit.goal_revision !== goal.revision || unit.current_job_id !== job.job_id
46
+ || payload?.goal_binding?.revision !== goal.revision || payload?.goal_binding?.identity !== goal.goal_identity)
47
+ return denied('provision-goal-grant-stale');
48
+ const contract = db.prepare('SELECT * FROM contracts WHERE attempt_id=?').get(attemptId);
49
+ const context = parse(contract?.context_json), packet = context?.packet;
50
+ if (contract?.workflow_id !== guard.workflowId || contract.job_id !== job.job_id || !contract.markdown?.trim()
51
+ || packet?.op !== guard.op || packet.context?.workflow?.id !== guard.workflowId
52
+ || packet.context?.workflow?.goal_revision !== goal.revision || packet.context?.workflow?.goal_identity !== goal.goal_identity
53
+ || context.contract?.schema !== 'starci/contract-version@1' || context.contract?.op !== guard.op
54
+ || !/^[a-f0-9]{64}$/u.test(context.contract?.digest ?? '') || !context.worktree || pathKey(context.worktree) !== pathKey(worktree)
55
+ || !job.lease_token || context.lease?.token !== job.lease_token || !context.lease?.fencing || typeof context.lease.fencing !== 'object' || Array.isArray(context.lease.fencing))
56
+ return denied('provision-filed-contract-unverified');
57
+ const leases = db.prepare('SELECT resource_key,generation,token,expires_at FROM leases WHERE job_id=?').all(job.job_id);
58
+ const fencing = context.lease.fencing;
59
+ if (Object.keys(fencing).length !== leases.length || leases.some(lease => fencing[lease.resource_key] !== job.generation
60
+ || lease.generation !== job.generation || lease.token !== job.lease_token || lease.expires_at <= Date.now()))
61
+ return denied('provision-filed-contract-unverified');
62
+ const grants = goalJson.provisionGrants, approval = goalJson.provisionGrantsApproval;
63
+ if (!Array.isArray(grants) || new Set(grants.map(grant => grant?.operation)).size !== grants.length || grants.some(grant => !grant || Object.keys(grant).sort(byCodeUnit).join(',') !== 'digest,op,operation'
64
+ || typeof grant.op !== 'string' || typeof grant.operation !== 'string' || !/^[a-f0-9]{64}$/u.test(grant.digest ?? '')))
65
+ return denied('provision-goal-grant-unsupported');
66
+ if (approval?.source !== 'owner-goal-entry' || approval.digest !== sha256(canonicalJSON(grants))
67
+ || approval.assurance !== 'conversation-context-not-authenticated') return denied('provision-goal-grant-unaccepted');
68
+ if (goal.revision === 0 ? goal.approval_ref !== approval.digest
69
+ : goal.approval_ref !== goalJson.revision?.approvalToken || !/^rev-[a-f0-9]{64}$/u.test(goal.approval_ref ?? ''))
70
+ return denied('provision-goal-grant-unaccepted');
71
+ const matches = grants.filter(grant => grant.op === guard.op && grant.operation === operationId && grant.digest === planSha);
72
+ if (matches.length !== 1) return denied('provision-goal-grant-missing');
73
+ return { ok: true, kind: 'owner-goal', workflow: guard.workflowId, goalRevision: goal.revision,
74
+ goalIdentity: goal.goal_identity, op: guard.op, job: job.job_id, attempt: attemptId,
75
+ dispatch: attempt.dispatch_id, operation: operationId, digest: planSha, assurance: approval.assurance };
76
+ }
@@ -0,0 +1,29 @@
1
+ // The machine writer owns compare-and-release of an observed dead provisioning executor.
2
+ import { pidAlive } from '../../scripts/lib/pid-alive.mjs';
3
+
4
+ /**
5
+ * Release only the exact observed provisioning lock after its PID is absent. A live reused PID
6
+ * conservatively holds the target. The caller may resume only the same persisted operation's
7
+ * read-only reconciliation; absence proves neither remote rollback nor absence of provider effects.
8
+ */
9
+ export function releaseObservedProvisionLock(m, { observed, holder }) {
10
+ if (!observed?.name?.startsWith('provision:') || observed.holder !== holder || observed.state === 'released'
11
+ || pidAlive(observed.holder_pid)) return false;
12
+ return m.transaction(db => db.prepare("UPDATE host_locks SET state='released',heartbeat_at=? WHERE name=? AND holder_pid=? AND holder=? AND started_at=? AND heartbeat_at=? AND expires_at=? AND state=?")
13
+ .run(m.now(), observed.name, observed.holder_pid, observed.holder, observed.started_at, observed.heartbeat_at, observed.expires_at, observed.state).changes === 1);
14
+ }
15
+
16
+ /** Renew only this provisioning operation's exact holder, including inside one shared process. */
17
+ export function renewProvisionLock(m, { name, holder, ttlMs, pid = process.pid }) {
18
+ if (!name?.startsWith('provision:') || typeof holder !== 'string' || !Number.isSafeInteger(ttlMs) || ttlMs < 1) return false;
19
+ const at = m.now();
20
+ return m.db.prepare("UPDATE host_locks SET heartbeat_at=?,expires_at=? WHERE name=? AND holder_pid=? AND holder=? AND state<>'released'")
21
+ .run(at, at + ttlMs, name, pid, holder).changes === 1;
22
+ }
23
+
24
+ /** Release only the exact provisioning holder; unrelated native host locks keep their own policy. */
25
+ export function releaseProvisionLock(m, { name, holder, pid = process.pid }) {
26
+ if (!name?.startsWith('provision:') || typeof holder !== 'string') return false;
27
+ return m.db.prepare("UPDATE host_locks SET state='released',heartbeat_at=? WHERE name=? AND holder_pid=? AND holder=? AND state<>'released'")
28
+ .run(m.now(), name, pid, holder).changes === 1;
29
+ }
@@ -0,0 +1,146 @@
1
+ // Standalone operation writes are reached only through the ledger writer's typed namespace.
2
+ import { sha256 } from '../digest.mjs';
3
+ import { provisionGoalGrant } from './provision-authority.mjs';
4
+ import { describeSchema, readSchemaObjects } from './machine-schema.mjs';
5
+
6
+ const failures = new WeakSet();
7
+ const need = (condition, code) => {
8
+ if (!condition) { const error = Object.assign(new Error(code), { code }); failures.add(error); throw error; }
9
+ };
10
+ /** Recognize only this writer's fixed invariant refusals; arbitrary database errors stay private. */
11
+ export const isProvisionOperationFailure = error => typeof error === 'object' && error !== null && failures.has(error);
12
+
13
+ const SCHEMA_FAILURE = 'STARCI_LEDGER_SCHEMA_REFUSED';
14
+ const SCHEMA_BEGIN = '-- starci:provision-schema:begin';
15
+ const SCHEMA_END = '-- starci:provision-schema:end';
16
+ const identifierKey = name => name.toLowerCase();
17
+ const provisionName = name => identifierKey(name).startsWith('provision_');
18
+
19
+ function canonicalProvisionSchema(canonicalSql) {
20
+ const begin = canonicalSql.indexOf(SCHEMA_BEGIN), end = canonicalSql.indexOf(SCHEMA_END);
21
+ need(begin >= 0 && end > begin && canonicalSql.lastIndexOf(SCHEMA_BEGIN) === begin
22
+ && canonicalSql.lastIndexOf(SCHEMA_END) === end, SCHEMA_FAILURE);
23
+ const canonical = describeSchema(canonicalSql);
24
+ const objects = canonical.objects.filter(row => provisionName(row.name));
25
+ const names = new Set(objects.map(row => identifierKey(row.name)));
26
+ const tables = new Set(objects.filter(row => row.type === 'table').map(row => identifierKey(row.name)));
27
+ const blobRefs = canonical.blobRefs.filter(row => tables.has(identifierKey(row.table_name)));
28
+ need(objects.length > 0 && tables.size > 0 && blobRefs.length > 0, SCHEMA_FAILURE);
29
+ return { sql: canonicalSql.slice(begin + SCHEMA_BEGIN.length, end), objects, names, tables, blobRefs };
30
+ }
31
+
32
+ function provisionSchemaState(db, schema) {
33
+ const all = readSchemaObjects(db);
34
+ need(['blobs', 'blob_ref_columns'].every(name => all.some(row => row.type === 'table' && row.name === name)), SCHEMA_FAILURE);
35
+ return {
36
+ objects: all.filter(row => provisionName(row.name) || schema.names.has(identifierKey(row.name)) || schema.tables.has(identifierKey(row.tbl_name))),
37
+ blobRefs: db.prepare('SELECT table_name,column_name FROM blob_ref_columns ORDER BY table_name,column_name').all()
38
+ .filter(row => provisionName(row.table_name) || schema.tables.has(identifierKey(row.table_name))),
39
+ };
40
+ }
41
+
42
+ function exactProvisionSchema(actual, expected) {
43
+ const objects = rows => JSON.stringify(rows.map(row => [row.type, row.name, row.tbl_name, row.identity]));
44
+ return objects(actual.objects) === objects(expected.objects)
45
+ && JSON.stringify(actual.blobRefs) === JSON.stringify(expected.blobRefs);
46
+ }
47
+
48
+ /** Install only a wholly absent canonical domain inside the ledger's fenced write transaction. */
49
+ function ensureProvisionSchema(db, schema) {
50
+ const before = provisionSchemaState(db, schema);
51
+ if (before.objects.length || before.blobRefs.length) {
52
+ need(exactProvisionSchema(before, schema), SCHEMA_FAILURE);
53
+ return;
54
+ }
55
+ db.exec(schema.sql);
56
+ need(exactProvisionSchema(provisionSchemaState(db, schema), schema), SCHEMA_FAILURE);
57
+ }
58
+
59
+ /** Read one provider operation; older compatible ledgers without this additive store return null. */
60
+ export function provisionOperation(db, operationId) {
61
+ if (!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='provision_operations'").get()) return null;
62
+ return db.prepare('SELECT * FROM provision_operations WHERE operation_id=?').get(operationId) ?? null;
63
+ }
64
+
65
+ /** Prior completed operations bind the exact public plan, including credential references and destination. */
66
+ export function priorProvisionOperations(db, { planSha, excludeId }) {
67
+ return db.prepare("SELECT * FROM provision_operations WHERE plan_sha=? AND operation_id<>? AND state IN ('applied','verified') ORDER BY updated_at DESC LIMIT 1")
68
+ .all(planSha, excludeId);
69
+ }
70
+
71
+ function journal(db, row, kind, at) {
72
+ const prior = db.prepare('SELECT digest FROM provision_events WHERE operation_id=? ORDER BY seq DESC LIMIT 1').get(row.operation_id)?.digest ?? null;
73
+ const digest = sha256(JSON.stringify([prior, row.operation_id, kind, row.state, row.receipt_sha, at]));
74
+ db.prepare('INSERT INTO provision_events(operation_id,kind,state,receipt_sha,prev_digest,digest,created_at) VALUES(?,?,?,?,?,?,?)')
75
+ .run(row.operation_id, kind, row.state, row.receipt_sha, prior, digest, at);
76
+ return row;
77
+ }
78
+
79
+ /** Prepare immutable intent. A reused identifier must retain the exact original plan and target. */
80
+ function prepareProvisionOperation(db, { operationId, action, targetId, transport, endpointIdentity, coordinationKey, planSha, at = Date.now() }) {
81
+ need(typeof operationId === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9-]{0,127}$/u.test(operationId), 'provision-operation-id-invalid');
82
+ const previous = provisionOperation(db, operationId);
83
+ if (previous) {
84
+ need(previous.action === action && previous.transport === transport && previous.endpoint_identity === endpointIdentity
85
+ && previous.coordination_key === coordinationKey && previous.plan_sha === planSha, 'provision-operation-intent-conflict');
86
+ return previous;
87
+ }
88
+ db.prepare("INSERT INTO provision_operations(operation_id,action,target_id,transport,endpoint_identity,coordination_key,plan_sha,state,code,created_at,updated_at) VALUES(?,?,?,?,?,?,?,'planned','plan-prepared',?,?)")
89
+ .run(operationId, action, targetId, transport, endpointIdentity, coordinationKey, planSha, at, at);
90
+ return journal(db, provisionOperation(db, operationId), 'prepared', at);
91
+ }
92
+
93
+ /** Record the native terminal's scoped acceptance; a digest flag alone never reaches this writer. */
94
+ function acceptProvisionOperation(db, { operationId, planSha, at = Date.now() }) {
95
+ const row = provisionOperation(db, operationId);
96
+ need(row && row.plan_sha === planSha, 'provision-plan-digest-mismatch');
97
+ need(['planned', 'accepted', 'unknown', 'partial'].includes(row.state), 'provision-accept-state-refused');
98
+ if (row.accepted_by === 'owner-terminal') return row;
99
+ db.prepare("UPDATE provision_operations SET state=CASE WHEN state='planned' THEN 'accepted' ELSE state END,accepted_by='owner-terminal',approval_ref=?,accepted_at=?,updated_at=?,code='owner-plan-accepted' WHERE operation_id=?")
100
+ .run(planSha, at, at, operationId);
101
+ return journal(db, provisionOperation(db, operationId), 'accepted', at);
102
+ }
103
+
104
+ /** Consume an exact accepted goal grant after the native binding check, rejoining current rows atomically. */
105
+ function authorizeProvisionGrant(db, { operationId, planSha, caller, ledgerId, repoRoot, worktree, authoritySha, at = Date.now() }) {
106
+ const authority = provisionGoalGrant(db, { operationId, planSha, caller, ledgerId, repoRoot, worktree });
107
+ need(authority.ok, 'provision-goal-grant-refused');
108
+ const row = provisionOperation(db, operationId);
109
+ need(row && row.plan_sha === planSha, 'provision-plan-digest-mismatch');
110
+ db.prepare("UPDATE provision_operations SET state=CASE WHEN state='planned' THEN 'accepted' ELSE state END,accepted_by='owner-goal',approval_ref=?,accepted_at=COALESCE(accepted_at,?),updated_at=? WHERE operation_id=?")
111
+ .run(authoritySha, at, at, operationId);
112
+ return journal(db, provisionOperation(db, operationId), 'goal-authorized', at);
113
+ }
114
+
115
+ /** Atomically claim one accepted execution or explicit unknown-outcome reconciliation. */
116
+ function claimProvisionOperation(db, { operationId, planSha, reconcile = false, at = Date.now(), pid = process.pid }) {
117
+ const row = provisionOperation(db, operationId);
118
+ need(row && row.plan_sha === planSha && row.accepted_at !== null, 'provision-acceptance-required');
119
+ need(reconcile ? ['unknown', 'partial', 'running'].includes(row.state) : row.state === 'accepted', 'provision-reconcile-required');
120
+ db.prepare("UPDATE provision_operations SET state='running',owner_pid=?,generation=generation+1,updated_at=?,code=? WHERE operation_id=?")
121
+ .run(pid, at, reconcile ? 'reconciliation-started' : 'execution-started', operationId);
122
+ return journal(db, provisionOperation(db, operationId), reconcile ? 'reconciling' : 'started', at);
123
+ }
124
+
125
+ /** Persist safe checkpoints and terminal receipts under the execution generation that owns them. */
126
+ function updateProvisionOperation(db, { operationId, generation, state = 'running', receiptSha = null, metadataSha = null, writeAck = false, code, at = Date.now(), pid = process.pid }) {
127
+ const row = provisionOperation(db, operationId);
128
+ need(row?.state === 'running' && row.generation === generation && row.owner_pid === pid, 'provision-executor-stale');
129
+ need(['running', 'applied', 'verified', 'failed', 'unknown', 'partial'].includes(state), 'provision-state-invalid');
130
+ db.prepare('UPDATE provision_operations SET state=?,receipt_sha=COALESCE(?,receipt_sha),metadata_sha=COALESCE(?,metadata_sha),write_ack=MAX(write_ack,?),code=?,updated_at=? WHERE operation_id=?')
131
+ .run(state, receiptSha, metadataSha, writeAck ? 1 : 0, code, at, operationId);
132
+ return journal(db, provisionOperation(db, operationId), state === 'running' ? 'checkpoint' : 'finished', at);
133
+ }
134
+
135
+ /** Canonical schema text is injected once; schema work stays lazy inside the existing typed transaction. */
136
+ export function provisionWrites(canonicalSql) {
137
+ let schema;
138
+ return Object.freeze({
139
+ prepareProvisionOperation(db, args) {
140
+ schema ??= canonicalProvisionSchema(canonicalSql);
141
+ ensureProvisionSchema(db, schema);
142
+ return prepareProvisionOperation(db, args);
143
+ },
144
+ acceptProvisionOperation, authorizeProvisionGrant, claimProvisionOperation, updateProvisionOperation,
145
+ });
146
+ }
@@ -0,0 +1,67 @@
1
+ // ref-value.mjs — the one accessor of a column that holds content or a reference to it (the storage convention: content is a file in the blob store under the runtime state
2
+ // dir, a row holds the reference).
3
+ //
4
+ // A column that may hold content holds either the content inline (the legacy shape, every row written before the convention) or a REFERENCE: the JSON stub machine.sqlite already
5
+ // writes for a value it keeps in the blob store, {"truncated":true,"bytes":<n>,"sha256":"<sha>"} (valid JSON, so it also passes a column's json_valid check). Every handle either
6
+ // store opens installs the resolver below, so a reader that selects such a column gets the content whichever shape the row has; a writer stores content with putContent, which
7
+ // keeps a short value inline and puts a long one in the blob store. A value is resolved byte for byte: the blob holds exactly the text the writer was given.
8
+ import { createRequire } from 'node:module';
9
+ import { getBlob } from './blob.mjs';
10
+
11
+ const REF = /^\{"truncated":true,"bytes":(\d+),"sha256":"([0-9a-f]{64})"\}$/;
12
+
13
+ /** Whether a stored value is a reference. */
14
+ export const isRef = (value) => typeof value === 'string' && value.codePointAt(0) === 123 && REF.test(value);
15
+
16
+ /** The reference string of a blob. */
17
+ export const refOf = (sha, bytes) => JSON.stringify({ truncated: true, bytes, sha256: sha });
18
+
19
+ /** The {sha, bytes} a reference names, or null. */
20
+ export function parseRef(value) {
21
+ const match = typeof value === 'string' && value.codePointAt(0) === 123 ? REF.exec(value) : null;
22
+ return match ? { sha: match[2], bytes: Number(match[1]) } : null;
23
+ }
24
+
25
+ /** The content behind a stored value: a reference is read from the blob store (utf8), anything else is the content itself. */
26
+ function resolveValue(value) {
27
+ const ref = parseRef(value);
28
+ if (!ref) return value;
29
+ return getBlob(ref.sha).toString('utf8');
30
+ }
31
+
32
+
33
+ const resolveRow = (row) => {
34
+ if (row === null || row === undefined || typeof row !== 'object') return row;
35
+ for (const key of Object.keys(row)) if (isRef(row[key])) row[key] = resolveValue(row[key]);
36
+ return row;
37
+ };
38
+
39
+ /**
40
+ * Makes every row a statement of `db` returns resolve its reference values: get, all and iterate. The handle is changed in place and returned; a handle that was already
41
+ * installed is left alone. Other statement methods (run, columns, ...) are the statement's own.
42
+ */
43
+ /** A SQLite file opened read-only with the given busy timeout, its references resolving. */
44
+ export const openReadOnlyDb = (file, timeoutMs) => installRefResolver(new (createRequire(import.meta.url)('node:sqlite').DatabaseSync)(file, { readOnly: true, timeout: timeoutMs }));
45
+
46
+ /** A statement over exact stored cells, for self-contained archives that retain references and their original bytes. */
47
+ export const prepareStored = (db, sql, ...rest) => Object.getPrototypeOf(db).prepare.call(db, sql, ...rest);
48
+
49
+ export function installRefResolver(db) {
50
+ if (db.__refResolver) return db;
51
+ // The prototype's prepare is looked up at each call, so a later change of the driver's own method (a spec's mock, a corruption injection) still applies.
52
+ db.prepare = (sql, ...rest) => {
53
+ const statement = prepareStored(db, sql, ...rest);
54
+ return new Proxy(statement, {
55
+ get(target, name) {
56
+ const member = target[name];
57
+ if (typeof member !== 'function') return member;
58
+ if (name === 'get') return (...args) => resolveRow(member.apply(target, args));
59
+ if (name === 'all') return (...args) => member.apply(target, args).map(resolveRow);
60
+ if (name === 'iterate') return (...args) => (function* resolved(iterator) { for (const row of iterator) yield resolveRow(row); })(member.apply(target, args));
61
+ return member.bind(target);
62
+ },
63
+ });
64
+ };
65
+ Object.defineProperty(db, '__refResolver', { value: true });
66
+ return db;
67
+ }