@starci/hfs 4.2.17 → 4.4.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 +7 -0
- package/db/migrate.mjs +361 -0
- package/db/post-cutover.mjs +136 -0
- package/emit/db-types.mjs +10 -1
- package/lint/run.mjs +1 -1
- package/package.json +4 -1
- package/release/gate.mjs +298 -0
- package/release/index.mjs +103 -0
- package/release/push.mjs +110 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
- package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
- package/runtime/knowledge/hfs/rules.yaml +3437 -160
- package/runtime/knowledge/hfs/slots.yaml +430 -92
- package/runtime/knowledge/patterns/be/api.yaml +92 -2
- package/runtime/knowledge/patterns/be/cli.yaml +37 -9
- package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
- package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
- package/runtime/knowledge/patterns/be/projections.yaml +4 -2
- package/runtime/knowledge/patterns/be/queues.yaml +11 -3
- package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
- package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
- package/runtime/knowledge/patterns/be/saga.yaml +20 -3
- package/runtime/knowledge/patterns/be/webhooks.yaml +210 -36
- package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/kernel/failure-codes.yaml +3128 -47
- package/runtime/modules/models/runtimes.yaml +1 -1
- package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
- package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
- package/runtime/packages/cli/src/validate-args.mjs +213 -0
- package/runtime/scripts/api/git/add.mjs +5 -0
- package/runtime/scripts/api/git/cat-file.mjs +5 -0
- package/runtime/scripts/api/git/init.mjs +7 -0
- package/runtime/scripts/api/git/lib.mjs +3 -0
- package/runtime/scripts/api/git/tag.mjs +5 -0
- package/runtime/scripts/api/node/lib.mjs +20 -0
- package/runtime/scripts/api/node/run-node.mjs +6 -0
- package/runtime/scripts/api/sops/lib.mjs +41 -0
- package/runtime/scripts/api/sops/resolve-sops.mjs +7 -0
- package/runtime/scripts/api/sops/seal.mjs +1 -1
- package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
- package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
- package/runtime/scripts/api/supabase/database-query.mjs +5 -0
- package/runtime/scripts/api/supabase/lib.mjs +44 -0
- package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
- package/runtime/scripts/hfs/app-wiring.mjs +201 -0
- package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
- package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
- package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
- package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
- package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
- package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
- package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
- package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
- package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
- package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
- package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
- package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
- package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
- package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
- package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
- package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
- package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
- package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
- package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
- package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
- package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
- package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
- package/runtime/scripts/hfs/check-results.mjs +28 -0
- package/runtime/scripts/hfs/check.mjs +63 -72
- package/runtime/scripts/hfs/coverage-from.mjs +70 -0
- package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
- package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
- package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
- package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
- package/runtime/scripts/hfs/doc-commands.mjs +367 -0
- package/runtime/scripts/hfs/edition-slots.mjs +2 -2
- package/runtime/scripts/hfs/example-secrets.mjs +158 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
- package/runtime/scripts/hfs/path-findings.mjs +32 -2
- package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
- package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
- package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
- package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
- package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
- package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
- package/runtime/scripts/hfs/rules/assets.mjs +79 -0
- package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
- package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
- package/runtime/scripts/hfs/rules/contract.mjs +66 -5
- package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
- package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
- package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
- package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
- package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +317 -37
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
- package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
- package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
- package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
- package/runtime/scripts/hfs/rules/database.mjs +11 -2
- package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
- package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
- package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
- package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
- package/runtime/scripts/hfs/rules/docker.mjs +24 -7
- package/runtime/scripts/hfs/rules/edition.mjs +11 -10
- package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
- package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
- package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
- package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
- package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
- package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
- package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
- package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
- package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
- package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
- package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
- package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -0
- package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
- package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
- package/runtime/scripts/hfs/rules/saga.mjs +8 -16
- package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
- package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
- package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
- package/runtime/scripts/hfs/rules/services.mjs +4 -4
- package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
- package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
- package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
- package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +168 -0
- package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
- package/runtime/scripts/hfs/rules/stacks.mjs +13 -8
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
- package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
- package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
- package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
- package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
- package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
- package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
- package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
- package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
- package/runtime/scripts/hfs/sealed-members.mjs +101 -0
- package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
- package/runtime/scripts/hfs/secret-document.mjs +97 -0
- package/runtime/scripts/hfs/secret-edit.mjs +170 -0
- package/runtime/scripts/hfs/secret-errors.mjs +20 -0
- package/runtime/scripts/hfs/secret-files.mjs +124 -0
- package/runtime/scripts/hfs/secret-init.mjs +300 -0
- package/runtime/scripts/hfs/secret-output.mjs +100 -0
- package/runtime/scripts/hfs/secret-sops.mjs +130 -0
- package/runtime/scripts/hfs/secret-store.mjs +163 -0
- package/runtime/scripts/hfs/secret.mjs +162 -193
- package/runtime/scripts/hfs/slot-imports.mjs +18 -3
- package/runtime/scripts/hfs/slot-required.mjs +13 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
- package/runtime/scripts/hfs/slots.mjs +1 -0
- package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
- package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
- package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
- package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
- package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
- package/runtime/scripts/hfs/tree-findings.mjs +22 -0
- package/runtime/scripts/lib/command-tokens.mjs +126 -0
- package/runtime/scripts/lib/compose-values.mjs +11 -0
- package/runtime/scripts/lib/deployment-findings.mjs +7 -0
- package/runtime/scripts/lib/json-clone.mjs +61 -0
- package/runtime/scripts/lib/list.mjs +4 -0
- package/runtime/scripts/lib/roots.mjs +59 -0
- package/runtime/scripts/lib/runtime-example.mjs +13 -0
- package/runtime/scripts/lib/secret-patterns.mjs +60 -5
- package/runtime/scripts/lib/sql-ast.mjs +32 -0
- package/runtime/scripts/lib/stack-services.mjs +1 -1
- package/runtime/scripts/lib/swarm-stack.mjs +126 -0
- package/runtime/scripts/lib/trim-end.mjs +8 -0
- package/runtime/scripts/lib/ts-analysis.mjs +23 -0
- package/runtime/scripts/lib/ts-ast.mjs +27 -2
- package/runtime/scripts/lib/workflow-events.mjs +8 -0
- package/scaffold/add-app.mjs +77 -59
- package/scaffold/add-cli-lite.mjs +24 -17
- package/scaffold/add-compose.mjs +79 -0
- package/scaffold/add-database-lite.mjs +14 -0
- package/scaffold/add-fe-overlay.mjs +122 -0
- package/scaffold/add-fe-page.mjs +269 -0
- package/scaffold/add-inbox-lite.mjs +87 -124
- package/scaffold/add-plan.mjs +183 -0
- package/scaffold/add-table.mjs +132 -70
- package/scaffold/add.mjs +186 -204
- package/scaffold/app.mjs +128 -43
- package/scaffold/edition-gate.mjs +3 -3
- package/scaffold/image.mjs +2 -2
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +2 -2
- package/src/main.mjs +238 -119
- package/stack/age-install.mjs +37 -0
- package/stack/backup.mjs +100 -0
- package/stack/build-args.mjs +42 -0
- package/stack/build.mjs +173 -0
- package/stack/cli.mjs +130 -0
- package/stack/config.mjs +57 -0
- package/stack/context.mjs +57 -0
- package/stack/converge.mjs +107 -0
- package/stack/deploy.mjs +301 -0
- package/stack/digest.mjs +16 -0
- package/stack/docker-transport.mjs +53 -0
- package/stack/edge-alias.mjs +118 -0
- package/stack/errors.mjs +21 -0
- package/stack/image-scan.mjs +152 -0
- package/stack/ingress-create.mjs +114 -0
- package/stack/ingress-publish.mjs +273 -0
- package/stack/ingress.mjs +706 -0
- package/stack/jobs.mjs +51 -0
- package/stack/live-restore.mjs +177 -0
- package/stack/prepare.mjs +266 -0
- package/stack/private-file.mjs +22 -0
- package/stack/provision.mjs +90 -0
- package/stack/prune.mjs +46 -0
- package/stack/release-store.mjs +87 -0
- package/stack/remote.mjs +205 -0
- package/stack/render.mjs +149 -0
- package/stack/retire.mjs +47 -0
- package/stack/sealed.mjs +149 -0
- package/stack/task.mjs +42 -0
- package/stack/verify.mjs +99 -0
- package/sync/ci-commands.mjs +13 -0
- package/sync/index.mjs +127 -64
- package/sync/stack-images.mjs +127 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
- package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
- package/templates/app/codegen-graphql.mjs.partial +13 -2
- package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
- package/templates/app/codegen-prelude.mjs.partial +8 -0
- package/templates/app/gitignore +4 -0
- package/templates/app/gitignore-lite +4 -0
- package/templates/app/hooks/husky/pre-commit +7 -12
- package/templates/app/package-scripts/package.json +1 -0
- package/templates/app/package-scripts/supabase.json +7 -0
- package/templates/app/package-scripts-lite/package.json +1 -7
- package/templates/app/skeleton/.editorconfig +1 -0
- package/templates/app/skeleton/.gitattributes +7 -1
- package/templates/app/skeleton/.github/dependabot.yml +17 -0
- package/templates/app/skeleton/.nvmrc +1 -1
- package/templates/app/skeleton/.sops.yaml +1 -1
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
- package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
- package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
- package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
- package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
- package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
- package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
- package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
- package/templates/app/skeleton/README.md +6 -6
- package/templates/app/skeleton/scripts/codegen.mjs +1 -2
- package/templates/app/skeleton-lite/.editorconfig +16 -0
- package/templates/app/skeleton-lite/.gitattributes +8 -0
- package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
- package/templates/app/skeleton-lite/.nvmrc +1 -0
- package/templates/app/skeleton-lite/.sops.yaml +3 -2
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +193 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
- package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
- package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
- package/templates/app/skeleton-lite/.starciwork/workspace.yaml +9 -0
- package/templates/app/skeleton-lite/README.md +8 -4
- package/templates/app/skeleton-lite/supabase/config.toml +8 -1
- package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
- package/templates/app/starciwork.gitignore-lite +6 -0
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/inbox/database-client.spec.ts.tpl +22 -28
- package/templates/be/inbox/database-client.ts.tpl +14 -28
- package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
- package/templates/be/inbox/event-index.ts.tpl +1 -0
- package/templates/be/inbox/events.json.tpl +10 -0
- package/templates/be/inbox/migration.sql.tpl +1 -0
- package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
- package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
- package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
- package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
- package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
- package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
- package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
- package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
- package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
- package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
- package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
- package/templates/be/inbox/platform/migration.sql.tpl +48 -0
- package/templates/be/inbox/service.spec.ts.tpl +49 -20
- package/templates/be/inbox/service.ts.tpl +27 -16
- package/templates/be/inbox/sql.ts.tpl +2 -2
- package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -3
- package/templates/be/inbox/webhook-door.ts.tpl +4 -3
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
- package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
- package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
- package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
- package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
- package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
- package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
- package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
- package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
- package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
- package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
- package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
- package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
- package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
- package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
- package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
- package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
- package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +3 -3
- package/templates/be/patterns/webhooks/webhook.door.ts.tpl +3 -3
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
- package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
- package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
- package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
- package/templates/be/skeleton/apps/__app__/src/__app__.env.ts +21 -0
- package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +1 -1
- package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -45
- package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
- package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
- package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +44 -0
- package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
- package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
- package/templates/be/skeleton/apps/__app__/src/main.ts +18 -10
- package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
- package/templates/be/skeleton/apps/cli/src/cli.env.ts +13 -0
- package/templates/be/skeleton/apps/cli/src/cli.options.ts +7 -17
- package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
- package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +3 -2
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
- package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
- package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
- package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
- package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
- package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
- package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
- package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
- package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +6 -6
- package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
- package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
- package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
- package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
- package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
- package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
- package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
- package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
- package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
- package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
- package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
- package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
- package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
- package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +25 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +1 -1
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +18 -58
- package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +47 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/main.ts +5 -13
- package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +17 -0
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
- package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
- package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
- package/templates/be/table/api/feature.module.ts.tpl +2 -1
- package/templates/be/table/api/feature.options.ts.tpl +2 -0
- package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
- package/templates/be/table/api/graphql.module.ts.tpl +3 -2
- package/templates/be/table/api/graphql.options.ts.tpl +2 -0
- package/templates/be/table/migration.sql.tpl +1 -0
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
- package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
- package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
- package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
- package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
- package/templates/fe/auth/supabase-client.ts.tpl +34 -0
- package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
- package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
- package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
- package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
- package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
- package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
- package/upgrade/check.mjs +3 -1
- package/upgrade/file-state.mjs +68 -0
- package/upgrade/index.mjs +58 -94
- package/runtime/scripts/api/git/log.mjs +0 -5
- package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +0 -55
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// (sides, app kinds) and refuses a manifest whole on any problem (HFS_MANIFEST_INVALID). Pure: every function takes a
|
|
5
5
|
// parsed manifest or slot and returns a list of problems in the words of modules/schemas/hfs-slots.schema.yaml.
|
|
6
6
|
import { isPlainObject } from '../../engine/plain-object.mjs';
|
|
7
|
-
import { stringList, byCodeUnit } from '../lib/list.mjs';
|
|
7
|
+
import { stringList, byCodeUnit, nonemptyUniqueList } from '../lib/list.mjs';
|
|
8
8
|
|
|
9
9
|
export const SEMVER = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
10
10
|
export const NAME = /^[a-z][a-z0-9-]*$/;
|
|
@@ -86,7 +86,7 @@ export const MANIFEST_KINDS = [APP_KIND, RUNTIME_KIND];
|
|
|
86
86
|
export const manifestKind = (m) => (isPlainObject(m) && m.kind !== undefined ? m.kind : APP_KIND);
|
|
87
87
|
const strList = (v) => stringList(v);
|
|
88
88
|
|
|
89
|
-
const APP_SLOT_KEYS = ['id', 'profiles', 'path', 'presence', 'tracked', 'tier', 'tests', 'owner', 'appKind', 'minInstances', 'requiredWhen', 'requiredInstances', 'requires', 'pattern', 'trigger', 'allows', 'forbids', 'layers', 'kinds', 'roles', 'composedBy', 'budget', 'managedBy', 'liteManagedBy', 'rules', 'goesTo', 'why', 'since', 'perConnection', 'parent', 'editions', 'litePresence', 'lite', 'provider', 'coverage', 'entries', 'outcomeHome'];
|
|
89
|
+
const APP_SLOT_KEYS = ['id', 'profiles', 'path', 'presence', 'tracked', 'tier', 'tests', 'owner', 'appKind', 'minInstances', 'requiredWhen', 'requiredInstances', 'requires', 'pattern', 'trigger', 'allows', 'forbids', 'layers', 'kinds', 'roles', 'composedBy', 'budget', 'managedBy', 'liteManagedBy', 'rules', 'goesTo', 'why', 'since', 'perConnection', 'parent', 'editions', 'editionWhy', 'litePresence', 'lite', 'provider', 'coverage', 'entries', 'outcomeHome', 'templateNa', 'exemplifiedBy'];
|
|
90
90
|
/** The fields a slot's `lite` overlay may hold: the same keys it would carry in the slot body, resolved under edition lite. */
|
|
91
91
|
const LITE_OVERLAY_KEYS = ['path', 'requires', 'allows', 'forbids', 'minInstances', 'requiredInstances', 'why'];
|
|
92
92
|
/** A runtime slot has no app kind, side composition, layer or managed template; it may name the generator of a generated copy. */
|
|
@@ -184,6 +184,9 @@ function slotListProblems(slot, at) {
|
|
|
184
184
|
|
|
185
185
|
function slotAppOptionProblems(slot, at) {
|
|
186
186
|
const bad = [];
|
|
187
|
+
bad.push(...slotEditionWhyProblems(slot, at), ...slotCoverageEvidenceProblems(slot, at));
|
|
188
|
+
// Legacy coverage status derives only from an explicit `since`; a slot that omits it is refused, so a new slot can never be taken for an old one.
|
|
189
|
+
if (slot.since === undefined) bad.push(`${at}: since is required (the manifest version that added the slot)`);
|
|
187
190
|
if (slot.editions !== undefined && !(Array.isArray(slot.editions) && slot.editions.length && slot.editions.every((e) => EDITIONS.includes(e)) && new Set(slot.editions).size === slot.editions.length)) bad.push(`${at}: editions must be a non-empty subset of ${EDITIONS.join(', ')}`);
|
|
188
191
|
if (slot.litePresence !== undefined && !(PRESENCE.includes(slot.litePresence) || (isPlainObject(slot.litePresence) && Object.keys(slot.litePresence).length && Object.entries(slot.litePresence).every(([side, value]) => ['be', 'fe'].includes(side) && PRESENCE.includes(value))))) bad.push(`${at}: litePresence must be a presence value or map sides to presence values`);
|
|
189
192
|
if (slot.provider !== undefined) {
|
|
@@ -193,6 +196,38 @@ function slotAppOptionProblems(slot, at) {
|
|
|
193
196
|
return bad;
|
|
194
197
|
}
|
|
195
198
|
|
|
199
|
+
function slotEditionWhyProblems(slot, at) {
|
|
200
|
+
if (slot.editionWhy === undefined) return [];
|
|
201
|
+
return typeof slot.editionWhy === 'string' && slot.editionWhy.trim()
|
|
202
|
+
? [] : [`${at}: editionWhy must be non-empty text`];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const slotEvidencePathValid = (value) => relPath(value) && /^[^\s]+$/.test(value);
|
|
206
|
+
|
|
207
|
+
function slotEvidenceRefValid(ref) {
|
|
208
|
+
if (typeof ref === 'string') return slotEvidencePathValid(ref);
|
|
209
|
+
if (!isPlainObject(ref) || !slotEvidencePathValid(ref.path)) return false;
|
|
210
|
+
if (!Object.keys(ref).every((key) => ['path', 'edition', 'editions'].includes(key))) return false;
|
|
211
|
+
if (ref.edition !== undefined && !EDITIONS.includes(ref.edition)) return false;
|
|
212
|
+
if (ref.editions !== undefined && !nonemptyUniqueList(ref.editions, (edition) => EDITIONS.includes(edition))) return false;
|
|
213
|
+
return ref.edition === undefined || ref.editions === undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const slotEvidenceRefKey = (ref) => typeof ref === 'string' ? ref
|
|
217
|
+
: JSON.stringify([ref.path, ref.edition, ref.editions]);
|
|
218
|
+
|
|
219
|
+
function slotCoverageEvidenceProblems(slot, at) {
|
|
220
|
+
const bad = [];
|
|
221
|
+
if (slot.templateNa !== undefined && !(typeof slot.templateNa === 'string' && slot.templateNa.trim()))
|
|
222
|
+
bad.push(`${at}: templateNa must be non-empty text`);
|
|
223
|
+
if (slot.exemplifiedBy === undefined) return bad;
|
|
224
|
+
const refs = slot.exemplifiedBy;
|
|
225
|
+
const valid = Array.isArray(refs) && refs.length > 0 && refs.every(slotEvidenceRefValid)
|
|
226
|
+
&& new Set(refs.map(slotEvidenceRefKey)).size === refs.length;
|
|
227
|
+
if (!valid) bad.push(`${at}: exemplifiedBy must be unique repository-relative paths or {path, edition? | editions?} references`);
|
|
228
|
+
return bad;
|
|
229
|
+
}
|
|
230
|
+
|
|
196
231
|
function slotLiteOverlayProblems(slot, at) {
|
|
197
232
|
if (slot.lite === undefined) return [];
|
|
198
233
|
const ok = isPlainObject(slot.lite) && Object.keys(slot.lite).length && Object.keys(slot.lite).every((key) => LITE_OVERLAY_KEYS.includes(key))
|
|
@@ -209,7 +244,7 @@ function slotEntryProblems(slot, at) {
|
|
|
209
244
|
if (slot.entries !== undefined) {
|
|
210
245
|
const listed = strList(slot.entries) && slot.entries.length > 0 && new Set(slot.entries).size === slot.entries.length && slot.entries.every(relPath);
|
|
211
246
|
if (!listed) bad.push(`${at}: entries must be a non-empty list of unique relative file paths`);
|
|
212
|
-
else if (typeof slot.path !== 'string' || !slot.path.endsWith('/')
|
|
247
|
+
else if (typeof slot.path !== 'string' || !slot.path.endsWith('/')) bad.push(`${at}: entries belong to a directory slot (they name its explicit public API files)`);
|
|
213
248
|
else if (Array.isArray(slot.allows) && slot.entries.some((entry) => !slot.allows.includes(entry))) bad.push(`${at}: every entry must be one of the names the slot allows`);
|
|
214
249
|
}
|
|
215
250
|
if (slot.outcomeHome !== undefined && !(slot.outcomeHome === true && typeof slot.path === 'string' && slot.path && !slot.path.endsWith('/'))) bad.push(`${at}: outcomeHome is true and belongs to a file slot (the one file that declares the repository's Outcome union)`);
|
|
@@ -323,21 +358,71 @@ function runtimeParamProblems(rp) {
|
|
|
323
358
|
];
|
|
324
359
|
}
|
|
325
360
|
|
|
361
|
+
const folderKindsValid = list => strList(list) && list.length > 0 && new Set(list).size === list.length && list.every(kind => NAME.test(kind));
|
|
362
|
+
const kindPairValid = pair => isPlainObject(pair) && Object.keys(pair).sort(byCodeUnit).join() === 'from,to'
|
|
363
|
+
&& ['from', 'to'].every(side => folderKindsValid(pair[side]));
|
|
364
|
+
const kindRestrictionValid = kinds => isPlainObject(kinds) && Object.keys(kinds).length > 0
|
|
365
|
+
&& Object.entries(kinds).every(([target, pair]) => NAME.test(target) && kindPairValid(pair));
|
|
366
|
+
const slotIdsValid = list => strList(list) && list.length > 0 && new Set(list).size === list.length
|
|
367
|
+
&& list.every(id => SLOT_ID.test(id) || RUNTIME_SLOT_ID.test(id));
|
|
368
|
+
const slotRestrictionPairValid = pair => isPlainObject(pair) && Object.keys(pair).join() === 'from' && slotIdsValid(pair.from);
|
|
369
|
+
const slotRestrictionValid = slots => isPlainObject(slots) && Object.keys(slots).length > 0
|
|
370
|
+
&& Object.entries(slots).every(([target, pair]) => NAME.test(target) && slotRestrictionPairValid(pair));
|
|
371
|
+
|
|
372
|
+
function tierDefinitionProblems(profile, tier, def) {
|
|
373
|
+
const bad = [];
|
|
374
|
+
const at = `tiers.${profile}.${tier}`;
|
|
375
|
+
if (!isPlainObject(def) || !Array.isArray(def.mayImport) || !def.mayImport.every((t) => NAME.test(String(t)))) bad.push(`${at}.mayImport must be a list of tier names`);
|
|
376
|
+
if (def?.mayImportKinds !== undefined && !kindRestrictionValid(def.mayImportKinds)) bad.push(`${at}.mayImportKinds must map tier names to {from, to} non-empty lists of unique folder kinds`);
|
|
377
|
+
if (def?.mayImportSlots !== undefined && !slotRestrictionValid(def.mayImportSlots)) bad.push(`${at}.mayImportSlots must map tier names to {from} non-empty lists of unique slot ids`);
|
|
378
|
+
for (const key of Object.keys(def ?? {})) if (!['mayImport', 'mayImportKinds', 'mayImportSlots', 'acyclic', 'lowerLayerOnly'].includes(key)) bad.push(`${at}.${key} is not a tier field`);
|
|
379
|
+
return bad;
|
|
380
|
+
}
|
|
381
|
+
|
|
326
382
|
/** Shape problems of the tier map of one profile. */
|
|
327
383
|
export function tierMapProblems(profile, tiers) {
|
|
328
384
|
const bad = [];
|
|
329
385
|
if (!isPlainObject(tiers) || !Object.keys(tiers).length) return [`tiers.${profile} must be a non-empty map`];
|
|
330
386
|
for (const [tier, def] of Object.entries(tiers)) {
|
|
331
387
|
if (!NAME.test(tier)) bad.push(`tiers.${profile}.${tier} is not a tier name`);
|
|
332
|
-
|
|
333
|
-
|
|
388
|
+
bad.push(...tierDefinitionProblems(profile, tier, def));
|
|
389
|
+
}
|
|
390
|
+
return bad;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function tierFolderKindProblems(manifest, profile, tier, kinds, at) {
|
|
394
|
+
const known = new Set(manifest.slots.filter((slot) => slot.profiles.includes(profile) && slot.tier === tier).flatMap((slot) => [...(slot.kinds ?? []), ...(slot.layers ?? [])]));
|
|
395
|
+
return kinds.filter(kind => !known.has(kind)).map(kind => `${at} names ${kind}, which is not a folder kind of the ${tier} tier`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function tierSlotRestrictionProblems(manifest, profile, tier, def) {
|
|
399
|
+
const bad = [];
|
|
400
|
+
const known = new Set(manifest.slots.filter(slot => slot.profiles.includes(profile) && slot.tier === tier).map(slot => slot.id));
|
|
401
|
+
for (const [target, restriction] of Object.entries(def.mayImportSlots ?? {})) {
|
|
402
|
+
const at = `tiers.${profile}.${tier}.mayImportSlots.${target}`;
|
|
403
|
+
if (!def.mayImport.includes(target)) bad.push(`${at} restricts a tier that mayImport does not allow`);
|
|
404
|
+
for (const id of restriction.from) if (!known.has(id)) bad.push(`${at}.from names ${id}, which is not a slot of the ${profile} ${tier} tier`);
|
|
405
|
+
}
|
|
406
|
+
return bad;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Kind and slot restrictions narrow allowed tier edges and name declared source and target roles. */
|
|
410
|
+
export function tierKindProblems(manifest, profile) {
|
|
411
|
+
const bad = [];
|
|
412
|
+
for (const [tier, def] of Object.entries(manifest.tiers[profile])) {
|
|
413
|
+
bad.push(...tierSlotRestrictionProblems(manifest, profile, tier, def));
|
|
414
|
+
for (const [target, kinds] of Object.entries(def.mayImportKinds ?? {})) {
|
|
415
|
+
const at = `tiers.${profile}.${tier}.mayImportKinds.${target}`;
|
|
416
|
+
if (!def.mayImport.includes(target)) bad.push(`${at} restricts a tier that mayImport does not allow`);
|
|
417
|
+
bad.push(...tierFolderKindProblems(manifest, profile, tier, kinds.from, `${at}.from`), ...tierFolderKindProblems(manifest, profile, target, kinds.to, `${at}.to`));
|
|
418
|
+
}
|
|
334
419
|
}
|
|
335
420
|
return bad;
|
|
336
421
|
}
|
|
337
422
|
|
|
338
423
|
/** The semantic rules of a runtime manifest beyond the slot rules both kinds share: tier names and the schema major. */
|
|
339
424
|
export function runtimeSemanticProblems(m) {
|
|
340
|
-
const bad =
|
|
425
|
+
const bad = tierKindProblems(m, RUNTIME_KIND);
|
|
341
426
|
const tiers = m.tiers.runtime;
|
|
342
427
|
for (const [tier, def] of Object.entries(tiers)) {
|
|
343
428
|
for (const target of def.mayImport) if (!(target in tiers)) bad.push(`tiers.runtime.${tier}.mayImport names ${target}, which is not a runtime tier`);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// (scripts/hfs/project-graph.mjs) runs it over the same tree and serves the findings on a TypeScript file as ESLint reports.
|
|
4
4
|
// HFS_SLOT_UNDECLARED / HFS_SLOT_AMBIGUOUS / HFS_SLOT_NOT_ENABLED / HFS_FORBIDDEN_PRESENT / HFS_TOOL_CONFIG_LOCAL / HFS_TRACKED_MUST_BE_IGNORED (R01, R16)
|
|
5
5
|
// BE_SOURCE_FORM (R89, the file name), BE_SPEC_PLACEMENT (R102)
|
|
6
|
+
import fs from 'node:fs';
|
|
6
7
|
import path from 'node:path';
|
|
7
8
|
import { allowsFile } from './allows.mjs';
|
|
8
9
|
import { isFeTestPath } from './rules/fe-no-tests.mjs';
|
|
@@ -30,8 +31,11 @@ function sourceFormContext(file, resolver, suffixes, bannedSuffixes) {
|
|
|
30
31
|
const slot = resolver.slot(c.slot);
|
|
31
32
|
if ([...(slot?.requires ?? []), ...(slot?.allows ?? [])].includes(base)) return null;
|
|
32
33
|
const admitted = allowsFile(resolver, file);
|
|
34
|
+
// Environment parsing is an app slot role, not a source suffix admitted outside that matching app file.
|
|
35
|
+
if (c.slot.startsWith('be.app.') && admitted?.allowed && admitted.entry === '<app>.env.ts') return null;
|
|
33
36
|
if (admitted?.allowed && admitted.entry?.includes('/') && path.posix.basename(admitted.entry) === base) return null;
|
|
34
|
-
|
|
37
|
+
// A plain entry names its file by a variable only (`<x>.ts`, or `compose/<area>.ts` of an app slot): a kebab name is its form.
|
|
38
|
+
if (admitted?.allowed && PLAIN_ENTRY.test(path.posix.basename(admitted.entry ?? '')) && KEBAB.test(base.slice(0, -'.ts'.length))) return null;
|
|
35
39
|
if (c.slot === 'be.persistence' && path.posix.basename(path.posix.dirname(file)) === 'migrations') return null;
|
|
36
40
|
return { c, base, suffixes, bannedSuffixes };
|
|
37
41
|
}
|
|
@@ -54,7 +58,7 @@ function sourceFormFinding(file, { c, base, suffixes, bannedSuffixes }, boundSuf
|
|
|
54
58
|
|
|
55
59
|
/**
|
|
56
60
|
* BE_SOURCE_FORM (R89): every tracked src/ or apps/ TypeScript file of a back end is index.ts, main.ts, a migration of
|
|
57
|
-
* be.persistence, or <kebab-name>.<suffix>.ts with <suffix> in the closed vocabulary ruleParams.be.suffixes (a name such
|
|
61
|
+
* be.persistence, the matching <app>.env.ts explicitly admitted by its app slot, or <kebab-name>.<suffix>.ts with <suffix> in the closed vocabulary ruleParams.be.suffixes (a name such
|
|
58
62
|
* as order.service.spec.ts keeps its inner words kebab-case). A suffix of ruleParams.be.bannedSuffixes anywhere in the
|
|
59
63
|
* name is refused by name. Paths no slot owns are HFS_SLOT_UNDECLARED's, not this code's.
|
|
60
64
|
*/
|
|
@@ -86,6 +90,8 @@ function forbiddenPathFinding(file, c, resolver) {
|
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
function pathStatusFinding(file, resolver) {
|
|
93
|
+
const liteWork = liteStarciworkFinding(file, resolver);
|
|
94
|
+
if (liteWork) return liteWork;
|
|
89
95
|
const c = resolver.classifyPath(file);
|
|
90
96
|
if (c.status === 'no-slot') {
|
|
91
97
|
const nearestNote = c.nearest ? `; nearest slot ${c.nearest.slot} (${c.nearest.pattern}), matched ${c.nearest.matchedPrefix || '.'} then expected ${c.nearest.expectedNext ?? 'nothing'}` : '';
|
|
@@ -98,6 +104,30 @@ function pathStatusFinding(file, resolver) {
|
|
|
98
104
|
return null;
|
|
99
105
|
}
|
|
100
106
|
|
|
107
|
+
/** The lite Work boundary, read from app.starciwork's edition-specific admission list, including paths a narrower slot owns. */
|
|
108
|
+
export function liteStarciworkFinding(file, resolver, { dir = false } = {}) {
|
|
109
|
+
if (resolver.repo?.edition !== 'lite' || !resolver.slot('app.starciwork')) return null;
|
|
110
|
+
const p = resolver.classifyPath(file).path;
|
|
111
|
+
if (!p.startsWith('.starciwork/')) return null;
|
|
112
|
+
const admitted = allowsFile(resolver, dir ? `${p}/.probe` : p);
|
|
113
|
+
if (admitted?.slot === 'app.starciwork' && admitted.allowed) return null;
|
|
114
|
+
return { code: 'HFS_STARCIWORK_LITE_FORBIDDEN', level: 'error', path: p, slot: 'app.starciwork', message: `${p}: lite .starciwork holds only workspace.yaml and brand/ (plus .gitignore); remove this path or use the full edition` };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Local lite Work leftovers, including metadata ignored by an old full-edition .gitignore. */
|
|
118
|
+
export function liteStarciworkTreeFindings(repoRoot, resolver, tracked) {
|
|
119
|
+
if (resolver.repo?.edition !== 'lite' || !resolver.slot('app.starciwork')) return [];
|
|
120
|
+
let entries = [];
|
|
121
|
+
try { entries = fs.readdirSync(path.join(repoRoot, '.starciwork'), { withFileTypes: true }); } catch { /* Work is optional. */ }
|
|
122
|
+
const trackedSet = new Set(tracked);
|
|
123
|
+
return entries.flatMap(entry => {
|
|
124
|
+
const file = `.starciwork/${entry.name}`;
|
|
125
|
+
if (trackedSet.has(file)) return [];
|
|
126
|
+
const finding = liteStarciworkFinding(file, resolver, { dir: entry.isDirectory() });
|
|
127
|
+
return finding ? [finding] : [];
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
101
131
|
/** The findings of the slot manifest over `files` (repository-relative tracked paths) of a repository of `profile`. */
|
|
102
132
|
export function pathFindings({ files, resolver, profile }) {
|
|
103
133
|
const findings = [];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Infrastructure refusals are shared with the dependency-free runtime copy generator.
|
|
2
|
+
// They describe checks that could not run, rather than catalogued obligations.
|
|
3
|
+
export const REFUSAL_CODES = Object.freeze([
|
|
4
|
+
'HFS_REPO_UNREADABLE',
|
|
5
|
+
'HFS_DECLARATION_INVALID', 'HFS_MANIFEST_MAJOR_MISMATCH', 'HFS_MANIFEST_INVALID', 'HFS_FORMAT_TOOL_MISSING', 'HFS_EDITION_INVALID',
|
|
6
|
+
]);
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { skillRoot } from '../../engine/runtime-root.mjs';
|
|
5
5
|
import { parseYamlCached } from '../lib/yaml-cached.mjs';
|
|
6
|
+
import { jsonClone } from '../lib/json-clone.mjs';
|
|
6
7
|
import { isPlainObject } from '../../engine/plain-object.mjs';
|
|
7
8
|
import { SEMVER } from './manifest-shape.mjs';
|
|
8
9
|
import { enforcerJudgedInEdition, judgedInEdition, ruleEditionProblems } from './edition-slots.mjs';
|
|
@@ -15,10 +16,129 @@ const RULE_KINDS = Object.freeze(['codemod', 'lint', 'check', 'design']);
|
|
|
15
16
|
const FINDING_CODE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$/;
|
|
16
17
|
const ENFORCER_ID = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
17
18
|
const FILE_ENFORCERS = new Set(['machine', 'hfs', 'work-validate', 'sonar', 'runtime']);
|
|
18
|
-
const RULE_KEYS = new Set(['id', 'code', 'law', 'scope', 'kinds', 'gates', 'failureCodes', 'editions', 'enforcers', 'cases']);
|
|
19
|
-
const CASE_KEYS = new Set(['id', 'date', 'source', 'ruling', 'quote']);
|
|
19
|
+
const RULE_KEYS = new Set(['id', 'code', 'law', 'scope', 'kinds', 'gates', 'failureCodes', 'editions', 'codeEditions', 'enforcers', 'cases', 'docs', 'exemplifiedBy', 'proves', 'witness', 'editionWhy', 'amendedBy', 'templateNa']);
|
|
20
|
+
const CASE_KEYS = new Set(['id', 'date', 'source', 'ruling', 'quote', 'supersedes', 'status', 'staleWhen', 'amendedBy']);
|
|
20
21
|
const CASE_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
21
|
-
const ENFORCER_KEYS = new Set(['kind', 'id', 'status', 'at', 'editions']);
|
|
22
|
+
const ENFORCER_KEYS = new Set(['kind', 'id', 'status', 'at', 'editions', 'editionWhy']);
|
|
23
|
+
const CASE_REFERENCE = /^(?:case-[1-9][0-9]*|(?:ruling:)?R[0-9]{2,3}(?:\/case-[1-9][0-9]*)?|owner:[a-z][a-z0-9]*(?:-[a-z0-9]+)*)$/;
|
|
24
|
+
const EVIDENCE_KEYS = new Set(['path', 'edition', 'editions']);
|
|
25
|
+
const PROOF_KEYS = new Set([...EVIDENCE_KEYS, 'codes', 'fixtures']);
|
|
26
|
+
const WITNESS_KEYS = new Set(['edition', 'base', 'mutate', 'expect']);
|
|
27
|
+
const MUTATION_KEYS = new Set(['op', 'path', 'content']);
|
|
28
|
+
|
|
29
|
+
function textProblem(value, label, bad) {
|
|
30
|
+
if (typeof value !== 'string' || !value.trim()) bad.push(`${label} must be non-empty text`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function dateProblem(value, label, bad) {
|
|
34
|
+
const date = typeof value === 'string' && CASE_DATE.test(value) ? new Date(`${value}T00:00:00Z`) : null;
|
|
35
|
+
if (!date || !Number.isFinite(date.getTime()) || date.toISOString() !== `${value}T00:00:00.000Z`) bad.push(`${label} must be a valid YYYY-MM-DD date`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function relativePathProblem(value, label, bad, anchor = false) {
|
|
39
|
+
const parts = typeof value === 'string' ? value.split('#') : [];
|
|
40
|
+
const file = parts[0];
|
|
41
|
+
const valid = typeof value === 'string' && value.trim() === value && value.length > 0
|
|
42
|
+
&& !/[\r\n\u0000\\]/.test(value) && !/^(?:\/|[a-z][a-z0-9+.-]*:)/i.test(value) && !file.includes(':')
|
|
43
|
+
&& !file.split('/').some((part) => ['', '.', '..'].includes(part))
|
|
44
|
+
&& (parts.length === 1 || (anchor && parts.length === 2 && /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(parts[1])));
|
|
45
|
+
if (!valid) bad.push(`${label} must be a repository-relative path${anchor ? ' with an optional #anchor' : ''}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function unknownKeys(item, allowed, label, bad) {
|
|
49
|
+
for (const key of Object.keys(item)) if (!allowed.has(key)) bad.push(`${label} has unknown key ${key}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function nonEmptyList(value, label, bad) {
|
|
53
|
+
if (!Array.isArray(value) || !value.length) { bad.push(`${label} must be a non-empty list`); return false; }
|
|
54
|
+
const normalized = value.map((value) => JSON.stringify(value, (_key, item) => isPlainObject(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item));
|
|
55
|
+
if (new Set(normalized).size !== value.length) bad.push(`${label} repeats a value`);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function optionalText(item, key, label, bad) {
|
|
60
|
+
if (item[key] !== undefined) textProblem(item[key], `${label}.${key}`, bad);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function evidenceEditionProblems(item, label, bad) {
|
|
64
|
+
if (item.edition !== undefined && !['full', 'lite'].includes(item.edition)) bad.push(`${label}.edition must be full or lite`);
|
|
65
|
+
bad.push(...ruleEditionProblems(item, label));
|
|
66
|
+
if (item.edition !== undefined && item.editions !== undefined) bad.push(`${label} names edition or editions, not both`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function codeListProblems(codes, label, bad) {
|
|
70
|
+
if (!nonEmptyList(codes, label, bad)) return;
|
|
71
|
+
for (const code of codes) if (typeof code !== 'string' || !FINDING_CODE.test(code)) bad.push(`${label} must contain UPPER_SNAKE finding codes`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function evidenceMapProblems(item, key, label, bad) {
|
|
75
|
+
unknownKeys(item, key === 'docs' ? EVIDENCE_KEYS : PROOF_KEYS, label, bad);
|
|
76
|
+
relativePathProblem(item.path, `${label}.path`, bad, key === 'docs');
|
|
77
|
+
evidenceEditionProblems(item, label, bad);
|
|
78
|
+
if (item.codes !== undefined) codeListProblems(item.codes, `${label}.codes`, bad);
|
|
79
|
+
if (item.fixtures !== undefined && nonEmptyList(item.fixtures, `${label}.fixtures`, bad)) item.fixtures.forEach((value) => textProblem(value, `${label}.fixtures`, bad));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function evidenceProblems(rule, key, label, bad) {
|
|
83
|
+
if (rule[key] === undefined || !nonEmptyList(rule[key], `${label}.${key}`, bad)) return;
|
|
84
|
+
rule[key].forEach((item, index) => {
|
|
85
|
+
const at = `${label}.${key}[${index}]`;
|
|
86
|
+
if (typeof item === 'string') { relativePathProblem(item, at, bad, key === 'docs'); return; }
|
|
87
|
+
if (!isPlainObject(item)) { bad.push(`${at} must be a path or a map`); return; }
|
|
88
|
+
evidenceMapProblems(item, key, at, bad);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function mutationProblems(item, label, bad) {
|
|
93
|
+
if (!isPlainObject(item)) { bad.push(`${label} must be a map`); return; }
|
|
94
|
+
unknownKeys(item, MUTATION_KEYS, label, bad);
|
|
95
|
+
if (!['add', 'edit', 'delete'].includes(item.op)) bad.push(`${label}.op must be add, edit or delete`);
|
|
96
|
+
relativePathProblem(item.path, `${label}.path`, bad);
|
|
97
|
+
if (['add', 'edit'].includes(item.op) && typeof item.content !== 'string') bad.push(`${label}.content must be text for add or edit`);
|
|
98
|
+
if (item.op === 'delete' && item.content !== undefined) bad.push(`${label}.content is absent for delete`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function witnessEntryProblems(item, label, bad) {
|
|
102
|
+
if (!isPlainObject(item)) { bad.push(`${label} must be a map`); return; }
|
|
103
|
+
if (Object.hasOwn(item, 'path')) { evidenceMapProblems(item, 'proves', label, bad); return; }
|
|
104
|
+
unknownKeys(item, WITNESS_KEYS, label, bad);
|
|
105
|
+
if (!['full', 'lite'].includes(item.edition)) bad.push(`${label}.edition must be full or lite`);
|
|
106
|
+
relativePathProblem(item.base, `${label}.base`, bad);
|
|
107
|
+
codeListProblems(item.expect, `${label}.expect`, bad);
|
|
108
|
+
if (nonEmptyList(item.mutate, `${label}.mutate`, bad)) item.mutate.forEach((mutation, index) => mutationProblems(mutation, `${label}.mutate[${index}]`, bad));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function witnessProblems(rule, label, bad) {
|
|
112
|
+
const value = rule.witness;
|
|
113
|
+
if (value === undefined) return;
|
|
114
|
+
const at = `${label}.witness`;
|
|
115
|
+
if (isPlainObject(value)) {
|
|
116
|
+
unknownKeys(value, new Set(['status', 'reason', 'date']), at, bad);
|
|
117
|
+
if (value.status !== 'unreachable') bad.push(`${at}.status must be unreachable`);
|
|
118
|
+
textProblem(value.reason, `${at}.reason`, bad);
|
|
119
|
+
dateProblem(value.date, `${at}.date`, bad);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!nonEmptyList(value, at, bad)) return;
|
|
123
|
+
value.forEach((item, index) => witnessEntryProblems(item, `${at}[${index}]`, bad));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function caseReferenceProblems(value, label, bad) {
|
|
127
|
+
const values = Array.isArray(value) ? value : [value];
|
|
128
|
+
if (!nonEmptyList(values, label, bad)) return;
|
|
129
|
+
for (const ref of values) if (typeof ref !== 'string' || !CASE_REFERENCE.test(ref)) bad.push(`${label} must name case-N, R<id>[/case-N], ruling:R<id>[/case-N] or owner:<id>`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function caseTransitionProblems(item, label, bad) {
|
|
133
|
+
if (item.status !== undefined && !['live', 'superseded', 'amended'].includes(item.status)) bad.push(`${label}.status must be live, superseded or amended`);
|
|
134
|
+
for (const key of ['supersedes', 'amendedBy']) if (item[key] !== undefined) caseReferenceProblems(item[key], `${label}.${key}`, bad);
|
|
135
|
+
if (item.staleWhen !== undefined && nonEmptyList(item.staleWhen, `${label}.staleWhen`, bad)) {
|
|
136
|
+
for (const pattern of item.staleWhen) {
|
|
137
|
+
if (typeof pattern !== 'string' || !pattern.trim()) { bad.push(`${label}.staleWhen must contain non-empty regex strings`); continue; }
|
|
138
|
+
try { new RegExp(pattern, 'i'); } catch { bad.push(`${label}.staleWhen has an invalid regex ${JSON.stringify(pattern)}`); }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
22
142
|
|
|
23
143
|
function vocabularyProblems(catalog, key, names, bad) {
|
|
24
144
|
if (!isPlainObject(catalog[key])) { bad.push(`${key} must be a map`); return; }
|
|
@@ -75,13 +195,15 @@ function checkRuleCases(rule, label, bad) {
|
|
|
75
195
|
if (typeof item.date !== 'string' || !CASE_DATE.test(item.date)) bad.push(`${at}.date must be YYYY-MM-DD`);
|
|
76
196
|
if (item.source !== 'owner') bad.push(`${at}.source must be owner`);
|
|
77
197
|
for (const key of ['ruling', ...(item.quote === undefined ? [] : ['quote'])]) if (typeof item[key] !== 'string' || !item[key].trim()) bad.push(`${at}.${key} must be text`);
|
|
198
|
+
caseTransitionProblems(item, at, bad);
|
|
78
199
|
});
|
|
79
200
|
}
|
|
80
201
|
|
|
81
|
-
function checkRuleGates(label, gates, bad) {
|
|
202
|
+
function checkRuleGates(rule, label, gates, bad) {
|
|
82
203
|
if (!gates.length) return;
|
|
83
|
-
|
|
84
|
-
|
|
204
|
+
const releaseGate = gates.includes('land') || gates.includes('ci');
|
|
205
|
+
const runtimeGate = rule.scope === 'runtime' && gates.includes('runtime');
|
|
206
|
+
if (!releaseGate && !runtimeGate) bad.push(`${label} must run at land or ci or, for runtime scope, the runtime gate`);
|
|
85
207
|
}
|
|
86
208
|
|
|
87
209
|
function checkRuleFailureCodes(rule, label, codeOwner, bad) {
|
|
@@ -97,6 +219,19 @@ function checkRuleFailureCodes(rule, label, codeOwner, bad) {
|
|
|
97
219
|
}
|
|
98
220
|
}
|
|
99
221
|
|
|
222
|
+
function checkCodeEditions(rule, label, bad) {
|
|
223
|
+
const value = rule.codeEditions;
|
|
224
|
+
if (value === undefined) return;
|
|
225
|
+
if (!isPlainObject(value) || !Object.keys(value).length) { bad.push(`${label}.codeEditions must be a non-empty map`); return; }
|
|
226
|
+
const editions = Array.isArray(rule.editions) ? rule.editions : ['full', 'lite'];
|
|
227
|
+
for (const [code, selected] of Object.entries(value)) {
|
|
228
|
+
const at = `${label}.codeEditions.${code}`;
|
|
229
|
+
if (!Array.isArray(rule.failureCodes) || !rule.failureCodes.includes(code)) bad.push(`${at} must name a code owned by this rule`);
|
|
230
|
+
enumListProblems({ editions: selected }, at, 'editions', ['full', 'lite'], bad);
|
|
231
|
+
if (Array.isArray(selected) && selected.some((edition) => !editions.includes(edition))) bad.push(`${at} must be a subset of the rule's editions`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
100
235
|
function checkEnforcerIdentity(enforcer, label, seen, bad) {
|
|
101
236
|
if (!ENFORCER_FAMILIES.includes(enforcer.kind)) bad.push(`${label}.kind must be one of ${ENFORCER_FAMILIES.join(', ')}`);
|
|
102
237
|
if (!ENFORCER_ID.test(String(enforcer.id))) bad.push(`${label}.id must be kebab-case`);
|
|
@@ -119,6 +254,7 @@ function checkEnforcer(enforcer, index, ruleLabel, seen, bad) {
|
|
|
119
254
|
if (!ENFORCER_KEYS.has(key)) bad.push(`${label} has unknown key ${key}`);
|
|
120
255
|
}
|
|
121
256
|
bad.push(...ruleEditionProblems(enforcer, label));
|
|
257
|
+
optionalText(enforcer, 'editionWhy', label, bad);
|
|
122
258
|
checkEnforcerIdentity(enforcer, label, seen, bad);
|
|
123
259
|
if (enforcer.status !== undefined && enforcer.status !== 'planned') bad.push(`${label}.status is either absent or planned`);
|
|
124
260
|
checkEnforcerLocation(enforcer, label, bad);
|
|
@@ -140,11 +276,17 @@ function checkRule(rule, index, catalog, codeOwner, bad) {
|
|
|
140
276
|
checkRuleIdentity(rule, index, catalog, at, label, bad);
|
|
141
277
|
checkRuleText(rule, label, bad);
|
|
142
278
|
checkRuleCases(rule, label, bad);
|
|
279
|
+
if (rule.amendedBy !== undefined) caseReferenceProblems(rule.amendedBy, `${label}.amendedBy`, bad);
|
|
280
|
+
optionalText(rule, 'editionWhy', label, bad);
|
|
281
|
+
optionalText(rule, 'templateNa', label, bad);
|
|
282
|
+
for (const key of ['docs', 'exemplifiedBy', 'proves']) evidenceProblems(rule, key, label, bad);
|
|
283
|
+
witnessProblems(rule, label, bad);
|
|
143
284
|
enumListProblems(rule, label, 'kinds', RULE_KINDS, bad);
|
|
144
285
|
bad.push(...ruleEditionProblems(rule, label));
|
|
145
286
|
const gates = enumListProblems(rule, label, 'gates', RULE_GATES, bad);
|
|
146
|
-
checkRuleGates(label, gates, bad);
|
|
287
|
+
checkRuleGates(rule, label, gates, bad);
|
|
147
288
|
checkRuleFailureCodes(rule, label, codeOwner, bad);
|
|
289
|
+
checkCodeEditions(rule, label, bad);
|
|
148
290
|
checkRuleEnforcers(rule, label, bad);
|
|
149
291
|
if (Array.isArray(rule.gates)) {
|
|
150
292
|
const hasSonar = rule.enforcers.some((enforcer) => isPlainObject(enforcer) && enforcer.kind === 'sonar');
|
|
@@ -183,15 +325,18 @@ export function loadRuleCatalog({ root = skillRoot, file = path.join(root, HFS_R
|
|
|
183
325
|
const byCode = new Map(list.flatMap((rule) => rule.failureCodes.map((code) => [code, rule])));
|
|
184
326
|
return Object.freeze({
|
|
185
327
|
version: doc.version, major, minor, patch,
|
|
186
|
-
gates: deepFreeze(
|
|
187
|
-
enforcerKinds: deepFreeze(
|
|
328
|
+
gates: deepFreeze(jsonClone(doc.gates)),
|
|
329
|
+
enforcerKinds: deepFreeze(jsonClone(doc.enforcerKinds)),
|
|
188
330
|
rules: list,
|
|
189
331
|
/** The rule with this id (R01..), or null. */
|
|
190
332
|
rule: (id) => byId.get(id) ?? null,
|
|
191
333
|
/** The rule that owns this failure code (its own or a sub-check code), or null. */
|
|
192
334
|
byCode: (code) => byCode.get(code) ?? null,
|
|
193
|
-
/** Whether a finding code is judged under `edition`:
|
|
194
|
-
judgedIn: (code, edition = 'full') =>
|
|
335
|
+
/** Whether a finding code is judged under `edition`: codeEditions narrows its owner's editions; uncatalogued codes remain judged. */
|
|
336
|
+
judgedIn: (code, edition = 'full') => {
|
|
337
|
+
const owner = byCode.get(code);
|
|
338
|
+
return judgedInEdition(owner, edition) && (!owner?.codeEditions?.[code] || owner.codeEditions[code].includes(edition));
|
|
339
|
+
},
|
|
195
340
|
enforcerJudgedIn: (kind, id, edition = 'full') => enforcerJudgedInEdition(list, kind, id, edition),
|
|
196
341
|
/** The rules that run at a gate. */
|
|
197
342
|
forGate: (gate) => list.filter((rule) => rule.gates.includes(gate)),
|
|
@@ -4,22 +4,33 @@
|
|
|
4
4
|
import { isPlainObject } from '../../engine/plain-object.mjs';
|
|
5
5
|
import { paramNamesOk } from './param-names.mjs';
|
|
6
6
|
import { kindParamProblems, scenarioProblem } from './declaration-slots.mjs';
|
|
7
|
-
import { roleListProblems, SCHEMA_AUTHORITIES, unitRolesProblems } from './manifest-shape.mjs';
|
|
8
|
-
import { byCodeUnit } from '../lib/list.mjs';
|
|
7
|
+
import { NAME, roleListProblems, SCHEMA_AUTHORITIES, unitRolesProblems } from './manifest-shape.mjs';
|
|
8
|
+
import { byCodeUnit, nonemptyUniqueList } from '../lib/list.mjs';
|
|
9
9
|
|
|
10
10
|
const PROFILES = ['be', 'fe'];
|
|
11
11
|
const BE_OWN = ['infraOwners', 'eventBus', 'specDoubles', 'paramNames', 'suffixes', 'bannedSuffixes', 'contractShape', 'unitRoles', 'logicRoles', 'thinRoles', 'patternScenarios', 'kindPatterns', 'addKinds'];
|
|
12
12
|
const FORM_NAMES = new Set(['call', 'new', 'curried', 'object', 'primitive', 'array']);
|
|
13
13
|
const blockOk = (v) => isPlainObject(v) && Number.isInteger(v.lines) && v.lines >= 2 && Number.isInteger(v.tokens) && v.tokens >= 1 && Object.keys(v).length === 2;
|
|
14
14
|
const fileLinesOk = (v) => isPlainObject(v) && Number.isInteger(v.soft) && v.soft >= 1 && typeof v.hardGrowth === 'boolean' && Object.keys(v).length === 2;
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const SHARED_PARAMS = {
|
|
16
|
+
fileLines: fileLinesOk,
|
|
17
|
+
duplicateBlock: blockOk,
|
|
18
|
+
functionLines: v => Number.isInteger(v) && v >= 1,
|
|
19
|
+
featureFiles: v => Number.isInteger(v) && v >= 1,
|
|
20
|
+
};
|
|
21
|
+
const SHARED_KEYS = Object.keys(SHARED_PARAMS);
|
|
22
|
+
const sharedParam = (key, v) => Object.hasOwn(SHARED_PARAMS, key) && SHARED_PARAMS[key](v);
|
|
23
|
+
const sideOk = (side, own, extra = []) => own.every((k) => side[k] !== undefined) && Object.entries(side).every(([k, v]) => own.includes(k) || extra.includes(k) || sharedParam(k, v));
|
|
24
|
+
const liteKeyOk = (lite, key, keys) => key !== 'lite' && keys.includes(key)
|
|
25
|
+
&& (!['functionLines', 'featureFiles'].includes(key) || sharedParam(key, lite[key]));
|
|
26
|
+
const liteOk = (lite, keys) => lite === undefined || (isPlainObject(lite) && Object.keys(lite).length > 0 && Object.keys(lite).every((k) => liteKeyOk(lite, k, keys)));
|
|
18
27
|
const memberMap = (v, allowEmpty) => isPlainObject(v) && Object.keys(v).length > 0 && Object.values(v).every((list) => Array.isArray(list) && (allowEmpty || list.length > 0) && list.every((x) => /^[A-Z][A-Za-z0-9]*$/.test(String(x))) && new Set(list).size === list.length);
|
|
19
|
-
const roleList = (v) =>
|
|
28
|
+
const roleList = (v) => nonemptyUniqueList(v, (role) => NAME.test(String(role)));
|
|
20
29
|
const formsOk = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => FORM_NAMES.has(x)) && new Set(v).size === v.length;
|
|
21
30
|
const doubleOk = (v) => isPlainObject(v) && /^[A-Za-z][A-Za-z0-9]*$/.test(String(v.double)) && formsOk(v.forms);
|
|
22
31
|
const regexOk = (v) => { try { return typeof v === 'string' && v.length > 0 && Boolean(new RegExp(v)); } catch { return false; } };
|
|
32
|
+
const dbExtensionsOk = (value) => Array.isArray(value) && value.every((name) => typeof name === 'string' && name.length > 0) && new Set(value).size === value.length;
|
|
33
|
+
const dbParamsOk = (db) => db === undefined || (isPlainObject(db) && Object.entries(db).every(([key, value]) => key === 'extensions' && dbExtensionsOk(value)));
|
|
23
34
|
|
|
24
35
|
function beSpecificProblems(be, manifest) {
|
|
25
36
|
const bad = [];
|
|
@@ -43,14 +54,15 @@ function beSpecificProblems(be, manifest) {
|
|
|
43
54
|
export function ruleParamsProblems(m) {
|
|
44
55
|
const bad = [];
|
|
45
56
|
const rp = m.ruleParams;
|
|
46
|
-
if (!isPlainObject(rp) || Object.keys(rp).some((k) => ![...PROFILES, 'common'].includes(k)) || !isPlainObject(rp.common) || !isPlainObject(rp.be) || (rp.fe !== undefined && !isPlainObject(rp.fe))) return ['ruleParams must be a map with common, be and optionally fe'];
|
|
57
|
+
if (!isPlainObject(rp) || Object.keys(rp).some((k) => ![...PROFILES, 'common', 'db'].includes(k)) || !isPlainObject(rp.common) || !isPlainObject(rp.be) || (rp.fe !== undefined && !isPlainObject(rp.fe))) return ['ruleParams must be a map with common, be and optionally fe and db'];
|
|
47
58
|
else {
|
|
48
59
|
// common holds the parameters both sides share; a side may restate one of them (a valid value) as an override, nothing else.
|
|
49
60
|
// Edition lite: a side's optional `lite` map overrides keys of the same side (never `lite` itself) when the app declares edition lite.
|
|
50
|
-
if (
|
|
51
|
-
if (!sideOk(rp.be, BE_OWN, ['schemaAuthority', 'lite']) || (rp.be.schemaAuthority !== undefined && !SCHEMA_AUTHORITIES.includes(rp.be.schemaAuthority)) || !liteOk(rp.be.lite, [...BE_OWN,
|
|
61
|
+
if (!sideOk(rp.common, []) || !fileLinesOk(rp.common.fileLines) || !blockOk(rp.common.duplicateBlock)) bad.push('ruleParams.common needs fileLines {soft, hardGrowth} and duplicateBlock {lines >= 2, tokens >= 1}; optional functionLines and featureFiles must be positive integers');
|
|
62
|
+
if (!sideOk(rp.be, BE_OWN, ['schemaAuthority', 'lite']) || (rp.be.schemaAuthority !== undefined && !SCHEMA_AUTHORITIES.includes(rp.be.schemaAuthority)) || !liteOk(rp.be.lite, [...BE_OWN, ...SHARED_KEYS, 'schemaAuthority'])) bad.push('ruleParams.be needs infraOwners, eventBus, specDoubles, paramNames, suffixes, bannedSuffixes, contractShape {helper}, unitRoles, logicRoles, thinRoles, patternScenarios, kindPatterns and addKinds (shared parameters live in ruleParams.common, a side may override them); optional: schemaAuthority (' + SCHEMA_AUTHORITIES.join(' | ') + ') and lite (overrides of the same keys)'); else bad.push(...unitRolesProblems(rp.be), ...roleListProblems(rp.be));
|
|
52
63
|
bad.push(...beSpecificProblems(rp.be, m));
|
|
53
|
-
if (rp.fe !== undefined && (!sideOk(rp.fe, [], ['lite']) || !liteOk(rp.fe.lite,
|
|
64
|
+
if (rp.fe !== undefined && (!sideOk(rp.fe, [], ['lite']) || !liteOk(rp.fe.lite, SHARED_KEYS))) bad.push('ruleParams.fe may only restate the shared parameters of ruleParams.common, and carry a lite override of them');
|
|
65
|
+
if (!dbParamsOk(rp.db)) bad.push('ruleParams.db may contain only extensions: a list of unique non-empty extension names');
|
|
54
66
|
}
|
|
55
67
|
return bad;
|
|
56
68
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// a15-findings.mjs - the one place where the alpha.15 content rules (R247-R280) are wired into `starci app check`.
|
|
2
|
+
// Each rule module of a batch exports a synchronous finder `({ repoRoot, files, all, repo, resolver, manifest, isRoot, inScope }) => finding[]`
|
|
3
|
+
// and is wired here by appending ONE import line and ONE entry to FINDERS. Nothing else in this file changes; the file is union-merged.
|
|
4
|
+
// A finder judges its own edition (`repo.edition`): lite and full are both in scope unless the rule says otherwise.
|
|
5
|
+
// finders-import-begin
|
|
6
|
+
import { featureFileFindings } from './feature-files.mjs';
|
|
7
|
+
import { lexiconFindings } from './lexicon.mjs';
|
|
8
|
+
import { assetsFindings } from './assets.mjs';
|
|
9
|
+
import { unusedSymbolFindings } from './unused-symbol.mjs';
|
|
10
|
+
import { databaseBaselineFindings } from './database-baseline.mjs';
|
|
11
|
+
import { databaseQueuesFindings } from './database-queues.mjs';
|
|
12
|
+
import { databaseConfigFindings } from './database-config.mjs';
|
|
13
|
+
import { docFreshFindings } from './doc-fresh.mjs';
|
|
14
|
+
import { releaseProvenanceFindings } from './release-provenance.mjs';
|
|
15
|
+
import { feStackAllowlistFindings } from './fe-stack.mjs';
|
|
16
|
+
import { appEnvRequiredFindings } from './app-env-required.mjs';
|
|
17
|
+
import { ageKeyCustodyFindings } from './secrets.mjs';
|
|
18
|
+
import { canonCopyDriftFindings } from './canon-copy-drift.mjs';
|
|
19
|
+
import { secretCustodyOnlyFindings } from './secret-custody-only.mjs';
|
|
20
|
+
import { bootProofFindings } from './boot-proof.mjs';
|
|
21
|
+
import { coverageBelowFindings } from './coverage-below.mjs';
|
|
22
|
+
import { deployScanFindings } from './deploy-scan.mjs';
|
|
23
|
+
import { deployWorkflowFindings } from './deploy-workflow.mjs';
|
|
24
|
+
import { scriptHygieneFindings } from './script-hygiene.mjs';
|
|
25
|
+
import { supplyChainFindings } from './supply-chain.mjs';
|
|
26
|
+
import { envParityFindings } from './env-parity.mjs';
|
|
27
|
+
import { envValueShapeFindings } from './env-value-shape.mjs';
|
|
28
|
+
import { liteProofLaneFindings } from './lite-proof-lane.mjs';
|
|
29
|
+
import { stacksSwarmFindings } from './stacks-swarm.mjs';
|
|
30
|
+
import { stacksReleaseFindings } from './stacks-release.mjs';
|
|
31
|
+
import { stacksIngressFindings } from './stacks-ingress.mjs';
|
|
32
|
+
import { stacksLiteBackingFindings } from './stacks-lite-backing.mjs';
|
|
33
|
+
import { stackToolingFindings } from './stack-tooling.mjs';
|
|
34
|
+
import { stacksDeployFindings } from './stacks-deploy.mjs';
|
|
35
|
+
// finders-import-end
|
|
36
|
+
|
|
37
|
+
/** The failure codes the finders above emit, so `starci app check` ships their labels (check.mjs adds them to CHECK_CODES). Append-only; the coordinator regenerates it at merge time. */
|
|
38
|
+
export const A15_CODES = [
|
|
39
|
+
// codes-begin
|
|
40
|
+
'BACKUP_PROOF_MISSING', 'BACKUP_UNDECLARED', 'BE_APP_BOOT_PROOF_MISSING', 'BE_APP_ENV_REQUIRED', 'BE_APP_MODULE_LIST_INLINE', 'BE_LITE_PROOF_LANE_MISSING', 'DB_API_EXPOSED', 'DB_ARCHIVE_MUTATED', 'DB_BASELINE_SHAPE', 'DB_CRON_OWNED', 'DB_EXTENSION_ALLOWED', 'DB_MIGRATION_DESTRUCTIVE', 'DB_PGMQ_DECLARED', 'DB_VAULT_WRITE', 'ENV_DUPLICATE', 'ENV_EXAMPLE_DRIFT', 'ENV_PUBLIC_SEALED', 'ENV_REQUIRED_UNSET', 'ENV_SEALED_UNREAD', 'ENV_VALUE_SHAPE', 'FE_STACK_CORE_MISSING', 'FE_STACK_UNLISTED', 'HFS_AGE_KEY_CUSTODY', 'HFS_APP_LEXICON', 'HFS_APP_WORKFLOW_TRIGGER', 'HFS_ASSET_KIND', 'HFS_ASSET_OVERSIZE', 'HFS_AUDIT_STEP_MISSING', 'HFS_CANON_COPY_DRIFT', 'HFS_CI_TOOL_INSTALL', 'HFS_COVERAGE_BELOW', 'HFS_DB_DEPLOY_UNGUARDED', 'HFS_DEPENDABOT_MISSING', 'HFS_DEPLOY_COMPOSE_ON_VPS', 'HFS_DEPLOY_NO_LOGIC', 'HFS_DEPLOY_SCHEDULER', 'HFS_DEPLOY_WORKFLOW', 'HFS_DOC_COMMAND_UNKNOWN', 'HFS_DOC_ENV_UNKNOWN', 'HFS_DOC_PATH_MISSING', 'HFS_DOC_RULE_UNKNOWN', 'HFS_FEATURE_FILE_BUDGET', 'HFS_HOST_CRON', 'HFS_RELEASE_TAG_MISMATCH', 'HFS_SBOM_MISSING', 'HFS_SCHEDULE_UNDECLARED', 'HFS_SCRIPT_PLAINTEXT_LOAD', 'HFS_SCRIPT_XTRACE', 'HFS_SECRET_OUTSIDE_ENC', 'HFS_SECRET_PLAINTEXT_FILE', 'HFS_SEED_PII', 'HFS_SERVICE_ROLE_SCOPE', 'HFS_UNUSED_SYMBOL', 'HFS_WORKFLOW_PIN', 'STACKS_DEPLOY_CONFIG_INVALID', 'STACKS_DNS_UNDECLARED', 'STACKS_HEALTHCHECK_MISSING', 'STACKS_HOST_UNROUTED', 'STACKS_INGRESS_UNDECLARED', 'STACKS_INGRESS_UNKNOWN', 'STACKS_LITE_SELFHOSTED_DB', 'STACKS_MODEL_UNAVAILABLE', 'STACKS_PLACEMENT_MISSING', 'STACKS_PORT_PUBLISHED', 'STACKS_REALTIME_SCALED', 'STACKS_RELEASE_UNDECLARED', 'STACKS_SECOND_INGRESS', 'STACKS_SECRET_ENV_LITERAL', 'STACKS_SECRET_NOT_EXTERNAL', 'STACKS_SECRET_UNVERSIONED', 'STACKS_SIDECAR_DB_UNSUPPORTED', 'STACKS_SIDECAR_VOLUME_UNDECLARED', 'STACKS_TLS_UNDECLARED', 'STACKS_UPDATE_POLICY_MISSING', 'STACK_TOOLING_REIMPLEMENTED',
|
|
41
|
+
// codes-end
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export const FINDERS = [
|
|
45
|
+
// finders-begin
|
|
46
|
+
featureFileFindings,
|
|
47
|
+
lexiconFindings,
|
|
48
|
+
assetsFindings,
|
|
49
|
+
unusedSymbolFindings,
|
|
50
|
+
databaseBaselineFindings,
|
|
51
|
+
databaseQueuesFindings,
|
|
52
|
+
databaseConfigFindings,
|
|
53
|
+
docFreshFindings,
|
|
54
|
+
releaseProvenanceFindings,
|
|
55
|
+
feStackAllowlistFindings,
|
|
56
|
+
appEnvRequiredFindings,
|
|
57
|
+
ageKeyCustodyFindings,
|
|
58
|
+
canonCopyDriftFindings,
|
|
59
|
+
secretCustodyOnlyFindings,
|
|
60
|
+
bootProofFindings,
|
|
61
|
+
coverageBelowFindings,
|
|
62
|
+
deployScanFindings,
|
|
63
|
+
deployWorkflowFindings,
|
|
64
|
+
scriptHygieneFindings,
|
|
65
|
+
supplyChainFindings,
|
|
66
|
+
envParityFindings,
|
|
67
|
+
envValueShapeFindings,
|
|
68
|
+
liteProofLaneFindings,
|
|
69
|
+
stacksSwarmFindings,
|
|
70
|
+
stacksReleaseFindings,
|
|
71
|
+
stacksIngressFindings,
|
|
72
|
+
stacksLiteBackingFindings,
|
|
73
|
+
stackToolingFindings,
|
|
74
|
+
stacksDeployFindings,
|
|
75
|
+
// finders-end
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
/** Findings of every alpha.15 content rule over one repository. */
|
|
79
|
+
export function a15Findings(context) {
|
|
80
|
+
// `starci app check` always passes the full context; a partial one (a finder spec driving the registry with only the fields its rule reads) must not crash the finders of other rules.
|
|
81
|
+
const full = Boolean(context.repoRoot && context.repo && context.resolver && context.manifest);
|
|
82
|
+
return FINDERS.flatMap((finder) => {
|
|
83
|
+
if (full) return finder(context);
|
|
84
|
+
try { return finder(context); } catch { return []; }
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// A02: include every deployment and supply-chain finding in the bundled app-check catalog.
|