@starci/hfs 1.0.1 → 2.0.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 +46 -0
- package/README.md +110 -12
- package/bin/hfs.mjs +119 -15
- package/emit/compiler.mjs +35 -0
- package/emit/contracts.mjs +97 -0
- package/emit/operations-worker.mjs +24 -0
- package/emit/operations.mjs +126 -0
- package/emit/schema-worker.mjs +117 -0
- package/emit/static-graph.mjs +670 -0
- package/emit/type-schema.mjs +145 -0
- package/package.json +4 -1
- package/report/sonar.mjs +180 -0
- package/runtime/engine/admission.mjs +284 -0
- package/runtime/engine/digest.mjs +10 -0
- package/runtime/engine/ledger-db.mjs +1245 -0
- package/runtime/engine/machine-db.mjs +1484 -0
- package/runtime/engine/migrations/machine/0001-init.sql +887 -0
- package/runtime/engine/migrations/runtime/0001-init.sql +1072 -0
- package/runtime/engine/migrations/runtime/0003-usage-unavailable.sql +13 -0
- package/runtime/engine/migrations/runtime/0004-attempt-why.sql +43 -0
- package/runtime/engine/plain-object.mjs +5 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +16 -58
- package/runtime/knowledge/hfs/slots.yaml +405 -137
- package/runtime/knowledge/patterns/fe/folder.yaml +309 -0
- package/runtime/knowledge/sonar-gate.yaml +85 -0
- package/runtime/modules/kernel/failure-codes.yaml +1480 -16
- package/runtime/scripts/checks/architecture/backend.mjs +350 -0
- package/runtime/scripts/checks/architecture/background-unowned.mjs +107 -0
- package/runtime/scripts/checks/architecture/client-reaches-server.mjs +94 -0
- package/runtime/scripts/checks/architecture/clones.mjs +200 -0
- package/runtime/scripts/checks/architecture/config-unread.mjs +35 -0
- package/runtime/scripts/checks/architecture/config.mjs +310 -0
- package/runtime/scripts/checks/architecture/connection-map.mjs +208 -0
- package/runtime/scripts/checks/architecture/constructor-deps.mjs +100 -0
- package/runtime/scripts/checks/architecture/contract-fixture-guard.mjs +128 -0
- package/runtime/scripts/checks/architecture/contracts.mjs +792 -0
- package/runtime/scripts/checks/architecture/cross-app-duplicate.mjs +73 -0
- package/runtime/scripts/checks/architecture/dead-exports.mjs +265 -0
- package/runtime/scripts/checks/architecture/default-deny.mjs +129 -0
- package/runtime/scripts/checks/architecture/doc-language.mjs +39 -0
- package/runtime/scripts/checks/architecture/entrypoint.mjs +57 -0
- package/runtime/scripts/checks/architecture/error-codes.mjs +45 -0
- package/runtime/scripts/checks/architecture/error-masked.mjs +60 -0
- package/runtime/scripts/checks/architecture/fe-slot-allows.mjs +38 -0
- package/runtime/scripts/checks/architecture/feature-shape.mjs +49 -0
- package/runtime/scripts/checks/architecture/framework-pinned.mjs +83 -0
- package/runtime/scripts/checks/architecture/frontend.mjs +995 -0
- package/runtime/scripts/checks/architecture/hfs-graph.mjs +61 -0
- package/runtime/scripts/checks/architecture/hfs.mjs +521 -0
- package/runtime/scripts/checks/architecture/hooks-are-hooks.mjs +88 -0
- package/runtime/scripts/checks/architecture/i18n-keys.mjs +146 -0
- package/runtime/scripts/checks/architecture/index.mjs +316 -0
- package/runtime/scripts/checks/architecture/injection-token-exported.mjs +62 -0
- package/runtime/scripts/checks/architecture/machine-ast.mjs +138 -0
- package/runtime/scripts/checks/architecture/module-per-transport.mjs +130 -0
- package/runtime/scripts/checks/architecture/next-data.mjs +775 -0
- package/runtime/scripts/checks/architecture/owners.mjs +89 -0
- package/runtime/scripts/checks/architecture/package-shape.mjs +63 -0
- package/runtime/scripts/checks/architecture/reachability.mjs +233 -0
- package/runtime/scripts/checks/architecture/register-once.mjs +141 -0
- package/runtime/scripts/checks/architecture/registration.mjs +319 -0
- package/runtime/scripts/checks/architecture/required-files.mjs +172 -0
- package/runtime/scripts/checks/architecture/route-files-thin.mjs +97 -0
- package/runtime/scripts/checks/architecture/schema-owner.mjs +261 -0
- package/runtime/scripts/checks/architecture/size-growth.mjs +73 -0
- package/runtime/scripts/checks/architecture/source-names.mjs +607 -0
- package/runtime/scripts/checks/architecture/sql-owner.mjs +142 -0
- package/runtime/scripts/checks/architecture/sql-tokens.mjs +327 -0
- package/runtime/scripts/checks/architecture/symbols.mjs +193 -0
- package/runtime/scripts/checks/architecture/test-world-files.mjs +163 -0
- package/runtime/scripts/checks/architecture/tiers.mjs +130 -0
- package/runtime/scripts/checks/architecture/transport-owner.mjs +112 -0
- package/runtime/scripts/checks/architecture/typescript.mjs +500 -0
- package/runtime/scripts/checks/architecture/unit-spec-providers.mjs +122 -0
- package/runtime/scripts/checks/architecture.mjs +41 -0
- package/runtime/scripts/checks/common.mjs +37 -0
- package/runtime/scripts/checks/typescript-programs.mjs +82 -0
- package/runtime/scripts/lib/artifact-hold.mjs +89 -0
- package/runtime/scripts/lib/artifact-store.mjs +103 -0
- package/runtime/scripts/lib/fs-kind.mjs +10 -0
- package/runtime/scripts/lib/git.mjs +53 -0
- package/runtime/scripts/lib/hfs-allows.mjs +57 -0
- package/runtime/scripts/lib/hfs-check.mjs +254 -28
- package/runtime/scripts/lib/hfs-rules/contract.mjs +126 -0
- package/runtime/scripts/lib/hfs-rules/deps.mjs +63 -0
- package/runtime/scripts/lib/hfs-rules/fe-no-tests.mjs +47 -0
- package/runtime/scripts/lib/hfs-rules/frontend.mjs +124 -0
- package/runtime/scripts/lib/hfs-rules/lint-suppression.mjs +34 -0
- package/runtime/scripts/lib/hfs-rules/pipeline.mjs +51 -0
- package/runtime/scripts/lib/hfs-rules/proof-commands.mjs +64 -0
- package/runtime/scripts/lib/hfs-rules/read.mjs +28 -0
- package/runtime/scripts/lib/hfs-rules/repo-local-checks.mjs +32 -0
- package/runtime/scripts/lib/hfs-rules/secrets.mjs +54 -0
- package/runtime/scripts/lib/hfs-rules/spec-placement.mjs +31 -0
- package/runtime/scripts/lib/hfs-rules/stacks.mjs +54 -0
- package/runtime/scripts/lib/hfs-rules/test-topology.mjs +31 -0
- package/runtime/scripts/lib/hfs-slots.mjs +88 -40
- package/runtime/scripts/lib/hfs-tree.mjs +80 -0
- package/runtime/scripts/lib/hfs-view.mjs +68 -0
- package/runtime/scripts/lib/json.mjs +22 -0
- package/runtime/scripts/lib/language.mjs +107 -0
- package/runtime/scripts/lib/path-key.mjs +2 -0
- package/runtime/scripts/lib/redact.mjs +148 -0
- package/runtime/scripts/lib/repo-identity.mjs +50 -0
- package/runtime/scripts/lib/safe-remove.mjs +179 -0
- package/runtime/scripts/lib/secret-patterns.mjs +44 -0
- package/runtime/scripts/lib/sleep-sync.mjs +17 -0
- package/runtime/scripts/lib/stack-declaration.mjs +52 -0
- package/runtime/scripts/lib/stack-services.mjs +217 -0
- package/runtime/scripts/lib/test-secrets.mjs +120 -0
- package/scaffold/service.mjs +333 -0
- package/sync/format.mjs +46 -0
- package/sync/hygiene.mjs +56 -24
- package/sync/index.mjs +126 -41
- package/sync/managed.mjs +170 -0
- package/sync/skeleton.mjs +32 -10
- package/sync/sonar-key.mjs +13 -0
- package/sync/ts-strict.mjs +48 -0
- package/templates/{common → be/ci-workflows/github/workflows}/ci.yml +13 -13
- package/templates/be/{e2e.yml → ci-workflows/github/workflows/e2e.yml} +1 -0
- package/templates/be/hooks/husky/pre-commit +13 -0
- package/templates/be/hooks/husky/pre-push +7 -0
- package/templates/be/package-scripts/package.json +21 -0
- package/templates/be/{sonar-project.properties → quality-config/sonar-project.properties} +2 -3
- package/templates/be/skeleton/apps/__app__/src/app.module.ts +14 -4
- package/templates/be/skeleton/apps/__app__/src/main.ts +9 -6
- package/templates/be/skeleton/scripts/.gitkeep +0 -0
- package/templates/be/skeleton/src/features/system-health/application/check-liveness.contracts.ts +2 -0
- package/templates/be/skeleton/src/features/system-health/application/check-liveness.handler.ts +22 -0
- package/templates/be/skeleton/src/features/system-health/application/check-liveness.query.ts +11 -0
- package/templates/be/skeleton/src/features/system-health/index.ts +1 -1
- package/templates/be/skeleton/src/features/system-health/system-health.module.ts +3 -3
- package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.ts +8 -9
- package/templates/be/skeleton/src/features/system-health/transport/http/system-health-http.module.ts +3 -6
- package/templates/be/skeleton/src/modules/domain/liveness/index.ts +3 -0
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.contracts.ts +9 -0
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module.ts +14 -0
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.options.ts +2 -0
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.service.spec.ts +40 -0
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.service.ts +26 -0
- package/templates/be/skeleton/src/modules/platform/clock/clock.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/clock/clock.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/clock/clock.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/clock/clock.port.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/clock/index.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/clock/system-clock.service.spec.ts +20 -0
- package/templates/be/skeleton/src/modules/platform/clock/system-clock.service.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/composition/composition.decorators.ts +8 -0
- package/templates/be/skeleton/src/modules/platform/composition/index.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/config/config.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/config/index.ts +2 -3
- package/templates/be/skeleton/src/modules/platform/config/server.config.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/config/server.options.ts +0 -3
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.handler.ts +27 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.log-events.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module.ts +18 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/index.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/errors/error.filter.ts +8 -12
- package/templates/be/skeleton/src/modules/platform/errors/errors.log-events.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/errors/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/logging/index.ts +4 -4
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +99 -0
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +38 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +23 -10
- package/templates/be/skeleton/src/modules/platform/logging/logging.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.port.ts +15 -0
- package/templates/be/tool-config/eslint.config.mjs +3 -0
- package/templates/be/tool-config/jest.config.js +1 -0
- package/templates/be/tool-config/prettierignore +8 -0
- package/templates/be/tool-config/prettierrc +1 -0
- package/templates/be/tool-config/src/tests/tsconfig.json +5 -0
- package/templates/be/tool-config/tsconfig.build.json +5 -0
- package/templates/be/tool-config/tsconfig.json +11 -0
- package/templates/common/gitignore.base +1 -1
- package/templates/fe/ci-workflows/github/workflows/ci.yml +54 -0
- package/templates/fe/hooks/husky/pre-commit +16 -0
- package/templates/fe/hooks/husky/pre-push +6 -0
- package/templates/fe/package-scripts/package.json +17 -0
- package/templates/fe/parts/api-client.ts +44 -0
- package/templates/fe/parts/api-outcome.ts +7 -0
- package/templates/fe/quality-config/sonar-project.properties +8 -0
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +1 -1
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/not-found.tsx +1 -1
- package/templates/fe/skeleton/apps/__app__/src/app/global-error.tsx +1 -1
- package/templates/fe/skeleton/scripts/.gitkeep +0 -0
- package/templates/fe/skeleton-app/apps/__app__/src/modules/api/client.ts +1 -0
- package/templates/fe/skeleton-app/apps/__app__/src/modules/api/index.ts +3 -0
- package/templates/fe/skeleton-app/apps/__app__/src/modules/api/outcome.ts +1 -0
- package/templates/fe/skeleton-app/apps/__app__/src/modules/i18n/index.ts +4 -0
- package/templates/fe/skeleton-shared/apps/__app__/next.config.ts +12 -0
- package/templates/fe/skeleton-shared/apps/__app__/src/modules/api/index.ts +2 -0
- package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/index.ts +9 -0
- package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/request.ts +5 -0
- package/templates/fe/skeleton-shared/apps/__app__/src/proxy.ts +5 -0
- package/templates/fe/skeleton-shared/packages/__family__-api/package.json +12 -0
- package/templates/fe/skeleton-shared/packages/__family__-api/src/client.ts +1 -0
- package/templates/fe/skeleton-shared/packages/__family__-api/src/index.ts +3 -0
- package/templates/fe/skeleton-shared/packages/__family__-api/src/outcome.ts +1 -0
- package/templates/fe/skeleton-shared/packages/__family__-api/tsconfig.json +5 -0
- package/templates/fe/skeleton-shared/packages/__family__-i18n/package.json +18 -0
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/app.ts +19 -0
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/index.ts +2 -0
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/proxy.ts +12 -0
- package/templates/fe/skeleton-shared/packages/__family__-i18n/src/request.ts +15 -0
- package/templates/fe/skeleton-shared/packages/__family__-i18n/tsconfig.json +5 -0
- package/templates/fe/tool-config/eslint.config.mjs +3 -0
- package/templates/fe/tool-config/prettierignore +10 -0
- package/templates/fe/tool-config/prettierrc +1 -0
- package/templates/fe/tool-config/stylelint.config.mjs +3 -0
- package/templates/fe/tool-config/tsconfig.json +4 -0
- package/templates/be/pre-commit +0 -8
- package/templates/be/skeleton/apps/__app__/src/__app__.composition.spec.ts +0 -31
- package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.spec.ts +0 -13
- package/templates/be/skeleton/src/modules/platform/config/env-source.spec.ts +0 -36
- package/templates/be/skeleton/src/modules/platform/config/server.config.spec.ts +0 -19
- package/templates/be/skeleton/src/modules/platform/errors/domain-error.spec.ts +0 -15
- package/templates/be/skeleton/src/modules/platform/errors/error.filter.spec.ts +0 -38
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.spec.ts +0 -33
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.ts +0 -35
- package/templates/be/skeleton/src/modules/platform/logging/log-id.ts +0 -9
- package/templates/be/skeleton/src/modules/platform/logging/logger.port.ts +0 -19
- package/templates/common/codecov.yml +0 -13
- package/templates/common/pre-push +0 -5
- package/templates/fe/e2e.yml +0 -22
- package/templates/fe/pre-commit +0 -7
- package/templates/fe/skeleton/apps/__app__/src/app/health/live/route.spec.ts +0 -10
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages.spec.ts +0 -27
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.spec.ts +0 -10
- package/templates/fe/sonar-project.properties +0 -11
- /package/templates/be/skeleton/src/modules/platform/config/{env-source.ts → env-source.config.ts} +0 -0
- /package/templates/be/skeleton/src/modules/platform/errors/{domain-error.ts → domain.error.ts} +0 -0
- /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/next.config.ts +0 -0
- /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/config.ts +0 -0
- /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/navigation.ts +0 -0
- /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/request.ts +0 -0
- /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/routing.ts +0 -0
- /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/proxy.ts +0 -0
|
@@ -42,9 +42,13 @@ versioning:
|
|
|
42
42
|
# requiredWhen connections: required only when hfs.json declares a database connection.
|
|
43
43
|
# allows / forbids names inside the instance; the loader exposes them, slot-check enforces them.
|
|
44
44
|
# tests unit-beside | e2e | none.
|
|
45
|
+
# composedBy the app kinds allowed to compose a transport slot (an app of another kind imports none of its modules).
|
|
45
46
|
# budget size budgets (lines per file, files per slot, exports).
|
|
46
47
|
# appKind the app kind of hfs.json apps[].kind whose directory this slot describes.
|
|
47
|
-
# managedBy template
|
|
48
|
+
# managedBy the template directory hfs sync renders this slot's files from. The slot path then lists literal files
|
|
49
|
+
# only, and each is rendered from templates/<profile or common>/<managedBy>/<path, leading dots dropped>;
|
|
50
|
+
# the managed file list lives here and nowhere in code. A difference is HFS_MANAGED_FILE_DRIFT (a
|
|
51
|
+
# back end's tsconfig.json: HFS_TS_STRICT when a flag or an option was added).
|
|
48
52
|
# rules rule ids (the catalog lands with knowledge/hfs/rules.yaml).
|
|
49
53
|
# since / retiredIn / successor lifecycle.
|
|
50
54
|
presenceValues: [required, optional, opt-in, forbidden]
|
|
@@ -80,7 +84,6 @@ tiers:
|
|
|
80
84
|
foundation: {mayImport: [foundation, package], acyclic: true} # modules/{config,i18n,routes,types}
|
|
81
85
|
modules: {mayImport: [foundation, modules, transport, package], acyclic: true}
|
|
82
86
|
transport: {mayImport: [foundation, modules, package]} # modules/api: the one fetch
|
|
83
|
-
e2e: {mayImport: [e2e, package]} # black box: no app source
|
|
84
87
|
package: {mayImport: [package]}
|
|
85
88
|
crossOwner: every import across owners targets the owner's public entry (index.ts or index.tsx); never export *.
|
|
86
89
|
crossApp: apps never import each other; shared code is a packages/<pkg> slot.
|
|
@@ -88,18 +91,69 @@ crossApp: apps never import each other; shared code is a packages/<pkg> slot.
|
|
|
88
91
|
# Parameters the lint factories and checks read per profile (through ruleParams(profile) in scripts/lib/hfs-slots.mjs).
|
|
89
92
|
ruleParams:
|
|
90
93
|
be:
|
|
91
|
-
# BE_MODULE_SHAPE: the only modules that may be @Global(); directories under src/modules/platform/.
|
|
92
|
-
globalModules: [src/modules/platform/config/, src/modules/platform/logging/, src/modules/platform/database/]
|
|
93
94
|
# HFS_SIZE_GROWTH: a file above soft may not grow against its parent commit; a new file stays within soft.
|
|
94
95
|
fileLines: {soft: 500, hardGrowth: true}
|
|
95
96
|
# R21 HFS_DUPLICATE_CODE: the ONE threshold of duplicate code (the architecture machine reads it here; no other file states it):
|
|
96
97
|
# a token-normalised block of at least `lines` source lines and `tokens` tokens that appears twice.
|
|
97
98
|
duplicateBlock: {lines: 8, tokens: 60}
|
|
99
|
+
# R90 BE_INFRA_OWNER: raw infrastructure library (a module specifier or a global reference) -> the platform or
|
|
100
|
+
# integration capabilities allowed to import it; [] means nowhere in a back end. A specifier also covers its subpaths.
|
|
101
|
+
infraOwners:
|
|
102
|
+
fetch: [platform/http]
|
|
103
|
+
axios: [platform/http]
|
|
104
|
+
got: [platform/http]
|
|
105
|
+
undici: [platform/http]
|
|
106
|
+
"node:http": [platform/http]
|
|
107
|
+
"node:https": [platform/http]
|
|
108
|
+
http: [platform/http]
|
|
109
|
+
https: [platform/http]
|
|
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
|
+
redis: [integrations/cache, integrations/redis]
|
|
113
|
+
ioredis: [integrations/cache, integrations/redis]
|
|
114
|
+
bullmq: [platform/messaging]
|
|
115
|
+
"@nestjs/bullmq": [platform/messaging]
|
|
116
|
+
kafkajs: [platform/messaging]
|
|
117
|
+
"@nestjs/schedule": [platform/scheduling, platform/retry, platform/messaging, platform/http]
|
|
118
|
+
setInterval: [platform/scheduling, platform/retry, platform/messaging, platform/http]
|
|
119
|
+
setTimeout: [platform/scheduling, platform/retry, platform/messaging, platform/http]
|
|
120
|
+
"node:timers": [platform/scheduling, platform/retry, platform/messaging, platform/http]
|
|
121
|
+
"node:timers/promises": [platform/scheduling, platform/retry, platform/messaging, platform/http]
|
|
122
|
+
winston: [platform/logging]
|
|
123
|
+
dayjs: [platform/clock, platform/primitives]
|
|
124
|
+
moment: [platform/clock, platform/primitives]
|
|
125
|
+
date-fns-tz: [platform/clock, platform/primitives]
|
|
126
|
+
"@nestjs/config": []
|
|
127
|
+
dotenv: []
|
|
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]}
|
|
146
|
+
# R89 BE_SOURCE_FORM: the closed role-suffix vocabulary of a source file name (<kebab-name>.<suffix>.ts; BE-CONVENTION 1.15)
|
|
147
|
+
# and the suffixes that are never a role. index.ts, main.ts, app.module.ts and the migrations of be.persistence are the
|
|
148
|
+
# only names outside it.
|
|
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]
|
|
98
154
|
fe:
|
|
99
155
|
# Slot budgets (component.tsx 300, hooks 200, modules 400) are stricter than this cap where they apply.
|
|
100
156
|
fileLines: {soft: 500, hardGrowth: true}
|
|
101
|
-
# FE_TRANSPORT_OWNER: the one module per app that may call fetch.
|
|
102
|
-
clientModule: "apps/<app>/src/modules/api/client.ts"
|
|
103
157
|
duplicateBlock: {lines: 8, tokens: 60}
|
|
104
158
|
|
|
105
159
|
slots:
|
|
@@ -121,14 +175,40 @@ slots:
|
|
|
121
175
|
tier: none
|
|
122
176
|
tests: none
|
|
123
177
|
rules: [HFS_ARCH_CONFIG_UNREAD]
|
|
124
|
-
- id:
|
|
125
|
-
profiles: [
|
|
126
|
-
path:
|
|
178
|
+
- id: fe.package-manifest
|
|
179
|
+
profiles: [fe]
|
|
180
|
+
path: package.json
|
|
181
|
+
presence: required
|
|
182
|
+
tracked: tracked
|
|
183
|
+
tier: none
|
|
184
|
+
tests: none
|
|
185
|
+
managedBy: package-scripts # the `scripts` block only; the rest of the file is the repository's
|
|
186
|
+
rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW, HFS_CANON_PIN_DRIFT, HFS_MANAGED_FILE_DRIFT]
|
|
187
|
+
- id: fe.lockfile
|
|
188
|
+
profiles: [fe]
|
|
189
|
+
path: package-lock.json
|
|
190
|
+
presence: required
|
|
191
|
+
tracked: tracked
|
|
192
|
+
tier: none
|
|
193
|
+
tests: none
|
|
194
|
+
rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW]
|
|
195
|
+
- id: be.package-manifest
|
|
196
|
+
profiles: [be]
|
|
197
|
+
path: package.json
|
|
127
198
|
presence: required
|
|
128
199
|
tracked: tracked
|
|
129
200
|
tier: none
|
|
130
201
|
tests: none
|
|
131
|
-
|
|
202
|
+
managedBy: package-scripts # the `scripts` block only; the rest of the file is the repository's
|
|
203
|
+
rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW, HFS_CANON_PIN_DRIFT, HFS_MANAGED_FILE_DRIFT]
|
|
204
|
+
- id: be.lockfile
|
|
205
|
+
profiles: [be]
|
|
206
|
+
path: package-lock.json
|
|
207
|
+
presence: required
|
|
208
|
+
tracked: tracked
|
|
209
|
+
tier: none
|
|
210
|
+
tests: none
|
|
211
|
+
rules: [HFS_PACKAGE_MANAGER_MIXED, HFS_DEP_VERSION_SKEW]
|
|
132
212
|
- id: repo.git-meta
|
|
133
213
|
profiles: [be, fe]
|
|
134
214
|
path: "{.gitignore,.gitattributes}"
|
|
@@ -136,47 +216,89 @@ slots:
|
|
|
136
216
|
tracked: tracked
|
|
137
217
|
tier: none
|
|
138
218
|
tests: none
|
|
139
|
-
|
|
140
|
-
rules: [HFS_GITIGNORE_BLOCK_DRIFT]
|
|
219
|
+
rules: [HFS_GITIGNORE_BLOCK_DRIFT] # the marked block of .gitignore is rendered by hfs sync (templates/<profile>/gitignore)
|
|
141
220
|
- id: repo.tool-config
|
|
142
221
|
profiles: [be, fe]
|
|
143
|
-
path: "{
|
|
144
|
-
presence: required #
|
|
222
|
+
path: "{.editorconfig,.nvmrc}"
|
|
223
|
+
presence: required # plain dotfiles, the repository's own
|
|
145
224
|
tracked: tracked
|
|
146
225
|
tier: none
|
|
147
226
|
tests: none
|
|
148
|
-
managedBy: tool-config
|
|
149
|
-
rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT, HFS_TS_STRICT]
|
|
150
227
|
- id: repo.tool-config-optional
|
|
151
228
|
profiles: [be, fe]
|
|
152
|
-
path:
|
|
229
|
+
path: .npmrc
|
|
153
230
|
presence: optional
|
|
154
231
|
tracked: tracked
|
|
155
232
|
tier: none
|
|
156
233
|
tests: none
|
|
157
|
-
managedBy: tool-config
|
|
158
|
-
rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT]
|
|
159
234
|
- id: be.tool-config
|
|
160
235
|
profiles: [be]
|
|
161
|
-
|
|
236
|
+
# Managed files (hfs sync renders them, hfs check compares them): the whole tool configuration of a back end. Each is
|
|
237
|
+
# a reference to a canon package, never a configuration: tsconfig.json extends @starci/tsconfig/be.json and adds the
|
|
238
|
+
# three path aliases and excludes the world, integration, e2e and contract trees (fixtures stay in the root program: unit
|
|
239
|
+
# specs import them); tsconfig.build.json puts an overlay preset after it and adds only the path-relative options a
|
|
240
|
+
# preset cannot hold; the tests' own src/tests/tsconfig.json (the nearest config of a world, integration, e2e or contract
|
|
241
|
+
# file, so typed lint and typecheck:tests both find it) extends it with the e2e preset; eslint.config.mjs is the
|
|
242
|
+
# one-line starciBeConfig call; jest.config.js calls the @starci/jest-preset factory; .prettierrc names
|
|
243
|
+
# @starci/prettier-config; .prettierignore is the template.
|
|
244
|
+
path: "{tsconfig.json,tsconfig.build.json,src/tests/tsconfig.json,eslint.config.mjs,jest.config.js,.prettierrc,.prettierignore}"
|
|
162
245
|
presence: required
|
|
163
246
|
tracked: tracked
|
|
164
247
|
tier: none
|
|
165
248
|
tests: none
|
|
166
249
|
managedBy: tool-config
|
|
167
|
-
rules: [HFS_TOOL_CONFIG_LOCAL,
|
|
250
|
+
rules: [HFS_MANAGED_FILE_DRIFT, HFS_TOOL_CONFIG_LOCAL, HFS_TS_STRICT]
|
|
251
|
+
- id: be.nest-cli
|
|
252
|
+
profiles: [be]
|
|
253
|
+
path: nest-cli.json
|
|
254
|
+
presence: required
|
|
255
|
+
tracked: tracked
|
|
256
|
+
tier: none
|
|
257
|
+
tests: none
|
|
258
|
+
- id: be.tool-config-local
|
|
259
|
+
profiles: [be]
|
|
260
|
+
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}"
|
|
261
|
+
presence: forbidden
|
|
262
|
+
tracked: external
|
|
263
|
+
tier: none
|
|
264
|
+
tests: none
|
|
265
|
+
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"
|
|
266
|
+
rules: [HFS_TOOL_CONFIG_LOCAL]
|
|
168
267
|
- id: fe.tool-config
|
|
169
268
|
profiles: [fe]
|
|
170
|
-
|
|
171
|
-
|
|
269
|
+
# Managed files (hfs sync renders them, hfs check compares them): the tool configuration of a front end. Each is a
|
|
270
|
+
# reference to a canon package, never a configuration: tsconfig.json extends @starci/tsconfig/next.json and only
|
|
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}"
|
|
276
|
+
presence: required
|
|
172
277
|
tracked: tracked
|
|
173
278
|
tier: none
|
|
174
279
|
tests: none
|
|
175
280
|
managedBy: tool-config
|
|
176
|
-
rules: [HFS_TOOL_CONFIG_LOCAL,
|
|
281
|
+
rules: [HFS_MANAGED_FILE_DRIFT, HFS_TOOL_CONFIG_LOCAL, HFS_RULE_OFF_WITHOUT_REPLACEMENT, HFS_TS_STRICT]
|
|
282
|
+
- id: fe.tool-config-repo
|
|
283
|
+
profiles: [fe]
|
|
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
|
|
286
|
+
presence: optional
|
|
287
|
+
tracked: tracked
|
|
288
|
+
tier: none
|
|
289
|
+
tests: none
|
|
290
|
+
- id: fe.tool-config-local
|
|
291
|
+
profiles: [fe]
|
|
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*}"
|
|
293
|
+
presence: forbidden
|
|
294
|
+
tracked: external
|
|
295
|
+
tier: none
|
|
296
|
+
tests: none
|
|
297
|
+
goesTo: "nowhere: a front end has exactly the managed tool configuration (fe.tool-config); a change to a rule or a flag is proposed in the .claude runtime"
|
|
298
|
+
rules: [HFS_TOOL_CONFIG_LOCAL]
|
|
177
299
|
- id: repo.quality-config
|
|
178
300
|
profiles: [be, fe]
|
|
179
|
-
path:
|
|
301
|
+
path: sonar-project.properties
|
|
180
302
|
presence: required
|
|
181
303
|
tracked: tracked
|
|
182
304
|
tier: none
|
|
@@ -190,7 +312,7 @@ slots:
|
|
|
190
312
|
tracked: tracked
|
|
191
313
|
tier: none
|
|
192
314
|
tests: none
|
|
193
|
-
managedBy:
|
|
315
|
+
managedBy: hooks
|
|
194
316
|
rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
|
|
195
317
|
- id: repo.ci
|
|
196
318
|
profiles: [be, fe]
|
|
@@ -202,7 +324,7 @@ slots:
|
|
|
202
324
|
managedBy: ci-workflows
|
|
203
325
|
rules: [HFS_CI_E2E_AUTOMATIC, HFS_CI_MISSING_CANON, HFS_MANAGED_FILE_DRIFT]
|
|
204
326
|
- id: repo.ci-e2e
|
|
205
|
-
profiles: [be
|
|
327
|
+
profiles: [be]
|
|
206
328
|
path: ".github/workflows/e2e.yml"
|
|
207
329
|
presence: optional # on: workflow_dispatch only
|
|
208
330
|
tracked: tracked
|
|
@@ -219,12 +341,15 @@ slots:
|
|
|
219
341
|
tests: none
|
|
220
342
|
- id: repo.scripts
|
|
221
343
|
profiles: [be, fe]
|
|
222
|
-
|
|
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}"
|
|
223
348
|
presence: optional
|
|
224
349
|
tracked: tracked
|
|
225
350
|
tier: none
|
|
226
351
|
tests: none
|
|
227
|
-
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
|
|
228
353
|
- id: repo.docs
|
|
229
354
|
profiles: [be, fe]
|
|
230
355
|
path: "docs/{adr,runbooks,guides}/**/*.md"
|
|
@@ -269,7 +394,7 @@ slots:
|
|
|
269
394
|
# ----- never tracked: build output the tools need in place ---------------------------------------------
|
|
270
395
|
- id: repo.build-output
|
|
271
396
|
profiles: [be, fe]
|
|
272
|
-
path: "{**/node_modules/,dist/,apps/*/dist/,packages/*/dist/,.next/,apps/*/.next/,coverage/,test-results
|
|
397
|
+
path: "{**/node_modules/,dist/,apps/*/dist/,packages/*/dist/,.next/,apps/*/.next/,coverage/,reports/,test-results/,**/next-env.d.ts,**/*.tsbuildinfo}"
|
|
273
398
|
presence: optional
|
|
274
399
|
tracked: ignored
|
|
275
400
|
tier: none
|
|
@@ -335,7 +460,7 @@ slots:
|
|
|
335
460
|
tracked: tracked
|
|
336
461
|
tier: none
|
|
337
462
|
tests: none
|
|
338
|
-
why: emitted by `npm run contract:emit
|
|
463
|
+
why: emitted by `npm run contract:emit` (`hfs emit-contracts`, never written by hand); CI fails when the emitted file differs (a contract, not a build artefact)
|
|
339
464
|
rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
|
|
340
465
|
- id: be.contract.openapi
|
|
341
466
|
profiles: [be]
|
|
@@ -344,6 +469,7 @@ slots:
|
|
|
344
469
|
tracked: tracked
|
|
345
470
|
tier: none
|
|
346
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
|
|
347
473
|
rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
|
|
348
474
|
- id: fe.contract.copy
|
|
349
475
|
profiles: [fe]
|
|
@@ -365,9 +491,9 @@ slots:
|
|
|
365
491
|
tier: app
|
|
366
492
|
owner: true
|
|
367
493
|
minInstances: 1
|
|
368
|
-
requires: [main.ts, app.module.ts
|
|
369
|
-
allows: [main.ts, app.module.ts, "<app>.
|
|
370
|
-
tests:
|
|
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
|
|
371
497
|
budget: {app.module.ts: 250, main.ts: 80}
|
|
372
498
|
rules: [BE_APP_COMPOSITION_ONLY, BE_APP_BUSINESS_ROLE, BE_DEFAULT_DENY, BE_ERROR_MASKED, BE_FEATURE_NOT_COMPOSED]
|
|
373
499
|
- id: be.app.worker
|
|
@@ -378,8 +504,9 @@ slots:
|
|
|
378
504
|
tracked: tracked
|
|
379
505
|
tier: app
|
|
380
506
|
owner: true
|
|
381
|
-
requires: [main.ts, app.module.ts
|
|
382
|
-
|
|
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
|
|
383
510
|
rules: [BE_APP_COMPOSITION_ONLY, BE_BACKGROUND_UNOWNED]
|
|
384
511
|
- id: be.app.migrate
|
|
385
512
|
profiles: [be]
|
|
@@ -390,8 +517,9 @@ slots:
|
|
|
390
517
|
tracked: tracked
|
|
391
518
|
tier: app
|
|
392
519
|
owner: true
|
|
393
|
-
requires: [main.ts
|
|
394
|
-
|
|
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
|
|
395
523
|
rules: [BE_SCHEMA_AUTHORITY, BE_ENTRYPOINT_ONLY_IN_APPS]
|
|
396
524
|
- id: be.app.cli
|
|
397
525
|
profiles: [be]
|
|
@@ -401,8 +529,9 @@ slots:
|
|
|
401
529
|
tracked: tracked
|
|
402
530
|
tier: app
|
|
403
531
|
owner: true
|
|
404
|
-
requires: [main.ts, app.module.ts
|
|
405
|
-
|
|
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
|
|
406
535
|
|
|
407
536
|
# ----- BE: features ------------------------------------------------------------------------------------
|
|
408
537
|
- id: be.feature
|
|
@@ -415,7 +544,7 @@ slots:
|
|
|
415
544
|
minInstances: 1
|
|
416
545
|
requires: [index.ts, "<feature>.module.ts", application/]
|
|
417
546
|
allows: [index.ts, "<feature>.module.ts", application/, transport/, messages/] # nothing else at feature root
|
|
418
|
-
tests:
|
|
547
|
+
tests: none
|
|
419
548
|
budget: {files: 250, indexExports: 60} # a feature above 250 source files must be split by product capability
|
|
420
549
|
rules: [BE_FEATURE_SHAPE, BE_FEATURE_IMPORTS_FEATURE, BE_PUBLIC_SURFACE, BE_FEATURE_NOT_COMPOSED]
|
|
421
550
|
- id: be.feature.application
|
|
@@ -424,19 +553,19 @@ slots:
|
|
|
424
553
|
presence: required
|
|
425
554
|
tracked: tracked
|
|
426
555
|
tier: feature
|
|
427
|
-
allows: ["<action>.
|
|
556
|
+
allows: ["<action>.{command,query,handler,contracts}.ts"]
|
|
428
557
|
forbids: [graphql/, http/, message/, schedule/, transport/] # no protocol names under application/
|
|
429
|
-
tests:
|
|
430
|
-
rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_ENTITY_IN_CONTRACT]
|
|
558
|
+
tests: none
|
|
559
|
+
rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_ENTITY_IN_CONTRACT, BE_CQRS_SHAPE]
|
|
431
560
|
- id: be.feature.application.support
|
|
432
561
|
profiles: [be]
|
|
433
562
|
path: "src/features/<feature>/application/support/"
|
|
434
563
|
presence: optional # helpers and types local to ONE feature; same tier as application, never a shared home
|
|
435
564
|
tracked: tracked
|
|
436
565
|
tier: feature # feature tier: another feature cannot import it (features never import features)
|
|
437
|
-
allows: ["<name>.ts"
|
|
566
|
+
allows: ["<name>.<role>.ts"] # <role> from ruleParams.be.suffixes; no <name>.types.ts
|
|
438
567
|
forbids: [graphql/, http/, message/, schedule/, transport/]
|
|
439
|
-
tests:
|
|
568
|
+
tests: none
|
|
440
569
|
rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_FEATURE_IMPORTS_FEATURE]
|
|
441
570
|
since: 1.0.0
|
|
442
571
|
- id: be.transport.http
|
|
@@ -446,9 +575,10 @@ slots:
|
|
|
446
575
|
tracked: tracked
|
|
447
576
|
tier: feature
|
|
448
577
|
requires: ["<feature>-http.module.ts"]
|
|
449
|
-
allows: ["<action>.controller.ts", "dto/<action>.{request,response}.ts", "<action>.mapper.ts"
|
|
450
|
-
tests:
|
|
451
|
-
|
|
578
|
+
allows: ["<action>.controller.ts", "dto/<action>.{request,response}.ts", "<action>.mapper.ts"]
|
|
579
|
+
tests: none
|
|
580
|
+
composedBy: [api]
|
|
581
|
+
rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED, BE_TRANSPORT_SHAPE]
|
|
452
582
|
- id: be.transport.graphql
|
|
453
583
|
profiles: [be]
|
|
454
584
|
path: "src/features/<feature>/transport/graphql/"
|
|
@@ -456,9 +586,10 @@ slots:
|
|
|
456
586
|
tracked: tracked
|
|
457
587
|
tier: feature
|
|
458
588
|
requires: ["<feature>-graphql.module.ts"]
|
|
459
|
-
allows: ["<action>.resolver.ts", "dto/<action>.{input,type,args}.ts", "<action>.mapper.ts"
|
|
460
|
-
tests:
|
|
461
|
-
|
|
589
|
+
allows: ["<action>.resolver.ts", "dto/<action>.{input,type,args}.ts", "<action>.mapper.ts"]
|
|
590
|
+
tests: none
|
|
591
|
+
composedBy: [api]
|
|
592
|
+
rules: [BE_DEFAULT_DENY, BE_INPUT_BOUNDED, BE_TRANSPORT_SHAPE]
|
|
462
593
|
- id: be.transport.message
|
|
463
594
|
profiles: [be]
|
|
464
595
|
path: "src/features/<feature>/transport/message/"
|
|
@@ -466,8 +597,9 @@ slots:
|
|
|
466
597
|
tracked: tracked
|
|
467
598
|
tier: feature
|
|
468
599
|
requires: ["<feature>-message.module.ts"]
|
|
469
|
-
allows: ["<event>.consumer.ts"
|
|
470
|
-
tests:
|
|
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
|
|
602
|
+
composedBy: [worker]
|
|
471
603
|
rules: [BE_BACKGROUND_UNOWNED]
|
|
472
604
|
- id: be.transport.schedule
|
|
473
605
|
profiles: [be]
|
|
@@ -476,8 +608,9 @@ slots:
|
|
|
476
608
|
tracked: tracked
|
|
477
609
|
tier: feature
|
|
478
610
|
requires: ["<feature>-schedule.module.ts"]
|
|
479
|
-
allows: ["<job>.job.ts"
|
|
480
|
-
tests:
|
|
611
|
+
allows: ["<job>.job.ts"]
|
|
612
|
+
tests: none
|
|
613
|
+
composedBy: [worker]
|
|
481
614
|
rules: [BE_BACKGROUND_UNOWNED]
|
|
482
615
|
- id: be.transport.websocket
|
|
483
616
|
profiles: [be]
|
|
@@ -486,18 +619,20 @@ slots:
|
|
|
486
619
|
tracked: tracked
|
|
487
620
|
tier: feature
|
|
488
621
|
requires: ["<feature>-websocket.module.ts"]
|
|
489
|
-
allows: ["<channel>.gateway.ts", "dto/*.ts"
|
|
490
|
-
tests:
|
|
622
|
+
allows: ["<channel>.gateway.ts", "dto/*.ts"]
|
|
623
|
+
tests: none
|
|
624
|
+
composedBy: [api]
|
|
491
625
|
since: 1.0.0
|
|
492
626
|
- id: be.feature.transport.cli
|
|
493
627
|
profiles: [be]
|
|
494
628
|
path: "src/features/<feature>/transport/cli/"
|
|
495
|
-
presence: opt-in # a command line entry:
|
|
629
|
+
presence: opt-in # a command line entry: dispatches one command or query, composed by an app of kind cli
|
|
496
630
|
tracked: tracked
|
|
497
631
|
tier: feature
|
|
498
632
|
requires: ["<feature>-cli.module.ts"]
|
|
499
|
-
allows: ["<
|
|
500
|
-
tests:
|
|
633
|
+
allows: ["<name>.cli.ts", "dto/*.ts"] # .cli.ts: .command.ts is a CQRS message (HFS delta 3)
|
|
634
|
+
tests: none
|
|
635
|
+
composedBy: [cli]
|
|
501
636
|
rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_FEATURE_NOT_COMPOSED, BE_ENTRYPOINT_ONLY_IN_APPS]
|
|
502
637
|
since: 1.0.0
|
|
503
638
|
|
|
@@ -510,9 +645,9 @@ slots:
|
|
|
510
645
|
tier: domain
|
|
511
646
|
owner: true
|
|
512
647
|
requires: [index.ts]
|
|
513
|
-
allows: ["<capability>.module.ts", "<capability>.config.ts", "<capability>.options.ts", errors/, messages/, persistence/, policies/, "*.service.ts", "*.
|
|
648
|
+
allows: ["<capability>.module.ts", "<capability>.config.ts", "<capability>.options.ts", errors/, messages/, persistence/, policies/, "*.service.ts", "*.service.spec.ts", "*.contracts.ts"]
|
|
514
649
|
forbids: [testing/, exceptions/, utils/, helpers/, shared/]
|
|
515
|
-
tests: unit-beside
|
|
650
|
+
tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
|
|
516
651
|
budget: {indexExports: 60}
|
|
517
652
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_PUBLIC_SURFACE, BE_ERROR_HOME, BE_CONFIG_OWNER]
|
|
518
653
|
- id: be.errors
|
|
@@ -521,18 +656,17 @@ slots:
|
|
|
521
656
|
presence: optional
|
|
522
657
|
tracked: tracked
|
|
523
658
|
tier: inherit
|
|
524
|
-
allows: ["<capability>.error.ts", "<name>.error.ts"
|
|
525
|
-
tests:
|
|
659
|
+
allows: ["<capability>.error.ts", "<name>.error.ts"] # one family per capability, extends platform/errors DomainError
|
|
660
|
+
tests: none
|
|
526
661
|
rules: [BE_ERROR_HOME]
|
|
527
662
|
- id: be.domain.messages
|
|
528
663
|
profiles: [be]
|
|
529
|
-
path: "src/modules/{domain,integrations}/<capability>/messages/"
|
|
530
|
-
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
|
|
531
666
|
tracked: tracked
|
|
532
667
|
tier: inherit
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
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
|
|
536
670
|
rules: [BE_USER_COPY_LITERAL]
|
|
537
671
|
since: 1.0.0
|
|
538
672
|
- id: be.feature.messages
|
|
@@ -541,9 +675,8 @@ slots:
|
|
|
541
675
|
presence: optional
|
|
542
676
|
tracked: tracked
|
|
543
677
|
tier: feature
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
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
|
|
547
680
|
rules: [BE_USER_COPY_LITERAL]
|
|
548
681
|
since: 1.0.0
|
|
549
682
|
- id: be.persistence
|
|
@@ -552,10 +685,11 @@ slots:
|
|
|
552
685
|
presence: optional
|
|
553
686
|
tracked: tracked
|
|
554
687
|
tier: inherit
|
|
555
|
-
requires: [connection.ts] #
|
|
556
|
-
allows: ["entities/<table>.entity.ts", "migrations/<
|
|
557
|
-
|
|
558
|
-
|
|
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
|
|
689
|
+
allows: ["entities/<table>.entity.ts", "migrations/<epochMs13>-<kebab-name>.ts", "<name>.sql.ts", "<name>.rows.ts", connection.ts]
|
|
690
|
+
forbids: [index.ts, "<name>.repository.ts"]
|
|
691
|
+
tests: none
|
|
692
|
+
rules: [BE_SCHEMA_OWNER, BE_SCHEMA_AUTHORITY, BE_SQL_OUTSIDE_PERSISTENCE]
|
|
559
693
|
- id: be.platform
|
|
560
694
|
profiles: [be]
|
|
561
695
|
path: "src/modules/platform/<capability>/"
|
|
@@ -573,7 +707,8 @@ slots:
|
|
|
573
707
|
requiredInstances: {capability: [config, logging, errors, primitives, clock, i18n]}
|
|
574
708
|
requires: [index.ts]
|
|
575
709
|
forbids: [common/, utils/, shared/, testing/] # primitives live in platform/primitives
|
|
576
|
-
tests: unit-beside
|
|
710
|
+
tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
|
|
711
|
+
budget: {indexExports: 60}
|
|
577
712
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE]
|
|
578
713
|
- id: be.integrations
|
|
579
714
|
profiles: [be]
|
|
@@ -583,7 +718,8 @@ slots:
|
|
|
583
718
|
tier: integrations
|
|
584
719
|
owner: true
|
|
585
720
|
requires: [index.ts, "<provider>.config.ts"]
|
|
586
|
-
tests: unit-beside
|
|
721
|
+
tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
|
|
722
|
+
budget: {indexExports: 60}
|
|
587
723
|
rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT]
|
|
588
724
|
- id: be.integrations.model
|
|
589
725
|
profiles: [be]
|
|
@@ -591,42 +727,104 @@ slots:
|
|
|
591
727
|
presence: opt-in
|
|
592
728
|
tracked: tracked
|
|
593
729
|
tier: integrations
|
|
594
|
-
tests:
|
|
730
|
+
tests: none
|
|
595
731
|
why: an ML or LLM model client is an integration; weights and datasets are never in git
|
|
596
732
|
|
|
597
|
-
# ----- BE: tests
|
|
598
|
-
|
|
733
|
+
# ----- BE: tests (owner test layout 2026-09-30). Unit specs are <name>.service.spec.ts beside their service and nothing else; everything else
|
|
734
|
+
# lives under src/tests/, one folder per kind, and the folder and the file suffix always agree (a file whose suffix does
|
|
735
|
+
# not match its folder matches no slot: HFS_SLOT_UNDECLARED).
|
|
736
|
+
- id: be.tests.world
|
|
599
737
|
profiles: [be]
|
|
600
|
-
path: "src/tests/
|
|
738
|
+
path: "src/tests/world/"
|
|
601
739
|
presence: optional
|
|
602
740
|
tracked: tracked
|
|
603
741
|
tier: e2e
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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)
|
|
743
|
+
tests: none
|
|
744
|
+
why: >-
|
|
745
|
+
the ONLY test infrastructure location, shared by integration and e2e: global-setup.ts starts the shared
|
|
746
|
+
infrastructure (containers) and runs apps/migrate's exported bootstrap once; use-test-world.ts exports
|
|
747
|
+
useTestWorld({ apps: { <name>: { module, listen? } } } | { modules: [...] }) -> world.apps.<name>.api,
|
|
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
|
|
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
|
|
751
|
+
overridden in the DI container. It is the only test location that may import typeorm's DataSource or testcontainers,
|
|
752
|
+
call migrate/runMigrations/synchronize, or write process.env.
|
|
753
|
+
- id: be.tests.world.kit
|
|
615
754
|
profiles: [be]
|
|
616
|
-
path: "src/tests/
|
|
755
|
+
path: "src/tests/world/kit/"
|
|
617
756
|
presence: optional
|
|
618
757
|
tracked: tracked
|
|
619
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)
|
|
620
760
|
tests: none
|
|
621
|
-
why:
|
|
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
|
|
622
762
|
- id: be.tests.fixtures
|
|
623
763
|
profiles: [be]
|
|
624
764
|
path: "src/tests/fixtures/"
|
|
625
765
|
presence: optional
|
|
626
766
|
tracked: tracked
|
|
627
767
|
tier: fixtures
|
|
628
|
-
|
|
629
|
-
|
|
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
|
|
793
|
+
- id: be.tests.integration
|
|
794
|
+
profiles: [be]
|
|
795
|
+
path: "src/tests/integration/<capability>/*.integration-spec.ts"
|
|
796
|
+
presence: optional
|
|
797
|
+
tracked: tracked
|
|
798
|
+
tier: e2e
|
|
799
|
+
tests: e2e
|
|
800
|
+
why: one capability module on the real database, no HTTP (SQL, transactions, concurrency, inbox claims) through useTestWorld({ modules }); run by test:integration
|
|
801
|
+
rules: [BE_TEST_TOPOLOGY]
|
|
802
|
+
- id: be.tests.e2e
|
|
803
|
+
profiles: [be]
|
|
804
|
+
path: "src/tests/e2e/<area>/*.e2e-spec.ts"
|
|
805
|
+
presence: optional
|
|
806
|
+
tracked: tracked
|
|
807
|
+
tier: e2e
|
|
808
|
+
tests: e2e
|
|
809
|
+
why: flow e2e through useTestWorld({ apps }); external services are the world's network fakes; run by test:e2e
|
|
810
|
+
rules: [HFS_CI_E2E_AUTOMATIC, BE_TEST_TOPOLOGY, BE_E2E_FLOW]
|
|
811
|
+
- id: be.tests.contract
|
|
812
|
+
profiles: [be]
|
|
813
|
+
path: "src/tests/contract/<provider>/*.contract-spec.ts"
|
|
814
|
+
presence: optional
|
|
815
|
+
tracked: tracked
|
|
816
|
+
tier: e2e
|
|
817
|
+
tests: e2e
|
|
818
|
+
why: provider sandbox contracts, skipped without sandbox config; run only by test:contract, never part of test or test:e2e
|
|
819
|
+
rules: [BE_TEST_TOPOLOGY]
|
|
820
|
+
- id: be.tests.e2e-world-retired
|
|
821
|
+
profiles: [be]
|
|
822
|
+
path: "src/tests/e2e/world/"
|
|
823
|
+
presence: forbidden
|
|
824
|
+
tracked: external
|
|
825
|
+
tier: none
|
|
826
|
+
tests: none
|
|
827
|
+
goesTo: "src/tests/world/ (the only test infrastructure location; e2e/<area>/ holds only *.e2e-spec.ts)"
|
|
630
828
|
|
|
631
829
|
# ----- BE: work and stacks -----------------------------------------------------------------------------
|
|
632
830
|
- id: be.starciwork
|
|
@@ -651,7 +849,7 @@ slots:
|
|
|
651
849
|
tier: none
|
|
652
850
|
tests: none
|
|
653
851
|
requires: [application-stacks.yaml]
|
|
654
|
-
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",
|
|
655
853
|
"<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.enc", "<env>/seeds/**"]
|
|
656
854
|
forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
|
|
657
855
|
rules: [HFS_STACKS_SHAPE, HFS_PLAINTEXT_SECRET, HFS_IDENTITY_CUSTODY]
|
|
@@ -662,7 +860,6 @@ slots:
|
|
|
662
860
|
tracked: tracked
|
|
663
861
|
tier: none
|
|
664
862
|
tests: none
|
|
665
|
-
managedBy: sops-policy
|
|
666
863
|
|
|
667
864
|
# ----- FE: apps ----------------------------------------------------------------------------------------
|
|
668
865
|
- id: fe.app.next
|
|
@@ -673,13 +870,13 @@ slots:
|
|
|
673
870
|
tracked: tracked
|
|
674
871
|
tier: none
|
|
675
872
|
minInstances: 1
|
|
676
|
-
requires: [package.json, next.config.ts, tsconfig.json, postcss.config.mjs, src/app/, src/modules/i18n/,
|
|
873
|
+
requires: [package.json, next.config.ts, tsconfig.json, postcss.config.mjs, src/app/, src/modules/i18n/,
|
|
677
874
|
"src/app/global-error.tsx", "src/app/[locale]/layout.tsx", "src/app/[locale]/error.tsx", "src/app/[locale]/not-found.tsx", "src/app/[locale]/loading.tsx"]
|
|
678
875
|
tests: none
|
|
679
876
|
rules: [FE_APP_ISOLATION, FE_ERROR_BOUNDARY_MISSING, FE_NEXT_CONVENTIONS]
|
|
680
877
|
- id: fe.app-optional
|
|
681
878
|
profiles: [fe]
|
|
682
|
-
path: "apps/<app>/
|
|
879
|
+
path: "apps/<app>/public/"
|
|
683
880
|
presence: optional
|
|
684
881
|
tracked: tracked
|
|
685
882
|
tier: none
|
|
@@ -690,8 +887,13 @@ slots:
|
|
|
690
887
|
presence: required
|
|
691
888
|
tracked: tracked
|
|
692
889
|
tier: route
|
|
693
|
-
allows: ["[locale]/**/{page,layout,template,loading,error,not-found}.tsx", "[locale]/**/route.ts",
|
|
694
|
-
|
|
890
|
+
allows: ["[locale]/**/{page,layout,template,loading,error,not-found,default}.tsx", "[locale]/**/route.ts", "[locale]/providers.tsx",
|
|
891
|
+
global-error.tsx, page.tsx, # the root page.tsx only redirects (FE_ROUTE_FILES_THIN)
|
|
892
|
+
not-found.tsx, layout.tsx, # next-intl: a request outside every locale renders the root not-found inside a root layout
|
|
893
|
+
"health/{live,ready}/route.ts", "api/**/route.ts", "_*/**", providers.tsx, globals.css,
|
|
894
|
+
"{icon,favicon,apple-icon,opengraph-image,twitter-image}.*", robots.ts, sitemap.ts, manifest.ts]
|
|
895
|
+
# providers.tsx (root or [locale]) is the client wrapper the server layout renders around its children to host context providers.
|
|
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}
|
|
695
897
|
tests: none
|
|
696
898
|
rules: [FE_ROUTE_FILES_THIN, FE_CLIENT_BOUNDARY, FE_OWNER_REACHABLE]
|
|
697
899
|
- id: fe.source-root-pinned
|
|
@@ -700,8 +902,18 @@ slots:
|
|
|
700
902
|
presence: optional
|
|
701
903
|
tracked: tracked
|
|
702
904
|
tier: route
|
|
905
|
+
roles: {proxy: proxy.ts, instrumentation: instrumentation.ts, instrumentation-client: instrumentation-client.ts}
|
|
906
|
+
tests: none
|
|
907
|
+
rules: [FE_NEXT_CONVENTIONS] # middleware.ts is refused on Next >= 16 (fe.source-root-retired)
|
|
908
|
+
- id: fe.source-root-retired
|
|
909
|
+
profiles: [fe]
|
|
910
|
+
path: "apps/<app>/src/{middleware.ts,middleware.js}"
|
|
911
|
+
presence: forbidden
|
|
912
|
+
tracked: external
|
|
913
|
+
tier: none
|
|
703
914
|
tests: none
|
|
704
|
-
|
|
915
|
+
goesTo: "apps/<app>/src/proxy.ts: Next >= 16 renamed middleware to proxy and refuses the old name"
|
|
916
|
+
rules: [FE_NEXT_CONVENTIONS]
|
|
705
917
|
- id: fe.feature
|
|
706
918
|
profiles: [fe]
|
|
707
919
|
path: "apps/<app>/src/features/{pages,layouts,overlays}/<name>/"
|
|
@@ -709,10 +921,12 @@ slots:
|
|
|
709
921
|
tracked: tracked
|
|
710
922
|
tier: feature
|
|
711
923
|
owner: true
|
|
924
|
+
kinds: [pages, layouts, overlays]
|
|
712
925
|
requires: [index.tsx]
|
|
713
|
-
allows: [index.tsx, component.tsx, classNames.ts
|
|
714
|
-
|
|
715
|
-
|
|
926
|
+
allows: [index.tsx, component.tsx, classNames.ts]
|
|
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
|
|
928
|
+
tests: none
|
|
929
|
+
budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
|
|
716
930
|
rules: [FE_SIZE_BUDGET, FE_OWNER_REACHABLE, FE_I18N_LITERAL]
|
|
717
931
|
- id: fe.components
|
|
718
932
|
profiles: [fe]
|
|
@@ -723,8 +937,9 @@ slots:
|
|
|
723
937
|
owner: true
|
|
724
938
|
layers: [blocks, composites, branches, leaves] # a layer imports only the layers after it
|
|
725
939
|
requires: [index.tsx]
|
|
726
|
-
allows: [index.tsx, component.tsx, classNames.ts
|
|
727
|
-
|
|
940
|
+
allows: [index.tsx, component.tsx, classNames.ts]
|
|
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
|
|
942
|
+
tests: none
|
|
728
943
|
budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
|
|
729
944
|
rules: [FE_CONNECTED_BLOCK_RENDER_PAIR, FE_I18N_LITERAL, FE_NATIVE_FORM_CONTROL, FE_CLIENT_BOUNDARY]
|
|
730
945
|
- id: fe.hooks
|
|
@@ -735,9 +950,10 @@ slots:
|
|
|
735
950
|
tier: hooks
|
|
736
951
|
owner: true # a domain is an owner: other domains import it through index.ts only
|
|
737
952
|
requires: [index.ts]
|
|
738
|
-
allows: [index.ts, "use<name>.ts", "<domain>.shared.ts"
|
|
739
|
-
|
|
740
|
-
|
|
953
|
+
allows: [index.ts, "use<name>.ts", "<domain>.shared.ts"] # React hooks only; <domain>.shared.ts = sanctioned non-hook helpers
|
|
954
|
+
roles: {entry: index.ts, shared: "<domain>.shared.ts"}
|
|
955
|
+
tests: none
|
|
956
|
+
budget: {file: 200, dataHooks: 6, useState: 6}
|
|
741
957
|
rules: [FE_HOOKS_ARE_HOOKS, FE_DATA_FRESHNESS]
|
|
742
958
|
- id: fe.modules
|
|
743
959
|
profiles: [fe]
|
|
@@ -746,28 +962,50 @@ slots:
|
|
|
746
962
|
tracked: tracked
|
|
747
963
|
tier: modules
|
|
748
964
|
owner: true
|
|
749
|
-
requiredInstances: {capability: [
|
|
965
|
+
requiredInstances: {capability: [config, i18n, routes]}
|
|
750
966
|
requires: [index.ts]
|
|
751
|
-
tests:
|
|
967
|
+
tests: none
|
|
752
968
|
budget: {file: 400}
|
|
753
969
|
rules: [FE_ENV_OWNER, FE_TRANSPORT_OWNER]
|
|
754
970
|
- id: fe.modules.api
|
|
755
971
|
profiles: [fe]
|
|
756
972
|
path: "apps/<app>/src/modules/api/"
|
|
757
|
-
presence:
|
|
973
|
+
presence: optional
|
|
758
974
|
tracked: tracked
|
|
759
975
|
tier: transport
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
976
|
+
# The app's reads of the one client. A repository has exactly ONE transport client and ONE Outcome union: the app's
|
|
977
|
+
# client.ts/outcome.ts (fe.transport.client/outcome) in a one-app repository, or the api package's
|
|
978
|
+
# (fe.package.api.client/outcome) when the repository shares it (FE_TRANSPORT_OWNER, machine).
|
|
979
|
+
requires: [index.ts]
|
|
980
|
+
allows: [index.ts, client.ts, outcome.ts, "<domain>/read-*.ts", "<domain>/*.graphql", "<domain>/<domain>.mapper.ts", contract/, __generated__/]
|
|
981
|
+
roles: {entry: index.ts, reader: "read-*.ts"} # a reader is a server module (FE_CLIENT_REACHES_SERVER)
|
|
982
|
+
tests: none
|
|
763
983
|
rules: [FE_TRANSPORT_OWNER, FE_HTTP_STATUS_COLLAPSE, FE_WIRE_GENERATED]
|
|
984
|
+
- id: fe.transport.client
|
|
985
|
+
profiles: [fe]
|
|
986
|
+
path: "apps/<app>/src/modules/api/client.ts"
|
|
987
|
+
presence: optional
|
|
988
|
+
tracked: tracked
|
|
989
|
+
tier: transport
|
|
990
|
+
tests: none
|
|
991
|
+
why: the one fetch of a one-app repository (timeout, abort, 401/403 -> refused); a shared client is fe.package.api.client
|
|
992
|
+
rules: [FE_TRANSPORT_OWNER]
|
|
993
|
+
- id: fe.transport.outcome
|
|
994
|
+
profiles: [fe]
|
|
995
|
+
path: "apps/<app>/src/modules/api/outcome.ts"
|
|
996
|
+
presence: optional
|
|
997
|
+
tracked: tracked
|
|
998
|
+
tier: transport
|
|
999
|
+
tests: none
|
|
1000
|
+
why: the one Outcome union of a one-app repository; a shared union is fe.package.api.outcome
|
|
1001
|
+
rules: [FE_HTTP_STATUS_COLLAPSE]
|
|
764
1002
|
- id: fe.modules.config
|
|
765
1003
|
profiles: [fe]
|
|
766
1004
|
path: "apps/<app>/src/modules/config/"
|
|
767
1005
|
presence: required
|
|
768
1006
|
tracked: tracked
|
|
769
1007
|
tier: foundation
|
|
770
|
-
tests:
|
|
1008
|
+
tests: none
|
|
771
1009
|
why: the only reader of process.env and NEXT_PUBLIC_*; fails fast, no localhost fallback
|
|
772
1010
|
- id: fe.modules.i18n
|
|
773
1011
|
profiles: [fe]
|
|
@@ -775,8 +1013,11 @@ slots:
|
|
|
775
1013
|
presence: required
|
|
776
1014
|
tracked: tracked
|
|
777
1015
|
tier: foundation
|
|
778
|
-
|
|
779
|
-
|
|
1016
|
+
# The app's i18n module: its catalogs and the one call of the repository's i18n factory. The next-intl stack (routing,
|
|
1017
|
+
# navigation, request config) is written once per repository: in the i18n package (fe.package.i18n, `createAppI18n`)
|
|
1018
|
+
# when the repository has one, else in the only app's module.
|
|
1019
|
+
requires: [index.ts]
|
|
1020
|
+
tests: none
|
|
780
1021
|
rules: [FE_I18N_PLACEMENT, FE_I18N_CATALOG]
|
|
781
1022
|
- id: fe.modules.brand
|
|
782
1023
|
profiles: [fe]
|
|
@@ -793,7 +1034,7 @@ slots:
|
|
|
793
1034
|
presence: required
|
|
794
1035
|
tracked: tracked
|
|
795
1036
|
tier: foundation
|
|
796
|
-
tests:
|
|
1037
|
+
tests: none
|
|
797
1038
|
why: every href builder; FE_HREF_RESOLVES checks each against app/
|
|
798
1039
|
- id: fe.modules.types
|
|
799
1040
|
profiles: [fe]
|
|
@@ -801,7 +1042,7 @@ slots:
|
|
|
801
1042
|
presence: optional
|
|
802
1043
|
tracked: tracked
|
|
803
1044
|
tier: foundation
|
|
804
|
-
tests:
|
|
1045
|
+
tests: none
|
|
805
1046
|
why: shared types with no runtime behaviour; a component may import them
|
|
806
1047
|
- id: fe.package.ui
|
|
807
1048
|
profiles: [fe]
|
|
@@ -810,26 +1051,53 @@ slots:
|
|
|
810
1051
|
tracked: tracked
|
|
811
1052
|
tier: package
|
|
812
1053
|
owner: true
|
|
1054
|
+
kinds: [composites, branches, leaves] # the layer folders under src/ (a package owns no blocks)
|
|
1055
|
+
roles: {entry: index.tsx, drawing: component.tsx, styles: classNames.ts}
|
|
813
1056
|
requires: [package.json, src/index.ts, tsconfig.json]
|
|
814
|
-
tests:
|
|
1057
|
+
tests: none
|
|
1058
|
+
budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
|
|
815
1059
|
why: built to dist with explicit named exports; only composites, branches and leaves; no unused export; no colour literal
|
|
816
1060
|
rules: [FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT, FE_STYLE_TOKEN_ONLY]
|
|
817
|
-
- id: fe.
|
|
1061
|
+
- id: fe.package.api
|
|
818
1062
|
profiles: [fe]
|
|
819
|
-
path: "
|
|
820
|
-
presence:
|
|
1063
|
+
path: "packages/<family>-api/"
|
|
1064
|
+
presence: opt-in
|
|
821
1065
|
tracked: tracked
|
|
822
|
-
tier:
|
|
823
|
-
|
|
824
|
-
requires: [/
|
|
825
|
-
|
|
826
|
-
|
|
1066
|
+
tier: package
|
|
1067
|
+
owner: true
|
|
1068
|
+
requires: [package.json, src/index.ts, src/client.ts, src/outcome.ts, tsconfig.json]
|
|
1069
|
+
tests: none
|
|
1070
|
+
why: the one transport client and the one Outcome union shared by every app of the repository; no app keeps its own
|
|
1071
|
+
rules: [FE_TRANSPORT_OWNER, FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
|
|
1072
|
+
- id: fe.package.api.client
|
|
827
1073
|
profiles: [fe]
|
|
828
|
-
path: "
|
|
829
|
-
presence: optional
|
|
1074
|
+
path: "packages/<family>-api/src/client.ts"
|
|
1075
|
+
presence: optional # enabled with its package (fe.package.api is the opt-in)
|
|
830
1076
|
tracked: tracked
|
|
831
|
-
tier:
|
|
1077
|
+
tier: package
|
|
1078
|
+
tests: none
|
|
1079
|
+
why: the repository's one fetch (timeout, abort, 401/403 -> refused)
|
|
1080
|
+
rules: [FE_TRANSPORT_OWNER]
|
|
1081
|
+
- id: fe.package.api.outcome
|
|
1082
|
+
profiles: [fe]
|
|
1083
|
+
path: "packages/<family>-api/src/outcome.ts"
|
|
1084
|
+
presence: optional # enabled with its package (fe.package.api is the opt-in)
|
|
1085
|
+
tracked: tracked
|
|
1086
|
+
tier: package
|
|
1087
|
+
tests: none
|
|
1088
|
+
why: the repository's one Outcome union
|
|
1089
|
+
rules: [FE_HTTP_STATUS_COLLAPSE]
|
|
1090
|
+
- id: fe.package.i18n
|
|
1091
|
+
profiles: [fe]
|
|
1092
|
+
path: "packages/<family>-i18n/"
|
|
1093
|
+
presence: opt-in
|
|
1094
|
+
tracked: tracked
|
|
1095
|
+
tier: package
|
|
1096
|
+
owner: true
|
|
1097
|
+
requires: [package.json, src/index.ts, tsconfig.json]
|
|
832
1098
|
tests: none
|
|
1099
|
+
why: the next-intl stack (routing, navigation, request config) written once, exported as the createAppI18n factory every app calls
|
|
1100
|
+
rules: [FE_I18N_PLACEMENT, FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT]
|
|
833
1101
|
|
|
834
1102
|
# Checks that read this manifest (all ship from .claude; none keeps its own path list)
|
|
835
1103
|
consumers:
|
|
@@ -839,5 +1107,5 @@ consumers:
|
|
|
839
1107
|
- packages/eslint/be starciBeConfig({hfs}) # file globs for rules come from slots
|
|
840
1108
|
- packages/eslint/fe starciFeConfig({hfs})
|
|
841
1109
|
- packages/stylelint-canon # colour and brand allowances from fe.modules.brand
|
|
842
|
-
-
|
|
1110
|
+
- packages/hfs/sync # renders managedBy templates (hfs sync) and judges them (hfs check)
|
|
843
1111
|
- modules/kernel/failure-codes.yaml # every rule id listed above has a Vietnamese why
|