@starci/hfs 4.11.21 → 4.12.0
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 +28 -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 +415 -322
- 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 +72 -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 +28 -0
- 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 +494 -346
- 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
package/stack/ingress.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import { refusal } from './errors.mjs';
|
|
|
20
20
|
import { remoteDocker, shellQuote, withRunner } from './remote.mjs';
|
|
21
21
|
import { createIngress } from './ingress-create.mjs';
|
|
22
22
|
import { publishIngress } from './ingress-publish.mjs';
|
|
23
|
-
|
|
23
|
+
const declaresBackup = (ingress) => ingress.backup !== null;
|
|
24
24
|
const MATCHER = /(Host|HostRegexp)\(([^)]*)\)/gu;
|
|
25
25
|
const QUOTED = /`([^`]*)`/gu;
|
|
26
26
|
const FILE_MARK = /^#@@FILE (.+)$/mu;
|
|
@@ -29,678 +29,688 @@ const SETTLE_MS = 2000;
|
|
|
29
29
|
const START_ATTEMPTS = 6;
|
|
30
30
|
const AGE_RECIPIENT = /age1[0-9a-z]{50,}/u;
|
|
31
31
|
const MOUNTPOINT = /^\/[A-Za-z0-9._/-]+$/u;
|
|
32
|
-
|
|
33
32
|
const asMap = (value) => (plain(value) ? value : {});
|
|
34
33
|
const sorted = (items) => [...items].sort(byCodeUnit);
|
|
35
34
|
const originOf = (url) => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
try {
|
|
36
|
+
const parsed = new URL(url);
|
|
37
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return '[unparsed]';
|
|
41
|
+
}
|
|
42
42
|
};
|
|
43
|
-
|
|
44
43
|
// ---- the live and the declared routes -------------------------------------------------------------------------------------------
|
|
45
|
-
|
|
46
44
|
/** Host keys of a proxy router rule: `a.example` for Host(...), `regexp:<pattern>` for HostRegexp(...). */
|
|
47
45
|
function hostsOfRule(rule) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const [, kind = '', args = ''] of String(rule).matchAll(MATCHER)) {
|
|
48
|
+
for (const [, value = ''] of args.matchAll(QUOTED))
|
|
49
|
+
out.push(kind === 'Host' ? value.toLowerCase() : `regexp:${value}`);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
53
52
|
}
|
|
54
|
-
|
|
55
53
|
/** { routes: [{ host, targets }], middlewares: [name] } of one parsed file-provider dynamic document (any shape is tolerated). */
|
|
56
54
|
function routesOfDocument(doc) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
55
|
+
const http = asMap(asMap(doc).http);
|
|
56
|
+
const services = asMap(http.services);
|
|
57
|
+
const targetsOf = (name) => {
|
|
58
|
+
const servers = asMap(asMap(services[String(name).split('@')[0]]).loadBalancer).servers;
|
|
59
|
+
return (Array.isArray(servers) ? servers : []).map((server) => originOf(String(asMap(server).url)));
|
|
60
|
+
};
|
|
61
|
+
const routes = Object.values(asMap(http.routers)).flatMap((router) => {
|
|
62
|
+
const own = asMap(router);
|
|
63
|
+
const targets = typeof own.service === 'string' ? targetsOf(own.service) : [];
|
|
64
|
+
return hostsOfRule(own.rule ?? '').map((host) => ({ host, targets }));
|
|
65
|
+
});
|
|
66
|
+
return { routes, middlewares: Object.keys(asMap(http.middlewares)) };
|
|
67
|
+
}
|
|
70
68
|
function liveCommand(dir) {
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
const loop = String.raw `for f in *.yml *.yaml; do if [ -f "$f" ]; then printf '#@@FILE %s\n' "$f"; cat "$f"; echo; fi; done`;
|
|
70
|
+
return `cd ${shellQuote(dir)} && ${loop}`;
|
|
73
71
|
}
|
|
74
|
-
|
|
75
72
|
/** Reads every route file of the old edge: { routes, files, unparsable }. */
|
|
76
73
|
function readLive(run, legacy) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
const result = run(liveCommand(legacy.dynamicDir));
|
|
75
|
+
if (result.status !== 0)
|
|
76
|
+
return { error: `cannot read ${legacy.dynamicDir} on the manager: ${firstLine(result.stderr)}` };
|
|
77
|
+
const parts = String(result.stdout).split(FILE_MARK);
|
|
78
|
+
const live = { routes: [], files: [], unparsable: [] };
|
|
79
|
+
for (let index = 1; index < parts.length; index += 2) {
|
|
80
|
+
const name = parts[index];
|
|
81
|
+
live.files.push(name);
|
|
82
|
+
try {
|
|
83
|
+
live.routes.push(...routesOfDocument(parseYaml(parts[index + 1])).routes);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
live.unparsable.push(name);
|
|
87
|
+
}
|
|
87
88
|
}
|
|
88
|
-
|
|
89
|
-
return live;
|
|
89
|
+
return live;
|
|
90
90
|
}
|
|
91
|
-
|
|
92
91
|
/** The app's own routes file (relative to the environment dir): { routes, middlewares } or an empty set. */
|
|
93
92
|
function readOwnRoutes(config, ingress) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
93
|
+
if (ingress.routes === null)
|
|
94
|
+
return { routes: [], middlewares: [] };
|
|
95
|
+
try {
|
|
96
|
+
return routesOfDocument(parseYaml(fs.readFileSync(path.join(config.envDir, ingress.routes), 'utf8')));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw refusal('STACK_INGRESS_ROUTES', `cannot read ${ingress.routes} (deploy.ingress.routes) as YAML`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
102
|
/** Declared host -> where it is declared (`upstream <name>` or `routes file`). */
|
|
103
103
|
function declaredHosts(ingress, own) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
104
|
+
const out = new Map();
|
|
105
|
+
for (const entry of own.routes)
|
|
106
|
+
out.set(entry.host, 'routes file');
|
|
107
|
+
for (const host of ingress.drop)
|
|
108
|
+
out.set(host, 'dropped');
|
|
109
|
+
for (const entry of ingress.redirects)
|
|
110
|
+
out.set(entry.host, `redirect to ${entry.to}`);
|
|
111
|
+
for (const entry of ingress.upstreams)
|
|
112
|
+
for (const host of entry.hosts)
|
|
113
|
+
out.set(host, `upstream ${entry.name}`);
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
112
116
|
function undeclaredMiddlewares(ingress, own) {
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
const known = new Set([...Object.keys(ingress.middlewares), ...own.middlewares]);
|
|
118
|
+
return ingress.upstreams.flatMap((entry) => entry.middlewares.filter((name) => !known.has(name)).map((name) => `${name} (upstream ${entry.name})`));
|
|
115
119
|
}
|
|
116
|
-
|
|
117
120
|
// ---- docker probes over ssh ---------------------------------------------------------------------------------------------------
|
|
118
|
-
|
|
119
121
|
function containerState(run, container, network) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
const inspect = remoteDocker(run, ['inspect', '--type', 'container', '--format', '{{.State.Running}}|{{json .NetworkSettings.Networks}}', container]);
|
|
123
|
+
if (inspect.status !== 0)
|
|
124
|
+
return { present: false, attached: false };
|
|
125
|
+
const [running, json = 'null'] = String(inspect.stdout).trim().split(/\|(.*)/su);
|
|
126
|
+
let networks = null;
|
|
127
|
+
try {
|
|
128
|
+
networks = JSON.parse(json);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
networks = null;
|
|
132
|
+
}
|
|
133
|
+
return { present: running === 'true', attached: plain(networks) && Object.hasOwn(networks, network) };
|
|
134
|
+
}
|
|
128
135
|
/** 'missing' | 'wrong' (exists, not an attachable overlay) | 'ok'. */
|
|
129
136
|
function networkState(run, network) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
const inspect = remoteDocker(run, ['network', 'inspect', '--format', '{{.Driver}} {{.Attachable}}', network]);
|
|
138
|
+
if (inspect.status !== 0)
|
|
139
|
+
return 'missing';
|
|
140
|
+
return String(inspect.stdout).trim() === 'overlay true' ? 'ok' : 'wrong';
|
|
133
141
|
}
|
|
134
|
-
|
|
135
142
|
const onOverlay = (ingress) => ingress.upstreams.filter((entry) => entry.container !== null);
|
|
136
|
-
|
|
137
143
|
const volumePresent = (run, volume) => remoteDocker(run, ['volume', 'inspect', volume]).status === 0;
|
|
138
|
-
|
|
139
144
|
// ---- plan ---------------------------------------------------------------------------------------------------------------------------
|
|
140
|
-
|
|
141
145
|
function gather(run, config, ingress) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
146
|
+
const own = readOwnRoutes(config, ingress);
|
|
147
|
+
const live = readLive(run, ingress.legacy);
|
|
148
|
+
if (live.error !== undefined)
|
|
149
|
+
return { error: live.error };
|
|
150
|
+
const declared = declaredHosts(ingress, own);
|
|
151
|
+
const liveHosts = new Map();
|
|
152
|
+
for (const entry of live.routes)
|
|
153
|
+
liveHosts.set(entry.host, sorted(new Set([...(liveHosts.get(entry.host) ?? []), ...entry.targets])));
|
|
154
|
+
const containers = Object.fromEntries(onOverlay(ingress).map((entry) => [entry.container, containerState(run, entry.container, ingress.network)])); // onOverlay keeps only entries with a container
|
|
155
|
+
return {
|
|
156
|
+
declared, liveHosts, live, own,
|
|
157
|
+
missing: sorted([...liveHosts.keys()].filter((host) => !declared.has(host))),
|
|
158
|
+
declaredNotLive: sorted([...declared.keys()].filter((host) => !liveHosts.has(host))),
|
|
159
|
+
badMiddlewares: undeclaredMiddlewares(ingress, own),
|
|
160
|
+
containers,
|
|
161
|
+
network: networkState(run, ingress.network),
|
|
162
|
+
certificates: volumePresent(run, ingress.acmeVolume),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
160
165
|
function containerText(state) {
|
|
161
|
-
|
|
162
|
-
|
|
166
|
+
if (!state.present)
|
|
167
|
+
return 'MISSING or not running';
|
|
168
|
+
return state.attached ? 'running, attached' : 'running, not attached';
|
|
163
169
|
}
|
|
164
|
-
|
|
165
170
|
function planLines(ingress, found) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
171
|
+
const lines = [`live edge files: ${found.live.files.length === 0 ? 'none' : found.live.files.join(', ')}`];
|
|
172
|
+
for (const file of found.live.unparsable)
|
|
173
|
+
lines.push(`WARNING ${file} is not valid YAML: its routes could not be compared`);
|
|
174
|
+
for (const host of sorted(found.liveHosts.keys())) {
|
|
175
|
+
const state = found.declared.has(host) ? `declared (${found.declared.get(host)})` : 'NOT DECLARED';
|
|
176
|
+
lines.push(`live ${host} -> ${(found.liveHosts.get(host) ?? []).join(', ') || '(no target)'}: ${state}`);
|
|
177
|
+
}
|
|
178
|
+
for (const host of found.declaredNotLive)
|
|
179
|
+
lines.push(`declared, not live yet: ${host} (${found.declared.get(host)})`);
|
|
180
|
+
for (const name of found.badMiddlewares)
|
|
181
|
+
lines.push(`middleware ${name} is declared nowhere`);
|
|
182
|
+
for (const [name, state] of Object.entries(found.containers)) {
|
|
183
|
+
lines.push(`container ${name}: ${containerText(state)}`);
|
|
184
|
+
}
|
|
185
|
+
lines.push(`network ${ingress.network}: ${found.network}`, `certificates volume ${ingress.acmeVolume}: ${found.certificates ? 'present' : 'MISSING'}`);
|
|
186
|
+
return lines;
|
|
187
|
+
}
|
|
181
188
|
const planOk = (found) => found.missing.length === 0 && found.badMiddlewares.length === 0 && found.live.unparsable.length === 0;
|
|
182
|
-
|
|
183
189
|
function planResult(ingress, found) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
190
|
+
const lines = planLines(ingress, found);
|
|
191
|
+
const ok = planOk(found);
|
|
192
|
+
if (found.missing.length > 0)
|
|
193
|
+
lines.push(`REFUSED: ${found.missing.length} live host(s) not covered by the declaration: ${found.missing.join(', ')}`);
|
|
194
|
+
lines.push(ok ? 'plan green: every live host is covered' : 'plan NOT green');
|
|
195
|
+
const data = {
|
|
196
|
+
ok, steps: ['plan'], live: sorted(found.liveHosts.keys()), declared: sorted(found.declared.keys()), missing: found.missing,
|
|
197
|
+
declaredNotLive: found.declaredNotLive, badMiddlewares: found.badMiddlewares, unparsable: found.live.unparsable,
|
|
198
|
+
containers: found.containers, network: found.network, certificates: found.certificates,
|
|
199
|
+
};
|
|
200
|
+
return { ok, lines, data };
|
|
201
|
+
}
|
|
196
202
|
function planAction(run, config, ingress) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
203
|
+
const found = gather(run, config, ingress);
|
|
204
|
+
if (found.error !== undefined)
|
|
205
|
+
return { ok: false, lines: [found.error], data: { ok: false, steps: ['plan'], error: found.error } };
|
|
206
|
+
return planResult(ingress, found);
|
|
200
207
|
}
|
|
201
|
-
|
|
202
208
|
// ---- attach -------------------------------------------------------------------------------------------------------------------------
|
|
203
|
-
|
|
204
209
|
function attachFailure(lines, steps, message) {
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
lines.push(message);
|
|
211
|
+
return { ok: false, lines, data: { ok: false, steps } };
|
|
207
212
|
}
|
|
208
|
-
|
|
209
213
|
function connectArgs(ingress, entry) {
|
|
210
|
-
|
|
214
|
+
return ['network', 'connect', ...entry.aliases.flatMap((alias) => ['--alias', alias]), '--gw-priority', '-1', ingress.network, entry.container];
|
|
211
215
|
}
|
|
212
|
-
|
|
213
216
|
/** Connects every upstream container; stops at the first failed command. */
|
|
214
217
|
function connectAll(run, ingress, pending, lines, steps) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
218
|
+
for (const entry of pending) {
|
|
219
|
+
const done = remoteDocker(run, connectArgs(ingress, entry));
|
|
220
|
+
if (done.status !== 0)
|
|
221
|
+
return attachFailure(lines, steps, `connect ${entry.container} to ${ingress.network} FAILED: ${firstLine(done.stderr)}`);
|
|
222
|
+
steps.push(`connect:${entry.container}`);
|
|
223
|
+
lines.push(`connected ${entry.container} to ${ingress.network} (aliases ${entry.aliases.join(', ')})`);
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
224
227
|
/** Creates the overlay when it is missing (execute) or says it would; returns a failure result or null. */
|
|
225
228
|
function ensureNetwork(run, ingress, { network, execute, lines, steps }) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
if (network !== 'missing')
|
|
230
|
+
return null;
|
|
231
|
+
if (!execute) {
|
|
232
|
+
lines.push(`dry run: would create the attachable overlay network ${ingress.network}`);
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const created = remoteDocker(run, ['network', 'create', '--driver', 'overlay', '--attachable', ingress.network]);
|
|
236
|
+
if (created.status !== 0)
|
|
237
|
+
return attachFailure(lines, steps, `creating ${ingress.network} FAILED: ${firstLine(created.stderr)}`);
|
|
238
|
+
steps.push('network-create');
|
|
239
|
+
lines.push(`created the attachable overlay network ${ingress.network}`);
|
|
229
240
|
return null;
|
|
230
|
-
|
|
231
|
-
const created = remoteDocker(run, ['network', 'create', '--driver', 'overlay', '--attachable', ingress.network]);
|
|
232
|
-
if (created.status !== 0) return attachFailure(lines, steps, `creating ${ingress.network} FAILED: ${firstLine(created.stderr)}`);
|
|
233
|
-
steps.push('network-create');
|
|
234
|
-
lines.push(`created the attachable overlay network ${ingress.network}`);
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
|
|
241
|
+
}
|
|
238
242
|
function attachAction(run, ingress, execute) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
243
|
+
const lines = [];
|
|
244
|
+
const steps = [];
|
|
245
|
+
const states = onOverlay(ingress).map((entry) => ({ entry, state: containerState(run, entry.container, ingress.network) })); // onOverlay keeps only entries with a container
|
|
246
|
+
const absent = states.filter(({ state }) => !state.present).map(({ entry }) => entry.container);
|
|
247
|
+
if (absent.length > 0)
|
|
248
|
+
return attachFailure(lines, steps, `upstream container(s) missing or not running: ${absent.join(', ')}; nothing was changed`);
|
|
249
|
+
const network = networkState(run, ingress.network);
|
|
250
|
+
if (network === 'wrong')
|
|
251
|
+
return attachFailure(lines, steps, `network ${ingress.network} exists but is not an attachable overlay; nothing was changed`);
|
|
252
|
+
const pending = states.filter(({ state }) => !state.attached).map(({ entry }) => entry);
|
|
253
|
+
for (const { entry, state } of states)
|
|
254
|
+
if (state.attached)
|
|
255
|
+
lines.push(`${entry.container} is already on ${ingress.network}: skipped`);
|
|
256
|
+
const failedNetwork = ensureNetwork(run, ingress, { network, execute, lines, steps });
|
|
257
|
+
if (failedNetwork)
|
|
258
|
+
return failedNetwork;
|
|
259
|
+
const done = { ok: true, lines, data: { ok: true, steps, network, pending: pending.map((entry) => entry.container) } };
|
|
260
|
+
if (!execute) {
|
|
261
|
+
for (const entry of pending)
|
|
262
|
+
lines.push(`dry run: would connect ${entry.container} to ${ingress.network} (aliases ${entry.aliases.join(', ')})`);
|
|
263
|
+
return done;
|
|
264
|
+
}
|
|
265
|
+
return connectAll(run, ingress, pending, lines, steps) ?? done;
|
|
266
|
+
}
|
|
258
267
|
// ---- takeover: probes ---------------------------------------------------------------------------------------------------------------
|
|
259
|
-
|
|
260
268
|
/** The hosts to prove: the declared `verify` list or every plain declared host that is not dropped (path /). */
|
|
261
269
|
function targetsOf(ingress, declared) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
270
|
+
if (ingress.verify.length > 0)
|
|
271
|
+
return ingress.verify;
|
|
272
|
+
const plainHosts = [...declared.entries()].filter(([host, where]) => !host.startsWith('regexp:') && where !== 'dropped').map(([host]) => host);
|
|
273
|
+
return sorted(plainHosts).map((host) => ({ host, path: '/', status: null }));
|
|
265
274
|
}
|
|
266
|
-
|
|
267
275
|
const bashScript = (script) => `bash -c ${shellQuote(script)}`;
|
|
268
|
-
|
|
269
276
|
/** The URL and the Host stay exactly as in production (a redirect rule that matches on the URL must see the same text): an alternate port is reached with --connect-to, never by putting the port in the URL. */
|
|
270
277
|
function statusCommand(target, port) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
278
|
+
const route = port === 443 ? ['--resolve', `${target.host}:443:127.0.0.1`] : ['--connect-to', `${target.host}:443:127.0.0.1:${port}`];
|
|
279
|
+
const args = ['-sS', '--max-time', '10', ...route, '-o', '/dev/null', '-w', '%{http_code}', `https://${target.host}${target.path}`];
|
|
280
|
+
return `curl ${args.map(shellQuote).join(' ')}`;
|
|
274
281
|
}
|
|
275
|
-
|
|
276
282
|
function certificateCommand(target, port) {
|
|
277
|
-
|
|
278
|
-
|
|
283
|
+
const connect = ['openssl', 's_client', '-connect', `127.0.0.1:${port}`, '-servername', target.host].map(shellQuote).join(' ');
|
|
284
|
+
return bashScript(`printf '' | ${connect} 2>/dev/null | openssl x509 -noout -serial -subject`);
|
|
279
285
|
}
|
|
280
|
-
|
|
281
286
|
/** What 127.0.0.1:<port> serves for one host: the HTTP status (0 = connection or TLS error) and the certificate serial and subject (public data). */
|
|
282
287
|
function observe(run, target, port = 443) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
288
|
+
const status = run(statusCommand(target, port));
|
|
289
|
+
const code = String(status.stdout).trim();
|
|
290
|
+
const certificate = run(certificateCommand(target, port));
|
|
291
|
+
const text = String(certificate.stdout).trim();
|
|
292
|
+
return {
|
|
293
|
+
code: status.status === 0 && /^\d{3}$/u.test(code) ? Number(code) : 0,
|
|
294
|
+
certificate: certificate.status === 0 && text !== '' ? text : null,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
293
297
|
const label = (target) => `${target.host}${target.path}`;
|
|
294
298
|
const answers = (code) => code >= 200 && code < 500;
|
|
295
|
-
|
|
296
299
|
/**
|
|
297
300
|
* The old edge is the baseline: it must answer every host and present a certificate, and whatever it answers IS the expectation of the takeover (the new
|
|
298
301
|
* ingress serves the same dynamic directory, so every host must answer exactly the same). The declared `verify.status` values describe the state AFTER
|
|
299
302
|
* `ingress publish` (for example a 308 redirect that only the app's own routes file creates) and are judged by publish and by `stack verify`.
|
|
300
303
|
*/
|
|
301
304
|
function baselines(run, targets) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
305
|
+
return targets.map((target) => {
|
|
306
|
+
const seen = observe(run, target);
|
|
307
|
+
return { target, ...seen, ok: answers(seen.code) && seen.certificate !== null };
|
|
308
|
+
});
|
|
306
309
|
}
|
|
307
|
-
|
|
308
310
|
/** Runs `check` until it is ok or the attempts are used up, sleeping between attempts. */
|
|
309
311
|
async function settle(ctx, attempts, intervalMs, check) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
312
|
+
const last = await check();
|
|
313
|
+
if (last.ok || attempts <= 1)
|
|
314
|
+
return last;
|
|
315
|
+
await ctx.sleep(intervalMs);
|
|
316
|
+
return settle(ctx, attempts - 1, intervalMs, check);
|
|
314
317
|
}
|
|
315
|
-
|
|
316
318
|
/** Every host answers the baseline status and presents the baseline certificate (retried while the edge boots). */
|
|
317
319
|
function matchBaseline(ctx, run, base) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
return settle(ctx, SETTLE_ATTEMPTS, SETTLE_MS, () => {
|
|
321
|
+
const rows = base.map((entry) => {
|
|
322
|
+
const seen = observe(run, entry.target);
|
|
323
|
+
return { target: entry.target, code: seen.code, ok: seen.code === entry.code && seen.certificate === entry.certificate };
|
|
324
|
+
});
|
|
325
|
+
return { ok: rows.every((row) => row.ok), rows };
|
|
322
326
|
});
|
|
323
|
-
return { ok: rows.every((row) => row.ok), rows };
|
|
324
|
-
});
|
|
325
327
|
}
|
|
326
|
-
|
|
327
328
|
const mismatches = (rows) => rows.filter((row) => !row.ok).map((row) => `${label(row.target)}=${row.code}`).join(', ');
|
|
328
|
-
|
|
329
329
|
// ---- takeover: backup ---------------------------------------------------------------------------------------------------------------
|
|
330
|
-
|
|
331
330
|
/** The first age recipient of the creation_rules of <repoRoot>/.sops.yaml (the custody's own public recipient). */
|
|
332
331
|
function readRecipient(repoRoot) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
332
|
+
let rules;
|
|
333
|
+
try {
|
|
334
|
+
rules = parseYaml(fs.readFileSync(path.join(repoRoot, '.sops.yaml'), 'utf8'))?.creation_rules;
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
rules = null;
|
|
338
|
+
}
|
|
339
|
+
const found = AGE_RECIPIENT.exec(JSON.stringify(rules ?? null));
|
|
340
|
+
if (!found)
|
|
341
|
+
throw refusal('STACK_INGRESS_RECIPIENT', 'no age recipient found in the creation_rules of .sops.yaml');
|
|
342
|
+
return found[0];
|
|
338
343
|
}
|
|
339
|
-
|
|
340
344
|
const stampOf = (epochMs) => new Date(epochMs).toISOString().slice(0, 19).replaceAll('-', '').replaceAll(':', '').replace('T', '-');
|
|
341
|
-
|
|
342
345
|
function archiveStep(run, { source, file, recipient, dir }) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
346
|
+
const steps = [
|
|
347
|
+
`mkdir -p ${shellQuote(dir)}`,
|
|
348
|
+
`sudo -n tar -cf - -C ${shellQuote(source)} . | age -r ${shellQuote(recipient)} -o ${shellQuote(file)}`,
|
|
349
|
+
`test -s ${shellQuote(file)}`,
|
|
350
|
+
];
|
|
351
|
+
const done = run(bashScript(`set -o pipefail; umask 077; ${steps.join(' && ')}`));
|
|
352
|
+
return done.status === 0 ? null : firstLine(done.stderr);
|
|
353
|
+
}
|
|
352
354
|
/** Age-encrypted copies of the certificates store and of the dynamic directory, made ON the manager: { files } or { error }. */
|
|
353
355
|
function backupStores(ctx, run, ingress, recipient) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
|
|
356
|
+
const mount = remoteDocker(run, ['volume', 'inspect', '--format', '{{.Mountpoint}}', ingress.acmeVolume]);
|
|
357
|
+
const mountpoint = String(mount.stdout).trim();
|
|
358
|
+
if (mount.status !== 0 || !MOUNTPOINT.test(mountpoint))
|
|
359
|
+
return { error: `cannot locate the volume ${ingress.acmeVolume}` };
|
|
360
|
+
const { dir } = ingress.backup;
|
|
361
|
+
const stamp = stampOf(ctx.now());
|
|
362
|
+
const jobs = [
|
|
363
|
+
{ source: mountpoint, file: `${dir}/acme-${stamp}.tar.age` },
|
|
364
|
+
{ source: ingress.legacy.dynamicDir, file: `${dir}/dynamic-${stamp}.tar.age` },
|
|
365
|
+
];
|
|
366
|
+
for (const job of jobs) {
|
|
367
|
+
const reason = archiveStep(run, { ...job, recipient, dir });
|
|
368
|
+
if (reason !== null)
|
|
369
|
+
return { error: `backup ${job.file} FAILED: ${reason}` };
|
|
370
|
+
}
|
|
371
|
+
return { files: jobs.map((job) => job.file) };
|
|
372
|
+
}
|
|
370
373
|
// ---- takeover: preconditions --------------------------------------------------------------------------------------------------------
|
|
371
|
-
|
|
372
374
|
const serviceName = (ingress) => `${ingress.name}_${ingress.service}`;
|
|
373
|
-
|
|
374
375
|
/** { exists, desired, running } of a swarm service from the service listing (desired = the replica count the service is set to). */
|
|
375
376
|
function serviceState(run, name) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
377
|
+
const listed = remoteDocker(run, ['service', 'ls', '--filter', `name=${name}`, '--format', '{{.Name}} {{.Replicas}}']);
|
|
378
|
+
const row = String(listed.stdout).split('\n').map((line) => line.trim().split(' ')).find(([found]) => found === name);
|
|
379
|
+
if (listed.status !== 0 || !row)
|
|
380
|
+
return { exists: false, desired: -1, running: -1 };
|
|
381
|
+
const [running, desired] = String(row[1]).split('/').map(Number);
|
|
382
|
+
return { exists: true, desired, running };
|
|
381
383
|
}
|
|
382
|
-
|
|
383
384
|
function preconditions(run, ingress, found) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
385
|
+
const swarm = remoteDocker(run, ['info', '--format', '{{.Swarm.LocalNodeState}} {{.Swarm.ControlAvailable}}']);
|
|
386
|
+
const service = serviceState(run, serviceName(ingress));
|
|
387
|
+
const detached = Object.entries(found.containers).filter(([, state]) => !state.attached).map(([name]) => name);
|
|
388
|
+
const checks = [
|
|
389
|
+
['plan green', planOk(found), `${found.missing.length} live host(s) not covered`],
|
|
390
|
+
['swarm active (manager)', swarm.status === 0 && String(swarm.stdout).trim() === 'active true', 'this host is not an active swarm manager'],
|
|
391
|
+
[`swarm service ${serviceName(ingress)} exists with 0 replicas (host-mode ports bind only when a task starts)`, service.exists && service.desired === 0, 'create it with replicas 0 first'],
|
|
392
|
+
[`network ${ingress.network} is an attachable overlay`, found.network === 'ok', `network state: ${found.network}`],
|
|
393
|
+
['every upstream container attached', detached.length === 0, `not attached: ${detached.join(', ')}`],
|
|
394
|
+
[`certificates volume ${ingress.acmeVolume} present`, found.certificates, 'the persisted certificates volume is missing'],
|
|
395
|
+
];
|
|
396
|
+
return checks.map(([name, ok, detail]) => ({ name, ok, detail }));
|
|
397
|
+
}
|
|
398
398
|
// ---- takeover: the switch -----------------------------------------------------------------------------------------------------------
|
|
399
|
-
|
|
400
399
|
const dockerLine = (args) => ['docker', ...args.map(shellQuote)].join(' ');
|
|
401
|
-
|
|
402
400
|
/** Waits until 127.0.0.1:443 accepts a TCP connection (every 100 ms, at most gateSeconds). */
|
|
403
401
|
const tcpLoop = (gateSeconds) => bashScript(`for i in $(seq 1 ${gateSeconds * 10}); do if (exec 3<>/dev/tcp/127.0.0.1/443) 2>/dev/null; then exit 0; fi; sleep 0.1; done; exit 1`);
|
|
404
|
-
|
|
405
402
|
/** T0: stop the old edge and start the ingress in one remote command; then wait for 443 and prove every host. Returns { failure, gapMs }. */
|
|
406
403
|
async function swap(ctx, run, ingress, base) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
404
|
+
const gateMs = ingress.takeover.gateSeconds * 1000;
|
|
405
|
+
const t0 = ctx.now();
|
|
406
|
+
const stop = dockerLine(['stop', '-t', '10', ingress.legacy.container]);
|
|
407
|
+
const start = dockerLine(['service', 'scale', `${serviceName(ingress)}=1`]);
|
|
408
|
+
const switched = run(`${stop} && ${start}`);
|
|
409
|
+
if (switched.status !== 0)
|
|
410
|
+
return { failure: `stop and scale FAILED: ${firstLine(switched.stderr)}`, gapMs: ctx.now() - t0 };
|
|
411
|
+
const up = run(tcpLoop(ingress.takeover.gateSeconds));
|
|
412
|
+
const gapMs = ctx.now() - t0;
|
|
413
|
+
if (up.status !== 0 || gapMs > gateMs)
|
|
414
|
+
return { failure: `port 443 was not accepting connections within ${ingress.takeover.gateSeconds} s`, gapMs };
|
|
415
|
+
const proof = await matchBaseline(ctx, run, base);
|
|
416
|
+
return proof.ok ? { failure: null, gapMs } : { failure: `hosts differ from the old edge: ${mismatches(proof.rows)}`, gapMs };
|
|
417
|
+
}
|
|
420
418
|
/** Automatic fallback: free the ports, start the old edge, prove it answers every host as before. */
|
|
421
419
|
async function fallback(ctx, run, ingress, base, steps) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
|
|
420
|
+
const name = serviceName(ingress);
|
|
421
|
+
const scale = remoteDocker(run, ['service', 'scale', `${name}=0`]);
|
|
422
|
+
steps.push('fallback-scale-0');
|
|
423
|
+
const manual = `docker service scale ${name}=0; docker start ${ingress.legacy.container}; check every host`;
|
|
424
|
+
const started = await settle(ctx, START_ATTEMPTS, SETTLE_MS, () => ({ ok: remoteDocker(run, ['start', ingress.legacy.container]).status === 0 }));
|
|
425
|
+
steps.push('fallback-start-old-edge');
|
|
426
|
+
if (scale.status !== 0 || !started.ok)
|
|
427
|
+
return { ok: false, line: `FALLBACK FAILED (manual: ${manual})` };
|
|
428
|
+
const proof = await matchBaseline(ctx, run, base);
|
|
429
|
+
steps.push('fallback-verify');
|
|
430
|
+
if (!proof.ok)
|
|
431
|
+
return { ok: false, line: `FALLBACK FAILED (manual: the old edge started but does not answer as before (${mismatches(proof.rows)}); ${manual})` };
|
|
432
|
+
return { ok: true, line: `FALLBACK ok: ${ingress.legacy.container} answers every host again; the ingress service is scaled to 0 (docker service scale ${name}=1 after fixing the cause)` };
|
|
433
|
+
}
|
|
435
434
|
const result = (ok, lines, steps, extra = {}) => ({ ok, lines, data: { ok, steps, ...extra } });
|
|
436
|
-
|
|
437
435
|
async function cutover(ctx, run, ingress, context) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
|
|
436
|
+
const { base, lines, steps, files } = context;
|
|
437
|
+
const outcome = await swap(ctx, run, ingress, base);
|
|
438
|
+
steps.push('stop-and-scale');
|
|
439
|
+
lines.push(`measured unavailability gap: ${outcome.gapMs} ms (gate ${ingress.takeover.gateSeconds} s)`);
|
|
440
|
+
if (outcome.failure === null) {
|
|
441
|
+
lines.push(`takeover complete: the swarm ingress serves ${base.length} host(s) with the old edge's certificates; ${ingress.legacy.container} is stopped, not removed`);
|
|
442
|
+
return result(true, lines, steps, { gapMs: outcome.gapMs, backups: files, fallback: null });
|
|
443
|
+
}
|
|
444
|
+
lines.push(`takeover FAILED: ${outcome.failure}`);
|
|
445
|
+
const back = await fallback(ctx, run, ingress, base, steps);
|
|
446
|
+
lines.push(back.line);
|
|
447
|
+
return result(false, lines, steps, { gapMs: outcome.gapMs, backups: files, fallback: back.ok ? 'ok' : 'failed' });
|
|
448
|
+
}
|
|
452
449
|
async function takeoverAction(ctx, run, config, ingress, execute) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
450
|
+
if (!declaresBackup(ingress))
|
|
451
|
+
throw refusal('STACK_INGRESS_BACKUP_UNDECLARED', 'deploy.ingress.backup (dir) is not declared: the takeover needs an encrypted backup');
|
|
452
|
+
const recipient = readRecipient(config.repoRoot);
|
|
453
|
+
const steps = ['plan'];
|
|
454
|
+
const found = gather(run, config, ingress);
|
|
455
|
+
if (found.error !== undefined)
|
|
456
|
+
return result(false, [found.error], steps);
|
|
457
|
+
const checks = preconditions(run, ingress, found);
|
|
458
|
+
const lines = checks.map((check) => (check.ok ? `precondition ok: ${check.name}` : `precondition FAILED: ${check.name} (${check.detail})`));
|
|
459
|
+
steps.push('preconditions');
|
|
460
|
+
if (checks.some((check) => !check.ok))
|
|
461
|
+
return result(false, [...lines, 'takeover refused: nothing was changed'], steps);
|
|
462
|
+
const base = baselines(run, targetsOf(ingress, found.declared));
|
|
463
|
+
steps.push('baseline');
|
|
464
|
+
for (const row of base)
|
|
465
|
+
lines.push(`${row.ok ? 'baseline ok' : 'baseline FAILED'} ${label(row.target)}: old edge answers ${row.code}`);
|
|
466
|
+
if (base.some((row) => !row.ok))
|
|
467
|
+
return result(false, [...lines, 'takeover refused: the old edge does not answer as declared; nothing was changed'], steps);
|
|
468
|
+
if (!execute) {
|
|
469
|
+
lines.push(`dry run: would back up the certificates store and the dynamic directory (age) to ${ingress.backup.dir}, stop ${ingress.legacy.container} and scale ${serviceName(ingress)} to 1, verify ${base.length} host(s) and fall back on any failure`);
|
|
470
|
+
return result(true, lines, steps);
|
|
471
|
+
}
|
|
472
|
+
const saved = backupStores(ctx, run, ingress, recipient);
|
|
473
|
+
if (saved.files === undefined)
|
|
474
|
+
return result(false, [...lines, saved.error, 'takeover refused: nothing was changed'], steps);
|
|
475
|
+
steps.push('backup');
|
|
476
|
+
lines.push(`backup written: ${saved.files.join(', ')}`);
|
|
477
|
+
return cutover(ctx, run, ingress, { base, lines, steps, files: saved.files });
|
|
478
|
+
}
|
|
477
479
|
// ---- rehearse -----------------------------------------------------------------------------------------------------------------------
|
|
478
480
|
// A throwaway copy of the ingress on alternate host ports, proving that the persisted certificates and the dynamic routes serve every host
|
|
479
481
|
// exactly as the live edge does. The live edge, its container, the production certificates volume and ports 80/443 are never touched: the
|
|
480
482
|
// store is only READ (read-only mount of a helper container) into a NEW volume, the service reads the legacy dynamic directory read-only,
|
|
481
483
|
// the ACME http challenge is moved to an unused internal entrypoint so no certificate can be requested, and everything created is torn
|
|
482
484
|
// down in a finally path.
|
|
483
|
-
|
|
484
485
|
const UNUSED_ENTRYPOINT = 'rehearsal-unused';
|
|
485
486
|
const CHALLENGE_ARG = /^(--certificatesresolvers\.[^.]+\.acme\.httpchallenge\.entrypoint)=.*$/iu;
|
|
486
487
|
const ACME_ACTIVITY = /\b(?:obtain|obtaining|registering|new account|authorization)\b/iu;
|
|
487
488
|
const READY_ATTEMPTS = 12;
|
|
488
|
-
|
|
489
489
|
const rehearsalName = (ingress) => `${ingress.name}-rehearsal`;
|
|
490
490
|
const rehearsalVolume = (ingress) => `${ingress.acmeVolume}-rehearsal`;
|
|
491
491
|
const classOf = (code) => Math.floor(code / 100);
|
|
492
|
-
|
|
493
492
|
/** The static arguments of the ingress service with the ACME http challenge moved to an unused internal entrypoint. */
|
|
494
493
|
function rehearsalArgs(args) {
|
|
495
|
-
|
|
496
|
-
|
|
494
|
+
const quiet = args.map((arg) => arg.replace(CHALLENGE_ARG, `$1=${UNUSED_ENTRYPOINT}`));
|
|
495
|
+
return [...quiet, `--entrypoints.${UNUSED_ENTRYPOINT}.address=127.0.0.1:1`];
|
|
497
496
|
}
|
|
498
|
-
|
|
499
497
|
/** Image, static arguments and the container paths of the two mounts the rehearsal copies; null when the service cannot be rehearsed. */
|
|
500
498
|
function ingressSpec(run, ingress) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
499
|
+
const inspect = remoteDocker(run, ['service', 'inspect', '--format', '{{json .Spec.TaskTemplate.ContainerSpec}}', serviceName(ingress)]);
|
|
500
|
+
// reason: `docker service inspect` ContainerSpec, parsed JSON read defensively and checked for completeness below
|
|
501
|
+
let spec = null;
|
|
502
|
+
try {
|
|
503
|
+
spec = JSON.parse(inspect.stdout);
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
spec = null;
|
|
507
|
+
}
|
|
508
|
+
const mounts = Array.isArray(spec?.Mounts) ? spec.Mounts : [];
|
|
509
|
+
const targetOf = (source) => mounts.find((mount) => asMap(mount).Source === source)?.Target;
|
|
510
|
+
const out = {
|
|
511
|
+
image: spec?.Image,
|
|
512
|
+
args: (Array.isArray(spec?.Args) ? spec.Args : []).filter((arg) => typeof arg === 'string'),
|
|
513
|
+
dynamicTarget: targetOf(ingress.legacy.dynamicDir),
|
|
514
|
+
acmeTarget: targetOf(ingress.acmeVolume),
|
|
515
|
+
};
|
|
516
|
+
const complete = typeof out.image === 'string' && out.args.length > 0 && typeof out.dynamicTarget === 'string' && typeof out.acmeTarget === 'string';
|
|
517
|
+
return inspect.status === 0 && complete ? out : null; // complete: every field was checked above
|
|
518
|
+
}
|
|
516
519
|
function rehearseChecks(run, config, ingress, found, spec) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
|
|
520
|
+
const leftoverService = remoteDocker(run, ['service', 'inspect', rehearsalName(ingress)]).status === 0;
|
|
521
|
+
const leftoverVolume = volumePresent(run, rehearsalVolume(ingress));
|
|
522
|
+
const checks = [
|
|
523
|
+
['plan green', planOk(found), `${found.missing.length} live host(s) not covered`],
|
|
524
|
+
[`network ${ingress.network} is an attachable overlay`, found.network === 'ok', `network state: ${found.network}`],
|
|
525
|
+
[`certificates volume ${ingress.acmeVolume} present`, found.certificates, 'the persisted certificates volume is missing'],
|
|
526
|
+
['helper image declared (deploy.backup.image)', typeof config.backup?.image === 'string', 'the copy of the store needs a digest-pinned helper image'],
|
|
527
|
+
[`swarm service ${serviceName(ingress)} readable (image, arguments, dynamic and certificates mounts)`, spec !== null, 'create the ingress service first'],
|
|
528
|
+
[`no leftover ${rehearsalName(ingress)} service or ${rehearsalVolume(ingress)} volume`, !leftoverService && !leftoverVolume, 'remove the leftover of an earlier rehearsal first'],
|
|
529
|
+
];
|
|
530
|
+
return checks.map(([name, ok, detail]) => ({ name, ok, detail }));
|
|
531
|
+
}
|
|
530
532
|
function serviceCreateArgs(ingress, spec) {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
|
|
533
|
+
const ports = ingress.takeover.rehearsalPorts;
|
|
534
|
+
const mounts = [
|
|
535
|
+
`type=bind,source=${ingress.legacy.dynamicDir},target=${spec.dynamicTarget},readonly`,
|
|
536
|
+
`type=volume,source=${rehearsalVolume(ingress)},target=${spec.acmeTarget}`,
|
|
537
|
+
];
|
|
538
|
+
return [
|
|
539
|
+
'service', 'create', '--detach', '--name', rehearsalName(ingress), '--replicas', '1', '--restart-condition', 'none', '--network', ingress.network,
|
|
540
|
+
'--publish', `mode=host,target=80,published=${ports.http}`, '--publish', `mode=host,target=443,published=${ports.https}`,
|
|
541
|
+
...mounts.flatMap((mount) => ['--mount', mount]), spec.image, ...rehearsalArgs(spec.args),
|
|
542
|
+
];
|
|
543
|
+
}
|
|
543
544
|
/** Volume, read-only copy of the store, service: stops at the first failed step. Returns a failure message or null. */
|
|
544
545
|
async function rehearsalUp(ctx, run, config, ingress, context) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
|
|
546
|
+
const { spec, steps, created } = context;
|
|
547
|
+
const volume = rehearsalVolume(ingress);
|
|
548
|
+
const made = remoteDocker(run, ['volume', 'create', volume]);
|
|
549
|
+
if (made.status !== 0)
|
|
550
|
+
return `creating ${volume} FAILED: ${firstLine(made.stderr)}`;
|
|
551
|
+
created.volume = true;
|
|
552
|
+
steps.push('volume-create');
|
|
553
|
+
const copy = remoteDocker(run, ['run', '--rm', '--network', 'none', '-v', `${ingress.acmeVolume}:/from:ro`, '-v', `${volume}:/to`, config.backup.image, 'cp', '-a', '/from/.', '/to/']);
|
|
554
|
+
if (copy.status !== 0)
|
|
555
|
+
return `copying the certificates store FAILED: ${firstLine(copy.stderr)}`;
|
|
556
|
+
steps.push('volume-copy');
|
|
557
|
+
const service = remoteDocker(run, serviceCreateArgs(ingress, spec));
|
|
558
|
+
if (service.status !== 0)
|
|
559
|
+
return `creating ${rehearsalName(ingress)} FAILED: ${firstLine(service.stderr)}`;
|
|
560
|
+
created.service = true;
|
|
561
|
+
steps.push('service-create');
|
|
562
|
+
const ready = await settle(ctx, READY_ATTEMPTS, SETTLE_MS, () => {
|
|
563
|
+
const state = serviceState(run, rehearsalName(ingress));
|
|
564
|
+
return { ok: state.desired > 0 && state.running === state.desired };
|
|
565
|
+
});
|
|
566
|
+
steps.push('service-ready');
|
|
567
|
+
return ready.ok ? null : `${rehearsalName(ingress)} did not start`;
|
|
568
|
+
}
|
|
566
569
|
/** Status class and certificate of every host on the rehearsal ports against the live edge baseline; hosts without a live certificate are skipped. */
|
|
567
570
|
function compareRehearsal(run, ingress, base) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
|
|
571
|
+
const port = ingress.takeover.rehearsalPorts.https;
|
|
572
|
+
return base.map((entry) => {
|
|
573
|
+
if (entry.certificate === null)
|
|
574
|
+
return { target: entry.target, live: entry.code, skipped: true, ok: true };
|
|
575
|
+
const seen = observe(run, entry.target, port);
|
|
576
|
+
const sameStatus = classOf(seen.code) === classOf(entry.code);
|
|
577
|
+
const sameCertificate = seen.certificate === entry.certificate;
|
|
578
|
+
return { target: entry.target, live: entry.code, code: seen.code, sameCertificate, skipped: false, ok: sameStatus && sameCertificate };
|
|
579
|
+
});
|
|
580
|
+
}
|
|
578
581
|
function certificateVerdict(row) {
|
|
579
|
-
|
|
580
|
-
|
|
582
|
+
if (row.skipped)
|
|
583
|
+
return 'skipped';
|
|
584
|
+
return row.sameCertificate ? 'equal' : 'different';
|
|
581
585
|
}
|
|
582
|
-
|
|
583
586
|
function rowLine(row) {
|
|
584
|
-
|
|
585
|
-
|
|
587
|
+
if (row.skipped)
|
|
588
|
+
return `skipped: needs fresh issuance ${label(row.target)} (the live edge presents no certificate for it)`;
|
|
589
|
+
return `${row.ok ? 'ok' : 'MISMATCH'} ${label(row.target)}: status ${row.code} (live ${row.live}), certificate serial and subject ${certificateVerdict(row)}`;
|
|
586
590
|
}
|
|
587
|
-
|
|
588
591
|
/** The rehearsal log must show no certificate request (obtain, register, authorization). Returns a failure message or null. */
|
|
589
592
|
function acmeLogProblem(run, ingress) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
+
const logs = remoteDocker(run, ['service', 'logs', '--raw', '--no-task-ids', rehearsalName(ingress)]);
|
|
594
|
+
if (logs.status !== 0)
|
|
595
|
+
return 'cannot read the rehearsal log: no proof that no certificate was requested';
|
|
596
|
+
return String(logs.stdout).split('\n').some((line) => ACME_ACTIVITY.test(line)) ? 'the rehearsal log shows certificate activity (a certificate was requested)' : null;
|
|
593
597
|
}
|
|
594
|
-
|
|
595
598
|
/** Brings the rehearsal up, probes every host and checks the log. Returns { failure, rows }. */
|
|
596
599
|
async function rehearsalBody(ctx, run, config, ingress, context) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
|
|
600
|
+
const failure = await rehearsalUp(ctx, run, config, ingress, context);
|
|
601
|
+
if (failure !== null)
|
|
602
|
+
return { failure, rows: [] };
|
|
603
|
+
const compared = await settle(ctx, SETTLE_ATTEMPTS, SETTLE_MS, () => {
|
|
604
|
+
const rows = compareRehearsal(run, ingress, context.base);
|
|
605
|
+
return { ok: rows.every((row) => row.ok), rows };
|
|
606
|
+
});
|
|
607
|
+
context.steps.push('probe');
|
|
608
|
+
const problem = acmeLogProblem(run, ingress);
|
|
609
|
+
context.steps.push('acme-log');
|
|
610
|
+
const mismatched = compared.rows.filter((row) => !row.ok).map((row) => label(row.target));
|
|
611
|
+
const failures = [mismatched.length > 0 ? `mismatch on ${mismatched.join(', ')}` : null, problem].filter((item) => Boolean(item));
|
|
612
|
+
return { failure: failures.length > 0 ? failures.join('; ') : null, rows: compared.rows };
|
|
613
|
+
}
|
|
611
614
|
/** Removes what was created: the service, then its tasks, then the volume. Always reports each step. */
|
|
612
615
|
async function rehearsalTeardown(ctx, run, ingress, created, steps) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
|
|
616
|
+
const lines = [];
|
|
617
|
+
let clean = true;
|
|
618
|
+
const record = (name, done, text) => {
|
|
619
|
+
steps.push(`teardown:${name}`);
|
|
620
|
+
lines.push(done ? `teardown: ${text}` : `TEARDOWN FAILED: ${text} not done`);
|
|
621
|
+
clean = clean && done;
|
|
622
|
+
};
|
|
623
|
+
if (created.service) {
|
|
624
|
+
record('service-rm', remoteDocker(run, ['service', 'rm', rehearsalName(ingress)]).status === 0, 'service removed');
|
|
625
|
+
const gone = await settle(ctx, READY_ATTEMPTS, SETTLE_MS, () => ({ ok: String(remoteDocker(run, ['ps', '-q', '--filter', `label=com.docker.swarm.service.name=${rehearsalName(ingress)}`]).stdout).trim() === '' }));
|
|
626
|
+
record('tasks-gone', gone.ok, 'tasks gone');
|
|
627
|
+
}
|
|
628
|
+
if (created.volume) {
|
|
629
|
+
const removed = await settle(ctx, SETTLE_ATTEMPTS, SETTLE_MS, () => ({ ok: remoteDocker(run, ['volume', 'rm', rehearsalVolume(ingress)]).status === 0 }));
|
|
630
|
+
record('volume-rm', removed.ok, 'volume removed');
|
|
631
|
+
}
|
|
632
|
+
if (!clean)
|
|
633
|
+
lines.push(`manual: remove the service ${rehearsalName(ingress)} and the volume ${rehearsalVolume(ingress)}`);
|
|
634
|
+
return { clean, lines };
|
|
635
|
+
}
|
|
633
636
|
async function rehearsalRun(ctx, run, config, ingress, context) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
637
|
+
const created = { volume: false, service: false };
|
|
638
|
+
let body = { failure: 'rehearsal did not run', rows: [] };
|
|
639
|
+
let down = { clean: true, lines: [] };
|
|
640
|
+
try {
|
|
641
|
+
body = await rehearsalBody(ctx, run, config, ingress, { ...context, created });
|
|
642
|
+
}
|
|
643
|
+
finally {
|
|
644
|
+
down = await rehearsalTeardown(ctx, run, ingress, created, context.steps);
|
|
645
|
+
}
|
|
646
|
+
const lines = [...context.lines, ...body.rows.map(rowLine), ...down.lines];
|
|
647
|
+
if (body.failure !== null)
|
|
648
|
+
lines.push(`rehearsal FAILED: ${body.failure}`);
|
|
649
|
+
const ok = body.failure === null && down.clean;
|
|
650
|
+
if (ok)
|
|
651
|
+
lines.push('rehearsal passed: every host matches the live edge and nothing of the rehearsal is left');
|
|
652
|
+
const hosts = body.rows.map((row) => ({ host: row.target.host, path: row.target.path, status: row.code ?? null, certificate: certificateVerdict(row) }));
|
|
653
|
+
return result(ok, lines, context.steps, { hosts, teardown: down.clean ? 'clean' : 'failed' });
|
|
654
|
+
}
|
|
650
655
|
async function rehearseAction(ctx, run, config, ingress, execute) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
656
|
+
const steps = ['plan'];
|
|
657
|
+
const found = gather(run, config, ingress);
|
|
658
|
+
if (found.error !== undefined)
|
|
659
|
+
return result(false, [found.error], steps);
|
|
660
|
+
const spec = ingressSpec(run, ingress);
|
|
661
|
+
const checks = rehearseChecks(run, config, ingress, found, spec);
|
|
662
|
+
const readySpec = spec; // a missing spec fails the checks below, which return before it is used
|
|
663
|
+
const lines = checks.map((check) => (check.ok ? `precondition ok: ${check.name}` : `precondition FAILED: ${check.name} (${check.detail})`));
|
|
664
|
+
steps.push('preconditions');
|
|
665
|
+
if (checks.some((check) => !check.ok))
|
|
666
|
+
return result(false, [...lines, 'rehearsal refused: nothing was changed'], steps);
|
|
667
|
+
const base = baselines(run, targetsOf(ingress, found.declared));
|
|
668
|
+
steps.push('baseline');
|
|
669
|
+
for (const row of base)
|
|
670
|
+
lines.push(`live edge ${label(row.target)}: status ${row.code}`);
|
|
671
|
+
if (!execute) {
|
|
672
|
+
const ports = ingress.takeover.rehearsalPorts;
|
|
673
|
+
lines.push(`dry run: would copy ${ingress.acmeVolume} read-only into ${rehearsalVolume(ingress)}, start ${rehearsalName(ingress)} on host ports ${ports.http}/${ports.https} with no certificate requests, compare ${base.length} host(s) with the live edge and tear everything down`);
|
|
674
|
+
return result(true, lines, steps);
|
|
675
|
+
}
|
|
676
|
+
return rehearsalRun(ctx, run, config, ingress, { spec: readySpec, base, lines, steps });
|
|
668
677
|
}
|
|
669
|
-
|
|
670
678
|
// ---- entry --------------------------------------------------------------------------------------------------------------------------
|
|
671
|
-
|
|
672
679
|
/** The probe helpers the publish action shares with the takeover (injected so the two modules never import each other). */
|
|
673
680
|
const PUBLISH_KIT = {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
681
|
+
readRecipient,
|
|
682
|
+
stampOf,
|
|
683
|
+
observe,
|
|
684
|
+
baselines,
|
|
685
|
+
settle,
|
|
686
|
+
serviceState,
|
|
687
|
+
liveCommand,
|
|
688
|
+
targets: (config, ingress) => targetsOf(ingress, declaredHosts(ingress, readOwnRoutes(config, ingress))),
|
|
682
689
|
};
|
|
683
|
-
|
|
684
690
|
const ACTIONS = ['plan', 'attach', 'create', 'takeover', 'rehearse', 'publish'];
|
|
685
|
-
|
|
691
|
+
const isAction = (value) => ACTIONS.includes(value);
|
|
686
692
|
/**
|
|
687
693
|
* `starci app stack ingress <plan|attach|create|takeover|rehearse|publish>`; effects need execute === true.
|
|
688
694
|
* deps.ssh an injected ssh runner (specs).
|
|
689
695
|
*/
|
|
690
696
|
export async function runIngress(ctx, config, { action, execute = false, args = [], sshCmd = null }, deps = {}) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
697
|
+
if (!isAction(action))
|
|
698
|
+
throw refusal('STACK_USAGE', `starci app stack ingress takes ${ACTIONS.join(' | ')}`);
|
|
699
|
+
if (args.length > 0)
|
|
700
|
+
throw refusal('STACK_USAGE', `ingress ${action} takes no further argument (got ${args.join(' ')})`);
|
|
701
|
+
const { ingress } = config;
|
|
702
|
+
if (!ingress)
|
|
703
|
+
throw refusal('STACK_INGRESS_UNDECLARED', 'deploy.ingress is not declared for this environment');
|
|
704
|
+
return withRunner(ctx, config, { sshCmd }, deps, async (run, notes) => {
|
|
705
|
+
const table = {
|
|
706
|
+
plan: () => planAction(run, config, ingress),
|
|
707
|
+
attach: () => attachAction(run, ingress, execute),
|
|
708
|
+
create: () => createIngress(run, ingress, execute),
|
|
709
|
+
takeover: () => takeoverAction(ctx, run, config, ingress, execute),
|
|
710
|
+
rehearse: () => rehearseAction(ctx, run, config, ingress, execute),
|
|
711
|
+
publish: () => publishIngress(ctx, run, config, ingress, execute, PUBLISH_KIT),
|
|
712
|
+
};
|
|
713
|
+
const outcome = await table[action]();
|
|
714
|
+
return { ...outcome, lines: [...notes, ...outcome.lines], data: { action, ...outcome.data } };
|
|
715
|
+
});
|
|
706
716
|
}
|