@starci/hfs 5.4.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +4 -3
- package/cache/test-family.d.mts +1 -1
- package/cache/test-family.mjs +20 -15
- package/codemod/main.mjs +6 -1
- package/codemod/seeds-layout.d.mts +36 -0
- package/codemod/seeds-layout.mjs +113 -0
- package/codemod/sops-rules.d.mts +1 -1
- package/codemod/sops-rules.mjs +8 -3
- package/lint/run.d.mts +5 -4
- package/lint/run.mjs +53 -27
- package/package.json +1 -1
- package/release/lint-report.mjs +4 -3
- package/runtime/knowledge/hfs/canon-pins.yaml +12 -12
- package/runtime/knowledge/hfs/ci-standard.yaml +2 -2
- package/runtime/knowledge/hfs/rules.yaml +15 -2
- package/runtime/knowledge/hfs/slots.yaml +15 -18
- package/runtime/modules/kernel/failure-codes.yaml +64 -3
- package/runtime/scripts/hfs/architecture/root-packages.mjs +1 -1
- package/runtime/scripts/hfs/check.mjs +4 -1
- package/runtime/scripts/hfs/coverage-scope.d.mts +15 -0
- package/runtime/scripts/hfs/coverage-scope.mjs +26 -0
- package/runtime/scripts/hfs/db-squash.mjs +9 -2
- package/runtime/scripts/hfs/declaration-shape.mjs +3 -1
- package/runtime/scripts/hfs/rules/app-root.mjs +4 -1
- package/runtime/scripts/hfs/rules/ci-standard.mjs +5 -4
- package/runtime/scripts/hfs/rules/coverage-below.mjs +12 -4
- package/runtime/scripts/hfs/rules/database-config.mjs +8 -7
- package/runtime/scripts/hfs/rules/database-constants.d.mts +2 -1
- package/runtime/scripts/hfs/rules/database-constants.mjs +2 -1
- package/runtime/scripts/hfs/rules/database.d.mts +2 -2
- package/runtime/scripts/hfs/rules/database.mjs +7 -6
- package/runtime/scripts/hfs/rules/docker.mjs +24 -7
- package/runtime/scripts/hfs/rules/monorepo.d.mts +1 -1
- package/runtime/scripts/hfs/rules/monorepo.mjs +1 -1
- package/runtime/scripts/hfs/rules/seeds.d.mts +21 -0
- package/runtime/scripts/hfs/rules/seeds.mjs +194 -0
- package/runtime/scripts/hfs/rules/stacks.mjs +8 -4
- package/runtime/scripts/hfs/slots.d.mts +2 -0
- package/runtime/scripts/hfs/slots.mjs +1 -0
- package/runtime/scripts/hfs/stack-seeds-config.d.mts +88 -0
- package/runtime/scripts/hfs/stack-seeds-config.mjs +239 -0
- package/runtime/scripts/hfs/view.d.mts +25 -0
- package/runtime/scripts/hfs/view.mjs +41 -0
- package/scaffold/add-app.mjs +2 -2
- package/scaffold/add-cli-lite.mjs +41 -14
- package/scaffold/add-module.mjs +6 -3
- package/scaffold/lite-exports.mjs +1 -1
- package/scaffold/module-registry.mjs +10 -8
- package/src/main.mjs +8 -8
- package/stack/deploy.d.mts +3 -0
- package/stack/deploy.mjs +4 -1
- package/stack/local-up.mjs +25 -3
- package/stack/seeds.d.mts +29 -0
- package/stack/seeds.mjs +197 -0
- package/sync/canon-layout.d.mts +38 -0
- package/sync/canon-layout.mjs +62 -0
- package/sync/ci-commands.d.mts +2 -1
- package/sync/ci-commands.mjs +10 -7
- package/sync/index.d.mts +13 -2
- package/sync/index.mjs +52 -24
- package/templates/app/ci-workflows/github/workflows/ci.yml +2 -2
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +2 -2
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +26 -0
- package/templates/app/skeleton/.starcistacks/dev/README.md +21 -10
- package/templates/app/skeleton/.starcistacks/dev/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/dev/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/dev/seeds/seeds.yaml +3 -0
- package/templates/app/skeleton/.starcistacks/production/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/production/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/production/seeds/seeds.yaml +2 -0
- package/templates/app/skeleton/.starcistacks/production/seeds/sql/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton/.starcistacks/seeds/seeds.yaml +2 -0
- package/templates/app/skeleton/.starcistacks/seeds/sql/.gitkeep +0 -0
- package/templates/app/skeleton/README.md +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +2 -2
- package/templates/app/skeleton-lite/.starcistacks/dev/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/seeds/seeds.yaml +2 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/seeds/sql/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/production/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/production/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/production/seeds/seeds.yaml +2 -0
- package/templates/app/skeleton-lite/.starcistacks/production/seeds/sql/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/seeds/data/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/seeds/files/.gitkeep +0 -0
- package/templates/app/skeleton-lite/.starcistacks/seeds/seeds.yaml +2 -0
- package/templates/app/skeleton-lite/.starcistacks/seeds/sql/.gitkeep +0 -0
- package/templates/app/skeleton-lite/README.md +2 -2
- package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +2 -0
- package/templates/be/auth-lite/src/features/api/auth/application/signup.handler.ts +6 -1
- package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +14 -1
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +5 -1
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/reset.request.ts +0 -1
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/signup.request.ts +7 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +15 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +9 -3
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +79 -16
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +39 -9
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.spec.ts +108 -20
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.ts +34 -7
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/account-doors.port.ts +28 -4
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +6 -1
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +9 -2
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +11 -2
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +59 -9
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +4 -1
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +25 -4
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +8 -1
- package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +3 -1
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +3 -3
- package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +6 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +14 -9
- package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +32 -7
- package/templates/be/tool-config/jest.config.js +1 -1
- package/templates/fe/auth-lite/apps/web/src/modules/auth/email-doors.ts +4 -1
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/component.tsx +3 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/index.tsx +14 -6
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +1 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +1 -0
- package/templates/app/module/jest.config.js.tpl +0 -8
- /package/templates/app/skeleton/.starcistacks/dev/seeds/{primary-notes.sql → sql/primary-notes.sql} +0 -0
|
@@ -38,7 +38,7 @@ pins:
|
|
|
38
38
|
source: packages/next-data/package.json
|
|
39
39
|
why: 'the SWR data helpers of every front end (fe-stack data, owner:next-data-package): pollingRestart, the viewer and tenant key helpers, viewerSwrConfig and the viewer cache; each operation hook stays in the app and the package holds no component.'
|
|
40
40
|
'@starci/grammar':
|
|
41
|
-
version: 0.21.
|
|
41
|
+
version: 0.21.17
|
|
42
42
|
group: starci
|
|
43
43
|
install: registry
|
|
44
44
|
side: fe
|
|
@@ -52,28 +52,28 @@ pins:
|
|
|
52
52
|
source: packages/next-i18n/package.json
|
|
53
53
|
why: 'the next-intl stack of every front end (fe-stack i18n, owner:next-i18n-package): defineI18n over the locales of the app, the locale-negotiating proxy, the locale-aware navigation, the request config that loads the catalogs of the app and the locale read of the [locale] layout; the app keeps its message catalogs and writes none of the stack.'
|
|
54
54
|
'@starci/eslint-canon-be':
|
|
55
|
-
version: 3.
|
|
55
|
+
version: 3.15.0
|
|
56
56
|
group: starci
|
|
57
57
|
install: registry
|
|
58
58
|
side: be
|
|
59
59
|
source: packages/eslint-canon-be/package.json
|
|
60
60
|
why: 'the back-end ESLint canon: `loadHfs(import.meta.url)` of be/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the be side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; `starciBeConfig({ hfs: loadHfs(import.meta.url) })` is the typed factory and the BE-CONVENTION laws are its rules.'
|
|
61
61
|
'@starci/eslint-canon-fe':
|
|
62
|
-
version: 9.
|
|
62
|
+
version: 9.13.0
|
|
63
63
|
group: starci
|
|
64
64
|
install: registry
|
|
65
65
|
side: fe
|
|
66
66
|
source: packages/eslint-canon-fe/package.json
|
|
67
67
|
why: 'the front-end ESLint canon: `loadHfs(import.meta.url)` of fe/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the fe side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; the front end has no tests (FE_NO_TESTS R97 in hfs) and no-vietnamese-in-source (R91) applies.'
|
|
68
68
|
'@starci/eslint-grammar-core':
|
|
69
|
-
version: 0.21.
|
|
69
|
+
version: 0.21.17
|
|
70
70
|
group: starci
|
|
71
71
|
install: registry
|
|
72
72
|
side: fe
|
|
73
73
|
source: packages/eslint-grammar-core/package.json
|
|
74
74
|
why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
|
|
75
75
|
'@starci/eslint-grammar-offset-pop':
|
|
76
|
-
version: 0.21.
|
|
76
|
+
version: 0.21.17
|
|
77
77
|
group: starci
|
|
78
78
|
install: registry
|
|
79
79
|
side: fe
|
|
@@ -99,7 +99,7 @@ pins:
|
|
|
99
99
|
side: both
|
|
100
100
|
source: packages/prettier-config/package.json
|
|
101
101
|
'@starci/jest-preset':
|
|
102
|
-
version: 2.
|
|
102
|
+
version: 2.7.0
|
|
103
103
|
group: starci
|
|
104
104
|
install: registry
|
|
105
105
|
side: be
|
|
@@ -127,14 +127,14 @@ pins:
|
|
|
127
127
|
source: packages/nest-kit/package.json
|
|
128
128
|
why: 'the module definition of every back-end module: defineConfigurableModule and defineCapabilityModule return the module base class, the options token and the options types with their types stated, so a <x>.module-definition.ts is one call that the lint on stated export types (R368) and the duplicate-code check (R21) both accept; @starci/jest-preset compiles it as an ESM-only dependency.'
|
|
129
129
|
'@starci/auth':
|
|
130
|
-
version: 0.
|
|
130
|
+
version: 0.3.0
|
|
131
131
|
group: starci
|
|
132
132
|
install: registry
|
|
133
133
|
side: both
|
|
134
134
|
source: packages/auth/package.json
|
|
135
135
|
why: 'the lite sign-in: the NestJS module (EdDSA access tokens with a multi-key JWKS, passwords, Google OAuth with state and PKCE, rotating refresh sessions, the sign-in budget, the refresh cookie), the front-end client and its React bindings, the door contracts and test doubles; the one importer of the identity libraries, reached by a back end only through integrations/passport; @starci/jest-preset compiles it as an ESM-only dependency.'
|
|
136
136
|
'@starci/auth-oidc':
|
|
137
|
-
version: 0.1.
|
|
137
|
+
version: 0.1.2
|
|
138
138
|
group: starci
|
|
139
139
|
install: registry
|
|
140
140
|
side: both
|
|
@@ -155,7 +155,7 @@ pins:
|
|
|
155
155
|
source: packages/mail/package.json
|
|
156
156
|
why: 'outgoing mail of a back end: a NestJS module and the Mailer port over SMTP (connect, greeting, socket and total timeouts, STARTTLS required by default, recipient, message and attachment checks, a shared SMTP target policy, coded errors, typed escaping render helpers returning { subject, text, html }), with @starci/mail/testing for its specs; the only importer of nodemailer (R90).'
|
|
157
157
|
'@starci/hfs':
|
|
158
|
-
version: 5.
|
|
158
|
+
version: 5.5.0
|
|
159
159
|
group: starci
|
|
160
160
|
install: registry
|
|
161
161
|
side: both
|
|
@@ -592,7 +592,7 @@ pins:
|
|
|
592
592
|
version: 3.1150.0
|
|
593
593
|
group: framework
|
|
594
594
|
side: be
|
|
595
|
-
why: 'the S3 transport inside @starci/object-storage, the one ObjectStorage adapter of both editions (MinIO, S3, Spaces), which depends on it at exactly this version and which no app file imports; 3.1150.0 is the release of 2026-10-12
|
|
595
|
+
why: 'the S3 transport inside @starci/object-storage, the one ObjectStorage adapter of both editions (MinIO, S3, Spaces), which depends on it at exactly this version and which no app file imports; 3.1150.0 is the release of 2026-10-12.'
|
|
596
596
|
'@aws-sdk/s3-request-presigner':
|
|
597
597
|
version: 3.1150.0
|
|
598
598
|
group: framework
|
|
@@ -602,7 +602,7 @@ pins:
|
|
|
602
602
|
version: 10.1.7
|
|
603
603
|
group: framework
|
|
604
604
|
side: be
|
|
605
|
-
why: 'the SMTP client inside @starci/mail, which depends on it at exactly this version and which no app file imports; 10.1.7 is the release of 2026-10-18
|
|
605
|
+
why: 'the SMTP client inside @starci/mail, which depends on it at exactly this version and which no app file imports; 10.1.7 is the release of 2026-10-18.'
|
|
606
606
|
'@apollo/server':
|
|
607
607
|
version: 5.5.1
|
|
608
608
|
group: framework
|
|
@@ -652,7 +652,7 @@ pins:
|
|
|
652
652
|
version: 6.29.0
|
|
653
653
|
group: framework
|
|
654
654
|
side: be
|
|
655
|
-
why: 'the Agent of platform/http (its one owner, R90): FetchHttpClient hands fetch a pool that trusts HttpOptions.trustedCa beside the bundled roots (a test world''s per-run fake CA); the 6 line is the undici the Node 22 runtime of the app images bundles, and its Agent also serves the fetch of Node 24; 6.29.0 is the release of 2026-09-25
|
|
655
|
+
why: 'the Agent of platform/http (its one owner, R90): FetchHttpClient hands fetch a pool that trusts HttpOptions.trustedCa beside the bundled roots (a test world''s per-run fake CA); the 6 line is the undici the Node 22 runtime of the app images bundles, and its Agent also serves the fetch of Node 24; 6.29.0 is the release of 2026-09-25.'
|
|
656
656
|
# --- lite identity (integrations/passport, ruling lite-self-hosted-postgres-passport-minio)
|
|
657
657
|
'@nestjs/passport':
|
|
658
658
|
version: 11.0.5
|
|
@@ -77,7 +77,7 @@ items:
|
|
|
77
77
|
check: ci-standard
|
|
78
78
|
- id: sonar-lint-per-side
|
|
79
79
|
title: the Sonar lint runs per side
|
|
80
|
-
requires: "a lint with a Sonar import (`starci app check --gate lint --out`) names its side (--side be|fe); the reports are merged into reports/lint.sonar.json afterwards; no combined lint with Sonar"
|
|
80
|
+
requires: "a lint with a Sonar import (`starci app check --gate lint --out`) names its side (--side be|fe|packages); the reports are merged into reports/lint.sonar.json afterwards; no combined lint with Sonar"
|
|
81
81
|
covers: [app-ci, runtime]
|
|
82
82
|
check: ci-standard
|
|
83
83
|
- id: test-sharding
|
|
@@ -87,7 +87,7 @@ items:
|
|
|
87
87
|
check: ci-standard
|
|
88
88
|
- id: lint-parallel
|
|
89
89
|
title: lint each side in its own job
|
|
90
|
-
requires: "the lint job is a matrix over side [be, fe]; each
|
|
90
|
+
requires: "the lint job is a matrix over side [be, fe], plus packages in an app with root packages; each scope lints (with its own Sonar report when a token is set) in parallel and on its own, and the sonar job merges the reports"
|
|
91
91
|
covers: [app-ci]
|
|
92
92
|
check: ci-standard
|
|
93
93
|
- id: cache-saved-always
|
|
@@ -3299,7 +3299,7 @@ rules:
|
|
|
3299
3299
|
code: "HFS_DOCKER_BUILD_CONTEXT"
|
|
3300
3300
|
editions: [lite, full]
|
|
3301
3301
|
docs: [docs/application-stacks.md#r187]
|
|
3302
|
-
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,
|
|
3302
|
+
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, no COPY or ADD source leaves the build context (a `..` segment or an absolute path), and the image of a Next app whose front end reads be/contracts/ copies that folder exactly when the app holds it (a COPY of a missing folder fails the build, a missing COPY leaves the codegen without its contract)."
|
|
3303
3303
|
kinds: [check]
|
|
3304
3304
|
gates: [settle, land, ci]
|
|
3305
3305
|
failureCodes: ["HFS_DOCKER_BUILD_CONTEXT"]
|
|
@@ -5791,7 +5791,7 @@ rules:
|
|
|
5791
5791
|
ruling: "Lite storage is the MinIO already running on the Tay Son host, reached through the one S3 integration."
|
|
5792
5792
|
- id: R332
|
|
5793
5793
|
code: "LITE_DB_MIGRATION"
|
|
5794
|
-
law: "A lite app's schema lives only in db/: every schema change is one db/migrations/<ts14>_<kebab>.sql (archive db/migrations/archive/,
|
|
5794
|
+
law: "A lite app's schema lives only in db/: every schema change is one db/migrations/<ts14>_<kebab>.sql (archive db/migrations/archive/, the managed db/bootstrap.sql), every other SQL file under db/ is refused, and the tree holds no supabase/ folder and no SQL file outside db/ but the sql/ entries of its seed levels (R375)."
|
|
5795
5795
|
kinds: [check]
|
|
5796
5796
|
gates: [settle, land, ci]
|
|
5797
5797
|
failureCodes: ["LITE_DB_MIGRATION"]
|
|
@@ -6628,3 +6628,16 @@ rules:
|
|
|
6628
6628
|
- {path: contexts/project/stacks.md#project-stacks-11-a-customised-service-is-an-image-the-app-builds-the-same-in-every-environment, editions: [lite, full]}
|
|
6629
6629
|
proves:
|
|
6630
6630
|
- {path: packages/hfs/src/stacks-service-images.spec.mjs, editions: [lite, full]}
|
|
6631
|
+
- id: R375
|
|
6632
|
+
code: "HFS_SEEDS_SHAPE"
|
|
6633
|
+
law: "Seed data lives only in the seed levels of `.starcistacks/`, where `seeds/` is applied in every environment first and `<env>/seeds/` in the one environment the stack declaration names `<env>`; each level is a `seeds.yaml` manifest (schema starci/seeds@1) whose entries list every file of its `sql/`, `data/` and `files/` folders; a seed is never a migration (no statement that creates, alters, drops, truncates, grants, revokes or comments on an object), never custody, a data document validates against the schema its entry names, a level a production environment applies holds no test data but the one test account an environment declares (a test-account entry with its reason), and a document names a secret only as a reference to a sealed seeds-<name> member of its environment."
|
|
6634
|
+
kinds: [check]
|
|
6635
|
+
gates: [settle, land, ci]
|
|
6636
|
+
failureCodes: ["HFS_SEEDS_SHAPE", "HFS_SEED_MANIFEST", "HFS_SEED_DDL", "HFS_SEED_DATA_SCHEMA", "HFS_SEED_TEST_DATA", "HFS_SEED_SECRET_REF"]
|
|
6637
|
+
enforcers:
|
|
6638
|
+
- {kind: hfs, id: seeds-shape, at: scripts/hfs/rules/seeds.mjs}
|
|
6639
|
+
editions: [lite, full]
|
|
6640
|
+
docs:
|
|
6641
|
+
- {path: contexts/project/stacks.md#project-stacks-12-seed-data-lives-in-the-seed-levels-of-the-stack, editions: [lite, full]}
|
|
6642
|
+
proves:
|
|
6643
|
+
- {path: packages/hfs/src/hfs-seeds-shape.spec.mjs, editions: [lite, full]}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# catalog READ this file through scripts/hfs/slots.mjs; none of them hardcodes a path.
|
|
8
8
|
# Shape: modules/schemas/hfs-slots.schema.yaml. The repository side is hfs.json: modules/schemas/hfs-repo.schema.yaml.
|
|
9
9
|
schema: starci/hfs-slots@3
|
|
10
|
-
version: 3.
|
|
10
|
+
version: 3.19.0
|
|
11
11
|
|
|
12
12
|
versioning:
|
|
13
13
|
# MAJOR.MINOR.PATCH of this manifest. A repository pins only the MAJOR in hfs.json ("hfs": 3).
|
|
@@ -61,6 +61,10 @@ versioning:
|
|
|
61
61
|
# 3.17.0 (ruling auth-oidc-package, 2026-11-03) adds the optional full-edition be.integrations.keycloak, the glue of @starci/auth-oidc (the OIDC client of the
|
|
62
62
|
# identity provider behind the PrincipalVerifier of @starci/auth), and gives @starci/auth-oidc to it in ruleParams.be.infraOwners.
|
|
63
63
|
# 3.17.0 (ruling module-packages, 2026-11-03) adds the optional app.packages and app.module-registry: the workspace packages of the app root (the agent SDK, the module packages, the managed registry).
|
|
64
|
+
# 3.18.0 (ruling seed-levels, 2026-11-03) gives app.starcistacks its two seed levels, the shared .starcistacks/seeds/ and each environment's
|
|
65
|
+
# <env>/seeds/, each a seeds.yaml manifest and the folders sql/, data/ and files/, and retires app.db.seed (its rows are the shared level's
|
|
66
|
+
# sql/ entries); `starci app codemod seeds-layout` moves an older app's seed data into them.
|
|
67
|
+
# 3.19.0 (ruling root-packages-any-name, 2026-11-03) widens app.packages to any packages/<name>/: the agent SDK, a block library, a module package.
|
|
64
68
|
# 3.15.0 (ruling app-check-cache, 2026-10-31) adds the optional app.starcistacks.cache, the committed check cache of the app (.starcistacks/cache/, both editions).
|
|
65
69
|
# 3.14.0 (ruling app-secrets-one-standard, 2026-10-29) adds the optional app.ci-stack-key-restore, the managed workflow that writes the
|
|
66
70
|
# deploy server's age identity back from its GitHub Environment backup.
|
|
@@ -502,7 +506,7 @@ ruleParams:
|
|
|
502
506
|
# R334 (lite): the only `.query(` outside platform/database's composer and the test world is the pass-through of a file listed here
|
|
503
507
|
# (or in hfs.json sides.be.sqlPassThrough), whose SQL text is not the app's own. Paths are relative to the side folder.
|
|
504
508
|
sqlPassThrough:
|
|
505
|
-
- {file: src/modules/platform/database/seed-runner.service.ts, reason: "the tracked seed
|
|
509
|
+
- {file: src/modules/platform/database/seed-runner.service.ts, reason: "the tracked seed files' text, run as it is (the sql/ entries of .starcistacks/seeds/, then of .starcistacks/<env>/seeds/); the app writes no SQL there"}
|
|
506
510
|
|
|
507
511
|
lite:
|
|
508
512
|
schemaAuthority: sql
|
|
@@ -891,10 +895,10 @@ slots:
|
|
|
891
895
|
- id: app.packages
|
|
892
896
|
since: 3.17.0
|
|
893
897
|
profiles: [app]
|
|
894
|
-
editionWhy: Lite and full share the slot; a product
|
|
895
|
-
# A workspace package of the app ROOT (packages
|
|
898
|
+
editionWhy: Lite and full share the slot; every root workspace package of a product (its agent SDK, a block library, a module package, a reviewed plugin package) is one in both.
|
|
899
|
+
# A workspace package of the app ROOT (packages/<name>: the agent SDK, a block library, packages/module-<name>): first-party source that both sides import through its
|
|
896
900
|
# package entry points and never past them (ARCH_INTERNAL_IMPORT_OUTSIDE), linted by the root eslint config by subpath.
|
|
897
|
-
path: "packages
|
|
901
|
+
path: "packages/<name>/"
|
|
898
902
|
presence: optional
|
|
899
903
|
tracked: tracked
|
|
900
904
|
tier: none
|
|
@@ -964,16 +968,18 @@ slots:
|
|
|
964
968
|
tests: none
|
|
965
969
|
requires: [application-stacks.yaml]
|
|
966
970
|
allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,swarm,k8s,terraform}/**", "<env>/infra/metadata.json",
|
|
967
|
-
"<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.{enc.yaml,enc}", "<env>/secrets/.gitkeep",
|
|
971
|
+
"<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.{enc.yaml,enc}", "<env>/secrets/.gitkeep",
|
|
972
|
+
"seeds/seeds.yaml", "seeds/{sql,data,files}/**", "<env>/seeds/seeds.yaml", "<env>/seeds/{sql,data,files}/**"]
|
|
968
973
|
forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
|
|
969
|
-
# Owner ruling 2026-10-14: lite declares docker-swarm production, app runtime config, sealed custody and seed
|
|
974
|
+
# Owner ruling 2026-10-14: lite declares docker-swarm production, app runtime config, sealed custody and the seed levels.
|
|
970
975
|
# The db service supplies the database; schedules are platform/jobs ticks (R335); production has no Compose or host cron.
|
|
971
976
|
lite:
|
|
972
977
|
requires: [application-stacks.yaml]
|
|
973
978
|
allows: [application-stacks.yaml, images/db/Dockerfile, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,swarm}/**", "<env>/infra/metadata.json",
|
|
974
|
-
"<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.{enc.yaml,enc}", "<env>/secrets/.gitkeep",
|
|
979
|
+
"<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.{enc.yaml,enc}", "<env>/secrets/.gitkeep",
|
|
980
|
+
"seeds/seeds.yaml", "seeds/{sql,data,files}/**", "<env>/seeds/seeds.yaml", "<env>/seeds/{sql,data,files}/**"]
|
|
975
981
|
forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
|
|
976
|
-
rules: [HFS_STACKS_SHAPE, HFS_STACKS_IN_SIDE, HFS_PLAINTEXT_SECRET, HFS_IDENTITY_CUSTODY]
|
|
982
|
+
rules: [HFS_STACKS_SHAPE, HFS_STACKS_IN_SIDE, HFS_PLAINTEXT_SECRET, HFS_IDENTITY_CUSTODY, HFS_SEEDS_SHAPE, HFS_SEED_MANIFEST, HFS_SEED_DDL, HFS_SEED_DATA_SCHEMA, HFS_SEED_TEST_DATA, HFS_SEED_SECRET_REF, HFS_SEED_PII]
|
|
977
983
|
- id: app.sops
|
|
978
984
|
since: 3.1.0
|
|
979
985
|
profiles: [app]
|
|
@@ -1072,15 +1078,6 @@ slots:
|
|
|
1072
1078
|
tier: none
|
|
1073
1079
|
tests: none
|
|
1074
1080
|
since: 3.9.0
|
|
1075
|
-
- id: app.db.seed
|
|
1076
|
-
profiles: [app]
|
|
1077
|
-
editions: [lite]
|
|
1078
|
-
path: "db/seed.sql"
|
|
1079
|
-
presence: optional # synthetic dev data only; a lite app seeds nothing it cannot regenerate
|
|
1080
|
-
tracked: tracked
|
|
1081
|
-
tier: none
|
|
1082
|
-
tests: none
|
|
1083
|
-
since: 3.9.0
|
|
1084
1081
|
- id: app.db.image
|
|
1085
1082
|
profiles: [app]
|
|
1086
1083
|
editions: [lite]
|
|
@@ -4233,6 +4233,67 @@ HFS_SEED_PII:
|
|
|
4233
4233
|
owner: op-retry
|
|
4234
4234
|
kind: check-finding
|
|
4235
4235
|
|
|
4236
|
+
HFS_SEEDS_SHAPE:
|
|
4237
|
+
title: "Seed data lives in a seed level of .starcistacks and holds no custody"
|
|
4238
|
+
title_vi: "Dữ liệu seed nằm ngoài cấp seed hoặc chứa custody"
|
|
4239
|
+
meaning_vi: "Một tệp dưới `.starcistacks/seeds/` hoặc `.starcistacks/<env>/seeds/` không phải `seeds.yaml` và không nằm trong `sql/`, `data/`, `files/`; hoặc thuộc cấp seed của một môi trường không được khai báo; hoặc là custody (envelope `.enc`, khoá `.pem`/`.key`, tệp `.env`)."
|
|
4240
|
+
causes_vi:
|
|
4241
|
+
- "Vi phạm R375: dữ liệu seed chỉ nằm trong hai cấp seed của `.starcistacks/`, mỗi cấp là một manifest và các thư mục sql/, data/, files/."
|
|
4242
|
+
- "Một secret được đặt cạnh seed thay vì niêm phong bằng sops trong `.starcistacks/<env>/secrets/`."
|
|
4243
|
+
nextStep_vi: "Chuyển tệp vào thư mục loại của cấp seed (`starci app codemod seeds-layout` chuyển dữ liệu cũ); niêm phong secret bằng `starci app secret set --env <env>` và xoá bản rõ."
|
|
4244
|
+
owner: op-retry
|
|
4245
|
+
kind: check-finding
|
|
4246
|
+
|
|
4247
|
+
HFS_SEED_MANIFEST:
|
|
4248
|
+
title: "Every seed file is an entry of its level's seeds.yaml"
|
|
4249
|
+
title_vi: "Manifest seeds.yaml thiếu, sai hoặc không liệt kê tệp seed"
|
|
4250
|
+
meaning_vi: "Cấp seed có tệp nhưng không có `seeds.yaml`, manifest không đọc được hoặc sai schema starci/seeds@1, một mục trỏ tới đường dẫn không tồn tại, `after` nêu id không có hoặc tạo vòng, hay một tệp trong sql/, data/, files/ không thuộc mục nào."
|
|
4251
|
+
causes_vi:
|
|
4252
|
+
- "Vi phạm R375: manifest là nơi khai báo thứ tự, loại, đích và chế độ (once, upsert) của mọi seed."
|
|
4253
|
+
nextStep_vi: "Thêm hoặc sửa mục `{ id, kind, path, target, mode, after? }` trong `seeds.yaml` của cấp đó cho tệp được nêu."
|
|
4254
|
+
owner: op-retry
|
|
4255
|
+
kind: check-finding
|
|
4256
|
+
|
|
4257
|
+
HFS_SEED_DDL:
|
|
4258
|
+
title: "A seed only writes rows: seed data is never a migration"
|
|
4259
|
+
title_vi: "Seed chứa câu lệnh thay đổi schema"
|
|
4260
|
+
meaning_vi: "Một tệp SQL của mục sql có câu lệnh CREATE, ALTER, DROP, TRUNCATE, GRANT, REVOKE hoặc COMMENT: thay đổi schema hoặc quyền, việc chỉ migration được làm."
|
|
4261
|
+
causes_vi:
|
|
4262
|
+
- "Vi phạm R375 và BE-PERSISTENCE-1: dữ liệu seed không bao giờ là migration, và migration là nơi duy nhất thay đổi schema."
|
|
4263
|
+
nextStep_vi: "Chuyển câu lệnh schema thành một migration (lite: `db/migrations/<ts14>_<kebab>.sql`), để seed chỉ ghi dòng dữ liệu một cách idempotent (`on conflict do nothing` hoặc upsert)."
|
|
4264
|
+
owner: op-retry
|
|
4265
|
+
kind: check-finding
|
|
4266
|
+
|
|
4267
|
+
HFS_SEED_DATA_SCHEMA:
|
|
4268
|
+
title: "A data seed validates against the schema its entry names"
|
|
4269
|
+
title_vi: "Tài liệu seed không khớp schema đã khai báo"
|
|
4270
|
+
meaning_vi: "Một tài liệu JSON hoặc YAML của mục data không qua được JSON schema mà mục đó nêu (`schema`, do app khai báo), hoặc schema đó không đọc được."
|
|
4271
|
+
causes_vi:
|
|
4272
|
+
- "Vi phạm R375: app sở hữu ý nghĩa và schema của dữ liệu, harness kiểm tra tài liệu trước khi lệnh của app nạp nó."
|
|
4273
|
+
nextStep_vi: "Sửa tài liệu cho khớp schema, hoặc sửa đường dẫn `schema` của mục trỏ tới schema app khai báo."
|
|
4274
|
+
owner: op-retry
|
|
4275
|
+
kind: check-finding
|
|
4276
|
+
|
|
4277
|
+
HFS_SEED_TEST_DATA:
|
|
4278
|
+
title: "A level production applies holds no test data"
|
|
4279
|
+
title_vi: "Seed áp dụng cho production chứa dữ liệu thử"
|
|
4280
|
+
meaning_vi: "Cấp seed dùng chung hoặc cấp của một môi trường không phải docker-compose có danh tính example.test hoặc giá trị, tên tệp bắt đầu bằng uat- hay test- ngoài mục test-account duy nhất mà môi trường đó khai báo."
|
|
4281
|
+
causes_vi:
|
|
4282
|
+
- "Vi phạm R375: dữ liệu thử (tài khoản mẫu, workspace mẫu) chỉ nằm trong cấp seed của môi trường dev."
|
|
4283
|
+
nextStep_vi: "Chuyển dữ liệu thử vào `.starcistacks/dev/seeds/`, hoặc khai báo tài khoản thử duy nhất của production là một mục `kind: test-account` có `reason`; chỉ để dữ liệu ban đầu thật (gói, danh mục) ở cấp dùng chung."
|
|
4284
|
+
owner: op-retry
|
|
4285
|
+
kind: check-finding
|
|
4286
|
+
|
|
4287
|
+
HFS_SEED_SECRET_REF:
|
|
4288
|
+
title: "A seed names a secret only as a sealed seeds-<name> member of its environment"
|
|
4289
|
+
title_vi: "Tham chiếu secret của seed sai tên hoặc thiếu member niêm phong"
|
|
4290
|
+
meaning_vi: "Một tài liệu seed có `{ \"secret\": \"<member>\" }` mà tên không có dạng seeds-<name>, hoặc môi trường của cấp đó (cấp dùng chung: mọi môi trường) không có `.starcistacks/<env>/secrets/<member>.enc.yaml`."
|
|
4291
|
+
causes_vi:
|
|
4292
|
+
- "Vi phạm R375: giá trị bí mật của seed nằm trong custody sops của môi trường, được bước seed mở trong bộ nhớ, không bao giờ nằm trong tệp seed."
|
|
4293
|
+
nextStep_vi: "Niêm phong giá trị bằng `starci app secret set --env <env>` vào member seeds-<name> của môi trường đó và tham chiếu đúng tên."
|
|
4294
|
+
owner: op-retry
|
|
4295
|
+
kind: check-finding
|
|
4296
|
+
|
|
4236
4297
|
HFS_SERVICE_PLACEMENT:
|
|
4237
4298
|
title: "A back-end service lives only at be/apps/<service>"
|
|
4238
4299
|
title_vi: "Dịch vụ back end nằm ngoài be/apps/<service>"
|
|
@@ -5668,10 +5729,10 @@ BE_STORAGE_OWNER:
|
|
|
5668
5729
|
LITE_DB_MIGRATION:
|
|
5669
5730
|
title: "A lite schema change lives outside db/migrations"
|
|
5670
5731
|
title_vi: "Thay đổi schema của app lite nằm ngoài db/migrations"
|
|
5671
|
-
meaning_vi: "`<path>` là tệp của thư mục supabase
|
|
5732
|
+
meaning_vi: "`<path>` là tệp của thư mục supabase/, một tệp SQL ngoài db/ (trừ mục sql của các cấp seed trong `.starcistacks/`), hoặc một tệp SQL trong db/ không phải migration, archive hay bootstrap."
|
|
5672
5733
|
causes_vi:
|
|
5673
|
-
- "Vi phạm R332: schema của app lite chỉ nằm trong db/migrations/<ts14>_<kebab>.sql, archive và seed trong
|
|
5674
|
-
nextStep_vi: "Chuyển migration sang db/migrations (giữ nguyên stamp), seed sang
|
|
5734
|
+
- "Vi phạm R332: schema của app lite chỉ nằm trong db/migrations/<ts14>_<kebab>.sql, archive và db/bootstrap.sql; dữ liệu seed nằm trong các cấp seed của `.starcistacks/` (R375)."
|
|
5735
|
+
nextStep_vi: "Chuyển migration sang db/migrations (giữ nguyên stamp), seed sang `.starcistacks/seeds/sql/` (`starci app codemod seeds-layout`), types sang db/types/; xoá supabase/."
|
|
5675
5736
|
owner: op-retry
|
|
5676
5737
|
kind: check-finding
|
|
5677
5738
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// root-packages.mjs - the workspace packages of an app ROOT (packages
|
|
1
|
+
// root-packages.mjs - the workspace packages of an app ROOT (packages/<name>: the agent SDK, a block library, a module package, the managed packages/modules).
|
|
2
2
|
// They lie outside both side folders and belong to no side; both sides import them by package name, through their package entry points
|
|
3
3
|
// (the `exports` of the manifest), so the import graph reads them as first-party packages and the back-end canon never reads them as
|
|
4
4
|
// a third-party library. The app root's one package.json lists them in `workspaces`; the patterns that start with a side folder
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
// HFS_MIN_INSTANCES fewer instances of a slot than minInstances
|
|
19
19
|
// HFS_PLAINTEXT_SECRET (R06, rules/secrets.mjs) a plaintext secret file or value in a tracked file; a `.enc` that is no sops envelope
|
|
20
20
|
// HFS_STACKS_SHAPE (R10, rules/stacks.mjs) invalid stack shape, Swarm runtime policy, host crontab installation or lite backing duplication
|
|
21
|
+
// HFS_SEEDS_SHAPE, HFS_SEED_MANIFEST, HFS_SEED_DDL, HFS_SEED_DATA_SCHEMA, HFS_SEED_TEST_DATA, HFS_SEED_SECRET_REF (R375, rules/seeds.mjs) seed
|
|
22
|
+
// data outside the seed levels or unlisted, custody, a schema statement, a document off its schema, test data
|
|
23
|
+
// production applies outside its one test account, a secret reference to no sealed seeds-<name> member
|
|
21
24
|
// HFS_CI_MISSING_CANON (R13, rules/pipeline.mjs) CI without the pinned `starci app check`, pre-push without typecheck or lint
|
|
22
25
|
// HFS_DEP_VERSION_SKEW (R14, rules/deps.mjs) a dependency at two versions in the workspace, or a nested copy in the lockfile
|
|
23
26
|
// HFS_CONTRACT_SNAPSHOT_DRIFT (R23, rules/contract.mjs) an uncommitted back-end snapshot, or a front-end copy that differs from it
|
|
@@ -108,7 +111,7 @@ export const CHECK_CODES = Object.freeze([
|
|
|
108
111
|
'HFS_SLOT_UNDECLARED', 'HFS_SLOT_NOT_ENABLED', 'HFS_SLOT_AMBIGUOUS', 'HFS_TRACKED_MUST_BE_IGNORED', 'HFS_FORBIDDEN_PRESENT',
|
|
109
112
|
'HFS_SLOT_REQUIRED_MISSING', 'HFS_MIN_INSTANCES', 'HFS_CANON_PIN_DRIFT', 'HFS_SIZE_SOFT_BACKLOG', 'BE_SOURCE_FORM',
|
|
110
113
|
'HFS_MANAGED_FILE_DRIFT', 'HFS_TOOL_CONFIG_LOCAL', 'HFS_RULE_OFF_WITHOUT_REPLACEMENT', 'HFS_TS_STRICT',
|
|
111
|
-
'HFS_PLAINTEXT_SECRET', 'HFS_STACKS_SHAPE', 'HFS_CI_MISSING_CANON', 'HFS_DEP_VERSION_SKEW', 'HFS_CONTRACT_SNAPSHOT_DRIFT', 'HFS_MCP_TOOLS_SNAPSHOT',
|
|
114
|
+
'HFS_PLAINTEXT_SECRET', 'HFS_STACKS_SHAPE', 'HFS_SEEDS_SHAPE', 'HFS_SEED_MANIFEST', 'HFS_SEED_DDL', 'HFS_SEED_DATA_SCHEMA', 'HFS_SEED_TEST_DATA', 'HFS_SEED_SECRET_REF', 'HFS_CI_MISSING_CANON', 'HFS_DEP_VERSION_SKEW', 'HFS_CONTRACT_SNAPSHOT_DRIFT', 'HFS_MCP_TOOLS_SNAPSHOT',
|
|
112
115
|
'BE_TEST_TOPOLOGY', 'BE_SPEC_PLACEMENT', 'HFS_REPO_LOCAL_CHECK', 'HFS_LINT_SUPPRESSION_FILE', 'HFS_BYPASS_MARKER', 'HFS_PROOF_COMMAND_FILE_MISSING', 'HFS_PEER_INTEGRATION_MISSING', 'BE_INTEGRATION_SPEC_MISSING', 'FE_GRAPHQL_CONTRACT', 'FE_NO_TESTS', 'HFS_MONO_WORKSPACES', 'HFS_MONO_FE_WORKSPACE', 'HFS_MONO_NEST_PROJECTS', 'HFS_MONO_WORKSPACE_DEP', 'BE_CLI_REQUIRED', 'FE_WIRE_GENERATED', 'FE_I18N_PLACEMENT', 'FE_I18N_CATALOG', 'HFS_SERVICE_PLACEMENT', 'HFS_IMAGE_UNPINNED', 'HFS_SERVICE_STACK_DECLARATION', 'HFS_EVENT_CONTRACT', 'BE_ASYNC_SPEC_MISSING', 'BE_SAGA_STEP_COMPENSATION', 'BE_SAGA_STATE_VERSIONED', 'BE_SAGA_EVENT_CONTRACT', 'BE_SAGA_CONSUMER_DEDUPE', 'BE_EVENT_CLASS_CONTRACT', 'BE_PATTERN_SPEC_MISSING', 'BE_CONTRACT_BREAKING', 'BE_KIND_DECLARATION', 'BE_KIND_EMPTY', 'HFS_DOCKER_BUILD_CONTEXT', 'HFS_DOCKER_STAGES', 'HFS_DOCKER_ENTRY', 'HFS_DOCKER_BASE_PIN', 'HFS_DOCKER_SECRETS', 'BE_SAGA_E2E_MISSING',
|
|
113
116
|
'FE_STACK_FORBIDDEN', 'HFS_EDITION_FORBIDDEN_PRESENT', 'HFS_BACKING_UNDECLARED', 'HFS_BACKING_UNUSED', 'HFS_LITE_SECRET_CUSTODY', 'HFS_STARCIWORK_LITE_FORBIDDEN',
|
|
114
117
|
'DB_MIGRATION_SHAPE', 'DB_RLS_REQUIRED', 'DB_DYNAMIC_DDL', 'DB_POLICY_SHAPE', 'DB_DEFINER_SAFE', 'DB_STORAGE_POLICY', 'LITE_DB_MIGRATION',
|
|
@@ -84,4 +84,19 @@ export declare function measuredBy({ roots, roles, files, excludes }: {
|
|
|
84
84
|
}): (file: string) => boolean;
|
|
85
85
|
/** True when the be-relative `file` is measured by the scope of the whole manifest (measuredBy). */
|
|
86
86
|
export declare const isMeasured: (manifest: ScopeManifest, file: string) => boolean;
|
|
87
|
+
/** The measured scope of the app's root packages, every path app-relative: the folders that hold specs, the files measured and the ones never measured. */
|
|
88
|
+
export interface PackagesCoverage {
|
|
89
|
+
folders: string[];
|
|
90
|
+
files: string[];
|
|
91
|
+
excludes: string[];
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* THE derivation of what the root packages measure (BE-TEST-13): every TypeScript file below a package's `src/` but its front-end subtrees and barrels, at
|
|
95
|
+
* 100 per file in the back end's unit run. The rendered be/jest.config.js, Sonar's coverage exclusions and sources, Codecov's paths and R273 read it.
|
|
96
|
+
*/
|
|
97
|
+
export declare function packagesCoverage(folders: readonly string[]): PackagesCoverage;
|
|
98
|
+
/** The app-relative folders of the root packages a tracked file list holds: each `packages/<name>/` with a `package.json` and a `src/`, the registry aside. */
|
|
99
|
+
export declare function rootPackageFoldersOf(files: readonly string[]): string[];
|
|
100
|
+
/** The one test of whether an app-relative file is measured by a packages scope (a spec never is): R273 asks it. */
|
|
101
|
+
export declare function packageMeasuredBy({ files, excludes }: PackagesCoverage): (file: string) => boolean;
|
|
87
102
|
export {};
|
|
@@ -243,3 +243,29 @@ export function measuredBy({ roots, roles, files, excludes }) {
|
|
|
243
243
|
}
|
|
244
244
|
/** True when the be-relative `file` is measured by the scope of the whole manifest (measuredBy). */
|
|
245
245
|
export const isMeasured = (manifest, file) => measuredBy(coverageScope(manifest))(file);
|
|
246
|
+
/**
|
|
247
|
+
* The parts of a root workspace package (`packages/<name>` at the app root, PROJECT-3) that are never measured: its workbench and its public face (front
|
|
248
|
+
* ends, judged by the front-end canon and the Grammar lint) and its barrels. Everything else below its `src/` is its logic.
|
|
249
|
+
*/
|
|
250
|
+
const PACKAGE_UNMEASURED = Object.freeze(['src/workbench/**', 'src/public/**', 'src/**/index.ts']);
|
|
251
|
+
/**
|
|
252
|
+
* THE derivation of what the root packages measure (BE-TEST-13): every TypeScript file below a package's `src/` but its front-end subtrees and barrels, at
|
|
253
|
+
* 100 per file in the back end's unit run. The rendered be/jest.config.js, Sonar's coverage exclusions and sources, Codecov's paths and R273 read it.
|
|
254
|
+
*/
|
|
255
|
+
export function packagesCoverage(folders) {
|
|
256
|
+
const sorted = [...new Set(folders)].sort(byCodeUnit);
|
|
257
|
+
return { folders: sorted, files: sorted.map((folder) => `${folder}/src/**/*.ts`), excludes: sorted.flatMap((folder) => PACKAGE_UNMEASURED.map((glob) => `${folder}/${glob}`)) };
|
|
258
|
+
}
|
|
259
|
+
/** The registry of the module packages (slot app.module-registry): managed, it lists the modules and carries no logic of its own. */
|
|
260
|
+
const PACKAGE_REGISTRY = 'modules';
|
|
261
|
+
/** The app-relative folders of the root packages a tracked file list holds: each `packages/<name>/` with a `package.json` and a `src/`, the registry aside. */
|
|
262
|
+
export function rootPackageFoldersOf(files) {
|
|
263
|
+
const names = new Set(files.map((file) => /^packages\/([^/]+)\/package\.json$/.exec(file)?.[1]).filter((name) => name !== undefined && name !== PACKAGE_REGISTRY));
|
|
264
|
+
return [...names].filter((name) => files.some((file) => file.startsWith(`packages/${name}/src/`))).map((name) => `packages/${name}`).sort(byCodeUnit);
|
|
265
|
+
}
|
|
266
|
+
/** The one test of whether an app-relative file is measured by a packages scope (a spec never is): R273 asks it. */
|
|
267
|
+
export function packageMeasuredBy({ files, excludes }) {
|
|
268
|
+
const measured = files.map((glob) => globExpression(glob));
|
|
269
|
+
const unmeasured = excludes.map((glob) => globExpression(glob));
|
|
270
|
+
return (file) => !/\.spec\.ts$/.test(file) && measured.some((glob) => glob.test(file)) && !unmeasured.some((glob) => glob.test(file));
|
|
271
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// cutover whose production starts on an empty database. Nothing is rebuilt from migration SQL: the source's schema is dumped with its
|
|
3
3
|
// privileges, the objects of the extensions the target image lacks (--drop-extension) and every function or view whose body reaches their
|
|
4
4
|
// schemas are left out by name, the role facts a per-database dump loses go to a marked section of db/bootstrap.sql, and the reference rows
|
|
5
|
-
// of --seed-tables go to
|
|
5
|
+
// of --seed-tables go to .starcistacks/seeds/sql/reference.sql, an entry of the shared seed level (every environment). The verb proves its own output before it writes anything: a scratch container of the app's db image
|
|
6
6
|
// replays bootstrap + baseline + seed (the seed twice) and the same projection read back from the replay must equal the source's, or it
|
|
7
7
|
// refuses with the differences. The source is only read; its migration history is never changed.
|
|
8
8
|
import fs from 'node:fs';
|
|
@@ -12,9 +12,12 @@ import { randomBytes } from 'node:crypto';
|
|
|
12
12
|
import { DB_DOCKERFILE, DB_IMAGE_CONTEXT } from '../../../db/image.mjs';
|
|
13
13
|
import { DbVerbError } from './db-errors.mjs';
|
|
14
14
|
import { BOOTSTRAP_FILE, BOOTSTRAP_SQUASH_BEGIN, BOOTSTRAP_SQUASH_END, SEED_FILE, bootstrapSection } from './rules/database-constants.mjs';
|
|
15
|
+
import { addSeedEntries } from './stack-seeds-config.mjs';
|
|
15
16
|
/** The extensions a lite app's db image does not ship, dropped unless --drop-extension names others (pgmq stays: it is the lite event bus). */
|
|
16
17
|
export const DEFAULT_DROPPED_EXTENSIONS = Object.freeze(['pg_net', 'pg_cron']);
|
|
17
18
|
export const SEED_HEADER = '-- Generated by starci app db baseline --squash';
|
|
19
|
+
/** The entry of the shared seed level that declares the reference rows the squash writes (upserted on every change of them). */
|
|
20
|
+
const SQUASH_SEED_ENTRY = { id: 'reference', kind: 'sql', path: 'sql/reference.sql', target: 'primary', mode: 'upsert' };
|
|
18
21
|
// Roles of a hosted platform (Supabase) that a self-hosted lite database never has; a grant naming one is platform state, not the app's.
|
|
19
22
|
const PLATFORM_ROLE = /^(?:supabase_\w+|authenticator|dashboard_user|pgbouncer)$/;
|
|
20
23
|
// Role-level settings carried to the bootstrap; any other setting of a carried role is refused, so no secret travels.
|
|
@@ -566,8 +569,12 @@ export function squashBaseline({ root, opts, plan, urlEnv, connection, commandOf
|
|
|
566
569
|
stdout(`proof: bootstrap + baseline + seed (twice) replayed on ${image}; schema, grants, roles and seed rows equal the source minus ${config.dropped.join(', ')}\n`);
|
|
567
570
|
if (!opts.execute)
|
|
568
571
|
return 0;
|
|
569
|
-
for (const [file, text] of files)
|
|
572
|
+
for (const [file, text] of files) {
|
|
573
|
+
fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true });
|
|
570
574
|
fs.writeFileSync(path.join(root, file), text, { flag: file === plan.file ? 'wx' : 'w' });
|
|
575
|
+
}
|
|
576
|
+
if (projected.seed)
|
|
577
|
+
addSeedEntries(root, null, [SQUASH_SEED_ENTRY]);
|
|
571
578
|
archive();
|
|
572
579
|
return 0;
|
|
573
580
|
}
|
|
@@ -140,7 +140,7 @@ function observeBlockProblems(observe) {
|
|
|
140
140
|
bad.push('observe.reporter must name the optional error reporter');
|
|
141
141
|
return bad;
|
|
142
142
|
}
|
|
143
|
-
const DECLARATION_KEYS = new Set(['hfs', 'kind', 'project', 'edition', 'db', 'sides', 'browser', 'lexicon', 'observe']);
|
|
143
|
+
const DECLARATION_KEYS = new Set(['hfs', 'kind', 'project', 'edition', 'db', 'sides', 'browser', 'modules', 'lexicon', 'observe']);
|
|
144
144
|
const RUNTIME_DECLARATION_KEYS = new Set(['hfs', 'kind', 'project']);
|
|
145
145
|
function productDeclarationProblems(declaration, profiles) {
|
|
146
146
|
const bad = [];
|
|
@@ -150,6 +150,8 @@ function productDeclarationProblems(declaration, profiles) {
|
|
|
150
150
|
bad.push(...dbBlockProblems(declaration.db), ...lexiconBlockProblems(declaration.lexicon), ...observeBlockProblems(declaration.observe));
|
|
151
151
|
if (declaration.browser !== undefined && declaration.browser !== true)
|
|
152
152
|
bad.push('browser is `true` when the app has a browser journey (slot app.browser), and is left out otherwise');
|
|
153
|
+
if (declaration.modules !== undefined && declaration.modules !== 'data')
|
|
154
|
+
bad.push('modules is `"data"` when the app keeps its modules as data (seeds, no rendered registry package), and is left out otherwise');
|
|
153
155
|
if (declaration.kind !== APP_KIND)
|
|
154
156
|
bad.push(`kind must be ${APP_KIND} (a product is one app repository with a be and an fe side) or ${RUNTIME_KIND} (the StarCi runtime repository)`);
|
|
155
157
|
if (!isPlainObject(declaration.sides) || Object.keys(declaration.sides).sort(byCodeUnit).join() !== profiles.join()) {
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// (HFS_MONO_*, monorepo.mjs), the one cli app (BE_CLI_REQUIRED, cli.mjs), BE_INTEGRATION_SPEC_MISSING (R112,
|
|
5
5
|
// integration-specs.mjs), FE_GRAPHQL_CONTRACT (R113, fe-contract-documents.mjs), HFS_CI_MISSING_CANON (R13, pipeline.mjs),
|
|
6
6
|
// BE_TEST_TOPOLOGY (R47, test-topology.mjs), HFS_PROOF_COMMAND_FILE_MISSING (R105, proof-commands.mjs), the app front-end
|
|
7
|
-
// tree (FE_WIRE_GENERATED, FE_I18N_PLACEMENT, frontend-tree.mjs)
|
|
7
|
+
// tree (FE_WIRE_GENERATED, FE_I18N_PLACEMENT, frontend-tree.mjs), HFS_STACKS_SHAPE (R10, stacks.mjs) and the root seed data
|
|
8
|
+
// (HFS_SEED_*: R375, seeds.mjs).
|
|
8
9
|
import { cliFindings } from './cli.mjs';
|
|
9
10
|
import { contractCompatFindings } from './contract-compat.mjs';
|
|
10
11
|
import { depFindings } from './deps.mjs';
|
|
@@ -19,6 +20,7 @@ import { peerIntegrationFindings } from './peer-integrations.mjs';
|
|
|
19
20
|
import { pipelineFindings } from './pipeline.mjs';
|
|
20
21
|
import { proofCommandFindings } from './proof-commands.mjs';
|
|
21
22
|
import { sagaFindings } from './saga.mjs';
|
|
23
|
+
import { seedsFindings } from './seeds.mjs';
|
|
22
24
|
import { serviceFindings } from './services.mjs';
|
|
23
25
|
import { stacksFindings } from './stacks.mjs';
|
|
24
26
|
import { testTopologyFindings } from './test-topology.mjs';
|
|
@@ -46,5 +48,6 @@ export function appRootFindings({ repoRoot, files, all, repo, resolver, pins, ph
|
|
|
46
48
|
...proofCommandFindings({ repoRoot, files: all, resolver, sides: Object.keys(repo.sides ?? {}) }),
|
|
47
49
|
...appFrontendFindings({ repoRoot, files: all, repo, phase }),
|
|
48
50
|
...stacksFindings({ repoRoot, files, resolver }),
|
|
51
|
+
...seedsFindings({ repoRoot, files }),
|
|
49
52
|
];
|
|
50
53
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// sonar-lint-per-side a lint with --out names --side
|
|
7
7
|
// required-providers no continue-on-error; Codecov fail_ci_if_error true; no availability probe, no secret-gated Codecov or Sonar step
|
|
8
8
|
// test-sharding the test job is a shard matrix; each shard runs --shard=i/N with STARCI_COVERAGE_SHARD and no threshold or pass-empty flag; the coverage job merges, then proves
|
|
9
|
-
// lint-parallel the lint job is a matrix over side [be, fe]
|
|
9
|
+
// lint-parallel the lint job is a matrix over side [be, fe], or [be, fe, packages] in an app with root packages
|
|
10
10
|
// cache-saved-always no cache keyed by the commit sha; actions/cache/save runs under if: always()
|
|
11
11
|
// timeouts every job declares timeout-minutes
|
|
12
12
|
// sha-pinned-actions every remote `uses:` names a 40-hex commit sha (docker:// a sha256 digest; ./ local actions allowed)
|
|
@@ -123,7 +123,8 @@ function testSharding(doc, say) {
|
|
|
123
123
|
}
|
|
124
124
|
function lintParallel(doc, say) {
|
|
125
125
|
const sides = record(doc.jobs?.lint) ? doc.jobs.lint.strategy?.matrix?.side : null;
|
|
126
|
-
|
|
126
|
+
const joined = Array.isArray(sides) ? sides.map(String).sort(byCodeUnit).join() : '';
|
|
127
|
+
return joined === 'be,fe' || joined === 'be,fe,packages' ? [] : [say('lint-parallel', 'the lint job must be a matrix over side [be, fe], with packages when the app has root packages')];
|
|
127
128
|
}
|
|
128
129
|
function cacheSavedAlways(doc, say) {
|
|
129
130
|
const out = [];
|
|
@@ -174,8 +175,8 @@ function sonarLintPerSide(doc, say) {
|
|
|
174
175
|
for (const [id, job] of jobsOf(doc)) {
|
|
175
176
|
for (const step of stepsOf(job)) {
|
|
176
177
|
const run = runOf(step);
|
|
177
|
-
if (/\b(?:npm run lint|app check)\b/.test(run) && /\s--out\b/.test(run) && !/\s--side\s+(?:be\b|fe\b|\$\{\{\s*matrix\.side\s*\}\})/.test(run)) {
|
|
178
|
-
out.push(say('sonar-lint-per-side', `job ${id}, step ${step.name ?? 'lint'}: a lint with a Sonar import must name its side (--side be|fe) and the reports are merged afterwards; a combined lint with Sonar breaks the standard`));
|
|
178
|
+
if (/\b(?:npm run lint|app check)\b/.test(run) && /\s--out\b/.test(run) && !/\s--side\s+(?:be\b|fe\b|packages\b|\$\{\{\s*matrix\.side\s*\}\})/.test(run)) {
|
|
179
|
+
out.push(say('sonar-lint-per-side', `job ${id}, step ${step.name ?? 'lint'}: a lint with a Sonar import must name its side (--side be|fe|packages) and the reports are merged afterwards; a combined lint with Sonar breaks the standard`));
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
182
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// lcov carries lines, functions and branches; the unit runner also enforces its statement threshold.
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import { coverageScope, measuredBy } from '../coverage-scope.mjs';
|
|
6
|
+
import { coverageScope, measuredBy, packageMeasuredBy, packagesCoverage, rootPackageFoldersOf } from '../coverage-scope.mjs';
|
|
7
7
|
import { editionRuleParams, effectiveSlot, slotInEdition } from '../edition-slots.mjs';
|
|
8
8
|
import { byCodeUnit } from '../../lib/list.mjs';
|
|
9
9
|
import { found } from './read.mjs';
|
|
@@ -120,12 +120,17 @@ function inventory({ files, all, repo, manifest }) {
|
|
|
120
120
|
const effective = manifestOfEdition(manifest, repo.edition ?? 'full');
|
|
121
121
|
const scope = coverageScope(effective, repo.providers ?? repo.sides?.be?.providers ?? []);
|
|
122
122
|
const measures = measuredBy(scope);
|
|
123
|
-
const
|
|
123
|
+
const tracked = [...new Set((all ?? files ?? []).map(slash))];
|
|
124
|
+
// At the app root the root packages are measured too, by the same unit run as the back end (packagesCoverage, BE-TEST-13).
|
|
125
|
+
const packages = packagesCoverage(prefix ? rootPackageFoldersOf(tracked) : []);
|
|
126
|
+
const measuresPackage = packageMeasuredBy(packages);
|
|
127
|
+
const inPackage = (file) => packages.folders.some((folder) => file.startsWith(`${folder}/src/`));
|
|
128
|
+
const candidates = tracked.filter((file) => (file.startsWith(prefix) || inPackage(file)) && file.endsWith('.ts') && !SPEC.test(file)).sort(byCodeUnit);
|
|
124
129
|
const measured = [];
|
|
125
130
|
const unmeasured = [];
|
|
126
131
|
for (const file of candidates)
|
|
127
|
-
(measures(file.slice(prefix.length)) ? measured : unmeasured).push(file);
|
|
128
|
-
return { measured, unmeasured, unmeasuredScopes: scope.none.map((entry) => `${prefix}${entry}`), scope, prefix };
|
|
132
|
+
((inPackage(file) ? measuresPackage(file) : measures(file.slice(prefix.length))) ? measured : unmeasured).push(file);
|
|
133
|
+
return { measured, unmeasured, unmeasuredScopes: [...scope.none.map((entry) => `${prefix}${entry}`), ...packages.excludes], scope, prefix };
|
|
129
134
|
}
|
|
130
135
|
function sourcePath(source) {
|
|
131
136
|
if (source.startsWith('file:')) {
|
|
@@ -144,6 +149,9 @@ function matchesSource(source, file, prefix) {
|
|
|
144
149
|
const choices = [file, relative, ...(prefix ? [] : [`be/${file}`])];
|
|
145
150
|
const windows = /^[a-z]:\//i.test(source) || source.startsWith('//');
|
|
146
151
|
const absolute = source.startsWith('/') || windows;
|
|
152
|
+
// A relative source is relative to the jest root, the back end: a root package's file is `../packages/<name>/...` there.
|
|
153
|
+
if (!absolute && prefix && slash(`${prefix}${source}`) === file)
|
|
154
|
+
return true;
|
|
147
155
|
const key = windows ? source.toLowerCase() : source;
|
|
148
156
|
return choices.some((choice) => {
|
|
149
157
|
const candidate = windows ? choice.toLowerCase() : choice;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
// database-config.mjs - the database content checks of db/ (R259: extensions, custody SQL, destructive migrations, seed data). It starts no
|
|
1
|
+
// database-config.mjs - the database content checks of db/ and seeds/ (R259: extensions, custody SQL, destructive migrations, seed data). It starts no
|
|
2
2
|
// database and no Docker process.
|
|
3
3
|
import { found, readText } from './read.mjs';
|
|
4
|
-
import { DB_EXTENSION_ALLOWED, DB_VAULT_WRITE, HFS_SEED_PII, isActiveMigration,
|
|
5
|
-
import { databaseExecutableTokens, databaseSqlStatements,
|
|
4
|
+
import { DB_EXTENSION_ALLOWED, DB_VAULT_WRITE, HFS_SEED_PII, isActiveMigration, } from './database-constants.mjs';
|
|
5
|
+
import { databaseExecutableTokens, databaseSqlStatements, destructiveMigrationFindings } from './database-migrations.mjs';
|
|
6
|
+
import { isSeedFile, seedLiterals } from './seeds.mjs';
|
|
6
7
|
import { byCodeUnit } from '../../lib/list.mjs';
|
|
7
8
|
const DEFAULT_EXTENSIONS = ['pgcrypto', 'pg_trgm', 'vector', 'uuid-ossp', 'unaccent', 'btree_gist', 'pgmq'];
|
|
8
9
|
const word = (token, value) => token?.t === 'word' && token.v === value;
|
|
@@ -108,10 +109,10 @@ function seedPiiFindings(repoRoot, seeds) {
|
|
|
108
109
|
const text = readText(repoRoot, file);
|
|
109
110
|
if (text === null)
|
|
110
111
|
continue;
|
|
111
|
-
for (const
|
|
112
|
-
if (!
|
|
112
|
+
for (const literal of seedLiterals(file, text)) {
|
|
113
|
+
if (!piiLiteral(literal.v))
|
|
113
114
|
continue;
|
|
114
|
-
findings.push(found(HFS_SEED_PII, file, `${file}:${
|
|
115
|
+
findings.push(found(HFS_SEED_PII, file, `${file}:${literal.line} contains an email or phone literal; seed data uses synthetic identities and the example.test domain`, { line: literal.line }));
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
return findings;
|
|
@@ -127,7 +128,7 @@ export function databaseConfigFindings({ repoRoot, files = [], all = files, repo
|
|
|
127
128
|
for (const { file, tokens } of sources)
|
|
128
129
|
findings.push(...extensionFindings(file, tokens, new Set(parameters.extensions)), ...custodySqlFindings(file, tokens));
|
|
129
130
|
findings.push(...destructiveMigrationFindings({ repoRoot, migrations, files: all }));
|
|
130
|
-
const seeds = all.filter((file) => file
|
|
131
|
+
const seeds = all.filter((file) => isSeedFile(file) && /\/(?:sql|data)\/.+\.(?:sql|json|ya?ml)$/.test(file));
|
|
131
132
|
findings.push(...seedPiiFindings(repoRoot, seeds));
|
|
132
133
|
return findings.filter((finding) => inScope(finding.path));
|
|
133
134
|
}
|