@intentius/chant 0.19.0 → 0.20.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 (156) hide show
  1. package/dist/build-params.d.ts +108 -0
  2. package/dist/build-params.d.ts.map +1 -0
  3. package/dist/build.d.ts +79 -1
  4. package/dist/build.d.ts.map +1 -1
  5. package/dist/cli/commands/build.d.ts +31 -0
  6. package/dist/cli/commands/build.d.ts.map +1 -1
  7. package/dist/cli/commands/check-lexicon-examples.d.ts +41 -0
  8. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -0
  9. package/dist/cli/commands/check-lexicon-intrinsics.d.ts +54 -0
  10. package/dist/cli/commands/check-lexicon-intrinsics.d.ts.map +1 -0
  11. package/dist/cli/commands/check-lexicon.d.ts +1 -1
  12. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  13. package/dist/cli/commands/lint.d.ts +27 -0
  14. package/dist/cli/commands/lint.d.ts.map +1 -1
  15. package/dist/cli/handlers/build.d.ts.map +1 -1
  16. package/dist/cli/handlers/components.d.ts.map +1 -1
  17. package/dist/cli/handlers/lint.d.ts.map +1 -1
  18. package/dist/cli/handlers/run-client.d.ts +1 -1
  19. package/dist/cli/handlers/run-client.d.ts.map +1 -1
  20. package/dist/cli/handlers/run.d.ts.map +1 -1
  21. package/dist/cli/main.d.ts.map +1 -1
  22. package/dist/cli/plugins.d.ts +16 -0
  23. package/dist/cli/plugins.d.ts.map +1 -1
  24. package/dist/cli/registry.d.ts +10 -0
  25. package/dist/cli/registry.d.ts.map +1 -1
  26. package/dist/codegen/docs-sections.d.ts.map +1 -1
  27. package/dist/components/cli-support.d.ts +12 -5
  28. package/dist/components/cli-support.d.ts.map +1 -1
  29. package/dist/components/discover.d.ts +62 -7
  30. package/dist/components/discover.d.ts.map +1 -1
  31. package/dist/components/sandbox/driver.d.ts +12 -0
  32. package/dist/components/sandbox/driver.d.ts.map +1 -0
  33. package/dist/components/sandbox/run.d.ts +42 -0
  34. package/dist/components/sandbox/run.d.ts.map +1 -0
  35. package/dist/composite.d.ts +5 -0
  36. package/dist/composite.d.ts.map +1 -1
  37. package/dist/config.d.ts +71 -0
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/discovery/collect.d.ts.map +1 -1
  40. package/dist/discovery/entity-wire-codec.d.ts +166 -0
  41. package/dist/discovery/entity-wire-codec.d.ts.map +1 -0
  42. package/dist/discovery/entity-wire.d.ts +50 -0
  43. package/dist/discovery/entity-wire.d.ts.map +1 -0
  44. package/dist/discovery/fold-import.d.ts +210 -0
  45. package/dist/discovery/fold-import.d.ts.map +1 -0
  46. package/dist/discovery/index.d.ts +74 -1
  47. package/dist/discovery/index.d.ts.map +1 -1
  48. package/dist/discovery/sandbox/bundle.d.ts +18 -0
  49. package/dist/discovery/sandbox/bundle.d.ts.map +1 -0
  50. package/dist/discovery/sandbox/child-errors.d.ts +15 -0
  51. package/dist/discovery/sandbox/child-errors.d.ts.map +1 -0
  52. package/dist/discovery/sandbox/driver.d.ts +13 -0
  53. package/dist/discovery/sandbox/driver.d.ts.map +1 -0
  54. package/dist/discovery/sandbox/run.d.ts +69 -0
  55. package/dist/discovery/sandbox/run.d.ts.map +1 -0
  56. package/dist/errors.d.ts +9 -1
  57. package/dist/errors.d.ts.map +1 -1
  58. package/dist/fold/fold.d.ts +226 -0
  59. package/dist/fold/fold.d.ts.map +1 -0
  60. package/dist/fold/subset.d.ts +102 -0
  61. package/dist/fold/subset.d.ts.map +1 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/lexicon-output.d.ts +7 -2
  65. package/dist/lexicon-output.d.ts.map +1 -1
  66. package/dist/lexicon-schema.d.ts +2 -2
  67. package/dist/lexicon-schema.d.ts.map +1 -1
  68. package/dist/lexicon.d.ts +81 -1
  69. package/dist/lexicon.d.ts.map +1 -1
  70. package/dist/lifecycle/release-ledger.d.ts +11 -0
  71. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  72. package/dist/lint/component-checks.d.ts +7 -1
  73. package/dist/lint/component-checks.d.ts.map +1 -1
  74. package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
  75. package/dist/lint/rules/evl003-dynamic-property-access.d.ts.map +1 -1
  76. package/dist/params.d.ts +60 -0
  77. package/dist/params.d.ts.map +1 -0
  78. package/dist/provenance.d.ts +21 -0
  79. package/dist/provenance.d.ts.map +1 -1
  80. package/dist/terraform/aws-resources.d.ts.map +1 -1
  81. package/package.json +2 -1
  82. package/src/build-params.test.ts +144 -0
  83. package/src/build-params.ts +207 -0
  84. package/src/build.test.ts +38 -0
  85. package/src/build.ts +144 -7
  86. package/src/cli/commands/build.test.ts +220 -2
  87. package/src/cli/commands/build.ts +111 -3
  88. package/src/cli/commands/check-lexicon-examples.test.ts +92 -0
  89. package/src/cli/commands/check-lexicon-examples.ts +103 -0
  90. package/src/cli/commands/check-lexicon-intrinsics.test.ts +145 -0
  91. package/src/cli/commands/check-lexicon-intrinsics.ts +312 -0
  92. package/src/cli/commands/check-lexicon.test.ts +34 -0
  93. package/src/cli/commands/check-lexicon.ts +119 -1
  94. package/src/cli/commands/lint.ts +31 -3
  95. package/src/cli/commands/onboard.ts +1 -1
  96. package/src/cli/component-security-boundary.test.ts +170 -0
  97. package/src/cli/handlers/build.ts +24 -3
  98. package/src/cli/handlers/components.ts +9 -2
  99. package/src/cli/handlers/dev.ts +1 -1
  100. package/src/cli/handlers/graph.ts +7 -5
  101. package/src/cli/handlers/lifecycle.ts +2 -2
  102. package/src/cli/handlers/lint.ts +2 -0
  103. package/src/cli/handlers/misc.ts +2 -2
  104. package/src/cli/handlers/run-client.ts +1 -1
  105. package/src/cli/handlers/run.ts +20 -5
  106. package/src/cli/main.test.ts +22 -0
  107. package/src/cli/main.ts +39 -0
  108. package/src/cli/plugins.ts +20 -4
  109. package/src/cli/registry.ts +10 -0
  110. package/src/cli/security-boundary.test.ts +135 -0
  111. package/src/codegen/docs-sections.test.ts +61 -0
  112. package/src/codegen/docs-sections.ts +7 -3
  113. package/src/components/cli-support.ts +22 -10
  114. package/src/components/discover.ts +127 -25
  115. package/src/components/sandbox/driver.ts +114 -0
  116. package/src/components/sandbox/run.test.ts +185 -0
  117. package/src/components/sandbox/run.ts +177 -0
  118. package/src/composite.test.ts +21 -0
  119. package/src/composite.ts +20 -1
  120. package/src/config.ts +81 -0
  121. package/src/discovery/collect.ts +17 -3
  122. package/src/discovery/entity-wire-codec.ts +485 -0
  123. package/src/discovery/entity-wire.test.ts +240 -0
  124. package/src/discovery/entity-wire.ts +67 -0
  125. package/src/discovery/fold-import.test.ts +1026 -0
  126. package/src/discovery/fold-import.ts +1805 -0
  127. package/src/discovery/index.test.ts +191 -1
  128. package/src/discovery/index.ts +233 -1
  129. package/src/discovery/sandbox/bundle.ts +218 -0
  130. package/src/discovery/sandbox/child-errors.ts +65 -0
  131. package/src/discovery/sandbox/driver.ts +147 -0
  132. package/src/discovery/sandbox/run.test.ts +179 -0
  133. package/src/discovery/sandbox/run.ts +196 -0
  134. package/src/errors.ts +9 -1
  135. package/src/fold/fold.test.ts +535 -0
  136. package/src/fold/fold.ts +648 -0
  137. package/src/fold/subset-doc-parity.test.ts +183 -0
  138. package/src/fold/subset.test.ts +241 -0
  139. package/src/fold/subset.ts +302 -0
  140. package/src/index.ts +2 -0
  141. package/src/lexicon-output.ts +7 -2
  142. package/src/lexicon-schema.test.ts +14 -0
  143. package/src/lexicon-schema.ts +3 -1
  144. package/src/lexicon.ts +82 -1
  145. package/src/lifecycle/git.test.ts +10 -5
  146. package/src/lifecycle/release-ledger.test.ts +28 -0
  147. package/src/lifecycle/release-ledger.ts +11 -0
  148. package/src/lint/component-checks.ts +8 -1
  149. package/src/lint/rules/evl001-non-literal-expression.ts +26 -110
  150. package/src/lint/rules/evl003-dynamic-property-access.ts +11 -2
  151. package/src/params.test.ts +22 -0
  152. package/src/params.ts +66 -0
  153. package/src/provenance.ts +22 -0
  154. package/src/terraform/adopt-state.test.ts +1 -1
  155. package/src/terraform/aws-resources.test.ts +11 -4
  156. package/src/terraform/aws-resources.ts +60 -0
@@ -0,0 +1,196 @@
1
+ import { fork } from "node:child_process";
2
+ import { realpathSync, rmSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import type { Declarable } from "../../declarable";
5
+ import { DiscoveryError, type DiscoveryErrorType } from "../../errors";
6
+ import { decodeEntitySet, type EntitySetWire } from "../entity-wire-codec";
7
+ import { bundleDriver } from "./bundle";
8
+ import { classifyChildError } from "./child-errors";
9
+ import { generateDriverSource } from "./driver";
10
+
11
+ /**
12
+ * chant #1045 Phase 2 — runs every run-fallback file for a build TOGETHER, as
13
+ * one bundled module graph, inside one sandboxed child process, and returns
14
+ * the same shape `discover()`'s own in-process run path would have produced:
15
+ * a named, ref-resolved entities map plus any errors.
16
+ *
17
+ * Isolation mechanics (verified on Node v24.13.1 — see the chant#1045 PR
18
+ * description for the full write-up):
19
+ * - `--permission --allow-fs-read=<bundle dir>,<project dir>[,<trusted
20
+ * external package dirs>]` — no filesystem write, no child-process, no
21
+ * worker-thread access. Bundling with esbuild first (not a packaging
22
+ * change — see `./bundle.ts`) means the child needs NO TypeScript loader
23
+ * (no `tsx`, so no `--allow-worker` and no writable temp dir either),
24
+ * unlike the plain `tsx`-based run path. The "trusted external package
25
+ * dirs" allowance is narrow and specific: `./bundle.ts` deliberately
26
+ * leaves a couple of chant/lexicon-internal dependencies (`typescript`)
27
+ * unbundled and resolves them to their real, fixed location instead —
28
+ * project source never controls what's installed there.
29
+ * - The env is a spawn-time scrub (`env: {}` below, plus `PATH` — see the
30
+ * option below), not `--permission`: Node's Permission Model does not gate
31
+ * `process.env` at all (confirmed: every key stays readable even under
32
+ * `--permission`).
33
+ * - Network egress is NOT addressed here — Node has no flag for it. See the
34
+ * chant#1045 PR description / docs for the residual-risk statement and
35
+ * deployment guidance (a container with no egress, a network namespace).
36
+ * This function does not claim to close that gap.
37
+ *
38
+ * What does NOT run inside the child: fold (`tryFoldFile`, `../fold-import`)
39
+ * stays exactly where it is today, in the parent, unsandboxed — fold already
40
+ * executes zero of the file's own top-level code (chant #1022/#1023), so
41
+ * isolating it buys nothing and would only cost a bundle+spawn per build.
42
+ * Only genuine run-fallback files are handed to this function.
43
+ */
44
+
45
+ export interface SandboxRunResult {
46
+ /** Named, ref-resolved entities from the run-fallback set — decoded from the child's `EntitySetWire` response, functionally indistinguishable from what `importModule` + `collectEntities` + `resolveAttrRefs` would have produced in-process for this same file set (see `../entity-wire.ts`'s `decodeEntitySet` doc). */
47
+ entities: Map<string, Declarable>;
48
+ /** Import/collection/resolution/permission errors, already chant-shaped (see `./child-errors.ts` — a permission denial names the file and the operation, never a raw `ERR_ACCESS_DENIED`). */
49
+ errors: DiscoveryError[];
50
+ /** esbuild bundling wall-clock time — chant#1045 asks this be measured, not silently accepted. */
51
+ bundleMs: number;
52
+ /** Bundle size in bytes. */
53
+ bundleBytes: number;
54
+ /**
55
+ * Entity name → declaring file, for every entity in {@link entities} whose
56
+ * provenance (`../../provenance.ts`) named one. The wire format itself
57
+ * doesn't carry this (`encodeEntitySet` intentionally drops build metadata,
58
+ * not declared configuration) — it rides back as a small side channel so a
59
+ * parent-side merge collision against the fold-only set (a bare name
60
+ * genuinely exported by both a folded and a run-fallback file in the same
61
+ * directory) can name the real file, not the entity name. See `../index.ts`.
62
+ */
63
+ provenanceByName: Record<string, string>;
64
+ }
65
+
66
+ interface ChildResponse {
67
+ entitySet: EntitySetWire;
68
+ errors: Array<{ name: string; file: string; message: string; type: DiscoveryErrorType }>;
69
+ provenanceByName?: Record<string, string>;
70
+ fatal?: boolean;
71
+ }
72
+
73
+ /** How long to wait for the sandboxed child to report back before treating it as hung and killing it. Generous: bundling+import+collect for a whole build's run-fallback set should be a small multiple of what the equivalent in-process run takes. */
74
+ const CHILD_TIMEOUT_MS = 120_000;
75
+
76
+ function isChildResponse(value: unknown): value is ChildResponse {
77
+ return typeof value === "object" && value !== null && "entitySet" in value && "errors" in value;
78
+ }
79
+
80
+ /**
81
+ * Run `files` (already decided "run" by `discover()`'s fold/taint pass)
82
+ * together, isolated, in one sandboxed child process. Returns the same
83
+ * `{ entities, errors }` shape the parent's own `collectEntities` +
84
+ * `resolveAttrRefs` would have — see `../index.ts`'s `discover()`, which
85
+ * merges this result into the entities it collected from folded files.
86
+ *
87
+ * @param files - Absolute paths to run-fallback files.
88
+ * @param buildRoot - The directory `discover()` was pointed at — threaded to
89
+ * `collectEntities` inside the child (stack-prefix disambiguation, #932)
90
+ * and used to compute the project-directory read allowance.
91
+ */
92
+ export async function runFallbackFilesSandboxed(
93
+ files: readonly string[],
94
+ buildRoot: string,
95
+ ): Promise<SandboxRunResult> {
96
+ if (files.length === 0) {
97
+ return { entities: new Map(), errors: [], bundleMs: 0, bundleBytes: 0, provenanceByName: {} };
98
+ }
99
+
100
+ const driverSource = generateDriverSource({ files, buildRoot });
101
+ const { bundlePath, bundleDir, externalReadPaths, durationMs, bytes } = await bundleDriver(driverSource);
102
+
103
+ try {
104
+ let projectRealpath: string;
105
+ try {
106
+ projectRealpath = realpathSync(resolve(buildRoot));
107
+ } catch {
108
+ projectRealpath = resolve(buildRoot);
109
+ }
110
+
111
+ const response = await runChildProcess(bundlePath, bundleDir, projectRealpath, externalReadPaths);
112
+
113
+ const errors = (response.errors ?? []).map(
114
+ (e) => new DiscoveryError(e.file, e.message, e.type),
115
+ );
116
+ const provenanceByName = response.provenanceByName ?? {};
117
+ if (response.fatal) {
118
+ return { entities: new Map(), errors, bundleMs: durationMs, bundleBytes: bytes, provenanceByName };
119
+ }
120
+ const entities = decodeEntitySet(response.entitySet ?? { entities: [] });
121
+ return { entities, errors, bundleMs: durationMs, bundleBytes: bytes, provenanceByName };
122
+ } catch (err) {
123
+ return {
124
+ entities: new Map(),
125
+ errors: [classifyChildError("", err, "import")],
126
+ provenanceByName: {},
127
+ bundleMs: durationMs,
128
+ bundleBytes: bytes,
129
+ };
130
+ } finally {
131
+ rmSync(bundleDir, { recursive: true, force: true });
132
+ }
133
+ }
134
+
135
+ /** Fork the bundle under `--permission`, with a scrubbed environment, and resolve with its one IPC message (or reject on crash/timeout/fork error). */
136
+ function runChildProcess(
137
+ bundlePath: string,
138
+ bundleDir: string,
139
+ projectRealpath: string,
140
+ externalReadPaths: readonly string[],
141
+ ): Promise<ChildResponse> {
142
+ return new Promise((resolvePromise, reject) => {
143
+ const readAllowances = [bundleDir, projectRealpath, ...externalReadPaths].map(
144
+ (p) => `--allow-fs-read=${p}`,
145
+ );
146
+ const child = fork(bundlePath, [], {
147
+ execArgv: ["--permission", ...readAllowances],
148
+ // chant #1045 Phase 2 — Node's Permission Model does not gate
149
+ // `process.env`; scrubbing it here is the only way to keep the ambient
150
+ // environment out of untrusted project source's reach. `PATH` is kept
151
+ // only because some platforms' module resolution/dynamic linking
152
+ // consults it; it carries no project secrets.
153
+ env: { PATH: process.env.PATH ?? "" },
154
+ stdio: ["ignore", "pipe", "pipe", "ipc"],
155
+ });
156
+
157
+ let settled = false;
158
+ let stderrBuf = "";
159
+
160
+ const timeout = setTimeout(() => {
161
+ if (settled) return;
162
+ settled = true;
163
+ child.kill();
164
+ reject(new Error(`sandboxed run timed out after ${CHILD_TIMEOUT_MS}ms`));
165
+ }, CHILD_TIMEOUT_MS);
166
+
167
+ child.stderr?.on("data", (chunk: Buffer) => {
168
+ stderrBuf += chunk.toString();
169
+ });
170
+
171
+ child.on("message", (msg: unknown) => {
172
+ if (settled || !isChildResponse(msg)) return;
173
+ settled = true;
174
+ clearTimeout(timeout);
175
+ resolvePromise(msg);
176
+ });
177
+
178
+ child.on("error", (err) => {
179
+ if (settled) return;
180
+ settled = true;
181
+ clearTimeout(timeout);
182
+ reject(err);
183
+ });
184
+
185
+ child.on("exit", (code, signal) => {
186
+ if (settled) return;
187
+ settled = true;
188
+ clearTimeout(timeout);
189
+ reject(
190
+ new Error(
191
+ `sandboxed child exited before reporting results (code ${code}, signal ${signal})${stderrBuf.trim() ? `: ${stderrBuf.trim()}` : ""}`,
192
+ ),
193
+ );
194
+ });
195
+ });
196
+ }
package/src/errors.ts CHANGED
@@ -2,7 +2,15 @@
2
2
  * Error types for chant discovery, build, and lint failures
3
3
  */
4
4
 
5
- export type DiscoveryErrorType = "import" | "resolution" | "circular";
5
+ /**
6
+ * `"permission"` (chant #1045 Phase 2) — a run-fallback file executing inside
7
+ * the sandboxed child (`./discovery/sandbox/`) hit Node's Permission Model
8
+ * (`ERR_ACCESS_DENIED`) — filesystem, child-process, or worker-thread access
9
+ * outside the sandbox's allowlist. Distinct from `"import"` (a module simply
10
+ * failed to load/execute for an ordinary reason) so a permission denial is
11
+ * recognizable as a sandbox boundary hit, not a generic import failure.
12
+ */
13
+ export type DiscoveryErrorType = "import" | "resolution" | "circular" | "permission";
6
14
 
7
15
  /**
8
16
  * Error during file discovery or module import