@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.
@@ -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
- 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"
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
- 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"
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
- 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"
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
- 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"
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
- 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"
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
- 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"
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
- 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"
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]
@@ -209,7 +209,7 @@ export function checkBackendSupabase(input) {
209
209
  };
210
210
  }
211
211
 
212
- function checkJwtPolicy(input) {
212
+ export function checkJwtPolicy(input) {
213
213
  const kit = machineKit(input);
214
214
  const violations = [];
215
215
  let verifyCalls = 0;
@@ -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', 'exampleNa', 'exemplifiedBy'];
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
- const RULE_KEYS = new Set(['id', 'code', 'law', 'scope', 'kinds', 'gates', 'failureCodes', 'editions', 'codeEditions', 'enforcers', 'cases', 'docs', 'exemplifiedBy', 'proves', 'witness', 'editionWhy', 'amendedBy', 'templateNa', 'specNa', 'exampleNa']);
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);