@starci/hfs 4.11.22 → 4.12.2
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.
- package/CHANGELOG.md +31 -0
- package/README.md +31 -31
- package/ci/cli.d.mts +15 -0
- package/ci/cli.mjs +71 -52
- package/ci/github.d.mts +43 -0
- package/ci/github.mjs +33 -28
- package/ci/local.d.mts +12 -0
- package/ci/local.mjs +145 -108
- package/ci/secret.d.mts +15 -0
- package/ci/secret.mjs +31 -21
- package/ci/vars.d.mts +17 -0
- package/ci/vars.mjs +47 -33
- package/ci/verify.d.mts +18 -0
- package/ci/verify.mjs +52 -40
- package/codemod/button-intent.d.mts +63 -0
- package/codemod/button-intent.mjs +338 -285
- package/coverage/cli.d.mts +28 -0
- package/coverage/cli.mjs +65 -51
- package/db/backup-receipt.d.mts +28 -0
- package/db/backup-receipt.mjs +78 -58
- package/db/drift.d.mts +86 -0
- package/db/drift.mjs +213 -180
- package/db/dump.d.mts +52 -0
- package/db/dump.mjs +63 -56
- package/db/entity-list.d.mts +4 -0
- package/db/entity-list.mjs +0 -2
- package/db/image.d.mts +8 -0
- package/db/image.mjs +1 -2
- package/db/migrate.d.mts +104 -0
- package/db/migrate.mjs +207 -167
- package/db/new.d.mts +11 -0
- package/db/new.mjs +25 -25
- package/db/post-cutover.d.mts +5 -0
- package/db/post-cutover.mjs +89 -82
- package/db/postgres.d.mts +71 -0
- package/db/postgres.mjs +62 -61
- package/emit/compiler.d.mts +3 -0
- package/emit/compiler.mjs +25 -27
- package/emit/contracts.d.mts +35 -0
- package/emit/contracts.mjs +46 -50
- package/emit/events.d.mts +9 -0
- package/emit/events.mjs +19 -20
- package/emit/form-bounds.d.mts +83 -0
- package/emit/form-bounds.mjs +133 -117
- package/emit/graphql-input-bounds.d.mts +63 -0
- package/emit/graphql-input-bounds.mjs +90 -79
- package/emit/input-bounds.d.mts +30 -0
- package/emit/input-bounds.mjs +29 -27
- package/emit/operation-schemas.d.mts +24 -0
- package/emit/operation-schemas.mjs +136 -116
- package/emit/operations-worker.d.mts +1 -0
- package/emit/operations-worker.mjs +9 -7
- package/emit/operations.d.mts +47 -0
- package/emit/operations.mjs +124 -133
- package/emit/schema-worker.d.mts +1 -0
- package/emit/schema-worker.mjs +57 -50
- package/emit/static-graph.d.mts +48 -0
- package/emit/static-graph.mjs +766 -640
- package/emit/type-schema.d.mts +29 -0
- package/emit/type-schema.mjs +153 -140
- package/lint/bound-sys.cjs +27 -33
- package/lint/chunk.d.mts +17 -0
- package/lint/chunk.mjs +41 -31
- package/lint/run.d.mts +120 -0
- package/lint/run.mjs +414 -325
- package/package.json +21 -4
- package/release/candidate-ci.d.mts +46 -0
- package/release/candidate-ci.mjs +76 -70
- package/release/gate.d.mts +120 -0
- package/release/gate.mjs +358 -330
- package/release/index.d.mts +68 -0
- package/release/index.mjs +95 -73
- package/release/lint-report.d.mts +14 -0
- package/release/lint-report.mjs +33 -32
- package/release/push.d.mts +32 -0
- package/release/push.mjs +92 -79
- package/release/records.d.mts +18 -0
- package/release/records.mjs +22 -21
- package/release/sonar-quality-gate.d.mts +18 -0
- package/release/sonar-quality-gate.mjs +60 -52
- package/release/urgent.d.mts +20 -0
- package/release/urgent.mjs +76 -74
- package/report/order.d.mts +1 -0
- package/report/sonar.d.mts +73 -0
- package/report/sonar.mjs +82 -89
- package/runtime/engine/admission.d.mts +209 -0
- package/runtime/engine/admission.mjs +225 -204
- package/runtime/engine/by-code-unit.d.mts +5 -0
- package/runtime/engine/by-code-unit.mjs +6 -6
- package/runtime/engine/config.d.mts +333 -0
- package/runtime/engine/config.mjs +486 -328
- package/runtime/engine/db/blob.d.mts +83 -0
- package/runtime/engine/db/blob.mjs +344 -268
- package/runtime/engine/db/event-compact.d.mts +21 -0
- package/runtime/engine/db/event-compact.mjs +34 -38
- package/runtime/engine/db/machine-connection.d.mts +68 -0
- package/runtime/engine/db/machine-connection.mjs +166 -119
- package/runtime/engine/db/machine-open.d.mts +26 -0
- package/runtime/engine/db/machine-open.mjs +59 -45
- package/runtime/engine/db/machine-schema.d.mts +34 -0
- package/runtime/engine/db/machine-schema.mjs +102 -83
- package/runtime/engine/db/machine.d.mts +1746 -0
- package/runtime/engine/db/machine.mjs +1040 -734
- package/runtime/engine/db/provider-reservations.d.mts +169 -0
- package/runtime/engine/db/provider-reservations.mjs +141 -119
- package/runtime/engine/db/provision-lock.d.mts +23 -0
- package/runtime/engine/db/provision-lock.mjs +14 -14
- package/runtime/engine/db/ref-value.d.mts +19 -0
- package/runtime/engine/db/ref-value.mjs +36 -35
- package/runtime/engine/db/sqlite-types.d.mts +28 -0
- package/runtime/engine/db/sqlite-types.mjs +4 -0
- package/runtime/engine/digest.d.mts +4 -0
- package/runtime/engine/digest.mjs +16 -7
- package/runtime/engine/invalid-config.d.mts +13 -0
- package/runtime/engine/invalid-config.mjs +17 -11
- package/runtime/engine/model-config.d.mts +91 -0
- package/runtime/engine/model-config.mjs +98 -76
- package/runtime/engine/orca-config.d.mts +26 -0
- package/runtime/engine/orca-config.mjs +22 -17
- package/runtime/engine/plain-object.d.mts +2 -0
- package/runtime/engine/refuse.d.mts +9 -0
- package/runtime/engine/refuse.mjs +3 -6
- package/runtime/engine/release-config.d.mts +19 -0
- package/runtime/engine/release-config.mjs +22 -18
- package/runtime/engine/removed-vocabulary.d.mts +12 -0
- package/runtime/engine/removed-vocabulary.mjs +21 -20
- package/runtime/engine/resources-config.d.mts +34 -0
- package/runtime/engine/resources-config.mjs +42 -35
- package/runtime/engine/runtime-root.d.mts +28 -0
- package/runtime/engine/runtime-root.mjs +11 -13
- package/runtime/engine/secrets.d.mts +41 -0
- package/runtime/engine/secrets.mjs +170 -102
- package/runtime/engine/sonar-config.d.mts +10 -0
- package/runtime/engine/sonar-config.mjs +16 -13
- package/runtime/engine/temp-root.d.mts +17 -0
- package/runtime/engine/temp-root.mjs +3 -9
- package/runtime/engine/yaml.d.mts +9 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +10 -10
- package/runtime/knowledge/hfs/ci-standard.yaml +2 -2
- package/runtime/knowledge/hfs/rules.yaml +95 -6
- package/runtime/knowledge/hfs/slots.yaml +3 -3
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/kernel/failure-codes.yaml +43 -32
- package/runtime/modules/schemas/application-stacks.schema.yaml +3 -3
- package/runtime/packages/cli/src/catalog.generated.d.mts +13 -0
- package/runtime/packages/cli/src/catalog.generated.mjs +1853 -14824
- package/runtime/packages/cli/src/validate-args.d.mts +74 -0
- package/runtime/packages/cli/src/validate-args.mjs +183 -171
- package/runtime/packages/jest-preset/sonar-exclusions.cjs +5 -5
- package/runtime/scripts/api/fs/claim-file.d.mts +12 -0
- package/runtime/scripts/api/fs/claim-file.mjs +31 -17
- package/runtime/scripts/api/fs/ensure-temp-root.d.mts +4 -0
- package/runtime/scripts/api/fs/ensure-temp-root.mjs +15 -14
- package/runtime/scripts/api/fs/forbidden-root.d.mts +15 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +31 -23
- package/runtime/scripts/api/fs/is-link-like.d.mts +10 -0
- package/runtime/scripts/api/fs/is-link-like.mjs +25 -11
- package/runtime/scripts/api/fs/lib.d.mts +12 -0
- package/runtime/scripts/api/fs/lib.mjs +28 -8
- package/runtime/scripts/api/fs/make-temp-dir.d.mts +5 -0
- package/runtime/scripts/api/fs/make-temp-dir.mjs +3 -4
- package/runtime/scripts/api/fs/rename-over.d.mts +13 -0
- package/runtime/scripts/api/fs/rename-over.mjs +16 -10
- package/runtime/scripts/api/fs/safe-remove.d.mts +25 -0
- package/runtime/scripts/api/fs/safe-remove.mjs +129 -71
- package/runtime/scripts/api/git/add.d.mts +3 -0
- package/runtime/scripts/api/git/add.mjs +0 -1
- package/runtime/scripts/api/git/cat-file.d.mts +3 -0
- package/runtime/scripts/api/git/cat-file.mjs +0 -1
- package/runtime/scripts/api/git/check-ignore.d.mts +3 -0
- package/runtime/scripts/api/git/check-ignore.mjs +0 -1
- package/runtime/scripts/api/git/commit-tree.d.mts +3 -0
- package/runtime/scripts/api/git/commit-tree.mjs +0 -1
- package/runtime/scripts/api/git/config-get.d.mts +10 -0
- package/runtime/scripts/api/git/config-get.mjs +1 -2
- package/runtime/scripts/api/git/diff.d.mts +3 -0
- package/runtime/scripts/api/git/diff.mjs +0 -1
- package/runtime/scripts/api/git/init.d.mts +7 -0
- package/runtime/scripts/api/git/init.mjs +1 -2
- package/runtime/scripts/api/git/is-ancestor.d.mts +5 -0
- package/runtime/scripts/api/git/is-ancestor.mjs +0 -1
- package/runtime/scripts/api/git/lib.d.mts +67 -0
- package/runtime/scripts/api/git/lib.mjs +32 -32
- package/runtime/scripts/api/git/ls-files.d.mts +3 -0
- package/runtime/scripts/api/git/ls-files.mjs +0 -1
- package/runtime/scripts/api/git/ls-tree.d.mts +3 -0
- package/runtime/scripts/api/git/ls-tree.mjs +0 -1
- package/runtime/scripts/api/git/merge-base.d.mts +5 -0
- package/runtime/scripts/api/git/merge-base.mjs +2 -3
- package/runtime/scripts/api/git/remote.d.mts +3 -0
- package/runtime/scripts/api/git/remote.mjs +0 -1
- package/runtime/scripts/api/git/rev-parse-query.d.mts +3 -0
- package/runtime/scripts/api/git/rev-parse-query.mjs +0 -1
- package/runtime/scripts/api/git/show.d.mts +3 -0
- package/runtime/scripts/api/git/show.mjs +0 -1
- package/runtime/scripts/api/git/tag.d.mts +3 -0
- package/runtime/scripts/api/git/tag.mjs +0 -1
- package/runtime/scripts/api/git/update-ref.d.mts +7 -0
- package/runtime/scripts/api/git/update-ref.mjs +1 -2
- package/runtime/scripts/api/git/write-tree.d.mts +3 -0
- package/runtime/scripts/api/git/write-tree.mjs +0 -1
- package/runtime/scripts/api/node/lib.d.mts +7 -0
- package/runtime/scripts/api/node/lib.mjs +17 -7
- package/runtime/scripts/api/node/run-node.d.mts +3 -0
- package/runtime/scripts/api/node/run-node.mjs +0 -3
- package/runtime/scripts/api/process/resolve-real-tool.d.mts +12 -0
- package/runtime/scripts/api/process/resolve-real-tool.mjs +46 -38
- package/runtime/scripts/api/process/run-program.d.mts +3 -0
- package/runtime/scripts/api/process/run-program.mjs +5 -2
- package/runtime/scripts/api/sops/decrypt.d.mts +9 -0
- package/runtime/scripts/api/sops/decrypt.mjs +1 -2
- package/runtime/scripts/api/sops/lib.d.mts +149 -0
- package/runtime/scripts/api/sops/lib.mjs +417 -318
- package/runtime/scripts/api/sops/resolve-sops.d.mts +3 -0
- package/runtime/scripts/api/sops/resolve-sops.mjs +1 -2
- package/runtime/scripts/api/sops/seal.d.mts +19 -0
- package/runtime/scripts/api/sops/seal.mjs +14 -10
- package/runtime/scripts/api/sops/selected-age-recipient.d.mts +3 -0
- package/runtime/scripts/api/sops/selected-age-recipient.mjs +1 -2
- package/runtime/scripts/api/sops/update-keys.d.mts +16 -0
- package/runtime/scripts/api/sops/update-keys.mjs +11 -8
- package/runtime/scripts/cli/gen-catalog.d.mts +42 -0
- package/runtime/scripts/cli/gen-catalog.mjs +72 -0
- package/runtime/scripts/gates/runtime-host.d.mts +23 -0
- package/runtime/scripts/gates/runtime-host.mjs +48 -41
- package/runtime/scripts/gates/sonar-gate.d.mts +219 -0
- package/runtime/scripts/gates/sonar-gate.mjs +166 -153
- package/runtime/scripts/gates/starcistacks-ci.d.mts +35 -0
- package/runtime/scripts/gates/starcistacks-ci.mjs +47 -39
- package/runtime/scripts/gates/starcistacks-form.d.mts +80 -0
- package/runtime/scripts/gates/starcistacks-form.mjs +16 -14
- package/runtime/scripts/gates/starcistacks-runtime.d.mts +1 -0
- package/runtime/scripts/gates/starcistacks-value-file.d.mts +2 -0
- package/runtime/scripts/gates/starcistacks-value-file.mjs +14 -11
- package/runtime/scripts/gates/starcistacks.d.mts +141 -0
- package/runtime/scripts/gates/starcistacks.mjs +470 -355
- package/runtime/scripts/guards/git-pathspec.d.mts +48 -0
- package/runtime/scripts/guards/git-pathspec.mjs +215 -130
- package/runtime/scripts/guards/git-policy.d.mts +30 -0
- package/runtime/scripts/guards/git-policy.mjs +278 -235
- package/runtime/scripts/guards/hook-io.d.mts +22 -0
- package/runtime/scripts/guards/hook-io.mjs +77 -63
- package/runtime/scripts/guards/push-scan.d.mts +31 -0
- package/runtime/scripts/guards/push-scan.mjs +93 -80
- package/runtime/scripts/guards/release-push-gate.d.mts +22 -0
- package/runtime/scripts/guards/release-push-gate.mjs +40 -38
- package/runtime/scripts/hfs/allows.d.mts +24 -0
- package/runtime/scripts/hfs/allows.mjs +37 -38
- package/runtime/scripts/hfs/app-wiring.d.mts +72 -0
- package/runtime/scripts/hfs/app-wiring.mjs +197 -167
- package/runtime/scripts/hfs/architecture/ast-walks.d.mts +89 -0
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +180 -163
- package/runtime/scripts/hfs/architecture/automatic-gates.d.mts +21 -0
- package/runtime/scripts/hfs/architecture/automatic-gates.mjs +80 -73
- package/runtime/scripts/hfs/architecture/backend-app-files.d.mts +10 -0
- package/runtime/scripts/hfs/architecture/backend-app-files.mjs +88 -77
- package/runtime/scripts/hfs/architecture/backend.d.mts +5 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +378 -357
- package/runtime/scripts/hfs/architecture/background-unowned.d.mts +25 -0
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +201 -187
- package/runtime/scripts/hfs/architecture/canon-clones.d.mts +8 -0
- package/runtime/scripts/hfs/architecture/canon-clones.mjs +260 -209
- package/runtime/scripts/hfs/architecture/canon-jsx-clones.d.mts +5 -0
- package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +149 -116
- package/runtime/scripts/hfs/architecture/client-reaches-server.d.mts +28 -0
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +72 -68
- package/runtime/scripts/hfs/architecture/clones.d.mts +54 -0
- package/runtime/scripts/hfs/architecture/clones.mjs +212 -217
- package/runtime/scripts/hfs/architecture/config-unread.d.mts +14 -0
- package/runtime/scripts/hfs/architecture/config-unread.mjs +7 -9
- package/runtime/scripts/hfs/architecture/config.d.mts +74 -0
- package/runtime/scripts/hfs/architecture/config.mjs +302 -268
- package/runtime/scripts/hfs/architecture/connection-map.d.mts +33 -0
- package/runtime/scripts/hfs/architecture/connection-map.mjs +258 -225
- package/runtime/scripts/hfs/architecture/constructor-deps.d.mts +57 -0
- package/runtime/scripts/hfs/architecture/constructor-deps.mjs +80 -71
- package/runtime/scripts/hfs/architecture/context-coupling.d.mts +21 -0
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +82 -77
- package/runtime/scripts/hfs/architecture/context-map.d.mts +102 -0
- package/runtime/scripts/hfs/architecture/context-map.mjs +267 -234
- package/runtime/scripts/hfs/architecture/context-owner.d.mts +15 -0
- package/runtime/scripts/hfs/architecture/context-owner.mjs +73 -61
- package/runtime/scripts/hfs/architecture/context-platform-tables.d.mts +18 -0
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -43
- package/runtime/scripts/hfs/architecture/context-transaction.d.mts +20 -0
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +54 -52
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.d.mts +37 -0
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +113 -104
- package/runtime/scripts/hfs/architecture/contracts-readonly.d.mts +32 -0
- package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +288 -270
- package/runtime/scripts/hfs/architecture/contracts.d.mts +30 -0
- package/runtime/scripts/hfs/architecture/contracts.mjs +408 -393
- package/runtime/scripts/hfs/architecture/cross-app-duplicate.d.mts +27 -0
- package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +50 -51
- package/runtime/scripts/hfs/architecture/dead-exports-bindings.d.mts +21 -0
- package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +143 -119
- package/runtime/scripts/hfs/architecture/dead-exports.d.mts +32 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +286 -228
- package/runtime/scripts/hfs/architecture/default-deny.d.mts +21 -0
- package/runtime/scripts/hfs/architecture/default-deny.mjs +129 -110
- package/runtime/scripts/hfs/architecture/doc-language.d.mts +17 -0
- package/runtime/scripts/hfs/architecture/doc-language.mjs +24 -19
- package/runtime/scripts/hfs/architecture/entity-registry.d.mts +49 -0
- package/runtime/scripts/hfs/architecture/entity-registry.mjs +20 -22
- package/runtime/scripts/hfs/architecture/entrypoint.d.mts +13 -0
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +47 -45
- package/runtime/scripts/hfs/architecture/error-codes.d.mts +16 -0
- package/runtime/scripts/hfs/architecture/error-codes.mjs +37 -35
- package/runtime/scripts/hfs/architecture/error-masked.d.mts +16 -0
- package/runtime/scripts/hfs/architecture/error-masked.mjs +55 -59
- package/runtime/scripts/hfs/architecture/fe-slot-allows.d.mts +38 -0
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +24 -23
- package/runtime/scripts/hfs/architecture/feature-shape.d.mts +26 -0
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +40 -38
- package/runtime/scripts/hfs/architecture/framework-pinned.d.mts +3 -0
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +21 -21
- package/runtime/scripts/hfs/architecture/frontend-block-render.d.mts +26 -0
- package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +106 -98
- package/runtime/scripts/hfs/architecture/frontend-grammar.d.mts +7 -0
- package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +153 -150
- package/runtime/scripts/hfs/architecture/frontend-routing.d.mts +23 -0
- package/runtime/scripts/hfs/architecture/frontend-routing.mjs +161 -149
- package/runtime/scripts/hfs/architecture/frontend-world-render.d.mts +72 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +343 -305
- package/runtime/scripts/hfs/architecture/frontend-world.d.mts +94 -0
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +340 -294
- package/runtime/scripts/hfs/architecture/frontend.d.mts +31 -0
- package/runtime/scripts/hfs/architecture/frontend.mjs +174 -184
- package/runtime/scripts/hfs/architecture/function-spec-providers.d.mts +27 -0
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +124 -106
- package/runtime/scripts/hfs/architecture/hfs-graph.d.mts +78 -0
- package/runtime/scripts/hfs/architecture/hfs-graph.mjs +52 -48
- package/runtime/scripts/hfs/architecture/hfs.d.mts +135 -0
- package/runtime/scripts/hfs/architecture/hfs.mjs +328 -282
- package/runtime/scripts/hfs/architecture/hooks-are-hooks.d.mts +24 -0
- package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +81 -71
- package/runtime/scripts/hfs/architecture/i18n-keys.d.mts +31 -0
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +155 -141
- package/runtime/scripts/hfs/architecture/identity.d.mts +7 -0
- package/runtime/scripts/hfs/architecture/identity.mjs +141 -125
- package/runtime/scripts/hfs/architecture/index.d.mts +101 -0
- package/runtime/scripts/hfs/architecture/index.mjs +272 -272
- package/runtime/scripts/hfs/architecture/injection-token-exported.d.mts +21 -0
- package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +45 -43
- package/runtime/scripts/hfs/architecture/jwt-ast.d.mts +16 -0
- package/runtime/scripts/hfs/architecture/jwt-ast.mjs +195 -176
- package/runtime/scripts/hfs/architecture/machine-ast.d.mts +88 -0
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +163 -155
- package/runtime/scripts/hfs/architecture/managed-scripts.d.mts +2 -0
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +19 -20
- package/runtime/scripts/hfs/architecture/module-per-transport.d.mts +35 -0
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +177 -172
- package/runtime/scripts/hfs/architecture/next-data-contract.d.mts +35 -0
- package/runtime/scripts/hfs/architecture/next-data-contract.mjs +100 -78
- package/runtime/scripts/hfs/architecture/next-data-inference.d.mts +39 -0
- package/runtime/scripts/hfs/architecture/next-data-inference.mjs +170 -154
- package/runtime/scripts/hfs/architecture/next-data-key.d.mts +50 -0
- package/runtime/scripts/hfs/architecture/next-data-key.mjs +311 -275
- package/runtime/scripts/hfs/architecture/next-data-symbols.d.mts +5 -0
- package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +17 -17
- package/runtime/scripts/hfs/architecture/next-data.d.mts +36 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +351 -334
- package/runtime/scripts/hfs/architecture/operation-contract-roots.d.mts +10 -0
- package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +39 -37
- package/runtime/scripts/hfs/architecture/owners.d.mts +5 -0
- package/runtime/scripts/hfs/architecture/owners.mjs +97 -98
- package/runtime/scripts/hfs/architecture/package-shape.d.mts +26 -0
- package/runtime/scripts/hfs/architecture/package-shape.mjs +56 -55
- package/runtime/scripts/hfs/architecture/presentation.d.mts +24 -0
- package/runtime/scripts/hfs/architecture/presentation.mjs +138 -130
- package/runtime/scripts/hfs/architecture/reachability.d.mts +25 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +258 -241
- package/runtime/scripts/hfs/architecture/register-once.d.mts +18 -0
- package/runtime/scripts/hfs/architecture/register-once.mjs +152 -139
- package/runtime/scripts/hfs/architecture/registration.d.mts +15 -0
- package/runtime/scripts/hfs/architecture/registration.mjs +301 -267
- package/runtime/scripts/hfs/architecture/required-files.d.mts +64 -0
- package/runtime/scripts/hfs/architecture/required-files.mjs +172 -167
- package/runtime/scripts/hfs/architecture/route-files-thin.d.mts +23 -0
- package/runtime/scripts/hfs/architecture/route-files-thin.mjs +85 -77
- package/runtime/scripts/hfs/architecture/schema-owner.d.mts +34 -0
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +206 -181
- package/runtime/scripts/hfs/architecture/server-action.d.mts +22 -0
- package/runtime/scripts/hfs/architecture/server-action.mjs +48 -48
- package/runtime/scripts/hfs/architecture/side-boundary.d.mts +3 -0
- package/runtime/scripts/hfs/architecture/side-boundary.mjs +2 -5
- package/runtime/scripts/hfs/architecture/source-names-shape.d.mts +85 -0
- package/runtime/scripts/hfs/architecture/source-names-shape.mjs +261 -259
- package/runtime/scripts/hfs/architecture/source-names.d.mts +7 -0
- package/runtime/scripts/hfs/architecture/source-names.mjs +304 -278
- package/runtime/scripts/hfs/architecture/sql-keys.d.mts +5 -0
- package/runtime/scripts/hfs/architecture/sql-keys.mjs +277 -194
- package/runtime/scripts/hfs/architecture/sql-owner.d.mts +30 -0
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +236 -194
- package/runtime/scripts/hfs/architecture/sql-returning.d.mts +22 -0
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +46 -45
- package/runtime/scripts/hfs/architecture/sql-token-forms.d.mts +21 -0
- package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +86 -74
- package/runtime/scripts/hfs/architecture/sql-tokens.d.mts +32 -0
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +456 -341
- package/runtime/scripts/hfs/architecture/staged-consumers.d.mts +7 -0
- package/runtime/scripts/hfs/architecture/staged-consumers.mjs +35 -30
- package/runtime/scripts/hfs/architecture/surface.d.mts +26 -0
- package/runtime/scripts/hfs/architecture/surface.mjs +64 -74
- package/runtime/scripts/hfs/architecture/symbols.d.mts +11 -0
- package/runtime/scripts/hfs/architecture/symbols.mjs +209 -216
- package/runtime/scripts/hfs/architecture/test-world-files.d.mts +45 -0
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +165 -155
- package/runtime/scripts/hfs/architecture/tiers.d.mts +29 -0
- package/runtime/scripts/hfs/architecture/tiers.mjs +161 -154
- package/runtime/scripts/hfs/architecture/transport-owner.d.mts +37 -0
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +155 -149
- package/runtime/scripts/hfs/architecture/type-context.d.mts +60 -0
- package/runtime/scripts/hfs/architecture/type-context.mjs +255 -250
- package/runtime/scripts/hfs/architecture/typescript.d.mts +94 -0
- package/runtime/scripts/hfs/architecture/typescript.mjs +358 -318
- package/runtime/scripts/hfs/architecture/unit-spec-providers.d.mts +34 -0
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +110 -92
- package/runtime/scripts/hfs/architecture.d.mts +13 -0
- package/runtime/scripts/hfs/architecture.mjs +39 -29
- package/runtime/scripts/hfs/canon-pins.d.mts +26 -0
- package/runtime/scripts/hfs/canon-pins.mjs +5 -8
- package/runtime/scripts/hfs/catalog-once.d.mts +2 -0
- package/runtime/scripts/hfs/catalog-once.mjs +8 -9
- package/runtime/scripts/hfs/check-results.d.mts +29 -0
- package/runtime/scripts/hfs/check-results.mjs +14 -18
- package/runtime/scripts/hfs/check.d.mts +144 -0
- package/runtime/scripts/hfs/check.mjs +309 -329
- package/runtime/scripts/hfs/ci-declaration.d.mts +77 -0
- package/runtime/scripts/hfs/ci-declaration.mjs +99 -87
- package/runtime/scripts/hfs/coverage-from.d.mts +22 -0
- package/runtime/scripts/hfs/coverage-from.mjs +52 -49
- package/runtime/scripts/hfs/coverage-scope.d.mts +87 -0
- package/runtime/scripts/hfs/coverage-scope.mjs +139 -142
- package/runtime/scripts/hfs/db-baseline-verb.d.mts +84 -0
- package/runtime/scripts/hfs/db-baseline-verb.mjs +327 -266
- package/runtime/scripts/hfs/db-errors.d.mts +2 -0
- package/runtime/scripts/hfs/db-errors.mjs +2 -1
- package/runtime/scripts/hfs/db-plan-verb.d.mts +33 -0
- package/runtime/scripts/hfs/db-plan-verb.mjs +174 -128
- package/runtime/scripts/hfs/db-restamp-verb.d.mts +5 -0
- package/runtime/scripts/hfs/db-restamp-verb.mjs +94 -70
- package/runtime/scripts/hfs/db-squash.d.mts +117 -0
- package/runtime/scripts/hfs/db-squash.mjs +414 -351
- package/runtime/scripts/hfs/declaration-shape.d.mts +3 -0
- package/runtime/scripts/hfs/declaration-shape.mjs +140 -103
- package/runtime/scripts/hfs/declaration-slots.d.mts +18 -0
- package/runtime/scripts/hfs/declaration-slots.mjs +64 -61
- package/runtime/scripts/hfs/doc-commands.d.mts +71 -0
- package/runtime/scripts/hfs/doc-commands.mjs +257 -250
- package/runtime/scripts/hfs/edition-slots.d.mts +37 -0
- package/runtime/scripts/hfs/edition-slots.mjs +43 -40
- package/runtime/scripts/hfs/example-secrets.d.mts +18 -0
- package/runtime/scripts/hfs/example-secrets.mjs +173 -154
- package/runtime/scripts/hfs/gate-threshold.d.mts +20 -0
- package/runtime/scripts/hfs/gate-threshold.mjs +14 -22
- package/runtime/scripts/hfs/grammar-presentation.d.mts +74 -0
- package/runtime/scripts/hfs/grammar-presentation.mjs +109 -106
- package/runtime/scripts/hfs/linear-text.d.mts +6 -0
- package/runtime/scripts/hfs/linear-text.mjs +20 -20
- package/runtime/scripts/hfs/manifest-shape.d.mts +216 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +399 -324
- package/runtime/scripts/hfs/param-names.d.mts +3 -0
- package/runtime/scripts/hfs/param-names.mjs +6 -11
- package/runtime/scripts/hfs/path-findings.d.mts +22 -0
- package/runtime/scripts/hfs/path-findings.mjs +120 -100
- package/runtime/scripts/hfs/pin-findings.d.mts +19 -0
- package/runtime/scripts/hfs/pin-findings.mjs +26 -21
- package/runtime/scripts/hfs/refusal-codes.d.mts +1 -0
- package/runtime/scripts/hfs/refusal-codes.mjs +2 -2
- package/runtime/scripts/hfs/repo-identity.d.mts +15 -0
- package/runtime/scripts/hfs/repo-identity.mjs +55 -42
- package/runtime/scripts/hfs/rule-catalog.d.mts +77 -0
- package/runtime/scripts/hfs/rule-catalog.mjs +353 -257
- package/runtime/scripts/hfs/rule-params-shape.d.mts +10 -0
- package/runtime/scripts/hfs/rule-params-shape.mjs +60 -47
- package/runtime/scripts/hfs/rules/a15-findings.d.mts +18 -0
- package/runtime/scripts/hfs/rules/a15-findings.mjs +59 -55
- package/runtime/scripts/hfs/rules/app-env-required.d.mts +9 -0
- package/runtime/scripts/hfs/rules/app-env-required.mjs +13 -13
- package/runtime/scripts/hfs/rules/app-root.d.mts +15 -0
- package/runtime/scripts/hfs/rules/app-root.mjs +20 -21
- package/runtime/scripts/hfs/rules/app-workflow-findings.d.mts +12 -0
- package/runtime/scripts/hfs/rules/app-workflow-findings.mjs +4 -4
- package/runtime/scripts/hfs/rules/assets.d.mts +13 -0
- package/runtime/scripts/hfs/rules/assets.mjs +58 -46
- package/runtime/scripts/hfs/rules/boot-proof.d.mts +11 -0
- package/runtime/scripts/hfs/rules/boot-proof.mjs +7 -8
- package/runtime/scripts/hfs/rules/bypass-markers.d.mts +9 -0
- package/runtime/scripts/hfs/rules/bypass-markers.mjs +7 -12
- package/runtime/scripts/hfs/rules/canon-copy-drift.d.mts +10 -0
- package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +70 -68
- package/runtime/scripts/hfs/rules/ci-custody.d.mts +7 -0
- package/runtime/scripts/hfs/rules/ci-custody.mjs +72 -73
- package/runtime/scripts/hfs/rules/ci-standard.d.mts +12 -0
- package/runtime/scripts/hfs/rules/ci-standard.mjs +149 -142
- package/runtime/scripts/hfs/rules/cli.d.mts +7 -0
- package/runtime/scripts/hfs/rules/cli.mjs +24 -23
- package/runtime/scripts/hfs/rules/contract-compat.d.mts +11 -0
- package/runtime/scripts/hfs/rules/contract-compat.mjs +64 -53
- package/runtime/scripts/hfs/rules/contract.d.mts +26 -0
- package/runtime/scripts/hfs/rules/contract.mjs +126 -111
- package/runtime/scripts/hfs/rules/coverage-below.d.mts +17 -0
- package/runtime/scripts/hfs/rules/coverage-below.mjs +200 -167
- package/runtime/scripts/hfs/rules/database-access.d.mts +12 -0
- package/runtime/scripts/hfs/rules/database-access.mjs +231 -202
- package/runtime/scripts/hfs/rules/database-baseline.d.mts +22 -0
- package/runtime/scripts/hfs/rules/database-baseline.mjs +104 -104
- package/runtime/scripts/hfs/rules/database-config.d.mts +11 -0
- package/runtime/scripts/hfs/rules/database-config.mjs +101 -135
- package/runtime/scripts/hfs/rules/database-constants.d.mts +36 -0
- package/runtime/scripts/hfs/rules/database-constants.mjs +7 -7
- package/runtime/scripts/hfs/rules/database-migrations.d.mts +41 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +428 -388
- package/runtime/scripts/hfs/rules/database-plpgsql.d.mts +5 -0
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +278 -259
- package/runtime/scripts/hfs/rules/database-queue-sources.d.mts +4 -0
- package/runtime/scripts/hfs/rules/database-queue-sources.mjs +48 -43
- package/runtime/scripts/hfs/rules/database-queues.d.mts +15 -0
- package/runtime/scripts/hfs/rules/database-queues.mjs +211 -182
- package/runtime/scripts/hfs/rules/database-sql.d.mts +16 -0
- package/runtime/scripts/hfs/rules/database-sql.mjs +394 -394
- package/runtime/scripts/hfs/rules/database-transaction.d.mts +3 -0
- package/runtime/scripts/hfs/rules/database-transaction.mjs +25 -27
- package/runtime/scripts/hfs/rules/database.d.mts +22 -0
- package/runtime/scripts/hfs/rules/database.mjs +42 -58
- package/runtime/scripts/hfs/rules/deploy-scan.d.mts +10 -0
- package/runtime/scripts/hfs/rules/deploy-scan.mjs +177 -168
- package/runtime/scripts/hfs/rules/deploy-workflow.d.mts +3 -0
- package/runtime/scripts/hfs/rules/deploy-workflow.mjs +194 -171
- package/runtime/scripts/hfs/rules/deps.d.mts +6 -0
- package/runtime/scripts/hfs/rules/deps.mjs +61 -60
- package/runtime/scripts/hfs/rules/doc-fresh.d.mts +11 -0
- package/runtime/scripts/hfs/rules/doc-fresh.mjs +242 -223
- package/runtime/scripts/hfs/rules/docker-health.d.mts +9 -0
- package/runtime/scripts/hfs/rules/docker-health.mjs +222 -196
- package/runtime/scripts/hfs/rules/docker.d.mts +13 -0
- package/runtime/scripts/hfs/rules/docker.mjs +234 -208
- package/runtime/scripts/hfs/rules/edition.d.mts +18 -0
- package/runtime/scripts/hfs/rules/edition.mjs +103 -101
- package/runtime/scripts/hfs/rules/env-parity.d.mts +25 -0
- package/runtime/scripts/hfs/rules/env-parity.mjs +278 -252
- package/runtime/scripts/hfs/rules/env-value-shape.d.mts +21 -0
- package/runtime/scripts/hfs/rules/env-value-shape.mjs +82 -67
- package/runtime/scripts/hfs/rules/event-bus.d.mts +17 -0
- package/runtime/scripts/hfs/rules/event-bus.mjs +145 -133
- package/runtime/scripts/hfs/rules/event-tables.d.mts +7 -0
- package/runtime/scripts/hfs/rules/event-tables.mjs +77 -75
- package/runtime/scripts/hfs/rules/fe-build-args.d.mts +10 -0
- package/runtime/scripts/hfs/rules/fe-build-args.mjs +116 -99
- package/runtime/scripts/hfs/rules/fe-contract-documents.d.mts +7 -0
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +121 -111
- package/runtime/scripts/hfs/rules/fe-no-tests.d.mts +9 -0
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +37 -36
- package/runtime/scripts/hfs/rules/fe-stack.d.mts +23 -0
- package/runtime/scripts/hfs/rules/fe-stack.mjs +108 -105
- package/runtime/scripts/hfs/rules/feature-files.d.mts +10 -0
- package/runtime/scripts/hfs/rules/feature-files.mjs +14 -19
- package/runtime/scripts/hfs/rules/frontend-tree.d.mts +18 -0
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +105 -91
- package/runtime/scripts/hfs/rules/integration-specs.d.mts +7 -0
- package/runtime/scripts/hfs/rules/integration-specs.mjs +190 -177
- package/runtime/scripts/hfs/rules/kinds.d.mts +6 -0
- package/runtime/scripts/hfs/rules/kinds.mjs +88 -81
- package/runtime/scripts/hfs/rules/lexicon.d.mts +9 -0
- package/runtime/scripts/hfs/rules/lexicon.mjs +43 -40
- package/runtime/scripts/hfs/rules/lint-suppression.d.mts +5 -0
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +22 -20
- package/runtime/scripts/hfs/rules/lite-proof-lane.d.mts +9 -0
- package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +59 -58
- package/runtime/scripts/hfs/rules/lite-secrets.d.mts +12 -0
- package/runtime/scripts/hfs/rules/lite-secrets.mjs +60 -63
- package/runtime/scripts/hfs/rules/mcp-tools.d.mts +7 -0
- package/runtime/scripts/hfs/rules/mcp-tools.mjs +56 -41
- package/runtime/scripts/hfs/rules/monorepo.d.mts +17 -0
- package/runtime/scripts/hfs/rules/monorepo.mjs +153 -138
- package/runtime/scripts/hfs/rules/peer-integrations.d.mts +7 -0
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +39 -36
- package/runtime/scripts/hfs/rules/pipeline.d.mts +7 -0
- package/runtime/scripts/hfs/rules/pipeline.mjs +53 -53
- package/runtime/scripts/hfs/rules/proof-commands.d.mts +14 -0
- package/runtime/scripts/hfs/rules/proof-commands.mjs +52 -41
- package/runtime/scripts/hfs/rules/read.d.mts +7 -0
- package/runtime/scripts/hfs/rules/read.mjs +19 -15
- package/runtime/scripts/hfs/rules/release-provenance.d.mts +12 -0
- package/runtime/scripts/hfs/rules/release-provenance.mjs +196 -159
- package/runtime/scripts/hfs/rules/repo-local-checks.d.mts +5 -0
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +19 -17
- package/runtime/scripts/hfs/rules/rule-types.d.mts +40 -0
- package/runtime/scripts/hfs/rules/rule-types.mjs +4 -0
- package/runtime/scripts/hfs/rules/saga-delivery.d.mts +2 -0
- package/runtime/scripts/hfs/rules/saga-delivery.mjs +58 -50
- package/runtime/scripts/hfs/rules/saga.d.mts +7 -0
- package/runtime/scripts/hfs/rules/saga.mjs +151 -145
- package/runtime/scripts/hfs/rules/script-hygiene.d.mts +11 -0
- package/runtime/scripts/hfs/rules/script-hygiene.mjs +112 -95
- package/runtime/scripts/hfs/rules/secret-custody-only.d.mts +7 -0
- package/runtime/scripts/hfs/rules/secret-custody-only.mjs +29 -35
- package/runtime/scripts/hfs/rules/secrets.d.mts +29 -0
- package/runtime/scripts/hfs/rules/secrets.mjs +45 -44
- package/runtime/scripts/hfs/rules/services.d.mts +7 -0
- package/runtime/scripts/hfs/rules/services.mjs +148 -148
- package/runtime/scripts/hfs/rules/spec-placement.d.mts +8 -0
- package/runtime/scripts/hfs/rules/spec-placement.mjs +12 -13
- package/runtime/scripts/hfs/rules/stack-tooling.d.mts +11 -0
- package/runtime/scripts/hfs/rules/stack-tooling.mjs +45 -52
- package/runtime/scripts/hfs/rules/stacks-auth-cookie-domain.d.mts +30 -0
- package/runtime/scripts/hfs/rules/stacks-auth-cookie-domain.mjs +149 -0
- package/runtime/scripts/hfs/rules/stacks-deploy.d.mts +7 -0
- package/runtime/scripts/hfs/rules/stacks-deploy.mjs +32 -42
- package/runtime/scripts/hfs/rules/stacks-ingress.d.mts +16 -0
- package/runtime/scripts/hfs/rules/stacks-ingress.mjs +62 -64
- package/runtime/scripts/hfs/rules/stacks-lite-backing.d.mts +17 -0
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -116
- package/runtime/scripts/hfs/rules/stacks-release.d.mts +26 -0
- package/runtime/scripts/hfs/rules/stacks-release.mjs +335 -309
- package/runtime/scripts/hfs/rules/stacks-runtime.d.mts +31 -0
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +130 -124
- package/runtime/scripts/hfs/rules/stacks-swarm.d.mts +5 -0
- package/runtime/scripts/hfs/rules/stacks-swarm.mjs +366 -344
- package/runtime/scripts/hfs/rules/stacks.d.mts +7 -0
- package/runtime/scripts/hfs/rules/stacks.mjs +37 -38
- package/runtime/scripts/hfs/rules/staged-specs.d.mts +10 -0
- package/runtime/scripts/hfs/rules/staged-specs.mjs +14 -18
- package/runtime/scripts/hfs/rules/supply-chain.d.mts +10 -0
- package/runtime/scripts/hfs/rules/supply-chain.mjs +143 -150
- package/runtime/scripts/hfs/rules/test-topology.d.mts +5 -0
- package/runtime/scripts/hfs/rules/test-topology.mjs +23 -21
- package/runtime/scripts/hfs/rules/text-integrity.d.mts +9 -0
- package/runtime/scripts/hfs/rules/text-integrity.mjs +47 -43
- package/runtime/scripts/hfs/rules/unused-symbol-flow.d.mts +22 -0
- package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +226 -204
- package/runtime/scripts/hfs/rules/unused-symbol-ledger.d.mts +7 -0
- package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +70 -49
- package/runtime/scripts/hfs/rules/unused-symbol-properties.d.mts +12 -0
- package/runtime/scripts/hfs/rules/unused-symbol-properties.mjs +141 -126
- package/runtime/scripts/hfs/rules/unused-symbol-translators.d.mts +8 -0
- package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +160 -140
- package/runtime/scripts/hfs/rules/unused-symbol-values.d.mts +9 -0
- package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +138 -125
- package/runtime/scripts/hfs/rules/unused-symbol.d.mts +11 -0
- package/runtime/scripts/hfs/rules/unused-symbol.mjs +283 -253
- package/runtime/scripts/hfs/rules/workflow-contexts.d.mts +8 -0
- package/runtime/scripts/hfs/rules/workflow-contexts.mjs +79 -67
- package/runtime/scripts/hfs/runtime-rules/git-triggers.d.mts +6 -0
- package/runtime/scripts/hfs/runtime-rules/git-triggers.mjs +36 -35
- package/runtime/scripts/hfs/runtime-rules/hook-shape.d.mts +10 -0
- package/runtime/scripts/hfs/runtime-rules/hook-shape.mjs +45 -42
- package/runtime/scripts/hfs/runtime-rules/source-ast.d.mts +39 -0
- package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +130 -116
- package/runtime/scripts/hfs/runtime-rules/workflow-source.d.mts +5 -0
- package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +8 -7
- package/runtime/scripts/hfs/sealed-members.d.mts +22 -0
- package/runtime/scripts/hfs/sealed-members.mjs +79 -75
- package/runtime/scripts/hfs/secret-bootstrap-crypto.d.mts +12 -0
- package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +151 -167
- package/runtime/scripts/hfs/secret-document.d.mts +16 -0
- package/runtime/scripts/hfs/secret-document.mjs +59 -75
- package/runtime/scripts/hfs/secret-edit.d.mts +14 -0
- package/runtime/scripts/hfs/secret-edit.mjs +115 -135
- package/runtime/scripts/hfs/secret-errors.d.mts +4 -0
- package/runtime/scripts/hfs/secret-errors.mjs +19 -16
- package/runtime/scripts/hfs/secret-files.d.mts +20 -0
- package/runtime/scripts/hfs/secret-files.mjs +103 -112
- package/runtime/scripts/hfs/secret-init.d.mts +108 -0
- package/runtime/scripts/hfs/secret-init.mjs +304 -346
- package/runtime/scripts/hfs/secret-output.d.mts +18 -0
- package/runtime/scripts/hfs/secret-output.mjs +52 -80
- package/runtime/scripts/hfs/secret-prompt.d.mts +39 -0
- package/runtime/scripts/hfs/secret-prompt.mjs +108 -93
- package/runtime/scripts/hfs/secret-sops.d.mts +29 -0
- package/runtime/scripts/hfs/secret-sops.mjs +161 -199
- package/runtime/scripts/hfs/secret-standard.d.mts +89 -0
- package/runtime/scripts/hfs/secret-standard.mjs +300 -264
- package/runtime/scripts/hfs/secret-store.d.mts +36 -0
- package/runtime/scripts/hfs/secret-store.mjs +120 -136
- package/runtime/scripts/hfs/secret.d.mts +40 -0
- package/runtime/scripts/hfs/secret.mjs +254 -222
- package/runtime/scripts/hfs/slot-app-view.d.mts +11 -0
- package/runtime/scripts/hfs/slot-app-view.mjs +69 -74
- package/runtime/scripts/hfs/slot-classify.d.mts +75 -0
- package/runtime/scripts/hfs/slot-classify.mjs +42 -38
- package/runtime/scripts/hfs/slot-errors.d.mts +6 -0
- package/runtime/scripts/hfs/slot-errors.mjs +8 -7
- package/runtime/scripts/hfs/slot-imports.d.mts +8 -0
- package/runtime/scripts/hfs/slot-imports.mjs +70 -63
- package/runtime/scripts/hfs/slot-manifest-shape.d.mts +9 -0
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +74 -52
- package/runtime/scripts/hfs/slot-match.d.mts +50 -0
- package/runtime/scripts/hfs/slot-match.mjs +122 -111
- package/runtime/scripts/hfs/slot-path.d.mts +1 -0
- package/runtime/scripts/hfs/slot-path.mjs +0 -2
- package/runtime/scripts/hfs/slot-required.d.mts +11 -0
- package/runtime/scripts/hfs/slot-required.mjs +65 -59
- package/runtime/scripts/hfs/slot-semantic-problems.d.mts +9 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +138 -112
- package/runtime/scripts/hfs/slot-side-problems.d.mts +3 -0
- package/runtime/scripts/hfs/slot-side-problems.mjs +23 -19
- package/runtime/scripts/hfs/slots.d.mts +188 -0
- package/runtime/scripts/hfs/slots.mjs +175 -152
- package/runtime/scripts/hfs/sql/pg-parse.d.mts +9 -0
- package/runtime/scripts/hfs/sql/pg-parse.mjs +25 -25
- package/runtime/scripts/hfs/stack-config-kit.d.mts +22 -0
- package/runtime/scripts/hfs/stack-config-kit.mjs +21 -17
- package/runtime/scripts/hfs/stack-deploy-config.d.mts +155 -0
- package/runtime/scripts/hfs/stack-deploy-config.mjs +345 -273
- package/runtime/scripts/hfs/stack-ingress-config.d.mts +74 -0
- package/runtime/scripts/hfs/stack-ingress-config.mjs +211 -177
- package/runtime/scripts/hfs/stacks-gate.d.mts +52 -0
- package/runtime/scripts/hfs/stacks-gate.mjs +557 -397
- package/runtime/scripts/hfs/starcistacks-runtime.d.mts +7 -0
- package/runtime/scripts/hfs/starcistacks-runtime.mjs +51 -40
- package/runtime/scripts/hfs/test-world-slot.d.mts +2 -0
- package/runtime/scripts/hfs/test-world-slot.mjs +0 -1
- package/runtime/scripts/hfs/trailing-slashes.d.mts +2 -0
- package/runtime/scripts/hfs/trailing-slashes.mjs +4 -4
- package/runtime/scripts/hfs/tree-findings.d.mts +9 -0
- package/runtime/scripts/hfs/tree-findings.mjs +18 -17
- package/runtime/scripts/hfs/tree.d.mts +35 -0
- package/runtime/scripts/hfs/tree.mjs +56 -46
- package/runtime/scripts/hfs/typescript-programs.d.mts +36 -0
- package/runtime/scripts/hfs/typescript-programs.mjs +66 -69
- package/runtime/scripts/hfs/view.d.mts +45 -0
- package/runtime/scripts/hfs/view.mjs +38 -40
- package/runtime/scripts/lib/bypass-markers.d.mts +15 -0
- package/runtime/scripts/lib/bypass-markers.mjs +210 -197
- package/runtime/scripts/lib/cli-arg.d.mts +67 -0
- package/runtime/scripts/lib/cli-arg.mjs +59 -63
- package/runtime/scripts/lib/command-tokens.d.mts +11 -0
- package/runtime/scripts/lib/command-tokens.mjs +77 -78
- package/runtime/scripts/lib/compose-values.d.mts +6 -0
- package/runtime/scripts/lib/compose-values.mjs +7 -7
- package/runtime/scripts/lib/css-rules.d.mts +36 -0
- package/runtime/scripts/lib/css-rules.mjs +181 -171
- package/runtime/scripts/lib/deployment-findings.d.mts +9 -0
- package/runtime/scripts/lib/deployment-findings.mjs +4 -4
- package/runtime/scripts/lib/dockerfile.d.mts +39 -0
- package/runtime/scripts/lib/dockerfile.mjs +131 -114
- package/runtime/scripts/lib/dom-tree.d.mts +7 -0
- package/runtime/scripts/lib/dom-tree.mjs +3 -3
- package/runtime/scripts/lib/env.d.mts +12 -0
- package/runtime/scripts/lib/env.mjs +3 -6
- package/runtime/scripts/lib/escape.d.mts +6 -0
- package/runtime/scripts/lib/escape.mjs +2 -6
- package/runtime/scripts/lib/event-contract.d.mts +21 -0
- package/runtime/scripts/lib/event-contract.mjs +73 -78
- package/runtime/scripts/lib/event-items.d.mts +20 -0
- package/runtime/scripts/lib/event-items.mjs +27 -15
- package/runtime/scripts/lib/example-refs.d.mts +69 -0
- package/runtime/scripts/lib/example-refs.mjs +145 -129
- package/runtime/scripts/lib/fs-kind.d.mts +12 -0
- package/runtime/scripts/lib/fs-kind.mjs +20 -10
- package/runtime/scripts/lib/git-hooks-dir.d.mts +15 -0
- package/runtime/scripts/lib/git-hooks-dir.mjs +16 -16
- package/runtime/scripts/lib/git.d.mts +24 -0
- package/runtime/scripts/lib/git.mjs +15 -16
- package/runtime/scripts/lib/glob.d.mts +4 -0
- package/runtime/scripts/lib/glob.mjs +23 -14
- package/runtime/scripts/lib/grammar-lint.d.mts +47 -0
- package/runtime/scripts/lib/grammar-lint.mjs +33 -37
- package/runtime/scripts/lib/grammar-vendor-parity-jsx.d.mts +40 -0
- package/runtime/scripts/lib/grammar-vendor-parity-jsx.mjs +285 -266
- package/runtime/scripts/lib/grammar-vendor-parity.d.mts +61 -0
- package/runtime/scripts/lib/grammar-vendor-parity.mjs +209 -216
- package/runtime/scripts/lib/graphql-contract.d.mts +53 -0
- package/runtime/scripts/lib/graphql-contract.mjs +201 -198
- package/runtime/scripts/lib/graphql-sdl.d.mts +66 -0
- package/runtime/scripts/lib/graphql-sdl.mjs +230 -205
- package/runtime/scripts/lib/hook-limits.d.mts +11 -0
- package/runtime/scripts/lib/hook-limits.mjs +0 -1
- package/runtime/scripts/lib/i18n.d.mts +48 -0
- package/runtime/scripts/lib/i18n.mjs +32 -35
- package/runtime/scripts/lib/in-order.d.mts +12 -0
- package/runtime/scripts/lib/in-order.mjs +57 -38
- package/runtime/scripts/lib/is-main.d.mts +6 -0
- package/runtime/scripts/lib/is-main.mjs +11 -4
- package/runtime/scripts/lib/js-lex.d.mts +32 -0
- package/runtime/scripts/lib/js-lex.mjs +196 -162
- package/runtime/scripts/lib/json-clone.d.mts +11 -0
- package/runtime/scripts/lib/json-clone.mjs +45 -36
- package/runtime/scripts/lib/json-schema.d.mts +2 -0
- package/runtime/scripts/lib/json-schema.mjs +127 -90
- package/runtime/scripts/lib/json.d.mts +16 -0
- package/runtime/scripts/lib/json.mjs +26 -14
- package/runtime/scripts/lib/language.d.mts +44 -0
- package/runtime/scripts/lib/language.mjs +133 -129
- package/runtime/scripts/lib/list.d.mts +17 -0
- package/runtime/scripts/lib/list.mjs +9 -14
- package/runtime/scripts/lib/loosening-budgets.d.mts +12 -0
- package/runtime/scripts/lib/loosening-budgets.mjs +35 -29
- package/runtime/scripts/lib/loosening-config.d.mts +64 -0
- package/runtime/scripts/lib/loosening-config.mjs +267 -236
- package/runtime/scripts/lib/loosening-state.d.mts +77 -0
- package/runtime/scripts/lib/loosening-state.mjs +134 -128
- package/runtime/scripts/lib/mutation-fence.d.mts +11 -0
- package/runtime/scripts/lib/mutation-fence.mjs +10 -5
- package/runtime/scripts/lib/normalize.d.mts +12 -0
- package/runtime/scripts/lib/normalize.mjs +11 -11
- package/runtime/scripts/lib/numeric-match.d.mts +2 -0
- package/runtime/scripts/lib/numeric-match.mjs +2 -2
- package/runtime/scripts/lib/package-at.d.mts +24 -0
- package/runtime/scripts/lib/package-at.mjs +34 -31
- package/runtime/scripts/lib/path-key.d.mts +76 -0
- package/runtime/scripts/lib/path-key.mjs +53 -38
- package/runtime/scripts/lib/pid-alive.d.mts +2 -0
- package/runtime/scripts/lib/pid-alive.mjs +10 -3
- package/runtime/scripts/lib/properties.d.mts +2 -0
- package/runtime/scripts/lib/properties.mjs +17 -11
- package/runtime/scripts/lib/provision-operation-schema.d.mts +4 -0
- package/runtime/scripts/lib/provision-operation-schema.mjs +4 -5
- package/runtime/scripts/lib/read-text.d.mts +12 -0
- package/runtime/scripts/lib/read-text.mjs +30 -17
- package/runtime/scripts/lib/redact.d.mts +52 -0
- package/runtime/scripts/lib/redact.mjs +135 -121
- package/runtime/scripts/lib/regex.d.mts +2 -0
- package/runtime/scripts/lib/regex.mjs +1 -2
- package/runtime/scripts/lib/roots.d.mts +32 -0
- package/runtime/scripts/lib/roots.mjs +5 -15
- package/runtime/scripts/lib/runtime-example.d.mts +1 -0
- package/runtime/scripts/lib/runtime-example.mjs +10 -8
- package/runtime/scripts/lib/same-text.d.mts +2 -0
- package/runtime/scripts/lib/same-text.mjs +0 -1
- package/runtime/scripts/lib/sealed-member.d.mts +16 -0
- package/runtime/scripts/lib/sealed-member.mjs +8 -13
- package/runtime/scripts/lib/secret-patterns.d.mts +30 -0
- package/runtime/scripts/lib/secret-patterns.mjs +175 -163
- package/runtime/scripts/lib/shell-source.d.mts +2 -0
- package/runtime/scripts/lib/shell-source.mjs +0 -1
- package/runtime/scripts/lib/sleep-sync.d.mts +2 -0
- package/runtime/scripts/lib/sleep-sync.mjs +5 -6
- package/runtime/scripts/lib/sops-envelope.d.mts +34 -0
- package/runtime/scripts/lib/sops-envelope.mjs +148 -120
- package/runtime/scripts/lib/source-phrases.d.mts +4 -0
- package/runtime/scripts/lib/source-phrases.mjs +13 -13
- package/runtime/scripts/lib/sql-ast.d.mts +13 -0
- package/runtime/scripts/lib/sql-ast.mjs +19 -18
- package/runtime/scripts/lib/sqlite.d.mts +21 -0
- package/runtime/scripts/lib/sqlite.mjs +3 -9
- package/runtime/scripts/lib/stack-declaration.d.mts +51 -0
- package/runtime/scripts/lib/stack-declaration.mjs +27 -20
- package/runtime/scripts/lib/stack-services.d.mts +73 -0
- package/runtime/scripts/lib/stack-services.mjs +128 -129
- package/runtime/scripts/lib/starciwork-boundary.d.mts +10 -0
- package/runtime/scripts/lib/starciwork-boundary.mjs +98 -92
- package/runtime/scripts/lib/swarm-stack.d.mts +24 -0
- package/runtime/scripts/lib/swarm-stack.mjs +112 -100
- package/runtime/scripts/lib/trim-end.d.mts +2 -0
- package/runtime/scripts/lib/trim-end.mjs +4 -4
- package/runtime/scripts/lib/ts-analysis.d.mts +24 -0
- package/runtime/scripts/lib/ts-analysis.mjs +28 -26
- package/runtime/scripts/lib/ts-ast.d.mts +36 -0
- package/runtime/scripts/lib/ts-ast.mjs +36 -42
- package/runtime/scripts/lib/walk.d.mts +19 -0
- package/runtime/scripts/lib/walk.mjs +33 -27
- package/runtime/scripts/lib/work-edition.d.mts +10 -0
- package/runtime/scripts/lib/work-edition.mjs +29 -21
- package/runtime/scripts/lib/workflow-events.d.mts +2 -0
- package/runtime/scripts/lib/workflow-events.mjs +5 -4
- package/runtime/scripts/lib/worktree-exclude.d.mts +16 -0
- package/runtime/scripts/lib/worktree-exclude.mjs +6 -8
- package/runtime/scripts/lib/yaml-cached.d.mts +2 -0
- package/runtime/scripts/lib/yaml-cached.mjs +4 -4
- package/runtime/scripts/machine/artifact-hold.d.mts +26 -0
- package/runtime/scripts/machine/artifact-hold.mjs +22 -23
- package/runtime/scripts/work/asset-slot.d.mts +96 -0
- package/runtime/scripts/work/asset-slot.mjs +201 -175
- package/runtime/scripts/work/draw/draw-dna-anatomy.d.mts +30 -0
- package/runtime/scripts/work/draw/draw-dna-anatomy.mjs +40 -36
- package/runtime/scripts/work/draw/draw-dna-findings.d.mts +12 -0
- package/runtime/scripts/work/draw/draw-dna-findings.mjs +240 -206
- package/runtime/scripts/work/draw/draw-dna-selector.d.mts +7 -0
- package/runtime/scripts/work/draw/draw-dna-selector.mjs +28 -28
- package/runtime/scripts/work/draw/draw-dna.d.mts +191 -0
- package/runtime/scripts/work/draw/draw-dna.mjs +283 -249
- package/runtime/scripts/work/grammar-proposal.d.mts +83 -0
- package/runtime/scripts/work/grammar-proposal.mjs +215 -178
- package/runtime/scripts/work/work-io.d.mts +62 -0
- package/runtime/scripts/work/work-io.mjs +82 -63
- package/scaffold/add-app.d.mts +15 -0
- package/scaffold/add-app.mjs +111 -163
- package/scaffold/add-cli-lite.d.mts +8 -0
- package/scaffold/add-cli-lite.mjs +173 -207
- package/scaffold/add-compose.d.mts +44 -0
- package/scaffold/add-compose.mjs +62 -62
- package/scaffold/add-database-lite.d.mts +2 -0
- package/scaffold/add-database-lite.mjs +10 -10
- package/scaffold/add-fe-layout.d.mts +16 -0
- package/scaffold/add-fe-layout.mjs +23 -23
- package/scaffold/add-fe-overlay.d.mts +17 -0
- package/scaffold/add-fe-overlay.mjs +76 -78
- package/scaffold/add-fe-page.d.mts +41 -0
- package/scaffold/add-fe-page.mjs +84 -89
- package/scaffold/add-fe-unit.d.mts +59 -0
- package/scaffold/add-fe-unit.mjs +205 -197
- package/scaffold/add-inbox-lite.d.mts +17 -0
- package/scaffold/add-inbox-lite.mjs +209 -264
- package/scaffold/add-plan.d.mts +68 -0
- package/scaffold/add-plan.mjs +164 -149
- package/scaffold/add-queue-lite.d.mts +34 -0
- package/scaffold/add-queue-lite.mjs +267 -267
- package/scaffold/add-table.d.mts +16 -0
- package/scaffold/add-table.mjs +405 -442
- package/scaffold/add.d.mts +34 -0
- package/scaffold/add.mjs +260 -246
- package/scaffold/app.d.mts +198 -0
- package/scaffold/app.mjs +413 -391
- package/scaffold/edition-gate.d.mts +49 -0
- package/scaffold/edition-gate.mjs +19 -29
- package/scaffold/image.d.mts +4 -0
- package/scaffold/image.mjs +12 -12
- package/scaffold/lite-exports.d.mts +15 -0
- package/scaffold/lite-exports.mjs +262 -255
- package/scaffold/pattern-file-slots.d.mts +7 -0
- package/scaffold/pattern-file-slots.mjs +29 -24
- package/scaffold/service.d.mts +60 -0
- package/scaffold/service.mjs +297 -279
- package/scaffold/test-world.d.mts +31 -0
- package/scaffold/test-world.mjs +71 -78
- package/scaffold/yaml-block-edit.d.mts +23 -0
- package/scaffold/yaml-block-edit.mjs +95 -87
- package/scripts/sync-bundles.d.mts +31 -0
- package/src/fixtures/bypass-index.d.mts +4 -0
- package/src/fixtures/hfs-stack-tooling/passing.d.mts +8 -0
- package/src/fixtures/hfs-stack-tooling/violating.d.mts +19 -0
- package/src/fixtures/hfs-stacks-full/model-unavailable.d.mts +10 -0
- package/src/fixtures/hfs-stacks-full/passing.d.mts +19 -0
- package/src/fixtures/hfs-stacks-full/violating.d.mts +10 -0
- package/src/fixtures/loosening/adversarial.d.mts +4 -0
- package/src/fixtures/loosening/forms.d.mts +16 -0
- package/src/fixtures/loosening/swallows.d.mts +5 -0
- package/src/helpers/hfs-arch-fixture.d.mts +47 -0
- package/src/helpers/hfs-cli-fixture.d.mts +65 -0
- package/src/helpers/hfs-hfs-docker-rules-fixture.d.mts +5 -0
- package/src/helpers/hfs-secret-fixture.d.mts +19 -0
- package/src/helpers/pre-push-fixture.d.mts +21 -0
- package/src/helpers/stack-compose.d.mts +6 -0
- package/src/helpers/swarm-stack-fixture.d.mts +18 -0
- package/src/helpers/tmpdir.d.mts +2 -0
- package/src/main.d.mts +58 -0
- package/src/main.mjs +493 -348
- package/stack/age-install.d.mts +13 -0
- package/stack/age-install.mjs +28 -25
- package/stack/backup.d.mts +11 -0
- package/stack/backup.mjs +76 -69
- package/stack/build-args.d.mts +7 -0
- package/stack/build-args.mjs +22 -23
- package/stack/build.d.mts +8 -0
- package/stack/build.mjs +138 -119
- package/stack/cli.d.mts +55 -0
- package/stack/cli.mjs +187 -130
- package/stack/config.d.mts +197 -0
- package/stack/config.mjs +47 -33
- package/stack/context.d.mts +122 -0
- package/stack/context.mjs +63 -65
- package/stack/converge.d.mts +13 -0
- package/stack/converge.mjs +73 -69
- package/stack/deploy.d.mts +88 -0
- package/stack/deploy.mjs +272 -224
- package/stack/digest.d.mts +8 -0
- package/stack/digest.mjs +1 -6
- package/stack/docker-transport.d.mts +5 -0
- package/stack/docker-transport.mjs +101 -93
- package/stack/edge-alias.d.mts +42 -0
- package/stack/edge-alias.mjs +62 -60
- package/stack/errors.d.mts +18 -0
- package/stack/errors.mjs +8 -10
- package/stack/image-scan.d.mts +20 -0
- package/stack/image-scan.mjs +114 -101
- package/stack/ingress-create.d.mts +4 -0
- package/stack/ingress-create.mjs +82 -86
- package/stack/ingress-publish.d.mts +6 -0
- package/stack/ingress-publish.mjs +194 -182
- package/stack/ingress.d.mts +55 -0
- package/stack/ingress.mjs +534 -524
- package/stack/jobs.d.mts +12 -0
- package/stack/jobs.mjs +35 -34
- package/stack/live-restore.d.mts +35 -0
- package/stack/live-restore.mjs +120 -125
- package/stack/post-deploy.d.mts +8 -0
- package/stack/post-deploy.mjs +25 -25
- package/stack/prepare.d.mts +24 -0
- package/stack/prepare.mjs +213 -200
- package/stack/private-file.d.mts +5 -0
- package/stack/private-file.mjs +9 -9
- package/stack/provision.d.mts +25 -0
- package/stack/provision.mjs +64 -63
- package/stack/prune.d.mts +11 -0
- package/stack/prune.mjs +38 -27
- package/stack/release-store.d.mts +28 -0
- package/stack/release-store.mjs +68 -67
- package/stack/remote.d.mts +83 -0
- package/stack/remote.mjs +176 -162
- package/stack/render.d.mts +14 -0
- package/stack/render.mjs +132 -124
- package/stack/retire.d.mts +9 -0
- package/stack/retire.mjs +31 -34
- package/stack/sealed.d.mts +46 -0
- package/stack/sealed.mjs +78 -82
- package/stack/server-key.d.mts +67 -0
- package/stack/server-key.mjs +217 -203
- package/stack/ssh-session.d.mts +29 -0
- package/stack/ssh-session.mjs +133 -123
- package/stack/target-binding.d.mts +21 -0
- package/stack/target-binding.mjs +68 -44
- package/stack/task.d.mts +12 -0
- package/stack/task.mjs +26 -31
- package/stack/urgent-floor.d.mts +106 -0
- package/stack/urgent-floor.mjs +211 -160
- package/stack/urgent-release.d.mts +38 -0
- package/stack/urgent-release.mjs +26 -22
- package/stack/verify.d.mts +14 -0
- package/stack/verify.mjs +75 -65
- package/sync/ci-commands.d.mts +26 -0
- package/sync/ci-commands.mjs +181 -182
- package/sync/cli.d.mts +8 -0
- package/sync/cli.mjs +9 -9
- package/sync/format.d.mts +38 -0
- package/sync/format.mjs +30 -24
- package/sync/hygiene.d.mts +23 -0
- package/sync/hygiene.mjs +113 -99
- package/sync/index.d.mts +237 -0
- package/sync/index.mjs +457 -436
- package/sync/managed.d.mts +23 -0
- package/sync/managed.mjs +104 -94
- package/sync/quality-services.d.mts +35 -0
- package/sync/quality-services.mjs +48 -41
- package/sync/readme-badges.d.mts +13 -0
- package/sync/readme-badges.mjs +45 -44
- package/sync/sonar-key.d.mts +31 -0
- package/sync/sonar-key.mjs +29 -28
- package/sync/stack-images.d.mts +36 -0
- package/sync/stack-images.mjs +85 -95
- package/sync/ts-strict.d.mts +14 -0
- package/sync/ts-strict.mjs +43 -34
- package/templates/app/ci-workflows/github/workflows/ci.yml +1 -1
- package/templates/app/ci-workflows/github/workflows/images.yml +1 -1
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +2 -2
- package/templates/app/ci-workflows/github/workflows/stack-key-restore.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +2 -2
- package/templates/app/ci-workflows-lite/github/workflows/stack-key-restore.yml +1 -1
- package/templates/app/codegen-lite/scripts/codegen.mjs +1 -1
- package/templates/app/db-bootstrap/db/bootstrap.sql +1 -1
- package/templates/app/db-image/starcistacks/images/db/Dockerfile +1 -1
- package/templates/app/hooks/husky/pre-commit +2 -2
- package/templates/app/package-scripts/db.json +3 -3
- package/templates/app/package-scripts/package.json +1 -1
- package/templates/app/quality-config/sonar-project.properties +1 -1
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +2 -2
- package/templates/app/skeleton/.starcistacks/production/README.md +4 -4
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +8 -3
- package/templates/app/skeleton/README.md +1 -1
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +2 -2
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +6 -2
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +3 -2
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +5 -5
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +7 -2
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +3 -2
- package/templates/app/skeleton-lite/README.md +2 -2
- package/templates/be/skeleton/apps/cli/src/compose/platform.ts +1 -1
- package/templates/be/skeleton/apps/cli/src/main.ts +6 -4
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/database/abstract.entity.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +4 -3
- package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/logging/logging.options.ts +5 -2
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.spec.ts +40 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts +48 -1
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +5 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +5 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +7 -3
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +7 -1
- package/templates/be/table/entity.ts.tpl +4 -15
- package/upgrade/check.d.mts +9 -0
- package/upgrade/check.mjs +8 -8
- package/upgrade/file-state.d.mts +6 -0
- package/upgrade/file-state.mjs +32 -40
- package/upgrade/index.d.mts +13 -0
- package/upgrade/index.mjs +122 -151
- package/vendor-types.d.mts +8 -0
|
@@ -37,29 +37,32 @@ import path from 'node:path';
|
|
|
37
37
|
import crypto from 'node:crypto';
|
|
38
38
|
import { createRequire } from 'node:module';
|
|
39
39
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
40
|
-
import { putBlob as storeBlob, blobPath, getBlob } from './blob.mjs';
|
|
41
|
-
import {
|
|
40
|
+
import { putBlob as storeBlob, blobPath, getBlob } from './blob.mjs';
|
|
41
|
+
import { EVENT_LIMITS } from './event-compact.mjs';
|
|
42
|
+
import { redactBytes, redactData, redactText } from '../../scripts/lib/redact.mjs';
|
|
43
|
+
import { isMain } from '../../scripts/lib/is-main.mjs';
|
|
42
44
|
import { isSpecRun, readEnv } from '../../scripts/lib/env.mjs';
|
|
43
|
-
import { pathKey } from '../../scripts/lib/path-key.mjs';
|
|
45
|
+
import { pathKey } from '../../scripts/lib/path-key.mjs';
|
|
46
|
+
import { pidAlive } from '../../scripts/lib/pid-alive.mjs';
|
|
47
|
+
import { releaseObservedProvisionLock, renewProvisionLock, releaseProvisionLock } from './provision-lock.mjs';
|
|
44
48
|
import { insertRowWith } from '../../scripts/lib/sqlite.mjs';
|
|
45
49
|
import { need as refuseUnless } from '../refuse.mjs';
|
|
46
50
|
import { sha256 } from '../digest.mjs';
|
|
47
51
|
import { starciLocalRoot } from '../runtime-root.mjs';
|
|
48
52
|
import { machineSchemaMethods } from './machine-schema.mjs';
|
|
49
|
-
import { machineConnectionMethods, corruptDiagnostic, MACHINE_BUSY_TIMEOUT_MS, MACHINE_CORRUPT_CODE, CORRUPT_RETRY_DELAYS_MS,
|
|
50
|
-
waitForRetry, isCorruptError, errText } from './machine-connection.mjs';
|
|
53
|
+
import { machineConnectionMethods, corruptDiagnostic, MACHINE_BUSY_TIMEOUT_MS, MACHINE_CORRUPT_CODE, CORRUPT_RETRY_DELAYS_MS, waitForRetry, isCorruptError, errText } from './machine-connection.mjs';
|
|
51
54
|
export { MACHINE_BUSY_CODE, isMachineBusy, isBusyError } from './machine-connection.mjs';
|
|
52
55
|
import { providerReservationMethods } from './provider-reservations.mjs';
|
|
53
56
|
import { tempRoot } from '../temp-root.mjs';
|
|
54
|
-
|
|
55
57
|
const require = createRequire(import.meta.url);
|
|
58
|
+
/** The reads an observer binds. */
|
|
59
|
+
const OBSERVER_READS = () => ({ listLedgers, services, seats, providerHealth, logs, latestMetrics, throttleState, poolBackoff, quotas, hostLeases, budgets });
|
|
56
60
|
const ENGINE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
57
61
|
export const MACHINE_SCHEMA = 'starci/machine@1';
|
|
58
62
|
export const MACHINE_VERSION = 3;
|
|
59
63
|
/** Test seam: STARCI_MACHINE_BUSY_TIMEOUT_MS (a positive integer) replaces the writer's busy_timeout; unset in production. */
|
|
60
64
|
export const INIT_SQL_FILE = path.join(ENGINE_DIR, 'schema', 'machine.sql');
|
|
61
65
|
export const CONTROLLERS = Object.freeze(['job', 'workflow', 'resource', 'host', 'gc', 'workers', 'learning']);
|
|
62
|
-
|
|
63
66
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
64
67
|
// Paths
|
|
65
68
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
@@ -75,66 +78,97 @@ export { LOCAL_ROOT_ENV, starciLocalRoot } from '../runtime-root.mjs';
|
|
|
75
78
|
export const localProjectsRoot = (env = process.env) => path.join(starciLocalRoot(env), 'projects');
|
|
76
79
|
/** The runtime.sqlite of one ledger (decision Q1): <runtime root>/.runtime/projects/<ledger_id>/runtime.sqlite. */
|
|
77
80
|
const projectLedgerFile = (ledgerId, env = process.env) => {
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
if (!/^[A-Za-z0-9-]{8,64}$/.test(String(ledgerId ?? '')))
|
|
82
|
+
throw new Error(`projectLedgerFile needs a ledger id, got ${ledgerId}`);
|
|
83
|
+
return path.join(localProjectsRoot(env), String(ledgerId), 'runtime.sqlite');
|
|
80
84
|
};
|
|
81
85
|
/** The explicit test registry: a machine.sqlite that replaces the host's for this process tree (e2e/setup/isolated-registry.mjs). */
|
|
82
86
|
export const TEST_REGISTRY_ENV = 'STARCI_TEST_MACHINE_FILE';
|
|
83
87
|
const normDir = (file) => pathKey(file, { fold: true });
|
|
84
|
-
const tempDirsOf = (env = process.env) => [...new Set([os.tmpdir(), tempRoot({ env }), env.TEMP, env.TMP].filter(Boolean)
|
|
85
|
-
|
|
88
|
+
const tempDirsOf = (env = process.env) => [...new Set([os.tmpdir(), tempRoot({ env }), env.TEMP, env.TMP].filter((dir) => Boolean(dir))
|
|
89
|
+
.flatMap((dir) => { const out = [normDir(dir)]; try {
|
|
90
|
+
out.push(normDir(fs.realpathSync.native(dir)));
|
|
91
|
+
}
|
|
92
|
+
catch { /* missing */ } return out; }))].filter((dir) => !/^(?:[a-z]:)?$/.test(dir));
|
|
86
93
|
/** True when `file` sits under the OS temp directory or the configured temp root, as written or as its realpath. */
|
|
87
94
|
export function isUnderTempDir(file, { env = process.env, tempDirs = tempDirsOf(env) } = {}) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
if (typeof file !== 'string' || !file)
|
|
96
|
+
return false;
|
|
97
|
+
const forms = [normDir(file)];
|
|
98
|
+
try {
|
|
99
|
+
forms.push(normDir(fs.realpathSync.native(file)));
|
|
100
|
+
}
|
|
101
|
+
catch { /* missing */ }
|
|
102
|
+
return forms.some((form) => tempDirs.map(normDir).some((dir) => form.startsWith(`${dir}/`)));
|
|
92
103
|
}
|
|
93
104
|
/**
|
|
94
105
|
* machine.sqlite for `env`: TEST_REGISTRY_ENV when set; else <starciLocalRoot>/machine.sqlite (beside projects/) — except inside a node --test
|
|
95
106
|
* process tree whose runtime root is not under the temp directory, which gets a shared temp registry instead.
|
|
96
107
|
*/
|
|
97
108
|
export const machineFileFor = (env = process.env) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
if (env[TEST_REGISTRY_ENV])
|
|
110
|
+
return path.resolve(env[TEST_REGISTRY_ENV]);
|
|
111
|
+
const file = path.join(starciLocalRoot(env), 'machine.sqlite');
|
|
112
|
+
if (isSpecRun(env) && !isUnderTempDir(file, { env }))
|
|
113
|
+
return path.join(tempRoot({ env }), 'starci-test-registry', 'machine.sqlite');
|
|
114
|
+
return file;
|
|
102
115
|
};
|
|
103
|
-
|
|
104
116
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
105
117
|
// Small helpers
|
|
106
118
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
107
|
-
|
|
119
|
+
function need(ok, message, code = 'STARCI_MACHINE_DB') { refuseUnless(ok, message, code); }
|
|
108
120
|
const hex = (bytes) => crypto.randomBytes(bytes).toString('hex');
|
|
109
121
|
export const newTraceId = () => hex(16);
|
|
110
122
|
export const newSpanId = () => hex(8);
|
|
111
123
|
const JSON_LIMIT = 65536;
|
|
112
|
-
const toJson = (value) => { if (value === undefined || value === null) {
|
|
113
|
-
|
|
124
|
+
const toJson = (value) => { if (value === undefined || value === null) {
|
|
125
|
+
return null;
|
|
126
|
+
} if (typeof value !== 'string') {
|
|
127
|
+
return JSON.stringify(value);
|
|
128
|
+
} JSON.parse(value); return value; };
|
|
129
|
+
const parse = (text) => { if (text == null) {
|
|
130
|
+
return null;
|
|
131
|
+
} try {
|
|
132
|
+
return JSON.parse(text);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
} };
|
|
114
137
|
const int = (v) => (v === undefined || v === null || v === '' ? null : Math.trunc(Number(v)));
|
|
115
|
-
const bool = (v) => { if (v === undefined || v === null) {
|
|
138
|
+
const bool = (v) => { if (v === undefined || v === null) {
|
|
139
|
+
return null;
|
|
140
|
+
} return v ? 1 : 0; };
|
|
116
141
|
export { pidAlive };
|
|
117
142
|
const { openWithRetry, connectionState } = machineConnectionMethods({ reportIncident: corruptIncident });
|
|
118
143
|
/** Operational failures also persist their incident through a fresh writer or the deferred outbox. */
|
|
119
144
|
function corruptIncident(file, error, details) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const { DatabaseSync } = require('node:sqlite');
|
|
127
|
-
const db = new DatabaseSync(file, { timeout: MACHINE_BUSY_TIMEOUT_MS });
|
|
145
|
+
const out = corruptDiagnostic(file, error, details);
|
|
146
|
+
if (out === error)
|
|
147
|
+
return out;
|
|
148
|
+
const { retries, where } = details;
|
|
149
|
+
const row = { actor: 'harness', kind: 'machine-db.corrupt', level: 'error', msg: out.message,
|
|
150
|
+
data: { file, where, retries, error: errText(error), errcode: error?.errcode ?? null, quickCheck: out.quickCheck, pid: process.pid, sqlite: process.versions.sqlite, node: process.version } };
|
|
128
151
|
try {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
152
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
153
|
+
const db = new DatabaseSync(file, { timeout: MACHINE_BUSY_TIMEOUT_MS });
|
|
154
|
+
try {
|
|
155
|
+
db.exec('PRAGMA wal_autocheckpoint=0;');
|
|
156
|
+
db.prepare('INSERT INTO machine_logs(at,actor,level,kind,msg,data_json) VALUES(?,?,?,?,?,?)').run(Date.now(), row.actor, row.level, row.kind, row.msg, JSON.stringify(row.data));
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
db.close();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
try {
|
|
164
|
+
out.deferred = deferWrite({ op: 'log', args: [{ ...row, src: null }], file, error: e });
|
|
165
|
+
}
|
|
166
|
+
catch (error_) {
|
|
167
|
+
process.stderr.write(`[machine-db] INCIDENT could not be recorded: ${errText(error_)}\n`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
138
172
|
/**
|
|
139
173
|
* The rev of the runtime this process runs, as its callers know it: '<HEAD committer time, ms, 13 digits>:<short sha>' of
|
|
140
174
|
* the harness checkout, which the caller computes and passes in (`rev`,
|
|
@@ -143,57 +177,63 @@ function corruptIncident(file, error, details) {
|
|
|
143
177
|
* 'unknown' sorts before every real rev.
|
|
144
178
|
*/
|
|
145
179
|
export function runtimeRev() {
|
|
146
|
-
|
|
180
|
+
return readEnv('STARCI_RUNTIME_REV') ? String(readEnv('STARCI_RUNTIME_REV')) : 'unknown';
|
|
147
181
|
}
|
|
148
182
|
/** Order of two runtime revs: <0 when a is older than b (time prefix; anything unparsable is oldest). */
|
|
149
183
|
const compareRevs = (a, b) => { const t = (r) => (/^\d{13}:/.test(String(r ?? '')) ? Number(String(r).slice(0, 13)) : -1); return t(a) - t(b); };
|
|
150
|
-
|
|
151
184
|
const { checkSchema, createSchema } = machineSchemaMethods({ schema: MACHINE_SCHEMA, version: MACHINE_VERSION, initSqlFile: INIT_SQL_FILE, controllers: CONTROLLERS, runtimeRev, pragma });
|
|
152
185
|
const { openConnection } = machineOpenMethods({ checkSchema, createSchema, openWithRetry, corruptIncident });
|
|
153
|
-
|
|
154
186
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
155
187
|
// Generic row writes (column names are checked against the live table; table names are this module's constants)
|
|
156
188
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
157
189
|
const columnsCache = new WeakMap();
|
|
158
190
|
function columnsOf(db, table) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
byTable.
|
|
165
|
-
|
|
166
|
-
|
|
191
|
+
let byTable = columnsCache.get(db);
|
|
192
|
+
if (!byTable) {
|
|
193
|
+
byTable = new Map();
|
|
194
|
+
columnsCache.set(db, byTable);
|
|
195
|
+
}
|
|
196
|
+
if (!byTable.has(table)) {
|
|
197
|
+
const cols = db.prepare(`PRAGMA table_xinfo(${JSON.stringify(table)})`).all().filter((c) => !c.hidden).map((c) => c.name);
|
|
198
|
+
need(cols.length, `machine-db: unknown table ${table}`);
|
|
199
|
+
byTable.set(table, new Set(cols));
|
|
200
|
+
}
|
|
201
|
+
return byTable.get(table);
|
|
167
202
|
}
|
|
168
203
|
const cellOf = (key, value) => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
204
|
+
if (value === undefined)
|
|
205
|
+
return undefined;
|
|
206
|
+
if (value === null)
|
|
207
|
+
return null;
|
|
208
|
+
if (key.endsWith('_json'))
|
|
209
|
+
return toJson(value);
|
|
210
|
+
if (typeof value === 'boolean')
|
|
211
|
+
return value ? 1 : 0;
|
|
212
|
+
if (typeof value === 'object')
|
|
213
|
+
return JSON.stringify(value);
|
|
214
|
+
return value;
|
|
175
215
|
};
|
|
176
216
|
function rowCells(db, table, row) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
217
|
+
const cols = columnsOf(db, table);
|
|
218
|
+
const entries = Object.entries(row).map(([k, v]) => [k, cellOf(k, v)]).filter(([, v]) => v !== undefined);
|
|
219
|
+
for (const [k] of entries)
|
|
220
|
+
need(cols.has(k), `machine-db: ${table} has no column ${k}`);
|
|
221
|
+
return entries;
|
|
181
222
|
}
|
|
182
223
|
const insertRow = insertRowWith(rowCells);
|
|
183
224
|
function upsertRow(db, table, row, keys) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
225
|
+
const entries = rowCells(db, table, row);
|
|
226
|
+
const updates = entries.filter(([k]) => !keys.includes(k));
|
|
227
|
+
const sql = `INSERT INTO ${table}(${entries.map(([k]) => k).join(',')}) VALUES(${entries.map(() => '?').join(',')}) ON CONFLICT(${keys.join(',')}) DO `
|
|
228
|
+
+ (updates.length ? `UPDATE SET ${updates.map(([k]) => k + '=excluded.' + k).join(',')}` : 'NOTHING');
|
|
229
|
+
return db.prepare(sql).run(...entries.map(([, v]) => v));
|
|
189
230
|
}
|
|
190
231
|
function updateRow(db, table, set, where) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
232
|
+
const s = rowCells(db, table, set), w = rowCells(db, table, where);
|
|
233
|
+
need(s.length && w.length, `machine-db: update ${table} needs set and where`);
|
|
234
|
+
return db.prepare(`UPDATE ${table} SET ${s.map(([k]) => k + '=?').join(',')} WHERE ${w.map(([k]) => k + ' IS ?').join(' AND ')}`)
|
|
235
|
+
.run(...s.map(([, v]) => v), ...w.map(([, v]) => v));
|
|
195
236
|
}
|
|
196
|
-
|
|
197
237
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
198
238
|
// The handle
|
|
199
239
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
@@ -203,173 +243,263 @@ function updateRow(db, table, set, where) {
|
|
|
203
243
|
* the engine_leader row names it (header).
|
|
204
244
|
*/
|
|
205
245
|
export function openMachine({ file = null, env = process.env, now = Date.now, checkpointer = false, tempDirs = null } = {}) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
246
|
+
const resolved = path.resolve(file ?? machineFileFor(env));
|
|
247
|
+
const live = !env[TEST_REGISTRY_ENV] && !isUnderTempDir(resolved, { env, tempDirs: tempDirs ?? tempDirsOf(env) });
|
|
248
|
+
return makeHandle(() => openConnection(resolved, { env, now }), { file: resolved, env, now, live, readOnly: false, checkpointer, tempDirs: tempDirs ?? tempDirsOf(env) });
|
|
209
249
|
}
|
|
210
250
|
/** Open machine.sqlite read-only (query_only); null when the file does not exist yet. */
|
|
211
251
|
export function openMachineReader({ file = null, env = process.env, now = Date.now } = {}) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
252
|
+
const resolved = path.resolve(file ?? machineFileFor(env));
|
|
253
|
+
if (!fs.existsSync(resolved))
|
|
254
|
+
return null;
|
|
255
|
+
return makeHandle(() => openConnection(resolved, { readOnly: true }), { file: resolved, env, now, live: false, readOnly: true, checkpointer: false, tempDirs: [] });
|
|
215
256
|
}
|
|
216
257
|
/** Observation-only reads: supported schemas, bounded retries and diagnostics without incident or recovery writes. */
|
|
217
258
|
export function openMachineObserver({ file = null, env = process.env, now = Date.now } = {}) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
{ file: resolved, onCorrupt: corruptDiagnostic });
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
259
|
+
const resolved = path.resolve(file ?? machineFileFor(env));
|
|
260
|
+
if (!fs.existsSync(resolved))
|
|
261
|
+
return null;
|
|
262
|
+
const { db, recovered, close } = connectionState(() => openConnection(resolved, { readOnly: true, env, now, onCorrupt: corruptDiagnostic }), { file: resolved, onCorrupt: corruptDiagnostic });
|
|
263
|
+
const observer = { schema: MACHINE_SCHEMA, file: resolved, path: resolved, readOnly: true, db, recovered, close }; // the reads are bound just below
|
|
264
|
+
for (const [name, fn] of Object.entries(OBSERVER_READS()))
|
|
265
|
+
observer[name] = (...args) => fn(observer, ...args);
|
|
266
|
+
return observer;
|
|
226
267
|
}
|
|
227
268
|
/** fn(handle) over a writer, closed afterwards. */
|
|
228
269
|
export function withMachine(fn, options = {}) {
|
|
229
|
-
|
|
230
|
-
|
|
270
|
+
const m = openMachine(options);
|
|
271
|
+
try {
|
|
272
|
+
return fn(m);
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
m.close();
|
|
276
|
+
}
|
|
231
277
|
}
|
|
232
|
-
/** fn(handle) over a reader; `fallback` when the store does not exist or cannot be read — but a corrupt store is thrown, never hidden. */
|
|
233
278
|
export function readMachine(fn, fallback = null, options = {}) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
279
|
+
let m = null;
|
|
280
|
+
try {
|
|
281
|
+
m = openMachineReader(options);
|
|
282
|
+
return m ? fn(m) : fallback;
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (isCorruptError(error)) {
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
return fallback;
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
try {
|
|
292
|
+
m?.close();
|
|
293
|
+
}
|
|
294
|
+
catch { /* closed */ }
|
|
295
|
+
}
|
|
238
296
|
}
|
|
239
|
-
|
|
240
297
|
// The fenced unit of a transaction(): COMMIT or ROLLBACK-and-rethrow.
|
|
241
|
-
const commitUnit = (db, fn) => { try {
|
|
298
|
+
const commitUnit = (db, fn) => { try {
|
|
299
|
+
assertMutationFence({ kind: 'machine-write', db });
|
|
300
|
+
const out = fn(db);
|
|
301
|
+
db.exec('COMMIT');
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
try {
|
|
306
|
+
if (db.isTransaction)
|
|
307
|
+
db.raw.exec('ROLLBACK');
|
|
308
|
+
}
|
|
309
|
+
catch { /* none */ }
|
|
310
|
+
throw error;
|
|
311
|
+
} };
|
|
242
312
|
// transaction()'s catch: a transient corrupt error waits, reopens and retries; anything else (or retries spent) throws.
|
|
243
|
-
const retryCorrupt = (error, { file, retries, db }) => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
function makeHandle(openRaw, { file, env, now, live, readOnly, checkpointer, tempDirs }) {
|
|
247
|
-
let depth = 0;
|
|
248
|
-
const { db, recovered, noteRecovered, close } = connectionState(openRaw, { file, inTransaction: () => depth > 0 });
|
|
249
|
-
/**
|
|
250
|
-
* BEGIN IMMEDIATE … COMMIT (nested calls join the open one). A transient corrupt error anywhere in the unit rolls it
|
|
251
|
-
* back, reopens the connection and runs the unit again (bodies are DB-only, RESEARCH-STORAGE §3 rule 3, so a re-run is
|
|
252
|
-
* exact); still corrupt after CORRUPT_RETRY_DELAYS_MS it is an incident.
|
|
253
|
-
*/
|
|
254
|
-
const transaction = (fn) => {
|
|
255
|
-
if (depth > 0) return fn(db); // nested: join the open transaction
|
|
256
|
-
need(!readOnly, 'machine-db: read-only handle');
|
|
257
|
-
for (let retries = 0; ; retries += 1) {
|
|
258
|
-
try {
|
|
259
|
-
db.exec('BEGIN IMMEDIATE'); // a refused BEGIN changed nothing: db.exec backs off (BUSY_RETRY_DELAYS_MS), then throws STARCI_MACHINE_BUSY
|
|
260
|
-
depth += 1;
|
|
261
|
-
let out;
|
|
262
|
-
try { out = commitUnit(db, fn); } finally { depth -= 1; }
|
|
263
|
-
if (retries) noteRecovered({ where: 'transaction', retries });
|
|
264
|
-
return out;
|
|
265
|
-
} catch (error) { retryCorrupt(error, { file, retries, db }); }
|
|
313
|
+
const retryCorrupt = (error, { file, retries, db }) => {
|
|
314
|
+
if (!isCorruptError(error) || error.code === MACHINE_CORRUPT_CODE) {
|
|
315
|
+
throw error;
|
|
266
316
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
317
|
+
if (retries >= CORRUPT_RETRY_DELAYS_MS.length) {
|
|
318
|
+
throw corruptIncident(file, error, { retries, where: 'transaction' });
|
|
319
|
+
}
|
|
320
|
+
waitForRetry(CORRUPT_RETRY_DELAYS_MS[retries]);
|
|
321
|
+
try {
|
|
322
|
+
db.reopen();
|
|
323
|
+
}
|
|
324
|
+
catch (openError) {
|
|
325
|
+
if (!isCorruptError(openError)) {
|
|
326
|
+
throw openError;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
function makeHandle(openRaw, { file, env, now, live, readOnly, checkpointer, tempDirs }) {
|
|
331
|
+
let depth = 0;
|
|
332
|
+
const { db, recovered, noteRecovered, close } = connectionState(openRaw, { file, inTransaction: () => depth > 0 });
|
|
333
|
+
/**
|
|
334
|
+
* BEGIN IMMEDIATE … COMMIT (nested calls join the open one). A transient corrupt error anywhere in the unit rolls it
|
|
335
|
+
* back, reopens the connection and runs the unit again (bodies are DB-only, RESEARCH-STORAGE §3 rule 3, so a re-run is
|
|
336
|
+
* exact); still corrupt after CORRUPT_RETRY_DELAYS_MS it is an incident.
|
|
337
|
+
*/
|
|
338
|
+
const transaction = (fn) => {
|
|
339
|
+
if (depth > 0)
|
|
340
|
+
return fn(db); // nested: join the open transaction
|
|
341
|
+
need(!readOnly, 'machine-db: read-only handle');
|
|
342
|
+
for (let retries = 0;; retries += 1) {
|
|
343
|
+
try {
|
|
344
|
+
db.exec('BEGIN IMMEDIATE'); // a refused BEGIN changed nothing: db.exec backs off (BUSY_RETRY_DELAYS_MS), then throws STARCI_MACHINE_BUSY
|
|
345
|
+
depth += 1;
|
|
346
|
+
let out;
|
|
347
|
+
try {
|
|
348
|
+
out = commitUnit(db, fn);
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
depth -= 1;
|
|
352
|
+
}
|
|
353
|
+
if (retries)
|
|
354
|
+
noteRecovered({ where: 'transaction', retries });
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
retryCorrupt(error, { file, retries, db });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
const core = { schema: MACHINE_SCHEMA, file, path: file, db, env, now, live, readOnly, checkpointer: Boolean(checkpointer) && !readOnly, transaction,
|
|
363
|
+
/** Retries that recovered on this handle ({where, retries, at}); close() records them as one machine_logs warn row. */
|
|
364
|
+
recovered,
|
|
365
|
+
close() {
|
|
366
|
+
if (recovered.length) {
|
|
367
|
+
const row = { actor: 'harness', kind: 'machine-db.corrupt-recovered', level: 'warn', msg: `transient SQLITE_CORRUPT recovered ${recovered.length} time(s) on ${path.basename(file)}`,
|
|
368
|
+
data: { file, pid: process.pid, recovered: recovered.slice(0, 20), sqlite: process.versions.sqlite, node: process.version } };
|
|
369
|
+
recovered.length = 0;
|
|
370
|
+
// a reader cannot write: its notice waits in the outbox for the next flush
|
|
371
|
+
try {
|
|
372
|
+
if (readOnly) {
|
|
373
|
+
throw new Error('read-only handle');
|
|
374
|
+
}
|
|
375
|
+
API.log(m, row);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
try {
|
|
379
|
+
deferWrite({ op: 'log', args: [row], file, error });
|
|
380
|
+
}
|
|
381
|
+
catch { /* stderr already has it */ }
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
close();
|
|
385
|
+
} };
|
|
386
|
+
const m = core; // the typed functions are bound below, tempDirs after them
|
|
387
|
+
for (const [name, fn] of Object.entries(API))
|
|
388
|
+
m[name] = (...args) => {
|
|
389
|
+
if (!readOnly)
|
|
390
|
+
assertMutationFence({ kind: 'machine-access', name });
|
|
391
|
+
return fn(m, ...args);
|
|
392
|
+
};
|
|
393
|
+
m.tempDirs = tempDirs;
|
|
394
|
+
return m;
|
|
395
|
+
}
|
|
289
396
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
290
397
|
// Blobs
|
|
291
398
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
292
399
|
/** Store bytes (Buffer | string | file path via {file}) in the blob store and record the blobs row. Returns the sha. */
|
|
293
400
|
function putMachineBlob(m, content, { mediaType = 'application/octet-stream', pinned = false } = {}) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
401
|
+
// Every blob is redacted before it is stored (scripts/lib/redact.mjs): text media 'v1', anything else 'binary'.
|
|
402
|
+
const raw = Buffer.isBuffer(content) ? content : Buffer.from(typeof content === 'string' ? content : JSON.stringify(content));
|
|
403
|
+
const { bytes, redaction } = redactBytes(raw, mediaType);
|
|
404
|
+
const { sha, size, mediaType: stored } = storeBlob(bytes, { mediaType });
|
|
405
|
+
const file = blobPath(sha);
|
|
406
|
+
insertRow(m.db, 'blobs', { sha256: sha, bytes: size, media_type: stored, redaction, file_uri: String(file).replaceAll('\\', '/'), created_at: m.now(), pinned: pinned ? 1 : 0 }, { orIgnore: true });
|
|
407
|
+
return sha;
|
|
301
408
|
}
|
|
302
409
|
/** A JSON value as `{json, sha}`: inline when it fits `limit`, else a small stub inline and the full value as a blob. */
|
|
303
410
|
function jsonOrBlob(m, value, limit = JSON_LIMIT) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
411
|
+
if (value === undefined || value === null)
|
|
412
|
+
return { json: null, sha: null };
|
|
413
|
+
const text = typeof value === 'string' ? value : JSON.stringify(value);
|
|
414
|
+
if (Buffer.byteLength(text) <= limit)
|
|
415
|
+
return { json: text, sha: null };
|
|
416
|
+
const sha = putMachineBlob(m, text, { mediaType: 'application/json' });
|
|
417
|
+
return { json: JSON.stringify({ truncated: true, bytes: Buffer.byteLength(text), sha256: sha }), sha };
|
|
309
418
|
}
|
|
310
419
|
/** The full JSON value behind a jsonOrBlob stub ({truncated, sha256}); the value itself otherwise. */
|
|
311
420
|
export function fullJson(value) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
421
|
+
const stub = value;
|
|
422
|
+
if (stub && typeof stub === 'object' && stub.truncated === true && typeof stub.sha256 === 'string') {
|
|
423
|
+
try {
|
|
424
|
+
return JSON.parse(getBlob(stub.sha256).toString('utf8'));
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return value;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return value;
|
|
316
431
|
}
|
|
317
432
|
const textBlob = (m, text, mediaType = 'text/plain') => (text == null || text === '' ? null : putMachineBlob(m, String(text), { mediaType }));
|
|
318
|
-
|
|
319
433
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
320
434
|
// B0. Ledgers registry and repositories
|
|
321
435
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
322
|
-
const
|
|
323
|
-
|
|
436
|
+
const ledgerOf = (row) => ({ ledgerId: row.ledger_id, name: row.name, product: row.product, repoRoot: row.repo_root, file: row.file, state: row.state,
|
|
437
|
+
schemaVersion: row.schema_version, registeredAt: row.registered_at, seenAt: row.seen_at, retiredAt: row.retired_at, retiredReason: row.retired_reason });
|
|
438
|
+
const ledgerRow = (row) => (row ? ledgerOf(row) : null);
|
|
324
439
|
/** A ledger's own meta (key → value), read-only; {} when the file cannot be read. */
|
|
325
440
|
function ledgerMetaOf(file) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
441
|
+
let db = null;
|
|
442
|
+
try {
|
|
443
|
+
if (!fs.existsSync(file))
|
|
444
|
+
return {};
|
|
445
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
446
|
+
db = new DatabaseSync(file, { readOnly: true, timeout: MACHINE_BUSY_TIMEOUT_MS });
|
|
447
|
+
return Object.fromEntries(db.prepare('SELECT key, value FROM meta').all().map((r) => [r.key, r.value]));
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
return {};
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
try {
|
|
454
|
+
db?.close();
|
|
455
|
+
}
|
|
456
|
+
catch { /* closed */ }
|
|
457
|
+
}
|
|
333
458
|
}
|
|
334
459
|
const repoKey = (root) => path.resolve(String(root)).replaceAll('\\', '/').replace(/^[a-z]:/, (d) => d.toUpperCase());
|
|
335
|
-
const repoKeyOf = repoKey;
|
|
336
460
|
// The live registry also refuses a repository under the OS temp directory (a repro or test run outside the isolated
|
|
337
461
|
// registry would leave a junk workflow in the harness UI): typed refusal, nothing written.
|
|
338
462
|
const tempRepoRefusal = (m, ledgerId, root) => (m.live && root && isUnderTempDir(String(root), { env: m.env, tempDirs: m.tempDirs })
|
|
339
|
-
|
|
340
|
-
|
|
463
|
+
? { ledgerId, registered: false, code: 'STARCI_REGISTRY_TEMP_REPO',
|
|
464
|
+
refused: `registry-temp-repo: repo_root ${repoKey(root)} is under the OS temp directory and ${m.file} is the live registry; set ${TEST_REGISTRY_ENV}` } : null);
|
|
341
465
|
const ledgerRefresh = (existing, { target, at, name, repoRoot, product, schemaVersion }) => ({ file: target, seen_at: at, ...(name ? { name } : {}), ...(repoRoot ? { repo_root: repoKey(repoRoot) } : {}),
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Register (or refresh) a ledger. `ledgerId` is the ledger's own meta.ledger_id. A new row needs name and repoRoot;
|
|
346
|
-
* `file` defaults to projectLedgerFile(ledgerId). The live registry refuses a ledger file under the OS temp directory
|
|
347
|
-
* (returns {registered:false, refused}); nothing is written then.
|
|
348
|
-
*/
|
|
466
|
+
...(product ? { product } : {}), ...(schemaVersion != null ? { schema_version: int(schemaVersion) } : {}),
|
|
467
|
+
...(existing.state === 'retired' ? {} : { state: 'active' }) });
|
|
349
468
|
function registerLedger(m, { ledgerId, name = null, repoRoot = null, file = null, product = null, schemaVersion = null } = {}) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
469
|
+
need(ledgerId, 'registerLedger needs the ledger meta.ledger_id');
|
|
470
|
+
const target = path.resolve(file ?? projectLedgerFile(ledgerId, m.env));
|
|
471
|
+
if (m.live && isUnderTempDir(target, { env: m.env, tempDirs: m.tempDirs }))
|
|
472
|
+
return { ledgerId, registered: false, refused: `registry-temp-ledger: ${target} is under the OS temp directory and ${m.file} is the live registry; set ${TEST_REGISTRY_ENV}` };
|
|
473
|
+
// A ledger opened by its writer names itself: repo_root / product come from its own meta when not given.
|
|
474
|
+
const insertLedger = (db, at) => {
|
|
475
|
+
if (!repoRoot) {
|
|
476
|
+
const own = ledgerMetaOf(target);
|
|
477
|
+
repoRoot = own.repo_root ?? null;
|
|
478
|
+
product = product ?? own.product ?? null;
|
|
479
|
+
}
|
|
480
|
+
name = name ?? (repoRoot ? path.basename(path.resolve(repoRoot)) : null);
|
|
481
|
+
if (!name || !repoRoot)
|
|
482
|
+
return { ledgerId, registered: false, refused: `registry-no-repo-root: ${target} names no repo_root in its meta and none was given` };
|
|
483
|
+
insertRow(db, 'ledgers', { ledger_id: ledgerId, name, product, repo_root: repoKey(repoRoot), file: target, state: 'active', schema_version: int(schemaVersion), registered_at: at, seen_at: at });
|
|
484
|
+
return null;
|
|
485
|
+
};
|
|
486
|
+
return m.transaction((db) => {
|
|
487
|
+
const at = m.now();
|
|
488
|
+
const existing = db.prepare('SELECT * FROM ledgers WHERE ledger_id=?').get(ledgerId);
|
|
489
|
+
const refusedRepo = tempRepoRefusal(m, ledgerId, repoRoot ?? (existing ? null : ledgerMetaOf(target).repo_root ?? null));
|
|
490
|
+
if (refusedRepo)
|
|
491
|
+
return refusedRepo;
|
|
492
|
+
if (existing)
|
|
493
|
+
updateRow(db, 'ledgers', ledgerRefresh(existing, { target, at, name, repoRoot, product, schemaVersion }), { ledger_id: ledgerId });
|
|
494
|
+
else {
|
|
495
|
+
const refusedInsert = insertLedger(db, at);
|
|
496
|
+
if (refusedInsert)
|
|
497
|
+
return refusedInsert;
|
|
498
|
+
}
|
|
499
|
+
if (repoRoot)
|
|
500
|
+
upsertRow(db, 'repositories', { repo_root: repoKey(repoRoot), name: name ?? existing?.name ?? path.basename(repoRoot), role: 'backend', ledger_id: ledgerId, seen_at: at }, ['repo_root']);
|
|
501
|
+
return { ledgerId, registered: true, file: target };
|
|
502
|
+
});
|
|
373
503
|
}
|
|
374
504
|
/**
|
|
375
505
|
* The ledger for a repository root, name or id; null when unregistered. `create:true` with repoRoot (and name) mints a
|
|
@@ -377,299 +507,349 @@ function registerLedger(m, { ledgerId, name = null, repoRoot = null, file = null
|
|
|
377
507
|
* with meta.ledger_id = ledgerId.
|
|
378
508
|
*/
|
|
379
509
|
function resolveLedger(m, { ledgerId = null, name = null, repoRoot = null, create = false, product = null } = {}) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
510
|
+
const db = m.db;
|
|
511
|
+
let row = null;
|
|
512
|
+
if (ledgerId)
|
|
513
|
+
row = db.prepare('SELECT * FROM ledgers WHERE ledger_id=?').get(ledgerId);
|
|
514
|
+
else if (repoRoot)
|
|
515
|
+
row = db.prepare("SELECT * FROM ledgers WHERE repo_root=? ORDER BY state='retired', seen_at DESC LIMIT 1").get(repoKey(repoRoot));
|
|
516
|
+
else if (name)
|
|
517
|
+
row = db.prepare('SELECT * FROM ledgers WHERE name=?').get(name);
|
|
518
|
+
if (row || !create)
|
|
519
|
+
return ledgerRow(row);
|
|
520
|
+
need(repoRoot, 'resolveLedger create needs repoRoot');
|
|
521
|
+
const id = crypto.randomUUID();
|
|
522
|
+
const made = registerLedger(m, { ledgerId: id, name: name ?? path.basename(path.resolve(repoRoot)), repoRoot, product });
|
|
523
|
+
if (made.refused)
|
|
524
|
+
throw Object.assign(new Error(made.refused), { code: made.code ?? 'STARCI_MACHINE_DB' });
|
|
525
|
+
return ledgerRow(db.prepare('SELECT * FROM ledgers WHERE ledger_id=?').get(id));
|
|
391
526
|
}
|
|
392
527
|
function listLedgers(m, { state = null, includeRetired = false } = {}) {
|
|
393
|
-
|
|
394
|
-
|
|
528
|
+
let rows;
|
|
529
|
+
if (state)
|
|
530
|
+
rows = m.db.prepare('SELECT * FROM ledgers WHERE state=? ORDER BY name').all(state);
|
|
531
|
+
else
|
|
532
|
+
rows = m.db.prepare(`SELECT * FROM ledgers ${includeRetired ? '' : "WHERE state<>'retired'"} ORDER BY name`).all();
|
|
533
|
+
return rows.map(ledgerOf);
|
|
395
534
|
}
|
|
396
535
|
function touchLedger(m, ledgerId, { at = m.now() } = {}) { return m.db.prepare('UPDATE ledgers SET seen_at=? WHERE ledger_id=?').run(at, ledgerId).changes > 0; }
|
|
397
536
|
function setLedgerState(m, ledgerId, state, { reason = null } = {}) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
537
|
+
const at = m.now();
|
|
538
|
+
return m.db.prepare('UPDATE ledgers SET state=?, retired_at=CASE WHEN ?=\'retired\' THEN ? ELSE NULL END, retired_reason=CASE WHEN ?=\'retired\' THEN ? ELSE NULL END WHERE ledger_id=?')
|
|
539
|
+
.run(state, state, at, state, reason, ledgerId).changes > 0;
|
|
401
540
|
}
|
|
402
541
|
function upsertRepository(m, { repoRoot, name, role, ledgerId = null, defaultBranch = null, remote = null }) {
|
|
403
|
-
|
|
542
|
+
return upsertRow(m.db, 'repositories', { repo_root: repoKey(repoRoot), name, role, ledger_id: ledgerId, default_branch: defaultBranch, remote, seen_at: m.now() }, ['repo_root']);
|
|
404
543
|
}
|
|
405
544
|
/**
|
|
406
545
|
* Run fn({ledger, db}) over every registered ledger opened READ-ONLY, one at a time (B8: no ATTACH-UNION). A ledger that
|
|
407
546
|
* cannot be opened yields {ledger, error}. Returns the array of results.
|
|
408
547
|
*/
|
|
409
548
|
function forEachLedger(m, fn, { state = 'active' } = {}) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
549
|
+
const out = [];
|
|
550
|
+
for (const ledger of listLedgers(m, { state })) {
|
|
551
|
+
let db = null;
|
|
552
|
+
try {
|
|
553
|
+
need(fs.existsSync(ledger.file), `ledger file missing: ${ledger.file}`);
|
|
554
|
+
db = openReadOnlyDb(ledger.file, MACHINE_BUSY_TIMEOUT_MS);
|
|
555
|
+
db.exec('PRAGMA query_only=ON;');
|
|
556
|
+
out.push({ ledger, result: fn({ ledger, db }) });
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
out.push({ ledger, error: String(error?.message ?? error) });
|
|
560
|
+
}
|
|
561
|
+
finally {
|
|
562
|
+
try {
|
|
563
|
+
db?.close();
|
|
564
|
+
}
|
|
565
|
+
catch { /* closed */ }
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return out;
|
|
421
569
|
}
|
|
422
570
|
/** Manual queries only: ATTACH up to 9 ledgers read-only to this handle as l0..l8. Returns the attached names. */
|
|
423
571
|
function attachLedgers(m, ledgers = listLedgers(m)) {
|
|
424
|
-
|
|
425
|
-
|
|
572
|
+
need(ledgers.length <= 9, 'attachLedgers: at most 9 ledgers per batch');
|
|
573
|
+
return ledgers.map((l, i) => { m.db.exec(`ATTACH DATABASE ${JSON.stringify(pathToFileURL(l.file).href + '?mode=ro')} AS l${i}`); return { alias: `l${i}`, ...l }; });
|
|
426
574
|
}
|
|
427
|
-
|
|
428
575
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
429
576
|
// B1. Supervisor
|
|
430
577
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
431
578
|
/** Append one sup_events row; the digest chain is computed here (JS), inside the caller's or a new transaction. */
|
|
432
579
|
function supEvent(m, { entityType = 'supervisor', entityId = 'main', kind, payload = null, spanId = null, at = m.now(), eventId = null }) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
580
|
+
need(kind, 'supEvent needs kind');
|
|
581
|
+
return m.transaction((db) => {
|
|
582
|
+
const { json, sha } = jsonOrBlob(m, payload == null ? null : redactData(payload), EVENT_LIMITS.payloadBytes);
|
|
583
|
+
const prev = db.prepare('SELECT digest FROM sup_events ORDER BY seq DESC LIMIT 1').get()?.digest ?? null;
|
|
584
|
+
const id = eventId ?? crypto.randomUUID();
|
|
585
|
+
const digest = sha256([prev ?? '', id, entityType, entityId, kind, json ?? '', sha ?? '', at].join('\n'));
|
|
586
|
+
const r = insertRow(db, 'sup_events', { event_id: id, entity_type: entityType, entity_id: String(entityId), kind, span_id: spanId, payload_json: json,
|
|
587
|
+
payload_sha: sha, prev_digest: prev, digest, created_at: at });
|
|
588
|
+
return { seq: Number(r.lastInsertRowid), eventId: id, digest };
|
|
589
|
+
});
|
|
443
590
|
}
|
|
444
591
|
function supEvents(m, { kind = null, kinds = null, entityType = null, entityId = null, since = null, limit = 200, order = 'desc' } = {}) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
592
|
+
const where = [], args = [];
|
|
593
|
+
const list = kinds ?? (kind ? [kind] : null);
|
|
594
|
+
if (list) {
|
|
595
|
+
where.push(`kind IN (${list.map(() => '?').join(',')})`);
|
|
596
|
+
args.push(...list);
|
|
597
|
+
}
|
|
598
|
+
if (entityType) {
|
|
599
|
+
where.push('entity_type=?');
|
|
600
|
+
args.push(entityType);
|
|
601
|
+
}
|
|
602
|
+
if (entityId) {
|
|
603
|
+
where.push('entity_id=?');
|
|
604
|
+
args.push(String(entityId));
|
|
605
|
+
}
|
|
606
|
+
if (since != null) {
|
|
607
|
+
where.push('seq>?');
|
|
608
|
+
args.push(since);
|
|
609
|
+
}
|
|
610
|
+
const rows = m.db.prepare(`SELECT * FROM sup_events ${where.length ? 'WHERE ' + where.join(' AND ') : ''} ORDER BY seq ${order === 'asc' ? 'ASC' : 'DESC'} LIMIT ?`).all(...args, limit);
|
|
611
|
+
return rows.map((r) => ({ ...r, payload: fullJson(parse(r.payload_json)) }));
|
|
453
612
|
}
|
|
454
613
|
const newestSupEvent = (m, kind) => supEvents(m, { kind, limit: 1 })[0] ?? null;
|
|
455
|
-
|
|
456
614
|
function upsertSupJob(m, { jobId, traceId = null, kind, role = 'worker', cluster = null, title, status = 'queued', lane = null, files = null, brief = null, payload = null }) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
615
|
+
const at = m.now();
|
|
616
|
+
const existing = m.db.prepare('SELECT trace_id, created_at FROM sup_jobs WHERE job_id=?').get(jobId);
|
|
617
|
+
return upsertRow(m.db, 'sup_jobs', { job_id: jobId, trace_id: existing?.trace_id ?? traceId ?? newTraceId(), kind, role, cluster, title, status, lane,
|
|
618
|
+
files_json: files, brief, payload_json: payload, created_at: existing?.created_at ?? at, updated_at: at }, ['job_id']);
|
|
461
619
|
}
|
|
462
620
|
function setSupJobStatus(m, jobId, status, { payload = undefined } = {}) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
621
|
+
return m.transaction(() => {
|
|
622
|
+
const changed = updateRow(m.db, 'sup_jobs', { status, updated_at: m.now(), ...(payload !== undefined ? { payload_json: payload } : {}) }, { job_id: jobId }).changes;
|
|
623
|
+
if (changed)
|
|
624
|
+
supEvent(m, { entityType: 'sup-job', entityId: jobId, kind: `sup-job-${status}` });
|
|
625
|
+
return changed > 0;
|
|
626
|
+
});
|
|
468
627
|
}
|
|
469
628
|
const supJob = (m, jobId) => { const r = m.db.prepare('SELECT * FROM sup_jobs WHERE job_id=?').get(jobId); return r ? { ...r, files: parse(r.files_json), payload: parse(r.payload_json) } : null; };
|
|
470
629
|
function listSupJobs(m, { status = null, statuses = null, kind = null } = {}) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
630
|
+
const where = [], args = [];
|
|
631
|
+
const list = statuses ?? (status ? [status] : null);
|
|
632
|
+
if (list) {
|
|
633
|
+
where.push(`status IN (${list.map(() => '?').join(',')})`);
|
|
634
|
+
args.push(...list);
|
|
635
|
+
}
|
|
636
|
+
if (kind) {
|
|
637
|
+
where.push('kind=?');
|
|
638
|
+
args.push(kind);
|
|
639
|
+
}
|
|
640
|
+
return m.db.prepare(`SELECT * FROM sup_jobs ${where.length ? 'WHERE ' + where.join(' AND ') : ''} ORDER BY created_at, job_id`).all(...args)
|
|
641
|
+
.map((r) => ({ ...r, files: parse(r.files_json), payload: parse(r.payload_json) }));
|
|
477
642
|
}
|
|
478
643
|
function acquireSupLeases(m, jobId, paths, { ttlMs = 3600000 } = {}) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
644
|
+
return m.transaction((db) => {
|
|
645
|
+
const at = m.now();
|
|
646
|
+
db.prepare('DELETE FROM sup_leases WHERE expires_at<?').run(at);
|
|
647
|
+
const held = paths.map((p) => db.prepare('SELECT job_id FROM sup_leases WHERE path=?').get(p)).map((r, i) => (r && r.job_id !== jobId ? { path: paths[i], holder: r.job_id } : null)).filter(Boolean);
|
|
648
|
+
if (held.length)
|
|
649
|
+
return { ok: false, conflicts: held };
|
|
650
|
+
for (const p of paths)
|
|
651
|
+
upsertRow(db, 'sup_leases', { path: p, job_id: jobId, acquired_at: at, expires_at: at + ttlMs }, ['path']);
|
|
652
|
+
return { ok: true };
|
|
653
|
+
});
|
|
487
654
|
}
|
|
488
655
|
const releaseSupLeases = (m, jobId) => m.db.prepare('DELETE FROM sup_leases WHERE job_id=?').run(jobId).changes;
|
|
489
656
|
const supLeases = (m) => m.db.prepare('SELECT * FROM sup_leases ORDER BY path').all();
|
|
490
657
|
function startSupAttempt(m, { jobId, spanId = newSpanId(), parentSpanId = null, ...rest }) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
658
|
+
const seq = Number(m.db.prepare('SELECT COALESCE(max(dispatch_seq),0)+1 n FROM sup_attempts WHERE job_id=?').get(jobId)?.n);
|
|
659
|
+
const cols = snake(rest);
|
|
660
|
+
const r = insertRow(m.db, 'sup_attempts', { job_id: jobId, dispatch_seq: seq, span_id: spanId, parent_span_id: parentSpanId, spawned_at: m.now(), ...cols });
|
|
661
|
+
return { attemptId: Number(r.lastInsertRowid), dispatchSeq: seq, spanId };
|
|
495
662
|
}
|
|
496
663
|
const updateSupAttempt = (m, attemptId, fields) => updateRow(m.db, 'sup_attempts', snake(fields), { attempt_id: attemptId }).changes > 0;
|
|
497
664
|
const latestSupAttempt = (m, jobId) => m.db.prepare('SELECT * FROM sup_attempts WHERE job_id=? ORDER BY dispatch_seq DESC LIMIT 1').get(jobId) ?? null;
|
|
498
665
|
function recordSupReport(m, { attemptId, jobId, outcome, report, reportText = null }) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
666
|
+
const reportSha = reportText ? textBlob(m, reportText, 'text/markdown') : null;
|
|
667
|
+
const r = upsertRow(m.db, 'sup_reports', { attempt_id: attemptId, job_id: jobId, outcome, report_json: report ?? {}, report_sha: reportSha, created_at: m.now() }, ['attempt_id']);
|
|
668
|
+
return { reportId: Number(r.lastInsertRowid), reportSha };
|
|
502
669
|
}
|
|
503
670
|
const supReports = (m, { jobId = null, unconsumed = false } = {}) => m.db.prepare(`SELECT * FROM sup_reports WHERE 1=1 ${jobId ? 'AND job_id=?' : ''} ${unconsumed ? 'AND consumed_at IS NULL' : ''} ORDER BY report_id`)
|
|
504
|
-
|
|
671
|
+
.all(...(jobId ? [jobId] : [])).map((r) => ({ ...r, report: parse(r.report_json) }));
|
|
505
672
|
const consumeSupReport = (m, reportId) => m.db.prepare('UPDATE sup_reports SET consumed_at=? WHERE report_id=? AND consumed_at IS NULL').run(m.now(), reportId).changes > 0;
|
|
506
|
-
|
|
507
673
|
/**
|
|
508
674
|
* Open a Supervisor Decision Item, idempotent on its key (MB-07: '<kind>:<entity>:<signature>:<head>', no empty part).
|
|
509
675
|
* Returns {diId, created}. An existing open item with the same key is returned unchanged.
|
|
510
676
|
*/
|
|
511
|
-
function openSupDecision(m, { keyParts, kind, decider = 'supervisor', summary, ledgerId = null, workflowId = null, entityType = null, entityId = null,
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
677
|
+
function openSupDecision(m, { keyParts, kind, decider = 'supervisor', summary, ledgerId = null, workflowId = null, entityType = null, entityId = null, openedBy = 'reconciler', dueAt = null, escalateTo = null, evidence = null, options = null, allowedVerbs = null, payload = {} }) {
|
|
678
|
+
need(keyParts && typeof keyParts === 'object' && !Array.isArray(keyParts), 'openSupDecision needs keyParts {kind, entity, signature, head, ...}');
|
|
679
|
+
const parts = Object.values(keyParts).map((v) => String(v ?? ''));
|
|
680
|
+
need(parts.length >= 2 && parts.every((p) => p.trim() && !p.includes(':')), `openSupDecision: empty or ':'-bearing key part in ${JSON.stringify(keyParts)} (MB-07)`);
|
|
681
|
+
const key = parts.join(':');
|
|
682
|
+
return m.transaction((db) => {
|
|
683
|
+
const hit = db.prepare('SELECT di_id, status FROM sup_decision_items WHERE idempotency_key=?').get(key);
|
|
684
|
+
if (hit)
|
|
685
|
+
return { diId: hit.di_id, created: false, status: hit.status };
|
|
686
|
+
const diId = `sdi-${crypto.randomUUID()}`;
|
|
687
|
+
insertRow(db, 'sup_decision_items', { di_id: diId, idempotency_key: key, key_parts_json: keyParts, ledger_id: ledgerId, workflow_id: workflowId, kind, decider,
|
|
688
|
+
entity_type: entityType, entity_id: entityId, summary, status: 'open', opened_by: openedBy, opened_at: m.now(), due_at: dueAt, escalate_to: escalateTo,
|
|
689
|
+
evidence_json: evidence, options_json: options, allowed_verbs_json: allowedVerbs, payload_json: payload ?? {} });
|
|
690
|
+
supEvent(m, { entityType: 'sup-decision', entityId: diId, kind: 'sup-decision-opened', payload: { key, kind, summary } });
|
|
691
|
+
return { diId, created: true, status: 'open' };
|
|
692
|
+
});
|
|
527
693
|
}
|
|
528
694
|
function setSupDecision(m, diId, { status, by = null, verb = null, choice = null, rationale = null, result = null, supersededBy = null, spanId = newSpanId() }) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
695
|
+
return m.transaction((db) => {
|
|
696
|
+
const at = m.now();
|
|
697
|
+
const set = { status };
|
|
698
|
+
if (status === 'claimed')
|
|
699
|
+
Object.assign(set, { claim_by: by, claim_at: at });
|
|
700
|
+
if (status === 'escalated')
|
|
701
|
+
db.prepare('UPDATE sup_decision_items SET escalations=escalations+1 WHERE di_id=?').run(diId);
|
|
702
|
+
if (status === 'superseded')
|
|
703
|
+
set.superseded_by = supersededBy;
|
|
704
|
+
let decisionId = null;
|
|
705
|
+
if (status === 'resolved') {
|
|
706
|
+
decisionId = `sdec-${crypto.randomUUID()}`;
|
|
707
|
+
const di = db.prepare('SELECT * FROM sup_decision_items WHERE di_id=?').get(diId);
|
|
708
|
+
insertRow(db, 'sup_decisions', { decision_id: decisionId, di_id: diId, decider: by ?? di?.decider ?? 'supervisor', span_id: spanId, ledger_id: di?.ledger_id, workflow_id: di?.workflow_id,
|
|
709
|
+
subject_type: di?.entity_type, subject_id: di?.entity_id, choice: choice ?? verb ?? 'resolved', rationale, result_json: result, decided_at: at });
|
|
710
|
+
Object.assign(set, { resolved_by: by, resolved_at: at, resolution_verb: verb, decision_id: decisionId });
|
|
711
|
+
}
|
|
712
|
+
const changed = updateRow(db, 'sup_decision_items', set, { di_id: diId }).changes;
|
|
713
|
+
if (changed)
|
|
714
|
+
supEvent(m, { entityType: 'sup-decision', entityId: diId, kind: `sup-decision-${status}`, payload: { by, verb } });
|
|
715
|
+
return { changed: changed > 0, decisionId };
|
|
716
|
+
});
|
|
547
717
|
}
|
|
548
718
|
const markSupDecisionDelivered = (m, diId) => m.db.prepare('UPDATE sup_decision_items SET delivered_at=COALESCE(delivered_at,?) WHERE di_id=?').run(m.now(), diId).changes > 0;
|
|
549
719
|
function listSupDecisions(m, { open = true, kind = null } = {}) {
|
|
550
|
-
|
|
551
|
-
|
|
720
|
+
return m.db.prepare(`SELECT * FROM sup_decision_items WHERE 1=1 ${open ? "AND status IN ('open','claimed','escalated')" : ''} ${kind ? 'AND kind=?' : ''} ORDER BY opened_at`)
|
|
721
|
+
.all(...(kind ? [kind] : [])).map((r) => ({ ...r, keyParts: parse(r.key_parts_json), payload: parse(r.payload_json), evidence: parse(r.evidence_json) }));
|
|
552
722
|
}
|
|
553
723
|
function openOwed(m, { owedId, kind, subject, cluster = null, dueAt = null, detail = null }) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
724
|
+
return m.transaction((db) => {
|
|
725
|
+
const hit = db.prepare('SELECT state FROM sup_owed WHERE owed_id=?').get(owedId);
|
|
726
|
+
if (hit) {
|
|
727
|
+
if (detail !== null) {
|
|
728
|
+
updateRow(db, 'sup_owed', { detail_json: detail, ...(dueAt ? { due_at: dueAt } : {}) }, { owed_id: owedId });
|
|
729
|
+
}
|
|
730
|
+
return { owedId, created: false, state: hit.state };
|
|
731
|
+
}
|
|
732
|
+
insertRow(db, 'sup_owed', { owed_id: owedId, kind, subject, cluster, state: 'open', opened_at: m.now(), due_at: dueAt, detail_json: detail });
|
|
733
|
+
return { owedId, created: true, state: 'open' };
|
|
734
|
+
});
|
|
560
735
|
}
|
|
561
736
|
const ackOwed = (m, owedId, { by = 'supervisor' } = {}) => m.db.prepare("UPDATE sup_owed SET state='acked', acked_at=?, acked_by=? WHERE owed_id=? AND state='open'").run(m.now(), by, owedId).changes > 0;
|
|
562
737
|
const closeOwed = (m, owedId) => m.db.prepare("UPDATE sup_owed SET state='closed', closed_at=? WHERE owed_id=? AND state<>'closed'").run(m.now(), owedId).changes > 0;
|
|
563
738
|
const listOwed = (m, { open = true } = {}) => m.db.prepare(`SELECT * FROM sup_owed ${open ? "WHERE state<>'closed'" : ''} ORDER BY opened_at`).all().map((r) => ({ ...r, detail: parse(r.detail_json) }));
|
|
564
739
|
function upsertLearning(m, { itemId, kind, parentId = null, title, state = null, sourceRef = null, lane = null, landedSha = null, detail = null }) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
740
|
+
const at = m.now();
|
|
741
|
+
const created = m.db.prepare('SELECT created_at FROM sup_learning WHERE item_id=?').get(itemId)?.created_at ?? at;
|
|
742
|
+
return upsertRow(m.db, 'sup_learning', { item_id: itemId, kind, parent_id: parentId, title, state, source_ref: sourceRef, lane, landed_sha: landedSha, detail_json: detail, created_at: created, updated_at: at }, ['item_id']);
|
|
568
743
|
}
|
|
569
744
|
const listLearning = (m, { kind = null } = {}) => m.db.prepare(`SELECT * FROM sup_learning ${kind ? 'WHERE kind=?' : ''} ORDER BY created_at, item_id`).all(...(kind ? [kind] : [])).map((r) => ({ ...r, detail: parse(r.detail_json) }));
|
|
570
745
|
const recordOwnerRuling = (m, { rulingId = `rul-${crypto.randomUUID()}`, saidAt, channel = null, verbatim, paraphrase = null, appliesTo = null, contractRef = null, recordedBy = null }) => {
|
|
571
|
-
|
|
746
|
+
insertRow(m.db, 'sup_owner_rulings', { ruling_id: rulingId, said_at: saidAt, channel, verbatim, paraphrase, applies_to: appliesTo, contract_ref: contractRef, recorded_by: recordedBy, created_at: m.now() });
|
|
747
|
+
return rulingId;
|
|
748
|
+
};
|
|
572
749
|
function upsertBridge(m, { bridgeId, ledgerId = null, action, state, approvedBy = null, detail = null }) {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
750
|
+
const at = m.now();
|
|
751
|
+
const created = m.db.prepare('SELECT created_at FROM sup_bridges WHERE bridge_id=?').get(bridgeId)?.created_at ?? at;
|
|
752
|
+
return upsertRow(m.db, 'sup_bridges', { bridge_id: bridgeId, ledger_id: ledgerId, action, state, approved_by: approvedBy, detail_json: detail, created_at: created, updated_at: at }, ['bridge_id']);
|
|
576
753
|
}
|
|
577
754
|
function recordSupMessage(m, { msgId = `msg-${crypto.randomUUID()}`, direction, channel, chatId = null, messageId = null, from = null, to = null, via = null, text, ok = null, at = m.now() }) {
|
|
578
|
-
|
|
579
|
-
|
|
755
|
+
insertRow(m.db, 'sup_messages', { msg_id: msgId, direction, channel, chat_id: chatId, message_id: messageId, from_ref: from, to_ref: to, via, text, ok: bool(ok), at }, { orIgnore: true });
|
|
756
|
+
return msgId;
|
|
580
757
|
}
|
|
581
758
|
const supMessages = (m, { direction = null, unread = false, limit = 200 } = {}) => m.db.prepare(`SELECT * FROM sup_messages WHERE 1=1 ${direction ? 'AND direction=?' : ''} ${unread ? 'AND read_at IS NULL' : ''} ORDER BY at DESC LIMIT ?`)
|
|
582
|
-
|
|
583
|
-
const markSupMessagesRead = (m, ids) => { let n = 0; for (const id of ids) {
|
|
759
|
+
.all(...(direction ? [direction] : []), limit).reverse();
|
|
760
|
+
const markSupMessagesRead = (m, ids) => { let n = 0; for (const id of ids) {
|
|
761
|
+
n += m.db.prepare('UPDATE sup_messages SET read_at=? WHERE msg_id=? AND read_at IS NULL').run(m.now(), id).changes;
|
|
762
|
+
} return n; };
|
|
584
763
|
function setSupSignal(m, { scope, key = 'main', value = null, token = null, holderPid = process.pid, expiresAt = null }) {
|
|
585
|
-
|
|
764
|
+
return upsertRow(m.db, 'sup_signals', { scope, key, holder_pid: holderPid, token, value_json: value, at: m.now(), expires_at: expiresAt }, ['scope', 'key']);
|
|
586
765
|
}
|
|
587
766
|
const supSignal = (m, scope, key = 'main') => { const r = m.db.prepare('SELECT * FROM sup_signals WHERE scope=? AND key=?').get(scope, key); return r ? { ...r, value: parse(r.value_json) } : null; };
|
|
588
767
|
const clearSupSignal = (m, scope, key = 'main') => m.db.prepare('DELETE FROM sup_signals WHERE scope=? AND key=?').run(scope, key).changes > 0;
|
|
589
768
|
function recordMachineLlmUsage(m, { subjectType, supAttemptId = null, turnRef = null, spanId = null, provider, requestModel = null, responseModel = null, source = 'cli-transcript', ...counts }) {
|
|
590
|
-
|
|
591
|
-
|
|
769
|
+
return Number(insertRow(m.db, 'llm_usage', { subject_type: subjectType, sup_attempt_id: supAttemptId, turn_ref: turnRef, span_id: spanId, provider, request_model: requestModel,
|
|
770
|
+
response_model: responseModel, source, at: m.now(), ...snake(counts) }).lastInsertRowid);
|
|
592
771
|
}
|
|
593
|
-
|
|
594
772
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
595
773
|
// B2. Engine: process runs, leader, cursors, queue, schedules, actions, modes, SLA, invariants
|
|
596
774
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
597
775
|
/** A long-lived runtime process starts (MB-04, G1). Returns run_id. */
|
|
598
776
|
function startProcessRun(m, { role, pid = process.pid, rev = runtimeRev(), epoch = null, parentActionId = null, startReason = 'manual' }) {
|
|
599
|
-
|
|
600
|
-
|
|
777
|
+
return Number(insertRow(m.db, 'process_runs', { role, pid, host: os.hostname(), rev, epoch, parent_action_id: parentActionId, start_reason: startReason,
|
|
778
|
+
started_at: m.now(), last_heartbeat_at: m.now() }).lastInsertRowid);
|
|
601
779
|
}
|
|
602
780
|
const heartbeatProcessRun = (m, runId, { draining = null } = {}) => m.db.prepare('UPDATE process_runs SET last_heartbeat_at=?, draining_since=CASE WHEN ?=1 THEN COALESCE(draining_since,?) ELSE draining_since END WHERE run_id=? AND ended_at IS NULL')
|
|
603
|
-
|
|
781
|
+
.run(m.now(), draining ? 1 : 0, m.now(), runId).changes > 0;
|
|
604
782
|
/** A process run ends, once (trigger process_runs_end_once). exitReason: clean|reload-handover|crash|killed|lost-lease|stopped|unknown. */
|
|
605
783
|
function endProcessRun(m, runId, { exitCode = null, exitReason, killedBy = null }) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
.
|
|
784
|
+
const at = m.now();
|
|
785
|
+
const row = m.db.prepare('SELECT last_heartbeat_at, ended_at FROM process_runs WHERE run_id=?').get(runId);
|
|
786
|
+
if (!row || row.ended_at != null)
|
|
787
|
+
return false;
|
|
788
|
+
return m.db.prepare('UPDATE process_runs SET ended_at=?, exit_code=?, exit_reason=?, killed_by=?, heartbeat_age_at_end_ms=? WHERE run_id=? AND ended_at IS NULL')
|
|
789
|
+
.run(at, exitCode, exitReason, killedBy, row.last_heartbeat_at != null ? at - row.last_heartbeat_at : null, runId).changes > 0;
|
|
611
790
|
}
|
|
612
791
|
const openProcessRuns = (m, { role = null } = {}) => m.db.prepare(`SELECT * FROM process_runs WHERE ended_at IS NULL ${role ? 'AND role=?' : ''} ORDER BY run_id`).all(...(role ? [role] : []));
|
|
613
792
|
const processRuns = (m, { role = null, sinceMs = null, limit = 200 } = {}) => m.db.prepare(`SELECT * FROM process_runs WHERE 1=1 ${role ? 'AND role=?' : ''} ${sinceMs != null ? 'AND started_at>?' : ''} ORDER BY run_id DESC LIMIT ?`)
|
|
614
|
-
|
|
615
|
-
|
|
793
|
+
.all(...(role ? [role] : []), ...(sinceMs != null ? [m.now() - sinceMs] : []), limit);
|
|
616
794
|
const leaderOf = (m, name = 'reconciler') => m.db.prepare('SELECT * FROM engine_leader WHERE name=?').get(name) ?? null;
|
|
617
795
|
/**
|
|
618
796
|
* Acquire or renew the leader lease (fencing epoch). A fresh/expired lease is taken with a new epoch and a leader_history
|
|
619
797
|
* row; renewal by the holder keeps the epoch. Returns {leader:boolean, epoch, row}.
|
|
620
798
|
*/
|
|
621
799
|
function acquireLeader(m, { name = 'reconciler', holder, pid = process.pid, leaseMs, rev = runtimeRev(), processRunId = null, handover = false }) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
800
|
+
return m.transaction((db) => {
|
|
801
|
+
const at = m.now();
|
|
802
|
+
const cur = db.prepare('SELECT * FROM engine_leader WHERE name=?').get(name);
|
|
803
|
+
if (cur?.holder === holder && cur?.pid === pid) {
|
|
804
|
+
db.prepare('UPDATE engine_leader SET heartbeat_at=?, expires_at=?, rev=?, process_run_id=COALESCE(?,process_run_id) WHERE name=?').run(at, at + leaseMs, rev, processRunId, name);
|
|
805
|
+
return { leader: true, epoch: cur?.epoch, renewed: true };
|
|
806
|
+
}
|
|
807
|
+
if (cur?.expires_at > at && !handover)
|
|
808
|
+
return { leader: false, epoch: cur?.epoch, holder: cur?.holder, pid: cur?.pid };
|
|
809
|
+
const epoch = Math.max(Number(cur?.epoch ?? 0), Number(db.prepare('SELECT COALESCE(max(epoch),0) e FROM leader_history').get()?.e)) + 1;
|
|
810
|
+
if (cur)
|
|
811
|
+
db.prepare('UPDATE leader_history SET released_at=?, release_reason=? WHERE epoch=? AND released_at IS NULL').run(at, handover ? 'reload' : 'lost', cur.epoch);
|
|
812
|
+
upsertRow(db, 'engine_leader', { name, holder, pid, epoch, process_run_id: processRunId, heartbeat_at: at, expires_at: at + leaseMs, rev, draining: 0, passes: 0, last_pass_ms: null, last_error: null }, ['name']);
|
|
813
|
+
let how = 'fresh';
|
|
814
|
+
if (cur)
|
|
815
|
+
how = handover ? 'handover' : 'takeover-stale';
|
|
816
|
+
insertRow(db, 'leader_history', { epoch, holder, pid, process_run_id: processRunId, rev, acquired_at: at, acquired_how: how });
|
|
817
|
+
return { leader: true, epoch, renewed: false };
|
|
818
|
+
});
|
|
637
819
|
}
|
|
638
820
|
/** Renew only if still the holder at `epoch` (fence). Also records pass stats. */
|
|
639
821
|
function renewLeader(m, { name = 'reconciler', epoch, leaseMs, passes = null, lastPassMs = null, lastError = undefined, draining = null }) {
|
|
640
|
-
|
|
641
|
-
|
|
822
|
+
const at = m.now();
|
|
823
|
+
return m.db.prepare(`UPDATE engine_leader SET heartbeat_at=?, expires_at=?, passes=COALESCE(?,passes), last_pass_ms=COALESCE(?,last_pass_ms)
|
|
642
824
|
${lastError !== undefined ? ', last_error=?' : ''} ${draining != null ? ', draining=?' : ''} WHERE name=? AND epoch=?`)
|
|
643
|
-
|
|
825
|
+
.run(at, at + leaseMs, passes, lastPassMs, ...(lastError !== undefined ? [lastError] : []), ...(draining != null ? [draining ? 1 : 0] : []), name, epoch).changes > 0;
|
|
644
826
|
}
|
|
645
827
|
/** Release the lease held at `epoch`; closes its leader_history row with the reason (reload|lost|killed|stop|crash). */
|
|
646
828
|
function releaseLeader(m, { name = 'reconciler', epoch, reason = 'stop' }) {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
829
|
+
return m.transaction((db) => {
|
|
830
|
+
const n = db.prepare('DELETE FROM engine_leader WHERE name=? AND epoch=?').run(name, epoch).changes;
|
|
831
|
+
db.prepare('UPDATE leader_history SET released_at=?, release_reason=? WHERE epoch=? AND released_at IS NULL').run(m.now(), reason, epoch);
|
|
832
|
+
return n > 0;
|
|
833
|
+
});
|
|
652
834
|
}
|
|
653
835
|
const leaderHistory = (m, { limit = 50 } = {}) => m.db.prepare('SELECT * FROM leader_history ORDER BY epoch DESC LIMIT ?').all(limit);
|
|
654
836
|
const cursorOf = (m, ledgerId) => m.db.prepare('SELECT last_seq FROM engine_cursors WHERE ledger_id=?').get(ledgerId)?.last_seq ?? null;
|
|
655
837
|
const setCursor = (m, ledgerId, lastSeq) => upsertRow(m.db, 'engine_cursors', { ledger_id: ledgerId, last_seq: lastSeq, updated_at: m.now() }, ['ledger_id']);
|
|
656
838
|
const cursors = (m) => m.db.prepare('SELECT * FROM engine_cursors').all();
|
|
657
|
-
|
|
658
839
|
function enqueue(m, { controller, key, dueAt = m.now(), reason = null }) {
|
|
659
|
-
|
|
660
|
-
|
|
840
|
+
return m.db.prepare('INSERT INTO engine_queue(controller,key,due_at,reason) VALUES(?,?,?,?) ON CONFLICT(controller,key) DO UPDATE SET due_at=min(COALESCE(due_at,excluded.due_at),excluded.due_at), reason=excluded.reason')
|
|
841
|
+
.run(controller, key, dueAt, reason);
|
|
661
842
|
}
|
|
662
843
|
const dueQueue = (m, { controller = null, limit = 100 } = {}) => m.db.prepare(`SELECT * FROM engine_queue WHERE due_at<=? ${controller ? 'AND controller=?' : ''} ORDER BY due_at LIMIT ?`)
|
|
663
|
-
|
|
844
|
+
.all(m.now(), ...(controller ? [controller] : []), limit);
|
|
664
845
|
const queueRows = (m) => m.db.prepare('SELECT * FROM engine_queue ORDER BY due_at').all();
|
|
665
846
|
const dequeue = (m, controller, key) => m.db.prepare('DELETE FROM engine_queue WHERE controller=? AND key=?').run(controller, key).changes > 0;
|
|
666
847
|
const requeue = (m, { controller, key, dueAt, error = null }) => m.db.prepare('UPDATE engine_queue SET due_at=?, tries=tries+1, last_error=? WHERE controller=? AND key=?').run(dueAt, error, controller, key).changes > 0;
|
|
667
|
-
|
|
668
848
|
/** MB-01: the schedule row of a periodic duty; created with next_due_at = now + interval when absent (never "first run now"). */
|
|
669
849
|
function ensureSchedule(m, { controller, duty, intervalMs, firstDueAt = null }) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
850
|
+
m.db.prepare('INSERT INTO schedules(controller,duty,interval_ms,next_due_at) VALUES(?,?,?,?) ON CONFLICT(controller,duty) DO UPDATE SET interval_ms=excluded.interval_ms')
|
|
851
|
+
.run(controller, duty, intervalMs, firstDueAt ?? m.now() + intervalMs);
|
|
852
|
+
return m.db.prepare('SELECT * FROM schedules WHERE controller=? AND duty=?').get(controller, duty);
|
|
673
853
|
}
|
|
674
854
|
/** Claim a due duty (no overlap): true when this caller should run it now. */
|
|
675
855
|
/**
|
|
@@ -677,230 +857,243 @@ function ensureSchedule(m, { controller, duty, intervalMs, firstDueAt = null })
|
|
|
677
857
|
* is older than `ttlMs` (default: the interval, at least 1 h), is released first, so a crashed run never blocks the duty.
|
|
678
858
|
*/
|
|
679
859
|
function claimSchedule(m, { controller, duty, actionId = null, pid = process.pid, ttlMs = null, alive = pidAlive }) {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
860
|
+
return m.transaction((db) => {
|
|
861
|
+
const at = m.now();
|
|
862
|
+
const row = db.prepare('SELECT * FROM schedules WHERE controller=? AND duty=?').get(controller, duty);
|
|
863
|
+
if (!row)
|
|
864
|
+
return false;
|
|
865
|
+
if (row.running_pid != null && row.running_pid !== pid) {
|
|
866
|
+
const ttl = ttlMs ?? Math.max(row.interval_ms, 3600000);
|
|
867
|
+
if (!alive(row.running_pid) || at - (row.last_started_at ?? 0) > ttl)
|
|
868
|
+
db.prepare("UPDATE schedules SET running_pid=NULL, last_result='unknown', last_finished_at=? WHERE controller=? AND duty=? AND running_pid=?").run(at, controller, duty, row.running_pid);
|
|
869
|
+
}
|
|
870
|
+
return db.prepare('UPDATE schedules SET last_started_at=?, running_pid=?, last_action_id=COALESCE(?,last_action_id) WHERE controller=? AND duty=? AND next_due_at<=? AND (running_pid IS NULL OR running_pid=?)')
|
|
871
|
+
.run(at, pid, actionId, controller, duty, at, pid).changes > 0;
|
|
872
|
+
});
|
|
692
873
|
}
|
|
693
874
|
function finishSchedule(m, { controller, duty, result = 'done', digest = null, nextDueAt = null }) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
.
|
|
875
|
+
const at = m.now();
|
|
876
|
+
const row = m.db.prepare('SELECT interval_ms FROM schedules WHERE controller=? AND duty=?').get(controller, duty);
|
|
877
|
+
if (!row)
|
|
878
|
+
return false;
|
|
879
|
+
return m.db.prepare('UPDATE schedules SET last_finished_at=?, last_result=?, last_result_digest=?, next_due_at=?, running_pid=NULL WHERE controller=? AND duty=?')
|
|
880
|
+
.run(at, result, digest, nextDueAt ?? at + row.interval_ms, controller, duty).changes > 0;
|
|
699
881
|
}
|
|
700
882
|
const schedules = (m) => m.db.prepare('SELECT * FROM schedules ORDER BY controller, duty').all();
|
|
701
|
-
|
|
702
883
|
/** Deterministic action id = sha(controller, key, verb, epoch, observed_generation). */
|
|
703
884
|
const actionIdOf = ({ controller, key = '', verb = '', epoch = 0, observedGeneration = 0 }) => sha256([controller, key, verb, epoch, observedGeneration].join('\u0000')).slice(0, 32);
|
|
704
885
|
/** Record an action intent (idempotent on id). */
|
|
705
|
-
function actionIntent(m, { id = null, controller, duty = null, key = null, verb = null, argvDigest = null, epoch = null, observedGeneration = null, spanId = newSpanId(), traceId = null,
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
return actionId;
|
|
886
|
+
function actionIntent(m, { id = null, controller, duty = null, key = null, verb = null, argvDigest = null, epoch = null, observedGeneration = null, spanId = newSpanId(), traceId = null, mode = null, ledgerId = null, workflowId = null, jobId = null, attemptId = null }) {
|
|
887
|
+
const actionId = id ?? actionIdOf({ controller, key, verb, epoch, observedGeneration });
|
|
888
|
+
insertRow(m.db, 'engine_actions', { id: actionId, controller, duty, key, verb, argv_digest: argvDigest, epoch, observed_generation: observedGeneration, span_id: spanId, trace_id: traceId,
|
|
889
|
+
state: 'intent', mode, ledger_id: ledgerId, workflow_id: workflowId, job_id: jobId, attempt_id: attemptId }, { orIgnore: true });
|
|
890
|
+
return actionId;
|
|
711
891
|
}
|
|
712
892
|
const actionRunning = (m, id, { requestId = null, childRunId = null } = {}) => m.db.prepare("UPDATE engine_actions SET state='running', started_at=COALESCE(started_at,?), request_id=COALESCE(?,request_id), child_run_id=COALESCE(?,child_run_id) WHERE id=? AND state IN ('intent','running')")
|
|
713
|
-
|
|
893
|
+
.run(m.now(), requestId, childRunId, id).changes > 0;
|
|
714
894
|
/**
|
|
715
895
|
* Finish an action (MB-03): `result` is kept IN FULL as a blob (result_sha); result_json holds only a ≤8 KiB summary.
|
|
716
896
|
* stdout/stderr are stored in full as blobs.
|
|
717
897
|
*/
|
|
718
898
|
function actionFinish(m, id, { state = 'done', exitCode = null, result: rawResult = null, summary: rawSummary = null, stdout = null, stderr = null, errorSignature = null }) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
899
|
+
const result = rawResult == null ? null : redactData(rawResult);
|
|
900
|
+
const summary = rawSummary == null ? null : redactData(rawSummary);
|
|
901
|
+
let full = null;
|
|
902
|
+
if (result != null)
|
|
903
|
+
full = typeof result === 'string' ? result : JSON.stringify(result);
|
|
904
|
+
const resultSha = full == null ? null : putMachineBlob(m, full, { mediaType: typeof result === 'string' ? 'text/plain' : 'application/json' });
|
|
905
|
+
let brief = summary ?? (full != null && Buffer.byteLength(full) <= 8192 && typeof result !== 'string' ? result : null);
|
|
906
|
+
if (brief != null) {
|
|
907
|
+
const s = JSON.stringify(brief);
|
|
908
|
+
if (Buffer.byteLength(s) > 8192)
|
|
909
|
+
brief = { truncated: true, bytes: Buffer.byteLength(s), sha256: resultSha };
|
|
910
|
+
}
|
|
911
|
+
const stdoutSha = textBlob(m, stdout), stderrSha = textBlob(m, stderr);
|
|
912
|
+
return m.db.prepare('UPDATE engine_actions SET state=?, finished_at=?, started_at=COALESCE(started_at,?), exit_code=?, result_json=?, result_sha=?, stdout_sha=?, stderr_sha=?, error_signature=? WHERE id=?')
|
|
913
|
+
.run(state, m.now(), m.now(), exitCode, brief == null ? null : JSON.stringify(brief), resultSha, stdoutSha, stderrSha, errorSignature, id).changes > 0;
|
|
728
914
|
}
|
|
729
915
|
/** Actions left in intent/running by a dead engine become unknown (never replayed). */
|
|
730
916
|
const markStaleActionsUnknown = (m, { olderThanMs = 0, exceptEpoch = null } = {}) => m.db.prepare(`UPDATE engine_actions SET state='unknown', finished_at=? WHERE state IN ('intent','running') AND COALESCE(started_at,0)<=? ${exceptEpoch != null ? 'AND COALESCE(epoch,-1)<>?' : ''}`)
|
|
731
|
-
|
|
917
|
+
.run(m.now(), m.now() - olderThanMs, ...(exceptEpoch != null ? [exceptEpoch] : [])).changes;
|
|
732
918
|
const actionOf = (m, id) => { const r = m.db.prepare('SELECT * FROM engine_actions WHERE id=?').get(id); return r ? { ...r, result: parse(r.result_json) } : null; };
|
|
733
919
|
function actions(m, { controller = null, state = null, sinceMs = null, limit = 200 } = {}) {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
920
|
+
const where = [], args = [];
|
|
921
|
+
if (controller) {
|
|
922
|
+
where.push('controller=?');
|
|
923
|
+
args.push(controller);
|
|
924
|
+
}
|
|
925
|
+
if (state) {
|
|
926
|
+
where.push('state=?');
|
|
927
|
+
args.push(state);
|
|
928
|
+
}
|
|
929
|
+
if (sinceMs != null) {
|
|
930
|
+
where.push('COALESCE(finished_at,started_at)>?');
|
|
931
|
+
args.push(m.now() - sinceMs);
|
|
932
|
+
}
|
|
933
|
+
return m.db.prepare(`SELECT * FROM engine_actions ${where.length ? 'WHERE ' + where.join(' AND ') : ''} ORDER BY COALESCE(finished_at,started_at) DESC LIMIT ?`).all(...args, limit)
|
|
934
|
+
.map((r) => ({ ...r, result: parse(r.result_json) }));
|
|
740
935
|
}
|
|
741
936
|
function actionStep(m, actionId, { step, ms = null, ok = null, detail = null }) {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
937
|
+
const n = Number(m.db.prepare('SELECT COALESCE(max(step_no),0)+1 n FROM action_steps WHERE action_id=?').get(actionId)?.n);
|
|
938
|
+
insertRow(m.db, 'action_steps', { action_id: actionId, step_no: n, step, started_at: m.now(), ms, ok: bool(ok), detail: detail == null ? null : String(detail) });
|
|
939
|
+
return n;
|
|
745
940
|
}
|
|
746
|
-
|
|
747
941
|
const controllerModes = (m) => Object.fromEntries(m.db.prepare('SELECT controller, mode FROM controller_modes').all().map((r) => [r.controller, r.mode]));
|
|
748
942
|
/** Change a controller mode: a mode_changes row first (who and why, G6), then controller_modes (trigger enforces the order). */
|
|
749
943
|
function setControllerMode(m, { controller, mode, by, reason }) {
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
944
|
+
need(by && reason, 'setControllerMode needs by and reason');
|
|
945
|
+
return m.transaction((db) => {
|
|
946
|
+
const from = db.prepare('SELECT mode FROM controller_modes WHERE controller=?').get(controller)?.mode ?? null;
|
|
947
|
+
if (from === mode)
|
|
948
|
+
return { changed: false, mode };
|
|
949
|
+
insertRow(db, 'mode_changes', { controller, from_mode: from, to_mode: mode, by, reason, at: m.now() });
|
|
950
|
+
upsertRow(db, 'controller_modes', { controller, mode, set_at: m.now(), set_by: by }, ['controller']);
|
|
951
|
+
return { changed: true, from, mode };
|
|
952
|
+
});
|
|
758
953
|
}
|
|
759
954
|
const modeChanges = (m, { limit = 100 } = {}) => m.db.prepare('SELECT * FROM mode_changes ORDER BY change_id DESC LIMIT ?').all(limit);
|
|
760
|
-
|
|
761
955
|
/** Open an SLA episode for (entity,state) unless one is open (G3). Returns the episode id. */
|
|
762
956
|
function openSlaEpisode(m, { entity, state, code, severity = 'warn', ledgerId = null, workflowId = null, slaMs, enteredAt = m.now() }) {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
957
|
+
const open = m.db.prepare('SELECT episode_id FROM sla_episodes WHERE entity=? AND state=? AND cleared_at IS NULL').get(entity, state);
|
|
958
|
+
if (open)
|
|
959
|
+
return Number(open.episode_id);
|
|
960
|
+
return Number(insertRow(m.db, 'sla_episodes', { entity, state, code, severity, ledger_id: ledgerId, workflow_id: workflowId, entered_at: enteredAt, sla_ms: slaMs }).lastInsertRowid);
|
|
766
961
|
}
|
|
767
962
|
const markSlaViolated = (m, episodeId) => m.db.prepare('UPDATE sla_episodes SET violated_at=? WHERE episode_id=? AND violated_at IS NULL').run(m.now(), episodeId).changes > 0;
|
|
768
963
|
const markSlaReported = (m, episodeId, { diId = null } = {}) => m.db.prepare('UPDATE sla_episodes SET reported_at=?, di_id=COALESCE(di_id,?) WHERE episode_id=? AND reported_at IS NULL').run(m.now(), diId, episodeId).changes > 0;
|
|
769
964
|
function clearSla(m, { entity, state = null, reason = 'resolved' }) {
|
|
770
|
-
|
|
965
|
+
return m.db.prepare(`UPDATE sla_episodes SET cleared_at=?, clear_reason=? WHERE entity=? ${state ? 'AND state=?' : ''} AND cleared_at IS NULL`).run(m.now(), reason, entity, ...(state ? [state] : [])).changes;
|
|
771
966
|
}
|
|
772
967
|
const openSla = (m) => m.db.prepare('SELECT * FROM v_sla_open ORDER BY entered_at').all();
|
|
773
968
|
function recordViolation(m, { code, severity = 'warn', entity, ledgerId = null, workflowId = null, episodeId = null, diId = null, lessonId = null, detail = null }) {
|
|
774
|
-
|
|
969
|
+
return Number(insertRow(m.db, 'invariant_violations', { code, severity, entity, ledger_id: ledgerId, workflow_id: workflowId, episode_id: episodeId, violated_at: m.now(), di_id: diId, lesson_id: lessonId, detail_json: detail }).lastInsertRowid);
|
|
775
970
|
}
|
|
776
971
|
const clearViolation = (m, violationId) => m.db.prepare('UPDATE invariant_violations SET cleared_at=? WHERE violation_id=? AND cleared_at IS NULL').run(m.now(), violationId).changes > 0;
|
|
777
|
-
|
|
778
972
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
779
973
|
// B3. Services, seats, deliveries, terminals, locks, claims, sessions
|
|
780
974
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
781
975
|
/** Set a service's state; a change appends service_events (G5). */
|
|
782
976
|
function setService(m, { name, kind, state, pid = undefined, port = undefined, url = undefined, probe = undefined, quarantinedUntil = undefined, action = null, actionId = null, probeMs = null, probeError = null }) {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
977
|
+
return m.transaction((db) => {
|
|
978
|
+
const cur = db.prepare('SELECT state FROM services WHERE name=?').get(name);
|
|
979
|
+
const at = m.now();
|
|
980
|
+
upsertRow(db, 'services', { name, kind, state, since: cur?.state === state ? undefined : at, pid, port, url, last_probe_json: probe, quarantined_until: quarantinedUntil }, ['name']);
|
|
981
|
+
if (cur?.state !== state || (action && action !== 'none'))
|
|
982
|
+
insertRow(db, 'service_events', { name, at, from_state: cur?.state ?? null, to_state: state, probe_ms: probeMs, probe_error: probeError, action, action_id: actionId });
|
|
983
|
+
return { changed: cur?.state !== state, from: cur?.state ?? null };
|
|
984
|
+
});
|
|
791
985
|
}
|
|
792
986
|
const recordProbe = (m, { name, ok, latencyMs = null, detail = null }) => insertRow(m.db, 'service_probes', { name, at: m.now(), ok: ok ? 1 : 0, latency_ms: latencyMs, detail_json: detail });
|
|
793
987
|
const services = (m) => m.db.prepare('SELECT * FROM v_services ORDER BY name').all().map((r) => ({ ...r, lastProbe: parse(r.last_probe_json) }));
|
|
794
988
|
const serviceEvents = (m, { name = null, sinceMs = 86400000 } = {}) => m.db.prepare(`SELECT * FROM service_events WHERE at>? ${name ? 'AND name=?' : ''} ORDER BY seq`).all(m.now() - sinceMs, ...(name ? [name] : []));
|
|
795
|
-
|
|
796
989
|
const upsertSeat = (m, seat) => upsertRow(m.db, 'seats', snake(seat), ['seat_id']);
|
|
797
990
|
const seatOf = (m, seatId) => { const r = m.db.prepare('SELECT * FROM seats WHERE seat_id=?').get(seatId); return r ? { ...r, detail: parse(r.detail_json) } : null; };
|
|
798
991
|
const seats = (m) => m.db.prepare('SELECT * FROM v_seats ORDER BY seat_id').all();
|
|
799
992
|
/** One delivery attempt to a seat (G11, MB-02); the trigger counts consecutive input failures on the seat. */
|
|
800
993
|
function recordDelivery(m, { messageKind, messageRef, ledgerId = null, seatId = null, terminalHandle = null, channel, outcome, turnId = null, detail = null }) {
|
|
801
|
-
|
|
802
|
-
|
|
994
|
+
return Number(insertRow(m.db, 'deliveries', { message_kind: messageKind, message_ref: String(messageRef), ledger_id: ledgerId, seat_id: seatId, terminal_handle: terminalHandle,
|
|
995
|
+
channel, attempted_at: m.now(), outcome, turn_id: turnId, detail: detail == null ? null : String(detail) }).lastInsertRowid);
|
|
803
996
|
}
|
|
804
997
|
/** A wake outcome (scripts/kernel/wake-delivery.mjs action) as a deliveries.outcome, or null when it says nothing about input. */
|
|
805
998
|
const WAKE_OUTCOMES = Object.freeze({ 'kernel-woken': 'delivered', 'kernel-unwritable': 'unwritable', 'kernel-exited': 'exited', 'kernel-unavailable': 'unavailable',
|
|
806
|
-
|
|
999
|
+
'kernel-send-failed': 'failed', 'kernel-busy': 'busy-deferred' });
|
|
807
1000
|
/**
|
|
808
1001
|
* MB-05: count one wake of a seat. The outcome becomes a deliveries row; the deliveries trigger keeps
|
|
809
1002
|
* seats.input_failures_consecutive / _total (a refused input adds one, a delivered one resets). A seat whose
|
|
810
1003
|
* terminal changed starts from zero. Returns {failures, since, replace} with replace at `max` in a row.
|
|
811
1004
|
*/
|
|
812
1005
|
function recordSeatInput(m, { seatId, terminal = null, action, messageKind = 'wake', messageRef = 'wake', channel = 'orca', role = 'supervisor', max = 3, detail = null }) {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1006
|
+
return m.transaction((db) => {
|
|
1007
|
+
const seat = db.prepare('SELECT * FROM seats WHERE seat_id=?').get(seatId);
|
|
1008
|
+
if (!seat)
|
|
1009
|
+
insertRow(db, 'seats', { seat_id: seatId, role, state: 'live', terminal_handle: terminal });
|
|
1010
|
+
else if (terminal && seat.terminal_handle !== terminal)
|
|
1011
|
+
db.prepare('UPDATE seats SET terminal_handle=?, input_failures_consecutive=0, last_input_failure_at=NULL WHERE seat_id=?').run(terminal, seatId);
|
|
1012
|
+
const outcome = WAKE_OUTCOMES[action] ?? null;
|
|
1013
|
+
// A busy seat is not an input answer: its row carries no seat_id, so the trigger leaves the run of failures as it is.
|
|
1014
|
+
if (outcome)
|
|
1015
|
+
recordDelivery(m, { messageKind, messageRef, seatId: outcome === 'busy-deferred' ? null : seatId, terminalHandle: terminal, channel, outcome, detail: detail ?? `${seatId} ${action}` });
|
|
1016
|
+
const row = db.prepare('SELECT input_failures_consecutive n, last_input_ok_at ok, last_input_failure_at bad FROM seats WHERE seat_id=?').get(seatId);
|
|
1017
|
+
const failures = Number(row?.n) || 0;
|
|
1018
|
+
const since = failures ? db.prepare("SELECT min(attempted_at) at FROM (SELECT attempted_at FROM deliveries WHERE seat_id=? AND outcome IN ('unwritable','exited','unavailable','failed') ORDER BY delivery_id DESC LIMIT ?)").get(seatId, failures)?.at ?? row?.bad : null;
|
|
1019
|
+
return { failures, since, replace: failures >= max };
|
|
1020
|
+
});
|
|
826
1021
|
}
|
|
827
1022
|
const startSeatTurn = (m, { seatId, wokenByDelivery = null, spanId = newSpanId() }) => Number(insertRow(m.db, 'seat_turns', { seat_id: seatId, woken_by_delivery: wokenByDelivery, started_at: m.now(), span_id: spanId }).lastInsertRowid);
|
|
828
1023
|
const endSeatTurn = (m, turnId, { endReason = 'idle', actionsCount = null } = {}) => m.db.prepare('UPDATE seat_turns SET ended_at=?, end_reason=?, actions_count=COALESCE(?,actions_count) WHERE turn_id=? AND ended_at IS NULL').run(m.now(), endReason, actionsCount, turnId).changes > 0;
|
|
829
1024
|
function seatTranscriptSnapshot(m, { seatId, terminalHandle = null, text }) {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1025
|
+
const sha = putMachineBlob(m, String(text), { mediaType: 'text/plain' });
|
|
1026
|
+
insertRow(m.db, 'seat_transcript_snapshots', { seat_id: seatId, terminal_handle: terminalHandle, at: m.now(), lines: String(text).split('\n').length, bytes: Buffer.byteLength(String(text)), sha256: sha }, { orIgnore: true });
|
|
1027
|
+
return sha;
|
|
833
1028
|
}
|
|
834
1029
|
const upsertTerminal = (m, terminal) => upsertRow(m.db, 'terminals', snake(terminal), ['handle']);
|
|
835
1030
|
const closeTerminal = (m, handle, { by = null, verified = false } = {}) => m.db.prepare('UPDATE terminals SET closed_at=COALESCE(closed_at,?), closed_by=COALESCE(closed_by,?), close_verified_at=CASE WHEN ? THEN ? ELSE close_verified_at END WHERE handle=?')
|
|
836
|
-
|
|
837
|
-
|
|
1031
|
+
.run(m.now(), by, verified ? 1 : 0, m.now(), handle).changes > 0;
|
|
838
1032
|
/**
|
|
839
1033
|
* Take a host lock (replaces connectors/*.lock and *.starting.json): succeeds when free, released, expired, or already
|
|
840
1034
|
* ours. Returns {ok, holder?}.
|
|
841
1035
|
*/
|
|
842
1036
|
function acquireHostLock(m, { name, holder = null, pid = process.pid, ttlMs = 60000, processRunId = null, state = 'held', retainExpired = false }) {
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1037
|
+
return m.transaction((db) => {
|
|
1038
|
+
const at = m.now();
|
|
1039
|
+
const cur = db.prepare('SELECT * FROM host_locks WHERE name=?').get(name);
|
|
1040
|
+
if (cur && cur.state !== 'released' && (cur.expires_at > at || (retainExpired && name.startsWith('provision:'))) && (cur.holder_pid !== pid || (retainExpired && name.startsWith('provision:') && cur.holder !== holder)))
|
|
1041
|
+
return { ok: false, holder: cur };
|
|
1042
|
+
upsertRow(db, 'host_locks', { name, holder_pid: pid, holder, process_run_id: processRunId, started_at: cur && cur.holder_pid === pid && cur.state !== 'released' ? cur.started_at : at,
|
|
1043
|
+
heartbeat_at: at, expires_at: at + ttlMs, handed_over_from: cur && cur.holder_pid !== pid && cur.state !== 'released' ? cur.holder_pid : null, state }, ['name']);
|
|
1044
|
+
return { ok: true };
|
|
1045
|
+
});
|
|
851
1046
|
}
|
|
852
1047
|
const renewHostLock = (m, { name, pid = process.pid, ttlMs = 60000, state = undefined }) => m.db.prepare(`UPDATE host_locks SET heartbeat_at=?, expires_at=? ${state ? ', state=?' : ''} WHERE name=? AND holder_pid=? AND state<>'released'`)
|
|
853
|
-
|
|
1048
|
+
.run(m.now(), m.now() + ttlMs, ...(state ? [state] : []), name, pid).changes > 0;
|
|
854
1049
|
const releaseHostLock = (m, { name, pid = process.pid, force = false }) => m.db.prepare(`UPDATE host_locks SET state='released', heartbeat_at=? WHERE name=? ${force ? '' : 'AND holder_pid=?'} AND state<>'released'`)
|
|
855
|
-
|
|
1050
|
+
.run(m.now(), name, ...(force ? [] : [pid])).changes > 0;
|
|
856
1051
|
const hostLock = (m, name) => m.db.prepare('SELECT * FROM host_locks WHERE name=?').get(name) ?? null;
|
|
857
1052
|
const hostLocks = (m) => m.db.prepare("SELECT * FROM host_locks WHERE state<>'released' ORDER BY name").all();
|
|
858
|
-
|
|
859
1053
|
function claimResource(m, { resourcePath, kind, ownerActionId = null, ownerPid = process.pid, ownerRunId = null, hasJunctions = false }) {
|
|
860
|
-
|
|
861
|
-
|
|
1054
|
+
return Number(insertRow(m.db, 'claims', { resource_path: path.resolve(resourcePath), kind, owner_action_id: ownerActionId, owner_pid: ownerPid, owner_run_id: ownerRunId,
|
|
1055
|
+
has_junctions: hasJunctions ? 1 : 0, created_at: m.now() }).lastInsertRowid);
|
|
862
1056
|
}
|
|
863
1057
|
const releaseClaim = (m, claimId) => m.db.prepare('UPDATE claims SET released_at=? WHERE claim_id=? AND released_at IS NULL').run(m.now(), claimId).changes > 0;
|
|
864
1058
|
const sweptClaim = (m, claimId, { error = null } = {}) => m.db.prepare('UPDATE claims SET swept_at=?, sweep_error=? WHERE claim_id=?').run(m.now(), error, claimId).changes > 0;
|
|
865
1059
|
const liveClaims = (m) => m.db.prepare('SELECT * FROM claims WHERE released_at IS NULL AND swept_at IS NULL ORDER BY claim_id').all();
|
|
866
1060
|
const upsertAgentSession = (m, session) => upsertRow(m.db, 'agent_sessions', snake(session), ['session_id']);
|
|
867
|
-
|
|
868
1061
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
869
1062
|
// B4. Resources: throttle, samples, providers, pool backoff, quotas, guards, host leases, budgets
|
|
870
1063
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
871
1064
|
const throttleState = (m) => { const r = m.db.prepare('SELECT * FROM throttle_state WHERE id=1').get(); return r ? { ...r, slotTargets: parse(r.slot_targets_json), priorities: parse(r.priorities_json) } : null; };
|
|
872
1065
|
/** Write the current throttle row (MB-15: writer + rev). A mode change appends throttle_events first (G7). */
|
|
873
|
-
function setThrottle(m, { mode, effectiveCap = null, heavyCap = null, running = null, freeRamPct = null, freeRamMb = null, cpuPct = null, cpuHot = null, reason = null,
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1066
|
+
function setThrottle(m, { mode, effectiveCap = null, heavyCap = null, running = null, freeRamPct = null, freeRamMb = null, cpuPct = null, cpuHot = null, reason = null, writer, writerRev = runtimeRev(), slotTargets = null, priorities = null, sample = null }) {
|
|
1067
|
+
need(writer, 'setThrottle needs writer');
|
|
1068
|
+
return m.transaction((db) => {
|
|
1069
|
+
const at = m.now();
|
|
1070
|
+
const cur = db.prepare('SELECT mode, since, writer, writer_rev FROM throttle_state WHERE id=1').get();
|
|
1071
|
+
// MB-15: a process running an OLDER runtime never overwrites what a newer runtime wrote.
|
|
1072
|
+
if (cur && compareRevs(writerRev, cur.writer_rev) < 0)
|
|
1073
|
+
return { changed: false, refused: `stale-writer-rev: ${writerRev} is older than ${cur.writer_rev} (${cur.writer})`, from: cur.mode };
|
|
1074
|
+
const changed = cur?.mode !== mode;
|
|
1075
|
+
if (changed)
|
|
1076
|
+
insertRow(db, 'throttle_events', { at, from_mode: cur?.mode ?? null, to_mode: mode, reason, free_ram_pct: freeRamPct, cpu_pct: cpuPct, effective_cap: effectiveCap, running, writer_rev: writerRev, sample_json: sample });
|
|
1077
|
+
upsertRow(db, 'throttle_state', { id: 1, mode, effective_cap: effectiveCap, heavy_cap: heavyCap, running, free_ram_pct: freeRamPct, free_ram_mb: freeRamMb, cpu_pct: cpuPct, cpu_hot: int(cpuHot),
|
|
1078
|
+
since: changed ? at : cur.since, updated_at: at, reason, writer, writer_rev: writerRev, slot_targets_json: slotTargets, priorities_json: priorities }, ['id']);
|
|
1079
|
+
return { changed, from: cur?.mode ?? null };
|
|
1080
|
+
});
|
|
888
1081
|
}
|
|
889
1082
|
const throttleEvents = (m, { sinceMs = 86400000 } = {}) => m.db.prepare('SELECT * FROM throttle_events WHERE at>? ORDER BY seq').all(m.now() - sinceMs);
|
|
890
|
-
const recordThrottleDecision = (m, { ledgerId = null, workflowId = null, jobId = null, reason, waitedMs = null }) =>
|
|
891
|
-
Number(insertRow(m.db, 'throttle_decisions', { at: m.now(), ledger_id: ledgerId, workflow_id: workflowId, job_id: jobId, reason, waited_ms: waitedMs }).lastInsertRowid);
|
|
1083
|
+
const recordThrottleDecision = (m, { ledgerId = null, workflowId = null, jobId = null, reason, waitedMs = null }) => Number(insertRow(m.db, 'throttle_decisions', { at: m.now(), ledger_id: ledgerId, workflow_id: workflowId, job_id: jobId, reason, waited_ms: waitedMs }).lastInsertRowid);
|
|
892
1084
|
const releaseThrottleDecision = (m, seq) => m.db.prepare('UPDATE throttle_decisions SET released_at=?, waited_ms=?-at WHERE seq=? AND released_at IS NULL').run(m.now(), m.now(), seq).changes > 0;
|
|
893
1085
|
const recordHostSample = (m, sample) => Number(insertRow(m.db, 'host_samples', { at: m.now(), ...snake(sample) }).lastInsertRowid);
|
|
894
1086
|
const hostSamples = (m, { kind = 'host', sinceMs = 3600000 } = {}) => m.db.prepare('SELECT * FROM host_samples WHERE kind=? AND at>? ORDER BY seq').all(kind, m.now() - sinceMs);
|
|
895
1087
|
function setProviderHealth(m, { provider, status, failureKind = null, strikes = 0, strikeLimit = null, circuitOpenUntil = null, reason = null, ledgerId = null, attemptId = null, detail = null }) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1088
|
+
return m.transaction((db) => {
|
|
1089
|
+
const cur = db.prepare('SELECT status FROM provider_health WHERE provider=?').get(provider);
|
|
1090
|
+
const at = m.now();
|
|
1091
|
+
upsertRow(db, 'provider_health', { provider, status, failure_kind: failureKind, strikes, strike_limit: strikeLimit, circuit_open_until: circuitOpenUntil,
|
|
1092
|
+
recovered_at: status === 'recovered' ? at : undefined, reason, updated_at: at, detail_json: detail }, ['provider']);
|
|
1093
|
+
if (cur?.status !== status)
|
|
1094
|
+
insertRow(db, 'provider_health_events', { provider, at, from_status: cur?.status ?? null, to_status: status, failure_kind: failureKind, ledger_id: ledgerId, attempt_id: attemptId, detail_json: circuitOpenUntil == null ? detail : { ...detail, circuitOpenUntil } });
|
|
1095
|
+
return { changed: cur?.status !== status };
|
|
1096
|
+
});
|
|
904
1097
|
}
|
|
905
1098
|
const providerHealth = (m) => m.db.prepare('SELECT * FROM provider_health ORDER BY provider').all();
|
|
906
1099
|
const poolBackoff = (m, pool = null) => (pool ? m.db.prepare('SELECT * FROM pool_backoff WHERE pool=?').get(pool) ?? null : m.db.prepare('SELECT * FROM pool_backoff ORDER BY pool').all());
|
|
@@ -908,65 +1101,69 @@ const setPoolBackoff = (m, { pool, untilAt = null, strikes = 0, reason = null })
|
|
|
908
1101
|
const clearPoolBackoff = (m, pool) => m.db.prepare('DELETE FROM pool_backoff WHERE pool=?').run(pool).changes > 0;
|
|
909
1102
|
const setQuota = (m, { provider, window, used = null, limitValue = null, resetAt = null, source = null }) => upsertRow(m.db, 'quotas', { provider, window, used, limit_value: limitValue, reset_at: resetAt, source, observed_at: m.now() }, ['provider', 'window']);
|
|
910
1103
|
const quotas = (m) => m.db.prepare('SELECT * FROM quotas ORDER BY provider, window').all();
|
|
911
|
-
|
|
912
1104
|
const { reserveProvider, providerReservations, providerReservationUsage, markProviderReservation, releaseProviderReservation } = providerReservationMethods({ need, parse, toJson, hex });
|
|
913
|
-
|
|
914
|
-
const upsertGuardJob = (m, { jobId, ledgerId = null, workflowId = null, attemptId = null, allow, hooks = null, ttlMs = 86400000 }) =>
|
|
915
|
-
upsertRow(m.db, 'guard_jobs', { job_id: jobId, ledger_id: ledgerId, workflow_id: workflowId, attempt_id: attemptId, allow_json: allow, hooks_json: hooks, created_at: m.now(), expires_at: m.now() + ttlMs, released_at: null }, ['job_id']);
|
|
1105
|
+
const upsertGuardJob = (m, { jobId, ledgerId = null, workflowId = null, attemptId = null, allow, hooks = null, ttlMs = 86400000 }) => upsertRow(m.db, 'guard_jobs', { job_id: jobId, ledger_id: ledgerId, workflow_id: workflowId, attempt_id: attemptId, allow_json: allow, hooks_json: hooks, created_at: m.now(), expires_at: m.now() + ttlMs, released_at: null }, ['job_id']);
|
|
916
1106
|
const guardJob = (m, jobId) => { const r = m.db.prepare('SELECT * FROM guard_jobs WHERE job_id=?').get(jobId); return r ? { ...r, allow: parse(r.allow_json), hooks: parse(r.hooks_json) } : null; };
|
|
917
1107
|
const releaseGuardJob = (m, jobId) => m.db.prepare('UPDATE guard_jobs SET released_at=? WHERE job_id=? AND released_at IS NULL').run(m.now(), jobId).changes > 0;
|
|
918
1108
|
const recordGuardRefusal = (m, refusal) => Number(insertRow(m.db, 'guard_refusals', { at: m.now(), ...snake(refusal) }).lastInsertRowid);
|
|
919
1109
|
/** Host leases (the machine side of a ledger's two-phase reserve): drop by token. */
|
|
920
1110
|
function releaseHostLeases(m, tokens) {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1111
|
+
const list = [...new Set([tokens].flat().map((t) => (typeof t === 'string' ? t : t?.token)).filter(Boolean))];
|
|
1112
|
+
let released = 0;
|
|
1113
|
+
for (const token of list)
|
|
1114
|
+
released += m.db.prepare('DELETE FROM host_leases WHERE token=?').run(token).changes;
|
|
1115
|
+
return { ok: true, released };
|
|
924
1116
|
}
|
|
925
1117
|
const hostLeases = (m) => m.db.prepare('SELECT * FROM host_leases ORDER BY resource_key').all();
|
|
926
1118
|
const setBudget = (m, { scopeKey, limitValue, window = null }) => m.db.prepare('INSERT INTO budgets(scope_key,limit_value,window,updated_at) VALUES(?,?,?,?) ON CONFLICT(scope_key) DO UPDATE SET limit_value=excluded.limit_value, window=excluded.window, updated_at=excluded.updated_at')
|
|
927
|
-
|
|
1119
|
+
.run(scopeKey, limitValue, window, m.now());
|
|
928
1120
|
/** Reserve units against a budget; refuses when used+reserved+units > limit. */
|
|
929
1121
|
function reserveBudget(m, { scopeKey, ledgerId, jobId, units }) {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1122
|
+
return m.transaction((db) => {
|
|
1123
|
+
const b = db.prepare('SELECT * FROM budgets WHERE scope_key=?').get(scopeKey);
|
|
1124
|
+
if (!b)
|
|
1125
|
+
return { ok: false, reason: 'no-budget' };
|
|
1126
|
+
const held = db.prepare('SELECT units FROM budget_reservations WHERE scope_key=? AND ledger_id=? AND job_id=?').get(scopeKey, ledgerId, jobId);
|
|
1127
|
+
if (held)
|
|
1128
|
+
return { ok: true, already: true };
|
|
1129
|
+
if (b.used_value + b.reserved_value + units > b.limit_value)
|
|
1130
|
+
return { ok: false, reason: 'budget-exhausted', budget: b };
|
|
1131
|
+
insertRow(db, 'budget_reservations', { scope_key: scopeKey, ledger_id: ledgerId, job_id: jobId, units, at: m.now() });
|
|
1132
|
+
db.prepare('UPDATE budgets SET reserved_value=reserved_value+?, updated_at=? WHERE scope_key=?').run(units, m.now(), scopeKey);
|
|
1133
|
+
return { ok: true };
|
|
1134
|
+
});
|
|
940
1135
|
}
|
|
941
1136
|
/** Settle a reservation: `used` units move to used_value, the rest is returned. */
|
|
942
1137
|
function settleBudget(m, { scopeKey, ledgerId, jobId, used = null }) {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1138
|
+
return m.transaction((db) => {
|
|
1139
|
+
const r = db.prepare('SELECT units FROM budget_reservations WHERE scope_key=? AND ledger_id=? AND job_id=?').get(scopeKey, ledgerId, jobId);
|
|
1140
|
+
if (!r)
|
|
1141
|
+
return false;
|
|
1142
|
+
db.prepare('DELETE FROM budget_reservations WHERE scope_key=? AND ledger_id=? AND job_id=?').run(scopeKey, ledgerId, jobId);
|
|
1143
|
+
db.prepare('UPDATE budgets SET reserved_value=max(0,reserved_value-?), used_value=used_value+?, updated_at=? WHERE scope_key=?').run(r.units, used ?? r.units, m.now(), scopeKey);
|
|
1144
|
+
return true;
|
|
1145
|
+
});
|
|
950
1146
|
}
|
|
951
1147
|
const budgets = (m) => m.db.prepare('SELECT * FROM budgets ORDER BY scope_key').all();
|
|
952
|
-
|
|
953
1148
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
954
1149
|
// B5. GC, lanes, land queue, land runs, pushes, worktrees, env servers, UAT slots, connectors, asks
|
|
955
1150
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
956
1151
|
const startGcRun = (m, { trigger = 'sweep', actionId = null, collectors = null, startedAt = m.now() } = {}) => Number(insertRow(m.db, 'gc_runs', { action_id: actionId, started_at: startedAt, trigger, collectors_json: collectors }).lastInsertRowid);
|
|
957
1152
|
/** Finish a GC run; the full report is a blob (G4). */
|
|
958
1153
|
function finishGcRun(m, runId, { freedBytes = null, counts = null, errors = null, report = null, finishedAt = m.now() } = {}) {
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1154
|
+
const reportSha = report == null ? null : putMachineBlob(m, JSON.stringify(report), { mediaType: 'application/json' });
|
|
1155
|
+
return m.db.prepare('UPDATE gc_runs SET finished_at=?, freed_bytes=?, counts_json=?, errors_json=?, report_sha=? WHERE run_id=?')
|
|
1156
|
+
.run(finishedAt, freedBytes, toJson(counts), toJson(errors), reportSha, runId).changes > 0;
|
|
962
1157
|
}
|
|
963
1158
|
/** One thing GC touched (G4, MB-14). `outcome` is the final fate; a retry sets nextTryAt past the grace window. */
|
|
964
1159
|
const recordGcItem = (m, item) => Number(insertRow(m.db, 'gc_items', { at: m.now(), ...snake(item) }).lastInsertRowid);
|
|
965
1160
|
const updateGcItem = (m, itemId, fields) => updateRow(m.db, 'gc_items', snake(fields), { item_id: itemId }).changes > 0;
|
|
966
1161
|
const gcItems = (m, { runId = null, open = false, collector = null, limit = 500 } = {}) => m.db.prepare(`SELECT * FROM gc_items WHERE 1=1 ${runId != null ? 'AND run_id=?' : ''} ${open ? 'AND outcome IS NULL' : ''} ${collector ? 'AND collector=?' : ''} ORDER BY item_id DESC LIMIT ?`)
|
|
967
|
-
|
|
1162
|
+
.all(...(runId != null ? [runId] : []), ...(collector ? [collector] : []), limit);
|
|
968
1163
|
function gcMark(m, runId, entries) {
|
|
969
|
-
|
|
1164
|
+
return m.transaction((db) => { const st = db.prepare('INSERT OR IGNORE INTO gc_marks(run_id,sha256,source,pinned) VALUES(?,?,?,?)'); for (const e of entries) {
|
|
1165
|
+
st.run(runId, e.sha256, e.source, e.pinned ? 1 : 0);
|
|
1166
|
+
} return entries.length; });
|
|
970
1167
|
}
|
|
971
1168
|
/** A blob whose bytes were archived (zip) before the sweep: archived_at + archive_ref on the machine's blobs row. */
|
|
972
1169
|
const markMachineBlobArchived = (m, { sha256: sha, archivedAt = m.now(), archiveRef }) => m.db.prepare('UPDATE blobs SET archived_at=?, archive_ref=? WHERE sha256=?').run(archivedAt, archiveRef, sha).changes > 0;
|
|
@@ -976,60 +1173,69 @@ const markMachineBlobArchived = (m, { sha256: sha, archivedAt = m.now(), archive
|
|
|
976
1173
|
* except each seat's newest one. Returns the rows deleted.
|
|
977
1174
|
*/
|
|
978
1175
|
const pruneSeatSnapshots = (m, { now = m.now(), seatMs }) => m.transaction((db) => {
|
|
979
|
-
|
|
1176
|
+
const final = db.prepare(`DELETE FROM seat_transcript_snapshots WHERE EXISTS (SELECT 1 FROM agent_sessions a
|
|
980
1177
|
WHERE a.seat_id=seat_transcript_snapshots.seat_id AND a.transcript_sha IS NOT NULL AND a.ended_at IS NOT NULL
|
|
981
1178
|
AND seat_transcript_snapshots.at<=a.ended_at)`).run().changes;
|
|
982
|
-
|
|
1179
|
+
const aged = db.prepare(`DELETE FROM seat_transcript_snapshots WHERE at<? AND snapshot_id NOT IN
|
|
983
1180
|
(SELECT max(snapshot_id) FROM seat_transcript_snapshots GROUP BY seat_id)`).run(now - seatMs).changes;
|
|
984
|
-
|
|
1181
|
+
return final + aged;
|
|
985
1182
|
});
|
|
986
1183
|
const gcRuns = (m, { limit = 20 } = {}) => m.db.prepare('SELECT * FROM gc_runs ORDER BY run_id DESC LIMIT ?').all(limit);
|
|
987
|
-
|
|
988
1184
|
function upsertLane(m, { name, worktreePath, branch, baseSha = null, headSha = null, owner, supJobId = null, state = 'open' }) {
|
|
989
|
-
|
|
990
|
-
|
|
1185
|
+
const cur = m.db.prepare('SELECT created_at FROM lanes WHERE name=?').get(name);
|
|
1186
|
+
return upsertRow(m.db, 'lanes', { name, worktree_path: path.resolve(worktreePath), branch, base_sha: baseSha, head_sha: headSha, owner, sup_job_id: supJobId, state, created_at: cur?.created_at ?? m.now() }, ['name']);
|
|
991
1187
|
}
|
|
992
1188
|
function setLaneState(m, name, state, { reportText = null, headSha = null } = {}) {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1189
|
+
const set = { state };
|
|
1190
|
+
if (state === 'landed')
|
|
1191
|
+
set.landed_at = m.now();
|
|
1192
|
+
if (state === 'removed')
|
|
1193
|
+
set.removed_at = m.now();
|
|
1194
|
+
if (headSha)
|
|
1195
|
+
set.head_sha = headSha;
|
|
1196
|
+
if (reportText)
|
|
1197
|
+
set.report_sha = textBlob(m, reportText, 'text/markdown');
|
|
1198
|
+
return updateRow(m.db, 'lanes', set, { name }).changes > 0;
|
|
999
1199
|
}
|
|
1000
1200
|
const laneOf = (m, name) => m.db.prepare('SELECT * FROM lanes WHERE name=?').get(name) ?? null;
|
|
1001
1201
|
const lanes = (m, { state = null } = {}) => m.db.prepare(`SELECT * FROM lanes ${state ? 'WHERE state=?' : ''} ORDER BY created_at`).all(...(state ? [state] : []));
|
|
1002
1202
|
/** Enqueue a land ticket (replaces the land queue dirs). The lane row is created when absent (FK). */
|
|
1003
1203
|
function enqueueLand(m, { ticketId = `land-${Date.now().toString(36)}-${hex(4)}`, lane = null, commitSha, commits = null, requestedBy = `pid:${process.pid}` }) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1204
|
+
return m.transaction((db) => {
|
|
1205
|
+
if (lane && !db.prepare('SELECT 1 FROM lanes WHERE name=?').get(lane))
|
|
1206
|
+
insertRow(db, 'lanes', { name: lane, worktree_path: '', branch: `lane/${lane}`, owner: requestedBy ?? 'unknown', state: 'open', created_at: m.now() });
|
|
1207
|
+
insertRow(db, 'land_queue', { ticket_id: ticketId, lane, commit_sha: commitSha, commits, requested_by: requestedBy, state: 'queued', enqueued_at: m.now() });
|
|
1208
|
+
return ticketId;
|
|
1209
|
+
});
|
|
1009
1210
|
}
|
|
1010
1211
|
/** The head of the queue enters the gate when nothing is running. Returns the ticket, or {busy: running ticket}. */
|
|
1011
1212
|
/** requested_by 'pid:<n>' names the process that waits for and then runs the ticket. */
|
|
1012
1213
|
function claimLandGate(m, { ticketId, alive = pidAlive }) {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1214
|
+
return m.transaction((db) => {
|
|
1215
|
+
// A waiter or holder whose process is gone never blocks the queue: its ticket is cancelled.
|
|
1216
|
+
for (const t of db.prepare("SELECT ticket_id, state, requested_by, busy_holder FROM land_queue WHERE state IN ('queued','running')").all()) {
|
|
1217
|
+
const owner = /^pid:(\d+)$/.exec(String(t.requested_by ?? ''));
|
|
1218
|
+
if (t.ticket_id !== ticketId && owner && !alive(Number(owner[1])))
|
|
1219
|
+
db.prepare("UPDATE land_queue SET state='cancelled', finished_at=? WHERE ticket_id=?").run(m.now(), t.ticket_id);
|
|
1220
|
+
}
|
|
1221
|
+
const running = db.prepare("SELECT * FROM land_queue WHERE state='running' ORDER BY started_at LIMIT 1").get();
|
|
1222
|
+
if (running && running.ticket_id !== ticketId) {
|
|
1223
|
+
db.prepare('UPDATE land_queue SET busy_holder=? WHERE ticket_id=?').run(running.ticket_id, ticketId);
|
|
1224
|
+
return { ok: false, busy: running };
|
|
1225
|
+
}
|
|
1226
|
+
const head = db.prepare("SELECT ticket_id FROM land_queue WHERE state='queued' ORDER BY enqueued_at, ticket_id LIMIT 1").get();
|
|
1227
|
+
if (!running && head && head.ticket_id !== ticketId)
|
|
1228
|
+
return { ok: false, behind: head.ticket_id };
|
|
1229
|
+
db.prepare("UPDATE land_queue SET state='running', gate_at=COALESCE(gate_at,?), started_at=COALESCE(started_at,?) WHERE ticket_id=? AND state IN ('queued','running')").run(m.now(), m.now(), ticketId);
|
|
1230
|
+
return { ok: true, ticket: db.prepare('SELECT * FROM land_queue WHERE ticket_id=?').get(ticketId) };
|
|
1231
|
+
});
|
|
1026
1232
|
}
|
|
1027
1233
|
const finishLandTicket = (m, ticketId, state) => m.db.prepare("UPDATE land_queue SET state=?, finished_at=? WHERE ticket_id=? AND state IN ('queued','running')").run(state, m.now(), ticketId).changes > 0;
|
|
1028
1234
|
const landQueue = (m, { open = true } = {}) => m.db.prepare(`SELECT * FROM land_queue ${open ? "WHERE state IN ('queued','running')" : ''} ORDER BY enqueued_at, ticket_id`).all();
|
|
1029
1235
|
/** One land-gate run with full stdout/stderr blobs. */
|
|
1030
1236
|
function recordLandRun(m, { ticketId = null, lane = null, spanId = newSpanId(), parentSpanId = null, commitSha, commits = null, landedSha = null, result, reason = null, pushId = null, specs = null, stdout = null, stderr = null, startedAt, finishedAt = m.now() }) {
|
|
1031
|
-
|
|
1032
|
-
|
|
1237
|
+
return Number(insertRow(m.db, 'land_runs', { ticket_id: ticketId, lane, span_id: spanId, parent_span_id: parentSpanId, commit_sha: commitSha, commits_json: commits ?? (commitSha ? [commitSha] : null), landed_sha: landedSha, result, reason, push_id: pushId,
|
|
1238
|
+
specs_json: specs, stdout_sha: textBlob(m, stdout), stderr_sha: textBlob(m, stderr), started_at: startedAt ?? m.now(), finished_at: finishedAt }).lastInsertRowid);
|
|
1033
1239
|
}
|
|
1034
1240
|
/**
|
|
1035
1241
|
* The core record of one land in ONE transaction, idempotent on spanId (a replay from the outbox or a second try never
|
|
@@ -1037,46 +1243,51 @@ function recordLandRun(m, { ticketId = null, lane = null, spanId = newSpanId(),
|
|
|
1037
1243
|
* runId). {runId, pushId, duplicate}.
|
|
1038
1244
|
*/
|
|
1039
1245
|
function recordLandOutcome(m, { spanId, lane = null, push = null, run, laneHead = null, log: logRow = null }) {
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1246
|
+
need(spanId && run, 'recordLandOutcome needs spanId and run');
|
|
1247
|
+
return m.transaction((db) => {
|
|
1248
|
+
const hit = db.prepare('SELECT run_id, push_id FROM land_runs WHERE span_id=?').get(spanId);
|
|
1249
|
+
if (hit)
|
|
1250
|
+
return { runId: Number(hit.run_id), pushId: hit.push_id == null ? null : Number(hit.push_id), duplicate: true };
|
|
1251
|
+
if (lane && !db.prepare('SELECT 1 FROM lanes WHERE name=?').get(lane))
|
|
1252
|
+
insertRow(db, 'lanes', { name: lane, worktree_path: '', branch: `lane/${lane}`, owner: 'land-gate', state: 'open', created_at: m.now() });
|
|
1253
|
+
const pushId = push ? recordPush(m, push) : null;
|
|
1254
|
+
const runId = recordLandRun(m, { ...run, lane, spanId, pushId });
|
|
1255
|
+
if (lane && laneHead)
|
|
1256
|
+
updateRow(db, 'lanes', { head_sha: laneHead }, { name: lane });
|
|
1257
|
+
if (logRow)
|
|
1258
|
+
log(m, { ...logRow, data: { ...logRow.data, runId } });
|
|
1259
|
+
return { runId, pushId, duplicate: false };
|
|
1260
|
+
});
|
|
1051
1261
|
}
|
|
1052
1262
|
const landRuns = (m, { lane = null, limit = 50 } = {}) => m.db.prepare(`SELECT * FROM land_runs ${lane ? 'WHERE lane=?' : ''} ORDER BY run_id DESC LIMIT ?`).all(...(lane ? [lane] : []), limit);
|
|
1053
1263
|
/** One push (G8, MB-03): a refusal needs a stable failure signature; logs are full blobs. */
|
|
1054
1264
|
function recordPush(m, { repoRoot, branch = null, head, fromSha = null, toSha = null, result, reason = null, failureSignature = null, ms = null, actionId = null, scan = null, stdout = null, stderr = null }) {
|
|
1055
|
-
|
|
1056
|
-
|
|
1265
|
+
return Number(insertRow(m.db, 'pushes', { repo_root: repoKey(repoRoot), branch, head, from_sha: fromSha, to_sha: toSha, result, reason: reason == null ? null : redactText(String(reason)), failure_signature: failureSignature, ms, action_id: actionId,
|
|
1266
|
+
scan_json: scan, stdout_sha: textBlob(m, stdout), stderr_sha: textBlob(m, stderr), at: m.now() }).lastInsertRowid);
|
|
1057
1267
|
}
|
|
1058
1268
|
const pushes = (m, { repoRoot = null, limit = 50 } = {}) => m.db.prepare(`SELECT * FROM pushes ${repoRoot ? 'WHERE repo_root=?' : ''} ORDER BY push_id DESC LIMIT ?`).all(...(repoRoot ? [repoKey(repoRoot)] : []), limit);
|
|
1059
1269
|
const upsertWorktree = (m, wt) => upsertRow(m.db, 'worktrees', { created_at: m.now(), ...snake(wt), path: path.resolve(wt.path) }, ['path']);
|
|
1060
1270
|
const removedWorktree = (m, wtPath, { error = null, archivedRef = null } = {}) => m.db.prepare('UPDATE worktrees SET removed_at=CASE WHEN ? IS NULL THEN ? ELSE removed_at END, remove_error=?, archived_ref=COALESCE(?,archived_ref) WHERE path=?')
|
|
1061
|
-
|
|
1271
|
+
.run(error, m.now(), error, archivedRef, path.resolve(wtPath)).changes > 0;
|
|
1062
1272
|
/**
|
|
1063
1273
|
* The worktree registry (scripts/machine/worktree-registry.mjs and the worktree api files are its callers): reserve a row for a worktree about to be created,
|
|
1064
1274
|
* atomically against the per-repo cap. BEGIN IMMEDIATE: two dispatches never both take the last slot. `cap` null: no cap.
|
|
1065
1275
|
* {ok, live} | {ok:false, reason:'worktree-cap', live, cap}
|
|
1066
1276
|
*/
|
|
1067
1277
|
function reserveWorktree(m, wt, { cap = null } = {}) {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1278
|
+
return m.transaction((db) => {
|
|
1279
|
+
const repoRoot = repoKey(wt.repoRoot);
|
|
1280
|
+
const target = path.resolve(wt.path);
|
|
1281
|
+
const live = db.prepare('SELECT path FROM worktrees WHERE repo_root=? AND removed_at IS NULL').all(repoRoot).filter((r) => r.path !== target).length;
|
|
1282
|
+
if (cap != null && live >= cap)
|
|
1283
|
+
return { ok: false, reason: 'worktree-cap', live, cap };
|
|
1284
|
+
upsertRow(db, 'worktrees', { ...snake(wt), path: target, repo_root: repoRoot, created_at: m.now(), removed_at: null, remove_error: null }, ['path']);
|
|
1285
|
+
return { ok: true, live: live + 1 };
|
|
1286
|
+
});
|
|
1076
1287
|
}
|
|
1077
1288
|
/** Live (not removed) registry rows, optionally of one repo. */
|
|
1078
1289
|
const liveWorktrees = (m, { repoRoot = null } = {}) => m.db.prepare(`SELECT * FROM worktrees WHERE removed_at IS NULL ${repoRoot ? 'AND repo_root=?' : ''} ORDER BY created_at`)
|
|
1079
|
-
|
|
1290
|
+
.all(...(repoRoot ? [repoKey(repoRoot)] : []));
|
|
1080
1291
|
const worktreeRow = (m, wtPath) => m.db.prepare('SELECT * FROM worktrees WHERE path=?').get(path.resolve(wtPath)) ?? null;
|
|
1081
1292
|
/** Every repo the registry ever held a worktree of. */
|
|
1082
1293
|
const worktreeRepos = (m) => m.db.prepare('SELECT DISTINCT repo_root FROM worktrees ORDER BY repo_root').all().map((r) => r.repo_root);
|
|
@@ -1084,13 +1295,13 @@ const worktreeRepos = (m) => m.db.prepare('SELECT DISTINCT repo_root FROM worktr
|
|
|
1084
1295
|
/** A GC collector's resume point (machine_meta gc_cursor:<name>): where its last bounded pass stopped, or null. */
|
|
1085
1296
|
const gcCursor = (m, name) => m.db.prepare('SELECT value FROM machine_meta WHERE key=?').get(`gc_cursor:${name}`)?.value ?? null;
|
|
1086
1297
|
const setGcCursor = (m, name, value) => (value == null
|
|
1087
|
-
|
|
1088
|
-
|
|
1298
|
+
? m.db.prepare('DELETE FROM machine_meta WHERE key=?').run(`gc_cursor:${name}`)
|
|
1299
|
+
: m.db.prepare('INSERT INTO machine_meta(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value').run(`gc_cursor:${name}`, String(value))).changes > 0;
|
|
1089
1300
|
/** The worktree GC's stop mark (a removal changed the main checkout): {at, damage, path} or null. set(null) clears it. */
|
|
1090
1301
|
const worktreeGcStop = (m) => parse(m.db.prepare("SELECT value FROM machine_meta WHERE key='worktree_gc_stopped'").get()?.value ?? null);
|
|
1091
1302
|
const setWorktreeGcStop = (m, stop) => (stop
|
|
1092
|
-
|
|
1093
|
-
|
|
1303
|
+
? m.db.prepare("INSERT INTO machine_meta(key,value) VALUES('worktree_gc_stopped',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(JSON.stringify(stop))
|
|
1304
|
+
: m.db.prepare("DELETE FROM machine_meta WHERE key='worktree_gc_stopped'").run()).changes > 0;
|
|
1094
1305
|
const dropWorktree = (m, wtPath) => m.db.prepare('DELETE FROM worktrees WHERE path=? AND removed_at IS NULL').run(path.resolve(wtPath)).changes > 0;
|
|
1095
1306
|
const upsertEnvServer = (m, server) => upsertRow(m.db, 'env_servers', snake(server), ['server_id']);
|
|
1096
1307
|
const envServer = (m, serverId) => m.db.prepare('SELECT * FROM env_servers WHERE server_id=?').get(serverId) ?? null;
|
|
@@ -1098,11 +1309,9 @@ const envServers = (m, { live = false } = {}) => m.db.prepare(`SELECT * FROM env
|
|
|
1098
1309
|
const upsertUatSlot = (m, slot) => upsertRow(m.db, 'uat_slots', snake(slot), ['slot_id']);
|
|
1099
1310
|
const uatSlots = (m, { live = true } = {}) => m.db.prepare(`SELECT * FROM uat_slots ${live ? 'WHERE released_at IS NULL' : ''} ORDER BY acquired_at`).all();
|
|
1100
1311
|
const releaseUatSlot = (m, slotId) => m.db.prepare('UPDATE uat_slots SET released_at=? WHERE slot_id=? AND released_at IS NULL').run(m.now(), slotId).changes > 0;
|
|
1101
|
-
const upsertConnector = (m, { name, kind = null, state = null, pid = null, port = null, publicUrl = null, config = undefined, cursor = undefined }) =>
|
|
1102
|
-
upsertRow(m.db, 'connectors', { name, kind, state, pid, port, public_url: publicUrl, config_json: config, cursor_json: cursor, updated_at: m.now() }, ['name']);
|
|
1312
|
+
const upsertConnector = (m, { name, kind = null, state = null, pid = null, port = null, publicUrl = null, config = undefined, cursor = undefined }) => upsertRow(m.db, 'connectors', { name, kind, state, pid, port, public_url: publicUrl, config_json: config, cursor_json: cursor, updated_at: m.now() }, ['name']);
|
|
1103
1313
|
const connectorOf = (m, name) => { const r = m.db.prepare('SELECT * FROM connectors WHERE name=?').get(name); return r ? { ...r, config: parse(r.config_json), cursor: parse(r.cursor_json) } : null; };
|
|
1104
1314
|
const upsertAsk = (m, ask) => upsertRow(m.db, 'ask_requests', { asked_at: m.now(), ...snake(ask) }, ['ask_id']);
|
|
1105
|
-
|
|
1106
1315
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
1107
1316
|
// B6. Observation: machine_logs (+FTS), metrics, notifications, archives
|
|
1108
1317
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
@@ -1113,65 +1322,94 @@ const LOG_ACTORS = new Set(['reconciler', 'supervisor', 'worker', 'gc', 'host',
|
|
|
1113
1322
|
* 64 KiB goes to a blob. Returns the number written.
|
|
1114
1323
|
*/
|
|
1115
1324
|
function log(m, rows) {
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1325
|
+
const list = (Array.isArray(rows) ? rows : [rows]).filter(Boolean);
|
|
1326
|
+
if (!list.length)
|
|
1327
|
+
return 0;
|
|
1328
|
+
return m.transaction((db) => {
|
|
1329
|
+
let n = 0;
|
|
1330
|
+
for (const r of list) {
|
|
1331
|
+
need(LOG_ACTORS.has(r.actor), `machine log: unknown actor ${r.actor}`);
|
|
1332
|
+
const { json } = jsonOrBlob(m, r.data == null ? null : redactData(r.data));
|
|
1333
|
+
n += insertRow(db, 'machine_logs', { at: r.at ?? m.now(), actor: r.actor, controller: r.controller ?? null, ledger_id: r.ledgerId ?? null, workflow_id: r.workflowId ?? null,
|
|
1334
|
+
job_id: r.jobId ?? null, attempt_id: int(r.attemptId), action_id: r.actionId ?? null, trace_id: r.traceId ?? null, span_id: r.spanId ?? null, level: r.level ?? 'info',
|
|
1335
|
+
kind: String(r.kind), msg: redactText(String(r.msg ?? '')), data_json: json, refs_json: r.refs == null ? null : redactData(r.refs), src: r.src ?? null }, { orIgnore: true }).changes;
|
|
1336
|
+
}
|
|
1337
|
+
return n;
|
|
1338
|
+
});
|
|
1129
1339
|
}
|
|
1130
1340
|
function logs(m, { actor = null, kind = null, level = null, ledgerId = null, workflowId = null, jobId = null, since = null, search = null, limit = 200 } = {}) {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1341
|
+
const where = [], args = [];
|
|
1342
|
+
if (actor) {
|
|
1343
|
+
where.push('l.actor=?');
|
|
1344
|
+
args.push(actor);
|
|
1345
|
+
}
|
|
1346
|
+
if (kind) {
|
|
1347
|
+
where.push(kind.endsWith('*') ? 'l.kind LIKE ?' : 'l.kind=?');
|
|
1348
|
+
args.push(kind.endsWith('*') ? `${kind.slice(0, -1)}%` : kind);
|
|
1349
|
+
}
|
|
1350
|
+
if (level) {
|
|
1351
|
+
where.push('l.level=?');
|
|
1352
|
+
args.push(level);
|
|
1353
|
+
}
|
|
1354
|
+
if (ledgerId) {
|
|
1355
|
+
where.push('l.ledger_id=?');
|
|
1356
|
+
args.push(ledgerId);
|
|
1357
|
+
}
|
|
1358
|
+
if (workflowId) {
|
|
1359
|
+
where.push('l.workflow_id=?');
|
|
1360
|
+
args.push(workflowId);
|
|
1361
|
+
}
|
|
1362
|
+
if (jobId) {
|
|
1363
|
+
where.push('l.job_id=?');
|
|
1364
|
+
args.push(jobId);
|
|
1365
|
+
}
|
|
1366
|
+
if (since != null) {
|
|
1367
|
+
where.push('l.seq>?');
|
|
1368
|
+
args.push(since);
|
|
1369
|
+
}
|
|
1370
|
+
if (search) {
|
|
1371
|
+
where.push('l.seq IN (SELECT rowid FROM machine_logs_fts WHERE machine_logs_fts MATCH ?)');
|
|
1372
|
+
args.push(search);
|
|
1373
|
+
}
|
|
1374
|
+
return m.db.prepare(`SELECT l.* FROM machine_logs l ${where.length ? 'WHERE ' + where.join(' AND ') : ''} ORDER BY l.seq DESC LIMIT ?`).all(...args, limit)
|
|
1375
|
+
.map((r) => ({ ...r, data: fullJson(parse(r.data_json)), refs: parse(r.refs_json) }));
|
|
1142
1376
|
}
|
|
1143
1377
|
/** Retention (DBTREE B6): debug rows older than 14 days, the rest older than 90 days. Returns rows deleted. */
|
|
1144
1378
|
function pruneLogs(m, { debugMs = 14 * 86400000, restMs = 90 * 86400000 } = {}) {
|
|
1145
|
-
|
|
1379
|
+
return m.transaction((db) => db.prepare("DELETE FROM machine_logs WHERE (level='debug' AND at<?) OR at<?").run(m.now() - debugMs, m.now() - restMs).changes);
|
|
1146
1380
|
}
|
|
1147
1381
|
function recordMetrics(m, { kind, ledgerId = null, workflowId = null, windowMs = null, subject = null, data }) {
|
|
1148
|
-
|
|
1149
|
-
|
|
1382
|
+
const { json, sha } = jsonOrBlob(m, data);
|
|
1383
|
+
return Number(insertRow(m.db, 'metrics_snapshots', { at: m.now(), kind, ledger_id: ledgerId, workflow_id: workflowId, window_ms: windowMs, subject, data_json: json, data_sha: sha }).lastInsertRowid);
|
|
1150
1384
|
}
|
|
1151
1385
|
const latestMetrics = (m, { kind, ledgerId = null, workflowId = null } = {}) => { const r = m.db.prepare('SELECT * FROM metrics_snapshots WHERE kind=? AND ledger_id IS ? AND workflow_id IS ? ORDER BY snap_id DESC LIMIT 1').get(kind, ledgerId, workflowId); return r ? { ...r, data: parse(r.data_json) } : null; };
|
|
1152
1386
|
/** A notification, deduplicated on dedupeKey (replaces telegram-sent.json). Returns {id, duplicate}. */
|
|
1153
1387
|
function recordNotification(m, { channel, kind, text, media = null, mediaType = 'image/png', sentAt = null, delivery = null, ref = null, dedupeKey = null }) {
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1388
|
+
if (dedupeKey) {
|
|
1389
|
+
const hit = m.db.prepare('SELECT notif_id FROM notifications WHERE dedupe_key=?').get(dedupeKey);
|
|
1390
|
+
if (hit)
|
|
1391
|
+
return { id: Number(hit.notif_id), duplicate: true };
|
|
1392
|
+
}
|
|
1393
|
+
const mediaSha = media ? putMachineBlob(m, media, { mediaType }) : null;
|
|
1394
|
+
return { id: Number(insertRow(m.db, 'notifications', { channel, kind, text, media_sha: mediaSha, sent_at: sentAt, delivery, ref, dedupe_key: dedupeKey }).lastInsertRowid), duplicate: false };
|
|
1157
1395
|
}
|
|
1158
1396
|
const notificationSent = (m, dedupeKey) => Boolean(m.db.prepare('SELECT 1 FROM notifications WHERE dedupe_key=?').get(dedupeKey));
|
|
1159
1397
|
const markNotificationSent = (m, id, { delivery = 'sent' } = {}) => m.db.prepare('UPDATE notifications SET sent_at=?, delivery=? WHERE notif_id=?').run(m.now(), delivery, id).changes > 0;
|
|
1160
|
-
const recordArchive = (m, archive) => upsertRow(m.db, 'archives', { created_at: m.now(), ...snake(archive), archive_path: path.resolve(archive.archivePath ?? archive.archive_path) }, ['archive_path']);
|
|
1161
|
-
|
|
1398
|
+
const recordArchive = (m, archive) => upsertRow(m.db, 'archives', { created_at: m.now(), ...snake(archive), archive_path: path.resolve((archive.archivePath ?? archive.archive_path)) }, ['archive_path']);
|
|
1162
1399
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
1163
1400
|
// Catalog projection (agents / models from modules/models/*.yaml, rewritten at engine start)
|
|
1164
1401
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
1165
1402
|
function projectCatalog(m, { agents = [], models = [], sourceRev = runtimeRev() }) {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1403
|
+
return m.transaction((db) => {
|
|
1404
|
+
const at = m.now();
|
|
1405
|
+
for (const a of agents)
|
|
1406
|
+
upsertRow(db, 'agents', { agent: a.agent, provider: a.provider ?? null, spawn_card: a.spawnCard ?? null, cli_name: a.cliName ?? null, source_rev: sourceRev, loaded_at: at }, ['agent']);
|
|
1407
|
+
for (const p of models)
|
|
1408
|
+
upsertRow(db, 'models', { profile: p.profile, agent: p.agent ?? null, model: p.model ?? null, pool: p.pool ?? null, max_parallel: int(p.maxParallel),
|
|
1409
|
+
roles_json: p.roles ?? null, cost_json: p.cost ?? null, source_rev: sourceRev, loaded_at: at }, ['profile']);
|
|
1410
|
+
return { agents: agents.length, models: models.length };
|
|
1411
|
+
});
|
|
1173
1412
|
}
|
|
1174
|
-
|
|
1175
1413
|
/**
|
|
1176
1414
|
* The ONE checkpoint of machine.sqlite: PASSIVE, on the checkpointer handle (openMachine({checkpointer:true}), the engine),
|
|
1177
1415
|
* and only while the engine_leader row `name` still names `holder` at `epoch` with a live lease. A standby, a draining or a
|
|
@@ -1179,16 +1417,17 @@ function projectCatalog(m, { agents = [], models = [], sourceRev = runtimeRev()
|
|
|
1179
1417
|
* Returns {busy, log, checkpointed} or {skipped: reason}.
|
|
1180
1418
|
*/
|
|
1181
1419
|
function checkpoint(m, { name = 'reconciler', holder, epoch } = {}) {
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1420
|
+
need(m.checkpointer, 'machine-db: only the checkpointer handle (the reconciler engine leader) checkpoints machine.sqlite', 'STARCI_MACHINE_NOT_CHECKPOINTER');
|
|
1421
|
+
need(holder && Number.isInteger(Number(epoch)), 'machine-db: checkpoint needs the leader {name, holder, epoch}');
|
|
1422
|
+
const row = m.db.prepare('SELECT holder, epoch, expires_at FROM engine_leader WHERE name=?').get(name);
|
|
1423
|
+
if (!row || row.holder !== holder || Number(row.epoch) !== Number(epoch))
|
|
1424
|
+
return { skipped: `not the ${name} leader at epoch ${epoch}` };
|
|
1425
|
+
if (Number(row.expires_at) <= m.now())
|
|
1426
|
+
return { skipped: `the ${name} lease of epoch ${epoch} expired` };
|
|
1427
|
+
return m.db.prepare('PRAGMA wal_checkpoint(PASSIVE)').get();
|
|
1188
1428
|
}
|
|
1189
1429
|
/** machine_meta as an object. */
|
|
1190
1430
|
const meta = (m) => Object.fromEntries(m.db.prepare('SELECT key, value FROM machine_meta').all().map((r) => [r.key, r.value]));
|
|
1191
|
-
|
|
1192
1431
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
1193
1432
|
// Outbox: a typed write the store refused waits here until the next flush (the land gate flushes it).
|
|
1194
1433
|
// Why a file: when machine.sqlite itself refuses the write (a persistent SQLITE_CORRUPT, a lock held past busy_timeout)
|
|
@@ -1197,120 +1436,187 @@ const meta = (m) => Object.fromEntries(m.db.prepare('SELECT key, value FROM mach
|
|
|
1197
1436
|
// in the normal case; flushOutbox renames it before applying, so appends during a flush start a fresh file.
|
|
1198
1437
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
1199
1438
|
const outboxFileFor = (machineFile) => `${path.resolve(machineFile)}.outbox.jsonl`;
|
|
1200
|
-
|
|
1439
|
+
// The outbox replays the JSON args a typed writer was called with, so the replay is typed by the writer's own contract.
|
|
1201
1440
|
const DEFERRABLE = Object.freeze({ recordLandOutcome, log });
|
|
1202
1441
|
// The outbox 'log' op stamps src:'outbox:<id>' on each row of its first arg; other ops keep args as passed.
|
|
1203
|
-
const outboxArgs = (op, args, id) => {
|
|
1204
|
-
|
|
1442
|
+
const outboxArgs = (op, args, id) => {
|
|
1443
|
+
if (op !== 'log')
|
|
1444
|
+
return args;
|
|
1445
|
+
return args.map((a, i) => { if (i !== 0) {
|
|
1446
|
+
return a;
|
|
1447
|
+
} return (Array.isArray(a) ? a : [a]).map((r) => ({ ...r, src: r.src ?? `outbox:${id}` })); });
|
|
1448
|
+
};
|
|
1205
1449
|
/** Append one deferred write; returns its id. */
|
|
1206
1450
|
function deferWrite({ op, args = [], file = null, env = process.env, error = null }) {
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1451
|
+
need(Object.hasOwn(DEFERRABLE, op), `machine-db: ${op} is not a deferrable write (${Object.keys(DEFERRABLE).join(', ')})`);
|
|
1452
|
+
const id = `ob-${Date.now().toString(36)}-${hex(4)}`;
|
|
1453
|
+
const list = outboxArgs(op, args, id);
|
|
1454
|
+
const target = outboxFileFor(file ?? machineFileFor(env));
|
|
1455
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
1456
|
+
fs.appendFileSync(target, `${JSON.stringify({ id, at: Date.now(), pid: process.pid, op, args: list, error: error ? errText(error) : null })}\n`);
|
|
1457
|
+
process.stderr.write(`[machine-db] deferred ${op} ${id} to ${target}` + (error ? `: ${errText(error)}` : '') + '\n');
|
|
1458
|
+
return id;
|
|
1215
1459
|
}
|
|
1216
1460
|
/** withMachine(API[op](m, ...args)); when the store refuses it, the write goes to the outbox. {ok, value} | {ok:false, deferred, error}. */
|
|
1217
1461
|
export function writeOrDefer(op, args = [], { env = process.env, file = null } = {}) {
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1462
|
+
need(Object.hasOwn(DEFERRABLE, op), `machine-db: ${op} is not a deferrable write`);
|
|
1463
|
+
const write = DEFERRABLE[op]; // the hasOwn check above
|
|
1464
|
+
try {
|
|
1465
|
+
return { ok: true, value: withMachine((m) => write(m, ...args), { env, file }) };
|
|
1466
|
+
}
|
|
1467
|
+
catch (error) {
|
|
1468
|
+
return { ok: false, deferred: deferWrite({ op, args, file, env, error }), error: errText(error) };
|
|
1469
|
+
}
|
|
1221
1470
|
}
|
|
1222
1471
|
// Flush one claimed outbox file: each line through its typed writer, failed lines appended back to `base`.
|
|
1223
|
-
const flushOutboxFile = (m, f, base) => {
|
|
1224
|
-
|
|
1225
|
-
let
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1472
|
+
const flushOutboxFile = (m, f, base) => {
|
|
1473
|
+
const back = [];
|
|
1474
|
+
let flushed = 0, failed = 0;
|
|
1475
|
+
for (const line of fs.readFileSync(f, 'utf8').split(/\r?\n/).filter(Boolean)) {
|
|
1476
|
+
let item;
|
|
1477
|
+
try {
|
|
1478
|
+
item = JSON.parse(line);
|
|
1479
|
+
}
|
|
1480
|
+
catch {
|
|
1481
|
+
back.push(line);
|
|
1482
|
+
failed += 1;
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
try {
|
|
1486
|
+
need(Object.hasOwn(DEFERRABLE, item.op), `unknown op ${item.op}`);
|
|
1487
|
+
m.transaction(() => DEFERRABLE[item.op](m, ...(item.args ?? [])));
|
|
1488
|
+
flushed += 1;
|
|
1489
|
+
}
|
|
1490
|
+
catch (error) {
|
|
1491
|
+
back.push(JSON.stringify({ ...item, error: errText(error), tries: (item.tries ?? 1) + 1 }));
|
|
1492
|
+
failed += 1;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
if (back.length) {
|
|
1496
|
+
fs.appendFileSync(base, `${back.join('\n')}\n`);
|
|
1497
|
+
}
|
|
1498
|
+
fs.rmSync(f, { force: true });
|
|
1499
|
+
return { flushed, failed };
|
|
1500
|
+
};
|
|
1231
1501
|
// The outbox files this flusher owns: the live file renamed aside, plus dead flushers' leftovers.
|
|
1232
|
-
const claimOutboxFiles = (m) => {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1502
|
+
const claimOutboxFiles = (m) => {
|
|
1503
|
+
const base = outboxFileFor(m.file), dir = path.dirname(base), stem = path.basename(base);
|
|
1504
|
+
const claimed = [];
|
|
1505
|
+
try {
|
|
1506
|
+
const to = `${base}.${process.pid}.${Date.now()}.flushing`;
|
|
1507
|
+
fs.renameSync(base, to);
|
|
1508
|
+
claimed.push(to);
|
|
1509
|
+
}
|
|
1510
|
+
catch (error) {
|
|
1511
|
+
if (error.code !== 'ENOENT')
|
|
1512
|
+
return { claimed, busy: errText(error), base };
|
|
1513
|
+
}
|
|
1514
|
+
let names = [];
|
|
1515
|
+
try {
|
|
1516
|
+
names = fs.readdirSync(dir);
|
|
1517
|
+
}
|
|
1518
|
+
catch { /* none */ }
|
|
1519
|
+
for (const n of names) {
|
|
1520
|
+
const hit = n.startsWith(`${stem}.`) && /^(\d+)\.\d+\.flushing$/.exec(n.slice(stem.length + 1));
|
|
1521
|
+
const full = path.join(dir, n);
|
|
1522
|
+
if (hit && !claimed.includes(full) && Number(hit[1]) !== process.pid && !pidAlive(Number(hit[1])))
|
|
1523
|
+
claimed.push(full);
|
|
1524
|
+
}
|
|
1525
|
+
return { claimed, busy: null, base };
|
|
1526
|
+
};
|
|
1239
1527
|
/**
|
|
1240
1528
|
* Apply every outbox line through its typed writer (each in its own transaction). A line that fails again goes back to
|
|
1241
1529
|
* the outbox; a claimed file of a dead flusher is taken over. {flushed, failed, pending}.
|
|
1242
1530
|
*/
|
|
1243
1531
|
function flushOutbox(m) {
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1532
|
+
need(!m.readOnly, 'machine-db: flushOutbox needs a writer');
|
|
1533
|
+
const { claimed, busy, base } = claimOutboxFiles(m);
|
|
1534
|
+
if (busy)
|
|
1535
|
+
return { flushed: 0, failed: 0, pending: base, busy };
|
|
1536
|
+
let flushed = 0, failed = 0;
|
|
1537
|
+
for (const f of claimed) {
|
|
1538
|
+
const r = flushOutboxFile(m, f, base);
|
|
1539
|
+
flushed += r.flushed;
|
|
1540
|
+
failed += r.failed;
|
|
1541
|
+
}
|
|
1542
|
+
if (flushed)
|
|
1543
|
+
log(m, { actor: 'harness', kind: 'machine-db.outbox-flushed', level: failed ? 'warn' : 'info', msg: `outbox: ${flushed} deferred write(s) applied` + (failed ? `, ${failed} still pending` : ''), data: { flushed, failed } });
|
|
1544
|
+
return { flushed, failed, pending: failed ? base : null };
|
|
1545
|
+
}
|
|
1253
1546
|
// camelCase → snake_case keys for the pass-through writers (seat, terminal, worktree ...).
|
|
1254
1547
|
function snake(obj) {
|
|
1255
|
-
|
|
1548
|
+
return Object.fromEntries(Object.entries(obj ?? {}).filter(([, v]) => v !== undefined).map(([k, v]) => [k.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`), v]));
|
|
1256
1549
|
}
|
|
1257
|
-
|
|
1258
1550
|
const API = {
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1551
|
+
insert: (m, table, row, opts) => insertRow(m.db, table, row, opts), upsert: (m, table, row, keys) => upsertRow(m.db, table, row, keys),
|
|
1552
|
+
update: (m, table, set, where) => updateRow(m.db, table, set, where),
|
|
1553
|
+
putMachineBlob, jsonOrBlob, meta, checkpoint,
|
|
1554
|
+
registerLedger, resolveLedger, listLedgers, touchLedger, setLedgerState, upsertRepository, forEachLedger, attachLedgers,
|
|
1555
|
+
supEvent, supEvents, newestSupEvent, upsertSupJob, setSupJobStatus, supJob, listSupJobs, acquireSupLeases, releaseSupLeases, supLeases,
|
|
1556
|
+
startSupAttempt, updateSupAttempt, latestSupAttempt, recordSupReport, supReports, consumeSupReport,
|
|
1557
|
+
openSupDecision, setSupDecision, markSupDecisionDelivered, listSupDecisions, openOwed, ackOwed, closeOwed, listOwed,
|
|
1558
|
+
upsertLearning, listLearning, recordOwnerRuling, upsertBridge, recordSupMessage, supMessages, markSupMessagesRead,
|
|
1559
|
+
setSupSignal, supSignal, clearSupSignal, recordMachineLlmUsage,
|
|
1560
|
+
startProcessRun, heartbeatProcessRun, endProcessRun, openProcessRuns, processRuns,
|
|
1561
|
+
leaderOf, acquireLeader, renewLeader, releaseLeader, leaderHistory, cursorOf, setCursor, cursors,
|
|
1562
|
+
enqueue, dueQueue, queueRows, dequeue, requeue, ensureSchedule, claimSchedule, finishSchedule, schedules,
|
|
1563
|
+
actionIntent, actionRunning, actionFinish, markStaleActionsUnknown, actionOf, actions, actionStep,
|
|
1564
|
+
controllerModes, setControllerMode, modeChanges, openSlaEpisode, markSlaViolated, markSlaReported, clearSla, openSla, recordViolation, clearViolation,
|
|
1565
|
+
setService, recordProbe, services, serviceEvents, upsertSeat, seatOf, seats, recordDelivery, recordSeatInput, startSeatTurn, endSeatTurn, seatTranscriptSnapshot,
|
|
1566
|
+
upsertTerminal, closeTerminal, acquireHostLock, renewHostLock, releaseHostLock, releaseObservedProvisionLock, renewProvisionLock, releaseProvisionLock, hostLock, hostLocks,
|
|
1567
|
+
claimResource, releaseClaim, sweptClaim, liveClaims, upsertAgentSession,
|
|
1568
|
+
throttleState, setThrottle, throttleEvents, recordThrottleDecision, releaseThrottleDecision, recordHostSample, hostSamples,
|
|
1569
|
+
setProviderHealth, providerHealth, poolBackoff, setPoolBackoff, clearPoolBackoff, setQuota, quotas,
|
|
1570
|
+
reserveProvider, providerReservations, providerReservationUsage, markProviderReservation, releaseProviderReservation,
|
|
1571
|
+
upsertGuardJob, guardJob, releaseGuardJob, recordGuardRefusal, releaseHostLeases, release: releaseHostLeases, hostLeases, setBudget, reserveBudget, settleBudget, budgets,
|
|
1572
|
+
startGcRun, finishGcRun, recordGcItem, addGcItem: recordGcItem, updateGcItem, gcItems, gcMark, addGcMarks: gcMark, gcRuns, markMachineBlobArchived, pruneSeatSnapshots,
|
|
1573
|
+
upsertLane, setLaneState, laneOf, lanes, enqueueLand, claimLandGate, finishLandTicket, landQueue, recordLandRun, recordLandOutcome, landRuns, recordPush, pushes, flushOutbox,
|
|
1574
|
+
upsertWorktree, removedWorktree, reserveWorktree, liveWorktrees, worktreeRow, worktreeRepos, dropWorktree, worktreeGcStop, setWorktreeGcStop, gcCursor, setGcCursor, upsertEnvServer, envServer, envServers, upsertUatSlot, uatSlots, releaseUatSlot, upsertConnector, connectorOf, upsertAsk,
|
|
1575
|
+
log, logs, pruneLogs, recordMetrics, latestMetrics, recordNotification, notificationSent, markNotificationSent, recordArchive, projectCatalog,
|
|
1283
1576
|
};
|
|
1284
|
-
|
|
1285
1577
|
/** Every typed function at module level too: fn(handle, ...args) — blob-gc and callers holding a handle. */
|
|
1286
1578
|
export { putMachineBlob, meta, checkpoint, registerLedger, resolveLedger, listLedgers, setLedgerState, upsertRepository, forEachLedger, attachLedgers, supEvent, supEvents, newestSupEvent, upsertSupJob, setSupJobStatus, supJob, listSupJobs, acquireSupLeases, releaseSupLeases, startSupAttempt, updateSupAttempt, latestSupAttempt, recordSupReport, supReports, consumeSupReport, openSupDecision, setSupDecision, markSupDecisionDelivered, listSupDecisions, ackOwed, upsertLearning, recordSupMessage, supMessages, setSupSignal, supSignal, recordMachineLlmUsage, startProcessRun, heartbeatProcessRun, endProcessRun, openProcessRuns, leaderOf, acquireLeader, renewLeader, releaseLeader, cursorOf, setCursor, cursors, enqueue, requeue, ensureSchedule, claimSchedule, finishSchedule, schedules, actionIntent, actionRunning, actionFinish, actionOf, actions, controllerModes, setControllerMode, openSlaEpisode, markSlaViolated, markSlaReported, clearSla, recordViolation, setService, recordProbe, services, upsertSeat, seatOf, seats, recordDelivery, recordSeatInput, seatTranscriptSnapshot, upsertTerminal, closeTerminal, acquireHostLock, renewHostLock, releaseHostLock, hostLock, claimResource, releaseClaim, throttleState, setThrottle, recordThrottleDecision, releaseThrottleDecision, recordHostSample, setProviderHealth, providerHealth, poolBackoff, setPoolBackoff, clearPoolBackoff, setQuota, quotas, setBudget, budgets, startGcRun, finishGcRun, recordGcItem, gcRuns, upsertLane, laneOf, lanes, enqueueLand, claimLandGate, finishLandTicket, landQueue, recordLandRun, recordLandOutcome, landRuns, recordPush, pushes, flushOutbox, removedWorktree, reserveWorktree, liveWorktrees, worktreeRow, worktreeRepos, dropWorktree, worktreeGcStop, setWorktreeGcStop, gcCursor, setGcCursor, upsertEnvServer, envServer, envServers, upsertUatSlot, releaseUatSlot, connectorOf, upsertAsk, log, logs, recordMetrics };
|
|
1287
1579
|
export { reserveProvider, providerReservations, providerReservationUsage, markProviderReservation, releaseProviderReservation };
|
|
1288
|
-
|
|
1289
1580
|
/** Best-effort machine log line from anywhere (never throws): opens, appends, closes. */
|
|
1290
1581
|
export function machineLog(row, { env = process.env } = {}) {
|
|
1291
|
-
|
|
1582
|
+
try {
|
|
1583
|
+
return withMachine((m) => m.log(row), { env });
|
|
1584
|
+
}
|
|
1585
|
+
catch {
|
|
1586
|
+
return 0;
|
|
1587
|
+
}
|
|
1292
1588
|
}
|
|
1293
|
-
|
|
1294
1589
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
1295
1590
|
// CLI: starci runtime machine-db <init|status|ledgers|register|resolve> [--file <machine.sqlite>] [--json]
|
|
1296
1591
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
1297
1592
|
if (isMain(import.meta.url)) {
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1593
|
+
const args = process.argv.slice(2);
|
|
1594
|
+
const flag = (name) => { const i = args.indexOf(`--${name}`); return i >= 0 ? args[i + 1] ?? null : null; };
|
|
1595
|
+
const cmd = args[0] ?? 'status';
|
|
1596
|
+
const file = flag('file');
|
|
1597
|
+
const out = (value) => process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
1598
|
+
try {
|
|
1599
|
+
if (cmd === 'init' || cmd === 'status') {
|
|
1600
|
+
withMachine((m) => out({ ok: true, file: m.file, meta: m.meta(), userVersion: Number(pragma(m.db, 'user_version')), journalMode: pragma(m.db, 'journal_mode'),
|
|
1601
|
+
modes: m.controllerModes(), ledgers: m.listLedgers().length, integrity: m.db.prepare('PRAGMA quick_check').get()?.quick_check,
|
|
1602
|
+
foreignKeyViolations: m.db.prepare('PRAGMA foreign_key_check').all().length }), { file });
|
|
1603
|
+
}
|
|
1604
|
+
else if (cmd === 'ledgers') {
|
|
1605
|
+
withMachine((m) => out(m.listLedgers({ includeRetired: args.includes('--all') })), { file });
|
|
1606
|
+
}
|
|
1607
|
+
else if (cmd === 'register') {
|
|
1608
|
+
withMachine((m) => out(m.registerLedger({ ledgerId: flag('ledger-id'), name: flag('name'), repoRoot: flag('repo'), file: flag('ledger-file'), product: flag('product') })), { file });
|
|
1609
|
+
}
|
|
1610
|
+
else if (cmd === 'resolve') {
|
|
1611
|
+
withMachine((m) => out(m.resolveLedger({ ledgerId: flag('ledger-id'), name: flag('name'), repoRoot: flag('repo'), create: args.includes('--create') })), { file });
|
|
1612
|
+
}
|
|
1613
|
+
else {
|
|
1614
|
+
throw new Error(`unknown command ${cmd}: init | status | ledgers [--all] | register --ledger-id --name --repo | resolve (--repo|--name|--ledger-id) [--create]`);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
catch (error) {
|
|
1618
|
+
const failure = error;
|
|
1619
|
+
process.stderr.write(`${failure.code ?? 'error'}: ${failure.message}\n`);
|
|
1620
|
+
process.exit(2);
|
|
1621
|
+
}
|
|
1316
1622
|
}
|