@everfur/cli 0.1.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.
@@ -0,0 +1,2511 @@
1
+ /**
2
+ * CREDENTIAL RESOLUTION, and the rules that make "the CLI never prints a secret" a structural
3
+ * property rather than a promise.
4
+ *
5
+ * 1. A credential comes from a NAMED ENVIRONMENT VARIABLE or from nowhere. This package never
6
+ * reads a `.env` file, never walks up looking for one, and has no dotenv dependency: a repo
7
+ * `.env` is a file a partner commits by accident, and a tool that reads one teaches them
8
+ * that committing it is fine. The names are documented in README.md and printed by
9
+ * `--help`, so an operator is told what to export rather than where a file should live.
10
+ * 2. The resolved value is wrapped. `toString` and `toJSON` both return `[redacted]`, so the
11
+ * two ways a value normally escapes (string interpolation into a message, and
12
+ * `JSON.stringify` of an outcome) yield the marker instead of the secret. Only `reveal()`
13
+ * returns the real bytes, and `tests/unit/secret-never-printed.test.ts` asserts that the
14
+ * HTTP client is the only module that calls it.
15
+ * 3. Shape is checked before the value is used. A truncated paste is reported by NAME as a
16
+ * malformed credential, which is a better answer than the opaque 401 the server gives for
17
+ * every credential failure alike (`sessions_router._auth_rejected`).
18
+ */
19
+ declare const REDACTED = "[redacted]";
20
+ interface Credential {
21
+ /** The variable this came from. Safe to print; it is a name, not a value. */
22
+ readonly envVar: string;
23
+ toString(): string;
24
+ toJSON(): string;
25
+ /** The real value. Called by `src/http/client.ts` and nowhere else. */
26
+ reveal(): string;
27
+ }
28
+ interface CredentialSpec {
29
+ readonly envVar: string;
30
+ /** Required literal prefix, or null when the value is opaque (a bearer token). */
31
+ readonly prefix: string | null;
32
+ /**
33
+ * Additional accepted prefixes, beyond `prefix`.
34
+ *
35
+ * A publishable key is `pk_live_` on a production deployment and `pk_test_` on a sandbox one, and the
36
+ * CLI cannot tell which host it is pointed at before it makes a call. Hardcoding one of them would make
37
+ * the CLI reject exactly the sandbox credentials the platform now issues, which is the opposite of what
38
+ * a test-mode tool should do. The SERVER is the authority: it accepts one prefix per deployment and
39
+ * refuses the other on the fast-fail path, so accepting both here narrows nothing.
40
+ */
41
+ readonly alsoAccepts?: readonly string[];
42
+ /** Exact expected length when the prefix is known, or null. */
43
+ readonly length: number | null;
44
+ readonly summary: string;
45
+ }
46
+ /** The publishable key. Public by construction, but still never echoed back to the operator. */
47
+ declare const PARTNER_KEY: CredentialSpec;
48
+ /**
49
+ * The server-side half of the session mint. The variable name is the same constant `plan`
50
+ * already names in its configure step; see `src/commands/plan/planner.ts`.
51
+ */
52
+ declare const PARTNER_SECRET_KEY: CredentialSpec;
53
+ /** An already minted device bearer, for diagnosing the signed path without a mint. */
54
+ declare const SESSION_TOKEN: CredentialSpec;
55
+ /** The operator bearer for the admin routes. */
56
+ declare const ADMIN_TOKEN: CredentialSpec;
57
+ declare const CREDENTIAL_SPECS: readonly CredentialSpec[];
58
+ type CredentialStatus = 'present' | 'absent' | 'malformed';
59
+ interface ResolvedCredential {
60
+ readonly spec: CredentialSpec;
61
+ readonly status: CredentialStatus;
62
+ /** Populated only when `status` is `present`. */
63
+ readonly credential: Credential | null;
64
+ /** Why the value was rejected. Names the variable and the expected shape, never the value. */
65
+ readonly reason: string | null;
66
+ }
67
+ /**
68
+ * Wrap a value that did NOT come from the environment, for example a session token the CLI just
69
+ * minted. It is exactly as unprintable as an environment credential, which is the point: a
70
+ * token that arrives over the wire is no less a secret than one an operator exported, and
71
+ * `verify` has to carry one from step one to step two without it reaching a report.
72
+ *
73
+ * `envVar` names the origin rather than a variable, because there is no variable.
74
+ */
75
+ declare function ephemeralCredential(origin: string, value: string): Credential;
76
+ /**
77
+ * Wrap a credential so a scheme is prepended when it is finally revealed, for example turning a
78
+ * session token into `Bearer <token>`. The value is never interpolated here: the concatenation
79
+ * happens inside `reveal`, which only the HTTP client calls, so a prefixed credential is exactly
80
+ * as unprintable as an unprefixed one.
81
+ */
82
+ declare function withPrefix(credential: Credential, prefix: string): Credential;
83
+ /**
84
+ * Read one credential out of the injected environment. Absent and malformed are DIFFERENT
85
+ * answers, exactly as an absent and an invalid flag are different to `plan`: absent means the
86
+ * operator has not set it up yet, malformed means they did and it is wrong.
87
+ */
88
+ declare function resolveCredential(env: Readonly<Record<string, string | undefined>>, spec: CredentialSpec): ResolvedCredential;
89
+
90
+ /**
91
+ * The filesystem seam. Detection and contract loading are pure functions over this interface,
92
+ * which is the whole reason they can be tested against fixtures without a temp directory
93
+ * dance and without a process spawn.
94
+ *
95
+ * Synchronous by design. The CLI reads a handful of small manifests and then exits; async
96
+ * would buy nothing and would make every detection helper a promise for no reason.
97
+ *
98
+ * `readFile` returns null rather than throwing for a missing or unreadable path. Detection
99
+ * probes many paths that are legitimately absent, so absence is a value, not an exception.
100
+ */
101
+ interface FileSystemPort {
102
+ /** UTF-8 contents, or null when the path is missing, unreadable, or a directory. */
103
+ readFile(path: string): string | null;
104
+ /** True when the path exists, whatever its type. */
105
+ exists(path: string): boolean;
106
+ /** True when the path exists and is a directory. */
107
+ isDirectory(path: string): boolean;
108
+ }
109
+
110
+ /**
111
+ * THE NETWORK SEAM. One interface, injected exactly like `FileSystemPort`, which is what lets
112
+ * every command that talks to the API be exercised in a unit test with a scripted transport and
113
+ * no socket. Nothing above this file knows that `fetch` exists.
114
+ *
115
+ * The request and response are plain data. In particular a `HttpRequest` is NEVER carried into
116
+ * a `CommandOutcome`: it holds the credential headers, and an outcome is a thing the formatter
117
+ * prints. Failures carry the method, the path, and the request id, never the request object.
118
+ */
119
+ type HttpMethod = 'GET' | 'POST' | 'DELETE';
120
+ /** Methods that may be retried. A retry of anything else could mint a second credential. */
121
+ declare const IDEMPOTENT_METHODS: readonly HttpMethod[];
122
+ interface HttpRequest {
123
+ readonly method: HttpMethod;
124
+ readonly url: string;
125
+ /** Header names as written. Values may include a credential; see the note above. */
126
+ readonly headers: Readonly<Record<string, string>>;
127
+ readonly body: string | null;
128
+ /** Hard ceiling for this single attempt, in milliseconds. Enforced by the adapter. */
129
+ readonly timeoutMs: number;
130
+ }
131
+ interface HttpResponse {
132
+ readonly status: number;
133
+ /** Header names LOWERCASED by the adapter, so callers never guess at casing. */
134
+ readonly headers: Readonly<Record<string, string>>;
135
+ readonly body: string;
136
+ }
137
+ /**
138
+ * The only exception a transport may throw. Anything else escaping an adapter is a bug in that
139
+ * adapter; `createApiClient` still catches it and reports it as a network failure rather than
140
+ * letting a raw stack reach the user.
141
+ */
142
+ declare class TransportError extends Error {
143
+ readonly kind: 'timeout' | 'network';
144
+ constructor(kind: 'timeout' | 'network', message: string);
145
+ }
146
+ interface TransportPort {
147
+ send(request: HttpRequest): Promise<HttpResponse>;
148
+ }
149
+ /** True when a method may be retried after a transport fault or a retryable server answer. */
150
+ declare function isIdempotent(method: HttpMethod): boolean;
151
+
152
+ /**
153
+ * The time seam. Only `verify` needs it, to space out its entitlement poll, and only because a
154
+ * real sleep in a unit test is a slow test that eventually becomes a flaky one.
155
+ *
156
+ * Deliberately minimal: no `now()`. Every timestamp this CLI emits comes from the server
157
+ * (`evaluated_at`, `expires_at`), so there is no clock reading to inject and no output that
158
+ * changes between two runs of the same command.
159
+ */
160
+ interface ClockPort {
161
+ sleep(milliseconds: number): Promise<void>;
162
+ }
163
+ declare const systemClock: ClockPort;
164
+ /** A clock that never waits and records what it was asked to wait for. For tests. */
165
+ declare function createRecordingClock(): ClockPort & {
166
+ readonly waits: readonly number[];
167
+ };
168
+
169
+ /**
170
+ * A command declares its flags as data. Binding is a pure function from that declaration plus
171
+ * a ParsedArgv to either typed values or a list of usage errors, so "is --identity a known
172
+ * flag on this command" is answered by the registry, not by each command re-implementing it.
173
+ */
174
+ interface FlagSpec {
175
+ readonly name: string;
176
+ readonly kind: 'string' | 'boolean';
177
+ readonly summary: string;
178
+ /** Rendered in help as `--name <placeholder>`. Value flags only. */
179
+ readonly placeholder?: string;
180
+ readonly required?: boolean;
181
+ }
182
+ interface BoundFlags {
183
+ /** Value of a string flag, or null when absent. */
184
+ string(name: string): string | null;
185
+ /** Value of a boolean flag. Absent means false. */
186
+ boolean(name: string): boolean;
187
+ }
188
+
189
+ /**
190
+ * The frozen exit-code table. Every command settles to one of these; nothing else may
191
+ * reach the process. Codes are additive: a future command may claim a new code, but an
192
+ * existing code never changes meaning, because CI pipelines branch on them.
193
+ *
194
+ * The split between UNDERSPECIFIED (3) and INVALID_CONTRACT (4) / DIGEST_MISMATCH (5) is
195
+ * deliberate. 3 means "the CLI declined to answer because you did not tell it enough";
196
+ * it is a refusal, not a failure, and a wrapper script is expected to prompt and retry.
197
+ * 4 and 5 mean an artifact on disk is wrong and a human has to look at it.
198
+ */
199
+ declare const ExitCode: Readonly<{
200
+ /** The command produced a complete answer. */
201
+ readonly OK: 0;
202
+ /** An unexpected internal failure. A bug in the CLI, or an unreadable filesystem. */
203
+ readonly INTERNAL_ERROR: 1;
204
+ /** The invocation was malformed: unknown command, unknown flag, missing flag value. */
205
+ readonly USAGE_ERROR: 2;
206
+ /** The CLI refused to guess. Required facts were missing; see `missingFacts`. */
207
+ readonly UNDERSPECIFIED: 3;
208
+ /**
209
+ * A NAMED ARTIFACT was missing, unparseable, or failed shape validation against its JSON
210
+ * Schema. All three mean the same thing to a caller: a file this command was pointed at
211
+ * cannot be used, and a human has to look at that file.
212
+ *
213
+ * The wording was tightened from "failed shape validation" because `file_not_found` and
214
+ * `invalid_json` have always mapped here too, and a code whose documented meaning is
215
+ * narrower than its behaviour is a code CI branches on wrongly. What does NOT belong here is
216
+ * a bad INVOCATION: `--cwd /nope` is a usage error (2), because nothing was loaded.
217
+ */
218
+ readonly INVALID_CONTRACT: 4;
219
+ /** A loaded artifact was well shaped but its digest did not match its content. */
220
+ readonly DIGEST_MISMATCH: 5;
221
+ /**
222
+ * The API could not be reached, did not answer in time, or answered with a typed error.
223
+ * The invocation was fine and no local artifact is wrong: the far side is the problem, so a
224
+ * CI wrapper branching on this retries or pages rather than sending someone to edit a file.
225
+ */
226
+ readonly REMOTE_FAILURE: 6;
227
+ /**
228
+ * `verify` completed and at least one step FAILED. Distinct from 6 because the wire worked:
229
+ * the CLI got answers and one of them was wrong, which is a report to read rather than an
230
+ * outage to retry. A run with no failures but a skipped step settles at 3, not here, because
231
+ * a skip means the operator did not supply enough to exercise that step.
232
+ */
233
+ readonly VERIFICATION_FAILED: 7;
234
+ /**
235
+ * `sandbox create` provisioned a key, and the server offered no claim URL for its secret.
236
+ * The CLI will not print a raw secret, so it discarded the one it was handed unread. The key
237
+ * exists and nobody can use it; the human projection prints the revoke command.
238
+ */
239
+ readonly SECRET_UNCLAIMABLE: 8;
240
+ /**
241
+ * `plan` was told everything it needed and NOTHING the caller asked for can be built: every
242
+ * capability named resolved to a BLOCKED terminal in the contract.
243
+ *
244
+ * Distinct from 3 because supplying another flag will not help, and distinct from 0 because
245
+ * no plan was produced. A CI wrapper branching on 3 prompts for more input, which would loop
246
+ * forever here; one branching on 9 reads the reason and the request path out of the document
247
+ * and escalates to a human, which is the only thing that resolves it.
248
+ */
249
+ readonly BLOCKED: 9;
250
+ }>;
251
+ type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];
252
+
253
+ /**
254
+ * The JSON value lattice. Command outcomes carry a `JsonValue`, never an arbitrary object,
255
+ * so the machine projection is provably serialisable and provably free of class instances,
256
+ * functions, and `undefined` holes that would silently vanish through JSON.stringify.
257
+ */
258
+ type JsonPrimitive = string | number | boolean | null;
259
+ type JsonValue = JsonPrimitive | readonly JsonValue[] | {
260
+ readonly [key: string]: JsonValue;
261
+ };
262
+ type JsonObject = {
263
+ readonly [key: string]: JsonValue;
264
+ };
265
+
266
+ /**
267
+ * The single shared result type every command settles to. There is exactly one of these and
268
+ * every future command returns it, which is what lets the formatter, the bin shim, and the
269
+ * exit-code table stay unaware of what any individual command does.
270
+ *
271
+ * Both projections are always populated. A command never chooses a rendering: it produces the
272
+ * machine document and the human text, and `formatOutcome` decides which one reaches which
273
+ * stream. That is why command logic never imports a stream and never calls console.
274
+ */
275
+ interface CommandOutcome {
276
+ readonly exitCode: ExitCode;
277
+ /** The machine projection. Emitted verbatim under `--json`, on stdout, success or failure. */
278
+ readonly json: JsonObject;
279
+ /** The human projection. Already wrapped; the formatter adds only a trailing newline. */
280
+ readonly text: string;
281
+ /**
282
+ * Where the human projection goes. Diagnostics belong on stderr so that `everfur plan > out`
283
+ * still shows the operator why nothing useful landed in the file.
284
+ */
285
+ readonly stream: 'stdout' | 'stderr';
286
+ }
287
+
288
+ /**
289
+ * Everything a command is allowed to touch, injected. There is no ambient `process.cwd()`,
290
+ * no ambient `process.env`, and no module-level filesystem. That is what lets every command
291
+ * be exercised in a unit test with a fixture directory and no child process.
292
+ */
293
+ interface CommandContext {
294
+ readonly fs: FileSystemPort;
295
+ /** Absolute working directory. Resolved once, in the bin shim. */
296
+ readonly cwd: string;
297
+ readonly env: Readonly<Record<string, string | undefined>>;
298
+ /** The CLI's own version, stamped into every document it emits. */
299
+ readonly version: string;
300
+ /**
301
+ * The network seam. Injected for the same reason the filesystem is: a test scripts it and no
302
+ * socket opens. `src/bin` supplies the one real adapter.
303
+ */
304
+ readonly transport: TransportPort;
305
+ /** Waiting, injected so a poll loop in a test costs nothing. */
306
+ readonly clock: ClockPort;
307
+ }
308
+ /**
309
+ * A positional argument. Declared as data beside the flags so `--help` documents it and the
310
+ * orchestrator can enforce arity, rather than each command counting its own leftovers.
311
+ */
312
+ interface PositionalSpec {
313
+ readonly name: string;
314
+ readonly summary: string;
315
+ readonly required: boolean;
316
+ }
317
+ /**
318
+ * A command is data plus one pure-ish function. `run` may read the filesystem through the
319
+ * injected port and nothing else: no writing, no network, no stream, no exit.
320
+ *
321
+ * Adding a future command is adding one of these to the registry array. No other file changes,
322
+ * which is the property this scaffold exists to establish before there are five more commands.
323
+ */
324
+ interface Command {
325
+ /** Command path tokens, for example `["contract", "validate"]`. Matched in order. */
326
+ readonly path: readonly string[];
327
+ readonly summary: string;
328
+ /** Usage line shown by `--help`, without the leading `everfur `. */
329
+ readonly usage: string;
330
+ readonly flags: readonly FlagSpec[];
331
+ /** Ordered positional arguments this command accepts. Absent means it accepts none. */
332
+ readonly positionals?: readonly PositionalSpec[];
333
+ /**
334
+ * The credentials this command can actually use, declared as data for the same reason the
335
+ * flags are: `--help` renders what the command declares, so a credential a command reads is a
336
+ * credential its own help names. Absent means the command touches none, which is the truth
337
+ * for `plan`, `contract validate`, and `explain-error`.
338
+ *
339
+ * This exists because per-command help used to render only the flags. A partner told to run
340
+ * one command had no way to learn from the tool that they must export EVERFUR_PARTNER_KEY;
341
+ * they found out from a `credential_missing` failure at runtime instead.
342
+ */
343
+ readonly credentials?: readonly CredentialSpec[];
344
+ run(context: CommandContext, flags: BoundFlags, positionals: readonly string[]): Promise<CommandOutcome>;
345
+ }
346
+
347
+ /**
348
+ * THE single output formatter. Every byte the CLI writes is produced here, from a
349
+ * CommandOutcome, which is why no command needs a stream and why `--json` needed no per-command
350
+ * support.
351
+ *
352
+ * The rule, chosen once so scripts can rely on it:
353
+ *
354
+ * - with `--json`, the machine document goes to stdout ALWAYS, success or failure. A caller
355
+ * that has to parse a failure differently from a success ends up parsing stderr, and then
356
+ * the contract is prose.
357
+ * - without `--json`, human text goes to stdout on success and stderr on failure, so
358
+ * `everfur plan > plan.txt` still shows the operator why the file is empty.
359
+ * - exactly one trailing newline on each non-empty stream, and nothing at all on the other.
360
+ */
361
+ interface FormattedOutput {
362
+ readonly stdout: string;
363
+ readonly stderr: string;
364
+ readonly exitCode: number;
365
+ }
366
+ interface FormatOptions {
367
+ readonly json: boolean;
368
+ }
369
+ declare function formatOutcome(outcome: CommandOutcome, options: FormatOptions): FormattedOutput;
370
+
371
+ /**
372
+ * The orchestrator: argv in, formatted output plus exit code out. It writes nothing and exits
373
+ * nothing, so the whole CLI including its exit codes can be asserted in a unit test without a
374
+ * child process. `src/bin/everfur.ts` is the only file that turns this return value into an
375
+ * actual process effect.
376
+ *
377
+ * `--json` is read from the raw parse rather than from bound flags, because the formatter has
378
+ * to honour it even for an invocation whose flags failed to bind. A usage error under `--json`
379
+ * still emits a JSON error document.
380
+ */
381
+ interface RunOptions {
382
+ readonly argv: readonly string[];
383
+ readonly context: CommandContext;
384
+ readonly commands?: readonly Command[];
385
+ }
386
+ declare function runCli(options: RunOptions): Promise<FormattedOutput>;
387
+
388
+ /**
389
+ * THE REGISTRY. Exactly the commands this release ships, and nothing else.
390
+ *
391
+ * `login` and `test message` are deliberately ABSENT. They are described in ROADMAP.md and have
392
+ * no code here, registered or otherwise. A stub that resolves to "not implemented" is worse than
393
+ * a missing command: it teaches a partner that the command exists and then wastes their time,
394
+ * and it invites a half-implementation to accrete behind it. `everfur login` prints
395
+ * "unknown command", which is the truth.
396
+ *
397
+ * Longer paths are matched first so `contract validate` and `sandbox create` win over any future
398
+ * bare `contract` or `sandbox`.
399
+ */
400
+ declare const COMMANDS: readonly Command[];
401
+ interface CommandMatch {
402
+ readonly command: Command;
403
+ /** Argv path tokens the command consumed. */
404
+ readonly consumed: number;
405
+ }
406
+ /**
407
+ * Resolve a command path prefix to a command. Returns null when nothing matches, which the
408
+ * caller renders as a usage error rather than falling back to a default command.
409
+ */
410
+ declare function matchCommand(path: readonly string[], commands?: readonly Command[]): CommandMatch | null;
411
+
412
+ /**
413
+ * The one real transport adapter, and the only place in the package that opens a socket.
414
+ * Everything it can go wrong with collapses to a `TransportError` with a kind, so the client
415
+ * above it branches on two values rather than on the shape of an undici error.
416
+ *
417
+ * `AbortSignal.timeout` bounds the whole attempt including the body read, which a
418
+ * `setTimeout` around the fetch promise would not: a response whose headers arrive quickly and
419
+ * whose body never finishes would hang forever under the naive version.
420
+ *
421
+ * No redirect following, because a redirect off the configured base URL would carry the
422
+ * partner's credential headers to whatever host answered.
423
+ */
424
+ declare const fetchTransport: TransportPort;
425
+
426
+ /**
427
+ * WHERE THE CLI TALKS TO, resolved once and passed down. Two sources, in this order:
428
+ *
429
+ * 1. `--base-url <url>`, which wins.
430
+ * 2. `EVERFUR_API_BASE_URL`, the configured default for a shell that works against one
431
+ * environment all day.
432
+ *
433
+ * There is deliberately NO built-in default. A partner CLI that silently points at production
434
+ * when nothing is configured is one typo away from an operator diagnosing staging and reading a
435
+ * production answer, and the failure is invisible because both answers look plausible. Absent
436
+ * configuration is a usage error that names both ways to fix it.
437
+ *
438
+ * `API_PREFIX` is appended by this module rather than being part of what the operator types,
439
+ * because it is a deployment fact of the API and not a choice. A base URL that already ends in
440
+ * the prefix is accepted as-is, so both spellings work and neither doubles up.
441
+ */
442
+ declare const BASE_URL_ENV_VAR = "EVERFUR_API_BASE_URL";
443
+ /** `app/main.py` mounts every router used here at `settings.api_prefix`. */
444
+ declare const API_PREFIX = "/api/v1";
445
+ interface ResolvedEndpoint {
446
+ /** Normalised, without a trailing slash. Safe to print: it is never a credential. */
447
+ readonly baseUrl: string;
448
+ /** True when the operator's base URL already carried `API_PREFIX`. */
449
+ readonly prefixIncluded: boolean;
450
+ readonly source: 'flag' | 'env';
451
+ }
452
+ type EndpointResolution = {
453
+ readonly ok: true;
454
+ readonly endpoint: ResolvedEndpoint;
455
+ } | {
456
+ readonly ok: false;
457
+ readonly errors: readonly string[];
458
+ };
459
+ declare function resolveEndpoint(flagValue: string | null, env: Readonly<Record<string, string | undefined>>): EndpointResolution;
460
+ /**
461
+ * Join an API-relative path (one starting `/widget/v1/...` or `/admin/...`) onto the resolved
462
+ * base, inserting `API_PREFIX` exactly once.
463
+ */
464
+ declare function apiUrl(endpoint: ResolvedEndpoint, path: string): string;
465
+
466
+ /**
467
+ * The JSON Schema files are the single source of truth for validation, and the TypeScript types
468
+ * beside them are the compile-time view of the same contract. Nothing hand-rolls a second
469
+ * validator: `tests/contract` runs real emitted output through these compiled schemas, so a
470
+ * type that drifts from its schema fails the suite rather than shipping.
471
+ *
472
+ * The schemas are imported, not read from disk at runtime, so a globally installed CLI cannot
473
+ * end up validating against a schema file that a different version left lying around.
474
+ *
475
+ * Compilation is lazy and memoised: a `plan` run that never emits JSON should not pay for ajv
476
+ * compiling a schema it will not use.
477
+ */
478
+ interface SchemaViolation {
479
+ /** JSON Pointer into the validated document, for example `/steps/2/phase`. */
480
+ readonly path: string;
481
+ readonly message: string;
482
+ }
483
+ interface SchemaValidation {
484
+ readonly valid: boolean;
485
+ readonly violations: readonly SchemaViolation[];
486
+ }
487
+ /** Validate a document against the committed plan contract. */
488
+ declare function validatePlanDocument(document: unknown): SchemaValidation;
489
+ /** Validate a document against the contract-public envelope contract. */
490
+ declare function validateContractPublicEnvelope(document: unknown): SchemaValidation;
491
+ /** Validate a document against the committed `everfur diagnose --json` contract. */
492
+ declare function validateDiagnosisDocument(document: unknown): SchemaValidation;
493
+ /** Validate a document against the committed `everfur sandbox create --json` contract. */
494
+ declare function validateSandboxCreateDocument(document: unknown): SchemaValidation;
495
+ /** Validate a document against the committed `everfur verify --json` contract. */
496
+ declare function validateVerificationDocument(document: unknown): SchemaValidation;
497
+ /** Validate a document against the committed `everfur explain-error --json` contract. */
498
+ declare function validateErrorExplanationDocument(document: unknown): SchemaValidation;
499
+ /** The raw schema objects, exported so tests and downstream tools can assert against them. */
500
+ declare const SCHEMAS: Readonly<{
501
+ plan: {
502
+ $schema: string;
503
+ $id: string;
504
+ title: string;
505
+ description: string;
506
+ type: string;
507
+ required: string[];
508
+ properties: {
509
+ schemaVersion: {
510
+ const: number;
511
+ };
512
+ kind: {
513
+ const: string;
514
+ };
515
+ generatedBy: {
516
+ type: string;
517
+ required: string[];
518
+ properties: {
519
+ name: {
520
+ type: string;
521
+ minLength: number;
522
+ };
523
+ version: {
524
+ type: string;
525
+ minLength: number;
526
+ };
527
+ };
528
+ };
529
+ status: {
530
+ enum: string[];
531
+ };
532
+ detected: {
533
+ $ref: string;
534
+ };
535
+ supplied: {
536
+ type: string;
537
+ required: string[];
538
+ properties: {
539
+ capabilities: {
540
+ description: string;
541
+ oneOf: ({
542
+ type: string;
543
+ minItems?: never;
544
+ items?: never;
545
+ uniqueItems?: never;
546
+ } | {
547
+ type: string;
548
+ minItems: number;
549
+ items: {
550
+ $ref: string;
551
+ };
552
+ uniqueItems: boolean;
553
+ })[];
554
+ };
555
+ identityMode: {
556
+ oneOf: ({
557
+ type: string;
558
+ $ref?: never;
559
+ } | {
560
+ $ref: string;
561
+ type?: never;
562
+ })[];
563
+ };
564
+ sessionMintLanguage: {
565
+ description: string;
566
+ oneOf: ({
567
+ type: string;
568
+ $ref?: never;
569
+ } | {
570
+ $ref: string;
571
+ type?: never;
572
+ })[];
573
+ };
574
+ };
575
+ };
576
+ missingFacts: {
577
+ type: string;
578
+ items: {
579
+ $ref: string;
580
+ };
581
+ };
582
+ steps: {
583
+ type: string;
584
+ items: {
585
+ $ref: string;
586
+ };
587
+ };
588
+ blocked: {
589
+ description: string;
590
+ type: string;
591
+ items: {
592
+ $ref: string;
593
+ };
594
+ };
595
+ principles: {
596
+ description: string;
597
+ type: string;
598
+ items: {
599
+ $ref: string;
600
+ };
601
+ };
602
+ warnings: {
603
+ type: string;
604
+ items: {
605
+ $ref: string;
606
+ };
607
+ };
608
+ decisionSpace: {
609
+ description: string;
610
+ oneOf: ({
611
+ type: string;
612
+ $ref?: never;
613
+ } | {
614
+ $ref: string;
615
+ type?: never;
616
+ })[];
617
+ };
618
+ };
619
+ allOf: ({
620
+ description: string;
621
+ if: {
622
+ properties: {
623
+ status: {
624
+ const: string;
625
+ };
626
+ };
627
+ required: string[];
628
+ };
629
+ then: {
630
+ properties: {
631
+ missingFacts: {
632
+ minItems: number;
633
+ maxItems?: never;
634
+ };
635
+ steps: {
636
+ maxItems: number;
637
+ minItems?: never;
638
+ };
639
+ blocked?: never;
640
+ };
641
+ required?: never;
642
+ $comment?: never;
643
+ };
644
+ } | {
645
+ description: string;
646
+ if: {
647
+ properties: {
648
+ status: {
649
+ const: string;
650
+ };
651
+ };
652
+ required: string[];
653
+ };
654
+ then: {
655
+ properties: {
656
+ missingFacts: {
657
+ maxItems: number;
658
+ minItems?: never;
659
+ };
660
+ steps: {
661
+ minItems: number;
662
+ maxItems?: never;
663
+ };
664
+ blocked?: never;
665
+ };
666
+ required: string[];
667
+ $comment: string;
668
+ };
669
+ } | {
670
+ description: string;
671
+ if: {
672
+ properties: {
673
+ status: {
674
+ const: string;
675
+ };
676
+ };
677
+ required: string[];
678
+ };
679
+ then: {
680
+ properties: {
681
+ missingFacts: {
682
+ maxItems: number;
683
+ minItems?: never;
684
+ };
685
+ steps: {
686
+ maxItems: number;
687
+ minItems?: never;
688
+ };
689
+ blocked: {
690
+ minItems: number;
691
+ };
692
+ };
693
+ required?: never;
694
+ $comment?: never;
695
+ };
696
+ })[];
697
+ $defs: {
698
+ OptionId: {
699
+ description: string;
700
+ type: string;
701
+ pattern: string;
702
+ };
703
+ Capability: {
704
+ description: string;
705
+ $ref: string;
706
+ };
707
+ IdentityMode: {
708
+ description: string;
709
+ $ref: string;
710
+ };
711
+ ProjectType: {
712
+ enum: string[];
713
+ };
714
+ PackageManager: {
715
+ enum: string[];
716
+ };
717
+ Detection: {
718
+ type: string;
719
+ required: string[];
720
+ properties: {
721
+ root: {
722
+ type: string;
723
+ minLength: number;
724
+ };
725
+ projectType: {
726
+ $ref: string;
727
+ };
728
+ projectTypeEvidence: {
729
+ type: string;
730
+ items: {
731
+ type: string;
732
+ };
733
+ };
734
+ packageManager: {
735
+ $ref: string;
736
+ };
737
+ packageManagerEvidence: {
738
+ type: string;
739
+ items: {
740
+ type: string;
741
+ };
742
+ };
743
+ dependencies: {
744
+ type: string;
745
+ items: {
746
+ $ref: string;
747
+ };
748
+ };
749
+ };
750
+ };
751
+ Dependency: {
752
+ type: string;
753
+ required: string[];
754
+ properties: {
755
+ name: {
756
+ type: string;
757
+ minLength: number;
758
+ };
759
+ declared: {
760
+ oneOf: {
761
+ type: string;
762
+ }[];
763
+ };
764
+ installed: {
765
+ oneOf: {
766
+ type: string;
767
+ }[];
768
+ };
769
+ source: {
770
+ enum: string[];
771
+ };
772
+ scope: {
773
+ enum: string[];
774
+ };
775
+ };
776
+ };
777
+ BlockedCapability: {
778
+ type: string;
779
+ required: string[];
780
+ properties: {
781
+ capability: {
782
+ $ref: string;
783
+ };
784
+ reason: {
785
+ type: string;
786
+ minLength: number;
787
+ };
788
+ unblockOwner: {
789
+ type: string;
790
+ minLength: number;
791
+ };
792
+ requestPath: {
793
+ description: string;
794
+ type: string;
795
+ pattern: string;
796
+ };
797
+ capabilityKeys: {
798
+ description: string;
799
+ type: string;
800
+ items: {
801
+ type: string;
802
+ minLength: number;
803
+ };
804
+ };
805
+ };
806
+ };
807
+ PlanPrinciple: {
808
+ type: string;
809
+ required: string[];
810
+ properties: {
811
+ treeId: {
812
+ $ref: string;
813
+ };
814
+ id: {
815
+ $ref: string;
816
+ };
817
+ title: {
818
+ type: string;
819
+ minLength: number;
820
+ };
821
+ statement: {
822
+ type: string;
823
+ minLength: number;
824
+ };
825
+ docPath: {
826
+ oneOf: ({
827
+ type: string;
828
+ pattern?: never;
829
+ } | {
830
+ type: string;
831
+ pattern: string;
832
+ })[];
833
+ };
834
+ };
835
+ };
836
+ DecisionSpaceProvenance: {
837
+ type: string;
838
+ required: string[];
839
+ properties: {
840
+ source: {
841
+ type: string;
842
+ minLength: number;
843
+ };
844
+ digest: {
845
+ type: string;
846
+ pattern: string;
847
+ };
848
+ trees: {
849
+ type: string;
850
+ items: {
851
+ $ref: string;
852
+ };
853
+ };
854
+ };
855
+ };
856
+ MissingFact: {
857
+ type: string;
858
+ required: string[];
859
+ properties: {
860
+ fact: {
861
+ enum: string[];
862
+ };
863
+ reason: {
864
+ type: string;
865
+ minLength: number;
866
+ };
867
+ supplyWith: {
868
+ type: string;
869
+ minLength: number;
870
+ };
871
+ };
872
+ };
873
+ PlanStep: {
874
+ type: string;
875
+ required: string[];
876
+ properties: {
877
+ id: {
878
+ type: string;
879
+ pattern: string;
880
+ };
881
+ phase: {
882
+ enum: string[];
883
+ };
884
+ title: {
885
+ type: string;
886
+ minLength: number;
887
+ };
888
+ detail: {
889
+ type: string;
890
+ minLength: number;
891
+ };
892
+ commands: {
893
+ type: string;
894
+ items: {
895
+ type: string;
896
+ minLength: number;
897
+ };
898
+ };
899
+ files: {
900
+ type: string;
901
+ items: {
902
+ type: string;
903
+ minLength: number;
904
+ };
905
+ };
906
+ };
907
+ };
908
+ PlanWarning: {
909
+ type: string;
910
+ required: string[];
911
+ properties: {
912
+ code: {
913
+ type: string;
914
+ pattern: string;
915
+ };
916
+ message: {
917
+ type: string;
918
+ minLength: number;
919
+ };
920
+ };
921
+ };
922
+ };
923
+ };
924
+ contractPublic: {
925
+ $schema: string;
926
+ $id: string;
927
+ title: string;
928
+ description: string;
929
+ type: string;
930
+ required: string[];
931
+ properties: {
932
+ schemaVersion: {
933
+ const: number;
934
+ };
935
+ kind: {
936
+ const: string;
937
+ };
938
+ generatedAt: {
939
+ type: string;
940
+ format: string;
941
+ description: string;
942
+ };
943
+ digest: {
944
+ type: string;
945
+ required: string[];
946
+ properties: {
947
+ algorithm: {
948
+ const: string;
949
+ description: string;
950
+ };
951
+ value: {
952
+ type: string;
953
+ pattern: string;
954
+ description: string;
955
+ };
956
+ };
957
+ };
958
+ projection: {
959
+ type: string;
960
+ description: string;
961
+ };
962
+ };
963
+ };
964
+ diagnosis: {
965
+ $schema: string;
966
+ $id: string;
967
+ title: string;
968
+ description: string;
969
+ type: string;
970
+ required: string[];
971
+ properties: {
972
+ schemaVersion: {
973
+ const: number;
974
+ };
975
+ kind: {
976
+ const: string;
977
+ };
978
+ generatedBy: {
979
+ $ref: string;
980
+ };
981
+ endpoint: {
982
+ type: string;
983
+ required: string[];
984
+ properties: {
985
+ baseUrl: {
986
+ type: string;
987
+ minLength: number;
988
+ };
989
+ path: {
990
+ type: string;
991
+ minLength: number;
992
+ };
993
+ };
994
+ };
995
+ requestId: {
996
+ description: string;
997
+ oneOf: ({
998
+ type: string;
999
+ minLength?: never;
1000
+ } | {
1001
+ type: string;
1002
+ minLength: number;
1003
+ })[];
1004
+ };
1005
+ evaluatedAt: {
1006
+ type: string;
1007
+ };
1008
+ client: {
1009
+ type: string;
1010
+ required: string[];
1011
+ properties: {
1012
+ id: {
1013
+ type: string;
1014
+ };
1015
+ slug: {
1016
+ type: string;
1017
+ };
1018
+ };
1019
+ };
1020
+ auth: {
1021
+ type: string;
1022
+ required: string[];
1023
+ properties: {
1024
+ mode: {
1025
+ type: string;
1026
+ };
1027
+ anonymous: {
1028
+ type: string;
1029
+ };
1030
+ userRefPrefix: {
1031
+ description: string;
1032
+ type: string;
1033
+ maxLength: number;
1034
+ };
1035
+ userRole: {
1036
+ type: string;
1037
+ };
1038
+ roleSource: {
1039
+ type: string;
1040
+ };
1041
+ sessionId: {
1042
+ type: string;
1043
+ };
1044
+ partnerKeyScopeResolved: {
1045
+ type: string;
1046
+ };
1047
+ };
1048
+ };
1049
+ scope: {
1050
+ type: string;
1051
+ required: string[];
1052
+ properties: {
1053
+ provisioned: {
1054
+ type: string;
1055
+ };
1056
+ resolverStatus: {
1057
+ type: string;
1058
+ };
1059
+ revision: {
1060
+ type: string;
1061
+ minimum: number;
1062
+ };
1063
+ region: {
1064
+ type: string;
1065
+ };
1066
+ levels: {
1067
+ type: string;
1068
+ items: {
1069
+ type: string;
1070
+ required: string[];
1071
+ properties: {
1072
+ level: {
1073
+ type: string;
1074
+ minimum: number;
1075
+ maximum: number;
1076
+ };
1077
+ kind: {
1078
+ type: string;
1079
+ };
1080
+ present: {
1081
+ type: string;
1082
+ };
1083
+ };
1084
+ };
1085
+ };
1086
+ };
1087
+ };
1088
+ summary: {
1089
+ type: string;
1090
+ required: string[];
1091
+ properties: {
1092
+ total: {
1093
+ type: string;
1094
+ minimum: number;
1095
+ };
1096
+ available: {
1097
+ type: string;
1098
+ minimum: number;
1099
+ };
1100
+ unavailable: {
1101
+ type: string;
1102
+ minimum: number;
1103
+ };
1104
+ byReasonCode: {
1105
+ description: string;
1106
+ type: string;
1107
+ additionalProperties: {
1108
+ type: string;
1109
+ minimum: number;
1110
+ };
1111
+ };
1112
+ };
1113
+ };
1114
+ capabilities: {
1115
+ type: string;
1116
+ items: {
1117
+ $ref: string;
1118
+ };
1119
+ };
1120
+ };
1121
+ $defs: {
1122
+ GeneratedBy: {
1123
+ type: string;
1124
+ required: string[];
1125
+ properties: {
1126
+ name: {
1127
+ type: string;
1128
+ minLength: number;
1129
+ };
1130
+ version: {
1131
+ type: string;
1132
+ minLength: number;
1133
+ };
1134
+ };
1135
+ };
1136
+ CapabilityDiagnosis: {
1137
+ type: string;
1138
+ required: string[];
1139
+ properties: {
1140
+ key: {
1141
+ type: string;
1142
+ minLength: number;
1143
+ };
1144
+ available: {
1145
+ type: string;
1146
+ };
1147
+ reasonCode: {
1148
+ description: string;
1149
+ type: string;
1150
+ minLength: number;
1151
+ };
1152
+ reason: {
1153
+ description: string;
1154
+ type: string;
1155
+ };
1156
+ entitlementReasonCode: {
1157
+ description: string;
1158
+ type: string;
1159
+ minLength: number;
1160
+ };
1161
+ decidedAtLevel: {
1162
+ oneOf: ({
1163
+ type: string;
1164
+ minimum?: never;
1165
+ maximum?: never;
1166
+ } | {
1167
+ type: string;
1168
+ minimum: number;
1169
+ maximum: number;
1170
+ })[];
1171
+ };
1172
+ decidedAtKind: {
1173
+ oneOf: {
1174
+ type: string;
1175
+ }[];
1176
+ };
1177
+ surface: {
1178
+ description: string;
1179
+ oneOf: {
1180
+ type: string;
1181
+ }[];
1182
+ };
1183
+ nextAction: {
1184
+ description: string;
1185
+ type: string;
1186
+ minLength: number;
1187
+ };
1188
+ };
1189
+ };
1190
+ };
1191
+ };
1192
+ sandboxCreate: {
1193
+ $schema: string;
1194
+ $id: string;
1195
+ title: string;
1196
+ description: string;
1197
+ type: string;
1198
+ required: string[];
1199
+ properties: {
1200
+ schemaVersion: {
1201
+ const: number;
1202
+ };
1203
+ kind: {
1204
+ const: string;
1205
+ };
1206
+ generatedBy: {
1207
+ type: string;
1208
+ required: string[];
1209
+ properties: {
1210
+ name: {
1211
+ type: string;
1212
+ minLength: number;
1213
+ };
1214
+ version: {
1215
+ type: string;
1216
+ minLength: number;
1217
+ };
1218
+ };
1219
+ };
1220
+ status: {
1221
+ enum: string[];
1222
+ };
1223
+ requestId: {
1224
+ oneOf: ({
1225
+ type: string;
1226
+ minLength?: never;
1227
+ } | {
1228
+ type: string;
1229
+ minLength: number;
1230
+ })[];
1231
+ };
1232
+ clientId: {
1233
+ type: string;
1234
+ };
1235
+ keyId: {
1236
+ type: string;
1237
+ minLength: number;
1238
+ };
1239
+ label: {
1240
+ oneOf: {
1241
+ type: string;
1242
+ }[];
1243
+ };
1244
+ createdAt: {
1245
+ oneOf: {
1246
+ type: string;
1247
+ }[];
1248
+ };
1249
+ claimUrl: {
1250
+ description: string;
1251
+ oneOf: ({
1252
+ type: string;
1253
+ minLength?: never;
1254
+ } | {
1255
+ type: string;
1256
+ minLength: number;
1257
+ })[];
1258
+ };
1259
+ claimExpiresAt: {
1260
+ oneOf: {
1261
+ type: string;
1262
+ }[];
1263
+ };
1264
+ secretDisclosed: {
1265
+ description: string;
1266
+ const: boolean;
1267
+ };
1268
+ secretDiscarded: {
1269
+ description: string;
1270
+ type: string;
1271
+ };
1272
+ orphanedKey: {
1273
+ description: string;
1274
+ enum: string[];
1275
+ };
1276
+ nextActions: {
1277
+ type: string;
1278
+ minItems: number;
1279
+ items: {
1280
+ type: string;
1281
+ minLength: number;
1282
+ };
1283
+ };
1284
+ };
1285
+ allOf: ({
1286
+ if: {
1287
+ properties: {
1288
+ status: {
1289
+ const: string;
1290
+ };
1291
+ };
1292
+ required: string[];
1293
+ };
1294
+ then: {
1295
+ properties: {
1296
+ claimUrl: {
1297
+ type: string;
1298
+ minLength?: never;
1299
+ };
1300
+ };
1301
+ };
1302
+ } | {
1303
+ if: {
1304
+ properties: {
1305
+ status: {
1306
+ const: string;
1307
+ };
1308
+ };
1309
+ required: string[];
1310
+ };
1311
+ then: {
1312
+ properties: {
1313
+ claimUrl: {
1314
+ type: string;
1315
+ minLength: number;
1316
+ };
1317
+ };
1318
+ };
1319
+ })[];
1320
+ };
1321
+ verification: {
1322
+ $schema: string;
1323
+ $id: string;
1324
+ title: string;
1325
+ description: string;
1326
+ type: string;
1327
+ required: string[];
1328
+ properties: {
1329
+ schemaVersion: {
1330
+ const: number;
1331
+ };
1332
+ kind: {
1333
+ const: string;
1334
+ };
1335
+ generatedBy: {
1336
+ type: string;
1337
+ required: string[];
1338
+ properties: {
1339
+ name: {
1340
+ type: string;
1341
+ minLength: number;
1342
+ };
1343
+ version: {
1344
+ type: string;
1345
+ minLength: number;
1346
+ };
1347
+ };
1348
+ };
1349
+ status: {
1350
+ description: string;
1351
+ enum: string[];
1352
+ };
1353
+ baseUrl: {
1354
+ type: string;
1355
+ minLength: number;
1356
+ };
1357
+ summary: {
1358
+ type: string;
1359
+ required: string[];
1360
+ properties: {
1361
+ passed: {
1362
+ type: string;
1363
+ minimum: number;
1364
+ };
1365
+ failed: {
1366
+ type: string;
1367
+ minimum: number;
1368
+ };
1369
+ skipped: {
1370
+ type: string;
1371
+ minimum: number;
1372
+ };
1373
+ };
1374
+ };
1375
+ steps: {
1376
+ type: string;
1377
+ minItems: number;
1378
+ items: {
1379
+ type: string;
1380
+ required: string[];
1381
+ properties: {
1382
+ id: {
1383
+ type: string;
1384
+ minLength: number;
1385
+ };
1386
+ title: {
1387
+ type: string;
1388
+ minLength: number;
1389
+ };
1390
+ result: {
1391
+ enum: string[];
1392
+ };
1393
+ reason: {
1394
+ description: string;
1395
+ type: string;
1396
+ minLength: number;
1397
+ };
1398
+ requestId: {
1399
+ oneOf: ({
1400
+ type: string;
1401
+ minLength?: never;
1402
+ } | {
1403
+ type: string;
1404
+ minLength: number;
1405
+ })[];
1406
+ };
1407
+ details: {
1408
+ type: string;
1409
+ items: {
1410
+ type: string;
1411
+ };
1412
+ };
1413
+ };
1414
+ };
1415
+ };
1416
+ };
1417
+ };
1418
+ errorExplanation: {
1419
+ $schema: string;
1420
+ $id: string;
1421
+ title: string;
1422
+ description: string;
1423
+ type: string;
1424
+ required: string[];
1425
+ properties: {
1426
+ schemaVersion: {
1427
+ const: number;
1428
+ };
1429
+ kind: {
1430
+ const: string;
1431
+ };
1432
+ generatedBy: {
1433
+ type: string;
1434
+ required: string[];
1435
+ properties: {
1436
+ name: {
1437
+ type: string;
1438
+ minLength: number;
1439
+ };
1440
+ version: {
1441
+ type: string;
1442
+ minLength: number;
1443
+ };
1444
+ };
1445
+ };
1446
+ code: {
1447
+ description: string;
1448
+ type: string;
1449
+ minLength: number;
1450
+ };
1451
+ source: {
1452
+ type: string;
1453
+ required: string[];
1454
+ properties: {
1455
+ path: {
1456
+ type: string;
1457
+ minLength: number;
1458
+ };
1459
+ digest: {
1460
+ type: string;
1461
+ pattern: string;
1462
+ };
1463
+ };
1464
+ };
1465
+ envelope: {
1466
+ type: string;
1467
+ minLength: number;
1468
+ };
1469
+ httpStatus: {
1470
+ type: string;
1471
+ minimum: number;
1472
+ };
1473
+ namingFamily: {
1474
+ type: string;
1475
+ minLength: number;
1476
+ };
1477
+ retryable: {
1478
+ type: string;
1479
+ };
1480
+ confirmedOnWire: {
1481
+ description: string;
1482
+ type: string;
1483
+ };
1484
+ surfaces: {
1485
+ type: string;
1486
+ items: {
1487
+ type: string;
1488
+ };
1489
+ };
1490
+ cause: {
1491
+ type: string;
1492
+ };
1493
+ display: {
1494
+ description: string;
1495
+ type: string;
1496
+ };
1497
+ docUrl: {
1498
+ type: string;
1499
+ };
1500
+ nextAction: {
1501
+ type: string;
1502
+ minLength: number;
1503
+ };
1504
+ };
1505
+ };
1506
+ }>;
1507
+
1508
+ /**
1509
+ * THE DIGEST RULE, stated once so both sides of the wire can implement it independently:
1510
+ *
1511
+ * digest.value = lowercase hex sha256( utf8( canonicalJson( envelope.projection ) ) )
1512
+ *
1513
+ * Three consequences worth being explicit about, because each one is a bug someone would
1514
+ * otherwise hit:
1515
+ *
1516
+ * 1. The digest covers `projection` ONLY. `generatedAt`, `kind`, `schemaVersion`, and the
1517
+ * digest field itself are outside it, so re-stamping a projection with a new timestamp does
1518
+ * not invalidate a digest, and the producer never has to solve the self-reference problem
1519
+ * of hashing a document that contains its own hash.
1520
+ * 2. Canonicalisation sorts object keys, so a producer that serialises in insertion order and
1521
+ * a producer that serialises alphabetically agree.
1522
+ * 3. The comparison is constant time. A digest is not a secret, so this is belt and braces
1523
+ * rather than a requirement, but the cost is a few microseconds and the habit is worth more.
1524
+ */
1525
+ interface DigestCheck {
1526
+ readonly matches: boolean;
1527
+ readonly expected: string;
1528
+ readonly actual: string;
1529
+ }
1530
+ /** Compute the digest of a projection value under the rule above. */
1531
+ declare function digestOfProjection(projection: JsonValue): string;
1532
+ /** Length-independent, value-independent comparison of two hex digests. */
1533
+ declare function digestsEqual(left: string, right: string): boolean;
1534
+ /** Recompute and compare in one step. */
1535
+ declare function verifyDigest(projection: JsonValue, declared: string): DigestCheck;
1536
+
1537
+ /**
1538
+ * Load a contract-public projection from disk and answer three questions in order, stopping at
1539
+ * the first failure because the later answers are meaningless without the earlier ones:
1540
+ *
1541
+ * 1. Is it there and is it JSON?
1542
+ * 2. Does the envelope match the committed schema?
1543
+ * 3. Does the declared digest match the projection it wraps?
1544
+ *
1545
+ * Entirely offline and entirely local. The real projection is produced by the contract lane;
1546
+ * this reads whatever file it is pointed at, which is why it works today against a fixture and
1547
+ * will work unchanged against the real artifact.
1548
+ */
1549
+ type ContractValidationOutcome = {
1550
+ readonly kind: 'file_not_found';
1551
+ readonly path: string;
1552
+ } | {
1553
+ readonly kind: 'invalid_json';
1554
+ readonly path: string;
1555
+ readonly detail: string;
1556
+ } | {
1557
+ readonly kind: 'invalid_contract';
1558
+ readonly path: string;
1559
+ readonly violations: readonly SchemaViolation[];
1560
+ } | {
1561
+ readonly kind: 'digest_mismatch';
1562
+ readonly path: string;
1563
+ readonly digest: DigestCheck;
1564
+ } | {
1565
+ readonly kind: 'valid';
1566
+ readonly path: string;
1567
+ readonly digest: DigestCheck;
1568
+ readonly generatedAt: string;
1569
+ readonly projectionKeys: readonly string[];
1570
+ /**
1571
+ * The parsed projection this validation just proved intact.
1572
+ *
1573
+ * Carried out rather than thrown away because the alternative is what the error registry
1574
+ * used to do: call this function, take the keys, then re-read and re-JSON.parse the SAME
1575
+ * file. Two reads of one path is a race (a projection rewritten between them by a
1576
+ * concurrent `tools.contract.compile --write` surfaces as a raw SyntaxError escaping a
1577
+ * command, in a package whose whole design is that every anticipated failure settles as a
1578
+ * CommandOutcome), and the second parse had no try/catch while the one above does.
1579
+ */
1580
+ readonly projection: JsonObject;
1581
+ };
1582
+ /** Resolve a possibly relative `--path` against the injected cwd. Never touches process.cwd. */
1583
+ declare function resolveContractPath(cwd: string, path: string): string;
1584
+ declare function validateContractFile(fs: FileSystemPort, path: string): ContractValidationOutcome;
1585
+
1586
+ /**
1587
+ * THE ERROR REGISTRY, read from the contract-public projection rather than restated here.
1588
+ *
1589
+ * The platform compiles `contract/errors.yaml` into the redacted projection; this module reads
1590
+ * the `errors.yaml` document out of that projection and nothing else. Copying the table into
1591
+ * TypeScript would have been less code today and a guaranteed drift tomorrow: the wire codes
1592
+ * change when a handler changes, and only the compiler sees that happen.
1593
+ *
1594
+ * The registry is reachable ONLY through `validateContractFile`, so the envelope schema and the
1595
+ * digest are both checked before a single code is trusted. An operator running
1596
+ * `everfur explain-error` against a file someone edited gets a digest mismatch, not a confident
1597
+ * answer from a tampered table.
1598
+ *
1599
+ * `internal` and `evidence` are absent from the projection by design (the compiler redacts
1600
+ * them), which is why this shape has no field for either.
1601
+ */
1602
+ interface ErrorRegistryEntry {
1603
+ /** Exactly the string the wire carries. Not normalised; three naming families are live. */
1604
+ readonly code: string;
1605
+ readonly envelope: string;
1606
+ readonly httpStatus: number;
1607
+ readonly namingFamily: string;
1608
+ readonly retryable: boolean;
1609
+ /** False means the code exists in source but no live route was traced emitting it. */
1610
+ readonly confirmedOnWire: boolean;
1611
+ readonly surfaces: readonly string[];
1612
+ /** Safe copy a client may show a person as-is. */
1613
+ readonly display: string;
1614
+ readonly docUrl: string;
1615
+ /** The mechanical cause, authored by the contract lane. */
1616
+ readonly note: string;
1617
+ }
1618
+ interface ErrorRegistry {
1619
+ lookup(code: string): ErrorRegistryEntry | null;
1620
+ readonly codes: readonly string[];
1621
+ }
1622
+ /**
1623
+ * The registry a command falls back to when no projection was supplied. Every lookup misses,
1624
+ * which downgrades an HTTP failure's rendering to code plus status rather than inventing copy.
1625
+ */
1626
+ declare const EMPTY_REGISTRY: ErrorRegistry;
1627
+ /** The projection document key the compiler writes the registry under. */
1628
+ declare const ERRORS_DOCUMENT_KEY = "errors.yaml";
1629
+ /**
1630
+ * Narrow one raw projection row. A row missing `code` is dropped rather than defaulted: an
1631
+ * entry whose identity is unknown cannot be looked up, so keeping it would only inflate a count.
1632
+ * Every other field falls back to a neutral value, because a registry that refuses to load over
1633
+ * one missing `note` is a registry that stops answering during the incident it exists for.
1634
+ */
1635
+ declare function entryFromRow(row: unknown): ErrorRegistryEntry | null;
1636
+ type RegistryLoad = {
1637
+ readonly kind: 'loaded';
1638
+ readonly registry: ErrorRegistry;
1639
+ readonly path: string;
1640
+ readonly digest: string;
1641
+ } | {
1642
+ readonly kind: 'no_registry';
1643
+ readonly path: string;
1644
+ } | {
1645
+ readonly kind: 'contract_rejected';
1646
+ readonly outcome: ContractValidationOutcome;
1647
+ };
1648
+ /**
1649
+ * Load the registry out of a contract-public envelope on disk. Contract failures are handed
1650
+ * back unchanged so the caller can reuse `contract validate`'s exact error rendering rather
1651
+ * than inventing a second, subtly different set of messages for the same three failures.
1652
+ */
1653
+ declare function loadErrorRegistry(fs: FileSystemPort, path: string): RegistryLoad;
1654
+
1655
+ declare const DEFAULT_TIMEOUT_MS = 10000;
1656
+ interface ApiSuccess<T> {
1657
+ readonly ok: true;
1658
+ readonly value: T;
1659
+ readonly status: number;
1660
+ /** The server's id, or null when the response carried none. */
1661
+ readonly requestId: string | null;
1662
+ readonly attempts: number;
1663
+ }
1664
+ type ApiFailure = {
1665
+ readonly ok: false;
1666
+ readonly kind: 'network';
1667
+ readonly method: HttpMethod;
1668
+ readonly path: string;
1669
+ readonly detail: string;
1670
+ readonly attempts: number;
1671
+ readonly requestId: null;
1672
+ } | {
1673
+ readonly ok: false;
1674
+ readonly kind: 'timeout';
1675
+ readonly method: HttpMethod;
1676
+ readonly path: string;
1677
+ readonly timeoutMs: number;
1678
+ readonly attempts: number;
1679
+ readonly requestId: null;
1680
+ } | {
1681
+ readonly ok: false;
1682
+ readonly kind: 'http';
1683
+ readonly method: HttpMethod;
1684
+ readonly path: string;
1685
+ readonly status: number;
1686
+ /** The wire code, or null when the body carried none. */
1687
+ readonly code: string | null;
1688
+ /**
1689
+ * UNTRUSTED server free text, kept for a debugger and NEVER rendered.
1690
+ *
1691
+ * This field used to be called `message` and used to be interpolated into the failure
1692
+ * summary, on the assumption that "the API's handlers emit vetted copy". They do not:
1693
+ * `grep 'detail=str(exc'` across the platform's api service finds twenty-odd handlers
1694
+ * that put a caught exception's `str()` straight into `detail`, so an upstream key, a
1695
+ * connection string, or a bearer becomes CLI output on a path where the operator typed
1696
+ * nothing sensitive. `describeFailure` now renders operator-owned data only.
1697
+ *
1698
+ * It is defined NON-ENUMERABLE (see `httpFailure` below), mirroring the SDK's
1699
+ * `EverfurError.wireMessage`, so the serialisers that normally walk an object,
1700
+ * `JSON.stringify`, spread, `Object.entries`, cannot carry it into a `--json` document
1701
+ * either. Only a deliberate read reaches it.
1702
+ */
1703
+ readonly wireMessage: string;
1704
+ readonly entry: ErrorRegistryEntry | null;
1705
+ readonly retryable: boolean;
1706
+ readonly requestId: string | null;
1707
+ readonly attempts: number;
1708
+ } | {
1709
+ readonly ok: false;
1710
+ readonly kind: 'malformed';
1711
+ readonly method: HttpMethod;
1712
+ readonly path: string;
1713
+ readonly status: number;
1714
+ readonly detail: string;
1715
+ readonly requestId: string | null;
1716
+ readonly attempts: number;
1717
+ };
1718
+ type ApiResult<T> = ApiSuccess<T> | ApiFailure;
1719
+ interface ApiClientOptions {
1720
+ readonly transport: TransportPort;
1721
+ readonly clock: ClockPort;
1722
+ readonly endpoint: ResolvedEndpoint;
1723
+ readonly registry?: ErrorRegistry;
1724
+ readonly timeoutMs?: number;
1725
+ /** Caller supplied correlation id. Sent only when it is a shape the API will reuse. */
1726
+ readonly requestId?: string | null;
1727
+ /** Attempts allowed for an IDEMPOTENT call. Non-idempotent calls always get exactly one. */
1728
+ readonly maxAttempts?: number;
1729
+ /** Delay between attempts, in milliseconds. */
1730
+ readonly retryDelayMs?: number;
1731
+ }
1732
+ interface RequestSpec {
1733
+ readonly method: HttpMethod;
1734
+ /** API-relative, for example `/widget/v1/diagnostics`. The prefix is added here. */
1735
+ readonly path: string;
1736
+ readonly headers?: Readonly<Record<string, string>>;
1737
+ /** Header name to credential. Revealed into the outgoing header map only. */
1738
+ readonly credentials?: Readonly<Record<string, Credential>>;
1739
+ readonly body?: JsonValue;
1740
+ /** Statuses that are a successful outcome for this call, in addition to 2xx. */
1741
+ readonly acceptStatuses?: readonly number[];
1742
+ }
1743
+ interface ApiClient {
1744
+ request(spec: RequestSpec): Promise<ApiResult<JsonValue>>;
1745
+ /** The resolved base URL, for reporting. Never a credential. */
1746
+ readonly baseUrl: string;
1747
+ }
1748
+ declare function clampTimeout(value: number): number;
1749
+ /** True when the value is a shape `RequestIDMiddleware` will reuse rather than replace. */
1750
+ declare function isCorrelatableRequestId(value: string): boolean;
1751
+ /**
1752
+ * Read the wire code out of an error body. Three shapes are live at once and the registry says
1753
+ * so, so this reads all three rather than pretending there is one:
1754
+ *
1755
+ * {"code": "...", "message": ...} the status-mapped envelope
1756
+ * {"error": "...", "message": ...} a dict-detail dependency, flattened
1757
+ * {"detail": {"error": "...", ...}} the same, unflattened by a passthrough handler
1758
+ *
1759
+ * THE CODE IS TRUSTED, THE TEXT IS NOT. `code` is a wire enum this CLI matches against the
1760
+ * compiled registry, so it is operator-safe to print. `wireMessage` is whatever the server
1761
+ * chose to say, from any of the four branches below, and every one of those branches can carry
1762
+ * a caught exception's `str()`. It is named `wireMessage` rather than `message` precisely so
1763
+ * that no future caller reads it as "the copy to show".
1764
+ */
1765
+ declare function errorCodeOf(body: unknown): {
1766
+ readonly code: string | null;
1767
+ readonly wireMessage: string;
1768
+ };
1769
+ declare function createApiClient(options: ApiClientOptions): ApiClient;
1770
+ /**
1771
+ * One-line human summary of a failure. Assembled from OPERATOR-OWNED DATA ONLY: the method and
1772
+ * path the operator's own command named, the status, the wire code, and, when that code
1773
+ * resolves, the registry's own vetted `display` copy. Never a credential and never server free
1774
+ * text, which are the same rule: this CLI cannot tell them apart at the point of rendering.
1775
+ *
1776
+ * The `network` and `malformed` details are Node's own (a DNS failure, a `JSON.parse` message),
1777
+ * not the server's, so they stay. The `http` case is the one that used to interpolate
1778
+ * `failure.wireMessage`, and that is exactly the string a handler builds from `str(exc)`.
1779
+ */
1780
+ declare function describeFailure(failure: ApiFailure): string;
1781
+ /** The detail lines a command appends under `describeFailure`. */
1782
+ declare function failureDetails(failure: ApiFailure): readonly string[];
1783
+
1784
+ declare function failureOutcome(failure: ApiFailure, extraDetails?: readonly string[]): CommandOutcome;
1785
+
1786
+ /**
1787
+ * Narrow `GET /widget/v1/diagnostics` into the CLI's own shape.
1788
+ *
1789
+ * Two rules, both deliberate:
1790
+ *
1791
+ * 1. STRUCTURAL fields are required. A body with no `capabilities` array is not a diagnosis,
1792
+ * and pretending it is would print a confident "0 capabilities" for what is actually a
1793
+ * proxy error page. Those cases surface as `malformed_response`, exit 6.
1794
+ * 2. VALUE fields are tolerated. A new reason code, a new lattice kind, or an added field is
1795
+ * the platform growing, and this CLI must keep answering across that. Unknown reason codes
1796
+ * are carried through verbatim and get the upgrade next-action.
1797
+ *
1798
+ * Field names are snake_case on the wire and camelCase here, converted once, at this boundary.
1799
+ */
1800
+ interface CapabilityDiagnosis {
1801
+ readonly key: string;
1802
+ readonly available: boolean;
1803
+ readonly reasonCode: string;
1804
+ readonly reason: string;
1805
+ readonly entitlementReasonCode: string;
1806
+ readonly decidedAtLevel: number | null;
1807
+ readonly decidedAtKind: string | null;
1808
+ readonly surface: string | null;
1809
+ /** CLI-authored. What the partner should do; see `reasons.ts`. */
1810
+ readonly nextAction: string;
1811
+ }
1812
+ interface ScopeLevelDiagnosis {
1813
+ readonly level: number;
1814
+ readonly kind: string;
1815
+ readonly present: boolean;
1816
+ }
1817
+ interface Diagnosis {
1818
+ readonly requestId: string | null;
1819
+ readonly evaluatedAt: string;
1820
+ readonly clientId: string;
1821
+ readonly clientSlug: string;
1822
+ readonly auth: {
1823
+ readonly mode: string;
1824
+ readonly anonymous: boolean;
1825
+ readonly userRefPrefix: string;
1826
+ readonly userRole: string;
1827
+ readonly roleSource: string;
1828
+ readonly sessionId: string;
1829
+ readonly partnerKeyScopeResolved: boolean;
1830
+ };
1831
+ readonly scope: {
1832
+ readonly provisioned: boolean;
1833
+ readonly resolverStatus: string;
1834
+ readonly revision: number;
1835
+ readonly region: string;
1836
+ readonly levels: readonly ScopeLevelDiagnosis[];
1837
+ };
1838
+ readonly capabilities: readonly CapabilityDiagnosis[];
1839
+ }
1840
+ type DiagnosisParse = {
1841
+ readonly ok: true;
1842
+ readonly diagnosis: Diagnosis;
1843
+ } | {
1844
+ readonly ok: false;
1845
+ readonly detail: string;
1846
+ };
1847
+ declare function parseDiagnostics(body: unknown, headerRequestId: string | null): DiagnosisParse;
1848
+
1849
+ interface DiagnosisDocumentInput {
1850
+ readonly diagnosis: Diagnosis;
1851
+ readonly baseUrl: string;
1852
+ readonly path: string;
1853
+ readonly cliVersion: string;
1854
+ }
1855
+ declare function buildDiagnosisDocument(input: DiagnosisDocumentInput): JsonObject;
1856
+
1857
+ /**
1858
+ * The human projection of a diagnosis. Same facts as the JSON, same order, nothing added.
1859
+ *
1860
+ * The unavailable list is GROUPED BY CAUSE rather than printed key by key. That is the whole
1861
+ * point of the command: a partner staring at eight unavailable capabilities needs to see that
1862
+ * they are eight symptoms of one unprovisioned scope, not eight problems. Each group prints its
1863
+ * machine code once, the server's copy once, and the next action once.
1864
+ */
1865
+ interface DiagnosisRenderInput {
1866
+ readonly diagnosis: Diagnosis;
1867
+ readonly baseUrl: string;
1868
+ }
1869
+ declare function renderDiagnosis(input: DiagnosisRenderInput): string;
1870
+
1871
+ /**
1872
+ * WHY A CAPABILITY IS UNAVAILABLE, and what to do about it.
1873
+ *
1874
+ * The nine codes are the platform's own `DiagnosticsReasonCode`, which exists because an empty
1875
+ * `capabilities:{}` verdict has five byte-identical causes and the render payload deliberately
1876
+ * carries no "why". The server sends the code and its own safe copy; this table adds the part
1877
+ * the server cannot know, which is what the PARTNER should do next.
1878
+ *
1879
+ * The distinction that makes this table worth having: three of these are somebody else's job
1880
+ * (`scope_not_provisioned`, `not_in_grant`, `runtime_gate_off`), one is a commercial
1881
+ * conversation (`outside_ceiling`), one is deliberate configuration (`capability_denied`), one
1882
+ * is transient (`resolver_unavailable`), and one is a bug to report (`gate_unknown`). A single
1883
+ * "the feature is unavailable" message collapses all seven into one wrong next step.
1884
+ *
1885
+ * An UNKNOWN code is not an error. The enum grows when the platform grows a new decision point,
1886
+ * and a CLI that rejected the answer would be useless exactly when something new broke. It
1887
+ * renders the code verbatim with an upgrade instruction.
1888
+ */
1889
+ declare const REASON_CODES: readonly ["granted", "scope_not_provisioned", "capability_denied", "outside_ceiling", "not_in_grant", "runtime_gate_off", "gate_unknown", "platform_invariant", "resolver_unavailable"];
1890
+ type ReasonCode = (typeof REASON_CODES)[number];
1891
+ declare function isKnownReasonCode(value: string): value is ReasonCode;
1892
+ /** The next action for a code, including one this CLI has never heard of. */
1893
+ declare function nextActionFor$1(code: string): string;
1894
+
1895
+ /**
1896
+ * THE SECRET FENCE. This module turns an admin mint response into the only shape this CLI is
1897
+ * allowed to hand back, and it is written so that a raw secret CANNOT travel through it.
1898
+ *
1899
+ * The rule, stated once and enforced by construction rather than by care:
1900
+ *
1901
+ * The projection reads a FIXED LIST OF FIELD NAMES off the response. There is no spread, no
1902
+ * `Object.entries` loop, no "copy everything except". `secret_value` is not in the list, so
1903
+ * no future field added to the response can arrive here by accident either.
1904
+ *
1905
+ * That mirrors the reasoning in the platform's own `_metadata` helper, which forbids a
1906
+ * `**record` splat for the same reason: the one field that must never reach a response is
1907
+ * exactly the field a splat would carry.
1908
+ *
1909
+ * THE SERVER-SIDE GAP. The approved design has this command return `{key_id, claim_url,
1910
+ * claim_expires_at}` and a human claiming the secret in a browser. The live route
1911
+ * (`POST /api/v1/admin/clients/{client_id}/secret-keys`) returns `secret_value` inline and no
1912
+ * claim URL at all: there is no claim flow on the server yet. This client models the approved
1913
+ * design, reads a `claim_url` when one appears, and reports `claim_unavailable` when it does
1914
+ * not. It never falls back to printing what it was handed. See DECISIONS.md, open question 6.
1915
+ *
1916
+ * AND IT CLEANS UP AFTER ITSELF. Discarding the secret is only half an answer: the key is
1917
+ * still live server side, so a command that stopped there would leave a credential nobody
1918
+ * holds behind on EVERY invocation against the live route, and then print an instruction
1919
+ * telling the operator to go and revoke it by hand. That is worse than the stub the registry's
1920
+ * own doctrine forbids, because it has a side effect. So `claim_unavailable` now REVOKES the
1921
+ * key it just minted, through the route's own idempotent
1922
+ * `DELETE /admin/clients/{client_id}/secret-keys/{key_id}`, and reports whether that worked.
1923
+ * The operator is only ever asked to revoke by hand when the revoke itself failed.
1924
+ */
1925
+ type SandboxCreateStatus = 'claim_issued' | 'claim_unavailable';
1926
+ /**
1927
+ * What happened to the key on the `claim_unavailable` path.
1928
+ *
1929
+ * not_needed a claim URL was issued, so the key is wanted and stays.
1930
+ * revoked the mint produced an unclaimable key and this command revoked it.
1931
+ * revoke_failed the revoke was attempted and did not succeed. THIS is the only case where a
1932
+ * live orphaned credential is left behind, and the only case where the report
1933
+ * asks a human to remove it.
1934
+ */
1935
+ type SandboxRevocation = 'not_needed' | 'revoked' | 'revoke_failed';
1936
+ interface SandboxCreation {
1937
+ readonly status: SandboxCreateStatus;
1938
+ readonly keyId: string;
1939
+ readonly clientId: string;
1940
+ readonly label: string | null;
1941
+ readonly createdAt: string | null;
1942
+ readonly claimUrl: string | null;
1943
+ readonly claimExpiresAt: string | null;
1944
+ /**
1945
+ * True when the response carried a raw secret that this CLI discarded unread. Reported
1946
+ * because it is operationally important: a credential now exists that nobody holds.
1947
+ */
1948
+ readonly secretDiscarded: boolean;
1949
+ }
1950
+ type SandboxCreateParse = {
1951
+ readonly ok: true;
1952
+ readonly creation: SandboxCreation;
1953
+ } | {
1954
+ readonly ok: false;
1955
+ readonly detail: string;
1956
+ };
1957
+ /**
1958
+ * Project the mint response. Every field is named; nothing is copied wholesale.
1959
+ *
1960
+ * `secretDiscarded` is derived by TESTING FOR PRESENCE ONLY. The value is never bound to a
1961
+ * variable, never returned, and never reaches a string, which is why a `--json` document and a
1962
+ * rendered report are both provably free of it whatever the server sent.
1963
+ */
1964
+ declare function projectSandboxCreate(body: unknown): SandboxCreateParse;
1965
+ interface SandboxDocumentInput {
1966
+ readonly creation: SandboxCreation;
1967
+ readonly revocation: SandboxRevocation;
1968
+ readonly requestId: string | null;
1969
+ readonly cliVersion: string;
1970
+ }
1971
+ /** The next actions for each status. Never contains a value, only names and commands. */
1972
+ declare function nextActionsFor(creation: SandboxCreation, revocation: SandboxRevocation): readonly string[];
1973
+ declare function buildSandboxDocument(input: SandboxDocumentInput): JsonObject;
1974
+ /** The human projection. */
1975
+ declare function renderSandboxCreate(creation: SandboxCreation, revocation: SandboxRevocation, requestId: string | null): string;
1976
+
1977
+ /**
1978
+ * THE WIRE EXERCISE. Three steps, run in order, each of which reports what happened rather than
1979
+ * what the process is about to exit with.
1980
+ *
1981
+ * mint-session POST /widget/v1/sessions with the sk + pk pair.
1982
+ * poll-entitlements GET /widget/v1/entitlements with the minted bearer.
1983
+ * assert-verdict the verdict carries at least one capability.
1984
+ *
1985
+ * Three properties this file exists to guarantee:
1986
+ *
1987
+ * 1. EVERY STEP CARRIES A REASON, including a passing one. "PASS" alone tells an operator
1988
+ * nothing they can paste into a ticket; "PASS: minted a session valid until ..." does.
1989
+ * 2. A STEP THAT COULD NOT RUN IS SKIP, NOT FAIL. Missing credentials are not a broken
1990
+ * integration, and reporting them as failures would send someone debugging the API when
1991
+ * they simply have not exported a key yet.
1992
+ * 3. THE MINTED TOKEN NEVER REACHES A REPORT. It is wrapped as an ephemeral credential the
1993
+ * moment it is read, so the only thing that can travel into step two is a header the HTTP
1994
+ * client writes, and the only thing that can travel into the report is its expiry.
1995
+ *
1996
+ * The mint is a POST and gets exactly one attempt. The poll is a GET, so repeating it is safe,
1997
+ * and it repeats on purpose: an entitlement scope provisioned seconds ago propagates on a
1998
+ * revision bump, and a single read would report a stale empty verdict as a failure.
1999
+ */
2000
+ type StepResult = 'PASS' | 'FAIL' | 'SKIP';
2001
+ interface VerifyStep {
2002
+ /** Stable within a schemaVersion. Callers may key on it. */
2003
+ readonly id: string;
2004
+ readonly title: string;
2005
+ readonly result: StepResult;
2006
+ /** Always populated, for every result including PASS. */
2007
+ readonly reason: string;
2008
+ readonly requestId: string | null;
2009
+ readonly details: readonly string[];
2010
+ }
2011
+ interface VerifyOptions {
2012
+ readonly client: ApiClient;
2013
+ readonly clock: ClockPort;
2014
+ readonly env: Readonly<Record<string, string | undefined>>;
2015
+ readonly userRef: string;
2016
+ /** How many times to read the verdict before concluding it is empty. */
2017
+ readonly attempts: number;
2018
+ readonly pollDelayMs: number;
2019
+ }
2020
+ interface Verification {
2021
+ readonly steps: readonly VerifyStep[];
2022
+ readonly status: 'passed' | 'failed' | 'incomplete';
2023
+ }
2024
+ declare function runVerification(options: VerifyOptions): Promise<Verification>;
2025
+
2026
+ declare function buildVerificationDocument(verification: Verification, baseUrl: string, cliVersion: string): JsonObject;
2027
+ declare function renderVerification(verification: Verification, baseUrl: string): string;
2028
+
2029
+ declare function nextActionFor(entry: ErrorRegistryEntry): string;
2030
+ interface ExplanationInput {
2031
+ readonly entry: ErrorRegistryEntry;
2032
+ readonly contractPath: string;
2033
+ readonly digest: string;
2034
+ readonly cliVersion: string;
2035
+ }
2036
+ declare function buildExplanationDocument(input: ExplanationInput): JsonObject;
2037
+ declare function renderExplanation(input: ExplanationInput): string;
2038
+
2039
+ /**
2040
+ * Suggestions for a code the registry does not list.
2041
+ *
2042
+ * Worth the twenty lines because the registry deliberately keeps THREE naming families at once
2043
+ * (`AuthorizationException`, `authExpired`, `session_required`), so a partner guessing at a code
2044
+ * guesses in the wrong family constantly, and the codes are case sensitive because a client
2045
+ * branches on the literal. "Did you mean authExpired" is the whole answer in that case.
2046
+ *
2047
+ * Ranked: an exact case-insensitive hit first, then a containment hit, then a near miss by edit
2048
+ * distance. Capped, because a list of twenty guesses is not a suggestion.
2049
+ */
2050
+ /** Iterative Levenshtein over two rows. No dependency for something this small. */
2051
+ declare function editDistance(left: string, right: string): number;
2052
+ declare function suggestCodes(query: string, codes: readonly string[]): readonly string[];
2053
+
2054
+ /**
2055
+ * The one real filesystem adapter. Every syscall failure collapses to a null or false, so no
2056
+ * EACCES on an unrelated probe can take the whole command down; a genuinely required file
2057
+ * that reads as null surfaces as a typed `file_not_found` error at the call site instead.
2058
+ */
2059
+ declare const nodeFileSystem: FileSystemPort;
2060
+
2061
+ /**
2062
+ * An in-memory FileSystemPort for tests that need a shape no fixture on disk should carry,
2063
+ * for example two competing lockfiles or a manifest that is not valid JSON. Fixture projects
2064
+ * on disk stay the primary detection test surface; this covers the pathological cases.
2065
+ *
2066
+ * Directories are implied by the file keys, so a caller only ever declares files.
2067
+ */
2068
+ declare function createMemoryFileSystem(files: Readonly<Record<string, string>>): FileSystemPort;
2069
+
2070
+ /**
2071
+ * The detection vocabulary. Every value here is something the CLI can establish from files on
2072
+ * disk. Anything it cannot establish lives in `src/commands/plan/facts.ts` as a supplied fact,
2073
+ * and the two sets are deliberately disjoint.
2074
+ */
2075
+ type ProjectType = 'expo-managed' | 'bare-react-native' | 'flutter' | 'web' | 'unknown';
2076
+ type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'not-applicable' | 'undetermined';
2077
+ type DependencySource = 'package.json' | 'pubspec.yaml';
2078
+ type DependencyScope = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'sdk';
2079
+ /**
2080
+ * `declared` is the range written in the manifest. `installed` is the exact version resolved
2081
+ * on disk when the dependency tree is present. Reporting both is the honest answer: a plan
2082
+ * built against `^50.0.0` with nothing installed is materially less certain than one built
2083
+ * against a resolved 50.0.17, and a reader deserves to see which of the two they are getting.
2084
+ */
2085
+ interface DetectedDependency {
2086
+ readonly name: string;
2087
+ readonly declared: string | null;
2088
+ readonly installed: string | null;
2089
+ readonly source: DependencySource;
2090
+ readonly scope: DependencyScope;
2091
+ }
2092
+ interface ProjectDetection {
2093
+ readonly root: string;
2094
+ readonly projectType: ProjectType;
2095
+ /** Human-readable file-level facts that produced `projectType`, in the order they were found. */
2096
+ readonly projectTypeEvidence: readonly string[];
2097
+ readonly packageManager: PackageManager;
2098
+ readonly packageManagerEvidence: readonly string[];
2099
+ readonly dependencies: readonly DetectedDependency[];
2100
+ }
2101
+
2102
+ declare function detectProject(fs: FileSystemPort, root: string): ProjectDetection;
2103
+
2104
+ type AttributeValue = string | number | boolean | null | readonly string[];
2105
+ interface TreePrinciple {
2106
+ readonly id: string;
2107
+ readonly title: string;
2108
+ readonly statement: string;
2109
+ readonly docPath: string | null;
2110
+ }
2111
+ interface BranchOutcome {
2112
+ readonly kind: 'branch';
2113
+ readonly nextQuestionId: string;
2114
+ }
2115
+ interface TerminalOutcome {
2116
+ readonly kind: 'terminal';
2117
+ readonly attributes: Readonly<Record<string, AttributeValue>>;
2118
+ readonly docPaths: readonly string[];
2119
+ readonly capabilityRefs: readonly string[];
2120
+ /** The tree that must be answered next, or null when this answer stands alone. */
2121
+ readonly thenAskTreeId: string | null;
2122
+ }
2123
+ interface BlockedOutcome {
2124
+ readonly kind: 'blocked';
2125
+ readonly reason: string;
2126
+ readonly unblockOwner: string;
2127
+ readonly requestPath: string;
2128
+ readonly capabilityRefs: readonly string[];
2129
+ }
2130
+ type Outcome = BranchOutcome | TerminalOutcome | BlockedOutcome;
2131
+ interface TreeOption {
2132
+ readonly id: string;
2133
+ readonly label: string;
2134
+ readonly when: string;
2135
+ readonly outcome: Outcome;
2136
+ }
2137
+ interface TreeQuestion {
2138
+ readonly id: string;
2139
+ readonly prompt: string;
2140
+ readonly whyItIsAsked: string;
2141
+ /**
2142
+ * The exact invocation change that answers this question, or null when there is none. Null is
2143
+ * an answer, not an omission: a question whose only option is implied has no flag.
2144
+ */
2145
+ readonly supplyWith: string | null;
2146
+ readonly detectable: boolean;
2147
+ /** Sorted by id, because the published order is alphabetical and means nothing. */
2148
+ readonly options: readonly TreeOption[];
2149
+ }
2150
+ interface DecisionTree {
2151
+ readonly treeId: string;
2152
+ readonly title: string;
2153
+ readonly decides: string;
2154
+ /** The exact invocation change that answers this tree. Rendered in a refusal. */
2155
+ readonly supplyWith: string;
2156
+ readonly entryQuestionId: string;
2157
+ readonly principles: readonly TreePrinciple[];
2158
+ readonly questions: readonly TreeQuestion[];
2159
+ }
2160
+ interface DecisionSpace {
2161
+ readonly trees: readonly DecisionTree[];
2162
+ /** Where it came from and what it hashed to, carried into the plan document. */
2163
+ readonly source: string;
2164
+ readonly digest: string;
2165
+ tree(treeId: string): DecisionTree | null;
2166
+ question(treeId: string, questionId: string): TreeQuestion | null;
2167
+ /** The option ids of one question, sorted. The enum a flag is parsed against. */
2168
+ choices(treeId: string, questionId: string): readonly string[];
2169
+ }
2170
+ declare function buildDecisionSpace(trees: readonly DecisionTree[], source: string, digest: string): DecisionSpace;
2171
+ type DecisionSpaceLoad = {
2172
+ readonly kind: 'loaded';
2173
+ readonly space: DecisionSpace;
2174
+ } | {
2175
+ readonly kind: 'no_trees';
2176
+ readonly path: string;
2177
+ } | {
2178
+ readonly kind: 'contract_rejected';
2179
+ readonly outcome: ContractValidationOutcome;
2180
+ };
2181
+ /**
2182
+ * Load the decision space out of a contract-public envelope on disk. Contract failures are
2183
+ * handed back unchanged so the caller can reuse `contract validate`'s exact error rendering
2184
+ * rather than inventing a second, subtly different set of messages for the same three failures.
2185
+ */
2186
+ declare function loadDecisionSpace(fs: FileSystemPort, path: string): DecisionSpaceLoad;
2187
+ /** One question the walk passed through, and the option it took. */
2188
+ interface WalkStep {
2189
+ readonly questionId: string;
2190
+ readonly optionId: string;
2191
+ /** True when the question had exactly one option, so nothing was chosen. */
2192
+ readonly implied: boolean;
2193
+ }
2194
+ type WalkResult = {
2195
+ readonly kind: 'terminal';
2196
+ readonly outcome: TerminalOutcome;
2197
+ readonly path: readonly WalkStep[];
2198
+ } | {
2199
+ readonly kind: 'blocked';
2200
+ readonly outcome: BlockedOutcome;
2201
+ readonly path: readonly WalkStep[];
2202
+ } | {
2203
+ readonly kind: 'unanswered';
2204
+ readonly question: TreeQuestion;
2205
+ } | {
2206
+ readonly kind: 'unknown_option';
2207
+ readonly question: TreeQuestion;
2208
+ readonly given: string;
2209
+ } | {
2210
+ readonly kind: 'unwalkable';
2211
+ readonly detail: string;
2212
+ };
2213
+ /** Supplies the answer to one question, or null when the caller has not answered it. */
2214
+ type AnswerFor = (questionId: string) => string | null;
2215
+ /**
2216
+ * Walk one tree. Terminates on a terminal, a block, or the first question nobody answered.
2217
+ *
2218
+ * A QUESTION WITH EXACTLY ONE OPTION IS TAKEN WITHOUT AN ANSWER, and the step records that it
2219
+ * was implied. One option is not a decision: it is the contract saying "everyone who gets here
2220
+ * ends up in the same place", which is how three backend languages with no first-party minter
2221
+ * converge on one description of the direct call. Demanding a flag for it would be demanding
2222
+ * the caller confirm the only thing that could happen.
2223
+ *
2224
+ * The `visited` guard is not a substitute for the compiler's cycle check; it is what keeps a
2225
+ * projection built by something other than that compiler from hanging this process.
2226
+ */
2227
+ declare function walkTree(tree: DecisionTree, answerFor: AnswerFor): WalkResult;
2228
+ /** Walk a tree whose whole answer is one option of its entry question. */
2229
+ declare function walkSingle(tree: DecisionTree, answer: string): WalkResult;
2230
+
2231
+ /** One file a plan tells the caller to write. */
2232
+ interface PlanCode {
2233
+ /** Suggested path, relative to the project root. */
2234
+ readonly path: string;
2235
+ /** For syntax highlighting and for an agent deciding how to write it. */
2236
+ readonly language: 'tsx' | 'ts' | 'dart';
2237
+ readonly contents: string;
2238
+ }
2239
+
2240
+ /**
2241
+ * THE PLAN CONTRACT.
2242
+ *
2243
+ * This file and `schema/plan.v1.schema.json` are two views of one committed artifact. Anything
2244
+ * a caller can parse out of `everfur plan --json` is declared here, and `tests/contract`
2245
+ * validates real emitted output against the JSON Schema on every run, so the two cannot drift
2246
+ * without the suite going red.
2247
+ *
2248
+ * Compatibility rule for `schemaVersion: 1`: fields may be ADDED, and enum members may be
2249
+ * ADDED. A field is never removed, never renamed, and never changes type or meaning. Any of
2250
+ * those is `schemaVersion: 2` and a new schema file beside this one.
2251
+ *
2252
+ * WHAT CHANGED WHEN THE DECISION TREES LANDED, and why it stayed v1:
2253
+ *
2254
+ * - `status` gained `blocked`, `missingFacts[].fact` gained `decisionTrees` and
2255
+ * `sessionMintLanguage`: added enum members, which v1 permits.
2256
+ * - `blocked`, `principles` and `decisionSpace` are new fields, which v1 permits.
2257
+ * - `Capability` and `IdentityMode` stopped being enums and became patterned strings. This is
2258
+ * the one change worth arguing about, so it is argued here: the set was never this package's
2259
+ * to own. It lives in `contract/decision-trees/`, and an enum here would reject a capability
2260
+ * the platform published until somebody remembered to edit a CLI. The relaxation accepts
2261
+ * every document v1 already accepted and rejects nothing it used to, which is the test that
2262
+ * keeps it inside v1.
2263
+ */
2264
+ declare const PLAN_SCHEMA_VERSION = 1;
2265
+ declare const PLAN_DOCUMENT_KIND = "everfur.plan";
2266
+ /**
2267
+ * A capability id: one option of the capabilities tree's entry question. NOT an enum here.
2268
+ * The authoritative list is the contract's, and `everfur plan` reads it from the projection.
2269
+ */
2270
+ type Capability = string;
2271
+ /** An identity mode id: one option of the identity-mode tree's entry question. */
2272
+ type IdentityMode = string;
2273
+ /** A backend language id: one option of the session-mint-language tree's entry question. */
2274
+ type SessionMintLanguage = string;
2275
+ type PlanStatus = 'ready' | 'underspecified' | 'blocked';
2276
+ /**
2277
+ * The facts `plan` needs. Each is either detected from disk, supplied by flag, or loaded from
2278
+ * the contract, and never guessed.
2279
+ *
2280
+ * `decisionTrees` comes first because it is the fact the others are judged against: with no
2281
+ * decision space the CLI cannot say whether a capability exists, let alone whether it is
2282
+ * reachable. It is a REFUSAL rather than a broken-artifact failure because nothing was loaded
2283
+ * and nothing on disk is wrong; the operator simply has not said where the contract is.
2284
+ */
2285
+ declare const MISSING_FACT_NAMES: readonly ["decisionTrees", "projectType", "packageManager", "capabilities", "identityMode", "sessionMintLanguage"];
2286
+ type MissingFactName = (typeof MISSING_FACT_NAMES)[number];
2287
+ interface MissingFact {
2288
+ readonly fact: MissingFactName;
2289
+ /** Why the CLI could not establish it, phrased as what it looked at. */
2290
+ readonly reason: string;
2291
+ /** The exact invocation change that resolves it. */
2292
+ readonly supplyWith: string;
2293
+ }
2294
+ type PlanPhase = 'install' | 'native' | 'configure' | 'code' | 'verify';
2295
+ interface PlanStep {
2296
+ /** Stable within a schemaVersion. Callers may key on it. */
2297
+ readonly id: string;
2298
+ readonly phase: PlanPhase;
2299
+ readonly title: string;
2300
+ readonly detail: string;
2301
+ /** Shell commands, in order. Never contains a credential value. */
2302
+ readonly commands: readonly string[];
2303
+ /** Files the partner is expected to create or edit. */
2304
+ readonly files: readonly string[];
2305
+ /**
2306
+ * Code to write, when this package can name every symbol in it.
2307
+ *
2308
+ * Absent rather than invented when it cannot: a plausible-looking snippet naming a component that
2309
+ * does not exist is worse than no snippet, because it compiles in the reader's head and fails in
2310
+ * their editor. See codegen.ts for which surfaces qualify.
2311
+ */
2312
+ readonly code?: readonly PlanCode[];
2313
+ }
2314
+ interface PlanWarning {
2315
+ readonly code: string;
2316
+ readonly message: string;
2317
+ }
2318
+ /**
2319
+ * A capability the caller asked for that a planner must not build against, lifted verbatim from
2320
+ * a BLOCKED terminal in the contract.
2321
+ *
2322
+ * This is the field that stops the CLI producing a confident, wrong plan. Without it, a
2323
+ * capability whose runtime gate is a control-plane switch reads as ordinary: the plan installs
2324
+ * the SDK, mounts the surface, and every route answers 404 in production, which the partner
2325
+ * reads as a broken SDK rather than as a switch nobody turned on.
2326
+ */
2327
+ interface BlockedCapability {
2328
+ /** The capability id the caller named. */
2329
+ readonly capability: string;
2330
+ readonly reason: string;
2331
+ readonly unblockOwner: string;
2332
+ /** Site-relative path on the partner documentation site. Resolved at compile time. */
2333
+ readonly requestPath: string;
2334
+ /** The capability pack keys the block derives from. */
2335
+ readonly capabilityKeys: readonly string[];
2336
+ }
2337
+ /**
2338
+ * Guidance that is true regardless of what was answered, carried in the document rather than
2339
+ * left in a prompt. An agent that has compacted its context away still holds these.
2340
+ */
2341
+ interface PlanPrinciple {
2342
+ readonly treeId: string;
2343
+ readonly id: string;
2344
+ readonly title: string;
2345
+ readonly statement: string;
2346
+ readonly docPath: string | null;
2347
+ }
2348
+ /** Which decision space produced this plan, so a stale plan can be traced to a stale contract. */
2349
+ interface DecisionSpaceProvenance {
2350
+ readonly source: string;
2351
+ readonly digest: string;
2352
+ readonly trees: readonly string[];
2353
+ }
2354
+ interface PlanDetection {
2355
+ readonly root: string;
2356
+ readonly projectType: ProjectType;
2357
+ readonly projectTypeEvidence: readonly string[];
2358
+ readonly packageManager: PackageManager;
2359
+ readonly packageManagerEvidence: readonly string[];
2360
+ readonly dependencies: readonly {
2361
+ readonly name: string;
2362
+ readonly declared: string | null;
2363
+ readonly installed: string | null;
2364
+ readonly source: string;
2365
+ readonly scope: string;
2366
+ }[];
2367
+ }
2368
+ interface PlanDocument {
2369
+ readonly schemaVersion: typeof PLAN_SCHEMA_VERSION;
2370
+ readonly kind: typeof PLAN_DOCUMENT_KIND;
2371
+ readonly generatedBy: {
2372
+ readonly name: string;
2373
+ readonly version: string;
2374
+ };
2375
+ readonly status: PlanStatus;
2376
+ /** What the CLI established by reading the project. Always populated, refusal included. */
2377
+ readonly detected: PlanDetection;
2378
+ /** What the caller supplied. `null` means the flag was absent. */
2379
+ readonly supplied: {
2380
+ readonly capabilities: readonly Capability[] | null;
2381
+ readonly identityMode: IdentityMode | null;
2382
+ readonly sessionMintLanguage: SessionMintLanguage | null;
2383
+ };
2384
+ /** Empty when `status` is `ready`. Non-empty exactly when it is `underspecified`. */
2385
+ readonly missingFacts: readonly MissingFact[];
2386
+ /** Empty when `status` is `underspecified` or `blocked`. The CLI emits no partial plan. */
2387
+ readonly steps: readonly PlanStep[];
2388
+ /** Non-empty exactly when a selected capability cannot be built today. */
2389
+ readonly blocked: readonly BlockedCapability[];
2390
+ /** The principles of every tree this plan consulted. */
2391
+ readonly principles: readonly PlanPrinciple[];
2392
+ readonly warnings: readonly PlanWarning[];
2393
+ /** Null only on the refusal that says no decision space was found. */
2394
+ readonly decisionSpace: DecisionSpaceProvenance | null;
2395
+ }
2396
+
2397
+ /**
2398
+ * The planner. A pure function from (what was detected, what was supplied, what the contract
2399
+ * says) to a plan document. No filesystem, no clock, no network, no environment: the same
2400
+ * inputs always produce a byte-identical document, which is what makes the JSON contract
2401
+ * testable and diffable in CI.
2402
+ *
2403
+ * THE HEADLINE BEHAVIOUR IS STILL THE REFUSAL. When any required fact is absent the planner
2404
+ * returns a complete, schema-valid document with `status: "underspecified"`, an empty `steps`,
2405
+ * and a `missingFacts` entry per absent fact naming both the reason and the exact flag that
2406
+ * supplies it. It does not emit a partial plan, and it never substitutes a default. A wrong
2407
+ * plan that looks right costs a partner a day; a refusal costs them one flag.
2408
+ *
2409
+ * THE SECOND BEHAVIOUR IS THE BLOCK, and it is new. The decision space now comes from the
2410
+ * contract, and the contract can say that a capability is not buildable: its runtime gate is a
2411
+ * control-plane switch that is off, or every action in it is closed to an agent. Before this,
2412
+ * asking for such a capability produced a confident plan whose every route answers 404 in
2413
+ * production, which a partner reads as a broken SDK. Now it produces the reason and the path to
2414
+ * ask, and no steps for that capability at all.
2415
+ *
2416
+ * WHAT THIS FILE NO LONGER DECIDES: which capabilities exist, which identity modes exist, what
2417
+ * a capability's import target is, which optional native peers it binds, which verb installs
2418
+ * into this project, and what to tell somebody who is missing a fact. All of that is read from
2419
+ * the trees. What is left here is composition: turning answers into ordered steps.
2420
+ *
2421
+ * SECRETS. Nothing in this file mints, derives, reads, or prints a credential. Configuration
2422
+ * steps name ENVIRONMENT VARIABLES and never values, and `tests/unit/plan-no-secrets.test.ts`
2423
+ * asserts that over the emitted document for every capability and identity combination.
2424
+ */
2425
+ interface PlanInput {
2426
+ readonly detection: ProjectDetection;
2427
+ readonly capabilities: readonly Capability[] | null;
2428
+ readonly identityMode: IdentityMode | null;
2429
+ readonly sessionMintLanguage: SessionMintLanguage | null;
2430
+ /** The decision space from the contract projection, or null when none was found. */
2431
+ readonly space: DecisionSpace | null;
2432
+ readonly cliVersion: string;
2433
+ }
2434
+ /**
2435
+ * Compute the missing facts in a fixed order: the contract first, because everything else is
2436
+ * judged against it; then the detected facts, which the operator fixes by pointing the CLI
2437
+ * somewhere else; then the supplied facts, which are fixed by adding a flag.
2438
+ */
2439
+ declare function missingFactsFor(input: PlanInput): readonly MissingFact[];
2440
+ /**
2441
+ * Build the plan.
2442
+ *
2443
+ * Three outcomes, and the order between them is deliberate:
2444
+ *
2445
+ * 1. `underspecified` when any required fact is absent. Nothing else is decidable, so this
2446
+ * wins even when some capability is also blocked.
2447
+ * 2. `blocked` when everything was answered and NOTHING the caller asked for can be built.
2448
+ * Steps are empty: an install plan for a surface that answers 404 is not a smaller plan,
2449
+ * it is a wrong one.
2450
+ * 3. `ready` otherwise, with the blocked capabilities still reported beside the steps that
2451
+ * cover the rest.
2452
+ */
2453
+ declare function planIntegration(input: PlanInput): PlanDocument;
2454
+
2455
+ /** Render whichever projection of the plan the status calls for. Total over `status`. */
2456
+ declare function renderPlan(document: PlanDocument): string;
2457
+
2458
+ /**
2459
+ * Parsing for the facts the CLI cannot detect.
2460
+ *
2461
+ * The distinction this file protects: an ABSENT flag is a missing fact, which the planner turns
2462
+ * into a structured refusal. A PRESENT but invalid flag is a usage error, which fails at exit
2463
+ * code 2 before the planner runs. Collapsing the two would let a typo'd `--identity partner`
2464
+ * read as "the operator did not tell us", which is a materially different and much worse report.
2465
+ *
2466
+ * WHAT IS VALID COMES FROM THE CONTRACT. The accepted values are the option ids of one question
2467
+ * of one decision tree, so a capability the platform published is accepted the day the
2468
+ * projection carries it, with no release of this package. When no decision space is available
2469
+ * the flags are parsed but NOT judged: without the trees this CLI does not know what a valid
2470
+ * capability is, and rejecting a value it cannot evaluate would be guessing in the direction
2471
+ * that looks strict rather than the direction that is honest. The refusal names `decisionTrees`
2472
+ * instead, which is the fact that is actually missing.
2473
+ */
2474
+ interface SuppliedFactsResult {
2475
+ readonly ok: boolean;
2476
+ readonly capabilities: readonly Capability[] | null;
2477
+ readonly identityMode: IdentityMode | null;
2478
+ readonly sessionMintLanguage: SessionMintLanguage | null;
2479
+ readonly errors: readonly string[];
2480
+ }
2481
+ /**
2482
+ * Parse `--capabilities chat,records`. Order is normalised to sorted order and duplicates
2483
+ * collapse, so two equivalent invocations produce a byte-identical plan document.
2484
+ */
2485
+ declare function parseCapabilities(raw: string | null, space: DecisionSpace | null): {
2486
+ readonly value: readonly Capability[] | null;
2487
+ readonly errors: readonly string[];
2488
+ };
2489
+ declare function parseIdentityMode(raw: string | null, space: DecisionSpace | null): {
2490
+ readonly value: IdentityMode | null;
2491
+ readonly errors: readonly string[];
2492
+ };
2493
+ /** Parse `--backend node`. */
2494
+ declare function parseSessionMintLanguage(raw: string | null, space: DecisionSpace | null): {
2495
+ readonly value: SessionMintLanguage | null;
2496
+ readonly errors: readonly string[];
2497
+ };
2498
+ /** Parse every supplied fact, accumulating all errors rather than stopping at the first. */
2499
+ declare function parseSuppliedFacts(capabilitiesRaw: string | null, identityRaw: string | null, backendRaw: string | null, space: DecisionSpace | null): SuppliedFactsResult;
2500
+
2501
+ /**
2502
+ * The CLI version, stamped into every emitted document under `generatedBy.version`.
2503
+ *
2504
+ * Declared here rather than read from package.json at runtime so the bundled bin has no
2505
+ * dependency on its own package layout, which differs between a local checkout, a global
2506
+ * install, and a bundled artifact. `tests/unit/version.test.ts` asserts this constant equals
2507
+ * the package.json version, so the two cannot drift.
2508
+ */
2509
+ declare const CLI_VERSION = "0.1.0";
2510
+
2511
+ export { ADMIN_TOKEN, API_PREFIX, type ApiClient, type ApiFailure, type ApiResult, type ApiSuccess, BASE_URL_ENV_VAR, type BlockedCapability, type BlockedOutcome, CLI_VERSION, COMMANDS, CREDENTIAL_SPECS, type Capability, type CapabilityDiagnosis, type ClockPort, type Command, type CommandContext, type CommandOutcome, type ContractValidationOutcome, type Credential, type CredentialSpec, DEFAULT_TIMEOUT_MS, type DecisionSpace, type DecisionSpaceProvenance, type DecisionTree, type DetectedDependency, type Diagnosis, EMPTY_REGISTRY, ERRORS_DOCUMENT_KEY, type ErrorRegistry, type ErrorRegistryEntry, ExitCode, type FileSystemPort, type FormatOptions, type FormattedOutput, type HttpMethod, type HttpRequest, type HttpResponse, IDEMPOTENT_METHODS, type IdentityMode, MISSING_FACT_NAMES, type MissingFact, type MissingFactName, type Outcome, PARTNER_KEY, PARTNER_SECRET_KEY, PLAN_DOCUMENT_KIND, PLAN_SCHEMA_VERSION, type PackageManager, type PlanDocument, type PlanInput, type PlanPrinciple, type PlanStep, type PlanWarning, type PositionalSpec, type ProjectDetection, type ProjectType, REASON_CODES, REDACTED, type ReasonCode, type RegistryLoad, type RequestSpec, type ResolvedCredential, type ResolvedEndpoint, type RunOptions, SCHEMAS, SESSION_TOKEN, type SandboxCreateStatus, type SandboxCreation, type SchemaValidation, type SchemaViolation, type SessionMintLanguage, type StepResult, type TerminalOutcome, TransportError, type TransportPort, type TreeOption, type TreeQuestion, type Verification, type VerifyStep, type WalkResult, apiUrl, buildDecisionSpace, buildDiagnosisDocument, buildExplanationDocument, buildSandboxDocument, buildVerificationDocument, clampTimeout, createApiClient, createMemoryFileSystem, createRecordingClock, describeFailure, detectProject, digestOfProjection, digestsEqual, editDistance, entryFromRow, ephemeralCredential, errorCodeOf, failureDetails, failureOutcome, fetchTransport, formatOutcome, isCorrelatableRequestId, isIdempotent, isKnownReasonCode, loadDecisionSpace, loadErrorRegistry, matchCommand, missingFactsFor, nextActionFor as nextActionForErrorCode, nextActionFor$1 as nextActionForReason, nextActionsFor, nodeFileSystem, parseCapabilities, parseDiagnostics, parseIdentityMode, parseSessionMintLanguage, parseSuppliedFacts, planIntegration, projectSandboxCreate, renderDiagnosis, renderExplanation, renderPlan, renderSandboxCreate, renderVerification, resolveContractPath, resolveCredential, resolveEndpoint, runCli, runVerification, suggestCodes, systemClock, validateContractFile, validateContractPublicEnvelope, validateDiagnosisDocument, validateErrorExplanationDocument, validatePlanDocument, validateSandboxCreateDocument, validateVerificationDocument, verifyDigest, walkSingle, walkTree, withPrefix };