carveout 0.1.1 → 0.1.2

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 (78) hide show
  1. package/AGENTS.md +21 -3
  2. package/NOTICE.md +2 -1
  3. package/README.md +248 -473
  4. package/dist/src/account.d.ts +37 -0
  5. package/dist/src/account.js +275 -0
  6. package/dist/src/api.d.ts +148 -0
  7. package/dist/src/api.js +120 -0
  8. package/dist/src/carve.d.ts +788 -0
  9. package/dist/src/carve.js +1721 -125
  10. package/dist/src/cli.d.ts +10 -0
  11. package/dist/src/cli.js +674 -47
  12. package/dist/src/command.d.ts +10 -0
  13. package/dist/src/core.d.ts +11 -0
  14. package/dist/src/cut.d.ts +78 -0
  15. package/dist/src/cut.js +25 -7
  16. package/dist/src/extract/compiler-config.d.ts +49 -0
  17. package/dist/src/extract/fixture.d.ts +103 -0
  18. package/dist/src/extract/hash.d.ts +6 -0
  19. package/dist/src/extract/package-registry.d.ts +47 -0
  20. package/dist/src/extract/package-types.d.ts +52 -0
  21. package/dist/src/extract/surface.d.ts +44 -0
  22. package/dist/src/extract/typescript.d.ts +180 -0
  23. package/dist/src/gate.d.ts +55 -0
  24. package/dist/src/gate.js +1 -0
  25. package/dist/src/graph.d.ts +142 -0
  26. package/dist/src/graph.js +4 -3
  27. package/dist/src/impact.d.ts +113 -0
  28. package/dist/src/imports.d.ts +67 -0
  29. package/dist/src/mergeback.d.ts +182 -0
  30. package/dist/src/mergeback.js +580 -35
  31. package/dist/src/platform.d.ts +69 -0
  32. package/dist/src/platform.js +184 -0
  33. package/dist/src/policy.d.ts +181 -0
  34. package/dist/src/policy.js +716 -230
  35. package/dist/src/project/build.d.ts +23 -0
  36. package/dist/src/project/catalog.d.ts +25 -0
  37. package/dist/src/project/project.d.ts +287 -0
  38. package/dist/src/project/project.js +479 -52
  39. package/dist/src/project/provenance.d.ts +73 -0
  40. package/dist/src/project/provenance.js +12 -0
  41. package/dist/src/project/runner.d.ts +19 -0
  42. package/dist/src/project/secrets.d.ts +64 -0
  43. package/dist/src/project/vitest-config.d.ts +70 -0
  44. package/dist/src/project/vitest-config.js +291 -0
  45. package/dist/src/pseudonyms/aliases.d.ts +107 -0
  46. package/dist/src/pseudonyms/aliases.js +394 -0
  47. package/dist/src/pseudonyms/draft.d.ts +64 -0
  48. package/dist/src/pseudonyms/draft.js +284 -0
  49. package/dist/src/pseudonyms/folder.d.ts +172 -0
  50. package/dist/src/pseudonyms/folder.js +527 -0
  51. package/dist/src/pseudonyms/index.d.ts +11 -0
  52. package/dist/src/pseudonyms/index.js +11 -0
  53. package/dist/src/pseudonyms/vocabulary.d.ts +113 -0
  54. package/dist/src/pseudonyms/vocabulary.js +522 -0
  55. package/dist/src/readiness.d.ts +71 -0
  56. package/dist/src/readiness.js +131 -0
  57. package/dist/src/receipt.d.ts +154 -0
  58. package/dist/src/receipt.js +296 -0
  59. package/dist/src/sandbox.d.ts +53 -0
  60. package/dist/src/sandbox.js +154 -0
  61. package/dist/src/slice/analysis.d.ts +75 -0
  62. package/dist/src/slice/externals.d.ts +51 -0
  63. package/dist/src/slice/graph.d.ts +69 -0
  64. package/dist/src/slice/manifest.d.ts +262 -0
  65. package/dist/src/slice/manifest.js +4 -3
  66. package/dist/src/slice/reach.d.ts +204 -0
  67. package/dist/src/slice/reach.js +11 -5
  68. package/dist/src/slice/report.d.ts +49 -0
  69. package/dist/src/slice/report.js +5 -2
  70. package/dist/src/specifiers.d.ts +48 -0
  71. package/dist/src/status.d.ts +46 -0
  72. package/dist/src/status.js +3 -2
  73. package/dist/src/test-integrity.d.ts +54 -0
  74. package/dist/src/test-integrity.js +812 -0
  75. package/package.json +24 -5
  76. package/skills/carveout/SKILL.md +517 -0
  77. package/skills/carveout-gate/SKILL.md +98 -0
  78. package/SKILL.md +0 -423
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Running code that came back from outside in a container.
3
+ *
4
+ * A folder that came back holds the contributor's code, and its tests run
5
+ * it. On the maintainer's machine that code would run as the maintainer,
6
+ * with their files (the closed repository, the receipts that map the
7
+ * folder's pseudonyms back to the repository's names, credentials) and
8
+ * their network. Here each step is its own `docker run --rm` that mounts one
9
+ * directory, the scratch copy, and nothing else: no HOME, no receipts, no
10
+ * checkout beyond the copy. Only the install step has a network, and it runs
11
+ * a package manager with lifecycle scripts off on the receipt's manifests,
12
+ * so none of the folder's code runs while the network is there. Every other
13
+ * step (the build, the tests, the owner's acceptance tests, a merge's
14
+ * verify command) runs with `--network none`, all capabilities dropped, a
15
+ * read-only root, and bounds on memory and processes.
16
+ *
17
+ * Docker by default; `CARVEOUT_CONTAINER` names another runtime that takes
18
+ * the same arguments (podman), and `CARVEOUT_SANDBOX_IMAGE` the image (a
19
+ * Node image at this process's major version unless given).
20
+ */
21
+ import { execFileSync, spawn } from "node:child_process";
22
+ import { randomBytes } from "node:crypto";
23
+ import { relative, resolve, sep } from "node:path";
24
+ /** The runtime and image from the environment, or Docker and a Node image at this process's major version. */
25
+ export function defaultSandbox(image) {
26
+ return {
27
+ runtime: process.env["CARVEOUT_CONTAINER"] ?? "docker",
28
+ image: image ?? process.env["CARVEOUT_SANDBOX_IMAGE"] ?? `node:${process.versions.node.split(".")[0] ?? "22"}`,
29
+ };
30
+ }
31
+ /**
32
+ * Why the sandbox cannot run, or null when it can: the runtime answers, its
33
+ * daemon runs Linux containers, and the image is there or can be pulled.
34
+ */
35
+ export function sandboxUnavailable(spec) {
36
+ try {
37
+ const os = execFileSync(spec.runtime, ["info", "--format", "{{.OSType}}"], {
38
+ stdio: ["ignore", "pipe", "pipe"],
39
+ timeout: 20_000,
40
+ })
41
+ .toString("utf8")
42
+ .trim();
43
+ if (os !== "" && os !== "linux")
44
+ return `${spec.runtime} runs ${os} containers; the sandbox needs Linux ones.`;
45
+ }
46
+ catch (error) {
47
+ return `${spec.runtime} does not answer (${firstLine(error)}): start it, or set CARVEOUT_CONTAINER to a runtime that takes Docker's arguments.`;
48
+ }
49
+ try {
50
+ execFileSync(spec.runtime, ["image", "inspect", spec.image], { stdio: "ignore", timeout: 20_000 });
51
+ }
52
+ catch {
53
+ try {
54
+ execFileSync(spec.runtime, ["pull", "--quiet", spec.image], { stdio: "ignore", timeout: 10 * 60_000 });
55
+ }
56
+ catch (error) {
57
+ return `${spec.runtime} has no image ${spec.image} and could not pull it (${firstLine(error)}): pull it, or name another with --sandbox-image or CARVEOUT_SANDBOX_IMAGE.`;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+ const firstLine = (error) => (error instanceof Error ? (error.stderr?.toString("utf8") || error.message) : String(error)).trim().split("\n")[0] ?? "";
63
+ /** Where a directory under the mounted one is inside the container. */
64
+ export const MOUNT = "/work";
65
+ /**
66
+ * Runs one command in a fresh container that mounts `root` at `/work` and
67
+ * nothing else, in `cwd` (a directory under `root`). Host paths under
68
+ * `root` in the arguments are rewritten to the container's, and this Node's
69
+ * own path to `node`. Only the names in `env` are passed, with HOME and the
70
+ * npm cache in the container's own /tmp. With `network` false the container
71
+ * has none. At `limitMs` the container is killed.
72
+ */
73
+ export function sandboxRun(spec, root, command, options) {
74
+ const base = resolve(root);
75
+ const inside = (path) => {
76
+ const at = relative(base, resolve(path));
77
+ if (at === "")
78
+ return MOUNT;
79
+ if (at.startsWith("..") || at.startsWith(sep))
80
+ throw new Error(`${path} is outside the sandbox's folder ${base}.`);
81
+ return `${MOUNT}/${at.split(sep).join("/")}`;
82
+ };
83
+ const mapArgument = (value) => {
84
+ if (value === process.execPath)
85
+ return "node";
86
+ return value.split(base).join(MOUNT);
87
+ };
88
+ const [executable = "true", ...args] = command;
89
+ const name = `carveout-${randomBytes(6).toString("hex")}`;
90
+ const env = {};
91
+ for (const [key, value] of Object.entries(options.env ?? {}))
92
+ if (key !== "PATH" && key !== "HOME" && key !== "SystemRoot" && !key.startsWith("npm_config_"))
93
+ env[key] = value;
94
+ const runArgs = [
95
+ "run",
96
+ "--rm",
97
+ "--name",
98
+ name,
99
+ "--init",
100
+ ...(options.network ? [] : ["--network", "none"]),
101
+ ...(typeof process.getuid === "function" && typeof process.getgid === "function" ? ["--user", `${process.getuid()}:${process.getgid()}`] : []),
102
+ "--cap-drop",
103
+ "ALL",
104
+ "--security-opt",
105
+ "no-new-privileges",
106
+ "--read-only",
107
+ "--tmpfs",
108
+ "/tmp:rw,exec,size=2g",
109
+ "--memory",
110
+ process.env["CARVEOUT_SANDBOX_MEMORY"] ?? "4g",
111
+ "--pids-limit",
112
+ "2048",
113
+ "--volume",
114
+ `${base}:${MOUNT}`,
115
+ "--workdir",
116
+ inside(options.cwd ?? base),
117
+ "--env",
118
+ "HOME=/tmp",
119
+ "--env",
120
+ "npm_config_cache=/tmp/.npm",
121
+ "--env",
122
+ "npm_config_update_notifier=false",
123
+ "--env",
124
+ "CI=1",
125
+ ...Object.entries(env).flatMap(([key, value]) => ["--env", `${key}=${mapArgument(value)}`]),
126
+ spec.image,
127
+ mapArgument(executable),
128
+ ...args.map(mapArgument),
129
+ ];
130
+ return new Promise((resolvePromise) => {
131
+ const child = spawn(spec.runtime, runArgs, { stdio: ["ignore", "pipe", "pipe"] });
132
+ let stdout = "";
133
+ let stderr = "";
134
+ child.stdout.setEncoding("utf8").on("data", (chunk) => (stdout += chunk));
135
+ child.stderr.setEncoding("utf8").on("data", (chunk) => (stderr += chunk));
136
+ const timer = setTimeout(() => {
137
+ try {
138
+ execFileSync(spec.runtime, ["kill", name], { stdio: "ignore", timeout: 30_000 });
139
+ }
140
+ catch {
141
+ // Already gone.
142
+ }
143
+ child.kill("SIGKILL");
144
+ }, options.limitMs ?? 10 * 60_000);
145
+ child.on("error", (error) => {
146
+ clearTimeout(timer);
147
+ resolvePromise({ stdout, stderr: `${stderr}${error.message}`, status: null });
148
+ });
149
+ child.on("close", (code) => {
150
+ clearTimeout(timer);
151
+ resolvePromise({ stdout, stderr, status: code });
152
+ });
153
+ });
154
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The request a cut is made from, and the JSON form every record is
3
+ * written in, so two cuts of the same bytes give the same files.
4
+ */
5
+ export declare const SLICE_TOOL_VERSION = "carveout-slice@2";
6
+ export interface SliceBudget {
7
+ /** Files inside the slice; the first file past it on any path is a cut. */
8
+ readonly maxFiles: number;
9
+ /** Edges from the nearest entry point; a file further away is a cut. */
10
+ readonly maxDepth: number;
11
+ }
12
+ export declare const SLICE_BUDGET_DEFAULTS: SliceBudget;
13
+ export interface SliceParams {
14
+ /** Repository-relative file or directory paths, sorted and unique. */
15
+ readonly entryPoints: readonly string[];
16
+ readonly budget: SliceBudget;
17
+ /** Also bring along the tests outside the slice that import it. */
18
+ readonly tests: boolean;
19
+ /**
20
+ * The setup files the package's Vitest tests run before them (its
21
+ * config's `setupFiles`), walked from as the entry points are but no
22
+ * entry point: an edit to one is held as a test-runner setting's is.
23
+ */
24
+ readonly setupFiles?: readonly string[];
25
+ /**
26
+ * The runner the package's own tests run on, when it is Vitest: the
27
+ * folder installs it, pinned as the package pins it, for the tests the
28
+ * contributor writes, whether or not a test of the slice came along.
29
+ */
30
+ readonly testRunner?: "vitest";
31
+ /**
32
+ * Service packages kept real rather than mocked: installed and typed as
33
+ * the repository has them, with the environment their importers read
34
+ * left to the shell. Absent when none.
35
+ */
36
+ readonly live?: readonly string[];
37
+ /**
38
+ * Include every file of the entry points' package the walk reaches, at
39
+ * any depth: the cut falls at package and service boundaries only, so a
40
+ * sibling module is copied rather than stubbed. Absent when off.
41
+ */
42
+ readonly wholePackage?: boolean;
43
+ /**
44
+ * Follow an import of one of the repository's own packages into its
45
+ * source, as an import of a file is followed, rather than stubbing the
46
+ * package at the seam. Absent when off.
47
+ */
48
+ readonly followWorkspaces?: boolean;
49
+ /**
50
+ * Copy pure code the slice runs (validators, error classes, formatters)
51
+ * rather than mock it, past the depth; see `ReachOptions.pure`. Absent
52
+ * when off.
53
+ */
54
+ readonly copyPure?: boolean;
55
+ /**
56
+ * Files never copied, as paths or globs from the repository's root:
57
+ * wherever the walk reaches one it is cut and stubbed, with its literal
58
+ * values widened. Absent when none.
59
+ */
60
+ readonly private?: readonly string[];
61
+ /**
62
+ * Folders (or files) copied in whole beside the slice, as paths from the
63
+ * repository's root: what the code reads by a name the walk cannot
64
+ * follow, such as a migrations folder. A path read into one is no
65
+ * blocker. Absent when none.
66
+ */
67
+ readonly copy?: readonly string[];
68
+ }
69
+ /**
70
+ * Whether a repository path matches one of `patterns`: the path itself,
71
+ * anything under it as a folder, or a glob (`*` within one segment, `**`
72
+ * across segments). Pure, so a slice's record can say what was kept out.
73
+ */
74
+ export declare function pathMatcher(patterns: readonly string[]): (path: string) => boolean;
75
+ export declare function canonicalJson(value: unknown): string;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * What a slice reaches outside the repository: the service SDKs and HTTP
3
+ * clients its files import, and the environment variables they read. Listed
4
+ * by name so the sandbox phase knows what to mock; values are never read.
5
+ *
6
+ * A fixed table over import specifiers plus a regex pass over the text, so
7
+ * the result is the same for the same bytes. The regexes are deliberately
8
+ * simple: a false positive costs one spurious line in a list a person reads;
9
+ * a parser per language would cost a dependency per language.
10
+ */
11
+ export interface ExternalServiceRule {
12
+ /** A neutral name for the service family: `aws`, `postgres`, `http`. */
13
+ readonly service: string;
14
+ /** Exact specifiers, or a scope prefix ending in `/*`. */
15
+ readonly specifiers: readonly string[];
16
+ }
17
+ export declare const EXTERNAL_SERVICE_RULES: readonly ExternalServiceRule[];
18
+ export interface ExternalPackage {
19
+ readonly specifier: string;
20
+ readonly service: string;
21
+ /** Included files importing it, sorted. */
22
+ readonly files: readonly string[];
23
+ }
24
+ export interface EnvironmentRead {
25
+ readonly name: string;
26
+ readonly files: readonly string[];
27
+ }
28
+ export interface SliceExternals {
29
+ readonly packages: readonly ExternalPackage[];
30
+ readonly environment: readonly EnvironmentRead[];
31
+ }
32
+ export interface ExternalsInput {
33
+ readonly path: string;
34
+ readonly text: string;
35
+ }
36
+ /** The service a specifier belongs to, or null when the table has none. */
37
+ export declare function serviceOf(specifier: string): string | null;
38
+ export declare function detectExternals(files: readonly ExternalsInput[]): SliceExternals;
39
+ /** Node's modules pure code may import: no disk, network or process. */
40
+ export declare const PURE_NODE_MODULES: ReadonlySet<string>;
41
+ /**
42
+ * What makes a module pure for `ReachOptions.pure`: it imports no service
43
+ * package and none of Node's modules that reach the disk, the network or
44
+ * the process, and its own text reads no environment and calls no
45
+ * network. Read from the text, so a false "impure" costs only a mock, as
46
+ * before; `read` gives a file's text, or null when it cannot be read.
47
+ */
48
+ export declare function purityRules(read: (file: string) => string | null, isBuiltin: (specifier: string) => boolean): {
49
+ readonly package: (specifier: string) => boolean;
50
+ readonly sideEffects: (file: string) => boolean;
51
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The import graph of a checkout, as the slice walk reads it.
3
+ *
4
+ * Node ids: `file:<path>` for a file of the checkout, and
5
+ * `dependency:<path>:<specifier>` for an import from `<path>` that resolves
6
+ * to no file of it (a package, a path that misses, a computed name). A
7
+ * node's `sourceFile` is the repository path it was read from.
8
+ *
9
+ * Each link is one import, re-export or reference as the source wrote it,
10
+ * with the names it takes, so the walk can follow a barrel only to the
11
+ * modules that supply those names, and pass over an import of types.
12
+ */
13
+ export type SliceNodeKind = "file" | "dependency";
14
+ /** The names a file declares and exports itself, read from its syntax. */
15
+ export interface FileExports {
16
+ /** Names that exist at runtime, `default` among them. */
17
+ readonly values: readonly string[];
18
+ /** Interfaces and type aliases, which the compiler erases. */
19
+ readonly types: readonly string[];
20
+ }
21
+ export interface SliceGraphNode {
22
+ readonly id: string;
23
+ readonly kind: SliceNodeKind;
24
+ readonly label: string;
25
+ /** Repository-relative path, `/`-separated. */
26
+ readonly sourceFile: string;
27
+ /** Only on `dependency` nodes: a package, an import nothing resolved, or
28
+ * a literal path read at runtime that is no file a slice can copy. */
29
+ readonly dependencyStatus: "external" | "unresolved" | "path" | null;
30
+ /** On a script's `file` node: its own exports; null when not known
31
+ * (`export =`, or a file that is not a script). */
32
+ readonly exports: FileExports | null;
33
+ }
34
+ /**
35
+ * - `imports`: an import, re-export, `require` or `import()` of a literal.
36
+ * - `references`: a module named to a test runner or a resolver
37
+ * (`vi.mock`, `jest.requireActual`, `require.resolve`), which needs the
38
+ * module to exist but never loads its code into the slice.
39
+ * - `reads`: a file read by a literal path at runtime
40
+ * (`new URL("./x", import.meta.url)`, `join(__dirname, "x")`), which needs
41
+ * the file itself at that path.
42
+ */
43
+ export type SliceRelation = "imports" | "references" | "reads";
44
+ export interface SliceGraphLink {
45
+ readonly source: string;
46
+ readonly target: string;
47
+ readonly relation: SliceRelation;
48
+ /** The text as written, for an import or a reference; null otherwise. */
49
+ readonly specifier: string | null;
50
+ /** `L<n>`, the line it is on. */
51
+ readonly location: string | null;
52
+ /** `import type`, `export type`, `import("x").T`: erased at runtime. */
53
+ readonly typeOnly: boolean;
54
+ /** The names it takes, or null for the whole module. */
55
+ readonly names: readonly string[] | null;
56
+ /**
57
+ * For `export ... from`, what it forwards as [name in the module, name
58
+ * exported]: `*` for `export *`, `["*", "ns"]` for `export * as ns`.
59
+ * Null for anything else.
60
+ */
61
+ readonly forwards: readonly (readonly [string, string])[] | "*" | null;
62
+ }
63
+ export interface SliceGraph {
64
+ readonly nodes: readonly SliceGraphNode[];
65
+ readonly links: readonly SliceGraphLink[];
66
+ }
67
+ export declare function nodeKind(id: string): SliceNodeKind | null;
68
+ /** The file a node belongs to; a dependency placeholder belongs to none. */
69
+ export declare function fileOf(node: SliceGraphNode): string | null;
@@ -0,0 +1,262 @@
1
+ /**
2
+ * What a slice run writes, and the rules that read it.
3
+ *
4
+ * `slice-manifest.json` is the immutable record of a proposed slice: the
5
+ * source it was cut from, the copied files pinned by hash, the generated
6
+ * files kept apart from them, the cut edges, the externals and whatever
7
+ * blocks the slice from becoming a sandbox. `boundary-contract.json` is the
8
+ * machine-readable shape of the boundary: the symbols the slice imports from
9
+ * outside (stubs) and the symbols outside code imports from it (its public
10
+ * surface), with their declarations. The markdown views are rendered from
11
+ * the contract and never carry anything it does not.
12
+ */
13
+ import type { SliceBudget, SliceParams } from "./analysis.js";
14
+ import type { SliceExternals } from "./externals.js";
15
+ import type { CutEdge } from "./reach.js";
16
+ export declare const SLICE_MANIFEST_SCHEMA_VERSION = 1;
17
+ export declare const BOUNDARY_CONTRACT_SCHEMA_VERSION = 1;
18
+ /**
19
+ * How much of the boundary carries declarations. `full` means every cut
20
+ * and public symbol has one and the fixture compiled; it says nothing about
21
+ * runtime readiness. `partial` and `names-only` are diagnostic output.
22
+ */
23
+ export type StubCoverage = "full" | "partial" | "names-only";
24
+ export declare const SLICE_BLOCKER_CODES: readonly ["unknown_entry_point", "unresolved_import", "dynamic_dependency", "path_read", "missing_build_input", "no_extractor", "declaration_incomplete", "compile_error", "secret_file"];
25
+ export type SliceBlockerCode = (typeof SLICE_BLOCKER_CODES)[number];
26
+ /**
27
+ * The blockers a contributor meets in the code itself: an import that
28
+ * resolves to no file, a dynamic import of a computed name, a path read at
29
+ * runtime the folder will not have. Every other
30
+ * code is the slicer's own build falling short (a compile error in its
31
+ * fixture, a configuration it could not read, a language it cannot
32
+ * extract), which says how well the code was measured, not how hard the
33
+ * work is, and one cause can raise dozens of them.
34
+ */
35
+ export declare const WORK_BLOCKER_CODES: readonly SliceBlockerCode[];
36
+ export declare const isWorkBlocker: (blocker: Pick<SliceBlocker, "code">) => boolean;
37
+ export interface SliceBlocker {
38
+ readonly code: SliceBlockerCode;
39
+ readonly file: string | null;
40
+ readonly location: string | null;
41
+ readonly detail: string;
42
+ }
43
+ export interface BoundarySymbol {
44
+ readonly name: string;
45
+ /** `function`, `class`, `interface`, `type`, `enum`, `variable`, `module`, `unknown`. */
46
+ readonly kind: string;
47
+ /** Declaration text, `.d.ts` style; null when the language has no extractor. */
48
+ readonly declaration: string | null;
49
+ }
50
+ export interface BoundaryModule {
51
+ /** Repository path of the module. */
52
+ readonly module: string;
53
+ readonly symbols: readonly BoundarySymbol[];
54
+ /** Files on the other side of the cut that import it, sorted. */
55
+ readonly importedBy: readonly string[];
56
+ /** For an outbound module: the generated stub's artifact path. */
57
+ readonly stubPath: string | null;
58
+ }
59
+ export interface CompilationResult {
60
+ readonly attempted: boolean;
61
+ readonly ok: boolean;
62
+ readonly diagnostics: readonly {
63
+ readonly file: string | null;
64
+ readonly line: number | null;
65
+ readonly code: string;
66
+ readonly message: string;
67
+ }[];
68
+ /** Package specifiers declared `any` so the fixture could type-check. */
69
+ readonly shimmedPackages: readonly string[];
70
+ /**
71
+ * Errors the source has too, checked the same way with its own code in
72
+ * place of the stubs: what declaring its packages untyped costs, not
73
+ * counted against the slice. Absent when nothing failed.
74
+ */
75
+ readonly untypedPackageErrors?: CompilationResult["diagnostics"];
76
+ /**
77
+ * Packages whose published types the check read, at the versions read,
78
+ * rather than declaring them untyped. Absent when none were fetched.
79
+ */
80
+ readonly typedPackages?: readonly {
81
+ readonly name: string;
82
+ readonly version: string;
83
+ }[];
84
+ }
85
+ export interface BoundaryContract {
86
+ readonly schemaVersion: typeof BOUNDARY_CONTRACT_SCHEMA_VERSION;
87
+ readonly toolVersion: string;
88
+ readonly extractorVersion: string | null;
89
+ /** The commit the source was read at; null outside Git. */
90
+ readonly sourceCommitSha: string | null;
91
+ readonly language: "typescript" | "none";
92
+ /** Slice → outside: what the slice needs, as stubs. */
93
+ readonly outbound: readonly BoundaryModule[];
94
+ /** Outside → slice: what the slice must keep offering. */
95
+ readonly inbound: readonly BoundaryModule[];
96
+ readonly stubCoverage: StubCoverage;
97
+ readonly compilation: CompilationResult;
98
+ readonly blockers: readonly SliceBlocker[];
99
+ }
100
+ export interface IncludedFile {
101
+ readonly path: string;
102
+ /** Git's blob id of the bytes read, so the sandbox can verify its copy. */
103
+ readonly blobId: string;
104
+ readonly sha256: string;
105
+ /** `100644` or `100755`, as Git records it. */
106
+ readonly mode: string;
107
+ readonly sizeBytes: number;
108
+ readonly operations: readonly "edit"[];
109
+ }
110
+ export interface SyntheticFile {
111
+ readonly path: string;
112
+ readonly kind: "stub" | "shim" | "config";
113
+ readonly generator: string;
114
+ readonly generatorVersion: string;
115
+ readonly sha256: string;
116
+ readonly sizeBytes: number;
117
+ }
118
+ export interface RequiredBuildInputs {
119
+ /** Compiler and package configuration the included files are read with. */
120
+ readonly configs: readonly string[];
121
+ /** Packages the included files import, with the version the source pins. */
122
+ readonly packages: readonly {
123
+ readonly name: string;
124
+ readonly version: string | null;
125
+ readonly declaredIn: string | null;
126
+ /**
127
+ * One of the repository's own workspace packages: its source lives
128
+ * here, not on the registry, so it is mocked and never installed.
129
+ */
130
+ readonly workspace?: true;
131
+ /**
132
+ * Where the repository says it is fetched from; absent when nothing
133
+ * it holds says. Only a package shown to come from the public
134
+ * registry is installed by its name.
135
+ */
136
+ readonly registry?: PackageRegistry;
137
+ }[];
138
+ }
139
+ /**
140
+ * What a repository's lockfiles and npm configuration say of a package's
141
+ * registry. `public` is a lockfile entry resolved from the public registry
142
+ * with no configuration mapping its scope elsewhere; `private` is any
143
+ * lockfile entry or configuration that names another registry.
144
+ */
145
+ export interface PackageRegistry {
146
+ readonly source: "public" | "private";
147
+ /** The file that says so, such as `package-lock.json` or `.npmrc`. */
148
+ readonly evidence: string;
149
+ }
150
+ /** Every operation a contribution may make on the slice has a stated rule. */
151
+ export interface OperationPolicy {
152
+ readonly edit: "included-source-only";
153
+ readonly add: "requires-approved-path-mapping";
154
+ readonly delete: "requires-explicit-allowance";
155
+ readonly rename: "requires-explicit-allowance";
156
+ readonly synthetic: "rejected";
157
+ readonly unknown: "refused";
158
+ }
159
+ export declare const OPERATION_POLICY: OperationPolicy;
160
+ export interface SliceMeta {
161
+ readonly includedFiles: number;
162
+ readonly includedBytes: number;
163
+ readonly outboundCuts: number;
164
+ readonly inboundCuts: number;
165
+ readonly stubCoverage: StubCoverage;
166
+ readonly externals: number;
167
+ readonly blockers: number;
168
+ readonly ready: boolean;
169
+ }
170
+ export interface SliceManifest {
171
+ readonly schemaVersion: typeof SLICE_MANIFEST_SCHEMA_VERSION;
172
+ readonly toolVersion: string;
173
+ readonly extractorVersion: string | null;
174
+ /** The commit the source was read at; null outside Git. */
175
+ readonly sourceCommitSha: string | null;
176
+ /** Whether any included file differed from that commit. */
177
+ readonly sourceDirty: boolean;
178
+ /** The import graph walked, as `canonicalJson` writes it. */
179
+ readonly graphSha256: string;
180
+ readonly params: SliceParams;
181
+ readonly entryPoints: readonly string[];
182
+ readonly budget: SliceBudget;
183
+ readonly included: readonly IncludedFile[];
184
+ readonly synthetic: readonly SyntheticFile[];
185
+ readonly requiredBuildInputs: RequiredBuildInputs;
186
+ readonly cuts: {
187
+ readonly outbound: readonly CutEdge[];
188
+ readonly inbound: readonly CutEdge[];
189
+ };
190
+ /** Import edges between included files, with their specifiers; see `reach`. */
191
+ readonly internalImports: readonly CutEdge[];
192
+ readonly externals: SliceExternals;
193
+ readonly blockers: readonly SliceBlocker[];
194
+ readonly policy: OperationPolicy;
195
+ readonly boundaryContractSha256: string;
196
+ readonly meta: SliceMeta;
197
+ }
198
+ /**
199
+ * Whether a slice may advance past diagnosis. Only full declaration
200
+ * coverage with no blockers may; the sandbox phase then proves the runtime.
201
+ */
202
+ export declare function sliceReadiness(input: {
203
+ readonly stubCoverage: StubCoverage;
204
+ readonly blockers: readonly SliceBlocker[];
205
+ }): {
206
+ ready: boolean;
207
+ reasons: string[];
208
+ };
209
+ /** The bounded view of a boundary a console renders without the files. */
210
+ export interface SliceBoundarySummary {
211
+ readonly schemaVersion: 1;
212
+ readonly language: BoundaryContract["language"];
213
+ readonly stubCoverage: StubCoverage;
214
+ readonly ready: boolean;
215
+ readonly counts: {
216
+ readonly includedFiles: number;
217
+ readonly includedBytes: number;
218
+ readonly outboundModules: number;
219
+ readonly inboundModules: number;
220
+ readonly stubs: number;
221
+ readonly publicSymbols: number;
222
+ readonly externals: number;
223
+ readonly blockers: number;
224
+ };
225
+ readonly included: readonly string[];
226
+ readonly outbound: readonly SummaryModule[];
227
+ readonly inbound: readonly SummaryModule[];
228
+ readonly externals: {
229
+ readonly packages: readonly {
230
+ specifier: string;
231
+ service: string;
232
+ }[];
233
+ readonly environment: readonly string[];
234
+ };
235
+ readonly blockers: readonly SliceBlocker[];
236
+ /** Whether any list above was cut short. */
237
+ readonly truncated: boolean;
238
+ }
239
+ export interface SummaryModule {
240
+ readonly module: string;
241
+ readonly symbols: readonly string[];
242
+ readonly importedBy: readonly string[];
243
+ readonly truncated: boolean;
244
+ }
245
+ export declare const SUMMARY_LIMITS: {
246
+ readonly files: 200;
247
+ readonly modules: 100;
248
+ readonly symbols: 50;
249
+ readonly importers: 10;
250
+ readonly blockers: 50;
251
+ readonly externalPackages: 100;
252
+ readonly environment: 100;
253
+ };
254
+ export declare function boundarySummary(contract: BoundaryContract, manifest: Pick<SliceManifest, "included" | "externals" | "meta">): SliceBoundarySummary;
255
+ /**
256
+ * `BOUNDARY.md`: an index of the boundary, rendered from the contract. What
257
+ * the slice holds is in the README and `carveout.json`; what each stub
258
+ * declares is in the stub.
259
+ */
260
+ export declare function renderBoundaryMarkdown(contract: BoundaryContract, manifest: Pick<SliceManifest, "included" | "entryPoints" | "budget" | "externals" | "meta">): string;
261
+ /** `public-surface.md`: only what a contribution must keep. */
262
+ export declare function renderPublicSurfaceMarkdown(contract: BoundaryContract): string;
@@ -169,13 +169,14 @@ export function boundarySummary(contract, manifest) {
169
169
  * them; repeating them here cost a session an eighth of everything it read
170
170
  * (E6c: BOUNDARY.md opened in 60 of 60 slice runs).
171
171
  */
172
- function renderModules(modules, empty) {
172
+ function renderModules(modules, empty, rest = "") {
173
173
  if (modules.length === 0)
174
174
  return [empty];
175
175
  return modules.map((module) => {
176
176
  const names = module.symbols.map((symbol) => symbol.name);
177
177
  const shown = names.slice(0, 12).map((name) => `\`${name}\``).join(", ");
178
- const more = names.length > 12 ? `, +${names.length - 12} more` : "";
178
+ // Where every name of a long list is, rather than all of them here.
179
+ const more = names.length > 12 ? `, +${names.length - 12} more${rest}` : "";
179
180
  // The folder writes each stub beside the module's path, not where the
180
181
  // contract files it.
181
182
  const stub = module.stubPath === null
@@ -206,7 +207,7 @@ export function renderBoundaryMarkdown(contract, manifest) {
206
207
  lines.push(`- ${reason}`);
207
208
  lines.push("");
208
209
  }
209
- lines.push("## Stubbed modules", "", "What the slice imports from outside, with the names it takes. Each stub's `.d.ts` declares them; the `.js` beside it is a recording mock.", "", ...renderModules(contract.outbound, "The slice imports nothing outside itself."), "", "## Public surface", "", "What outside code imports from the slice; a change must keep these exports to fit back.", "", ...renderModules(contract.inbound, "Nothing outside the slice imports it."), "", "## Externals to mock", "");
210
+ lines.push("## Stubbed modules", "", "What the slice imports from outside, with the names it takes. Each stub's `.d.ts` declares them; the `.js` beside it is a recording mock.", "", ...renderModules(contract.outbound, "The slice imports nothing outside itself.", " (the stub declares each)"), "", "## Public surface", "", "What outside code imports from the slice; a change must keep these exports to fit back.", "", ...renderModules(contract.inbound, "Nothing outside the slice imports it.", " (`carveout/surface/check.ts` holds each, a line apiece)"), "", "## Externals to mock", "");
210
211
  if (manifest.externals.packages.length === 0 &&
211
212
  manifest.externals.environment.length === 0)
212
213
  lines.push("No service SDKs or environment reads were detected.");