@endora-commerce/cli 0.100.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/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/bin/endora.d.ts +28 -0
- package/dist/bin/endora.d.ts.map +1 -0
- package/dist/bin/endora.js +926 -0
- package/dist/bin/endora.js.map +1 -0
- package/dist/check/estate.d.ts +189 -0
- package/dist/check/estate.d.ts.map +1 -0
- package/dist/check/estate.js +1037 -0
- package/dist/check/estate.js.map +1 -0
- package/dist/check/hosts.d.ts +25 -0
- package/dist/check/hosts.d.ts.map +1 -0
- package/dist/check/hosts.js +1347 -0
- package/dist/check/hosts.js.map +1 -0
- package/dist/check/index.d.ts +60 -0
- package/dist/check/index.d.ts.map +1 -0
- package/dist/check/index.js +111 -0
- package/dist/check/index.js.map +1 -0
- package/dist/check/layout.d.ts +136 -0
- package/dist/check/layout.d.ts.map +1 -0
- package/dist/check/layout.js +262 -0
- package/dist/check/layout.js.map +1 -0
- package/dist/check/ledger.d.ts +98 -0
- package/dist/check/ledger.d.ts.map +1 -0
- package/dist/check/ledger.js +173 -0
- package/dist/check/ledger.js.map +1 -0
- package/dist/check/peer-owners.d.ts +116 -0
- package/dist/check/peer-owners.d.ts.map +1 -0
- package/dist/check/peer-owners.js +225 -0
- package/dist/check/peer-owners.js.map +1 -0
- package/dist/check/report.d.ts +33 -0
- package/dist/check/report.d.ts.map +1 -0
- package/dist/check/report.js +107 -0
- package/dist/check/report.js.map +1 -0
- package/dist/check/run.d.ts +147 -0
- package/dist/check/run.d.ts.map +1 -0
- package/dist/check/run.js +111 -0
- package/dist/check/run.js.map +1 -0
- package/dist/checks.d.ts +17 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +17 -0
- package/dist/checks.js.map +1 -0
- package/dist/dev/index.d.ts +83 -0
- package/dist/dev/index.d.ts.map +1 -0
- package/dist/dev/index.js +298 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/generate/divergence.d.ts +38 -0
- package/dist/generate/divergence.d.ts.map +1 -0
- package/dist/generate/divergence.js +237 -0
- package/dist/generate/divergence.js.map +1 -0
- package/dist/generate/index.d.ts +90 -0
- package/dist/generate/index.d.ts.map +1 -0
- package/dist/generate/index.js +369 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/inputs/declaration.d.ts +18 -0
- package/dist/inputs/declaration.d.ts.map +1 -0
- package/dist/inputs/declaration.js +64 -0
- package/dist/inputs/declaration.js.map +1 -0
- package/dist/inputs/env-file.d.ts +73 -0
- package/dist/inputs/env-file.d.ts.map +1 -0
- package/dist/inputs/env-file.js +134 -0
- package/dist/inputs/env-file.js.map +1 -0
- package/dist/inputs/prompt.d.ts +21 -0
- package/dist/inputs/prompt.d.ts.map +1 -0
- package/dist/inputs/prompt.js +59 -0
- package/dist/inputs/prompt.js.map +1 -0
- package/dist/inputs/resolve.d.ts +163 -0
- package/dist/inputs/resolve.d.ts.map +1 -0
- package/dist/inputs/resolve.js +290 -0
- package/dist/inputs/resolve.js.map +1 -0
- package/dist/install/host.d.ts +27 -0
- package/dist/install/host.d.ts.map +1 -0
- package/dist/install/host.js +90 -0
- package/dist/install/host.js.map +1 -0
- package/dist/install/index.d.ts +173 -0
- package/dist/install/index.d.ts.map +1 -0
- package/dist/install/index.js +793 -0
- package/dist/install/index.js.map +1 -0
- package/dist/install/wizard.d.ts +144 -0
- package/dist/install/wizard.d.ts.map +1 -0
- package/dist/install/wizard.js +362 -0
- package/dist/install/wizard.js.map +1 -0
- package/dist/lib/admin-artefacts.d.ts +70 -0
- package/dist/lib/admin-artefacts.d.ts.map +1 -0
- package/dist/lib/admin-artefacts.js +354 -0
- package/dist/lib/admin-artefacts.js.map +1 -0
- package/dist/lib/admin-surfaces.d.ts +298 -0
- package/dist/lib/admin-surfaces.d.ts.map +1 -0
- package/dist/lib/admin-surfaces.js +669 -0
- package/dist/lib/admin-surfaces.js.map +1 -0
- package/dist/lib/delegated-composer.d.ts +85 -0
- package/dist/lib/delegated-composer.d.ts.map +1 -0
- package/dist/lib/delegated-composer.js +241 -0
- package/dist/lib/delegated-composer.js.map +1 -0
- package/dist/lib/divergence-artefacts.d.ts +305 -0
- package/dist/lib/divergence-artefacts.d.ts.map +1 -0
- package/dist/lib/divergence-artefacts.js +828 -0
- package/dist/lib/divergence-artefacts.js.map +1 -0
- package/dist/lib/divergence.d.ts +337 -0
- package/dist/lib/divergence.d.ts.map +1 -0
- package/dist/lib/divergence.js +1005 -0
- package/dist/lib/divergence.js.map +1 -0
- package/dist/lib/docs-artefacts.d.ts +395 -0
- package/dist/lib/docs-artefacts.d.ts.map +1 -0
- package/dist/lib/docs-artefacts.js +781 -0
- package/dist/lib/docs-artefacts.js.map +1 -0
- package/dist/lib/emitted-exports.d.ts +21 -0
- package/dist/lib/emitted-exports.d.ts.map +1 -0
- package/dist/lib/emitted-exports.js +96 -0
- package/dist/lib/emitted-exports.js.map +1 -0
- package/dist/lib/emitted-freshness.d.ts +112 -0
- package/dist/lib/emitted-freshness.d.ts.map +1 -0
- package/dist/lib/emitted-freshness.js +288 -0
- package/dist/lib/emitted-freshness.js.map +1 -0
- package/dist/lib/entity-index-artefact.d.ts +95 -0
- package/dist/lib/entity-index-artefact.d.ts.map +1 -0
- package/dist/lib/entity-index-artefact.js +210 -0
- package/dist/lib/entity-index-artefact.js.map +1 -0
- package/dist/lib/instance-build-inputs.d.ts +108 -0
- package/dist/lib/instance-build-inputs.d.ts.map +1 -0
- package/dist/lib/instance-build-inputs.js +165 -0
- package/dist/lib/instance-build-inputs.js.map +1 -0
- package/dist/lib/module-docs.d.ts +473 -0
- package/dist/lib/module-docs.d.ts.map +1 -0
- package/dist/lib/module-docs.js +711 -0
- package/dist/lib/module-docs.js.map +1 -0
- package/dist/lib/module-package-subpaths.d.ts +56 -0
- package/dist/lib/module-package-subpaths.d.ts.map +1 -0
- package/dist/lib/module-package-subpaths.js +223 -0
- package/dist/lib/module-package-subpaths.js.map +1 -0
- package/dist/lib/module-packages.d.ts +200 -0
- package/dist/lib/module-packages.d.ts.map +1 -0
- package/dist/lib/module-packages.js +580 -0
- package/dist/lib/module-packages.js.map +1 -0
- package/dist/lib/module-population.d.ts +129 -0
- package/dist/lib/module-population.d.ts.map +1 -0
- package/dist/lib/module-population.js +172 -0
- package/dist/lib/module-population.js.map +1 -0
- package/dist/lib/module-roots.d.ts +337 -0
- package/dist/lib/module-roots.d.ts.map +1 -0
- package/dist/lib/module-roots.js +586 -0
- package/dist/lib/module-roots.js.map +1 -0
- package/dist/lib/nested-checkouts.d.ts +33 -0
- package/dist/lib/nested-checkouts.d.ts.map +1 -0
- package/dist/lib/nested-checkouts.js +160 -0
- package/dist/lib/nested-checkouts.js.map +1 -0
- package/dist/lib/platform-root.d.ts +43 -0
- package/dist/lib/platform-root.d.ts.map +1 -0
- package/dist/lib/platform-root.js +134 -0
- package/dist/lib/platform-root.js.map +1 -0
- package/dist/lib/platform-surface.d.ts +235 -0
- package/dist/lib/platform-surface.d.ts.map +1 -0
- package/dist/lib/platform-surface.js +393 -0
- package/dist/lib/platform-surface.js.map +1 -0
- package/dist/lib/port-registrations.d.ts +223 -0
- package/dist/lib/port-registrations.d.ts.map +1 -0
- package/dist/lib/port-registrations.js +532 -0
- package/dist/lib/port-registrations.js.map +1 -0
- package/dist/lib/read-size.d.ts +154 -0
- package/dist/lib/read-size.d.ts.map +1 -0
- package/dist/lib/read-size.js +182 -0
- package/dist/lib/read-size.js.map +1 -0
- package/dist/lib/registration-owners.d.ts +79 -0
- package/dist/lib/registration-owners.d.ts.map +1 -0
- package/dist/lib/registration-owners.js +77 -0
- package/dist/lib/registration-owners.js.map +1 -0
- package/dist/lib/release-index.d.ts +53 -0
- package/dist/lib/release-index.d.ts.map +1 -0
- package/dist/lib/release-index.js +162 -0
- package/dist/lib/release-index.js.map +1 -0
- package/dist/lib/repeating-timers.d.ts +79 -0
- package/dist/lib/repeating-timers.d.ts.map +1 -0
- package/dist/lib/repeating-timers.js +189 -0
- package/dist/lib/repeating-timers.js.map +1 -0
- package/dist/lib/source-text.d.ts +34 -0
- package/dist/lib/source-text.d.ts.map +1 -0
- package/dist/lib/source-text.js +80 -0
- package/dist/lib/source-text.js.map +1 -0
- package/dist/lib/specifiers.d.ts +20 -0
- package/dist/lib/specifiers.d.ts.map +1 -0
- package/dist/lib/specifiers.js +130 -0
- package/dist/lib/specifiers.js.map +1 -0
- package/dist/lib/sql-tables.d.ts +166 -0
- package/dist/lib/sql-tables.d.ts.map +1 -0
- package/dist/lib/sql-tables.js +464 -0
- package/dist/lib/sql-tables.js.map +1 -0
- package/dist/lib/switchable-modules.d.ts +54 -0
- package/dist/lib/switchable-modules.d.ts.map +1 -0
- package/dist/lib/switchable-modules.js +104 -0
- package/dist/lib/switchable-modules.js.map +1 -0
- package/dist/lib/tailwind-sources.d.ts +136 -0
- package/dist/lib/tailwind-sources.d.ts.map +1 -0
- package/dist/lib/tailwind-sources.js +307 -0
- package/dist/lib/tailwind-sources.js.map +1 -0
- package/dist/lib/ui-layer.d.ts +54 -0
- package/dist/lib/ui-layer.d.ts.map +1 -0
- package/dist/lib/ui-layer.js +57 -0
- package/dist/lib/ui-layer.js.map +1 -0
- package/dist/lib/workspace-packages.d.ts +186 -0
- package/dist/lib/workspace-packages.d.ts.map +1 -0
- package/dist/lib/workspace-packages.js +351 -0
- package/dist/lib/workspace-packages.js.map +1 -0
- package/dist/new-instance/deploy.d.ts +211 -0
- package/dist/new-instance/deploy.d.ts.map +1 -0
- package/dist/new-instance/deploy.js +1381 -0
- package/dist/new-instance/deploy.js.map +1 -0
- package/dist/new-instance/docs-toolchain.d.ts +66 -0
- package/dist/new-instance/docs-toolchain.d.ts.map +1 -0
- package/dist/new-instance/docs-toolchain.js +69 -0
- package/dist/new-instance/docs-toolchain.js.map +1 -0
- package/dist/new-instance/host.d.ts +124 -0
- package/dist/new-instance/host.d.ts.map +1 -0
- package/dist/new-instance/host.js +276 -0
- package/dist/new-instance/host.js.map +1 -0
- package/dist/new-instance/index.d.ts +118 -0
- package/dist/new-instance/index.d.ts.map +1 -0
- package/dist/new-instance/index.js +567 -0
- package/dist/new-instance/index.js.map +1 -0
- package/dist/new-instance/modules.d.ts +188 -0
- package/dist/new-instance/modules.d.ts.map +1 -0
- package/dist/new-instance/modules.js +392 -0
- package/dist/new-instance/modules.js.map +1 -0
- package/dist/new-instance/template.d.ts +505 -0
- package/dist/new-instance/template.d.ts.map +1 -0
- package/dist/new-instance/template.js +1886 -0
- package/dist/new-instance/template.js.map +1 -0
- package/dist/new-module/emit.d.ts +67 -0
- package/dist/new-module/emit.d.ts.map +1 -0
- package/dist/new-module/emit.js +1393 -0
- package/dist/new-module/emit.js.map +1 -0
- package/dist/new-module/host.d.ts +67 -0
- package/dist/new-module/host.d.ts.map +1 -0
- package/dist/new-module/host.js +224 -0
- package/dist/new-module/host.js.map +1 -0
- package/dist/new-module/index.d.ts +32 -0
- package/dist/new-module/index.d.ts.map +1 -0
- package/dist/new-module/index.js +193 -0
- package/dist/new-module/index.js.map +1 -0
- package/dist/new-module/spec.d.ts +188 -0
- package/dist/new-module/spec.d.ts.map +1 -0
- package/dist/new-module/spec.js +404 -0
- package/dist/new-module/spec.js.map +1 -0
- package/dist/new-module/text.d.ts +15 -0
- package/dist/new-module/text.d.ts.map +1 -0
- package/dist/new-module/text.js +22 -0
- package/dist/new-module/text.js.map +1 -0
- package/dist/new-storefront/dockerfile.d.ts +20 -0
- package/dist/new-storefront/dockerfile.d.ts.map +1 -0
- package/dist/new-storefront/dockerfile.js +131 -0
- package/dist/new-storefront/dockerfile.js.map +1 -0
- package/dist/new-storefront/gitignore.d.ts +23 -0
- package/dist/new-storefront/gitignore.d.ts.map +1 -0
- package/dist/new-storefront/gitignore.js +39 -0
- package/dist/new-storefront/gitignore.js.map +1 -0
- package/dist/new-storefront/index.d.ts +93 -0
- package/dist/new-storefront/index.d.ts.map +1 -0
- package/dist/new-storefront/index.js +329 -0
- package/dist/new-storefront/index.js.map +1 -0
- package/dist/new-storefront/npmrc.d.ts +98 -0
- package/dist/new-storefront/npmrc.d.ts.map +1 -0
- package/dist/new-storefront/npmrc.js +217 -0
- package/dist/new-storefront/npmrc.js.map +1 -0
- package/dist/new-storefront/reference.d.ts +189 -0
- package/dist/new-storefront/reference.d.ts.map +1 -0
- package/dist/new-storefront/reference.js +430 -0
- package/dist/new-storefront/reference.js.map +1 -0
- package/dist/new-storefront/rewrite.d.ts +171 -0
- package/dist/new-storefront/rewrite.d.ts.map +1 -0
- package/dist/new-storefront/rewrite.js +701 -0
- package/dist/new-storefront/rewrite.js.map +1 -0
- package/dist/release-index.json +284 -0
- package/dist/rules/action-route-permissions.d.ts +141 -0
- package/dist/rules/action-route-permissions.d.ts.map +1 -0
- package/dist/rules/action-route-permissions.js +556 -0
- package/dist/rules/action-route-permissions.js.map +1 -0
- package/dist/rules/bundle-pairing.d.ts +74 -0
- package/dist/rules/bundle-pairing.d.ts.map +1 -0
- package/dist/rules/bundle-pairing.js +281 -0
- package/dist/rules/bundle-pairing.js.map +1 -0
- package/dist/rules/channel-resolution.d.ts +17 -0
- package/dist/rules/channel-resolution.d.ts.map +1 -0
- package/dist/rules/channel-resolution.js +382 -0
- package/dist/rules/channel-resolution.js.map +1 -0
- package/dist/rules/command-coverage.d.ts +210 -0
- package/dist/rules/command-coverage.d.ts.map +1 -0
- package/dist/rules/command-coverage.js +714 -0
- package/dist/rules/command-coverage.js.map +1 -0
- package/dist/rules/container-imports.d.ts +60 -0
- package/dist/rules/container-imports.d.ts.map +1 -0
- package/dist/rules/container-imports.js +158 -0
- package/dist/rules/container-imports.js.map +1 -0
- package/dist/rules/default-language-prose.d.ts +212 -0
- package/dist/rules/default-language-prose.d.ts.map +1 -0
- package/dist/rules/default-language-prose.js +710 -0
- package/dist/rules/default-language-prose.js.map +1 -0
- package/dist/rules/diacritic-folds.d.ts +238 -0
- package/dist/rules/diacritic-folds.d.ts.map +1 -0
- package/dist/rules/diacritic-folds.js +681 -0
- package/dist/rules/diacritic-folds.js.map +1 -0
- package/dist/rules/entity-tenant-classification.d.ts +171 -0
- package/dist/rules/entity-tenant-classification.d.ts.map +1 -0
- package/dist/rules/entity-tenant-classification.js +323 -0
- package/dist/rules/entity-tenant-classification.js.map +1 -0
- package/dist/rules/entry-presence.d.ts +142 -0
- package/dist/rules/entry-presence.d.ts.map +1 -0
- package/dist/rules/entry-presence.js +339 -0
- package/dist/rules/entry-presence.js.map +1 -0
- package/dist/rules/entry-scope.d.ts +91 -0
- package/dist/rules/entry-scope.d.ts.map +1 -0
- package/dist/rules/entry-scope.js +404 -0
- package/dist/rules/entry-scope.js.map +1 -0
- package/dist/rules/env-inputs.d.ts +222 -0
- package/dist/rules/env-inputs.d.ts.map +1 -0
- package/dist/rules/env-inputs.js +951 -0
- package/dist/rules/env-inputs.js.map +1 -0
- package/dist/rules/kernel-boundary.d.ts +37 -0
- package/dist/rules/kernel-boundary.d.ts.map +1 -0
- package/dist/rules/kernel-boundary.js +195 -0
- package/dist/rules/kernel-boundary.js.map +1 -0
- package/dist/rules/nul-bytes.d.ts +233 -0
- package/dist/rules/nul-bytes.d.ts.map +1 -0
- package/dist/rules/nul-bytes.js +332 -0
- package/dist/rules/nul-bytes.js.map +1 -0
- package/dist/rules/platform-surface.d.ts +479 -0
- package/dist/rules/platform-surface.d.ts.map +1 -0
- package/dist/rules/platform-surface.js +749 -0
- package/dist/rules/platform-surface.js.map +1 -0
- package/dist/rules/port-catches.d.ts +225 -0
- package/dist/rules/port-catches.d.ts.map +1 -0
- package/dist/rules/port-catches.js +1374 -0
- package/dist/rules/port-catches.js.map +1 -0
- package/dist/rules/port-shape.d.ts +213 -0
- package/dist/rules/port-shape.d.ts.map +1 -0
- package/dist/rules/port-shape.js +670 -0
- package/dist/rules/port-shape.js.map +1 -0
- package/dist/rules/queue-names.d.ts +108 -0
- package/dist/rules/queue-names.d.ts.map +1 -0
- package/dist/rules/queue-names.js +395 -0
- package/dist/rules/queue-names.js.map +1 -0
- package/dist/rules/singleton-identity.d.ts +205 -0
- package/dist/rules/singleton-identity.d.ts.map +1 -0
- package/dist/rules/singleton-identity.js +830 -0
- package/dist/rules/singleton-identity.js.map +1 -0
- package/dist/rules/subscribe-seam.d.ts +121 -0
- package/dist/rules/subscribe-seam.d.ts.map +1 -0
- package/dist/rules/subscribe-seam.js +594 -0
- package/dist/rules/subscribe-seam.js.map +1 -0
- package/dist/rules/transaction-context.d.ts +40 -0
- package/dist/rules/transaction-context.d.ts.map +1 -0
- package/dist/rules/transaction-context.js +294 -0
- package/dist/rules/transaction-context.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI check — Sales-Channel Resolution Unification (feature 053, FR-011).
|
|
3
|
+
*
|
|
4
|
+
* Locks the invariant established by feature 053: the current sales channel
|
|
5
|
+
* for a request is resolved EXACTLY ONCE, by the canonical resolver
|
|
6
|
+
* (`kernel/sales-channels/sales-channel-resolver.middleware.ts`, relocated from
|
|
7
|
+
* the `sales_channels` module by feature 072 T019), and exposed on the request
|
|
8
|
+
* scope, read through `getResolvedChannel()` / `currentSalesChannel()` (feature
|
|
9
|
+
* 072 T028 moved it off the `request.salesChannel` property). No
|
|
10
|
+
* storefront-facing module may re-derive it.
|
|
11
|
+
*
|
|
12
|
+
* Three static signals are flagged (TypeScript compiler API, no DB, no new
|
|
13
|
+
* dependency — mirrors `check-entity-tenant-classification.ts`):
|
|
14
|
+
*
|
|
15
|
+
* 1. RAW CHANNEL HEADER READ — any string literal `x-sales-channel` or
|
|
16
|
+
* `x-sales-channel-id` anywhere under `src/**` outside the resolver's own
|
|
17
|
+
* directory. Legitimate code never reads these headers; only the resolver
|
|
18
|
+
* does. Global scope = strongest guard.
|
|
19
|
+
*
|
|
20
|
+
* 2. REQUEST-CHANNEL RE-RESOLUTION — inside the storefront "surface" files
|
|
21
|
+
* (routes.public / routes.storefront / *storefront-resolver / the catalog
|
|
22
|
+
* & search query services / product-link service): a `SalesChannel`
|
|
23
|
+
* entity query (`em.findOne/find(SalesChannel, …)`) or a raw
|
|
24
|
+
* `from sales_channels` SQL string used to derive the request channel.
|
|
25
|
+
* Scoped to surfaces so admin CRUD / membership / seeds that legitimately
|
|
26
|
+
* query channels are not false-positived.
|
|
27
|
+
*
|
|
28
|
+
* **Issue #256 — measured, and the SQL shape was drained rather than made
|
|
29
|
+
* visible.** MR !775 found four public storefront endpoints re-resolving
|
|
30
|
+
* the request channel with `select id from sales_channels where code = ?`
|
|
31
|
+
* while this check reported clean, and the filed diagnosis was that the
|
|
32
|
+
* signal cannot see SQL. It can: 2b above is exactly that shape. What it
|
|
33
|
+
* could not see was the *population* — all four sat in
|
|
34
|
+
* `settings/services/*-resolver.ts`, which is no storefront surface by the
|
|
35
|
+
* predicate above. Widening the population was rejected on measurement,
|
|
36
|
+
* not on taste: at the time the question was asked, module code held eight
|
|
37
|
+
* raw reads of `sales_channels`, and every one of them was the
|
|
38
|
+
* administration this signal deliberately spares — enumerating every
|
|
39
|
+
* channel in a boot seed, or fetching one by an admin-supplied id. None
|
|
40
|
+
* was keyed by a request-supplied code, so a widened signal would have
|
|
41
|
+
* reported eight false positives and nothing else. Feature 075's D-87
|
|
42
|
+
* drain then took all eight through the kernel's `SalesChannel` entity, so
|
|
43
|
+
* `src/modules` now holds **no** raw `sales_channels` statement at all; the
|
|
44
|
+
* only live one left in `src/` is the kernel's own, in
|
|
45
|
+
* `sales-channel-membership.service.ts`. A second predicate over that
|
|
46
|
+
* shape would report a vacuous green from its first run, which is why this
|
|
47
|
+
* paragraph exists and the predicate does not.
|
|
48
|
+
*
|
|
49
|
+
* 3. SETTINGS-CHANNEL LITERAL (feature 072, D-42) — a `.get` / `.getMany`
|
|
50
|
+
* call on a `settings`-ish receiver whose channel argument can be a string
|
|
51
|
+
* that is not a channel uuid, or is the nil uuid. Both are spellings of "I
|
|
52
|
+
* have no channel", and since D-41 that has a real one: `null`. The literal
|
|
53
|
+
* `'default'` — a channel **code**, against a `sales_channel_id uuid`
|
|
54
|
+
* column — made PostgreSQL reject the comparison outright, and the caller's
|
|
55
|
+
* `catch` reported it as "not configured yet"; three settings were
|
|
56
|
+
* therefore ignored on every deployment. The nil uuid resolved to the right
|
|
57
|
+
* tier, but by accident.
|
|
58
|
+
*
|
|
59
|
+
* 4. INVENTED CHANNEL IDENTIFIER (feature 072, D-48) — the two *positions*
|
|
60
|
+
* signal 3 could not see, because it only ever inspected the channel
|
|
61
|
+
* argument of a settings read:
|
|
62
|
+
*
|
|
63
|
+
* a. a **default parameter value**: `salesChannelId: string = 'default'`.
|
|
64
|
+
* `quick_order`'s `OneClickService` carried exactly that — a fifth
|
|
65
|
+
* constructor argument production never passed — so one-click buy was
|
|
66
|
+
* off on every deployment while its unit test, which *did* pass the
|
|
67
|
+
* argument, stayed green (issue #99).
|
|
68
|
+
* b. a **channel id falling back to `randomUUID()`**. `orders` stamped
|
|
69
|
+
* `salesChannelId: channel?.id ?? randomUUID()` onto every order
|
|
70
|
+
* placed without an explicit channel (issue #85) — an identifier
|
|
71
|
+
* invented at write time, in a column other reads join on. Restricted
|
|
72
|
+
* to `randomUUID()` deliberately: a *string* fallback off a channel id
|
|
73
|
+
* is caught by signal 3 wherever it reaches a settings read, and
|
|
74
|
+
* flagging every one of them here turns twenty ordinary
|
|
75
|
+
* `channel?.defaultCurrency ?? 'PLN'` defaults into noise.
|
|
76
|
+
*
|
|
77
|
+
* `?? null` is never flagged, in either signal: that is the sanctioned
|
|
78
|
+
* platform-wide read, and it is the answer all four of these were reaching
|
|
79
|
+
* for. **Signal 4 is deliberately not the settings seam** — L2 and L4 were
|
|
80
|
+
* not settings calls at all, which is part of why D-42's check read clean
|
|
81
|
+
* across all four.
|
|
82
|
+
*
|
|
83
|
+
* Reading `channel.isPublic` (price-visibility) off an already-resolved
|
|
84
|
+
* channel is NOT a violation — it is a property read, neither signal.
|
|
85
|
+
*
|
|
86
|
+
* ## One analysis, two hosts
|
|
87
|
+
*
|
|
88
|
+
* This file is the analysis (`specs/101-endora-check/contracts/package-scope-layout.md`
|
|
89
|
+
* §6). `backend/scripts/check-channel-resolution.ts` hosts it over this
|
|
90
|
+
* repository's module walk roots and carries the rollout allow-list, which is a
|
|
91
|
+
* statement about *this* tree; `endora check` hosts it over one module package's
|
|
92
|
+
* declared source layer, where every violation is blocking because a stranger's
|
|
93
|
+
* package is in no rollout of ours.
|
|
94
|
+
*/
|
|
95
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
96
|
+
import { join } from 'node:path';
|
|
97
|
+
import ts from 'typescript';
|
|
98
|
+
/**
|
|
99
|
+
* The whole of `src/` is scanned, not `modules/` plus `kernel/`.
|
|
100
|
+
*
|
|
101
|
+
* Feature 072 T019 moved the resolver, its service and its cache into the
|
|
102
|
+
* kernel, so scanning the kernel is what keeps the one place the header may
|
|
103
|
+
* legally be read from being the one place nothing checks. D-42 widened it
|
|
104
|
+
* again: of the settings-channel family's ten sites, two lived in
|
|
105
|
+
* `composition.ts` and two in a module's `scripts/` directory, so a
|
|
106
|
+
* `modules/**`-only scan would have missed nearly half of it.
|
|
107
|
+
*/
|
|
108
|
+
/** Header names that only the canonical resolver may read. */
|
|
109
|
+
const CHANNEL_HEADERS = new Set(['x-sales-channel', 'x-sales-channel-id']);
|
|
110
|
+
/**
|
|
111
|
+
* Resolution is owned by the kernel's `sales-channels/` directory and by what
|
|
112
|
+
* is left of the `sales_channels` module (its admin CRUD service and routes,
|
|
113
|
+
* which legitimately query channels) — never scanned.
|
|
114
|
+
*
|
|
115
|
+
* The kernel half is matched **wherever the kernel is**, not at one prefix. The
|
|
116
|
+
* relocation moved it into `@endora-commerce/platform`, so `layout.keyOf` spells
|
|
117
|
+
* it `packages/platform/src/kernel/sales-channels/…`; an anchored prefix stopped
|
|
118
|
+
* matching and the canonical resolver reported *itself* for reading the
|
|
119
|
+
* `x-sales-channel` header, which is the one file whose whole job that is.
|
|
120
|
+
*/
|
|
121
|
+
function isResolverOwned(relPath) {
|
|
122
|
+
return relPath.startsWith('modules/sales_channels/') || /(^|\/)kernel\/sales-channels\//.test(relPath);
|
|
123
|
+
}
|
|
124
|
+
/** Storefront surfaces where re-resolving the request channel is forbidden. */
|
|
125
|
+
function isStorefrontSurface(relPath) {
|
|
126
|
+
return (relPath.endsWith('/routes.public.ts') ||
|
|
127
|
+
relPath.endsWith('/routes.storefront.ts') ||
|
|
128
|
+
relPath.endsWith('storefront-resolver.ts') ||
|
|
129
|
+
relPath.endsWith('/catalog-query.service.ts') ||
|
|
130
|
+
relPath.endsWith('/search-query.service.ts') ||
|
|
131
|
+
relPath.endsWith('/product-link.service.ts'));
|
|
132
|
+
}
|
|
133
|
+
const RAW_CHANNEL_SQL = /\bfrom\s+sales_channels\b/i;
|
|
134
|
+
/** The shape `sales_channels.id` has. */
|
|
135
|
+
const CHANNEL_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
136
|
+
const NIL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
137
|
+
/**
|
|
138
|
+
* Which `.get(…)` calls are settings reads. Syntactic and receiver-based,
|
|
139
|
+
* because the check has no type information: `settingsReadPort`,
|
|
140
|
+
* `settingsService`, `this.settings`, `options.settings`, `deps.settingsRead`.
|
|
141
|
+
* A `cache.get(code, 'default')` or a `dictionaries.get(…)` is not one.
|
|
142
|
+
*/
|
|
143
|
+
function isSettingsReceiver(receiver) {
|
|
144
|
+
const last = receiver.split('.').pop() ?? receiver;
|
|
145
|
+
return /settings/i.test(last);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* True when this literal cannot be a sales-channel id, or is the nil uuid.
|
|
149
|
+
*
|
|
150
|
+
* The nil uuid is included deliberately: it *is* well-formed, which is exactly
|
|
151
|
+
* why it survived in three modules. It addresses no row, so resolution fell
|
|
152
|
+
* through to `global_value ?? default_value` — the right answer, reached by
|
|
153
|
+
* accident and unreadably. A reader cannot tell `'00000000-…'` meaning
|
|
154
|
+
* "platform-wide" from `'00000000-…'` meaning "somebody had to put something
|
|
155
|
+
* here", and that ambiguity is what let `'default'` survive beside it.
|
|
156
|
+
*/
|
|
157
|
+
function isNotAChannelId(literal) {
|
|
158
|
+
return !CHANNEL_UUID.test(literal) || literal.toLowerCase() === NIL_UUID;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Naming the header in a CORS policy is not reading it.
|
|
162
|
+
*
|
|
163
|
+
* `http/server.ts` must list `X-Sales-Channel` under `allowedHeaders`, or the
|
|
164
|
+
* browser strips the very header the canonical resolver exists to read. Signal
|
|
165
|
+
* 1 is a global-scope rule and stays one; this exempts the shape that declares
|
|
166
|
+
* a header rather than consuming it — a string inside an array literal on a
|
|
167
|
+
* `…Headers` property — so the exemption cannot quietly cover a real read.
|
|
168
|
+
* Feature 072 (D-42) widened the scan from `modules/**` + `kernel/**` to all of
|
|
169
|
+
* `src/**`, which is what first brought this file into range.
|
|
170
|
+
*/
|
|
171
|
+
function isCorsHeaderDeclaration(node) {
|
|
172
|
+
const array = node.parent;
|
|
173
|
+
if (array === undefined || !ts.isArrayLiteralExpression(array))
|
|
174
|
+
return false;
|
|
175
|
+
const property = array.parent;
|
|
176
|
+
if (property === undefined || !ts.isPropertyAssignment(property))
|
|
177
|
+
return false;
|
|
178
|
+
const name = property.name;
|
|
179
|
+
return (ts.isIdentifier(name) || ts.isStringLiteralLike(name)) && /Headers$/.test(name.text);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Every TypeScript source under `dir`, with the rule's own prunes applied.
|
|
183
|
+
*
|
|
184
|
+
* Exported because the population is part of the rule: two hosts computing
|
|
185
|
+
* "which files this check reads" two ways is the shape that lets one of them go
|
|
186
|
+
* half-blind.
|
|
187
|
+
*/
|
|
188
|
+
export function collectChannelSources(dir, out = []) {
|
|
189
|
+
for (const name of readdirSync(dir)) {
|
|
190
|
+
const full = join(dir, name);
|
|
191
|
+
if (statSync(full).isDirectory()) {
|
|
192
|
+
if (name === 'node_modules' || name === 'dist')
|
|
193
|
+
continue;
|
|
194
|
+
collectChannelSources(full, out);
|
|
195
|
+
}
|
|
196
|
+
else if (name.endsWith('.ts') && !name.endsWith('.test.ts') && !name.endsWith('.d.ts')) {
|
|
197
|
+
out.push(full);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* A parameter that holds a sales-channel **id**. Name-based, because the check
|
|
204
|
+
* has no type information — and a name is what a reader has too. `channelCode`
|
|
205
|
+
* and `salesChannelCode` deliberately do not match: a code default is a
|
|
206
|
+
* legitimate thing to have (the reconciler seeds one), an id default is not.
|
|
207
|
+
*/
|
|
208
|
+
const CHANNEL_ID_PARAM = /channelid$/i;
|
|
209
|
+
/**
|
|
210
|
+
* "This expression is a resolved channel **id**." Text-based, since the check
|
|
211
|
+
* has no type information, and narrow on purpose: signal 4b is about inventing
|
|
212
|
+
* an *identifier*, so it must not fire on a property read off an
|
|
213
|
+
* already-resolved channel. `channel?.defaultCurrency ?? 'PLN'` and
|
|
214
|
+
* `channel?.defaultLanguage ?? 'en-US'` are ordinary defaults for ordinary
|
|
215
|
+
* fields — twenty of them exist in the tree — and neither is a channel id.
|
|
216
|
+
*/
|
|
217
|
+
function isChannelIdExpression(text) {
|
|
218
|
+
const trimmed = text.trim();
|
|
219
|
+
if (/(^|[^a-z0-9_])(sales)?channelid$/i.test(trimmed))
|
|
220
|
+
return true;
|
|
221
|
+
return /\??\.id$/.test(trimmed) && /channel|getSystemDefault/i.test(trimmed);
|
|
222
|
+
}
|
|
223
|
+
/** Analyze one source string. Exported for the unit self-test. */
|
|
224
|
+
export function analyzeSource(source, relPath) {
|
|
225
|
+
if (isResolverOwned(relPath))
|
|
226
|
+
return [];
|
|
227
|
+
const surface = isStorefrontSurface(relPath);
|
|
228
|
+
const sf = ts.createSourceFile(relPath, source, ts.ScriptTarget.Latest, true);
|
|
229
|
+
const violations = [];
|
|
230
|
+
const at = (node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
231
|
+
/**
|
|
232
|
+
* Same-file `const X = '…'` and `const X = a ?? '…'` initializers.
|
|
233
|
+
*
|
|
234
|
+
* D-42 recorded a single value and followed one hop, which is exactly the
|
|
235
|
+
* reach `branding.service.ts` escaped on: `const scopeId = salesChannelId ??
|
|
236
|
+
* GLOBAL_SENTINEL` is two hops and a `??`. What is recorded now is the set of
|
|
237
|
+
* **string values the name can hold**, which is the question signal 3 was
|
|
238
|
+
* always asking. A channel id that arrives as a parameter or through DI still
|
|
239
|
+
* contributes nothing and is what the runtime seam guard in `SettingsService`
|
|
240
|
+
* is for.
|
|
241
|
+
*/
|
|
242
|
+
const stringConsts = new Map();
|
|
243
|
+
/**
|
|
244
|
+
* The string values an expression can evaluate to, as far as syntax can tell.
|
|
245
|
+
* `null`, `undefined` and anything dynamic contribute nothing — so `x ?? null`
|
|
246
|
+
* yields the empty set and is never flagged, which is the whole point.
|
|
247
|
+
*/
|
|
248
|
+
const possibleStrings = (node, depth = 0) => {
|
|
249
|
+
if (depth > 8)
|
|
250
|
+
return [];
|
|
251
|
+
if (ts.isParenthesizedExpression(node))
|
|
252
|
+
return possibleStrings(node.expression, depth + 1);
|
|
253
|
+
if (ts.isStringLiteralLike(node))
|
|
254
|
+
return [node.text];
|
|
255
|
+
if (ts.isIdentifier(node))
|
|
256
|
+
return stringConsts.get(node.text) ?? [];
|
|
257
|
+
if (ts.isConditionalExpression(node)) {
|
|
258
|
+
return [
|
|
259
|
+
...possibleStrings(node.whenTrue, depth + 1),
|
|
260
|
+
...possibleStrings(node.whenFalse, depth + 1),
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
if (ts.isBinaryExpression(node) &&
|
|
264
|
+
(node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken ||
|
|
265
|
+
node.operatorToken.kind === ts.SyntaxKind.BarBarToken)) {
|
|
266
|
+
return [...possibleStrings(node.left, depth + 1), ...possibleStrings(node.right, depth + 1)];
|
|
267
|
+
}
|
|
268
|
+
return [];
|
|
269
|
+
};
|
|
270
|
+
const collectConsts = (node) => {
|
|
271
|
+
if (ts.isVariableDeclaration(node) &&
|
|
272
|
+
ts.isIdentifier(node.name) &&
|
|
273
|
+
node.initializer !== undefined) {
|
|
274
|
+
const values = possibleStrings(node.initializer);
|
|
275
|
+
if (values.length > 0)
|
|
276
|
+
stringConsts.set(node.name.text, values);
|
|
277
|
+
}
|
|
278
|
+
ts.forEachChild(node, collectConsts);
|
|
279
|
+
};
|
|
280
|
+
// Twice: a `const` may be initialized from one declared later in the file
|
|
281
|
+
// (a module-scope constant read inside a function), and one pass records the
|
|
282
|
+
// literals the second pass then resolves through.
|
|
283
|
+
collectConsts(sf);
|
|
284
|
+
collectConsts(sf);
|
|
285
|
+
/** `randomUUID()` — an identifier invented on the spot (issue #85). */
|
|
286
|
+
const isRandomUuidCall = (node) => ts.isCallExpression(node) && /(^|\.)randomUUID$/.test(node.expression.getText(sf));
|
|
287
|
+
const visit = (node) => {
|
|
288
|
+
// Signal 3 — a settings read whose channel argument can be a string that is
|
|
289
|
+
// not a channel id (feature 072, D-42, widened by D-48).
|
|
290
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
291
|
+
const method = node.expression.name.text;
|
|
292
|
+
const channelArg = node.arguments[1];
|
|
293
|
+
if ((method === 'get' || method === 'getMany') &&
|
|
294
|
+
channelArg !== undefined &&
|
|
295
|
+
isSettingsReceiver(node.expression.expression.getText(sf))) {
|
|
296
|
+
const offending = possibleStrings(channelArg).find(isNotAChannelId);
|
|
297
|
+
if (offending !== undefined) {
|
|
298
|
+
violations.push({
|
|
299
|
+
file: relPath,
|
|
300
|
+
line: at(node),
|
|
301
|
+
kind: 'settings-channel-literal',
|
|
302
|
+
detail: `settings.${method}(…) can read channel '${offending}', which is not a ` +
|
|
303
|
+
`sales-channel id — pass a channel uuid, or null for a platform-wide read`,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Signal 4a — a default parameter value standing in for a channel id.
|
|
309
|
+
if (ts.isParameter(node) &&
|
|
310
|
+
ts.isIdentifier(node.name) &&
|
|
311
|
+
CHANNEL_ID_PARAM.test(node.name.text) &&
|
|
312
|
+
node.initializer !== undefined) {
|
|
313
|
+
const offending = possibleStrings(node.initializer).find(isNotAChannelId);
|
|
314
|
+
if (offending !== undefined) {
|
|
315
|
+
violations.push({
|
|
316
|
+
file: relPath,
|
|
317
|
+
line: at(node),
|
|
318
|
+
kind: 'invented-channel-identifier',
|
|
319
|
+
detail: `default parameter '${node.name.text} = ${JSON.stringify(offending)}' invents a ` +
|
|
320
|
+
`sales-channel id — a parameter production never passes is a hole, not a default`,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Signal 4b — a channel resolution with an invented fallback.
|
|
325
|
+
if (ts.isBinaryExpression(node) &&
|
|
326
|
+
(node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken ||
|
|
327
|
+
node.operatorToken.kind === ts.SyntaxKind.BarBarToken) &&
|
|
328
|
+
isChannelIdExpression(node.left.getText(sf))) {
|
|
329
|
+
if (isRandomUuidCall(node.right)) {
|
|
330
|
+
violations.push({
|
|
331
|
+
file: relPath,
|
|
332
|
+
line: at(node),
|
|
333
|
+
kind: 'invented-channel-identifier',
|
|
334
|
+
detail: `a channel id falls back to randomUUID(), which addresses no sales_channels row — ` +
|
|
335
|
+
`the system default always exists (D-47/D-48), so resolve it; use null only where ` +
|
|
336
|
+
`"no channel" is a real answer`,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Signal 1 — raw channel-header read (global scope).
|
|
341
|
+
if (ts.isStringLiteralLike(node) &&
|
|
342
|
+
CHANNEL_HEADERS.has(node.text.toLowerCase()) &&
|
|
343
|
+
!isCorsHeaderDeclaration(node)) {
|
|
344
|
+
violations.push({
|
|
345
|
+
file: relPath,
|
|
346
|
+
line: at(node),
|
|
347
|
+
kind: 'raw-channel-header',
|
|
348
|
+
detail: `reads the '${node.text}' header — use getResolvedChannel()`,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
if (surface) {
|
|
352
|
+
// Signal 2a — em.findOne/find/findOneOrFail(SalesChannel, …)
|
|
353
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
354
|
+
const method = node.expression.name.text;
|
|
355
|
+
if ((method === 'findOne' || method === 'find' || method === 'findOneOrFail') &&
|
|
356
|
+
node.arguments[0] &&
|
|
357
|
+
ts.isIdentifier(node.arguments[0]) &&
|
|
358
|
+
node.arguments[0].text === 'SalesChannel') {
|
|
359
|
+
violations.push({
|
|
360
|
+
file: relPath,
|
|
361
|
+
line: at(node),
|
|
362
|
+
kind: 'request-channel-reresolution',
|
|
363
|
+
detail: `${method}(SalesChannel, …) re-resolves the request channel — call getResolvedChannel()`,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Signal 2b — raw `from sales_channels` SQL.
|
|
368
|
+
if (ts.isStringLiteralLike(node) && RAW_CHANNEL_SQL.test(node.text)) {
|
|
369
|
+
violations.push({
|
|
370
|
+
file: relPath,
|
|
371
|
+
line: at(node),
|
|
372
|
+
kind: 'request-channel-reresolution',
|
|
373
|
+
detail: `raw 'from sales_channels' query re-resolves the request channel — call getResolvedChannel()`,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
ts.forEachChild(node, visit);
|
|
378
|
+
};
|
|
379
|
+
visit(sf);
|
|
380
|
+
return violations;
|
|
381
|
+
}
|
|
382
|
+
//# sourceMappingURL=channel-resolution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-resolution.js","sourceRoot":"","sources":["../../src/rules/channel-resolution.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command-coverage check — feature 054 (FR-009 / FR-010, Constitution Principle XIII).
|
|
3
|
+
*
|
|
4
|
+
* `pnpm --filter backend run check:command-coverage -- [--strict] [--module <name> ...]`
|
|
5
|
+
*
|
|
6
|
+
* Flags, **per method/function/route handler** in every file a module owns:
|
|
7
|
+
* 1. an **unaudited sensitive write** — a method that performs a mutation call
|
|
8
|
+
* (`persist*`, `nativeUpdate`, `nativeDelete`, `remove*`, `flush`) but
|
|
9
|
+
* neither runs a Command (`commandBus.run(...)`) nor writes audit
|
|
10
|
+
* (`.record*(...)`) in the same method;
|
|
11
|
+
* 2. a **double-audit** — a method that BOTH runs a Command AND writes audit by
|
|
12
|
+
* hand (a converted write must remove its manual audit call, FR-010).
|
|
13
|
+
*
|
|
14
|
+
* Method-level (not file-level) so a partially-migrated file is judged per
|
|
15
|
+
* method: a converted `adjust` no longer masks an unaudited `grant` in the same
|
|
16
|
+
* file, and a Command in one method is not mistaken for a double-audit against a
|
|
17
|
+
* legacy `record()` in another.
|
|
18
|
+
*
|
|
19
|
+
* Escape hatch: a genuinely non-sensitive write (bookkeeping rows — progress
|
|
20
|
+
* counters, cache, queue state) can be exempted by putting a
|
|
21
|
+
* `command-coverage-ignore: <reason>` comment anywhere in the method. This keeps
|
|
22
|
+
* "build-breaking per module" honest without forcing audit onto non-domain writes,
|
|
23
|
+
* mirroring the audited `withSystemScope` escape hatch for tenancy.
|
|
24
|
+
*
|
|
25
|
+
* ## The escape hatch is a two-way ratchet (issue #116)
|
|
26
|
+
*
|
|
27
|
+
* 185 methods carry that comment, and until now nothing ever re-read one. An
|
|
28
|
+
* ignore written for a write that has since moved elsewhere — into a Command, or
|
|
29
|
+
* into another module's audited service — went on reading as a considered
|
|
30
|
+
* decision about a write that is no longer there, and the next person to add a
|
|
31
|
+
* write to that method inherited the exemption silently.
|
|
32
|
+
*
|
|
33
|
+
* So a marker on a method that **no longer writes at all** is reported as
|
|
34
|
+
* `stale-ignore`, in the idiom of `PORT_CATCHES_TO_DRAIN` and
|
|
35
|
+
* `BARE_SUBSCRIPTIONS_TO_DRAIN`: an unledgered violation fails the build, and an
|
|
36
|
+
* entry that no longer describes one fails it too. MR !532's marker in
|
|
37
|
+
* `orders/order-completion-reactor.ts` — added because a refactor made an
|
|
38
|
+
* *existing* write visible to this check — is exactly the entry that has to be
|
|
39
|
+
* re-verified rather than trusted, and now it is, on every run.
|
|
40
|
+
*
|
|
41
|
+
* **The staleness half looks for writes more widely than the flagging half**,
|
|
42
|
+
* and the asymmetry is deliberate: both errors then fall on the safe side. The
|
|
43
|
+
* flagging half only flags an ORM mutation call it is sure about; the staleness
|
|
44
|
+
* half additionally counts a raw SQL write statement (`conn.execute` with an
|
|
45
|
+
* `update`/`insert into`/`delete from`) and any write reached transitively
|
|
46
|
+
* through `this.<name>(…)` in the same file — so a marker guarding a real write
|
|
47
|
+
* this check cannot itself see is left alone, and only a marker guarding nothing
|
|
48
|
+
* is reported.
|
|
49
|
+
*
|
|
50
|
+
* **With one subtraction (D-89c): a downstream write that itself carries a
|
|
51
|
+
* marker does not keep the caller's marker alive.** The transitive rule exists
|
|
52
|
+
* so a marker over a write this check cannot see is not called stale; when the
|
|
53
|
+
* write it reaches is *already exempted where it happens*, the caller's marker
|
|
54
|
+
* is provably guarding nothing and the ratchet has to say so. The worked
|
|
55
|
+
* example carries the failure and its correction in one file:
|
|
56
|
+
* `payments/services/payment-reference-port.ts` marked `stampExternalReference`
|
|
57
|
+
* and `stampExternalReferenceIfAbsent`, neither of which writes, and later
|
|
58
|
+
* marked the private `stamp` that does the `flush` — with a comment explaining
|
|
59
|
+
* that the marker had to be repeated there "because the check reads the
|
|
60
|
+
* function that writes". Both redundant caller markers survived, and nothing
|
|
61
|
+
* could see them.
|
|
62
|
+
*
|
|
63
|
+
* ## What "every service" was allowed to mean (issue #122)
|
|
64
|
+
*
|
|
65
|
+
* The walk matched `**/services/<file>.ts` — **one level, nothing else**. Over
|
|
66
|
+
* the tree that is **472 of 1152** module files: 680 were never opened, 78 of
|
|
67
|
+
* them containing a write signal. Invisible were `pim_ergonode/services/import/`,
|
|
68
|
+
* `product_feeds/services/delivery/` and `services/queues/` (nested a level too
|
|
69
|
+
* deep), every `workers/`, `queues/` and `jobs/` file, every `commands/` file,
|
|
70
|
+
* every `routes*.ts`, every `backend.ts` boot hook, every `scripts/` entry point
|
|
71
|
+
* and every `seeds/` reconciler. Queue consumers and admin route handlers are
|
|
72
|
+
* exactly where writes live, so the check read clean over the two categories
|
|
73
|
+
* Principle XIII is most about.
|
|
74
|
+
*
|
|
75
|
+
* The boundary is now **every `.ts` file under `src/modules/` and `src/apps/`**.
|
|
76
|
+
* Four exclusions remain, and each is an argument rather than an omission:
|
|
77
|
+
*
|
|
78
|
+
* - `migrations/` — DDL applied by the migrator with no request, no actor and
|
|
79
|
+
* no undo; an audit entry for one would have nobody to attribute it to. The
|
|
80
|
+
* migration registry and `db:fresh` are its gate.
|
|
81
|
+
* - `*.test.ts` / `*.d.ts` — not shipped code.
|
|
82
|
+
* - `audit_logs/` — the audit writer itself. Its writes *are* the audit
|
|
83
|
+
* entries; requiring one for each is circular.
|
|
84
|
+
*
|
|
85
|
+
* Everything else is judged, including `seeds/` and `scripts/`: a CLI entry
|
|
86
|
+
* point creating an administrator and a boot reconciler writing predefined rows
|
|
87
|
+
* are operator-visible writes that happen to run outside a request, and a
|
|
88
|
+
* category-wide exemption for them is the same mistake one folder over.
|
|
89
|
+
*
|
|
90
|
+
* ## Two narrowings the widening forced
|
|
91
|
+
*
|
|
92
|
+
* A wider walk meets shapes a `services/` file rarely has, and a check that
|
|
93
|
+
* answers them with exemptions is lying about the tree rather than reading it:
|
|
94
|
+
*
|
|
95
|
+
* 1. **`remove` is only an ORM mutation off an EntityManager.** 30 of the 36
|
|
96
|
+
* first-pass findings were `deps.<x>Service.remove(id)` in a route handler
|
|
97
|
+
* — a call into an audited service. Every other name in the vocabulary
|
|
98
|
+
* (`persist`, `persistAndFlush`, `nativeUpdate`, `nativeDelete`,
|
|
99
|
+
* `removeAndFlush`, `flush`) is MikroORM's alone and still counts off any
|
|
100
|
+
* receiver. The **staleness half keeps counting `remove` everywhere**, so
|
|
101
|
+
* the asymmetry above survives: the narrowing can only add a report, never
|
|
102
|
+
* silence one.
|
|
103
|
+
* 2. **A route file is judged per handler.** `registerXAdminRoutes` is not a
|
|
104
|
+
* unit of work; each handler is. Read as one unit, a single
|
|
105
|
+
* `commandBus.run` anywhere in the file clears every other handler in it —
|
|
106
|
+
* the masking the per-method rule exists to prevent, one level up. It also
|
|
107
|
+
* manufactured two `double-audit` reports across handlers that never met.
|
|
108
|
+
*
|
|
109
|
+
* ## What this check cannot see, and will not pretend to (D-89b)
|
|
110
|
+
*
|
|
111
|
+
* This check reads **call** shapes. A field assignment on a managed entity —
|
|
112
|
+
* `order.status = ref`, `refund.settlementState = outcome.state` — is a write
|
|
113
|
+
* the unit of work will flush and this check cannot see it. The unit is judged
|
|
114
|
+
* by the calls it makes, so an assignment is caught only when the same unit also
|
|
115
|
+
* calls one of the vocabulary. A unit that assigns and lets its caller flush is
|
|
116
|
+
* outside the population, **by construction and not by exemption**.
|
|
117
|
+
*
|
|
118
|
+
* That is refused deliberately rather than deferred. The shapes are not
|
|
119
|
+
* separable by a static name test: `x.y = z` is the most common statement form
|
|
120
|
+
* in the language, `x` is a managed entity only when the type checker says so,
|
|
121
|
+
* and this check does not build a program. Reading every assignment as a
|
|
122
|
+
* candidate write would put a finding on most methods in the tree and teach
|
|
123
|
+
* people to write exemptions; reading none of them keeps the check's green
|
|
124
|
+
* honest — provided the green is read as what it is. So: a green
|
|
125
|
+
* `check:command-coverage --strict` means *"no unaudited sensitive write of a
|
|
126
|
+
* shape this check can see"*, and it does not mean *"every sensitive write is
|
|
127
|
+
* audited"*. It cannot be made to mean the second at anything like this cost.
|
|
128
|
+
*
|
|
129
|
+
* Scope & staging: build-breaks (exit 1) for **migrated modules**
|
|
130
|
+
* (`MIGRATED_MODULES`, in the repository-scope host, or `--module`); any other
|
|
131
|
+
* module would be report-only. The platform-wide rollout is COMPLETE and CI runs
|
|
132
|
+
* with `--strict`, so a finding in ANY module, including a brand-new one not yet
|
|
133
|
+
* in the list, fails the build.
|
|
134
|
+
*
|
|
135
|
+
* ## One analysis, two hosts
|
|
136
|
+
*
|
|
137
|
+
* This file is the analysis. `backend/scripts/check-command-coverage.ts` hosts it
|
|
138
|
+
* over this repository's module tree and owns the rollout ledger, which is a
|
|
139
|
+
* fact about *these* modules; `endora check` hosts it over one module package
|
|
140
|
+
* (`specs/101-endora-check/contracts/package-scope-layout.md` §6). A third-party
|
|
141
|
+
* package is in no rollout, so the package-scope host judges it strictly — which
|
|
142
|
+
* is what `--strict` already means here.
|
|
143
|
+
*/
|
|
144
|
+
export type FindingKind = 'unaudited-sensitive-write' | 'double-audit' | 'stale-ignore';
|
|
145
|
+
export interface CoverageFinding {
|
|
146
|
+
filePath: string;
|
|
147
|
+
line: number | null;
|
|
148
|
+
method: string;
|
|
149
|
+
kind: FindingKind;
|
|
150
|
+
message: string;
|
|
151
|
+
}
|
|
152
|
+
/** Static, dependency-free per-method analysis of a single source file. */
|
|
153
|
+
export declare function analyzeSource(filePath: string, source: string): CoverageFinding[];
|
|
154
|
+
/** Whether a repo-relative module path belongs to a build-breaking (migrated) module. */
|
|
155
|
+
export declare function isMigratedModulePath(relPath: string, migrated: readonly string[]): boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Registered modules {@link collectScannedFiles} excludes wholesale, so the
|
|
158
|
+
* population floor asks for the tree the check actually reads.
|
|
159
|
+
*
|
|
160
|
+
* One entry, and it is the argument in the header rather than a convenience:
|
|
161
|
+
* the audit writer's writes *are* the audit entries. Named here so the walk and
|
|
162
|
+
* the floor cannot disagree about it.
|
|
163
|
+
*/
|
|
164
|
+
export declare const EXCLUDED_MODULES: readonly string[];
|
|
165
|
+
/**
|
|
166
|
+
* Directory names this rule's walk prunes wherever they occur under its root.
|
|
167
|
+
*
|
|
168
|
+
* `migrations` is the one that is a *rule* rather than a convenience: a
|
|
169
|
+
* migration is schema, not a service write, and judging one would put a finding
|
|
170
|
+
* on every table this platform creates.
|
|
171
|
+
*
|
|
172
|
+
* `test-support` is the second of that kind, and it arrived measured
|
|
173
|
+
* (`specs/134-paid-module-extraction/` T015). A module's test doubles and
|
|
174
|
+
* fixtures moved out of `backend/test/helpers/` into `src/test-support/`, which
|
|
175
|
+
* is inside this walk where the old location was outside it — so
|
|
176
|
+
* `seedXlInstallation()`, a fixture writer that has always existed, became an
|
|
177
|
+
* `unaudited-sensitive-write` on the day it moved and took `master` red in
|
|
178
|
+
* `moved-module-tree.test.ts` rather than in the check anybody had run. A
|
|
179
|
+
* fixture is not a service write for the same reason a migration is not: nothing
|
|
180
|
+
* an operator did is being recorded, and requiring a Command of one would put a
|
|
181
|
+
* finding on every seed in the tree. The layer is excluded exactly as
|
|
182
|
+
* `*.test.ts` is below, which is what it is — the test tree, one directory over.
|
|
183
|
+
*/
|
|
184
|
+
export declare const PRUNED_DIRECTORIES: readonly string[];
|
|
185
|
+
/**
|
|
186
|
+
* Whether a file is in this rule's population — the **one** membership decision,
|
|
187
|
+
* asked by the walk and by anything that needs to know what the walk would open.
|
|
188
|
+
*
|
|
189
|
+
* It is one function called from two places on purpose, in the idiom
|
|
190
|
+
* `check-nul-bytes.ts` argues for in its own header: while the prune the walk
|
|
191
|
+
* does for speed and the rule the population states were two expressions of one
|
|
192
|
+
* predicate, one could go missing without the other noticing. The second caller
|
|
193
|
+
* is `endora check`'s package-scope floor, which has to know that a declared
|
|
194
|
+
* `./migrations` layer is **not** expected of this rule — otherwise a package
|
|
195
|
+
* that publishes migrations reports a short walk for a layer the rule excludes
|
|
196
|
+
* by design.
|
|
197
|
+
*/
|
|
198
|
+
export declare function isScannedPath(absolutePath: string, root: string): boolean;
|
|
199
|
+
/**
|
|
200
|
+
* Every file the check judges, under `root` (`src/modules` or `src/apps`).
|
|
201
|
+
*
|
|
202
|
+
* Exported so the check's own test can assert the **real** tree is clean rather
|
|
203
|
+
* than only that the analyzer can go red on a fixture — the scan scope then has
|
|
204
|
+
* one definition, shared by the CLI and the test.
|
|
205
|
+
*
|
|
206
|
+
* The exclusions are argued in the header; each is a claim that the category
|
|
207
|
+
* cannot hold an operator-visible write, not a convenience.
|
|
208
|
+
*/
|
|
209
|
+
export declare function collectScannedFiles(root: string): string[];
|
|
210
|
+
//# sourceMappingURL=command-coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-coverage.d.ts","sourceRoot":"","sources":["../../src/rules/command-coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8IG;AA2IH,MAAM,MAAM,WAAW,GAAG,2BAA2B,GAAG,cAAc,GAAG,cAAc,CAAC;AAExF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAqTD,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,EAAE,CA+GjF;AAED,yFAAyF;AACzF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAMT;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAAmB,CAAC;AAElE;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAK/C,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAMzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAgB1D"}
|