@intentius/chant 0.49.0 → 0.51.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 (294) hide show
  1. package/dist/audit/catalog.d.ts +13 -3
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +9 -0
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +6 -0
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/fetch.d.ts.map +1 -1
  8. package/dist/audit/report-html.d.ts.map +1 -1
  9. package/dist/audit/report-model.d.ts +6 -0
  10. package/dist/audit/report-model.d.ts.map +1 -1
  11. package/dist/audit/report.d.ts.map +1 -1
  12. package/dist/audit/rules-doc.d.ts.map +1 -1
  13. package/dist/audit/secrets.d.ts +95 -0
  14. package/dist/audit/secrets.d.ts.map +1 -0
  15. package/dist/audit/wrangler.d.ts +33 -0
  16. package/dist/audit/wrangler.d.ts.map +1 -0
  17. package/dist/build.d.ts.map +1 -1
  18. package/dist/cli/commands/audit.d.ts +7 -0
  19. package/dist/cli/commands/audit.d.ts.map +1 -1
  20. package/dist/cli/commands/build.d.ts +23 -0
  21. package/dist/cli/commands/build.d.ts.map +1 -1
  22. package/dist/cli/handlers/build.d.ts.map +1 -1
  23. package/dist/cli/handlers/components.d.ts +31 -0
  24. package/dist/cli/handlers/components.d.ts.map +1 -1
  25. package/dist/cli/handlers/lifecycle.d.ts +11 -0
  26. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  27. package/dist/cli/handlers/op-progress.d.ts +57 -0
  28. package/dist/cli/handlers/op-progress.d.ts.map +1 -0
  29. package/dist/cli/handlers/operator.d.ts +32 -0
  30. package/dist/cli/handlers/operator.d.ts.map +1 -0
  31. package/dist/cli/handlers/run-client.d.ts +21 -1
  32. package/dist/cli/handlers/run-client.d.ts.map +1 -1
  33. package/dist/cli/handlers/run-report.d.ts.map +1 -1
  34. package/dist/cli/handlers/run.d.ts.map +1 -1
  35. package/dist/cli/handlers/scenario.d.ts +39 -0
  36. package/dist/cli/handlers/scenario.d.ts.map +1 -0
  37. package/dist/cli/handlers/search.d.ts +22 -0
  38. package/dist/cli/handlers/search.d.ts.map +1 -1
  39. package/dist/cli/main.d.ts.map +1 -1
  40. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  41. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  42. package/dist/cli/mcp/server.d.ts +35 -2
  43. package/dist/cli/mcp/server.d.ts.map +1 -1
  44. package/dist/cli/mcp/types.d.ts +29 -1
  45. package/dist/cli/mcp/types.d.ts.map +1 -1
  46. package/dist/cli/registry.d.ts +47 -3
  47. package/dist/cli/registry.d.ts.map +1 -1
  48. package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
  49. package/dist/components/capability.d.ts +17 -2
  50. package/dist/components/capability.d.ts.map +1 -1
  51. package/dist/components/cli-support.d.ts +7 -0
  52. package/dist/components/cli-support.d.ts.map +1 -1
  53. package/dist/components/component.d.ts +15 -0
  54. package/dist/components/component.d.ts.map +1 -1
  55. package/dist/components/driver.d.ts.map +1 -1
  56. package/dist/components/run-progress.d.ts +7 -5
  57. package/dist/components/run-progress.d.ts.map +1 -1
  58. package/dist/components/verbs/index.d.ts +6 -1
  59. package/dist/components/verbs/index.d.ts.map +1 -1
  60. package/dist/components/verbs/run-agent.d.ts +499 -0
  61. package/dist/components/verbs/run-agent.d.ts.map +1 -0
  62. package/dist/components/verbs/sign.d.ts +30 -0
  63. package/dist/components/verbs/sign.d.ts.map +1 -1
  64. package/dist/composite.d.ts +6 -1
  65. package/dist/composite.d.ts.map +1 -1
  66. package/dist/discovery/collect.d.ts.map +1 -1
  67. package/dist/discovery/fold-import.d.ts +15 -1
  68. package/dist/discovery/fold-import.d.ts.map +1 -1
  69. package/dist/discovery/fold-rank.d.ts +66 -0
  70. package/dist/discovery/fold-rank.d.ts.map +1 -0
  71. package/dist/discovery/index.d.ts +15 -0
  72. package/dist/discovery/index.d.ts.map +1 -1
  73. package/dist/discovery/param-deps.d.ts +17 -0
  74. package/dist/discovery/param-deps.d.ts.map +1 -0
  75. package/dist/fold/fold.d.ts +55 -2
  76. package/dist/fold/fold.d.ts.map +1 -1
  77. package/dist/fold/subset.d.ts +21 -14
  78. package/dist/fold/subset.d.ts.map +1 -1
  79. package/dist/lexicon-schema.d.ts +2 -0
  80. package/dist/lexicon-schema.d.ts.map +1 -1
  81. package/dist/lexicon.d.ts +134 -0
  82. package/dist/lexicon.d.ts.map +1 -1
  83. package/dist/lifecycle/assert-live.d.ts +77 -0
  84. package/dist/lifecycle/assert-live.d.ts.map +1 -0
  85. package/dist/lifecycle/change-set.d.ts +17 -0
  86. package/dist/lifecycle/change-set.d.ts.map +1 -1
  87. package/dist/lifecycle/converge-ledger.d.ts +90 -0
  88. package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
  89. package/dist/lifecycle/deep-diff.d.ts +18 -0
  90. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  91. package/dist/lifecycle/deep-observe.d.ts +9 -1
  92. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  93. package/dist/lifecycle/disruption.d.ts +96 -0
  94. package/dist/lifecycle/disruption.d.ts.map +1 -0
  95. package/dist/lifecycle/gate-ledger.d.ts +33 -0
  96. package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
  97. package/dist/lifecycle/git.d.ts +145 -21
  98. package/dist/lifecycle/git.d.ts.map +1 -1
  99. package/dist/lifecycle/index.d.ts +6 -0
  100. package/dist/lifecycle/index.d.ts.map +1 -1
  101. package/dist/lifecycle/lease.d.ts +113 -0
  102. package/dist/lifecycle/lease.d.ts.map +1 -0
  103. package/dist/lifecycle/replay.d.ts +2 -0
  104. package/dist/lifecycle/replay.d.ts.map +1 -1
  105. package/dist/lifecycle/scenario-eval.d.ts +42 -0
  106. package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
  107. package/dist/lifecycle/scenario.d.ts +163 -0
  108. package/dist/lifecycle/scenario.d.ts.map +1 -0
  109. package/dist/lifecycle/symptoms.d.ts +63 -0
  110. package/dist/lifecycle/symptoms.d.ts.map +1 -0
  111. package/dist/lint/output-docs.d.ts +94 -0
  112. package/dist/lint/output-docs.d.ts.map +1 -0
  113. package/dist/lint/post-synth.d.ts +29 -0
  114. package/dist/lint/post-synth.d.ts.map +1 -1
  115. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
  116. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
  117. package/dist/lsp/lexicon-providers.d.ts +7 -0
  118. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  119. package/dist/op/activity-contract.d.ts +139 -0
  120. package/dist/op/activity-contract.d.ts.map +1 -0
  121. package/dist/op/builders.d.ts +42 -2
  122. package/dist/op/builders.d.ts.map +1 -1
  123. package/dist/op/converge-rule.d.ts +161 -0
  124. package/dist/op/converge-rule.d.ts.map +1 -0
  125. package/dist/op/generate-pipeline.d.ts +39 -0
  126. package/dist/op/generate-pipeline.d.ts.map +1 -0
  127. package/dist/op/index.d.ts +14 -0
  128. package/dist/op/index.d.ts.map +1 -1
  129. package/dist/op/local-executor.d.ts +7 -1
  130. package/dist/op/local-executor.d.ts.map +1 -1
  131. package/dist/op/op-verb-class.d.ts +42 -0
  132. package/dist/op/op-verb-class.d.ts.map +1 -0
  133. package/dist/op/operator.d.ts +128 -0
  134. package/dist/op/operator.d.ts.map +1 -0
  135. package/dist/op/step-output-ref.d.ts +187 -0
  136. package/dist/op/step-output-ref.d.ts.map +1 -0
  137. package/dist/op/types.d.ts +18 -1
  138. package/dist/op/types.d.ts.map +1 -1
  139. package/dist/provenance.d.ts +73 -3
  140. package/dist/provenance.d.ts.map +1 -1
  141. package/dist/runtime-adapter.d.ts +7 -1
  142. package/dist/runtime-adapter.d.ts.map +1 -1
  143. package/dist/serializer.d.ts +18 -0
  144. package/dist/serializer.d.ts.map +1 -1
  145. package/dist/testing.d.ts +23 -2
  146. package/dist/testing.d.ts.map +1 -1
  147. package/dist/toml.d.ts +40 -5
  148. package/dist/toml.d.ts.map +1 -1
  149. package/package.json +1 -1
  150. package/src/audit/catalog.test.ts +1 -1
  151. package/src/audit/catalog.ts +75 -3
  152. package/src/audit/core.ts +9 -0
  153. package/src/audit/discover.ts +29 -2
  154. package/src/audit/fetch.test.ts +216 -3
  155. package/src/audit/fetch.ts +270 -59
  156. package/src/audit/report-html.ts +5 -2
  157. package/src/audit/report-model.ts +9 -0
  158. package/src/audit/report.test.ts +22 -0
  159. package/src/audit/report.ts +3 -2
  160. package/src/audit/rules-doc.ts +2 -0
  161. package/src/audit/secrets.test.ts +303 -0
  162. package/src/audit/secrets.ts +406 -0
  163. package/src/audit/wrangler.test.ts +230 -0
  164. package/src/audit/wrangler.ts +290 -0
  165. package/src/build.ts +8 -3
  166. package/src/cli/command-group.ts +1 -1
  167. package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
  168. package/src/cli/commands/audit.test.ts +215 -1
  169. package/src/cli/commands/audit.ts +86 -17
  170. package/src/cli/commands/build.test.ts +167 -2
  171. package/src/cli/commands/build.ts +114 -23
  172. package/src/cli/handlers/build.ts +2 -0
  173. package/src/cli/handlers/components.test.ts +199 -1
  174. package/src/cli/handlers/components.ts +160 -3
  175. package/src/cli/handlers/graph.test.ts +20 -0
  176. package/src/cli/handlers/graph.ts +10 -1
  177. package/src/cli/handlers/lifecycle.test.ts +90 -0
  178. package/src/cli/handlers/lifecycle.ts +30 -5
  179. package/src/cli/handlers/op-progress.test.ts +202 -0
  180. package/src/cli/handlers/op-progress.ts +192 -0
  181. package/src/cli/handlers/operator.test.ts +255 -0
  182. package/src/cli/handlers/operator.ts +240 -0
  183. package/src/cli/handlers/run-client.test.ts +82 -0
  184. package/src/cli/handlers/run-client.ts +85 -2
  185. package/src/cli/handlers/run-report.test.ts +62 -0
  186. package/src/cli/handlers/run-report.ts +20 -58
  187. package/src/cli/handlers/run.test.ts +144 -0
  188. package/src/cli/handlers/run.ts +40 -18
  189. package/src/cli/handlers/scenario.test.ts +456 -0
  190. package/src/cli/handlers/scenario.ts +330 -0
  191. package/src/cli/handlers/search-drift.test.ts +263 -0
  192. package/src/cli/handlers/search.ts +150 -1
  193. package/src/cli/main.test.ts +23 -0
  194. package/src/cli/main.ts +81 -1
  195. package/src/cli/mcp/op-tools.ts +17 -6
  196. package/src/cli/mcp/resource-handlers.ts +13 -5
  197. package/src/cli/mcp/server.test.ts +265 -2
  198. package/src/cli/mcp/server.ts +84 -7
  199. package/src/cli/mcp/types.ts +27 -1
  200. package/src/cli/registry.ts +47 -3
  201. package/src/codegen/docs-rule-scanning.test.ts +42 -0
  202. package/src/codegen/docs-rule-scanning.ts +25 -2
  203. package/src/components/README.md +7 -0
  204. package/src/components/capability.ts +17 -2
  205. package/src/components/cli-support.test.ts +17 -0
  206. package/src/components/cli-support.ts +13 -1
  207. package/src/components/component-schema.test.ts +32 -0
  208. package/src/components/component.schema.json +6 -0
  209. package/src/components/component.test.ts +21 -0
  210. package/src/components/component.ts +15 -0
  211. package/src/components/driver.ts +12 -4
  212. package/src/components/run-progress.ts +9 -5
  213. package/src/components/verbs/index.ts +6 -1
  214. package/src/components/verbs/run-agent.test.ts +683 -0
  215. package/src/components/verbs/run-agent.ts +786 -0
  216. package/src/components/verbs/sign.test.ts +19 -0
  217. package/src/components/verbs/sign.ts +34 -2
  218. package/src/composite.ts +31 -2
  219. package/src/discovery/collect.ts +11 -2
  220. package/src/discovery/fold-import.test.ts +54 -0
  221. package/src/discovery/fold-import.ts +178 -38
  222. package/src/discovery/fold-rank.test.ts +197 -0
  223. package/src/discovery/fold-rank.ts +346 -0
  224. package/src/discovery/index.ts +16 -1
  225. package/src/discovery/param-deps.test.ts +118 -0
  226. package/src/discovery/param-deps.ts +170 -0
  227. package/src/fold/fold.test.ts +6 -2
  228. package/src/fold/fold.ts +184 -3
  229. package/src/fold/subset.test.ts +82 -19
  230. package/src/fold/subset.ts +79 -41
  231. package/src/lexicon-schema.ts +3 -0
  232. package/src/lexicon.ts +154 -2
  233. package/src/lifecycle/assert-live.test.ts +125 -0
  234. package/src/lifecycle/assert-live.ts +154 -0
  235. package/src/lifecycle/change-set.ts +35 -3
  236. package/src/lifecycle/converge-ledger.test.ts +199 -0
  237. package/src/lifecycle/converge-ledger.ts +179 -0
  238. package/src/lifecycle/deep-diff.test.ts +79 -1
  239. package/src/lifecycle/deep-diff.ts +23 -0
  240. package/src/lifecycle/deep-observe.ts +13 -2
  241. package/src/lifecycle/disruption.test.ts +186 -0
  242. package/src/lifecycle/disruption.ts +224 -0
  243. package/src/lifecycle/gate-ledger.test.ts +103 -0
  244. package/src/lifecycle/gate-ledger.ts +140 -0
  245. package/src/lifecycle/git.test.ts +430 -0
  246. package/src/lifecycle/git.ts +446 -84
  247. package/src/lifecycle/index.ts +6 -0
  248. package/src/lifecycle/lease.test.ts +343 -0
  249. package/src/lifecycle/lease.ts +270 -0
  250. package/src/lifecycle/replay.test.ts +25 -0
  251. package/src/lifecycle/replay.ts +11 -3
  252. package/src/lifecycle/scenario-eval.test.ts +199 -0
  253. package/src/lifecycle/scenario-eval.ts +158 -0
  254. package/src/lifecycle/scenario.test.ts +195 -0
  255. package/src/lifecycle/scenario.ts +321 -0
  256. package/src/lifecycle/symptoms.test.ts +116 -0
  257. package/src/lifecycle/symptoms.ts +126 -0
  258. package/src/lint/output-docs.test.ts +220 -0
  259. package/src/lint/output-docs.ts +204 -0
  260. package/src/lint/post-synth.test.ts +97 -0
  261. package/src/lint/post-synth.ts +45 -0
  262. package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
  263. package/src/lint/rules/comp/comp.test.ts +49 -1
  264. package/src/lint/rules/evl001-non-literal-expression.test.ts +8 -3
  265. package/src/lint/rules/evl001-non-literal-expression.ts +6 -6
  266. package/src/lsp/lexicon-providers.test.ts +44 -0
  267. package/src/lsp/lexicon-providers.ts +11 -1
  268. package/src/op/activity-contract.test.ts +180 -0
  269. package/src/op/activity-contract.ts +278 -0
  270. package/src/op/builders-exports.test.ts +17 -1
  271. package/src/op/builders.ts +59 -5
  272. package/src/op/converge-rule.test.ts +179 -0
  273. package/src/op/converge-rule.ts +311 -0
  274. package/src/op/generate-pipeline.test.ts +53 -0
  275. package/src/op/generate-pipeline.ts +99 -0
  276. package/src/op/index.ts +30 -0
  277. package/src/op/local-executor.test.ts +92 -0
  278. package/src/op/local-executor.ts +52 -10
  279. package/src/op/local-output.ts +1 -1
  280. package/src/op/op-verb-class.test.ts +126 -0
  281. package/src/op/op-verb-class.ts +115 -0
  282. package/src/op/operator.test.ts +346 -0
  283. package/src/op/operator.ts +213 -0
  284. package/src/op/step-output-ref.test.ts +334 -0
  285. package/src/op/step-output-ref.ts +453 -0
  286. package/src/op/types.ts +18 -1
  287. package/src/provenance.test.ts +151 -4
  288. package/src/provenance.ts +118 -4
  289. package/src/runtime-adapter.ts +31 -10
  290. package/src/serializer.ts +18 -0
  291. package/src/testing.test.ts +89 -2
  292. package/src/testing.ts +63 -3
  293. package/src/toml.test.ts +157 -384
  294. package/src/toml.ts +371 -5
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Wrangler config audit (#446) — a config format the audit engine could not
3
+ * parse at all before this: `wrangler.toml`, Cloudflare Workers' native
4
+ * declarative deploy file (routes, bindings, vars, observability). Fitting,
5
+ * since chant's own hosted audit path targets the same Workers runtime.
6
+ *
7
+ * Follows `secrets.ts`'s precedent for a format the issue says should ship
8
+ * "only a detector and a set of checks — no authoring surface, no output
9
+ * serializer": no lexicon plugin, no `AuditInput`/`classifyFiles` involvement,
10
+ * no npm package to install. `auditWranglerConfigs` scans the raw candidate
11
+ * files directly (the detector is `isWranglerConfigPath`) and is called
12
+ * alongside `scanForSecrets` from the CLI. `wrangler.toml` is also NOT the
13
+ * `fly` lexicon's `.fly` (this repo already has a `fly` lexicon covering the
14
+ * Machines/flaps REST API with its own `FLY0xx` ids) — this module owns a
15
+ * disjoint namespace (`WRG0xx`) and `AuditFinding.lexicon = "wrangler"`.
16
+ *
17
+ * Pure: no fs, no network, no Node-only global — Workers-safe (epic #350).
18
+ */
19
+
20
+ import { parseTOML, TomlParseError } from "../toml";
21
+ import type { AuditFinding } from "./core";
22
+
23
+ /** The minimal file shape the scanner needs (matches `discover.ts`'s `RepoFile`). */
24
+ export interface ScannableFile {
25
+ path: string;
26
+ content: string;
27
+ }
28
+
29
+ /** Detector: does this path look like a Wrangler config? (native TOML only — see module doc.) */
30
+ export function isWranglerConfigPath(path: string): boolean {
31
+ const base = path.split("/").pop() ?? path;
32
+ return base === "wrangler.toml";
33
+ }
34
+
35
+ type JsonRecord = Record<string, unknown>;
36
+
37
+ function isRecord(v: unknown): v is JsonRecord {
38
+ return typeof v === "object" && v !== null && !Array.isArray(v);
39
+ }
40
+
41
+ function asArray(v: unknown): unknown[] {
42
+ return Array.isArray(v) ? v : [];
43
+ }
44
+
45
+ /** One config scope: the top-level document, or one `[env.<name>]` block. */
46
+ interface Scope {
47
+ /** `"top-level"`, or the env name (`"production"`, `"staging"`, …). */
48
+ name: string;
49
+ /** Whether this scope is the account's default deploy target (`wrangler deploy` with no `--env`). */
50
+ isTopLevel: boolean;
51
+ config: JsonRecord;
52
+ }
53
+
54
+ function scopesOf(doc: JsonRecord): Scope[] {
55
+ const scopes: Scope[] = [{ name: "top-level", isTopLevel: true, config: doc }];
56
+ const env = doc.env;
57
+ if (isRecord(env)) {
58
+ for (const [name, cfg] of Object.entries(env)) {
59
+ if (isRecord(cfg)) scopes.push({ name, isTopLevel: false, config: cfg });
60
+ }
61
+ }
62
+ return scopes;
63
+ }
64
+
65
+ const PROD_NAME_RE = /^(prod|production|live)$/i;
66
+ const NON_PROD_NAME_RE = /^(dev|development|staging|stage|preview|test|testing|sandbox|local)$/i;
67
+
68
+ /** Best-effort 1-based line number of the first line containing `needle`, or undefined. */
69
+ function lineOf(content: string, needle: string): number | undefined {
70
+ if (!needle) return undefined;
71
+ const idx = content.indexOf(needle);
72
+ if (idx === -1) return undefined;
73
+ return content.slice(0, idx).split("\n").length;
74
+ }
75
+
76
+ // ── WRG001: workers_dev in a production-named environment ──────────────────
77
+
78
+ function checkWorkersDevInProd(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
79
+ const out: AuditFinding[] = [];
80
+ for (const scope of scopesOf(doc)) {
81
+ if (!PROD_NAME_RE.test(scope.name)) continue;
82
+ if (scope.config.workers_dev !== true) continue;
83
+ out.push({
84
+ checkId: "WRG001",
85
+ severity: "warning",
86
+ message: `Environment "${scope.name}" sets workers_dev = true — the Worker is reachable on the public *.workers.dev subdomain, bypassing whatever access control a custom domain/zone would provide.`,
87
+ file: file.path,
88
+ lexicon: "wrangler",
89
+ entity: scope.name,
90
+ line: lineOf(file.content, "workers_dev"),
91
+ });
92
+ }
93
+ return out;
94
+ }
95
+
96
+ // ── WRG002: sensitive-named key stored in [vars] instead of a secret ───────
97
+
98
+ const SENSITIVE_VAR_RE = /(SECRET|TOKEN|API[_-]?KEY|PASSWORD|PASSWD|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|CLIENT[_-]?SECRET|\bAUTH\b)/i;
99
+
100
+ function checkSecretShapedVars(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
101
+ const out: AuditFinding[] = [];
102
+ for (const scope of scopesOf(doc)) {
103
+ const vars = scope.config.vars;
104
+ if (!isRecord(vars)) continue;
105
+ for (const key of Object.keys(vars)) {
106
+ if (!SENSITIVE_VAR_RE.test(key)) continue;
107
+ out.push({
108
+ checkId: "WRG002",
109
+ severity: "error",
110
+ message: `"${key}" in [vars] (${scope.name}) looks like a credential name. Plaintext vars are committed to source and visible via 'wrangler dev'/the dashboard — use 'wrangler secret put ${key}' instead.`,
111
+ file: file.path,
112
+ lexicon: "wrangler",
113
+ entity: key,
114
+ line: lineOf(file.content, key),
115
+ });
116
+ }
117
+ }
118
+ return out;
119
+ }
120
+
121
+ // ── WRG003: observability explicitly disabled ───────────────────────────────
122
+
123
+ function checkObservabilityDisabled(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
124
+ const out: AuditFinding[] = [];
125
+ for (const scope of scopesOf(doc)) {
126
+ const obs = scope.config.observability;
127
+ if (!isRecord(obs)) continue;
128
+ if (obs.enabled !== false) continue;
129
+ out.push({
130
+ checkId: "WRG003",
131
+ severity: "info",
132
+ message: `Environment "${scope.name}" explicitly disables observability (observability.enabled = false) — no Workers Logs are recorded for this deployment, which will slow down or block incident investigation.`,
133
+ file: file.path,
134
+ lexicon: "wrangler",
135
+ entity: scope.name,
136
+ line: lineOf(file.content, "observability"),
137
+ });
138
+ }
139
+ return out;
140
+ }
141
+
142
+ // ── WRG004: an account-wide wildcard route (unrestricted ingress) ──────────
143
+
144
+ function routePatterns(config: JsonRecord): string[] {
145
+ const patterns: string[] = [];
146
+ for (const r of asArray(config.routes)) {
147
+ if (typeof r === "string") patterns.push(r);
148
+ else if (isRecord(r) && typeof r.pattern === "string") patterns.push(r.pattern);
149
+ }
150
+ if (typeof config.route === "string") patterns.push(config.route);
151
+ return patterns;
152
+ }
153
+
154
+ /** A route pattern with no host scoping at all — matches every zone in the account, not just one. */
155
+ function isAccountWildcard(pattern: string): boolean {
156
+ const p = pattern.trim();
157
+ return p === "*" || p === "*/*";
158
+ }
159
+
160
+ function checkWildcardRoute(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
161
+ const out: AuditFinding[] = [];
162
+ for (const scope of scopesOf(doc)) {
163
+ for (const pattern of routePatterns(scope.config)) {
164
+ if (!isAccountWildcard(pattern)) continue;
165
+ out.push({
166
+ checkId: "WRG004",
167
+ severity: "error",
168
+ message: `Environment "${scope.name}" declares route "${pattern}" — an unscoped wildcard that matches every zone/path on the account, not one domain. Scope it to a specific zone (e.g. "example.com/*").`,
169
+ file: file.path,
170
+ lexicon: "wrangler",
171
+ entity: scope.name,
172
+ line: lineOf(file.content, pattern),
173
+ });
174
+ }
175
+ }
176
+ return out;
177
+ }
178
+
179
+ // ── WRG005: a non-prod environment shares a data-store id with production ──
180
+
181
+ interface BindingSpec {
182
+ key: string;
183
+ idField: string;
184
+ }
185
+ const BINDING_KINDS: BindingSpec[] = [
186
+ { key: "kv_namespaces", idField: "id" },
187
+ { key: "r2_buckets", idField: "bucket_name" },
188
+ { key: "d1_databases", idField: "database_id" },
189
+ ];
190
+
191
+ function checkCrossEnvSharedResource(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
192
+ const out: AuditFinding[] = [];
193
+ const scopes = scopesOf(doc);
194
+ // The top-level scope is the default deploy target (`wrangler deploy` with
195
+ // no `--env`) — treated as "production" only when the file also declares at
196
+ // least one dev/staging-like env, so a single-environment file (top-level
197
+ // only) never trips this on itself.
198
+ const hasNonProdEnv = scopes.some((s) => !s.isTopLevel && NON_PROD_NAME_RE.test(s.name));
199
+ const prodScopeNames = new Set(
200
+ scopes.filter((s) => (s.isTopLevel && hasNonProdEnv) || PROD_NAME_RE.test(s.name)).map((s) => s.name),
201
+ );
202
+ const nonProdScopes = scopes.filter((s) => NON_PROD_NAME_RE.test(s.name));
203
+ if (prodScopeNames.size === 0 || nonProdScopes.length === 0) return out;
204
+
205
+ for (const { key, idField } of BINDING_KINDS) {
206
+ // id -> set of scope names that declare a binding with that id
207
+ const byId = new Map<string, Set<string>>();
208
+ for (const scope of scopes) {
209
+ for (const binding of asArray(scope.config[key])) {
210
+ if (!isRecord(binding)) continue;
211
+ const id = binding[idField];
212
+ if (typeof id !== "string" || id === "") continue;
213
+ const owners = byId.get(id) ?? new Set<string>();
214
+ owners.add(scope.name);
215
+ byId.set(id, owners);
216
+ }
217
+ }
218
+ for (const [id, owners] of byId) {
219
+ const prodOwner = [...owners].find((n) => prodScopeNames.has(n));
220
+ const nonProdOwner = [...owners].find((n) => NON_PROD_NAME_RE.test(n));
221
+ if (!prodOwner || !nonProdOwner) continue;
222
+ out.push({
223
+ checkId: "WRG005",
224
+ severity: "error",
225
+ message: `"${nonProdOwner}" and "${prodOwner}" both bind ${key} ${idField}="${id}" — the non-production environment has full read/write access to the production data store instead of its own copy.`,
226
+ file: file.path,
227
+ lexicon: "wrangler",
228
+ entity: `${key}:${id}`,
229
+ line: lineOf(file.content, id),
230
+ });
231
+ }
232
+ }
233
+ return out;
234
+ }
235
+
236
+ // ── WRG006: static assets/site served from the project root ────────────────
237
+
238
+ const ROOT_DIR_RE = /^\.?\/?$/;
239
+
240
+ function checkAssetsServeRoot(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
241
+ const out: AuditFinding[] = [];
242
+ for (const scope of scopesOf(doc)) {
243
+ const candidates: Array<{ table: string; field: string; value: unknown }> = [];
244
+ if (isRecord(scope.config.site)) candidates.push({ table: "site", field: "bucket", value: scope.config.site.bucket });
245
+ if (isRecord(scope.config.assets)) candidates.push({ table: "assets", field: "directory", value: scope.config.assets.directory });
246
+ for (const c of candidates) {
247
+ if (typeof c.value !== "string" || !ROOT_DIR_RE.test(c.value)) continue;
248
+ out.push({
249
+ checkId: "WRG006",
250
+ severity: "warning",
251
+ message: `Environment "${scope.name}" sets [${c.table}].${c.field} = "${c.value}" — the project root is served as public static assets, including any files not meant to be public (config, source maps, .git).`,
252
+ file: file.path,
253
+ lexicon: "wrangler",
254
+ entity: scope.name,
255
+ line: lineOf(file.content, c.field),
256
+ });
257
+ }
258
+ }
259
+ return out;
260
+ }
261
+
262
+ const CHECKS: Array<(file: ScannableFile, doc: JsonRecord) => AuditFinding[]> = [
263
+ checkWorkersDevInProd,
264
+ checkSecretShapedVars,
265
+ checkObservabilityDisabled,
266
+ checkWildcardRoute,
267
+ checkCrossEnvSharedResource,
268
+ checkAssetsServeRoot,
269
+ ];
270
+
271
+ /**
272
+ * Audit every `wrangler.toml` in `files`. Pure with respect to the filesystem
273
+ * and network. A file that fails to parse contributes no findings — the audit
274
+ * contract is "runs against any repo," never a crash on odd/partial config.
275
+ */
276
+ export function auditWranglerConfigs(files: ScannableFile[]): AuditFinding[] {
277
+ const findings: AuditFinding[] = [];
278
+ for (const file of files) {
279
+ if (!isWranglerConfigPath(file.path)) continue;
280
+ let doc: JsonRecord;
281
+ try {
282
+ doc = parseTOML(file.content);
283
+ } catch (err) {
284
+ if (err instanceof TomlParseError) continue;
285
+ throw err;
286
+ }
287
+ for (const check of CHECKS) findings.push(...check(file, doc));
288
+ }
289
+ return findings;
290
+ }
package/src/build.ts CHANGED
@@ -8,6 +8,7 @@ import { DiscoveryError, BuildError as BuildErrorClass } from "./errors";
8
8
  import { LexiconOutput, isLexiconOutput } from "./lexicon-output";
9
9
  import { isSecretDeclaration } from "./secret-provenance";
10
10
  import { splitReceiptEntities } from "./effect-receipt";
11
+ import { isScenario } from "./lifecycle/scenario";
11
12
  import { AttrRef } from "./attrref";
12
13
  import { isAttrRefLike } from "./utils";
13
14
  import { isChildProject, type ChildProjectInstance } from "./child-project";
@@ -114,9 +115,10 @@ export function computeStackGraph(
114
115
  // Dependency map: node → producers it depends on.
115
116
  const nodes = lexiconNames.length
116
117
  ? [...lexiconNames]
117
- : // Secret provenance declarations (#1828) never form a stack — their
118
- // pseudo-lexicon has no serializer and must not appear in the manifest.
119
- [...new Set([...entities.values()].filter((e) => !isSecretDeclaration(e)).map((e) => e.lexicon))];
118
+ : // Secret provenance declarations (#1828) and plan scenarios (#1292) never
119
+ // form a stack — their pseudo-lexicon has no serializer and must not
120
+ // appear in the manifest.
121
+ [...new Set([...entities.values()].filter((e) => !isSecretDeclaration(e) && !isScenario(e)).map((e) => e.lexicon))];
120
122
  const deps = new Map<string, Set<string>>();
121
123
  for (const n of nodes) deps.set(n, new Set());
122
124
  for (const { from, to } of edges) {
@@ -303,6 +305,9 @@ export function partitionByLexicon(
303
305
  // them out of every partition means no serializer sees them and no
304
306
  // "No serializer found" warning fires for their pseudo-lexicon.
305
307
  if (isSecretDeclaration(entity)) continue;
308
+ // Plan scenarios (#1292) are serializer-neutral the same way: a checkable
309
+ // expectation the CLI reads off the entity map, never output.
310
+ if (isScenario(entity)) continue;
306
311
  const lexicon = entity.lexicon;
307
312
  if (!partitions.has(lexicon)) {
308
313
  partitions.set(lexicon, new Map());
@@ -71,7 +71,7 @@ export interface CommandGroup {
71
71
  export const RESERVED_COMMAND_NAMES: ReadonlySet<string> = new Set([
72
72
  "build", "lint", "list", "describe", "import", "audit", "migrate", "carve",
73
73
  "init", "update", "doctor", "dev", "run", "graph", "vendor", "lifecycle",
74
- "lc", "components", "emulator", "serve",
74
+ "lc", "components", "emulator", "serve", "operator", "approve",
75
75
  ]);
76
76
 
77
77
  /**