@starci/hfs 1.0.0 → 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 +57 -0
- package/README.md +116 -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 +10 -2
- 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 +409 -140
- 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 +95 -41
- 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,70 @@ 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
|
-
|
|
96
|
+
# R21 HFS_DUPLICATE_CODE: the ONE threshold of duplicate code (the architecture machine reads it here; no other file states it):
|
|
97
|
+
# a token-normalised block of at least `lines` source lines and `tokens` tokens that appears twice.
|
|
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]
|
|
97
154
|
fe:
|
|
98
155
|
# Slot budgets (component.tsx 300, hooks 200, modules 400) are stricter than this cap where they apply.
|
|
99
156
|
fileLines: {soft: 500, hardGrowth: true}
|
|
100
|
-
|
|
101
|
-
clientModule: "apps/<app>/src/modules/api/client.ts"
|
|
102
|
-
duplicateBlockLines: 25
|
|
157
|
+
duplicateBlock: {lines: 8, tokens: 60}
|
|
103
158
|
|
|
104
159
|
slots:
|
|
105
160
|
|
|
@@ -120,14 +175,40 @@ slots:
|
|
|
120
175
|
tier: none
|
|
121
176
|
tests: none
|
|
122
177
|
rules: [HFS_ARCH_CONFIG_UNREAD]
|
|
123
|
-
- id:
|
|
124
|
-
profiles: [
|
|
125
|
-
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
|
|
126
198
|
presence: required
|
|
127
199
|
tracked: tracked
|
|
128
200
|
tier: none
|
|
129
201
|
tests: none
|
|
130
|
-
|
|
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]
|
|
131
212
|
- id: repo.git-meta
|
|
132
213
|
profiles: [be, fe]
|
|
133
214
|
path: "{.gitignore,.gitattributes}"
|
|
@@ -135,47 +216,89 @@ slots:
|
|
|
135
216
|
tracked: tracked
|
|
136
217
|
tier: none
|
|
137
218
|
tests: none
|
|
138
|
-
|
|
139
|
-
rules: [HFS_GITIGNORE_BLOCK_DRIFT]
|
|
219
|
+
rules: [HFS_GITIGNORE_BLOCK_DRIFT] # the marked block of .gitignore is rendered by hfs sync (templates/<profile>/gitignore)
|
|
140
220
|
- id: repo.tool-config
|
|
141
221
|
profiles: [be, fe]
|
|
142
|
-
path: "{
|
|
143
|
-
presence: required #
|
|
222
|
+
path: "{.editorconfig,.nvmrc}"
|
|
223
|
+
presence: required # plain dotfiles, the repository's own
|
|
144
224
|
tracked: tracked
|
|
145
225
|
tier: none
|
|
146
226
|
tests: none
|
|
147
|
-
managedBy: tool-config
|
|
148
|
-
rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT, HFS_TS_STRICT]
|
|
149
227
|
- id: repo.tool-config-optional
|
|
150
228
|
profiles: [be, fe]
|
|
151
|
-
path:
|
|
229
|
+
path: .npmrc
|
|
152
230
|
presence: optional
|
|
153
231
|
tracked: tracked
|
|
154
232
|
tier: none
|
|
155
233
|
tests: none
|
|
156
|
-
managedBy: tool-config
|
|
157
|
-
rules: [HFS_TOOL_CONFIG_LOCAL, HFS_MANAGED_FILE_DRIFT]
|
|
158
234
|
- id: be.tool-config
|
|
159
235
|
profiles: [be]
|
|
160
|
-
|
|
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}"
|
|
161
245
|
presence: required
|
|
162
246
|
tracked: tracked
|
|
163
247
|
tier: none
|
|
164
248
|
tests: none
|
|
165
249
|
managedBy: tool-config
|
|
166
|
-
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]
|
|
167
267
|
- id: fe.tool-config
|
|
168
268
|
profiles: [fe]
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
171
277
|
tracked: tracked
|
|
172
278
|
tier: none
|
|
173
279
|
tests: none
|
|
174
280
|
managedBy: tool-config
|
|
175
|
-
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]
|
|
176
299
|
- id: repo.quality-config
|
|
177
300
|
profiles: [be, fe]
|
|
178
|
-
path:
|
|
301
|
+
path: sonar-project.properties
|
|
179
302
|
presence: required
|
|
180
303
|
tracked: tracked
|
|
181
304
|
tier: none
|
|
@@ -189,7 +312,7 @@ slots:
|
|
|
189
312
|
tracked: tracked
|
|
190
313
|
tier: none
|
|
191
314
|
tests: none
|
|
192
|
-
managedBy:
|
|
315
|
+
managedBy: hooks
|
|
193
316
|
rules: [HFS_CI_E2E_AUTOMATIC, HFS_MANAGED_FILE_DRIFT]
|
|
194
317
|
- id: repo.ci
|
|
195
318
|
profiles: [be, fe]
|
|
@@ -201,7 +324,7 @@ slots:
|
|
|
201
324
|
managedBy: ci-workflows
|
|
202
325
|
rules: [HFS_CI_E2E_AUTOMATIC, HFS_CI_MISSING_CANON, HFS_MANAGED_FILE_DRIFT]
|
|
203
326
|
- id: repo.ci-e2e
|
|
204
|
-
profiles: [be
|
|
327
|
+
profiles: [be]
|
|
205
328
|
path: ".github/workflows/e2e.yml"
|
|
206
329
|
presence: optional # on: workflow_dispatch only
|
|
207
330
|
tracked: tracked
|
|
@@ -218,12 +341,15 @@ slots:
|
|
|
218
341
|
tests: none
|
|
219
342
|
- id: repo.scripts
|
|
220
343
|
profiles: [be, fe]
|
|
221
|
-
|
|
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}"
|
|
222
348
|
presence: optional
|
|
223
349
|
tracked: tracked
|
|
224
350
|
tier: none
|
|
225
351
|
tests: none
|
|
226
|
-
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
|
|
227
353
|
- id: repo.docs
|
|
228
354
|
profiles: [be, fe]
|
|
229
355
|
path: "docs/{adr,runbooks,guides}/**/*.md"
|
|
@@ -268,7 +394,7 @@ slots:
|
|
|
268
394
|
# ----- never tracked: build output the tools need in place ---------------------------------------------
|
|
269
395
|
- id: repo.build-output
|
|
270
396
|
profiles: [be, fe]
|
|
271
|
-
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}"
|
|
272
398
|
presence: optional
|
|
273
399
|
tracked: ignored
|
|
274
400
|
tier: none
|
|
@@ -334,7 +460,7 @@ slots:
|
|
|
334
460
|
tracked: tracked
|
|
335
461
|
tier: none
|
|
336
462
|
tests: none
|
|
337
|
-
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)
|
|
338
464
|
rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
|
|
339
465
|
- id: be.contract.openapi
|
|
340
466
|
profiles: [be]
|
|
@@ -343,6 +469,7 @@ slots:
|
|
|
343
469
|
tracked: tracked
|
|
344
470
|
tier: none
|
|
345
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
|
|
346
473
|
rules: [HFS_CONTRACT_SNAPSHOT_DRIFT]
|
|
347
474
|
- id: fe.contract.copy
|
|
348
475
|
profiles: [fe]
|
|
@@ -364,9 +491,9 @@ slots:
|
|
|
364
491
|
tier: app
|
|
365
492
|
owner: true
|
|
366
493
|
minInstances: 1
|
|
367
|
-
requires: [main.ts, app.module.ts
|
|
368
|
-
allows: [main.ts, app.module.ts, "<app>.
|
|
369
|
-
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
|
|
370
497
|
budget: {app.module.ts: 250, main.ts: 80}
|
|
371
498
|
rules: [BE_APP_COMPOSITION_ONLY, BE_APP_BUSINESS_ROLE, BE_DEFAULT_DENY, BE_ERROR_MASKED, BE_FEATURE_NOT_COMPOSED]
|
|
372
499
|
- id: be.app.worker
|
|
@@ -377,8 +504,9 @@ slots:
|
|
|
377
504
|
tracked: tracked
|
|
378
505
|
tier: app
|
|
379
506
|
owner: true
|
|
380
|
-
requires: [main.ts, app.module.ts
|
|
381
|
-
|
|
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
|
|
382
510
|
rules: [BE_APP_COMPOSITION_ONLY, BE_BACKGROUND_UNOWNED]
|
|
383
511
|
- id: be.app.migrate
|
|
384
512
|
profiles: [be]
|
|
@@ -389,8 +517,9 @@ slots:
|
|
|
389
517
|
tracked: tracked
|
|
390
518
|
tier: app
|
|
391
519
|
owner: true
|
|
392
|
-
requires: [main.ts
|
|
393
|
-
|
|
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
|
|
394
523
|
rules: [BE_SCHEMA_AUTHORITY, BE_ENTRYPOINT_ONLY_IN_APPS]
|
|
395
524
|
- id: be.app.cli
|
|
396
525
|
profiles: [be]
|
|
@@ -400,8 +529,9 @@ slots:
|
|
|
400
529
|
tracked: tracked
|
|
401
530
|
tier: app
|
|
402
531
|
owner: true
|
|
403
|
-
requires: [main.ts, app.module.ts
|
|
404
|
-
|
|
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
|
|
405
535
|
|
|
406
536
|
# ----- BE: features ------------------------------------------------------------------------------------
|
|
407
537
|
- id: be.feature
|
|
@@ -414,7 +544,7 @@ slots:
|
|
|
414
544
|
minInstances: 1
|
|
415
545
|
requires: [index.ts, "<feature>.module.ts", application/]
|
|
416
546
|
allows: [index.ts, "<feature>.module.ts", application/, transport/, messages/] # nothing else at feature root
|
|
417
|
-
tests:
|
|
547
|
+
tests: none
|
|
418
548
|
budget: {files: 250, indexExports: 60} # a feature above 250 source files must be split by product capability
|
|
419
549
|
rules: [BE_FEATURE_SHAPE, BE_FEATURE_IMPORTS_FEATURE, BE_PUBLIC_SURFACE, BE_FEATURE_NOT_COMPOSED]
|
|
420
550
|
- id: be.feature.application
|
|
@@ -423,19 +553,19 @@ slots:
|
|
|
423
553
|
presence: required
|
|
424
554
|
tracked: tracked
|
|
425
555
|
tier: feature
|
|
426
|
-
allows: ["<action>.
|
|
556
|
+
allows: ["<action>.{command,query,handler,contracts}.ts"]
|
|
427
557
|
forbids: [graphql/, http/, message/, schedule/, transport/] # no protocol names under application/
|
|
428
|
-
tests:
|
|
429
|
-
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]
|
|
430
560
|
- id: be.feature.application.support
|
|
431
561
|
profiles: [be]
|
|
432
562
|
path: "src/features/<feature>/application/support/"
|
|
433
563
|
presence: optional # helpers and types local to ONE feature; same tier as application, never a shared home
|
|
434
564
|
tracked: tracked
|
|
435
565
|
tier: feature # feature tier: another feature cannot import it (features never import features)
|
|
436
|
-
allows: ["<name>.ts"
|
|
566
|
+
allows: ["<name>.<role>.ts"] # <role> from ruleParams.be.suffixes; no <name>.types.ts
|
|
437
567
|
forbids: [graphql/, http/, message/, schedule/, transport/]
|
|
438
|
-
tests:
|
|
568
|
+
tests: none
|
|
439
569
|
rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_APPLICATION_TRANSPORT_FRAMEWORK, BE_FEATURE_IMPORTS_FEATURE]
|
|
440
570
|
since: 1.0.0
|
|
441
571
|
- id: be.transport.http
|
|
@@ -445,9 +575,10 @@ slots:
|
|
|
445
575
|
tracked: tracked
|
|
446
576
|
tier: feature
|
|
447
577
|
requires: ["<feature>-http.module.ts"]
|
|
448
|
-
allows: ["<action>.controller.ts", "dto/<action>.{request,response}.ts", "<action>.mapper.ts"
|
|
449
|
-
tests:
|
|
450
|
-
|
|
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]
|
|
451
582
|
- id: be.transport.graphql
|
|
452
583
|
profiles: [be]
|
|
453
584
|
path: "src/features/<feature>/transport/graphql/"
|
|
@@ -455,9 +586,10 @@ slots:
|
|
|
455
586
|
tracked: tracked
|
|
456
587
|
tier: feature
|
|
457
588
|
requires: ["<feature>-graphql.module.ts"]
|
|
458
|
-
allows: ["<action>.resolver.ts", "dto/<action>.{input,type,args}.ts", "<action>.mapper.ts"
|
|
459
|
-
tests:
|
|
460
|
-
|
|
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]
|
|
461
593
|
- id: be.transport.message
|
|
462
594
|
profiles: [be]
|
|
463
595
|
path: "src/features/<feature>/transport/message/"
|
|
@@ -465,8 +597,9 @@ slots:
|
|
|
465
597
|
tracked: tracked
|
|
466
598
|
tier: feature
|
|
467
599
|
requires: ["<feature>-message.module.ts"]
|
|
468
|
-
allows: ["<event>.consumer.ts"
|
|
469
|
-
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]
|
|
470
603
|
rules: [BE_BACKGROUND_UNOWNED]
|
|
471
604
|
- id: be.transport.schedule
|
|
472
605
|
profiles: [be]
|
|
@@ -475,8 +608,9 @@ slots:
|
|
|
475
608
|
tracked: tracked
|
|
476
609
|
tier: feature
|
|
477
610
|
requires: ["<feature>-schedule.module.ts"]
|
|
478
|
-
allows: ["<job>.job.ts"
|
|
479
|
-
tests:
|
|
611
|
+
allows: ["<job>.job.ts"]
|
|
612
|
+
tests: none
|
|
613
|
+
composedBy: [worker]
|
|
480
614
|
rules: [BE_BACKGROUND_UNOWNED]
|
|
481
615
|
- id: be.transport.websocket
|
|
482
616
|
profiles: [be]
|
|
@@ -485,18 +619,20 @@ slots:
|
|
|
485
619
|
tracked: tracked
|
|
486
620
|
tier: feature
|
|
487
621
|
requires: ["<feature>-websocket.module.ts"]
|
|
488
|
-
allows: ["<channel>.gateway.ts", "dto/*.ts"
|
|
489
|
-
tests:
|
|
622
|
+
allows: ["<channel>.gateway.ts", "dto/*.ts"]
|
|
623
|
+
tests: none
|
|
624
|
+
composedBy: [api]
|
|
490
625
|
since: 1.0.0
|
|
491
626
|
- id: be.feature.transport.cli
|
|
492
627
|
profiles: [be]
|
|
493
628
|
path: "src/features/<feature>/transport/cli/"
|
|
494
|
-
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
|
|
495
630
|
tracked: tracked
|
|
496
631
|
tier: feature
|
|
497
632
|
requires: ["<feature>-cli.module.ts"]
|
|
498
|
-
allows: ["<
|
|
499
|
-
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]
|
|
500
636
|
rules: [BE_APPLICATION_IMPORTS_TRANSPORT, BE_FEATURE_NOT_COMPOSED, BE_ENTRYPOINT_ONLY_IN_APPS]
|
|
501
637
|
since: 1.0.0
|
|
502
638
|
|
|
@@ -509,9 +645,9 @@ slots:
|
|
|
509
645
|
tier: domain
|
|
510
646
|
owner: true
|
|
511
647
|
requires: [index.ts]
|
|
512
|
-
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"]
|
|
513
649
|
forbids: [testing/, exceptions/, utils/, helpers/, shared/]
|
|
514
|
-
tests: unit-beside
|
|
650
|
+
tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
|
|
515
651
|
budget: {indexExports: 60}
|
|
516
652
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_PUBLIC_SURFACE, BE_ERROR_HOME, BE_CONFIG_OWNER]
|
|
517
653
|
- id: be.errors
|
|
@@ -520,18 +656,17 @@ slots:
|
|
|
520
656
|
presence: optional
|
|
521
657
|
tracked: tracked
|
|
522
658
|
tier: inherit
|
|
523
|
-
allows: ["<capability>.error.ts", "<name>.error.ts"
|
|
524
|
-
tests:
|
|
659
|
+
allows: ["<capability>.error.ts", "<name>.error.ts"] # one family per capability, extends platform/errors DomainError
|
|
660
|
+
tests: none
|
|
525
661
|
rules: [BE_ERROR_HOME]
|
|
526
662
|
- id: be.domain.messages
|
|
527
663
|
profiles: [be]
|
|
528
|
-
path: "src/modules/{domain,integrations}/<capability>/messages/"
|
|
529
|
-
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
|
|
530
666
|
tracked: tracked
|
|
531
667
|
tier: inherit
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
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
|
|
535
670
|
rules: [BE_USER_COPY_LITERAL]
|
|
536
671
|
since: 1.0.0
|
|
537
672
|
- id: be.feature.messages
|
|
@@ -540,9 +675,8 @@ slots:
|
|
|
540
675
|
presence: optional
|
|
541
676
|
tracked: tracked
|
|
542
677
|
tier: feature
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
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
|
|
546
680
|
rules: [BE_USER_COPY_LITERAL]
|
|
547
681
|
since: 1.0.0
|
|
548
682
|
- id: be.persistence
|
|
@@ -551,10 +685,11 @@ slots:
|
|
|
551
685
|
presence: optional
|
|
552
686
|
tracked: tracked
|
|
553
687
|
tier: inherit
|
|
554
|
-
requires: [connection.ts] #
|
|
555
|
-
allows: ["entities/<table>.entity.ts", "migrations/<
|
|
556
|
-
|
|
557
|
-
|
|
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]
|
|
558
693
|
- id: be.platform
|
|
559
694
|
profiles: [be]
|
|
560
695
|
path: "src/modules/platform/<capability>/"
|
|
@@ -572,7 +707,8 @@ slots:
|
|
|
572
707
|
requiredInstances: {capability: [config, logging, errors, primitives, clock, i18n]}
|
|
573
708
|
requires: [index.ts]
|
|
574
709
|
forbids: [common/, utils/, shared/, testing/] # primitives live in platform/primitives
|
|
575
|
-
tests: unit-beside
|
|
710
|
+
tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
|
|
711
|
+
budget: {indexExports: 60}
|
|
576
712
|
rules: [BE_TIER_DIRECTION, ARCH_OWNER_CYCLE, BE_MODULE_SHAPE]
|
|
577
713
|
- id: be.integrations
|
|
578
714
|
profiles: [be]
|
|
@@ -582,7 +718,8 @@ slots:
|
|
|
582
718
|
tier: integrations
|
|
583
719
|
owner: true
|
|
584
720
|
requires: [index.ts, "<provider>.config.ts"]
|
|
585
|
-
tests: unit-beside
|
|
721
|
+
tests: unit-beside # <name>.service.spec.ts beside each <name>.service.ts, nothing else
|
|
722
|
+
budget: {indexExports: 60}
|
|
586
723
|
rules: [BE_TIER_DIRECTION, BE_ERROR_HOME, BE_SECRET_DEFAULT]
|
|
587
724
|
- id: be.integrations.model
|
|
588
725
|
profiles: [be]
|
|
@@ -590,42 +727,104 @@ slots:
|
|
|
590
727
|
presence: opt-in
|
|
591
728
|
tracked: tracked
|
|
592
729
|
tier: integrations
|
|
593
|
-
tests:
|
|
730
|
+
tests: none
|
|
594
731
|
why: an ML or LLM model client is an integration; weights and datasets are never in git
|
|
595
732
|
|
|
596
|
-
# ----- BE: tests
|
|
597
|
-
|
|
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
|
|
598
737
|
profiles: [be]
|
|
599
|
-
path: "src/tests/
|
|
738
|
+
path: "src/tests/world/"
|
|
600
739
|
presence: optional
|
|
601
740
|
tracked: tracked
|
|
602
741
|
tier: e2e
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
|
614
754
|
profiles: [be]
|
|
615
|
-
path: "src/tests/
|
|
755
|
+
path: "src/tests/world/kit/"
|
|
616
756
|
presence: optional
|
|
617
757
|
tracked: tracked
|
|
618
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)
|
|
619
760
|
tests: none
|
|
620
|
-
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
|
|
621
762
|
- id: be.tests.fixtures
|
|
622
763
|
profiles: [be]
|
|
623
764
|
path: "src/tests/fixtures/"
|
|
624
765
|
presence: optional
|
|
625
766
|
tracked: tracked
|
|
626
767
|
tier: fixtures
|
|
627
|
-
|
|
628
|
-
|
|
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)"
|
|
629
828
|
|
|
630
829
|
# ----- BE: work and stacks -----------------------------------------------------------------------------
|
|
631
830
|
- id: be.starciwork
|
|
@@ -650,7 +849,7 @@ slots:
|
|
|
650
849
|
tier: none
|
|
651
850
|
tests: none
|
|
652
851
|
requires: [application-stacks.yaml]
|
|
653
|
-
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",
|
|
654
853
|
"<env>/runtime/{config,env}/**", "<env>/runtime/env/KEYS.md", "<env>/secrets/<slug>.enc", "<env>/seeds/**"]
|
|
655
854
|
forbids: ["<env>/runtime/files/**", "**/*.enc outside <env>/secrets/", DESIGN.md, deployment.json, "k8s/ at root"]
|
|
656
855
|
rules: [HFS_STACKS_SHAPE, HFS_PLAINTEXT_SECRET, HFS_IDENTITY_CUSTODY]
|
|
@@ -661,7 +860,6 @@ slots:
|
|
|
661
860
|
tracked: tracked
|
|
662
861
|
tier: none
|
|
663
862
|
tests: none
|
|
664
|
-
managedBy: sops-policy
|
|
665
863
|
|
|
666
864
|
# ----- FE: apps ----------------------------------------------------------------------------------------
|
|
667
865
|
- id: fe.app.next
|
|
@@ -672,13 +870,13 @@ slots:
|
|
|
672
870
|
tracked: tracked
|
|
673
871
|
tier: none
|
|
674
872
|
minInstances: 1
|
|
675
|
-
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/,
|
|
676
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"]
|
|
677
875
|
tests: none
|
|
678
876
|
rules: [FE_APP_ISOLATION, FE_ERROR_BOUNDARY_MISSING, FE_NEXT_CONVENTIONS]
|
|
679
877
|
- id: fe.app-optional
|
|
680
878
|
profiles: [fe]
|
|
681
|
-
path: "apps/<app>/
|
|
879
|
+
path: "apps/<app>/public/"
|
|
682
880
|
presence: optional
|
|
683
881
|
tracked: tracked
|
|
684
882
|
tier: none
|
|
@@ -689,8 +887,13 @@ slots:
|
|
|
689
887
|
presence: required
|
|
690
888
|
tracked: tracked
|
|
691
889
|
tier: route
|
|
692
|
-
allows: ["[locale]/**/{page,layout,template,loading,error,not-found}.tsx", "[locale]/**/route.ts",
|
|
693
|
-
|
|
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}
|
|
694
897
|
tests: none
|
|
695
898
|
rules: [FE_ROUTE_FILES_THIN, FE_CLIENT_BOUNDARY, FE_OWNER_REACHABLE]
|
|
696
899
|
- id: fe.source-root-pinned
|
|
@@ -699,8 +902,18 @@ slots:
|
|
|
699
902
|
presence: optional
|
|
700
903
|
tracked: tracked
|
|
701
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
|
|
702
914
|
tests: none
|
|
703
|
-
|
|
915
|
+
goesTo: "apps/<app>/src/proxy.ts: Next >= 16 renamed middleware to proxy and refuses the old name"
|
|
916
|
+
rules: [FE_NEXT_CONVENTIONS]
|
|
704
917
|
- id: fe.feature
|
|
705
918
|
profiles: [fe]
|
|
706
919
|
path: "apps/<app>/src/features/{pages,layouts,overlays}/<name>/"
|
|
@@ -708,10 +921,12 @@ slots:
|
|
|
708
921
|
tracked: tracked
|
|
709
922
|
tier: feature
|
|
710
923
|
owner: true
|
|
924
|
+
kinds: [pages, layouts, overlays]
|
|
711
925
|
requires: [index.tsx]
|
|
712
|
-
allows: [index.tsx, component.tsx, classNames.ts
|
|
713
|
-
|
|
714
|
-
|
|
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}
|
|
715
930
|
rules: [FE_SIZE_BUDGET, FE_OWNER_REACHABLE, FE_I18N_LITERAL]
|
|
716
931
|
- id: fe.components
|
|
717
932
|
profiles: [fe]
|
|
@@ -722,8 +937,9 @@ slots:
|
|
|
722
937
|
owner: true
|
|
723
938
|
layers: [blocks, composites, branches, leaves] # a layer imports only the layers after it
|
|
724
939
|
requires: [index.tsx]
|
|
725
|
-
allows: [index.tsx, component.tsx, classNames.ts
|
|
726
|
-
|
|
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
|
|
727
943
|
budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
|
|
728
944
|
rules: [FE_CONNECTED_BLOCK_RENDER_PAIR, FE_I18N_LITERAL, FE_NATIVE_FORM_CONTROL, FE_CLIENT_BOUNDARY]
|
|
729
945
|
- id: fe.hooks
|
|
@@ -734,9 +950,10 @@ slots:
|
|
|
734
950
|
tier: hooks
|
|
735
951
|
owner: true # a domain is an owner: other domains import it through index.ts only
|
|
736
952
|
requires: [index.ts]
|
|
737
|
-
allows: [index.ts, "use<name>.ts", "<domain>.shared.ts"
|
|
738
|
-
|
|
739
|
-
|
|
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}
|
|
740
957
|
rules: [FE_HOOKS_ARE_HOOKS, FE_DATA_FRESHNESS]
|
|
741
958
|
- id: fe.modules
|
|
742
959
|
profiles: [fe]
|
|
@@ -745,28 +962,50 @@ slots:
|
|
|
745
962
|
tracked: tracked
|
|
746
963
|
tier: modules
|
|
747
964
|
owner: true
|
|
748
|
-
requiredInstances: {capability: [
|
|
965
|
+
requiredInstances: {capability: [config, i18n, routes]}
|
|
749
966
|
requires: [index.ts]
|
|
750
|
-
tests:
|
|
967
|
+
tests: none
|
|
751
968
|
budget: {file: 400}
|
|
752
969
|
rules: [FE_ENV_OWNER, FE_TRANSPORT_OWNER]
|
|
753
970
|
- id: fe.modules.api
|
|
754
971
|
profiles: [fe]
|
|
755
972
|
path: "apps/<app>/src/modules/api/"
|
|
756
|
-
presence:
|
|
973
|
+
presence: optional
|
|
757
974
|
tracked: tracked
|
|
758
975
|
tier: transport
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
|
762
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]
|
|
763
1002
|
- id: fe.modules.config
|
|
764
1003
|
profiles: [fe]
|
|
765
1004
|
path: "apps/<app>/src/modules/config/"
|
|
766
1005
|
presence: required
|
|
767
1006
|
tracked: tracked
|
|
768
1007
|
tier: foundation
|
|
769
|
-
tests:
|
|
1008
|
+
tests: none
|
|
770
1009
|
why: the only reader of process.env and NEXT_PUBLIC_*; fails fast, no localhost fallback
|
|
771
1010
|
- id: fe.modules.i18n
|
|
772
1011
|
profiles: [fe]
|
|
@@ -774,8 +1013,11 @@ slots:
|
|
|
774
1013
|
presence: required
|
|
775
1014
|
tracked: tracked
|
|
776
1015
|
tier: foundation
|
|
777
|
-
|
|
778
|
-
|
|
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
|
|
779
1021
|
rules: [FE_I18N_PLACEMENT, FE_I18N_CATALOG]
|
|
780
1022
|
- id: fe.modules.brand
|
|
781
1023
|
profiles: [fe]
|
|
@@ -792,7 +1034,7 @@ slots:
|
|
|
792
1034
|
presence: required
|
|
793
1035
|
tracked: tracked
|
|
794
1036
|
tier: foundation
|
|
795
|
-
tests:
|
|
1037
|
+
tests: none
|
|
796
1038
|
why: every href builder; FE_HREF_RESOLVES checks each against app/
|
|
797
1039
|
- id: fe.modules.types
|
|
798
1040
|
profiles: [fe]
|
|
@@ -800,7 +1042,7 @@ slots:
|
|
|
800
1042
|
presence: optional
|
|
801
1043
|
tracked: tracked
|
|
802
1044
|
tier: foundation
|
|
803
|
-
tests:
|
|
1045
|
+
tests: none
|
|
804
1046
|
why: shared types with no runtime behaviour; a component may import them
|
|
805
1047
|
- id: fe.package.ui
|
|
806
1048
|
profiles: [fe]
|
|
@@ -809,26 +1051,53 @@ slots:
|
|
|
809
1051
|
tracked: tracked
|
|
810
1052
|
tier: package
|
|
811
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}
|
|
812
1056
|
requires: [package.json, src/index.ts, tsconfig.json]
|
|
813
|
-
tests:
|
|
1057
|
+
tests: none
|
|
1058
|
+
budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
|
|
814
1059
|
why: built to dist with explicit named exports; only composites, branches and leaves; no unused export; no colour literal
|
|
815
1060
|
rules: [FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT, FE_STYLE_TOKEN_ONLY]
|
|
816
|
-
- id: fe.
|
|
1061
|
+
- id: fe.package.api
|
|
817
1062
|
profiles: [fe]
|
|
818
|
-
path: "
|
|
819
|
-
presence:
|
|
1063
|
+
path: "packages/<family>-api/"
|
|
1064
|
+
presence: opt-in
|
|
820
1065
|
tracked: tracked
|
|
821
|
-
tier:
|
|
822
|
-
|
|
823
|
-
requires: [/
|
|
824
|
-
|
|
825
|
-
|
|
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
|
|
826
1073
|
profiles: [fe]
|
|
827
|
-
path: "
|
|
828
|
-
presence: optional
|
|
1074
|
+
path: "packages/<family>-api/src/client.ts"
|
|
1075
|
+
presence: optional # enabled with its package (fe.package.api is the opt-in)
|
|
829
1076
|
tracked: tracked
|
|
830
|
-
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]
|
|
831
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]
|
|
832
1101
|
|
|
833
1102
|
# Checks that read this manifest (all ship from .claude; none keeps its own path list)
|
|
834
1103
|
consumers:
|
|
@@ -838,5 +1107,5 @@ consumers:
|
|
|
838
1107
|
- packages/eslint/be starciBeConfig({hfs}) # file globs for rules come from slots
|
|
839
1108
|
- packages/eslint/fe starciFeConfig({hfs})
|
|
840
1109
|
- packages/stylelint-canon # colour and brand allowances from fe.modules.brand
|
|
841
|
-
-
|
|
1110
|
+
- packages/hfs/sync # renders managedBy templates (hfs sync) and judges them (hfs check)
|
|
842
1111
|
- modules/kernel/failure-codes.yaml # every rule id listed above has a Vietnamese why
|