@starci/hfs 4.0.10 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/emit/operations.mjs +1 -1
  3. package/emit/type-schema.mjs +2 -1
  4. package/lint/run.mjs +65 -24
  5. package/package.json +1 -1
  6. package/report/order.mjs +2 -0
  7. package/runtime/config.example.yaml +194 -0
  8. package/runtime/engine/by-code-unit.mjs +12 -0
  9. package/runtime/engine/config.mjs +222 -239
  10. package/runtime/engine/invalid-config.mjs +5 -5
  11. package/runtime/engine/model-config.mjs +90 -0
  12. package/runtime/engine/orca-config.mjs +3 -1
  13. package/runtime/engine/release-config.mjs +28 -0
  14. package/runtime/engine/removed-vocabulary.mjs +28 -0
  15. package/runtime/engine/resources-config.mjs +52 -0
  16. package/runtime/engine/runtime-root.mjs +17 -0
  17. package/runtime/engine/secrets.mjs +35 -31
  18. package/runtime/engine/sonar-config.mjs +21 -0
  19. package/runtime/engine/temp-root.mjs +32 -0
  20. package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
  21. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
  22. package/runtime/knowledge/hfs/rules.yaml +161 -65
  23. package/runtime/knowledge/hfs/slots.yaml +11 -14
  24. package/runtime/knowledge/patterns/be/api.yaml +7 -24
  25. package/runtime/knowledge/patterns/be/cli.yaml +3 -12
  26. package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
  27. package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
  28. package/runtime/modules/kernel/failure-codes.yaml +1 -41
  29. package/runtime/modules/kernel/removed-vocabulary.yaml +268 -0
  30. package/runtime/modules/models/registry.yaml +25 -359
  31. package/runtime/modules/models/runtimes.yaml +126 -265
  32. package/runtime/modules/models/tiers.yaml +66 -0
  33. package/runtime/scripts/api/fs/claim-file.mjs +30 -0
  34. package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
  35. package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
  36. package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
  37. package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
  38. package/runtime/scripts/api/git/lib.mjs +34 -1
  39. package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
  40. package/runtime/scripts/api/process/run-program.mjs +2 -1
  41. package/runtime/scripts/api/sops/lib.mjs +198 -120
  42. package/runtime/scripts/hfs/allows.mjs +3 -3
  43. package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
  44. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
  45. package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
  46. package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
  47. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
  48. package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
  49. package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
  50. package/runtime/scripts/hfs/architecture/config.mjs +95 -80
  51. package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
  52. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
  53. package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
  54. package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
  55. package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
  56. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
  57. package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
  58. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
  59. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
  60. package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
  61. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
  62. package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
  63. package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
  64. package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
  65. package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
  66. package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
  67. package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
  68. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
  69. package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
  70. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  71. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
  72. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
  73. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
  74. package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
  75. package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
  76. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
  77. package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
  78. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
  79. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
  80. package/runtime/scripts/hfs/architecture/index.mjs +164 -104
  81. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
  82. package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
  83. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
  84. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
  85. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
  86. package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
  87. package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
  88. package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
  89. package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
  90. package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
  91. package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
  92. package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
  93. package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
  94. package/runtime/scripts/hfs/architecture/required-files.mjs +121 -70
  95. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
  96. package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
  97. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
  98. package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
  99. package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
  100. package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
  101. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
  102. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
  103. package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
  104. package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
  105. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
  106. package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
  107. package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
  108. package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
  109. package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
  110. package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
  111. package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
  112. package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
  113. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
  114. package/runtime/scripts/hfs/architecture.mjs +3 -4
  115. package/runtime/scripts/hfs/catalog-once.mjs +15 -0
  116. package/runtime/scripts/hfs/check.mjs +63 -71
  117. package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
  118. package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
  119. package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
  120. package/runtime/scripts/hfs/edition-slots.mjs +5 -3
  121. package/runtime/scripts/hfs/linear-text.mjs +30 -0
  122. package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
  123. package/runtime/scripts/hfs/path-findings.mjs +58 -38
  124. package/runtime/scripts/hfs/pin-findings.mjs +31 -0
  125. package/runtime/scripts/hfs/repo-identity.mjs +21 -4
  126. package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
  127. package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
  128. package/runtime/scripts/hfs/rules/cli.mjs +5 -1
  129. package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
  130. package/runtime/scripts/hfs/rules/contract.mjs +35 -27
  131. package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
  132. package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
  133. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
  134. package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
  135. package/runtime/scripts/hfs/rules/database.mjs +36 -23
  136. package/runtime/scripts/hfs/rules/deps.mjs +64 -32
  137. package/runtime/scripts/hfs/rules/docker.mjs +113 -65
  138. package/runtime/scripts/hfs/rules/edition.mjs +118 -111
  139. package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
  140. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
  141. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
  142. package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
  143. package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
  144. package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
  145. package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
  146. package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
  147. package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
  148. package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
  149. package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
  150. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
  151. package/runtime/scripts/hfs/rules/saga.mjs +128 -61
  152. package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
  153. package/runtime/scripts/hfs/rules/services.mjs +34 -16
  154. package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
  155. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
  156. package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
  157. package/runtime/scripts/hfs/secret.mjs +59 -48
  158. package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
  159. package/runtime/scripts/hfs/slot-classify.mjs +65 -0
  160. package/runtime/scripts/hfs/slot-errors.mjs +11 -0
  161. package/runtime/scripts/hfs/slot-imports.mjs +76 -0
  162. package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
  163. package/runtime/scripts/hfs/slot-match.mjs +153 -0
  164. package/runtime/scripts/hfs/slot-path.mjs +8 -0
  165. package/runtime/scripts/hfs/slot-required.mjs +76 -0
  166. package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
  167. package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
  168. package/runtime/scripts/hfs/slots.mjs +74 -695
  169. package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
  170. package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
  171. package/runtime/scripts/hfs/tree.mjs +19 -14
  172. package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
  173. package/runtime/scripts/hfs/view.mjs +1 -1
  174. package/runtime/scripts/lib/dockerfile.mjs +56 -33
  175. package/runtime/scripts/lib/env.mjs +10 -0
  176. package/runtime/scripts/lib/event-contract.mjs +17 -13
  177. package/runtime/scripts/lib/git.mjs +2 -2
  178. package/runtime/scripts/lib/graphql-contract.mjs +158 -322
  179. package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
  180. package/runtime/scripts/lib/i18n.mjs +3 -2
  181. package/runtime/scripts/lib/in-order.mjs +71 -0
  182. package/runtime/scripts/lib/language.mjs +3 -3
  183. package/runtime/scripts/lib/list.mjs +8 -1
  184. package/runtime/scripts/lib/path-key.mjs +38 -5
  185. package/runtime/scripts/lib/pid-alive.mjs +7 -0
  186. package/runtime/scripts/lib/regex.mjs +1 -1
  187. package/runtime/scripts/lib/same-text.mjs +1 -1
  188. package/runtime/scripts/lib/secret-patterns.mjs +6 -2
  189. package/runtime/scripts/lib/sleep-sync.mjs +2 -2
  190. package/runtime/scripts/lib/sops-envelope.mjs +95 -48
  191. package/runtime/scripts/lib/stack-services.mjs +1 -2
  192. package/runtime/scripts/lib/ts-ast.mjs +9 -0
  193. package/runtime/scripts/lib/walk.mjs +6 -1
  194. package/runtime/scripts/lib/yaml-cached.mjs +14 -0
  195. package/scaffold/add-cli-lite.mjs +2 -1
  196. package/scaffold/add-table.mjs +2 -2
  197. package/scaffold/add.mjs +3 -2
  198. package/scaffold/app.mjs +4 -2
  199. package/scaffold/edition-gate.mjs +25 -25
  200. package/scaffold/lite-exports.mjs +2 -1
  201. package/scaffold/service.mjs +5 -4
  202. package/sync/hygiene.mjs +15 -9
  203. package/sync/index.mjs +7 -3
  204. package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
  205. package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
  206. package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
  207. package/templates/app/ci-workflows/sonar-steps.yml +2 -2
  208. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
  209. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
  210. package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
  211. package/templates/app/starciwork.gitignore +1 -1
  212. package/templates/be/image/api/Dockerfile +1 -1
  213. package/templates/be/image/cli/Dockerfile +1 -1
  214. package/templates/be/image/worker/Dockerfile +1 -1
  215. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
  216. package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
  217. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
  218. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
  219. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
  220. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
  221. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
  222. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
  223. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
  224. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
  225. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
  226. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
  227. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
  228. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
  229. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
  230. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  231. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
  232. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
  233. package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
  234. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  235. package/templates/fe/image/next/Dockerfile +2 -1
  236. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
  237. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
  238. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
  239. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
  240. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
  241. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
  242. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
  243. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
  244. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
  245. package/upgrade/index.mjs +9 -2
  246. package/runtime/engine/admission.mjs +0 -308
  247. package/runtime/engine/canonical-json.mjs +0 -10
  248. package/runtime/engine/db/blob.mjs +0 -315
  249. package/runtime/engine/db/ledger-paths.mjs +0 -83
  250. package/runtime/engine/db/ledger.mjs +0 -1118
  251. package/runtime/engine/db/machine-connection.mjs +0 -135
  252. package/runtime/engine/db/machine-schema.mjs +0 -84
  253. package/runtime/engine/db/machine.mjs +0 -1367
  254. package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
  255. package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
  256. package/runtime/engine/db/provider-reservations.mjs +0 -101
  257. package/runtime/engine/digest.mjs +0 -16
  258. package/runtime/engine/refuse.mjs +0 -11
  259. package/runtime/modules/ops/_labels.yaml +0 -53
  260. package/runtime/scripts/api/node/lib.mjs +0 -14
  261. package/runtime/scripts/api/node/spawn-node.mjs +0 -6
  262. package/runtime/scripts/api/process/lib.mjs +0 -111
  263. package/runtime/scripts/api/process/owned-process.mjs +0 -78
  264. package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
  265. package/runtime/scripts/connectors/lib.mjs +0 -488
  266. package/runtime/scripts/lib/clip.mjs +0 -19
  267. package/runtime/scripts/lib/display-names.mjs +0 -259
  268. package/runtime/scripts/lib/example-refs.mjs +0 -158
  269. package/runtime/scripts/lib/json-schema.mjs +0 -52
  270. package/runtime/scripts/lib/process-identity.mjs +0 -6
  271. package/runtime/scripts/lib/read-yaml.mjs +0 -18
  272. package/runtime/scripts/lib/redact.mjs +0 -161
  273. package/runtime/scripts/lib/sleep.mjs +0 -7
  274. package/runtime/scripts/lib/source-phrases.mjs +0 -34
  275. package/runtime/scripts/lib/sqlite.mjs +0 -21
@@ -0,0 +1,318 @@
1
+ import { relativePath, UNPROVEN_FRAMEWORK, unwrapExpression } from './typescript.mjs';
2
+ import { constructedDecoratorKind, mutableDecoratorKind, nodeDecorators, tracedFrameworkKinds, violation } from './ast-walks.mjs';
3
+ import { byCodeUnit } from '../../lib/list.mjs';
4
+ function locateSource(h, config, context, sourceFile, roots, state) {
5
+ const { featureRoots, moduleRoots, localFiles } = roots;
6
+ const { canonical, frameworkTargets, locatedRoot, sourceRole, exportedDeclarations, SPECIAL_BASENAMES } = h;
7
+ const fileName = canonical(sourceFile.fileName);
8
+ const featureRoot = locatedRoot(featureRoots, fileName);
9
+ const moduleRoot = locatedRoot(moduleRoots, fileName);
10
+ if (!featureRoot && !moduleRoot) return null;
11
+ state.checkedFiles += 1;
12
+ const checker = context.checkerFor(fileName);
13
+ if (!state.frameworkByChecker.has(checker)) state.frameworkByChecker.set(checker, frameworkTargets(config, context, checker, localFiles));
14
+ const framework = state.frameworkByChecker.get(checker);
15
+ const relativeToOwner = relativePath(featureRoot ?? moduleRoot, fileName);
16
+ const parts = relativeToOwner.split('/');
17
+ const directories = parts.slice(0, -1).map(part => part.toLowerCase());
18
+ const applicationIndex = directories.indexOf('application');
19
+ const transportIndex = directories.indexOf('transport');
20
+ const inApplication = applicationIndex >= 0;
21
+ const inTransport = transportIndex >= 0;
22
+ const role = sourceRole(context.ts, sourceFile);
23
+ const relative = relativePath(config.root, fileName);
24
+ const migrationName = /^\d{10,}-[A-Z][A-Za-z0-9]*$/.test(role.base);
25
+ const persistencePath = !role.spec && (migrationName || directories.includes('migrations'));
26
+ const special = SPECIAL_BASENAMES.has(role.base.toLowerCase()) || migrationName
27
+ || ['constants', 'enums', 'errors', 'migrations'].some(folder => directories.includes(folder));
28
+ const publicDeclarations = exportedDeclarations(context.ts, checker, sourceFile);
29
+ return { config, context, sourceFile, fileName, featureRoot, moduleRoot, checker, framework, directories, transportIndex,
30
+ inApplication, inTransport, role, relative, migrationName, persistencePath, special, publicDeclarations };
31
+ }
32
+
33
+ function reportSourceName(h, info, state) {
34
+ const { kebabSourceBase, SOURCE_NAME_RULE_ID } = h;
35
+ const { config, sourceFile, migrationName, role, relative } = info;
36
+ if (!migrationName && !kebabSourceBase(role.base)) state.violations.push(violation(config, sourceFile, sourceFile, SOURCE_NAME_RULE_ID,
37
+ `Source basename ${role.base} must use kebab-case segments plus an explicit role suffix.`));
38
+ if (!role.role && !info.special) state.namingReasons.push(`${relative} has no statically identifiable source role`);
39
+ }
40
+
41
+ function reportFeatureRoleConflicts(h, info, state) {
42
+ const { SOURCE_LAYOUT_RULE_ID, APPLICATION_ROLES, TRANSPORT_ROLES } = h;
43
+ const { config, sourceFile, role, directories, inApplication, inTransport, transportIndex } = info;
44
+ const { violations } = state;
45
+ if (inApplication && inTransport) violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
46
+ 'A feature source cannot belong to both application and transport layers.'));
47
+ if (role.role && APPLICATION_ROLES.has(role.role) && !inApplication) violations.push(violation(config, sourceFile, sourceFile,
48
+ SOURCE_LAYOUT_RULE_ID, `${role.role} source belongs under the feature application/ layer.`));
49
+ if (role.role && TRANSPORT_ROLES.has(role.role) && !inTransport) violations.push(violation(config, sourceFile, sourceFile,
50
+ SOURCE_LAYOUT_RULE_ID, `${role.role} source belongs under the feature transport/<protocol>/ layer.`));
51
+ if (inApplication && role.role && TRANSPORT_ROLES.has(role.role)) violations.push(violation(config, sourceFile, sourceFile,
52
+ SOURCE_LAYOUT_RULE_ID, `Transport role ${role.role} cannot live in application/.`));
53
+ if (inTransport && role.role && APPLICATION_ROLES.has(role.role)) violations.push(violation(config, sourceFile, sourceFile,
54
+ SOURCE_LAYOUT_RULE_ID, `Application role ${role.role} cannot live in transport/.`));
55
+ if (inTransport && transportIndex >= directories.length - 1) violations.push(violation(config, sourceFile, sourceFile,
56
+ SOURCE_LAYOUT_RULE_ID, 'Feature transport source must name a protocol below transport/.'));
57
+ }
58
+
59
+ /** A non-index source whose role is neither one of the layer's own roles nor one the other layer or the schema owns. */
60
+ function unselectedLayerRole(role, layerRoles, otherRoles) {
61
+ return role.base !== 'index' && (!role.role || !layerRoles.has(role.role)) && !(role.role && (otherRoles.has(role.role) || role.role === 'entity'));
62
+ }
63
+
64
+ function reportFeatureRoleSelection(h, info, state) {
65
+ const { APPLICATION_ROLES, TRANSPORT_ROLES, APPLICATION_LAYER_ROLES, TRANSPORT_LAYER_ROLES } = h;
66
+ const { role, relative, directories, inApplication, inTransport, persistencePath } = info;
67
+ const topLevelFeatureFile = directories.length === 1 && (role.base === 'index' || role.role === 'module' || role.spec);
68
+ if (inApplication && unselectedLayerRole(role, APPLICATION_LAYER_ROLES, TRANSPORT_ROLES)) {
69
+ state.layoutReasons.push(`${relative} role ${role.role ?? '(unclassified)'} is not a selected application-layer role`);
70
+ }
71
+ if (inTransport && unselectedLayerRole(role, TRANSPORT_LAYER_ROLES, APPLICATION_ROLES)) {
72
+ state.layoutReasons.push(`${relative} role ${role.role ?? '(unclassified)'} is not a selected transport-layer role`);
73
+ }
74
+ if (inApplication || inTransport || topLevelFeatureFile) return;
75
+ if (role.role && role.role !== 'entity' && !APPLICATION_ROLES.has(role.role) && !TRANSPORT_ROLES.has(role.role)) {
76
+ state.layoutReasons.push(`${relative} role ${role.role} has no statically selected feature layer`);
77
+ }
78
+ if (!role.role && !persistencePath) state.layoutReasons.push(`${relative} has no statically selected feature layer`);
79
+ }
80
+
81
+ function reportFeatureSchemaPlacement(h, info, state) {
82
+ const { SOURCE_LAYOUT_RULE_ID } = h;
83
+ const { config, sourceFile, role, persistencePath } = info;
84
+ if (persistencePath) state.violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
85
+ 'Migration source cannot be owned by a feature; place it under the declared persistence module.'));
86
+ if (role.role === 'entity') state.violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
87
+ 'Entity source cannot be owned by a feature; place schema under the declared persistence module.'));
88
+ }
89
+
90
+ function reportFeatureLayout(h, info, state) {
91
+ if (!info.featureRoot) return;
92
+ reportFeatureRoleConflicts(h, info, state);
93
+ reportFeatureRoleSelection(h, info, state);
94
+ reportFeatureSchemaPlacement(h, info, state);
95
+ }
96
+
97
+ function inspectClassPlacement(h, info, declaration, kinds, state) {
98
+ const { implementsFramework, SOURCE_LAYOUT_RULE_ID } = h;
99
+ const { config, context, sourceFile, featureRoot, inTransport, directories, transportIndex, checker, framework } = info;
100
+ const { ts } = context;
101
+ const graphQlDto = kinds.some(kind => kind === 'ArgsType' || kind === 'InputType' || kind === 'ObjectType');
102
+ if (graphQlDto && (!featureRoot || !inTransport || directories[transportIndex + 1] !== 'graphql')) {
103
+ state.violations.push(violation(config, sourceFile, declaration.name ?? declaration, SOURCE_LAYOUT_RULE_ID,
104
+ 'GraphQL DTO classes belong under a feature transport/graphql/ boundary.'));
105
+ }
106
+ const persistenceSchema = kinds.includes('Entity') || kinds.includes('ViewEntity')
107
+ || implementsFramework(ts, checker, declaration, framework.bySymbol, 'MigrationInterface');
108
+ if (persistenceSchema && featureRoot) state.violations.push(violation(config, sourceFile, declaration.name ?? declaration, SOURCE_LAYOUT_RULE_ID,
109
+ 'TypeORM entities and migrations cannot be owned by a feature; place schema under its declared persistence module.'));
110
+ }
111
+
112
+ function inspectClassName(h, info, declaration, state) {
113
+ const { implementedPortNames, SOURCE_NAME_RULE_ID, CLASS_ROLE_SUFFIX, NON_CLASS_ROLES } = h;
114
+ const { config, sourceFile, role, relative, checker } = info;
115
+ const { ts } = info.context;
116
+ const { publicDeclarations } = info;
117
+ const classSuffix = role.role && !NON_CLASS_ROLES.has(role.role) ? CLASS_ROLE_SUFFIX.get(role.role) : null;
118
+ if (publicDeclarations.has(declaration) && !declaration.name) state.namingReasons.push(`${relative} exports an anonymous class whose role name cannot be proved`);
119
+ else if (publicDeclarations.has(declaration) && declaration.name && classSuffix && !declaration.name.text.endsWith(classSuffix)
120
+ && !implementedPortNames(ts, checker, declaration).some(port => declaration.name.text.endsWith(port))) {
121
+ state.violations.push(violation(config, sourceFile, declaration.name, SOURCE_NAME_RULE_ID,
122
+ `Exported class ${declaration.name.text} must end in ${classSuffix} for a ${role.role} source file, or in the name of the port it implements.`));
123
+ } else if (publicDeclarations.has(declaration) && declaration.name && role.role && !NON_CLASS_ROLES.has(role.role) && !classSuffix) {
124
+ state.namingReasons.push(`${relative} declares exported class ${declaration.name.text} with unclassified file role ${role.role}`);
125
+ }
126
+ }
127
+
128
+ function inspectClass(h, info, declaration, state) {
129
+ const { decoratorKind } = h;
130
+ const { ts } = info.context;
131
+ const { checker, framework } = info;
132
+ const declarationDecorators = nodeDecorators(ts, declaration);
133
+ const kinds = declarationDecorators.map(decorator => decoratorKind(ts, checker, decorator, framework.bySymbol)).filter(Boolean);
134
+ inspectClassPlacement(h, info, declaration, kinds, state);
135
+ inspectClassName(h, info, declaration, state);
136
+ }
137
+
138
+ function inspectClasses(h, info, state) {
139
+ const declarations = info.sourceFile.statements.filter(statement => info.context.ts.isClassDeclaration(statement));
140
+ for (const declaration of declarations) inspectClass(h, info, declaration, state);
141
+ }
142
+
143
+ function inspectClassValue(h, info, declaration, state) {
144
+ const { classValueStatus, SOURCE_NAME_RULE_ID, CLASS_ROLE_SUFFIX, NON_CLASS_ROLES } = h;
145
+ const { config, sourceFile, relative, role, checker, publicDeclarations } = info;
146
+ const { ts } = info.context;
147
+ if (!publicDeclarations.has(declaration) || !declaration.initializer) return;
148
+ const classValue = classValueStatus(ts, checker, declaration.initializer);
149
+ if (classValue.status === 'unavailable') {
150
+ state.namingReasons.push(`${relative} exports a class-valued declaration whose role identity is not statically proved`);
151
+ return;
152
+ }
153
+ if (classValue.status !== 'class') return;
154
+ const classSuffix = role.role && !NON_CLASS_ROLES.has(role.role) ? CLASS_ROLE_SUFFIX.get(role.role) : null;
155
+ if (!classSuffix || !ts.isIdentifier(declaration.name)) {
156
+ state.namingReasons.push(`${relative} exports a class-valued declaration without a statically selected class role`);
157
+ return;
158
+ }
159
+ const names = [declaration.name.text, ...classValue.names];
160
+ const invalid = names.find(name => !name.endsWith(classSuffix));
161
+ if (invalid) state.violations.push(violation(config, sourceFile, declaration.name, SOURCE_NAME_RULE_ID,
162
+ `Exported class value ${invalid} must end in ${classSuffix} for a ${role.role} source file.`));
163
+ }
164
+
165
+ function inspectClassValues(h, info, state) {
166
+ const { ts } = info.context;
167
+ const { sourceFile } = info;
168
+ for (const statement of sourceFile.statements) if (ts.isVariableStatement(statement)) {
169
+ for (const declaration of statement.declarationList.declarations) inspectClassValue(h, info, declaration, state);
170
+ }
171
+ }
172
+
173
+ function inspectEnumMembers(h, info, statement, state) {
174
+ const { SOURCE_NAME_RULE_ID } = h;
175
+ const { config, sourceFile } = info;
176
+ const { ts } = info.context;
177
+ for (const member of statement.members) {
178
+ const name = ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name) ? member.name.text : null;
179
+ const initializer = member.initializer && unwrapExpression(ts, member.initializer);
180
+ if (!name || !/^[A-Z][A-Za-z0-9]*$/.test(name) || !initializer || !ts.isStringLiteralLike(initializer)) {
181
+ state.violations.push(violation(config, sourceFile, member.name, SOURCE_NAME_RULE_ID,
182
+ 'Enum members must use PascalCase names with explicit static string values.'));
183
+ }
184
+ }
185
+ }
186
+
187
+ function inspectObjectContract(h, info, statement, state) {
188
+ const { objectContractStatus, companionInterfaceAllowed, SOURCE_NAME_RULE_ID, TRANSPORT_OBJECT_SUFFIX } = h;
189
+ const { config, sourceFile, role, checker, publicDeclarations } = info;
190
+ const { ts } = info.context;
191
+ const contractStatus = objectContractStatus(ts, checker, statement);
192
+ const transportSuffix = TRANSPORT_OBJECT_SUFFIX.get(role.role);
193
+ if (contractStatus === 'object' && publicDeclarations.has(statement) && transportSuffix && !role.spec
194
+ && !statement.name.text.endsWith(transportSuffix) && !companionInterfaceAllowed(ts, sourceFile, statement.name.text)) {
195
+ state.violations.push(violation(config, sourceFile, statement.name, SOURCE_NAME_RULE_ID,
196
+ `Public object contract ${statement.name.text} in a ${role.role} source file must end in ${transportSuffix}.`));
197
+ }
198
+ }
199
+
200
+ function inspectEnumAndContract(h, info, statement, state) {
201
+ const { hasModifier, SOURCE_NAME_RULE_ID } = h;
202
+ const { config, sourceFile } = info;
203
+ const { ts } = info.context;
204
+ if (ts.isEnumDeclaration(statement)) {
205
+ if (hasModifier(ts, statement, ts.SyntaxKind.ConstKeyword) || !/^[A-Z][A-Za-z0-9]*$/.test(statement.name.text)) {
206
+ state.violations.push(violation(config, sourceFile, statement.name, SOURCE_NAME_RULE_ID,
207
+ 'Enums must be non-const declarations with PascalCase names.'));
208
+ }
209
+ inspectEnumMembers(h, info, statement, state);
210
+ }
211
+ inspectObjectContract(h, info, statement, state);
212
+ }
213
+
214
+ function inspectEnumsAndContracts(h, info, state) {
215
+ for (const statement of info.sourceFile.statements) inspectEnumAndContract(h, info, statement, state);
216
+ }
217
+
218
+ function inspectDynamicDecorator(info, dynamic, state) {
219
+ const detail = `${info.relative} uses a mutable or constructed ${dynamic} decorator identity`;
220
+ if (['Entity', 'ViewEntity', 'ArgsType', 'InputType', 'ObjectType', 'multiple framework', 'unproven framework'].includes(dynamic)) state.layoutReasons.push(detail);
221
+ if (!['Entity', 'ViewEntity'].includes(dynamic)) state.namingReasons.push(detail);
222
+ }
223
+
224
+ function inspectDecorator(h, info, node, decorator, state) {
225
+ const { graphqlField, graphqlArgument, decoratorKind, TRACE_NEW } = h;
226
+ const { ts } = info.context;
227
+ const { checker, framework } = info;
228
+ const kind = decoratorKind(ts, checker, decorator, framework.bySymbol);
229
+ if (!kind) {
230
+ const dynamic = mutableDecoratorKind(ts, checker, decorator, framework.bySymbol)
231
+ ?? constructedDecoratorKind(ts, checker, decorator, framework.bySymbol, TRACE_NEW);
232
+ if (dynamic) inspectDynamicDecorator(info, dynamic, state);
233
+ }
234
+ if (kind === 'Mutation' || kind === 'Query') graphqlField({ config: info.config, context: info.context, sourceFile: info.sourceFile,
235
+ node, decorator, kind, namingReasons: state.namingReasons, violations: state.violations });
236
+ if (kind === 'Args') graphqlArgument(info.config, info.context, info.sourceFile, node, decorator, state.namingReasons, state.violations);
237
+ }
238
+
239
+ function inspectNodeDecorators(h, info, node, state) {
240
+ for (const decorator of nodeDecorators(info.context.ts, node)) inspectDecorator(h, info, node, decorator, state);
241
+ }
242
+
243
+ function inspectEnumCalls(h, info, node, state) {
244
+ const { graphqlEnumRegistration, selectedCallKind, callsNamedHelper } = h;
245
+ const { ts } = info.context;
246
+ const { checker, framework } = info;
247
+ if (!ts.isCallExpression(node)) return;
248
+ if (selectedCallKind(ts, checker, node, framework.bySymbol) === 'registerEnumType') {
249
+ graphqlEnumRegistration(info.config, info.context, info.sourceFile, node, state.namingReasons, state.violations);
250
+ } else if (callsNamedHelper(ts, checker, node, 'createEnumType')) {
251
+ state.namingReasons.push(`${info.relative} calls a project GraphQL enum adapter whose emitted type name is not statically proved`);
252
+ }
253
+ }
254
+
255
+ function inspectConstructedSource(h, info, node, state) {
256
+ const { SOURCE_LAYOUT_RULE_ID, TRACE_NEW } = h;
257
+ const { ts } = info.context;
258
+ const { checker, framework, publicDeclarations, featureRoot } = info;
259
+ const exportedFactoryCall = ts.isCallExpression(node) && ts.isVariableDeclaration(node.parent) && publicDeclarations.has(node.parent);
260
+ if ((ts.isNewExpression(node) || exportedFactoryCall) && featureRoot) {
261
+ const constructed = tracedFrameworkKinds(ts, checker, node, framework.bySymbol, TRACE_NEW);
262
+ if (constructed.has('EntitySchema')) state.violations.push(violation(info.config, info.sourceFile, node.expression, SOURCE_LAYOUT_RULE_ID,
263
+ 'TypeORM EntitySchema cannot be owned by a feature; place schema under its declared persistence module.'));
264
+ if (constructed.has(UNPROVEN_FRAMEWORK)) state.layoutReasons.push(`${info.relative} has a constructed provider identity beyond the bounded static trace`);
265
+ }
266
+ }
267
+
268
+ function visitSourceNode(h, info, node, state) {
269
+ inspectNodeDecorators(h, info, node, state);
270
+ inspectEnumCalls(h, info, node, state);
271
+ inspectConstructedSource(h, info, node, state);
272
+ info.context.ts.forEachChild(node, child => visitSourceNode(h, info, child, state));
273
+ }
274
+
275
+ function inspectSourceFile(h, config, context, sourceFile, roots, state) {
276
+ const info = locateSource(h, config, context, sourceFile, roots, state);
277
+ if (!info) return;
278
+ reportSourceName(h, info, state);
279
+ reportFeatureLayout(h, info, state);
280
+ inspectClasses(h, info, state);
281
+ inspectClassValues(h, info, state);
282
+ inspectEnumsAndContracts(h, info, state);
283
+ visitSourceNode(h, info, sourceFile, state);
284
+ }
285
+
286
+ function coverageFor(state) {
287
+ for (const framework of state.frameworkByChecker.values()) {
288
+ state.layoutReasons.push(...framework.reasons);
289
+ state.namingReasons.push(...framework.reasons.filter(reason => reason.includes('@nestjs/graphql')));
290
+ }
291
+ if (state.checkedFiles === 0) {
292
+ state.layoutReasons.push('the checked TypeScript program contains no configured backend feature or module source');
293
+ state.namingReasons.push('the checked TypeScript program contains no configured backend feature or module source');
294
+ }
295
+ return {
296
+ files: state.checkedFiles,
297
+ layout: state.layoutReasons.length
298
+ ? { status: 'unavailable', reason: 'one or more backend source placement relations are not statically proved', details: [...new Set(state.layoutReasons)].sort(byCodeUnit) }
299
+ : { status: 'checked' },
300
+ naming: state.namingReasons.length
301
+ ? { status: 'unavailable', reason: 'one or more backend source naming relations are not statically proved', details: [...new Set(state.namingReasons)].sort(byCodeUnit) }
302
+ : { status: 'checked' },
303
+ };
304
+ }
305
+
306
+ /** Check the adopted domain-first backend source layout without inferring semantic ownership from folder names alone. */
307
+ export function createBackendSourceShapeChecker(helpers) {
308
+ const { absoluteRoots, canonical } = helpers;
309
+ return function checkBackendSourceShape(config, context) {
310
+ const featureRoots = absoluteRoots(config.root, config.backend.features);
311
+ const moduleRoots = absoluteRoots(config.root, config.backend.modules);
312
+ const localFiles = new Set(context.files.map(file => canonical(file.fileName)));
313
+ const state = { frameworkByChecker: new Map(), violations: [], layoutReasons: [], namingReasons: [], checkedFiles: 0 };
314
+ const roots = { featureRoots, moduleRoots, localFiles };
315
+ for (const sourceFile of context.files) inspectSourceFile(helpers, config, context, sourceFile, roots, state);
316
+ return { violations: state.violations, coverage: coverageFor(state) };
317
+ };
318
+ }