@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
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { machineKit } from './machine-ast.mjs';
|
|
2
|
-
import { callName, chainParts, contextTypesClient, isDatabaseType, isSupabaseCall, moduleNameOf, reportAt, supabaseClientType, SUPABASE_MODULES } from './supabase-ast.mjs';
|
|
2
|
+
import { callName, chainParts, contextTypesClient, isDatabaseType, isSupabaseCall, jwtPolicyProblems, moduleNameOf, reportAt, supabaseClientType, supabaseDataAccess, SUPABASE_MODULES } from './supabase-ast.mjs';
|
|
3
3
|
import { checkBackendJwt } from './supabase-be.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* L10-L16, the slot-driven Supabase machine. On the back end Supabase
|
|
6
|
+
* L10-L16, the slot-driven Supabase machine. On the back end Supabase provides Auth/Storage with one owner,
|
|
7
7
|
* `be.integrations.supabase`. On the front end Supabase is auth only (owner ruling 2026-10-13): `fe.modules.auth` is the one
|
|
8
8
|
* importer of `@supabase/*` and calls only the auth API; data, storage, rpc and realtime go through the back end (modules/api).
|
|
9
9
|
* The checks below recognise provider calls and types by their imported package declarations, not by local variable or type names.
|
|
10
10
|
*/
|
|
11
11
|
export const FRONTEND_SUPABASE_RULE_IDS = Object.freeze(['FE_SUPABASE_CLIENT_OWNER', 'FE_SERVICE_ROLE_FORBIDDEN']);
|
|
12
|
-
export const BACKEND_SUPABASE_RULE_IDS = Object.freeze(['BE_SUPABASE_CLIENT_OWNER', 'BE_SUPABASE_JWT_VERIFIED']);
|
|
12
|
+
export const BACKEND_SUPABASE_RULE_IDS = Object.freeze(['BE_SUPABASE_CLIENT_OWNER', 'BE_SUPABASE_JWT_VERIFIED', 'BE_JWT_ALG_PINNED']);
|
|
13
13
|
|
|
14
14
|
const FE_CLIENT_OWNER = 'FE_SUPABASE_CLIENT_OWNER';
|
|
15
15
|
const BE_CLIENT_OWNER = 'BE_SUPABASE_CLIENT_OWNER';
|
|
@@ -19,7 +19,7 @@ const FE_AUTH_SLOT = 'fe.modules.auth';
|
|
|
19
19
|
/** The front-end provider slot: enabled exactly when a connection declares provider supabase. */
|
|
20
20
|
const FE_SUPABASE_PROVIDER_SLOT = 'fe.route.callback';
|
|
21
21
|
const FE_CONFIG_SLOT = 'fe.modules.config';
|
|
22
|
-
const
|
|
22
|
+
const BE_DATA_MEMBERS = new Set(['from', 'rpc', 'schema', 'functions', 'channel', 'realtime', 'getChannels', 'removeChannel', 'removeAllChannels']);
|
|
23
23
|
/** The members of a Supabase client that read or write data, files or channels: never called from the front end. */
|
|
24
24
|
const FE_DATA_MEMBERS = new Set(['from', 'rpc', 'schema', 'storage', 'functions', 'realtime', 'channel', 'getChannels', 'removeChannel', 'removeAllChannels']);
|
|
25
25
|
const supabaseSlotEnabled = (input, slotId) => {
|
|
@@ -55,12 +55,21 @@ function inspectClientFactoryCall(scope, node) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function inspectProviderCall(scope, node) {
|
|
58
|
-
const { kit, checker, file, slots, ruleId, violations, counters } = scope;
|
|
58
|
+
const { kit, checker, file, slots, ruleId, violations, counters, reportedData } = scope;
|
|
59
59
|
const { ts } = kit;
|
|
60
|
+
const data = supabaseDataAccess(kit, checker, node, BE_DATA_MEMBERS, { storageAllowed: true });
|
|
61
|
+
if (data) {
|
|
62
|
+
if (!reportedData.has(data.node.pos)) {
|
|
63
|
+
reportedData.add(data.node.pos);
|
|
64
|
+
reportAt(violations, kit, ruleId, file, data.node, `A Supabase .${data.name} data call is made from ${file.slot ?? 'an unowned path'}; the back-end SDK is Auth/Storage only, including inside ${BE_SUPABASE_SLOT}. Data uses EntityManager as app_be.`, { member: data.name, slot: file.slot ?? null });
|
|
65
|
+
}
|
|
66
|
+
if (ts.isCallExpression(node)) counters.calls += 1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
60
69
|
if (!isSupabaseCall(kit, checker, node)) return;
|
|
61
70
|
const properties = chainParts(ts, node);
|
|
62
71
|
const method = callName(ts, node);
|
|
63
|
-
if (!
|
|
72
|
+
if (!properties.includes('auth') && !properties.includes('storage')) return;
|
|
64
73
|
counters.calls += 1;
|
|
65
74
|
if (!slots.has(file.slot)) reportAt(violations, kit, ruleId, file, node, `A Supabase ${method ?? 'client'} call is made from ${file.slot ?? 'an unowned path'}; database, auth and storage calls belong only to ${[...slots].join(' or ')}.`, { method, slot: file.slot ?? null });
|
|
66
75
|
}
|
|
@@ -68,8 +77,9 @@ function inspectProviderCall(scope, node) {
|
|
|
68
77
|
function inspectOwnershipNode(scope, node) {
|
|
69
78
|
inspectSupabaseImport(scope, node);
|
|
70
79
|
inspectClientTypeReference(scope, node);
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
const { ts } = scope.kit;
|
|
81
|
+
if (!ts.isCallExpression(node) && !ts.isPropertyAccessExpression(node) && !ts.isElementAccessExpression(node)) return;
|
|
82
|
+
if (ts.isCallExpression(node)) inspectClientFactoryCall(scope, node);
|
|
73
83
|
inspectProviderCall(scope, node);
|
|
74
84
|
}
|
|
75
85
|
|
|
@@ -79,7 +89,7 @@ function checkClientOwnership(input, { slots, ruleId }) {
|
|
|
79
89
|
const counters = { imports: 0, clients: 0, calls: 0 };
|
|
80
90
|
for (const file of input.graph.files.values()) {
|
|
81
91
|
const checker = kit.checkerOf(file.sourceFile);
|
|
82
|
-
const scope = { kit, checker, file, slots, ruleId, violations, counters };
|
|
92
|
+
const scope = { kit, checker, file, slots, ruleId, violations, counters, reportedData: new Set() };
|
|
83
93
|
kit.walk(file.sourceFile, node => inspectOwnershipNode(scope, node));
|
|
84
94
|
}
|
|
85
95
|
return { violations, coverage: { status: 'checked', ...counters, ownerSlots: [...slots] } };
|
|
@@ -138,7 +148,7 @@ function checkFrontendSecretNames(input) {
|
|
|
138
148
|
}
|
|
139
149
|
|
|
140
150
|
function inspectFrontendNode(scope, node) {
|
|
141
|
-
const { kit, checker, file, violations, counters } = scope;
|
|
151
|
+
const { kit, checker, file, violations, counters, reportedData } = scope;
|
|
142
152
|
const { ts } = kit;
|
|
143
153
|
const moduleName = moduleNameOf(ts, node);
|
|
144
154
|
if (moduleName?.startsWith('@supabase/')) {
|
|
@@ -146,14 +156,19 @@ function inspectFrontendNode(scope, node) {
|
|
|
146
156
|
if (file.slot !== FE_AUTH_SLOT) reportAt(violations, kit, FE_CLIENT_OWNER, file, node, `${moduleName} is imported by ${file.rel}, whose slot is ${file.slot ?? 'unowned'}; on the front end Supabase is auth only and ${FE_AUTH_SLOT} (modules/auth) is its one importer.`, { module: moduleName, slot: file.slot ?? null });
|
|
147
157
|
return;
|
|
148
158
|
}
|
|
149
|
-
if (!ts.isCallExpression(node)
|
|
159
|
+
if (!ts.isCallExpression(node) && !ts.isPropertyAccessExpression(node) && !ts.isElementAccessExpression(node)) return;
|
|
160
|
+
const data = supabaseDataAccess(kit, checker, node, FE_DATA_MEMBERS);
|
|
161
|
+
if (data) {
|
|
162
|
+
if (!reportedData.has(data.node.pos)) {
|
|
163
|
+
reportedData.add(data.node.pos);
|
|
164
|
+
reportAt(violations, kit, FE_CLIENT_OWNER, file, data.node, `A Supabase .${data.name} call is made from the front end; Supabase on the front end is auth only - read and write data, files and realtime through the back end (a modules/api GraphQL operation, an SWR hook, hooks/socketio).`, { member: data.name, slot: file.slot ?? null });
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (!isSupabaseCall(kit, checker, node)) return;
|
|
150
169
|
counters.calls += 1;
|
|
151
|
-
const member = callName(ts, node);
|
|
152
170
|
const parts = chainParts(ts, node);
|
|
153
|
-
|
|
154
|
-
if (FE_DATA_MEMBERS.has(member)) data = parts.find(name => FE_DATA_MEMBERS.has(name)) ?? member;
|
|
155
|
-
if (data) reportAt(violations, kit, FE_CLIENT_OWNER, file, node, `A Supabase .${data} call is made from the front end; Supabase on the front end is auth only - read and write data, files and realtime through the back end (a modules/api GraphQL operation, an SWR hook, hooks/socketio).`, { member: data, slot: file.slot ?? null });
|
|
156
|
-
else if (file.slot !== FE_AUTH_SLOT && parts.includes('auth')) reportAt(violations, kit, FE_CLIENT_OWNER, file, node, `A Supabase auth call is made from ${file.slot ?? 'an unowned path'}; the auth client is called only inside ${FE_AUTH_SLOT}, which exports the session and the access token.`, { slot: file.slot ?? null });
|
|
171
|
+
if (file.slot !== FE_AUTH_SLOT && parts.includes('auth')) reportAt(violations, kit, FE_CLIENT_OWNER, file, node, `A Supabase auth call is made from ${file.slot ?? 'an unowned path'}; the auth client is called only inside ${FE_AUTH_SLOT}, which exports the session and the access token.`, { slot: file.slot ?? null });
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
function checkFrontendClientOwner(input) {
|
|
@@ -161,15 +176,18 @@ function checkFrontendClientOwner(input) {
|
|
|
161
176
|
const violations = [];
|
|
162
177
|
const counters = { imports: 0, calls: 0 };
|
|
163
178
|
for (const file of input.graph.files.values()) {
|
|
164
|
-
const scope = { kit, checker: kit.checkerOf(file.sourceFile), file, violations, counters };
|
|
179
|
+
const scope = { kit, checker: kit.checkerOf(file.sourceFile), file, violations, counters, reportedData: new Set() };
|
|
165
180
|
kit.walk(file.sourceFile, node => inspectFrontendNode(scope, node));
|
|
166
181
|
}
|
|
167
182
|
return { violations, coverage: { status: 'checked', ...counters, ownerSlots: [FE_AUTH_SLOT] } };
|
|
168
183
|
}
|
|
169
184
|
|
|
170
185
|
export function checkFrontendSupabase(input) {
|
|
171
|
-
if (!supabaseSlotEnabled(input, FE_SUPABASE_PROVIDER_SLOT)) return { violations: [], coverage: { status: 'not-applicable', reason: 'no connection declares provider supabase' } };
|
|
172
186
|
const owner = checkFrontendClientOwner(input);
|
|
187
|
+
if (!supabaseSlotEnabled(input, FE_SUPABASE_PROVIDER_SLOT)) return {
|
|
188
|
+
violations: owner.violations,
|
|
189
|
+
coverage: { status: owner.coverage.imports || owner.coverage.calls || owner.violations.length ? 'checked' : 'not-applicable', owner: owner.coverage, reason: 'no connection declares provider supabase; SDK ownership is still checked' },
|
|
190
|
+
};
|
|
173
191
|
const secrets = checkFrontendSecretNames(input);
|
|
174
192
|
return {
|
|
175
193
|
violations: [...owner.violations, ...secrets.violations],
|
|
@@ -178,11 +196,31 @@ export function checkFrontendSupabase(input) {
|
|
|
178
196
|
}
|
|
179
197
|
|
|
180
198
|
export function checkBackendSupabase(input) {
|
|
181
|
-
if (!supabaseSlotEnabled(input, BE_SUPABASE_SLOT)) return { violations: [], coverage: { status: 'not-applicable', reason: 'the Supabase back-end integration slot is not enabled' } };
|
|
182
199
|
const owner = checkClientOwnership(input, { slots: new Set([BE_SUPABASE_SLOT]), ruleId: BE_CLIENT_OWNER });
|
|
200
|
+
if (!supabaseSlotEnabled(input, BE_SUPABASE_SLOT)) return {
|
|
201
|
+
violations: owner.violations,
|
|
202
|
+
coverage: { status: owner.coverage.imports || owner.coverage.calls || owner.violations.length ? 'checked' : 'not-applicable', owner: owner.coverage, reason: 'the Supabase integration slot is disabled; SDK ownership is still checked' },
|
|
203
|
+
};
|
|
183
204
|
const jwt = checkBackendJwt(input);
|
|
205
|
+
const policy = checkJwtPolicy(input);
|
|
184
206
|
return {
|
|
185
|
-
violations: [...owner.violations, ...jwt.violations],
|
|
186
|
-
coverage: { status: 'checked', owner: owner.coverage, jwt: jwt.coverage },
|
|
207
|
+
violations: [...owner.violations, ...jwt.violations, ...policy.violations],
|
|
208
|
+
coverage: { status: 'checked', owner: owner.coverage, jwt: jwt.coverage, jwtPolicy: policy.coverage },
|
|
187
209
|
};
|
|
188
210
|
}
|
|
211
|
+
|
|
212
|
+
export function checkJwtPolicy(input) {
|
|
213
|
+
const kit = machineKit(input);
|
|
214
|
+
const violations = [];
|
|
215
|
+
let verifyCalls = 0;
|
|
216
|
+
for (const file of input.graph.files.values()) {
|
|
217
|
+
const checker = kit.checkerOf(file.sourceFile);
|
|
218
|
+
kit.walk(file.sourceFile, node => {
|
|
219
|
+
if (!kit.ts.isCallExpression(node) || !kit.isImportOf(checker, node.expression, 'jwtVerify', 'jose')) return;
|
|
220
|
+
verifyCalls += 1;
|
|
221
|
+
const problems = jwtPolicyProblems(kit.ts, checker, node);
|
|
222
|
+
if (problems.length) reportAt(violations, kit, 'BE_JWT_ALG_PINNED', file, node, problems.join(' '));
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return { violations, coverage: { status: 'checked', verifyCalls } };
|
|
226
|
+
}
|
|
@@ -64,7 +64,8 @@ export const LINT_ENFORCERS = Object.freeze([
|
|
|
64
64
|
enforcer('public-contract-form', BE, ['BE_PUBLIC_CONTRACT_FORM'], 'A public signature is typed by a named contract, never inline or untyped.'),
|
|
65
65
|
enforcer('frontend-source-layout', FE, ['FE_SOURCE_LAYOUT_INVALID'], 'A front-end source file sits in a tier folder.'),
|
|
66
66
|
enforcer('component-purity', FE, ['FE_COMPONENT_WORLD_OWNERSHIP', 'FE_WORLD_OWNER_RENDER_BOUNDARY', 'FE_CONNECTED_BLOCK_RENDER_PAIR', 'FE_PURE_REACHES_DATA', 'FE_PURE_WORLD_HOOK', 'FE_PURE_WORLD_IMPORT'], 'A pure component reaches no data or world state; its connected entry owns them.'),
|
|
67
|
-
|
|
67
|
+
// Package-manifest contract findings stay with app check; only source import bypasses belong to lint.
|
|
68
|
+
enforcer('grammar-entry', FE, ['ARCH_GRAMMAR_EXPORT_BYPASS'], 'Vendor grammar is reached only through its owner entry.'),
|
|
68
69
|
enforcer('i18n-keys', FE, ['FE_I18N_KEYS'], 'Translation keys read by source and keys held by catalogs agree both ways.'),
|
|
69
70
|
// The per-path judgements of the slot manifest (scripts/hfs/path-findings.mjs), on a tracked TypeScript file.
|
|
70
71
|
enforcer('slot-undeclared', BOTH, ['HFS_SLOT_UNDECLARED', 'HFS_SLOT_AMBIGUOUS', 'HFS_SLOT_NOT_ENABLED'], 'A TypeScript file is owned by exactly one slot of the repository.', { origin: 'repo' }),
|
|
@@ -7,15 +7,22 @@ import { byCodeUnit } from '../../lib/list.mjs';
|
|
|
7
7
|
* is the one direction with its own code (BE_FEATURE_IMPORTS_FEATURE, R28), never BE_TIER_DIRECTION, and a feature of one trigger
|
|
8
8
|
* kind (slot field `trigger`: api, webhooks, realtime, saga, reactors, jobs, cli) importing a feature of another is
|
|
9
9
|
* BE_KIND_ISOLATION, whose fix is the event bus,
|
|
10
|
-
* an app never imports another app (FE_APP_ISOLATION), and a component layer imports only the layers after it;
|
|
10
|
+
* an app never imports another app (BE_SERVICE_ISOLATION / FE_APP_ISOLATION), and a component layer imports only the layers after it;
|
|
11
11
|
* 2. owner cycles: a strongly connected component of the owner graph, type-only imports included (ARCH_OWNER_CYCLE),
|
|
12
12
|
* reported with the cycle path and the import that closes each hop.
|
|
13
13
|
* Importing inside one owner is always allowed; the public-entry rule stays with owners.mjs (ARCH_OWNER_EXPORT_BYPASS).
|
|
14
14
|
*/
|
|
15
|
-
export const TIER_RULE_IDS = ['BE_TIER_DIRECTION', 'BE_FEATURE_IMPORTS_FEATURE', 'BE_KIND_ISOLATION', 'FE_TIER_DIRECTION', 'FE_APP_ISOLATION', 'ARCH_OWNER_CYCLE'];
|
|
15
|
+
export const TIER_RULE_IDS = ['BE_TIER_DIRECTION', 'BE_FEATURE_IMPORTS_FEATURE', 'BE_KIND_ISOLATION', 'BE_SERVICE_ISOLATION', 'FE_TIER_DIRECTION', 'FE_APP_ISOLATION', 'ARCH_OWNER_CYCLE'];
|
|
16
|
+
|
|
17
|
+
function tierDirectionText({ fromTier, toTier, mayImport, kindRestriction, fromKind, toKind }) {
|
|
18
|
+
if (!kindRestriction) return `a ${fromTier} may import only ${mayImport.join(', ') || 'nothing'}; it imports a ${toTier}`;
|
|
19
|
+
const source = fromKind ?? 'no folder kind';
|
|
20
|
+
const target = toKind ?? 'no folder kind';
|
|
21
|
+
return `a ${fromTier} may import ${toTier} owners only from kinds ${kindRestriction.from.join(', ')} to kinds ${kindRestriction.to.join(', ')}; it imports ${source} -> ${target}`;
|
|
22
|
+
}
|
|
16
23
|
|
|
17
24
|
const REASON_TEXT = {
|
|
18
|
-
tierDirection:
|
|
25
|
+
tierDirection: tierDirectionText,
|
|
19
26
|
layerOrder: ({ fromLayer, toLayer }) => `a ${fromLayer} component may import only the layers after it; it imports a ${toLayer}`,
|
|
20
27
|
crossApp: ({ from, to }) => `app ${from} imports app ${to}; apps never import each other, shared code is a packages/<pkg> slot`,
|
|
21
28
|
};
|
|
@@ -111,7 +118,7 @@ function reportReasonedTierViolation(graph, edge, verdict, tierRule, counts, vio
|
|
|
111
118
|
let ruleId = tierRule;
|
|
112
119
|
if (featureToFeature) ruleId = 'BE_FEATURE_IMPORTS_FEATURE';
|
|
113
120
|
if (crossKind) ruleId = 'BE_KIND_ISOLATION';
|
|
114
|
-
if (verdict.reason === 'crossApp') ruleId = 'FE_APP_ISOLATION';
|
|
121
|
+
if (verdict.reason === 'crossApp') ruleId = graph.profile === 'be' ? 'BE_SERVICE_ISOLATION' : 'FE_APP_ISOLATION';
|
|
115
122
|
const typeOnly = edge.runtime ? '' : ' (type-only imports count)';
|
|
116
123
|
const message = crossKind
|
|
117
124
|
? `${edge.from} -> ${edge.to}: a ${fromKind} feature imports a ${toKind} feature; no kind imports another, every cross-kind call is an event published with eventBus.publish(event, tx) from a domain service${typeOnly}.`
|
|
@@ -9,6 +9,7 @@ import { createTypeScriptProgram, readTypeScriptProject, resolveTypeScriptModule
|
|
|
9
9
|
import { readJsonFile } from '../../lib/json.mjs';
|
|
10
10
|
import { sourceLocation } from '../../lib/ts-ast.mjs';
|
|
11
11
|
import { locateDeclaration } from '../slots.mjs';
|
|
12
|
+
export { unwrapExpression } from '../../lib/ts-analysis.mjs';
|
|
12
13
|
|
|
13
14
|
const CODE_EXTENSIONS = /\.(?:[cm]?[jt]sx?)$/i;
|
|
14
15
|
const SCRIPT_TAIL = '[cm]?[jt]sx?';
|
|
@@ -399,14 +400,6 @@ export function reachableViolation(edges, firstEdge, forbidden, { follow = () =>
|
|
|
399
400
|
/** A framework identity the checkers cannot prove statically. */
|
|
400
401
|
export const UNPROVEN_FRAMEWORK = '(unproven framework identity)';
|
|
401
402
|
|
|
402
|
-
/** The expression under parentheses, type assertions, non-null and satisfies wrappers. */
|
|
403
|
-
export function unwrapExpression(ts, expression) {
|
|
404
|
-
while (expression && (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression)
|
|
405
|
-
|| ts.isTypeAssertionExpression(expression) || ts.isNonNullExpression(expression)
|
|
406
|
-
|| (ts.isSatisfiesExpression?.(expression) ?? false))) expression = expression.expression;
|
|
407
|
-
return expression;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
403
|
/* ---- the AST walks every architecture checker shares ---- */
|
|
411
404
|
|
|
412
405
|
/** The names of `expected` an import or re-export statement binds (all of them for a namespace or star form). */
|
|
@@ -4,6 +4,8 @@ import { machineKit } from './machine-ast.mjs';
|
|
|
4
4
|
import { constructorDependencies } from './constructor-deps.mjs';
|
|
5
5
|
import { treeOf } from './required-files.mjs';
|
|
6
6
|
import { byCodeUnit } from '../../lib/list.mjs';
|
|
7
|
+
import { importsOf } from '../../lib/ts-analysis.mjs';
|
|
8
|
+
import { checkFunctionSubject, functionSubjectExports, keyOf as literalKey, valueObjectSubjects } from './function-spec-providers.mjs';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* R48 `unit-spec-providers` (BE_SPEC_QUALITY), the unit test standard's rule 3: the unit spec of a service builds its
|
|
@@ -14,7 +16,10 @@ import { byCodeUnit } from '../../lib/list.mjs';
|
|
|
14
16
|
* outside the production program, so it is read from disk). A parameter decorated with a custom `Inject<Thing>()` provides
|
|
15
17
|
* the token that decorator's `injector<T>(TOKEN)` names; an undecorated parameter of a class type provides that class (see
|
|
16
18
|
* constructor-deps.mjs). Providers are matched by the exported name of the imported binding (`import { CACHE as C }` is
|
|
17
|
-
* CACHE), never by text. A provider is a class, or `{ provide: TOKEN, useValue: double }` and nothing else.
|
|
19
|
+
* CACHE), never by text. A provider is a class, or `{ provide: TOKEN, useValue: double }` and nothing else. A sibling
|
|
20
|
+
* containing only exported functions instead provides its complete real function object and exercises it through module.get;
|
|
21
|
+
* the constants, enums, re-exports and overload signatures beside those functions do not count. A value-object subject (an
|
|
22
|
+
* undecorated class whose constructor parameters carry no injection token) is built with `new`, Nest never constructs it.
|
|
18
23
|
*
|
|
19
24
|
* Findings: an extra provider, a missing provider, a service that is not the first provider, a dependency whose token cannot
|
|
20
25
|
* be resolved, a spec with no createTestingModule providers array, and a provider that is neither a class nor
|
|
@@ -25,17 +30,6 @@ export const UNIT_SPEC_PROVIDERS_RULE_IDS = ['BE_SPEC_QUALITY'];
|
|
|
25
30
|
const RULE = 'BE_SPEC_QUALITY';
|
|
26
31
|
const SPEC_ROOT = /^(?:src|apps)\//u;
|
|
27
32
|
|
|
28
|
-
/** local name -> {name, module} for every named import of a source file. */
|
|
29
|
-
function importsOf(ts, sourceFile) {
|
|
30
|
-
const imports = new Map();
|
|
31
|
-
for (const statement of sourceFile.statements) {
|
|
32
|
-
const bindings = ts.isImportDeclaration(statement) ? statement.importClause?.namedBindings : null;
|
|
33
|
-
if (!bindings || !ts.isNamedImports(bindings)) continue;
|
|
34
|
-
for (const element of bindings.elements) imports.set(element.name.text, { name: (element.propertyName ?? element.name).text, module: statement.moduleSpecifier.text });
|
|
35
|
-
}
|
|
36
|
-
return imports;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
33
|
/** The `Test.createTestingModule(...)` calls of a spec. */
|
|
40
34
|
function testingModules(ts, sourceFile) {
|
|
41
35
|
const calls = [];
|
|
@@ -47,7 +41,6 @@ function testingModules(ts, sourceFile) {
|
|
|
47
41
|
return calls;
|
|
48
42
|
}
|
|
49
43
|
|
|
50
|
-
const literalKey = (ts, property) => (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property)) && (ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)) ? property.name.text : null;
|
|
51
44
|
|
|
52
45
|
function providerList(ts, call, rel, report) {
|
|
53
46
|
const options = call.arguments[0];
|
|
@@ -107,6 +100,8 @@ function checkDependencies(kit, rel, service, serviceFile, list, provided, repor
|
|
|
107
100
|
function checkTestingModule({ ts, kit, rel, serviceRel, serviceFile, imports, call, report }) {
|
|
108
101
|
const list = providerList(ts, call, rel, report);
|
|
109
102
|
if (!list) return;
|
|
103
|
+
const names = functionSubjectExports(ts, serviceFile.sourceFile);
|
|
104
|
+
if (names) return checkFunctionSubject({ ts, rel, serviceRel, imports, call, list, names, report });
|
|
110
105
|
const nameOf = identifier => imports.get(identifier.text)?.name ?? identifier.text;
|
|
111
106
|
const { provided, readable } = readProviders(ts, list, rel, nameOf, report);
|
|
112
107
|
if (!readable) return;
|
|
@@ -125,6 +120,7 @@ function checkServiceSpec({ config, graph, kit, ts, rel, violations }) {
|
|
|
125
120
|
const serviceFile = graph.files.get(serviceRel);
|
|
126
121
|
if (!serviceFile) { report(sourceFile, `${rel} has no ${path.posix.basename(serviceRel)} beside it; a service spec sits next to the service it tests.`); return true; }
|
|
127
122
|
const modules = testingModules(ts, sourceFile);
|
|
123
|
+
if (!modules.length && valueObjectSubjects(ts, serviceFile.sourceFile)) return true;
|
|
128
124
|
if (!modules.length) { report(sourceFile, `${rel} never calls Test.createTestingModule({ providers: [...] }); build the service from a testing module holding exactly its constructor dependencies.`); return true; }
|
|
129
125
|
for (const call of modules) checkTestingModule({ ts, kit, rel, serviceRel, serviceFile, imports, call, report });
|
|
130
126
|
return true;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Common result shaping for the slot check and its optional proof reports.
|
|
2
|
+
import { loadRuleCatalog } from './slots.mjs';
|
|
3
|
+
|
|
4
|
+
/** Attach the localized catalog explanation to every finding. */
|
|
5
|
+
export function withWhy(findings, why) {
|
|
6
|
+
return findings.map((f) => ({ ...f, title: why[f.code].title, titleVi: why[f.code].titleVi, whyVi: why[f.code].whyVi, nextStepVi: why[f.code].nextStepVi }));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Count findings by severity and code without dropping report-only results. */
|
|
10
|
+
export function summarize(findings) {
|
|
11
|
+
const byCode = {};
|
|
12
|
+
for (const f of findings) {
|
|
13
|
+
byCode[f.code] ??= { level: f.level, count: 0 };
|
|
14
|
+
byCode[f.code].count += 1;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
error: findings.filter((f) => f.level === 'error').length,
|
|
18
|
+
warning: findings.filter((f) => f.level === 'warning').length,
|
|
19
|
+
info: findings.filter((f) => f.level === 'info').length,
|
|
20
|
+
byCode,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Keep findings judged in this edition; uncatalogued refusal codes always remain. */
|
|
25
|
+
export function judgedFindings(findings, edition, root) {
|
|
26
|
+
const catalog = loadRuleCatalog({ root });
|
|
27
|
+
return findings.filter((finding) => catalog.judgedIn(finding.code, edition));
|
|
28
|
+
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// HFS_SLOT_REQUIRED_MISSING a file or directory a required slot (or an instance of one) must contain
|
|
17
17
|
// HFS_MIN_INSTANCES fewer instances of a slot than minInstances
|
|
18
18
|
// HFS_PLAINTEXT_SECRET (R06, rules/secrets.mjs) a plaintext secret file or value in a tracked file; a `.enc` that is no sops envelope
|
|
19
|
-
// HFS_STACKS_SHAPE (R10, rules/stacks.mjs)
|
|
19
|
+
// HFS_STACKS_SHAPE (R10, rules/stacks.mjs) invalid stack shape, Swarm runtime policy, host crontab installation or lite backing duplication
|
|
20
20
|
// HFS_CI_MISSING_CANON (R13, rules/pipeline.mjs) CI without the pinned `starci app check`, pre-push without typecheck or lint
|
|
21
21
|
// HFS_DEP_VERSION_SKEW (R14, rules/deps.mjs) a dependency at two versions in the workspace, or a nested copy in the lockfile
|
|
22
22
|
// HFS_CONTRACT_SNAPSHOT_DRIFT (R23, rules/contract.mjs) an uncommitted back-end snapshot, or a front-end copy that differs from it
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// FE_STACK_FORBIDDEN (rules/fe-stack.mjs) a front-end manifest (the fe side's, or the app root's minus the back end's own libraries)
|
|
26
26
|
// declares a package knowledge/hfs/fe-stack.yaml forbids
|
|
27
27
|
// HFS_LITE_SECRET_CUSTODY (R220, rules/supabase-secrets.mjs) under edition lite: secret material or a forbidden custody path; the R06 scan itself runs for every edition
|
|
28
|
-
// HFS_EDITION_FORBIDDEN_PRESENT (R212, rules/edition.mjs) under edition lite: an e2e, contract, stack or browser script, or a
|
|
28
|
+
// HFS_EDITION_FORBIDDEN_PRESENT (R212, rules/edition.mjs) under edition lite: an e2e, contract, test-stack or browser script, or a
|
|
29
29
|
// dependency of self-hosted infrastructure (test containers, fault proxy, BullMQ, Kafka, Redis)
|
|
30
30
|
// DB_MIGRATION_SHAPE ... DB_TYPES_DRIFT (R213-R216, rules/database.mjs, async: passed in as extraFindings) the Supabase migrations, policies, definer functions, buckets, config.toml and generated types
|
|
31
31
|
// BE_SPEC_PLACEMENT (R102, rules/spec-placement.mjs) a spec or test file outside the four test layers, scripts/ and tools/ included
|
|
@@ -56,46 +56,47 @@
|
|
|
56
56
|
// findings fail the check.
|
|
57
57
|
import fs from 'node:fs';
|
|
58
58
|
import path from 'node:path';
|
|
59
|
+
import { appWiring, BOOT_PROOF_CODE } from './app-wiring.mjs';
|
|
60
|
+
import { coverageFrom as coverageFromReport } from './coverage-from.mjs';
|
|
59
61
|
import { skillRoot } from '../../engine/runtime-root.mjs';
|
|
60
62
|
import { ARCHITECTURE_RULE_IDS, checkArchitecture } from './architecture/index.mjs';
|
|
61
63
|
import { parseYaml } from '../../engine/yaml.mjs';
|
|
62
64
|
import { readCatalogOnce } from './catalog-once.mjs';
|
|
63
|
-
import { APP_SCOPE, HFS_DECLARATION_FILE, appRelativeMessages, HfsSlotsError, SIDES, createSlotResolver,
|
|
65
|
+
import { APP_SCOPE, HFS_DECLARATION_FILE, appRelativeMessages, HfsSlotsError, SIDES, createSlotResolver, loadSlotManifest, readRepoDeclaration, resolveRepoDeclaration } from './slots.mjs';
|
|
66
|
+
import { judgedFindings, summarize, withWhy } from './check-results.mjs';
|
|
64
67
|
import { RUNTIME_KIND } from './manifest-shape.mjs';
|
|
65
68
|
import { lsFiles } from '../api/git/ls-files.mjs';
|
|
66
69
|
import { diff as gitDiff } from '../api/git/diff.mjs';
|
|
67
70
|
import { mergeBase } from '../api/git/merge-base.mjs';
|
|
68
71
|
import { gitOutputOf } from '../lib/git.mjs';
|
|
69
72
|
import { posixPath } from '../lib/path-key.mjs';
|
|
70
|
-
import {
|
|
73
|
+
import { treeFindings } from './tree-findings.mjs';
|
|
71
74
|
import { contractFindings } from './rules/contract.mjs';
|
|
72
75
|
import { appRootFindings } from './rules/app-root.mjs';
|
|
73
76
|
import { frontendFindings } from './rules/frontend-tree.mjs';
|
|
74
77
|
import { lintSuppressionFindings } from './rules/lint-suppression.mjs';
|
|
75
78
|
import { repoLocalCheckFindings } from './rules/repo-local-checks.mjs';
|
|
76
79
|
import { supabaseSecretFindings } from './rules/supabase-secrets.mjs';
|
|
77
|
-
import {
|
|
80
|
+
import { untrackedSecretFindings } from './rules/secrets.mjs';
|
|
81
|
+
import { liteStarciworkFinding, pathFindings } from './path-findings.mjs';
|
|
78
82
|
import { onLintSurface } from './architecture/surface.mjs';
|
|
79
83
|
import { checkAppRoot, trackedTreeView } from './architecture/hfs.mjs';
|
|
80
84
|
import { testTopologyFindings } from './rules/test-topology.mjs';
|
|
81
85
|
import { feNoTestsFindings } from './rules/fe-no-tests.mjs';
|
|
82
86
|
import { editionFindings } from './rules/edition.mjs';
|
|
83
87
|
import { feStackFindings } from './rules/fe-stack.mjs';
|
|
88
|
+
import { A15_CODES, a15Findings } from './rules/a15-findings.mjs';
|
|
89
|
+
import { trackedStacksGateFindings } from './stacks-gate.mjs';
|
|
84
90
|
import { byCodeUnit } from '../lib/list.mjs';
|
|
85
91
|
import { pinFindings } from './pin-findings.mjs';
|
|
92
|
+
import { requiredFileApplies } from './slot-required.mjs';
|
|
93
|
+
import { REFUSAL_CODES } from './refusal-codes.mjs';
|
|
94
|
+
export { REFUSAL_CODES } from './refusal-codes.mjs';
|
|
86
95
|
// The database rules (R213-R216) read SQL through a WASM parser, so they are async: `starci app check` calls them beside the other emitters and passes the findings in as `extraFindings`.
|
|
87
96
|
export { checkDatabase } from './rules/database.mjs';
|
|
88
97
|
|
|
89
98
|
const CANON_PINS_FILE = 'knowledge/hfs/canon-pins.yaml';
|
|
90
99
|
export const FAILURE_CODES_FILE = 'modules/kernel/failure-codes.yaml';
|
|
91
|
-
/**
|
|
92
|
-
* The codes `starci app check` reports when it cannot judge (an unreadable repository, a refused declaration or manifest, a missing
|
|
93
|
-
* formatter): infrastructure refusals, never obligations, so no rule of knowledge/hfs/rules.yaml owns them.
|
|
94
|
-
*/
|
|
95
|
-
export const REFUSAL_CODES = Object.freeze([
|
|
96
|
-
'HFS_REPO_UNREADABLE',
|
|
97
|
-
'HFS_DECLARATION_INVALID', 'HFS_MANIFEST_MAJOR_MISMATCH', 'HFS_MANIFEST_INVALID', 'HFS_FORMAT_TOOL_MISSING', 'HFS_EDITION_INVALID',
|
|
98
|
-
]);
|
|
99
100
|
/** The codes this module emits that are not the slot loader's own: the why bundle of packages/hfs ships exactly these plus the loader's. */
|
|
100
101
|
export const CHECK_CODES = Object.freeze([
|
|
101
102
|
'HFS_SLOT_UNDECLARED', 'HFS_SLOT_NOT_ENABLED', 'HFS_SLOT_AMBIGUOUS', 'HFS_TRACKED_MUST_BE_IGNORED', 'HFS_FORBIDDEN_PRESENT',
|
|
@@ -103,10 +104,13 @@ export const CHECK_CODES = Object.freeze([
|
|
|
103
104
|
'HFS_MANAGED_FILE_DRIFT', 'HFS_TOOL_CONFIG_LOCAL', 'HFS_RULE_OFF_WITHOUT_REPLACEMENT', 'HFS_TS_STRICT',
|
|
104
105
|
'HFS_PLAINTEXT_SECRET', 'HFS_STACKS_SHAPE', 'HFS_CI_MISSING_CANON', 'HFS_DEP_VERSION_SKEW', 'HFS_CONTRACT_SNAPSHOT_DRIFT',
|
|
105
106
|
'BE_TEST_TOPOLOGY', 'BE_SPEC_PLACEMENT', 'HFS_REPO_LOCAL_CHECK', 'HFS_LINT_SUPPRESSION_FILE', 'HFS_PROOF_COMMAND_FILE_MISSING', 'HFS_PEER_INTEGRATION_MISSING', 'BE_INTEGRATION_SPEC_MISSING', 'FE_GRAPHQL_CONTRACT', 'FE_NO_TESTS', 'HFS_MONO_WORKSPACES', 'HFS_MONO_FE_WORKSPACE', 'HFS_MONO_NEST_PROJECTS', 'HFS_MONO_WORKSPACE_DEP', 'BE_CLI_REQUIRED', 'FE_WIRE_GENERATED', 'FE_I18N_PLACEMENT', 'FE_I18N_CATALOG', 'HFS_SERVICE_PLACEMENT', 'HFS_IMAGE_UNPINNED', 'HFS_SERVICE_STACK_DECLARATION', 'HFS_EVENT_CONTRACT', 'BE_ASYNC_SPEC_MISSING', 'BE_SAGA_STEP_COMPENSATION', 'BE_SAGA_STATE_VERSIONED', 'BE_SAGA_EVENT_CONTRACT', 'BE_SAGA_CONSUMER_DEDUPE', 'BE_EVENT_CLASS_CONTRACT', 'BE_PATTERN_SPEC_MISSING', 'BE_CONTRACT_BREAKING', 'BE_KIND_DECLARATION', 'BE_KIND_EMPTY', 'HFS_DOCKER_BUILD_CONTEXT', 'HFS_DOCKER_STAGES', 'HFS_DOCKER_ENTRY', 'HFS_DOCKER_BASE_PIN', 'HFS_DOCKER_SECRETS', 'BE_SAGA_E2E_MISSING',
|
|
106
|
-
'FE_STACK_FORBIDDEN', 'HFS_EDITION_FORBIDDEN_PRESENT', 'HFS_LITE_SECRET_CUSTODY',
|
|
107
|
+
'FE_STACK_FORBIDDEN', 'HFS_EDITION_FORBIDDEN_PRESENT', 'HFS_LITE_SECRET_CUSTODY', 'HFS_STARCIWORK_LITE_FORBIDDEN',
|
|
107
108
|
'DB_MIGRATION_SHAPE', 'DB_RLS_REQUIRED', 'DB_DYNAMIC_DDL', 'DB_POLICY_SHAPE', 'DB_DEFINER_SAFE', 'DB_STORAGE_POLICY', 'DB_CONFIG_POLICY', 'DB_TYPES_DRIFT',
|
|
109
|
+
'HFS_DOCKER_HEALTHCHECK_FAKE', 'DB_BE_ONLY_ACCESS', 'DB_REALTIME_UNLISTED', 'DB_APP_ROLE', 'DB_FORCE_RLS_DEFAULT',
|
|
110
|
+
'DB_POLICY_TAUTOLOGY', 'DB_VIEW_UNSAFE', 'DB_STORAGE_PREFIX', 'DB_DEFINER_REVOKE',
|
|
108
111
|
'HFS_GITIGNORE_BLOCK_DRIFT', 'HFS_SONAR_CONFIG', 'HFS_COVERAGE_SCOPE_DRIFT', 'HFS_FORMAT',
|
|
109
112
|
'HFS_EMPTY_DIR', 'HFS_GHOST_TREE', 'HFS_UNTRACKED_ROOT_ENTRY',
|
|
113
|
+
...A15_CODES,
|
|
110
114
|
...REFUSAL_CODES,
|
|
111
115
|
]);
|
|
112
116
|
/** Every code `starci app check` can report: its own and every code the architecture machine can emit (derived from the machine's rule id lists). */
|
|
@@ -170,52 +174,6 @@ const requiredOf = (slot, instance) => (slot.requires ?? []).map((entry) => {
|
|
|
170
174
|
|
|
171
175
|
const appOf = (p) => /^apps\/([^/]+)\//.exec(p)?.[1];
|
|
172
176
|
|
|
173
|
-
function withWhy(findings, why) {
|
|
174
|
-
return findings.map((f) => ({ ...f, title: why[f.code].title, titleVi: why[f.code].titleVi, whyVi: why[f.code].whyVi, nextStepVi: why[f.code].nextStepVi }));
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function summarize(findings) {
|
|
178
|
-
const byCode = {};
|
|
179
|
-
for (const f of findings) {
|
|
180
|
-
byCode[f.code] ??= { level: f.level, count: 0 };
|
|
181
|
-
byCode[f.code].count += 1;
|
|
182
|
-
}
|
|
183
|
-
return {
|
|
184
|
-
error: findings.filter((f) => f.level === 'error').length,
|
|
185
|
-
info: findings.filter((f) => f.level === 'info').length,
|
|
186
|
-
byCode,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* THE edition filter of findings: a finding whose code belongs, through the rule catalog, to a rule the app's edition does not
|
|
192
|
-
* judge (rules.yaml `editions`) is dropped. Codes outside the catalog (refusals, machine codes without a rule) stay. This is the
|
|
193
|
-
* only place the edition touches a finding; no check below it knows the edition.
|
|
194
|
-
*/
|
|
195
|
-
function judgedFindings(findings, edition, root) {
|
|
196
|
-
const catalog = loadRuleCatalog({ root });
|
|
197
|
-
return findings.filter((finding) => catalog.judgedIn(finding.code, edition));
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/** The tree findings of R03: empty directories, ghost siblings, untracked entries outside an ignored slot. */
|
|
201
|
-
function treeFindings({ repoRoot, resolver }) {
|
|
202
|
-
const inIgnoredSlot = (rel) => resolver.classifyPath(`${rel}/.probe`).tracking === 'ignored';
|
|
203
|
-
const findings = [];
|
|
204
|
-
const facts = treeFacts(readTree(repoRoot, { isIgnored: inIgnoredSlot }));
|
|
205
|
-
for (const { path: dir, below } of facts.empty) {
|
|
206
|
-
findings.push({ code: 'HFS_EMPTY_DIR', level: 'error', path: dir, below, message: `${dir} has no file below it` + (below ? ` (nor in its ${below} sub-director${below === 1 && 'y' || 'ies'})` : '') + '; git tracks no empty directory, so it is a leftover' });
|
|
207
|
-
}
|
|
208
|
-
for (const { path: dir, of, distance } of facts.ghosts) {
|
|
209
|
-
findings.push({ code: 'HFS_GHOST_TREE', level: 'error', path: dir, of, distance, message: `${dir} is empty and ${distance} edit${distance === 1 ? '' : 's'} from its sibling ${of}: a renamed or misspelt structure that was never removed` });
|
|
210
|
-
}
|
|
211
|
-
for (const entry of untrackedEntries(repoRoot)) {
|
|
212
|
-
const bare = entry.replace(/\/$/, '');
|
|
213
|
-
const ignored = entry.endsWith('/') ? inIgnoredSlot(bare) : resolver.classifyPath(bare).tracking === 'ignored';
|
|
214
|
-
if (!ignored) findings.push({ code: 'HFS_UNTRACKED_ROOT_ENTRY', level: 'error', path: bare, message: `${entry} is neither tracked nor git-ignored, and no ignored slot owns it` });
|
|
215
|
-
}
|
|
216
|
-
return findings;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
177
|
/** A side-relative path (or null) as an app-relative one. */
|
|
220
178
|
const onSide = (side, p) => (p ? path.posix.normalize(`${side}/${p}`) : p);
|
|
221
179
|
|
|
@@ -223,7 +181,7 @@ function appendRequiredPathFindings(findings, resolver, files, required, present
|
|
|
223
181
|
const missing = new Set();
|
|
224
182
|
const missingFile = (slot, p, via) => {
|
|
225
183
|
const key = `${slot}|${p}`;
|
|
226
|
-
if (missing.has(key) || present(p)) return;
|
|
184
|
+
if (missing.has(key) || present(p) || !requiredFileApplies(slot, p, files)) return;
|
|
227
185
|
missing.add(key);
|
|
228
186
|
const app = appOf(p);
|
|
229
187
|
const appNote = app ? ` (app ${app})` : '';
|
|
@@ -245,7 +203,7 @@ function minimumInstanceFindings(resolver, required, instances) {
|
|
|
245
203
|
return findings;
|
|
246
204
|
}
|
|
247
205
|
|
|
248
|
-
function contentRuleFindings({ repoRoot, repo, resolver, files, all, scoped, pins, inScope, isRoot, editionDeclaration, manifest }) {
|
|
206
|
+
function contentRuleFindings({ repoRoot, repo, resolver, files, all, scoped, pins, inScope, isRoot, appSide, editionDeclaration, manifest, phase }) {
|
|
249
207
|
const findings = [
|
|
250
208
|
...pinFindings({ repoRoot, files, profile: repo.profile, pins, only: scoped }),
|
|
251
209
|
...supabaseSecretFindings({ repoRoot, files: files.filter((file) => inScope(file)), resolver, repo }),
|
|
@@ -253,10 +211,12 @@ function contentRuleFindings({ repoRoot, repo, resolver, files, all, scoped, pin
|
|
|
253
211
|
...feStackFindings({ repoRoot, files: files.filter((file) => inScope(file)), repo, manifest }),
|
|
254
212
|
...repoLocalCheckFindings({ repoRoot, files }),
|
|
255
213
|
...lintSuppressionFindings({ repoRoot, files }),
|
|
214
|
+
...a15Findings({ repoRoot, files, all, repo, resolver, manifest, isRoot, appSide, inScope }),
|
|
256
215
|
];
|
|
216
|
+
findings.push(...trackedStacksGateFindings({ repoRoot, files, all, repo, resolver, manifest, isRoot, inScope, existingFindings: findings }));
|
|
257
217
|
if (isRoot) {
|
|
258
218
|
findings.push(
|
|
259
|
-
...appRootFindings({ repoRoot, files, all, repo, resolver, pins }),
|
|
219
|
+
...appRootFindings({ repoRoot, files, all, repo, resolver, pins, phase }),
|
|
260
220
|
...checkAppRoot({ root: repoRoot, resolver, tree: trackedTreeView(all) }).violations.map((item) => ({ code: item.ruleId, level: 'error', path: item.path, line: item.line, column: item.column, source: 'machine', message: `${item.path}: ${item.message}` })),
|
|
261
221
|
);
|
|
262
222
|
} else if (repo.profile === 'be') {
|
|
@@ -285,7 +245,7 @@ function sizeSoftFindings({ repoRoot, resolver, files, inScope }) {
|
|
|
285
245
|
* relative to it, exactly as the standalone repository root was). `files` are the scope's tracked paths; `all` (root only) every
|
|
286
246
|
* tracked path of the app, for the rules that read across it (dependency skew, proof commands).
|
|
287
247
|
*/
|
|
288
|
-
function scopeFindings({ repoRoot, root, repo, resolver, files, all = files, scoped, manifest, editionDeclaration = true }) {
|
|
248
|
+
function scopeFindings({ repoRoot, root, repo, resolver, files, all = files, scoped, manifest, appSide = false, editionDeclaration = true, phase }) {
|
|
289
249
|
const inScope = (file) => !scoped || scoped.has(file);
|
|
290
250
|
// A required directory of the root (be/, fe/) is present through the files below it, which are the sides' own.
|
|
291
251
|
const trackedSet = new Set(all);
|
|
@@ -299,7 +259,7 @@ function scopeFindings({ repoRoot, root, repo, resolver, files, all = files, sco
|
|
|
299
259
|
if (repo.kind === RUNTIME_KIND) return findings;
|
|
300
260
|
|
|
301
261
|
const pins = readPins(root);
|
|
302
|
-
findings.push(...contentRuleFindings({ repoRoot, repo, resolver, files, all, scoped, pins, inScope, isRoot, editionDeclaration, manifest }));
|
|
262
|
+
findings.push(...contentRuleFindings({ repoRoot, repo, resolver, files, all, scoped, pins, inScope, isRoot, appSide, editionDeclaration, manifest, phase }));
|
|
303
263
|
if (!isRoot) findings.push(...sizeSoftFindings({ repoRoot, resolver, files, inScope }));
|
|
304
264
|
return findings;
|
|
305
265
|
}
|
|
@@ -314,8 +274,11 @@ function scopeFindings({ repoRoot, root, repo, resolver, files, all = files, sco
|
|
|
314
274
|
* The root scope and each side run their own rules over their own files (hfs checks per side, the side folder as the old repository
|
|
315
275
|
* root, plus the root checks); every finding path is app-relative. Returns {ok, profile, apps, manifest, tracked, findings, counts};
|
|
316
276
|
* a missing or invalid hfs.json is one HFS_DECLARATION_INVALID / HFS_MANIFEST_MAJOR_MISMATCH error finding, never an exception.
|
|
277
|
+
* `phase: 'source'` is the runtime's no-install example-render pass: tracked obligations still run, while ignored generated
|
|
278
|
+
* artifact presence/freshness belongs to the default `workspace` phase after codegen. Ordinary app check/lint always use it.
|
|
317
279
|
*/
|
|
318
|
-
export function checkRepo({ repoRoot, root = skillRoot, declaration, files, only, extraFindings = [], tree = files === undefined, manifest = loadSlotManifest({ root }), surface = 'all' }) {
|
|
280
|
+
export function checkRepo({ repoRoot, root = skillRoot, declaration, files, only, extraFindings = [], tree = files === undefined, manifest = loadSlotManifest({ root }), surface = 'all', phase = 'workspace' }) {
|
|
281
|
+
if (!['source', 'workspace'].includes(phase)) throw new Error(`unknown HFS inspection phase ${phase}`);
|
|
319
282
|
const why = readWhy(root);
|
|
320
283
|
let repo;
|
|
321
284
|
try {
|
|
@@ -333,21 +296,23 @@ export function checkRepo({ repoRoot, root = skillRoot, declaration, files, only
|
|
|
333
296
|
const keep = (list, scopeRoot) => (surface === 'check' ? list.filter((finding) => !(finding.origin === 'repo' && onLintSurface(scopeRoot, finding))) : list);
|
|
334
297
|
if (repo.profile === APP_SCOPE) {
|
|
335
298
|
const own = tracked.filter((file) => resolver.sideOf(file) === null);
|
|
336
|
-
findings.push(...keep(scopeFindings({ repoRoot, root, repo, resolver, files: own, all: tracked, scoped, manifest }), repoRoot));
|
|
299
|
+
findings.push(...keep(scopeFindings({ repoRoot, root, repo, resolver, files: own, all: tracked, scoped, manifest, phase }), repoRoot));
|
|
337
300
|
for (const side of SIDES) {
|
|
338
301
|
const prefix = `${side}/`;
|
|
339
302
|
const sideRoot = path.join(repoRoot, side);
|
|
340
303
|
const sideFiles = tracked.filter((file) => file.startsWith(prefix)).map((file) => file.slice(prefix.length));
|
|
341
304
|
const sideScoped = scoped ? new Set([...scoped].filter((file) => file.startsWith(prefix)).map((file) => file.slice(prefix.length))) : null;
|
|
342
|
-
const sideFindings = scopeFindings({ repoRoot: sideRoot, root, repo: repo.sides[side], resolver: resolver.sides[side], files: sideFiles, scoped: sideScoped, manifest, editionDeclaration: false });
|
|
305
|
+
const sideFindings = scopeFindings({ repoRoot: sideRoot, root, repo: repo.sides[side], resolver: resolver.sides[side], files: sideFiles, scoped: sideScoped, manifest, appSide: true, editionDeclaration: false, phase });
|
|
343
306
|
const message = appRelativeMessages(side, sideRoot);
|
|
344
307
|
findings.push(...keep(sideFindings, sideRoot).map((finding) => ({ ...finding, side, path: onSide(side, finding.path), message: message(finding.message) })));
|
|
345
308
|
}
|
|
346
309
|
} else {
|
|
347
|
-
findings.push(...keep(scopeFindings({ repoRoot, root, repo, resolver, files: tracked, scoped, manifest }), repoRoot));
|
|
310
|
+
findings.push(...keep(scopeFindings({ repoRoot, root, repo, resolver, files: tracked, scoped, manifest, phase }), repoRoot));
|
|
348
311
|
}
|
|
349
312
|
findings.push(...extraFindings);
|
|
350
|
-
|
|
313
|
+
const untrackedSecrets = untrackedSecretFindings({ repoRoot, files: tracked, repo });
|
|
314
|
+
findings.push(...untrackedSecrets);
|
|
315
|
+
if (tree) findings.push(...treeFindings({ repoRoot, resolver, tracked, reportedUntracked: new Set(untrackedSecrets.map((finding) => finding.path)) }));
|
|
351
316
|
|
|
352
317
|
// The app-root tree check reports machine codes: their why is read with the check's own.
|
|
353
318
|
const judged = judgedFindings(findings, repo.edition ?? 'full', root);
|
|
@@ -395,7 +360,7 @@ function machineFindings(report) {
|
|
|
395
360
|
* templates or run the formatter (packages/hfs/sync), judged with checkRepo's own. `machine` is injectable for specs. A missing
|
|
396
361
|
* merge-base under `fast` is an Error, never a silent full pass. `repoRoot` may also be a side folder: that side alone is checked.
|
|
397
362
|
*/
|
|
398
|
-
export function checkRepository({ repoRoot, root = skillRoot, fast = false, base, extraFindings = [], manifest = loadSlotManifest({ root }), machine = checkArchitecture }) {
|
|
363
|
+
export function checkRepository({ repoRoot, root = skillRoot, fast = false, base, extraFindings = [], manifest = loadSlotManifest({ root }), machine = checkArchitecture, wiring = false, coverageFrom }) {
|
|
399
364
|
const changed = fast ? changedSince(repoRoot, base) : null;
|
|
400
365
|
const slotResult = checkRepo({ repoRoot, root, manifest, extraFindings, surface: 'check', ...(changed ? { only: changed.files, tree: false } : {}) });
|
|
401
366
|
if (slotResult.profile === null) return { ...slotResult, machine: { status: 'skipped', reason: 'hfs.json is not valid' } };
|
|
@@ -414,7 +379,7 @@ export function checkRepository({ repoRoot, root = skillRoot, fast = false, base
|
|
|
414
379
|
const findings = [...slotResult.findings, ...withWhy(judgedFindings(found, repo.edition ?? 'full', root), why)];
|
|
415
380
|
const counts = summarize(findings);
|
|
416
381
|
const ran = runs.filter((info) => info.status === 'ran');
|
|
417
|
-
|
|
382
|
+
const result = {
|
|
418
383
|
...slotResult,
|
|
419
384
|
ok: counts.error === 0,
|
|
420
385
|
findings,
|
|
@@ -424,6 +389,26 @@ export function checkRepository({ repoRoot, root = skillRoot, fast = false, base
|
|
|
424
389
|
: { status: 'skipped', reason: runs.map((info) => info.reason).join('; '), sides: runs, ...(changed ? { base: changed.base } : {}) },
|
|
425
390
|
...(changed ? { fast: { base: changed.base, changed: changed.files.length } } : {}),
|
|
426
391
|
};
|
|
392
|
+
return withCheckProofs(result, { repoRoot, root, manifest, wiring, coverageFrom });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Optional CLI proofs run over the whole tracked tree, including after a dry edition override. */
|
|
396
|
+
export function withCheckProofs(result, { repoRoot, root = skillRoot, manifest = loadSlotManifest({ root }), wiring = false, coverageFrom, edition } = {}) {
|
|
397
|
+
if ((!wiring && coverageFrom === undefined) || result.profile === null) return result;
|
|
398
|
+
const declared = readRepoDeclaration(manifest, repoRoot);
|
|
399
|
+
const repo = { ...declared, edition: result.editionOverride?.judged ?? edition ?? declared.edition };
|
|
400
|
+
if (repo.sides) repo.sides = Object.fromEntries(Object.entries(repo.sides).map(([side, declaration]) => [side, { ...declaration, edition: repo.edition }]));
|
|
401
|
+
const context = { repoRoot, repo, manifest, files: trackedFiles(repoRoot) };
|
|
402
|
+
const reports = {};
|
|
403
|
+
if (wiring) reports.wiring = appWiring(context);
|
|
404
|
+
if (coverageFrom !== undefined) reports.coverage = coverageFromReport({ ...context, coverageFrom });
|
|
405
|
+
const proofFindings = Object.values(reports).flatMap(report => report.findings);
|
|
406
|
+
const why = readWhy(root, [...new Set(proofFindings.map(finding => finding.code))]);
|
|
407
|
+
// The explicit whole-tree wiring proof supersedes the ordinary finder's scoped result.
|
|
408
|
+
const previous = result.findings.filter(finding => !wiring || finding.code !== BOOT_PROOF_CODE);
|
|
409
|
+
const findings = [...previous, ...withWhy(judgedFindings(proofFindings, repo.edition ?? 'full', root), why)];
|
|
410
|
+
const counts = summarize(findings);
|
|
411
|
+
return { ...result, ...reports, findings, counts, ok: counts.error === 0 };
|
|
427
412
|
}
|
|
428
413
|
|
|
429
414
|
/** The architecture machine over one scope (a side folder): `{ info, findings }`, finding paths app-relative. */
|
|
@@ -461,6 +446,12 @@ export function explainPath({ repoRoot, input, root = skillRoot, declaration, ma
|
|
|
461
446
|
const resolver = createSlotResolver(manifest, repo);
|
|
462
447
|
const why = readWhy(root);
|
|
463
448
|
const c = resolver.classifyPath(input);
|
|
449
|
+
const dir = /[\\/]$/.test(input) || (repoRoot && fs.existsSync(path.join(repoRoot, c.path)) && fs.statSync(path.join(repoRoot, c.path)).isDirectory());
|
|
450
|
+
const liteWork = liteStarciworkFinding(c.path, resolver, { dir });
|
|
451
|
+
if (liteWork) {
|
|
452
|
+
const entry = why[liteWork.code];
|
|
453
|
+
return { ...c, ...liteWork, status: 'forbidden', titleVi: entry.titleVi, whyVi: entry.whyVi };
|
|
454
|
+
}
|
|
464
455
|
if (c.status === 'no-slot') {
|
|
465
456
|
return { path: c.path, status: 'no-slot', code: 'HFS_SLOT_UNDECLARED', nearest: c.nearest, titleVi: why.HFS_SLOT_UNDECLARED.titleVi, whyVi: why.HFS_SLOT_UNDECLARED.whyVi };
|
|
466
457
|
}
|