@starci/eslint-canon-be 3.4.0 → 3.4.2

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.
@@ -535,7 +535,6 @@ slots:
535
535
  profiles: [app]
536
536
  exemplifiedBy:
537
537
  - packages/hfs/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml
538
- - {path: tests/fixtures/hfs-callback-full/.github/workflows/db-deploy.yml, edition: full}
539
538
  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."
540
539
  # The database deploy workflow: workflow_dispatch only (like app.ci-e2e); starci app sync renders it under the managed ci group.
541
540
  path: ".github/workflows/db-deploy.yml"
@@ -546,6 +545,7 @@ slots:
546
545
  liteManagedBy: ci-workflows-lite
547
546
  rules: [HFS_MANAGED_FILE_DRIFT]
548
547
  since: 2.1.0
548
+ 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"
549
549
  - id: app.ci-stack-deploy
550
550
  since: 3.3.0
551
551
  profiles: [app]
@@ -657,8 +657,6 @@ slots:
657
657
  - id: app.scripts.delivery
658
658
  since: 3.1.0
659
659
  profiles: [app]
660
- exemplifiedBy:
661
- - {path: tests/fixtures/hfs-delivery-lite/scripts/build-frontend.mjs, edition: lite}
662
660
  editionWhy: "Lite splits product delivery tooling from managed codegen; full owns the same files through app.scripts (ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4))."
663
661
  editions: [lite]
664
662
  # Product-owned environment, secret and build tooling, kept apart from lite's managed codegen script.
@@ -669,6 +667,7 @@ slots:
669
667
  tier: none
670
668
  tests: none
671
669
  rules: [HFS_PLAINTEXT_SECRET, HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK]
670
+ 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"
672
671
  - id: app.sides
673
672
  since: 3.1.0
674
673
  profiles: [app]
@@ -791,7 +790,6 @@ slots:
791
790
  profiles: [app]
792
791
  exemplifiedBy:
793
792
  - packages/hfs/templates/app/skeleton-lite/supabase/
794
- - {path: tests/fixtures/hfs-callback-full/supabase/, edition: full}
795
793
  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."
796
794
  # The one Supabase project directory, at the app root beside be/, fe/ and .starcistacks (never under a side).
797
795
  path: "supabase/"
@@ -802,11 +800,11 @@ slots:
802
800
  tier: none
803
801
  tests: none
804
802
  since: 2.1.0
803
+ 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"
805
804
  - id: app.supabase.config
806
805
  profiles: [app]
807
806
  exemplifiedBy:
808
807
  - packages/hfs/templates/app/skeleton-lite/supabase/config.toml
809
- - {path: tests/fixtures/hfs-callback-full/supabase/config.toml, edition: full}
810
808
  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."
811
809
  # The project configuration: auth keys are env(NAME) references, never literals (L08 judges its policy keys).
812
810
  path: "supabase/config.toml"
@@ -817,11 +815,11 @@ slots:
817
815
  tier: none
818
816
  tests: none
819
817
  since: 2.1.0
818
+ 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"
820
819
  - id: app.supabase.migrations
821
820
  profiles: [app]
822
821
  exemplifiedBy:
823
822
  - packages/hfs/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql
824
- - {path: tests/fixtures/hfs-callback-full/supabase/migrations/20261014100000_baseline.sql, edition: full}
825
823
  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."
826
824
  # The schema authority of an app on Supabase: forward-only SQL migrations named <timestamp14>_<kebab-name>.sql,
827
825
  # one file per change, immutable once on origin/main (L02-L08 judge them).
@@ -834,6 +832,7 @@ slots:
834
832
  tier: none
835
833
  tests: none
836
834
  since: 2.1.0
835
+ 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"
837
836
  - id: app.supabase.migrations.archive
838
837
  profiles: [app]
839
838
  # The migrations an existing app retired at its cutover (owner ruling 2026-10-13, migration baseline policy): the live schema is
@@ -858,8 +857,6 @@ slots:
858
857
  - id: app.supabase.types
859
858
  profiles: [app]
860
859
  templateNa: "Database row types are generated from the app's Supabase schema by contract:emit; a hand-authored scaffold template would invent the schema contract."
861
- exemplifiedBy:
862
- - {path: tests/fixtures/hfs-callback-full/supabase/types/database.types.ts, edition: full}
863
860
  editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite requires generated Supabase row types; full enables them with the provider."
864
861
  # The generated Database types (database.types.ts) contract:emit writes from the migrations; both sides read it
865
862
  # (sides.<side>.reads supabase/types/); drift against regeneration is L09.
@@ -871,6 +868,7 @@ slots:
871
868
  tier: none
872
869
  tests: none
873
870
  since: 2.1.0
871
+ 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"
874
872
  - id: app.supabase.templates
875
873
  profiles: [app]
876
874
  path: "supabase/templates/*.html"
@@ -1006,10 +1004,8 @@ slots:
1006
1004
  tier: none
1007
1005
  tests: none
1008
1006
  coverage: none
1009
- exemplifiedBy:
1010
- - {path: tests/fixtures/hfs-i18n-ledger/fe/i18n-dynamic-keys.yaml, editions: [lite, full]}
1011
- - {path: tests/fixtures/hfs-i18n-ledger/be/i18n-dynamic-keys.yaml, editions: [lite, full]}
1012
1007
  rules: [HFS_UNUSED_SYMBOL]
1008
+ 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"
1013
1009
  - id: repo.packages
1014
1010
  since: 3.1.0
1015
1011
  profiles: [be, fe]
@@ -1401,8 +1397,6 @@ slots:
1401
1397
  since: 1.0.0
1402
1398
  - id: be.feature.realtime
1403
1399
  profiles: [be]
1404
- exemplifiedBy:
1405
- - {path: tests/fixtures/hfs-lite-slots/be/src/features/realtime/order-status/, edition: lite}
1406
1400
  editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite runs socket.io in the api process, one instance without Redis; full may scale its gateway."
1407
1401
  path: "src/features/realtime/<channel>/"
1408
1402
  presence: opt-in # a push channel: reads and pushes only (a socket gateway or a GraphQL subscription), no domain write
@@ -1422,6 +1416,7 @@ slots:
1422
1416
  coverage: none
1423
1417
  rules: [BE_KIND_ISOLATION, BE_FEATURE_IMPORTS_FEATURE, BE_FEATURE_NOT_COMPOSED]
1424
1418
  since: 1.0.0
1419
+ 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"
1425
1420
  - id: be.feature.realtime.graphql
1426
1421
  profiles: [be]
1427
1422
  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."
@@ -1607,8 +1602,6 @@ slots:
1607
1602
  - id: be.platform.event-bus
1608
1603
  since: 3.1.0
1609
1604
  profiles: [be]
1610
- exemplifiedBy:
1611
- - {path: tests/fixtures/hfs-lite-slots/be/src/modules/platform/event-bus/, edition: lite}
1612
1605
  editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite implements the event ports with outbox and pgmq; full uses its Kafka adapter."
1613
1606
  path: "src/modules/platform/event-bus/"
1614
1607
  presence: opt-in
@@ -1627,6 +1620,7 @@ slots:
1627
1620
  coverage: required
1628
1621
  budget: {indexExports: 60}
1629
1622
  rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE, BE_INFRA_OWNER]
1623
+ 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"
1630
1624
  - id: be.platform.queue
1631
1625
  since: 3.1.0
1632
1626
  profiles: [be]
@@ -1750,8 +1744,6 @@ slots:
1750
1744
  - id: be.projections
1751
1745
  since: 3.1.0
1752
1746
  profiles: [be]
1753
- exemplifiedBy:
1754
- - {path: tests/fixtures/hfs-lite-slots/be/src/modules/projections/order-summary/, edition: lite}
1755
1747
  editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite projection tables live in Supabase migrations; full keeps the TypeORM projection entity beside its projection."
1756
1748
  path: "src/modules/projections/<name>/"
1757
1749
  presence: opt-in
@@ -1767,6 +1759,7 @@ slots:
1767
1759
  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)
1768
1760
  coverage: required
1769
1761
  rules: [BE_PROJECTION_WRITE_OWNER, BE_PROJECTION_SHAPE, BE_TIER_DIRECTION, BE_CONTEXT_COUPLING]
1762
+ 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"
1770
1763
  - id: be.integrations
1771
1764
  since: 3.1.0
1772
1765
  profiles: [be]
@@ -1796,7 +1789,6 @@ slots:
1796
1789
  profiles: [be]
1797
1790
  exemplifiedBy:
1798
1791
  - packages/hfs/templates/be/skeleton-lite/src/modules/integrations/supabase/
1799
- - {path: tests/fixtures/hfs-callback-full/be/src/modules/integrations/supabase/, edition: full}
1800
1792
  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."
1801
1793
  # The only be import of @supabase/* (L10): the Auth admin and Storage admin client, plus the JWKS-based verification of
1802
1794
  # the Supabase access token the guard chain reads (L16). The connection itself is ordinary Postgres (the shared
@@ -1814,6 +1806,7 @@ slots:
1814
1806
  rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT]
1815
1807
  coverage: required
1816
1808
  since: 2.1.0
1809
+ 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"
1817
1810
 
1818
1811
  # ----- BE: tests (owner test layout 2026-09-30). Unit specs are <name>.service.spec.ts beside their service and nothing else; everything else
1819
1812
  # lives under src/tests/, one folder per kind, and the folder and the file suffix always agree (a file whose suffix does
@@ -1821,8 +1814,6 @@ slots:
1821
1814
  - id: be.tests.world
1822
1815
  since: 3.1.0
1823
1816
  profiles: [be]
1824
- exemplifiedBy:
1825
- - {path: tests/fixtures/hfs-lite-slots/be/src/tests/world/, edition: lite}
1826
1817
  editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite integration proofs use the local Supabase stack and network fakes; full also boots apps and self-hosted services."
1827
1818
  path: "src/tests/world/"
1828
1819
  presence: optional
@@ -1848,6 +1839,7 @@ slots:
1848
1839
  overridden in the DI container. It is the only test location that may import typeorm's DataSource or testcontainers,
1849
1840
  call migrate/runMigrations/synchronize, or write process.env.
1850
1841
  rules: [BE_TEST_TOPOLOGY, BE_TEST_CONSTRAINTS_ON]
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"
1851
1843
 
1852
1844
  - id: be.tests.world.kit
1853
1845
  since: 3.1.0
@@ -2015,8 +2007,6 @@ slots:
2015
2007
  rules: [FE_NEXT_CONVENTIONS]
2016
2008
  - id: fe.route.callback
2017
2009
  profiles: [fe]
2018
- exemplifiedBy:
2019
- - {path: tests/fixtures/hfs-callback-full/fe/apps/web/src/app/auth/callback/route.ts, edition: full}
2020
2010
  # The auth callback of a Supabase session (the PKCE code exchange), kept only when the auth flow truly needs a server exchange
2021
2011
  # (prefer the client-side PKCE exchange in fe.modules.auth): the one non-health route handler of an app with a Supabase
2022
2012
  # connection; an app without one has no route handler in fe/ (L15).
@@ -2246,8 +2236,7 @@ slots:
2246
2236
  tests: none
2247
2237
  why: the canonical GraphQL client output of codegen is local and never committed; this specific slot overrides the tracked API parent
2248
2238
  rules: [FE_WIRE_GENERATED]
2249
- exemplifiedBy:
2250
- - {path: tests/fixtures/hfs-generated-wire/fe/apps/web/src/modules/api/graphql/__generated__/graphql.ts, editions: [lite, full]}
2239
+ 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"
2251
2240
  - id: fe.transport.client
2252
2241
  since: 3.1.0
2253
2242
  profiles: [fe]
@@ -309,3 +309,33 @@ removed:
309
309
  name: anonymousActions
310
310
  use: 'the front end holds no Server Action (R243 FE_SERVER_ACTION_FORBIDDEN), so no slot lists anonymous ones'
311
311
  since: 1.0.0-alpha.13
312
+ - id: verb-test-affected
313
+ kind: verb
314
+ name: starci test affected
315
+ use: 'the runtime has no specs of its own (owner ruling no-runtime-specs): `starci runtime verify` runs the check, `starci release clean-test --base <ref>` and `starci release scaffold-e2e`'
316
+ since: 1.0.0-alpha.19
317
+ match: '\bstarci test affected\b'
318
+ - id: verb-test-run
319
+ kind: verb
320
+ name: starci test run
321
+ use: 'the L1 to L4 spec ladder is gone with the runtime specs; `starci runtime verify` proves a change, `starci release cut` runs the release rows'
322
+ since: 1.0.0-alpha.19
323
+ match: '\bstarci test run\b'
324
+ - id: verb-gate-runtime-coverage
325
+ kind: verb
326
+ name: starci gate runtime-coverage
327
+ use: 'the runtime measures no coverage of its own; the example apps keep their Codecov flags'
328
+ since: 1.0.0-alpha.19
329
+ match: '\bgate runtime-coverage\b'
330
+ - id: verb-release-env-test
331
+ kind: verb
332
+ name: starci release env-test
333
+ use: 'the release rows are the package clean-test (`starci release clean-test`), the scaffold e2e (`starci release scaffold-e2e`) and the runtime check'
334
+ since: 1.0.0-alpha.19
335
+ match: '\brelease env-test\b'
336
+ - id: config-specs-harness
337
+ kind: config-key
338
+ name: specs.harness
339
+ use: 'the runtime has no specs of its own (owner ruling no-runtime-specs); specs.unit and specs.e2e switch the product workflow tests'
340
+ since: 1.0.0-alpha.19
341
+ match: '\bspecs\.harness\b'
@@ -199,9 +199,8 @@ allocation:
199
199
  # revision still differs from the live one that long past minIntervalMs + checkMs + handoverMs is a drift row (scripts/reconciler/drift.mjs).
200
200
  selfReload: {minIntervalMs: 300000, handoverMs: 30000, checkMs: 60000, driftGraceMs: 120000}
201
201
  # deploy (scripts/reconciler/runtime-deploy.mjs): a deploy waits up to waitMs, polling every pollMs, for the settles, Critic runs and prepared
202
- # decisions in flight to finish; after the engine restart it waits up to verifyMs for the new leader's fresh heartbeat on the new revision. The affected-spec child
203
- # may run its declared budget (modules/supervisor/affected-tests.yaml) plus affectedMarginMs before the deploy stops it.
204
- deploy: {waitMs: 1800000, pollMs: 5000, verifyMs: 120000, affectedMarginMs: 300000}
202
+ # decisions in flight to finish; after the engine restart it waits up to verifyMs for the new leader's fresh heartbeat on the new revision.
203
+ deploy: {waitMs: 1800000, pollMs: 5000, verifyMs: 120000}
205
204
  # dispatchRefusal (scripts/kernel/dispatch-refusal-memo.mjs): a ready job whose dispatch is refused for the same cause is tried again after baseMs, then twice as long each time (the one retry budget), up to capMs; a change of
206
205
  # the cause's fingerprint (runtime revision, a ruling, a resource the refusal names) tries it at once.
207
206
  dispatchRefusal: {baseMs: 60000, capMs: 900000}
@@ -210,9 +209,8 @@ allocation:
210
209
  footprint: {everyMs: 600000, lockStaleMs: 60000}
211
210
  # jobGuard (scripts/guards/hook-install.mjs): a guard file older than ttlMs is pruned.
212
211
  jobGuard: {ttlMs: 604800000}
213
- # landGate (scripts/supervisor/land.mjs): a land waits waitMs for its turn at the gate; its spec run may take
214
- # specsBaseMs plus perSpecMs for every spec it runs, specConcurrency spec files at a time.
215
- landGate: {waitMs: 1800000, specsBaseMs: 600000, perSpecMs: 60000, specConcurrency: 6, cliVerbs: 12}
212
+ # landGate (scripts/supervisor/land.mjs): a land waits waitMs for its turn at the gate.
213
+ landGate: {waitMs: 1800000}
216
214
  # workerClose (scripts/machine/worker-close.mjs; owner rule: a finished worker is closed COMPLETELY): after worker-release and the terminal
217
215
  # close the runtime waits verifyMs for every process of that terminal's shell tree to end (polled every pollMs); a survivor proven to belong
218
216
  # to the tree is stopped and given stopVerifyMs to end before the finding is raised.
@@ -20,7 +20,7 @@ import { withTempEnv } from '../../../engine/temp-root.mjs';
20
20
  * is a request for raw bytes, not an encoding: spawnSync encodes a string `input` with it and throws
21
21
  * ERR_UNKNOWN_ENCODING, so it reaches spawnSync as encoding:null (Buffer output either way).
22
22
  */
23
- export const gitSpawn = (file, args, options = {}) => {
23
+ const gitSpawn = (file, args, options = {}) => {
24
24
  assertMutationFence({ kind: 'git-effect', args });
25
25
  const spawn = { encoding: 'utf8', windowsHide: true, ...options };
26
26
  if (spawn.encoding === 'buffer') spawn.encoding = null;
@@ -76,5 +76,5 @@ export const gitExecutable = (options = null) => {
76
76
  return found;
77
77
  };
78
78
 
79
- /** The output ceiling of the whole-diff and `cat-file --batch` calls `test affected` batches (a release diff is tens of megabytes). */
80
- export const BATCH_MAX_BUFFER = 1024 * 1024 * 1024;
79
+ /** The output ceiling of the whole-diff and `cat-file --batch` calls batch (a release diff is tens of megabytes). */
80
+ const BATCH_MAX_BUFFER = 1024 * 1024 * 1024;
@@ -346,4 +346,4 @@ export function loadArchitectureConfig(repositoryRoot, { hfs } = {}) {
346
346
  };
347
347
  }
348
348
 
349
- export { canonical, enclosingRepository, exactKeys, isInside, slash };
349
+ export { canonical, exactKeys, isInside, slash };
@@ -162,7 +162,7 @@ const machineIds = () => [
162
162
  * backend composition and data machine and frontend repository machine. A spec about another rule judges its own
163
163
  * report without these (they have their own specs); every one of them is in ARCHITECTURE_RULE_IDS.
164
164
  */
165
- export const HFS_MACHINE_RULE_IDS = Object.freeze((() => {
165
+ const HFS_MACHINE_RULE_IDS = Object.freeze((() => {
166
166
  const ordinary = new Set([...COMMON_RULE_IDS, ...BACKEND_RULE_IDS, ...FRONTEND_RULE_IDS, ...OWNER_RULE_IDS, ...GRAMMAR_RULE_IDS, ...REGISTRATION_RULE_IDS,
167
167
  ...SWR_DATA_RULE_IDS, SOURCE_LAYOUT_RULE_ID, SOURCE_NAME_RULE_ID, PUBLIC_CONTRACT_RULE_ID, READONLY_BOUNDARY_RULE_ID]);
168
168
  return [...new Set(machineIds().filter((id) => !ordinary.has(id)))].sort(byCodeUnit);
@@ -209,7 +209,7 @@ export function checkBackendSupabase(input) {
209
209
  };
210
210
  }
211
211
 
212
- export function checkJwtPolicy(input) {
212
+ function checkJwtPolicy(input) {
213
213
  const kit = machineKit(input);
214
214
  const violations = [];
215
215
  let verifyCalls = 0;
@@ -67,7 +67,7 @@ function finishFrame(state, frame) {
67
67
  if (state.low.get(frame.node) === state.index.get(frame.node)) closeComponent(state, frame);
68
68
  }
69
69
 
70
- export function stronglyConnected(adjacency) {
70
+ function stronglyConnected(adjacency) {
71
71
  const state = { counter: 0, index: new Map(), low: new Map(), onStack: new Set(), stack: [], result: [], work: [] };
72
72
  for (const start of adjacency.keys()) {
73
73
  if (state.index.has(start)) continue;
@@ -56,8 +56,6 @@ export const unitRolesOf = (manifest) => manifest.ruleParams.be.unitRoles;
56
56
  /** The roles that carry logic (ruleParams.be.logicRoles): measured at 100 per file inside a coverage-required slot. */
57
57
  export const logicRolesOf = (manifest) => manifest.ruleParams.be.logicRoles;
58
58
 
59
- /** The roles a feature-tier file is judged thin by (ruleParams.be.thinRoles, R203 BE_FEATURE_THIN). */
60
- export const thinRolesOf = (manifest) => manifest.ruleParams.be.thinRoles;
61
59
 
62
60
  /** Shape problems of ruleParams.be.unitRoles: unique roles of the suffix vocabulary, each spec `<role>.spec`. */
63
61
  export function unitRolesProblems(be) {
@@ -86,7 +84,7 @@ export const MANIFEST_KINDS = [APP_KIND, RUNTIME_KIND];
86
84
  export const manifestKind = (m) => (isPlainObject(m) && m.kind !== undefined ? m.kind : APP_KIND);
87
85
  const strList = (v) => stringList(v);
88
86
 
89
- 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'];
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'];
90
88
  /** The fields a slot's `lite` overlay may hold: the same keys it would carry in the slot body, resolved under edition lite. */
91
89
  const LITE_OVERLAY_KEYS = ['path', 'requires', 'allows', 'forbids', 'minInstances', 'requiredInstances', 'why'];
92
90
  /** A runtime slot has no app kind, side composition, layer or managed template; it may name the generator of a generated copy. */
@@ -220,6 +218,8 @@ function slotCoverageEvidenceProblems(slot, at) {
220
218
  const bad = [];
221
219
  if (slot.templateNa !== undefined && !(typeof slot.templateNa === 'string' && slot.templateNa.trim()))
222
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
223
  if (slot.exemplifiedBy === undefined) return bad;
224
224
  const refs = slot.exemplifiedBy;
225
225
  const valid = Array.isArray(refs) && refs.length > 0 && refs.every(slotEvidenceRefValid)
@@ -1,6 +1,6 @@
1
1
  // param-names.mjs - the one shape of `ruleParams.be.paramNames` of knowledge/hfs/slots.yaml (R85 injected-param-name): a non-empty list of unique
2
2
  // entries `{type | typeSuffix, names, nameSuffix?}`. The loader of scripts/hfs/slots.mjs calls it; the schema (modules/schemas/hfs-slots.schema.yaml)
3
- // states the same shape and tests/hfs/hfs-slots.spec.mjs proves the two agree.
3
+ // states the same shape.
4
4
  import { isPlainObject } from '../../engine/plain-object.mjs';
5
5
 
6
6
  const UPPER = /^[A-Z][A-Za-z0-9]*$/u;
@@ -16,7 +16,7 @@ const RULE_KINDS = Object.freeze(['codemod', 'lint', 'check', 'design']);
16
16
  const FINDING_CODE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$/;
17
17
  const ENFORCER_ID = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
18
18
  const FILE_ENFORCERS = new Set(['machine', 'hfs', 'work-validate', 'sonar', 'runtime']);
19
- const RULE_KEYS = new Set(['id', 'code', 'law', 'scope', 'kinds', 'gates', 'failureCodes', 'editions', 'codeEditions', 'enforcers', 'cases', 'docs', 'exemplifiedBy', 'proves', 'witness', 'editionWhy', 'amendedBy', 'templateNa']);
19
+ 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
20
  const CASE_KEYS = new Set(['id', 'date', 'source', 'ruling', 'quote', 'supersedes', 'status', 'staleWhen', 'amendedBy']);
21
21
  const CASE_DATE = /^\d{4}-\d{2}-\d{2}$/;
22
22
  const ENFORCER_KEYS = new Set(['kind', 'id', 'status', 'at', 'editions', 'editionWhy']);
@@ -279,6 +279,8 @@ function checkRule(rule, index, catalog, codeOwner, bad) {
279
279
  if (rule.amendedBy !== undefined) caseReferenceProblems(rule.amendedBy, `${label}.amendedBy`, bad);
280
280
  optionalText(rule, 'editionWhy', label, bad);
281
281
  optionalText(rule, 'templateNa', label, bad);
282
+ optionalText(rule, 'specNa', label, bad);
283
+ optionalText(rule, 'exampleNa', label, bad);
282
284
  for (const key of ['docs', 'exemplifiedBy', 'proves']) evidenceProblems(rule, key, label, bad);
283
285
  witnessProblems(rule, label, bad);
284
286
  enumListProblems(rule, label, 'kinds', RULE_KINDS, bad);
@@ -5,7 +5,7 @@
5
5
  // is this path tracked isTracked(P) / trackingOf(P); the rule catalog (knowledge/hfs/rules.yaml, modules/schemas/hfs-rules.schema.yaml) loads through loadRuleCatalog / rules().
6
6
  // Every check and lint rule of HFS reads knowledge/hfs/slots.yaml through this module; none keeps its own path
7
7
  // list. The manifest shape is modules/schemas/hfs-slots.schema.yaml and hfs.json is modules/schemas/hfs-repo.schema.yaml;
8
- // the installed runtime carries no npm dependency, so this file re-states those shapes instead of loading ajv (tests/hfs/hfs-slots.spec.mjs proves the two agree).
8
+ // the installed runtime carries no npm dependency, so this file re-states those shapes instead of loading ajv.
9
9
  //
10
10
  // A product is ONE app repository: hfs.json at the app root has kind `app` and declares its two sides, `be` and `fe`, each in
11
11
  // the folder of that name. The resolver of the app answers for the whole tree: a root path with the slots of profile `app`,
@@ -41,7 +41,7 @@ export { HfsSlotsError } from './slot-errors.mjs';
41
41
  export { loadRuleCatalog };
42
42
  export { rules } from './rule-catalog.mjs';
43
43
  export { APP_SCOPE };
44
- export const HFS_MANIFEST_FILE = 'knowledge/hfs/slots.yaml';
44
+ const HFS_MANIFEST_FILE = 'knowledge/hfs/slots.yaml';
45
45
  /** The manifest of kind runtime: the standard tree of the StarCi runtime repository (judged by scripts/hfs/runtime-check.mjs). */
46
46
  export const RUNTIME_MANIFEST_FILE = 'knowledge/hfs/runtime-slots.yaml';
47
47
  export const HFS_DECLARATION_FILE = 'hfs.json';
@@ -9,14 +9,6 @@ import { byCodeUnit } from '../lib/list.mjs';
9
9
  // builds afresh.
10
10
  const runs = new AsyncLocalStorage();
11
11
 
12
- /** A program run: run(fn) shares what is built inside fn; release() drops it. */
13
- export function typeScriptProgramRun() {
14
- const values = new Map();
15
- return {
16
- run: fn => runs.run(values, fn),
17
- release: () => values.clear(),
18
- };
19
- }
20
12
 
21
13
  function keyOf(value) {
22
14
  if (value === undefined) return 'u';
@@ -11,8 +11,8 @@ export const isSpecRun = (env = process.env) => Boolean(env.NODE_TEST_CONTEXT);
11
11
 
12
12
  /**
13
13
  * The environment for a child that is itself a test runner: `env` without the mark of an enclosing spec run. A `node --test` that inherits NODE_TEST_CONTEXT believes it is a
14
- * subtest of that run: it reports to its parent and exits 0 whatever its tests did, so a verb that runs specs as a child (starci test affected, the land gate, the release cut, the
15
- * suite) would pass red specs from inside a test process. The runner marks the spec files it starts itself, so isSpecRun stays true inside them.
14
+ * subtest of that run: it reports to its parent and exits 0 whatever its tests did, so a verb that runs specs as a child (the package clean-test, the scaffold e2e, the release cut)
15
+ * would pass red specs from inside a test process. The runner marks the spec files it starts itself, so isSpecRun stays true inside them.
16
16
  */
17
17
  export function withoutTestRunner(env = process.env) {
18
18
  const { NODE_TEST_CONTEXT: _enclosing, ...rest } = env;
@@ -12,7 +12,7 @@ export const unquoteDiffPath = (value) =>
12
12
 
13
13
  // git's repository-local variables (git rev-parse --local-env-vars). A hook or alias run in a linked worktree exports
14
14
  // GIT_DIR=<main>/.git/worktrees/<wt>; a spec inheriting it pointed every fixture git at the LIVE .claude repo and re-inited
15
- // it core.bare=true (2026-09-29, a6f60352c). tests/setup/isolated-registry.mjs drops the same list.
15
+ // it core.bare=true (2026-09-29, a6f60352c). e2e/setup/isolated-registry.mjs drops the same list.
16
16
  export const GIT_LOCAL_ENV_VARS = ['GIT_DIR', 'GIT_COMMON_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_IMPLICIT_WORK_TREE', 'GIT_PREFIX', 'GIT_CONFIG', 'GIT_CONFIG_PARAMETERS', 'GIT_CONFIG_COUNT', 'GIT_GRAFT_FILE', 'GIT_NO_REPLACE_OBJECTS', 'GIT_REPLACE_REF_BASE', 'GIT_SHALLOW_FILE', 'GIT_INTERNAL_SUPER_PREFIX'];
17
17
  /** `parent` (the caller's env) without git's repository-local variables (GIT_CONFIG_KEY_n/VALUE_n go with GIT_CONFIG_COUNT). Pure. */
18
18
  export function withoutGitLocalEnv(parent) {
@@ -49,8 +49,6 @@ export function loadCatalog(root = skillRoot, { scope = 'runtime' } = {}) {
49
49
  return cache.get(key);
50
50
  }
51
51
 
52
- /** Forget the cached catalogs (a spec that rewrites a catalog file). */
53
- export const resetCatalogCache = () => cache.clear();
54
52
 
55
53
  /** The names each `pattern` match captures (group 1) in `text`, in match order (`sort` sorts them). */
56
54
  export const captureNames = (text, pattern, { sort = false } = {}) => {
@@ -76,7 +76,7 @@ const I18N_CATALOG_DIR = 'modules/i18n/messages/';
76
76
  const OP_MANIFEST_VIETNAMESE_FIELDS = Object.freeze({ fields: Object.freeze(['vi']) });
77
77
  const OP_MANIFEST_DIR = 'modules/ops/ops/';
78
78
  /** The declared Vietnamese fields of `rel`: its own entry, or the op-manifest text field for a manifest of modules/ops/ops. */
79
- export const declaredVietnameseFieldsOf = (rel) => DECLARED_VIETNAMESE_FIELDS[rel]
79
+ const declaredVietnameseFieldsOf = (rel) => DECLARED_VIETNAMESE_FIELDS[rel]
80
80
  ?? (rel.startsWith(I18N_CATALOG_DIR) && !rel.slice(I18N_CATALOG_DIR.length).includes('/') && /\.ya?ml$/.test(rel) ? I18N_CATALOG_VIETNAMESE_FIELDS : null)
81
81
  ?? (rel.startsWith(OP_MANIFEST_DIR) && !rel.slice(OP_MANIFEST_DIR.length).includes('/') && /\.ya?ml$/.test(rel) ? OP_MANIFEST_VIETNAMESE_FIELDS : null);
82
82
 
@@ -43,11 +43,11 @@ const SECRET_NAME = [
43
43
  String.raw`[A-Za-z_$][A-Za-z0-9_$.-]*\s*[:=]\s*`,
44
44
  ].join('');
45
45
 
46
- // `password = "hunter2hunter2"` — the quoted literal. `env(NAME)` is a reference, never a secret.
46
+ // `password = "example-password-1"` — the quoted literal. `env(NAME)` is a reference, never a secret.
47
47
  const ASSIGNED_QUOTED = new RegExp(
48
48
  SECRET_NAME + String.raw`['"\`](?!env\([A-Z_][A-Z0-9_]*\)['"\`])([^'"\`\n\s$<{]{12,})['"\`]`,
49
49
  );
50
- // `DB_PASSWORD=hunter2hunter2` — the compose list-item and dotenv form; the dotenv value has no `:` so
50
+ // `DB_PASSWORD=example-password-1` — the compose list-item and dotenv form; the dotenv value has no `:` so
51
51
  // `password: secret:db/password` and `token: ref:vault/api` stay references, and the end boundary keeps
52
52
  // calls and member/index references such as `secret = credentialsSource.current` out of the literals.
53
53
  const ASSIGNED_UNQUOTED = new RegExp(