@starci/hfs 4.0.10 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/emit/operations.mjs +1 -1
  3. package/emit/type-schema.mjs +2 -1
  4. package/lint/run.mjs +3 -2
  5. package/package.json +1 -1
  6. package/report/order.mjs +2 -0
  7. package/runtime/config.example.yaml +187 -0
  8. package/runtime/engine/by-code-unit.mjs +12 -0
  9. package/runtime/engine/config.mjs +221 -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/removed-vocabulary.mjs +28 -0
  14. package/runtime/engine/resources-config.mjs +52 -0
  15. package/runtime/engine/runtime-root.mjs +17 -0
  16. package/runtime/engine/secrets.mjs +35 -31
  17. package/runtime/engine/sonar-config.mjs +21 -0
  18. package/runtime/engine/temp-root.mjs +32 -0
  19. package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
  20. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
  21. package/runtime/knowledge/hfs/rules.yaml +98 -65
  22. package/runtime/knowledge/hfs/slots.yaml +11 -14
  23. package/runtime/knowledge/patterns/be/api.yaml +7 -24
  24. package/runtime/knowledge/patterns/be/cli.yaml +3 -12
  25. package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
  26. package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
  27. package/runtime/modules/kernel/failure-codes.yaml +1 -41
  28. package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
  29. package/runtime/modules/models/registry.yaml +25 -359
  30. package/runtime/modules/models/runtimes.yaml +92 -256
  31. package/runtime/modules/models/tiers.yaml +65 -0
  32. package/runtime/scripts/api/fs/claim-file.mjs +30 -0
  33. package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
  34. package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
  35. package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
  36. package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
  37. package/runtime/scripts/api/git/lib.mjs +34 -1
  38. package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
  39. package/runtime/scripts/api/process/run-program.mjs +2 -1
  40. package/runtime/scripts/api/sops/lib.mjs +198 -120
  41. package/runtime/scripts/hfs/allows.mjs +3 -3
  42. package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
  43. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
  44. package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
  45. package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
  46. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
  47. package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
  48. package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
  49. package/runtime/scripts/hfs/architecture/config.mjs +95 -80
  50. package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
  51. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
  52. package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
  53. package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
  54. package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
  55. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
  56. package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
  57. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
  58. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
  59. package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
  60. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
  61. package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
  62. package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
  63. package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
  64. package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
  65. package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
  66. package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
  67. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
  68. package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
  69. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  70. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
  71. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
  72. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
  73. package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
  74. package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
  75. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
  76. package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
  77. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
  78. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
  79. package/runtime/scripts/hfs/architecture/index.mjs +158 -102
  80. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
  81. package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
  82. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
  83. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
  84. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
  85. package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
  86. package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
  87. package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
  88. package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
  89. package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
  90. package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
  91. package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
  92. package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
  93. package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
  94. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
  95. package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
  96. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
  97. package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
  98. package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
  99. package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
  100. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
  101. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
  102. package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
  103. package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
  104. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
  105. package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
  106. package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
  107. package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
  108. package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
  109. package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
  110. package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
  111. package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
  112. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
  113. package/runtime/scripts/hfs/architecture.mjs +3 -4
  114. package/runtime/scripts/hfs/check.mjs +61 -70
  115. package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
  116. package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
  117. package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
  118. package/runtime/scripts/hfs/edition-slots.mjs +5 -3
  119. package/runtime/scripts/hfs/linear-text.mjs +30 -0
  120. package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
  121. package/runtime/scripts/hfs/path-findings.mjs +58 -38
  122. package/runtime/scripts/hfs/pin-findings.mjs +31 -0
  123. package/runtime/scripts/hfs/repo-identity.mjs +5 -2
  124. package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
  125. package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
  126. package/runtime/scripts/hfs/rules/cli.mjs +5 -1
  127. package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
  128. package/runtime/scripts/hfs/rules/contract.mjs +35 -27
  129. package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
  130. package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
  131. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
  132. package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
  133. package/runtime/scripts/hfs/rules/database.mjs +36 -23
  134. package/runtime/scripts/hfs/rules/deps.mjs +64 -32
  135. package/runtime/scripts/hfs/rules/docker.mjs +113 -65
  136. package/runtime/scripts/hfs/rules/edition.mjs +118 -111
  137. package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
  138. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
  139. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
  140. package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
  141. package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
  142. package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
  143. package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
  144. package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
  145. package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
  146. package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
  147. package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
  148. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
  149. package/runtime/scripts/hfs/rules/saga.mjs +128 -61
  150. package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
  151. package/runtime/scripts/hfs/rules/services.mjs +34 -16
  152. package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
  153. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
  154. package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
  155. package/runtime/scripts/hfs/secret.mjs +59 -48
  156. package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
  157. package/runtime/scripts/hfs/slot-classify.mjs +65 -0
  158. package/runtime/scripts/hfs/slot-errors.mjs +11 -0
  159. package/runtime/scripts/hfs/slot-imports.mjs +76 -0
  160. package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
  161. package/runtime/scripts/hfs/slot-match.mjs +153 -0
  162. package/runtime/scripts/hfs/slot-path.mjs +8 -0
  163. package/runtime/scripts/hfs/slot-required.mjs +76 -0
  164. package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
  165. package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
  166. package/runtime/scripts/hfs/slots.mjs +72 -693
  167. package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
  168. package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
  169. package/runtime/scripts/hfs/tree.mjs +19 -14
  170. package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
  171. package/runtime/scripts/hfs/view.mjs +1 -1
  172. package/runtime/scripts/lib/dockerfile.mjs +56 -33
  173. package/runtime/scripts/lib/event-contract.mjs +17 -13
  174. package/runtime/scripts/lib/git.mjs +2 -2
  175. package/runtime/scripts/lib/graphql-contract.mjs +158 -322
  176. package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
  177. package/runtime/scripts/lib/i18n.mjs +3 -2
  178. package/runtime/scripts/lib/in-order.mjs +71 -0
  179. package/runtime/scripts/lib/language.mjs +3 -3
  180. package/runtime/scripts/lib/list.mjs +8 -1
  181. package/runtime/scripts/lib/path-key.mjs +38 -5
  182. package/runtime/scripts/lib/pid-alive.mjs +7 -0
  183. package/runtime/scripts/lib/regex.mjs +1 -1
  184. package/runtime/scripts/lib/same-text.mjs +1 -1
  185. package/runtime/scripts/lib/secret-patterns.mjs +6 -2
  186. package/runtime/scripts/lib/sleep-sync.mjs +2 -2
  187. package/runtime/scripts/lib/sops-envelope.mjs +95 -48
  188. package/runtime/scripts/lib/stack-services.mjs +1 -2
  189. package/runtime/scripts/lib/ts-ast.mjs +9 -0
  190. package/runtime/scripts/lib/walk.mjs +6 -1
  191. package/scaffold/add-cli-lite.mjs +2 -1
  192. package/scaffold/add-table.mjs +2 -2
  193. package/scaffold/add.mjs +3 -2
  194. package/scaffold/app.mjs +4 -2
  195. package/scaffold/edition-gate.mjs +25 -25
  196. package/scaffold/lite-exports.mjs +2 -1
  197. package/scaffold/service.mjs +5 -4
  198. package/sync/hygiene.mjs +15 -9
  199. package/sync/index.mjs +7 -3
  200. package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
  201. package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
  202. package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
  203. package/templates/app/ci-workflows/sonar-steps.yml +2 -2
  204. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
  205. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
  206. package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
  207. package/templates/app/starciwork.gitignore +1 -1
  208. package/templates/be/image/api/Dockerfile +1 -1
  209. package/templates/be/image/cli/Dockerfile +1 -1
  210. package/templates/be/image/worker/Dockerfile +1 -1
  211. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
  212. package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
  213. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
  214. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
  215. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
  216. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
  217. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
  218. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
  219. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
  220. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
  221. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
  222. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
  223. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
  224. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
  225. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
  226. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  227. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
  228. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
  229. package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
  230. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  231. package/templates/fe/image/next/Dockerfile +2 -1
  232. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
  233. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
  234. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
  235. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
  236. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
  237. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
  238. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
  239. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
  240. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
  241. package/upgrade/index.mjs +9 -2
  242. package/runtime/engine/admission.mjs +0 -308
  243. package/runtime/engine/canonical-json.mjs +0 -10
  244. package/runtime/engine/db/blob.mjs +0 -315
  245. package/runtime/engine/db/ledger-paths.mjs +0 -83
  246. package/runtime/engine/db/ledger.mjs +0 -1118
  247. package/runtime/engine/db/machine-connection.mjs +0 -135
  248. package/runtime/engine/db/machine-schema.mjs +0 -84
  249. package/runtime/engine/db/machine.mjs +0 -1367
  250. package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
  251. package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
  252. package/runtime/engine/db/provider-reservations.mjs +0 -101
  253. package/runtime/engine/digest.mjs +0 -16
  254. package/runtime/engine/refuse.mjs +0 -11
  255. package/runtime/modules/ops/_labels.yaml +0 -53
  256. package/runtime/scripts/api/node/lib.mjs +0 -14
  257. package/runtime/scripts/api/node/spawn-node.mjs +0 -6
  258. package/runtime/scripts/api/process/lib.mjs +0 -111
  259. package/runtime/scripts/api/process/owned-process.mjs +0 -78
  260. package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
  261. package/runtime/scripts/connectors/lib.mjs +0 -488
  262. package/runtime/scripts/lib/clip.mjs +0 -19
  263. package/runtime/scripts/lib/display-names.mjs +0 -259
  264. package/runtime/scripts/lib/example-refs.mjs +0 -158
  265. package/runtime/scripts/lib/json-schema.mjs +0 -52
  266. package/runtime/scripts/lib/process-identity.mjs +0 -6
  267. package/runtime/scripts/lib/read-yaml.mjs +0 -18
  268. package/runtime/scripts/lib/redact.mjs +0 -161
  269. package/runtime/scripts/lib/sleep.mjs +0 -7
  270. package/runtime/scripts/lib/source-phrases.mjs +0 -34
  271. 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
+ }