@starci/eslint-canon-fe 8.2.18 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +19 -1
- package/callback-shape.mjs +344 -0
- package/client-boundary.mjs +1 -1
- package/docs/hooks-folder.md +1 -1
- package/docs/size-and-state-budget.md +6 -6
- package/drawing.mjs +65 -28
- package/env-owner.mjs +120 -9
- package/function-budget.mjs +49 -0
- package/hooks-folder.mjs +149 -18
- package/icon.mjs +67 -0
- package/index.mjs +47 -0
- package/lib/own-hook-origin.mjs +53 -0
- package/lib/program-for.mjs +22 -0
- package/lib/pure-owner-helper.mjs +148 -0
- package/lib/redux-hook-origin.mjs +44 -0
- package/next-conventions.mjs +38 -35
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
- package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
- package/runtime/knowledge/hfs/slots.yaml +430 -92
- package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/models/runtimes.yaml +1 -1
- package/runtime/packages/hfs/templates/app/package-scripts/package.json +1 -0
- package/runtime/scripts/api/git/cat-file.mjs +5 -0
- package/runtime/scripts/api/git/diff.mjs +5 -0
- package/runtime/scripts/api/git/lib.mjs +3 -0
- package/runtime/scripts/api/git/merge-base.mjs +9 -0
- package/runtime/scripts/api/git/recorded-lines.mjs +54 -8
- package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
- package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
- package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
- package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
- package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
- package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
- package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
- package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
- package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
- package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
- package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
- package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
- package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
- package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
- package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
- package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
- package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
- package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
- package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
- package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
- package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
- package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
- package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
- package/runtime/scripts/hfs/edition-slots.mjs +2 -2
- package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
- package/runtime/scripts/hfs/path-findings.mjs +32 -2
- package/runtime/scripts/hfs/project-rule.mjs +3 -1
- package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
- package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
- package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
- package/runtime/scripts/hfs/slot-imports.mjs +18 -3
- package/runtime/scripts/hfs/slot-required.mjs +13 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
- package/runtime/scripts/hfs/slots.mjs +1 -0
- package/runtime/scripts/lib/env.mjs +20 -0
- package/runtime/scripts/lib/event-contract.mjs +107 -0
- package/runtime/scripts/lib/json-clone.mjs +61 -0
- package/runtime/scripts/lib/list.mjs +4 -0
- package/runtime/scripts/lib/secret-patterns.mjs +60 -5
- package/runtime/scripts/lib/stack-services.mjs +1 -1
- package/runtime/scripts/lib/ts-analysis.mjs +23 -0
- package/runtime/scripts/lib/ts-ast.mjs +27 -2
- package/served-locale.mjs +80 -9
- package/size-and-state-budget.mjs +11 -8
- package/size-growth.mjs +10 -6
- package/swr-family.mjs +197 -0
- package/translation.mjs +135 -7
- package/transport.mjs +126 -59
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# catalog READ this file through scripts/hfs/slots.mjs; none of them hardcodes a path.
|
|
8
8
|
# Shape: modules/schemas/hfs-slots.schema.yaml. The repository side is hfs.json: modules/schemas/hfs-repo.schema.yaml.
|
|
9
9
|
schema: starci/hfs-slots@3
|
|
10
|
-
version: 3.
|
|
10
|
+
version: 3.3.0
|
|
11
11
|
|
|
12
12
|
versioning:
|
|
13
13
|
# MAJOR.MINOR.PATCH of this manifest. A repository pins only the MAJOR in hfs.json ("hfs": 3).
|
|
@@ -25,12 +25,19 @@ versioning:
|
|
|
25
25
|
# overlays and `provider`, and `sides.<side>.reads` of an app-root tree) and the lite slots; an app without `edition` in its
|
|
26
26
|
# hfs.json is edition full and every answer for it is unchanged.
|
|
27
27
|
# 2.2.0 adds the declaration-only fe.modules.types.ambient owner.
|
|
28
|
-
# 3.0.0 is a major (owner
|
|
29
|
-
#
|
|
28
|
+
# 3.0.0 is a major (ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4)): lite is the full code structure deployed with Supabase backing services
|
|
29
|
+
# and without e2e, and the front end is one standard for both editions (knowledge/hfs/fe-stack.yaml). The
|
|
30
30
|
# front-end database slots and the Supabase realtime feature slot are removed (Supabase on the front end is auth only, realtime
|
|
31
31
|
# is the socket.io gateway in both editions), fe.route holds pages, layouts and the one health/live route handler, and the slots
|
|
32
32
|
# marked `since: 3.0.0` are added: the Apollo, SWR, RHF, zustand and socket.io hooks, redux, modules/auth, modules/toast, the
|
|
33
33
|
# providers, be.assets and the migration archive.
|
|
34
|
+
# 3.1.0 is a minor (owner ruling 2026-10-14, R245 and R246): every be.app.* slot allows `<app>.env.ts` and the composition groups
|
|
35
|
+
# `compose/<area>.ts` and budgets its app files (`app.module.ts` 250, `<app>.options.ts` 200, `<app>.env.ts` 150,
|
|
36
|
+
# `compose/<area>.ts` 120, `main.ts` 80); `<app>.env.ts` exports interface <App>Env and read<App>Env(env: EnvSource): <App>Env
|
|
37
|
+
# for fail-fast env parsing only, and `<app>.options.ts` holds env-derived options and port bindings only, with no parsing
|
|
38
|
+
# or environment reads (BE_APP_OPTIONS_COMPOSITION). Secrets use EnvSource.secret(): <NAME>_FILE first, plain <NAME> only
|
|
39
|
+
# when the file key is absent; .starcistacks owns custody. Capability <capability>.config.ts roles stay unchanged.
|
|
40
|
+
# 3.3.0 is a minor (alpha.16): it ADDS the optional app.ci-stack-deploy slot, the managed workflow of the starci app stack verbs.
|
|
34
41
|
|
|
35
42
|
# Field meaning, per slot.
|
|
36
43
|
# id stable name, never changes meaning inside a major.
|
|
@@ -77,16 +84,16 @@ versioning:
|
|
|
77
84
|
# liteManagedBy is managed only in lite; its effective lite.path still lists literal files.
|
|
78
85
|
# the managed file list lives here and nowhere in code. A difference is HFS_MANAGED_FILE_DRIFT (a
|
|
79
86
|
# back end's tsconfig.json: HFS_TS_STRICT when a flag or an option was added).
|
|
80
|
-
# rules rule
|
|
87
|
+
# rules rule codes from knowledge/hfs/rules.yaml.
|
|
81
88
|
# editions the editions (below) the slot exists in; default all of them. A slot outside the declared edition owns
|
|
82
89
|
# no path - under lite, what full keeps absent is removed, and what lite forbids is a finding.
|
|
83
90
|
# litePresence the presence under edition lite (default: presence): a bare value, or {be, fe} for a both-sides slot.
|
|
84
91
|
# A slot forbidden in lite is external there and must name its goesTo. Only infrastructure is forbidden in lite:
|
|
85
|
-
# the
|
|
86
|
-
#
|
|
92
|
+
# database/broker services, the e2e layer and its workflow, and the browser journey. Lite may have contract specs.
|
|
93
|
+
# editionWhy why the slot's presence, shape or adapter differs between lite and full, with the owning ruling citation.
|
|
87
94
|
# lite the fields the slot carries under lite only (path, requires, allows, forbids, minInstances,
|
|
88
95
|
# requiredInstances, why), each shaped as the same field in the slot body; every other field is the edition's.
|
|
89
|
-
# `why` under lite names the
|
|
96
|
+
# `why` under lite names the adapter behind the slot's ports (pgmq, Postgres locks, in-process socket.io).
|
|
90
97
|
# `tests` is never overlaid: a lite slot owes exactly the unit specs its full slot owes.
|
|
91
98
|
# provider the hfs.json connections[].provider that enables this opt-in slot (supabase); never an app kind.
|
|
92
99
|
# since the manifest version that introduced the slot.
|
|
@@ -94,17 +101,18 @@ presenceValues: [required, optional, opt-in, forbidden]
|
|
|
94
101
|
trackedValues: [tracked, ignored, external]
|
|
95
102
|
testValues: [unit-beside, e2e, none]
|
|
96
103
|
|
|
97
|
-
# The editions a repository declares in hfs.json `edition` (absent means full).
|
|
98
|
-
# the full architecture
|
|
104
|
+
# The editions a repository declares in hfs.json `edition` (absent means full). ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite IS
|
|
105
|
+
# the full architecture with Supabase backing services - the same monorepo, slots, CQRS shape and patterns (queue,
|
|
99
106
|
# event bus, saga, reactors, projection, realtime, webhooks, fenced job), declared in hfs.json the same way, and the same unit
|
|
100
107
|
# specs (unitRoles, logicRoles, 100 per file, @starci/jest-preset doubles, builders). What differs is ADAPTERS and DELIVERY, all
|
|
101
|
-
# on Supabase: schema authority in supabase/migrations,
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
108
|
+
# on Supabase except realtime: schema authority in supabase/migrations, queue and event-bus ports on Supabase Queues (pgmq),
|
|
109
|
+
# cache and locks on Postgres, schedules on pg_cron (+ pg_net), storage and auth on Supabase. Realtime uses the socket.io gateway
|
|
110
|
+
# in the api process, one instance and no Redis adapter. Integration and provider sandbox contract specs are optional;
|
|
111
|
+
# declared patterns owe their integration proofs (R284). Lite has no e2e world. Production uses declared docker-swarm under
|
|
112
|
+
# .starcistacks/production/infra/swarm/ (owner ruling 2026-10-14); no self-hosted database or broker. The front end is full's (R97).
|
|
105
113
|
# Lite is this same manifest filtered: a slot whose `editions` lacks lite does not exist for a lite app, `litePresence` replaces
|
|
106
114
|
# presence and a `lite` map replaces the fields it names. Upgrade lite -> full adds only (swap the adapters, add the e2e and
|
|
107
|
-
#
|
|
115
|
+
# self-hosted backing services); downgrade is not supported. Lite contracts are allowed (owner decision 2026-10-14).
|
|
108
116
|
editions: [full, lite]
|
|
109
117
|
|
|
110
118
|
# The two sides of an app: the folder of each is its profile name (be/, fe/). `reads` lists the only paths of the OTHER side a side
|
|
@@ -139,6 +147,7 @@ naming:
|
|
|
139
147
|
# ---------------------------------------------------------------------------------------------------------
|
|
140
148
|
# Tiers and the dependency direction matrix (owner granularity; type-only imports count; no cycles anywhere)
|
|
141
149
|
# mayImport lists the tiers a file of that tier may import. Importing inside the same owner is always allowed.
|
|
150
|
+
# mayImportKinds narrows a permitted tier edge to its named from/to folder kinds; other kind pairs remain forbidden.
|
|
142
151
|
# ---------------------------------------------------------------------------------------------------------
|
|
143
152
|
tiers:
|
|
144
153
|
be:
|
|
@@ -155,12 +164,12 @@ tiers:
|
|
|
155
164
|
package: {mayImport: [package]}
|
|
156
165
|
fe:
|
|
157
166
|
route: {mayImport: [feature, foundation, modules, transport, package]} # app/** adapters
|
|
158
|
-
feature: {mayImport: [components, hooks, foundation, modules,
|
|
167
|
+
feature: {mayImport: [feature, components, hooks, foundation, modules, package], mayImportKinds: {feature: {from: [pages, layouts], to: [overlays]}}} # pages and layouts compose overlays and blocks; data goes through hooks/swr
|
|
159
168
|
# A component never reaches data: config, routes, i18n and types are foundation; every other module and the api
|
|
160
169
|
# transport are reached through a hook (owner decision 2026-09-29).
|
|
161
170
|
components: {mayImport: [components, hooks, foundation, package], lowerLayerOnly: true} # blocks, composites, branches, leaves
|
|
162
171
|
# Across domains a hook is imported only through the other domain's public index (hooks/<domain>/index.ts), never deep.
|
|
163
|
-
hooks: {mayImport: [hooks, foundation, modules, transport, package]}
|
|
172
|
+
hooks: {mayImport: [hooks, foundation, modules, transport, package], mayImportSlots: {transport: {from: [fe.hooks.swr]}}} # only hooks/swr imports an operation
|
|
164
173
|
foundation: {mayImport: [foundation, package], acyclic: true} # modules/{config,i18n,routes,types}
|
|
165
174
|
modules: {mayImport: [foundation, modules, transport, package], acyclic: true}
|
|
166
175
|
transport: {mayImport: [foundation, modules, package]} # modules/api: the one fetch
|
|
@@ -171,10 +180,16 @@ crossApp: apps never import each other; shared code is a packages/<pkg> slot.
|
|
|
171
180
|
# Parameters the lint factories and checks read per profile (through ruleParams(profile) in scripts/hfs/slots.mjs, which
|
|
172
181
|
# merges common under the profile: a side restates a common key only to override it).
|
|
173
182
|
ruleParams:
|
|
183
|
+
db:
|
|
184
|
+
# R259: btree_gist supplies UUID equality for the booking interval exclusion constraint.
|
|
185
|
+
# R259: unaccent is a first-party Supabase extension used by Vietnamese-content search functions.
|
|
186
|
+
extensions: [pgmq, pg_cron, pgcrypto, pg_trgm, vector, uuid-ossp, pg_net, supabase_vault, unaccent, btree_gist]
|
|
174
187
|
common:
|
|
175
188
|
# HFS_SIZE_GROWTH: a file above soft may not grow against its parent commit; a new file stays within soft. Slot
|
|
176
189
|
# budgets (component.tsx 300, hooks 200, modules 400) are stricter than this cap where they apply.
|
|
177
190
|
fileLines: {soft: 500, hardGrowth: true}
|
|
191
|
+
functionLines: 80
|
|
192
|
+
featureFiles: 250
|
|
178
193
|
# R21 HFS_DUPLICATE_CODE: the one threshold of duplicate code of both sides (the architecture machine reads it here;
|
|
179
194
|
# no other file states it): a token-normalised block of at least `lines` source lines and `tokens` tokens that appears twice.
|
|
180
195
|
duplicateBlock: {lines: 8, tokens: 60}
|
|
@@ -321,7 +336,7 @@ ruleParams:
|
|
|
321
336
|
# Edition lite (ruleParams(manifest, 'be', 'lite') merges these keys over the base; a map merges one level deep): the schema
|
|
322
337
|
# authority is the Supabase migration tree (supabase/migrations/*.sql, forward-only), so a lite back end holds no TypeORM
|
|
323
338
|
# migration or entity under be/ (R34/R35 read schemaAuthority; be.persistence's lite overlay allows neither). The unit roles,
|
|
324
|
-
# logic roles and spec doubles are full's (owner ruling 2026-10-12). infraOwners: the self-hosted libraries are owned nowhere -
|
|
339
|
+
# logic roles and spec doubles are full's (owner ruling 2026-10-12 (owner:lite-production-swarm), with the Work boundary amended by ruling:R212/case-4). infraOwners: the self-hosted libraries are owned nowhere -
|
|
325
340
|
# platform/queue and platform/event-bus run Supabase Queues (pgmq) through the shared EntityManager, cache and locks are
|
|
326
341
|
# Postgres (advisory locks or an unlogged table), and realtime is the same socket.io gateway as full, run inside the api
|
|
327
342
|
# process with no Redis adapter (owner ruling 2026-10-13).
|
|
@@ -340,6 +355,7 @@ slots:
|
|
|
340
355
|
|
|
341
356
|
# ----- app root (profile app: the one repository of a product) --------------------------------------------
|
|
342
357
|
- id: app.declaration
|
|
358
|
+
since: 3.1.0
|
|
343
359
|
profiles: [app]
|
|
344
360
|
path: hfs.json
|
|
345
361
|
presence: required
|
|
@@ -348,6 +364,7 @@ slots:
|
|
|
348
364
|
tests: none
|
|
349
365
|
rules: [HFS_ARCH_CONFIG_UNREAD]
|
|
350
366
|
- id: app.readme
|
|
367
|
+
since: 3.1.0
|
|
351
368
|
profiles: [app]
|
|
352
369
|
path: README.md
|
|
353
370
|
presence: required
|
|
@@ -356,7 +373,9 @@ slots:
|
|
|
356
373
|
tests: none
|
|
357
374
|
rules: [HFS_README_*]
|
|
358
375
|
- id: app.package-manifest
|
|
376
|
+
since: 3.1.0
|
|
359
377
|
profiles: [app]
|
|
378
|
+
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite manages Supabase and wiring scripts; full also manages e2e and stack test scripts. Both may run optional contracts."
|
|
360
379
|
# The one package.json of the app, at the app root: be's dependencies and every tool, and every script; npm workspaces are
|
|
361
380
|
# exactly fe/apps/* and fe/packages/* (HFS_MONO_WORKSPACES), each with its own package.json. be has no package.json of its own.
|
|
362
381
|
path: package.json
|
|
@@ -368,6 +387,7 @@ slots:
|
|
|
368
387
|
liteManagedBy: package-scripts-lite
|
|
369
388
|
rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW, HFS_CANON_PIN_DRIFT, HFS_MANAGED_FILE_DRIFT, HFS_MONO_WORKSPACES, HFS_MONO_WORKSPACE_DEP]
|
|
370
389
|
- id: app.task-graph
|
|
390
|
+
since: 3.1.0
|
|
371
391
|
profiles: [app]
|
|
372
392
|
# The turbo task graph of the app (build, dev, lint, typecheck over the fe workspaces; build depends on ^build and caches
|
|
373
393
|
# .next and dist): rendered by starci app sync from templates/app/task-graph/turbo.json, judged for drift by starci app check. turbo needs
|
|
@@ -380,6 +400,7 @@ slots:
|
|
|
380
400
|
managedBy: task-graph
|
|
381
401
|
rules: [HFS_MANAGED_FILE_DRIFT, HFS_MONO_WORKSPACES]
|
|
382
402
|
- id: app.lockfile
|
|
403
|
+
since: 3.1.0
|
|
383
404
|
profiles: [app]
|
|
384
405
|
path: package-lock.json
|
|
385
406
|
presence: required
|
|
@@ -388,22 +409,42 @@ slots:
|
|
|
388
409
|
tests: none
|
|
389
410
|
rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW]
|
|
390
411
|
- id: app.git-meta
|
|
412
|
+
since: 3.1.0
|
|
391
413
|
profiles: [app]
|
|
392
|
-
path:
|
|
414
|
+
path: .gitignore
|
|
393
415
|
presence: required
|
|
394
416
|
tracked: tracked
|
|
395
417
|
tier: none
|
|
396
418
|
tests: none
|
|
397
419
|
rules: [HFS_GITIGNORE_BLOCK_DRIFT] # the marked block of .gitignore is rendered by starci app sync (templates/app/gitignore)
|
|
420
|
+
- id: app.gitattributes
|
|
421
|
+
since: 3.2.0
|
|
422
|
+
profiles: [app]
|
|
423
|
+
path: .gitattributes
|
|
424
|
+
presence: required
|
|
425
|
+
tracked: tracked
|
|
426
|
+
tier: none
|
|
427
|
+
tests: none
|
|
428
|
+
managedBy: skeleton
|
|
429
|
+
liteManagedBy: skeleton-lite
|
|
430
|
+
editionWhy: "Lite renders the same managed file from skeleton-lite because the editions share one code structure while their adapters differ (ruling:R212/case-2, with Work amended by ruling:R212/case-4 and hosting by owner:lite-production-swarm)."
|
|
431
|
+
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
398
432
|
- id: app.tool-config
|
|
433
|
+
since: 3.1.0
|
|
399
434
|
profiles: [app]
|
|
400
435
|
path: "{.editorconfig,.nvmrc}"
|
|
401
|
-
presence: required
|
|
436
|
+
presence: required
|
|
402
437
|
tracked: tracked
|
|
403
438
|
tier: none
|
|
404
439
|
tests: none
|
|
440
|
+
managedBy: skeleton
|
|
441
|
+
liteManagedBy: skeleton-lite
|
|
442
|
+
editionWhy: "Lite renders the same managed files from skeleton-lite because the editions share one code structure while their adapters differ (ruling:R212/case-2, with Work amended by ruling:R212/case-4 and hosting by owner:lite-production-swarm)."
|
|
443
|
+
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
405
444
|
- id: app.format-config
|
|
445
|
+
since: 3.1.0
|
|
406
446
|
profiles: [app]
|
|
447
|
+
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite additionally ignores Supabase-generated row types when formatting; full has its TypeORM schema authority."
|
|
407
448
|
# The one formatter configuration of the app (prettier walks up from every file of both sides to it): .prettierrc names
|
|
408
449
|
# @starci/prettier-config, .prettierignore is the template.
|
|
409
450
|
path: "{.prettierrc,.prettierignore}"
|
|
@@ -415,6 +456,7 @@ slots:
|
|
|
415
456
|
liteManagedBy: tool-config-lite # the same files plus the generated supabase/types, which the Supabase CLI writes unformatted
|
|
416
457
|
rules: [HFS_MANAGED_FILE_DRIFT, HFS_TOOL_CONFIG_LOCAL]
|
|
417
458
|
- id: app.tool-config-optional
|
|
459
|
+
since: 3.1.0
|
|
418
460
|
profiles: [app]
|
|
419
461
|
path: "{.npmrc,.npmignore}" # .npmignore: npm reads the nearest ignore file instead of the root files negations, so an app that ships inside another package keeps its sealed credentials out of the tarball here
|
|
420
462
|
presence: optional
|
|
@@ -422,6 +464,7 @@ slots:
|
|
|
422
464
|
tier: none
|
|
423
465
|
tests: none
|
|
424
466
|
- id: app.tool-config-local
|
|
467
|
+
since: 3.1.0
|
|
425
468
|
profiles: [app]
|
|
426
469
|
path: "{tsconfig.json,.eslintrc,.eslintrc.*,.eslintignore,eslint.config.*,.stylelintrc,.stylelintrc.*,.stylelintignore,stylelint.config.*,.prettierrc.*,prettier.config.*,jest.config.*,lint-staged.config.*,.lintstagedrc*}"
|
|
427
470
|
presence: forbidden
|
|
@@ -431,6 +474,7 @@ slots:
|
|
|
431
474
|
goesTo: "nowhere at the app root: each side holds exactly its managed tool configuration (be.tool-config, fe.tool-config) and the root only the formatter (app.format-config)"
|
|
432
475
|
rules: [HFS_TOOL_CONFIG_LOCAL]
|
|
433
476
|
- id: app.quality-config
|
|
477
|
+
since: 3.1.0
|
|
434
478
|
profiles: [app]
|
|
435
479
|
path: "{sonar-project.properties,codecov.yml}"
|
|
436
480
|
presence: required
|
|
@@ -440,6 +484,7 @@ slots:
|
|
|
440
484
|
managedBy: quality-config # lite renders the same files: the same coverage scope, Sonar import and Codecov flags as full
|
|
441
485
|
rules: [HFS_SONAR_CONFIG, HFS_MANAGED_FILE_DRIFT]
|
|
442
486
|
- id: app.hooks
|
|
487
|
+
since: 3.1.0
|
|
443
488
|
profiles: [app]
|
|
444
489
|
path: ".husky/{pre-commit,pre-push}"
|
|
445
490
|
presence: required
|
|
@@ -449,7 +494,9 @@ slots:
|
|
|
449
494
|
managedBy: hooks
|
|
450
495
|
rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
|
|
451
496
|
- id: app.ci
|
|
497
|
+
since: 3.1.0
|
|
452
498
|
profiles: [app]
|
|
499
|
+
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite CI proves boot through R272 wiring and checks Supabase artifacts; full also provides its e2e lane."
|
|
453
500
|
path: ".github/workflows/ci.yml"
|
|
454
501
|
presence: required
|
|
455
502
|
tracked: tracked
|
|
@@ -459,18 +506,22 @@ slots:
|
|
|
459
506
|
liteManagedBy: ci-workflows-lite
|
|
460
507
|
rules: [HFS_CI_E2E_AUTOMATIC, HFS_CI_MISSING_CANON, HFS_MANAGED_FILE_DRIFT]
|
|
461
508
|
- id: app.ci-e2e
|
|
509
|
+
since: 3.1.0
|
|
462
510
|
profiles: [app]
|
|
511
|
+
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite has no e2e world; R272 supplies its wiring proof, while optional contracts run separately."
|
|
463
512
|
path: ".github/workflows/e2e.yml"
|
|
464
513
|
presence: optional # on: workflow_dispatch only
|
|
465
|
-
litePresence: forbidden #
|
|
514
|
+
litePresence: forbidden # ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite has no e2e world; unit specs and R272 wiring run in ci.yml
|
|
466
515
|
tracked: tracked
|
|
467
516
|
tier: none
|
|
468
517
|
tests: none
|
|
469
|
-
goesTo: "the full edition: a lite app has no e2e
|
|
518
|
+
goesTo: "the full edition: a lite app has no e2e world; its unit specs and wiring proof run in .github/workflows/ci.yml; contract specs use test:contract"
|
|
470
519
|
managedBy: ci-workflows
|
|
471
520
|
rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
|
|
472
521
|
- id: app.ci-images
|
|
522
|
+
since: 3.1.0
|
|
473
523
|
profiles: [app]
|
|
524
|
+
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): lite renders its Supabase-backed image workflow; full renders images for its separately deployed services."
|
|
474
525
|
# Builds every image of the app (one matrix entry per declared app, path-filtered), never pushes: rendered by starci app sync.
|
|
475
526
|
path: ".github/workflows/images.yml"
|
|
476
527
|
presence: required
|
|
@@ -482,6 +533,10 @@ slots:
|
|
|
482
533
|
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
483
534
|
- id: app.ci-db
|
|
484
535
|
profiles: [app]
|
|
536
|
+
exemplifiedBy:
|
|
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
|
+
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."
|
|
485
540
|
# The database deploy workflow: workflow_dispatch only (like app.ci-e2e); starci app sync renders it under the managed ci group.
|
|
486
541
|
path: ".github/workflows/db-deploy.yml"
|
|
487
542
|
presence: optional
|
|
@@ -491,7 +546,21 @@ slots:
|
|
|
491
546
|
liteManagedBy: ci-workflows-lite
|
|
492
547
|
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
493
548
|
since: 2.1.0
|
|
549
|
+
- id: app.ci-stack-deploy
|
|
550
|
+
since: 3.3.0
|
|
551
|
+
profiles: [app]
|
|
552
|
+
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): both editions deploy their images to a Docker Swarm stack through the same starci stack verbs; the workflow lists the images the app declares."
|
|
553
|
+
# The Swarm stack deploy workflow: workflow_dispatch only (like app.ci-db); a thin file that calls `starci app stack`, rendered by starci app sync.
|
|
554
|
+
path: ".github/workflows/stack-deploy.yml"
|
|
555
|
+
presence: optional
|
|
556
|
+
tracked: tracked
|
|
557
|
+
tier: none
|
|
558
|
+
tests: none
|
|
559
|
+
managedBy: ci-workflows
|
|
560
|
+
liteManagedBy: ci-workflows-lite
|
|
561
|
+
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
494
562
|
- id: app.dockerignore
|
|
563
|
+
since: 3.1.0
|
|
495
564
|
profiles: [app]
|
|
496
565
|
# The one build-context filter of every image (the context is the app root): rendered by starci app sync; it keeps secrets and build output out.
|
|
497
566
|
path: .dockerignore
|
|
@@ -501,8 +570,40 @@ slots:
|
|
|
501
570
|
tests: none
|
|
502
571
|
managedBy: docker-ignore
|
|
503
572
|
rules: [HFS_MANAGED_FILE_DRIFT]
|
|
573
|
+
- id: app.deploy
|
|
574
|
+
since: 3.1.0
|
|
575
|
+
profiles: [app]
|
|
576
|
+
# Production delivery tooling for the app's own processes: compose files, Caddyfile, *.env.example and ops shell scripts.
|
|
577
|
+
# Lite still keeps its backing services on Supabase and its encrypted custody in app.starcistacks; R06 scans every file here.
|
|
578
|
+
path: "deploy/"
|
|
579
|
+
presence: optional
|
|
580
|
+
tracked: tracked
|
|
581
|
+
tier: none
|
|
582
|
+
tests: none
|
|
583
|
+
rules: [HFS_PLAINTEXT_SECRET]
|
|
584
|
+
- id: app.ci-deploy
|
|
585
|
+
since: 3.1.0
|
|
586
|
+
profiles: [app]
|
|
587
|
+
# Product-owned delivery workflows; the literal app.ci-db path remains the managed database workflow's more specific slot.
|
|
588
|
+
path: ".github/workflows/<app>-deploy.yml"
|
|
589
|
+
presence: optional
|
|
590
|
+
tracked: tracked
|
|
591
|
+
tier: none
|
|
592
|
+
tests: none
|
|
593
|
+
rules: [HFS_PLAINTEXT_SECRET]
|
|
594
|
+
- id: app.ci-authorize-deploy-key
|
|
595
|
+
since: 3.1.0
|
|
596
|
+
profiles: [app]
|
|
597
|
+
path: ".github/workflows/authorize-deploy-key.yml"
|
|
598
|
+
presence: optional
|
|
599
|
+
tracked: tracked
|
|
600
|
+
tier: none
|
|
601
|
+
tests: none
|
|
602
|
+
rules: [HFS_PLAINTEXT_SECRET]
|
|
504
603
|
- id: app.browser
|
|
604
|
+
since: 3.1.0
|
|
505
605
|
profiles: [app]
|
|
606
|
+
editionWhy: "ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4): browser journeys belong to full; lite proves app boot through R272 wiring."
|
|
506
607
|
# The browser journey of the product (Playwright, owner ruling: one minimal journey per example app, run manually): the config, its
|
|
507
608
|
# journeys and the script that provisions the stack it runs against. It is the product's, never the front end's (FE_NO_TESTS keeps
|
|
508
609
|
# refusing any test or test tooling inside fe/). Declared by `browser: true` in hfs.json; the managed script `test:browser` exists only then.
|
|
@@ -516,14 +617,31 @@ slots:
|
|
|
516
617
|
requires: [playwright.config.ts, journeys/]
|
|
517
618
|
allows: [playwright.config.ts, stack.mjs, "journeys/*.ts"]
|
|
518
619
|
- id: app.github-meta
|
|
620
|
+
since: 3.1.0
|
|
519
621
|
profiles: [app]
|
|
520
|
-
path: ".github/{CODEOWNERS,pull_request_template.md,ISSUE_TEMPLATE
|
|
622
|
+
path: ".github/{CODEOWNERS,pull_request_template.md,ISSUE_TEMPLATE/**}"
|
|
521
623
|
presence: optional
|
|
522
624
|
tracked: tracked
|
|
523
625
|
tier: none
|
|
524
626
|
tests: none
|
|
627
|
+
- id: app.dependabot
|
|
628
|
+
since: 3.2.0
|
|
629
|
+
profiles: [app]
|
|
630
|
+
path: .github/dependabot.yml
|
|
631
|
+
presence: required
|
|
632
|
+
tracked: tracked
|
|
633
|
+
tier: none
|
|
634
|
+
tests: none
|
|
635
|
+
managedBy: skeleton
|
|
636
|
+
liteManagedBy: skeleton-lite
|
|
637
|
+
editionWhy: "Lite renders the same managed file from skeleton-lite because the editions share one code structure while their adapters differ (ruling:R212/case-2, with Work amended by ruling:R212/case-4 and hosting by owner:lite-production-swarm)."
|
|
638
|
+
rules: [HFS_MANAGED_FILE_DRIFT, HFS_DEPENDABOT_MISSING]
|
|
525
639
|
- id: app.scripts
|
|
640
|
+
since: 3.1.0
|
|
526
641
|
profiles: [app]
|
|
642
|
+
exemplifiedBy:
|
|
643
|
+
- {path: packages/hfs/templates/app/skeleton/scripts/codegen.mjs, edition: full}
|
|
644
|
+
editionWhy: "Lite manages its Supabase codegen script; full owns operational scripts through the generic path (ruling:R212/case-2 (with the Work boundary amended by ruling:R212/case-4))."
|
|
527
645
|
# The app's operational scripts only (`*.mjs`, `*.cjs`, `*.ps1`, `*.sh`), or nothing: an empty folder is `scripts/.gitkeep`, which
|
|
528
646
|
# `starci app scaffold` writes. A spec or a test (BE_SPEC_PLACEMENT, FE_NO_TESTS), a check or lint source (`check-*`, `eslint-local-rules*`,
|
|
529
647
|
# a local eslint plugin: HFS_REPO_LOCAL_CHECK) and anything that re-implements a canon check is not an operational script and has no place here.
|
|
@@ -536,7 +654,23 @@ slots:
|
|
|
536
654
|
path: scripts/codegen.mjs
|
|
537
655
|
liteManagedBy: codegen-lite
|
|
538
656
|
rules: [HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK] # one-off codemods and fix-* scripts are agent output, not tooling
|
|
657
|
+
- id: app.scripts.delivery
|
|
658
|
+
since: 3.1.0
|
|
659
|
+
profiles: [app]
|
|
660
|
+
exemplifiedBy:
|
|
661
|
+
- {path: tests/fixtures/hfs-delivery-lite/scripts/build-frontend.mjs, edition: lite}
|
|
662
|
+
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
|
+
editions: [lite]
|
|
664
|
+
# Product-owned environment, secret and build tooling, kept apart from lite's managed codegen script.
|
|
665
|
+
# Full already owns these files through app.scripts; its generic *.mjs pattern must not compete with build-*.mjs.
|
|
666
|
+
path: "scripts/{env-sync.mjs,with-secrets.mjs,set-secrets.ps1,build-*.mjs}"
|
|
667
|
+
presence: optional
|
|
668
|
+
tracked: tracked
|
|
669
|
+
tier: none
|
|
670
|
+
tests: none
|
|
671
|
+
rules: [HFS_PLAINTEXT_SECRET, HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK]
|
|
539
672
|
- id: app.sides
|
|
673
|
+
since: 3.1.0
|
|
540
674
|
profiles: [app]
|
|
541
675
|
# The two sides. Everything below be/ and fe/ is judged by the side's own slots (profiles be and fe) with the side folder as its root;
|
|
542
676
|
# this slot answers only for the side folders themselves.
|
|
@@ -546,7 +680,12 @@ slots:
|
|
|
546
680
|
tier: none
|
|
547
681
|
tests: none
|
|
548
682
|
- id: app.starciwork
|
|
683
|
+
since: 3.1.0
|
|
549
684
|
profiles: [app]
|
|
685
|
+
exemplifiedBy:
|
|
686
|
+
- {path: packages/hfs/templates/app/skeleton/.starciwork/, edition: full}
|
|
687
|
+
- {path: packages/hfs/templates/app/skeleton-lite/.starciwork/, edition: lite}
|
|
688
|
+
editionWhy: "ruling:R212/case-4: lite optionally keeps .gitignore, workspace.yaml and brand records; full keeps the Work catalog and product summaries."
|
|
550
689
|
path: ".starciwork/"
|
|
551
690
|
presence: required
|
|
552
691
|
tracked: tracked
|
|
@@ -556,27 +695,23 @@ slots:
|
|
|
556
695
|
allows: [workspace.yaml, index.yaml, "brand/**", shell/index.yaml, "features/<feature>/index.yaml",
|
|
557
696
|
"features/<feature>/<family>/<name>/index.yaml", "features/<feature>/br/<rule>/ac/<name>/index.yaml",
|
|
558
697
|
"features/<feature>/ui/<name>/assets/<approved-file>", "features/<feature>/uat/<name>/{index,accounts,fixtures}.yaml",
|
|
559
|
-
"features/<feature>/uat/<name>/{seed,cleanup}.sql", "_resources/{identities,environments,fixtures}/<slug>/resource.yaml"
|
|
560
|
-
|
|
561
|
-
|
|
698
|
+
"features/<feature>/uat/<name>/{seed,cleanup}.sql", "_resources/{identities,environments,fixtures}/<slug>/resource.yaml",
|
|
699
|
+
"_derived/{index.yaml,frontier.md,critique.yaml,critique.md}"]
|
|
700
|
+
forbids: [evidence/, logs/, reports/, captures/, runs/, "kernel-*/", "*.sqlite*"]
|
|
701
|
+
# ruling:R212/case-4: lite keeps workspace.yaml for project identity and repo roles, plus brand drawing records,
|
|
702
|
+
# references and assets. No brand means no Work tree is needed; workspace.yaml is legal but optional.
|
|
703
|
+
litePresence: optional
|
|
562
704
|
lite:
|
|
563
|
-
requires: [.gitignore
|
|
564
|
-
allows: [workspace.yaml,
|
|
705
|
+
requires: [.gitignore]
|
|
706
|
+
allows: [workspace.yaml, "brand/**"]
|
|
565
707
|
rules: [HFS_AGENT_DATA_TRACKED, HFS_IDENTITY_CUSTODY]
|
|
566
|
-
- id: app.starciwork.uat
|
|
567
|
-
profiles: [app]
|
|
568
|
-
# The UAT records of the full edition: they are proven on the e2e world (identities, fixtures, br/ac evidence) that lite does
|
|
569
|
-
# not have, so the trees are forbidden paths under lite only. In full this slot does not exist and .starciwork answers for them.
|
|
570
|
-
editions: [lite]
|
|
571
|
-
path: ".starciwork/{_resources/identities,_resources/fixtures,features/<feature>/uat,features/<feature>/br}/"
|
|
572
|
-
presence: forbidden
|
|
573
|
-
tracked: external
|
|
574
|
-
tier: none
|
|
575
|
-
tests: none
|
|
576
|
-
goesTo: "the full edition: a lite app has no e2e world, so no UAT records (no identities or fixtures resources, no br/ac evidence, no uat directories)"
|
|
577
|
-
since: 2.1.0
|
|
578
708
|
- id: app.starcistacks
|
|
709
|
+
since: 3.1.0
|
|
579
710
|
profiles: [app]
|
|
711
|
+
editionWhy: "owner:lite-production-swarm: lite requires declared docker-swarm production with Supabase backing; full also admits Compose development, Kubernetes and Terraform."
|
|
712
|
+
exemplifiedBy:
|
|
713
|
+
- {path: packages/hfs/templates/app/skeleton/.starcistacks/, edition: full}
|
|
714
|
+
- {path: packages/hfs/templates/app/skeleton-lite/.starcistacks/, edition: lite}
|
|
580
715
|
# The stack declarations and the sealed custody of the app, at the app root beside be/, fe/ and .starciwork (never under a side).
|
|
581
716
|
path: ".starcistacks/"
|
|
582
717
|
presence: required
|
|
@@ -584,18 +719,19 @@ slots:
|
|
|
584
719
|
tier: none
|
|
585
720
|
tests: none
|
|
586
721
|
requires: [application-stacks.yaml]
|
|
587
|
-
allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,k8s,terraform}/**", "<env>/infra/metadata.json",
|
|
722
|
+
allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,swarm,k8s,terraform}/**", "<env>/infra/metadata.json",
|
|
588
723
|
"<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.enc", "<env>/secrets/.gitkeep", "<env>/seeds/**"]
|
|
589
724
|
forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
|
|
590
|
-
#
|
|
591
|
-
#
|
|
592
|
-
# local CLI stack or the cloud project). infra/ (compose, k8s, terraform), runtime/ and seeds/ are the full edition's.
|
|
725
|
+
# Owner ruling 2026-10-14: lite declares docker-swarm production, app runtime config, sealed custody and seed data.
|
|
726
|
+
# Supabase supplies the database and broker; production has no Compose or host cron.
|
|
593
727
|
lite:
|
|
594
|
-
requires: []
|
|
595
|
-
allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/
|
|
728
|
+
requires: [application-stacks.yaml]
|
|
729
|
+
allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/infra/swarm/**", "<env>/infra/metadata.json",
|
|
730
|
+
"<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.enc", "<env>/secrets/.gitkeep", "<env>/seeds/**"]
|
|
596
731
|
forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
|
|
597
732
|
rules: [HFS_STACKS_SHAPE, HFS_STACKS_IN_SIDE, HFS_PLAINTEXT_SECRET, HFS_IDENTITY_CUSTODY]
|
|
598
733
|
- id: app.sops
|
|
734
|
+
since: 3.1.0
|
|
599
735
|
profiles: [app]
|
|
600
736
|
# The sops custody rule of .starcistacks/<env>/secrets/*.enc, at the app root beside the tree it seals.
|
|
601
737
|
path: .sops.yaml
|
|
@@ -604,6 +740,7 @@ slots:
|
|
|
604
740
|
tier: none
|
|
605
741
|
tests: none
|
|
606
742
|
- id: app.build-output
|
|
743
|
+
since: 3.1.0
|
|
607
744
|
profiles: [app]
|
|
608
745
|
path: "{**/node_modules/,coverage/,reports/,test-results/,**/*.tsbuildinfo}"
|
|
609
746
|
presence: optional
|
|
@@ -611,6 +748,7 @@ slots:
|
|
|
611
748
|
tier: none
|
|
612
749
|
tests: none
|
|
613
750
|
- id: app.tool-cache
|
|
751
|
+
since: 3.1.0
|
|
614
752
|
profiles: [app]
|
|
615
753
|
path: "{.eslintcache,.turbo/,.scannerwork/,.sonar/,.jest-cache/,.cache/,.tools/}"
|
|
616
754
|
presence: forbidden
|
|
@@ -619,6 +757,7 @@ slots:
|
|
|
619
757
|
tests: none
|
|
620
758
|
goesTo: "${STARCI_CACHE_HOME:-%LOCALAPPDATA%/StarCi/cache}/<repo>/<tool>/ (the canon presets set eslint --cache-location, jest cacheDirectory, turbo cacheDir, sonar.working.directory)"
|
|
621
759
|
- id: app.agent-output
|
|
760
|
+
since: 3.1.0
|
|
622
761
|
profiles: [app]
|
|
623
762
|
path: "{report*.json,*.tmp.json,%*%,nul,.artifacts/,.tmp-*,*-lint.json,lf.json,lt.json,*.log,design-plans/,.gitmounts/,.dat,vi-flat.txt}"
|
|
624
763
|
presence: forbidden
|
|
@@ -628,6 +767,7 @@ slots:
|
|
|
628
767
|
goesTo: "agent scratchpad or the StarCi blob store (%LOCALAPPDATA%/StarCi/blobs), cited by {name, sha256}"
|
|
629
768
|
rules: [HFS_UNTRACKED_ROOT_ENTRY, HFS_AGENT_DATA_TRACKED]
|
|
630
769
|
- id: app.worktrees
|
|
770
|
+
since: 3.1.0
|
|
631
771
|
profiles: [app]
|
|
632
772
|
path: "{.worktrees/,worktrees/,.starciwork/worktrees/}"
|
|
633
773
|
presence: forbidden
|
|
@@ -636,8 +776,9 @@ slots:
|
|
|
636
776
|
tests: none
|
|
637
777
|
goesTo: "<lanes root>/<project>/<lane>/ (outside every repository) for a lane; a Kernel workflow's worktree is created and owned by Orca outside the app checkout, never tracked, and removed by the runtime's host-side controller once the workflow's finish marked it release-pending"
|
|
638
778
|
- id: app.plaintext-env
|
|
779
|
+
since: 3.1.0
|
|
639
780
|
profiles: [app]
|
|
640
|
-
path: "{.env,.env
|
|
781
|
+
path: "{.env,.env.*,secrets.env,.secrets/,**/*.pem,**/*.key}"
|
|
641
782
|
presence: forbidden
|
|
642
783
|
tracked: external
|
|
643
784
|
tier: none
|
|
@@ -648,6 +789,10 @@ slots:
|
|
|
648
789
|
# ----- app root: the Supabase project of an app whose connection declares provider: supabase (every lite app) -------
|
|
649
790
|
- id: app.supabase
|
|
650
791
|
profiles: [app]
|
|
792
|
+
exemplifiedBy:
|
|
793
|
+
- packages/hfs/templates/app/skeleton-lite/supabase/
|
|
794
|
+
- {path: tests/fixtures/hfs-callback-full/supabase/, edition: full}
|
|
795
|
+
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."
|
|
651
796
|
# The one Supabase project directory, at the app root beside be/, fe/ and .starcistacks (never under a side).
|
|
652
797
|
path: "supabase/"
|
|
653
798
|
presence: opt-in # enabled by a connection with provider: supabase; required under edition lite
|
|
@@ -659,6 +804,10 @@ slots:
|
|
|
659
804
|
since: 2.1.0
|
|
660
805
|
- id: app.supabase.config
|
|
661
806
|
profiles: [app]
|
|
807
|
+
exemplifiedBy:
|
|
808
|
+
- packages/hfs/templates/app/skeleton-lite/supabase/config.toml
|
|
809
|
+
- {path: tests/fixtures/hfs-callback-full/supabase/config.toml, edition: full}
|
|
810
|
+
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."
|
|
662
811
|
# The project configuration: auth keys are env(NAME) references, never literals (L08 judges its policy keys).
|
|
663
812
|
path: "supabase/config.toml"
|
|
664
813
|
presence: opt-in
|
|
@@ -670,6 +819,10 @@ slots:
|
|
|
670
819
|
since: 2.1.0
|
|
671
820
|
- id: app.supabase.migrations
|
|
672
821
|
profiles: [app]
|
|
822
|
+
exemplifiedBy:
|
|
823
|
+
- 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
|
+
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."
|
|
673
826
|
# The schema authority of an app on Supabase: forward-only SQL migrations named <timestamp14>_<kebab-name>.sql,
|
|
674
827
|
# one file per change, immutable once on origin/main (L02-L08 judge them).
|
|
675
828
|
path: "supabase/migrations/<stamp>_<name>.sql"
|
|
@@ -704,6 +857,10 @@ slots:
|
|
|
704
857
|
since: 2.1.0
|
|
705
858
|
- id: app.supabase.types
|
|
706
859
|
profiles: [app]
|
|
860
|
+
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
|
+
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."
|
|
707
864
|
# The generated Database types (database.types.ts) contract:emit writes from the migrations; both sides read it
|
|
708
865
|
# (sides.<side>.reads supabase/types/); drift against regeneration is L09.
|
|
709
866
|
path: "supabase/types/database.types.ts"
|
|
@@ -742,6 +899,7 @@ slots:
|
|
|
742
899
|
|
|
743
900
|
# ----- side root (both sides): what a standalone repository root holds, less the app-root files ----------
|
|
744
901
|
- id: repo.side-root-forbidden
|
|
902
|
+
since: 3.1.0
|
|
745
903
|
profiles: [be, fe]
|
|
746
904
|
path: "{package.json,package-lock.json,hfs.json,README.md,.gitignore,.gitattributes,.husky/,.github/,.starciwork/,.starcistacks/,supabase/,.sops.yaml,sonar-project.properties,codecov.yml,.prettierrc,.prettierignore,scripts/}"
|
|
747
905
|
presence: forbidden
|
|
@@ -750,6 +908,7 @@ slots:
|
|
|
750
908
|
tests: none
|
|
751
909
|
goesTo: "the app root: one package.json, lockfile, hfs.json, README, git and CI files, hooks, formatter, Sonar and Codecov configuration, scripts/, .starciwork, .starcistacks and .sops.yaml per app"
|
|
752
910
|
- id: be.tool-config
|
|
911
|
+
since: 3.1.0
|
|
753
912
|
profiles: [be]
|
|
754
913
|
# Managed files (starci app sync renders them, starci app check compares them): the whole tool configuration of a back end. Each is
|
|
755
914
|
# a reference to a canon package, never a configuration: tsconfig.json extends @starci/tsconfig/be.json and adds the
|
|
@@ -765,11 +924,12 @@ slots:
|
|
|
765
924
|
tracked: tracked
|
|
766
925
|
tier: none
|
|
767
926
|
tests: none
|
|
768
|
-
# Lite renders the same files (owner ruling 2026-10-12): the unit run and its coverage are full's.
|
|
927
|
+
# Lite renders the same files (owner ruling 2026-10-12 (owner:lite-production-swarm), with the Work boundary amended by ruling:R212/case-4): the unit run and its coverage are full's.
|
|
769
928
|
coverage: none
|
|
770
929
|
managedBy: tool-config
|
|
771
930
|
rules: [HFS_MANAGED_FILE_DRIFT, HFS_TOOL_CONFIG_LOCAL, HFS_TS_STRICT]
|
|
772
931
|
- id: be.nest-cli
|
|
932
|
+
since: 3.1.0
|
|
773
933
|
profiles: [be]
|
|
774
934
|
# The Nest monorepo of the back end: `monorepo: true` and one project per declared be app (apps/<app>, even for one service).
|
|
775
935
|
path: nest-cli.json
|
|
@@ -780,6 +940,7 @@ slots:
|
|
|
780
940
|
coverage: none
|
|
781
941
|
rules: [HFS_MONO_NEST_PROJECTS]
|
|
782
942
|
- id: be.tool-config-local
|
|
943
|
+
since: 3.1.0
|
|
783
944
|
profiles: [be]
|
|
784
945
|
path: "{.eslintrc,.eslintrc.*,.eslintignore,eslint.config.js,eslint.config.cjs,eslint.config.ts,eslint.config.mts,eslint.config.cts,.prettierrc.*,prettier.config.*,jest.config.ts,jest.config.mjs,jest.config.cjs,jest.config.json,jest.config.e2e.js}"
|
|
785
946
|
presence: forbidden
|
|
@@ -789,6 +950,7 @@ slots:
|
|
|
789
950
|
goesTo: "nowhere: a back end has exactly the managed tool configuration (be.tool-config); a change to a rule or a flag is proposed in the .claude runtime"
|
|
790
951
|
rules: [HFS_TOOL_CONFIG_LOCAL]
|
|
791
952
|
- id: fe.tool-config
|
|
953
|
+
since: 3.1.0
|
|
792
954
|
profiles: [fe]
|
|
793
955
|
# Managed files (starci app sync renders them, starci app check compares them): the tool configuration of a front end. Each is a
|
|
794
956
|
# reference to a canon package, never a configuration: tsconfig.json extends @starci/tsconfig/next.json and only
|
|
@@ -804,6 +966,7 @@ slots:
|
|
|
804
966
|
managedBy: tool-config
|
|
805
967
|
rules: [HFS_MANAGED_FILE_DRIFT, HFS_TOOL_CONFIG_LOCAL, HFS_RULE_OFF_WITHOUT_REPLACEMENT, HFS_TS_STRICT]
|
|
806
968
|
- id: fe.tool-config-local
|
|
969
|
+
since: 3.1.0
|
|
807
970
|
profiles: [fe]
|
|
808
971
|
path: "{.eslintrc,.eslintrc.*,.eslintignore,eslint.config.js,eslint.config.cjs,eslint.config.ts,eslint.config.mts,eslint.config.cts,.stylelintrc,.stylelintrc.*,.stylelintignore,stylelint.config.js,stylelint.config.cjs,stylelint.config.ts,stylelint.config.json,prettier.config.*,.prettierrc.*,lint-staged.config.*,.lintstagedrc*,turbo.json}"
|
|
809
972
|
presence: forbidden
|
|
@@ -813,6 +976,7 @@ slots:
|
|
|
813
976
|
goesTo: "nowhere: a front end has exactly the managed tool configuration (fe.tool-config); the task graph is the app root's turbo.json (app.task-graph); a change to a rule or a flag is proposed in the .claude runtime"
|
|
814
977
|
rules: [HFS_TOOL_CONFIG_LOCAL]
|
|
815
978
|
- id: repo.docs
|
|
979
|
+
since: 3.1.0
|
|
816
980
|
profiles: [be, fe]
|
|
817
981
|
path: "docs/{adr,runbooks,guides}/**/*.md"
|
|
818
982
|
presence: opt-in
|
|
@@ -822,6 +986,7 @@ slots:
|
|
|
822
986
|
coverage: none
|
|
823
987
|
rules: [HFS_ROOT_MARKDOWN_FORBIDDEN]
|
|
824
988
|
- id: repo.docs-media
|
|
989
|
+
since: 3.1.0
|
|
825
990
|
profiles: [be, fe]
|
|
826
991
|
path: "docs/**/*.{png,svg,webp}"
|
|
827
992
|
presence: opt-in
|
|
@@ -830,7 +995,23 @@ slots:
|
|
|
830
995
|
tests: none
|
|
831
996
|
coverage: none
|
|
832
997
|
budget: {bytes: 512000}
|
|
998
|
+
- id: repo.i18n-dynamic-keys
|
|
999
|
+
since: 3.3.0
|
|
1000
|
+
profiles: [be, fe]
|
|
1001
|
+
# The side's authored ledger of catalog keys the R280 detector cannot see (fe/i18n-dynamic-keys.yaml, be/i18n-dynamic-keys.yaml:
|
|
1002
|
+
# each side owns its own catalogs). A key it names counts as read; an entry matching no catalog key, or a malformed file, is itself an R280 finding.
|
|
1003
|
+
path: i18n-dynamic-keys.yaml
|
|
1004
|
+
presence: opt-in
|
|
1005
|
+
tracked: tracked
|
|
1006
|
+
tier: none
|
|
1007
|
+
tests: none
|
|
1008
|
+
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
|
+
rules: [HFS_UNUSED_SYMBOL]
|
|
833
1013
|
- id: repo.packages
|
|
1014
|
+
since: 3.1.0
|
|
834
1015
|
profiles: [be, fe]
|
|
835
1016
|
path: "packages/<pkg>/"
|
|
836
1017
|
presence: opt-in
|
|
@@ -842,7 +1023,14 @@ slots:
|
|
|
842
1023
|
coverage: none
|
|
843
1024
|
rules: [FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
|
|
844
1025
|
- id: repo.app-image
|
|
1026
|
+
since: 3.1.0
|
|
845
1027
|
profiles: [be, fe]
|
|
1028
|
+
exemplifiedBy:
|
|
1029
|
+
- packages/hfs/templates/be/image/api/Dockerfile
|
|
1030
|
+
- packages/hfs/templates/be/image/cli/Dockerfile
|
|
1031
|
+
- {path: packages/hfs/templates/be/image/worker/Dockerfile, edition: full}
|
|
1032
|
+
- packages/hfs/templates/fe/image/next/Dockerfile
|
|
1033
|
+
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite may host Next without its own image; back-end images stay required in both editions."
|
|
846
1034
|
# One image per app: every declared app has its own Dockerfile (apps/<app>/Dockerfile), built from the APP ROOT (the folder with
|
|
847
1035
|
# the one package.json). It is app-owned (starci app scaffold writes it once from templates/<side>/image) and judged by the docker rules.
|
|
848
1036
|
# A Dockerfile anywhere else is owned by no slot.
|
|
@@ -857,6 +1045,7 @@ slots:
|
|
|
857
1045
|
|
|
858
1046
|
# ----- never tracked: build output the tools need in place ---------------------------------------------
|
|
859
1047
|
- id: repo.build-output
|
|
1048
|
+
since: 3.1.0
|
|
860
1049
|
profiles: [be, fe]
|
|
861
1050
|
path: "{**/node_modules/,dist/,apps/*/dist/,packages/*/dist/,.next/,apps/*/.next/,coverage/,reports/,test-results/,**/next-env.d.ts,**/*.tsbuildinfo}"
|
|
862
1051
|
presence: optional
|
|
@@ -864,6 +1053,7 @@ slots:
|
|
|
864
1053
|
tier: none
|
|
865
1054
|
tests: none
|
|
866
1055
|
- id: repo.generated
|
|
1056
|
+
since: 3.1.0
|
|
867
1057
|
profiles: [be, fe]
|
|
868
1058
|
path: "{**/__generated__/,apps/*/src/schema.gql}"
|
|
869
1059
|
presence: optional
|
|
@@ -874,6 +1064,7 @@ slots:
|
|
|
874
1064
|
|
|
875
1065
|
# ----- never in the working tree: caches, scratch, agent output ----------------------------------------
|
|
876
1066
|
- id: repo.tool-cache
|
|
1067
|
+
since: 3.1.0
|
|
877
1068
|
profiles: [be, fe]
|
|
878
1069
|
path: "{.eslintcache,.turbo/,.scannerwork/,.sonar/,.jest-cache/,.cache/,.tools/}"
|
|
879
1070
|
presence: forbidden
|
|
@@ -882,6 +1073,7 @@ slots:
|
|
|
882
1073
|
tests: none
|
|
883
1074
|
goesTo: "${STARCI_CACHE_HOME:-%LOCALAPPDATA%/StarCi/cache}/<repo>/<tool>/ (the canon presets set eslint --cache-location, jest cacheDirectory, turbo cacheDir, sonar.working.directory)"
|
|
884
1075
|
- id: repo.agent-output
|
|
1076
|
+
since: 3.1.0
|
|
885
1077
|
profiles: [be, fe]
|
|
886
1078
|
path: "{report*.json,*.tmp.json,%*%,nul,.artifacts/,.tmp-*,*-lint.json,lf.json,lt.json,*.log,design-plans/,.gitmounts/,.dat,vi-flat.txt}"
|
|
887
1079
|
presence: forbidden
|
|
@@ -891,6 +1083,7 @@ slots:
|
|
|
891
1083
|
goesTo: "agent scratchpad or the StarCi blob store (%LOCALAPPDATA%/StarCi/blobs), cited by {name, sha256}"
|
|
892
1084
|
rules: [HFS_UNTRACKED_ROOT_ENTRY, HFS_AGENT_DATA_TRACKED]
|
|
893
1085
|
- id: repo.worktrees
|
|
1086
|
+
since: 3.1.0
|
|
894
1087
|
profiles: [be, fe]
|
|
895
1088
|
path: "{.worktrees/,worktrees/,.starciwork/worktrees/}"
|
|
896
1089
|
presence: forbidden
|
|
@@ -899,6 +1092,7 @@ slots:
|
|
|
899
1092
|
tests: none
|
|
900
1093
|
goesTo: "<lanes root>/<project>/<lane>/ (outside every repository)"
|
|
901
1094
|
- id: repo.plaintext-env
|
|
1095
|
+
since: 3.1.0
|
|
902
1096
|
profiles: [be, fe]
|
|
903
1097
|
path: "{.env,.env.*,.secrets/,**/*.pem,**/*.key}"
|
|
904
1098
|
presence: forbidden
|
|
@@ -908,6 +1102,7 @@ slots:
|
|
|
908
1102
|
goesTo: ".starcistacks/<env>/secrets/<slug>.enc (at the app root); decrypted values only in memory or under %LOCALAPPDATA%/StarCi/secrets/<project>/<env>/ via *_FILE"
|
|
909
1103
|
rules: [HFS_PLAINTEXT_SECRET]
|
|
910
1104
|
- id: be.root-e2e
|
|
1105
|
+
since: 3.1.0
|
|
911
1106
|
profiles: [be]
|
|
912
1107
|
path: "e2e/"
|
|
913
1108
|
presence: forbidden
|
|
@@ -918,6 +1113,7 @@ slots:
|
|
|
918
1113
|
|
|
919
1114
|
# ----- contract snapshots (the committed machine-emitted files, because they are a cross-repo interface) --
|
|
920
1115
|
- id: be.contract.graphql
|
|
1116
|
+
since: 3.1.0
|
|
921
1117
|
profiles: [be]
|
|
922
1118
|
path: "contracts/<app>/schema.graphql"
|
|
923
1119
|
presence: opt-in
|
|
@@ -928,6 +1124,7 @@ slots:
|
|
|
928
1124
|
why: emitted by `npm run contract:emit` (`starci app emit`, never written by hand); CI fails when the emitted file differs (a contract, not a build artefact)
|
|
929
1125
|
rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
|
|
930
1126
|
- id: be.contract.openapi
|
|
1127
|
+
since: 3.1.0
|
|
931
1128
|
profiles: [be]
|
|
932
1129
|
path: "contracts/<app>/openapi.json"
|
|
933
1130
|
presence: opt-in
|
|
@@ -938,6 +1135,7 @@ slots:
|
|
|
938
1135
|
why: emitted by `npm run contract:emit` (`starci app emit`, never written by hand) from the app's typed operation table `apps/<app>/src/operations.ts` (canon BE-OPERATIONS-1); CI fails when the emitted file differs
|
|
939
1136
|
rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
|
|
940
1137
|
- id: be.contract.events
|
|
1138
|
+
since: 3.1.0
|
|
941
1139
|
profiles: [be]
|
|
942
1140
|
path: "contracts/<app>/events.json"
|
|
943
1141
|
presence: opt-in
|
|
@@ -948,6 +1146,7 @@ slots:
|
|
|
948
1146
|
why: the async contract of a service (`starci/event-contract@1`), emitted by `npm run contract:emit` (`starci app emit`, never written by hand) from the typed event classes of `src/modules/events/<service>/`; the consumers of its events rely on it (R166 HFS_EVENT_CONTRACT)
|
|
949
1147
|
rules: [HFS_EVENT_CONTRACT]
|
|
950
1148
|
- id: be.contract.events.pin
|
|
1149
|
+
since: 3.1.0
|
|
951
1150
|
profiles: [be]
|
|
952
1151
|
path: "contracts/<app>/events.pin.json"
|
|
953
1152
|
presence: opt-in
|
|
@@ -959,7 +1158,25 @@ slots:
|
|
|
959
1158
|
rules: [BE_CONTRACT_BREAKING]
|
|
960
1159
|
|
|
961
1160
|
# ----- BE: apps (one slot per app kind; a new kind = a new slot, minor bump) ---------------------------
|
|
1161
|
+
# The files of every app (owner ruling 2026-10-14, R245 and R246, since 3.1.0), each with a line budget that
|
|
1162
|
+
# BE_APP_COMPOSITION_BUDGET holds absolutely (not a ratchet):
|
|
1163
|
+
# main.ts the entry: builds EnvSource once, imports and calls read<App>Env from <app>.env.ts, builds options and boots.
|
|
1164
|
+
# <app>.env.ts exports interface <App>Env and read<App>Env(env: EnvSource): <App>Env (for example ApiEnv/readApiEnv);
|
|
1165
|
+
# fail-fast environment parsing only, via EnvSource and unchanged capability parse helpers; no localhost
|
|
1166
|
+
# or default host fallback, logic, module composition, business code or feature imports. Only main.ts
|
|
1167
|
+
# and this app file may read process.env or EnvSource. The interface declares its own readonly parsed
|
|
1168
|
+
# members with parser-derived types, structurally compatible with the app's options;
|
|
1169
|
+
# the role name stays <app>.env.ts, never env.ts. Secrets use EnvSource.secret(): <NAME>_FILE first
|
|
1170
|
+
# for .starcistacks custody, plain <NAME> only when the file key is absent. A declared file supplies
|
|
1171
|
+
# exact contents; an empty or unreadable path stops boot without fallback.
|
|
1172
|
+
# <app>.options.ts the options: env-derived options plus port bindings (provider maps such as { approvalGate: X }) only;
|
|
1173
|
+
# no environment parsing or process.env/EnvSource reads; module, queue or scheduler composition here or in <app>.env.ts is
|
|
1174
|
+
# BE_APP_OPTIONS_COMPOSITION.
|
|
1175
|
+
# compose/<area>.ts a composition group: exports the module list of one area (an imports array, and the providers of the
|
|
1176
|
+
# area's global ports when it has any); composition only, no business logic or process.env/EnvSource read.
|
|
1177
|
+
# app.module.ts the composition root: imports the groups and spreads them, binds the app-wide guards and filter.
|
|
962
1178
|
- id: be.app.api
|
|
1179
|
+
since: 3.1.0
|
|
963
1180
|
profiles: [be]
|
|
964
1181
|
path: "apps/<app>/src/"
|
|
965
1182
|
appKind: api
|
|
@@ -968,13 +1185,14 @@ slots:
|
|
|
968
1185
|
tier: app
|
|
969
1186
|
owner: true
|
|
970
1187
|
minInstances: 1
|
|
971
|
-
requires: [main.ts, app.module.ts]
|
|
972
|
-
allows: [main.ts, app.module.ts, "<app>.options.ts", operations.ts]
|
|
973
|
-
tests: none #
|
|
1188
|
+
requires: [main.ts, app.module.ts, "<app>.env.ts"]
|
|
1189
|
+
allows: [main.ts, app.module.ts, "<app>.env.ts", "<app>.options.ts", "compose/<area>.ts", operations.ts]
|
|
1190
|
+
tests: none # full app boot is proven by useTestWorld({ apps }); lite by starci app check --wiring (R272)
|
|
974
1191
|
coverage: none
|
|
975
|
-
budget: {app.module.ts: 250, main.ts: 80}
|
|
976
|
-
rules: [BE_APP_COMPOSITION_ONLY, BE_APP_BUSINESS_ROLE, BE_DEFAULT_DENY, BE_ERROR_MASKED, BE_FEATURE_NOT_COMPOSED, BE_CONTEXT_OWNER]
|
|
1192
|
+
budget: {app.module.ts: 250, main.ts: 80, "<app>.env.ts": 150, "<app>.options.ts": 200, "compose/<area>.ts": 120}
|
|
1193
|
+
rules: [BE_APP_COMPOSITION_ONLY, BE_APP_BUSINESS_ROLE, BE_APP_OPTIONS_COMPOSITION, BE_APP_COMPOSITION_BUDGET, BE_DEFAULT_DENY, BE_ERROR_MASKED, BE_FEATURE_NOT_COMPOSED, BE_CONTEXT_OWNER, BE_APP_ENV_REQUIRED]
|
|
977
1194
|
- id: be.app.worker
|
|
1195
|
+
since: 3.1.0
|
|
978
1196
|
profiles: [be]
|
|
979
1197
|
path: "apps/<app>/src/"
|
|
980
1198
|
appKind: worker # composes transport/message modules and job processors only (an api app may compose them too: a worker app is for separate scaling)
|
|
@@ -982,18 +1200,22 @@ slots:
|
|
|
982
1200
|
tracked: tracked
|
|
983
1201
|
tier: app
|
|
984
1202
|
owner: true
|
|
985
|
-
requires: [main.ts, app.module.ts]
|
|
986
|
-
allows: [main.ts, app.module.ts, "<app>.options.ts"]
|
|
987
|
-
tests: none #
|
|
1203
|
+
requires: [main.ts, app.module.ts, "<app>.env.ts"]
|
|
1204
|
+
allows: [main.ts, app.module.ts, "<app>.env.ts", "<app>.options.ts", "compose/<area>.ts"]
|
|
1205
|
+
tests: none # full app boot is proven by useTestWorld({ apps }); lite by starci app check --wiring (R272)
|
|
988
1206
|
coverage: none
|
|
989
|
-
|
|
1207
|
+
budget: {app.module.ts: 250, main.ts: 80, "<app>.env.ts": 150, "<app>.options.ts": 200, "compose/<area>.ts": 120}
|
|
1208
|
+
rules: [BE_APP_COMPOSITION_ONLY, BE_APP_OPTIONS_COMPOSITION, BE_APP_COMPOSITION_BUDGET, BE_BACKGROUND_UNOWNED, BE_CONTEXT_OWNER, BE_APP_ENV_REQUIRED]
|
|
990
1209
|
- id: be.app.cli
|
|
1210
|
+
since: 3.1.0
|
|
991
1211
|
profiles: [be]
|
|
1212
|
+
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): Supabase CLI owns lite migrations; a lite cli app is optional for declared operator commands."
|
|
992
1213
|
# The ONE app of every one-off action (migrate, seed, sync, backup, any operator command): be/apps/cli, on nest-commander.
|
|
993
1214
|
# main.ts boots with CommandFactory.run (BE_CLI_BOOTSTRAP); app.module.ts composes the platform and the cli feature root
|
|
994
|
-
# (src/features/cli/cli.module.ts); cli.
|
|
995
|
-
# among them (the test world migrates through the same list). It is required as soon as the
|
|
996
|
-
# or tracks a command, with its image apps/cli/Dockerfile (BE_CLI_REQUIRED): one image,
|
|
1215
|
+
# (src/features/cli/cli.module.ts); cli.env.ts exports CliEnv and readCliEnv(env: EnvSource): CliEnv, and cli.options.ts carries the env-derived values,
|
|
1216
|
+
# the connections it migrates among them (the test world migrates through the same list). It is required as soon as the
|
|
1217
|
+
# back end declares a connection or tracks a command, with its image apps/cli/Dockerfile (BE_CLI_REQUIRED): one image,
|
|
1218
|
+
# run as `cli <group> <command>`.
|
|
997
1219
|
path: "apps/<app>/src/"
|
|
998
1220
|
appKind: cli
|
|
999
1221
|
presence: opt-in
|
|
@@ -1001,17 +1223,19 @@ slots:
|
|
|
1001
1223
|
tracked: tracked
|
|
1002
1224
|
tier: app
|
|
1003
1225
|
owner: true
|
|
1004
|
-
requires: [main.ts, app.module.ts]
|
|
1005
|
-
allows: [main.ts, app.module.ts, "<app>.options.ts"]
|
|
1006
|
-
tests: none #
|
|
1226
|
+
requires: [main.ts, app.module.ts, "<app>.env.ts"]
|
|
1227
|
+
allows: [main.ts, app.module.ts, "<app>.env.ts", "<app>.options.ts", "compose/<area>.ts"]
|
|
1228
|
+
tests: none # full app boot is proven by useTestWorld({ apps }); lite by starci app check --wiring (R272)
|
|
1007
1229
|
coverage: none
|
|
1008
|
-
|
|
1230
|
+
budget: {app.module.ts: 250, main.ts: 80, "<app>.env.ts": 150, "<app>.options.ts": 200, "compose/<area>.ts": 120}
|
|
1231
|
+
rules: [BE_CLI_REQUIRED, BE_CLI_BOOTSTRAP, BE_CLI_OWNER, BE_SCHEMA_AUTHORITY, BE_APP_OPTIONS_COMPOSITION, BE_APP_COMPOSITION_BUDGET, BE_APP_ENV_REQUIRED]
|
|
1009
1232
|
|
|
1010
1233
|
# ----- BE: features ------------------------------------------------------------------------------------
|
|
1011
1234
|
# src/features/ is grouped by TRIGGER KIND (owner decision 2026-10-02): features/api/<feature>/ (a request: application/ and
|
|
1012
1235
|
# transport/<protocol>/, composed by the api apps), features/cli/ (a one-off action: the cli feature root, compiled only into
|
|
1013
1236
|
# apps/cli). Every kind imports only downward into modules/*; no kind imports another.
|
|
1014
1237
|
- id: be.feature
|
|
1238
|
+
since: 3.1.0
|
|
1015
1239
|
profiles: [be]
|
|
1016
1240
|
path: "src/features/api/<feature>/"
|
|
1017
1241
|
presence: required
|
|
@@ -1021,12 +1245,13 @@ slots:
|
|
|
1021
1245
|
owner: true
|
|
1022
1246
|
minInstances: 1
|
|
1023
1247
|
requires: [index.ts, "<feature>.module.ts", application/]
|
|
1024
|
-
allows: [index.ts, "<feature>.module.ts", application/, transport/, messages/] # nothing else at feature root
|
|
1248
|
+
allows: [index.ts, "<feature>.module.ts", "<feature>.module-definition.ts", "<feature>.options.ts", application/, transport/, messages/] # nothing else at feature root
|
|
1025
1249
|
tests: none
|
|
1026
1250
|
coverage: none
|
|
1027
1251
|
budget: {files: 250, indexExports: 60} # a feature above 250 source files must be split by product capability
|
|
1028
1252
|
rules: [BE_FEATURE_SHAPE, BE_FEATURE_IMPORTS_FEATURE, BE_PUBLIC_SURFACE, BE_FEATURE_NOT_COMPOSED]
|
|
1029
1253
|
- id: be.feature.application
|
|
1254
|
+
since: 3.1.0
|
|
1030
1255
|
profiles: [be]
|
|
1031
1256
|
path: "src/features/{api/,jobs/,reactors/,saga/}<feature>/application/" # inside a trigger kind (jobs, reactors, saga) the feature folder is one level down
|
|
1032
1257
|
presence: required
|
|
@@ -1038,6 +1263,7 @@ slots:
|
|
|
1038
1263
|
coverage: none
|
|
1039
1264
|
rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_ENTITY_IN_CONTRACT, BE_CQRS_SHAPE]
|
|
1040
1265
|
- id: be.feature.saga
|
|
1266
|
+
since: 3.1.0
|
|
1041
1267
|
profiles: [be]
|
|
1042
1268
|
path: "src/features/saga/<saga>/"
|
|
1043
1269
|
pattern: saga # enabled by `patterns: ["saga"]` in hfs.json sides.be (R169 to R173); an undeclared saga folder is HFS_SLOT_NOT_ENABLED
|
|
@@ -1047,11 +1273,12 @@ slots:
|
|
|
1047
1273
|
tier: feature
|
|
1048
1274
|
owner: true
|
|
1049
1275
|
requires: [index.ts, "<saga>.module.ts", "<saga>.saga.service.ts", "<saga>.saga-state.ts", steps/, compensations/, transport/]
|
|
1050
|
-
allows: [index.ts, "<saga>.module.ts", "<saga>.saga.service.ts", "<saga>.saga.service.spec.ts", "<saga>.saga-state.ts", "<saga>.saga.log-events.ts", application/, steps/, compensations/, transport/] # the orchestrator is a `*.service.ts` because a handler injects only services; unit-tested beside it
|
|
1276
|
+
allows: [index.ts, "<saga>.module.ts", "<saga>.module-definition.ts", "<saga>.options.ts", "<saga>.saga.service.ts", "<saga>.saga.service.spec.ts", "<saga>.saga-state.ts", "<saga>.saga.log-events.ts", application/, steps/, compensations/, transport/] # the orchestrator is a `*.service.ts` because a handler injects only services; unit-tested beside it
|
|
1051
1277
|
tests: unit-beside # <saga>.saga.service.spec.ts beside the orchestrator; the state machine and its fence are platform/saga's service; each compensation path has an e2e spec
|
|
1052
1278
|
coverage: none
|
|
1053
1279
|
rules: [BE_KIND_ISOLATION, BE_FEATURE_IMPORTS_FEATURE, BE_FEATURE_NOT_COMPOSED, BE_SAGA_STEP_COMPENSATION, BE_SAGA_STATE_VERSIONED, BE_SAGA_EVENT_CONTRACT, BE_SAGA_E2E_MISSING]
|
|
1054
1280
|
- id: be.feature.saga.steps
|
|
1281
|
+
since: 3.1.0
|
|
1055
1282
|
profiles: [be]
|
|
1056
1283
|
path: "src/features/saga/<saga>/steps/"
|
|
1057
1284
|
pattern: saga
|
|
@@ -1063,6 +1290,7 @@ slots:
|
|
|
1063
1290
|
coverage: none
|
|
1064
1291
|
rules: [BE_SAGA_STEP_COMPENSATION, BE_SAGA_EVENT_CONTRACT]
|
|
1065
1292
|
- id: be.feature.saga.compensations
|
|
1293
|
+
since: 3.1.0
|
|
1066
1294
|
profiles: [be]
|
|
1067
1295
|
path: "src/features/saga/<saga>/compensations/"
|
|
1068
1296
|
pattern: saga
|
|
@@ -1086,30 +1314,33 @@ slots:
|
|
|
1086
1314
|
rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_FEATURE_IMPORTS_FEATURE]
|
|
1087
1315
|
since: 1.0.0
|
|
1088
1316
|
- id: be.transport.http
|
|
1317
|
+
since: 3.1.0
|
|
1089
1318
|
profiles: [be]
|
|
1090
1319
|
path: "src/features/api/<feature>/transport/http/"
|
|
1091
1320
|
presence: optional
|
|
1092
1321
|
tracked: tracked
|
|
1093
1322
|
tier: feature
|
|
1094
1323
|
requires: ["<feature>-http.module.ts"]
|
|
1095
|
-
allows: ["<action>.controller.ts", "dto/<action>.{request,response}.ts", "<action>.mapper.ts"]
|
|
1324
|
+
allows: ["<action>.controller.ts", "dto/<action>.{request,response}.ts", "<action>.mapper.ts", "<feature>-http.module-definition.ts", "<feature>-http.options.ts"]
|
|
1096
1325
|
tests: none
|
|
1097
1326
|
coverage: none
|
|
1098
1327
|
composedBy: [api]
|
|
1099
1328
|
rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED, BE_TRANSPORT_SHAPE]
|
|
1100
1329
|
- id: be.transport.graphql
|
|
1330
|
+
since: 3.1.0
|
|
1101
1331
|
profiles: [be]
|
|
1102
1332
|
path: "src/features/api/<feature>/transport/graphql/"
|
|
1103
1333
|
presence: optional
|
|
1104
1334
|
tracked: tracked
|
|
1105
1335
|
tier: feature
|
|
1106
1336
|
requires: ["<feature>-graphql.module.ts"]
|
|
1107
|
-
allows: ["<action>.resolver.ts", "dto/<action>.{input,type,args}.ts", "<action>.mapper.ts"]
|
|
1337
|
+
allows: ["<action>.resolver.ts", "dto/<action>.{input,type,args}.ts", "<action>.mapper.ts", "<feature>-graphql.module-definition.ts", "<feature>-graphql.options.ts"]
|
|
1108
1338
|
tests: none
|
|
1109
1339
|
coverage: none
|
|
1110
1340
|
composedBy: [api]
|
|
1111
1341
|
rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED, BE_TRANSPORT_SHAPE]
|
|
1112
1342
|
- id: be.transport.message
|
|
1343
|
+
since: 3.1.0
|
|
1113
1344
|
profiles: [be]
|
|
1114
1345
|
path: "src/features/{api/,reactors/,saga/}<feature>/transport/message/"
|
|
1115
1346
|
presence: opt-in # queue consumer: composed by the service that owns it (an api app) or by a worker app scaled apart
|
|
@@ -1117,7 +1348,7 @@ slots:
|
|
|
1117
1348
|
tracked: tracked
|
|
1118
1349
|
tier: feature
|
|
1119
1350
|
requires: ["<feature>-message.module.ts"]
|
|
1120
|
-
allows: ["<event>.consumer.ts"] # BE-CONVENTION 1.6: transport/message holds only consumers; a .message.ts is outside the closed suffix list
|
|
1351
|
+
allows: ["<event>.consumer.ts", "<feature>-message.module-definition.ts", "<feature>-message.options.ts"] # BE-CONVENTION 1.6: transport/message holds only consumers; a .message.ts is outside the closed suffix list
|
|
1121
1352
|
tests: none
|
|
1122
1353
|
coverage: none
|
|
1123
1354
|
composedBy: [api, worker]
|
|
@@ -1149,7 +1380,7 @@ slots:
|
|
|
1149
1380
|
tracked: tracked
|
|
1150
1381
|
tier: feature
|
|
1151
1382
|
requires: ["<provider>-http.module.ts", "<provider>.webhook.ts", "<provider>.webhook.spec.ts"]
|
|
1152
|
-
allows: ["<provider>-http.module.ts", "<provider>.webhook.ts", "<provider>.webhook.spec.ts", dto/]
|
|
1383
|
+
allows: ["<provider>-http.module.ts", "<provider>-http.module-definition.ts", "<provider>-http.options.ts", "<provider>.webhook.ts", "<provider>.webhook.spec.ts", dto/]
|
|
1153
1384
|
tests: unit-beside # the door's own spec (<provider>.webhook.spec.ts) beside the door: the one non-service unit spec of this kind
|
|
1154
1385
|
coverage: none
|
|
1155
1386
|
composedBy: [api]
|
|
@@ -1170,6 +1401,9 @@ slots:
|
|
|
1170
1401
|
since: 1.0.0
|
|
1171
1402
|
- id: be.feature.realtime
|
|
1172
1403
|
profiles: [be]
|
|
1404
|
+
exemplifiedBy:
|
|
1405
|
+
- {path: tests/fixtures/hfs-lite-slots/be/src/features/realtime/order-status/, edition: lite}
|
|
1406
|
+
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."
|
|
1173
1407
|
path: "src/features/realtime/<channel>/"
|
|
1174
1408
|
presence: opt-in # a push channel: reads and pushes only (a socket gateway or a GraphQL subscription), no domain write
|
|
1175
1409
|
pattern: realtime # declaring the pattern (hfs.json sides.be.patterns) enables the kind (knowledge/patterns/be/realtime.yaml)
|
|
@@ -1190,15 +1424,16 @@ slots:
|
|
|
1190
1424
|
since: 1.0.0
|
|
1191
1425
|
- id: be.feature.realtime.graphql
|
|
1192
1426
|
profiles: [be]
|
|
1427
|
+
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."
|
|
1193
1428
|
path: "src/features/realtime/<channel>/transport/graphql/"
|
|
1194
1429
|
presence: opt-in # a GraphQL subscription over graphql-ws
|
|
1195
|
-
litePresence: forbidden #
|
|
1430
|
+
litePresence: forbidden # ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite pushes through the api's in-process socket.io gateway
|
|
1196
1431
|
pattern: realtime
|
|
1197
1432
|
tracked: tracked
|
|
1198
1433
|
tier: feature
|
|
1199
1434
|
goesTo: "src/features/realtime/<channel>/transport/websocket/ (be.feature.realtime.websocket): both editions push through the socket.io gateway; lite runs it in the api process"
|
|
1200
1435
|
requires: ["<channel>-graphql.module.ts", "<channel>.subscription.ts", "<channel>.subscription.spec.ts"]
|
|
1201
|
-
allows: ["<channel>-graphql.module.ts", "<channel>.subscription.ts", "<channel>.subscription.spec.ts", dto/]
|
|
1436
|
+
allows: ["<channel>-graphql.module.ts", "<channel>-graphql.module-definition.ts", "<channel>-graphql.options.ts", "<channel>.subscription.ts", "<channel>.subscription.spec.ts", dto/]
|
|
1202
1437
|
tests: unit-beside # the door's own spec beside the door (a subscription has no service of its own)
|
|
1203
1438
|
coverage: none
|
|
1204
1439
|
composedBy: [api]
|
|
@@ -1206,6 +1441,7 @@ slots:
|
|
|
1206
1441
|
since: 1.0.0
|
|
1207
1442
|
- id: be.feature.realtime.dto
|
|
1208
1443
|
profiles: [be]
|
|
1444
|
+
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): GraphQL subscription DTOs exist only with the full subscription door; lite uses socket.io contracts."
|
|
1209
1445
|
path: "src/features/realtime/<channel>/transport/graphql/dto/"
|
|
1210
1446
|
presence: opt-in
|
|
1211
1447
|
litePresence: forbidden # the dto of the graphql door, which lite does not serve
|
|
@@ -1227,20 +1463,21 @@ slots:
|
|
|
1227
1463
|
tracked: tracked
|
|
1228
1464
|
tier: feature
|
|
1229
1465
|
requires: ["<channel>-websocket.module.ts", "<channel>.gateway.ts", "<channel>.gateway.spec.ts"]
|
|
1230
|
-
allows: ["<channel>-websocket.module.ts", "<channel>.gateway.ts", "<channel>.gateway.spec.ts"]
|
|
1466
|
+
allows: ["<channel>-websocket.module.ts", "<channel>-websocket.module-definition.ts", "<channel>-websocket.options.ts", "<channel>.gateway.ts", "<channel>.gateway.spec.ts"]
|
|
1231
1467
|
tests: unit-beside # the door's own spec beside the door (a gateway has no service of its own)
|
|
1232
1468
|
coverage: none
|
|
1233
1469
|
composedBy: [api]
|
|
1234
1470
|
rules: [BE_REALTIME_SHAPE, BE_REALTIME_WRITES, BE_REALTIME_TOPIC_SCOPE, BE_DEFAULT_DENY]
|
|
1235
1471
|
since: 1.0.0
|
|
1236
1472
|
- id: be.cli
|
|
1473
|
+
since: 3.1.0
|
|
1237
1474
|
profiles: [be]
|
|
1238
1475
|
# The cli feature root: every one-off action of the back end, compiled only into apps/cli. A group is a nest-commander
|
|
1239
1476
|
# @Command (<group>/<group>.cli.ts) whose sub-commands are @SubCommand classes extending CommandRunner
|
|
1240
1477
|
# (<group>/subs/<name>.cli.ts), each with its unit spec beside it (<name>.cli.spec.ts): a command is an action runner, not a
|
|
1241
1478
|
# thin transport (BE_CLI_COMMAND_SHAPE). `.cli.ts` because `.command.ts` is the CQRS message (HFS delta 3). Every module of
|
|
1242
|
-
# the root
|
|
1243
|
-
# module provides its commands, and a command takes its inputs from a platform capability (the connections and their opener
|
|
1479
|
+
# the root has a module definition beside it (ruling:module-definition-everywhere) and no register override: cli.module.ts imports each
|
|
1480
|
+
# group module as <Group>Module.register({}), a group module provides its commands, and a command takes its inputs from a platform capability (the connections and their opener
|
|
1244
1481
|
# from platform/database) or from a command-line @Option.
|
|
1245
1482
|
path: "src/features/cli/"
|
|
1246
1483
|
presence: optional
|
|
@@ -1251,7 +1488,7 @@ slots:
|
|
|
1251
1488
|
requires: [index.ts, cli.module.ts]
|
|
1252
1489
|
# A composed root: an app of kind cli lists cli.module.ts, never a group module (BE_MODULE_SHAPE).
|
|
1253
1490
|
composedBy: [cli]
|
|
1254
|
-
allows: [index.ts, cli.module.ts, "<group>/<group>.cli.ts", "<group>/<group>.cli.spec.ts", "<group>/<group>.module.ts", "<group>/subs/<name>.cli.ts", "<group>/subs/<name>.cli.spec.ts"]
|
|
1491
|
+
allows: [index.ts, cli.module.ts, "cli.module-definition.ts", "cli.options.ts", "<group>/<group>.cli.ts", "<group>/<group>.cli.spec.ts", "<group>/<group>.module.ts", "<group>/<group>.module-definition.ts", "<group>/<group>.options.ts", "<group>/subs/<name>.cli.ts", "<group>/subs/<name>.cli.spec.ts"]
|
|
1255
1492
|
tests: unit-beside
|
|
1256
1493
|
coverage: none
|
|
1257
1494
|
rules: [BE_CLI_COMMAND_SHAPE, BE_CLI_OWNER, BE_CLI_REQUIRED, BE_MODULE_SHAPE, BE_SCHEMA_AUTHORITY]
|
|
@@ -1268,8 +1505,11 @@ slots:
|
|
|
1268
1505
|
tests: none
|
|
1269
1506
|
coverage: none
|
|
1270
1507
|
since: 3.0.0
|
|
1508
|
+
rules: [HFS_ASSET_OVERSIZE, HFS_ASSET_KIND]
|
|
1509
|
+
|
|
1271
1510
|
# ----- BE: modules (three tiers, closed set) -----------------------------------------------------------
|
|
1272
1511
|
- id: be.domain
|
|
1512
|
+
since: 3.1.0
|
|
1273
1513
|
profiles: [be]
|
|
1274
1514
|
path: "src/modules/domain/<capability>/"
|
|
1275
1515
|
presence: optional
|
|
@@ -1279,13 +1519,14 @@ slots:
|
|
|
1279
1519
|
requires: [index.ts]
|
|
1280
1520
|
# <name>.templates.ts holds the capability's static templates, copy and presets as TS constants (owner ruling 2026-10-13);
|
|
1281
1521
|
# user-editable ones are seed rows, large or user media live in object storage.
|
|
1282
|
-
allows: ["<capability>.module.ts", "<capability>.config.ts", "<capability>.options.ts", errors/, messages/, persistence/, policies/, "*.service.ts", "*.service.spec.ts", "*.contracts.ts", "*.templates.ts"]
|
|
1522
|
+
allows: ["<capability>.module.ts", "<capability>.module-definition.ts", "<capability>.config.ts", "<capability>.options.ts", errors/, messages/, persistence/, policies/, "*.service.ts", "*.service.spec.ts", "*.contracts.ts", "*.templates.ts"]
|
|
1283
1523
|
forbids: [testing/, exceptions/, utils/, helpers/, shared/]
|
|
1284
1524
|
tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
|
|
1285
1525
|
coverage: required
|
|
1286
1526
|
budget: {indexExports: 60}
|
|
1287
1527
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_PUBLIC_SURFACE, BE_ERROR_HOME, BE_CONFIG_OWNER, BE_CONTEXT_COUPLING, BE_CONTEXT_TRANSACTION]
|
|
1288
1528
|
- id: be.errors
|
|
1529
|
+
since: 3.1.0
|
|
1289
1530
|
profiles: [be]
|
|
1290
1531
|
path: "src/modules/{domain,integrations}/<capability>/errors/"
|
|
1291
1532
|
presence: optional
|
|
@@ -1318,7 +1559,9 @@ slots:
|
|
|
1318
1559
|
rules: [BE_USER_COPY_LITERAL]
|
|
1319
1560
|
since: 1.0.0
|
|
1320
1561
|
- id: be.persistence
|
|
1562
|
+
since: 3.1.0
|
|
1321
1563
|
profiles: [be]
|
|
1564
|
+
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): Supabase migrations own lite tables, with SQL and row types here; full keeps TypeORM entities and migrations here."
|
|
1322
1565
|
path: "src/modules/{domain,platform,projections}/<capability>/persistence/"
|
|
1323
1566
|
# R178: the platform capabilities whose tables exist on EVERY connection that uses them (the event-bus outbox, the inbox of every service, the job table); their arrays may be
|
|
1324
1567
|
# registered on several declared connections. Any other capability registers its arrays on exactly one (BE_SCHEMA_OWNER).
|
|
@@ -1339,6 +1582,7 @@ slots:
|
|
|
1339
1582
|
coverage: none
|
|
1340
1583
|
rules: [BE_SCHEMA_OWNER, BE_SCHEMA_AUTHORITY, BE_SQL_OUTSIDE_PERSISTENCE, BE_SQL_TABLE_OWNER, BE_SQL_RETURNING_SHAPE, BE_CONTEXT_COUPLING, BE_CONTEXT_PLATFORM_TABLES]
|
|
1341
1584
|
- id: be.platform
|
|
1585
|
+
since: 3.1.0
|
|
1342
1586
|
profiles: [be]
|
|
1343
1587
|
path: "src/modules/platform/<capability>/"
|
|
1344
1588
|
presence: required
|
|
@@ -1361,7 +1605,11 @@ slots:
|
|
|
1361
1605
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE]
|
|
1362
1606
|
# ----- BE: pattern slots (opt-in; enabled by hfs.json sides.be.patterns). A path under one is HFS_SLOT_NOT_ENABLED unless its pattern is declared.
|
|
1363
1607
|
- id: be.platform.event-bus
|
|
1608
|
+
since: 3.1.0
|
|
1364
1609
|
profiles: [be]
|
|
1610
|
+
exemplifiedBy:
|
|
1611
|
+
- {path: tests/fixtures/hfs-lite-slots/be/src/modules/platform/event-bus/, edition: lite}
|
|
1612
|
+
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."
|
|
1365
1613
|
path: "src/modules/platform/event-bus/"
|
|
1366
1614
|
presence: opt-in
|
|
1367
1615
|
pattern: event-bus # the ONE durable event transport: transactional outbox, relay, Kafka, inbox, retry and dead letter (knowledge/patterns/be/event-bus.yaml)
|
|
@@ -1380,7 +1628,9 @@ slots:
|
|
|
1380
1628
|
budget: {indexExports: 60}
|
|
1381
1629
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE, BE_INFRA_OWNER]
|
|
1382
1630
|
- id: be.platform.queue
|
|
1631
|
+
since: 3.1.0
|
|
1383
1632
|
profiles: [be]
|
|
1633
|
+
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite implements queue ports with pgmq and pg_cron; full uses BullMQ and Redis."
|
|
1384
1634
|
path: "src/modules/platform/queue/"
|
|
1385
1635
|
presence: opt-in
|
|
1386
1636
|
pattern: queue # the ONE BullMQ owner: the outbox relay into queues (jobId = outbox id) and job schedulers (knowledge/patterns/be/queues.yaml)
|
|
@@ -1399,6 +1649,7 @@ slots:
|
|
|
1399
1649
|
budget: {indexExports: 60}
|
|
1400
1650
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE, BE_INFRA_OWNER]
|
|
1401
1651
|
- id: be.events
|
|
1652
|
+
since: 3.1.0
|
|
1402
1653
|
profiles: [be]
|
|
1403
1654
|
path: "src/modules/events/<service>/"
|
|
1404
1655
|
presence: opt-in
|
|
@@ -1412,6 +1663,7 @@ slots:
|
|
|
1412
1663
|
coverage: none
|
|
1413
1664
|
rules: [BE_EVENT_CLASS_CONTRACT, BE_TIER_DIRECTION]
|
|
1414
1665
|
- id: be.queues
|
|
1666
|
+
since: 3.1.0
|
|
1415
1667
|
profiles: [be]
|
|
1416
1668
|
path: "src/modules/queues/<queue>/"
|
|
1417
1669
|
presence: opt-in
|
|
@@ -1420,11 +1672,12 @@ slots:
|
|
|
1420
1672
|
tier: queues
|
|
1421
1673
|
owner: true
|
|
1422
1674
|
requires: [index.ts, "<queue>.queue.ts"] # the module exists when a domain module imports the producer; a scheduler-only queue has none
|
|
1423
|
-
allows: ["<queue>.queue.ts", "<queue>.module.ts", "<queue>.contracts.ts", index.ts]
|
|
1675
|
+
allows: ["<queue>.queue.ts", "<queue>.module.ts", "<queue>.module-definition.ts", "<queue>.options.ts", "<queue>.contracts.ts", index.ts]
|
|
1424
1676
|
tests: none
|
|
1425
1677
|
coverage: none
|
|
1426
1678
|
rules: [BE_OUTBOX_WRITE_TX, BE_TIER_DIRECTION]
|
|
1427
1679
|
- id: be.feature.reactors
|
|
1680
|
+
since: 3.1.0
|
|
1428
1681
|
profiles: [be]
|
|
1429
1682
|
path: "src/features/reactors/<reactor>/"
|
|
1430
1683
|
presence: opt-in
|
|
@@ -1434,11 +1687,12 @@ slots:
|
|
|
1434
1687
|
tier: feature
|
|
1435
1688
|
owner: true
|
|
1436
1689
|
requires: [index.ts, "<reactor>.module.ts", transport/]
|
|
1437
|
-
allows: [index.ts, "<reactor>.module.ts", application/, transport/]
|
|
1690
|
+
allows: [index.ts, "<reactor>.module.ts", "<reactor>.module-definition.ts", "<reactor>.options.ts", application/, transport/]
|
|
1438
1691
|
tests: none
|
|
1439
1692
|
coverage: none
|
|
1440
1693
|
rules: [BE_KIND_ISOLATION, BE_FEATURE_IMPORTS_FEATURE, BE_FEATURE_NOT_COMPOSED]
|
|
1441
1694
|
- id: be.feature.jobs
|
|
1695
|
+
since: 3.1.0
|
|
1442
1696
|
profiles: [be]
|
|
1443
1697
|
path: "src/features/jobs/<job>/"
|
|
1444
1698
|
presence: opt-in
|
|
@@ -1448,11 +1702,12 @@ slots:
|
|
|
1448
1702
|
tier: feature
|
|
1449
1703
|
owner: true
|
|
1450
1704
|
requires: [index.ts, "<job>.module.ts", steps/, transport/]
|
|
1451
|
-
allows: [index.ts, "<job>.module.ts", application/, steps/, transport/]
|
|
1705
|
+
allows: [index.ts, "<job>.module.ts", "<job>.module-definition.ts", "<job>.options.ts", application/, steps/, transport/]
|
|
1452
1706
|
tests: none
|
|
1453
1707
|
coverage: none
|
|
1454
1708
|
rules: [BE_KIND_ISOLATION, BE_FEATURE_IMPORTS_FEATURE, BE_FEATURE_NOT_COMPOSED]
|
|
1455
1709
|
- id: be.feature.jobs.queue
|
|
1710
|
+
since: 3.1.0
|
|
1456
1711
|
profiles: [be]
|
|
1457
1712
|
path: "src/features/jobs/<job>/transport/queue/"
|
|
1458
1713
|
presence: opt-in # the queue transport of the job: its module and the processor the job runner hands the fenced claim to
|
|
@@ -1460,12 +1715,13 @@ slots:
|
|
|
1460
1715
|
tracked: tracked
|
|
1461
1716
|
tier: feature
|
|
1462
1717
|
requires: ["<job>-queue.module.ts", "<job>.processor.ts"]
|
|
1463
|
-
allows: ["<job>-queue.module.ts", "<job>.processor.ts"]
|
|
1718
|
+
allows: ["<job>-queue.module.ts", "<job>-queue.module-definition.ts", "<job>-queue.options.ts", "<job>.processor.ts"]
|
|
1464
1719
|
tests: none
|
|
1465
1720
|
coverage: none
|
|
1466
1721
|
composedBy: [api, worker]
|
|
1467
1722
|
rules: [BE_BACKGROUND_UNOWNED]
|
|
1468
1723
|
- id: be.feature.jobs.steps
|
|
1724
|
+
since: 3.1.0
|
|
1469
1725
|
profiles: [be]
|
|
1470
1726
|
path: "src/features/jobs/<job>/steps/"
|
|
1471
1727
|
presence: opt-in
|
|
@@ -1477,7 +1733,9 @@ slots:
|
|
|
1477
1733
|
coverage: none
|
|
1478
1734
|
rules: [BE_JOB_SHAPE, BE_JOB_RUN_KEY]
|
|
1479
1735
|
- id: be.platform.jobs
|
|
1736
|
+
since: 3.1.0
|
|
1480
1737
|
profiles: [be]
|
|
1738
|
+
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite persists fenced claims in Supabase migrations and dispatches through pgmq; full uses TypeORM schema authority and its queue adapter."
|
|
1481
1739
|
path: "src/modules/platform/jobs/"
|
|
1482
1740
|
presence: opt-in
|
|
1483
1741
|
pattern: fenced-job # the ONE fencing primitive: the job entity, the claim that bumps the token, guarded writes (knowledge/patterns/be/jobs.yaml)
|
|
@@ -1490,7 +1748,11 @@ slots:
|
|
|
1490
1748
|
budget: {indexExports: 60}
|
|
1491
1749
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE, BE_JOB_WRITE_OUTSIDE_OWNER, BE_JOB_FENCE_REQUIRED]
|
|
1492
1750
|
- id: be.projections
|
|
1751
|
+
since: 3.1.0
|
|
1493
1752
|
profiles: [be]
|
|
1753
|
+
exemplifiedBy:
|
|
1754
|
+
- {path: tests/fixtures/hfs-lite-slots/be/src/modules/projections/order-summary/, edition: lite}
|
|
1755
|
+
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."
|
|
1494
1756
|
path: "src/modules/projections/<name>/"
|
|
1495
1757
|
presence: opt-in
|
|
1496
1758
|
pattern: projection # a read model: only its projection writes its table; recompute is idempotent and replayable; readers use get*
|
|
@@ -1506,6 +1768,7 @@ slots:
|
|
|
1506
1768
|
coverage: required
|
|
1507
1769
|
rules: [BE_PROJECTION_WRITE_OWNER, BE_PROJECTION_SHAPE, BE_TIER_DIRECTION, BE_CONTEXT_COUPLING]
|
|
1508
1770
|
- id: be.integrations
|
|
1771
|
+
since: 3.1.0
|
|
1509
1772
|
profiles: [be]
|
|
1510
1773
|
path: "src/modules/integrations/<provider>/"
|
|
1511
1774
|
presence: optional
|
|
@@ -1515,9 +1778,10 @@ slots:
|
|
|
1515
1778
|
requires: [index.ts, "<provider>.config.ts"]
|
|
1516
1779
|
tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts; plus src/tests/integration/<provider>/ (R112)
|
|
1517
1780
|
coverage: required
|
|
1518
|
-
budget: {indexExports:
|
|
1781
|
+
budget: {indexExports: 80} # a provider's port surface includes clients, config, options, tokens and errors
|
|
1519
1782
|
rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT, BE_INTEGRATION_SPEC_MISSING]
|
|
1520
1783
|
- id: be.integrations.model
|
|
1784
|
+
since: 3.1.0
|
|
1521
1785
|
profiles: [be]
|
|
1522
1786
|
path: "src/modules/integrations/<provider>/model/"
|
|
1523
1787
|
presence: opt-in
|
|
@@ -1526,8 +1790,14 @@ slots:
|
|
|
1526
1790
|
tests: none
|
|
1527
1791
|
coverage: required
|
|
1528
1792
|
why: an ML or LLM model client is an integration; weights and datasets are never in git
|
|
1793
|
+
rules: [BE_TIER_DIRECTION, BE_INFRA_OWNER, BE_INTEGRATION_SPEC_MISSING]
|
|
1794
|
+
|
|
1529
1795
|
- id: be.integrations.supabase
|
|
1530
1796
|
profiles: [be]
|
|
1797
|
+
exemplifiedBy:
|
|
1798
|
+
- 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
|
+
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."
|
|
1531
1801
|
# The only be import of @supabase/* (L10): the Auth admin and Storage admin client, plus the JWKS-based verification of
|
|
1532
1802
|
# the Supabase access token the guard chain reads (L16). The connection itself is ordinary Postgres (the shared
|
|
1533
1803
|
# EntityManager of the declared connection); what lives here are the service calls no database role covers.
|
|
@@ -1549,7 +1819,11 @@ slots:
|
|
|
1549
1819
|
# lives under src/tests/, one folder per kind, and the folder and the file suffix always agree (a file whose suffix does
|
|
1550
1820
|
# not match its folder matches no slot: HFS_SLOT_UNDECLARED).
|
|
1551
1821
|
- id: be.tests.world
|
|
1822
|
+
since: 3.1.0
|
|
1552
1823
|
profiles: [be]
|
|
1824
|
+
exemplifiedBy:
|
|
1825
|
+
- {path: tests/fixtures/hfs-lite-slots/be/src/tests/world/, edition: lite}
|
|
1826
|
+
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."
|
|
1553
1827
|
path: "src/tests/world/"
|
|
1554
1828
|
presence: optional
|
|
1555
1829
|
tracked: tracked
|
|
@@ -1573,7 +1847,10 @@ slots:
|
|
|
1573
1847
|
failNext/replayWebhook/delay), world.waitFor; fakes/<provider>/ holds the fake servers and payload fixtures, kit/ the inlined test helpers, and the world root may hold role-suffixed helpers (<name>.client.ts, <name>.contracts.ts, ...). Nothing is
|
|
1574
1848
|
overridden in the DI container. It is the only test location that may import typeorm's DataSource or testcontainers,
|
|
1575
1849
|
call migrate/runMigrations/synchronize, or write process.env.
|
|
1850
|
+
rules: [BE_TEST_TOPOLOGY, BE_TEST_CONSTRAINTS_ON]
|
|
1851
|
+
|
|
1576
1852
|
- id: be.tests.world.kit
|
|
1853
|
+
since: 3.1.0
|
|
1577
1854
|
profiles: [be]
|
|
1578
1855
|
path: "src/tests/world/kit/"
|
|
1579
1856
|
presence: optional
|
|
@@ -1584,6 +1861,7 @@ slots:
|
|
|
1584
1861
|
coverage: none
|
|
1585
1862
|
why: the helpers the test world needs (polling, free ports, transports), inlined so the world imports no other repository; a bare <name>.ts entry in allows is what admits a plain name in BE_SOURCE_FORM
|
|
1586
1863
|
- id: be.tests.fixtures
|
|
1864
|
+
since: 3.1.0
|
|
1587
1865
|
profiles: [be]
|
|
1588
1866
|
path: "src/tests/fixtures/"
|
|
1589
1867
|
presence: optional
|
|
@@ -1594,6 +1872,7 @@ slots:
|
|
|
1594
1872
|
coverage: none
|
|
1595
1873
|
why: payload fixtures with a role suffix (the doubles come from @starci/jest-preset, not from here); the test data builders live in its builders/ slot; importable by unit specs, integration and e2e; never imports a feature
|
|
1596
1874
|
- id: be.tests.fixtures.builders
|
|
1875
|
+
since: 3.1.0
|
|
1597
1876
|
profiles: [be]
|
|
1598
1877
|
path: "src/tests/fixtures/builders/*.builder.ts"
|
|
1599
1878
|
presence: optional
|
|
@@ -1608,6 +1887,7 @@ slots:
|
|
|
1608
1887
|
*.fixture.ts or *.factory.ts anywhere, is a BE_SOURCE_FORM finding
|
|
1609
1888
|
rules: [BE_SOURCE_FORM]
|
|
1610
1889
|
- id: be.tests.fixtures.i18n
|
|
1890
|
+
since: 3.1.0
|
|
1611
1891
|
profiles: [be]
|
|
1612
1892
|
path: "src/tests/fixtures/i18n/"
|
|
1613
1893
|
presence: optional
|
|
@@ -1618,7 +1898,9 @@ slots:
|
|
|
1618
1898
|
coverage: none
|
|
1619
1899
|
why: the ONLY test-fixture location where Vietnamese or another language may appear in source (LANG_NOT_ENGLISH); placement is the marker, there is no comment pragma
|
|
1620
1900
|
- id: be.tests.integration
|
|
1901
|
+
since: 3.1.0
|
|
1621
1902
|
profiles: [be]
|
|
1903
|
+
editionWhy: "Lite integration specs use the local Supabase CLI stack without containers or toxiproxy (ruling:R212/case-1, amended by ruling:R212/case-2 (owner:lite-production-swarm) and ruling:R212/case-4, with contracts allowed by owner:lite-contract-specs); R284 requires declared-pattern proofs there. Full additionally proves infrastructure outages through its world (R112)."
|
|
1622
1904
|
path: "src/tests/integration/<capability>/*.integration-spec.ts"
|
|
1623
1905
|
presence: optional
|
|
1624
1906
|
tracked: tracked
|
|
@@ -1632,10 +1914,12 @@ slots:
|
|
|
1632
1914
|
why: one capability module through useTestWorld({ modules }), no HTTP door of ours (SQL, transactions, concurrency, inbox claims on the real database; and every integration client, `<capability>` being its provider folder, against our real infra, a real peer app or the library fake at the network edge, with its success, its ErrorCode refusal mapping and an outage through the world, R112); run by test:integration
|
|
1633
1915
|
rules: [BE_TEST_TOPOLOGY, BE_INTEGRATION_SPEC_MISSING]
|
|
1634
1916
|
- id: be.tests.e2e
|
|
1917
|
+
since: 3.1.0
|
|
1635
1918
|
profiles: [be]
|
|
1919
|
+
editionWhy: "ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): full proves app flows through its e2e world; lite uses the R272 wiring proof and unit/integration specs."
|
|
1636
1920
|
path: "src/tests/e2e/<area>/*.e2e-spec.ts"
|
|
1637
1921
|
presence: optional
|
|
1638
|
-
litePresence: forbidden #
|
|
1922
|
+
litePresence: forbidden # ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4): lite has no e2e world
|
|
1639
1923
|
tracked: tracked
|
|
1640
1924
|
tier: e2e
|
|
1641
1925
|
tests: e2e
|
|
@@ -1644,18 +1928,20 @@ slots:
|
|
|
1644
1928
|
why: flow e2e through useTestWorld({ apps }); external services are the world's network fakes; run by test:e2e
|
|
1645
1929
|
rules: [HFS_CI_E2E_AUTOMATIC, BE_TEST_TOPOLOGY, BE_E2E_FLOW]
|
|
1646
1930
|
- id: be.tests.contract
|
|
1931
|
+
since: 3.1.0
|
|
1647
1932
|
profiles: [be]
|
|
1648
1933
|
path: "src/tests/contract/<provider>/*.contract-spec.ts"
|
|
1649
1934
|
presence: optional
|
|
1650
|
-
litePresence:
|
|
1935
|
+
litePresence: optional # owner decision 2026-10-14: lite may run provider sandbox contracts
|
|
1651
1936
|
tracked: tracked
|
|
1652
1937
|
tier: e2e
|
|
1653
1938
|
tests: e2e
|
|
1654
|
-
goesTo: "
|
|
1939
|
+
goesTo: "src/tests/contract/<provider>/*.contract-spec.ts in either edition: optional provider sandbox specs via test:contract; e2e stays full-only"
|
|
1655
1940
|
coverage: none
|
|
1656
1941
|
why: provider sandbox contracts, skipped without sandbox config; run only by test:contract, never part of test or test:e2e
|
|
1657
1942
|
rules: [BE_TEST_TOPOLOGY]
|
|
1658
1943
|
- id: be.tests.e2e-world-dir
|
|
1944
|
+
since: 3.1.0
|
|
1659
1945
|
profiles: [be]
|
|
1660
1946
|
path: "src/tests/e2e/world/"
|
|
1661
1947
|
presence: forbidden
|
|
@@ -1666,6 +1952,7 @@ slots:
|
|
|
1666
1952
|
|
|
1667
1953
|
# ----- FE: apps ----------------------------------------------------------------------------------------
|
|
1668
1954
|
- id: fe.app.next
|
|
1955
|
+
since: 3.1.0
|
|
1669
1956
|
profiles: [fe]
|
|
1670
1957
|
# Each fe app is an npm workspace: its package.json is @<project>/<app> with its own runtime dependencies and the fixed
|
|
1671
1958
|
# workspace scripts (HFS_MONO_FE_WORKSPACE); turbo runs them.
|
|
@@ -1680,6 +1967,7 @@ slots:
|
|
|
1680
1967
|
tests: none
|
|
1681
1968
|
rules: [FE_APP_ISOLATION, FE_ERROR_BOUNDARY_MISSING, FE_NEXT_CONVENTIONS, HFS_MONO_FE_WORKSPACE, HFS_MONO_WORKSPACE_DEP]
|
|
1682
1969
|
- id: fe.app-optional
|
|
1970
|
+
since: 3.1.0
|
|
1683
1971
|
profiles: [fe]
|
|
1684
1972
|
path: "apps/<app>/public/"
|
|
1685
1973
|
presence: optional
|
|
@@ -1687,12 +1975,13 @@ slots:
|
|
|
1687
1975
|
tier: none
|
|
1688
1976
|
tests: none
|
|
1689
1977
|
- id: fe.route
|
|
1978
|
+
since: 3.1.0
|
|
1690
1979
|
profiles: [fe]
|
|
1691
1980
|
path: "apps/<app>/src/app/"
|
|
1692
1981
|
presence: required
|
|
1693
1982
|
tracked: tracked
|
|
1694
1983
|
tier: route
|
|
1695
|
-
#
|
|
1984
|
+
# ruling:R212/case-2 (owner:lite-production-swarm) (with the Work boundary amended by ruling:R212/case-4) (lite = full): routes are thin and the front end serves no API - health/live is the one
|
|
1696
1985
|
# route handler of this slot, the Supabase auth callback is fe.route.callback, and no file holds a Server Action
|
|
1697
1986
|
# (FE_ROUTE_HANDLER_FORBIDDEN, FE_SERVER_ACTION_FORBIDDEN).
|
|
1698
1987
|
allows: ["[locale]/**/{page,layout,template,loading,error,not-found,default}.tsx", "[locale]/providers.tsx",
|
|
@@ -1705,6 +1994,7 @@ slots:
|
|
|
1705
1994
|
tests: none
|
|
1706
1995
|
rules: [FE_ROUTE_FILES_THIN, FE_CLIENT_BOUNDARY, FE_OWNER_REACHABLE, FE_ROUTE_HANDLER_FORBIDDEN, FE_SERVER_ACTION_FORBIDDEN]
|
|
1707
1996
|
- id: fe.source-root-pinned
|
|
1997
|
+
since: 3.1.0
|
|
1708
1998
|
profiles: [fe]
|
|
1709
1999
|
path: "apps/<app>/src/{proxy.ts,instrumentation.ts,instrumentation-client.ts,sentry.server.config.ts,sentry.edge.config.ts}"
|
|
1710
2000
|
presence: optional
|
|
@@ -1714,6 +2004,7 @@ slots:
|
|
|
1714
2004
|
tests: none
|
|
1715
2005
|
rules: [FE_NEXT_CONVENTIONS] # middleware.ts is refused on Next >= 16 (fe.source-root-middleware)
|
|
1716
2006
|
- id: fe.source-root-middleware
|
|
2007
|
+
since: 3.1.0
|
|
1717
2008
|
profiles: [fe]
|
|
1718
2009
|
path: "apps/<app>/src/{middleware.ts,middleware.js}"
|
|
1719
2010
|
presence: forbidden
|
|
@@ -1724,6 +2015,8 @@ slots:
|
|
|
1724
2015
|
rules: [FE_NEXT_CONVENTIONS]
|
|
1725
2016
|
- id: fe.route.callback
|
|
1726
2017
|
profiles: [fe]
|
|
2018
|
+
exemplifiedBy:
|
|
2019
|
+
- {path: tests/fixtures/hfs-callback-full/fe/apps/web/src/app/auth/callback/route.ts, edition: full}
|
|
1727
2020
|
# The auth callback of a Supabase session (the PKCE code exchange), kept only when the auth flow truly needs a server exchange
|
|
1728
2021
|
# (prefer the client-side PKCE exchange in fe.modules.auth): the one non-health route handler of an app with a Supabase
|
|
1729
2022
|
# connection; an app without one has no route handler in fe/ (L15).
|
|
@@ -1734,7 +2027,10 @@ slots:
|
|
|
1734
2027
|
tier: route
|
|
1735
2028
|
tests: none
|
|
1736
2029
|
since: 2.1.0
|
|
2030
|
+
rules: [FE_ROUTE_HANDLER_FORBIDDEN, FE_CALLBACK_SHAPE, FE_SUPABASE_CLIENT_OWNER, FE_SERVICE_ROLE_FORBIDDEN]
|
|
2031
|
+
|
|
1737
2032
|
- id: fe.feature
|
|
2033
|
+
since: 3.1.0
|
|
1738
2034
|
profiles: [fe]
|
|
1739
2035
|
path: "apps/<app>/src/features/{pages,layouts,overlays}/<name>/"
|
|
1740
2036
|
presence: required
|
|
@@ -1749,6 +2045,7 @@ slots:
|
|
|
1749
2045
|
budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
|
|
1750
2046
|
rules: [FE_SIZE_BUDGET, FE_OWNER_REACHABLE, FE_I18N_LITERAL]
|
|
1751
2047
|
- id: fe.components
|
|
2048
|
+
since: 3.1.0
|
|
1752
2049
|
profiles: [fe]
|
|
1753
2050
|
path: "apps/<app>/src/components/{blocks,composites,branches,leaves}/<name>/"
|
|
1754
2051
|
presence: optional
|
|
@@ -1779,6 +2076,7 @@ slots:
|
|
|
1779
2076
|
since: 3.0.0
|
|
1780
2077
|
rules: [FE_CLIENT_BOUNDARY]
|
|
1781
2078
|
- id: fe.hooks
|
|
2079
|
+
since: 3.1.0
|
|
1782
2080
|
profiles: [fe]
|
|
1783
2081
|
path: "apps/<app>/src/hooks/<domain>/"
|
|
1784
2082
|
presence: optional
|
|
@@ -1789,7 +2087,7 @@ slots:
|
|
|
1789
2087
|
allows: [index.ts, "use<name>.ts", "<domain>.shared.ts"] # React hooks only; <domain>.shared.ts = sanctioned non-hook helpers
|
|
1790
2088
|
roles: {entry: index.ts, shared: "<domain>.shared.ts"}
|
|
1791
2089
|
tests: none
|
|
1792
|
-
budget: {file: 200, dataHooks: 6, useState: 6}
|
|
2090
|
+
budget: {file: 200, barrel: 400, dataHooks: 6, useState: 6} # barrel: index.ts containing only export ... from statements
|
|
1793
2091
|
rules: [FE_HOOKS_ARE_HOOKS, FE_DATA_FRESHNESS]
|
|
1794
2092
|
- id: fe.hooks.swr
|
|
1795
2093
|
profiles: [fe]
|
|
@@ -1806,7 +2104,7 @@ slots:
|
|
|
1806
2104
|
"api/rest/queries/useGet*Swr.ts", "api/rest/mutations/use{Post,Put,Patch,Delete}*Swr.ts"]
|
|
1807
2105
|
roles: {entry: index.ts, effects: SwrSideEffects.tsx}
|
|
1808
2106
|
tests: none
|
|
1809
|
-
budget: {file: 200}
|
|
2107
|
+
budget: {file: 200, barrel: 400}
|
|
1810
2108
|
since: 3.0.0
|
|
1811
2109
|
rules: [FE_HOOKS_ARE_HOOKS, FE_DATA_FRESHNESS]
|
|
1812
2110
|
- id: fe.hooks.rhf
|
|
@@ -1822,7 +2120,7 @@ slots:
|
|
|
1822
2120
|
allows: [index.ts, "use*Form.ts"]
|
|
1823
2121
|
roles: {entry: index.ts}
|
|
1824
2122
|
tests: none
|
|
1825
|
-
budget: {file: 200}
|
|
2123
|
+
budget: {file: 200, barrel: 400}
|
|
1826
2124
|
since: 3.0.0
|
|
1827
2125
|
rules: [FE_HOOKS_ARE_HOOKS]
|
|
1828
2126
|
- id: fe.hooks.zustand
|
|
@@ -1837,7 +2135,7 @@ slots:
|
|
|
1837
2135
|
allows: [index.ts, store.ts, "use*.ts"]
|
|
1838
2136
|
roles: {entry: index.ts, store: store.ts}
|
|
1839
2137
|
tests: none
|
|
1840
|
-
budget: {file: 200}
|
|
2138
|
+
budget: {file: 200, barrel: 400}
|
|
1841
2139
|
since: 3.0.0
|
|
1842
2140
|
rules: [FE_HOOKS_ARE_HOOKS]
|
|
1843
2141
|
- id: fe.hooks.socketio
|
|
@@ -1853,7 +2151,7 @@ slots:
|
|
|
1853
2151
|
allows: [index.ts, sockets.ts, SocketIoSideEffects.tsx, connectionStore.ts, "enums/*.ts", "types/*.ts", "use*.ts"]
|
|
1854
2152
|
roles: {entry: index.ts, effects: SocketIoSideEffects.tsx}
|
|
1855
2153
|
tests: none
|
|
1856
|
-
budget: {file: 200}
|
|
2154
|
+
budget: {file: 200, barrel: 400}
|
|
1857
2155
|
since: 3.0.0
|
|
1858
2156
|
rules: [FE_HOOKS_ARE_HOOKS]
|
|
1859
2157
|
- id: fe.hooks.effects
|
|
@@ -1869,7 +2167,7 @@ slots:
|
|
|
1869
2167
|
allows: [index.ts, "use*.ts", "*SideEffect.tsx", "*SideEffects.tsx"]
|
|
1870
2168
|
roles: {entry: index.ts, effects: "*SideEffect.tsx"}
|
|
1871
2169
|
tests: none
|
|
1872
|
-
budget: {file: 200}
|
|
2170
|
+
budget: {file: 200, barrel: 400}
|
|
1873
2171
|
since: 3.0.0
|
|
1874
2172
|
rules: [FE_HOOKS_ARE_HOOKS]
|
|
1875
2173
|
- id: fe.hooks.reuseables
|
|
@@ -1884,7 +2182,7 @@ slots:
|
|
|
1884
2182
|
allows: [index.ts, "use*.ts"]
|
|
1885
2183
|
roles: {entry: index.ts}
|
|
1886
2184
|
tests: none
|
|
1887
|
-
budget: {file: 200}
|
|
2185
|
+
budget: {file: 200, barrel: 400}
|
|
1888
2186
|
since: 3.0.0
|
|
1889
2187
|
rules: [FE_HOOKS_ARE_HOOKS]
|
|
1890
2188
|
- id: fe.redux
|
|
@@ -1901,7 +2199,10 @@ slots:
|
|
|
1901
2199
|
tests: none
|
|
1902
2200
|
budget: {file: 400}
|
|
1903
2201
|
since: 3.0.0
|
|
2202
|
+
rules: [FE_SOURCE_FORM, FE_HOOKS_ARE_HOOKS, FE_OWNER_REACHABLE]
|
|
2203
|
+
|
|
1904
2204
|
- id: fe.modules
|
|
2205
|
+
since: 3.1.0
|
|
1905
2206
|
profiles: [fe]
|
|
1906
2207
|
path: "apps/<app>/src/modules/<capability>/"
|
|
1907
2208
|
presence: required
|
|
@@ -1909,11 +2210,13 @@ slots:
|
|
|
1909
2210
|
tier: modules
|
|
1910
2211
|
owner: true
|
|
1911
2212
|
requiredInstances: {capability: [config, i18n, routes]}
|
|
2213
|
+
# A populated fe.modules instance with no .ts or .tsx files (CSS/assets only) needs no index.ts.
|
|
1912
2214
|
requires: [index.ts]
|
|
1913
2215
|
tests: none
|
|
1914
2216
|
budget: {file: 400}
|
|
1915
2217
|
rules: [FE_ENV_OWNER, FE_TRANSPORT_OWNER]
|
|
1916
2218
|
- id: fe.modules.api
|
|
2219
|
+
since: 3.1.0
|
|
1917
2220
|
profiles: [fe]
|
|
1918
2221
|
path: "apps/<app>/src/modules/api/"
|
|
1919
2222
|
presence: optional
|
|
@@ -1933,7 +2236,20 @@ slots:
|
|
|
1933
2236
|
entries: ["graphql/queries/query-*.ts", "graphql/queries/types/*.ts", "graphql/mutations/mutation-*.ts", "graphql/mutations/types/*.ts"]
|
|
1934
2237
|
tests: none
|
|
1935
2238
|
rules: [FE_TRANSPORT_OWNER, FE_HTTP_STATUS_COLLAPSE, FE_WIRE_GENERATED]
|
|
2239
|
+
- id: fe.transport.generated
|
|
2240
|
+
since: 3.2.0
|
|
2241
|
+
profiles: [fe]
|
|
2242
|
+
path: "apps/<app>/src/modules/api/graphql/__generated__/"
|
|
2243
|
+
presence: optional
|
|
2244
|
+
tracked: ignored
|
|
2245
|
+
tier: transport
|
|
2246
|
+
tests: none
|
|
2247
|
+
why: the canonical GraphQL client output of codegen is local and never committed; this specific slot overrides the tracked API parent
|
|
2248
|
+
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]}
|
|
1936
2251
|
- id: fe.transport.client
|
|
2252
|
+
since: 3.1.0
|
|
1937
2253
|
profiles: [fe]
|
|
1938
2254
|
path: "apps/<app>/src/modules/api/graphql/clients/"
|
|
1939
2255
|
presence: optional
|
|
@@ -1943,6 +2259,7 @@ slots:
|
|
|
1943
2259
|
why: the auth and no-auth Apollo clients and their link chain (retry, error, timeout, token refresh, attach access token, http) of the one app that calls the back end; when several apps do, the shared client is fe.package.api.client
|
|
1944
2260
|
rules: [FE_TRANSPORT_OWNER]
|
|
1945
2261
|
- id: fe.transport.outcome
|
|
2262
|
+
since: 3.1.0
|
|
1946
2263
|
profiles: [fe]
|
|
1947
2264
|
path: "apps/<app>/src/modules/api/graphql/types.ts"
|
|
1948
2265
|
presence: optional
|
|
@@ -1953,6 +2270,7 @@ slots:
|
|
|
1953
2270
|
why: the one GraphQLResponse union ({success, message, error, data}) of the one app that calls the back end; when several apps do, the shared union is fe.package.api.outcome
|
|
1954
2271
|
rules: [FE_HTTP_STATUS_COLLAPSE]
|
|
1955
2272
|
- id: fe.modules.config
|
|
2273
|
+
since: 3.1.0
|
|
1956
2274
|
profiles: [fe]
|
|
1957
2275
|
path: "apps/<app>/src/modules/config/"
|
|
1958
2276
|
presence: required
|
|
@@ -1960,7 +2278,10 @@ slots:
|
|
|
1960
2278
|
tier: foundation
|
|
1961
2279
|
tests: none
|
|
1962
2280
|
why: the only reader of process.env and NEXT_PUBLIC_*; fails fast, no localhost fallback
|
|
2281
|
+
rules: [FE_ENV_OWNER]
|
|
2282
|
+
|
|
1963
2283
|
- id: fe.modules.i18n
|
|
2284
|
+
since: 3.1.0
|
|
1964
2285
|
profiles: [fe]
|
|
1965
2286
|
path: "apps/<app>/src/modules/i18n/"
|
|
1966
2287
|
presence: required
|
|
@@ -1973,6 +2294,7 @@ slots:
|
|
|
1973
2294
|
tests: none
|
|
1974
2295
|
rules: [FE_I18N_PLACEMENT, FE_I18N_CATALOG]
|
|
1975
2296
|
- id: fe.modules.brand
|
|
2297
|
+
since: 3.1.0
|
|
1976
2298
|
profiles: [fe]
|
|
1977
2299
|
path: "apps/<app>/src/modules/brand/brand.css"
|
|
1978
2300
|
presence: optional
|
|
@@ -1982,6 +2304,7 @@ slots:
|
|
|
1982
2304
|
why: the ONLY app file that may hold colour values; sets grammar brand tokens only, light and dark
|
|
1983
2305
|
rules: [FE_STYLE_TOKEN_ONLY]
|
|
1984
2306
|
- id: fe.modules.routes
|
|
2307
|
+
since: 3.1.0
|
|
1985
2308
|
profiles: [fe]
|
|
1986
2309
|
path: "apps/<app>/src/modules/routes/"
|
|
1987
2310
|
presence: required
|
|
@@ -1989,7 +2312,10 @@ slots:
|
|
|
1989
2312
|
tier: foundation
|
|
1990
2313
|
tests: none
|
|
1991
2314
|
why: every href builder; FE_HREF_RESOLVES checks each against app/
|
|
2315
|
+
rules: [FE_HREF_RESOLVES, FE_ROUTE_HARDCODED]
|
|
2316
|
+
|
|
1992
2317
|
- id: fe.modules.types
|
|
2318
|
+
since: 3.1.0
|
|
1993
2319
|
profiles: [fe]
|
|
1994
2320
|
path: "apps/<app>/src/modules/types/"
|
|
1995
2321
|
presence: optional
|
|
@@ -1997,6 +2323,8 @@ slots:
|
|
|
1997
2323
|
tier: foundation
|
|
1998
2324
|
tests: none
|
|
1999
2325
|
why: shared types with no runtime behaviour; a component may import them
|
|
2326
|
+
rules: [FE_TYPE_ESCAPE, HFS_TS_STRICT]
|
|
2327
|
+
|
|
2000
2328
|
- id: fe.modules.types.ambient
|
|
2001
2329
|
parent: fe.modules.types
|
|
2002
2330
|
profiles: [fe]
|
|
@@ -2009,6 +2337,8 @@ slots:
|
|
|
2009
2337
|
tests: none
|
|
2010
2338
|
since: 2.2.0
|
|
2011
2339
|
why: global ambient declarations loaded by the app compiler; this owner exposes no runtime API
|
|
2340
|
+
rules: [FE_TYPE_ESCAPE, HFS_TS_STRICT]
|
|
2341
|
+
|
|
2012
2342
|
- id: fe.modules.auth
|
|
2013
2343
|
profiles: [fe]
|
|
2014
2344
|
# The identity-provider client (fe-stack auth, FE-AUTH): the session, the access token the Apollo link attaches and the
|
|
@@ -2020,6 +2350,7 @@ slots:
|
|
|
2020
2350
|
tier: modules
|
|
2021
2351
|
owner: true
|
|
2022
2352
|
requires: [index.ts]
|
|
2353
|
+
entries: [server.ts] # the server-only PKCE exchange API used by the opted-in callback route
|
|
2023
2354
|
tests: none
|
|
2024
2355
|
since: 3.0.0
|
|
2025
2356
|
rules: [FE_SUPABASE_CLIENT_OWNER, FE_SERVICE_ROLE_FORBIDDEN]
|
|
@@ -2037,7 +2368,10 @@ slots:
|
|
|
2037
2368
|
roles: {entry: index.ts, hooks: hooks.ts} # hooks.ts: the use*WithToast hooks of the toast module
|
|
2038
2369
|
tests: none
|
|
2039
2370
|
since: 3.0.0
|
|
2371
|
+
rules: [FE_I18N_LITERAL, FE_HOOKS_ARE_HOOKS, FE_HTTP_STATUS_COLLAPSE]
|
|
2372
|
+
|
|
2040
2373
|
- id: fe.package.ui
|
|
2374
|
+
since: 3.1.0
|
|
2041
2375
|
profiles: [fe]
|
|
2042
2376
|
path: "packages/<family>-ui/"
|
|
2043
2377
|
presence: opt-in
|
|
@@ -2052,6 +2386,7 @@ slots:
|
|
|
2052
2386
|
why: built to dist with explicit named exports; only composites, branches and leaves; no unused export; no colour literal
|
|
2053
2387
|
rules: [FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT, FE_STYLE_TOKEN_ONLY]
|
|
2054
2388
|
- id: fe.package.api
|
|
2389
|
+
since: 3.1.0
|
|
2055
2390
|
profiles: [fe]
|
|
2056
2391
|
path: "packages/<family>-api/"
|
|
2057
2392
|
presence: opt-in
|
|
@@ -2063,6 +2398,7 @@ slots:
|
|
|
2063
2398
|
why: the one transport client and the one Outcome union shared by every app of the repository; no app keeps its own
|
|
2064
2399
|
rules: [FE_TRANSPORT_OWNER, FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
|
|
2065
2400
|
- id: fe.package.api.client
|
|
2401
|
+
since: 3.1.0
|
|
2066
2402
|
profiles: [fe]
|
|
2067
2403
|
path: "packages/<family>-api/src/graphql/clients/"
|
|
2068
2404
|
presence: optional # enabled with its package (fe.package.api is the opt-in)
|
|
@@ -2072,6 +2408,7 @@ slots:
|
|
|
2072
2408
|
why: the repository's one pair of Apollo clients and their link chain
|
|
2073
2409
|
rules: [FE_TRANSPORT_OWNER]
|
|
2074
2410
|
- id: fe.package.api.outcome
|
|
2411
|
+
since: 3.1.0
|
|
2075
2412
|
profiles: [fe]
|
|
2076
2413
|
path: "packages/<family>-api/src/graphql/types.ts"
|
|
2077
2414
|
presence: optional # enabled with its package (fe.package.api is the opt-in)
|
|
@@ -2082,6 +2419,7 @@ slots:
|
|
|
2082
2419
|
why: the repository's one GraphQLResponse union
|
|
2083
2420
|
rules: [FE_HTTP_STATUS_COLLAPSE]
|
|
2084
2421
|
- id: fe.package.i18n
|
|
2422
|
+
since: 3.1.0
|
|
2085
2423
|
profiles: [fe]
|
|
2086
2424
|
path: "packages/<family>-i18n/"
|
|
2087
2425
|
presence: opt-in
|