@starci/hfs 4.7.32 → 4.7.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +13 -6
- package/emit/contracts.mjs +4 -3
- package/emit/form-bounds-reader.ts.partial +58 -0
- package/emit/form-bounds.mjs +155 -0
- package/emit/graphql-input-bounds.mjs +97 -0
- package/emit/input-bounds.mjs +44 -0
- package/emit/operation-schemas.mjs +137 -0
- package/emit/operations-worker.mjs +2 -2
- package/emit/operations.mjs +52 -16
- package/emit/schema-worker.mjs +17 -4
- package/emit/type-schema.mjs +17 -5
- package/package.json +9 -2
- package/release/gate.mjs +18 -23
- package/release/index.mjs +1 -1
- package/release/push.mjs +4 -5
- package/runtime/engine/admission.mjs +331 -0
- package/runtime/engine/canonical-json.mjs +14 -0
- package/runtime/engine/db/blob.mjs +380 -0
- package/runtime/engine/db/event-compact.mjs +73 -0
- package/runtime/engine/db/ledger-open.mjs +38 -0
- package/runtime/engine/db/ledger-paths.mjs +87 -0
- package/runtime/engine/db/ledger.mjs +1068 -0
- package/runtime/engine/db/machine-connection.mjs +150 -0
- package/runtime/engine/db/machine-open.mjs +60 -0
- package/runtime/engine/db/machine-schema.mjs +105 -0
- package/runtime/engine/db/machine.mjs +1317 -0
- package/runtime/engine/db/provider-reservations.mjs +127 -0
- package/runtime/engine/db/provision-authority.mjs +76 -0
- package/runtime/engine/db/provision-lock.mjs +29 -0
- package/runtime/engine/db/provision-operations.mjs +146 -0
- package/runtime/engine/db/ref-value.mjs +67 -0
- package/runtime/engine/db/schema/machine.sql +920 -0
- package/runtime/engine/db/schema/runtime.sql +1113 -0
- package/runtime/engine/refuse.mjs +11 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +65 -9
- package/runtime/knowledge/hfs/design-truths.yaml +149 -0
- package/runtime/knowledge/hfs/fe-stack.yaml +23 -5
- package/runtime/knowledge/hfs/rules.yaml +269 -48
- package/runtime/knowledge/hfs/slots.yaml +113 -53
- package/runtime/knowledge/patterns/be/api.yaml +46 -20
- package/runtime/knowledge/patterns/be/event-bus.yaml +19 -12
- package/runtime/knowledge/patterns/be/idempotency.yaml +265 -0
- package/runtime/knowledge/patterns/be/jobs.yaml +17 -12
- package/runtime/knowledge/patterns/be/projections.yaml +2 -2
- package/runtime/knowledge/patterns/be/queues.yaml +57 -22
- package/runtime/knowledge/patterns/be/reactors.yaml +1 -1
- package/runtime/knowledge/patterns/be/realtime.yaml +326 -27
- package/runtime/knowledge/patterns/be/saga.yaml +2 -2
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/kernel/command-policy.yaml +249 -0
- package/runtime/modules/kernel/failure-codes.yaml +211 -52
- package/runtime/modules/kernel/hook-input.yaml +6 -0
- package/runtime/modules/kernel/protected-zone.yaml +81 -0
- package/runtime/modules/kernel/removed-vocabulary.yaml +62 -0
- package/runtime/modules/kernel/roles.yaml +405 -0
- package/runtime/modules/models/registry.yaml +3 -1
- package/runtime/modules/models/runtimes.yaml +2 -0
- package/runtime/modules/schemas/application-stacks.schema.yaml +10 -2
- package/runtime/packages/cli/src/catalog.generated.mjs +46 -59
- package/runtime/packages/grammar/scripts/build-stamp.mjs +98 -0
- package/runtime/scripts/agent/codex-app-server.mjs +79 -0
- package/runtime/scripts/agent/codex-mirror-source.mjs +34 -0
- package/runtime/scripts/agent/host-agents.mjs +80 -0
- package/runtime/scripts/agent/launch-trust-policy.mjs +53 -0
- package/runtime/scripts/agent/trust-launch.mjs +191 -0
- package/runtime/scripts/agent/trust.mjs +523 -0
- package/runtime/scripts/api/fs/rename-over.mjs +21 -0
- package/runtime/scripts/api/fs/temp-path.mjs +8 -0
- package/runtime/scripts/api/fs/write-json-file.mjs +10 -0
- package/runtime/scripts/api/git/diff-names.mjs +8 -0
- package/runtime/scripts/api/git/head-time.mjs +10 -0
- package/runtime/scripts/api/git/index-lock.mjs +78 -0
- package/runtime/scripts/api/git/is-ancestor.mjs +5 -0
- package/runtime/scripts/api/git/log.mjs +5 -0
- package/runtime/scripts/api/git/merge-base-query.mjs +5 -0
- package/runtime/scripts/api/git/merge-tree.mjs +5 -0
- package/runtime/scripts/api/git/rev-list.mjs +5 -0
- package/runtime/scripts/api/git/rev-parse.mjs +13 -0
- package/runtime/scripts/api/git/status-query.mjs +5 -0
- package/runtime/scripts/api/git/worktree-list-query.mjs +5 -0
- package/runtime/scripts/api/node/spawn-node.mjs +6 -0
- package/runtime/scripts/api/npm/dist-tag.mjs +8 -0
- package/runtime/scripts/api/npm/lib.mjs +32 -0
- package/runtime/scripts/api/npm/pack-dry-run.mjs +9 -0
- package/runtime/scripts/api/npm/run-npm.mjs +6 -0
- package/runtime/scripts/api/npm/view.mjs +30 -0
- package/runtime/scripts/api/npm/whoami.mjs +8 -0
- package/runtime/scripts/api/process/hide-child-windows.mjs +31 -0
- package/runtime/scripts/api/process/kill-tree.mjs +22 -0
- package/runtime/scripts/api/process/lib.mjs +125 -0
- package/runtime/scripts/api/process/probe-guard-command.mjs +74 -0
- package/runtime/scripts/api/process/process-list.mjs +28 -0
- package/runtime/scripts/api/process/system-tool.mjs +37 -0
- package/runtime/scripts/gates/canon-digest.mjs +81 -0
- package/runtime/scripts/gates/canon-pins.mjs +16 -0
- package/runtime/scripts/gates/commit-proof.mjs +53 -0
- package/runtime/scripts/gates/gate-input.mjs +98 -0
- package/runtime/scripts/gates/gate.mjs +669 -0
- package/runtime/scripts/gates/grammar-dist.mjs +206 -0
- package/runtime/scripts/gates/runtime-package-selection.mjs +27 -0
- package/runtime/scripts/gates/test-world-run.mjs +254 -0
- package/runtime/scripts/gates/type-impact.mjs +277 -0
- package/runtime/scripts/gates/type-program.mjs +113 -0
- package/runtime/scripts/guards/assigned-command.mjs +12 -0
- package/runtime/scripts/guards/call-rights.mjs +47 -0
- package/runtime/scripts/guards/command-guard.mjs +468 -0
- package/runtime/scripts/guards/command-policy.mjs +342 -0
- package/runtime/scripts/guards/critic-reach.mjs +82 -0
- package/runtime/scripts/guards/deps-guard.mjs +150 -0
- package/runtime/scripts/guards/env-dump-verdict.mjs +89 -0
- package/runtime/scripts/guards/git-pathspec.mjs +225 -0
- package/runtime/scripts/guards/git-policy.mjs +355 -0
- package/runtime/scripts/guards/guards-root.mjs +27 -0
- package/runtime/scripts/guards/hook-install.mjs +343 -0
- package/runtime/scripts/guards/hook-io.mjs +76 -0
- package/runtime/scripts/guards/hook-verdict.mjs +12 -0
- package/runtime/scripts/guards/install-verdict.mjs +41 -0
- package/runtime/scripts/guards/kernel-seat.mjs +63 -0
- package/runtime/scripts/guards/launch-verdict.mjs +74 -0
- package/runtime/scripts/guards/op-starci.mjs +16 -0
- package/runtime/scripts/guards/op-write-scope.mjs +27 -0
- package/runtime/scripts/guards/orca-self-lifecycle.mjs +39 -0
- package/runtime/scripts/guards/process-kill-verdict.mjs +106 -0
- package/runtime/scripts/guards/protected-zone.mjs +56 -0
- package/runtime/scripts/guards/refusals.mjs +27 -0
- package/runtime/scripts/guards/release-definition.mjs +185 -0
- package/runtime/scripts/guards/release-push-gate.mjs +72 -0
- package/runtime/scripts/guards/release-record.mjs +58 -0
- package/runtime/scripts/guards/release-suite-mode.mjs +15 -0
- package/runtime/scripts/guards/rights.mjs +268 -0
- package/runtime/scripts/guards/seat-tools.mjs +47 -0
- package/runtime/scripts/guards/shell-commands.mjs +328 -0
- package/runtime/scripts/guards/supervisor-seat.mjs +80 -0
- package/runtime/scripts/guards/verdict-process.mjs +81 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +10 -10
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +1 -1
- package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +53 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +1 -1
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +17 -6
- package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +105 -0
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +38 -52
- package/runtime/scripts/hfs/architecture/supabase-keys.mjs +261 -0
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +49 -3
- package/runtime/scripts/hfs/architecture/typescript.mjs +1 -1
- package/runtime/scripts/hfs/check.mjs +6 -4
- package/runtime/scripts/hfs/coverage-scope.mjs +28 -1
- package/runtime/scripts/hfs/db-restamp-verb.mjs +54 -14
- package/runtime/scripts/hfs/declaration-shape.mjs +4 -4
- package/runtime/scripts/hfs/declaration-slots.mjs +14 -3
- package/runtime/scripts/hfs/manifest-shape.mjs +10 -6
- package/runtime/scripts/hfs/rule-params-shape.mjs +18 -6
- package/runtime/scripts/hfs/rules/a15-findings.mjs +4 -1
- package/runtime/scripts/hfs/rules/bypass-markers.mjs +22 -7
- package/runtime/scripts/hfs/rules/database-migrations.mjs +69 -14
- package/runtime/scripts/hfs/rules/database-queue-sources.mjs +9 -7
- package/runtime/scripts/hfs/rules/database-queues.mjs +68 -18
- package/runtime/scripts/hfs/rules/edition.mjs +56 -12
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -10
- package/runtime/scripts/hfs/rules/fe-stack.mjs +12 -7
- package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +75 -9
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks-swarm.mjs +15 -4
- package/runtime/scripts/hfs/rules/text-integrity.mjs +73 -0
- package/runtime/scripts/hfs/runtime-rules/hook-shape.mjs +74 -0
- package/runtime/scripts/hfs/runtime-rules/release-notes.mjs +44 -0
- package/runtime/scripts/hfs/sealed-members.mjs +3 -3
- package/runtime/scripts/hfs/slot-side-problems.mjs +3 -3
- package/runtime/scripts/hfs/slots.mjs +2 -1
- package/runtime/scripts/hfs/stack-deploy-config.mjs +22 -5
- package/runtime/scripts/hfs/stacks-gate.mjs +28 -7
- package/runtime/scripts/hfs/view.mjs +2 -1
- package/runtime/scripts/lib/app-data.mjs +11 -0
- package/runtime/scripts/lib/bypass-markers.mjs +272 -75
- package/runtime/scripts/lib/check-format.mjs +7 -0
- package/runtime/scripts/lib/cli-arg.mjs +120 -0
- package/runtime/scripts/lib/css-rules.mjs +220 -0
- package/runtime/scripts/lib/dom-tree.mjs +4 -0
- package/runtime/scripts/lib/escape.mjs +16 -0
- package/runtime/scripts/lib/event-items.mjs +22 -0
- package/runtime/scripts/lib/failure-code-template-errors.mjs +161 -0
- package/runtime/scripts/lib/git-dir.mjs +120 -0
- package/runtime/scripts/lib/grammar-vendor-parity.mjs +377 -0
- package/runtime/scripts/lib/guard-command.mjs +24 -0
- package/runtime/scripts/lib/hook-limits.mjs +5 -0
- package/runtime/scripts/lib/interrupt-cleanup.mjs +38 -0
- package/runtime/scripts/lib/js-lex.mjs +200 -0
- package/runtime/scripts/lib/json-formats.mjs +0 -1
- package/runtime/scripts/lib/language.mjs +94 -1
- package/runtime/scripts/lib/loosening-budgets.mjs +44 -0
- package/runtime/scripts/lib/loosening-config.mjs +361 -0
- package/runtime/scripts/lib/loosening-state.mjs +223 -0
- package/runtime/scripts/lib/menu-parts.mjs +11 -0
- package/runtime/scripts/lib/normalize.mjs +25 -0
- package/runtime/scripts/lib/op-gate.mjs +15 -0
- package/runtime/scripts/lib/redact.mjs +208 -0
- package/runtime/scripts/lib/runtime-location.mjs +21 -0
- package/runtime/scripts/lib/runtime-package-inventory.mjs +21 -0
- package/runtime/scripts/lib/runtime-package-manifests.mjs +84 -0
- package/runtime/scripts/lib/runtime-publication-tags.mjs +99 -0
- package/runtime/scripts/lib/seat-env.mjs +43 -0
- package/runtime/scripts/lib/secret-patterns.mjs +58 -9
- package/runtime/scripts/lib/shell-source.mjs +4 -0
- package/runtime/scripts/lib/source-phrases.mjs +34 -0
- package/runtime/scripts/lib/sqlite.mjs +21 -0
- package/runtime/scripts/lib/swarm-stack.mjs +2 -1
- package/runtime/scripts/lib/tar-files.mjs +39 -0
- package/runtime/scripts/lib/ts-analysis.mjs +10 -0
- package/runtime/scripts/lib/worktree-exclude.mjs +35 -0
- package/runtime/scripts/lib/worktree-record.mjs +14 -0
- package/runtime/scripts/machine/artifact-hold.mjs +52 -0
- package/runtime/scripts/machine/ask-receipts.mjs +113 -0
- package/runtime/scripts/machine/evidence-store.mjs +223 -0
- package/runtime/scripts/machine/home.mjs +216 -0
- package/runtime/scripts/machine/host-lock.mjs +184 -0
- package/runtime/scripts/machine/lock-recovery.mjs +55 -0
- package/runtime/scripts/machine/roles-contract.mjs +90 -0
- package/runtime/scripts/machine/runtime-change.mjs +20 -0
- package/runtime/scripts/machine/workflow-tree.mjs +122 -0
- package/runtime/scripts/reconciler/checklist-items.mjs +7 -0
- package/runtime/scripts/reconciler/guard-row.mjs +25 -0
- package/runtime/scripts/work/asset-slot.mjs +284 -0
- package/runtime/scripts/work/brand/brand-colour.mjs +125 -0
- package/runtime/scripts/work/brand/brand-tokens.mjs +167 -0
- package/runtime/scripts/work/brand/brand-value-source.mjs +106 -0
- package/runtime/scripts/work/brand/brand.mjs +892 -0
- package/runtime/scripts/work/draw/draw-dna-anatomy.mjs +60 -0
- package/runtime/scripts/work/draw/draw-dna-findings.mjs +256 -0
- package/runtime/scripts/work/draw/draw-dna-selector.mjs +38 -0
- package/runtime/scripts/work/draw/draw-dna.mjs +483 -0
- package/runtime/scripts/work/draw/draw-layer.mjs +305 -0
- package/runtime/scripts/work/draw/draw-rationale-page.mjs +237 -0
- package/runtime/scripts/work/draw/draw-rationale-ref.mjs +14 -0
- package/runtime/scripts/work/draw/draw-rationale.mjs +353 -0
- package/runtime/scripts/work/draw/draw-source.mjs +412 -0
- package/runtime/scripts/work/draw/draw-taste.mjs +278 -0
- package/runtime/scripts/work/draw/draw-typecheck.mjs +72 -0
- package/runtime/scripts/work/draw/rationale-findings.mjs +205 -0
- package/runtime/scripts/work/draw-grammar.mjs +175 -0
- package/runtime/scripts/work/draw-render-page.mjs +188 -0
- package/runtime/scripts/work/draw-render.mjs +626 -0
- package/runtime/scripts/work/grammar-proposal.mjs +279 -0
- package/runtime/scripts/work/png.mjs +237 -0
- package/runtime/scripts/work/render-tools.mjs +64 -0
- package/runtime/scripts/work/work-io.mjs +119 -0
- package/scaffold/add-inbox-lite.mjs +2 -2
- package/scaffold/add-plan.mjs +40 -13
- package/scaffold/add-queue-lite.mjs +372 -0
- package/scaffold/add.mjs +5 -2
- package/scaffold/app.mjs +1 -2
- package/scaffold/edition-gate.mjs +11 -7
- package/scaffold/lite-exports.mjs +10 -0
- package/scaffold/pattern-file-slots.mjs +33 -0
- package/scaffold/test-world.mjs +1 -1
- package/scaffold/yaml-block-edit.mjs +117 -0
- package/src/main.mjs +5 -4
- package/stack/deploy.mjs +7 -3
- package/stack/post-deploy.mjs +48 -0
- package/sync/index.mjs +10 -0
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +1 -1
- package/templates/app/codegen-graphql.mjs.partial +59 -12
- package/templates/app/hooks/husky/pre-commit +2 -2
- package/templates/app/hooks/husky/pre-push +10 -3
- package/templates/app/package-scripts/redis-dev.json +3 -0
- package/templates/app/package-scripts-lite/package.json +1 -0
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +36 -8
- package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/README.md +10 -9
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/postgres-init.sh +21 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +1 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +47 -5
- package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +5 -3
- package/templates/app/skeleton/README.md +2 -0
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +5 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +2 -2
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +1 -1
- package/templates/app/skeleton-lite/README.md +1 -1
- package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +18 -0
- package/templates/be/inbox/platform/event-bus/errors/event-bus.error.ts.tpl +16 -0
- package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +10 -0
- package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +1 -1
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +15 -3
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +11 -3
- package/templates/be/inbox/platform/event-bus/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/event-bus/messages/event-bus.messages.ts.tpl +7 -0
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +2 -2
- package/templates/be/inbox/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
- package/templates/be/inbox/platform/idempotency/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
- package/templates/be/inbox/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
- package/templates/be/inbox/platform/inbox/client-operation.policy.ts.tpl +17 -0
- package/templates/be/inbox/platform/inbox/errors/inbox.error.ts.tpl +22 -0
- package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +21 -0
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +334 -11
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +55 -4
- package/templates/be/inbox/platform/inbox/index.ts.tpl +2 -0
- package/templates/be/inbox/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +26 -0
- package/templates/be/inbox/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
- package/templates/be/inbox/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
- package/templates/be/inbox/platform/migration.sql.tpl +1 -0
- package/templates/be/inbox/service.spec.ts.tpl +4 -4
- package/templates/be/inbox/service.ts.tpl +3 -1
- package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -2
- package/templates/be/inbox/webhook-door.ts.tpl +3 -2
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +3 -0
- package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +10 -0
- package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +16 -3
- package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +11 -3
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +139 -50
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +25 -2
- package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +139 -78
- package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +10 -6
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +262 -159
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +32 -5
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +5 -1
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -4
- package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +1 -0
- package/templates/be/patterns/idempotency/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
- package/templates/be/patterns/idempotency/platform/idempotency/index.ts.tpl +2 -0
- package/templates/be/patterns/idempotency/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.ts.tpl +17 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.spec.ts.tpl +66 -0
- package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.ts.tpl +24 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.spec.ts.tpl +60 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.ts.tpl +12 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.spec.ts.tpl +49 -0
- package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.ts.tpl +12 -0
- package/templates/be/patterns/idempotency/platform/inbox/effectful.decorators.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/errors/inbox.error.ts.tpl +22 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.decorators.ts.tpl +17 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module.ts.tpl +28 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.options.ts.tpl +7 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.port.ts.tpl +44 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.service.spec.ts.tpl +388 -0
- package/templates/be/patterns/idempotency/platform/inbox/inbox.service.ts.tpl +77 -0
- package/templates/be/patterns/idempotency/platform/inbox/index.ts.tpl +10 -0
- package/templates/be/patterns/idempotency/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +9 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/entities/inbox-claim.entity.ts.tpl +25 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +39 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1789800005000-create-inbox-claims.ts.tpl +21 -0
- package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1792800000000-client-operation-outcomes.ts.tpl +21 -0
- package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
- package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +1 -0
- package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +4 -3
- package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +2 -0
- package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +20 -2
- package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +1 -0
- package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +4 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.index.ts.tpl +10 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +44 -0
- package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +33 -0
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +203 -18
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +78 -22
- package/templates/be/patterns/queues/platform/errors/queue.error.ts.tpl +19 -0
- package/templates/be/patterns/queues/platform/messages/queue.messages.ts.tpl +13 -0
- package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +1 -0
- package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +4 -0
- package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +2 -0
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -4
- package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +14 -2
- package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +5 -2
- package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +72 -74
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +23 -8
- package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +29 -8
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +7 -3
- package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +5 -0
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +2 -0
- package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +4 -0
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +2 -1
- package/templates/be/patterns/queues/platform/queue.port.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.spec.ts.tpl +417 -0
- package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.ts.tpl +172 -0
- package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +12 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +32 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +148 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +51 -0
- package/templates/be/patterns/queues/platform-lite/queue-transport.port.ts.tpl +163 -0
- package/templates/be/patterns/queues/platform-lite/queue.config.ts.tpl +17 -0
- package/templates/be/patterns/queues/platform-lite/queue.error.ts.tpl +25 -0
- package/templates/be/patterns/queues/platform-lite/queue.index.ts.tpl +8 -0
- package/templates/be/patterns/queues/platform-lite/queue.messages.ts.tpl +21 -0
- package/templates/be/patterns/queues/platform-lite/queue.module.ts.tpl +74 -0
- package/templates/be/patterns/queues/platform-lite/queue.options.ts.tpl +38 -0
- package/templates/be/patterns/queues/queue.queue.ts.tpl +2 -2
- package/templates/be/patterns/reactors/application.contracts.ts.tpl +2 -0
- package/templates/be/patterns/reactors/consumer.ts.tpl +5 -1
- package/templates/be/platform/graphql/graphql.contracts.ts +2 -1
- package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +1 -1
- package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +6 -1
- package/templates/be/skeleton/apps/__app__/src/main.ts +7 -4
- package/templates/be/skeleton/apps/cli/src/compose/platform.ts +4 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +7 -4
- package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/http/errors/http.error.ts +22 -0
- package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.spec.ts +235 -0
- package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.ts +85 -0
- package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.spec.ts +92 -0
- package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.ts +52 -0
- package/templates/be/skeleton/src/modules/platform/http/http.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http/http.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/http/http.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/http/http.port.ts +40 -0
- package/templates/be/skeleton/src/modules/platform/http/index.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http/messages/http.messages.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/ids/ids.port.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/ids/index.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.spec.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.ts +10 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.spec.ts +59 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.ts +21 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.contracts.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.spec.ts +150 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.ts +42 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.port.ts +15 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.service.spec.ts +23 -0
- package/templates/be/skeleton/src/modules/platform/logging/correlation.service.ts +28 -0
- package/templates/be/skeleton/src/modules/platform/logging/index.ts +14 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +39 -6
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +15 -3
- package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +17 -2
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +13 -1
- package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +13 -3
- package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.spec.ts +69 -0
- package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.ts +48 -0
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/record.policy.spec.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/primitives/record.policy.ts +3 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +6 -1
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +14 -1
- package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/be/test-world/world/test-apps.options.ts.tpl +2 -0
- package/templates/be/test-world/world/test-world.config.ts.tpl +1 -0
- package/upgrade/check.mjs +12 -135
- package/upgrade/file-state.mjs +1 -16
- package/upgrade/index.mjs +34 -361
- package/runtime/scripts/lib/check-code-loosening.mjs +0 -128
- package/runtime/scripts/lib/gate-loosening.mjs +0 -292
- package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +0 -89
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +0 -16
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +0 -13
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +0 -3
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +0 -12
- package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +0 -7
- package/templates/be/skeleton-lite/apps/api/src/main.ts +0 -26
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +0 -12
- package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +0 -7
- package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +0 -5
- package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +0 -9
- package/templates/be/upgrade-full/apps/cli/src/main.ts +0 -29
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +0 -7
- package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +0 -23
package/scaffold/add.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import { addModuleImport, compositionCode, readComposeGroup, writeComposeGroup }
|
|
|
22
22
|
import { addApp } from './add-app.mjs';
|
|
23
23
|
import { ensureLiteCli } from './add-cli-lite.mjs';
|
|
24
24
|
import { addLiteWebhookInbox } from './add-inbox-lite.mjs';
|
|
25
|
+
import { planLiteQueue } from './add-queue-lite.mjs';
|
|
25
26
|
import { addBuiltinLiteCli, fillKnownForms, formsOf, planKind, readAddContext } from './add-plan.mjs';
|
|
26
27
|
export { liteApiTemplates } from './add-plan.mjs';
|
|
27
28
|
import { bundledPins, jsonText, packageJsonText, peerDependencies } from './app.mjs';
|
|
@@ -58,8 +59,9 @@ function registerFeatureExports({ repoRoot, repo, noun, values }) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
function writeKind({ repoRoot, noun, name, now, manifest, repo, spec, declarationFile, values, forms, beRoot, patterns, planned }) {
|
|
62
|
+
function writeKind({ repoRoot, noun, name, now, options, manifest, repo, spec, declarationFile, values, forms, beRoot, patterns, planned }) {
|
|
62
63
|
if (noun === 'api' || noun === 'webhook') preflightFeatureComposition({ repoRoot, repo, noun, name });
|
|
64
|
+
const liteQueue = planLiteQueue({ repoRoot, repo, noun, patterns, now, emitTypes: options.noTypes === true ? false : options.emitTypes });
|
|
63
65
|
const bootstrapCreated = noun === 'cli' && repo.edition === 'lite' ? ensureLiteCli({ root: repoRoot, manifest, repo }) : [];
|
|
64
66
|
for (const file of planned) {
|
|
65
67
|
fs.mkdirSync(path.dirname(file.absolute), { recursive: true });
|
|
@@ -68,10 +70,11 @@ function writeKind({ repoRoot, noun, name, now, manifest, repo, spec, declaratio
|
|
|
68
70
|
registerFeatureExports({ repoRoot, repo, noun, values });
|
|
69
71
|
if (spec.wire) wire({ beRoot, wire: spec.wire, forms, noun, name });
|
|
70
72
|
const inboxCreated = addLiteWebhookInbox({ root: repoRoot, repo, noun, provider: name, values, now });
|
|
73
|
+
const queueBacking = liteQueue ? liteQueue.apply() : [];
|
|
71
74
|
const platformCreated = noun === 'api' || noun === 'webhook' ? wireFeature({ repoRoot, repo, noun, name }) : [];
|
|
72
75
|
const graphqlDoor = noun === 'api' ? ensureGraphqlPeers(repoRoot) : [];
|
|
73
76
|
const registered = register({ declarationFile, spec: { patterns, trigger: spec.trigger, optionalSlots: noun === 'api' ? [GRAPHQL_CONTRACT_SLOT] : [] } });
|
|
74
|
-
return { created: [...bootstrapCreated, ...planned.map((file) =>
|
|
77
|
+
return { created: [...bootstrapCreated, ...planned.map((file) => path.relative(repoRoot, file.absolute).replaceAll('\\', '/')), ...platformCreated, ...inboxCreated, ...queueBacking, ...graphqlDoor], registered };
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
/** The slot of the emitted GraphQL snapshot (contracts/<app>/schema.graphql), which an app serving a GraphQL api feature opts into. */
|
package/scaffold/app.mjs
CHANGED
|
@@ -417,7 +417,6 @@ const liteBaseSource = (rel) => !rel.startsWith('apps/cli/')
|
|
|
417
417
|
&& rel !== 'src/modules/platform/database/connection-source.client.ts'
|
|
418
418
|
&& rel !== 'src/modules/platform/database/migrate-connections.client.ts'
|
|
419
419
|
&& rel !== 'src/modules/platform/database/seed-connections.client.ts'
|
|
420
|
-
&& rel !== 'src/modules/platform/primitives/sequence.policy.ts'
|
|
421
420
|
&& !LITE_DEFERRED_GENERATOR_FILES.has(rel)
|
|
422
421
|
&& !rel.startsWith('src/tests/');
|
|
423
422
|
/** The be files the lite overlay replaces: a base unit spec of such a file tests the full variant, so lite carries its own. */
|
|
@@ -488,7 +487,7 @@ function skeletonDirectory(scope, directory, app, vars, include = () => true) {
|
|
|
488
487
|
}
|
|
489
488
|
|
|
490
489
|
/** The canonical full Work workspace, catalog and feature nodes an edition upgrade may need. */
|
|
491
|
-
|
|
490
|
+
function scaffoldWorkSeeds(project) {
|
|
492
491
|
return skeletonDirectory('app', 'skeleton', { project }, {},
|
|
493
492
|
rel => rel === '.starciwork/workspace.yaml' || rel === '.starciwork/index.yaml' || rel.startsWith('.starciwork/features/'));
|
|
494
493
|
}
|
|
@@ -7,28 +7,32 @@
|
|
|
7
7
|
|
|
8
8
|
/** The refusal of a verb a non-full edition does not carry: printed verbatim by the CLI, exit 2, nothing written. */
|
|
9
9
|
export class EditionRefusal extends Error {
|
|
10
|
-
constructor(command, message = `${command}: full edition only;
|
|
10
|
+
constructor(command, message = `${command}: full edition only; editions are fixed at scaffold, a lite app stays lite`) {
|
|
11
11
|
super(message);
|
|
12
12
|
this.name = 'EditionRefusal';
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* The refusal of a pattern noun in a lite app whose topic carries a `liteAdapter` (ruling:R212/case-1, amended by ruling:R212/case-4)
|
|
18
|
-
* lite has the pattern, but
|
|
17
|
+
* The refusal of a pattern noun in a lite app whose topic carries a `liteAdapter` (ruling:R212/case-1, amended by ruling:R212/case-4)
|
|
18
|
+
* and no lite template in this package: lite has the pattern, but generating its full-adapter templates would write adapter paths
|
|
19
19
|
* unavailable in lite. The pattern is written by hand using the topic's declared lite adapter until the package carries a lite template.
|
|
20
20
|
*/
|
|
21
21
|
export class LiteAdapterRefusal extends EditionRefusal {
|
|
22
22
|
constructor(command, topics) {
|
|
23
|
-
super(command, `${command}: the package carries only the full adapter of ${topics.join(', ')}; in a lite app write it on its
|
|
23
|
+
super(command, `${command}: the package carries only the full adapter of ${topics.join(', ')}; in a lite app write it on its lite adapter (knowledge/patterns/be/${topics[0]}.yaml liteAdapter)`);
|
|
24
24
|
this.name = 'LiteAdapterRefusal';
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Refuses `command` in a lite app when one of `topics` (pattern topic ids with their parsed documents) carries a `liteAdapter` and
|
|
30
|
+
* has no complete edition-specific file tree: neither a `liteFiles` list nor a place among `liteTemplated`, the topics whose lite
|
|
31
|
+
* templates the package carries.
|
|
32
|
+
*/
|
|
33
|
+
export function refuseFullAdapterInLite({ edition, topics, command, liteTemplated = [] }) {
|
|
30
34
|
if (edition !== 'lite') return;
|
|
31
|
-
const adapted = topics.filter(({ doc }) => doc?.liteAdapter !== undefined).map(({ id }) => id).sort();
|
|
35
|
+
const adapted = topics.filter(({ id, doc }) => doc?.liteAdapter !== undefined && !liteTemplated.includes(id) && (!Array.isArray(doc.liteFiles) || doc.liteFiles.length === 0)).map(({ id }) => id).sort();
|
|
32
36
|
if (adapted.length) throw new LiteAdapterRefusal(command, adapted);
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -36,6 +36,16 @@ const REGISTRATIONS = Object.freeze({
|
|
|
36
36
|
names: ["InjectPrimaryEntityManager", "PRIMARY_ENTITY_MANAGER"],
|
|
37
37
|
},
|
|
38
38
|
],
|
|
39
|
+
// The queue and jobs capabilities state their outbox and claim statements with the sql tag (the lite starter already carries the
|
|
40
|
+
// in-order relay and the record guard the BullMQ client reads stored jobs with).
|
|
41
|
+
queue: [
|
|
42
|
+
{
|
|
43
|
+
file: "be/src/modules/platform/database/index.ts",
|
|
44
|
+
source: "./database.sql",
|
|
45
|
+
names: ["sql"],
|
|
46
|
+
beforeSource: "./errors/database.error",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
39
49
|
webhook: [
|
|
40
50
|
{
|
|
41
51
|
file: "be/src/modules/platform/database/index.ts",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** The app-relative path of a topic file, whose root defaults to the back end. */
|
|
2
|
+
export const patternFilePath = (file, root = 'be') => root === 'app' ? file : `${root}/${file}`;
|
|
3
|
+
|
|
4
|
+
const unquote = (item) => item.trim().replace(/^["']|["']$/g, '');
|
|
5
|
+
|
|
6
|
+
/** Derives slots for files/liteFiles entries through the app resolver supplied by the caller. */
|
|
7
|
+
export function derivePatternFileSlots(text, classify) {
|
|
8
|
+
const lines = text.split('\n');
|
|
9
|
+
const problems = [];
|
|
10
|
+
let inFiles = false;
|
|
11
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
12
|
+
if (/^[A-Za-z][A-Za-z0-9_-]*:/.test(lines[i])) inFiles = /^(?:files|liteFiles):\s*$/.test(lines[i]);
|
|
13
|
+
if (!inFiles) continue;
|
|
14
|
+
const match = /^ {2}- path: (.+)$/.exec(lines[i]);
|
|
15
|
+
if (!match) continue;
|
|
16
|
+
const entry = deriveFileSlot(lines, i, match[1], classify);
|
|
17
|
+
if (entry.problem) problems.push(entry.problem);
|
|
18
|
+
else lines[entry.slotAt] = ` slot: ${entry.slot}`;
|
|
19
|
+
}
|
|
20
|
+
return { text: lines.join('\n'), problems };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The fields of one file entry stop at the next entry or top-level YAML key. */
|
|
24
|
+
function deriveFileSlot(lines, start, file, classify) {
|
|
25
|
+
let end = start + 1;
|
|
26
|
+
while (end < lines.length && !/^(?:[A-Za-z][A-Za-z0-9_-]*:| {2}- )/.test(lines[end])) end += 1;
|
|
27
|
+
const slotAt = lines.findIndex((line, index) => index > start && index < end && /^ {4}slot: /.test(line));
|
|
28
|
+
if (slotAt < 0) return { problem: `${file} has no slot line` };
|
|
29
|
+
const rootLine = lines.slice(start + 1, end).find((line) => /^ {4}root: /.test(line));
|
|
30
|
+
const root = rootLine ? unquote(rootLine.replace(/^ {4}root: /, '')) : 'be';
|
|
31
|
+
const slot = classify(patternFilePath(unquote(file), root));
|
|
32
|
+
return slot ? { slotAt, slot } : { problem: `${file} is owned by no slot` };
|
|
33
|
+
}
|
package/scaffold/test-world.mjs
CHANGED
|
@@ -57,7 +57,7 @@ function worldConfig({ apis, connections }) {
|
|
|
57
57
|
appModuleImports: apis.map((api) => `import { AppModule as ${pascalOf(api.name)}App } from "../../../apps/${api.name}/src/app.module"`).join('\n'),
|
|
58
58
|
optionsImports: ['cliOptionsOf', ...apis.map((api) => `${camelOf(api.name)}OptionsOf`), 'migrateCli', 'platformBase'].sort().join(', '),
|
|
59
59
|
connectionDeclarations: connections.map((connection) => `{ name: ${JSON.stringify(connection.name)} }`).join(', '),
|
|
60
|
-
worldApps: apis.map((api) => ` ${IDENTIFIER.test(api.name) ? api.name : JSON.stringify(api.name)}: { module: ${pascalOf(api.name)}App, options: ${camelOf(api.name)}OptionsOf },`).join('\n'),
|
|
60
|
+
worldApps: apis.map((api) => ` ${IDENTIFIER.test(api.name) ? api.name : JSON.stringify(api.name)}: { module: ${pascalOf(api.name)}App, options: ${camelOf(api.name)}OptionsOf, configure: applyCorrelation },`).join('\n'),
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Comment-preserving additions to a block-style YAML file an app owns (its stack declaration, its Swarm model). The caller
|
|
2
|
+
// applies every addition to the parsed document as well and accepts the edited text only when it parses to that document, so
|
|
3
|
+
// a shape this editor does not understand refuses instead of writing a wrong file.
|
|
4
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
5
|
+
import { parseYaml } from '../runtime/engine/yaml.mjs';
|
|
6
|
+
import { ScaffoldError } from './service.mjs';
|
|
7
|
+
|
|
8
|
+
const significant = (line) => line.trim() !== '' && !line.trimStart().startsWith('#');
|
|
9
|
+
const indentOf = (line) => line.length - line.trimStart().length;
|
|
10
|
+
const isItem = (line) => /^\s*-(?:\s|$)/.test(line);
|
|
11
|
+
|
|
12
|
+
/** The key's own indent: a `- key:` item line carries its first key two columns after the dash. */
|
|
13
|
+
const keyIndentOf = (line) => (isItem(line) ? indentOf(line) + 2 : indentOf(line));
|
|
14
|
+
|
|
15
|
+
/** The body of the value that starts after line `at` (a key line, or an item line for an item's body): its lines and their indent. */
|
|
16
|
+
function bodyOf(lines, at, ownIndent) {
|
|
17
|
+
let last = at;
|
|
18
|
+
let childIndent = null;
|
|
19
|
+
for (let index = at + 1; index < lines.length; index += 1) {
|
|
20
|
+
const line = lines[index];
|
|
21
|
+
if (!significant(line)) continue;
|
|
22
|
+
const indent = indentOf(line);
|
|
23
|
+
const indentless = indent === ownIndent && isItem(line) && (childIndent === null || childIndent === ownIndent);
|
|
24
|
+
if (indent <= ownIndent && !indentless) break;
|
|
25
|
+
childIndent ??= indent;
|
|
26
|
+
last = index;
|
|
27
|
+
}
|
|
28
|
+
return { from: at + 1, to: last + 1, childIndent };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The line of `key` among the direct children (indent `indent`) of lines [from, to); `itemLine` may hold the first key of an item. */
|
|
32
|
+
function keyLine(lines, { from, to }, indent, key, itemLine = -1) {
|
|
33
|
+
const pattern = new RegExp(`^\\s*(?:- )?${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}:(?:\\s|$)`);
|
|
34
|
+
for (let index = itemLine >= 0 ? itemLine : from; index < to; index += 1) {
|
|
35
|
+
const line = lines[index];
|
|
36
|
+
if (!significant(line) || keyIndentOf(line) !== indent || (isItem(line) && index !== itemLine)) continue;
|
|
37
|
+
if (pattern.test(line)) return index;
|
|
38
|
+
}
|
|
39
|
+
return -1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolves `path` to the value it names: a string steps into a mapping key, `{ item: key, is: value }` into the sequence item whose
|
|
44
|
+
* `key` is `value`. Returns the line that opens the value (the key or item line), the indent of its keys and its body.
|
|
45
|
+
*/
|
|
46
|
+
function locate(lines, path, file) {
|
|
47
|
+
let at = { line: -1, indent: -1, itemLine: -1, body: { from: 0, to: lines.length, childIndent: 0 } };
|
|
48
|
+
for (const step of path) {
|
|
49
|
+
const { body } = at;
|
|
50
|
+
if (typeof step === 'string') {
|
|
51
|
+
const indent = at.itemLine >= 0 ? at.indent : body.childIndent;
|
|
52
|
+
const line = keyLine(lines, body, indent, step, at.itemLine);
|
|
53
|
+
if (line < 0) throw new ScaffoldError('HFS_ADD_STACK_SHAPE', `${file} has no ${path.map(describe).join('.')}; add the declared Redis by hand`);
|
|
54
|
+
at = { line, indent: keyIndentOf(lines[line]), itemLine: -1, body: bodyOf(lines, line, keyIndentOf(lines[line])) };
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const items = [];
|
|
58
|
+
for (let index = body.from; index < body.to; index += 1) if (significant(lines[index]) && isItem(lines[index]) && indentOf(lines[index]) === body.childIndent) items.push(index);
|
|
59
|
+
const match = items.find((line, position) => {
|
|
60
|
+
const end = items[position + 1] ?? body.to;
|
|
61
|
+
return keyLine(lines, { from: line, to: end }, body.childIndent + 2, step.item, line) >= 0
|
|
62
|
+
&& new RegExp(`^\\s*(?:- )?${step.item}:\\s*${step.is}\\s*$`).test(lines[keyLine(lines, { from: line, to: end }, body.childIndent + 2, step.item, line)]);
|
|
63
|
+
});
|
|
64
|
+
if (match === undefined) throw new ScaffoldError('HFS_ADD_STACK_SHAPE', `${file} has no ${path.map(describe).join('.')}; add the declared Redis by hand`);
|
|
65
|
+
const itemBody = bodyOf(lines, match, body.childIndent);
|
|
66
|
+
at = { line: match, indent: body.childIndent + 2, itemLine: match, body: { ...itemBody, from: match, childIndent: body.childIndent + 2 } };
|
|
67
|
+
}
|
|
68
|
+
return at;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const describe = (step) => (typeof step === 'string' ? step : `[${step.item}=${step.is}]`);
|
|
72
|
+
|
|
73
|
+
/** The lines of `block` (written at indent 0) moved to `indent`. */
|
|
74
|
+
const indented = (block, indent) => block.replace(/\n$/, '').split('\n').map((line) => (line ? ' '.repeat(indent) + line : line));
|
|
75
|
+
|
|
76
|
+
/** A YAML text that adds entries under one mapping or items to one block sequence, and applies the same change to its document. */
|
|
77
|
+
export class YamlBlockEdit {
|
|
78
|
+
constructor(text, file) {
|
|
79
|
+
this.file = file;
|
|
80
|
+
this.newline = text.includes('\r\n') ? '\r\n' : '\n';
|
|
81
|
+
this.lines = text.replace(/\r\n/g, '\n').split('\n');
|
|
82
|
+
this.document = parseYaml(text);
|
|
83
|
+
this.expected = structuredClone(this.document);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The parsed document as the edits leave it; mutate it in step with each text edit. */
|
|
87
|
+
get value() { return this.expected; }
|
|
88
|
+
|
|
89
|
+
/** Appends `block` (YAML lines at indent 0) as the last children of the mapping or sequence at `path`. */
|
|
90
|
+
append(path, block) {
|
|
91
|
+
const at = locate(this.lines, path, this.file);
|
|
92
|
+
const { body } = at;
|
|
93
|
+
const indent = body.to > body.from && body.childIndent !== null ? body.childIndent : at.indent + 2;
|
|
94
|
+
this.lines.splice(body.to, 0, ...indented(block, indent));
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Adds `value` to the flow sequence (`key: [a, b]`) at `path`. */
|
|
99
|
+
extendFlow(path, value) {
|
|
100
|
+
const at = locate(this.lines, path, this.file);
|
|
101
|
+
const line = this.lines[at.line];
|
|
102
|
+
const flow = /^(.*:\s*\[)(.*)\](\s*)$/.exec(line);
|
|
103
|
+
if (!flow) throw new ScaffoldError('HFS_ADD_STACK_SHAPE', `${this.file} writes ${path.map(describe).join('.')} as no one-line flow sequence; add the declared Redis by hand`);
|
|
104
|
+
const items = flow[2].trim();
|
|
105
|
+
this.lines[at.line] = `${flow[1]}${items ? `${items}, ` : ''}${value}]${flow[3]}`;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The edited text, or a refusal when it does not parse to the expected document. */
|
|
110
|
+
text() {
|
|
111
|
+
const text = this.lines.join(this.newline);
|
|
112
|
+
let parsed;
|
|
113
|
+
try { parsed = parseYaml(text); } catch { parsed = undefined; }
|
|
114
|
+
if (!isDeepStrictEqual(parsed, this.expected)) throw new ScaffoldError('HFS_ADD_STACK_SHAPE', `${this.file} is not in a shape the declared Redis can be added to; add it by hand`);
|
|
115
|
+
return text;
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/main.mjs
CHANGED
|
@@ -97,7 +97,7 @@ const USAGE = `starci app scaffold <name> [--into <dir>] [--edition full|lite] [
|
|
|
97
97
|
starci app add <api|webhook|realtime|saga|job|reactor|queue|projection|cli|table|app> <name> [options] [--cwd <dir>]
|
|
98
98
|
starci app lint [--cwd <dir>] [--changed <file>...] [--workspace <dir>] [--side be|fe] [--fix] [--format text|json] [--sonar <file>]
|
|
99
99
|
starci app sync (--check | --write) [--cwd <dir>]
|
|
100
|
-
starci app check [--cwd <dir>] [--json] [--fast] [--base <ref>] [--
|
|
100
|
+
starci app check [--cwd <dir>] [--json] [--fast] [--base <ref>] [--db-types] [--wiring] [--coverage-from <lcov>]
|
|
101
101
|
starci app db baseline [--stamp <version>] [--local] [--apply] [--cwd <dir>]
|
|
102
102
|
starci app db restamp --map <restamp.yaml> [--local] [--apply] [--cwd <dir>]
|
|
103
103
|
starci app db plan [--local] [--json] [--cwd <dir>]
|
|
@@ -107,7 +107,7 @@ starci app db migrate [--execute] [--local | --linked | --url-env <NAME>] [--jso
|
|
|
107
107
|
starci app db post-cutover [--execute] [--green-at <ISO> | --owner-confirms-old-app-gone] [--linked | --url-env <NAME>] [--json] [--cwd <dir>]
|
|
108
108
|
starci app release gate [--execute] [--source <ref>] [--tag-prefix <prefix>] [--json] [--cwd <dir>]
|
|
109
109
|
starci app release push [--execute] [--remote <name>] [--json] [--cwd <dir>]
|
|
110
|
-
starci app upgrade [--
|
|
110
|
+
starci app upgrade [--plan] [--cwd <dir>]
|
|
111
111
|
starci app stack <up|down|status> [--stack <dir>] [--services <list>] [--k3d] [--force] [--json] [--cwd <dir>]
|
|
112
112
|
starci app stack <prepare|doctor|connect|disconnect|build-args|render|deploy|converge|verify|rollback|prune|backup|jobs> --env <env> [--execute] [--json] [--cwd <dir>]
|
|
113
113
|
starci app explain <path> [--cwd <dir>] [--json]
|
|
@@ -397,8 +397,9 @@ function explainMain(opts, { cwd: repoRoot, stdout: out }) {
|
|
|
397
397
|
return explained.status === 'no-slot' || explained.status === 'ambiguous' ? 1 : 0;
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
function upgradeAppMain(argv, { cwd, edition, stdout
|
|
401
|
-
|
|
400
|
+
async function upgradeAppMain(argv, { cwd, edition, stdout }) {
|
|
401
|
+
await checkEdition({ repoRoot: cwd, edition, normal: () => null });
|
|
402
|
+
return upgradeMain(['--repo', cwd, ...argv], { stdout });
|
|
402
403
|
}
|
|
403
404
|
|
|
404
405
|
/** The edition `hfs.json` declares, or undefined when it cannot be read (the test-world verbs then decide on their own). */
|
package/stack/deploy.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// deploy.mjs - the whole release (deployRelease) and the redeploy of a stored release (rollbackRelease).
|
|
2
|
-
// deploy: render, provision, store the release record, pre-pull, `docker stack deploy`, converge, verify, accept, prune. A failure at or
|
|
2
|
+
// deploy: render, provision, store the release record, pre-pull, `docker stack deploy`, converge, post-deploy tasks (deploy.postDeploy, when declared), verify, accept, prune. A failure at or
|
|
3
3
|
// after the stack deploy redeploys the previous accepted release from its STORED bytes; a failure before it leaves the services untouched.
|
|
4
4
|
// Without `execute` a deploy renders locally and prints the plan: NO remote call at all. A rollback cannot undo data effects.
|
|
5
5
|
//
|
|
@@ -17,6 +17,7 @@ import { describeClash, edgeCollisions, reconcileEdgeAliases } from './edge-alia
|
|
|
17
17
|
import { firstLine, tool } from './context.mjs';
|
|
18
18
|
import { hasPlaceholderDigest, isReleaseId } from './digest.mjs';
|
|
19
19
|
import { failure, refusal } from './errors.mjs';
|
|
20
|
+
import { postDeployStack } from './post-deploy.mjs';
|
|
20
21
|
import { pruneStack } from './prune.mjs';
|
|
21
22
|
import { parseYaml } from '../runtime/engine/yaml.mjs';
|
|
22
23
|
import { createReleaseStore } from './release-store.mjs';
|
|
@@ -33,6 +34,7 @@ export const DEFAULT_DEPS = Object.freeze({
|
|
|
33
34
|
provisionRelease: lazy('./provision.mjs', 'provisionRelease'),
|
|
34
35
|
convergeStack,
|
|
35
36
|
reconcileEdge: reconcileEdgeAliases,
|
|
37
|
+
postDeployStack,
|
|
36
38
|
verifyStack,
|
|
37
39
|
pruneStack,
|
|
38
40
|
releaseStore: createReleaseStore,
|
|
@@ -190,6 +192,7 @@ async function deployExecute(ctx, config, options, d, out, cleanups) {
|
|
|
190
192
|
['deploy', () => stackDeploy(ctx, config, path.join(out, 'stack.yaml'))],
|
|
191
193
|
['edge', async () => d.reconcileEdge(ctx, config, path.join(out, 'stack.yaml'))],
|
|
192
194
|
['converge', () => d.convergeStack(ctx, config, { since, timeoutSeconds: options.timeoutSeconds })],
|
|
195
|
+
...(config.postDeploy.length > 0 ? [['post-deploy', () => d.postDeployStack(ctx, config)]] : []),
|
|
193
196
|
['verify', () => d.verifyStack(ctx, config, { retrySeconds: options.retrySeconds, skipPublic: skipPublic === true, stackFile: path.join(out, 'stack.yaml') })],
|
|
194
197
|
...(skipPublic ? [] : [['accept', async () => { await store.accept(releaseId); return { ok: true, lines: [`accepted release ${releaseId}`] }; }]]),
|
|
195
198
|
], lines, steps);
|
|
@@ -224,9 +227,10 @@ export async function deployRelease(ctx, config, options = {}, deps = {}) {
|
|
|
224
227
|
const rendered = await d.renderRelease(ctx, config, renderOptions(options, out, null));
|
|
225
228
|
const { releaseId } = inspectRendered(out, config);
|
|
226
229
|
if (options.execute !== true) {
|
|
230
|
+
const post = config.postDeploy.length > 0 ? ` post-deploy tasks (${config.postDeploy.join(', ')}),` : '';
|
|
227
231
|
const plan = options.skipPublic
|
|
228
|
-
? `dry run: release ${releaseId} would be provisioned, stored, pre-pulled, deployed, converged and verified inside the stack (public hosts skipped); it would stay pending until ingress publish and stack accept`
|
|
229
|
-
: `dry run: release ${releaseId} would be provisioned, stored, pre-pulled, deployed, converged
|
|
232
|
+
? `dry run: release ${releaseId} would be provisioned, stored, pre-pulled, deployed, converged,${post} and verified inside the stack (public hosts skipped); it would stay pending until ingress publish and stack accept`
|
|
233
|
+
: `dry run: release ${releaseId} would be provisioned, stored, pre-pulled, deployed, converged,${post} verified, accepted`;
|
|
230
234
|
return { ok: true, lines: [...(rendered.lines ?? []), plan], data: { releaseId, steps: [{ step: 'render', ok: true }], executed: false } };
|
|
231
235
|
}
|
|
232
236
|
return await deployExecute(ctx, config, options, d, out, cleanups);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// post-deploy.mjs - the release tasks of a deploy (deploy.postDeploy): after the stack converged and before verification, each declared task
|
|
2
|
+
// (deploy.tasks.<name>) runs in order inside the running task of its service, through the daemon of this invocation like verify's checks.
|
|
3
|
+
// A post-deploy task is the place for an idempotent write a release owes the data (a catalogue sync, a seed upsert), so verification stays read-only.
|
|
4
|
+
// The first task that exits non-zero, or runs past its timeout, fails the step and the deploy rolls back; a rollback cannot undo what a task wrote.
|
|
5
|
+
// The deploy output carries the task's name, its exit and its line count only: the container holds decrypted secrets and the output reaches the
|
|
6
|
+
// CI log, so stdout and stderr are never relayed (`starci app stack task <name>` is the operator verb that shows them).
|
|
7
|
+
import { findInOrder } from '../runtime/scripts/lib/in-order.mjs';
|
|
8
|
+
import { lines as splitLines, tool } from './context.mjs';
|
|
9
|
+
|
|
10
|
+
/** The running container of a service on this daemon, or undefined. */
|
|
11
|
+
function runningContainer(ctx, config, service) {
|
|
12
|
+
const found = tool(ctx, 'docker', ['ps', '-q', '--filter', `label=com.docker.swarm.service.name=${config.stack}_${service}`, '--filter', 'status=running']);
|
|
13
|
+
return splitLines(found.stdout)[0];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The verdict of one finished `docker exec`: a timeout, a launch failure or a non-zero exit is a failure, whatever it printed. */
|
|
17
|
+
function verdictOf(run, timeoutSeconds) {
|
|
18
|
+
if (run.error?.code === 'ETIMEDOUT') return { ok: false, detail: `timed out after ${timeoutSeconds}s` };
|
|
19
|
+
if (run.error) return { ok: false, detail: 'could not run' };
|
|
20
|
+
if (run.status === null || run.status === undefined) return { ok: false, detail: 'ended without an exit status' };
|
|
21
|
+
return run.status === 0 ? { ok: true, detail: 'ok' } : { ok: false, detail: `exit ${run.status}` };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Runs one declared task; returns { name, ok, detail, outputLines }. */
|
|
25
|
+
function runTask(ctx, config, name) {
|
|
26
|
+
const declared = config.tasks[name];
|
|
27
|
+
const container = runningContainer(ctx, config, declared.service);
|
|
28
|
+
if (!container) return { name, ok: false, detail: `no running container of ${config.stack}_${declared.service} on the daemon`, outputLines: 0 };
|
|
29
|
+
const run = tool(ctx, 'docker', ['exec', container, ...declared.command], { timeout: declared.timeoutSeconds * 1000, killSignal: 'SIGKILL' });
|
|
30
|
+
return { name, ...verdictOf(run, declared.timeoutSeconds), outputLines: splitLines(run.stdout).length + splitLines(run.stderr).length };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Runs the declared post-deploy tasks in order and stops at the first failure.
|
|
35
|
+
* Returns { ok, lines, data: { tasks: [{ name, ok }] } }; the lines read `PASS|FAIL post-deploy <name> detail (N output lines withheld)`.
|
|
36
|
+
*/
|
|
37
|
+
export async function postDeployStack(ctx, config) {
|
|
38
|
+
const results = [];
|
|
39
|
+
await findInOrder(config.postDeploy, async (name) => {
|
|
40
|
+
results.push(runTask(ctx, config, name));
|
|
41
|
+
return !results.at(-1).ok;
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
ok: results.every((entry) => entry.ok),
|
|
45
|
+
lines: results.map((entry) => `${entry.ok ? 'PASS' : 'FAIL'} post-deploy ${entry.name} ${entry.detail} (${entry.outputLines} output line${entry.outputLines === 1 ? '' : 's'} withheld)`),
|
|
46
|
+
data: { tasks: results.map(({ name, ok }) => ({ name, ok })) },
|
|
47
|
+
};
|
|
48
|
+
}
|
package/sync/index.mjs
CHANGED
|
@@ -150,6 +150,14 @@ function trailingNewline(text) {
|
|
|
150
150
|
return text.slice(0, end) + '\n';
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/** The digest-pinned image of a lite app's declared Redis, in every environment that runs it (ruling:R212/case-10). */
|
|
154
|
+
export const REDIS_IMAGE = 'redis:7@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f';
|
|
155
|
+
|
|
156
|
+
const redisDevText = project => render(readBundled('app/package-scripts/redis-dev.json'), { project, redisImage: REDIS_IMAGE });
|
|
157
|
+
|
|
158
|
+
/** The development scripts of a lite app's declared Redis (create once, then start and stop it), as a scripts object. */
|
|
159
|
+
export const redisDevScripts = project => JSON.parse(`{${redisDevText(project).trim().replace(/,$/, '')}}`);
|
|
160
|
+
|
|
153
161
|
/** What the app's sync reads from the jest preset it installs for its be side: the Sonar exclusions ({ sonarExclusions }); both editions install it (a lite back end keeps full's unit run, ruling:R212/case-1, amended by ruling:R212/case-4). */
|
|
154
162
|
export async function loadPresets(root) {
|
|
155
163
|
const name = '@starci/jest-preset';
|
|
@@ -341,6 +349,8 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
|
|
|
341
349
|
appScripts: appScripts(app, source),
|
|
342
350
|
// A full upgrade keeps the app's declared Supabase connection and its local database commands.
|
|
343
351
|
supabaseScripts: providers.includes('supabase') ? render(readBundled('app/package-scripts/supabase.json'), { appBinary: APP_BINARY }) : '',
|
|
352
|
+
// A lite app's declared Redis (sides.be.backing, ruling:R212/case-10) runs in development as one container of its pinned image.
|
|
353
|
+
backingScripts: lite && (app.sides.be.backing ?? []).includes('redis') ? redisDevText(app.project) : '',
|
|
344
354
|
// turbo builds every fe app workspace; ^build (turbo.json) builds the packages each app imports first, and caches both.
|
|
345
355
|
buildFe: 'npm run codegen --silent && turbo run build --filter=./fe/apps/*',
|
|
346
356
|
// The be side with its tsc; every fe workspace with its own typecheck script through turbo (the packages are built first: ^build).
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# rollback and disconnect of "starci app stack". It runs by hand only (Actions > deploy stack > Run workflow) from a release tag v*
|
|
4
4
|
# whose version is the package.json version; it has no push or schedule trigger.
|
|
5
5
|
# operation deploy builds and pushes one image per environments.<env>.deploy.images entry of .starcistacks/application-stacks.yaml,
|
|
6
|
-
# then deploys their immutable digests (render, provision, converge, verify, accept; a failure redeploys the previous release)
|
|
6
|
+
# then deploys their immutable digests (render, provision, converge, post-deploy tasks, verify, accept; a failure redeploys the previous release)
|
|
7
7
|
# operation rollback redeploys a stored release; "release" is 16 lowercase hex characters, empty means the previous accepted release
|
|
8
8
|
# The environment input names a protected GitHub environment (required reviewers) that holds:
|
|
9
9
|
# vars VPS_HOST, VPS_USER (the swarm manager); a variable named in deploy.passEnv, when the stack interpolates one
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# rollback and disconnect of "starci app stack". It runs by hand only (Actions > deploy stack > Run workflow) from a release tag v*
|
|
4
4
|
# whose version is the package.json version; it has no push or schedule trigger.
|
|
5
5
|
# operation deploy builds and pushes one image per environments.<env>.deploy.images entry of .starcistacks/application-stacks.yaml,
|
|
6
|
-
# then deploys their immutable digests (render, provision, converge, verify, accept; a failure redeploys the previous release)
|
|
6
|
+
# then deploys their immutable digests (render, provision, converge, post-deploy tasks, verify, accept; a failure redeploys the previous release)
|
|
7
7
|
# operation rollback redeploys a stored release; "release" is 16 lowercase hex characters, empty means the previous accepted release
|
|
8
8
|
# The environment input names a protected GitHub environment (required reviewers) that holds:
|
|
9
9
|
# vars VPS_HOST, VPS_USER (the swarm manager); a variable named in deploy.passEnv, when the stack interpolates one
|
|
@@ -1,36 +1,66 @@
|
|
|
1
1
|
// GraphQL wire types, the same in both editions (owner ruling 2026-10-13): every fe app with operations under
|
|
2
|
-
// src/modules/api/graphql/{queries,mutations} gets its typed documents generated from the contract snapshots the back end emits
|
|
2
|
+
// src/modules/api/graphql/{queries,mutations,subscriptions} gets its typed documents generated from the contract snapshots the back end emits
|
|
3
3
|
// (be/contracts/<app>/schema.graphql, `npm run contract:emit`) into the gitignored src/modules/api/graphql/__generated__/.
|
|
4
|
+
// The input-bounds output carries the backend validators into translated form schemas through modules/api.
|
|
4
5
|
// Nothing to generate (no snapshot yet, or no fe app with a modules/api/graphql folder) is not an error.
|
|
5
6
|
const CONTRACTS = path.join("be", "contracts")
|
|
6
7
|
const FE_APPS = path.join("fe", "apps")
|
|
7
8
|
const posix = (file) => file.split(path.sep).join("/")
|
|
8
|
-
const
|
|
9
|
+
const services = (fs.existsSync(CONTRACTS) ? fs.readdirSync(CONTRACTS) : []).sort(byCodeUnit)
|
|
10
|
+
const frontendApps = (fs.existsSync(FE_APPS) ? fs.readdirSync(FE_APPS) : []).sort(byCodeUnit)
|
|
11
|
+
const schemas = services
|
|
9
12
|
.map((app) => path.join(CONTRACTS, app, "schema.graphql"))
|
|
10
13
|
.filter((file) => fs.existsSync(file))
|
|
11
14
|
.sort(byCodeUnit)
|
|
12
15
|
.map(posix)
|
|
13
|
-
const operationRoots =
|
|
16
|
+
const operationRoots = frontendApps
|
|
14
17
|
.map((app) => path.join(FE_APPS, app, "src", "modules", "api", "graphql"))
|
|
15
18
|
.filter((dir) => fs.existsSync(dir))
|
|
16
19
|
.sort(byCodeUnit)
|
|
17
20
|
.map(posix)
|
|
21
|
+
const writeFormBounds = (roots, source) => {
|
|
22
|
+
for (const root of roots) {
|
|
23
|
+
const generated = `${root}/__generated__`
|
|
24
|
+
fs.mkdirSync(generated, { recursive: true })
|
|
25
|
+
fs.writeFileSync(`${generated}/input-bounds.ts`, source)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
18
28
|
if (schemas.length === 0 || operationRoots.length === 0) {
|
|
19
29
|
process.stdout.write("codegen: no GraphQL contract under be/contracts yet\n")
|
|
30
|
+
if (schemas.length === 0 && operationRoots.length) {
|
|
31
|
+
const { emptyInputBoundsSource } = await import("@starci/cli/input-bounds")
|
|
32
|
+
writeFormBounds(operationRoots, emptyInputBoundsSource())
|
|
33
|
+
}
|
|
20
34
|
} else {
|
|
35
|
+
const { buildSchema } = await import("graphql")
|
|
36
|
+
const { assertCompatibleInputBounds } = await import("@starci/cli/input-bounds")
|
|
37
|
+
assertCompatibleInputBounds(
|
|
38
|
+
Object.fromEntries(schemas.map((file) => [file, buildSchema(fs.readFileSync(file, "utf8"))])),
|
|
39
|
+
)
|
|
21
40
|
const { generate } = await import("@graphql-codegen/cli")
|
|
22
41
|
const generates = Object.fromEntries(
|
|
23
|
-
operationRoots.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
operationRoots.flatMap((root) => [
|
|
43
|
+
[
|
|
44
|
+
`${root}/__generated__/`,
|
|
45
|
+
{
|
|
46
|
+
preset: "client",
|
|
47
|
+
documents: [`${root}/queries/*.ts`, `${root}/mutations/*.ts`, `${root}/subscriptions/*.ts`],
|
|
48
|
+
presetConfig: { fragmentMasking: false },
|
|
49
|
+
config: { useTypeImports: true, enumsAsTypes: true, avoidOptionals: true },
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
[`${root}/__generated__/input-bounds.ts`, { plugins: [{ "@starci/cli/input-bounds": {} }] }],
|
|
31
53
|
]),
|
|
32
54
|
)
|
|
33
|
-
await generate(
|
|
55
|
+
await generate(
|
|
56
|
+
{
|
|
57
|
+
schema: schemas,
|
|
58
|
+
generates,
|
|
59
|
+
ignoreNoDocuments: true,
|
|
60
|
+
silent: true,
|
|
61
|
+
},
|
|
62
|
+
true,
|
|
63
|
+
)
|
|
34
64
|
// Stamp only a successful generation, using the same sorted paths and LF-normalized schemas as R52.
|
|
35
65
|
const hash = createHash("sha256")
|
|
36
66
|
for (const schema of schemas) {
|
|
@@ -43,3 +73,20 @@ if (schemas.length === 0 || operationRoots.length === 0) {
|
|
|
43
73
|
if (fs.existsSync(generated)) fs.writeFileSync(`${generated}/.schema-sha256`, `${digest}\n`)
|
|
44
74
|
}
|
|
45
75
|
}
|
|
76
|
+
|
|
77
|
+
// REST forms consume the same backend DTO bounds through their API owner's generated output, even without GraphQL.
|
|
78
|
+
const restRoots = frontendApps
|
|
79
|
+
.map((app) => path.join(FE_APPS, app, "src", "modules", "api", "rest"))
|
|
80
|
+
.filter((dir) => fs.existsSync(dir))
|
|
81
|
+
.map(posix)
|
|
82
|
+
if (restRoots.length) {
|
|
83
|
+
const { openApiBoundsSource } = await import("@starci/cli/input-bounds")
|
|
84
|
+
const contracts = Object.fromEntries(
|
|
85
|
+
services
|
|
86
|
+
.map((service) => [service, path.join(CONTRACTS, service, "openapi.json")])
|
|
87
|
+
.filter(([, file]) => fs.existsSync(file))
|
|
88
|
+
.map(([service, file]) => [service, JSON.parse(fs.readFileSync(file, "utf8"))]),
|
|
89
|
+
)
|
|
90
|
+
// Generate an empty map when a contract disappears so stale form limits cannot survive a successful codegen.
|
|
91
|
+
writeFormBounds(restRoots, openApiBoundsSource(contracts))
|
|
92
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{{header}}
|
|
2
2
|
# Commit gate (L0 only): work hygiene, the secrets guard and the bypass guard (no inline disable, type-check escape, skipped test or
|
|
3
3
|
# swallowed failure: HFS_BYPASS_MARKER) of the staged files, managed files equal to their render, eslint of the staged files of each
|
|
4
|
-
# side, stylelint over the staged CSS of fe, a prettier check of the staged files. Every step ends the hook when it fails: none can be
|
|
4
|
+
# side (the fe packages are built first: the apps import their emitted types, the way the managed lint script does), stylelint over the staged CSS of fe, a prettier check of the staged files. Every step ends the hook when it fails: none can be
|
|
5
5
|
# skipped. Types and the targeted specs belong to the op gate.
|
|
6
6
|
npx starci app hygiene || exit $?
|
|
7
7
|
npx starci app sync --check || exit $?
|
|
@@ -9,7 +9,7 @@ npx starci app sync --check || exit $?
|
|
|
9
9
|
git diff --cached --name-only --diff-filter=ACMR -z --relative=be -- '*.ts' '*.mts' '*.cts' '*.js' '*.mjs' '*.cjs' |
|
|
10
10
|
xargs -0 -n 50 sh -c 'if [ "$#" -gt 0 ]; then cd be && npx eslint -- "$@"; fi' sh || exit $?
|
|
11
11
|
git diff --cached --name-only --diff-filter=ACMR -z --relative=fe -- '*.ts' '*.tsx' '*.mts' '*.cts' '*.js' '*.jsx' '*.mjs' '*.cjs' |
|
|
12
|
-
xargs -0 -n 50 sh -c 'if [ "$#" -gt 0 ]; then cd fe && npx eslint --max-warnings=0 --no-warn-ignored -- "$@"; fi' sh || exit $?
|
|
12
|
+
xargs -0 -n 50 sh -c 'if [ "$#" -gt 0 ]; then if [ -d fe/packages ]; then npx turbo run build --filter="./fe/packages/*" --output-logs=errors-only || exit $?; fi; cd fe && npx eslint --max-warnings=0 --no-warn-ignored -- "$@"; fi' sh || exit $?
|
|
13
13
|
git diff --cached --name-only --diff-filter=ACMR -z --relative=fe -- '*.css' |
|
|
14
14
|
xargs -0 -n 50 sh -c 'if [ "$#" -gt 0 ]; then cd fe && npx stylelint "$@"; fi' sh || exit $?
|
|
15
15
|
git diff --cached --name-only --diff-filter=ACMR -z |
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
# backs work up under refs/backup/*. Git hands this hook one line per ref: <local ref> <local sha> <remote ref> <remote sha>.
|
|
4
4
|
# refs/backup/* allowed: the coordinator backup namespace
|
|
5
5
|
# refs/heads/main, refs/tags/v* allowed only when HEAD carries an annotated tag v[0-9]* and the release cut recorded the
|
|
6
|
-
# full suite of HEAD: <git common dir>/starci-release/<HEAD sha>.l4.json names that head and tag
|
|
6
|
+
# full suite of HEAD: <git common dir>/starci-release/<HEAD sha>.l4.json names that head and tag;
|
|
7
|
+
# a pushed tag must be that tag and must point at HEAD
|
|
7
8
|
# refs/heads/release/candidate-* the release candidate (starci app release push sends the release sha there first and moves main
|
|
8
9
|
# and the tag only when its ci.yml run is green): allowed under the same release gate, never deleted
|
|
9
10
|
# any other ref, a delete refused
|
|
@@ -13,6 +14,7 @@ head=$(git rev-parse HEAD)
|
|
|
13
14
|
record="$common/starci-release/$head.l4.json"
|
|
14
15
|
status=0
|
|
15
16
|
gate=""
|
|
17
|
+
gate_tag=""
|
|
16
18
|
refuse() {
|
|
17
19
|
echo "RIGHTS_PUSH_NOT_RELEASE: $1 - the allowed path is the release cut (main, v* tags and the release/candidate-* ref) or refs/backup/* (backups)" >&2
|
|
18
20
|
status=1
|
|
@@ -28,7 +30,7 @@ release_gate() {
|
|
|
28
30
|
if [ ! -f "$record" ]; then gate="no L4 record $record"; continue; fi
|
|
29
31
|
flat=$(tr -d ' \t\r\n' < "$record")
|
|
30
32
|
case "$flat" in
|
|
31
|
-
*'"head":"'"$head"'"'*'"tag":"'"$tag"'"'* | *'"tag":"'"$tag"'"'*'"head":"'"$head"'"'*) gate="ok"; return 0 ;;
|
|
33
|
+
*'"head":"'"$head"'"'*'"tag":"'"$tag"'"'* | *'"tag":"'"$tag"'"'*'"head":"'"$head"'"'*) gate="ok"; gate_tag="$tag"; return 0 ;;
|
|
32
34
|
esac
|
|
33
35
|
gate="the L4 record $record does not name head $head and tag $tag"
|
|
34
36
|
done
|
|
@@ -43,7 +45,12 @@ while read -r local_ref local_sha remote_ref remote_sha; do
|
|
|
43
45
|
*) refuse "refusing to delete $remote_ref"; continue ;;
|
|
44
46
|
esac
|
|
45
47
|
release_gate
|
|
46
|
-
if [ "$gate" != "ok" ]; then refuse "$remote_ref is closed: $gate"; fi
|
|
48
|
+
if [ "$gate" != "ok" ]; then refuse "$remote_ref is closed: $gate"; continue; fi
|
|
49
|
+
case "$remote_ref" in
|
|
50
|
+
refs/tags/*)
|
|
51
|
+
if [ "$remote_ref" != "refs/tags/$gate_tag" ]; then refuse "$remote_ref is not the release tag $gate_tag of HEAD $head"
|
|
52
|
+
elif [ "$(git rev-parse "$local_sha^{commit}" 2>/dev/null)" != "$head" ]; then refuse "$remote_ref does not point at HEAD $head"; fi ;;
|
|
53
|
+
esac ;;
|
|
47
54
|
*) refuse "refusing to push $remote_ref" ;;
|
|
48
55
|
esac
|
|
49
56
|
done
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"test": "cd be && jest --selectProjects unit --coverage",
|
|
8
8
|
"test:integration": "npm run typecheck:tests && cd be && jest --selectProjects integration",
|
|
9
9
|
{{> app/package-scripts/supabase.json}}
|
|
10
|
+
{{backingScripts}}
|
|
10
11
|
"contract:emit": "{{appBinary}} emit"
|
|
11
12
|
}
|
|
12
13
|
}
|