@starci/eslint-canon-be 3.4.39 → 3.4.41
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 +10 -0
- package/README.md +2 -2
- package/boundary-schema.mjs +32 -4
- package/idempotency.mjs +3 -0
- package/index.mjs +2 -0
- package/infra-owner.mjs +4 -3
- package/lib/client-idempotency.mjs +403 -0
- package/lib/persistence.mjs +1 -1
- package/lib/schema-values.mjs +90 -0
- package/lib/unit-spec.mjs +6 -0
- package/operations.mjs +197 -32
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +65 -9
- package/runtime/knowledge/hfs/fe-stack.yaml +23 -5
- package/runtime/knowledge/hfs/rules.yaml +168 -47
- package/runtime/knowledge/hfs/slots.yaml +110 -52
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/kernel/removed-vocabulary.yaml +62 -0
- package/runtime/modules/models/registry.yaml +3 -1
- package/runtime/modules/models/runtimes.yaml +2 -0
- package/runtime/scripts/api/git/ls-tree.mjs +5 -0
- package/runtime/scripts/api/git/show.mjs +6 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +10 -10
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +1 -1
- package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +53 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +1 -1
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +17 -6
- package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +105 -0
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +38 -52
- package/runtime/scripts/hfs/architecture/supabase-keys.mjs +261 -0
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +49 -3
- package/runtime/scripts/hfs/declaration-shape.mjs +4 -4
- package/runtime/scripts/hfs/declaration-slots.mjs +12 -3
- package/runtime/scripts/hfs/manifest-shape.mjs +10 -6
- package/runtime/scripts/hfs/rule-params-shape.mjs +16 -6
- package/runtime/scripts/hfs/rules/database-constants.mjs +26 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +489 -0
- package/runtime/scripts/hfs/slot-side-problems.mjs +2 -2
- package/runtime/scripts/hfs/slots.mjs +1 -0
- package/runtime/scripts/hfs/view.mjs +2 -1
- package/runtime/scripts/lib/in-order.mjs +71 -0
- package/runtime/scripts/lib/language.mjs +94 -1
- package/runtime/scripts/lib/package-at.mjs +62 -0
- package/runtime/scripts/lib/same-text.mjs +5 -0
- package/runtime/scripts/lib/ts-analysis.mjs +10 -0
- package/suppression.mjs +1 -1
- package/unit-spec.mjs +66 -6
- package/uploads.mjs +112 -0
|
@@ -433,7 +433,7 @@ rules:
|
|
|
433
433
|
proves:
|
|
434
434
|
- {path: packages/hfs/src/hfs-arch-config-unread.spec.mjs, editions: [lite, full], codes: [HFS_ARCH_CONFIG_UNREAD]}
|
|
435
435
|
exemplifiedBy:
|
|
436
|
-
- {path: "packages/hfs/templates/be/skeleton
|
|
436
|
+
- {path: "packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts", edition: "lite"}
|
|
437
437
|
- {path: "packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts", edition: "full"}
|
|
438
438
|
- {path: packages/hfs/src/hfs-arch-config-unread.spec.mjs, editions: [lite, full], codes: [HFS_ARCH_CONFIG_UNREAD]}
|
|
439
439
|
witness:
|
|
@@ -452,8 +452,9 @@ rules:
|
|
|
452
452
|
- {kind: eslint-fe, id: dead-exports}
|
|
453
453
|
proves:
|
|
454
454
|
- {path: packages/eslint/be/hfs-unused-export.spec.mjs, editions: [lite, full], codes: [HFS_UNUSED_EXPORT, HFS_UNUSED_FILE]}
|
|
455
|
+
- {path: packages/hfs/src/operation-contract-roots.spec.mjs, editions: [lite, full], codes: [HFS_UNUSED_FILE]}
|
|
455
456
|
exemplifiedBy:
|
|
456
|
-
- {path: "packages/hfs/templates/be/skeleton
|
|
457
|
+
- {path: "packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts", edition: "lite"}
|
|
457
458
|
- {path: "packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts", edition: "full"}
|
|
458
459
|
- {path: "packages/hfs/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx", edition: "lite"}
|
|
459
460
|
- {path: "packages/hfs/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx", edition: "full"}
|
|
@@ -613,7 +614,7 @@ rules:
|
|
|
613
614
|
proves:
|
|
614
615
|
- {path: packages/eslint/be/a16-r32-r33-r35-project-graph.spec.mjs, editions: [lite, full]}
|
|
615
616
|
exemplifiedBy:
|
|
616
|
-
- {path: packages/hfs/templates/be/skeleton
|
|
617
|
+
- {path: packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts, edition: lite}
|
|
617
618
|
- {path: packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts, edition: full}
|
|
618
619
|
- {path: packages/eslint/be/a16-r32-r33-r35-project-graph.spec.mjs, editions: [lite, full]}
|
|
619
620
|
witness:
|
|
@@ -1339,7 +1340,7 @@ rules:
|
|
|
1339
1340
|
|
|
1340
1341
|
- id: R65
|
|
1341
1342
|
code: "FE_SIZE_AND_STATE_BUDGET"
|
|
1342
|
-
law: "Hook and state budgets; no hand-written poll loop; keyed lists; everything an effect starts is released by its cleanup; no swallowed error or console."
|
|
1343
|
+
law: "Hook and state budgets; live time uses Grammar's shared clock; no hand-written poll loop; keyed lists; everything an effect starts is released by its cleanup; no swallowed error or console."
|
|
1343
1344
|
kinds: [lint, design]
|
|
1344
1345
|
gates: [settle, land, ci]
|
|
1345
1346
|
failureCodes: ["FE_SIZE_AND_STATE_BUDGET", "FE_LIST_KEY", "FE_EFFECT_CLEANUP", "FE_SWALLOWED_ERROR", "FE_CONSOLE_CALL"]
|
|
@@ -1347,6 +1348,7 @@ rules:
|
|
|
1347
1348
|
- {kind: eslint-fe, id: component-line-budget}
|
|
1348
1349
|
- {kind: eslint-fe, id: unit-hook-budget}
|
|
1349
1350
|
- {kind: eslint-fe, id: no-hand-rolled-polling}
|
|
1351
|
+
- {kind: eslint-fe, id: no-app-relative-clock}
|
|
1350
1352
|
- {kind: eslint-fe, id: list-item-has-key}
|
|
1351
1353
|
- {kind: eslint-fe, id: no-index-key}
|
|
1352
1354
|
- {kind: eslint-fe, id: no-inline-literal-prop-in-list}
|
|
@@ -1875,17 +1877,21 @@ rules:
|
|
|
1875
1877
|
- {path: packages/eslint/fe/r56-r92-graph-coverage.spec.mjs, editions: [lite, full]}
|
|
1876
1878
|
- id: R93
|
|
1877
1879
|
code: "FE_VENDOR_BOUNDARY"
|
|
1878
|
-
law: "Vendor primitives and icons reach components only through their named owner: the one icon set of knowledge/hfs/fe-stack.yaml (the heroicons family) through the icon leaf at the glyph scale, every vendor primitive behind a named owner; the Grammar package is imported only through its selected code and style entry and keeps its declared contract."
|
|
1880
|
+
law: "Vendor primitives and icons reach components only through their named owner: the one icon set of knowledge/hfs/fe-stack.yaml (the heroicons family) through the icon leaf at the glyph scale, every vendor primitive behind a named owner; the Grammar package is imported only through its selected code and style entry and keeps its declared contract. Apps compose Grammar surfaces with layout-only CSS; surface context supplies nested field variants and panel list alignment, without raw surface paint or stacked horizontal list-wrapper padding."
|
|
1879
1881
|
kinds: [lint, codemod]
|
|
1880
1882
|
gates: [pre-commit, settle, land, ci]
|
|
1881
|
-
failureCodes: ["FE_VENDOR_BOUNDARY", "ARCH_GRAMMAR_CONTRACT_INVALID", "ARCH_GRAMMAR_EXPORT_BYPASS"]
|
|
1883
|
+
failureCodes: ["FE_VENDOR_BOUNDARY", "ARCH_GRAMMAR_CONTRACT_INVALID", "ARCH_GRAMMAR_EXPORT_BYPASS", "FE_NESTED_VARIANT", "FE_LIST_PANEL_PADDING"]
|
|
1882
1884
|
enforcers:
|
|
1883
1885
|
- {kind: eslint-fe, id: no-vendor-icon-outside-icon-leaf}
|
|
1884
1886
|
- {kind: eslint-fe, id: heroicons-is-the-glyph-vendor}
|
|
1885
1887
|
- {kind: eslint-fe, id: no-off-scale-glyph-size}
|
|
1888
|
+
- {kind: eslint-fe, id: no-icon-size-class}
|
|
1889
|
+
- {kind: eslint-fe, id: destructive-menu-entry-needs-danger-tone}
|
|
1886
1890
|
- {kind: eslint-fe, id: no-decorative-icon-in-metric-cell}
|
|
1887
1891
|
- {kind: eslint-fe, id: vendor-primitive-has-named-owner}
|
|
1888
1892
|
- {kind: eslint-fe, id: grammar-entry}
|
|
1893
|
+
- {kind: eslint-fe, id: nested-control-variant}
|
|
1894
|
+
- {kind: eslint-fe, id: no-list-panel-padding}
|
|
1889
1895
|
- {kind: machine, id: grammar, at: scripts/hfs/architecture/frontend-grammar.mjs}
|
|
1890
1896
|
editions: [lite, full]
|
|
1891
1897
|
docs: ["docs/code-pattern-enforcement.md#r93-vendor-and-grammar-ownership"]
|
|
@@ -1893,12 +1899,24 @@ rules:
|
|
|
1893
1899
|
proves:
|
|
1894
1900
|
- packages/eslint/fe/r93-vendor-coverage.spec.mjs
|
|
1895
1901
|
- packages/eslint/fe/r93-grammar-ownership.spec.mjs
|
|
1902
|
+
- packages/eslint/fe/menu-entries.spec.mjs
|
|
1903
|
+
- packages/eslint/fe/surface-rules.spec.mjs
|
|
1896
1904
|
exemplifiedBy:
|
|
1897
1905
|
- {path: packages/eslint/fe/r93-vendor-coverage.spec.mjs, editions: [lite, full]}
|
|
1898
1906
|
- {path: packages/eslint/fe/r93-grammar-ownership.spec.mjs, editions: [lite, full]}
|
|
1907
|
+
- {path: packages/eslint/fe/menu-entries.spec.mjs, editions: [lite, full]}
|
|
1908
|
+
- {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full]}
|
|
1899
1909
|
witness:
|
|
1910
|
+
- {path: packages/eslint/fe/menu-entries.spec.mjs, editions: [lite, full], codes: [FE_VENDOR_BOUNDARY]}
|
|
1900
1911
|
- {path: packages/eslint/fe/r93-vendor-coverage.spec.mjs, editions: [lite, full], codes: [FE_VENDOR_BOUNDARY]}
|
|
1901
1912
|
- {path: packages/eslint/fe/r93-grammar-ownership.spec.mjs, editions: [lite, full], codes: [ARCH_GRAMMAR_CONTRACT_INVALID, ARCH_GRAMMAR_EXPORT_BYPASS]}
|
|
1913
|
+
- {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full], codes: [FE_NESTED_VARIANT, FE_LIST_PANEL_PADDING]}
|
|
1914
|
+
cases:
|
|
1915
|
+
- id: case-1
|
|
1916
|
+
date: "2026-10-25"
|
|
1917
|
+
source: owner
|
|
1918
|
+
quote: "apps on Grammar write layout-only CSS; every visual element lives in @starci/grammar"
|
|
1919
|
+
ruling: "ANATOMY-2 owns contextual field defaults, PADDING-0 owns panel list alignment, and COLLECTION-3 owns composite member avatars for conversation and group identity. Grammar supplies these capabilities; the FE canon rejects explicit primary fields inside surfaces, raw app surface paint and stacked horizontal list-wrapper padding."
|
|
1902
1920
|
- id: R94
|
|
1903
1921
|
code: "FE_SLOT_FILE_ROLE"
|
|
1904
1922
|
law: "A front-end file is owned by a slot and named by it: a slot that owns a directory (route, feature, components, hooks, api module) holds only the files its `allows` list names, and holds them inside a folder of its own."
|
|
@@ -1918,19 +1936,29 @@ rules:
|
|
|
1918
1936
|
- {path: packages/hfs/src/r94-r96-r97-coverage.spec.mjs, editions: [lite, full], codes: [FE_SLOT_FILE_ROLE]}
|
|
1919
1937
|
- id: R95
|
|
1920
1938
|
code: "BE_OPERATION_CONTRACT"
|
|
1921
|
-
law: "A versioned operation is declared once
|
|
1939
|
+
law: "A versioned operation is declared once by defineOperations with bounded input and output zod schemas exported from owner-local *.schema.ts files (R304/R305); schemas infer closed types and refusal codes are a closed literal union. The route takes OperationRequest<Table> through OperationValidationPolicy constructed with that same table and answers Promise<OperationReply<Table>>. The pipe validates the envelope and selected input before dispatch, refusing malformed input through the canon AbstractException error shape. Specs check output against its schema; starci app emit derives OpenAPI with z.toJSONSchema."
|
|
1922
1940
|
kinds: [lint]
|
|
1923
1941
|
failureCodes: ["BE_OPERATION_CONTRACT"]
|
|
1924
1942
|
enforcers:
|
|
1925
1943
|
- {kind: eslint-be, id: operation-contract-decidable}
|
|
1926
1944
|
- {kind: eslint-be, id: operation-route-driven-by-table}
|
|
1945
|
+
- {kind: eslint-be, id: operation-table-schemas}
|
|
1927
1946
|
gates: ["pre-commit","settle","land","ci"]
|
|
1928
1947
|
editions: ["lite","full"]
|
|
1929
1948
|
docs: ["docs/operation-contracts.md#r95-closed-operations-and-matching-routes"]
|
|
1930
1949
|
templateNa: "Operation tables and their table-driven HTTP routes are application-authored feature contracts; managed templates scaffold neither defineOperations tables nor OperationRequest/OperationReply routes."
|
|
1931
|
-
exemplifiedBy:
|
|
1932
|
-
|
|
1933
|
-
|
|
1950
|
+
exemplifiedBy:
|
|
1951
|
+
- {path: packages/eslint/be/operation-contract-coverage.spec.mjs, editions: [lite, full], codes: [BE_OPERATION_CONTRACT]}
|
|
1952
|
+
- {path: examples/full-app/be/apps/order/src/operations.ts, edition: full}
|
|
1953
|
+
- {path: examples/full-app/be/src/modules/domain/order/order-operations.contracts.ts, edition: full}
|
|
1954
|
+
proves:
|
|
1955
|
+
- {path: packages/eslint/be/operation-contract-coverage.spec.mjs, editions: [lite, full], codes: [BE_OPERATION_CONTRACT]}
|
|
1956
|
+
- {path: packages/eslint/be/operation-schema-runtime.spec.mjs, editions: [lite, full], codes: [BE_OPERATION_CONTRACT]}
|
|
1957
|
+
witness:
|
|
1958
|
+
- {path: packages/eslint/be/operation-contract-coverage.spec.mjs, edition: lite, codes: [BE_OPERATION_CONTRACT]}
|
|
1959
|
+
- {path: packages/eslint/be/operation-contract-coverage.spec.mjs, edition: full, codes: [BE_OPERATION_CONTRACT]}
|
|
1960
|
+
- {path: packages/eslint/be/operation-schema-runtime.spec.mjs, edition: lite, codes: [BE_OPERATION_CONTRACT]}
|
|
1961
|
+
- {path: packages/eslint/be/operation-schema-runtime.spec.mjs, edition: full, codes: [BE_OPERATION_CONTRACT]}
|
|
1934
1962
|
- id: R96
|
|
1935
1963
|
code: "HFS_DOC_NOT_ENGLISH"
|
|
1936
1964
|
law: "Every Markdown and YAML document under a side's docs/, src/ and apps/ (be/, fe/) and under the runtime's knowledge/ and docs/ is English, code fences included; only YAML data in a message-catalog or i18n-fixtures slot may hold another language."
|
|
@@ -2150,7 +2178,7 @@ rules:
|
|
|
2150
2178
|
- {path: packages/eslint/be/legacy-safety-coverage.spec.mjs, editions: [lite, full], codes: [BE_ERROR_CAUSE_DROPPED]}
|
|
2151
2179
|
exemplifiedBy:
|
|
2152
2180
|
- {path: packages/eslint/be/legacy-safety-coverage.spec.mjs, editions: [lite, full], codes: [BE_ERROR_CAUSE_DROPPED]}
|
|
2153
|
-
- {path: packages/hfs/templates/be/skeleton
|
|
2181
|
+
- {path: packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts, edition: lite}
|
|
2154
2182
|
- {path: packages/hfs/templates/be/skeleton/src/modules/platform/cqrs/cqrs.handler.ts, edition: full}
|
|
2155
2183
|
witness:
|
|
2156
2184
|
- {path: packages/eslint/be/legacy-safety-coverage.spec.mjs, edition: lite, codes: [BE_ERROR_CAUSE_DROPPED]}
|
|
@@ -2338,7 +2366,7 @@ rules:
|
|
|
2338
2366
|
- {kind: runtime, id: generated-untracked, at: scripts/hfs/runtime-rules/generated-untracked.mjs}
|
|
2339
2367
|
- id: R122
|
|
2340
2368
|
code: "RT_CITED_PATH_MISSING"
|
|
2341
|
-
law: "A runtime path cited by live prose or contracts (modules, docs, skills, knowledge, CONTEXT.md, README.md, CONTRIBUTING.md, init, the ui docs) exists; history (a changelog, a
|
|
2369
|
+
law: "A runtime path cited by live prose or contracts (modules, docs, skills, knowledge, CONTEXT.md, README.md, CONTRIBUTING.md, init, the ui docs) exists; history (a changelog, a .starciwork record) is not read."
|
|
2342
2370
|
scope: runtime
|
|
2343
2371
|
kinds: [check]
|
|
2344
2372
|
gates: [runtime]
|
|
@@ -2833,7 +2861,7 @@ rules:
|
|
|
2833
2861
|
- {path: packages/hfs/src/hfs-services-legacy-coverage.spec.mjs, editions: [lite, full]}
|
|
2834
2862
|
exemplifiedBy:
|
|
2835
2863
|
- {path: packages/hfs/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml, edition: lite}
|
|
2836
|
-
- {path: packages/hfs/templates/app/
|
|
2864
|
+
- {path: packages/hfs/templates/app/skeleton/.starcistacks/application-stacks.yaml, edition: full}
|
|
2837
2865
|
- {path: packages/hfs/src/hfs-services-legacy-coverage.spec.mjs, editions: [lite, full]}
|
|
2838
2866
|
witness:
|
|
2839
2867
|
- {path: packages/hfs/src/hfs-services-legacy-coverage.spec.mjs, edition: lite, codes: [HFS_IMAGE_UNPINNED]}
|
|
@@ -3198,7 +3226,7 @@ rules:
|
|
|
3198
3226
|
witness:
|
|
3199
3227
|
- {path: packages/eslint/be/webhooks-realtime-coverage.spec.mjs, edition: lite, codes: [BE_REALTIME_SHAPE]}
|
|
3200
3228
|
- {path: packages/eslint/be/webhooks-realtime-coverage.spec.mjs, edition: full, codes: [BE_REALTIME_SHAPE]}
|
|
3201
|
-
law: "A realtime file is the one door its role names: `<channel>.gateway.ts` holds one `@WebSocketGateway` class with `@SubscribeMessage` handlers, `<x>.subscription.ts` holds one `@Resolver` class with `@Subscription` operations and `<channel>.controller.ts` of the sse slot holds one `@Controller` class whose one route is `@Sse`, and none carries a query, a mutation, a field resolver, a plain REST route or another protocol.
|
|
3229
|
+
law: "A realtime file is the one door its role names: `<channel>.gateway.ts` holds one `@WebSocketGateway` class with `@SubscribeMessage` handlers, `<x>.subscription.ts` holds one `@Resolver` class with `@Subscription` operations and `<channel>.controller.ts` of the sse slot holds one `@Controller` class whose one route is `@Sse`, and none carries a query, a mutation, a field resolver, a plain REST route or another protocol. Both editions serve all three doors from the api process: SSE (the default door for one-way push), the socket.io gateway (two-way) and the restricted GraphQL subscription (R212 case-2 (owner:lite-production-swarm), with the Work boundary amended by ruling:R212/case-4 and the realtime adapter amended by ruling:R212/case-5 and ruling:R212/case-7, owner:lite-graphql-subscription-door)."
|
|
3202
3230
|
kinds: [lint]
|
|
3203
3231
|
gates: [pre-commit, settle, land, ci]
|
|
3204
3232
|
failureCodes: ["BE_REALTIME_SHAPE"]
|
|
@@ -3346,7 +3374,7 @@ rules:
|
|
|
3346
3374
|
- {kind: runtime, id: facts, at: scripts/hfs/runtime-rules/facts.mjs}
|
|
3347
3375
|
- id: R193
|
|
3348
3376
|
code: "RT_RULE_ID_UNKNOWN"
|
|
3349
|
-
law: "Every rule id (R<digits>) that tracked knowledge, docs, code or data names is a rule of this catalog, and rule ids are unique and well-formed (a gap between two ids is fine), and every rule of the catalog is named by the `hfsRules:` of at least one knowledge/patterns topic or carries `scope: runtime` (RT_RULE_UNCITED). History paths (changelogs,
|
|
3377
|
+
law: "Every rule id (R<digits>) that tracked knowledge, docs, code or data names is a rule of this catalog, and rule ids are unique and well-formed (a gap between two ids is fine), and every rule of the catalog is named by the `hfsRules:` of at least one knowledge/patterns topic or carries `scope: runtime` (RT_RULE_UNCITED). History paths (changelogs, .starciwork records), specs and generated copies are not read."
|
|
3350
3378
|
scope: runtime
|
|
3351
3379
|
editions: [lite, full]
|
|
3352
3380
|
docs: [docs/knowledge-yaml.md#runtime-knowledge-evidence]
|
|
@@ -3566,15 +3594,15 @@ rules:
|
|
|
3566
3594
|
templateNa: "The authored app and runtime slot manifests are canon catalogs, not files scaffolded into apps."
|
|
3567
3595
|
- id: R212
|
|
3568
3596
|
editions: [lite, full]
|
|
3569
|
-
codeEditions: {HFS_EDITION_FORBIDDEN_PRESENT: [lite], HFS_STARCIWORK_LITE_FORBIDDEN: [lite]}
|
|
3570
|
-
editionWhy: "R212/case-3
|
|
3597
|
+
codeEditions: {HFS_EDITION_FORBIDDEN_PRESENT: [lite], HFS_STARCIWORK_LITE_FORBIDDEN: [lite], HFS_BACKING_UNDECLARED: [lite], HFS_BACKING_UNUSED: [lite]}
|
|
3598
|
+
editionWhy: "R212/case-3, R212/case-4 (owner:lite-production-swarm) and R212/case-10 preserve full Work, limit the lite infrastructure and Work surfaces and admit only declared backing services in lite."
|
|
3571
3599
|
docs: [docs/lite-edition.md#edition-validation]
|
|
3572
3600
|
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."
|
|
3573
3601
|
code: "HFS_EDITION_INVALID"
|
|
3574
|
-
law: "hfs.json names an edition the manifest knows (full, or lite; absent means full). Lite
|
|
3602
|
+
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, managed Supabase for Postgres, Auth and Storage, and one minimal instance of each backing service a declared capability needs (hfs.json sides.be.backing: Redis for the queue, with AOF persistence; Qdrant for vector search); 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, websocket-only, 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 components duplicating Supabase backing services (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 supabase/functions. 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."
|
|
3575
3603
|
kinds: [check]
|
|
3576
3604
|
gates: [settle, land, ci]
|
|
3577
|
-
failureCodes: ["HFS_EDITION_INVALID", "HFS_EDITION_FORBIDDEN_PRESENT", "HFS_STARCIWORK_LITE_FORBIDDEN"]
|
|
3605
|
+
failureCodes: ["HFS_EDITION_INVALID", "HFS_EDITION_FORBIDDEN_PRESENT", "HFS_STARCIWORK_LITE_FORBIDDEN", "HFS_BACKING_UNDECLARED", "HFS_BACKING_UNUSED"]
|
|
3578
3606
|
enforcers:
|
|
3579
3607
|
- {kind: hfs, id: edition, at: scripts/hfs/rules/edition.mjs}
|
|
3580
3608
|
- {kind: hfs, id: lite-brand-work, at: scripts/hfs/path-findings.mjs}
|
|
@@ -3584,17 +3612,17 @@ rules:
|
|
|
3584
3612
|
date: "2026-10-12"
|
|
3585
3613
|
source: owner
|
|
3586
3614
|
quote: "For a lite app the back end is exactly the same too, only deployed more simply; the fe and be of a lite app are exactly the same as a full app, except there is no e2e - unit tests stay; a lite app has no infra, a monorepo + Supabase; fe and be in a monorepo like a full app. (translated from Vietnamese)"
|
|
3587
|
-
ruling: "Lite = the full architecture minus self-hosted infrastructure. The same be/apps (api, cli, optional worker), be/src/{features,modules}, fe/apps and fe/packages, supabase/, .starcistacks and .starciwork; the same CQRS shape and patterns declared in hfs.json the same way. Only adapters and delivery differ, all on Supabase: schema authority in supabase/migrations; the queue pattern on Supabase Queues (pgmq) behind the same platform/queue ports (outbox to queue, processors, retry and backoff by visibility timeout and read_ct, archive on dead letter), no BullMQ or Redis; the event-bus pattern on pgmq-backed topics (or in process for a single api) behind the same EventPublisher/EventConsumer ports, no Kafka; realtime on Supabase Realtime (replaced by case-2: the socket.io gateway in the api process); cache and locks on Postgres (advisory locks or an unlogged table); schedules on pg_cron (+ pg_net) or the platform scheduler; storage and auth on Supabase. Back-end unit specs are required exactly as in full (unitRoles, logicRoles, 100 per file, @starci/jest-preset doubles, builders); integration specs are optional and run only on the local Supabase CLI stack (`starci supabase start`, no toxiproxy, no own containers); there is no e2e or contract world and no app.ci-e2e. .starcistacks holds env and secret custody only. The front end and the grammar stay exactly as in full: no tests (R97). The queue transport question was confirmed the same day: Supabase Queues (pgmq)
|
|
3615
|
+
ruling: "Lite = the full architecture minus self-hosted infrastructure (replaced by case-10: lite is a minimal production that runs one instance of each declared backing service). The same be/apps (api, cli, optional worker), be/src/{features,modules}, fe/apps and fe/packages, supabase/, .starcistacks and .starciwork; the same CQRS shape and patterns declared in hfs.json the same way. Only adapters and delivery differ, all on Supabase: schema authority in supabase/migrations; the queue pattern on Supabase Queues (pgmq) behind the same platform/queue ports (outbox to queue, processors, retry and backoff by visibility timeout and read_ct, archive on dead letter), no BullMQ or Redis (replaced by case-10: the queue is BullMQ on a declared Redis, as in full); the event-bus pattern on pgmq-backed topics (or in process for a single api) behind the same EventPublisher/EventConsumer ports, no Kafka; realtime on Supabase Realtime (replaced by case-2: the socket.io gateway in the api process); cache and locks on Postgres (advisory locks or an unlogged table); schedules on pg_cron (+ pg_net) or the platform scheduler; storage and auth on Supabase. Back-end unit specs are required exactly as in full (unitRoles, logicRoles, 100 per file, @starci/jest-preset doubles, builders); integration specs are optional and run only on the local Supabase CLI stack (`starci supabase start`, no toxiproxy, no own containers); there is no e2e or contract world and no app.ci-e2e. .starcistacks holds env and secret custody only. The front end and the grammar stay exactly as in full: no tests (R97). The queue transport question was confirmed the same day: Supabase Queues (pgmq) (replaced by case-10: Redis). The upgrade promise of this case is withdrawn by case-6: editions are fixed at scaffold."
|
|
3588
3616
|
- id: case-2
|
|
3589
3617
|
date: "2026-10-13"
|
|
3590
3618
|
source: owner
|
|
3591
3619
|
quote: "Lite and full apps are the same code structure; lite only swaps the infrastructure for Supabase, has no self-hosted infrastructure and no e2e, and saga and jobs are built exactly like full. The front end of lite and full follows starci-academy: the browser calls the back end directly through modules/api/graphql (Apollo auth and no-auth clients with the link chain) and axios only for non-GraphQL calls, hooks/swr wraps every operation and components call hooks only, modules/toast reports mutation outcomes. Forbidden: Server Actions, Next route handlers except health/live, front-end data reads from Supabase (Supabase on the front end is auth only) and the rpc + polling command pattern; modules/db goes away. Every back-end feature exposes GraphQL for the front end; heavy work is a mutation that enqueues a job and the front end follows its progress. (translated from Vietnamese)"
|
|
3592
|
-
ruling: "Lite and full share one code structure; the edition changes only the infrastructure (Supabase Postgres, Auth, Storage, pgmq and pg_cron instead of self-hosted services), runs the socket.io gateway inside the be api process (one instance, no Redis adapter; be.feature.realtime.websocket is legal in lite and the Supabase Realtime door is gone) and has no e2e. The front end is one standard for both editions, catalogued in knowledge/hfs/fe-stack.yaml (one canonical library, HFS home and pattern per concern, versions in canon-pins.yaml): Apollo GraphQL in modules/api/graphql with codegen from be/contracts/<app>/schema.graphql, axios in modules/api/rest for uploads, presigned URLs and auth endpoints, SWR hooks in hooks/swr, react-hook-form with zod in hooks/rhf, redux for app-wide state, zustand for local state, socket.io-client in hooks/socketio, modules/toast for mutation outcomes and modules/auth as the only front-end Supabase importer (auth only). The front end serves no API: no Server Action anywhere (FE_SERVER_ACTION_FORBIDDEN) and no route handler beyond health/live and the fe.route.callback slot (FE_ROUTE_HANDLER_FORBIDDEN); a package outside the catalog's choice is FE_STACK_FORBIDDEN. Back-end features expose GraphQL (be.transport.graphql) for the front end and keep REST for webhooks, the public API and OAuth callbacks; heavy work is a mutation that enqueues a fenced job or saga, and the front end polls a status query with SWR refreshInterval or listens on a socket namespace."
|
|
3620
|
+
ruling: "Lite and full share one code structure; the edition changes only the infrastructure (Supabase Postgres, Auth, Storage, pgmq and pg_cron instead of self-hosted services; replaced by case-10: plus one instance of each declared backing service, with the queue on Redis), runs the socket.io gateway inside the be api process (one instance, no Redis adapter; be.feature.realtime.websocket is legal in lite and the Supabase Realtime door is gone) and has no e2e. The front end is one standard for both editions, catalogued in knowledge/hfs/fe-stack.yaml (one canonical library, HFS home and pattern per concern, versions in canon-pins.yaml): Apollo GraphQL in modules/api/graphql with codegen from be/contracts/<app>/schema.graphql, axios in modules/api/rest for uploads, presigned URLs and auth endpoints, SWR hooks in hooks/swr, react-hook-form with zod in hooks/rhf, redux for app-wide state, zustand for local state, socket.io-client in hooks/socketio, modules/toast for mutation outcomes and modules/auth as the only front-end Supabase importer (auth only). The front end serves no API: no Server Action anywhere (FE_SERVER_ACTION_FORBIDDEN) and no route handler beyond health/live and the fe.route.callback slot (FE_ROUTE_HANDLER_FORBIDDEN); a package outside the catalog's choice is FE_STACK_FORBIDDEN. Back-end features expose GraphQL (be.transport.graphql) for the front end and keep REST for webhooks, the public API and OAuth callbacks; heavy work is a mutation that enqueues a fenced job or saga, and the front end polls a status query with SWR refreshInterval or listens on a socket namespace."
|
|
3593
3621
|
- id: case-3
|
|
3594
3622
|
date: "2026-10-13"
|
|
3595
3623
|
source: owner
|
|
3596
3624
|
quote: "As the student proposes. (translated from Vietnamese; the owner adopted the proposed HFS standard for front end and back end, lite = full)"
|
|
3597
|
-
ruling: "One HFS tree for both editions: the repository root, be/ (apps, contracts, features, modules, tests) and fe/apps/<app>/src have the same shape in lite and full, and code above the platform/integrations seam is the same. The editions differ only behind that seam: the database and its migration authority (lite supabase/migrations; full TypeORM entities and migrations in be.persistence), the auth provider (lite Supabase Auth through fe.modules.auth and a JWKS-verified back end; full the app's identity provider), object storage (lite Supabase Storage; full S3-compatible storage), queue and jobs (lite pgmq and pg_cron; full BullMQ) behind platform/queue, realtime (lite the socket.io gateway in the api process, one instance; full socket.io scaled with a Redis adapter), the event-bus adapter (lite outbox and pgmq; full outbox and Kafka) and hosting; lite has no e2e. Migration baseline at the cutover of an existing app: the live schema is dumped into one baseline migration written to the current standard and marked applied on production without a re-run, the older migrations move to app.supabase.migrations.archive (supabase/migrations/_archive/, never applied and never judged), and new migrations append after the baseline. Assets: static templates, copy and presets are TS constants in the owning domain capability (<x>.templates.ts) and user-editable ones are seed rows; small build-time binaries such as fonts live in be.assets (be/src/assets/<kind>/); large and user media live in object storage and are fetched at runtime; there is no be/resources/."
|
|
3625
|
+
ruling: "One HFS tree for both editions: the repository root, be/ (apps, contracts, features, modules, tests) and fe/apps/<app>/src have the same shape in lite and full, and code above the platform/integrations seam is the same. The editions differ only behind that seam: the database and its migration authority (lite supabase/migrations; full TypeORM entities and migrations in be.persistence), the auth provider (lite Supabase Auth through fe.modules.auth and a JWKS-verified back end; full the app's identity provider), object storage (lite Supabase Storage; full S3-compatible storage), queue and jobs (lite pgmq and pg_cron, replaced by case-10: BullMQ on a declared Redis as in full, with pg_cron ticks enqueueing through the outbox; full BullMQ) behind platform/queue, realtime (lite the socket.io gateway in the api process, one instance; full socket.io scaled with a Redis adapter), the event-bus adapter (lite outbox and pgmq; full outbox and Kafka) and hosting; lite has no e2e. Migration baseline at the cutover of an existing app: the live schema is dumped into one baseline migration written to the current standard and marked applied on production without a re-run, the older migrations move to app.supabase.migrations.archive (supabase/migrations/_archive/, never applied and never judged), and new migrations append after the baseline. Assets: static templates, copy and presets are TS constants in the owning domain capability (<x>.templates.ts) and user-editable ones are seed rows; small build-time binaries such as fonts live in be.assets (be/src/assets/<kind>/); large and user media live in object storage and are fetched at runtime; there is no be/resources/."
|
|
3598
3626
|
|
|
3599
3627
|
- id: case-4
|
|
3600
3628
|
date: "2026-10-14"
|
|
@@ -3606,15 +3634,42 @@ rules:
|
|
|
3606
3634
|
source: owner
|
|
3607
3635
|
quote: "Lite may serve SSE, and SSE is the default for one-way push. (alpha.30 BE patterns research, owner decisions 2026-10-24, relayed by the alpha.30 lead)"
|
|
3608
3636
|
ruling: "Server-sent events are the third door of the realtime pattern (be.feature.realtime.sse), legal in lite and in full, and the default door for one-way server-to-client push; this amends the lite realtime adapter clause of case-2, which named the socket.io gateway as lite's only push. The socket.io gateway stays for a bidirectional channel, graphql-ws and the GraphQL subscription door stay full-only, and the hub is single-replica (the lite api pins replicas: 1, STACKS_REALTIME_SCALED)."
|
|
3609
|
-
|
|
3637
|
+
- id: case-6
|
|
3638
|
+
date: "2026-10-24"
|
|
3639
|
+
source: owner
|
|
3640
|
+
quote: "Lite cannot become full. Lite is lite, full is full. (translated from Vietnamese; owner, 2026-10-24, relayed by the alpha.31 coordinator)"
|
|
3641
|
+
ruling: "The edition is chosen once, at scaffold. No verb moves an app between lite and full: `starci app upgrade` refuses a lite app (HFS_UPGRADE_EDITION_FIXED) and only completes the test world of a full app, and `starci app check --edition` accepts only the edition hfs.json declares (HFS_EDITION_INVALID otherwise). A product that wants full is scaffolded as a full app and its product code moves across; the code above the platform and integrations seam is the same in both editions. This supersedes the 'upgrade lite -> full' and 'downgrade is not supported' sentences of case-1 to case-4."
|
|
3642
|
+
- id: case-7
|
|
3643
|
+
date: "2026-10-24"
|
|
3644
|
+
source: owner
|
|
3645
|
+
quote: "GraphQL subscriptions are allowed in lite too, as a restricted door; SSE stays the default for one-way push and WebSocket the choice for two-way. (alpha.31 realtime canon brief, owner decisions 2026-10-24, relayed by the lead)"
|
|
3646
|
+
ruling: "The GraphQL subscription is the third restricted door of the realtime pattern in both editions (slot be.feature.realtime.graphql and its dto slot, no longer full-only; graphql-ws and graphql-subscriptions are owned by platform/realtime in lite as in full). SSE stays the default for one-way push, the socket.io gateway is the door for two-way messages, and the GraphQL subscription is used only when the client already uses GraphQL and needs the typed schema payload, with a snapshot refetch on reconnect because graphql-ws has no replay. Every door rule stays: a door reads and pushes only, topics are built from the principal, a door is never public and no token travels in the URL. The memory hub is non-scale (tests and one replica); the hub is Redis when Redis is declared (lite or full) and Postgres LISTEN/NOTIFY when lite declares no Redis. The lite Redis gate and the R212 clause that refused Redis are retired by case-10 (final owner ruling 2026-10-24: lite is a minimal production, with Redis or Qdrant when a declared capability needs them). This amends the clause of case-5 that kept the subscription door full-only."
|
|
3647
|
+
- id: case-8
|
|
3648
|
+
date: "2026-10-24"
|
|
3649
|
+
source: owner
|
|
3650
|
+
quote: "One S3-style storage port for BOTH editions; business code and the presigned pattern are written once. Lite: the adapter targets Supabase Storage, with no MinIO in lite because Supabase already provides storage. Full: MinIO for local, dev and the test world; S3 or DigitalOcean Spaces in production, where only the endpoint changes and never the code. Lite-app gets the same presigned upload/download case as full-app, so both editions teach one pattern. (alpha.31 storage lane brief, owner decision 2026-10-24, relayed by the alpha.31 coordinator)"
|
|
3651
|
+
ruling: "Object storage is one S3-style port in both editions (ObjectStorage of be.integrations.object-storage, one S3 adapter): lite points it at the Supabase Storage S3 endpoint and runs no MinIO; full uses MinIO locally and in the test world and S3 or Spaces in production, changing only the endpoint and credentials. The presigned case of BE-TRANSPORT-1 is a presigned PUT signed for the exact type and length, followed by a head check that deletes a mismatch, in both editions; the POST policy is not used because Supabase documents none."
|
|
3652
|
+
- id: case-9
|
|
3653
|
+
date: "2026-10-24"
|
|
3654
|
+
source: owner
|
|
3655
|
+
quote: "Lite scales too, but scales stateless; lite is one instance type that scales horizontally and stateless. (translated from Vietnamese; owner, 2026-10-24, relayed by the alpha.31 coordinator)"
|
|
3656
|
+
ruling: "Lite scales horizontally and stateless, as full does: one instance type (the api), every replica serves any request or reconnect, all shared state lives in Postgres/Supabase (and in Redis when the app declares it, holding loseable data only), and there are no sticky sessions. The realtime hub is chosen once per app by REALTIME_HUB with no default: memory (one replica or tests only; STACKS_REALTIME_SCALED refuses it for an api with more than one replica or the global mode), postgres (LISTEN/NOTIFY and the bounded realtime_events table with @socket.io/postgres-adapter) or redis (Streams and Pub/Sub with the sharded @socket.io/redis-adapter); all three are legal in both editions, redis is selected when Redis is declared and postgres when it is not, and the socket transport is websocket-only. This amends the realtime clauses of case-2 and case-3 (which stay as dated) and the single-replica clause of case-5 (the lite api no longer pins replicas: 1)."
|
|
3657
|
+
- id: case-10
|
|
3658
|
+
date: "2026-10-24"
|
|
3659
|
+
source: owner
|
|
3660
|
+
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\")"
|
|
3661
|
+
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, managed Supabase for Postgres, Auth and Storage, 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 it (redis, qdrant), nothing is on by default, 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)."
|
|
3662
|
+
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]
|
|
3610
3663
|
exemplifiedBy:
|
|
3611
3664
|
- {path: packages/hfs/src/hfs-edition-invalid.spec.mjs, editions: [lite, full]}
|
|
3612
3665
|
- {path: packages/hfs/src/hfs-edition-forbidden-present.spec.mjs, edition: lite}
|
|
3613
3666
|
- {path: packages/hfs/src/hfs-starciwork-lite-forbidden.spec.mjs, edition: lite}
|
|
3667
|
+
- {path: packages/hfs/src/hfs-backing-declared.spec.mjs, edition: lite}
|
|
3614
3668
|
witness:
|
|
3615
3669
|
- {path: packages/hfs/src/hfs-edition-invalid.spec.mjs, editions: [lite, full], codes: [HFS_EDITION_INVALID]}
|
|
3616
3670
|
- {path: packages/hfs/src/hfs-edition-forbidden-present.spec.mjs, edition: lite, codes: [HFS_EDITION_FORBIDDEN_PRESENT]}
|
|
3617
3671
|
- {path: packages/hfs/src/hfs-starciwork-lite-forbidden.spec.mjs, edition: lite, codes: [HFS_STARCIWORK_LITE_FORBIDDEN]}
|
|
3672
|
+
- {path: packages/hfs/src/hfs-backing-declared.spec.mjs, edition: lite, codes: [HFS_BACKING_UNDECLARED, HFS_BACKING_UNUSED]}
|
|
3618
3673
|
- id: R213
|
|
3619
3674
|
code: "DB_MIGRATION_SHAPE"
|
|
3620
3675
|
editions: [lite, full]
|
|
@@ -3962,17 +4017,17 @@ rules:
|
|
|
3962
4017
|
- {kind: eslint-be, id: max-params}
|
|
3963
4018
|
- {kind: eslint-fe, id: max-params}
|
|
3964
4019
|
- id: R236
|
|
3965
|
-
code: "
|
|
4020
|
+
code: "RT_LOOSENING_STATE"
|
|
3966
4021
|
editions: [lite, full]
|
|
3967
4022
|
docs: [docs/debugging.md#r236]
|
|
3968
|
-
templateNa: "
|
|
3969
|
-
law: "A
|
|
4023
|
+
templateNa: "The tree of the runtime repository is judged as it stands; product scaffolding is judged by the bypass rule R303 with the same library."
|
|
4024
|
+
law: "A loosening is always red, and the tree is judged as it stands, never by its history (owner:loosening-state-check): the working tree holds no bypass marker, no ignore or exclude entry of a codecov, Sonar, jest, ESLint, stylelint, tsconfig or ignore file that the canon does not derive and knowledge/hfs/design-truths.yaml does not allow, no coverage target or threshold below the canon, no relaxing setting, no disabled ESLint rule, no strictness option turned off, and no allowlist, approval or baseline file the manifest does not list. Every legitimate exception lives in that manifest, each with the cite of its ruling; changing it is an owner decision and the check prints its digest. It never lands and never passes the check; there is no approval route, the owner's included (owner:loosening-always-red), and the fix is to satisfy the check."
|
|
3970
4025
|
scope: runtime
|
|
3971
4026
|
kinds: [check]
|
|
3972
4027
|
gates: [runtime]
|
|
3973
|
-
failureCodes: ["
|
|
4028
|
+
failureCodes: ["RT_LOOSENING_STATE"]
|
|
3974
4029
|
enforcers:
|
|
3975
|
-
- {kind: runtime, id:
|
|
4030
|
+
- {kind: runtime, id: loosening-state, at: scripts/checks/check-loosening-state.mjs}
|
|
3976
4031
|
- id: R237
|
|
3977
4032
|
code: "RT_REVISION_SCOPE"
|
|
3978
4033
|
editions: [lite, full]
|
|
@@ -4069,7 +4124,7 @@ rules:
|
|
|
4069
4124
|
code: "FE_STACK_FORBIDDEN"
|
|
4070
4125
|
editions: [lite, full]
|
|
4071
4126
|
docs: [docs/code-pattern-enforcement.md#r244]
|
|
4072
|
-
law: "A front end uses the one canonical library of each concern of knowledge/hfs/fe-stack.yaml: no front-end package.json (an app or a `fe/packages/*` package) depends on, and no source file under `fe/` imports, a package that the catalog lists under a concern's `forbids`; a missing concern is added to the catalog (with its pin in canon-pins.yaml), never installed beside it."
|
|
4127
|
+
law: "A front end uses the one canonical library of each concern of knowledge/hfs/fe-stack.yaml: no front-end package.json (an app or a `fe/packages/*` package) depends on, and no source file under `fe/` imports, a package or scoped package family that the catalog lists under a concern's `forbids`; a missing concern is added to the catalog (with its pin in canon-pins.yaml), never installed beside it. Grammar owns DiceBear: apps use seed on Avatar, AvatarButton or AvatarGroup members and never declare or import @dicebear/*."
|
|
4073
4128
|
kinds: [check, lint]
|
|
4074
4129
|
gates: [pre-commit, settle, land, ci]
|
|
4075
4130
|
failureCodes: ["FE_STACK_FORBIDDEN"]
|
|
@@ -4110,7 +4165,7 @@ rules:
|
|
|
4110
4165
|
docs: [docs/microservices.md#r246]
|
|
4111
4166
|
proves: [packages/eslint/be/app-composition-coverage.spec.mjs]
|
|
4112
4167
|
exemplifiedBy:
|
|
4113
|
-
- {path: packages/hfs/templates/be/skeleton
|
|
4168
|
+
- {path: packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts, edition: lite}
|
|
4114
4169
|
- {path: packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts, edition: full}
|
|
4115
4170
|
- {path: packages/eslint/be/app-composition-coverage.spec.mjs, editions: [lite, full], codes: [BE_APP_COMPOSITION_BUDGET]}
|
|
4116
4171
|
witness:
|
|
@@ -4175,7 +4230,7 @@ rules:
|
|
|
4175
4230
|
editions: [lite, full]
|
|
4176
4231
|
docs:
|
|
4177
4232
|
- {path: docs/application-stacks.md#r250-deploy-scan, editions: [lite, full]}
|
|
4178
|
-
law: "In lite and full apps, scheduled work belongs to the back-end scheduler,
|
|
4233
|
+
law: "In lite and full apps, scheduled work belongs to the back-end queue scheduler, a pg_cron tick that enqueues through the queue outbox or a declared replicated-job. Deployment files, scripts, workflows, compose files and Dockerfiles install no host crontab, cron.d entry, systemd timer, @reboot job or at job; a workflow does not schedule app work. Docker compose up, down and pull run only against an explicitly dev environment. Deploy shell scripts delegate to docker, starci or the app CLI, and contain no node -e, psql, inline SQL or downloaded shell logic."
|
|
4179
4234
|
kinds: [check]
|
|
4180
4235
|
gates: [settle, land, ci]
|
|
4181
4236
|
failureCodes: ["HFS_HOST_CRON", "HFS_DEPLOY_NO_LOGIC", "HFS_DEPLOY_COMPOSE_ON_VPS", "HFS_DEPLOY_SCHEDULER"]
|
|
@@ -4327,10 +4382,10 @@ rules:
|
|
|
4327
4382
|
- id: R258
|
|
4328
4383
|
code: "DB_PGMQ_DECLARED"
|
|
4329
4384
|
editions: [lite]
|
|
4330
|
-
editionWhy: "owner:lite-backing-services makes Supabase lite's database backing; pgmq and pg_cron ownership requirements apply to that backing."
|
|
4385
|
+
editionWhy: "owner:lite-backing-services makes Supabase lite's database backing and ruling:R212/case-10 moves the lite queue to Redis; the pgmq retirement and pg_cron ownership requirements apply to that backing."
|
|
4331
4386
|
docs: [docs/microservices.md#r258]
|
|
4332
4387
|
templateNa: "Queue declarations and enqueue-only cron commands depend on product-owned work and are authored in its Supabase migrations."
|
|
4333
|
-
law: "DB_QUEUE_CRON:
|
|
4388
|
+
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 uniquely named pg_cron job belongs to a migration and only enqueues: 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); it 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."
|
|
4334
4389
|
kinds: [check]
|
|
4335
4390
|
gates: [settle, land, ci]
|
|
4336
4391
|
failureCodes: ["DB_PGMQ_DECLARED", "DB_CRON_OWNED"]
|
|
@@ -4398,7 +4453,7 @@ rules:
|
|
|
4398
4453
|
docs:
|
|
4399
4454
|
- "docs/microservices.md#r261"
|
|
4400
4455
|
exemplifiedBy:
|
|
4401
|
-
- {"path":"packages/hfs/templates/be/skeleton
|
|
4456
|
+
- {"path":"packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts","edition":"lite"}
|
|
4402
4457
|
- {"path":"packages/hfs/templates/be/skeleton/apps/__app__/src/main.ts","edition":"full"}
|
|
4403
4458
|
- {"path":"packages/hfs/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts","editions":["lite","full"]}
|
|
4404
4459
|
- {"path":"examples/lite-app/be/apps/api/src/main.ts","edition":"lite"}
|
|
@@ -4506,7 +4561,7 @@ rules:
|
|
|
4506
4561
|
- {"path":"packages/eslint/be/app-composition.spec.mjs","edition":"full","codes":["BE_COMPOSITION_UNTYPED"]}
|
|
4507
4562
|
- id: R266
|
|
4508
4563
|
code: "STACKS_SECRET_NOT_EXTERNAL"
|
|
4509
|
-
law: "Every rendered Swarm deployment uses external immutable secrets whose names match the declared runtimeName and positive version; services carry no secret environment literals. Only the one declared ingress publishes ports; its hosts have routes, DNS ownership receipts and TLS ownership evidence. Stateful services declare placement constraints, and runnable services declare healthchecks and update, rollback and restart policies.
|
|
4564
|
+
law: "Every rendered Swarm deployment uses external immutable secrets whose names match the declared runtimeName and positive version; services carry no secret environment literals. Only the one declared ingress publishes ports; its hosts have routes, DNS ownership receipts and TLS ownership evidence. Stateful services declare placement constraints, and runnable services declare healthchecks and update, rollback and restart policies. An api app composing be.feature.realtime.* that runs more than one replica selects a shared hub with REALTIME_HUB (postgres or redis)."
|
|
4510
4565
|
kinds: [check]
|
|
4511
4566
|
gates: ["settle","land","ci"]
|
|
4512
4567
|
failureCodes: ["STACKS_SECRET_NOT_EXTERNAL", "STACKS_SECRET_UNVERSIONED", "STACKS_SECRET_ENV_LITERAL", "STACKS_PORT_PUBLISHED", "STACKS_SECOND_INGRESS", "STACKS_DNS_UNDECLARED", "STACKS_HOST_UNROUTED", "STACKS_TLS_UNDECLARED", "STACKS_PLACEMENT_MISSING", "STACKS_HEALTHCHECK_MISSING", "STACKS_UPDATE_POLICY_MISSING", "STACKS_REALTIME_SCALED"]
|
|
@@ -4514,7 +4569,6 @@ rules:
|
|
|
4514
4569
|
- {kind: hfs, id: stacks-swarm, at: scripts/hfs/rules/stacks-swarm.mjs}
|
|
4515
4570
|
- {kind: hfs, id: stacks-second-ingress, at: scripts/hfs/rules/stacks-ingress.mjs}
|
|
4516
4571
|
editions: ["lite","full"]
|
|
4517
|
-
codeEditions: {"STACKS_REALTIME_SCALED":["lite"]}
|
|
4518
4572
|
docs:
|
|
4519
4573
|
- "docs/application-stacks.md#r266"
|
|
4520
4574
|
proves:
|
|
@@ -4719,20 +4773,23 @@ rules:
|
|
|
4719
4773
|
- {path: packages/hfs/src/hfs-stacks-full-fixtures.spec.mjs, edition: full}
|
|
4720
4774
|
- id: R276
|
|
4721
4775
|
code: "STACKS_LITE_SELFHOSTED_DB"
|
|
4722
|
-
law: "Lite
|
|
4776
|
+
law: "Lite keeps PostgreSQL on managed Supabase and runs no PostgreSQL or MongoDB backing image. A Redis or Qdrant image runs only when hfs.json sides.be.backing declares it (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."
|
|
4723
4777
|
kinds: [check]
|
|
4724
4778
|
gates: ["settle","land","ci"]
|
|
4725
|
-
failureCodes: ["STACKS_LITE_SELFHOSTED_DB", "STACKS_SIDECAR_VOLUME_UNDECLARED", "STACKS_SIDECAR_DB_UNSUPPORTED"]
|
|
4779
|
+
failureCodes: ["STACKS_LITE_SELFHOSTED_DB", "STACKS_SIDECAR_VOLUME_UNDECLARED", "STACKS_SIDECAR_DB_UNSUPPORTED", "STACKS_LITE_REDIS_PERSISTENCE"]
|
|
4726
4780
|
enforcers:
|
|
4727
4781
|
- {kind: hfs, id: stacks-lite-backing, at: scripts/hfs/rules/stacks-lite-backing.mjs}
|
|
4728
4782
|
editions: ["lite"]
|
|
4729
|
-
editionWhy: "ruling:R276 records owner ruling 4: lite
|
|
4783
|
+
editionWhy: "ruling:R276 records owner ruling 4, amended by ruling:R212/case-10: lite keeps Postgres on managed Supabase, runs a Redis or Qdrant 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."
|
|
4730
4784
|
docs:
|
|
4731
4785
|
- "docs/application-stacks.md#r276"
|
|
4732
4786
|
proves: [packages/hfs/src/hfs-stacks-lite-backing.spec.mjs]
|
|
4733
4787
|
exemplifiedBy:
|
|
4734
4788
|
- {"path":"packages/hfs/templates/app/skeleton-lite/.starcistacks","edition":"lite"}
|
|
4789
|
+
- {path: examples/lite-app/.starcistacks, edition: lite}
|
|
4735
4790
|
- {path: packages/hfs/src/hfs-stacks-lite-backing.spec.mjs, edition: lite}
|
|
4791
|
+
- {path: examples/lite-app/be/src/modules/platform/queue/queue-relay.service.spec.ts, edition: lite}
|
|
4792
|
+
- {path: examples/lite-app/be/src/modules/platform/queue/bullmq-queue-transport.client.spec.ts, edition: lite}
|
|
4736
4793
|
witness:
|
|
4737
4794
|
- {path: packages/hfs/src/hfs-stacks-lite-backing.spec.mjs, edition: lite}
|
|
4738
4795
|
- id: R277
|
|
@@ -4818,7 +4875,7 @@ rules:
|
|
|
4818
4875
|
- {path: packages/hfs/src/hfs-unused-symbol-ledger.spec.mjs, editions: [lite, full], codes: [HFS_UNUSED_SYMBOL]}
|
|
4819
4876
|
- id: R281
|
|
4820
4877
|
code: "BE_SPEC_FORM_ALL_ROLES"
|
|
4821
|
-
law: "Unit spec form applies to every declared logic role, including policies, mappers, guards and integration clients. A spec obtains its subject from a compiled Nest testing module with explicit providers, never constructs the subject or mocks an own module or third-party library, and asserts behavior or state rather than only toBeDefined existence checks."
|
|
4878
|
+
law: "Unit spec form applies to every declared logic role, including policies, mappers, guards and integration clients. A spec obtains its subject from a compiled Nest testing module with explicit providers, never constructs the subject or mocks an own module or third-party library, and asserts behavior or state rather than only toBeDefined existence checks. The real provider token declaration determines whether the Inbox kit is required: aliases retain that origin, while a service or queue producer whose name contains Inbox remains an ordinary collaborator. A role of ruleParams.be.valueRoles (a zod schema) declares a value Nest never constructs: its spec parses that value directly and builds no testing module."
|
|
4822
4879
|
kinds: [lint]
|
|
4823
4880
|
gates: [pre-commit, settle, land, ci]
|
|
4824
4881
|
failureCodes: ["BE_SPEC_FORM_ALL_ROLES"]
|
|
@@ -4834,11 +4891,14 @@ rules:
|
|
|
4834
4891
|
docs: [docs/microservices.md#r281]
|
|
4835
4892
|
exemplifiedBy:
|
|
4836
4893
|
- {path: packages/eslint/be/unit-spec.spec.mjs, editions: [lite, full], codes: [BE_SPEC_FORM_ALL_ROLES]}
|
|
4894
|
+
- {path: packages/eslint/be/unit-spec-inbox-origin.spec.mjs, editions: [lite, full], codes: [BE_SPEC_FORM_ALL_ROLES]}
|
|
4837
4895
|
- {path: packages/hfs/templates/be/skeleton/src/modules/platform/clock/system-clock.service.spec.ts, editions: [lite, full]}
|
|
4838
4896
|
proves:
|
|
4839
4897
|
- {path: packages/eslint/be/unit-spec.spec.mjs, editions: [lite, full], codes: [BE_SPEC_FORM_ALL_ROLES]}
|
|
4898
|
+
- {path: packages/eslint/be/unit-spec-inbox-origin.spec.mjs, editions: [lite, full], codes: [BE_SPEC_FORM_ALL_ROLES]}
|
|
4840
4899
|
witness:
|
|
4841
4900
|
- {path: packages/eslint/be/unit-spec.spec.mjs, editions: [lite, full], codes: [BE_SPEC_FORM_ALL_ROLES]}
|
|
4901
|
+
- {path: packages/eslint/be/unit-spec-inbox-origin.spec.mjs, editions: [lite, full], codes: [BE_SPEC_FORM_ALL_ROLES]}
|
|
4842
4902
|
|
|
4843
4903
|
- id: R282
|
|
4844
4904
|
code: "HFS_ASSET_OVERSIZE"
|
|
@@ -4876,7 +4936,7 @@ rules:
|
|
|
4876
4936
|
- {path: packages/hfs/src/hfs-lexicon.spec.mjs, editions: [lite, full], codes: [HFS_APP_LEXICON]}
|
|
4877
4937
|
- id: R284
|
|
4878
4938
|
code: "BE_LITE_PROOF_LANE_MISSING"
|
|
4879
|
-
law: "A lite back end declaring event-bus, queue, webhooks, realtime, fenced-job, projection or
|
|
4939
|
+
law: "A lite back end declaring event-bus, queue, webhooks, realtime, fenced-job, projection, saga or idempotency proves each declared pattern through integration specs under src/tests/integration: runnable tests carry titles `<pattern>/<scenario>: ...` for every required scenario of that pattern. A missing integration lane is refused; missing scenario proofs use R155's BE_PATTERN_SPEC_MISSING. Full-edition pattern proof behavior is unchanged."
|
|
4880
4940
|
kinds: [check]
|
|
4881
4941
|
gates: [land, ci]
|
|
4882
4942
|
failureCodes: ["BE_LITE_PROOF_LANE_MISSING"]
|
|
@@ -4894,23 +4954,27 @@ rules:
|
|
|
4894
4954
|
- {path: packages/hfs/src/hfs-lite-proof-lane.spec.mjs, editions: [lite], codes: [BE_LITE_PROOF_LANE_MISSING]}
|
|
4895
4955
|
- id: R285
|
|
4896
4956
|
code: "FE_SWR_OUTSIDE_FAMILY"
|
|
4897
|
-
law: "SWR runtime calls belong to the declared SWR hook family. A component, feature or domain hook consumes its wrapper hook instead of importing an API operation or calling SWR itself; each back-end operation is imported and invoked only by its SWR wrapper under hooks/swr, regardless of whether the tier matrix still permits a direct transport edge."
|
|
4957
|
+
law: "SWR runtime calls belong to the declared SWR hook family. A component, feature or domain hook consumes its wrapper hook instead of importing an API operation or calling SWR itself; each back-end operation is imported and invoked only by its SWR wrapper under hooks/swr, regardless of whether the tier matrix still permits a direct transport edge. Values the API owner re-exports from its generated `__generated__/input-bounds.ts` (INPUT_BOUNDS, REST_INPUT_BOUNDS and inputBoundsIssues) are contract data, not operations, and stay importable by hooks/rhf."
|
|
4898
4958
|
kinds: [lint]
|
|
4899
4959
|
gates: [pre-commit, settle, land, ci]
|
|
4900
|
-
failureCodes: ["FE_SWR_OUTSIDE_FAMILY", "FE_OP_OUTSIDE_SWR_HOOK"]
|
|
4960
|
+
failureCodes: ["FE_SWR_OUTSIDE_FAMILY", "FE_OP_OUTSIDE_SWR_HOOK", "FE_SUBSCRIPTION_NO_SNAPSHOT"]
|
|
4901
4961
|
enforcers:
|
|
4902
4962
|
- {kind: eslint-fe, id: swr-only-in-family}
|
|
4903
4963
|
- {kind: eslint-fe, id: operations-only-in-swr-hooks}
|
|
4964
|
+
- {kind: eslint-fe, id: subscription-refetches-snapshot}
|
|
4904
4965
|
editions: [lite, full]
|
|
4905
4966
|
docs: [docs/quality-bar.md#r285]
|
|
4906
4967
|
exemplifiedBy:
|
|
4907
4968
|
- {path: packages/eslint/fe/swr-family.spec.mjs, editions: [lite, full], codes: [FE_SWR_OUTSIDE_FAMILY, FE_OP_OUTSIDE_SWR_HOOK]}
|
|
4969
|
+
- {path: packages/eslint/fe/swr-family.spec.mjs, editions: [lite, full], codes: [FE_SUBSCRIPTION_NO_SNAPSHOT]}
|
|
4908
4970
|
- {path: packages/hfs/templates/fe/table/graphql/useQuerySwr.ts.tpl, editions: [lite, full]}
|
|
4909
4971
|
- {path: packages/hfs/templates/fe/table/graphql/useMutateSwr.ts.tpl, editions: [lite, full]}
|
|
4910
4972
|
proves:
|
|
4911
4973
|
- {path: packages/eslint/fe/swr-family.spec.mjs, editions: [lite, full], codes: [FE_SWR_OUTSIDE_FAMILY, FE_OP_OUTSIDE_SWR_HOOK]}
|
|
4974
|
+
- {path: packages/eslint/fe/swr-family.spec.mjs, editions: [lite, full], codes: [FE_SUBSCRIPTION_NO_SNAPSHOT]}
|
|
4912
4975
|
witness:
|
|
4913
4976
|
- {path: packages/eslint/fe/swr-family.spec.mjs, editions: [lite, full], codes: [FE_SWR_OUTSIDE_FAMILY, FE_OP_OUTSIDE_SWR_HOOK]}
|
|
4977
|
+
- {path: packages/eslint/fe/swr-family.spec.mjs, editions: [lite, full], codes: [FE_SUBSCRIPTION_NO_SNAPSHOT]}
|
|
4914
4978
|
|
|
4915
4979
|
- id: R286
|
|
4916
4980
|
code: "FE_COPY_IN_CALL"
|
|
@@ -4957,7 +5021,7 @@ rules:
|
|
|
4957
5021
|
|
|
4958
5022
|
- id: R288
|
|
4959
5023
|
code: "FE_STACK_UNLISTED"
|
|
4960
|
-
law: "Every 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. 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. GraphQL documents, including gql templates, match the back-end contracts, and SWR keys carry fetcher identities without an opt-in declaration."
|
|
5024
|
+
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. GraphQL documents, including gql templates, match the back-end contracts, and SWR keys carry fetcher identities without an opt-in declaration."
|
|
4961
5025
|
kinds: [check]
|
|
4962
5026
|
gates: [settle, land, ci]
|
|
4963
5027
|
failureCodes: ["FE_STACK_UNLISTED", "FE_STACK_CORE_MISSING"]
|
|
@@ -5083,7 +5147,7 @@ rules:
|
|
|
5083
5147
|
- {path: examples/lite-app/.github/workflows/stack-deploy.yml, edition: lite}
|
|
5084
5148
|
- id: R294
|
|
5085
5149
|
code: "STACKS_DEPLOY_CONFIG_INVALID"
|
|
5086
|
-
law: "In lite and full apps, every docker-swarm environment of .starcistacks/application-stacks.yaml that declares environments.<env>.deploy declares a coherent block: it is judged by the same normalizeDeploy the `starci app stack` verbs use, against the real tree. Unknown fields are refused, names, keys, volumes, hosts, URLs and commands match their patterns, the config and public environment files exist below .starcistacks/<env>/, every service names a service of the first stack source, every sealed member has a secrets/<member>.enc file, verify entries and configs are well formed with unique config names, and the backup helper image is pinned by a real sha256 digest (no placeholder). Each problem is one finding at environments.<env>.<field>; an environment without a deploy block is not a finding."
|
|
5150
|
+
law: "In lite and full apps, every docker-swarm environment of .starcistacks/application-stacks.yaml that declares environments.<env>.deploy declares a coherent block: it is judged by the same normalizeDeploy the `starci app stack` verbs use, against the real tree. Unknown fields are refused, names, keys, volumes, hosts, URLs and commands match their patterns, the config and public environment files exist below .starcistacks/<env>/, every service names a service of the first stack source, every sealed member has a secrets/<member>.enc file, verify entries and configs are well formed with unique config names, every postDeploy entry names a task declared under deploy.tasks, and the backup helper image is pinned by a real sha256 digest (no placeholder). Each problem is one finding at environments.<env>.<field>; an environment without a deploy block is not a finding."
|
|
5087
5151
|
kinds: [check]
|
|
5088
5152
|
gates: [settle, land, ci]
|
|
5089
5153
|
failureCodes: ["STACKS_DEPLOY_CONFIG_INVALID"]
|
|
@@ -5270,7 +5334,7 @@ rules:
|
|
|
5270
5334
|
- {path: packages/hfs/src/mcp-tools-snapshot.spec.mjs, editions: [lite, full], codes: [HFS_MCP_TOOLS_SNAPSHOT]}
|
|
5271
5335
|
- id: R303
|
|
5272
5336
|
code: "HFS_BYPASS_MARKER"
|
|
5273
|
-
law: "A product repository holds no bypass of a check (owner ruling loosening-always-red): no inline disable of any tool (eslint, stylelint, tslint, prettier, biome), no ts-ignore, ts-expect-error or ts-nocheck directive, no Sonar exclusion comment, no istanbul, c8 or v8 coverage ignore, no skipped, focused or todo test or skip option, and no failure swallowed in a hook, script or workflow (`|| true`, `continue-on-error`, `set +e`, `HUSKY=0`, `--no-verify`, `fail_ci_if_error: false`). A front end has no specs, but its source is still judged for the inline disables. `starci app check` reads the tracked tree and `starci app hygiene` (the managed pre-commit hook) the staged files from the index, so the bypass fails at the commit."
|
|
5337
|
+
law: "A product repository holds no bypass of a check (owner ruling loosening-always-red): no inline disable of any tool (eslint, stylelint, tslint, prettier, biome), no ts-ignore, ts-expect-error or ts-nocheck directive, no Sonar exclusion comment, no istanbul, c8 or v8 coverage ignore, no skipped, focused or todo test or skip option, and no failure swallowed in a hook, script or workflow (`|| true`, `; true`, `continue-on-error`, `set +e`, `HUSKY=0`, `--no-verify`, `fail_ci_if_error: false`, `--passWithNoTests`, `--coverageThreshold`, `--no-error-on-unmatched-pattern`, a `--max-warnings` budget). A marker is read where it acts: a suppression word opens a comment, a test marker stands in code, a swallowed failure stands in a shell line or in the string a script hands a shell; the same words quoted as data are no bypass. The runtime tree is judged by the same library (R236). A front end has no specs, but its source is still judged for the inline disables. `starci app check` reads the tracked tree and `starci app hygiene` (the managed pre-commit hook) the staged files from the index, so the bypass fails at the commit."
|
|
5274
5338
|
kinds: [check]
|
|
5275
5339
|
gates: [pre-commit, land, ci]
|
|
5276
5340
|
failureCodes: ["HFS_BYPASS_MARKER"]
|
|
@@ -5289,12 +5353,13 @@ rules:
|
|
|
5289
5353
|
- {path: packages/hfs/src/bypass-detector.spec.mjs, editions: [lite, full], codes: [HFS_BYPASS_MARKER]}
|
|
5290
5354
|
- id: R304
|
|
5291
5355
|
code: "BE_SCHEMA_PLACE"
|
|
5292
|
-
law: "The back end validates by zone. A transport door (GraphQL `@InputType`, REST request class) is validated by `class-validator` (R42); untrusted data that never reaches a DTO (a provider's HTTP response, structured model output, a queue or outbox payload read back, a tool argument) is read through a `zod` schema. `zod` is imported only from a `<name>.schema.ts` file (or its spec) of a `modules/domain`, `modules/platform` or `modules/integrations` owner, never in a feature, a transport door or a `dto/` folder, and a schema file imports neither `class-validator`, `class-transformer` nor `@nestjs/graphql
|
|
5356
|
+
law: "The back end validates by zone. A transport door (GraphQL `@InputType`, REST request class) is validated by `class-validator` (R42); untrusted data that never reaches a DTO (a provider's HTTP response, structured model output, a queue or outbox payload read back, a tool argument) is read through a `zod` schema. `zod` is imported only from a `<name>.schema.ts` file (or its spec) of a `modules/domain`, `modules/platform` or `modules/integrations` owner, never in a feature, a transport door or a `dto/` folder, and a schema file imports neither `class-validator`, `class-transformer` nor `@nestjs/graphql`, imports zod and declares only zod schema values and their types (no class, provider, function or other logic). The spec of a schema file parses the schema directly and builds no testing module only while its sibling is such a file. The reader that calls the schema maps a failed parse to its owner's error family. No schema is shared between the front end and the back end: the shared contract is the emitted `schema.graphql` and `openapi.json`."
|
|
5293
5357
|
kinds: [lint, design]
|
|
5294
5358
|
gates: [pre-commit, settle, land, ci]
|
|
5295
5359
|
failureCodes: ["BE_SCHEMA_PLACE"]
|
|
5296
5360
|
enforcers:
|
|
5297
5361
|
- {kind: eslint-be, id: schema-place}
|
|
5362
|
+
- {kind: eslint-be, id: schema-file-values-only}
|
|
5298
5363
|
editions: [lite, full]
|
|
5299
5364
|
docs: [docs/quality-bar.md#r304]
|
|
5300
5365
|
proves:
|
|
@@ -5327,3 +5392,59 @@ rules:
|
|
|
5327
5392
|
- {path: packages/eslint/be/boundary-schema.spec.mjs, edition: lite, codes: [BE_SCHEMA_BOUNDED]}
|
|
5328
5393
|
- {path: packages/eslint/be/boundary-schema.spec.mjs, edition: full, codes: [BE_SCHEMA_BOUNDED]}
|
|
5329
5394
|
templateNa: "A schema is declared per boundary by its owner; the canon scaffolds none, and the two example schemas are the reference."
|
|
5395
|
+
- id: R310
|
|
5396
|
+
code: "BE_MUTATION_IDEMPOTENT"
|
|
5397
|
+
law: "A client command marked with Effectful() from platform/inbox declares idempotency in hfs.json sides.be.patterns and carries a required string idempotencyKey; its transport validates a required UUID or a non-empty string bounded to 64 characters. The handler delegates once to a domain service with that key. The resolved domain method returns its own EntityManager transaction whose selected callback returns Inbox.runClient with that callback's manager, authenticated principal, operation, key and request fingerprint input. The inbox owner claims and persists the complete outcome atomically; a repeated key with different input refuses IDEMPOTENCY_KEY_REUSED (BE-IDEMPOTENCY-3)."
|
|
5398
|
+
kinds: [lint, design]
|
|
5399
|
+
gates: [pre-commit, settle, land, ci]
|
|
5400
|
+
failureCodes: ["BE_MUTATION_IDEMPOTENT"]
|
|
5401
|
+
enforcers:
|
|
5402
|
+
- {kind: eslint-be, id: mutation-idempotent}
|
|
5403
|
+
editions: [lite, full]
|
|
5404
|
+
docs:
|
|
5405
|
+
- {path: docs/microservices.md#r310, editions: [lite, full]}
|
|
5406
|
+
templateNa: "Effectful client intent and its request fields belong to the product command; the platform inbox templates provide the receipt API and marker without inventing a product operation."
|
|
5407
|
+
proves:
|
|
5408
|
+
- {path: packages/eslint/be/mutation-idempotent.spec.mjs, editions: [lite, full], codes: [BE_MUTATION_IDEMPOTENT]}
|
|
5409
|
+
exemplifiedBy:
|
|
5410
|
+
- {path: examples/full-app/be/src/features/api/checkout/application/place-order.command.ts, edition: full}
|
|
5411
|
+
- {path: packages/eslint/be/mutation-idempotent.spec.mjs, editions: [lite, full], codes: [BE_MUTATION_IDEMPOTENT]}
|
|
5412
|
+
witness:
|
|
5413
|
+
- {path: packages/eslint/be/mutation-idempotent.spec.mjs, editions: [lite, full], codes: [BE_MUTATION_IDEMPOTENT]}
|
|
5414
|
+
- id: R311
|
|
5415
|
+
code: "HFS_TEXT_CORRUPT"
|
|
5416
|
+
law: "The Vietnamese copy of an app reaches users as written. A message catalog (a `<owner>.messages.ts` file or a `vi.json` locale file) is valid UTF-8, and no `vi` string carries the mark of a lossy re-encode: a diacritic letter replaced by a literal `?` (a word then reads as letters around a question mark) or by the U+FFFD replacement character. Rewrite such a file with an editor or Node, never through a shell pipe that re-encodes it."
|
|
5417
|
+
kinds: [check]
|
|
5418
|
+
gates: [settle, land, ci]
|
|
5419
|
+
failureCodes: ["HFS_TEXT_CORRUPT"]
|
|
5420
|
+
enforcers:
|
|
5421
|
+
- {kind: hfs, id: text-integrity, at: scripts/hfs/rules/text-integrity.mjs}
|
|
5422
|
+
editions: [lite, full]
|
|
5423
|
+
docs: [docs/quality-bar.md#r311]
|
|
5424
|
+
templateNa: "A corrupted catalog is a defect of one authored file; the templates carry intact copy and no template scaffolds a corruption."
|
|
5425
|
+
proves:
|
|
5426
|
+
- {path: packages/hfs/src/hfs-text-integrity.spec.mjs, editions: [lite, full], codes: [HFS_TEXT_CORRUPT]}
|
|
5427
|
+
exemplifiedBy:
|
|
5428
|
+
- {path: packages/hfs/src/hfs-text-integrity.spec.mjs, editions: [lite, full], codes: [HFS_TEXT_CORRUPT]}
|
|
5429
|
+
witness:
|
|
5430
|
+
- {path: packages/hfs/src/hfs-text-integrity.spec.mjs, editions: [lite, full], codes: [HFS_TEXT_CORRUPT]}
|
|
5431
|
+
- id: R312
|
|
5432
|
+
code: "BE_UPLOAD_BUFFERED"
|
|
5433
|
+
law: "Browser uploads use direct presigned storage requests through the ObjectStorage port of integrations/object-storage, in both editions: one presigned PUT whose signature covers the exact Content-Type and Content-Length, then a head call that compares the stored object with the declaration and deletes a mismatch. Nest UploadedFile/UploadedFiles decorators, Multer upload interceptors and MulterModule, and imports of multer, express-fileupload, graphql-upload, graphql-upload-minimal, @fastify/multipart, fastify-multipart, busboy, formidable, multiparty and connect-multiparty are refused. Signing metadata supplies no byte-stream REST evidence; an actual byte-stream response remains a permitted reason under R278."
|
|
5434
|
+
kinds: [lint]
|
|
5435
|
+
gates: [pre-commit, settle, land, ci]
|
|
5436
|
+
failureCodes: ["BE_UPLOAD_BUFFERED"]
|
|
5437
|
+
enforcers:
|
|
5438
|
+
- {kind: eslint-be, id: no-buffered-upload}
|
|
5439
|
+
editions: [lite, full]
|
|
5440
|
+
docs:
|
|
5441
|
+
- {path: docs/be-shape-coverage.md#r312, editions: [lite, full]}
|
|
5442
|
+
proves:
|
|
5443
|
+
- {path: packages/eslint/be/uploads.spec.mjs, editions: [lite, full], codes: [BE_UPLOAD_BUFFERED]}
|
|
5444
|
+
exemplifiedBy:
|
|
5445
|
+
- {path: examples/full-app/be/src/features/api/checkout/transport/graphql/mint-attachment-upload.resolver.ts, edition: full}
|
|
5446
|
+
- {path: examples/lite-app/be/src/features/api/attachments/transport/graphql/mint-attachment-upload.resolver.ts, edition: lite}
|
|
5447
|
+
- {path: packages/eslint/be/uploads.spec.mjs, editions: [lite, full], codes: [BE_UPLOAD_BUFFERED]}
|
|
5448
|
+
witness:
|
|
5449
|
+
- {path: packages/eslint/be/uploads.spec.mjs, editions: [lite, full], codes: [BE_UPLOAD_BUFFERED]}
|
|
5450
|
+
templateNa: "A storage grant is declared per integration; the canon scaffolds none, and the presigned-upload examples of both editions are the reference."
|