@starci/eslint-grammar-core 0.15.8 → 0.15.11
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 +13 -0
- package/package.json +2 -2
- package/runtime/knowledge/hfs/canon-pins.yaml +54 -24
- package/runtime/knowledge/hfs/rules.yaml +248 -155
- package/runtime/knowledge/hfs/slots.yaml +185 -174
- package/runtime/scripts/hfs/architecture/side-boundary.mjs +1 -1
- package/runtime/scripts/hfs/declaration-shape.mjs +15 -18
- package/runtime/scripts/hfs/manifest-shape.mjs +4 -4
- package/runtime/scripts/hfs/rule-params-shape.mjs +4 -1
- package/runtime/scripts/hfs/slot-app-view.mjs +1 -1
- package/runtime/scripts/hfs/slots.mjs +8 -2
|
@@ -168,7 +168,7 @@ rules:
|
|
|
168
168
|
code: "HFS_STACKS_SHAPE"
|
|
169
169
|
editions: [lite, full]
|
|
170
170
|
docs: [docs/legacy-tree-rule-coverage.md#r10]
|
|
171
|
-
law: "`.starcistacks` has the edition-aware standard shape and the host Sonar owner. STACK-DEV keeps every dependency in Docker Linux containers in development (
|
|
171
|
+
law: "`.starcistacks` has the edition-aware standard shape and the host Sonar owner. STACK-DEV keeps every dependency in Docker Linux containers in development (the db service among them); application processes follow their declared development profile. Production runs the app and dependencies, its db service included, on Docker Swarm (owner:dev-docker-prod-swarm). Swarm stacks have digest-pinned images, healthchecks, rollout, rollback and restart policies, versioned external secrets, ingress-only published ports and declared schedulers for replicated jobs. Recurring work is app-owned; host crontab installation is forbidden. Lite declares its db service as backing (backing.db) and runs no other backing-service component."
|
|
172
172
|
kinds: [check, codemod]
|
|
173
173
|
gates: [settle, land, ci]
|
|
174
174
|
failureCodes: ["HFS_STACKS_SHAPE"]
|
|
@@ -640,7 +640,7 @@ rules:
|
|
|
640
640
|
- {path: packages/eslint/be/a16-r34-schema-authority.spec.mjs, editions: [lite, full], codes: [BE_SCHEMA_AUTHORITY]}
|
|
641
641
|
- id: R35
|
|
642
642
|
code: "BE_SCHEMA_OWNER"
|
|
643
|
-
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."
|
|
643
|
+
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. In lite a capability's `persistence/entities/index.ts` declares its `<c>Entities` and `platform/database/entities.ts` names it in `PRIMARY_ENTITIES`, the one list the app composition registers."
|
|
644
644
|
kinds: [lint, codemod, design]
|
|
645
645
|
gates: [settle, land, ci]
|
|
646
646
|
failureCodes: ["BE_SCHEMA_OWNER"]
|
|
@@ -665,7 +665,7 @@ rules:
|
|
|
665
665
|
enforcers:
|
|
666
666
|
- {kind: eslint-be, id: sql-text-only}
|
|
667
667
|
- {kind: eslint-be, id: no-query-builder}
|
|
668
|
-
editions: [
|
|
668
|
+
editions: [full]
|
|
669
669
|
docs: ["docs/architecture.md#r36"]
|
|
670
670
|
proves:
|
|
671
671
|
- path: packages/eslint/be/r36-sql-boundary.spec.mjs
|
|
@@ -722,7 +722,7 @@ rules:
|
|
|
722
722
|
proves:
|
|
723
723
|
- {path: packages/eslint/be/a16-error-home.spec.mjs, editions: [lite, full]}
|
|
724
724
|
exemplifiedBy:
|
|
725
|
-
- {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/
|
|
725
|
+
- {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts, edition: lite}
|
|
726
726
|
- {path: packages/hfs/templates/be/skeleton/src/modules/domain/identity/errors/identity.error.ts, edition: full}
|
|
727
727
|
- {path: packages/eslint/be/a16-error-home.spec.mjs, editions: [lite, full]}
|
|
728
728
|
witness:
|
|
@@ -1820,7 +1820,7 @@ rules:
|
|
|
1820
1820
|
- {path: packages/eslint/be/be-r89-source-form-coverage.spec.mjs, edition: full, codes: [BE_SOURCE_FORM, BE_PUBLIC_CONTRACT_FORM]}
|
|
1821
1821
|
- id: R90
|
|
1822
1822
|
code: "BE_INFRA_OWNER"
|
|
1823
|
-
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."
|
|
1823
|
+
law: "Each raw infrastructure library (HTTP, cache, queue, scheduler, logger, date, config, events, model SDKs) 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."
|
|
1824
1824
|
kinds: [lint, codemod]
|
|
1825
1825
|
failureCodes: ["BE_INFRA_OWNER"]
|
|
1826
1826
|
enforcers:
|
|
@@ -3619,7 +3619,7 @@ rules:
|
|
|
3619
3619
|
docs: [docs/lite-edition.md#edition-validation]
|
|
3620
3620
|
templateNa: "The hfs.json edition declaration is generated programmatically by packages/hfs/scaffold/app.mjs; no managed declaration template exists. Forbidden paths are rejected rather than scaffolded."
|
|
3621
3621
|
code: "HFS_EDITION_INVALID"
|
|
3622
|
-
law: "hfs.json names an edition the manifest knows (full, or lite; absent means full). Lite is a minimal production (case-10): the same monorepo, code structure, slots, patterns and back-end unit specs, one stateless instance type that scales horizontally,
|
|
3622
|
+
law: "hfs.json names an edition the manifest knows (full, or lite; absent means full). Lite is a minimal production (case-10): the same monorepo, code structure, slots, patterns and back-end unit specs, one stateless instance type that scales horizontally, its own PostgreSQL db service, Passport identity and S3-compatible storage (ruling:lite-self-hosted-postgres-passport-minio), and one minimal instance of each backing service a declared capability needs (hfs.json sides.be.backing: Redis for the queue, with AOF persistence); vector search uses pgvector in the db service (owner:lite-vector-is-pgvector, which excludes Qdrant from lite); a backing service is declared or absent, never on by default. The queue is BullMQ on Redis in both editions, fed by the Postgres outbox; pg_cron ticks enqueue through that outbox; the event bus keeps its lite adapter (pgmq-backed topics or in-process delivery) because full's event bus is Kafka, not Redis; cache and locks are Postgres unless Redis is declared; the api and sidecars run on docker-swarm on a VPS. The realtime pattern serves SSE (the default for one-way push), the same socket.io gateway or the restricted GraphQL subscription inside the be api process (scales horizontally and stateless: the memory hub is for a single replica or tests only, and replicas > 1 use the Redis hub and the sharded @socket.io/redis-adapter when Redis is declared, else the Postgres hub and @socket.io/postgres-adapter; both shared socket adapters force websocket-only connections with no sticky sessions; R212 case-9, which amends the realtime clauses of case-2, case-3 and case-5; the GraphQL subscription door is R212 case-7 and owner:lite-graphql-subscription-door), and both editions share knowledge/hfs/fe-stack.yaml. Lite requires application-stacks.yaml and permits environment metadata, runbooks, infra/swarm and encrypted-secret custody, while refusing infra/compose, infra/k8s, infra/terraform and backing components other than its db service (runtime/{config,env}, seeds and the sealed secrets of its environments are allowed). Contract specs and `test:contract` are allowed (owner 2026-10-14); it has no e2e spec, `test:e2e`, `test:stack` or `test:browser` script, e2e workflow, browser journey, Work catalog, shell, features, resources, UAT or br records, test container, toxiproxy, browser-runner or Kafka dependency, client library of an undeclared backing service (HFS_BACKING_UNDECLARED), declared backing service without a consumer (HFS_BACKING_UNUSED), or a supabase/ tree (LITE_DB_MIGRATION). Its .starciwork is optional and admits only .gitignore, workspace.yaml and brand/**; workspace.yaml is optional and carries project identity and repo roles for the Work tools."
|
|
3623
3623
|
kinds: [check]
|
|
3624
3624
|
gates: [settle, land, ci]
|
|
3625
3625
|
failureCodes: ["HFS_EDITION_INVALID", "HFS_EDITION_FORBIDDEN_PRESENT", "HFS_STARCIWORK_LITE_FORBIDDEN", "HFS_BACKING_UNDECLARED", "HFS_BACKING_UNUSED"]
|
|
@@ -3678,7 +3678,7 @@ rules:
|
|
|
3678
3678
|
date: "2026-10-24"
|
|
3679
3679
|
source: owner
|
|
3680
3680
|
quote: "Lite still has Redis, Qdrant and the like!!! That is, it deploys a minimal production. Lite is one instance that scales horizontally and is stateless! Full's infrastructure is more complex. Lite cannot go up to full!!! Lite is lite, full is full. The queue is Redis!!! (translated from Vietnamese; the owner approved the ruling table with \"ok, fix all of it, that is right\")"
|
|
3681
|
-
ruling: "This case replaces the clauses of case-1, case-2 and case-3 that ban backing services in lite and the clause of case-7 that keeps Redis out of the realtime hub of lite (no BullMQ or Redis, one instance with no Redis adapter, the full architecture minus self-hosted infrastructure, the queue on pgmq). Lite is a minimal production: one instance type (one api, an optional worker) that is stateless and scales horizontally,
|
|
3681
|
+
ruling: "This case replaces the clauses of case-1, case-2 and case-3 that ban backing services in lite and the clause of case-7 that keeps Redis out of the realtime hub of lite (no BullMQ or Redis, one instance with no Redis adapter, the full architecture minus self-hosted infrastructure, the queue on pgmq). Lite is a minimal production: one instance type (one api, an optional worker) that is stateless and scales horizontally, its own PostgreSQL db service, Passport identity and S3-compatible storage (ruling:lite-self-hosted-postgres-passport-minio), and one minimal instance of each backing service a declared capability needs, deployed compactly on docker-swarm; full is several services each with its own database plus Redis, Kafka/CDC, its own IdP, object storage and observability on a multi-node stack. A backing service is declared or absent: hfs.json sides.be.backing names redis in lite (redis or qdrant in full), nothing is on by default; owner:lite-vector-is-pgvector narrows the lite vector store to Supabase pgvector and refuses Qdrant even when declared, the lite stack renders only declared services (STACKS_LITE_SELFHOSTED_DB), a client library of an undeclared service is refused (HFS_BACKING_UNDECLARED) and a declared service without a consumer is refused (HFS_BACKING_UNUSED). The queue is BullMQ on Redis in both editions behind the same platform/queue ports (outbox in Postgres, relay to BullMQ, processors, retry with exponential backoff, failed jobs kept as the dead letter); pgmq is not a queue transport, a declared queue or fenced-job pattern requires Redis, and a pg_cron tick enqueues through the queue outbox. The lite Redis runs with AOF persistence (appendonly yes) on a named volume because queue jobs must not be lost (STACKS_LITE_REDIS_PERSISTENCE); cache, rate limit and realtime pub/sub may share that instance, which keeps maxmemory-policy noeviction so no queue job is evicted. Durable data and the outbox stay in Postgres. MongoDB stays out of lite. Editions are fixed at scaffold (case-6)."
|
|
3682
3682
|
proves: [packages/hfs/src/hfs-edition-invalid.spec.mjs, packages/hfs/src/hfs-edition-forbidden-present.spec.mjs, packages/hfs/src/hfs-starciwork-lite-forbidden.spec.mjs, packages/hfs/src/hfs-backing-declared.spec.mjs]
|
|
3683
3683
|
exemplifiedBy:
|
|
3684
3684
|
- {path: packages/hfs/src/hfs-edition-invalid.spec.mjs, editions: [lite, full]}
|
|
@@ -3694,8 +3694,8 @@ rules:
|
|
|
3694
3694
|
code: "DB_MIGRATION_SHAPE"
|
|
3695
3695
|
editions: [lite, full]
|
|
3696
3696
|
docs: [docs/architecture.md#r213]
|
|
3697
|
-
templateNa: "Full apps
|
|
3698
|
-
law: "A migration is `
|
|
3697
|
+
templateNa: "Full apps keep TypeORM migrations under be/; the managed db/migrations scaffold is lite's."
|
|
3698
|
+
law: "A migration is `db/migrations/<ts14>_<kebab>.sql`; the stamp is a valid UTC instant, active stamps are unique, and a new migration sorts strictly after the committed active migrations in HEAD. Repository history includes the archive: moving an active migration to the archive retires it from ordering, so a replacement baseline may predate archived stamps and later migrations append after the baseline. Wall-clock time, commit time and remote-branch stamps do not set the ordering boundary. A migration already on the base is immutable (a diff or an unarchived removal is a finding); a file that does not parse as PostgreSQL is a shape failure."
|
|
3699
3699
|
kinds: [check]
|
|
3700
3700
|
gates: [settle, land, ci]
|
|
3701
3701
|
failureCodes: ["DB_MIGRATION_SHAPE"]
|
|
@@ -3704,7 +3704,7 @@ rules:
|
|
|
3704
3704
|
proves:
|
|
3705
3705
|
- {path: packages/hfs/src/db-migration-shape.spec.mjs, editions: [lite, full]}
|
|
3706
3706
|
exemplifiedBy:
|
|
3707
|
-
- {path: packages/hfs/templates/app/skeleton-lite/
|
|
3707
|
+
- {path: packages/hfs/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql, edition: lite}
|
|
3708
3708
|
- {path: packages/hfs/src/db-migration-shape.spec.mjs, editions: [lite, full]}
|
|
3709
3709
|
witness:
|
|
3710
3710
|
- {path: packages/hfs/src/db-migration-shape.spec.mjs, editions: [lite, full], codes: [DB_MIGRATION_SHAPE]}
|
|
@@ -3712,7 +3712,7 @@ rules:
|
|
|
3712
3712
|
code: "DB_RLS_REQUIRED"
|
|
3713
3713
|
editions: [lite, full]
|
|
3714
3714
|
docs: [docs/lite-edition.md#r214]
|
|
3715
|
-
law: "Row level security is code a parser can read: every `create table` (or `create table as`) in
|
|
3715
|
+
law: "Row level security is code a parser can read: every `create table` (or `create table as`) in the app schema `public` is followed, in the same migration, by `alter table <t> enable row level security` (and `force` for a table hfs.json `db.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."
|
|
3716
3716
|
kinds: [check, codemod]
|
|
3717
3717
|
gates: [settle, land, ci]
|
|
3718
3718
|
failureCodes: ["DB_RLS_REQUIRED", "DB_DYNAMIC_DDL", "DB_POLICY_SHAPE"]
|
|
@@ -3756,98 +3756,24 @@ rules:
|
|
|
3756
3756
|
- {path: packages/hfs/src/db-storage-policy.spec.mjs, edition: lite, codes: [DB_STORAGE_POLICY]}
|
|
3757
3757
|
- {path: packages/hfs/src/db-definer-safe.spec.mjs, edition: full, codes: [DB_DEFINER_SAFE]}
|
|
3758
3758
|
- {path: packages/hfs/src/db-storage-policy.spec.mjs, edition: full, codes: [DB_STORAGE_POLICY]}
|
|
3759
|
-
- id: R216
|
|
3760
|
-
code: "DB_CONFIG_POLICY"
|
|
3761
|
-
editions: [lite, full]
|
|
3762
|
-
docs: [docs/lite-edition.md#r216]
|
|
3763
|
-
templateNa: "Database types are generated from each app migration set, not static templates; full-edition Supabase configuration is app-owned."
|
|
3764
|
-
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. Type emission refuses HFS_EMIT_DB_TYPES_STALE unless the complete applied migration version set equals the SQL filename stamps; lint and check report drift without writing types."
|
|
3765
|
-
kinds: [check]
|
|
3766
|
-
gates: [land, ci]
|
|
3767
|
-
failureCodes: ["DB_CONFIG_POLICY", "DB_TYPES_DRIFT", "HFS_EMIT_DB_TYPES_STALE"]
|
|
3768
|
-
enforcers:
|
|
3769
|
-
- {kind: hfs, id: db-config-types, at: scripts/hfs/rules/database.mjs}
|
|
3770
|
-
- {kind: hfs, id: db-types-emit, at: packages/hfs/emit/db-types.mjs}
|
|
3771
|
-
proves:
|
|
3772
|
-
- {path: packages/hfs/src/db-types-returned-collector.spec.mjs, editions: [lite, full], codes: [DB_TYPES_DRIFT]}
|
|
3773
|
-
- {path: packages/hfs/src/db-config-policy.spec.mjs, editions: [lite, full], codes: [DB_CONFIG_POLICY]}
|
|
3774
|
-
- {path: packages/hfs/src/db-types-emit-stale.spec.mjs, editions: [lite, full], codes: [HFS_EMIT_DB_TYPES_STALE]}
|
|
3775
|
-
exemplifiedBy:
|
|
3776
|
-
- {path: packages/hfs/templates/app/skeleton-lite/supabase/config.toml, edition: lite}
|
|
3777
|
-
- {path: packages/hfs/src/db-types-returned-collector.spec.mjs, editions: [lite, full]}
|
|
3778
|
-
- {path: packages/hfs/src/db-config-policy.spec.mjs, editions: [lite, full]}
|
|
3779
|
-
- {path: packages/hfs/src/db-types-emit-stale.spec.mjs, editions: [lite, full], codes: [HFS_EMIT_DB_TYPES_STALE]}
|
|
3780
|
-
witness:
|
|
3781
|
-
- {path: packages/hfs/src/db-types-returned-collector.spec.mjs, edition: lite, codes: [DB_TYPES_DRIFT]}
|
|
3782
|
-
- {path: packages/hfs/src/db-types-returned-collector.spec.mjs, edition: full, codes: [DB_TYPES_DRIFT]}
|
|
3783
|
-
- {path: packages/hfs/src/db-config-policy.spec.mjs, edition: lite, codes: [DB_CONFIG_POLICY]}
|
|
3784
|
-
- {path: packages/hfs/src/db-config-policy.spec.mjs, edition: full, codes: [DB_CONFIG_POLICY]}
|
|
3785
|
-
- {path: packages/hfs/src/db-types-emit-stale.spec.mjs, editions: [lite, full], codes: [HFS_EMIT_DB_TYPES_STALE]}
|
|
3786
|
-
- id: R217
|
|
3787
|
-
code: "FE_SUPABASE_CLIENT_OWNER"
|
|
3788
|
-
editions: [lite, full]
|
|
3789
|
-
docs: [docs/lite-edition.md#r217]
|
|
3790
|
-
law: "Supabase has one door per side: on the front end `@supabase/supabase-js` and `@supabase/ssr` are imported only by `fe.modules.auth` and only their auth API is called (sign-in, sign-out, session, access token; never `.from`, `.rpc`, `.storage` or `.channel`, so the front end reads and writes no data through Supabase), and on the back end only `be.integrations.supabase` imports them, every back-end client carrying the committed `Database` type; the front end holds no credential: `fe/` names no service-role or `*_SECRET`, `*_PRIVATE`, `*_TOKEN`, `*_PASSWORD` value (public configuration is read once by `modules/config`)."
|
|
3791
|
-
kinds: [check, design]
|
|
3792
|
-
gates: [settle, land, ci]
|
|
3793
|
-
failureCodes: ["FE_SUPABASE_CLIENT_OWNER", "BE_SUPABASE_CLIENT_OWNER", "FE_SERVICE_ROLE_FORBIDDEN"]
|
|
3794
|
-
enforcers:
|
|
3795
|
-
- {kind: machine, id: supabase-client-owner, at: scripts/hfs/architecture/supabase.mjs}
|
|
3796
|
-
- {kind: machine, id: supabase-service-role, at: scripts/hfs/architecture/supabase.mjs}
|
|
3797
|
-
proves:
|
|
3798
|
-
- {path: packages/hfs/src/fe-supabase-client-owner.spec.mjs, editions: [lite, full], codes: [FE_SUPABASE_CLIENT_OWNER]}
|
|
3799
|
-
- {path: packages/hfs/src/r217-backend-client-owner.spec.mjs, editions: [lite, full], codes: [BE_SUPABASE_CLIENT_OWNER]}
|
|
3800
|
-
- {path: packages/hfs/src/fe-service-role-forbidden.spec.mjs, editions: [lite, full], codes: [FE_SERVICE_ROLE_FORBIDDEN]}
|
|
3801
|
-
exemplifiedBy:
|
|
3802
|
-
- {path: packages/hfs/templates/fe/auth/supabase-client.ts.tpl, editions: [lite, full]}
|
|
3803
|
-
- {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts, edition: lite}
|
|
3804
|
-
- {path: packages/hfs/src/fe-supabase-client-owner.spec.mjs, editions: [lite, full]}
|
|
3805
|
-
- {path: packages/hfs/src/r217-backend-client-owner.spec.mjs, editions: [lite, full]}
|
|
3806
|
-
- {path: packages/hfs/src/fe-service-role-forbidden.spec.mjs, editions: [lite, full]}
|
|
3807
|
-
witness:
|
|
3808
|
-
- {path: packages/hfs/src/fe-supabase-client-owner.spec.mjs, edition: lite, codes: [FE_SUPABASE_CLIENT_OWNER]}
|
|
3809
|
-
- {path: packages/hfs/src/r217-backend-client-owner.spec.mjs, edition: lite, codes: [BE_SUPABASE_CLIENT_OWNER]}
|
|
3810
|
-
- {path: packages/hfs/src/fe-service-role-forbidden.spec.mjs, edition: lite, codes: [FE_SERVICE_ROLE_FORBIDDEN]}
|
|
3811
|
-
- {path: packages/hfs/src/fe-supabase-client-owner.spec.mjs, edition: full, codes: [FE_SUPABASE_CLIENT_OWNER]}
|
|
3812
|
-
- {path: packages/hfs/src/r217-backend-client-owner.spec.mjs, edition: full, codes: [BE_SUPABASE_CLIENT_OWNER]}
|
|
3813
|
-
- {path: packages/hfs/src/fe-service-role-forbidden.spec.mjs, edition: full, codes: [FE_SERVICE_ROLE_FORBIDDEN]}
|
|
3814
|
-
- id: R219
|
|
3815
|
-
code: "BE_SUPABASE_JWT_VERIFIED"
|
|
3816
|
-
editions: [lite, full]
|
|
3817
|
-
docs: [docs/lite-edition.md#r219]
|
|
3818
|
-
templateNa: "Full-edition Supabase verification is an optional integration with no managed verifier scaffold; lite has the referenced integration template."
|
|
3819
|
-
law: "The principal is verified, never trusted: 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."
|
|
3820
|
-
kinds: [check, design]
|
|
3821
|
-
gates: [settle, land, ci]
|
|
3822
|
-
failureCodes: ["BE_SUPABASE_JWT_VERIFIED"]
|
|
3823
|
-
enforcers:
|
|
3824
|
-
- {kind: machine, id: supabase-principal, at: scripts/hfs/architecture/supabase.mjs}
|
|
3825
|
-
proves:
|
|
3826
|
-
- {path: packages/hfs/src/be-supabase-jwt-verified.spec.mjs, editions: [lite, full]}
|
|
3827
|
-
exemplifiedBy:
|
|
3828
|
-
- {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts, edition: lite}
|
|
3829
|
-
- {path: packages/hfs/src/be-supabase-jwt-verified.spec.mjs, editions: [lite, full]}
|
|
3830
|
-
witness:
|
|
3831
|
-
- {path: packages/hfs/src/be-supabase-jwt-verified.spec.mjs, edition: lite, codes: [BE_SUPABASE_JWT_VERIFIED]}
|
|
3832
|
-
- {path: packages/hfs/src/be-supabase-jwt-verified.spec.mjs, edition: full, codes: [BE_SUPABASE_JWT_VERIFIED]}
|
|
3833
3759
|
- id: R220
|
|
3834
3760
|
code: "HFS_LITE_SECRET_CUSTODY"
|
|
3835
3761
|
editions: [lite]
|
|
3836
|
-
editionWhy: "ruling:lite-
|
|
3762
|
+
editionWhy: "ruling:lite-self-hosted-postgres-passport-minio gives lite its own database and identity; R220 adds lite's reduced custody tree and the fe/ and db/ literal scan, while R06 governs plaintext secret material in both editions."
|
|
3837
3763
|
docs: [docs/host-secrets.md#application-secret-rules]
|
|
3838
|
-
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 `
|
|
3764
|
+
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 `db/` hold no provider token, JWT or connection string with a password; the one custody is the sealed `.starcistacks/<env>/secrets/<slug>.enc`."
|
|
3839
3765
|
kinds: [check]
|
|
3840
3766
|
gates: [settle, land, ci]
|
|
3841
3767
|
failureCodes: ["HFS_LITE_SECRET_CUSTODY"]
|
|
3842
3768
|
enforcers:
|
|
3843
|
-
- {kind: hfs, id: lite-secret-custody, at: scripts/hfs/rules/
|
|
3769
|
+
- {kind: hfs, id: lite-secret-custody, at: scripts/hfs/rules/lite-secrets.mjs}
|
|
3844
3770
|
proves:
|
|
3845
|
-
- {path: packages/hfs/src/hfs-
|
|
3771
|
+
- {path: packages/hfs/src/hfs-lite-secrets.spec.mjs, edition: lite}
|
|
3846
3772
|
exemplifiedBy:
|
|
3847
3773
|
- {path: packages/hfs/templates/app/skeleton-lite/.sops.yaml, edition: lite}
|
|
3848
|
-
- {path: packages/hfs/src/hfs-
|
|
3774
|
+
- {path: packages/hfs/src/hfs-lite-secrets.spec.mjs, edition: lite}
|
|
3849
3775
|
witness:
|
|
3850
|
-
- {path: packages/hfs/src/hfs-
|
|
3776
|
+
- {path: packages/hfs/src/hfs-lite-secrets.spec.mjs, edition: lite}
|
|
3851
3777
|
- id: R221
|
|
3852
3778
|
code: "CI_TRIGGERS_RELEASE_ONLY"
|
|
3853
3779
|
law: "Workflow triggers are push filtered to v* tags or release/candidate-* branches, and workflow_dispatch. Runtime workflows and app ci.yml also admit main. App ci.yml includes the candidate branch filter for starci app release push. Other branches, events, path filters and ignore filters are refused."
|
|
@@ -4124,7 +4050,7 @@ rules:
|
|
|
4124
4050
|
code: "FE_SERVER_ACTION_FORBIDDEN"
|
|
4125
4051
|
editions: [lite, full]
|
|
4126
4052
|
docs: [docs/code-pattern-enforcement.md#r243]
|
|
4127
|
-
law: "The front end serves no API, in every app and edition (R212 case-2 (owner:lite-production-swarm), with the Work boundary amended by ruling:R212/case-4): no file under `fe/` carries a `\"use server\"` directive (module or function level), and the only route
|
|
4053
|
+
law: "The front end serves no API, in every app and edition (R212 case-2 (owner:lite-production-swarm), with the Work boundary amended by ruling:R212/case-4): no file under `fe/` carries a `\"use server\"` directive (module or function level), and the only route handler is `app/health/live/route.ts`; reads and writes reach the back end through `modules/api` and the SWR hooks, and a webhook, cron, public API or third-party callback is a back-end feature."
|
|
4128
4054
|
kinds: [check, lint]
|
|
4129
4055
|
gates: [pre-commit, settle, land, ci]
|
|
4130
4056
|
failureCodes: ["FE_SERVER_ACTION_FORBIDDEN", "FE_ROUTE_HANDLER_FORBIDDEN"]
|
|
@@ -4191,7 +4117,7 @@ rules:
|
|
|
4191
4117
|
witness:
|
|
4192
4118
|
- {path: packages/eslint/be/app-composition-coverage.spec.mjs, edition: lite, codes: [BE_APP_COMPOSITION_BUDGET]}
|
|
4193
4119
|
- {path: packages/eslint/be/app-composition-coverage.spec.mjs, edition: full, codes: [BE_APP_COMPOSITION_BUDGET]}
|
|
4194
|
-
law: "Each app file stays within the line budget its `be.app.*` slot names (`budget`, keyed by the slot entry the file matches): `main.ts` 80, `<app>.env.ts` 150, `<app>.options.ts` 200, `compose/<area>.ts` 120, `app.module.ts` 250. The budget is absolute, not a ratchet: an entrypoint that outgrows it moves typed, fail-fast parsing into `<app>.env.ts`; an app root that outgrows it moves an area into a new `compose/<area>.ts` group, never into the env or options file."
|
|
4120
|
+
law: "Each app file stays within the line budget its `be.app.*` slot names (`budget`, keyed by the slot entry the file matches): `main.ts` 80, `<app>.env.ts` 150, `<app>.options.ts` 200, `compose/<area>.ts` 120, `app.module.ts` 250, counted in lines excluding import declarations (a multi-line import counts none of its lines). The budget is absolute, not a ratchet: an entrypoint that outgrows it moves typed, fail-fast parsing into `<app>.env.ts`; an app root that outgrows it moves an area into a new `compose/<area>.ts` group, never into the env or options file."
|
|
4195
4121
|
kinds: [lint, codemod]
|
|
4196
4122
|
gates: [pre-commit, settle, land, ci]
|
|
4197
4123
|
failureCodes: ["BE_APP_COMPOSITION_BUDGET"]
|
|
@@ -4289,10 +4215,10 @@ rules:
|
|
|
4289
4215
|
editions: [lite, full]
|
|
4290
4216
|
docs:
|
|
4291
4217
|
- {path: docs/application-stacks.md#r252-script-hygiene, editions: [lite, full]}
|
|
4292
|
-
law: "In lite and full apps, executable scripts and workflow commands never load plaintext secrets.env, secret.env, .env secret files or a home-directory credential file, enable shell xtrace or request --reveal.
|
|
4218
|
+
law: "In lite and full apps, executable scripts and workflow commands never load plaintext secrets.env, secret.env, .env secret files or a home-directory credential file, enable shell xtrace or request --reveal. Scanners inspect source references and report paths without reading secret files or printing their values."
|
|
4293
4219
|
kinds: [check]
|
|
4294
4220
|
gates: [settle, land, ci]
|
|
4295
|
-
failureCodes: ["HFS_SCRIPT_PLAINTEXT_LOAD", "HFS_SCRIPT_XTRACE"
|
|
4221
|
+
failureCodes: ["HFS_SCRIPT_PLAINTEXT_LOAD", "HFS_SCRIPT_XTRACE"]
|
|
4296
4222
|
enforcers:
|
|
4297
4223
|
- {kind: hfs, id: script-hygiene, at: scripts/hfs/rules/script-hygiene.mjs}
|
|
4298
4224
|
proves:
|
|
@@ -4323,31 +4249,13 @@ rules:
|
|
|
4323
4249
|
- {path: packages/hfs/src/hfs-secret-custody.spec.mjs, editions: [lite, full]}
|
|
4324
4250
|
witness:
|
|
4325
4251
|
- {path: packages/hfs/src/hfs-secret-custody.spec.mjs, editions: [lite, full]}
|
|
4326
|
-
- id: R254
|
|
4327
|
-
code: "DB_BE_ONLY_ACCESS"
|
|
4328
|
-
editions: [lite]
|
|
4329
|
-
editionWhy: "owner:lite-backing-services makes Supabase lite's database backing; these SQL access requirements apply to that backing."
|
|
4330
|
-
docs: [docs/microservices.md#r254]
|
|
4331
|
-
templateNa: "Browser grants, policies and publication membership belong to product-authored Supabase migrations, rather than a scaffolded SQL surface."
|
|
4332
|
-
law: "In lite and full, browser data access stays closed on exposed schemas: authenticated receives neither table or sequence privileges nor data policies, and anon reads only through a select policy named *_public_read. A table listed in hfs.json supabase.directAccess is an explicit exception. A table joins supabase_realtime only when that direct access is declared. The front end uses Supabase for Auth and reaches product data through the back end."
|
|
4333
|
-
kinds: [check]
|
|
4334
|
-
gates: [settle, land, ci]
|
|
4335
|
-
failureCodes: ["DB_BE_ONLY_ACCESS", "DB_REALTIME_UNLISTED"]
|
|
4336
|
-
enforcers:
|
|
4337
|
-
- {kind: hfs, id: db-be-only-access, at: scripts/hfs/rules/database-access.mjs}
|
|
4338
|
-
proves:
|
|
4339
|
-
- {path: packages/hfs/src/hfs-database-access.spec.mjs, edition: lite, codes: [DB_BE_ONLY_ACCESS, DB_REALTIME_UNLISTED]}
|
|
4340
|
-
exemplifiedBy:
|
|
4341
|
-
- {path: packages/hfs/src/hfs-database-access.spec.mjs, edition: lite, codes: [DB_BE_ONLY_ACCESS, DB_REALTIME_UNLISTED]}
|
|
4342
|
-
witness:
|
|
4343
|
-
- {path: packages/hfs/src/hfs-database-access.spec.mjs, edition: lite, codes: [DB_BE_ONLY_ACCESS, DB_REALTIME_UNLISTED]}
|
|
4344
4252
|
- id: R255
|
|
4345
4253
|
code: "DB_APP_ROLE"
|
|
4346
4254
|
editions: [lite]
|
|
4347
|
-
editionWhy: "owner:lite-backing-services makes
|
|
4255
|
+
editionWhy: "owner:lite-backing-services makes the db service lite's database; application-role and forced-RLS requirements apply to that backing."
|
|
4348
4256
|
docs: [docs/microservices.md#r255]
|
|
4349
|
-
templateNa: "Application roles and table ownership are product-authored
|
|
4350
|
-
law: "The declared back-end database role (
|
|
4257
|
+
templateNa: "Application roles and table ownership are product-authored db migration content, rather than a scaffolded SQL surface."
|
|
4258
|
+
law: "The declared back-end database role (app_be by default) has only the privileges it needs: role declarations refuse superuser and bypassrls, require noinherit and nobypassrls, and never grant all schema or table privileges. Tables owned by that role enable and force row level security by default; a forceRls exemption identifies the table and carries a nonempty reason. This applies to every edition that declares Supabase."
|
|
4351
4259
|
kinds: [check]
|
|
4352
4260
|
gates: [settle, land, ci]
|
|
4353
4261
|
failureCodes: ["DB_APP_ROLE", "DB_FORCE_RLS_DEFAULT"]
|
|
@@ -4362,9 +4270,9 @@ rules:
|
|
|
4362
4270
|
- id: R256
|
|
4363
4271
|
code: "DB_POLICY_TAUTOLOGY"
|
|
4364
4272
|
editions: [lite]
|
|
4365
|
-
editionWhy: "owner:lite-backing-services makes
|
|
4273
|
+
editionWhy: "owner:lite-backing-services makes the db service lite's database; access-policy, view, definer and storage SQL requirements apply to that backing."
|
|
4366
4274
|
docs: [docs/microservices.md#r256]
|
|
4367
|
-
templateNa: "Access policies, views, definers and storage policies belong to product-authored
|
|
4275
|
+
templateNa: "Access policies, views, definers and storage policies belong to product-authored db migrations, rather than a scaffolded SQL surface."
|
|
4368
4276
|
law: "Exposed database objects keep their access boundaries in lite and full. Policies refuse tautological true, is-not-null and self-equality predicates; policies for browser roles prove a principal predicate. An exposed view or materialized view uses security_invoker or the migration set ends with access revoked from anon and authenticated. Disabling RLS or moving a table into an exposed schema preserves the R214 RLS obligation. A security definer declaration, including alter function, preserves R215 safety checks and the migration set ends with execute revoked from authenticated when exposed, in addition to R215's public and anon revocation. A storage.objects policy scopes an ownership prefix to the principal unless its bucket id ends _public."
|
|
4369
4277
|
kinds: [check]
|
|
4370
4278
|
gates: [settle, land, ci]
|
|
@@ -4384,9 +4292,9 @@ rules:
|
|
|
4384
4292
|
- id: R257
|
|
4385
4293
|
code: "DB_BASELINE_SHAPE"
|
|
4386
4294
|
editions: [lite]
|
|
4387
|
-
editionWhy: "owner:lite-backing-services makes
|
|
4295
|
+
editionWhy: "owner:lite-backing-services makes the db service lite's database; baseline and archive requirements apply to its migration history."
|
|
4388
4296
|
docs: [docs/microservices.md#r257]
|
|
4389
|
-
templateNa: "A baseline is derived from a product's existing
|
|
4297
|
+
templateNa: "A baseline is derived from a product's existing migration history and cannot be supplied by a scaffold template."
|
|
4390
4298
|
law: "An archived migration chain has exactly one active baseline marked -- starci:baseline <archived-last-stamp>, with a later valid stamp or an explicit restamp. Archived files only need the digits-stamp_name.sql shape, whatever the stamp value (they are immutable history and never applied); the baseline and active migrations keep the real-instant stamp check. Archive contents are immutable against repository history; an active migration enters the archive unchanged only in the same diff that adds the replacement baseline. The app db baseline, restamp and plan verbs are read-only unless --apply is explicit."
|
|
4391
4299
|
kinds: [check]
|
|
4392
4300
|
gates: [settle, land, ci]
|
|
@@ -4402,9 +4310,9 @@ rules:
|
|
|
4402
4310
|
- id: R258
|
|
4403
4311
|
code: "DB_PGMQ_DECLARED"
|
|
4404
4312
|
editions: [lite]
|
|
4405
|
-
editionWhy: "owner:lite-backing-services makes
|
|
4313
|
+
editionWhy: "owner:lite-backing-services makes the db service lite's database and ruling:R212/case-10 moves the lite queue to Redis; the pgmq retirement and pg_cron ownership requirements apply to that backing."
|
|
4406
4314
|
docs: [docs/microservices.md#r258]
|
|
4407
|
-
templateNa: "Queue declarations and enqueue-only cron commands depend on product-owned work and are authored in its
|
|
4315
|
+
templateNa: "Queue declarations and enqueue-only cron commands depend on product-owned work and are authored in its db migrations."
|
|
4408
4316
|
law: "DB_QUEUE_CRON: the queue is BullMQ on Redis (ruling:R212/case-10), so the active migrations leave no pgmq queue named by a queue constant under modules/queues; a pgmq queue a migration created for one is dropped by a later migration. Every pgmq queue the active migrations leave is an event-bus topic queue named by a literal *_TOPIC_QUEUE constant under platform/event-bus, every such constant has its pgmq queue, and pgmq objects are granted only to the application role while they exist. A pg_cron job belongs to a migration and only enqueues; its name is scheduled once while it is active, so a later migration schedules it again only after its own cron.unschedule of that name, and one tick function has one live job at a time. Its command, directly or through one enqueue-only *_tick function, inserts one row of a literal queue constant into the queue outbox table of ruleParams.db.queueOutbox, writing only its declared columns (the relay hands it to BullMQ). An app whose hfs.json sides.be.backing declares redis may declare its own outbox in sides.be.queueOutbox ({table, enqueue, queueParameter}); the command, or the tick, then calls the declared enqueue function with a literal queue constant for the declared parameter and pure other arguments, while the active definition of that function only inserts one row into the declared table with its queue column bound to that parameter (an ON CONFLICT DO NOTHING, a read of the declared table and pure expressions are the only other work). A direct insert into the app's outbox table, or any other function, stays refused. The job executes no business DML or HTTP request and shares no tick with an app scheduler. A pgmq.send validates only in the original body of a job an older migration scheduled while that queue existed; the live job and its current tick never enqueue that way. Removing a tick unschedules its job."
|
|
4409
4317
|
kinds: [check]
|
|
4410
4318
|
gates: [settle, land, ci]
|
|
@@ -4421,41 +4329,40 @@ rules:
|
|
|
4421
4329
|
- id: R259
|
|
4422
4330
|
code: "DB_EXTENSION_ALLOWED"
|
|
4423
4331
|
editions: [lite]
|
|
4424
|
-
editionWhy: "owner:lite-backing-services makes
|
|
4332
|
+
editionWhy: "owner:lite-backing-services makes the db service lite's database; database configuration, extension, migration and seed requirements apply to that backing."
|
|
4425
4333
|
docs: [docs/microservices.md#r259]
|
|
4426
|
-
templateNa: "Extension use, contract migrations, vault operations and seed literals are product-authored
|
|
4427
|
-
law: "DB_CONFIG_EXT: extensions belong to the database allowlist, vault writes and migration role passwords are refused,
|
|
4334
|
+
templateNa: "Extension use, contract migrations, vault operations and seed literals are product-authored db content, rather than a scaffolded SQL surface."
|
|
4335
|
+
law: "DB_CONFIG_EXT: extensions belong to the database allowlist, vault writes and migration role passwords are refused, and seed data holds no real email or phone. Destructive DDL belongs only to *_contract_* migrations whose targets are no longer referenced by back-end SQL or entities. Seeds contain no real email or phone literals."
|
|
4428
4336
|
kinds: [check]
|
|
4429
4337
|
gates: [settle, land, ci]
|
|
4430
|
-
failureCodes: ["DB_EXTENSION_ALLOWED", "
|
|
4338
|
+
failureCodes: ["DB_EXTENSION_ALLOWED", "DB_MIGRATION_DESTRUCTIVE", "HFS_SEED_PII", "DB_VAULT_WRITE"]
|
|
4431
4339
|
enforcers:
|
|
4432
4340
|
- {kind: hfs, id: db-config-ext, at: scripts/hfs/rules/database-config.mjs}
|
|
4433
4341
|
- {kind: hfs, id: db-migration-destructive, at: scripts/hfs/rules/database-migrations.mjs}
|
|
4434
4342
|
proves:
|
|
4435
|
-
- {path: packages/hfs/src/hfs-database-config.spec.mjs, editions: [lite], codes: [DB_EXTENSION_ALLOWED,
|
|
4343
|
+
- {path: packages/hfs/src/hfs-database-config.spec.mjs, editions: [lite], codes: [DB_EXTENSION_ALLOWED, DB_MIGRATION_DESTRUCTIVE, HFS_SEED_PII, DB_VAULT_WRITE]}
|
|
4436
4344
|
exemplifiedBy:
|
|
4437
|
-
- {path: packages/hfs/src/hfs-database-config.spec.mjs, editions: [lite], codes: [DB_EXTENSION_ALLOWED,
|
|
4345
|
+
- {path: packages/hfs/src/hfs-database-config.spec.mjs, editions: [lite], codes: [DB_EXTENSION_ALLOWED, DB_MIGRATION_DESTRUCTIVE, HFS_SEED_PII, DB_VAULT_WRITE]}
|
|
4438
4346
|
witness:
|
|
4439
|
-
- {path: packages/hfs/src/hfs-database-config.spec.mjs, editions: [lite], codes: [DB_EXTENSION_ALLOWED,
|
|
4347
|
+
- {path: packages/hfs/src/hfs-database-config.spec.mjs, editions: [lite], codes: [DB_EXTENSION_ALLOWED, DB_MIGRATION_DESTRUCTIVE, HFS_SEED_PII, DB_VAULT_WRITE]}
|
|
4440
4348
|
- id: R260
|
|
4441
4349
|
code: "BE_JWT_ALG_PINNED"
|
|
4442
|
-
law: "A back-end JWT verifier pins a nonempty literal subset of ES256, RS256 and EdDSA and obtains its issuer from configuration. A
|
|
4350
|
+
law: "A back-end JWT verifier pins a nonempty literal subset of ES256, RS256 and EdDSA and obtains its issuer from configuration. A principal is built only after verification and rejection of every role other than authenticated, in lite and full."
|
|
4443
4351
|
kinds: [check, lint]
|
|
4444
4352
|
gates: ["pre-commit","settle","land","ci"]
|
|
4445
4353
|
failureCodes: ["BE_JWT_ALG_PINNED"]
|
|
4446
4354
|
enforcers:
|
|
4447
4355
|
- {kind: eslint-be, id: jwt-alg-pinned}
|
|
4448
|
-
- {kind: machine, id: jwt-policy, at: scripts/hfs/architecture/
|
|
4356
|
+
- {kind: machine, id: jwt-policy, at: scripts/hfs/architecture/identity.mjs}
|
|
4449
4357
|
editions: ["lite","full"]
|
|
4450
|
-
templateNa: "Full identity providers are product-owned integrations with no scaffolded JWT verifier; the lite
|
|
4358
|
+
templateNa: "Full identity providers are product-owned integrations with no scaffolded JWT verifier; the lite own-token verifier is templated and both editions are exercised by the verifier specs."
|
|
4451
4359
|
docs:
|
|
4452
4360
|
- "docs/microservices.md#r260"
|
|
4453
4361
|
proves:
|
|
4454
4362
|
- "packages/eslint/be/jwt-alg.spec.mjs"
|
|
4455
4363
|
- packages/hfs/src/architecture-jwt-policy.spec.mjs
|
|
4456
4364
|
exemplifiedBy:
|
|
4457
|
-
- {"path":"packages/hfs/templates/be/skeleton-lite/src/modules/integrations/
|
|
4458
|
-
- {"path":"examples/lite-app/be/src/modules/integrations/supabase/supabase.jwks.ts","edition":"lite"}
|
|
4365
|
+
- {"path":"packages/hfs/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts","edition":"lite"}
|
|
4459
4366
|
- {"path":"packages/eslint/be/jwt-alg.spec.mjs","edition":"full","codes":["BE_JWT_ALG_PINNED"]}
|
|
4460
4367
|
witness:
|
|
4461
4368
|
- {"path":"packages/eslint/be/jwt-alg.spec.mjs","editions":["lite","full"],"codes":["BE_JWT_ALG_PINNED"]}
|
|
@@ -4794,14 +4701,14 @@ rules:
|
|
|
4794
4701
|
- {path: packages/hfs/src/hfs-stacks-full-fixtures.spec.mjs, edition: full}
|
|
4795
4702
|
- id: R276
|
|
4796
4703
|
code: "STACKS_LITE_SELFHOSTED_DB"
|
|
4797
|
-
law: "Lite
|
|
4704
|
+
law: "Lite runs exactly one database, its db component of image supabase/postgres (pinned by digest) with pgvector for vector search, and no other PostgreSQL, MongoDB or Qdrant backing image, even when declared (owner:lite-vector-is-pgvector, ruling:lite-self-hosted-postgres-passport-minio). A Redis image runs only when hfs.json sides.be.backing declares redis (ruling:R212/case-10), as one minimal instance; a declared Redis runs as exactly one stack service, with AOF persistence (`appendonly yes`, `appendfsync everysec` or `always`, never `no`) on a writable named volume so queue jobs survive a restart and with no maxmemory-policy but noeviction so none is evicted. A queue job Redis loses inside the at most one second `everysec` leaves unwritten is not lost work: every job is born from a Postgres outbox row, and the relay marks that row sent only after Redis confirms the job is in its append-only file (WAITAOF) and still holds it, so a job Redis did not persist leaves its row waiting and the relay adds it again (examples/lite-app queue-relay.service.spec.ts and bullmq-queue-transport.client.spec.ts). A sidecar keeps its own SQLite on a named persistent volume declared by its component and mounted in the rendered model, and never uses the app's declared Redis, which serves the back-end app services only; a sidecar requiring another database, that Redis included, is refused. Full retains its declared backing topology."
|
|
4798
4705
|
kinds: [check]
|
|
4799
4706
|
gates: ["settle","land","ci"]
|
|
4800
4707
|
failureCodes: ["STACKS_LITE_SELFHOSTED_DB", "STACKS_SIDECAR_VOLUME_UNDECLARED", "STACKS_SIDECAR_DB_UNSUPPORTED", "STACKS_LITE_REDIS_PERSISTENCE"]
|
|
4801
4708
|
enforcers:
|
|
4802
4709
|
- {kind: hfs, id: stacks-lite-backing, at: scripts/hfs/rules/stacks-lite-backing.mjs}
|
|
4803
4710
|
editions: ["lite"]
|
|
4804
|
-
editionWhy: "ruling:R276 records owner ruling 4, amended by ruling:R212/case-10: lite
|
|
4711
|
+
editionWhy: "ruling:R276 records owner ruling 4, amended by ruling:R212/case-10 and ruling:lite-self-hosted-postgres-passport-minio: lite runs its own Postgres as the db service, uses pgvector for vectors, refuses Qdrant even when declared (owner:lite-vector-is-pgvector), runs a Redis instance only when declared, runs a declared Redis as one service persisted with AOF fsynced at least every second on a named volume and never evicting (the outbox relay re-adds a job Redis did not persist), and permits database-dependent sidecars only with SQLite on declared persistent volumes, never on the app's Redis; full keeps its declared backing topology."
|
|
4805
4712
|
docs:
|
|
4806
4713
|
- "docs/application-stacks.md#r276"
|
|
4807
4714
|
proves: [packages/hfs/src/hfs-stacks-lite-backing.spec.mjs]
|
|
@@ -5021,25 +4928,6 @@ rules:
|
|
|
5021
4928
|
- {path: packages/eslint/fe/translation.spec.mjs, editions: [lite, full], codes: [FE_COPY_IN_CALL, FE_NO_ALERT]}
|
|
5022
4929
|
- {path: packages/eslint/grammar-core/icon.spec.mjs, editions: [lite, full], codes: [FE_ICON_INLINE_SVG]}
|
|
5023
4930
|
|
|
5024
|
-
- id: R287
|
|
5025
|
-
code: "FE_CALLBACK_SHAPE"
|
|
5026
|
-
amendedBy: [owner:auth-callback-email-link, owner:auth-callback-pure-helpers]
|
|
5027
|
-
law: "The fe.route.callback handler performs only the auth code exchange and redirect: read the callback URL, obtain the auth client, exchange the code and redirect to the declared route. An email confirmation or recovery link (owner:auth-callback-email-link) arrives as the query values token_hash and type; the handler then awaits one verifyOtp({ token_hash, type }) of the auth owner (the auth client, or verifyOtp from modules/auth/server), built from exactly those two query values, in place of or beside the one code exchange. The handler may read the exchange outcome's success or ok flag, and may call pure text helpers exported by the app's modules/routes or modules/auth (owner:auth-callback-pure-helpers): a synchronous function with no await, no network, storage, DOM, process or console access and no import beyond other such helpers, found by TypeScript symbol resolution, so the public origin and the same-origin validation of next stay in the owner modules. It carries no product data operation, business logic, additional request handler or JSON response."
|
|
5028
|
-
kinds: [lint]
|
|
5029
|
-
gates: [pre-commit, settle, land, ci]
|
|
5030
|
-
failureCodes: ["FE_CALLBACK_SHAPE"]
|
|
5031
|
-
enforcers:
|
|
5032
|
-
- {kind: eslint-fe, id: callback-shape}
|
|
5033
|
-
editions: [lite, full]
|
|
5034
|
-
docs: [docs/quality-bar.md#r287]
|
|
5035
|
-
exemplifiedBy:
|
|
5036
|
-
- {path: packages/eslint/fe/callback-shape.spec.mjs, editions: [lite, full], codes: [FE_CALLBACK_SHAPE]}
|
|
5037
|
-
- {path: packages/hfs/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts, editions: [lite, full]}
|
|
5038
|
-
proves:
|
|
5039
|
-
- {path: packages/eslint/fe/callback-shape.spec.mjs, editions: [lite, full], codes: [FE_CALLBACK_SHAPE]}
|
|
5040
|
-
witness:
|
|
5041
|
-
- {path: packages/eslint/fe/callback-shape.spec.mjs, editions: [lite, full], codes: [FE_CALLBACK_SHAPE]}
|
|
5042
|
-
|
|
5043
4931
|
- id: R288
|
|
5044
4932
|
code: "FE_STACK_UNLISTED"
|
|
5045
4933
|
law: "Every direct FE workspace dependency belongs to knowledge/hfs/fe-stack.yaml or is declared in hfs.json fe.extraPackages with its name and a nonempty reason. Installed runtime dependencies carried by a listed Grammar package are not app declarations and raise no FE_STACK_UNLISTED; direct forbidden dependencies still raise FE_STACK_FORBIDDEN. Every product Next app declares the catalog's core runtime packages; codegen, lint, TypeScript and Tailwind tools may be installed once at the app root. The marketing landing app is exempt (lite and full) from core presence. A front end that selects sides.fe.grammar and declares @starci/eslint-canon-fe 9 or later also declares that family's lint package from knowledge/grammars/index.yaml, which carries its Grammar component rules (FE_STACK_FAMILY_LINT_MISSING). GraphQL documents, including gql templates, match the back-end contracts, and SWR keys carry fetcher identities without an opt-in declaration."
|
|
@@ -5756,3 +5644,208 @@ rules:
|
|
|
5756
5644
|
source: owner
|
|
5757
5645
|
quote: "ok trò"
|
|
5758
5646
|
ruling: "A route page is one Grammar Page (owner ruling page-frame): the single owner of the page inset and rhythm; the lint refuses a sibling beside the Page and a bare PageHeader or PageContainer in a page owner."
|
|
5647
|
+
- id: R326
|
|
5648
|
+
code: "BE_AI_KEY_CUSTODY"
|
|
5649
|
+
law: "An upstream model provider key variable (OPENROUTER_API_KEY, OPENAI_API_KEY and the rest of the canon's one list) is named only inside the ai-router gateway's custody owner integrations/ai-gateway, in a literal, a member or a destructured property; every other file reaches a model through the gateway with a virtual key."
|
|
5650
|
+
kinds: [lint]
|
|
5651
|
+
gates: [pre-commit, settle, land, ci]
|
|
5652
|
+
failureCodes: ["BE_AI_KEY_CUSTODY"]
|
|
5653
|
+
enforcers:
|
|
5654
|
+
- {kind: eslint-be, id: provider-key-custody}
|
|
5655
|
+
editions: [lite, full]
|
|
5656
|
+
docs: [contexts/code/be/ai.md]
|
|
5657
|
+
templateNa: "The skeleton scaffolds no model call; an app adds the ai and ai-gateway integrations when it calls a model."
|
|
5658
|
+
proves:
|
|
5659
|
+
- packages/eslint-canon-be/src/ai-gateway.spec.ts
|
|
5660
|
+
exemplifiedBy:
|
|
5661
|
+
- {path: packages/eslint-canon-be/src/ai-gateway.spec.ts, editions: [lite, full], codes: [BE_AI_KEY_CUSTODY]}
|
|
5662
|
+
witness:
|
|
5663
|
+
- {path: packages/eslint-canon-be/src/ai-gateway.spec.ts, editions: [lite, full], codes: [BE_AI_KEY_CUSTODY]}
|
|
5664
|
+
cases:
|
|
5665
|
+
- id: case-1
|
|
5666
|
+
date: "2026-10-29"
|
|
5667
|
+
source: owner
|
|
5668
|
+
quote: "nhớ làm langchain nhé update .claude lun là call thẳng từ code phải dùng langchain với embeded context"
|
|
5669
|
+
ruling: "A model call made straight from code uses LangChain with retrieved context, through the ai-router gateway (owner ruling be-ai-langchain-gateway): only the gateway's custody owner reads a provider key."
|
|
5670
|
+
- id: R327
|
|
5671
|
+
code: "BE_AI_GATEWAY_CLIENT"
|
|
5672
|
+
law: "Code builds only the ChatOpenAI and OpenAIEmbeddings clients of @langchain/openai, each from an inline options object with no spread, its configuration.baseURL read from a <c>.options.ts type and its apiKey the reveal() of a platform/config Secret, so every model call goes through the ai-router gateway with a virtual key."
|
|
5673
|
+
kinds: [lint]
|
|
5674
|
+
gates: [pre-commit, settle, land, ci]
|
|
5675
|
+
failureCodes: ["BE_AI_GATEWAY_CLIENT"]
|
|
5676
|
+
enforcers:
|
|
5677
|
+
- {kind: eslint-be, id: ai-client-through-gateway}
|
|
5678
|
+
editions: [lite, full]
|
|
5679
|
+
docs: [contexts/code/be/ai.md]
|
|
5680
|
+
templateNa: "The skeleton scaffolds no model call; an app adds the ai and ai-gateway integrations when it calls a model."
|
|
5681
|
+
proves:
|
|
5682
|
+
- packages/eslint-canon-be/src/ai-gateway.spec.ts
|
|
5683
|
+
exemplifiedBy:
|
|
5684
|
+
- {path: packages/eslint-canon-be/src/ai-gateway.spec.ts, editions: [lite, full], codes: [BE_AI_GATEWAY_CLIENT]}
|
|
5685
|
+
witness:
|
|
5686
|
+
- {path: packages/eslint-canon-be/src/ai-gateway.spec.ts, editions: [lite, full], codes: [BE_AI_GATEWAY_CLIENT]}
|
|
5687
|
+
cases:
|
|
5688
|
+
- id: case-1
|
|
5689
|
+
date: "2026-10-29"
|
|
5690
|
+
source: owner
|
|
5691
|
+
quote: "nhớ làm langchain nhé update .claude lun là call thẳng từ code phải dùng langchain với embeded context"
|
|
5692
|
+
ruling: "A model call made straight from code uses LangChain with retrieved context, through the ai-router gateway (owner ruling be-ai-langchain-gateway): the LangChain clients point at the gateway's base URL with its virtual key."
|
|
5693
|
+
- id: R328
|
|
5694
|
+
code: "BE_AUTH_PASSPORT_OWNER"
|
|
5695
|
+
law: "The identity libraries (@nestjs/passport, passport, passport-google-oauth20, passport-local, @nestjs/jwt, jose and bcryptjs) are imported only by integrations/passport, the one owner of the Google OAuth 2.0 strategy (state and PKCE), the local email and password strategy (bcrypt) and the app's own access tokens; bcrypt and every Supabase client are owned nowhere."
|
|
5696
|
+
kinds: [lint]
|
|
5697
|
+
gates: [pre-commit, settle, land, ci]
|
|
5698
|
+
failureCodes: ["BE_AUTH_PASSPORT_OWNER"]
|
|
5699
|
+
enforcers:
|
|
5700
|
+
- {kind: eslint-be, id: infra-import-owner}
|
|
5701
|
+
editions: [lite]
|
|
5702
|
+
docs: [contexts/code/be/api-auth.md]
|
|
5703
|
+
proves:
|
|
5704
|
+
- packages/eslint-canon-be/src/auth-passport.spec.ts
|
|
5705
|
+
exemplifiedBy:
|
|
5706
|
+
- {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts, edition: lite}
|
|
5707
|
+
witness:
|
|
5708
|
+
- {path: packages/eslint-canon-be/src/auth-passport.spec.ts, edition: lite, codes: [BE_AUTH_PASSPORT_OWNER]}
|
|
5709
|
+
cases:
|
|
5710
|
+
- id: case-1
|
|
5711
|
+
date: "2026-10-29"
|
|
5712
|
+
source: owner
|
|
5713
|
+
quote: "self host postgresql + auth thì trò tự code gg auth (lite, full dùng keycloak); storage minio trên tayson có r!!!!; auth dùng passport nestjs; cập nhật lại luôn cả .claude nhé cho lite app"
|
|
5714
|
+
ruling: "Lite leaves Supabase: its identity is the app own Passport integration (Google OAuth and email/password) on its self-hosted PostgreSQL; full keeps Keycloak."
|
|
5715
|
+
- id: R329
|
|
5716
|
+
code: "BE_AUTH_JWT_OWN_JWKS"
|
|
5717
|
+
law: "A lite back-end principal comes only from the app's own access token verified by integrations/passport against its own key set (jose jwtVerify over createLocalJWKSet, issuer, audience and algorithms pinned), never from a remote JWKS, an unverified decode or a request body claim; domain/identity builds the principal from that verification."
|
|
5718
|
+
kinds: [check, lint]
|
|
5719
|
+
gates: [pre-commit, settle, land, ci]
|
|
5720
|
+
failureCodes: ["BE_AUTH_JWT_OWN_JWKS"]
|
|
5721
|
+
enforcers:
|
|
5722
|
+
- {kind: machine, id: own-jwks-principal, at: scripts/hfs/architecture/identity.mjs}
|
|
5723
|
+
- {kind: eslint-be, id: own-jwks-only}
|
|
5724
|
+
editions: [lite]
|
|
5725
|
+
docs: [contexts/code/be/api-auth.md]
|
|
5726
|
+
proves:
|
|
5727
|
+
- packages/hfs/src/be-auth-jwt-own-jwks.spec.mjs
|
|
5728
|
+
- packages/eslint-canon-be/src/auth-passport.spec.ts
|
|
5729
|
+
exemplifiedBy:
|
|
5730
|
+
- {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts, edition: lite}
|
|
5731
|
+
witness:
|
|
5732
|
+
- {path: packages/hfs/src/be-auth-jwt-own-jwks.spec.mjs, edition: lite, codes: [BE_AUTH_JWT_OWN_JWKS]}
|
|
5733
|
+
cases:
|
|
5734
|
+
- id: case-1
|
|
5735
|
+
date: "2026-10-29"
|
|
5736
|
+
source: owner
|
|
5737
|
+
quote: "self host postgresql + auth thì trò tự code gg auth (lite, full dùng keycloak); storage minio trên tayson có r!!!!; auth dùng passport nestjs; cập nhật lại luôn cả .claude nhé cho lite app"
|
|
5738
|
+
ruling: "The lite back end verifies its own asymmetric access tokens against its own JWKS; it trusts no remote identity provider."
|
|
5739
|
+
- id: R330
|
|
5740
|
+
code: "BE_AUTH_REFRESH_COOKIE"
|
|
5741
|
+
law: "A refresh token travels only in an httpOnly, Secure, SameSite=Strict cookie scoped to the refresh door (set and cleared only through integrations/passport), is rotated on every use with reuse detection, and never appears as a field of a request or response body."
|
|
5742
|
+
kinds: [lint]
|
|
5743
|
+
gates: [pre-commit, settle, land, ci]
|
|
5744
|
+
failureCodes: ["BE_AUTH_REFRESH_COOKIE"]
|
|
5745
|
+
enforcers:
|
|
5746
|
+
- {kind: eslint-be, id: refresh-token-cookie-only}
|
|
5747
|
+
editions: [lite]
|
|
5748
|
+
docs: [contexts/code/be/api-auth.md]
|
|
5749
|
+
proves:
|
|
5750
|
+
- packages/eslint-canon-be/src/auth-passport.spec.ts
|
|
5751
|
+
exemplifiedBy:
|
|
5752
|
+
- {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts, edition: lite}
|
|
5753
|
+
witness:
|
|
5754
|
+
- {path: packages/eslint-canon-be/src/auth-passport.spec.ts, edition: lite, codes: [BE_AUTH_REFRESH_COOKIE]}
|
|
5755
|
+
cases:
|
|
5756
|
+
- id: case-1
|
|
5757
|
+
date: "2026-10-29"
|
|
5758
|
+
source: owner
|
|
5759
|
+
quote: "self host postgresql + auth thì trò tự code gg auth (lite, full dùng keycloak); storage minio trên tayson có r!!!!; auth dùng passport nestjs; cập nhật lại luôn cả .claude nhé cho lite app"
|
|
5760
|
+
ruling: "The refresh token of the lite identity rotates and lives only in an httpOnly cookie; the browser holds no credential."
|
|
5761
|
+
- id: R331
|
|
5762
|
+
code: "BE_STORAGE_OWNER"
|
|
5763
|
+
law: "The S3 client (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) is imported only by integrations/object-storage, the ObjectStorage port over S3-compatible storage (MinIO in lite) with presigned PUT and GET; no module imports minio."
|
|
5764
|
+
kinds: [lint]
|
|
5765
|
+
gates: [pre-commit, settle, land, ci]
|
|
5766
|
+
failureCodes: ["BE_STORAGE_OWNER"]
|
|
5767
|
+
enforcers:
|
|
5768
|
+
- {kind: eslint-be, id: infra-import-owner}
|
|
5769
|
+
editions: [lite, full]
|
|
5770
|
+
docs: [contexts/code/be/infra.md]
|
|
5771
|
+
proves:
|
|
5772
|
+
- packages/eslint-canon-be/src/auth-passport.spec.ts
|
|
5773
|
+
exemplifiedBy:
|
|
5774
|
+
- {path: packages/hfs/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts, edition: lite}
|
|
5775
|
+
witness:
|
|
5776
|
+
- {path: packages/eslint-canon-be/src/auth-passport.spec.ts, editions: [lite, full], codes: [BE_STORAGE_OWNER]}
|
|
5777
|
+
cases:
|
|
5778
|
+
- id: case-1
|
|
5779
|
+
date: "2026-10-29"
|
|
5780
|
+
source: owner
|
|
5781
|
+
quote: "self host postgresql + auth thì trò tự code gg auth (lite, full dùng keycloak); storage minio trên tayson có r!!!!; auth dùng passport nestjs; cập nhật lại luôn cả .claude nhé cho lite app"
|
|
5782
|
+
ruling: "Lite storage is the MinIO already running on the Tay Son host, reached through the one S3 integration."
|
|
5783
|
+
- id: R332
|
|
5784
|
+
code: "LITE_DB_MIGRATION"
|
|
5785
|
+
law: "A lite app's schema lives only in db/: every schema change is one db/migrations/<ts14>_<kebab>.sql (archive db/migrations/archive/, seed db/seed.sql), and the tree holds no supabase/ folder and no SQL file outside db/ but a .starcistacks seed."
|
|
5786
|
+
kinds: [check]
|
|
5787
|
+
gates: [settle, land, ci]
|
|
5788
|
+
failureCodes: ["LITE_DB_MIGRATION"]
|
|
5789
|
+
enforcers:
|
|
5790
|
+
- {kind: hfs, id: lite-db-path, at: scripts/hfs/rules/database.mjs}
|
|
5791
|
+
editions: [lite]
|
|
5792
|
+
docs: [contexts/code/be/persistence.md]
|
|
5793
|
+
proves:
|
|
5794
|
+
- packages/hfs/src/db-migration-shape.spec.mjs
|
|
5795
|
+
exemplifiedBy:
|
|
5796
|
+
- {path: packages/hfs/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql, edition: lite}
|
|
5797
|
+
witness:
|
|
5798
|
+
- {path: packages/hfs/src/db-migration-shape.spec.mjs, edition: lite, codes: [LITE_DB_MIGRATION]}
|
|
5799
|
+
cases:
|
|
5800
|
+
- id: case-1
|
|
5801
|
+
date: "2026-10-29"
|
|
5802
|
+
source: owner
|
|
5803
|
+
quote: "self host postgresql + auth thì trò tự code gg auth (lite, full dùng keycloak); storage minio trên tayson có r!!!!; auth dùng passport nestjs; cập nhật lại luôn cả .claude nhé cho lite app"
|
|
5804
|
+
ruling: "The lite database is self-hosted PostgreSQL in the app stack; its migrations live in db/migrations and starci app db migrate applies them."
|
|
5805
|
+
- id: R333
|
|
5806
|
+
code: "BE_ENTITY_SCHEMA_DRIFT"
|
|
5807
|
+
law: "Every TypeORM entity column of an app's entity list (be/src/modules/platform/database/entities.ts) matches the schema its SQL migrations build: `starci app db drift` applies db/bootstrap.sql and every active db/migrations/*.sql to a scratch database (a container of the app's db image, or the empty database --url-env names), reads information_schema.columns and pg_catalog, and refuses a missing table, a missing column, a type that differs once both are normalised to PostgreSQL's format_type names (an enum by its type name), and a nullability that differs. A database column no entity maps is fine. A host that cannot provide the scratch database gets no verdict (exit 2, needs a database), never a green."
|
|
5808
|
+
kinds: [check]
|
|
5809
|
+
gates: [ci]
|
|
5810
|
+
failureCodes: ["BE_ENTITY_SCHEMA_DRIFT"]
|
|
5811
|
+
enforcers:
|
|
5812
|
+
- {kind: hfs, id: db-drift, at: packages/hfs/db/drift.mjs}
|
|
5813
|
+
editions: [lite]
|
|
5814
|
+
editionWhy: "The owner ruling of 2026-10-29 maps lite tables with TypeORM entities while db/migrations/*.sql stays the only DDL and synchronize is false, so only lite entities can drift from a schema they do not create; full entities ship with their own TypeORM migrations."
|
|
5815
|
+
docs: [contexts/code/be/persistence.md]
|
|
5816
|
+
templateNa: "The entities and the migrations are the product's tables; the check judges whichever an app writes, and a template scaffolds no table."
|
|
5817
|
+
proves:
|
|
5818
|
+
- {path: packages/hfs/src/db-drift.spec.mjs, edition: lite, codes: [BE_ENTITY_SCHEMA_DRIFT]}
|
|
5819
|
+
- {path: packages/hfs/src/db-drift-docker.spec.mjs, edition: lite, codes: [BE_ENTITY_SCHEMA_DRIFT]}
|
|
5820
|
+
exemplifiedBy:
|
|
5821
|
+
- {path: packages/hfs/src/db-drift.spec.mjs, edition: lite, codes: [BE_ENTITY_SCHEMA_DRIFT]}
|
|
5822
|
+
witness:
|
|
5823
|
+
- {path: packages/hfs/src/db-drift.spec.mjs, edition: lite, codes: [BE_ENTITY_SCHEMA_DRIFT]}
|
|
5824
|
+
cases:
|
|
5825
|
+
- id: case-1
|
|
5826
|
+
date: "2026-10-29"
|
|
5827
|
+
source: owner
|
|
5828
|
+
quote: "lite persistence moves to TypeORM entities while db/migrations/*.sql stays the only DDL and synchronize is false"
|
|
5829
|
+
ruling: "Lite entities only map tables the migrations create, so each entity column is checked against the migrated schema by starci app db drift, which CI runs in the build job and the release gate runs as its db-drift row."
|
|
5830
|
+
- id: R334
|
|
5831
|
+
code: "BE_PERSISTENCE_NO_RAW_SQL"
|
|
5832
|
+
law: "Lite domain and feature code reads and writes through TypeORM entities on the injected EntityManager (find, findOne, findBy, count, exists, insert, save, update, delete, upsert, increment, and createQueryBuilder(Entity, alias) with named parameters for joins, aggregates, FOR UPDATE / SKIP LOCKED and jsonb or vector operators; never a table-name builder, a builder with no entity or a Repository); no .query( call and no sql template outside platform/database; a database function TypeORM cannot express is called through the callDbFunction, advisoryXactLock and notify helpers of platform/database, and callDbFunction names only a function ruleParams.be.dbFunctions or hfs.json sides.be.dbFunctions lists with a reason. Entities only map tables db/migrations created (synchronize stays false); entity-versus-schema drift is BE_ENTITY_SCHEMA_DRIFT of starci app db drift."
|
|
5833
|
+
kinds: [lint]
|
|
5834
|
+
gates: [pre-commit, settle, land, ci]
|
|
5835
|
+
failureCodes: ["BE_PERSISTENCE_NO_RAW_SQL"]
|
|
5836
|
+
enforcers:
|
|
5837
|
+
- {kind: eslint-be, id: no-raw-sql}
|
|
5838
|
+
editions: [lite]
|
|
5839
|
+
docs: [contexts/code/be/persistence.md]
|
|
5840
|
+
proves:
|
|
5841
|
+
- packages/eslint-canon-be/src/no-raw-sql.spec.ts
|
|
5842
|
+
exemplifiedBy:
|
|
5843
|
+
- {path: packages/eslint-canon-be/src/no-raw-sql.spec.ts, edition: lite, codes: [BE_PERSISTENCE_NO_RAW_SQL]}
|
|
5844
|
+
witness:
|
|
5845
|
+
- {path: packages/eslint-canon-be/src/no-raw-sql.spec.ts, edition: lite, codes: [BE_PERSISTENCE_NO_RAW_SQL]}
|
|
5846
|
+
cases:
|
|
5847
|
+
- id: case-1
|
|
5848
|
+
date: "2026-10-29"
|
|
5849
|
+
source: owner
|
|
5850
|
+
quote: "big refactor đi … không viết raw sql như thế theo starci-academy-be"
|
|
5851
|
+
ruling: "Lite persistence moves to TypeORM entities on the EntityManager, the shape of full and starci-academy-be (ruling lite-persistence-typeorm-entities)."
|