@starci/hfs 4.0.8 → 4.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +81 -39
- package/emit/contracts.mjs +56 -9
- package/emit/db-types.mjs +169 -0
- package/emit/events.mjs +41 -0
- package/emit/operations-worker.mjs +1 -1
- package/emit/operations.mjs +2 -2
- package/emit/schema-worker.mjs +1 -1
- package/lint/bound-sys.cjs +50 -0
- package/lint/run.mjs +53 -19
- package/package.json +14 -9
- package/report/sonar.mjs +8 -8
- package/runtime/engine/config.mjs +494 -0
- package/runtime/engine/invalid-config.mjs +15 -0
- package/runtime/engine/orca-config.mjs +27 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +160 -29
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
- package/runtime/knowledge/hfs/rules.yaml +2059 -0
- package/runtime/knowledge/hfs/slots.yaml +912 -120
- package/runtime/knowledge/patterns/be/api.yaml +369 -0
- package/runtime/knowledge/patterns/be/cli.yaml +297 -0
- package/runtime/knowledge/patterns/be/domain.yaml +113 -0
- package/runtime/knowledge/patterns/be/event-bus.yaml +285 -0
- package/runtime/knowledge/patterns/be/jobs.yaml +261 -0
- package/runtime/knowledge/patterns/be/projections.yaml +178 -0
- package/runtime/knowledge/patterns/be/queues.yaml +236 -0
- package/runtime/knowledge/patterns/be/reactors.yaml +140 -0
- package/runtime/knowledge/patterns/be/realtime.yaml +222 -0
- package/runtime/knowledge/patterns/be/saga.yaml +248 -0
- package/runtime/knowledge/patterns/be/webhooks.yaml +221 -0
- package/runtime/knowledge/patterns/fe/folder.yaml +131 -25
- package/runtime/knowledge/sonar-gate.yaml +52 -18
- package/runtime/modules/kernel/failure-codes.yaml +571 -51
- package/runtime/modules/models/registry.yaml +591 -0
- package/runtime/modules/models/runtimes.yaml +701 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +51 -0
- package/runtime/scripts/api/fs/is-link-like.mjs +25 -0
- package/runtime/scripts/api/fs/lib.mjs +10 -2
- package/runtime/scripts/api/fs/safe-remove.mjs +12 -132
- package/runtime/scripts/api/git/config-get.mjs +10 -0
- package/runtime/scripts/api/git/diff.mjs +5 -0
- package/runtime/scripts/api/git/lib.mjs +16 -26
- package/runtime/scripts/api/git/log.mjs +5 -0
- package/runtime/scripts/api/git/ls-files.mjs +6 -0
- package/runtime/scripts/api/git/ls-tree.mjs +5 -0
- package/runtime/scripts/api/git/merge-base.mjs +9 -0
- package/runtime/scripts/api/git/remote.mjs +5 -0
- package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
- package/runtime/scripts/api/git/show.mjs +6 -0
- package/runtime/scripts/api/sops/decrypt.mjs +9 -5
- package/runtime/scripts/api/sops/lib.mjs +35 -0
- package/runtime/scripts/api/sops/seal.mjs +18 -0
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
- package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
- package/runtime/scripts/hfs/architecture/config.mjs +9 -6
- package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
- package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
- package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +4 -10
- package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
- package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +1 -1
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
- package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
- package/runtime/scripts/hfs/architecture/index.mjs +17 -4
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
- package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
- package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
- package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
- package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
- package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +17 -114
- package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
- package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
- package/runtime/scripts/hfs/architecture/source-names.mjs +27 -147
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +59 -18
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
- package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
- package/runtime/scripts/hfs/architecture/surface.mjs +9 -3
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
- package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
- package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
- package/runtime/scripts/hfs/architecture.mjs +4 -4
- package/runtime/scripts/hfs/check.mjs +75 -72
- package/runtime/scripts/hfs/coverage-scope.mjs +162 -0
- package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
- package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
- package/runtime/scripts/hfs/edition-slots.mjs +66 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +109 -32
- package/runtime/scripts/hfs/param-names.mjs +18 -0
- package/runtime/scripts/hfs/path-findings.mjs +1 -1
- package/runtime/scripts/hfs/repo-identity.mjs +9 -6
- package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
- package/runtime/scripts/hfs/rules/app-root.mjs +51 -0
- package/runtime/scripts/hfs/rules/cli.mjs +37 -0
- package/runtime/scripts/hfs/rules/contract-compat.mjs +71 -0
- package/runtime/scripts/hfs/rules/contract.mjs +10 -9
- package/runtime/scripts/hfs/rules/database-config.mjs +102 -0
- package/runtime/scripts/hfs/rules/database-constants.mjs +13 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +111 -0
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +233 -0
- package/runtime/scripts/hfs/rules/database-sql.mjs +336 -0
- package/runtime/scripts/hfs/rules/database.mjs +68 -0
- package/runtime/scripts/hfs/rules/deps.mjs +1 -1
- package/runtime/scripts/hfs/rules/docker.mjs +259 -0
- package/runtime/scripts/hfs/rules/edition.mjs +111 -0
- package/runtime/scripts/hfs/rules/event-bus.mjs +153 -0
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -1
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +9 -9
- package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -6
- package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +1 -1
- package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +3 -3
- package/runtime/scripts/hfs/rules/pipeline.mjs +11 -21
- package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -2
- package/runtime/scripts/hfs/rules/read.mjs +1 -1
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +2 -2
- package/runtime/scripts/hfs/rules/saga.mjs +170 -0
- package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
- package/runtime/scripts/hfs/rules/services.mjs +220 -0
- package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks.mjs +3 -3
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
- package/runtime/scripts/hfs/rules/test-topology.mjs +2 -2
- package/runtime/scripts/hfs/secret.mjs +175 -0
- package/runtime/scripts/hfs/slots.mjs +78 -108
- package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
- package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
- package/runtime/scripts/hfs/tree.mjs +5 -4
- package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
- package/runtime/scripts/hfs/view.mjs +3 -2
- package/runtime/scripts/lib/dockerfile.mjs +124 -0
- package/runtime/scripts/lib/env.mjs +10 -0
- package/runtime/scripts/lib/event-contract.mjs +103 -0
- package/runtime/scripts/lib/git.mjs +36 -0
- package/runtime/scripts/lib/graphql-contract.mjs +1 -1
- package/runtime/scripts/lib/i18n.mjs +86 -0
- package/runtime/scripts/lib/json.mjs +13 -3
- package/runtime/scripts/lib/language.mjs +24 -5
- package/runtime/scripts/lib/list.mjs +20 -0
- package/runtime/scripts/lib/package-at.mjs +37 -0
- package/runtime/scripts/lib/path-key.mjs +57 -2
- package/runtime/scripts/lib/read-text.mjs +34 -0
- package/runtime/scripts/lib/regex.mjs +4 -0
- package/runtime/scripts/lib/same-text.mjs +5 -0
- package/runtime/scripts/lib/secret-patterns.mjs +4 -3
- package/runtime/scripts/lib/sleep-sync.mjs +2 -1
- package/runtime/scripts/lib/sops-envelope.mjs +58 -0
- package/runtime/scripts/lib/stack-services.mjs +7 -31
- package/runtime/scripts/lib/ts-ast.mjs +35 -0
- package/runtime/scripts/lib/walk.mjs +4 -3
- package/scaffold/add-app.mjs +174 -0
- package/scaffold/add-cli-lite.mjs +257 -0
- package/scaffold/add-inbox-lite.mjs +314 -0
- package/scaffold/add-table.mjs +344 -0
- package/scaffold/add.mjs +299 -0
- package/scaffold/app.mjs +358 -69
- package/scaffold/edition-gate.mjs +25 -0
- package/scaffold/image.mjs +19 -0
- package/scaffold/lite-exports.mjs +186 -0
- package/scaffold/service.mjs +32 -26
- package/scaffold/supabase-ports.mjs +26 -0
- package/src/main.mjs +327 -0
- package/sync/cli.mjs +6 -9
- package/sync/format.mjs +1 -1
- package/sync/hygiene.mjs +10 -10
- package/sync/index.mjs +167 -104
- package/sync/managed.mjs +19 -11
- package/sync/sonar-key.mjs +5 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +14 -14
- package/templates/app/ci-workflows/github/workflows/images.yml +37 -0
- package/templates/app/ci-workflows/sonar-steps.yml +9 -0
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +66 -0
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +31 -0
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +37 -0
- package/templates/app/codegen-lite/scripts/codegen.mjs +32 -0
- package/templates/app/docker-ignore/dockerignore +50 -0
- package/templates/app/gitignore-lite +35 -0
- package/templates/app/hooks/husky/pre-commit +3 -8
- package/templates/app/hooks/husky/pre-push +45 -6
- package/templates/app/package-scripts/core.json +8 -0
- package/templates/app/package-scripts/package.json +3 -10
- package/templates/app/package-scripts-lite/package.json +15 -0
- package/templates/app/quality-config/codecov.yml +15 -3
- package/templates/app/quality-config/sonar-base.properties +4 -0
- package/templates/app/quality-config/sonar-project.properties +1 -4
- package/templates/app/quality-config-lite/sonar-project.properties +5 -0
- package/templates/app/skeleton/.starcistacks/dev/seeds/primary-notes.sql +5 -0
- package/templates/app/skeleton/.starciwork/features/system-health/index.yaml +4 -0
- package/templates/app/skeleton/.starciwork/{features/index.yaml → index.yaml} +2 -2
- package/templates/app/skeleton/README.md +7 -5
- package/templates/app/skeleton-lite/.sops.yaml +3 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +5 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/secrets/.gitkeep +1 -0
- package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +55 -0
- package/templates/app/skeleton-lite/README.md +40 -0
- package/templates/app/skeleton-lite/supabase/config.toml +41 -0
- package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +56 -0
- package/templates/app/skeleton-lite/supabase/seed.sql +0 -0
- package/templates/app/task-graph/turbo.json +23 -0
- package/templates/app/tool-config-lite/prettierignore +2 -0
- package/templates/app/tool-config-lite/prettierrc +1 -0
- package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +38 -0
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +10 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +13 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +3 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +12 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +7 -0
- package/templates/be/common/main-listen.ts.partial +14 -0
- package/templates/be/common/sql-ident.ts.partial +7 -0
- package/templates/be/common/sql-tag.ts.partial +5 -0
- package/templates/be/image/api/Dockerfile +38 -0
- package/templates/be/image/cli/Dockerfile +38 -0
- package/templates/be/image/worker/Dockerfile +36 -0
- package/templates/be/inbox/database-client.ts.tpl +50 -0
- package/templates/be/inbox/index.ts.tpl +2 -0
- package/templates/be/inbox/migration.sql.tpl +29 -0
- package/templates/be/inbox/module-definition.ts.tpl +6 -0
- package/templates/be/inbox/module.ts.tpl +14 -0
- package/templates/be/inbox/options.ts.tpl +2 -0
- package/templates/be/inbox/service.ts.tpl +33 -0
- package/templates/be/inbox/sql.ts.tpl +16 -0
- package/templates/be/inbox/webhook-door.ts.tpl +36 -0
- package/templates/be/patterns/api/application.command.http.ts.tpl +10 -0
- package/templates/be/patterns/api/application.command.ts.tpl +10 -0
- package/templates/be/patterns/api/application.contracts.ts.tpl +11 -0
- package/templates/be/patterns/api/application.handler.http.ts.tpl +22 -0
- package/templates/be/patterns/api/application.handler.ts.tpl +22 -0
- package/templates/be/patterns/api/feature.index.http.ts.tpl +1 -0
- package/templates/be/patterns/api/feature.index.ts.tpl +1 -0
- package/templates/be/patterns/api/feature.module.ts.tpl +6 -0
- package/templates/be/patterns/api/graphql.input.ts.tpl +13 -0
- package/templates/be/patterns/api/graphql.mapper.ts.tpl +9 -0
- package/templates/be/patterns/api/graphql.module.ts.tpl +7 -0
- package/templates/be/patterns/api/graphql.resolver.ts.tpl +27 -0
- package/templates/be/patterns/api/graphql.type.ts.tpl +9 -0
- package/templates/be/patterns/api/http.controller.ts.tpl +27 -0
- package/templates/be/patterns/api/http.mapper.ts.tpl +9 -0
- package/templates/be/patterns/api/http.module.ts.tpl +7 -0
- package/templates/be/patterns/api/http.request.ts.tpl +10 -0
- package/templates/be/patterns/api/http.response.ts.tpl +5 -0
- package/templates/be/patterns/cli/group.cli.spec.ts.tpl +15 -0
- package/templates/be/patterns/cli/group.cli.ts.tpl +11 -0
- package/templates/be/patterns/cli/group.module.ts.tpl +7 -0
- package/templates/be/patterns/cli/sub.run.cli.spec.ts.tpl +17 -0
- package/templates/be/patterns/cli/sub.run.cli.ts.tpl +15 -0
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +22 -0
- package/templates/be/patterns/event-bus/platform/event-bus.config.ts.tpl +19 -0
- package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +58 -0
- package/templates/be/patterns/event-bus/platform/event-bus.decorators.ts.tpl +46 -0
- package/templates/be/patterns/event-bus/platform/event-bus.log-events.ts.tpl +12 -0
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +10 -0
- package/templates/be/patterns/event-bus/platform/event-bus.module.ts.tpl +52 -0
- package/templates/be/patterns/event-bus/platform/event-bus.options.ts.tpl +19 -0
- package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +28 -0
- package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +165 -0
- package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +93 -0
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +90 -0
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +58 -0
- package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +136 -0
- package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +56 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +272 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +128 -0
- package/templates/be/patterns/event-bus/platform/event-transport.port.ts.tpl +180 -0
- package/templates/be/patterns/event-bus/platform/event.policy.spec.ts.tpl +67 -0
- package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +38 -0
- package/templates/be/patterns/event-bus/platform/index.ts.tpl +11 -0
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.spec.ts.tpl +348 -0
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +241 -0
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +15 -0
- package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +8 -0
- package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +37 -0
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +11 -0
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +12 -0
- package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +26 -0
- package/templates/be/patterns/jobs/job.index.ts.tpl +1 -0
- package/templates/be/patterns/jobs/job.module.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.processor.ts.tpl +20 -0
- package/templates/be/patterns/jobs/job.queue.module.ts.tpl +20 -0
- package/templates/be/patterns/jobs/job.step.ts.tpl +19 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +31 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.ts.tpl +13 -0
- package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +150 -0
- package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +100 -0
- package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +138 -0
- package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +69 -0
- package/templates/be/patterns/jobs/platform/jobs.config.ts.tpl +11 -0
- package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +62 -0
- package/templates/be/patterns/jobs/platform/jobs.decorators.ts.tpl +31 -0
- package/templates/be/patterns/jobs/platform/jobs.error.ts.tpl +22 -0
- package/templates/be/patterns/jobs/platform/jobs.index.ts.tpl +11 -0
- package/templates/be/patterns/jobs/platform/jobs.log-events.ts.tpl +9 -0
- package/templates/be/patterns/jobs/platform/jobs.messages.ts.tpl +15 -0
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +10 -0
- package/templates/be/patterns/jobs/platform/jobs.module.ts.tpl +32 -0
- package/templates/be/patterns/jobs/platform/jobs.options.ts.tpl +11 -0
- package/templates/be/patterns/jobs/platform/jobs.port.ts.tpl +36 -0
- package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +8 -0
- package/templates/be/patterns/jobs/platform/persistence.job.entity.ts.tpl +53 -0
- package/templates/be/patterns/jobs/platform/persistence.jobs.rows.ts.tpl +13 -0
- package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +33 -0
- package/templates/be/patterns/jobs/platform/persistence.migration.ts.tpl +29 -0
- package/templates/be/patterns/outbox/platform/index.ts.tpl +2 -0
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +96 -0
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +78 -0
- package/templates/be/patterns/outbox/platform/outbox.log-events.ts.tpl +5 -0
- package/templates/be/patterns/projections/migration.ts.tpl +18 -0
- package/templates/be/patterns/projections/persistence.connection.ts.tpl +8 -0
- package/templates/be/patterns/projections/projection.entity.ts.tpl +9 -0
- package/templates/be/patterns/projections/projection.index.ts.tpl +3 -0
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +7 -0
- package/templates/be/patterns/projections/projection.module.ts.tpl +18 -0
- package/templates/be/patterns/projections/projection.options.ts.tpl +2 -0
- package/templates/be/patterns/projections/projection.projection.spec.ts.tpl +55 -0
- package/templates/be/patterns/projections/projection.projection.ts.tpl +21 -0
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +142 -0
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +81 -0
- package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +8 -0
- package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +25 -0
- package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +25 -0
- package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +9 -0
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +12 -0
- package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +48 -0
- package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +17 -0
- package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +128 -0
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +48 -0
- package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +115 -0
- package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +91 -0
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +45 -0
- package/templates/be/patterns/queues/platform/queue.config.ts.tpl +15 -0
- package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +31 -0
- package/templates/be/patterns/queues/platform/queue.decorators.ts.tpl +45 -0
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +7 -0
- package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +5 -0
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +10 -0
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +62 -0
- package/templates/be/patterns/queues/platform/queue.options.ts.tpl +22 -0
- package/templates/be/patterns/queues/platform/queue.policy.spec.ts.tpl +19 -0
- package/templates/be/patterns/queues/platform/queue.policy.ts.tpl +11 -0
- package/templates/be/patterns/queues/platform/queue.port.ts.tpl +14 -0
- package/templates/be/patterns/queues/queue.index.ts.tpl +3 -0
- package/templates/be/patterns/queues/queue.module.ts.tpl +6 -0
- package/templates/be/patterns/queues/queue.queue.ts.tpl +23 -0
- package/templates/be/patterns/reactors/application.command.ts.tpl +10 -0
- package/templates/be/patterns/reactors/application.contracts.ts.tpl +12 -0
- package/templates/be/patterns/reactors/application.handler.ts.tpl +22 -0
- package/templates/be/patterns/reactors/consumer.ts.tpl +19 -0
- package/templates/be/patterns/reactors/message.module.ts.tpl +20 -0
- package/templates/be/patterns/reactors/reactor.index.ts.tpl +1 -0
- package/templates/be/patterns/reactors/reactor.module.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.index.ts.tpl +1 -0
- package/templates/be/patterns/realtime/channel.input.ts.tpl +12 -0
- package/templates/be/patterns/realtime/channel.subscription.spec.ts.tpl +51 -0
- package/templates/be/patterns/realtime/channel.subscription.ts.tpl +32 -0
- package/templates/be/patterns/realtime/channel.type.ts.tpl +9 -0
- package/templates/be/patterns/saga/saga.compensate.command.ts.tpl +11 -0
- package/templates/be/patterns/saga/saga.compensate.contracts.ts.tpl +7 -0
- package/templates/be/patterns/saga/saga.compensate.handler.ts.tpl +22 -0
- package/templates/be/patterns/saga/saga.compensation.ts.tpl +21 -0
- package/templates/be/patterns/saga/saga.complete.command.ts.tpl +11 -0
- package/templates/be/patterns/saga/saga.complete.contracts.ts.tpl +7 -0
- package/templates/be/patterns/saga/saga.complete.handler.ts.tpl +22 -0
- package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +21 -0
- package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +21 -0
- package/templates/be/patterns/saga/saga.index.ts.tpl +1 -0
- package/templates/be/patterns/saga/saga.message.module.ts.tpl +23 -0
- package/templates/be/patterns/saga/saga.module.ts.tpl +20 -0
- package/templates/be/patterns/saga/saga.service.spec.ts.tpl +70 -0
- package/templates/be/patterns/saga/saga.service.ts.tpl +44 -0
- package/templates/be/patterns/saga/saga.state.ts.tpl +14 -0
- package/templates/be/patterns/saga/saga.step.ts.tpl +11 -0
- package/templates/be/patterns/saga/saga.undo.command.ts.tpl +10 -0
- package/templates/be/patterns/saga/saga.undo.contracts.ts.tpl +5 -0
- package/templates/be/patterns/saga/saga.undo.handler.ts.tpl +21 -0
- package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +72 -0
- package/templates/be/patterns/webhooks/webhook.door.ts.tpl +36 -0
- package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +6 -0
- package/templates/be/patterns/webhooks/webhook.index.ts.tpl +1 -0
- package/templates/be/patterns/webhooks/webhook.request.ts.tpl +9 -0
- package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
- package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -2
- package/templates/be/skeleton/apps/__app__/src/main.ts +7 -15
- package/templates/be/skeleton/apps/cli/src/app.module.ts +30 -0
- package/templates/be/skeleton/apps/cli/src/cli.options.ts +22 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +24 -0
- package/templates/be/skeleton/src/features/cli/cli.module.ts +7 -0
- package/templates/be/skeleton/src/features/cli/index.ts +1 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +17 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +15 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +7 -0
- package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.spec.ts +17 -0
- package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.ts +18 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +17 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +15 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +7 -0
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +33 -0
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +34 -0
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +13 -0
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +40 -0
- package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +1 -1
- package/templates/be/skeleton/src/modules/domain/note/errors/note.error.ts +16 -0
- package/templates/be/skeleton/src/modules/domain/note/index.ts +3 -0
- package/templates/be/skeleton/src/modules/domain/note/note.contracts.ts +19 -0
- package/templates/be/skeleton/src/modules/domain/note/note.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/domain/note/note.module.ts +19 -0
- package/templates/be/skeleton/src/modules/domain/note/note.options.ts +2 -0
- package/templates/be/skeleton/src/modules/domain/note/note.service.spec.ts +76 -0
- package/templates/be/skeleton/src/modules/domain/note/note.service.ts +37 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +8 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/entities/note.entity.ts +17 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/migrations/1790000000000-init-note.ts +22 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.rows.ts +17 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +20 -0
- package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +18 -0
- package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +41 -0
- package/templates/be/skeleton/src/modules/platform/database/connection-source.client.ts +17 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module.ts +77 -0
- package/templates/be/skeleton/src/modules/platform/database/database.options.ts +30 -0
- package/templates/be/skeleton/src/modules/platform/database/database.port.ts +65 -0
- package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/database/errors/database.error.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/database/index.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +30 -0
- package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.spec.ts +84 -0
- package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.ts +23 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.config.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.connection.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +10 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +108 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +75 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +88 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +29 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +48 -0
- package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +8 -1
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +8 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +14 -1
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +10 -2
- package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +63 -0
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +108 -0
- package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.spec.ts +39 -0
- package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.ts +25 -0
- package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +172 -0
- package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.ts +66 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +3 -1
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +52 -0
- package/templates/be/skeleton-lite/apps/api/Dockerfile +37 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +81 -0
- package/templates/be/skeleton-lite/apps/api/src/main.ts +34 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +14 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +50 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +26 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +19 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +26 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +7 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +19 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +10 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +8 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +17 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +32 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +54 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +23 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +10 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +49 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +21 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +11 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +28 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +25 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +9 -0
- package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +5 -0
- package/templates/be/table/error.ts.tpl +16 -0
- package/templates/be/table/index.ts.tpl +2 -0
- package/templates/be/table/migration.sql.tpl +51 -0
- package/templates/be/table/module-definition.ts.tpl +7 -0
- package/templates/be/table/module.ts.tpl +14 -0
- package/templates/be/table/options.ts.tpl +2 -0
- package/templates/be/table/rows.ts.tpl +4 -0
- package/templates/be/table/service.ts.tpl +23 -0
- package/templates/be/table/sql.ts.tpl +4 -0
- package/templates/be/tool-config/jest.config.js +3 -1
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +14 -0
- package/templates/be/upgrade-full/apps/cli/src/main.ts +26 -0
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +6 -0
- package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +23 -0
- package/templates/common/gitignore.base +1 -2
- package/templates/fe/app/package.json.tpl +1 -0
- package/templates/fe/app/tsconfig.json +12 -0
- package/templates/fe/common/app-layout.tsx.partial +8 -0
- package/templates/fe/common/site-shell.tsx.partial +9 -0
- package/templates/fe/image/next/Dockerfile +42 -0
- package/templates/fe/skeleton/apps/app/next.config.ts +23 -0
- package/templates/fe/skeleton/apps/app/postcss.config.mjs +7 -0
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/error.tsx +2 -2
- package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +22 -0
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/loading.tsx +2 -2
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/not-found.tsx +2 -2
- package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +9 -0
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/global-error.tsx +2 -2
- package/templates/fe/skeleton/apps/app/src/app/globals.css +10 -0
- package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +18 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/component.tsx +19 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/index.tsx +11 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppGlobalErrorPage/index.tsx +16 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/component.tsx +10 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +15 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +9 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +4 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +28 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -0
- package/templates/fe/skeleton/apps/app/src/modules/routes/index.ts +4 -0
- package/templates/fe/skeleton/apps/app/src/proxy.ts +1 -0
- package/templates/fe/skeleton/apps/{__app__ → landing}/next.config.ts +4 -2
- package/templates/fe/skeleton/apps/landing/postcss.config.mjs +7 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/error.tsx +10 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +22 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/loading.tsx +6 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/not-found.tsx +6 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +9 -0
- package/templates/fe/skeleton/apps/landing/src/app/global-error.tsx +10 -0
- package/templates/fe/skeleton/apps/landing/src/app/globals.css +10 -0
- package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +24 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/component.tsx +18 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/index.tsx +11 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingGlobalErrorPage/index.tsx +16 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +9 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/component.tsx +24 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +16 -0
- package/templates/fe/skeleton/apps/landing/src/modules/config/index.ts +12 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +7 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/vi.json +31 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -0
- package/templates/fe/skeleton/apps/landing/src/modules/routes/index.ts +4 -0
- package/templates/fe/skeleton/apps/landing/src/proxy.ts +1 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +49 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +24 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +20 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +23 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/ErrorNotice/index.tsx +14 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/GlobalErrorNotice/index.tsx +25 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/LoadingNotice/index.tsx +9 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +21 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +19 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/classNames.ts +4 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/index.tsx +19 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +6 -0
- package/templates/fe/skeleton-lite/apps/web/next.config.ts +22 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/error.tsx +10 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +37 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/loading.tsx +6 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/not-found.tsx +6 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/providers.tsx +26 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/auth/callback/route.ts +24 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/global-error.tsx +10 -0
- package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/app/globals.css +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/app/health/live/route.ts +5 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/component.tsx +29 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +41 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/component.tsx +17 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/index.tsx +38 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/ErrorNotice/index.tsx +13 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/GlobalErrorNotice/index.tsx +24 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/LoadingNotice/index.tsx +7 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +18 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/classNames.ts +4 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/index.tsx +13 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +31 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/component.tsx +12 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/index.tsx +10 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppGlobalErrorPage/index.tsx +15 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +14 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +30 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +20 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/brand/index.ts +2 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +20 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/read-session.ts +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-in.ts +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +17 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/browser.ts +14 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/index.ts +3 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/outcome.ts +59 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/principal.ts +29 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/server.ts +70 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +50 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +8 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +48 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +16 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +14 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +7 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/routes/safe-next-path.ts +26 -0
- package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +43 -0
- package/templates/fe/table/read.ts.tpl +16 -0
- package/templates/fe/table/write.ts.tpl +16 -0
- package/upgrade/check.mjs +137 -0
- package/upgrade/index.mjs +524 -0
- package/bin/hfs.mjs +0 -233
- package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
- package/runtime/scripts/lib/test-secrets.mjs +0 -116
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +0 -20
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +0 -9
- package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +0 -28
- package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +0 -35
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +0 -19
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -11
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -24
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -14
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -21
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +0 -15
- package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +0 -23
- package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +0 -8
- package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +0 -28
- package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +0 -8
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +0 -8
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +0 -2
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +0 -25
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/request.ts +0 -19
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +0 -9
- package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +0 -4
- package/templates/fe/skeleton/apps/__app__/src/proxy.ts +0 -10
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.contracts.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.handler.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.query.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/index.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/system-health.module.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/live.controller.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/system-health-http.module.ts +0 -0
- /package/templates/fe/skeleton/apps/{__app__ → app}/src/app/health/live/route.ts +0 -0
- /package/templates/fe/skeleton/apps/{__app__ → app}/src/modules/config/index.ts +0 -0
- /package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/postcss.config.mjs +0 -0
|
@@ -0,0 +1,2059 @@
|
|
|
1
|
+
# knowledge/hfs/rules.yaml - the machine-readable catalog of the HFS rules (the ids run R01 up; an id with no rule is listed under `retired` with its reason, never reused; count the rules, never type the count).
|
|
2
|
+
#
|
|
3
|
+
# One entry per rule: its id, its finding code, the law in one line, how a violation is fixed
|
|
4
|
+
# (`kinds`), the gates it runs at, the failure codes it reports, and every enforcer that judges it. The prose of each
|
|
5
|
+
# rule is knowledge/hfs/README.md (section 12 lists the same ids and codes); this file is the data behind it. Read it only
|
|
6
|
+
# through scripts/hfs/slots.mjs (loadRuleCatalog, rules); its shape is modules/schemas/hfs-rules.schema.yaml.
|
|
7
|
+
#
|
|
8
|
+
# An enforcer is a check that exists or one still owed. `status: planned` marks one owed: the architecture machine, the
|
|
9
|
+
# hfs CLI, stylelint and Sonar lanes fill them, and the entry's `status` is removed the day the check ships (the check
|
|
10
|
+
# refuses a planned enforcer that already exists). An existing eslint enforcer is a rule id of the plugin
|
|
11
|
+
# `@starci/eslint-canon-be` or `@starci/eslint-canon-fe`; an existing machine, hfs or work-validate enforcer names the file
|
|
12
|
+
# (`at`) that emits one of the rule's codes. scripts/checks/check-hfs-rules.mjs holds all of this to the packages, the
|
|
13
|
+
# scripts and modules/kernel/failure-codes.yaml.
|
|
14
|
+
#
|
|
15
|
+
# Every rule is an error from 2.0. There is no baseline, allowlist or suppression file for a product. The runtime gate
|
|
16
|
+
# (the StarCi runtime repository judged by knowledge/hfs/runtime-slots.yaml) has exactly one, the shrink-only sections of
|
|
17
|
+
# modules/kernel/allowlist.yaml, held by RT_ALLOWLIST_SPRAWL. Codes are quoted: the failure-code
|
|
18
|
+
# scanner reads this file as an emitter of every code it names.
|
|
19
|
+
schema: starci/hfs-rules@2
|
|
20
|
+
version: 2.0.0
|
|
21
|
+
gates:
|
|
22
|
+
pre-commit: the managed .husky/pre-commit at the app root - starci app hygiene (staged .starciwork and .starcistacks paths, secrets guard), typecheck, ESLint per side and stylelint on the staged files, prettier --check, the unit specs the staged files touch
|
|
23
|
+
pre-push: typecheck, lint (starci app lint), format:check and the unit specs affected since origin/main; runs every pre-commit rule again
|
|
24
|
+
settle: the op gate scripts/gates/gate.mjs over the op's changed files (merge guard, starci app lint --changed, codegen, tsc, the slice's specs; only findings new against the workflow's previous checkpoint block), re-read at starci kernel settle, which refuses a red done; a green settle commits the op's checkpoint on wf-<workflowId>
|
|
25
|
+
land: the workflow's finish, the only time main is touched - gate.mjs over the whole workflow branch against its merge-base with main, the merge guard, review.verify of the exact head that lands, the branch rebased onto main, then main fast-forwarded and pushed
|
|
26
|
+
ci: GitHub Actions runs the same pinned starci app lint, format:check, typecheck, unit with the per-file coverage threshold, build:be and build:fe, then the Sonar scan and gate
|
|
27
|
+
sonar: Sonar quality gate (knowledge/sonar-gate.yaml) - the imported lint findings, duplication, cognitive complexity and the coverage of the logic of be/src/modules at 100
|
|
28
|
+
runtime: "`starci runtime check` (npm run check) of the StarCi runtime repository itself: scripts/checks/check-runtime.mjs runs node --check, scripts/hfs/runtime-check.mjs with knowledge/hfs/runtime-slots.yaml, and the retained self-checks"
|
|
29
|
+
enforcerKinds:
|
|
30
|
+
eslint-be: a rule of the plugin @starci/eslint-canon-be (id without the plugin prefix)
|
|
31
|
+
eslint-fe: a rule of the plugin @starci/eslint-canon-fe (id without the plugin prefix)
|
|
32
|
+
stylelint: a rule of the @starci/stylelint-canon package
|
|
33
|
+
machine: a check of the architecture machine (scripts/hfs/architecture), which reads the import graph
|
|
34
|
+
hfs: a check of the hfs CLI that reads the repository tree, configuration, contracts or catalogs
|
|
35
|
+
work-validate: a check of scripts/work/validate/work-validate.mjs over a .starciwork tree
|
|
36
|
+
sonar: a condition of the Sonar quality gate
|
|
37
|
+
runtime: a rule module of the runtime check (scripts/hfs/runtime-check.mjs, scripts/hfs/runtime-rules/*) or an emitter whose findings it judges, over the runtime repository
|
|
38
|
+
rules:
|
|
39
|
+
- id: R01
|
|
40
|
+
code: "HFS_SLOT_UNDECLARED"
|
|
41
|
+
law: "Every tracked path matches exactly one slot."
|
|
42
|
+
kinds: [check, lint]
|
|
43
|
+
gates: [pre-push, settle, land, ci, runtime]
|
|
44
|
+
failureCodes: ["HFS_SLOT_UNDECLARED", "HFS_SLOT_AMBIGUOUS", "HFS_SLOT_NOT_ENABLED", "HFS_FORBIDDEN_PRESENT", "HFS_TRACKED_MUST_BE_IGNORED", "HFS_ROOT_ENTRY_FORBIDDEN", "HFS_ROOT_MARKDOWN_FORBIDDEN", "HFS_ROOT_SRC_FORBIDDEN_FE", "HFS_SRC_LAYOUT_INVALID", "HFS_MODULE_TIER_INVALID", "HFS_WORK_IN_FE", "HFS_STACKS_IN_SIDE"]
|
|
45
|
+
enforcers:
|
|
46
|
+
- {kind: hfs, id: slot-undeclared, at: scripts/hfs/check.mjs}
|
|
47
|
+
- {kind: eslint-be, id: slot-undeclared}
|
|
48
|
+
- {kind: eslint-fe, id: slot-undeclared}
|
|
49
|
+
- {kind: machine, id: repository-tree-layout, at: scripts/hfs/architecture/hfs.mjs}
|
|
50
|
+
- {kind: runtime, id: runtime-tree, at: scripts/hfs/runtime-check.mjs}
|
|
51
|
+
- id: R02
|
|
52
|
+
code: "HFS_SLOT_REQUIRED_MISSING"
|
|
53
|
+
law: "A slot is missing a required file (feature `index.ts`, app `main.ts`, FE `global-error.tsx`), or the root README breaks its standard shape (title, description, standard sections in order, Work pointer, live badges only, no private URL)."
|
|
54
|
+
kinds: [check, codemod]
|
|
55
|
+
gates: [settle, land, ci, runtime]
|
|
56
|
+
failureCodes: ["HFS_SLOT_REQUIRED_MISSING", "HFS_MIN_INSTANCES", "HFS_REQUIRED_FILE_MISSING", "HFS_ROOT_ENTRY_MISSING", "HFS_APPS_REQUIRED", "HFS_APP_LAYOUT_INVALID", "HFS_README_TITLE_INVALID", "HFS_README_DESCRIPTION_INVALID", "HFS_README_SECTION_MISSING", "HFS_README_SECTION_ORDER", "HFS_README_DEVELOPMENT_INCOMPLETE", "HFS_README_WORK_POINTER_MISSING", "HFS_README_PRIVATE_URL", "HFS_README_BADGE_NOT_LIVE"]
|
|
57
|
+
enforcers:
|
|
58
|
+
- {kind: hfs, id: slot-required-missing, at: scripts/hfs/check.mjs}
|
|
59
|
+
- {kind: machine, id: required-files, at: scripts/hfs/architecture/required-files.mjs}
|
|
60
|
+
- {kind: machine, id: repository-tree-required, at: scripts/hfs/architecture/hfs.mjs}
|
|
61
|
+
- {kind: runtime, id: runtime-required, at: scripts/hfs/runtime-check.mjs}
|
|
62
|
+
- id: R03
|
|
63
|
+
code: "HFS_UNTRACKED_ROOT_ENTRY"
|
|
64
|
+
law: "No untracked or ignored entry outside an `ignored` slot."
|
|
65
|
+
kinds: [check, codemod]
|
|
66
|
+
gates: [pre-push, land, ci, runtime]
|
|
67
|
+
failureCodes: ["HFS_UNTRACKED_ROOT_ENTRY", "HFS_EMPTY_DIR", "HFS_GHOST_TREE"]
|
|
68
|
+
enforcers:
|
|
69
|
+
- {kind: hfs, id: untracked-root-entry, at: scripts/hfs/check.mjs}
|
|
70
|
+
- {kind: runtime, id: runtime-untracked, at: scripts/hfs/runtime-check.mjs}
|
|
71
|
+
- id: R04
|
|
72
|
+
code: "HFS_GITIGNORE_BLOCK_DRIFT"
|
|
73
|
+
law: "The managed `.gitignore` block equals its render."
|
|
74
|
+
kinds: [check, codemod]
|
|
75
|
+
gates: [pre-push, land, ci]
|
|
76
|
+
failureCodes: ["HFS_GITIGNORE_BLOCK_DRIFT"]
|
|
77
|
+
enforcers:
|
|
78
|
+
- {kind: hfs, id: gitignore-block-drift, at: packages/hfs/sync/managed.mjs}
|
|
79
|
+
- id: R05
|
|
80
|
+
code: "HFS_MANAGED_FILE_DRIFT"
|
|
81
|
+
law: "A managed file equals its template render."
|
|
82
|
+
kinds: [check, codemod]
|
|
83
|
+
gates: [pre-push, land, ci]
|
|
84
|
+
failureCodes: ["HFS_MANAGED_FILE_DRIFT"]
|
|
85
|
+
enforcers:
|
|
86
|
+
- {kind: hfs, id: managed-file-drift, at: packages/hfs/sync/managed.mjs}
|
|
87
|
+
- id: R06
|
|
88
|
+
code: "HFS_PLAINTEXT_SECRET"
|
|
89
|
+
law: "Secrets exist only as `.starcistacks/<env>/secrets/<slug>.enc`."
|
|
90
|
+
kinds: [check, design]
|
|
91
|
+
gates: [pre-commit, pre-push, land, ci]
|
|
92
|
+
failureCodes: ["HFS_PLAINTEXT_SECRET"]
|
|
93
|
+
enforcers:
|
|
94
|
+
- {kind: hfs, id: plaintext-secret, at: scripts/hfs/rules/secrets.mjs}
|
|
95
|
+
- {kind: hfs, id: plaintext-secret-staged, at: packages/hfs/sync/hygiene.mjs}
|
|
96
|
+
- id: R07
|
|
97
|
+
code: "HFS_AGENT_DATA_TRACKED"
|
|
98
|
+
law: "`.starciwork` holds product records only."
|
|
99
|
+
kinds: [check, codemod]
|
|
100
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
101
|
+
failureCodes: ["HFS_AGENT_DATA_TRACKED"]
|
|
102
|
+
enforcers:
|
|
103
|
+
- {kind: work-validate, id: agent-data-tracked, at: scripts/work/validate/check-example-work.mjs}
|
|
104
|
+
- id: R08
|
|
105
|
+
code: "HFS_WORK_NODE_RETIRED"
|
|
106
|
+
law: "Only flat family records, never `work/node`."
|
|
107
|
+
kinds: [check, codemod]
|
|
108
|
+
gates: [settle, land, ci]
|
|
109
|
+
failureCodes: ["HFS_WORK_NODE_RETIRED"]
|
|
110
|
+
enforcers:
|
|
111
|
+
- {kind: work-validate, id: work-node-retired, at: scripts/work/validate/check-example-work.mjs}
|
|
112
|
+
- id: R09
|
|
113
|
+
code: "HFS_IDENTITY_CUSTODY"
|
|
114
|
+
law: "An identity points its secret at `secrets/identity-<slug>.enc`; UAT chooses by role."
|
|
115
|
+
kinds: [check, codemod]
|
|
116
|
+
gates: [settle, land, ci]
|
|
117
|
+
failureCodes: ["HFS_IDENTITY_CUSTODY", "SEALED_CUSTODY_LOCATION", "SEALED_FILE_IN_WORK"]
|
|
118
|
+
enforcers:
|
|
119
|
+
- {kind: work-validate, id: sealed-custody-location, at: scripts/work/validate/check-work-artifacts.mjs}
|
|
120
|
+
- {kind: work-validate, id: identity-custody-role, at: scripts/work/validate/check-example-work.mjs}
|
|
121
|
+
- id: R10
|
|
122
|
+
code: "HFS_STACKS_SHAPE"
|
|
123
|
+
law: "`.starcistacks` has the standard shape and the host Sonar owner."
|
|
124
|
+
kinds: [check, codemod]
|
|
125
|
+
gates: [settle, land, ci]
|
|
126
|
+
failureCodes: ["HFS_STACKS_SHAPE"]
|
|
127
|
+
enforcers:
|
|
128
|
+
- {kind: hfs, id: stacks-shape, at: scripts/hfs/rules/stacks.mjs}
|
|
129
|
+
- id: R11
|
|
130
|
+
code: "HFS_SONAR_CONFIG"
|
|
131
|
+
law: "Sonar config is generated, with no host URL, importing the be lcov with the logic of be/src/modules (the coverage scope derived from the slot manifest) as the only coverage scope."
|
|
132
|
+
kinds: [check, codemod]
|
|
133
|
+
gates: [land, ci]
|
|
134
|
+
failureCodes: ["HFS_SONAR_CONFIG"]
|
|
135
|
+
enforcers:
|
|
136
|
+
- {kind: hfs, id: sonar-config, at: packages/hfs/sync/managed.mjs}
|
|
137
|
+
- id: R12
|
|
138
|
+
code: "HFS_E2E_IN_AUTOMATIC_GATE"
|
|
139
|
+
law: "e2e never joins husky, coverage or automatic CI."
|
|
140
|
+
kinds: [check]
|
|
141
|
+
gates: [pre-push, land, ci]
|
|
142
|
+
failureCodes: ["HFS_E2E_IN_AUTOMATIC_GATE"]
|
|
143
|
+
enforcers:
|
|
144
|
+
- {kind: machine, id: e2e-in-automatic-gate, at: scripts/hfs/architecture/hfs.mjs}
|
|
145
|
+
- id: R13
|
|
146
|
+
code: "HFS_CI_MISSING_CANON"
|
|
147
|
+
law: "CI runs the pinned `starci app lint` (`npm run lint`), and a clone never redirects `core.hooksPath` away from husky."
|
|
148
|
+
kinds: [check, codemod]
|
|
149
|
+
gates: [pre-push, land, ci]
|
|
150
|
+
failureCodes: ["HFS_CI_MISSING_CANON", "HFS_HOOKS_PATH_REDIRECTED"]
|
|
151
|
+
enforcers:
|
|
152
|
+
- {kind: machine, id: hooks-path-not-redirected, at: scripts/hfs/architecture/hfs.mjs}
|
|
153
|
+
- {kind: hfs, id: ci-missing-canon, at: scripts/hfs/rules/pipeline.mjs}
|
|
154
|
+
- id: R14
|
|
155
|
+
code: "HFS_DEP_VERSION_SKEW"
|
|
156
|
+
law: "One version per dependency in the workspace, a root `overrides` pin included, and npm is the only package manager."
|
|
157
|
+
kinds: [check, codemod]
|
|
158
|
+
gates: [pre-push, land, ci]
|
|
159
|
+
failureCodes: ["HFS_DEP_VERSION_SKEW", "HFS_PACKAGE_MANAGER_MIXED"]
|
|
160
|
+
enforcers:
|
|
161
|
+
- {kind: hfs, id: dep-version-skew, at: scripts/hfs/rules/deps.mjs}
|
|
162
|
+
- {kind: machine, id: package-manager, at: scripts/hfs/architecture/hfs.mjs}
|
|
163
|
+
- id: R15
|
|
164
|
+
code: "HFS_CANON_PIN_DRIFT"
|
|
165
|
+
law: "Canon packages and frameworks match `canon-pins.yaml`."
|
|
166
|
+
kinds: [check, codemod]
|
|
167
|
+
gates: [pre-push, land, ci]
|
|
168
|
+
failureCodes: ["HFS_CANON_PIN_DRIFT"]
|
|
169
|
+
enforcers:
|
|
170
|
+
- {kind: hfs, id: canon-pin-drift, at: scripts/hfs/check.mjs}
|
|
171
|
+
- id: R16
|
|
172
|
+
code: "HFS_TOOL_CONFIG_LOCAL"
|
|
173
|
+
law: "Configs only call the factories; no local rules."
|
|
174
|
+
kinds: [check, codemod]
|
|
175
|
+
gates: [land, ci]
|
|
176
|
+
failureCodes: ["HFS_TOOL_CONFIG_LOCAL"]
|
|
177
|
+
enforcers:
|
|
178
|
+
- {kind: hfs, id: tool-config-local, at: packages/hfs/sync/managed.mjs}
|
|
179
|
+
- {kind: hfs, id: tool-config-forbidden, at: scripts/hfs/check.mjs}
|
|
180
|
+
- id: R17
|
|
181
|
+
code: "HFS_RULE_OFF_WITHOUT_REPLACEMENT"
|
|
182
|
+
law: "The ESLint configuration (and a front end's stylelint configuration) is the rendered one-liner, so no rule is off, warned or redefined in a repository."
|
|
183
|
+
kinds: [check]
|
|
184
|
+
gates: [land, ci]
|
|
185
|
+
failureCodes: ["HFS_RULE_OFF_WITHOUT_REPLACEMENT"]
|
|
186
|
+
enforcers:
|
|
187
|
+
- {kind: hfs, id: rule-off-without-replacement, at: packages/hfs/sync/managed.mjs}
|
|
188
|
+
- id: R18
|
|
189
|
+
code: "HFS_INLINE_SUPPRESSION"
|
|
190
|
+
law: "No inline suppression of any kind."
|
|
191
|
+
kinds: [lint, design]
|
|
192
|
+
gates: [pre-commit, pre-push, land, ci]
|
|
193
|
+
failureCodes: ["HFS_INLINE_SUPPRESSION"]
|
|
194
|
+
enforcers:
|
|
195
|
+
- {kind: eslint-be, id: no-inline-suppression}
|
|
196
|
+
- {kind: eslint-fe, id: no-inline-lint-config}
|
|
197
|
+
- {kind: stylelint, id: no-inline-lint-config}
|
|
198
|
+
- id: R19
|
|
199
|
+
code: "HFS_FORMAT"
|
|
200
|
+
law: "Prettier is the only formatter."
|
|
201
|
+
kinds: [codemod]
|
|
202
|
+
gates: [pre-commit, pre-push, land, ci]
|
|
203
|
+
failureCodes: ["HFS_FORMAT"]
|
|
204
|
+
enforcers:
|
|
205
|
+
- {kind: hfs, id: format, at: packages/hfs/sync/format.mjs}
|
|
206
|
+
- id: R20
|
|
207
|
+
code: "HFS_SIZE_GROWTH"
|
|
208
|
+
law: "Over-budget files do not grow; new files are inside budget."
|
|
209
|
+
kinds: [lint, design]
|
|
210
|
+
gates: [land, ci, sonar, runtime]
|
|
211
|
+
failureCodes: ["HFS_SIZE_GROWTH", "HFS_SIZE_SOFT_BACKLOG"]
|
|
212
|
+
enforcers:
|
|
213
|
+
- {kind: eslint-be, id: file-size-growth}
|
|
214
|
+
- {kind: eslint-fe, id: file-size-growth}
|
|
215
|
+
- {kind: sonar, id: cognitive-complexity, at: knowledge/sonar-gate.yaml}
|
|
216
|
+
- {kind: runtime, id: runtime-size, at: scripts/hfs/runtime-rules/size.mjs}
|
|
217
|
+
- id: R21
|
|
218
|
+
code: "HFS_DUPLICATE_CODE"
|
|
219
|
+
law: "No duplicated blocks or twice-defined helpers."
|
|
220
|
+
kinds: [lint, design]
|
|
221
|
+
gates: [land, ci, sonar]
|
|
222
|
+
failureCodes: ["HFS_DUPLICATE_CODE", "HFS_DUPLICATE_SYMBOL", "FE_CROSS_APP_DUPLICATE"]
|
|
223
|
+
enforcers:
|
|
224
|
+
- {kind: eslint-be, id: duplicate-code}
|
|
225
|
+
- {kind: eslint-fe, id: duplicate-code}
|
|
226
|
+
- {kind: eslint-be, id: duplicate-symbol}
|
|
227
|
+
- {kind: eslint-fe, id: duplicate-symbol}
|
|
228
|
+
- {kind: eslint-fe, id: cross-app-duplicate}
|
|
229
|
+
- {kind: sonar, id: duplicated-lines-density, at: knowledge/sonar-gate.yaml}
|
|
230
|
+
- id: R22
|
|
231
|
+
code: "HFS_TS_STRICT"
|
|
232
|
+
law: "tsconfig extends `@starci/tsconfig` and lowers no flag; no assertion, `!` or `any` in product source."
|
|
233
|
+
kinds: [codemod, design, lint]
|
|
234
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
235
|
+
failureCodes: ["HFS_TS_STRICT", "FE_TYPE_ESCAPE"]
|
|
236
|
+
enforcers:
|
|
237
|
+
- {kind: eslint-fe, id: no-type-assertion}
|
|
238
|
+
- {kind: eslint-fe, id: no-non-null-assertion}
|
|
239
|
+
- {kind: eslint-fe, id: no-explicit-any}
|
|
240
|
+
- {kind: hfs, id: ts-strict, at: packages/hfs/sync/ts-strict.mjs}
|
|
241
|
+
- {kind: eslint-fe, id: no-double-cast}
|
|
242
|
+
- id: R23
|
|
243
|
+
code: "HFS_CONTRACT_SNAPSHOT_DRIFT"
|
|
244
|
+
law: "The be side commits its contract snapshot and it equals the emit; the fe side reads it in place (be/contracts/)."
|
|
245
|
+
kinds: [check]
|
|
246
|
+
gates: [land, ci]
|
|
247
|
+
failureCodes: ["HFS_CONTRACT_SNAPSHOT_DRIFT"]
|
|
248
|
+
enforcers:
|
|
249
|
+
- {kind: hfs, id: contract-snapshot-drift, at: scripts/hfs/rules/contract.mjs}
|
|
250
|
+
- id: R24
|
|
251
|
+
code: "HFS_ARCH_CONFIG_UNREAD"
|
|
252
|
+
law: "The machine reads `hfs.json`; zero files analysed is red."
|
|
253
|
+
kinds: [check]
|
|
254
|
+
gates: [land, ci]
|
|
255
|
+
failureCodes: ["HFS_ARCH_CONFIG_UNREAD"]
|
|
256
|
+
enforcers:
|
|
257
|
+
- {kind: machine, id: arch-config-unread, at: scripts/hfs/architecture/config-unread.mjs}
|
|
258
|
+
- id: R25
|
|
259
|
+
code: "HFS_UNUSED_EXPORT"
|
|
260
|
+
law: "No public export without a consumer; no source file nothing reaches."
|
|
261
|
+
kinds: [lint, codemod]
|
|
262
|
+
gates: [pre-push, settle, land, ci]
|
|
263
|
+
failureCodes: ["HFS_UNUSED_EXPORT", "HFS_UNUSED_FILE"]
|
|
264
|
+
enforcers:
|
|
265
|
+
- {kind: eslint-be, id: dead-exports}
|
|
266
|
+
- {kind: eslint-fe, id: dead-exports}
|
|
267
|
+
- id: R26
|
|
268
|
+
code: "BE_TIER_DIRECTION"
|
|
269
|
+
law: "The tier direction matrix; a shared package never imports an app."
|
|
270
|
+
kinds: [lint, design]
|
|
271
|
+
gates: [pre-push, settle, land, ci]
|
|
272
|
+
failureCodes: ["BE_TIER_DIRECTION", "FE_TIER_DIRECTION", "ARCH_PACKAGE_IMPORTS_APP"]
|
|
273
|
+
enforcers:
|
|
274
|
+
- {kind: eslint-be, id: tier-direction}
|
|
275
|
+
- {kind: eslint-fe, id: tier-direction}
|
|
276
|
+
- {kind: eslint-be, id: package-imports-app}
|
|
277
|
+
- {kind: eslint-fe, id: package-imports-app}
|
|
278
|
+
- id: R27
|
|
279
|
+
code: "ARCH_OWNER_CYCLE"
|
|
280
|
+
law: "No owner cycles, type-only included."
|
|
281
|
+
kinds: [lint, design]
|
|
282
|
+
gates: [pre-push, settle, land, ci, runtime]
|
|
283
|
+
failureCodes: ["ARCH_OWNER_CYCLE"]
|
|
284
|
+
enforcers:
|
|
285
|
+
- {kind: eslint-be, id: owner-cycle}
|
|
286
|
+
- {kind: eslint-fe, id: owner-cycle}
|
|
287
|
+
- {kind: runtime, id: runtime-cycles, at: scripts/hfs/runtime-rules/tier-direction.mjs}
|
|
288
|
+
- id: R28
|
|
289
|
+
code: "BE_FEATURE_IMPORTS_FEATURE"
|
|
290
|
+
law: "A feature never imports a feature."
|
|
291
|
+
kinds: [lint, design]
|
|
292
|
+
gates: [settle, land, ci]
|
|
293
|
+
failureCodes: ["BE_FEATURE_IMPORTS_FEATURE"]
|
|
294
|
+
enforcers:
|
|
295
|
+
- {kind: eslint-be, id: feature-imports-feature}
|
|
296
|
+
- id: R29
|
|
297
|
+
code: "BE_FEATURE_SHAPE"
|
|
298
|
+
law: "Feature root is `index.ts`, module, `application/`, `transport/<protocol>/`; `application/` never imports `transport/` or a protocol framework."
|
|
299
|
+
kinds: [check, codemod, design, lint]
|
|
300
|
+
gates: [settle, land, ci]
|
|
301
|
+
failureCodes: ["BE_FEATURE_SHAPE", "BE_FEATURE_LAYOUT_INVALID", "BE_APPLICATION_IMPORTS_TRANSPORT", "BE_APPLICATION_TRANSPORT_FRAMEWORK"]
|
|
302
|
+
enforcers:
|
|
303
|
+
- {kind: machine, id: feature-shape, at: scripts/hfs/architecture/feature-shape.mjs}
|
|
304
|
+
- {kind: eslint-be, id: feature-layout}
|
|
305
|
+
- {kind: eslint-be, id: application-never-imports-transport}
|
|
306
|
+
- id: R30
|
|
307
|
+
code: "BE_PUBLIC_SURFACE"
|
|
308
|
+
law: "Every owner has one `index.ts`; cross-owner imports use it, same-owner imports are relative and never go through it; it holds only named `export { }` lines, no `export *`, no alias re-export (`export { X as Y }`, `export const Y = X`, `export type Y = X`, `export interface Y extends X {}`: one declaration has one name), at most 60 exports; cross-package imports use the package name and a declared export."
|
|
309
|
+
kinds: [lint, codemod]
|
|
310
|
+
gates: [pre-push, settle, land, ci]
|
|
311
|
+
failureCodes: ["BE_PUBLIC_SURFACE", "ARCH_OWNER_EXPORT_BYPASS", "ARCH_OWNER_EXPORT_STAR", "HFS_ALIAS_REEXPORT", "ARCH_PACKAGE_EXPORT_BYPASS"]
|
|
312
|
+
enforcers:
|
|
313
|
+
- {kind: eslint-be, id: import-owner-entry}
|
|
314
|
+
- {kind: eslint-be, id: no-folder-reexport}
|
|
315
|
+
- {kind: eslint-be, id: owner-export-bypass}
|
|
316
|
+
- {kind: eslint-fe, id: owner-export-bypass}
|
|
317
|
+
- {kind: eslint-be, id: alias-reexport}
|
|
318
|
+
- {kind: eslint-fe, id: alias-reexport}
|
|
319
|
+
- {kind: eslint-be, id: no-relative-capability-escape}
|
|
320
|
+
- {kind: eslint-be, id: no-self-module-alias}
|
|
321
|
+
- {kind: eslint-be, id: package-export-bypass}
|
|
322
|
+
- {kind: eslint-fe, id: package-export-bypass}
|
|
323
|
+
- id: R31
|
|
324
|
+
code: "BE_FEATURE_NOT_COMPOSED"
|
|
325
|
+
law: "Every feature, transport module and capability module is composed by an app."
|
|
326
|
+
kinds: [lint, design]
|
|
327
|
+
gates: [land, ci]
|
|
328
|
+
failureCodes: ["BE_FEATURE_NOT_COMPOSED"]
|
|
329
|
+
enforcers:
|
|
330
|
+
- {kind: eslint-be, id: feature-not-composed}
|
|
331
|
+
- id: R32
|
|
332
|
+
code: "BE_APP_COMPOSITION_ONLY"
|
|
333
|
+
law: "Apps compose only; an app is proven by the e2e world (`useTestWorld({ apps })`), never by a unit spec."
|
|
334
|
+
kinds: [lint, codemod]
|
|
335
|
+
gates: [settle, land, ci]
|
|
336
|
+
failureCodes: ["BE_APP_COMPOSITION_ONLY", "BE_APP_BUSINESS_ROLE"]
|
|
337
|
+
enforcers:
|
|
338
|
+
- {kind: eslint-be, id: app-composition-only}
|
|
339
|
+
- id: R33
|
|
340
|
+
code: "BE_ENTRYPOINT_ONLY_IN_APPS"
|
|
341
|
+
law: "Entrypoints only in `be/apps/*/src`."
|
|
342
|
+
kinds: [lint, codemod]
|
|
343
|
+
gates: [land, ci]
|
|
344
|
+
failureCodes: ["BE_ENTRYPOINT_ONLY_IN_APPS"]
|
|
345
|
+
enforcers:
|
|
346
|
+
- {kind: eslint-be, id: entrypoint-only-in-apps}
|
|
347
|
+
- id: R34
|
|
348
|
+
code: "BE_SCHEMA_AUTHORITY"
|
|
349
|
+
law: "Migrations are the only schema authority, run only by the cli migrate command and the test world; `synchronize` is `false`."
|
|
350
|
+
kinds: [lint, codemod, design]
|
|
351
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
352
|
+
failureCodes: ["BE_SCHEMA_AUTHORITY"]
|
|
353
|
+
enforcers:
|
|
354
|
+
- {kind: eslint-be, id: no-runtime-schema}
|
|
355
|
+
- id: R35
|
|
356
|
+
code: "BE_SCHEMA_OWNER"
|
|
357
|
+
law: "Entities and migrations live in the owning capability's `persistence/`; its `<c>Entities` and `<c>Migrations` are registered under exactly one declared connection, which every `Inject<Conn>EntityManager` of the capability names (no `CONNECTION` alias); only a platform capability the manifest lists as `perConnection` (the event-bus outbox, the job table) registers on several."
|
|
358
|
+
kinds: [lint, codemod, design]
|
|
359
|
+
gates: [settle, land, ci]
|
|
360
|
+
failureCodes: ["BE_SCHEMA_OWNER"]
|
|
361
|
+
enforcers:
|
|
362
|
+
- {kind: eslint-be, id: schema-owner}
|
|
363
|
+
- id: R36
|
|
364
|
+
code: "BE_SQL_OUTSIDE_PERSISTENCE"
|
|
365
|
+
law: "Raw SQL is `sql`-tagged `SqlText` in `persistence/<name>.sql.ts` of the owning capability; `.query()` takes only `SqlText`; no QueryBuilder."
|
|
366
|
+
kinds: [lint, design]
|
|
367
|
+
gates: [settle, land, ci]
|
|
368
|
+
failureCodes: ["BE_SQL_OUTSIDE_PERSISTENCE"]
|
|
369
|
+
enforcers:
|
|
370
|
+
- {kind: eslint-be, id: sql-text-only}
|
|
371
|
+
- {kind: eslint-be, id: no-query-builder}
|
|
372
|
+
- id: R37
|
|
373
|
+
code: "BE_ENTITY_IN_CONTRACT"
|
|
374
|
+
law: "No ORM entity in a contract or transport type."
|
|
375
|
+
kinds: [lint, design]
|
|
376
|
+
gates: [settle, land, ci]
|
|
377
|
+
failureCodes: ["BE_ENTITY_IN_CONTRACT"]
|
|
378
|
+
enforcers:
|
|
379
|
+
- {kind: eslint-be, id: no-entity-in-contract}
|
|
380
|
+
- id: R38
|
|
381
|
+
code: "BE_ERROR_HOME"
|
|
382
|
+
law: "Errors live in the owning capability's `errors/` and extend `DomainError`."
|
|
383
|
+
kinds: [lint, codemod]
|
|
384
|
+
gates: [settle, land, ci]
|
|
385
|
+
failureCodes: ["BE_ERROR_HOME"]
|
|
386
|
+
enforcers:
|
|
387
|
+
- {kind: eslint-be, id: error-home}
|
|
388
|
+
- {kind: eslint-be, id: throw-domain-error}
|
|
389
|
+
- {kind: eslint-be, id: error-family-shape}
|
|
390
|
+
- {kind: eslint-be, id: error-code-unique}
|
|
391
|
+
- id: R39
|
|
392
|
+
code: "BE_ERROR_MASKED"
|
|
393
|
+
law: "One filter per app; undeclared errors are masked."
|
|
394
|
+
kinds: [lint, codemod]
|
|
395
|
+
gates: [land, ci]
|
|
396
|
+
failureCodes: ["BE_ERROR_MASKED"]
|
|
397
|
+
enforcers:
|
|
398
|
+
- {kind: eslint-be, id: error-masked}
|
|
399
|
+
- id: R40
|
|
400
|
+
code: "BE_LOGGER_REQUIRED"
|
|
401
|
+
law: "`platform/logging` exists and its `Logger` port is the only logger; every `catch` logs, rethrows or returns a reasoned outcome, and a log call names its event with a member of an owner's `<owner>.log-events.ts` enum."
|
|
402
|
+
kinds: [lint, design]
|
|
403
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
404
|
+
failureCodes: ["BE_LOGGER_REQUIRED"]
|
|
405
|
+
enforcers:
|
|
406
|
+
- {kind: eslint-be, id: catch-must-account}
|
|
407
|
+
- {kind: eslint-be, id: no-error-wording-as-log-identity}
|
|
408
|
+
- {kind: eslint-be, id: no-interpolated-log-message}
|
|
409
|
+
- {kind: eslint-be, id: no-framework-logger}
|
|
410
|
+
- id: R41
|
|
411
|
+
code: "BE_DEFAULT_DENY"
|
|
412
|
+
law: "APP_GUARD plus `@Public({ reason: PublicReason.X })`; no `@UseGuards`; typed bodies; `timingSafeEqual`."
|
|
413
|
+
kinds: [lint, codemod]
|
|
414
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
415
|
+
failureCodes: ["BE_DEFAULT_DENY"]
|
|
416
|
+
enforcers:
|
|
417
|
+
- {kind: eslint-be, id: no-untyped-body}
|
|
418
|
+
- {kind: eslint-be, id: public-needs-reason}
|
|
419
|
+
- {kind: eslint-be, id: secret-compare-timing-safe}
|
|
420
|
+
- {kind: eslint-be, id: no-auth-use-guards}
|
|
421
|
+
- {kind: eslint-be, id: default-deny-app-guard}
|
|
422
|
+
- id: R42
|
|
423
|
+
code: "BE_INPUT_BOUNDED"
|
|
424
|
+
law: "Bounded input, cursor-only pagination, depth limits, rate limits; every property of an input class carries the `class-validator` decorators its type calls for; a door opened for `PublicReason.AuthHandshake` or `PublicReason.SignedWebhook` carries `RateTier.Strict` of `platform/http-security`."
|
|
425
|
+
kinds: [lint, design]
|
|
426
|
+
gates: [settle, land, ci]
|
|
427
|
+
failureCodes: ["BE_INPUT_BOUNDED"]
|
|
428
|
+
enforcers:
|
|
429
|
+
- {kind: eslint-be, id: input-bounded}
|
|
430
|
+
- {kind: eslint-be, id: no-offset-pagination}
|
|
431
|
+
- {kind: eslint-be, id: auth-door-strict-rate-tier}
|
|
432
|
+
- id: R43
|
|
433
|
+
code: "BE_CONFIG_OWNER"
|
|
434
|
+
law: "Only `platform/config` reads `process.env`; config per capability."
|
|
435
|
+
kinds: [lint, design]
|
|
436
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
437
|
+
failureCodes: ["BE_CONFIG_OWNER"]
|
|
438
|
+
enforcers:
|
|
439
|
+
- {kind: eslint-be, id: no-direct-env-read}
|
|
440
|
+
- {kind: eslint-be, id: config-parsed-in-main}
|
|
441
|
+
- id: R44
|
|
442
|
+
code: "BE_SECRET_DEFAULT"
|
|
443
|
+
law: "No default for a secret key or infrastructure URL."
|
|
444
|
+
kinds: [lint, codemod]
|
|
445
|
+
gates: [pre-commit, pre-push, land, ci]
|
|
446
|
+
failureCodes: ["BE_SECRET_DEFAULT"]
|
|
447
|
+
enforcers:
|
|
448
|
+
- {kind: eslint-be, id: no-secret-default}
|
|
449
|
+
- id: R45
|
|
450
|
+
code: "BE_MODULE_SHAPE"
|
|
451
|
+
law: "`@Global` nowhere and `isGlobal: true` only at app roots; no cross-owner module imports; typed options; one module per transport; every handler and provider registered exactly once."
|
|
452
|
+
kinds: [lint, codemod]
|
|
453
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
454
|
+
failureCodes: ["BE_MODULE_SHAPE", "BE_MODULE_HANDLER_REGISTRATION", "BE_MODULE_PROVIDER_REREGISTRATION"]
|
|
455
|
+
enforcers:
|
|
456
|
+
- {kind: eslint-be, id: no-global-decorator}
|
|
457
|
+
- {kind: eslint-be, id: is-global-only-in-app}
|
|
458
|
+
- {kind: eslint-be, id: no-cross-owner-module-import}
|
|
459
|
+
- {kind: eslint-be, id: capability-module-shape}
|
|
460
|
+
- {kind: eslint-be, id: typed-module-definition}
|
|
461
|
+
- {kind: eslint-be, id: static-module-register}
|
|
462
|
+
- {kind: eslint-be, id: no-new-injectable}
|
|
463
|
+
- {kind: eslint-be, id: no-module-let}
|
|
464
|
+
- {kind: eslint-be, id: one-module-per-file}
|
|
465
|
+
- {kind: eslint-be, id: register-once}
|
|
466
|
+
- {kind: eslint-be, id: module-per-transport}
|
|
467
|
+
- {kind: eslint-be, id: module-registration}
|
|
468
|
+
- id: R46
|
|
469
|
+
code: "BE_BACKGROUND_UNOWNED"
|
|
470
|
+
law: "Every sweep, outbox or retry has a job or consumer composed by an app: the service that owns it (an api app) or a worker app scaled apart."
|
|
471
|
+
kinds: [lint, design]
|
|
472
|
+
gates: [land, ci]
|
|
473
|
+
failureCodes: ["BE_BACKGROUND_UNOWNED"]
|
|
474
|
+
editions: [full]
|
|
475
|
+
enforcers:
|
|
476
|
+
- {kind: eslint-be, id: background-unowned}
|
|
477
|
+
- id: R47
|
|
478
|
+
code: "BE_TEST_TOPOLOGY"
|
|
479
|
+
law: "One jest config, projects `unit`, `integration`, `e2e` and `contract`, live by folder, `diagnostics: false`; unit specs are `<name>.<role>.spec.ts` beside the file they test: required for the roles of `ruleParams.be.unitRoles` (`<name>.service.spec.ts` beside a `<name>.service.ts`, `<name>.cli.spec.ts` beside a cli command `<name>.cli.ts` of `src/features/cli`), optional for any other logic role (`ruleParams.be.logicRoles`) inside a slot whose `coverage` is required, and nowhere else but beside a webhook or realtime door that has no service of its own (`<provider>.webhook.spec.ts`, `<channel>.gateway.spec.ts`, `<x>.subscription.spec.ts`) (every unit-tested subject has exactly one; any other `*.spec.ts` outside `be/src/tests/{world,integration,e2e,contract}` is a finding, including specs in apps), and the unit project collects coverage from the logic roles of the measured roots only (the scope derived from the slot manifest, R204) with a per-file threshold of 100 on lines, branches, functions and statements (`test` runs `--coverage`; `importHelpers` and `tslib`); `be/src/tests/world/` is the ONLY test infrastructure: `global-setup.ts` starts the app's own stack (`.starcistacks/<env>`, named by `stack` in `test-world.config.ts`, the declaration of the test-world library in its one named form `export const { useTestWorld, useSandbox } = defineTestWorld({...})` (a default export is not read): every service it declares runs REAL, behind toxiproxy, images and versions read from the stack at run time, never spelled in test source) or attaches to a warm one that answers, and runs the migrations once over the cli app's connections (the runner of the cli migrate command); `global-teardown.ts` stops only what its setup started; `use-test-world.ts` exports `useTestWorld({ apps } | { modules })` (`world.apps.<name>.api`, `world.db.<connection>`, `world.infra.<service>` with `latency(ms)`, `cut()` and `restore()` on the real service, `world.fake.<provider>`, `world.waitFor`) and `fakes/<provider>/` holds the network-edge fakes (`failNext`, `replayWebhook`, `delay`, payload fixtures) of external SaaS the team does not operate ONLY: a fake of a service the stack declares is refused, except stateless compute that needs special hardware or an external model (GPU inference, a self-hosted embedding model), whose `stacks` entry in `test-world.config.ts` declares `{ fakedBy: <fake>, reason }` with a non-empty `reason` (a stateful service, or one with a persistent volume, is never accepted); it alone owns containers, DataSource, migrations and `process.env`; nothing under `be/src/tests` overrides a provider or DI token; `be/src/tests/integration/<capability>/*.integration-spec.ts` (`{ modules }`, with the real peer apps its client calls beside them as `apps`; real infrastructure, no HTTP door of ours), `be/src/tests/e2e/<area>/*.e2e-spec.ts` (`{ apps }`) and `be/src/tests/contract/<provider>/*.contract-spec.ts` (provider sandboxes, skipped without sandbox config, run only by `test:contract`, never by `test` or `test:e2e`) agree folder with suffix; an integration or e2e spec passes `useTestWorld` one object literal with plain keys (no variable, spread or computed key) and calls it; the contract layer skips only through the test-world library's `useSandbox(...)` when the sandbox config is absent (`sandbox.describe(...)`), and every `fakes/<provider>/` that serves payload fixtures (`payloads/*.json`) has a `contract/<provider>/*.contract-spec.ts` that references those fixtures and asserts `shapeOf(real)` equals `shapeOf(fixture)` (the helper `shapeOf` is named by `ruleParams.be.contractShape` of the slot manifest; fakes the test-world library provides are guarded by the library); no `.test.ts`, `int-spec` or `harness-spec`, no `live/` and no `be/src/tests/e2e/world`."
|
|
480
|
+
kinds: [check, codemod, lint]
|
|
481
|
+
gates: [land, ci]
|
|
482
|
+
failureCodes: ["BE_TEST_TOPOLOGY", "BE_CONTRACT_UNGUARDED"]
|
|
483
|
+
editions: [full]
|
|
484
|
+
enforcers:
|
|
485
|
+
- {kind: hfs, id: test-topology, at: scripts/hfs/rules/test-topology.mjs}
|
|
486
|
+
- {kind: machine, id: test-kind-retired, at: scripts/hfs/architecture/hfs.mjs}
|
|
487
|
+
- {kind: eslint-be, id: test-world-files}
|
|
488
|
+
- {kind: machine, id: contract-fixture-guard, at: scripts/hfs/architecture/contract-fixture-guard.mjs}
|
|
489
|
+
- {kind: eslint-be, id: unit-test-colocated}
|
|
490
|
+
- {kind: eslint-be, id: tests-infra-only-in-world}
|
|
491
|
+
- {kind: eslint-be, id: tests-no-override}
|
|
492
|
+
- {kind: eslint-be, id: test-world-shape}
|
|
493
|
+
- id: R48
|
|
494
|
+
code: "BE_SPEC_QUALITY"
|
|
495
|
+
law: "No source-reading specs; a spec asserts results or state, not only calls; no `as` and no `x!` in a spec (the borrowed rules of R72), no return-only generic (`<T>(value: unknown): T`), `unknown` handed back as a concrete type, `JSON.parse(JSON.stringify(x))` or `Object.assign(new X(), y)` returned as another type in a spec or a test fixture, and no `Date.now()`, argless `new Date()` or `process.env` (R79, R43); a unit spec (`<name>.service.spec.ts`) builds its subject with `Test.createTestingModule({ providers: [...] }).compile()` and `moduleRef.get(Subject)`: no `new` of the service, no `imports` key, no `override*` call, and no `jest.mock`, `jest.doMock`, `jest.unstable_mockModule` or `jest.requireMock` of an own module or a third-party library; it provides each infrastructure token from its `@starci/jest-preset` double (`mockEntityManager(...)` for an EntityManager token, `new FakeClock(...)`, `recordingEventBus()`, `recordingQueueOutbox()`, `fakeCache(...)`, `fakeLock(...)`, `fakeIds()`, `builder(...)(...)` or a plain literal of real values for an options token, `mock<T>()` for everything else; the token table is `ruleParams.be.specDoubles` of the slot manifest) and takes its EntityManager only from the kit's `mockEntityManager()` or `fakeTransaction()`, never an ad-hoc `jest.fn` object, and asserts exact values: ids from `fakeIds()` and dates from `FakeClock` are compared as themselves, never `expect.any(String)`, `expect.any(Number)` or `expect.any(Date)`; an e2e enters through transport, waits with `waitFor`, boots through `useTestWorld`, reads persisted state back and reaches no model provider. Test data is arranged by builders, which R98 to R101 hold. No spec of any layer skips, focuses or marks a test todo (`skip`, `skipIf`, `runIf`, `todo`, `only`, `xit`, `xdescribe`, `xtest`) and none selects its runner conditionally, and no file of the test tree (world, fixtures, kit) writes one either; the only skip is the test-world library's `useSandbox(...)` (`@starci/test-world`), which skips a contract spec when the sandbox config is absent."
|
|
496
|
+
kinds: [lint, design]
|
|
497
|
+
gates: [settle, land, ci]
|
|
498
|
+
failureCodes: ["BE_SPEC_QUALITY"]
|
|
499
|
+
editions: [full]
|
|
500
|
+
enforcers:
|
|
501
|
+
- {kind: eslint-be, id: spec-no-source-read}
|
|
502
|
+
- {kind: eslint-be, id: e2e-uses-production-transport}
|
|
503
|
+
- {kind: eslint-be, id: no-branch-in-flow-step}
|
|
504
|
+
- {kind: eslint-be, id: no-sleep-in-flow}
|
|
505
|
+
- {kind: eslint-be, id: no-wiring-in-flow-spec}
|
|
506
|
+
- {kind: eslint-be, id: e2e-asserts-persisted-state}
|
|
507
|
+
- {kind: eslint-be, id: no-api-shaped-e2e-filename}
|
|
508
|
+
- {kind: eslint-be, id: no-call-only-spec}
|
|
509
|
+
- {kind: eslint-be, id: no-marker-model-stub}
|
|
510
|
+
- {kind: eslint-be, id: no-model-call-in-e2e}
|
|
511
|
+
- {kind: eslint-be, id: spec-typed-entity-manager}
|
|
512
|
+
- {kind: eslint-be, id: spec-builds-with-testing-module}
|
|
513
|
+
- {kind: eslint-be, id: spec-no-new-subject}
|
|
514
|
+
- {kind: eslint-be, id: spec-module-definition-only-providers}
|
|
515
|
+
- {kind: eslint-be, id: spec-no-module-mock}
|
|
516
|
+
- {kind: eslint-be, id: no-return-only-generic}
|
|
517
|
+
- {kind: eslint-be, id: spec-infra-double-from-kit}
|
|
518
|
+
- {kind: eslint-be, id: spec-exact-values}
|
|
519
|
+
- {kind: eslint-be, id: spec-no-skip}
|
|
520
|
+
- {kind: eslint-be, id: unit-spec-providers}
|
|
521
|
+
- id: R49
|
|
522
|
+
code: "FE_ENV_OWNER"
|
|
523
|
+
law: "Only `modules/config` reads the environment; no localhost fallback."
|
|
524
|
+
kinds: [lint, codemod]
|
|
525
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
526
|
+
failureCodes: ["FE_ENV_OWNER"]
|
|
527
|
+
enforcers:
|
|
528
|
+
- {kind: eslint-fe, id: no-env-outside-config}
|
|
529
|
+
- {kind: eslint-fe, id: no-hardcoded-endpoint-fallback}
|
|
530
|
+
- id: R50
|
|
531
|
+
code: "FE_TRANSPORT_OWNER"
|
|
532
|
+
law: "One `fetch` per front end, in the transport client: `fe/apps/<app>/src/modules/api/client.ts` of a one-app front end or `fe/packages/<family>-api/src/client.ts` shared by every app; timeout and abort; a client read is SWR whose key carries the identity of its result, and a mutation is tied to its resource."
|
|
533
|
+
kinds: [lint, design]
|
|
534
|
+
gates: [settle, land, ci]
|
|
535
|
+
failureCodes: ["FE_TRANSPORT_OWNER", "FE_EFFECT_FETCH", "FE_SWR_KEY_IDENTITY", "FE_SWR_MUTATION_RESOURCE_IDENTITY"]
|
|
536
|
+
enforcers:
|
|
537
|
+
- {kind: eslint-fe, id: fetch-only-in-api-client}
|
|
538
|
+
- {kind: eslint-fe, id: client-fetch-has-signal}
|
|
539
|
+
- {kind: eslint-fe, id: no-shared-transport-state}
|
|
540
|
+
- {kind: eslint-fe, id: no-data-fetch-in-effect}
|
|
541
|
+
- {kind: eslint-fe, id: transport-owner}
|
|
542
|
+
- {kind: eslint-fe, id: api-client-attaches-the-locale}
|
|
543
|
+
- {kind: eslint-fe, id: locale-header-belongs-to-the-link}
|
|
544
|
+
- {kind: eslint-fe, id: swr-data-lifecycle}
|
|
545
|
+
- id: R51
|
|
546
|
+
code: "FE_HTTP_STATUS_COLLAPSE"
|
|
547
|
+
law: "One `Outcome<T>` union per front end (the api slot's `outcome.ts`); 401 and 403 become `refused`."
|
|
548
|
+
kinds: [lint, design]
|
|
549
|
+
gates: [settle, land, ci]
|
|
550
|
+
failureCodes: ["FE_HTTP_STATUS_COLLAPSE", "FE_OUTCOME_KIND_UNHANDLED"]
|
|
551
|
+
enforcers:
|
|
552
|
+
- {kind: eslint-fe, id: client-maps-auth-to-refused}
|
|
553
|
+
- {kind: eslint-fe, id: no-failure-collapse}
|
|
554
|
+
- {kind: eslint-fe, id: outcome-kinds-exhaustive}
|
|
555
|
+
- {kind: eslint-fe, id: one-outcome-union}
|
|
556
|
+
- id: R52
|
|
557
|
+
code: "FE_WIRE_GENERATED"
|
|
558
|
+
law: "Wire types generated from the be contract snapshots the fe side reads."
|
|
559
|
+
kinds: [lint, check, design]
|
|
560
|
+
gates: [settle, land, ci]
|
|
561
|
+
failureCodes: ["FE_WIRE_GENERATED"]
|
|
562
|
+
enforcers:
|
|
563
|
+
- {kind: eslint-fe, id: no-hand-typed-wire}
|
|
564
|
+
- {kind: hfs, id: wire-generated, at: scripts/hfs/rules/frontend-tree.mjs}
|
|
565
|
+
- id: R53
|
|
566
|
+
code: "FE_ERROR_BOUNDARY_MISSING"
|
|
567
|
+
law: "Global, locale error, not-found and loading boundaries exist."
|
|
568
|
+
kinds: [check, codemod, lint]
|
|
569
|
+
gates: [settle, land, ci]
|
|
570
|
+
failureCodes: ["FE_ERROR_BOUNDARY_MISSING", "FE_SUSPENSE_NULL_FALLBACK"]
|
|
571
|
+
enforcers:
|
|
572
|
+
- {kind: eslint-fe, id: no-null-suspense-fallback}
|
|
573
|
+
- {kind: machine, id: error-boundary-missing, at: scripts/hfs/architecture/required-files.mjs}
|
|
574
|
+
- id: R54
|
|
575
|
+
code: "FE_ROUTE_FILES_THIN"
|
|
576
|
+
law: "Route files mount one owner; Next conventions (`proxy.ts`, metadata)."
|
|
577
|
+
kinds: [lint]
|
|
578
|
+
gates: [settle, land, ci]
|
|
579
|
+
failureCodes: ["FE_ROUTE_FILES_THIN", "FE_NEXT_CONVENTIONS", "FE_PAGE_METADATA_MISSING", "FE_ROUTE_ONE_PAGE", "FE_ROUTE_DRAWING_DECISION", "FE_ROUTE_CLIENT_BOUNDARY", "FE_ROUTE_CLIENT_HOOK", "FE_ROUTE_DEFAULT_EXPORT"]
|
|
580
|
+
enforcers:
|
|
581
|
+
- {kind: eslint-fe, id: no-middleware-file}
|
|
582
|
+
- {kind: eslint-fe, id: page-exports-metadata}
|
|
583
|
+
- {kind: eslint-fe, id: route-files-thin}
|
|
584
|
+
- {kind: eslint-fe, id: route-adapter}
|
|
585
|
+
- id: R55
|
|
586
|
+
code: "FE_CLIENT_BOUNDARY"
|
|
587
|
+
law: "Server first; `\"use client\"` only where allowed."
|
|
588
|
+
kinds: [lint]
|
|
589
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
590
|
+
failureCodes: ["FE_CLIENT_BOUNDARY", "FE_CLIENT_SERVER_IMPORT", "FE_CLIENT_REACHES_SERVER", "FE_SERVER_ONLY_MARK", "FE_STORAGE_OUTSIDE_MODULES", "FE_DANGEROUS_HTML"]
|
|
591
|
+
enforcers:
|
|
592
|
+
- {kind: eslint-fe, id: use-client-only-at-boundary}
|
|
593
|
+
- {kind: eslint-fe, id: client-no-server-import}
|
|
594
|
+
- {kind: eslint-fe, id: server-module-marks-server-only}
|
|
595
|
+
- {kind: eslint-fe, id: web-storage-only-in-modules}
|
|
596
|
+
- {kind: eslint-fe, id: no-dangerous-html}
|
|
597
|
+
- {kind: eslint-fe, id: client-reaches-server}
|
|
598
|
+
- id: R56
|
|
599
|
+
code: "FE_HOOKS_ARE_HOOKS"
|
|
600
|
+
law: "`hooks/` hold hooks and one shared file per domain; a product hook is defined only there and imported through its index."
|
|
601
|
+
kinds: [lint, codemod]
|
|
602
|
+
gates: [settle, land, ci]
|
|
603
|
+
failureCodes: ["FE_HOOKS_ARE_HOOKS", "FE_CUSTOM_HOOK_LOCATION", "FE_BLOCK_PRODUCT_HOOK_DEFINITION", "FE_COMPONENT_DEEP_HOOK_IMPORT"]
|
|
604
|
+
enforcers:
|
|
605
|
+
- {kind: eslint-fe, id: hooks-folder-holds-hooks-only}
|
|
606
|
+
- {kind: eslint-fe, id: hooks-are-hooks}
|
|
607
|
+
- {kind: eslint-fe, id: hook-location}
|
|
608
|
+
- id: R57
|
|
609
|
+
code: "FE_OWNER_REACHABLE"
|
|
610
|
+
law: "Every owner is mounted; every href resolves to a route."
|
|
611
|
+
kinds: [lint, design]
|
|
612
|
+
gates: [settle, land, ci]
|
|
613
|
+
failureCodes: ["FE_OWNER_REACHABLE", "FE_ROUTE_HARDCODED", "FE_HREF_RESOLVES"]
|
|
614
|
+
enforcers:
|
|
615
|
+
- {kind: eslint-fe, id: no-hardcoded-route}
|
|
616
|
+
- {kind: eslint-fe, id: owner-reachable}
|
|
617
|
+
- id: R58
|
|
618
|
+
code: "FE_I18N_LITERAL"
|
|
619
|
+
law: "No display text at any tier, no escape comment."
|
|
620
|
+
kinds: [lint, codemod]
|
|
621
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
622
|
+
failureCodes: ["FE_I18N_LITERAL"]
|
|
623
|
+
enforcers:
|
|
624
|
+
- {kind: eslint-fe, id: no-hardcoded-copy}
|
|
625
|
+
- {kind: eslint-fe, id: html-lang-from-locale}
|
|
626
|
+
- {kind: eslint-fe, id: no-copy-resolution-below-block}
|
|
627
|
+
- id: R59
|
|
628
|
+
code: "FE_I18N_PLACEMENT"
|
|
629
|
+
law: "`next-intl` with `[locale]`; the next-intl stack is written once per front end: `fe/packages/<family>-i18n` (`createAppI18n`) called by each app's `modules/i18n/index.ts`, or the only app's `modules/i18n/`."
|
|
630
|
+
kinds: [check, codemod, lint]
|
|
631
|
+
gates: [settle, land, ci]
|
|
632
|
+
failureCodes: ["FE_I18N_PLACEMENT", "FE_I18N_NAVIGATION", "FE_I18N_FORMATTER"]
|
|
633
|
+
enforcers:
|
|
634
|
+
- {kind: eslint-fe, id: locale-segment-is-locale}
|
|
635
|
+
- {kind: eslint-fe, id: no-second-i18n-stack}
|
|
636
|
+
- {kind: eslint-fe, id: i18n-stack-in-one-module}
|
|
637
|
+
- {kind: eslint-fe, id: navigation-from-intl}
|
|
638
|
+
- {kind: eslint-fe, id: no-native-anchor}
|
|
639
|
+
- {kind: eslint-fe, id: use-intl-formatter}
|
|
640
|
+
- {kind: hfs, id: i18n-placement, at: scripts/hfs/rules/frontend-tree.mjs}
|
|
641
|
+
- id: R60
|
|
642
|
+
code: "FE_I18N_CATALOG"
|
|
643
|
+
law: "Same keys in every catalog, `pick` for clients."
|
|
644
|
+
kinds: [check]
|
|
645
|
+
gates: [land, ci]
|
|
646
|
+
failureCodes: ["FE_I18N_CATALOG"]
|
|
647
|
+
enforcers:
|
|
648
|
+
- {kind: hfs, id: i18n-catalog, at: scripts/hfs/rules/frontend-tree.mjs}
|
|
649
|
+
- id: R61
|
|
650
|
+
code: "FE_STYLE_TOKEN_ONLY"
|
|
651
|
+
law: "Token-only CSS; colour only in `brand.css`; every `@source` resolves (sub-check `FE_STYLE_SOURCE_UNRESOLVED`). Status colours follow HeroUI soft pairs: every status tone has `--<tone>-soft` and `--<tone>-soft-foreground` in light and dark, the soft foreground reaches 3:1 on its tint and on `--background`, body text 4.5:1, a solid tone is never text below 4.5:1, and text and icons take the soft foreground, never the solid tone."
|
|
652
|
+
kinds: [lint, design]
|
|
653
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
654
|
+
failureCodes: ["FE_STYLE_TOKEN_ONLY", "FE_STYLE_SOURCE_UNRESOLVED"]
|
|
655
|
+
enforcers:
|
|
656
|
+
- {kind: stylelint, id: token-only}
|
|
657
|
+
- {kind: stylelint, id: raw-brand-value}
|
|
658
|
+
- {kind: stylelint, id: no-apply-raw}
|
|
659
|
+
- {kind: stylelint, id: no-important}
|
|
660
|
+
- {kind: stylelint, id: globals-shape}
|
|
661
|
+
- {kind: stylelint, id: globals-import-order}
|
|
662
|
+
- {kind: stylelint, id: no-token-redefinition}
|
|
663
|
+
- {kind: stylelint, id: brand-layer-shape}
|
|
664
|
+
- {kind: stylelint, id: status-contrast}
|
|
665
|
+
- {kind: stylelint, id: no-css-module}
|
|
666
|
+
- {kind: stylelint, id: no-class-selector}
|
|
667
|
+
- {kind: stylelint, id: breakpoint-scale}
|
|
668
|
+
- {kind: stylelint, id: source-resolves}
|
|
669
|
+
- {kind: eslint-fe, id: no-raw-brand-value}
|
|
670
|
+
- {kind: eslint-fe, id: no-fractional-step}
|
|
671
|
+
- {kind: eslint-fe, id: no-arbitrary-value}
|
|
672
|
+
- {kind: eslint-fe, id: no-hand-rolled-heading}
|
|
673
|
+
- {kind: eslint-fe, id: no-unresolved-token-class}
|
|
674
|
+
- {kind: eslint-fe, id: no-heading-tag-outside-heading-component}
|
|
675
|
+
- {kind: eslint-fe, id: status-text-uses-soft-foreground}
|
|
676
|
+
- id: R62
|
|
677
|
+
code: "FE_NATIVE_FORM_CONTROL"
|
|
678
|
+
law: "No raw form controls, native images or raw structural tags in product tiers; the grammar renders them."
|
|
679
|
+
kinds: [lint, design]
|
|
680
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
681
|
+
failureCodes: ["FE_NATIVE_FORM_CONTROL", "FE_NATIVE_IMAGE"]
|
|
682
|
+
enforcers:
|
|
683
|
+
- {kind: eslint-fe, id: no-native-form-control}
|
|
684
|
+
- {kind: eslint-fe, id: no-native-img}
|
|
685
|
+
- {kind: eslint-fe, id: no-raw-structural-element}
|
|
686
|
+
- {kind: eslint-fe, id: image-has-size}
|
|
687
|
+
- id: R63
|
|
688
|
+
code: "FE_PACKAGE_SHAPE"
|
|
689
|
+
law: "Packages build to `dist` with explicit exports and no dead unit."
|
|
690
|
+
kinds: [check, design]
|
|
691
|
+
gates: [land, ci]
|
|
692
|
+
failureCodes: ["FE_PACKAGE_SHAPE"]
|
|
693
|
+
enforcers:
|
|
694
|
+
- {kind: machine, id: package-shape, at: scripts/hfs/architecture/package-shape.mjs}
|
|
695
|
+
- id: R64
|
|
696
|
+
code: "FE_APP_ISOLATION"
|
|
697
|
+
law: "Apps never import apps; ui-screen declares `app`."
|
|
698
|
+
kinds: [lint, codemod]
|
|
699
|
+
gates: [settle, land, ci]
|
|
700
|
+
failureCodes: ["FE_APP_ISOLATION"]
|
|
701
|
+
enforcers:
|
|
702
|
+
- {kind: eslint-fe, id: app-isolation}
|
|
703
|
+
- id: R65
|
|
704
|
+
code: "FE_SIZE_AND_STATE_BUDGET"
|
|
705
|
+
law: "Hook and state budgets; no hand-written poll loop; keyed lists; everything an effect starts is released by its cleanup; no swallowed error or console."
|
|
706
|
+
kinds: [lint, design]
|
|
707
|
+
gates: [settle, land, ci]
|
|
708
|
+
failureCodes: ["FE_SIZE_AND_STATE_BUDGET", "FE_LIST_KEY", "FE_EFFECT_CLEANUP", "FE_SWALLOWED_ERROR", "FE_CONSOLE_CALL"]
|
|
709
|
+
enforcers:
|
|
710
|
+
- {kind: eslint-fe, id: component-line-budget}
|
|
711
|
+
- {kind: eslint-fe, id: unit-hook-budget}
|
|
712
|
+
- {kind: eslint-fe, id: no-hand-rolled-polling}
|
|
713
|
+
- {kind: eslint-fe, id: list-item-has-key}
|
|
714
|
+
- {kind: eslint-fe, id: no-index-key}
|
|
715
|
+
- {kind: eslint-fe, id: no-inline-literal-prop-in-list}
|
|
716
|
+
- {kind: eslint-fe, id: effect-subscription-needs-cleanup}
|
|
717
|
+
- {kind: eslint-fe, id: no-empty-catch}
|
|
718
|
+
- {kind: eslint-fe, id: no-console}
|
|
719
|
+
- id: R68
|
|
720
|
+
code: "BE_SQL_INTERPOLATED"
|
|
721
|
+
law: "SQL text carries no runtime substitution; values are numbered parameters."
|
|
722
|
+
kinds: [lint, codemod]
|
|
723
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
724
|
+
failureCodes: ["BE_SQL_INTERPOLATED"]
|
|
725
|
+
enforcers:
|
|
726
|
+
- {kind: eslint-be, id: no-interpolated-sql}
|
|
727
|
+
- id: R69
|
|
728
|
+
code: "BE_QUERY_UNBOUNDED"
|
|
729
|
+
law: "A read that can return many rows states `take`, `limit` or `LIMIT`, or pages by cursor."
|
|
730
|
+
kinds: [lint, codemod]
|
|
731
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
732
|
+
failureCodes: ["BE_QUERY_UNBOUNDED"]
|
|
733
|
+
enforcers:
|
|
734
|
+
- {kind: eslint-be, id: query-needs-limit}
|
|
735
|
+
- id: R70
|
|
736
|
+
code: "BE_HTTP_TIMEOUT"
|
|
737
|
+
law: "Every outbound `fetch`, axios or HttpService call states a timeout or an abort signal."
|
|
738
|
+
kinds: [lint, codemod]
|
|
739
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
740
|
+
failureCodes: ["BE_HTTP_TIMEOUT"]
|
|
741
|
+
enforcers:
|
|
742
|
+
- {kind: eslint-be, id: http-needs-timeout}
|
|
743
|
+
- id: R71
|
|
744
|
+
code: "BE_LOG_SECRET"
|
|
745
|
+
law: "A `Logger` call carries no `Secret` or `Pii` value and no credential or personal identifier by name; log an id or a masked form. A constructed error carries no `Secret` value and no text value named like a credential."
|
|
746
|
+
kinds: [lint, codemod]
|
|
747
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
748
|
+
failureCodes: ["BE_LOG_SECRET"]
|
|
749
|
+
enforcers:
|
|
750
|
+
- {kind: eslint-be, id: no-secret-in-log}
|
|
751
|
+
- {kind: eslint-be, id: no-secret-in-error}
|
|
752
|
+
- id: R72
|
|
753
|
+
code: "BE_TYPE_ESCAPE"
|
|
754
|
+
law: "No type escape in any file, specs included: no `as X` (only `as const`), `<X>y`, `x!` or `any` (the factory turns on the typescript-eslint rules `consistent-type-assertions` with `never`, `no-non-null-assertion` and `no-explicit-any`), no `Function` type, no `eval`."
|
|
755
|
+
kinds: [lint, codemod]
|
|
756
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
757
|
+
failureCodes: ["BE_TYPE_ESCAPE"]
|
|
758
|
+
enforcers:
|
|
759
|
+
- {kind: eslint-be, id: no-function-or-eval}
|
|
760
|
+
- id: R73
|
|
761
|
+
code: "BE_ASYNC_NO_AWAIT"
|
|
762
|
+
law: "An `async` function contains an `await`; otherwise it is not `async`."
|
|
763
|
+
kinds: [lint, codemod]
|
|
764
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
765
|
+
failureCodes: ["BE_ASYNC_NO_AWAIT"]
|
|
766
|
+
enforcers:
|
|
767
|
+
- {kind: eslint-be, id: async-needs-await}
|
|
768
|
+
- id: R74
|
|
769
|
+
code: "BE_MIGRATION_REVERSIBLE"
|
|
770
|
+
law: "Every migration declares a `down()` that reverses its `up()`; never empty, never a bare throw."
|
|
771
|
+
kinds: [lint, codemod]
|
|
772
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
773
|
+
failureCodes: ["BE_MIGRATION_REVERSIBLE"]
|
|
774
|
+
enforcers:
|
|
775
|
+
- {kind: eslint-be, id: migration-down-reversible}
|
|
776
|
+
- id: R75
|
|
777
|
+
code: "BE_RETURN_TYPE"
|
|
778
|
+
law: "Handlers and public methods of an Injectable, Resolver or Controller declare their return type."
|
|
779
|
+
kinds: [lint, codemod]
|
|
780
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
781
|
+
failureCodes: ["BE_RETURN_TYPE"]
|
|
782
|
+
enforcers:
|
|
783
|
+
- {kind: eslint-be, id: explicit-handler-return-type}
|
|
784
|
+
- id: R76
|
|
785
|
+
code: "BE_JSON_PARSE_UNGUARDED"
|
|
786
|
+
law: "`JSON.parse` sits inside a `try` in its own function and fails as a typed outcome."
|
|
787
|
+
kinds: [lint, codemod]
|
|
788
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
789
|
+
failureCodes: ["BE_JSON_PARSE_UNGUARDED"]
|
|
790
|
+
enforcers:
|
|
791
|
+
- {kind: eslint-be, id: json-parse-needs-guard}
|
|
792
|
+
- id: R77
|
|
793
|
+
code: "BE_QUERY_IN_LOOP"
|
|
794
|
+
law: "A read through the shared EntityManager does not run once per element of a loop; read once before the loop by key list."
|
|
795
|
+
kinds: [lint, codemod]
|
|
796
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
797
|
+
failureCodes: ["BE_QUERY_IN_LOOP"]
|
|
798
|
+
enforcers:
|
|
799
|
+
- {kind: eslint-be, id: no-query-in-loop}
|
|
800
|
+
- id: R78
|
|
801
|
+
code: "BE_USER_COPY_LITERAL"
|
|
802
|
+
law: "A literal exception message, notification text or response copy comes from the per-capability messages catalog through the typed `MessageCatalog` port, not from source."
|
|
803
|
+
kinds: [lint, codemod]
|
|
804
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
805
|
+
failureCodes: ["BE_USER_COPY_LITERAL"]
|
|
806
|
+
enforcers:
|
|
807
|
+
- {kind: eslint-be, id: user-copy-through-catalog}
|
|
808
|
+
- id: R79
|
|
809
|
+
code: "BE_AMBIENT_CLOCK"
|
|
810
|
+
law: "`Date.now`, a bare `new Date()`, `performance.now`, `process.hrtime` and `Temporal.Now` are referenced only inside `platform/clock`, specs included; code asks the injected `Clock` port and specs use `FakeClock`."
|
|
811
|
+
kinds: [lint, codemod]
|
|
812
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
813
|
+
failureCodes: ["BE_AMBIENT_CLOCK"]
|
|
814
|
+
enforcers:
|
|
815
|
+
- {kind: eslint-be, id: no-ambient-clock}
|
|
816
|
+
- id: R80
|
|
817
|
+
code: "BE_INBOX_DEDUPE_MISSING"
|
|
818
|
+
law: "Every service method that takes a delivery (a public method of a `*.service.ts` whose first parameter has an `eventId`) makes `claim(source, eventId)` on the `Inbox` port its first awaited expression and returns early when the claim answers `false`; consumers and `SignedWebhook` controllers stay thin doors (R88) and dispatch to the handler whose service claims."
|
|
819
|
+
kinds: [lint, design]
|
|
820
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
821
|
+
failureCodes: ["BE_INBOX_DEDUPE_MISSING"]
|
|
822
|
+
enforcers:
|
|
823
|
+
- {kind: eslint-be, id: inbox-dedupe-required}
|
|
824
|
+
- id: R81
|
|
825
|
+
code: "BE_HAND_ROLLED_RETRY"
|
|
826
|
+
law: "A loop that catches an error and waits before trying again goes through the shared `platform/retry` helper, never a hand-written loop."
|
|
827
|
+
kinds: [lint, codemod]
|
|
828
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
829
|
+
failureCodes: ["BE_HAND_ROLLED_RETRY"]
|
|
830
|
+
enforcers:
|
|
831
|
+
- {kind: eslint-be, id: no-hand-rolled-retry}
|
|
832
|
+
- id: R82
|
|
833
|
+
code: "BE_TRANSACTION_EXTERNAL_CALL"
|
|
834
|
+
law: "No transaction spans an external call; commit first and call out after, or write an outbox message inside the transaction."
|
|
835
|
+
kinds: [lint, design]
|
|
836
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
837
|
+
failureCodes: ["BE_TRANSACTION_EXTERNAL_CALL"]
|
|
838
|
+
enforcers:
|
|
839
|
+
- {kind: eslint-be, id: no-external-call-in-transaction}
|
|
840
|
+
- id: R83
|
|
841
|
+
code: "BE_UNNAMED_DATA_ACCESS"
|
|
842
|
+
law: "The database is reached through the shared EntityManager, injected as a constructor parameter by the `Inject<Conn>EntityManager()` of a declared connection and called directly; no bare `@InjectEntityManager()`, `getRepository`, repository, QueryBuilder or property injection; a `DataSource` or `QueryRunner` only in `platform/database`, the cli (`be/apps/cli`, `be/src/features/cli`) and the test world `be/src/tests/world`, whose `world.db.<connection>` EntityManager the specs use. No class outside an application handler, a domain service or a platform persistence capability takes, holds or returns an `EntityManager`, `Repository`, `DataSource` or `QueryRunner` (a repository under any name, a store, dao, gateway or persistence wrapper included), no exported function whose first parameter is an `EntityManager` (a statement module), and no wrapper return type, awaited value or `provide:` token hands out a connection object."
|
|
843
|
+
kinds: [lint, design]
|
|
844
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
845
|
+
failureCodes: ["BE_UNNAMED_DATA_ACCESS"]
|
|
846
|
+
enforcers:
|
|
847
|
+
- {kind: eslint-be, id: named-entity-manager-only}
|
|
848
|
+
- {kind: eslint-be, id: no-injected-repository}
|
|
849
|
+
- {kind: eslint-be, id: must-inject-entity-manager}
|
|
850
|
+
- {kind: eslint-be, id: no-repository-class}
|
|
851
|
+
- {kind: eslint-be, id: no-eager-relation}
|
|
852
|
+
- {kind: eslint-be, id: no-outer-manager-in-transaction}
|
|
853
|
+
- {kind: eslint-be, id: require-entity-table-name}
|
|
854
|
+
- id: R84
|
|
855
|
+
code: "BE_CONNECTION_DUPLICATE"
|
|
856
|
+
law: "One physical database is one connection and one `Inject<Conn>EntityManager()` injector declared once in `platform/database`; `hfs.json` connections, connection files, injectors and module registrations correspond one to one."
|
|
857
|
+
kinds: [lint]
|
|
858
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
859
|
+
failureCodes: ["BE_CONNECTION_DUPLICATE"]
|
|
860
|
+
enforcers:
|
|
861
|
+
- {kind: eslint-be, id: one-connection-per-database}
|
|
862
|
+
- {kind: eslint-be, id: connection-map}
|
|
863
|
+
- id: R85
|
|
864
|
+
code: "BE_RAW_INJECT"
|
|
865
|
+
law: "Every injected infrastructure dependency arrives through a zero-argument `Inject<Thing>()` from its owner's `<owner>.decorators.ts` over a `unique symbol` token; raw `@Inject(` exists only there, and every such token is exported so a spec can provide it; a constructor parameter of a provider is typed by a class or carries an `Inject<Thing>()`, never a bare primitive, `Map`, union or interface; the parameter that injects a port of the table `ruleParams.be.paramNames` of `slots.yaml` (`Clock`, `Logger`, `Cache`, `CommandBus`, `QueryBus`, `EntityManager`, an `*Options`) carries the name that table gives it."
|
|
866
|
+
kinds: [lint, codemod]
|
|
867
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
868
|
+
failureCodes: ["BE_RAW_INJECT"]
|
|
869
|
+
enforcers:
|
|
870
|
+
- {kind: eslint-be, id: injector-only}
|
|
871
|
+
- {kind: eslint-be, id: injector-shape}
|
|
872
|
+
- {kind: eslint-be, id: injector-type-match}
|
|
873
|
+
- {kind: eslint-be, id: infra-needs-injector}
|
|
874
|
+
- {kind: eslint-be, id: provider-param-token}
|
|
875
|
+
- {kind: eslint-be, id: injected-param-name}
|
|
876
|
+
- {kind: eslint-be, id: no-module-ref}
|
|
877
|
+
- {kind: eslint-be, id: no-forward-ref}
|
|
878
|
+
- {kind: eslint-be, id: no-string-token}
|
|
879
|
+
- {kind: eslint-be, id: injection-token-exported}
|
|
880
|
+
- id: R86
|
|
881
|
+
code: "BE_SQL_TABLE_OWNER"
|
|
882
|
+
law: "SQL writes only the tables of its own capability's entities, reads only tables of its own context (its own capability's, or a same-context capability it may import; a cross-context JOIN is refused), and every multi-row SELECT is bounded."
|
|
883
|
+
kinds: [lint, design]
|
|
884
|
+
gates: [settle, land, ci]
|
|
885
|
+
failureCodes: ["BE_SQL_TABLE_OWNER"]
|
|
886
|
+
enforcers:
|
|
887
|
+
- {kind: eslint-be, id: sql-owner}
|
|
888
|
+
- id: R87
|
|
889
|
+
code: "BE_CQRS_SHAPE"
|
|
890
|
+
law: "The application layer is CQRS: typed `Command<R>`/`Query<R>` messages carrying one `params`, handlers extending `ICQRSHandler` that override `process`, where `process` is one `return this.<service>.<method>(...)` and the handler injects only `*Service` classes and the Logger (no EntityManager, no branch, no loop, no second call); no use-case classes, forwarder services or in-process events (`EventBus`, `EventEmitter`, an RxJS `Subject`, a stored listener list); a message and an injected dependency are `readonly`."
|
|
891
|
+
kinds: [lint, codemod]
|
|
892
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
893
|
+
failureCodes: ["BE_CQRS_SHAPE", "BE_READONLY_BOUNDARY"]
|
|
894
|
+
enforcers:
|
|
895
|
+
- {kind: eslint-be, id: handler-overrides-process}
|
|
896
|
+
- {kind: eslint-be, id: handler-is-thin}
|
|
897
|
+
- {kind: eslint-be, id: message-carries-params-only}
|
|
898
|
+
- {kind: eslint-be, id: message-typed-result}
|
|
899
|
+
- {kind: eslint-be, id: no-use-case}
|
|
900
|
+
- {kind: eslint-be, id: no-event-bus}
|
|
901
|
+
- {kind: eslint-be, id: execute-params-shape}
|
|
902
|
+
- {kind: eslint-be, id: readonly-boundary}
|
|
903
|
+
- id: R88
|
|
904
|
+
code: "BE_TRANSPORT_SHAPE"
|
|
905
|
+
law: "A transport handler (a controller, resolver, gateway, consumer or job; a cli command is an action runner, R149) maps its input, dispatches exactly one command or query through the injected bus and maps the result; it injects nothing else (no EntityManager, no Inbox), holds no branch, loop or other call besides pure mapper functions and `unwrapOutcome` of `platform/primitives`, returns no envelope and takes no `GraphQLJSON`."
|
|
906
|
+
kinds: [lint, codemod]
|
|
907
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
908
|
+
failureCodes: ["BE_TRANSPORT_SHAPE"]
|
|
909
|
+
enforcers:
|
|
910
|
+
- {kind: eslint-be, id: door-lives-in-features}
|
|
911
|
+
- {kind: eslint-be, id: no-capability-imports-features}
|
|
912
|
+
- {kind: eslint-be, id: rest-door-needs-a-reason, editions: [full]}
|
|
913
|
+
- {kind: eslint-be, id: transport-is-thin}
|
|
914
|
+
- {kind: eslint-be, id: no-response-envelope}
|
|
915
|
+
- {kind: eslint-be, id: no-graphql-json}
|
|
916
|
+
- {kind: eslint-be, id: em-injection-slots}
|
|
917
|
+
- id: R89
|
|
918
|
+
code: "BE_SOURCE_FORM"
|
|
919
|
+
law: "Files use the closed role-suffix vocabulary of the slot manifest, `ruleParams.be.suffixes`; named exports only; every export has English JSDoc (its public members: R109); no emoji, and no Vietnamese in identifiers, string literals, comments or test titles outside message catalogs and the i18n fixtures slot; a public input or output is a named contract, never an inline object type; no `Mock*`, `Fake*` or `Stub*` class, function or constant in production source."
|
|
920
|
+
kinds: [lint, check, codemod]
|
|
921
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
922
|
+
failureCodes: ["BE_SOURCE_FORM", "BE_PUBLIC_CONTRACT_FORM"]
|
|
923
|
+
enforcers:
|
|
924
|
+
- {kind: eslint-be, id: require-export-jsdoc}
|
|
925
|
+
- {kind: eslint-be, id: require-enum-member-jsdoc}
|
|
926
|
+
- {kind: eslint-be, id: no-non-ascii-source}
|
|
927
|
+
- {kind: eslint-be, id: no-restated-name-jsdoc}
|
|
928
|
+
- {kind: eslint-be, id: no-bare-verb-export}
|
|
929
|
+
- {kind: eslint-be, id: no-vendor-module-factory-name}
|
|
930
|
+
- {kind: eslint-be, id: no-version-in-name}
|
|
931
|
+
- {kind: eslint-be, id: no-const-enum}
|
|
932
|
+
- {kind: eslint-be, id: no-inline-object-type}
|
|
933
|
+
- {kind: eslint-be, id: no-inline-param-type}
|
|
934
|
+
- {kind: eslint-be, id: no-test-double-in-source}
|
|
935
|
+
- {kind: eslint-be, id: no-default-export}
|
|
936
|
+
- {kind: hfs, id: source-suffix, at: scripts/hfs/check.mjs}
|
|
937
|
+
- {kind: eslint-be, id: source-suffix}
|
|
938
|
+
- {kind: eslint-be, id: source-names}
|
|
939
|
+
- {kind: eslint-be, id: public-contract-form}
|
|
940
|
+
- id: R90
|
|
941
|
+
code: "BE_INFRA_OWNER"
|
|
942
|
+
law: "Each raw infrastructure library (HTTP, cache, queue, scheduler, logger, date, config, events) is imported or referenced only by its one owning platform or integration capability, and the `HttpClient` port of `platform/http` is called only by an `integrations` capability."
|
|
943
|
+
kinds: [lint, codemod]
|
|
944
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
945
|
+
failureCodes: ["BE_INFRA_OWNER"]
|
|
946
|
+
enforcers:
|
|
947
|
+
- {kind: eslint-be, id: infra-import-owner}
|
|
948
|
+
- {kind: eslint-be, id: http-client-only-in-integrations}
|
|
949
|
+
- id: R91
|
|
950
|
+
code: "FE_SOURCE_FORM"
|
|
951
|
+
law: "Front-end files sit in their tier folder with the fixed names, export arrow functions named after the folder, carry English JSDoc, no emoji, and no Vietnamese in identifiers, string literals, comments or test titles outside the i18n catalogs and the i18n e2e fixtures slot."
|
|
952
|
+
kinds: [lint, codemod]
|
|
953
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
954
|
+
failureCodes: ["FE_SOURCE_FORM", "FE_SOURCE_LAYOUT_INVALID"]
|
|
955
|
+
enforcers:
|
|
956
|
+
- {kind: eslint-fe, id: require-export-jsdoc}
|
|
957
|
+
- {kind: eslint-fe, id: no-emoji-in-source}
|
|
958
|
+
- {kind: eslint-fe, id: no-vietnamese-in-source}
|
|
959
|
+
- {kind: eslint-fe, id: no-shell-tier}
|
|
960
|
+
- {kind: eslint-fe, id: source-tier-marker-matches-folder}
|
|
961
|
+
- {kind: eslint-fe, id: surface-folder-two-files-only}
|
|
962
|
+
- {kind: eslint-fe, id: route-tree-holds-routes-only}
|
|
963
|
+
- {kind: eslint-fe, id: route-slot-fixed-name}
|
|
964
|
+
- {kind: eslint-fe, id: no-helper-folder-in-components}
|
|
965
|
+
- {kind: eslint-fe, id: export-matches-folder}
|
|
966
|
+
- {kind: eslint-fe, id: no-runtime-namespace}
|
|
967
|
+
- {kind: eslint-fe, id: monorepo-tier-belongs-to-its-side}
|
|
968
|
+
- {kind: eslint-fe, id: prefer-arrow-export}
|
|
969
|
+
- {kind: eslint-fe, id: handler-on-prefix}
|
|
970
|
+
- {kind: eslint-fe, id: no-second-language-in-path}
|
|
971
|
+
- {kind: eslint-fe, id: no-direct-const-alias}
|
|
972
|
+
- {kind: eslint-fe, id: frontend-source-layout}
|
|
973
|
+
- id: R92
|
|
974
|
+
code: "FE_COMPONENT_API"
|
|
975
|
+
law: "A component exposes the canon surface: typed named props and slots, a presentational twin for every connected block, status through the slot view, no className or CSS doors, no resting twin or placeholder prop, class names in the colocated file; a pure component reaches no hook, router, locale or API."
|
|
976
|
+
kinds: [lint, design]
|
|
977
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
978
|
+
failureCodes: ["FE_COMPONENT_API", "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"]
|
|
979
|
+
enforcers:
|
|
980
|
+
- {kind: eslint-fe, id: no-inline-parameter-type}
|
|
981
|
+
- {kind: eslint-fe, id: public-component-signature}
|
|
982
|
+
- {kind: eslint-fe, id: no-per-part-classname-prop}
|
|
983
|
+
- {kind: eslint-fe, id: no-public-classname-prop}
|
|
984
|
+
- {kind: eslint-fe, id: no-public-frame-css-props}
|
|
985
|
+
- {kind: eslint-fe, id: no-css-door-type-laundering}
|
|
986
|
+
- {kind: eslint-fe, id: base-props-atom}
|
|
987
|
+
- {kind: eslint-fe, id: base-import-pair}
|
|
988
|
+
- {kind: eslint-fe, id: no-data-status-shape}
|
|
989
|
+
- {kind: eslint-fe, id: slot-status-through-slotview}
|
|
990
|
+
- {kind: eslint-fe, id: presentational-purity}
|
|
991
|
+
- {kind: eslint-fe, id: connected-block-has-presentational-twin}
|
|
992
|
+
- {kind: eslint-fe, id: no-resting-twin-component}
|
|
993
|
+
- {kind: eslint-fe, id: no-placeholder-prop}
|
|
994
|
+
- {kind: eslint-fe, id: no-resting-branch-at-call-site}
|
|
995
|
+
- {kind: eslint-fe, id: no-inline-class-name}
|
|
996
|
+
- {kind: eslint-fe, id: class-names-in-colocated-file}
|
|
997
|
+
- {kind: eslint-fe, id: cn-arguments-are-single-tokens}
|
|
998
|
+
- {kind: eslint-fe, id: component-purity}
|
|
999
|
+
- id: R93
|
|
1000
|
+
code: "FE_VENDOR_BOUNDARY"
|
|
1001
|
+
law: "Vendor primitives and icons reach components only through their named owner: heroicons through the icon leaf at the glyph scale, every vendor primitive behind a named owner; the Grammar package is imported only through its selected code and style entry and keeps its declared contract."
|
|
1002
|
+
kinds: [lint, codemod]
|
|
1003
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1004
|
+
failureCodes: ["FE_VENDOR_BOUNDARY", "ARCH_GRAMMAR_CONTRACT_INVALID", "ARCH_GRAMMAR_EXPORT_BYPASS"]
|
|
1005
|
+
enforcers:
|
|
1006
|
+
- {kind: eslint-fe, id: no-vendor-icon-outside-icon-leaf}
|
|
1007
|
+
- {kind: eslint-fe, id: heroicons-is-the-glyph-vendor}
|
|
1008
|
+
- {kind: eslint-fe, id: no-off-scale-glyph-size}
|
|
1009
|
+
- {kind: eslint-fe, id: no-decorative-icon-in-metric-cell}
|
|
1010
|
+
- {kind: eslint-fe, id: vendor-primitive-has-named-owner}
|
|
1011
|
+
- {kind: eslint-fe, id: grammar-entry}
|
|
1012
|
+
- id: R94
|
|
1013
|
+
code: "FE_SLOT_FILE_ROLE"
|
|
1014
|
+
law: "A front-end file is owned by a slot and named by it: a slot that owns a directory (route, feature, components, hooks, api module) holds only the files its `allows` list names, and holds them inside a folder of its own."
|
|
1015
|
+
kinds: [check, design]
|
|
1016
|
+
gates: [settle, land, ci]
|
|
1017
|
+
failureCodes: ["FE_SLOT_FILE_ROLE"]
|
|
1018
|
+
enforcers:
|
|
1019
|
+
- {kind: machine, id: fe-slot-allows, at: scripts/hfs/architecture/fe-slot-allows.mjs}
|
|
1020
|
+
- id: R95
|
|
1021
|
+
code: "BE_OPERATION_CONTRACT"
|
|
1022
|
+
law: "A versioned operation is declared once in the app's typed operation table (`OperationContract<Input, Output, RefusalCode>` registered by `defineOperations`): its input and output are closed types (never `any`, `unknown` or `Record<string, unknown>`), its refusal codes a closed union of string literals, and the route that serves the table takes `OperationRequest<Table>` and answers `Promise<OperationReply<Table>>` of that one table."
|
|
1023
|
+
kinds: [lint]
|
|
1024
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1025
|
+
failureCodes: ["BE_OPERATION_CONTRACT"]
|
|
1026
|
+
enforcers:
|
|
1027
|
+
- {kind: eslint-be, id: operation-contract-decidable}
|
|
1028
|
+
- {kind: eslint-be, id: operation-route-driven-by-table}
|
|
1029
|
+
- id: R96
|
|
1030
|
+
code: "HFS_DOC_NOT_ENGLISH"
|
|
1031
|
+
law: "Every Markdown and YAML document under a side's docs/, src/ and apps/ (be/, fe/) and under the runtime's knowledge/ and docs/ is English, code fences included; only YAML data in a message-catalog or i18n-fixtures slot may hold another language."
|
|
1032
|
+
kinds: [check]
|
|
1033
|
+
gates: [settle, land, ci]
|
|
1034
|
+
failureCodes: ["HFS_DOC_NOT_ENGLISH"]
|
|
1035
|
+
enforcers:
|
|
1036
|
+
- {kind: machine, id: doc-language, at: scripts/hfs/architecture/doc-language.mjs}
|
|
1037
|
+
- id: R97
|
|
1038
|
+
code: "FE_NO_TESTS"
|
|
1039
|
+
law: "The front end (`fe/`) has no tests by standard: no `*.spec.*`, `*.test.*` or `*-spec.*` file, no `e2e/`, `__tests__/`, `__mocks__/` or `test-support/` directory, no vitest, Playwright, jest or Cypress configuration or setup file, no test script, and no test-runner, test-environment, testing-library or axe dependency in any `package.json`; there is no exception, `scripts/` included."
|
|
1040
|
+
kinds: [check]
|
|
1041
|
+
gates: [pre-push, settle, land, ci]
|
|
1042
|
+
failureCodes: ["FE_NO_TESTS"]
|
|
1043
|
+
enforcers:
|
|
1044
|
+
- {kind: hfs, id: fe-no-tests, at: scripts/hfs/rules/fe-no-tests.mjs}
|
|
1045
|
+
- id: R98
|
|
1046
|
+
code: "BE_TEST_BUILDER_HOME"
|
|
1047
|
+
law: "A test data builder lives only in `be/src/tests/fixtures/builders/<area>.builder.ts` (slot `be.tests.fixtures.builders`, its SQL text in `<area>.sql.ts` beside it): a `*.repository.ts`, `*.factory.ts` or `*.fixture.ts` under `be/src/tests` and a `*.builder.ts` elsewhere are BE_SOURCE_FORM findings, and any other module of the test tree that exports a function creating persisted rows through an EntityManager, DataSource or QueryRunner is a builder in the wrong place."
|
|
1048
|
+
kinds: [lint]
|
|
1049
|
+
gates: [pre-push, settle, land, ci]
|
|
1050
|
+
failureCodes: ["BE_TEST_BUILDER_HOME"]
|
|
1051
|
+
editions: [full]
|
|
1052
|
+
enforcers:
|
|
1053
|
+
- {kind: eslint-be, id: persisting-builder-in-builders-slot}
|
|
1054
|
+
- id: R99
|
|
1055
|
+
code: "BE_TEST_ROW_BY_BUILDER"
|
|
1056
|
+
law: "A unit, integration or e2e spec never hand-builds rows: no raw INSERT/UPDATE/DELETE text, no `save`, `insert`, `upsert`, `update`, `delete` or `remove` on an EntityManager, DataSource or QueryRunner, and no persistence entity built as an object literal more than once in the spec; rows come from the area's builder."
|
|
1057
|
+
kinds: [lint]
|
|
1058
|
+
gates: [pre-push, settle, land, ci]
|
|
1059
|
+
failureCodes: ["BE_TEST_ROW_BY_BUILDER"]
|
|
1060
|
+
editions: [full]
|
|
1061
|
+
enforcers:
|
|
1062
|
+
- {kind: eslint-be, id: spec-no-raw-insert}
|
|
1063
|
+
- {kind: eslint-be, id: spec-no-repeated-row-literal}
|
|
1064
|
+
- id: R100
|
|
1065
|
+
code: "BE_TEST_CONSTRAINTS_ON"
|
|
1066
|
+
law: "Nothing under `be/src/tests` disables or drops a database constraint: no `session_replication_role`, `SET CONSTRAINTS ... DEFERRED`, `DEFERRABLE INITIALLY DEFERRED`, `ALTER TABLE ... DISABLE`, `DISABLE TRIGGER`, `DROP CONSTRAINT`, and no `dropForeignKey(s)`, `dropCheckConstraint(s)` or `dropUniqueConstraint(s)` on a QueryRunner; a builder creates the parent chain instead."
|
|
1067
|
+
kinds: [lint]
|
|
1068
|
+
gates: [pre-push, settle, land, ci]
|
|
1069
|
+
failureCodes: ["BE_TEST_CONSTRAINTS_ON"]
|
|
1070
|
+
editions: [full]
|
|
1071
|
+
enforcers:
|
|
1072
|
+
- {kind: eslint-be, id: tests-keep-constraints}
|
|
1073
|
+
- id: R101
|
|
1074
|
+
code: "BE_TEST_BUILDER_ARRANGES"
|
|
1075
|
+
law: "A test data builder arranges data only: it imports no assertion or test-framework global (`expect`, `@jest/globals` or the Jest namespace) and its defaults are deterministic (no `Date.now()`, argless `new Date()`, `Math.random()`, `randomUUID()` or `crypto.random*`)."
|
|
1076
|
+
kinds: [lint]
|
|
1077
|
+
gates: [pre-push, settle, land, ci]
|
|
1078
|
+
failureCodes: ["BE_TEST_BUILDER_ARRANGES"]
|
|
1079
|
+
editions: [full]
|
|
1080
|
+
enforcers:
|
|
1081
|
+
- {kind: eslint-be, id: builder-arranges-only}
|
|
1082
|
+
- id: R102
|
|
1083
|
+
code: "BE_SPEC_PLACEMENT"
|
|
1084
|
+
law: "A back-end spec or test file lives in one of the four test layers and nowhere else: a unit spec of a unit-tested role (`ruleParams.be.unitRoles`: `<name>.service.spec.ts` beside its service, `<name>.cli.spec.ts` beside its cli command), or an integration, e2e or contract spec under `be/src/tests/{integration,e2e,contract}`; a `*.spec.*`, `*.test.*` or `*-spec.*` file anywhere else, `scripts/` and `tools/` included, is a finding."
|
|
1085
|
+
kinds: [check, lint]
|
|
1086
|
+
gates: [land, ci]
|
|
1087
|
+
failureCodes: ["BE_SPEC_PLACEMENT"]
|
|
1088
|
+
editions: [full]
|
|
1089
|
+
enforcers:
|
|
1090
|
+
- {kind: hfs, id: spec-placement, at: scripts/hfs/rules/spec-placement.mjs}
|
|
1091
|
+
- {kind: eslint-be, id: spec-placement}
|
|
1092
|
+
- id: R103
|
|
1093
|
+
code: "HFS_REPO_LOCAL_CHECK"
|
|
1094
|
+
law: "A repository keeps no check, lint rule or lint plugin of its own: no `check-*` file in `scripts/` or `tools/`, no `eslint-local-rules*` or local eslint plugin, no script that runs one; every check lives in the `.claude` runtime and the canons."
|
|
1095
|
+
kinds: [check]
|
|
1096
|
+
gates: [land, ci]
|
|
1097
|
+
failureCodes: ["HFS_REPO_LOCAL_CHECK"]
|
|
1098
|
+
enforcers:
|
|
1099
|
+
- {kind: hfs, id: repo-local-check, at: scripts/hfs/rules/repo-local-checks.mjs}
|
|
1100
|
+
- id: R104
|
|
1101
|
+
code: "HFS_LINT_SUPPRESSION_FILE"
|
|
1102
|
+
law: "A repository keeps no lint-suppression file, script or option: no `eslint.suppressions*`, no `lint:suppressions` script, no eslint `--suppress-all` or `--suppressions-location` flag and no suppressions configuration passed to eslint; a finding is fixed in the code."
|
|
1103
|
+
kinds: [check]
|
|
1104
|
+
gates: [land, ci]
|
|
1105
|
+
failureCodes: ["HFS_LINT_SUPPRESSION_FILE"]
|
|
1106
|
+
enforcers:
|
|
1107
|
+
- {kind: hfs, id: lint-suppression, at: scripts/hfs/rules/lint-suppression.mjs}
|
|
1108
|
+
- id: R105
|
|
1109
|
+
code: "HFS_PROOF_COMMAND_FILE_MISSING"
|
|
1110
|
+
law: "A proof command of a `.starciwork` record (`requiresProof.<kind>.command`) is runnable as written: every repository file it names exists."
|
|
1111
|
+
kinds: [check]
|
|
1112
|
+
gates: [land, ci]
|
|
1113
|
+
failureCodes: ["HFS_PROOF_COMMAND_FILE_MISSING"]
|
|
1114
|
+
enforcers:
|
|
1115
|
+
- {kind: hfs, id: proof-command-files, at: scripts/hfs/rules/proof-commands.mjs}
|
|
1116
|
+
- id: R106
|
|
1117
|
+
code: "FE_I18N_KEYS"
|
|
1118
|
+
law: "The catalogs of a front-end app and its source agree both ways: a literal key read through `next-intl` (`useTranslations`, `getTranslations`, `t`, `t.rich`, `t.raw`, `t.markup`) exists in every locale, and a catalog key that no string of the app's or the shared packages' source can be reading is deleted."
|
|
1119
|
+
kinds: [lint]
|
|
1120
|
+
gates: [land, ci]
|
|
1121
|
+
failureCodes: ["FE_I18N_KEYS"]
|
|
1122
|
+
enforcers:
|
|
1123
|
+
- {kind: eslint-fe, id: i18n-keys}
|
|
1124
|
+
- id: R107
|
|
1125
|
+
code: "FE_COOKIE_ATTRIBUTES"
|
|
1126
|
+
law: "A cookie the front end writes through Next's response cookies (`response.cookies.set`, `cookies().set`) states `httpOnly` as a literal (`true` for a session, `false` only for a preference page script reads), carries `secure` and has `sameSite` `lax` or `strict`."
|
|
1127
|
+
kinds: [lint]
|
|
1128
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1129
|
+
failureCodes: ["FE_COOKIE_ATTRIBUTES"]
|
|
1130
|
+
enforcers:
|
|
1131
|
+
- {kind: eslint-fe, id: response-cookie-attributes}
|
|
1132
|
+
- id: R108
|
|
1133
|
+
code: "BE_ERROR_CAUSE_DROPPED"
|
|
1134
|
+
law: "A `throw` that escapes a `catch` (or a promise `.catch` handler) rethrows the caught value or carries it: the replacement capability error takes the exact caught value as `cause`; a `catch` with no binding throws nothing new."
|
|
1135
|
+
kinds: [lint]
|
|
1136
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1137
|
+
failureCodes: ["BE_ERROR_CAUSE_DROPPED"]
|
|
1138
|
+
enforcers:
|
|
1139
|
+
- {kind: eslint-be, id: replacement-throw-carries-cause}
|
|
1140
|
+
- id: R109
|
|
1141
|
+
code: "BE_MEMBER_DOC_MISSING"
|
|
1142
|
+
law: "Every public member of an exported class, interface or object type alias in product source opens with English JSDoc: methods, accessors, fields and signatures; a private, protected or `#` member, the constructor, an index signature and a property set to a literal or a named constant are not judged, and overloads of one name share one doc."
|
|
1143
|
+
kinds: [lint]
|
|
1144
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1145
|
+
failureCodes: ["BE_MEMBER_DOC_MISSING"]
|
|
1146
|
+
enforcers:
|
|
1147
|
+
- {kind: eslint-be, id: require-public-member-jsdoc}
|
|
1148
|
+
- id: R110
|
|
1149
|
+
code: "FE_PROPS_MUTABLE"
|
|
1150
|
+
law: "The props type of an exported rendering function in front-end product source is readonly all the way down: every field and index signature is `readonly`, and every collection it holds, directly or in a nested object, is `readonly T[]`, `readonly [A, B]` or `ReadonlyArray<T>`."
|
|
1151
|
+
kinds: [lint]
|
|
1152
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1153
|
+
failureCodes: ["FE_PROPS_MUTABLE"]
|
|
1154
|
+
enforcers:
|
|
1155
|
+
- {kind: eslint-fe, id: props-fields-readonly}
|
|
1156
|
+
- id: R111
|
|
1157
|
+
code: "HFS_PEER_INTEGRATION_MISSING"
|
|
1158
|
+
law: "An app declares the runtime peer every driver integration it uses needs: when the app root package.json depends on every package of a pair of knowledge/hfs/peer-integrations.yaml (at the named major), it declares the pair's `requires` in its dependencies (`@nestjs/apollo` on `@nestjs/platform-express` 11, Express 5, needs `@as-integrations/express5`)."
|
|
1159
|
+
kinds: [check]
|
|
1160
|
+
gates: [land, ci]
|
|
1161
|
+
failureCodes: ["HFS_PEER_INTEGRATION_MISSING"]
|
|
1162
|
+
enforcers:
|
|
1163
|
+
- {kind: hfs, id: peer-integrations, at: scripts/hfs/rules/peer-integrations.mjs}
|
|
1164
|
+
- id: R112
|
|
1165
|
+
code: "BE_INTEGRATION_SPEC_MISSING"
|
|
1166
|
+
law: "Every integration of a back end (`src/modules/integrations/<provider>/` holding `<provider>.config.ts`) has at least one `src/tests/integration/<provider>/*.integration-spec.ts` that, read as a syntax tree, calls `useTestWorld({ modules })` with a factory registering the integration's own module (`<Provider>Module.register`, imported from the integration folder; an inline array, a local const or an exported const of another file), references the ErrorCode enum the integration exports, and drives an outage through the world (`world.infra.<service>.cut|latency|during|connection`, `world.fake.<name>.failNext`, `world.apps.<peer>.during`, `world.interruptDatabase`); the finding sits on the integration folder."
|
|
1167
|
+
kinds: [check]
|
|
1168
|
+
gates: [land, ci]
|
|
1169
|
+
failureCodes: ["BE_INTEGRATION_SPEC_MISSING"]
|
|
1170
|
+
editions: [full]
|
|
1171
|
+
enforcers:
|
|
1172
|
+
- {kind: hfs, id: integration-specs, at: scripts/hfs/rules/integration-specs.mjs}
|
|
1173
|
+
- id: R113
|
|
1174
|
+
code: "FE_GRAPHQL_CONTRACT"
|
|
1175
|
+
law: "Every GraphQL operation a front end keeps in a `.graphql` file under `fe/` is one its back end serves: it is judged against the contract snapshot (`be/contracts/<service>/schema.graphql`) whose root type declares its first root field, and every selected field, argument and input field must be declared there, every required argument and required input field given, every variable passed with the argument's named type, every leaf selected without and every object with a selection, and every declared variable used; a root field no contract declares is a finding too."
|
|
1176
|
+
kinds: [check]
|
|
1177
|
+
gates: [land, ci]
|
|
1178
|
+
failureCodes: ["FE_GRAPHQL_CONTRACT"]
|
|
1179
|
+
enforcers:
|
|
1180
|
+
- {kind: hfs, id: fe-contract-documents, at: scripts/hfs/rules/fe-contract-documents.mjs}
|
|
1181
|
+
- id: R114
|
|
1182
|
+
code: "RT_EXTERNAL_OWNER"
|
|
1183
|
+
law: "In the runtime repository a process, the network, SQLite or an external binary is reached only by its owner in ruleParams.runtime.infraOwners of knowledge/hfs/runtime-slots.yaml: an owned module specifier, global or spawned program word is used only by the files of scripts/api/<system> or engine/db that the map names."
|
|
1184
|
+
scope: runtime
|
|
1185
|
+
kinds: [check]
|
|
1186
|
+
gates: [land, runtime]
|
|
1187
|
+
failureCodes: ["RT_EXTERNAL_OWNER"]
|
|
1188
|
+
enforcers:
|
|
1189
|
+
- {kind: runtime, id: external-owner, at: scripts/hfs/runtime-rules/external-owner.mjs}
|
|
1190
|
+
- id: R115
|
|
1191
|
+
code: "RT_TIER_DIRECTION"
|
|
1192
|
+
law: "Every relative import between two runtime owners goes from a tier to a tier its mayImport lists in tiers.runtime of knowledge/hfs/runtime-slots.yaml; owner cycles are ARCH_OWNER_CYCLE."
|
|
1193
|
+
scope: runtime
|
|
1194
|
+
kinds: [check]
|
|
1195
|
+
gates: [land, runtime]
|
|
1196
|
+
failureCodes: ["RT_TIER_DIRECTION"]
|
|
1197
|
+
enforcers:
|
|
1198
|
+
- {kind: runtime, id: runtime-tiers, at: scripts/hfs/runtime-rules/tier-direction.mjs}
|
|
1199
|
+
- id: R116
|
|
1200
|
+
code: "RT_BASE_IMPURE"
|
|
1201
|
+
law: "The runtime base tier (the engine foundation and scripts/lib) never writes the filesystem and reads process.env only in a declared seam of ruleParams.runtime.baseEnvSeams."
|
|
1202
|
+
scope: runtime
|
|
1203
|
+
kinds: [check]
|
|
1204
|
+
gates: [land, runtime]
|
|
1205
|
+
failureCodes: ["RT_BASE_IMPURE"]
|
|
1206
|
+
enforcers:
|
|
1207
|
+
- {kind: runtime, id: base-pure, at: scripts/hfs/runtime-rules/base-pure.mjs}
|
|
1208
|
+
- id: R117
|
|
1209
|
+
code: "RT_API_SHAPE"
|
|
1210
|
+
law: "A call file of scripts/api/<system>/ exports exactly one function named in camelCase after the file; only the files of its own system import its lib.mjs; no api system imports another; a system with a calls contract has a call file only for a call id the contract declares."
|
|
1211
|
+
scope: runtime
|
|
1212
|
+
kinds: [check]
|
|
1213
|
+
gates: [land, runtime]
|
|
1214
|
+
failureCodes: ["RT_API_SHAPE"]
|
|
1215
|
+
enforcers:
|
|
1216
|
+
- {kind: runtime, id: api-shape, at: scripts/hfs/runtime-rules/api-shape.mjs}
|
|
1217
|
+
- id: R118
|
|
1218
|
+
code: "RT_SPEC_PLACEMENT"
|
|
1219
|
+
law: "A runtime spec is tests/<area>/<module>[.<topic>].spec.mjs, shared spec code is tests/helpers/<name>.mjs, preloads tests/setup/<name>.mjs and data tests/fixtures/**; a package spec sits beside its source as <name>.spec.*; no file uses the .test. suffix."
|
|
1220
|
+
scope: runtime
|
|
1221
|
+
kinds: [check]
|
|
1222
|
+
gates: [land, runtime]
|
|
1223
|
+
failureCodes: ["RT_SPEC_PLACEMENT"]
|
|
1224
|
+
enforcers:
|
|
1225
|
+
- {kind: runtime, id: spec-placement, at: scripts/hfs/runtime-rules/test-layout.mjs}
|
|
1226
|
+
- id: R119
|
|
1227
|
+
code: "RT_SOURCE_NAME"
|
|
1228
|
+
law: "A runtime source file is named <kebab-name>.mjs, never a one-off of ruleParams.runtime.oneOffNames (_*, tmp-*, fix-*, *-backfill, migrate-*, *-old, *.bak*), and its basename is unique inside its tier except lib.mjs and index.mjs."
|
|
1229
|
+
scope: runtime
|
|
1230
|
+
kinds: [check]
|
|
1231
|
+
gates: [land, runtime]
|
|
1232
|
+
failureCodes: ["RT_SOURCE_NAME"]
|
|
1233
|
+
enforcers:
|
|
1234
|
+
- {kind: runtime, id: source-name, at: scripts/hfs/runtime-rules/source-name.mjs}
|
|
1235
|
+
- id: R120
|
|
1236
|
+
code: "RT_RETIRED_PRESENT"
|
|
1237
|
+
law: "No path of retired[] and no `from` of moved[] in modules/kernel/retired-paths.yaml is tracked again, and no symbol of its retiredSymbols[] is declared in runtime production code."
|
|
1238
|
+
scope: runtime
|
|
1239
|
+
kinds: [check]
|
|
1240
|
+
gates: [land, runtime]
|
|
1241
|
+
failureCodes: ["RT_RETIRED_PRESENT"]
|
|
1242
|
+
enforcers:
|
|
1243
|
+
- {kind: runtime, id: retired-present, at: scripts/hfs/runtime-rules/retired.mjs}
|
|
1244
|
+
- id: R121
|
|
1245
|
+
code: "RT_GENERATED_DRIFT"
|
|
1246
|
+
law: "A generated root of ruleParams.runtime.generated is git-ignored output of its declared generatedBy: the repo's check regenerates every root, the runtime copies (packages/hfs/runtime, packages/eslint/be/runtime, packages/eslint/fe/runtime) are judged equal to what scripts/hfs/sync-runtime.mjs writes from the single source, and no git-tracked path lies under a generated root."
|
|
1247
|
+
scope: runtime
|
|
1248
|
+
kinds: [check, codemod]
|
|
1249
|
+
gates: [land, runtime]
|
|
1250
|
+
failureCodes: ["RT_GENERATED_DRIFT", "GENERATED_UNTRACKED"]
|
|
1251
|
+
enforcers:
|
|
1252
|
+
- {kind: runtime, id: generated-drift, at: scripts/hfs/sync-runtime.mjs}
|
|
1253
|
+
- {kind: runtime, id: generated-untracked, at: scripts/hfs/runtime-rules/generated-untracked.mjs}
|
|
1254
|
+
- id: R122
|
|
1255
|
+
code: "RT_CITED_PATH_MISSING"
|
|
1256
|
+
law: "A runtime path cited by live prose or contracts (modules outside the contract history, docs, skills, knowledge, CONTEXT.md, README.md, CONTRIBUTING.md, init, the ui docs) exists; a retired or moved path is cited only by history."
|
|
1257
|
+
scope: runtime
|
|
1258
|
+
kinds: [check]
|
|
1259
|
+
gates: [land, runtime]
|
|
1260
|
+
failureCodes: ["RT_CITED_PATH_MISSING"]
|
|
1261
|
+
enforcers:
|
|
1262
|
+
- {kind: runtime, id: cited-path, at: scripts/checks/check-contract-cites.mjs}
|
|
1263
|
+
- id: R124
|
|
1264
|
+
code: "RT_PINNED_PATH_MOVED"
|
|
1265
|
+
law: "A pinned path of ruleParams.runtime.pinned (persisted outside git) exists, or moved through a modules/kernel/retired-paths.yaml moved[] entry marked quiesced: true, landed with the workers stopped."
|
|
1266
|
+
scope: runtime
|
|
1267
|
+
kinds: [check]
|
|
1268
|
+
gates: [land, runtime]
|
|
1269
|
+
failureCodes: ["RT_PINNED_PATH_MOVED"]
|
|
1270
|
+
enforcers:
|
|
1271
|
+
- {kind: runtime, id: pinned-path, at: scripts/hfs/runtime-rules/retired.mjs}
|
|
1272
|
+
- id: R125
|
|
1273
|
+
code: "RT_NODE_MODULES_LINK"
|
|
1274
|
+
law: "No runtime source creates a junction or a symlink for a node_modules folder, through node:fs (symlink, symlinkSync, directly or through a local wrapper) or a spawned link command (mklink /J or /D, New-Item -ItemType Junction or SymbolicLink, ln -s): every checkout installs its own dependencies with a real npm ci from the cache."
|
|
1275
|
+
scope: runtime
|
|
1276
|
+
kinds: [check]
|
|
1277
|
+
gates: [land, runtime]
|
|
1278
|
+
failureCodes: ["RT_NODE_MODULES_LINK"]
|
|
1279
|
+
enforcers:
|
|
1280
|
+
- {kind: runtime, id: node-modules-link, at: scripts/hfs/runtime-rules/node-modules-link.mjs}
|
|
1281
|
+
- id: R126
|
|
1282
|
+
code: "RT_CONTROL_CHARACTER"
|
|
1283
|
+
law: "Tracked text source of the runtime (*.mjs, *.cjs, *.js, *.ts, *.tsx, *.yaml, *.yml, *.md, *.json outside the generated copies) holds no raw control character (U+0000 to U+001F except tab, LF and CR, and U+007F); the character is written as its escape."
|
|
1284
|
+
scope: runtime
|
|
1285
|
+
kinds: [check]
|
|
1286
|
+
gates: [land, runtime]
|
|
1287
|
+
failureCodes: ["RT_CONTROL_CHARACTER"]
|
|
1288
|
+
enforcers:
|
|
1289
|
+
- {kind: runtime, id: control-characters, at: scripts/hfs/runtime-rules/control-chars.mjs}
|
|
1290
|
+
- id: R127
|
|
1291
|
+
code: "RT_ABSOLUTE_PATH"
|
|
1292
|
+
law: "No tracked file of the runtime holds a hard-coded absolute host path: a drive-letter path, a user-profile path (/Users/<name>/, /home/<name>/) or an expanded AppData path. Paths resolve from the runtime root, os.tmpdir(), the config or a state-root helper, and a spec builds every path and prompt fixture from its temp dir; %LOCALAPPDATA% as an unexpanded name is clean. In source the rule reads string and template literals and comments through the syntax tree (a URL and a generic drive-pattern regular expression are not paths); in yaml, json and md it reads line text."
|
|
1293
|
+
scope: runtime
|
|
1294
|
+
kinds: [check]
|
|
1295
|
+
gates: [land, runtime]
|
|
1296
|
+
failureCodes: ["RT_ABSOLUTE_PATH"]
|
|
1297
|
+
enforcers:
|
|
1298
|
+
- {kind: runtime, id: absolute-path, at: scripts/hfs/runtime-rules/absolute-path.mjs}
|
|
1299
|
+
- id: R141
|
|
1300
|
+
code: "BE_COOKIE_ATTRIBUTES"
|
|
1301
|
+
law: "A cookie a back-end door writes through the response of Express or Fastify (`cookie`, `setCookie`) states `httpOnly: true`, `secure: true` and `sameSite` `lax` or `strict` in options typed with those literals, and `Set-Cookie` is never written as a header (`setHeader`, `append`, `header`); the call is recognised by where its signature is declared, never by the receiver's name."
|
|
1302
|
+
kinds: [lint]
|
|
1303
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1304
|
+
failureCodes: ["BE_COOKIE_ATTRIBUTES"]
|
|
1305
|
+
enforcers:
|
|
1306
|
+
- {kind: eslint-be, id: response-cookie-attributes}
|
|
1307
|
+
- id: R142
|
|
1308
|
+
code: "BE_AMBIENT_ID"
|
|
1309
|
+
law: "`randomUUID` of Node's crypto (named, namespace or global), the `v1`, `v4`, `v6` and `v7` generators of `uuid`, `nanoid` and `ulid` are used only inside `platform/ids`, specs included; code asks the injected `Ids` port (`ids.next()`) and specs use `fakeIds()`."
|
|
1310
|
+
kinds: [lint, codemod]
|
|
1311
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1312
|
+
failureCodes: ["BE_AMBIENT_ID"]
|
|
1313
|
+
enforcers:
|
|
1314
|
+
- {kind: eslint-be, id: no-ambient-id}
|
|
1315
|
+
- id: R143
|
|
1316
|
+
code: "HFS_MONO_WORKSPACES"
|
|
1317
|
+
law: "Every app is a monorepo, even with one service: the app root package.json declares `workspaces` exactly `[\"fe/apps/*\", \"fe/packages/*\"]`, `packageManager` as `npm@<exact version>` and `turbo` in its devDependencies (at the canon pin), the app root holds the one `package-lock.json` and the managed `turbo.json` (app.task-graph: build, dev, lint and typecheck, build depending on `^build` with cached `.next` and `dist` outputs)."
|
|
1318
|
+
kinds: [check]
|
|
1319
|
+
gates: [settle, land, ci]
|
|
1320
|
+
failureCodes: ["HFS_MONO_WORKSPACES"]
|
|
1321
|
+
enforcers:
|
|
1322
|
+
- {kind: hfs, id: monorepo, at: scripts/hfs/rules/monorepo.mjs}
|
|
1323
|
+
- id: R144
|
|
1324
|
+
code: "HFS_MONO_FE_WORKSPACE"
|
|
1325
|
+
law: "Every fe app `fe/apps/<app>` is an npm workspace: its `package.json` is named `@<project>/<app>`, is private, declares its own runtime dependencies and has exactly the scripts `build: next build`, `dev: next dev`, `lint: starci app lint --workspace .`, `start: next start` and `typecheck: tsc --noEmit`; every fe package `fe/packages/<pkg>` is private with `build`, `typecheck` and the same workspace `lint` script."
|
|
1326
|
+
kinds: [check]
|
|
1327
|
+
gates: [settle, land, ci]
|
|
1328
|
+
failureCodes: ["HFS_MONO_FE_WORKSPACE"]
|
|
1329
|
+
enforcers:
|
|
1330
|
+
- {kind: hfs, id: monorepo, at: scripts/hfs/rules/monorepo.mjs}
|
|
1331
|
+
- id: R145
|
|
1332
|
+
code: "HFS_MONO_NEST_PROJECTS"
|
|
1333
|
+
law: "The back end is a Nest monorepo of `be/apps/<app>`, even with one service: `be/nest-cli.json` is `\"monorepo\": true`, its default project (`root`, `sourceRoot`) is an api app, and its `projects` are exactly the be apps hfs.json declares, each `{\"type\": \"application\", \"root\": \"apps/<app>\", \"sourceRoot\": \"apps/<app>/src\"}`."
|
|
1334
|
+
kinds: [check]
|
|
1335
|
+
gates: [settle, land, ci]
|
|
1336
|
+
failureCodes: ["HFS_MONO_NEST_PROJECTS"]
|
|
1337
|
+
enforcers:
|
|
1338
|
+
- {kind: hfs, id: monorepo, at: scripts/hfs/rules/monorepo.mjs}
|
|
1339
|
+
- id: R146
|
|
1340
|
+
code: "HFS_MONO_WORKSPACE_DEP"
|
|
1341
|
+
law: "An fe workspace declares every package its files import (read with TypeScript's import pre-processor, its tsconfig path aliases excluded) in its own `package.json`, a sibling workspace package at `\"*\"`; the app root `package.json` never declares a workspace package."
|
|
1342
|
+
kinds: [check]
|
|
1343
|
+
gates: [settle, land, ci]
|
|
1344
|
+
failureCodes: ["HFS_MONO_WORKSPACE_DEP"]
|
|
1345
|
+
enforcers:
|
|
1346
|
+
- {kind: hfs, id: monorepo, at: scripts/hfs/rules/monorepo.mjs}
|
|
1347
|
+
- id: R147
|
|
1348
|
+
code: "BE_CLI_REQUIRED"
|
|
1349
|
+
law: "Every one-off action of a back end lives in ONE app, `be/apps/cli` (kind cli, named cli): a back end that declares a connection (its migrations run as `cli migrate run`) or tracks a command under `src/features/cli/` declares exactly that app, and tracks its image `be/apps/cli/Dockerfile` (one image, run as `cli <group> <command>`)."
|
|
1350
|
+
kinds: [check]
|
|
1351
|
+
gates: [settle, land, ci]
|
|
1352
|
+
failureCodes: ["BE_CLI_REQUIRED"]
|
|
1353
|
+
enforcers:
|
|
1354
|
+
- {kind: hfs, id: cli, at: scripts/hfs/rules/cli.mjs}
|
|
1355
|
+
- id: R148
|
|
1356
|
+
code: "BE_CLI_BOOTSTRAP"
|
|
1357
|
+
law: "The cli app boots in its `main.ts` with `CommandFactory.run(...)` of nest-commander and never serves: `NestFactory.create*` of `@nestjs/core`, a `.listen()`, a microservice connection, an `@nestjs/platform-*` adapter or `@nestjs/microservices` in the cli app is refused (by the import that binds each name)."
|
|
1358
|
+
kinds: [lint]
|
|
1359
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1360
|
+
failureCodes: ["BE_CLI_BOOTSTRAP"]
|
|
1361
|
+
enforcers:
|
|
1362
|
+
- {kind: eslint-be, id: cli-bootstrap}
|
|
1363
|
+
- id: R149
|
|
1364
|
+
code: "BE_CLI_COMMAND_SHAPE"
|
|
1365
|
+
law: "A nest-commander command (`@Command` or `@SubCommand`, by the import that binds the decorator) is declared only in the cli feature root `src/features/cli/`: a group `@Command` in `<group>/<group>.cli.ts`, a `@SubCommand` extending `CommandRunner` in `<group>/subs/<name>.cli.ts`, one command per file, and every sub-command has its unit spec `<name>.cli.spec.ts` beside it (a command is an action runner, not a thin transport: R88 does not apply to it). `.cli.ts`, because `.command.ts` is the CQRS message."
|
|
1366
|
+
kinds: [lint]
|
|
1367
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1368
|
+
failureCodes: ["BE_CLI_COMMAND_SHAPE"]
|
|
1369
|
+
enforcers:
|
|
1370
|
+
- {kind: eslint-be, id: cli-command-shape}
|
|
1371
|
+
- {kind: eslint-be, id: unit-test-colocated}
|
|
1372
|
+
- id: R150
|
|
1373
|
+
code: "BE_CLI_OWNER"
|
|
1374
|
+
law: "`nest-commander` is imported only by the cli app and the cli feature root, and no back-end source parses command-line arguments itself: `process.argv` and the parsers commander, yargs and minimist are refused everywhere; a one-off action anywhere but a cli command is a door the rule closes."
|
|
1375
|
+
kinds: [lint]
|
|
1376
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1377
|
+
failureCodes: ["BE_CLI_OWNER"]
|
|
1378
|
+
enforcers:
|
|
1379
|
+
- {kind: eslint-be, id: cli-owner}
|
|
1380
|
+
- id: R151
|
|
1381
|
+
code: "BE_EVENT_CLASS_CONTRACT"
|
|
1382
|
+
law: "Every typed event class `be/src/modules/events/<service>/<event>.event.ts` (one class with literal `static readonly eventName` and `version`, in a file named after its event: dots become dashes) equals an entry of the vendored contract `be/contracts/<service>/events.json` at the same name and version, and every entry of a vendored contract has its class; a class the contract does not list, a version mismatch, a contract entry with no class or a misnamed file is a finding (pattern event-bus)."
|
|
1383
|
+
kinds: [check, codemod]
|
|
1384
|
+
gates: [settle, land, ci]
|
|
1385
|
+
failureCodes: ["BE_EVENT_CLASS_CONTRACT"]
|
|
1386
|
+
editions: [full]
|
|
1387
|
+
enforcers:
|
|
1388
|
+
- {kind: hfs, id: event-class-contract, at: scripts/hfs/rules/event-bus.mjs}
|
|
1389
|
+
- id: R152
|
|
1390
|
+
code: "BE_OUTBOX_WRITE_TX"
|
|
1391
|
+
law: "The outbox is written only by a domain service, with the manager of its own transaction: `eventBus.publish(event, tx)` and a queue producer's `enqueueX(payload, tx)` are called only in a file of `modules/domain` (the test world, e2e and fixtures aside), and `tx` is the `manager` parameter of a `.transaction(async (manager) => ...)` callback or a function parameter typed `EntityManager`; the injected shared manager, an alias, an outer manager or a missing argument is a finding. The call is found by the receiver's TYPE (the `EventBus` port of platform/event-bus) or by the resolved signature's declaring tier (queues), never by a name."
|
|
1392
|
+
kinds: [lint]
|
|
1393
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1394
|
+
failureCodes: ["BE_OUTBOX_WRITE_TX"]
|
|
1395
|
+
editions: [full]
|
|
1396
|
+
enforcers:
|
|
1397
|
+
- {kind: eslint-be, id: outbox-write-tx}
|
|
1398
|
+
- id: R153
|
|
1399
|
+
code: "BE_EVENT_CONSUMER_SHAPE"
|
|
1400
|
+
law: "A class that implements the `EventConsumer` port of platform/event-bus (by type origin) is `transport/message/<event>.consumer.ts` (slot be.transport.message), its file stem is the kebab form of its event class's literal `eventName` (dots become dashes), and its `handle` reads `delivery.eventId` so the dispatched command carries the id the domain service claims in platform/inbox; a `.consumer.ts` that implements no `EventConsumer` is a finding."
|
|
1401
|
+
kinds: [lint]
|
|
1402
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1403
|
+
failureCodes: ["BE_EVENT_CONSUMER_SHAPE"]
|
|
1404
|
+
editions: [full]
|
|
1405
|
+
enforcers:
|
|
1406
|
+
- {kind: eslint-be, id: event-consumer-shape}
|
|
1407
|
+
- id: R154
|
|
1408
|
+
code: "BE_QUEUE_PRODUCER_SHAPE"
|
|
1409
|
+
law: "In `modules/queues/<queue>/<queue>.queue.ts` every exported function or class method that writes the `QueueOutbox` port of platform/queue declares a `tx` parameter typed `EntityManager` and passes that parameter as the first argument of the write; a write with another manager, an outer manager or none is a finding (pattern queue)."
|
|
1410
|
+
kinds: [lint]
|
|
1411
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1412
|
+
failureCodes: ["BE_QUEUE_PRODUCER_SHAPE"]
|
|
1413
|
+
editions: [full]
|
|
1414
|
+
enforcers:
|
|
1415
|
+
- {kind: eslint-be, id: queue-producer-shape}
|
|
1416
|
+
- id: R155
|
|
1417
|
+
code: "BE_PATTERN_SPEC_MISSING"
|
|
1418
|
+
law: "Every pattern a back end declares (hfs.json sides.be.patterns) is proven on the test world: each scenario id of ruleParams.be.patternScenarios.<pattern> has a test titled `<pattern>/<scenario>: ...` (the first argument of `it(` or `test(`) in an e2e or integration spec under be/src/tests; a declared pattern with a missing scenario is a finding."
|
|
1419
|
+
kinds: [check]
|
|
1420
|
+
gates: [land, ci]
|
|
1421
|
+
failureCodes: ["BE_PATTERN_SPEC_MISSING"]
|
|
1422
|
+
editions: [full]
|
|
1423
|
+
enforcers:
|
|
1424
|
+
- {kind: hfs, id: pattern-spec-missing, at: scripts/hfs/rules/event-bus.mjs}
|
|
1425
|
+
- id: R156
|
|
1426
|
+
code: "BE_KIND_ISOLATION"
|
|
1427
|
+
law: "No trigger kind imports another: a feature whose owner slot names one `trigger` (api, webhooks, realtime, saga, reactors, jobs, cli) never imports (static, dynamic, re-export or type-only) a feature of another kind; each kind imports only `modules/*` and only be/apps/* compose features. The kinds meet through the event bus: a domain service publishes `eventBus.publish(event, tx)` and the other kind consumes it. The importing and imported owners are read from the slot view (slot field `trigger`), never from a folder name; the finding replaces BE_FEATURE_IMPORTS_FEATURE when both features name different kinds."
|
|
1428
|
+
kinds: [lint, design]
|
|
1429
|
+
gates: [settle, land, ci]
|
|
1430
|
+
failureCodes: ["BE_KIND_ISOLATION"]
|
|
1431
|
+
enforcers:
|
|
1432
|
+
- {kind: eslint-be, id: kind-isolation}
|
|
1433
|
+
- {kind: machine, id: tiers, at: scripts/hfs/architecture/tiers.mjs}
|
|
1434
|
+
- id: R157
|
|
1435
|
+
code: "BE_JOB_WRITE_OUTSIDE_OWNER"
|
|
1436
|
+
law: "The job entity (the class `platform/jobs` declares in its persistence) is written only by `platform/jobs`: an `update`, `increment`, `decrement`, `save`, `insert`, `upsert`, `delete`, `remove`, `softDelete` or a query-builder write on it in any other file (the test world, e2e and fixtures aside) is a finding. The entity is found by the type origin of the call's arguments or builder chain, never by a name; raw SQL is judged by BE_SQL_TABLE_OWNER (pattern fenced-job)."
|
|
1437
|
+
kinds: [lint]
|
|
1438
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1439
|
+
failureCodes: ["BE_JOB_WRITE_OUTSIDE_OWNER"]
|
|
1440
|
+
editions: [full]
|
|
1441
|
+
enforcers:
|
|
1442
|
+
- {kind: eslint-be, id: job-write-owner}
|
|
1443
|
+
- id: R158
|
|
1444
|
+
code: "BE_JOB_FENCE_REQUIRED"
|
|
1445
|
+
law: "Every `JobClaims` method that takes a job (a parameter type with `jobId`) declares a required, non-nullable `expectedFencingToken: number`; no call on `JobClaims` outside `platform/jobs` casts around it (`as`, a type assertion or `!` in an argument); and a `catch` around a `JobClaims` write rethrows, so a `JobFencedOut` stops the zombie with no further effect (pattern fenced-job)."
|
|
1446
|
+
kinds: [lint]
|
|
1447
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1448
|
+
failureCodes: ["BE_JOB_FENCE_REQUIRED"]
|
|
1449
|
+
editions: [full]
|
|
1450
|
+
enforcers:
|
|
1451
|
+
- {kind: eslint-be, id: job-fence-required}
|
|
1452
|
+
- id: R159
|
|
1453
|
+
code: "BE_JOB_RUN_KEY"
|
|
1454
|
+
law: "In a job step (slot be.feature.jobs.steps) every call on a receiver typed by an `integrations` owner passes an argument of the `RunKey` type of `platform/jobs`, produced by `JobClaims.runKey(job, step)` (it includes the fencing token), never built with a cast; a read belongs in the application layer, not in a step (pattern fenced-job)."
|
|
1455
|
+
kinds: [lint]
|
|
1456
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1457
|
+
failureCodes: ["BE_JOB_RUN_KEY"]
|
|
1458
|
+
editions: [full]
|
|
1459
|
+
enforcers:
|
|
1460
|
+
- {kind: eslint-be, id: job-run-key}
|
|
1461
|
+
- id: R160
|
|
1462
|
+
code: "BE_JOB_SHAPE"
|
|
1463
|
+
law: "A `<job>.processor.ts` exists only in `features/jobs/<job>/transport/queue/`, is named after its job and declares a class extending the `FencedProcessor` of `platform/jobs`; a `steps/<step>.step.ts` exists only in `features/jobs/<job>/steps/` and declares a class implementing `JobStep`; a class implementing `JobStep` exists nowhere else (pattern fenced-job)."
|
|
1464
|
+
kinds: [lint]
|
|
1465
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1466
|
+
failureCodes: ["BE_JOB_SHAPE"]
|
|
1467
|
+
editions: [full]
|
|
1468
|
+
enforcers:
|
|
1469
|
+
- {kind: eslint-be, id: job-shape}
|
|
1470
|
+
- id: R161
|
|
1471
|
+
code: "BE_PROJECTION_WRITE_OWNER"
|
|
1472
|
+
law: "A projection's entity (a class of `<name>.projection-entity.ts` in `modules/projections/<name>/`) is written only by the `<name>.projection.ts` of the same folder (the test world, e2e and fixtures aside): a write on it from a domain service, a handler, another projection or another file of the folder is a finding. The entity is found by the type origin of the write's arguments or builder chain (pattern projection)."
|
|
1473
|
+
kinds: [lint]
|
|
1474
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1475
|
+
failureCodes: ["BE_PROJECTION_WRITE_OWNER"]
|
|
1476
|
+
editions: [full]
|
|
1477
|
+
enforcers:
|
|
1478
|
+
- {kind: eslint-be, id: projection-write-owner}
|
|
1479
|
+
- id: R162
|
|
1480
|
+
code: "BE_PROJECTION_SHAPE"
|
|
1481
|
+
law: "Every public method of a `<name>.projection.ts` class is `recompute*` (idempotent write from the source facts) or `get*` (read); the `index.ts` of a projection exports nothing from a `.projection-entity` file; and a feature whose kind is api calls no `recompute*` method of a projection (it reads through `get*`). The kind is read from the owner slot's `trigger` (pattern projection)."
|
|
1482
|
+
kinds: [lint]
|
|
1483
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1484
|
+
failureCodes: ["BE_PROJECTION_SHAPE"]
|
|
1485
|
+
editions: [full]
|
|
1486
|
+
enforcers:
|
|
1487
|
+
- {kind: eslint-be, id: projection-shape}
|
|
1488
|
+
- id: R163
|
|
1489
|
+
code: "HFS_SERVICE_PLACEMENT"
|
|
1490
|
+
law: "A back-end service lives only at `be/apps/<service>/`: a folder with a `Dockerfile` of its own, or with a `package.json` other than the app root's and those of the fe workspaces (`fe/apps/<app>`, `fe/packages/<pkg>`), outside `be/apps/<service>/` (and outside a front end's `fe/apps/<app>/Dockerfile`) is a second service root and a finding; a service is a Nest app of the one repository that shares the `be/src` libraries and the one package.json, and its image is built from its own Dockerfile."
|
|
1491
|
+
kinds: [check]
|
|
1492
|
+
gates: [land, ci]
|
|
1493
|
+
failureCodes: ["HFS_SERVICE_PLACEMENT"]
|
|
1494
|
+
enforcers:
|
|
1495
|
+
- {kind: hfs, id: services-placement, at: scripts/hfs/rules/services.mjs}
|
|
1496
|
+
- id: R164
|
|
1497
|
+
code: "HFS_IMAGE_UNPINNED"
|
|
1498
|
+
law: "In a product with more than one service (two or more be apps of kind api or worker) every component `image` of `.starcistacks/application-stacks.yaml` is pinned: a digest (`@sha256:<64 hex>`) or an exact version tag (`x.y.z`, optionally with a suffix); no tag, `latest`, a branch word or a major or minor-only tag is a finding. An own service image is `<repo>/<service>:<x.y.z>`, built from `be/apps/<service>/Dockerfile`; a service the product does not own stays a pinned third-party image."
|
|
1499
|
+
kinds: [check]
|
|
1500
|
+
gates: [land, ci]
|
|
1501
|
+
failureCodes: ["HFS_IMAGE_UNPINNED"]
|
|
1502
|
+
enforcers:
|
|
1503
|
+
- {kind: hfs, id: services-image-pins, at: scripts/hfs/rules/services.mjs}
|
|
1504
|
+
- id: R165
|
|
1505
|
+
code: "HFS_SERVICE_STACK_DECLARATION"
|
|
1506
|
+
law: "Every api or worker app `be/apps/<service>` of a product with more than one service is a component of the same name with `role: service` in `.starcistacks/application-stacks.yaml`."
|
|
1507
|
+
kinds: [check]
|
|
1508
|
+
gates: [land, ci]
|
|
1509
|
+
failureCodes: ["HFS_SERVICE_STACK_DECLARATION"]
|
|
1510
|
+
enforcers:
|
|
1511
|
+
- {kind: hfs, id: services-stack-declaration, at: scripts/hfs/rules/services.mjs}
|
|
1512
|
+
- id: R166
|
|
1513
|
+
code: "HFS_EVENT_CONTRACT"
|
|
1514
|
+
law: "The async contract between services is vendored under `be/contracts/<service>/events.json` (`starci/event-contract@1`, emitted by `starci app emit` from the literal `EVENTS` table of `be/apps/<service>/src/events.ts`, never written by hand) and equals that table; an event that undoes the step of another declares `compensates: \"<event>\"` and the named event must exist in a contract; a consumer declares what it reads in the literal `CONSUMES` table of `be/apps/<app>/src/consumes.ts` (service, event, version), every entry must exist in the provider's vendored snapshot at the same version, and a missing or stale snapshot, an unknown event or a version mismatch is a finding."
|
|
1515
|
+
kinds: [check, codemod]
|
|
1516
|
+
gates: [land, ci]
|
|
1517
|
+
failureCodes: ["HFS_EVENT_CONTRACT"]
|
|
1518
|
+
editions: [full]
|
|
1519
|
+
enforcers:
|
|
1520
|
+
- {kind: hfs, id: services-event-contract, at: scripts/hfs/rules/services.mjs}
|
|
1521
|
+
- id: R167
|
|
1522
|
+
code: "BE_ASYNC_SPEC_MISSING"
|
|
1523
|
+
law: "Every event an app consumes (an entry of its `consumes.ts`) is named by an e2e spec, a file `be/src/tests/e2e/<area>/*.e2e-spec.ts` that boots the apps through `useTestWorld` (it publishes the event, reads the effect back and redelivers it); when the contract of the event declares `compensates: \"<event>\"` it is a saga step, and a spec naming it also names the compensated event, so the spec drives the whole flow: the step, the failure, then the compensated state."
|
|
1524
|
+
kinds: [check]
|
|
1525
|
+
gates: [land, ci]
|
|
1526
|
+
failureCodes: ["BE_ASYNC_SPEC_MISSING"]
|
|
1527
|
+
editions: [full]
|
|
1528
|
+
enforcers:
|
|
1529
|
+
- {kind: hfs, id: services-async-specs, at: scripts/hfs/rules/services.mjs}
|
|
1530
|
+
- id: R168
|
|
1531
|
+
code: "BE_SERVICE_ISOLATION"
|
|
1532
|
+
law: "A service app (`be/apps/<service>/`) imports no file of a sibling service app: the app that owns the importing file and the app that owns the file a specifier resolves to (a relative path or an alias of the program's own `compilerOptions.paths`) are read from the HFS slot view, and two different apps is a finding; services share only the `be/src` libraries and meet through the wire (a client of the sibling's API, or the events of its vendored contract under `be/contracts/<service>/`)."
|
|
1533
|
+
kinds: [lint]
|
|
1534
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1535
|
+
failureCodes: ["BE_SERVICE_ISOLATION"]
|
|
1536
|
+
enforcers:
|
|
1537
|
+
- {kind: eslint-be, id: service-isolation}
|
|
1538
|
+
- id: R169
|
|
1539
|
+
code: "BE_SAGA_STEP_COMPENSATION"
|
|
1540
|
+
law: "In a product that declares the `saga` pattern (`patterns: [\"saga\"]` in hfs.json sides.be) a saga is the folder `be/src/features/saga/<saga>/` (`<saga>.saga.ts` the orchestrator, `steps/<step>.step.ts`, `compensations/<step>.compensation.ts`): every step has a compensation of the same name and every compensation a step, the orchestrator imports every step and every compensation of its folder, and a folder of steps or compensations has an orchestrator; the folders themselves are slots enabled only by the declared pattern, so an undeclared saga is `HFS_SLOT_NOT_ENABLED`."
|
|
1541
|
+
kinds: [check]
|
|
1542
|
+
gates: [land, ci]
|
|
1543
|
+
failureCodes: ["BE_SAGA_STEP_COMPENSATION"]
|
|
1544
|
+
editions: [full]
|
|
1545
|
+
enforcers:
|
|
1546
|
+
- {kind: hfs, id: saga-step-compensation, at: scripts/hfs/rules/saga.mjs}
|
|
1547
|
+
- id: R170
|
|
1548
|
+
code: "BE_SAGA_STATE_VERSIONED"
|
|
1549
|
+
law: "Every saga orchestrator has `<saga>.saga-state.ts` beside it, whose exported state type declares `status` and `version: number`, and imports the fenced store of `platform/saga`: the persisted state of a run moves only through the store, every transition naming the version it read, so a zombie delivery cannot move a run twice."
|
|
1550
|
+
kinds: [check]
|
|
1551
|
+
gates: [land, ci]
|
|
1552
|
+
failureCodes: ["BE_SAGA_STATE_VERSIONED"]
|
|
1553
|
+
editions: [full]
|
|
1554
|
+
enforcers:
|
|
1555
|
+
- {kind: hfs, id: saga-state-versioned, at: scripts/hfs/rules/saga.mjs}
|
|
1556
|
+
- id: R171
|
|
1557
|
+
code: "BE_SAGA_EVENT_CONTRACT"
|
|
1558
|
+
law: "A saga step and a saga compensation each name their event (`readonly event = \"<name>\"`), both events are declared in a vendored contract (`be/contracts/<service>/events.json`), and the contract of the compensation's event declares `compensates: \"<event of the step of the same name>\"`."
|
|
1559
|
+
kinds: [check]
|
|
1560
|
+
gates: [land, ci]
|
|
1561
|
+
failureCodes: ["BE_SAGA_EVENT_CONTRACT"]
|
|
1562
|
+
editions: [full]
|
|
1563
|
+
enforcers:
|
|
1564
|
+
- {kind: hfs, id: saga-event-contract, at: scripts/hfs/rules/saga.mjs}
|
|
1565
|
+
- id: R172
|
|
1566
|
+
code: "BE_SAGA_CONSUMER_DEDUPE"
|
|
1567
|
+
law: "A consumer of a saga (`be/src/features/saga/<saga>/transport/message/<event>.consumer.ts`) passes the id of the delivery on (`message.eventId`) to the command it dispatches: the saga takes every event through the inbox (R80), so the consumer must hand it the dedupe key."
|
|
1568
|
+
kinds: [check]
|
|
1569
|
+
gates: [land, ci]
|
|
1570
|
+
failureCodes: ["BE_SAGA_CONSUMER_DEDUPE"]
|
|
1571
|
+
editions: [full]
|
|
1572
|
+
enforcers:
|
|
1573
|
+
- {kind: hfs, id: saga-consumer-dedupe, at: scripts/hfs/rules/saga.mjs}
|
|
1574
|
+
- id: R173
|
|
1575
|
+
code: "BE_SAGA_E2E_MISSING"
|
|
1576
|
+
law: "Every compensation path of a saga has an e2e spec (`be/src/tests/e2e/<area>/*.e2e-spec.ts` through `useTestWorld`) that names the event of the compensation and injects a failure through the world (an outage of an infra service or an app with `during`, `cut` or `latency`, a fake's `failNext`, `interruptDatabase`): the compensation is proven under a fault, not only on the happy flow."
|
|
1577
|
+
kinds: [check]
|
|
1578
|
+
gates: [land, ci]
|
|
1579
|
+
failureCodes: ["BE_SAGA_E2E_MISSING"]
|
|
1580
|
+
editions: [full]
|
|
1581
|
+
enforcers:
|
|
1582
|
+
- {kind: hfs, id: saga-e2e, at: scripts/hfs/rules/saga.mjs}
|
|
1583
|
+
- id: R174
|
|
1584
|
+
code: "BE_CONTEXT_OWNER"
|
|
1585
|
+
law: "A connection of hfs.json is a bounded context owned by one api or worker app (`owner`, `isolation` database or schema): an app composes only the connections and capabilities of the contexts it owns, and the migrate and cli apps compose every declared connection (migrations run only through them, once per connection)."
|
|
1586
|
+
kinds: [lint, design]
|
|
1587
|
+
gates: [settle, land, ci]
|
|
1588
|
+
failureCodes: ["BE_CONTEXT_OWNER"]
|
|
1589
|
+
enforcers:
|
|
1590
|
+
- {kind: eslint-be, id: context-owner}
|
|
1591
|
+
- id: R175
|
|
1592
|
+
code: "BE_CONTEXT_COUPLING"
|
|
1593
|
+
law: "Contexts are coupled only by events: no entity relation, no migration foreign key and no import (entities, services or SQL) of a domain or projection capability reaches a capability of another context."
|
|
1594
|
+
kinds: [lint, design]
|
|
1595
|
+
gates: [settle, land, ci]
|
|
1596
|
+
failureCodes: ["BE_CONTEXT_COUPLING"]
|
|
1597
|
+
enforcers:
|
|
1598
|
+
- {kind: eslint-be, id: context-coupling}
|
|
1599
|
+
- id: R176
|
|
1600
|
+
code: "BE_CONTEXT_TRANSACTION"
|
|
1601
|
+
law: "One transaction touches one context's connection: inside `transaction(async (manager) => ...)` of a connection's entity manager no entity manager of another connection is used and no capability of another context is called; work across contexts is a saga."
|
|
1602
|
+
kinds: [lint, design]
|
|
1603
|
+
gates: [settle, land, ci]
|
|
1604
|
+
failureCodes: ["BE_CONTEXT_TRANSACTION"]
|
|
1605
|
+
enforcers:
|
|
1606
|
+
- {kind: eslint-be, id: context-transaction}
|
|
1607
|
+
- id: R177
|
|
1608
|
+
code: "BE_CONTRACT_BREAKING"
|
|
1609
|
+
law: "An event contract evolves only additively: `be/contracts/<service>/events.json` against its pinned previous copy `events.pin.json` keeps every event, stream, version, payload field and type, adds only optional fields, and a breaking change is a new `<event>.v2` key."
|
|
1610
|
+
kinds: [check]
|
|
1611
|
+
gates: [settle, land, ci]
|
|
1612
|
+
failureCodes: ["BE_CONTRACT_BREAKING"]
|
|
1613
|
+
editions: [full]
|
|
1614
|
+
enforcers:
|
|
1615
|
+
- {kind: hfs, id: contract-compat, at: scripts/hfs/rules/contract-compat.mjs}
|
|
1616
|
+
- id: R178
|
|
1617
|
+
code: "BE_CONTEXT_PLATFORM_TABLES"
|
|
1618
|
+
law: "A platform capability the manifest lists as `perConnection` (the event-bus outbox, the job table) has its entities and migrations registered on every connection whose entity manager is passed to it."
|
|
1619
|
+
kinds: [lint, design]
|
|
1620
|
+
gates: [settle, land, ci]
|
|
1621
|
+
failureCodes: ["BE_CONTEXT_PLATFORM_TABLES"]
|
|
1622
|
+
enforcers:
|
|
1623
|
+
- {kind: eslint-be, id: context-platform-tables}
|
|
1624
|
+
- id: R179
|
|
1625
|
+
code: "BE_KIND_DECLARATION"
|
|
1626
|
+
law: "`hfs.json` `sides.be.kinds` lists exactly the trigger kinds (api, webhooks, realtime, saga, reactors, jobs, cli) the back end uses: a kind in use that is not declared, a declared kind with no feature, a declared kind whose patterns (`ruleParams.be.kindPatterns`, e.g. jobs needs fenced-job and queue) are missing from `sides.be.patterns`, and a declared kind whose platform capabilities (the tier-platform slots naming those patterns) are not tracked are each a finding. A kind is declared only when it is used; `starci app add <noun> <name>` creates the first member, the platform capabilities it needs and the registration."
|
|
1627
|
+
kinds: [check]
|
|
1628
|
+
gates: [settle, land, ci]
|
|
1629
|
+
failureCodes: ["BE_KIND_DECLARATION"]
|
|
1630
|
+
enforcers:
|
|
1631
|
+
- {kind: hfs, id: kind-declaration, at: scripts/hfs/rules/kinds.mjs}
|
|
1632
|
+
- id: R180
|
|
1633
|
+
code: "BE_KIND_EMPTY"
|
|
1634
|
+
law: "A kind folder `be/src/features/<kind>/` holds only instance folders `<kind>/<name>/`, and each instance holds TypeScript source: a file directly in the kind folder (a feature named like a kind, a `.gitkeep`) and an instance with no TypeScript file are findings; an empty kind folder is never kept."
|
|
1635
|
+
kinds: [check]
|
|
1636
|
+
gates: [settle, land, ci]
|
|
1637
|
+
failureCodes: ["BE_KIND_EMPTY"]
|
|
1638
|
+
enforcers:
|
|
1639
|
+
- {kind: hfs, id: kind-empty, at: scripts/hfs/rules/kinds.mjs}
|
|
1640
|
+
- id: R181
|
|
1641
|
+
code: "BE_SQL_RETURNING_SHAPE"
|
|
1642
|
+
law: "A `sql` constant that UPDATEs or DELETEs with RETURNING is wrapped as `WITH changed AS (UPDATE ... RETURNING ...) SELECT ... FROM changed`, because EntityManager.query returns a bare UPDATE or DELETE with RETURNING as `[rows, affectedCount]`, not as rows."
|
|
1643
|
+
kinds: [lint, design]
|
|
1644
|
+
gates: [settle, land, ci]
|
|
1645
|
+
failureCodes: ["BE_SQL_RETURNING_SHAPE"]
|
|
1646
|
+
enforcers:
|
|
1647
|
+
- {kind: eslint-be, id: sql-returning}
|
|
1648
|
+
- id: R182
|
|
1649
|
+
code: "BE_WEBHOOK_SHAPE"
|
|
1650
|
+
law: "A provider webhook door (`src/features/webhooks/<provider>/transport/http/<provider>.webhook.ts`) is one `@Controller` with one `@Post` handler declared `@Public({ reason: PublicReason.SignedWebhook })`; it injects only the `WebhookSignatureService` of `platform/http-security` and services of `modules/domain`, and its handler holds no branch, loop or `try`, calls only `verify` on the signature service and exactly one method of a domain service (the intake that records the delivery and publishes the event in its own transaction), and returns nothing."
|
|
1651
|
+
kinds: [lint]
|
|
1652
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1653
|
+
failureCodes: ["BE_WEBHOOK_SHAPE"]
|
|
1654
|
+
enforcers:
|
|
1655
|
+
- {kind: eslint-be, id: webhook-shape}
|
|
1656
|
+
- id: R183
|
|
1657
|
+
code: "BE_WEBHOOK_UNVERIFIED"
|
|
1658
|
+
law: "The first statement of a webhook handler is the unconditional `verify` call of the `WebhookSignatureService` (signature and replay window): a delivery that fails the proof throws before any other code runs, so no path reaches the domain intake unproven."
|
|
1659
|
+
kinds: [lint]
|
|
1660
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1661
|
+
failureCodes: ["BE_WEBHOOK_UNVERIFIED"]
|
|
1662
|
+
enforcers:
|
|
1663
|
+
- {kind: eslint-be, id: webhook-verify-first}
|
|
1664
|
+
- id: R184
|
|
1665
|
+
code: "BE_REALTIME_SHAPE"
|
|
1666
|
+
law: "A realtime file is the one door its role names: `<channel>.gateway.ts` holds one `@WebSocketGateway` class with `@SubscribeMessage` handlers and `<x>.subscription.ts` holds one `@Resolver` class with `@Subscription` operations, and neither carries a query, a mutation, a field resolver, a REST route or the other protocol."
|
|
1667
|
+
kinds: [lint]
|
|
1668
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1669
|
+
failureCodes: ["BE_REALTIME_SHAPE"]
|
|
1670
|
+
enforcers:
|
|
1671
|
+
- {kind: eslint-be, id: realtime-shape}
|
|
1672
|
+
- id: R185
|
|
1673
|
+
code: "BE_REALTIME_WRITES"
|
|
1674
|
+
law: "A realtime door reads and pushes only: the one value injected into it is the `RealtimeHub` of `platform/realtime`; no `EntityManager`, bus, queue, event bus or domain service reaches it."
|
|
1675
|
+
kinds: [lint]
|
|
1676
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1677
|
+
failureCodes: ["BE_REALTIME_WRITES"]
|
|
1678
|
+
enforcers:
|
|
1679
|
+
- {kind: eslint-be, id: realtime-read-only}
|
|
1680
|
+
- id: R186
|
|
1681
|
+
code: "BE_REALTIME_TOPIC_SCOPE"
|
|
1682
|
+
law: "The topic a realtime handler passes to `RealtimeHub.subscribe` is built from the handler's `@CurrentPrincipal()` parameter of `domain/identity`, so a client can only listen to channels its principal owns."
|
|
1683
|
+
kinds: [lint]
|
|
1684
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1685
|
+
failureCodes: ["BE_REALTIME_TOPIC_SCOPE"]
|
|
1686
|
+
enforcers:
|
|
1687
|
+
- {kind: eslint-be, id: realtime-topic-scope}
|
|
1688
|
+
- id: R187
|
|
1689
|
+
code: "HFS_DOCKER_BUILD_CONTEXT"
|
|
1690
|
+
law: "Every app image is built from the app root: the header comment of an app's Dockerfile states `starci docker build <app>` for its own path, and no COPY or ADD source leaves the build context (a `..` segment or an absolute path)."
|
|
1691
|
+
kinds: [check]
|
|
1692
|
+
gates: [settle, land, ci]
|
|
1693
|
+
failureCodes: ["HFS_DOCKER_BUILD_CONTEXT"]
|
|
1694
|
+
enforcers:
|
|
1695
|
+
- {kind: hfs, id: docker, at: scripts/hfs/rules/docker.mjs}
|
|
1696
|
+
- id: R188
|
|
1697
|
+
code: "HFS_DOCKER_STAGES"
|
|
1698
|
+
law: "An app Dockerfile is multi-stage: a stage named `build` and a last stage named `runtime`; the runtime stage ends as `USER node` and runs no build (`npm run build*`, tsc, next, turbo, nest); every install is `npm ci`, never `npm install`; a back-end runtime runs `npm ci --omit=dev --ignore-scripts` and a front-end runtime installs nothing."
|
|
1699
|
+
kinds: [check]
|
|
1700
|
+
gates: [settle, land, ci]
|
|
1701
|
+
failureCodes: ["HFS_DOCKER_STAGES"]
|
|
1702
|
+
enforcers:
|
|
1703
|
+
- {kind: hfs, id: docker, at: scripts/hfs/rules/docker.mjs}
|
|
1704
|
+
- id: R189
|
|
1705
|
+
code: "HFS_DOCKER_ENTRY"
|
|
1706
|
+
law: "The runtime stage starts the app's own built entry in exec form (be `node be/dist/apps/<app>/src/main.js`, fe `node` on the standalone `server.js` of the app); an api or Next app sets `ENV PORT` and EXPOSEs the same port and carries a HEALTHCHECK, a worker carries a process HEALTHCHECK and exposes nothing, a cli app says `HEALTHCHECK NONE` and exposes nothing; a Next app's build stage runs `turbo run build --filter=@<project>/<app>` and its `next.config.ts` sets `output: \"standalone\"`."
|
|
1707
|
+
kinds: [check]
|
|
1708
|
+
gates: [settle, land, ci]
|
|
1709
|
+
failureCodes: ["HFS_DOCKER_ENTRY"]
|
|
1710
|
+
enforcers:
|
|
1711
|
+
- {kind: hfs, id: docker, at: scripts/hfs/rules/docker.mjs}
|
|
1712
|
+
- id: R190
|
|
1713
|
+
code: "HFS_DOCKER_BASE_PIN"
|
|
1714
|
+
law: "Every FROM of an app Dockerfile is a prior stage, the one node base image of the canon (an exact node version on an exact alpine release, `NODE_IMAGE` in scripts/hfs/rules/docker.mjs), or an image pinned by `@sha256` digest; never a moving tag, never an unresolved build argument."
|
|
1715
|
+
kinds: [check]
|
|
1716
|
+
gates: [settle, land, ci]
|
|
1717
|
+
failureCodes: ["HFS_DOCKER_BASE_PIN"]
|
|
1718
|
+
enforcers:
|
|
1719
|
+
- {kind: hfs, id: docker, at: scripts/hfs/rules/docker.mjs}
|
|
1720
|
+
- id: R191
|
|
1721
|
+
code: "HFS_DOCKER_SECRETS"
|
|
1722
|
+
law: "No secret enters an image: no COPY or ADD of an `.env*` file (except `.env.example`) or of a path under `.starcistacks`, `.secrets`, `.volume`, a key, certificate or kubeconfig; no ADD of a URL; no ARG or ENV whose name is a credential (PASSWORD, SECRET, TOKEN, KEY, CREDENTIAL, PRIVATE), `NEXT_PUBLIC_*` excepted because it is published by design."
|
|
1723
|
+
kinds: [check]
|
|
1724
|
+
gates: [settle, land, ci]
|
|
1725
|
+
failureCodes: ["HFS_DOCKER_SECRETS"]
|
|
1726
|
+
enforcers:
|
|
1727
|
+
- {kind: hfs, id: docker, at: scripts/hfs/rules/docker.mjs}
|
|
1728
|
+
- id: R192
|
|
1729
|
+
code: "RT_FACT_FALSE"
|
|
1730
|
+
law: "Every claim of knowledge/hfs/facts.yaml holds against knowledge/hfs/slots.yaml (the one source of the product shape), and no prose of knowledge/**, docs/** or a README states the opposite of a fact (for example that an fe app lacks its own package.json, which fe.app.next requires); prose quotes a fact id or a slot id and never restates the claim."
|
|
1731
|
+
scope: runtime
|
|
1732
|
+
kinds: [check]
|
|
1733
|
+
gates: [land, runtime]
|
|
1734
|
+
failureCodes: ["RT_FACT_FALSE"]
|
|
1735
|
+
enforcers:
|
|
1736
|
+
- {kind: runtime, id: facts, at: scripts/hfs/runtime-rules/facts.mjs}
|
|
1737
|
+
- id: R193
|
|
1738
|
+
code: "RT_RULE_ID_UNKNOWN"
|
|
1739
|
+
law: "Every rule id (R<digits>) that tracked knowledge, docs, code or data names is a rule of this catalog, and the ids run from R01 to the last rule with no undeclared gap: an id with no rule is listed under `retired` with its reason (RT_RULE_ID_GAP), and every rule of the catalog is named by the `hfsRules:` of at least one knowledge/patterns topic or carries `scope: runtime` (RT_RULE_UNCITED). A retired id is a history name that only the contract changes and the changelogs may still spell; specs and generated copies are not read."
|
|
1740
|
+
scope: runtime
|
|
1741
|
+
kinds: [check]
|
|
1742
|
+
gates: [land, runtime]
|
|
1743
|
+
failureCodes: ["RT_RULE_ID_UNKNOWN", "RT_RULE_ID_GAP", "RT_RULE_UNCITED"]
|
|
1744
|
+
enforcers:
|
|
1745
|
+
- {kind: runtime, id: rule-ids, at: scripts/hfs/runtime-rules/rule-ids.mjs}
|
|
1746
|
+
- id: R194
|
|
1747
|
+
code: "RT_GENERATED_BLOCK_STALE"
|
|
1748
|
+
law: "The app map (section 4), the back-end source tree (5.1), the tier matrix (5.2), the front-end source tree (6.1) and the rule catalog (12) of knowledge/hfs/README.md are generated blocks, rendered by scripts/hfs/readme-blocks.mjs from knowledge/hfs/slots.yaml and rules.yaml between hfs:generated markers and equal to the render byte for byte; the README never restates them by hand. Likewise the `verification.automated` list of a pattern rule is the failure codes of the rules its `hfsRules` names, the `slot` of a `files:` entry is the slot that owns its path, and the `code` of a why-map entry is the code of the catalog rule that lists the lint rule: scripts/hfs/derived-fields.mjs writes them and they equal the derivation byte for byte."
|
|
1749
|
+
scope: runtime
|
|
1750
|
+
kinds: [check]
|
|
1751
|
+
gates: [land, runtime]
|
|
1752
|
+
failureCodes: ["RT_GENERATED_BLOCK_STALE"]
|
|
1753
|
+
enforcers:
|
|
1754
|
+
- {kind: runtime, id: generated-block, at: scripts/hfs/runtime-rules/generated-block.mjs}
|
|
1755
|
+
- id: R195
|
|
1756
|
+
code: "RT_PROSE_PATH_NO_SLOT"
|
|
1757
|
+
law: "Every product path the knowledge, the docs and the READMEs name (a be/ or fe/ path, whole or with placeholders, globs and braces) is owned by a slot of knowledge/hfs/slots.yaml or is a folder above one; a path no slot owns is prose that invented a place or kept one a slot no longer has. App names are free: a literal app name is one the examples or the starter declare, a placeholder stands for any; a ** glob and a topic file of the knowledge are not paths."
|
|
1758
|
+
scope: runtime
|
|
1759
|
+
kinds: [check]
|
|
1760
|
+
gates: [land, runtime]
|
|
1761
|
+
failureCodes: ["RT_PROSE_PATH_NO_SLOT"]
|
|
1762
|
+
enforcers:
|
|
1763
|
+
- {kind: runtime, id: prose-path, at: scripts/hfs/runtime-rules/prose-path.mjs}
|
|
1764
|
+
- id: R196
|
|
1765
|
+
code: "RT_PROSE_RESTATES_SLOTS"
|
|
1766
|
+
law: "Knowledge, docs and READMEs never restate knowledge/hfs/slots.yaml: no fenced block or line names three or more product paths a slot owns, and no sentence lists three or more of the root or side file names the slots declare; a slot is referenced by its id, and the only allowed copy is a generated block between hfs:generated markers. The slot and rule catalogs, the example READMEs and the templates are sources or snapshots, not prose."
|
|
1767
|
+
scope: runtime
|
|
1768
|
+
kinds: [check]
|
|
1769
|
+
gates: [land, runtime]
|
|
1770
|
+
failureCodes: ["RT_PROSE_RESTATES_SLOTS"]
|
|
1771
|
+
enforcers:
|
|
1772
|
+
- {kind: runtime, id: prose-restates, at: scripts/hfs/runtime-rules/prose-restates.mjs}
|
|
1773
|
+
- id: R197
|
|
1774
|
+
code: "RT_RETIRED_CLI_CALL"
|
|
1775
|
+
law: "No tracked file invokes a command the unified CLI removed: the retired kernel, hfs, test-stack and top-level runtime spellings listed in the `removed` fields of the catalog and in the dispatcher table; those fields, changelogs and contract-change history are exempt."
|
|
1776
|
+
scope: runtime
|
|
1777
|
+
kinds: [check]
|
|
1778
|
+
gates: [land, runtime]
|
|
1779
|
+
failureCodes: ["RT_RETIRED_CLI_CALL"]
|
|
1780
|
+
enforcers:
|
|
1781
|
+
- {kind: runtime, id: retired-cli, at: scripts/checks/check-retired-cli.mjs}
|
|
1782
|
+
- id: R198
|
|
1783
|
+
code: "RT_CLI_CATALOG_DRIFT"
|
|
1784
|
+
law: "Every generated output of the unified CLI catalog (packages/cli/src/catalog.generated.mjs, docs/cli.md, packages/cli/completions/*) equals what scripts/cli/gen-catalog.mjs writes from modules/cli/commands."
|
|
1785
|
+
scope: runtime
|
|
1786
|
+
kinds: [check]
|
|
1787
|
+
gates: [land, runtime]
|
|
1788
|
+
failureCodes: ["RT_CLI_CATALOG_DRIFT"]
|
|
1789
|
+
enforcers:
|
|
1790
|
+
- {kind: runtime, id: cli-catalog-drift, at: scripts/cli/gen-catalog.mjs}
|
|
1791
|
+
- id: R199
|
|
1792
|
+
code: "RT_CLI_VERB_PARITY"
|
|
1793
|
+
law: "The CLI catalog of modules/cli/commands and the implementation agree: every catalog verb has a handler and a doc, every kernel verb module and cli.mjs dispatch case has a catalog file, and every --flag a verb module names (usage, required, api-boolean-flags.txt) is a catalog flag."
|
|
1794
|
+
scope: runtime
|
|
1795
|
+
kinds: [check]
|
|
1796
|
+
gates: [land, runtime]
|
|
1797
|
+
failureCodes: ["RT_CLI_VERB_PARITY"]
|
|
1798
|
+
enforcers:
|
|
1799
|
+
- {kind: runtime, id: cli-parity, at: scripts/checks/check-cli-parity.mjs}
|
|
1800
|
+
- id: R200
|
|
1801
|
+
code: "RT_CLI_APP_ONLY_TEMPLATES"
|
|
1802
|
+
law: "Managed app templates and their generated runtime copies invoke product actions only through `starci app <verb>`; retired hfs and test-stack commands, and any other starci group, are forbidden there."
|
|
1803
|
+
scope: runtime
|
|
1804
|
+
kinds: [check]
|
|
1805
|
+
gates: [land, runtime]
|
|
1806
|
+
failureCodes: ["RT_CLI_APP_ONLY_TEMPLATES"]
|
|
1807
|
+
enforcers:
|
|
1808
|
+
- {kind: runtime, id: cli-app-only-templates, at: scripts/hfs/runtime-rules/app-cli-templates.mjs}
|
|
1809
|
+
- id: R201
|
|
1810
|
+
code: "CLI_ONLY_ENTRY"
|
|
1811
|
+
law: "Every action enters runtime code through `starci <group> <verb>`: tracked prompts, docs, skills, knowledge files, configs, CI files, hooks and scheduled-task registrations do not bypass the catalog through direct runtime scripts or package-script wrappers; the dispatcher and the specs' own spawn helpers are the declared exceptions."
|
|
1812
|
+
scope: runtime
|
|
1813
|
+
kinds: [check]
|
|
1814
|
+
gates: [land, runtime]
|
|
1815
|
+
failureCodes: ["CLI_ONLY_ENTRY"]
|
|
1816
|
+
enforcers:
|
|
1817
|
+
- {kind: runtime, id: cli-only-entry, at: scripts/checks/check-cli-only-entry.mjs}
|
|
1818
|
+
- id: R202
|
|
1819
|
+
code: "CI_UPLOAD_NOT_SILENT"
|
|
1820
|
+
law: "A coverage upload of a CI workflow never skips or fails silently: every step that uses codecov/codecov-action, in a root workflow, an example workflow or a template workflow of packages/hfs, authenticates by OIDC (`use_oidc: true`, with `permissions: id-token: write` on the job or the workflow), sets `fail_ci_if_error: true`, has no `continue-on-error`, and has no `if` that reads a secret or CODECOV_TOKEN, so a missing token can never turn the upload into a skipped step."
|
|
1821
|
+
scope: runtime
|
|
1822
|
+
kinds: [check]
|
|
1823
|
+
gates: [land, runtime]
|
|
1824
|
+
failureCodes: ["CI_UPLOAD_NOT_SILENT"]
|
|
1825
|
+
enforcers:
|
|
1826
|
+
- {kind: runtime, id: ci-upload, at: scripts/hfs/runtime-rules/ci-upload.mjs}
|
|
1827
|
+
- id: R203
|
|
1828
|
+
code: "BE_FEATURE_THIN"
|
|
1829
|
+
law: "A feature file (a role of ruleParams.be.thinRoles inside a slot of tier feature: handler, resolver, controller, consumer, processor, step, saga, saga-step, compensation, webhook, gateway, subscription, cli, mapper, the saga orchestrator service) maps its parameters and makes ONE delegating call into modules/ (a service, the command or query bus, the event bus, a queue producer): no branch, loop, ternary, try or throw and no computation. Business logic lives in modules/**, where every logic file owes 100 per file; orchestration (step order, compensation, claim, fencing, retry) lives in platform/*; the feature layer is proven by integration and e2e. The gate calls a kind requires are not delegation: a webhook's `verify` and a job step's `JobClaims` fence (`advance`, `runKey`); a logger call, a pure mapper function and a modules function in argument position are free too; a mapper may map a collection item by item (`map`) and write a date as text (`toISOString`), and a cli group command shows its help."
|
|
1830
|
+
kinds: [lint]
|
|
1831
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1832
|
+
failureCodes: ["BE_FEATURE_THIN"]
|
|
1833
|
+
enforcers:
|
|
1834
|
+
- {kind: eslint-be, id: feature-thin}
|
|
1835
|
+
- id: R204
|
|
1836
|
+
code: "HFS_COVERAGE_SCOPE_DRIFT"
|
|
1837
|
+
law: "What a back end measures at 100 per file is derived once from the slot manifest (the `coverage: required|none` field of every be slot and ruleParams.be.logicRoles, scripts/hfs/coverage-scope.mjs), and the three files that state it are its render: be/jest.config.js (thresholds), the `sonar.coverage.exclusions` line of sonar-project.properties, and codecov.yml (paths and one component per service app plus platform, each at 100). A difference is drift; the scope is never edited by hand."
|
|
1838
|
+
kinds: [check, codemod]
|
|
1839
|
+
gates: [pre-push, land, ci]
|
|
1840
|
+
failureCodes: ["HFS_COVERAGE_SCOPE_DRIFT"]
|
|
1841
|
+
enforcers:
|
|
1842
|
+
- {kind: hfs, id: coverage-scope-drift, at: packages/hfs/sync/managed.mjs}
|
|
1843
|
+
- id: R205
|
|
1844
|
+
code: "RT_DOC_NO_OWNER"
|
|
1845
|
+
law: "Every tracked docs/*.md declares exactly one ownership header on its first line — \"Owner: <knowledge/ or modules/ path that exists>\" when the document explains a machine-owned source, \"Task: <one task>\" for a how-to — no two documents carry the same task, and two documents whose titles share two or more topic words while at least 30% of the shorter one's sentences appear in the other are one document merged, not two."
|
|
1846
|
+
scope: runtime
|
|
1847
|
+
kinds: [check]
|
|
1848
|
+
gates: [land, runtime]
|
|
1849
|
+
failureCodes: ["RT_DOC_NO_OWNER"]
|
|
1850
|
+
enforcers:
|
|
1851
|
+
- {kind: runtime, id: doc-owner, at: scripts/checks/check-doc-owner.mjs}
|
|
1852
|
+
- id: R206
|
|
1853
|
+
code: "RT_EXAMPLE_COUPLING"
|
|
1854
|
+
law: "Runtime source never couples to a product or its example: no tracked file under scripts/, engine/, packages/, ui/src or ui/api - and no managed template under packages/hfs/templates/ - spells a literal examples/<name>, one of the product names this runtime shipped against (the closed PRODUCT_NAMES list of scripts/lib/example-refs.mjs), an inc-<hash> a product name prefixes, or a host drive path. Specs, tests, the generated runtime copies, contract-change history, changelogs and .starciwork records are out of scope; a read the runtime genuinely makes at run time is declared once in scripts/lib/example-refs.mjs and every consumer names that constant."
|
|
1855
|
+
scope: runtime
|
|
1856
|
+
kinds: [check]
|
|
1857
|
+
gates: [land, runtime]
|
|
1858
|
+
failureCodes: ["RT_EXAMPLE_COUPLING", "RT_PRODUCT_NAME_IN_SOURCE", "RT_HOST_PATH_IN_TEMPLATE"]
|
|
1859
|
+
enforcers:
|
|
1860
|
+
- {kind: runtime, id: example-coupling, at: scripts/checks/check-example-coupling.mjs}
|
|
1861
|
+
- id: R207
|
|
1862
|
+
code: "RT_RETIRED_NAME_LIVE"
|
|
1863
|
+
law: "A name the retired registry declares dead never appears in a live tracked file — not in prose, a comment, a string literal or a path: every retired[].path and moved[].from of modules/kernel/retired-paths.yaml, and every retiredNames[] naming that is not a path (a deleted app, a layer naming, a verb prefix). History (the contract changes, a changelog, a benchmark finding, a .starciwork record), the registry itself and the files of the check may name what was deleted; a slot manifest's forbids value or forbidden-presence tombstone declares a refusal, not a use."
|
|
1864
|
+
scope: runtime
|
|
1865
|
+
kinds: [check]
|
|
1866
|
+
gates: [land, runtime]
|
|
1867
|
+
failureCodes: ["RT_RETIRED_NAME_LIVE"]
|
|
1868
|
+
enforcers:
|
|
1869
|
+
- {kind: runtime, id: retired-names, at: scripts/checks/check-retired-names.mjs}
|
|
1870
|
+
- id: R208
|
|
1871
|
+
code: "RT_PORT_RESTATED"
|
|
1872
|
+
law: "Every port literal of the runtime is spelled once, by its owner, and every other file references the owner: the harness UI ports live in modules/models/runtimes.yaml statusApp.port/devPort read through ui/ports.mjs, the local SonarQube host in scripts/gates/sonar-local.mjs DEFAULT_HOST. No in-scope file that is not the owner spells an owned literal in a port position (a scheme://host:NNNN or host:NNNN authority, a *port*/PORT assignment, a listen(NNNN) call), and an unowned literal in a port position of two or more runtime source files gets one owning exported constant the rest reference. Specs, tests, node_modules, the generated runtime copies, contract-change history, changelogs, .starciwork and the product stack declarations (.starcistacks, starcistacks-services - a product owns the ports of the services it runs) are out of scope."
|
|
1873
|
+
scope: runtime
|
|
1874
|
+
kinds: [check]
|
|
1875
|
+
gates: [land, runtime]
|
|
1876
|
+
failureCodes: ["RT_PORT_RESTATED"]
|
|
1877
|
+
enforcers:
|
|
1878
|
+
- {kind: runtime, id: port-once, at: scripts/checks/check-port-once.mjs}
|
|
1879
|
+
- id: R209
|
|
1880
|
+
code: "RT_CONFIG_DEFAULT_TWICE"
|
|
1881
|
+
law: "Every documented configuration default is stated once, by its owner, and read through it: a camelCase leaf or lowercase key of config.example.yaml, or a value an owner declares - scripts/machine/home.mjs DEFAULTS (supervisor workers, the land gate, frozenMinutes, pollIntervalMs), engine/config.mjs (connector, ask, UAT, spec and allocation defaults, and DEFAULT_OWNER_LANGUAGE which scripts/machine/home.mjs re-exports), engine/orca-config.mjs (worker depth), scripts/reconciler/state.mjs (the controller mode) - is never restated as a literal fallback (?? or ||, a parameter default, or a language ternary) in any other runtime source file; the reader calls the owner accessor (supervisorSettings(), configuredMode(), ownerLanguage(), ...). Specs, tests, the generated runtime copies, stories and the HFS templates are out of scope, and a bare identifier or a computed value is a read, not a restatement."
|
|
1882
|
+
scope: runtime
|
|
1883
|
+
kinds: [check]
|
|
1884
|
+
gates: [land, runtime]
|
|
1885
|
+
failureCodes: ["RT_CONFIG_DEFAULT_TWICE"]
|
|
1886
|
+
enforcers:
|
|
1887
|
+
- {kind: runtime, id: default-once, at: scripts/checks/check-default-once.mjs}
|
|
1888
|
+
- id: R210
|
|
1889
|
+
code: "RT_VERSION_RESTATED"
|
|
1890
|
+
law: "knowledge/hfs/canon-pins.yaml spells the version of every @starci package and every canon-pinned dependency once; a package.json is the declared install site and a lockfile its resolution. Anywhere else a semver literal equal to a pin - or a name@x.y.z specifier naming one - restates it, except on the declared binding keys a refresher owns and rewrites in place (canon.version of the code-pattern bindings held by check-canon-pins.mjs, provenance.version and identity.version of the grammar snapshots rewritten by scripts/work/ui/grammar-knowledge.mjs). Package manifests, lockfiles, changelogs, contract-change history, specs, tests, the generated runtime copies and .starciwork are out of scope; a restatement is fixed by naming the pin, never the literal."
|
|
1891
|
+
scope: runtime
|
|
1892
|
+
kinds: [check]
|
|
1893
|
+
gates: [land, runtime]
|
|
1894
|
+
failureCodes: ["RT_VERSION_RESTATED"]
|
|
1895
|
+
enforcers:
|
|
1896
|
+
- {kind: runtime, id: version-pin-once, at: scripts/checks/check-version-pin-once.mjs}
|
|
1897
|
+
- id: R211
|
|
1898
|
+
code: "RT_SLOT_ID_SHAPE"
|
|
1899
|
+
law: "A slot id follows the grammar its manifest declares, read from the manifest not from a list: dot-separated lowercase kebab segments, a first segment of naming.prefixes in knowledge/hfs/slots.yaml (profiles in knowledge/hfs/runtime-slots.yaml), <side>.app.<kind> naming a kind of appKinds.<side>, and every deeper id extending a declared id - an explicit parent field, or a family at least two slots share; a lone deep orphan invents a family. The role-suffix vocabulary holds no synonym pairs: a profile's suffixes never share a word with bannedSuffixes, no naming.refusedPairs pair is live in suffixes on both sides, and naming.sameConceptPairs is the one declared exception (step + saga-step), live on both sides or dropped; naming.glossary binds the five incoming-thing names (consumer, subscription, webhook, processor, handler) to their distinct kinds."
|
|
1900
|
+
scope: runtime
|
|
1901
|
+
kinds: [check]
|
|
1902
|
+
gates: [land, runtime]
|
|
1903
|
+
failureCodes: ["RT_SLOT_ID_SHAPE"]
|
|
1904
|
+
enforcers:
|
|
1905
|
+
- {kind: runtime, id: slot-id-shape, at: scripts/checks/check-slot-id-shape.mjs}
|
|
1906
|
+
- id: R212
|
|
1907
|
+
code: "HFS_EDITION_INVALID"
|
|
1908
|
+
law: "hfs.json names an edition the manifest knows (full, or lite; absent means full), and a lite app holds none of what lite does not have: no spec or test file, test script, test dependency or test-tool config, no UAT record, no supabase/functions, no worker app, no event, queue, job, projection or saga pattern, no saga, reactors, jobs or realtime kind."
|
|
1909
|
+
kinds: [check]
|
|
1910
|
+
gates: [pre-push, settle, land, ci]
|
|
1911
|
+
failureCodes: ["HFS_EDITION_INVALID", "HFS_EDITION_FORBIDDEN_PRESENT"]
|
|
1912
|
+
enforcers:
|
|
1913
|
+
- {kind: hfs, id: edition, at: scripts/hfs/rules/edition.mjs}
|
|
1914
|
+
- id: R213
|
|
1915
|
+
code: "DB_MIGRATION_SHAPE"
|
|
1916
|
+
law: "A migration is `supabase/migrations/<ts14>_<kebab>.sql`; the stamp is a valid UTC instant not in the future, new migrations sort strictly after every migration on the base branch, and a migration already on the base is immutable (a diff or a removal is a finding); a file that does not parse as PostgreSQL is a shape failure."
|
|
1917
|
+
kinds: [check]
|
|
1918
|
+
gates: [pre-push, settle, land, ci]
|
|
1919
|
+
failureCodes: ["DB_MIGRATION_SHAPE"]
|
|
1920
|
+
enforcers:
|
|
1921
|
+
- {kind: hfs, id: db-migration-shape, at: scripts/hfs/rules/database.mjs}
|
|
1922
|
+
- id: R214
|
|
1923
|
+
code: "DB_RLS_REQUIRED"
|
|
1924
|
+
law: "Row level security is code a parser can read: every `create table` (or `create table as`) in an exposed schema (`public` plus the config.toml `[api] schemas`) is followed, in the same migration, by `alter table <t> enable row level security` (and `force` for a table hfs.json `supabase.forceRls` declares); no `DO` block or function runs dynamic SQL that builds DDL or grants; a write or `for all` policy never uses `(true)`, `to anon`/`to public` is legal only on a `for select` policy named `*_public_read`, every policy is named `<table>_<role>_<action>`, and `service_role` is never granted."
|
|
1925
|
+
kinds: [check, codemod]
|
|
1926
|
+
gates: [pre-push, settle, land, ci]
|
|
1927
|
+
failureCodes: ["DB_RLS_REQUIRED", "DB_DYNAMIC_DDL", "DB_POLICY_SHAPE"]
|
|
1928
|
+
enforcers:
|
|
1929
|
+
- {kind: hfs, id: db-rls, at: scripts/hfs/rules/database.mjs}
|
|
1930
|
+
- id: R215
|
|
1931
|
+
code: "DB_DEFINER_SAFE"
|
|
1932
|
+
law: "Privileged database objects are declared safely: a `security definer` function pins `search_path` (never `from current`), lives outside the exposed schemas or is `revoke execute ... from public` in the same migration, declares a return type and interpolates parameters into dynamic SQL only through `format('%I/%L')`; a bucket inserted into `storage.buckets` declares `public = false` (true only for an id ending `_public`), `file_size_limit` and `allowed_mime_types`, and has a `storage.objects` policy naming its `bucket_id` in the same migration."
|
|
1933
|
+
kinds: [check]
|
|
1934
|
+
gates: [pre-push, settle, land, ci]
|
|
1935
|
+
failureCodes: ["DB_DEFINER_SAFE", "DB_STORAGE_POLICY"]
|
|
1936
|
+
enforcers:
|
|
1937
|
+
- {kind: hfs, id: db-privileged-objects, at: scripts/hfs/rules/database.mjs}
|
|
1938
|
+
- id: R216
|
|
1939
|
+
code: "DB_CONFIG_POLICY"
|
|
1940
|
+
law: "The database configuration and its types are checked facts: `supabase/config.toml` carries no literal value under a credential key (the value is `env(NAME)`), `auth.jwt_expiry` is at most 3600, and signup, site url and redirect urls equal the Supabase block of hfs.json where it declares them; `supabase/types/database.types.ts` is committed and equals the text `contract:emit` regenerates from the migrations."
|
|
1941
|
+
kinds: [check]
|
|
1942
|
+
gates: [land, ci]
|
|
1943
|
+
failureCodes: ["DB_CONFIG_POLICY", "DB_TYPES_DRIFT"]
|
|
1944
|
+
enforcers:
|
|
1945
|
+
- {kind: hfs, id: db-config-types, at: scripts/hfs/rules/database.mjs}
|
|
1946
|
+
- id: R217
|
|
1947
|
+
code: "FE_SUPABASE_CLIENT_OWNER"
|
|
1948
|
+
law: "The database has one door per side: `@supabase/ssr` and `@supabase/supabase-js` are imported and called only by the fe db owner (`fe.modules.db`, `fe.package.db`) and, on the back end, by `be.integrations.supabase`, and every client carries the committed `Database` type; the front end has no route handler beyond health/live and the auth callback slot, and holds no credential: `fe/` names no service-role or `*_SECRET`, `*_PRIVATE`, `*_TOKEN`, `*_PASSWORD` value (public configuration is read once by `modules/config`)."
|
|
1949
|
+
kinds: [check, design]
|
|
1950
|
+
gates: [pre-push, settle, land, ci]
|
|
1951
|
+
failureCodes: ["FE_SUPABASE_CLIENT_OWNER", "BE_SUPABASE_CLIENT_OWNER", "FE_ROUTE_HANDLER_FORBIDDEN", "FE_SERVICE_ROLE_FORBIDDEN"]
|
|
1952
|
+
enforcers:
|
|
1953
|
+
- {kind: machine, id: supabase-client-owner, at: scripts/hfs/architecture/supabase.mjs}
|
|
1954
|
+
- {kind: machine, id: supabase-service-role, at: scripts/hfs/architecture/supabase.mjs}
|
|
1955
|
+
- id: R218
|
|
1956
|
+
code: "FE_DB_RESULT_TYPED"
|
|
1957
|
+
law: "A Supabase result is typed by `Database` inference and never swallowed: no explicit generic on `single`/`maybeSingle`/`returns`, no cast or non-null assertion on a result, every list read carries `.limit()` or a keyset bound, every awaited call reads `error` or hands the whole result to `toOutcome`, and a failure never becomes empty data."
|
|
1958
|
+
kinds: [check, design]
|
|
1959
|
+
gates: [pre-push, settle, land, ci]
|
|
1960
|
+
failureCodes: ["FE_DB_RESULT_TYPED", "FE_DB_ERROR_HANDLED"]
|
|
1961
|
+
enforcers:
|
|
1962
|
+
- {kind: machine, id: supabase-result, at: scripts/hfs/architecture/supabase.mjs}
|
|
1963
|
+
- id: R219
|
|
1964
|
+
code: "FE_AUTH_SESSION_TRUST"
|
|
1965
|
+
law: "The principal is verified, never trusted: server code never calls `auth.getSession()` (`getClaims()` through `getPrincipal()`, `auth.getUser()` for money, account security and role changes); a `write-*.ts` Server Action awaits `getPrincipal()` first, refuses an anonymous principal (the sign-in action is the one exception) and parses its input by a schema before any use; a back-end principal comes only from a Supabase access token verified against the project JWKS (signature, issuer, audience, expiry, pinned algorithm), never decoded unverified and never from the request body."
|
|
1966
|
+
kinds: [check, design]
|
|
1967
|
+
gates: [pre-push, settle, land, ci]
|
|
1968
|
+
failureCodes: ["FE_AUTH_SESSION_TRUST", "FE_DB_WRITE_SHAPE", "BE_SUPABASE_JWT_VERIFIED"]
|
|
1969
|
+
enforcers:
|
|
1970
|
+
- {kind: machine, id: supabase-principal, at: scripts/hfs/architecture/supabase.mjs}
|
|
1971
|
+
- id: R220
|
|
1972
|
+
code: "HFS_LITE_SECRET_CUSTODY"
|
|
1973
|
+
editions: [lite]
|
|
1974
|
+
law: "A lite app holds no secret material in the tree: root `.env*`, `secrets.env`, `.secrets/` and an `.enc` file outside `.starcistacks/<env>/secrets` are forbidden, and `fe/` and `supabase/` hold no Supabase token, JWT or connection string with a password; the one custody is the sealed `.starcistacks/<env>/secrets/<slug>.enc`."
|
|
1975
|
+
kinds: [check]
|
|
1976
|
+
gates: [pre-commit, pre-push, settle, land, ci]
|
|
1977
|
+
failureCodes: ["HFS_LITE_SECRET_CUSTODY"]
|
|
1978
|
+
enforcers:
|
|
1979
|
+
- {kind: hfs, id: lite-secret-custody, at: scripts/hfs/rules/supabase-secrets.mjs}
|
|
1980
|
+
- id: R221
|
|
1981
|
+
code: "CI_TRIGGERS_RELEASE_ONLY"
|
|
1982
|
+
law: "The only workflow triggers are a push of release tags and a person dispatching it: every tracked workflow of the runtime, its examples and the hfs app templates (so every scaffolded app inherits it) declares an `on` that holds only `push` filtered to `tags: ['v*']` (no branches, paths or ignore filter beside it) and `workflow_dispatch`. A branch push, a pull_request, a schedule, a workflow_call or any other event refuses the workflow: CI runs once per release, on its tag, and Codecov and Sonar upload only from that run."
|
|
1983
|
+
scope: runtime
|
|
1984
|
+
kinds: [check]
|
|
1985
|
+
gates: [land, runtime]
|
|
1986
|
+
failureCodes: ["CI_TRIGGERS_RELEASE_ONLY"]
|
|
1987
|
+
enforcers:
|
|
1988
|
+
- {kind: runtime, id: git-triggers, at: scripts/hfs/runtime-rules/git-triggers.mjs}
|
|
1989
|
+
- id: R222
|
|
1990
|
+
code: "RELEASE_NOTES"
|
|
1991
|
+
law: "A release tag `v<version>` on HEAD has its CHANGELOG.md section `## [<version>]` with no TODO, PENDING or TBD left in it and no `in preparation` mark: the tag message and the GitHub Release are that section, so a tag is never cut over an unfinished or missing section."
|
|
1992
|
+
scope: runtime
|
|
1993
|
+
kinds: [check]
|
|
1994
|
+
gates: [land, runtime]
|
|
1995
|
+
failureCodes: ["RELEASE_NOTES"]
|
|
1996
|
+
enforcers:
|
|
1997
|
+
- {kind: runtime, id: release-notes, at: scripts/hfs/runtime-rules/release-notes.mjs}
|
|
1998
|
+
- id: R223
|
|
1999
|
+
code: "RIGHTS_ROLE_DENIED"
|
|
2000
|
+
law: "By the caller's role, the command guard refuses git push, git tag, raw git commit/add/rm/mv, raw git synchronization writes, npm publish, a whole-suite run outside the release cut and unit.verify/e2e.verify, npm ci without the host lock, a release cut, and raw side-effecting tools; modules/kernel/command-policy.yaml is the one rule table read by both the PreToolUse guard and the PATH shim."
|
|
2001
|
+
scope: runtime
|
|
2002
|
+
kinds: [check]
|
|
2003
|
+
gates: [land, runtime]
|
|
2004
|
+
failureCodes: ["RIGHTS_ROLE_DENIED", "RIGHTS_GIT_PUSH", "RIGHTS_GIT_TAG", "RIGHTS_GIT_COMMIT", "RIGHTS_GIT_SYNC", "RIGHTS_NPM_PUBLISH", "RIGHTS_NPM_CI_UNLOCKED", "RIGHTS_SUITE_RUN", "RIGHTS_RELEASE_CUT", "RIGHTS_RAW_TOOL"]
|
|
2005
|
+
enforcers:
|
|
2006
|
+
- {kind: runtime, id: role-rights, at: scripts/hfs/runtime-rules/rights-policy.mjs}
|
|
2007
|
+
- id: R224
|
|
2008
|
+
code: "RIGHTS_PROTECTED_ZONE"
|
|
2009
|
+
law: "The protected zone declared once in modules/kernel/protected-zone.yaml is edited only by the owner path; a supervisor self-upgrade files an owner proposal, and an op never writes the .claude runtime checkout."
|
|
2010
|
+
scope: runtime
|
|
2011
|
+
kinds: [check]
|
|
2012
|
+
gates: [land, runtime]
|
|
2013
|
+
failureCodes: ["RIGHTS_PROTECTED_ZONE", "RIGHTS_OP_RUNTIME_WRITE"]
|
|
2014
|
+
enforcers:
|
|
2015
|
+
- {kind: runtime, id: protected-zone, at: scripts/hfs/runtime-rules/rights-policy.mjs}
|
|
2016
|
+
- id: R225
|
|
2017
|
+
code: "RT_HOOK_SHAPE"
|
|
2018
|
+
law: "The app hook templates keep the gate model: pre-commit is L0 only (no typecheck, no test run); pre-push checks the release gate (the starci-release L4 record, refs/backup/, v[0-9] tags) and runs no npm test, jest, typecheck or lint."
|
|
2019
|
+
scope: runtime
|
|
2020
|
+
kinds: [check]
|
|
2021
|
+
gates: [land, runtime]
|
|
2022
|
+
failureCodes: ["RT_HOOK_SHAPE", "RIGHTS_PUSH_NOT_RELEASE"]
|
|
2023
|
+
enforcers:
|
|
2024
|
+
- {kind: runtime, id: hook-shape, at: scripts/hfs/runtime-rules/hook-shape.mjs}
|
|
2025
|
+
- id: R226
|
|
2026
|
+
code: "RT_SPEC_OVER_BUDGET"
|
|
2027
|
+
law: "Every recorded spec duration should stay within the shared per-file land budget in modules/kernel/spec-durations.yaml: for alpha.4 an over-budget spec is an advisory info finding ranked by its excess (it does not fail the check stage; the budget turns blocking in alpha.5), and every recorded spec path must still exist. An over-budget spec is repaired without removing tests or assertions: share a per-process fixture, remove per-test install or boot, inject the clock and never lengthen a timeout; stale rows are refreshed from the current land record."
|
|
2028
|
+
scope: runtime
|
|
2029
|
+
kinds: [check]
|
|
2030
|
+
gates: [land, runtime]
|
|
2031
|
+
failureCodes: ["RT_SPEC_OVER_BUDGET", "RT_SPEC_BUDGET_STALE"]
|
|
2032
|
+
enforcers:
|
|
2033
|
+
- {kind: runtime, id: spec-budget, at: scripts/checks/check-spec-budget.mjs}
|
|
2034
|
+
- id: R227
|
|
2035
|
+
code: "RT_SYNTAX_INVALID"
|
|
2036
|
+
law: "Every git-tracked .mjs, .cjs and .js file parses as JavaScript, including tests and package sources; node_modules, generated roots of ruleParams.runtime.generated and vendored build output are outside the authored-source scope. The runtime folder's Node-exact syntax pass remains in place, while this batched in-process pass extends coverage to the whole tracked repository and reports every parser diagnostic at its file and line."
|
|
2037
|
+
scope: runtime
|
|
2038
|
+
kinds: [check]
|
|
2039
|
+
gates: [land, runtime]
|
|
2040
|
+
failureCodes: ["RT_SYNTAX_INVALID"]
|
|
2041
|
+
enforcers:
|
|
2042
|
+
- {kind: runtime, id: syntax, at: scripts/hfs/runtime-rules/syntax.mjs}
|
|
2043
|
+
retired:
|
|
2044
|
+
- {id: R66, reason: "FE_E2E_SHAPE retired with eslint-canon-fe 7.0.0: a front end has no tests by standard"}
|
|
2045
|
+
- {id: R67, reason: "FE_SPEC_QUALITY retired with eslint-canon-fe 7.0.0: a front end has no tests by standard"}
|
|
2046
|
+
- {id: R123, reason: "RT_PENDING_STALE retired with the pending list itself: ruleParams.runtime.pending and its pending-ratchet enforcer are gone; nothing ratchets a tolerated-finding list"}
|
|
2047
|
+
- {id: R128, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2048
|
+
- {id: R129, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2049
|
+
- {id: R130, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2050
|
+
- {id: R131, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2051
|
+
- {id: R132, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2052
|
+
- {id: R133, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2053
|
+
- {id: R134, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2054
|
+
- {id: R135, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2055
|
+
- {id: R136, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2056
|
+
- {id: R137, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2057
|
+
- {id: R138, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2058
|
+
- {id: R139, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|
|
2059
|
+
- {id: R140, reason: "skipped when the shape block was renumbered past the ids of main (R143 up); never the id of a landed rule"}
|