@starci/eslint-canon-be 3.4.34 → 3.4.35
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 +4 -0
- package/ambient-id.mjs +4 -5
- package/package.json +1 -1
- package/runtime/engine/yaml.mjs +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +4 -4
- package/runtime/knowledge/hfs/rules.yaml +912 -637
- package/runtime/knowledge/hfs/slots.yaml +21 -14
- package/runtime/scripts/hfs/architecture/supabase.mjs +1 -1
- package/runtime/scripts/hfs/manifest-shape.mjs +1 -3
- package/runtime/scripts/hfs/rule-catalog.mjs +7 -10
|
@@ -538,6 +538,7 @@ slots:
|
|
|
538
538
|
profiles: [app]
|
|
539
539
|
exemplifiedBy:
|
|
540
540
|
- packages/hfs/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml
|
|
541
|
+
- {path: packages/hfs/src/hfs-release-provenance.spec.mjs, edition: full}
|
|
541
542
|
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite manages its Supabase database deploy workflow; full owns an optional provider-specific workflow."
|
|
542
543
|
# The database deploy workflow: workflow_dispatch only (like app.ci-e2e); starci app sync renders it under the managed ci group.
|
|
543
544
|
path: ".github/workflows/db-deploy.yml"
|
|
@@ -548,7 +549,6 @@ slots:
|
|
|
548
549
|
liteManagedBy: ci-workflows-lite
|
|
549
550
|
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
550
551
|
since: 2.1.0
|
|
551
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
552
552
|
- id: app.ci-stack-deploy
|
|
553
553
|
since: 3.3.0
|
|
554
554
|
profiles: [app]
|
|
@@ -687,7 +687,8 @@ slots:
|
|
|
687
687
|
tier: none
|
|
688
688
|
tests: none
|
|
689
689
|
rules: [HFS_PLAINTEXT_SECRET, HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK]
|
|
690
|
-
|
|
690
|
+
exemplifiedBy:
|
|
691
|
+
- {path: packages/hfs/src/hfs-slot-ownership.spec.mjs, edition: lite}
|
|
691
692
|
- id: app.sides
|
|
692
693
|
since: 3.1.0
|
|
693
694
|
profiles: [app]
|
|
@@ -810,6 +811,7 @@ slots:
|
|
|
810
811
|
profiles: [app]
|
|
811
812
|
exemplifiedBy:
|
|
812
813
|
- packages/hfs/templates/app/skeleton-lite/supabase/
|
|
814
|
+
- {path: packages/hfs/src/hfs-database-access.spec.mjs, edition: full}
|
|
813
815
|
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): Supabase is required in lite; full enables it only through a declared connection."
|
|
814
816
|
# The one Supabase project directory, at the app root beside be/, fe/ and .starcistacks (never under a side).
|
|
815
817
|
path: "supabase/"
|
|
@@ -820,11 +822,11 @@ slots:
|
|
|
820
822
|
tier: none
|
|
821
823
|
tests: none
|
|
822
824
|
since: 2.1.0
|
|
823
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
824
825
|
- id: app.supabase.config
|
|
825
826
|
profiles: [app]
|
|
826
827
|
exemplifiedBy:
|
|
827
828
|
- packages/hfs/templates/app/skeleton-lite/supabase/config.toml
|
|
829
|
+
- {path: packages/hfs/src/db-config-policy.spec.mjs, edition: full}
|
|
828
830
|
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite requires Supabase configuration; full requires it only when Supabase is declared."
|
|
829
831
|
# The project configuration: auth keys are env(NAME) references, never literals (L08 judges its policy keys).
|
|
830
832
|
path: "supabase/config.toml"
|
|
@@ -835,11 +837,11 @@ slots:
|
|
|
835
837
|
tier: none
|
|
836
838
|
tests: none
|
|
837
839
|
since: 2.1.0
|
|
838
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
839
840
|
- id: app.supabase.migrations
|
|
840
841
|
profiles: [app]
|
|
841
842
|
exemplifiedBy:
|
|
842
843
|
- packages/hfs/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql
|
|
844
|
+
- {path: packages/hfs/src/db-migration-shape.spec.mjs, edition: full}
|
|
843
845
|
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite uses Supabase schema authority and requires a migration; full opts into that provider."
|
|
844
846
|
# The schema authority of an app on Supabase: forward-only SQL migrations named <timestamp14>_<kebab-name>.sql,
|
|
845
847
|
# one file per change, immutable once on origin/main (L02-L08 judge them).
|
|
@@ -852,7 +854,6 @@ slots:
|
|
|
852
854
|
tier: none
|
|
853
855
|
tests: none
|
|
854
856
|
since: 2.1.0
|
|
855
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
856
857
|
- id: app.supabase.migrations.archive
|
|
857
858
|
profiles: [app]
|
|
858
859
|
# The migrations an existing app retired at its cutover (owner ruling 2026-10-13, migration baseline policy): the live schema is
|
|
@@ -888,7 +889,8 @@ slots:
|
|
|
888
889
|
tier: none
|
|
889
890
|
tests: none
|
|
890
891
|
since: 2.1.0
|
|
891
|
-
|
|
892
|
+
exemplifiedBy:
|
|
893
|
+
- {path: packages/hfs/src/db-types-returned-collector.spec.mjs, edition: full}
|
|
892
894
|
- id: app.supabase.templates
|
|
893
895
|
profiles: [app]
|
|
894
896
|
path: "supabase/templates/*.html"
|
|
@@ -1025,7 +1027,8 @@ slots:
|
|
|
1025
1027
|
tests: none
|
|
1026
1028
|
coverage: none
|
|
1027
1029
|
rules: [HFS_UNUSED_SYMBOL]
|
|
1028
|
-
|
|
1030
|
+
exemplifiedBy:
|
|
1031
|
+
- {path: packages/hfs/src/hfs-unused-symbol-ledger.spec.mjs, editions: [lite, full]}
|
|
1029
1032
|
- id: repo.packages
|
|
1030
1033
|
since: 3.1.0
|
|
1031
1034
|
profiles: [be, fe]
|
|
@@ -1183,8 +1186,9 @@ slots:
|
|
|
1183
1186
|
tests: none
|
|
1184
1187
|
coverage: none
|
|
1185
1188
|
why: the MCP tool catalog an app's MCP server publishes at one contract major (`<server>@<major>`), the `tools/list` result its clients rely on, committed as a cross-repo interface; the canon emits no MCP catalog, so its shape is judged (R302 HFS_MCP_TOOLS_SNAPSHOT)
|
|
1186
|
-
exampleNa: "owner:mcp-tools-snapshot-no-example (2026-10-21): no example app serves an MCP server; the package spec judges the shape"
|
|
1187
1189
|
rules: [HFS_MCP_TOOLS_SNAPSHOT]
|
|
1190
|
+
exemplifiedBy:
|
|
1191
|
+
- {path: packages/hfs/src/mcp-tools-snapshot.spec.mjs, editions: [lite, full]}
|
|
1188
1192
|
|
|
1189
1193
|
# ----- BE: apps (one slot per app kind; a new kind = a new slot, minor bump) ---------------------------
|
|
1190
1194
|
# The files of every app (owner ruling 2026-10-14, R245 and R246, since 3.1.0), each with a line budget that
|
|
@@ -1449,7 +1453,8 @@ slots:
|
|
|
1449
1453
|
coverage: none
|
|
1450
1454
|
rules: [BE_KIND_ISOLATION, BE_FEATURE_IMPORTS_FEATURE, BE_FEATURE_NOT_COMPOSED]
|
|
1451
1455
|
since: 1.0.0
|
|
1452
|
-
|
|
1456
|
+
exemplifiedBy:
|
|
1457
|
+
- {path: packages/hfs/src/hfs-stacks-swarm.spec.mjs, edition: lite}
|
|
1453
1458
|
- id: be.feature.realtime.graphql
|
|
1454
1459
|
profiles: [be]
|
|
1455
1460
|
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite uses only the in-process socket.io gateway; the GraphQL subscription door remains full-only."
|
|
@@ -1653,7 +1658,8 @@ slots:
|
|
|
1653
1658
|
coverage: required
|
|
1654
1659
|
budget: {indexExports: 60}
|
|
1655
1660
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE, BE_INFRA_OWNER]
|
|
1656
|
-
|
|
1661
|
+
exemplifiedBy:
|
|
1662
|
+
- {path: packages/eslint/be/webhook-handoff-inbox.spec.mjs, edition: lite}
|
|
1657
1663
|
- id: be.platform.queue
|
|
1658
1664
|
since: 3.1.0
|
|
1659
1665
|
profiles: [be]
|
|
@@ -1792,7 +1798,8 @@ slots:
|
|
|
1792
1798
|
tests: unit-beside # a measured slot: <name>.projection.spec.ts beside its projection is legal (the file owes 100 per file; a spec of its own is optional)
|
|
1793
1799
|
coverage: required
|
|
1794
1800
|
rules: [BE_PROJECTION_WRITE_OWNER, BE_PROJECTION_SHAPE, BE_TIER_DIRECTION, BE_CONTEXT_COUPLING]
|
|
1795
|
-
|
|
1801
|
+
exemplifiedBy:
|
|
1802
|
+
- {path: packages/eslint/be/jobs-projections-coverage.spec.mjs, edition: lite}
|
|
1796
1803
|
- id: be.integrations
|
|
1797
1804
|
since: 3.1.0
|
|
1798
1805
|
profiles: [be]
|
|
@@ -1822,6 +1829,7 @@ slots:
|
|
|
1822
1829
|
profiles: [be]
|
|
1823
1830
|
exemplifiedBy:
|
|
1824
1831
|
- packages/hfs/templates/be/skeleton-lite/src/modules/integrations/supabase/
|
|
1832
|
+
- {path: packages/hfs/src/be-supabase-jwt-verified.spec.mjs, edition: full}
|
|
1825
1833
|
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite requires the Supabase Auth and Storage integration; full opts in through a connection."
|
|
1826
1834
|
# The only be import of @supabase/* (L10): the Auth admin and Storage admin client, plus the JWKS-based verification of
|
|
1827
1835
|
# the Supabase access token the guard chain reads (L16). The connection itself is ordinary Postgres (the shared
|
|
@@ -1839,7 +1847,6 @@ slots:
|
|
|
1839
1847
|
rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT]
|
|
1840
1848
|
coverage: required
|
|
1841
1849
|
since: 2.1.0
|
|
1842
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
1843
1850
|
|
|
1844
1851
|
# ----- BE: tests (owner test layout 2026-09-30). Unit specs are <name>.service.spec.ts beside their service and nothing else; everything else
|
|
1845
1852
|
# lives under src/tests/, one folder per kind, and the folder and the file suffix always agree (a file whose suffix does
|
|
@@ -1872,7 +1879,6 @@ slots:
|
|
|
1872
1879
|
overridden in the DI container. It is the only test location that may import typeorm's DataSource or testcontainers,
|
|
1873
1880
|
call migrate/runMigrations/synchronize, or write process.env.
|
|
1874
1881
|
rules: [BE_TEST_TOPOLOGY, BE_TEST_CONSTRAINTS_ON]
|
|
1875
|
-
exampleNa: "owner:no-runtime-specs (2026-10-17): the only example of this slot in an edition was a deleted runtime fixture; the scaffold e2e exercises the scaffolded slot"
|
|
1876
1882
|
|
|
1877
1883
|
- id: be.tests.world.kit
|
|
1878
1884
|
since: 3.1.0
|
|
@@ -2269,7 +2275,8 @@ slots:
|
|
|
2269
2275
|
tests: none
|
|
2270
2276
|
why: the canonical GraphQL client output of codegen is local and never committed; this specific slot overrides the tracked API parent
|
|
2271
2277
|
rules: [FE_WIRE_GENERATED]
|
|
2272
|
-
|
|
2278
|
+
exemplifiedBy:
|
|
2279
|
+
- {path: packages/hfs/src/r52-wire-generated.spec.mjs, editions: [lite, full]}
|
|
2273
2280
|
- id: fe.transport.client
|
|
2274
2281
|
since: 3.1.0
|
|
2275
2282
|
profiles: [fe]
|
|
@@ -84,7 +84,7 @@ export const MANIFEST_KINDS = [APP_KIND, RUNTIME_KIND];
|
|
|
84
84
|
export const manifestKind = (m) => (isPlainObject(m) && m.kind !== undefined ? m.kind : APP_KIND);
|
|
85
85
|
const strList = (v) => stringList(v);
|
|
86
86
|
|
|
87
|
-
const APP_SLOT_KEYS = ['id', 'profiles', 'path', 'presence', 'tracked', 'tier', 'tests', 'owner', 'appKind', 'minInstances', 'requiredWhen', 'requiredInstances', 'requires', 'pattern', 'trigger', 'allows', 'forbids', 'layers', 'kinds', 'roles', 'composedBy', 'budget', 'managedBy', 'liteManagedBy', 'rules', 'goesTo', 'why', 'since', 'perConnection', 'parent', 'editions', 'editionWhy', 'litePresence', 'lite', 'provider', 'coverage', 'entries', 'outcomeHome', 'templateNa', '
|
|
87
|
+
const APP_SLOT_KEYS = ['id', 'profiles', 'path', 'presence', 'tracked', 'tier', 'tests', 'owner', 'appKind', 'minInstances', 'requiredWhen', 'requiredInstances', 'requires', 'pattern', 'trigger', 'allows', 'forbids', 'layers', 'kinds', 'roles', 'composedBy', 'budget', 'managedBy', 'liteManagedBy', 'rules', 'goesTo', 'why', 'since', 'perConnection', 'parent', 'editions', 'editionWhy', 'litePresence', 'lite', 'provider', 'coverage', 'entries', 'outcomeHome', 'templateNa', 'exemplifiedBy'];
|
|
88
88
|
/** The fields a slot's `lite` overlay may hold: the same keys it would carry in the slot body, resolved under edition lite. */
|
|
89
89
|
const LITE_OVERLAY_KEYS = ['path', 'requires', 'allows', 'forbids', 'minInstances', 'requiredInstances', 'why'];
|
|
90
90
|
/** A runtime slot has no app kind, side composition, layer or managed template; it may name the generator of a generated copy. */
|
|
@@ -218,8 +218,6 @@ function slotCoverageEvidenceProblems(slot, at) {
|
|
|
218
218
|
const bad = [];
|
|
219
219
|
if (slot.templateNa !== undefined && !(typeof slot.templateNa === 'string' && slot.templateNa.trim()))
|
|
220
220
|
bad.push(`${at}: templateNa must be non-empty text`);
|
|
221
|
-
if (slot.exampleNa !== undefined && !(typeof slot.exampleNa === 'string' && slot.exampleNa.trim()))
|
|
222
|
-
bad.push(`${at}: exampleNa must be non-empty text`);
|
|
223
221
|
if (slot.exemplifiedBy === undefined) return bad;
|
|
224
222
|
const refs = slot.exemplifiedBy;
|
|
225
223
|
const valid = Array.isArray(refs) && refs.length > 0 && refs.every(slotEvidenceRefValid)
|
|
@@ -17,7 +17,13 @@ const RULE_KINDS = Object.freeze(['codemod', 'lint', 'check', 'design']);
|
|
|
17
17
|
const FINDING_CODE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$/;
|
|
18
18
|
const ENFORCER_ID = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
19
19
|
const FILE_ENFORCERS = new Set(['machine', 'hfs', 'work-validate', 'sonar', 'runtime']);
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The enforcer kinds that run only inside the StarCi runtime repository (`starci runtime check`, `starci work`): no published package carries
|
|
22
|
+
* them, so no package spec can hold them (owner ruling no-runtime-specs: the runtime has no specs of its own; the package clean-test and the
|
|
23
|
+
* scaffold e2e are its tests), and no example app is a product they judge. A rule is runtime-judged when every enforcer it has is of these kinds.
|
|
24
|
+
*/
|
|
25
|
+
export const RUNTIME_ONLY_ENFORCERS = Object.freeze(['work-validate', 'runtime']);
|
|
26
|
+
const RULE_KEYS = new Set(['id', 'code', 'law', 'scope', 'kinds', 'gates', 'failureCodes', 'editions', 'codeEditions', 'enforcers', 'cases', 'docs', 'exemplifiedBy', 'proves', 'witness', 'editionWhy', 'amendedBy', 'templateNa']);
|
|
21
27
|
const CASE_KEYS = new Set(['id', 'date', 'source', 'ruling', 'quote', 'supersedes', 'status', 'staleWhen', 'amendedBy']);
|
|
22
28
|
const CASE_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
23
29
|
const ENFORCER_KEYS = new Set(['kind', 'id', 'status', 'at', 'editions', 'editionWhy']);
|
|
@@ -116,13 +122,6 @@ function witnessProblems(rule, label, bad) {
|
|
|
116
122
|
const value = rule.witness;
|
|
117
123
|
if (value === undefined) return;
|
|
118
124
|
const at = `${label}.witness`;
|
|
119
|
-
if (isPlainObject(value)) {
|
|
120
|
-
unknownKeys(value, new Set(['status', 'reason', 'date']), at, bad);
|
|
121
|
-
if (value.status !== 'unreachable') bad.push(`${at}.status must be unreachable`);
|
|
122
|
-
textProblem(value.reason, `${at}.reason`, bad);
|
|
123
|
-
dateProblem(value.date, `${at}.date`, bad);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
125
|
if (!nonEmptyList(value, at, bad)) return;
|
|
127
126
|
value.forEach((item, index) => witnessEntryProblems(item, `${at}[${index}]`, bad));
|
|
128
127
|
}
|
|
@@ -283,8 +282,6 @@ function checkRule(rule, index, catalog, codeOwner, bad) {
|
|
|
283
282
|
if (rule.amendedBy !== undefined) caseReferenceProblems(rule.amendedBy, `${label}.amendedBy`, bad);
|
|
284
283
|
optionalText(rule, 'editionWhy', label, bad);
|
|
285
284
|
optionalText(rule, 'templateNa', label, bad);
|
|
286
|
-
optionalText(rule, 'specNa', label, bad);
|
|
287
|
-
optionalText(rule, 'exampleNa', label, bad);
|
|
288
285
|
for (const key of ['docs', 'exemplifiedBy', 'proves']) evidenceProblems(rule, key, label, bad);
|
|
289
286
|
witnessProblems(rule, label, bad);
|
|
290
287
|
enumListProblems(rule, label, 'kinds', RULE_KINDS, bad);
|