@starci/eslint-canon-fe 6.0.2 → 7.1.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.
Files changed (117) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +3 -5
  3. package/client-boundary.mjs +2 -5
  4. package/comments.mjs +38 -15
  5. package/docs/brand-values.md +2 -2
  6. package/docs/client-boundary.md +10 -10
  7. package/docs/env-owner.md +4 -4
  8. package/docs/formatting.md +2 -2
  9. package/docs/grammar-boundary.md +2 -2
  10. package/docs/hooks-folder.md +2 -2
  11. package/docs/hygiene.md +8 -8
  12. package/docs/lint-escape-hatch.md +2 -2
  13. package/docs/lists.md +6 -6
  14. package/docs/native-controls.md +6 -6
  15. package/docs/next-conventions.md +20 -20
  16. package/docs/size-and-state-budget.md +6 -6
  17. package/docs/size-growth.md +2 -2
  18. package/docs/status-colors.md +2 -2
  19. package/docs/translation.md +2 -2
  20. package/docs/transport.md +18 -18
  21. package/docs/type-safety.md +8 -8
  22. package/env-owner.mjs +2 -3
  23. package/file-layout.mjs +3 -43
  24. package/formatting.mjs +2 -2
  25. package/grammar-boundary.mjs +1 -2
  26. package/hooks-folder.mjs +6 -7
  27. package/index.mjs +5 -27
  28. package/lib/config.mjs +6 -22
  29. package/lib/scope.mjs +3 -12
  30. package/lib/why.mjs +3 -68
  31. package/lint-escape-hatch.mjs +3 -3
  32. package/lists.mjs +0 -4
  33. package/loading.mjs +2 -2
  34. package/native-controls.mjs +0 -4
  35. package/next-conventions.mjs +3 -7
  36. package/package.json +1 -1
  37. package/project-graph.mjs +23 -0
  38. package/props-and-slots.mjs +4 -5
  39. package/runtime/knowledge/hfs/slots.yaml +145 -107
  40. package/runtime/knowledge/patterns/fe/folder.yaml +309 -0
  41. package/runtime/scripts/checks/architecture/backend.mjs +350 -0
  42. package/runtime/scripts/checks/architecture/background-unowned.mjs +107 -0
  43. package/runtime/scripts/checks/architecture/client-reaches-server.mjs +94 -0
  44. package/runtime/scripts/checks/architecture/clones.mjs +200 -0
  45. package/runtime/scripts/checks/architecture/config-unread.mjs +35 -0
  46. package/runtime/scripts/checks/architecture/config.mjs +310 -0
  47. package/runtime/scripts/checks/architecture/connection-map.mjs +208 -0
  48. package/runtime/scripts/checks/architecture/constructor-deps.mjs +100 -0
  49. package/runtime/scripts/checks/architecture/contract-fixture-guard.mjs +128 -0
  50. package/runtime/scripts/checks/architecture/contracts.mjs +792 -0
  51. package/runtime/scripts/checks/architecture/cross-app-duplicate.mjs +73 -0
  52. package/runtime/scripts/checks/architecture/dead-exports.mjs +265 -0
  53. package/runtime/scripts/checks/architecture/default-deny.mjs +129 -0
  54. package/runtime/scripts/checks/architecture/doc-language.mjs +39 -0
  55. package/runtime/scripts/checks/architecture/entrypoint.mjs +57 -0
  56. package/runtime/scripts/checks/architecture/error-codes.mjs +45 -0
  57. package/runtime/scripts/checks/architecture/error-masked.mjs +60 -0
  58. package/runtime/scripts/checks/architecture/fe-slot-allows.mjs +38 -0
  59. package/runtime/scripts/checks/architecture/feature-shape.mjs +49 -0
  60. package/runtime/scripts/checks/architecture/framework-pinned.mjs +83 -0
  61. package/runtime/scripts/checks/architecture/frontend.mjs +995 -0
  62. package/runtime/scripts/checks/architecture/hfs-graph.mjs +61 -0
  63. package/runtime/scripts/checks/architecture/hfs.mjs +521 -0
  64. package/runtime/scripts/checks/architecture/hooks-are-hooks.mjs +88 -0
  65. package/runtime/scripts/checks/architecture/i18n-keys.mjs +146 -0
  66. package/runtime/scripts/checks/architecture/index.mjs +321 -0
  67. package/runtime/scripts/checks/architecture/injection-token-exported.mjs +62 -0
  68. package/runtime/scripts/checks/architecture/machine-ast.mjs +138 -0
  69. package/runtime/scripts/checks/architecture/module-per-transport.mjs +130 -0
  70. package/runtime/scripts/checks/architecture/next-data.mjs +775 -0
  71. package/runtime/scripts/checks/architecture/owners.mjs +95 -0
  72. package/runtime/scripts/checks/architecture/package-shape.mjs +63 -0
  73. package/runtime/scripts/checks/architecture/reachability.mjs +233 -0
  74. package/runtime/scripts/checks/architecture/register-once.mjs +141 -0
  75. package/runtime/scripts/checks/architecture/registration.mjs +319 -0
  76. package/runtime/scripts/checks/architecture/required-files.mjs +172 -0
  77. package/runtime/scripts/checks/architecture/route-files-thin.mjs +97 -0
  78. package/runtime/scripts/checks/architecture/schema-owner.mjs +261 -0
  79. package/runtime/scripts/checks/architecture/source-names.mjs +607 -0
  80. package/runtime/scripts/checks/architecture/sql-owner.mjs +142 -0
  81. package/runtime/scripts/checks/architecture/sql-tokens.mjs +327 -0
  82. package/runtime/scripts/checks/architecture/surface.mjs +91 -0
  83. package/runtime/scripts/checks/architecture/symbols.mjs +193 -0
  84. package/runtime/scripts/checks/architecture/test-world-files.mjs +163 -0
  85. package/runtime/scripts/checks/architecture/tiers.mjs +130 -0
  86. package/runtime/scripts/checks/architecture/transport-owner.mjs +112 -0
  87. package/runtime/scripts/checks/architecture/typescript.mjs +573 -0
  88. package/runtime/scripts/checks/architecture/unit-spec-providers.mjs +122 -0
  89. package/runtime/scripts/checks/common.mjs +44 -0
  90. package/runtime/scripts/checks/typescript-programs.mjs +82 -0
  91. package/runtime/scripts/lib/hfs-allows.mjs +15 -0
  92. package/runtime/scripts/lib/hfs-path-findings.mjs +84 -0
  93. package/runtime/scripts/lib/hfs-rules/fe-no-tests.mjs +47 -0
  94. package/runtime/scripts/lib/hfs-rules/read.mjs +28 -0
  95. package/runtime/scripts/lib/hfs-rules/secrets.mjs +54 -0
  96. package/runtime/scripts/lib/hfs-rules/spec-placement.mjs +29 -0
  97. package/runtime/scripts/lib/hfs-slots.mjs +13 -6
  98. package/runtime/scripts/lib/json.mjs +22 -0
  99. package/runtime/scripts/lib/language.mjs +107 -0
  100. package/runtime/scripts/lib/project-graph.mjs +0 -0
  101. package/runtime/scripts/lib/project-rule.mjs +39 -0
  102. package/runtime/scripts/lib/repo-identity.mjs +50 -0
  103. package/runtime/scripts/lib/secret-patterns.mjs +44 -0
  104. package/runtime/scripts/lib/stack-services.mjs +217 -0
  105. package/runtime/scripts/lib/test-secrets.mjs +120 -0
  106. package/served-locale.mjs +2 -2
  107. package/shape-slot.mjs +2 -3
  108. package/size-and-state-budget.mjs +1 -4
  109. package/size-growth.mjs +1 -2
  110. package/status-colors.mjs +0 -2
  111. package/tokens.mjs +1 -1
  112. package/translation.mjs +1 -29
  113. package/transport.mjs +13 -17
  114. package/docs/e2e-shape.md +0 -161
  115. package/docs/spec-quality.md +0 -147
  116. package/e2e-shape.mjs +0 -345
  117. package/spec-quality.mjs +0 -318
@@ -84,7 +84,6 @@ tiers:
84
84
  foundation: {mayImport: [foundation, package], acyclic: true} # modules/{config,i18n,routes,types}
85
85
  modules: {mayImport: [foundation, modules, transport, package], acyclic: true}
86
86
  transport: {mayImport: [foundation, modules, package]} # modules/api: the one fetch
87
- e2e: {mayImport: [e2e, package]} # black box: no app source
88
87
  package: {mayImport: [package]}
89
88
  crossOwner: every import across owners targets the owner's public entry (index.ts or index.tsx); never export *.
90
89
  crossApp: apps never import each other; shared code is a packages/<pkg> slot.
@@ -109,6 +108,7 @@ ruleParams:
109
108
  http: [platform/http]
110
109
  https: [platform/http]
111
110
  cache-manager: [integrations/cache, integrations/redis]
111
+ "@nestjs/cache-manager": [integrations/cache, integrations/redis] # the CACHE_MANAGER token: caching goes through the cache capability (retired local rule must-use-cache-service)
112
112
  redis: [integrations/cache, integrations/redis]
113
113
  ioredis: [integrations/cache, integrations/redis]
114
114
  bullmq: [platform/messaging]
@@ -126,11 +126,31 @@ ruleParams:
126
126
  "@nestjs/config": []
127
127
  dotenv: []
128
128
  "@nestjs/event-emitter": []
129
+ # R48 BE_SPEC_QUALITY spec-infra-double-from-kit: in a `<name>.service.spec.ts` a provider `{ provide: TOKEN, useValue: X }` takes X from
130
+ # the kit named here (the package root of `kit`). The token name is normalised to UPPER_SNAKE (`EntityManager` -> ENTITY_MANAGER), the
131
+ # FIRST entry whose `token` regex matches decides, and `fallback` decides for every other token. `forms` say what X may be:
132
+ # call = `double(...)`, new = `new Double(...)`, curried = `double(...)(...)`, object = a plain object literal (REAL values),
133
+ # primitive = a string/number/boolean literal, array = an array literal (multi-providers of doubles). A const bound to such a value counts.
134
+ specDoubles:
135
+ kit: "@starci/jest-preset"
136
+ doubles:
137
+ - {token: "(?:^|_)OPTIONS$", double: builder, forms: [curried, object]}
138
+ - {token: "(?:^|_)ENTITY_MANAGER$", double: mockEntityManager, forms: [call]}
139
+ - {token: "(?:^|_)TRANSACTION(?:_RUNNER)?$", double: fakeTransaction, forms: [call]}
140
+ - {token: "(?:^|_)CLOCK$", double: FakeClock, forms: [new]}
141
+ - {token: "(?:^|_)OUTBOX(?:_|$)", double: recordingOutbox, forms: [call]}
142
+ - {token: "(?:^|_)CACHE(?:_|$)", double: fakeCache, forms: [call]}
143
+ - {token: "(?:^|_)(?:LOCK|LEASE|FENCE|HOLD)(?:_|$)", double: fakeLock, forms: [call]}
144
+ - {token: "(?:^|_)(?:IDS|ID_GENERATOR|ID_FACTORY)$", double: fakeIds, forms: [call]}
145
+ fallback: {double: mock, forms: [call, primitive, array]}
129
146
  # R89 BE_SOURCE_FORM: the closed role-suffix vocabulary of a source file name (<kebab-name>.<suffix>.ts; BE-CONVENTION 1.15)
130
147
  # and the suffixes that are never a role. index.ts, main.ts, app.module.ts and the migrations of be.persistence are the
131
148
  # only names outside it.
132
- suffixes: [module, module-definition, decorators, options, config, connection, service, command, query, handler, contracts, port, resolver, controller, gateway, consumer, job, cli, input, type, args, request, response, mapper, policy, entity, sql, rows, error, messages, log-events, cache-keys, guard, interceptor, filter, client, spec, integration-spec, e2e-spec, contract-spec, global-setup]
133
- bannedSuffixes: [use-case, repository, store, worker, scheduler, cron, listener, dto, util, utils, helper, helpers, rules, types, constants, int-spec, harness-spec, test]
149
+ suffixes: [module, module-definition, decorators, options, config, connection, service, command, query, handler, contracts, port, resolver, controller, gateway, consumer, job, cli, input, type, args, request, response, mapper, policy, entity, sql, rows, error, messages, log-events, cache-keys, guard, interceptor, filter, client, spec, integration-spec, e2e-spec, contract-spec, global-setup, builder]
150
+ # R47 BE_CONTRACT_UNGUARDED: the world's shape vocabulary. `helper` names the function a contract spec imports to erase the values of a JSON
151
+ # payload (`shapeOf`); a contract spec proves a fake's fixtures by asserting helper(real) equals helper(fixture).
152
+ contractShape: {helper: shapeOf}
153
+ bannedSuffixes: [use-case, repository, fixture, factory, store, worker, scheduler, cron, listener, dto, util, utils, helper, helpers, rules, types, constants, int-spec, harness-spec, test]
134
154
  fe:
135
155
  # Slot budgets (component.tsx 300, hooks 200, modules 400) are stricter than this cap where they apply.
136
156
  fileLines: {soft: 500, hardGrowth: true}
@@ -248,12 +268,11 @@ slots:
248
268
  profiles: [fe]
249
269
  # Managed files (hfs sync renders them, hfs check compares them): the tool configuration of a front end. Each is a
250
270
  # reference to a canon package, never a configuration: tsconfig.json extends @starci/tsconfig/next.json and only
251
- # excludes the e2e tree (each app's own tsconfig.json, in fe.app.next, carries its aliases and include);
252
- # tsconfig.e2e.json extends the e2e preset over e2e/ and playwright.config.ts; eslint.config.mjs is the one-line
253
- # starciFeConfig call (it lints apps, packages and e2e/ in one run, so there is no separate e2e lint); stylelint.config.mjs
254
- # is the one-line starciStylelintConfig call; vitest.config.ts is the one-line starciVitestWorkspace call;
255
- # .prettierrc names @starci/prettier-config; .prettierignore is the template.
256
- path: "{tsconfig.json,tsconfig.e2e.json,eslint.config.mjs,stylelint.config.mjs,vitest.config.ts,.prettierrc,.prettierignore}"
271
+ # names the preset and nothing else (each app's own tsconfig.json, in fe.app.next, carries its aliases and include; there is
272
+ # no test tsconfig: a front end has no tests, FE_NO_TESTS); eslint.config.mjs is the one-line starciFeConfig call;
273
+ # stylelint.config.mjs is the one-line starciStylelintConfig call; .prettierrc names @starci/prettier-config;
274
+ # .prettierignore is the template. A front end has no test configuration at all (FE_NO_TESTS).
275
+ path: "{tsconfig.json,eslint.config.mjs,stylelint.config.mjs,.prettierrc,.prettierignore}"
257
276
  presence: required
258
277
  tracked: tracked
259
278
  tier: none
@@ -262,18 +281,15 @@ slots:
262
281
  rules: [HFS_MANAGED_FILE_DRIFT, HFS_TOOL_CONFIG_LOCAL, HFS_RULE_OFF_WITHOUT_REPLACEMENT, HFS_TS_STRICT]
263
282
  - id: fe.tool-config-repo
264
283
  profiles: [fe]
265
- # The three tool files a preset cannot render because they carry the repository's own facts: vitest.setup.ts (its
266
- # setup), playwright.config.ts (its web servers, ports and projects: @starci/playwright-preset returns the shared
267
- # defaults and the repository adds what only it knows) and turbo.json (its task graph).
268
- path: "{vitest.setup.ts,playwright.config.ts,turbo.json}"
269
- roles: {playwright: playwright.config.ts}
284
+ # The one tool file a preset cannot render because it carries the repository's own facts: turbo.json (its task graph).
285
+ path: turbo.json
270
286
  presence: optional
271
287
  tracked: tracked
272
288
  tier: none
273
289
  tests: none
274
290
  - id: fe.tool-config-local
275
291
  profiles: [fe]
276
- 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.*,vitest.config.js,vitest.config.mjs,vitest.config.cjs,vitest.workspace.*,jest.config.*,lint-staged.config.*,.lintstagedrc*}"
292
+ 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*}"
277
293
  presence: forbidden
278
294
  tracked: external
279
295
  tier: none
@@ -282,7 +298,7 @@ slots:
282
298
  rules: [HFS_TOOL_CONFIG_LOCAL]
283
299
  - id: repo.quality-config
284
300
  profiles: [be, fe]
285
- path: "{sonar-project.properties,codecov.yml}"
301
+ path: sonar-project.properties
286
302
  presence: required
287
303
  tracked: tracked
288
304
  tier: none
@@ -308,7 +324,7 @@ slots:
308
324
  managedBy: ci-workflows
309
325
  rules: [HFS_CI_E2E_AUTOMATIC, HFS_CI_MISSING_CANON, HFS_MANAGED_FILE_DRIFT]
310
326
  - id: repo.ci-e2e
311
- profiles: [be, fe]
327
+ profiles: [be]
312
328
  path: ".github/workflows/e2e.yml"
313
329
  presence: optional # on: workflow_dispatch only
314
330
  tracked: tracked
@@ -325,12 +341,15 @@ slots:
325
341
  tests: none
326
342
  - id: repo.scripts
327
343
  profiles: [be, fe]
328
- path: "scripts/<name>.mjs"
344
+ # The repository's operational scripts only (`*.mjs`, `*.cjs`, `*.ps1`, `*.sh`), or nothing: an empty folder is `scripts/.gitkeep`, which hfs sync --init writes
345
+ # when the folder is missing. A spec or a test (BE_SPEC_PLACEMENT, FE_NO_TESTS), a check or lint source (`check-*`, `eslint-local-rules*`, a local eslint
346
+ # plugin: HFS_REPO_LOCAL_CHECK) and anything that re-implements a canon check is not an operational script and has no place here.
347
+ path: "scripts/{.gitkeep,*.mjs,*.cjs,*.ps1,*.sh}"
329
348
  presence: optional
330
349
  tracked: tracked
331
350
  tier: none
332
351
  tests: none
333
- rules: [HFS_SCRIPT_ONE_OFF] # one-off codemods and fix-* scripts are agent output, not tooling
352
+ rules: [HFS_SCRIPT_ONE_OFF, BE_SPEC_PLACEMENT, HFS_REPO_LOCAL_CHECK] # one-off codemods and fix-* scripts are agent output, not tooling
334
353
  - id: repo.docs
335
354
  profiles: [be, fe]
336
355
  path: "docs/{adr,runbooks,guides}/**/*.md"
@@ -375,7 +394,7 @@ slots:
375
394
  # ----- never tracked: build output the tools need in place ---------------------------------------------
376
395
  - id: repo.build-output
377
396
  profiles: [be, fe]
378
- path: "{**/node_modules/,dist/,apps/*/dist/,packages/*/dist/,.next/,apps/*/.next/,coverage/,reports/,test-results/,playwright-report/,**/next-env.d.ts,**/*.tsbuildinfo}"
397
+ path: "{**/node_modules/,dist/,apps/*/dist/,packages/*/dist/,.next/,apps/*/.next/,coverage/,reports/,test-results/,**/next-env.d.ts,**/*.tsbuildinfo}"
379
398
  presence: optional
380
399
  tracked: ignored
381
400
  tier: none
@@ -450,6 +469,7 @@ slots:
450
469
  tracked: tracked
451
470
  tier: none
452
471
  tests: none
472
+ why: emitted by `npm run contract:emit` (`hfs emit-contracts`, 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
453
473
  rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
454
474
  - id: fe.contract.copy
455
475
  profiles: [fe]
@@ -471,9 +491,9 @@ slots:
471
491
  tier: app
472
492
  owner: true
473
493
  minInstances: 1
474
- requires: [main.ts, app.module.ts, "<app>.composition.spec.ts"]
475
- allows: [main.ts, app.module.ts, "<app>.composition.spec.ts", "<app>.options.ts"]
476
- tests: unit-beside # the composition spec boots the REAL AppModule with stubbed options
494
+ requires: [main.ts, app.module.ts]
495
+ allows: [main.ts, app.module.ts, "<app>.options.ts", operations.ts]
496
+ tests: none # an app is proven by the e2e world (useTestWorld({ apps })), never by a unit spec
477
497
  budget: {app.module.ts: 250, main.ts: 80}
478
498
  rules: [BE_APP_COMPOSITION_ONLY, BE_APP_BUSINESS_ROLE, BE_DEFAULT_DENY, BE_ERROR_MASKED, BE_FEATURE_NOT_COMPOSED]
479
499
  - id: be.app.worker
@@ -484,8 +504,9 @@ slots:
484
504
  tracked: tracked
485
505
  tier: app
486
506
  owner: true
487
- requires: [main.ts, app.module.ts, "<app>.composition.spec.ts"]
488
- tests: unit-beside
507
+ requires: [main.ts, app.module.ts]
508
+ allows: [main.ts, app.module.ts, "<app>.options.ts"]
509
+ tests: none # an app is proven by the e2e world (useTestWorld({ apps })), never by a unit spec
489
510
  rules: [BE_APP_COMPOSITION_ONLY, BE_BACKGROUND_UNOWNED]
490
511
  - id: be.app.migrate
491
512
  profiles: [be]
@@ -496,8 +517,9 @@ slots:
496
517
  tracked: tracked
497
518
  tier: app
498
519
  owner: true
499
- requires: [main.ts, "<app>.composition.spec.ts"]
500
- tests: unit-beside
520
+ requires: [main.ts]
521
+ allows: [main.ts, "<app>.options.ts"]
522
+ tests: none # an app is proven by the e2e world (useTestWorld({ apps })), never by a unit spec
501
523
  rules: [BE_SCHEMA_AUTHORITY, BE_ENTRYPOINT_ONLY_IN_APPS]
502
524
  - id: be.app.cli
503
525
  profiles: [be]
@@ -507,8 +529,9 @@ slots:
507
529
  tracked: tracked
508
530
  tier: app
509
531
  owner: true
510
- requires: [main.ts, app.module.ts, "<app>.composition.spec.ts"]
511
- tests: unit-beside
532
+ requires: [main.ts, app.module.ts]
533
+ allows: [main.ts, app.module.ts, "<app>.options.ts"]
534
+ tests: none # an app is proven by the e2e world (useTestWorld({ apps })), never by a unit spec
512
535
 
513
536
  # ----- BE: features ------------------------------------------------------------------------------------
514
537
  - id: be.feature
@@ -521,7 +544,7 @@ slots:
521
544
  minInstances: 1
522
545
  requires: [index.ts, "<feature>.module.ts", application/]
523
546
  allows: [index.ts, "<feature>.module.ts", application/, transport/, messages/] # nothing else at feature root
524
- tests: unit-beside
547
+ tests: none
525
548
  budget: {files: 250, indexExports: 60} # a feature above 250 source files must be split by product capability
526
549
  rules: [BE_FEATURE_SHAPE, BE_FEATURE_IMPORTS_FEATURE, BE_PUBLIC_SURFACE, BE_FEATURE_NOT_COMPOSED]
527
550
  - id: be.feature.application
@@ -530,9 +553,9 @@ slots:
530
553
  presence: required
531
554
  tracked: tracked
532
555
  tier: feature
533
- allows: ["<action>.{command,query,handler,contracts}.ts", "*.spec.ts"]
556
+ allows: ["<action>.{command,query,handler,contracts}.ts"]
534
557
  forbids: [graphql/, http/, message/, schedule/, transport/] # no protocol names under application/
535
- tests: unit-beside
558
+ tests: none
536
559
  rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_ENTITY_IN_CONTRACT, BE_CQRS_SHAPE]
537
560
  - id: be.feature.application.support
538
561
  profiles: [be]
@@ -540,9 +563,9 @@ slots:
540
563
  presence: optional # helpers and types local to ONE feature; same tier as application, never a shared home
541
564
  tracked: tracked
542
565
  tier: feature # feature tier: another feature cannot import it (features never import features)
543
- allows: ["<name>.<role>.ts", "*.spec.ts"] # <role> from ruleParams.be.suffixes; no <name>.types.ts
566
+ allows: ["<name>.<role>.ts"] # <role> from ruleParams.be.suffixes; no <name>.types.ts
544
567
  forbids: [graphql/, http/, message/, schedule/, transport/]
545
- tests: unit-beside # a <name>.spec.ts beside every <name>.ts
568
+ tests: none
546
569
  rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_FEATURE_IMPORTS_FEATURE]
547
570
  since: 1.0.0
548
571
  - id: be.transport.http
@@ -552,8 +575,8 @@ slots:
552
575
  tracked: tracked
553
576
  tier: feature
554
577
  requires: ["<feature>-http.module.ts"]
555
- allows: ["<action>.controller.ts", "dto/<action>.{request,response}.ts", "<action>.mapper.ts", "*.spec.ts"]
556
- tests: unit-beside
578
+ allows: ["<action>.controller.ts", "dto/<action>.{request,response}.ts", "<action>.mapper.ts"]
579
+ tests: none
557
580
  composedBy: [api]
558
581
  rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED, BE_TRANSPORT_SHAPE]
559
582
  - id: be.transport.graphql
@@ -563,8 +586,8 @@ slots:
563
586
  tracked: tracked
564
587
  tier: feature
565
588
  requires: ["<feature>-graphql.module.ts"]
566
- allows: ["<action>.resolver.ts", "dto/<action>.{input,type,args}.ts", "<action>.mapper.ts", "*.spec.ts"]
567
- tests: unit-beside
589
+ allows: ["<action>.resolver.ts", "dto/<action>.{input,type,args}.ts", "<action>.mapper.ts"]
590
+ tests: none
568
591
  composedBy: [api]
569
592
  rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED, BE_TRANSPORT_SHAPE]
570
593
  - id: be.transport.message
@@ -574,8 +597,8 @@ slots:
574
597
  tracked: tracked
575
598
  tier: feature
576
599
  requires: ["<feature>-message.module.ts"]
577
- allows: ["<event>.consumer.ts", "<event>.message.ts", "*.spec.ts"]
578
- tests: unit-beside
600
+ allows: ["<event>.consumer.ts"] # BE-CONVENTION 1.6: transport/message holds only consumers; a .message.ts is outside the closed suffix list
601
+ tests: none
579
602
  composedBy: [worker]
580
603
  rules: [BE_BACKGROUND_UNOWNED]
581
604
  - id: be.transport.schedule
@@ -585,8 +608,8 @@ slots:
585
608
  tracked: tracked
586
609
  tier: feature
587
610
  requires: ["<feature>-schedule.module.ts"]
588
- allows: ["<job>.job.ts", "*.spec.ts"]
589
- tests: unit-beside
611
+ allows: ["<job>.job.ts"]
612
+ tests: none
590
613
  composedBy: [worker]
591
614
  rules: [BE_BACKGROUND_UNOWNED]
592
615
  - id: be.transport.websocket
@@ -596,8 +619,8 @@ slots:
596
619
  tracked: tracked
597
620
  tier: feature
598
621
  requires: ["<feature>-websocket.module.ts"]
599
- allows: ["<channel>.gateway.ts", "dto/*.ts", "*.spec.ts"]
600
- tests: unit-beside
622
+ allows: ["<channel>.gateway.ts", "dto/*.ts"]
623
+ tests: none
601
624
  composedBy: [api]
602
625
  since: 1.0.0
603
626
  - id: be.feature.transport.cli
@@ -607,8 +630,8 @@ slots:
607
630
  tracked: tracked
608
631
  tier: feature
609
632
  requires: ["<feature>-cli.module.ts"]
610
- allows: ["<name>.cli.ts", "dto/*.ts", "*.spec.ts"] # .cli.ts: .command.ts is a CQRS message (HFS delta 3)
611
- tests: unit-beside
633
+ allows: ["<name>.cli.ts", "dto/*.ts"] # .cli.ts: .command.ts is a CQRS message (HFS delta 3)
634
+ tests: none
612
635
  composedBy: [cli]
613
636
  rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_FEATURE_NOT_COMPOSED, BE_ENTRYPOINT_ONLY_IN_APPS]
614
637
  since: 1.0.0
@@ -622,9 +645,9 @@ slots:
622
645
  tier: domain
623
646
  owner: true
624
647
  requires: [index.ts]
625
- allows: ["<capability>.module.ts", "<capability>.config.ts", "<capability>.options.ts", errors/, messages/, persistence/, policies/, "*.service.ts", "*.contracts.ts", "*.spec.ts"]
648
+ allows: ["<capability>.module.ts", "<capability>.config.ts", "<capability>.options.ts", errors/, messages/, persistence/, policies/, "*.service.ts", "*.service.spec.ts", "*.contracts.ts"]
626
649
  forbids: [testing/, exceptions/, utils/, helpers/, shared/]
627
- tests: unit-beside
650
+ tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
628
651
  budget: {indexExports: 60}
629
652
  rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_PUBLIC_SURFACE, BE_ERROR_HOME, BE_CONFIG_OWNER]
630
653
  - id: be.errors
@@ -633,17 +656,17 @@ slots:
633
656
  presence: optional
634
657
  tracked: tracked
635
658
  tier: inherit
636
- allows: ["<capability>.error.ts", "<name>.error.ts", "*.spec.ts"] # one family per capability, extends platform/errors DomainError
637
- tests: unit-beside
659
+ allows: ["<capability>.error.ts", "<name>.error.ts"] # one family per capability, extends platform/errors DomainError
660
+ tests: none
638
661
  rules: [BE_ERROR_HOME]
639
662
  - id: be.domain.messages
640
663
  profiles: [be]
641
- path: "src/modules/{domain,integrations}/<capability>/messages/"
642
- presence: optional # a capability that raises no user-facing text carries none
664
+ path: "src/modules/{domain,platform,integrations}/<capability>/messages/"
665
+ presence: optional # a capability (any tier) that raises no user-facing text carries none
643
666
  tracked: tracked
644
667
  tier: inherit
645
- allows: ["<capability>.messages.ts", "*.spec.ts"] # one keyed catalog per capability, vi and en, read through the platform/i18n MessageCatalog port
646
- tests: unit-beside
668
+ allows: ["<capability>.messages.ts"] # one keyed catalog per capability, vi and en, read through the platform/i18n MessageCatalog port
669
+ tests: none
647
670
  rules: [BE_USER_COPY_LITERAL]
648
671
  since: 1.0.0
649
672
  - id: be.feature.messages
@@ -652,8 +675,8 @@ slots:
652
675
  presence: optional
653
676
  tracked: tracked
654
677
  tier: feature
655
- allows: ["<feature>.messages.ts", "*.spec.ts"] # one keyed catalog per feature, vi and en, read through the platform/i18n MessageCatalog port
656
- tests: unit-beside
678
+ allows: ["<feature>.messages.ts"] # one keyed catalog per feature, vi and en, read through the platform/i18n MessageCatalog port
679
+ tests: none
657
680
  rules: [BE_USER_COPY_LITERAL]
658
681
  since: 1.0.0
659
682
  - id: be.persistence
@@ -663,9 +686,9 @@ slots:
663
686
  tracked: tracked
664
687
  tier: inherit
665
688
  requires: [connection.ts] # the capability's <c>Entities / <c>Migrations arrays (the owner index re-exports the two arrays by name); no CONNECTION alias: the connection is the one the apps register the arrays on
666
- allows: ["entities/<table>.entity.ts", "migrations/<epochMs13>-<kebab-name>.ts", "<name>.sql.ts", "<name>.rows.ts", connection.ts, "*.spec.ts"]
689
+ allows: ["entities/<table>.entity.ts", "migrations/<epochMs13>-<kebab-name>.ts", "<name>.sql.ts", "<name>.rows.ts", connection.ts]
667
690
  forbids: [index.ts, "<name>.repository.ts"]
668
- tests: unit-beside
691
+ tests: none
669
692
  rules: [BE_SCHEMA_OWNER, BE_SCHEMA_AUTHORITY, BE_SQL_OUTSIDE_PERSISTENCE]
670
693
  - id: be.platform
671
694
  profiles: [be]
@@ -684,7 +707,7 @@ slots:
684
707
  requiredInstances: {capability: [config, logging, errors, primitives, clock, i18n]}
685
708
  requires: [index.ts]
686
709
  forbids: [common/, utils/, shared/, testing/] # primitives live in platform/primitives
687
- tests: unit-beside
710
+ tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
688
711
  budget: {indexExports: 60}
689
712
  rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE]
690
713
  - id: be.integrations
@@ -695,7 +718,7 @@ slots:
695
718
  tier: integrations
696
719
  owner: true
697
720
  requires: [index.ts, "<provider>.config.ts"]
698
- tests: unit-beside
721
+ tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
699
722
  budget: {indexExports: 60}
700
723
  rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT]
701
724
  - id: be.integrations.model
@@ -704,10 +727,10 @@ slots:
704
727
  presence: opt-in
705
728
  tracked: tracked
706
729
  tier: integrations
707
- tests: unit-beside
730
+ tests: none
708
731
  why: an ML or LLM model client is an integration; weights and datasets are never in git
709
732
 
710
- # ----- BE: tests (owner test layout 2026-09-30). Unit specs sit beside their subject (<name>.spec.ts); everything else
733
+ # ----- BE: tests (owner test layout 2026-09-30). Unit specs are <name>.service.spec.ts beside their service and nothing else; everything else
711
734
  # lives under src/tests/, one folder per kind, and the folder and the file suffix always agree (a file whose suffix does
712
735
  # not match its folder matches no slot: HFS_SLOT_UNDECLARED).
713
736
  - id: be.tests.world
@@ -716,25 +739,57 @@ slots:
716
739
  presence: optional
717
740
  tracked: tracked
718
741
  tier: e2e
719
- allows: [global-setup.ts, global-teardown.ts, use-test-world.ts, fakes/, kit/] # jest globalSetup/globalTeardown default-export (Jest API); plus root files with a role suffix (test-world-files, R47)
742
+ allows: [global-setup.ts, global-teardown.ts, use-test-world.ts, fakes/] # jest globalSetup/globalTeardown default-export (Jest API); plus root files with a role suffix (test-world-files, R47)
720
743
  tests: none
721
744
  why: >-
722
745
  the ONLY test infrastructure location, shared by integration and e2e: global-setup.ts starts the shared
723
746
  infrastructure (containers) and runs apps/migrate's exported bootstrap once; use-test-world.ts exports
724
747
  useTestWorld({ apps: { <name>: { module, listen? } } } | { modules: [...] }) -> world.apps.<name>.api,
725
- world.db.<connection> (the shared EntityManager), world.fake.<provider> (a network-edge fake started by the world, with
748
+ world.db.<connection> (the shared EntityManager), world.infra.<service> (latency/cut/restore on a REAL service of the repository's
749
+ own stack, `.starcistacks/<env>`, each behind toxiproxy), world.fake.<provider> (a network-edge fake of an external SaaS started by the world, with
726
750
  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
727
751
  overridden in the DI container. It is the only test location that may import typeorm's DataSource or testcontainers,
728
752
  call migrate/runMigrations/synchronize, or write process.env.
753
+ - id: be.tests.world.kit
754
+ profiles: [be]
755
+ path: "src/tests/world/kit/"
756
+ presence: optional
757
+ tracked: tracked
758
+ tier: e2e
759
+ allows: ["<name>.ts"] # plain kebab names, like platform/primitives (BE-CONVENTION 1.11 names primitives/time.ts): the world's inlined test helpers (poll, free-ports)
760
+ tests: none
761
+ 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
729
762
  - id: be.tests.fixtures
730
763
  profiles: [be]
731
764
  path: "src/tests/fixtures/"
732
765
  presence: optional
733
766
  tracked: tracked
734
767
  tier: fixtures
735
- allows: [database.ts] # mockEntityManager() and fakeTransaction() (BE-CONVENTION 1.16); other fixtures use role suffixes
736
- tests: unit-beside
737
- why: typed doubles and data builders; importable by unit specs, integration and e2e; never imports a feature
768
+ allows: [i18n/] # i18n/ is the one folder that may carry localized text
769
+ tests: none
770
+ 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
771
+ - id: be.tests.fixtures.builders
772
+ profiles: [be]
773
+ path: "src/tests/fixtures/builders/*.builder.ts"
774
+ presence: optional
775
+ tracked: tracked
776
+ tier: fixtures
777
+ tests: none
778
+ why: >-
779
+ the ONLY home of test data builders, one <area>.builder.ts per area, shared by every spec of that area: pure object builders
780
+ (commissionRow(overrides), accrueInput(overrides)) for unit specs and persisting builders (orderBuilder(world.db.primary).pending().build(overrides))
781
+ for integration and e2e; arrange only, constraints on, deterministic defaults. A .builder.ts anywhere else, and a *.repository.ts,
782
+ *.fixture.ts or *.factory.ts anywhere, is a BE_SOURCE_FORM finding
783
+ rules: [BE_SOURCE_FORM]
784
+ - id: be.tests.fixtures.i18n
785
+ profiles: [be]
786
+ path: "src/tests/fixtures/i18n/"
787
+ presence: optional
788
+ tracked: tracked
789
+ tier: fixtures
790
+ allows: ["<name>.<role>.ts"] # data files that deliberately carry localized text (a real Vietnamese string a parser or formatter must accept); no specs here
791
+ tests: none
792
+ 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
738
793
  - id: be.tests.integration
739
794
  profiles: [be]
740
795
  path: "src/tests/integration/<capability>/*.integration-spec.ts"
@@ -794,7 +849,7 @@ slots:
794
849
  tier: none
795
850
  tests: none
796
851
  requires: [application-stacks.yaml]
797
- allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,k8s,terraform}/**",
852
+ allows: [application-stacks.yaml, "<env>/README.md", "<env>/environment.json", "<env>/infra/{compose,k8s,terraform}/**", "<env>/infra/metadata.json",
798
853
  "<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.enc", "<env>/seeds/**"]
799
854
  forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
800
855
  rules: [HFS_STACKS_SHAPE, HFS_PLAINTEXT_SECRET, HFS_IDENTITY_CUSTODY]
@@ -821,11 +876,10 @@ slots:
821
876
  rules: [FE_APP_ISOLATION, FE_ERROR_BOUNDARY_MISSING, FE_NEXT_CONVENTIONS]
822
877
  - id: fe.app-optional
823
878
  profiles: [fe]
824
- path: "apps/<app>/{vitest.config.ts,playwright.config.ts,public/}"
879
+ path: "apps/<app>/public/"
825
880
  presence: optional
826
881
  tracked: tracked
827
882
  tier: none
828
- roles: {playwright: playwright.config.ts}
829
883
  tests: none
830
884
  - id: fe.route
831
885
  profiles: [fe]
@@ -837,7 +891,7 @@ slots:
837
891
  global-error.tsx, page.tsx, # the root page.tsx only redirects (FE_ROUTE_FILES_THIN)
838
892
  not-found.tsx, layout.tsx, # next-intl: a request outside every locale renders the root not-found inside a root layout
839
893
  "health/{live,ready}/route.ts", "api/**/route.ts", "_*/**", providers.tsx, globals.css,
840
- "{icon,favicon,apple-icon,opengraph-image,twitter-image}.*", robots.ts, sitemap.ts, manifest.ts, "**/*.spec.ts", "**/*.spec.tsx"]
894
+ "{icon,favicon,apple-icon,opengraph-image,twitter-image}.*", robots.ts, sitemap.ts, manifest.ts]
841
895
  # providers.tsx (root or [locale]) is the client wrapper the server layout renders around its children to host context providers.
842
896
  roles: {page: page.tsx, layout: layout.tsx, template: template.tsx, loading: loading.tsx, error: error.tsx, not-found: not-found.tsx, default: default.tsx, route: route.ts, global-error: global-error.tsx, providers: providers.tsx}
843
897
  tests: none
@@ -869,9 +923,9 @@ slots:
869
923
  owner: true
870
924
  kinds: [pages, layouts, overlays]
871
925
  requires: [index.tsx]
872
- allows: [index.tsx, component.tsx, classNames.ts, "*.spec.tsx", "*.spec.ts"]
926
+ allows: [index.tsx, component.tsx, classNames.ts]
873
927
  roles: {entry: index.tsx, drawing: component.tsx, styles: classNames.ts} # the connected entry, the pure drawing half and the styling declarations of a component owner
874
- tests: unit-beside
928
+ tests: none
875
929
  budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
876
930
  rules: [FE_SIZE_BUDGET, FE_OWNER_REACHABLE, FE_I18N_LITERAL]
877
931
  - id: fe.components
@@ -883,9 +937,9 @@ slots:
883
937
  owner: true
884
938
  layers: [blocks, composites, branches, leaves] # a layer imports only the layers after it
885
939
  requires: [index.tsx]
886
- allows: [index.tsx, component.tsx, classNames.ts, "*.spec.tsx", "*.spec.ts"]
940
+ allows: [index.tsx, component.tsx, classNames.ts]
887
941
  roles: {entry: index.tsx, drawing: component.tsx, styles: classNames.ts} # the connected entry, the pure drawing half and the styling declarations of a component owner
888
- tests: unit-beside
942
+ tests: none
889
943
  budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
890
944
  rules: [FE_CONNECTED_BLOCK_RENDER_PAIR, FE_I18N_LITERAL, FE_NATIVE_FORM_CONTROL, FE_CLIENT_BOUNDARY]
891
945
  - id: fe.hooks
@@ -896,9 +950,9 @@ slots:
896
950
  tier: hooks
897
951
  owner: true # a domain is an owner: other domains import it through index.ts only
898
952
  requires: [index.ts]
899
- allows: [index.ts, "use<name>.ts", "<domain>.shared.ts", "*.spec.ts"] # React hooks only; <domain>.shared.ts = sanctioned non-hook helpers
953
+ allows: [index.ts, "use<name>.ts", "<domain>.shared.ts"] # React hooks only; <domain>.shared.ts = sanctioned non-hook helpers
900
954
  roles: {entry: index.ts, shared: "<domain>.shared.ts"}
901
- tests: unit-beside
955
+ tests: none
902
956
  budget: {file: 200, dataHooks: 6, useState: 6}
903
957
  rules: [FE_HOOKS_ARE_HOOKS, FE_DATA_FRESHNESS]
904
958
  - id: fe.modules
@@ -910,7 +964,7 @@ slots:
910
964
  owner: true
911
965
  requiredInstances: {capability: [config, i18n, routes]}
912
966
  requires: [index.ts]
913
- tests: unit-beside
967
+ tests: none
914
968
  budget: {file: 400}
915
969
  rules: [FE_ENV_OWNER, FE_TRANSPORT_OWNER]
916
970
  - id: fe.modules.api
@@ -923,9 +977,9 @@ slots:
923
977
  # client.ts/outcome.ts (fe.transport.client/outcome) in a one-app repository, or the api package's
924
978
  # (fe.package.api.client/outcome) when the repository shares it (FE_TRANSPORT_OWNER, machine).
925
979
  requires: [index.ts]
926
- allows: [index.ts, client.ts, outcome.ts, "<domain>/read-*.ts", "<domain>/*.graphql", "<domain>/<domain>.mapper.ts", contract/, __generated__/, "*.spec.ts"]
980
+ allows: [index.ts, client.ts, outcome.ts, "<domain>/read-*.ts", "<domain>/*.graphql", "<domain>/<domain>.mapper.ts", contract/, __generated__/]
927
981
  roles: {entry: index.ts, reader: "read-*.ts"} # a reader is a server module (FE_CLIENT_REACHES_SERVER)
928
- tests: unit-beside
982
+ tests: none
929
983
  rules: [FE_TRANSPORT_OWNER, FE_HTTP_STATUS_COLLAPSE, FE_WIRE_GENERATED]
930
984
  - id: fe.transport.client
931
985
  profiles: [fe]
@@ -933,7 +987,7 @@ slots:
933
987
  presence: optional
934
988
  tracked: tracked
935
989
  tier: transport
936
- tests: unit-beside
990
+ tests: none
937
991
  why: the one fetch of a one-app repository (timeout, abort, 401/403 -> refused); a shared client is fe.package.api.client
938
992
  rules: [FE_TRANSPORT_OWNER]
939
993
  - id: fe.transport.outcome
@@ -942,7 +996,7 @@ slots:
942
996
  presence: optional
943
997
  tracked: tracked
944
998
  tier: transport
945
- tests: unit-beside
999
+ tests: none
946
1000
  why: the one Outcome union of a one-app repository; a shared union is fe.package.api.outcome
947
1001
  rules: [FE_HTTP_STATUS_COLLAPSE]
948
1002
  - id: fe.modules.config
@@ -951,7 +1005,7 @@ slots:
951
1005
  presence: required
952
1006
  tracked: tracked
953
1007
  tier: foundation
954
- tests: unit-beside
1008
+ tests: none
955
1009
  why: the only reader of process.env and NEXT_PUBLIC_*; fails fast, no localhost fallback
956
1010
  - id: fe.modules.i18n
957
1011
  profiles: [fe]
@@ -963,7 +1017,7 @@ slots:
963
1017
  # navigation, request config) is written once per repository: in the i18n package (fe.package.i18n, `createAppI18n`)
964
1018
  # when the repository has one, else in the only app's module.
965
1019
  requires: [index.ts]
966
- tests: unit-beside
1020
+ tests: none
967
1021
  rules: [FE_I18N_PLACEMENT, FE_I18N_CATALOG]
968
1022
  - id: fe.modules.brand
969
1023
  profiles: [fe]
@@ -980,7 +1034,7 @@ slots:
980
1034
  presence: required
981
1035
  tracked: tracked
982
1036
  tier: foundation
983
- tests: unit-beside
1037
+ tests: none
984
1038
  why: every href builder; FE_HREF_RESOLVES checks each against app/
985
1039
  - id: fe.modules.types
986
1040
  profiles: [fe]
@@ -988,7 +1042,7 @@ slots:
988
1042
  presence: optional
989
1043
  tracked: tracked
990
1044
  tier: foundation
991
- tests: unit-beside
1045
+ tests: none
992
1046
  why: shared types with no runtime behaviour; a component may import them
993
1047
  - id: fe.package.ui
994
1048
  profiles: [fe]
@@ -1000,7 +1054,7 @@ slots:
1000
1054
  kinds: [composites, branches, leaves] # the layer folders under src/ (a package owns no blocks)
1001
1055
  roles: {entry: index.tsx, drawing: component.tsx, styles: classNames.ts}
1002
1056
  requires: [package.json, src/index.ts, tsconfig.json]
1003
- tests: unit-beside
1057
+ tests: none
1004
1058
  budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
1005
1059
  why: built to dist with explicit named exports; only composites, branches and leaves; no unused export; no colour literal
1006
1060
  rules: [FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT, FE_STYLE_TOKEN_ONLY]
@@ -1012,7 +1066,7 @@ slots:
1012
1066
  tier: package
1013
1067
  owner: true
1014
1068
  requires: [package.json, src/index.ts, src/client.ts, src/outcome.ts, tsconfig.json]
1015
- tests: unit-beside
1069
+ tests: none
1016
1070
  why: the one transport client and the one Outcome union shared by every app of the repository; no app keeps its own
1017
1071
  rules: [FE_TRANSPORT_OWNER, FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
1018
1072
  - id: fe.package.api.client
@@ -1021,7 +1075,7 @@ slots:
1021
1075
  presence: optional # enabled with its package (fe.package.api is the opt-in)
1022
1076
  tracked: tracked
1023
1077
  tier: package
1024
- tests: unit-beside
1078
+ tests: none
1025
1079
  why: the repository's one fetch (timeout, abort, 401/403 -> refused)
1026
1080
  rules: [FE_TRANSPORT_OWNER]
1027
1081
  - id: fe.package.api.outcome
@@ -1030,7 +1084,7 @@ slots:
1030
1084
  presence: optional # enabled with its package (fe.package.api is the opt-in)
1031
1085
  tracked: tracked
1032
1086
  tier: package
1033
- tests: unit-beside
1087
+ tests: none
1034
1088
  why: the repository's one Outcome union
1035
1089
  rules: [FE_HTTP_STATUS_COLLAPSE]
1036
1090
  - id: fe.package.i18n
@@ -1041,25 +1095,9 @@ slots:
1041
1095
  tier: package
1042
1096
  owner: true
1043
1097
  requires: [package.json, src/index.ts, tsconfig.json]
1044
- tests: unit-beside
1098
+ tests: none
1045
1099
  why: the next-intl stack (routing, navigation, request config) written once, exported as the createAppI18n factory every app calls
1046
1100
  rules: [FE_I18N_PLACEMENT, FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
1047
- - id: fe.e2e
1048
- profiles: [fe]
1049
- path: "e2e/<area>/*.e2e-spec.ts"
1050
- presence: optional
1051
- tracked: tracked
1052
- tier: e2e
1053
- tests: e2e
1054
- requires: [/playwright.config.ts, /tsconfig.e2e.json]
1055
- rules: [FE_E2E_SHAPE, HFS_CI_E2E_AUTOMATIC]
1056
- - id: fe.e2e-support
1057
- profiles: [fe]
1058
- path: "e2e/{support,fixtures}/"
1059
- presence: optional
1060
- tracked: tracked
1061
- tier: e2e
1062
- tests: none
1063
1101
 
1064
1102
  # Checks that read this manifest (all ship from .claude; none keeps its own path list)
1065
1103
  consumers: