pi-session-mcp 0.0.0-stage → 0.6.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 (88) hide show
  1. package/.pi-session-mcp-cli/config.js +88 -0
  2. package/.pi-session-mcp-cli/doctor-cli.js +11 -0
  3. package/.pi-session-mcp-cli/doctor.js +178 -0
  4. package/.pi-session-mcp-cli/execution-profile.js +29 -0
  5. package/.pi-session-mcp-cli/mcp-config.js +70 -0
  6. package/.pi-session-mcp-cli/package-metadata.js +32 -0
  7. package/CHANGELOG.md +62 -0
  8. package/CONTRIBUTING.md +57 -0
  9. package/LICENSE +21 -0
  10. package/README.md +183 -2
  11. package/SECURITY.md +37 -0
  12. package/dist/capabilities.d.ts +25 -0
  13. package/dist/capabilities.js +26 -0
  14. package/dist/client-adapters/claude-code.d.ts +4 -0
  15. package/dist/client-adapters/claude-code.js +33 -0
  16. package/dist/client-adapters/codex.d.ts +4 -0
  17. package/dist/client-adapters/codex.js +73 -0
  18. package/dist/client-adapters/command.d.ts +12 -0
  19. package/dist/client-adapters/command.js +25 -0
  20. package/dist/client-adapters/index.d.ts +4 -0
  21. package/dist/client-adapters/index.js +3 -0
  22. package/dist/client-adapters/mcp-verifier.d.ts +20 -0
  23. package/dist/client-adapters/mcp-verifier.js +174 -0
  24. package/dist/client-adapters/types.d.ts +54 -0
  25. package/dist/client-adapters/types.js +1 -0
  26. package/dist/config.d.ts +50 -0
  27. package/dist/config.js +88 -0
  28. package/dist/doctor-cli.d.ts +1 -0
  29. package/dist/doctor-cli.js +11 -0
  30. package/dist/doctor.d.ts +47 -0
  31. package/dist/doctor.js +178 -0
  32. package/dist/entry-point.d.ts +5 -0
  33. package/dist/entry-point.js +16 -0
  34. package/dist/execution-profile.d.ts +37 -0
  35. package/dist/execution-profile.js +29 -0
  36. package/dist/main.d.ts +2 -0
  37. package/dist/main.js +51 -0
  38. package/dist/mcp-config.d.ts +22 -0
  39. package/dist/mcp-config.js +70 -0
  40. package/dist/mcp-session-tools.d.ts +54 -0
  41. package/dist/mcp-session-tools.js +323 -0
  42. package/dist/mcp-stdio-client.d.ts +3 -0
  43. package/dist/mcp-stdio-client.js +59 -0
  44. package/dist/package-metadata.d.ts +18 -0
  45. package/dist/package-metadata.js +32 -0
  46. package/dist/pi-adapter.d.ts +42 -0
  47. package/dist/pi-adapter.js +22 -0
  48. package/dist/process-diagnostics.d.ts +8 -0
  49. package/dist/process-diagnostics.js +9 -0
  50. package/dist/sdk-pi-adapter.d.ts +75 -0
  51. package/dist/sdk-pi-adapter.js +410 -0
  52. package/dist/server.d.ts +5 -0
  53. package/dist/server.js +102 -0
  54. package/dist/session-registry.d.ts +104 -0
  55. package/dist/session-registry.js +373 -0
  56. package/dist/setup-cli.d.ts +11 -0
  57. package/dist/setup-cli.js +38 -0
  58. package/dist/setup-command-guardian.d.ts +2 -0
  59. package/dist/setup-command-guardian.js +297 -0
  60. package/dist/setup-main.d.ts +8 -0
  61. package/dist/setup-main.js +60 -0
  62. package/dist/setup-ownership.d.ts +53 -0
  63. package/dist/setup-ownership.js +228 -0
  64. package/dist/setup-process.d.ts +30 -0
  65. package/dist/setup-process.js +385 -0
  66. package/dist/setup-result.d.ts +22 -0
  67. package/dist/setup-result.js +8 -0
  68. package/dist/setup.d.ts +55 -0
  69. package/dist/setup.js +327 -0
  70. package/dist/shutdown.d.ts +10 -0
  71. package/dist/shutdown.js +52 -0
  72. package/docs/acceptance.md +227 -0
  73. package/docs/adr/0001-stdio-first.md +12 -0
  74. package/docs/adr/0002-embedded-pi-sdk.md +11 -0
  75. package/docs/adr/0003-post-mvp-maintenance.md +60 -0
  76. package/docs/adr/0004-client-contract-not-version.md +31 -0
  77. package/docs/architecture.md +82 -0
  78. package/docs/client-setup-design.md +90 -0
  79. package/docs/client-setup.md +207 -0
  80. package/docs/doctor-design.md +47 -0
  81. package/docs/installation.md +352 -0
  82. package/docs/releasing.md +216 -0
  83. package/docs/session-mcp.md +260 -0
  84. package/docs/threat-model.md +89 -0
  85. package/docs/tool-contracts.md +143 -0
  86. package/package.json +64 -4
  87. package/pi-session-mcp.example.json +21 -0
  88. package/scripts/doctor.mjs +72 -0
@@ -0,0 +1,385 @@
1
+ import { spawn } from "node:child_process";
2
+ import { lstatSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ const DEFAULT_TIMEOUT = 10_000;
5
+ const DEFAULT_MAX = 65_536;
6
+ const ABORT_GROUP_CLEANUP_TIMEOUT = 2_000;
7
+ const GUARDIAN_START_TIMEOUT = 5_000;
8
+ /** Kill a detached process and every descendant in its process group. */
9
+ function terminateProcessGroup(child, signal) {
10
+ if (child.pid === undefined)
11
+ return;
12
+ if (process.platform !== "win32") {
13
+ try {
14
+ process.kill(-child.pid, signal);
15
+ return;
16
+ }
17
+ catch { /* already exited or no group */ }
18
+ }
19
+ try {
20
+ child.kill(signal);
21
+ }
22
+ catch { /* already exited */ }
23
+ }
24
+ function processGroupState(child) {
25
+ if (child.pid === undefined || process.platform !== "linux")
26
+ return "unknown";
27
+ const group = child.pid;
28
+ try {
29
+ for (const entry of readdirSync("/proc", { withFileTypes: true })) {
30
+ if (!entry.isDirectory() || !/^\d+$/.test(entry.name))
31
+ continue;
32
+ try {
33
+ const stat = readProcStat(Number(entry.name));
34
+ if (stat.pgrp === group && stat.state !== "Z")
35
+ return "alive";
36
+ }
37
+ catch (error) {
38
+ if (error.code !== "ENOENT")
39
+ return "unknown";
40
+ }
41
+ }
42
+ }
43
+ catch {
44
+ return "unknown";
45
+ }
46
+ return "gone";
47
+ }
48
+ function readProcStat(pid) {
49
+ const line = readFileSync(`/proc/${pid}/stat`, "utf8").trim();
50
+ const close = line.lastIndexOf(") ");
51
+ const state = line[close + 2];
52
+ if (close < 3 || !state || !/^[A-Za-z]$/.test(state) || line[close + 3] !== " ")
53
+ throw new Error("process_stat_invalid");
54
+ const fields = line.slice(close + 4).trim().split(/\s+/);
55
+ const pgrp = Number(fields[1]);
56
+ if (!Number.isSafeInteger(pgrp) || pgrp < 0)
57
+ throw new Error("process_group_invalid");
58
+ return { pgrp, state };
59
+ }
60
+ const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
61
+ /** Runs only an explicit executable/argv pair, with bounded output and termination. */
62
+ export function createProcessRunner(options = {}) {
63
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT;
64
+ const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX;
65
+ return { run(command, args, signal) {
66
+ const effectiveSignal = options.signal && signal ? AbortSignal.any([options.signal, signal]) : options.signal ?? signal;
67
+ if (process.platform !== "linux")
68
+ return Promise.resolve({ exitCode: 126, stdout: "", stderr: "" });
69
+ return new Promise((resolve) => {
70
+ const child = spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"], env: options.env, detached: process.platform !== "win32" });
71
+ let stdout = "";
72
+ let stderr = "";
73
+ let overflow = false;
74
+ let aborted = false;
75
+ const collect = (part) => (chunk) => { const value = chunk.toString("utf8"); const current = part === "stdout" ? stdout : stderr; if (Buffer.byteLength(current + value, "utf8") > maxOutputBytes) {
76
+ overflow = true;
77
+ return;
78
+ } if (part === "stdout")
79
+ stdout += value;
80
+ else
81
+ stderr += value; };
82
+ child.stdout.on("data", collect("stdout"));
83
+ child.stderr.on("data", collect("stderr"));
84
+ let done = false;
85
+ let timedOut = false;
86
+ let killTimer;
87
+ let hardTimer;
88
+ let groupPoll;
89
+ let groupDeadline = 0;
90
+ const finish = (exitCode) => { if (done)
91
+ return; done = true; effectiveSignal?.removeEventListener("abort", abort); clearTimeout(timer); if (!timedOut && killTimer)
92
+ clearTimeout(killTimer); if (!timedOut && hardTimer)
93
+ clearTimeout(hardTimer); if (groupPoll)
94
+ clearTimeout(groupPoll); resolve({ exitCode, stdout: overflow ? Buffer.from(stdout).subarray(0, maxOutputBytes).toString("utf8") : stdout, stderr: overflow ? "output_limit" : stderr }); };
95
+ const finishAfterGroup = (successCode) => {
96
+ if (done)
97
+ return;
98
+ if (processGroupState(child) === "gone") {
99
+ finish(successCode);
100
+ return;
101
+ }
102
+ if (Date.now() >= groupDeadline) {
103
+ finish(126);
104
+ return;
105
+ }
106
+ groupPoll = setTimeout(() => finishAfterGroup(successCode), 10);
107
+ };
108
+ const abort = () => { if (done)
109
+ return; aborted = true; groupDeadline = Date.now() + ABORT_GROUP_CLEANUP_TIMEOUT; terminateProcessGroup(child, "SIGTERM"); killTimer = setTimeout(() => { terminateProcessGroup(child, "SIGKILL"); finishAfterGroup(125); }, 100); };
110
+ effectiveSignal?.addEventListener("abort", abort, { once: true });
111
+ if (effectiveSignal?.aborted)
112
+ abort();
113
+ const timer = setTimeout(() => {
114
+ timedOut = true;
115
+ groupDeadline = Date.now() + ABORT_GROUP_CLEANUP_TIMEOUT;
116
+ terminateProcessGroup(child, "SIGTERM");
117
+ killTimer = setTimeout(() => {
118
+ terminateProcessGroup(child, "SIGKILL");
119
+ hardTimer = setTimeout(() => finishAfterGroup(124), 100);
120
+ }, 100);
121
+ }, timeoutMs);
122
+ child.on("error", () => { if (!timedOut) {
123
+ if (aborted)
124
+ finishAfterGroup(125);
125
+ else
126
+ finish(127);
127
+ } });
128
+ child.on("close", (code) => { if (!timedOut) {
129
+ if (aborted)
130
+ finishAfterGroup(125);
131
+ else
132
+ finish(code ?? 1);
133
+ } });
134
+ });
135
+ } };
136
+ }
137
+ /** Starts the local MCP server with only the controlled configuration environment. */
138
+ export function createMcpStdioLauncher(options) {
139
+ return async (signal) => {
140
+ const child = spawn(options.nodePath, [options.entryPath], { stdio: ["pipe", "pipe", "pipe"], env: { PI_SESSION_MCP_CONFIG: options.configPath }, detached: process.platform !== "win32" });
141
+ const max = options.maxFrameBytes ?? DEFAULT_MAX;
142
+ let buffer = "";
143
+ let closed = false;
144
+ let overflow = false;
145
+ child.stdout.on("data", (chunk) => { if (overflow)
146
+ return; buffer += chunk.toString("utf8"); if (Buffer.byteLength(buffer, "utf8") > max) {
147
+ overflow = true;
148
+ buffer = "";
149
+ } });
150
+ child.stderr.on("data", () => { });
151
+ child.on("error", () => { closed = true; });
152
+ child.on("close", () => { closed = true; });
153
+ const abort = () => { if (!closed)
154
+ terminateProcessGroup(child, "SIGTERM"); };
155
+ signal.addEventListener("abort", abort, { once: true });
156
+ const waitForClose = async (milliseconds) => {
157
+ if (closed)
158
+ return true;
159
+ return new Promise((resolve) => {
160
+ const timer = setTimeout(() => resolve(closed), milliseconds);
161
+ child.once("close", () => { clearTimeout(timer); resolve(true); });
162
+ });
163
+ };
164
+ return {
165
+ write(frame) {
166
+ if (closed || overflow || Buffer.byteLength(frame, "utf8") > max)
167
+ return Promise.reject(new Error("mcp_write_unavailable"));
168
+ return new Promise((resolve, reject) => child.stdin.write(frame, (error) => error ? reject(new Error("mcp_write_failed")) : resolve()));
169
+ },
170
+ async read() {
171
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT);
172
+ while (!overflow && !closed && buffer.indexOf("\n") < 0 && Date.now() < deadline)
173
+ await new Promise((resolve) => setTimeout(resolve, 5));
174
+ if (overflow)
175
+ return null;
176
+ const newline = buffer.indexOf("\n");
177
+ if (newline >= 0) {
178
+ const line = buffer.slice(0, newline + 1);
179
+ buffer = buffer.slice(newline + 1);
180
+ return line;
181
+ }
182
+ if (closed && buffer.length > 0) {
183
+ const tail = buffer;
184
+ buffer = "";
185
+ return tail;
186
+ }
187
+ return null;
188
+ },
189
+ async kill() {
190
+ signal.removeEventListener("abort", abort);
191
+ if (!closed)
192
+ child.stdin.end();
193
+ terminateProcessGroup(child, "SIGTERM");
194
+ await delay(100);
195
+ terminateProcessGroup(child, "SIGKILL");
196
+ const childClosed = await waitForClose(1_000);
197
+ const groupDeadline = Date.now() + 1_000;
198
+ while (processGroupState(child) === "alive" && Date.now() < groupDeadline)
199
+ await delay(10);
200
+ if (!childClosed || processGroupState(child) !== "gone")
201
+ throw new Error("mcp_process_cleanup_failed");
202
+ },
203
+ };
204
+ };
205
+ }
206
+ /**
207
+ * Acquires the Linux kernel fence and starts the transaction guardian. The
208
+ * guardian inherits the locked file description, so parent death cannot release
209
+ * the fence while a client mutation or descendant is still live.
210
+ */
211
+ export async function acquireSetupGuardian(lockFd, timeoutMs, signal, validateFence) {
212
+ if (process.platform !== "linux")
213
+ throw new Error("platform_unsupported");
214
+ const flockPath = ["/usr/bin/flock", "/bin/flock"].find((candidate) => {
215
+ try {
216
+ const file = statSync(candidate);
217
+ return file.isFile() && file.uid === 0 && (file.mode & 0o022) === 0 && (file.mode & 0o111) !== 0;
218
+ }
219
+ catch {
220
+ return false;
221
+ }
222
+ });
223
+ if (!flockPath)
224
+ throw new Error("ownership_lock_unavailable");
225
+ const modulePath = fileURLToPath(import.meta.url);
226
+ const guardianPath = fileURLToPath(new URL(modulePath.endsWith(".ts") ? "./setup-command-guardian.ts" : "./setup-command-guardian.js", import.meta.url));
227
+ try {
228
+ if (!lstatSync(guardianPath).isFile())
229
+ throw new Error("guardian_invalid");
230
+ }
231
+ catch {
232
+ throw new Error("ownership_lock_unavailable");
233
+ }
234
+ const waitSeconds = String(Math.ceil(Math.max(1_000, Math.min(timeoutMs, 120_000)) / 1_000));
235
+ let locker;
236
+ try {
237
+ locker = spawn(flockPath, ["-E", "75", "-w", waitSeconds, "3"], {
238
+ stdio: ["ignore", "ignore", "ignore", lockFd],
239
+ detached: true,
240
+ });
241
+ }
242
+ catch {
243
+ throw new Error("ownership_lock_unavailable");
244
+ }
245
+ const stopAcquisition = () => { if (locker.pid !== undefined) {
246
+ try {
247
+ process.kill(-locker.pid, "SIGKILL");
248
+ }
249
+ catch { /* already gone */ }
250
+ } };
251
+ if (signal?.aborted)
252
+ stopAcquisition();
253
+ signal?.addEventListener("abort", stopAcquisition, { once: true });
254
+ try {
255
+ await new Promise((resolve, reject) => {
256
+ locker.once("error", () => reject(new Error("ownership_lock_unavailable")));
257
+ locker.once("close", (code) => code === 0 ? resolve() : reject(new Error(code === 75 ? "ownership_lock_busy" : signal?.aborted ? "operation_aborted" : "ownership_lock_unavailable")));
258
+ });
259
+ }
260
+ finally {
261
+ signal?.removeEventListener("abort", stopAcquisition);
262
+ }
263
+ if (signal?.aborted)
264
+ throw new Error("operation_aborted");
265
+ try {
266
+ await validateFence?.();
267
+ }
268
+ catch {
269
+ throw new Error("ownership_lock_unavailable");
270
+ }
271
+ let child;
272
+ try {
273
+ child = spawn(process.execPath, [guardianPath, String(process.pid)], { stdio: ["ignore", "ignore", "ignore", lockFd, "ipc"], detached: true });
274
+ }
275
+ catch {
276
+ throw new Error("ownership_lock_unavailable");
277
+ }
278
+ const stopGuardianStart = () => { if (child.pid !== undefined) {
279
+ try {
280
+ process.kill(-child.pid, "SIGKILL");
281
+ }
282
+ catch { /* already gone */ }
283
+ } };
284
+ signal?.addEventListener("abort", stopGuardianStart, { once: true });
285
+ try {
286
+ await new Promise((resolve, reject) => {
287
+ let settled = false;
288
+ const timer = setTimeout(() => { stopGuardianStart(); settle(new Error("ownership_lock_unavailable")); }, GUARDIAN_START_TIMEOUT);
289
+ const settle = (error) => { if (settled)
290
+ return; settled = true; clearTimeout(timer); if (error)
291
+ reject(error);
292
+ else
293
+ resolve(); };
294
+ child.once("error", () => settle(new Error("ownership_lock_unavailable")));
295
+ child.once("close", () => settle(new Error(signal?.aborted ? "operation_aborted" : "ownership_lock_unavailable")));
296
+ child.on("message", (message) => { if (message && typeof message === "object" && message.type === "ready")
297
+ settle(); });
298
+ });
299
+ }
300
+ finally {
301
+ signal?.removeEventListener("abort", stopGuardianStart);
302
+ }
303
+ const compromised = new AbortController();
304
+ let nextId = 1;
305
+ let pending;
306
+ let released = false;
307
+ let unsafe = false;
308
+ const safeResult = (message) => {
309
+ if (!message || typeof message !== "object")
310
+ return undefined;
311
+ const value = message;
312
+ if (value.type !== "result" || !Number.isSafeInteger(value.id) || !Number.isSafeInteger(value.exitCode) || typeof value.stdout !== "string" || typeof value.stderr !== "string")
313
+ return undefined;
314
+ if (value.stdout.length > DEFAULT_MAX || value.stderr.length > DEFAULT_MAX)
315
+ return undefined;
316
+ return { exitCode: value.exitCode, stdout: value.stdout, stderr: value.stderr };
317
+ };
318
+ const failPending = () => {
319
+ if (!pending)
320
+ return;
321
+ pending.signal?.removeEventListener("abort", pending.abort);
322
+ const resolve = pending.resolve;
323
+ pending = undefined;
324
+ unsafe = true;
325
+ compromised.abort();
326
+ resolve({ exitCode: 126, stdout: "", stderr: "" });
327
+ };
328
+ child.on("message", (message) => {
329
+ const result = safeResult(message);
330
+ if (!result || !pending || message.id !== pending.id)
331
+ return;
332
+ pending.signal?.removeEventListener("abort", pending.abort);
333
+ const resolve = pending.resolve;
334
+ pending = undefined;
335
+ if (result.exitCode === 126) {
336
+ unsafe = true;
337
+ compromised.abort();
338
+ }
339
+ resolve(result);
340
+ });
341
+ child.once("error", failPending);
342
+ child.once("close", () => { if (!released) {
343
+ unsafe = true;
344
+ compromised.abort();
345
+ failPending();
346
+ } });
347
+ const runner = {
348
+ run(command, args, runSignal) {
349
+ if (released || unsafe || pending || runSignal?.aborted || !child.connected)
350
+ return Promise.resolve({ exitCode: 126, stdout: "", stderr: "" });
351
+ const id = nextId++;
352
+ return new Promise((resolve) => {
353
+ const abort = () => { if (child.connected)
354
+ child.send({ type: "abort", id }, undefined, undefined, () => undefined); };
355
+ pending = { id, resolve, ...(runSignal ? { signal: runSignal } : {}), abort };
356
+ runSignal?.addEventListener("abort", abort, { once: true });
357
+ child.send({ type: "run", id, command, args: [...args], timeoutMs: DEFAULT_TIMEOUT, maxOutputBytes: DEFAULT_MAX }, undefined, undefined, (error) => { if (error)
358
+ failPending(); });
359
+ });
360
+ },
361
+ };
362
+ return {
363
+ runner,
364
+ signal: compromised.signal,
365
+ async release() {
366
+ if (released)
367
+ return;
368
+ released = true;
369
+ if (pending)
370
+ pending.abort();
371
+ if (!unsafe && child.connected) {
372
+ child.send({ type: "shutdown" }, undefined, undefined, () => undefined);
373
+ const closed = await new Promise((resolve) => {
374
+ const timer = setTimeout(() => resolve(false), 5_000);
375
+ child.once("close", () => { clearTimeout(timer); resolve(true); });
376
+ });
377
+ if (closed)
378
+ return;
379
+ }
380
+ if (child.connected)
381
+ child.disconnect();
382
+ child.unref();
383
+ },
384
+ };
385
+ }
@@ -0,0 +1,22 @@
1
+ /** Public, deliberately path-free setup result model. */
2
+ export type SetupOperation = "dry-run" | "apply" | "verify" | "rollback" | "remove";
3
+ export type SetupStatus = "ok" | "planned" | "unchanged" | "absent" | "divergent" | "unsupported" | "failed";
4
+ export type RegistrationState = "absent" | "equivalent" | "divergent" | "unsupported";
5
+ export interface SetupTarget {
6
+ readonly client: string;
7
+ readonly scope: string;
8
+ readonly alias: string;
9
+ }
10
+ export interface SetupFinding {
11
+ readonly target: SetupTarget;
12
+ readonly status: SetupStatus;
13
+ readonly code: string;
14
+ }
15
+ export interface SetupResult {
16
+ readonly schemaVersion: 1;
17
+ readonly operation: SetupOperation;
18
+ readonly status: SetupStatus;
19
+ readonly findings: readonly SetupFinding[];
20
+ readonly exitCode: 0 | 1 | 64 | 130 | 143;
21
+ }
22
+ export declare function result(operation: SetupOperation, findings: readonly SetupFinding[], status?: SetupStatus, exitCode?: 0 | 1 | 64 | 130 | 143): SetupResult;
@@ -0,0 +1,8 @@
1
+ export function result(operation, findings, status = findings.some((finding) => finding.status === "failed" || finding.status === "divergent" || finding.status === "unsupported") ? "failed" : "ok", exitCode = status === "failed" ? 1 : 0) {
2
+ const normalized = findings.map((finding) => Object.freeze({
3
+ target: Object.freeze({ ...finding.target }),
4
+ status: finding.status,
5
+ code: /^[a-z][a-z0-9_]{0,63}$/.test(finding.code) ? finding.code : "operation_failed",
6
+ }));
7
+ return Object.freeze({ schemaVersion: 1, operation, status, findings: Object.freeze(normalized), exitCode });
8
+ }
@@ -0,0 +1,55 @@
1
+ import type { CommandExecution, CommandRunner, RegistrationIntent, RegistrationState } from "./client-adapters/types.js";
2
+ import { type OwnershipStore } from "./setup-ownership.js";
3
+ import { type SetupOperation, type SetupResult, type SetupTarget } from "./setup-result.js";
4
+ export interface SetupClientAdapter {
5
+ readonly name: string;
6
+ readonly supportedScopes: readonly string[];
7
+ inspect(target: SetupTarget, fingerprint: string): Promise<{
8
+ state: RegistrationState;
9
+ fingerprint?: string;
10
+ }>;
11
+ apply(target: SetupTarget, execution?: CommandExecution): Promise<void>;
12
+ remove(target: SetupTarget, execution?: CommandExecution): Promise<void>;
13
+ verify(target: SetupTarget): Promise<{
14
+ ok: boolean;
15
+ code: string;
16
+ }>;
17
+ }
18
+ export interface SetupDoctor {
19
+ check(): Promise<{
20
+ ok: boolean;
21
+ code: string;
22
+ }>;
23
+ }
24
+ export interface SetupDependencies {
25
+ readonly adapters?: readonly SetupClientAdapter[];
26
+ readonly doctor?: SetupDoctor;
27
+ readonly ownership?: OwnershipStore;
28
+ readonly intendedFingerprint: string;
29
+ }
30
+ export interface SetupRequest {
31
+ readonly operation?: SetupOperation;
32
+ readonly targets: readonly SetupTarget[];
33
+ }
34
+ export declare const isSupportedTarget: (target: SetupTarget) => boolean;
35
+ export declare class SetupOrchestrator {
36
+ private readonly dependencies;
37
+ private readonly ownership;
38
+ constructor(dependencies: SetupDependencies);
39
+ run(request: SetupRequest): Promise<SetupResult>;
40
+ private adapter;
41
+ private assertOwnershipHealthy;
42
+ private signal;
43
+ private execution;
44
+ private plan;
45
+ private apply;
46
+ private verify;
47
+ private restore;
48
+ private safe;
49
+ }
50
+ export declare function registrationFingerprint(intent: RegistrationIntent, releaseBinding?: string): string;
51
+ /** Bind setup ownership to the exact package manifests and built runtime tree. */
52
+ export declare function immutableReleaseBinding(packageRoot: string): Promise<string>;
53
+ export declare function createProductionSetup(runner: CommandRunner, packageRoot: string, configPath: string, ownershipPath?: string, signal?: AbortSignal): Promise<SetupOrchestrator>;
54
+ export { result } from "./setup-result.js";
55
+ export type { SetupFinding, SetupOperation, SetupResult, SetupStatus, SetupTarget } from "./setup-result.js";