sessionpipe-core 0.13.3 → 0.13.4

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.
package/dist/control.js CHANGED
@@ -1,4 +1,91 @@
1
- // sessionpipe-core 0.13.3 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe-core 0.13.4 · Apache-2.0 · https://sessionpipe.org
2
+
3
+ // src/control/frames.ts
4
+ function hostOf(receiver) {
5
+ try {
6
+ return new URL(receiver).host;
7
+ } catch {
8
+ return receiver;
9
+ }
10
+ }
11
+ var IN_PLACE_ANSWER = "Answer it right here, as you would answer the person at the keyboard: your reply is sent back to them automatically. Don't run a command or call a tool just to send it.";
12
+ var COPY_WHYS = ["open", "running", "recent"];
13
+ var copyNote = (why) => `You are a copy of that session, started because the original is ${why === "open" ? "open in a window" : "in use by another process"}, and you have its conversation, folder and tools. Do what the message asks the way the original would: edits, commands and publishing all take effect for real. Only this conversation stays here, so the person reads your reply and not the original's${why === "open" ? " window" : ""}: say what you did, then stop.`;
14
+ var RE_ARM = "(Answer it right here, as you would answer the person at the keyboard: your reply is sent back to them automatically, so don't run a command or call a tool just to send it. Then, without mentioning it, start `sessionpipe wait` again as a background command, with the longest timeout your harness allows, to keep receiving messages.)";
15
+ function frame(text, receiver) {
16
+ return `Message via ${hostOf(receiver)} (sessionpipe control, signed on the sender's device):
17
+
18
+ ${text}`;
19
+ }
20
+ var WHOLE_ID = /^[0-9A-HJKMNP-TV-Z]{26}$/;
21
+ var messageMark = (id) => WHOLE_ID.test(id) ? `message ${id.slice(-12)}` : null;
22
+ var marked = (text, host, mark) => `Message via ${host} (sessionpipe control, signed on the sender's device; ${mark}):
23
+
24
+ ${text}`;
25
+ function frameMarked(text, receiver, id) {
26
+ const mark = messageMark(id);
27
+ return mark ? marked(text, hostOf(receiver), mark) : frame(text, receiver);
28
+ }
29
+ function frameStart(text, receiver) {
30
+ return `${text}
31
+
32
+ (Sent via ${hostOf(receiver)} with sessionpipe control, signed on the sender's device.)`;
33
+ }
34
+ function frameAsk(text, from, mark) {
35
+ return `${text}
36
+
37
+ (Asked by ${from ?? "another session"} on this machine with sessionpipe ask${mark ? `, ${mark}` : ""}. Answer here in full: your reply is returned to that session automatically. Don't run a command or call a tool just to send it.)`;
38
+ }
39
+ function frameHandoff(from, receiver, reply) {
40
+ return `Answer from ${from} (sessionpipe handoff, asked via ${hostOf(receiver)}):
41
+
42
+ ${reply}`;
43
+ }
44
+ var HOLE = "\0";
45
+ var MARK = "";
46
+ var lineOf = (built, hole = "(\\S+)") => new RegExp(
47
+ `^${built.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replaceAll(HOLE, hole).replaceAll(MARK, "message \\S+")}$`
48
+ );
49
+ var HEAD_LINES = [lineOf(frame("", HOLE)), lineOf(marked("", HOLE, MARK))];
50
+ var START_LINES = [lineOf(frameStart("", HOLE))];
51
+ var ASK_LINES = [lineOf(frameAsk("", HOLE), "[^\\n]{1,300}")];
52
+ var MARKED_ASK_LINES = [lineOf(frameAsk("", HOLE, MARK), "([^\\n]{1,300}?)")];
53
+ var HANDOFF_LINES = [lineOf(frameHandoff(HOLE, HOLE, ""))];
54
+ var COPY_NOTE_0_13_0 = "You are a copy of that session, started to answer this message: the conversation above is a replay, the original session is somewhere else, and nothing you do here appears in it. Answer the message and stop; don't carry on the original's work.";
55
+ var COPY_NOTES = [...new Set(COPY_WHYS.map(copyNote)), COPY_NOTE_0_13_0];
56
+ var NOTES = [IN_PLACE_ANSWER, ...COPY_NOTES, RE_ARM];
57
+ var ASK_NOTES = [...COPY_NOTES, RE_ARM];
58
+ var lineIn = (lines, line) => {
59
+ for (const l of lines) {
60
+ const m = l.exec(line);
61
+ if (m) return m;
62
+ }
63
+ return null;
64
+ };
65
+ function unframe(text) {
66
+ const cut = text.indexOf("\n\n");
67
+ const head = cut < 0 ? null : lineIn(HEAD_LINES, text.slice(0, cut));
68
+ return head ? { text: text.slice(cut + 2), via: head[1] } : { text, via: null };
69
+ }
70
+ function deliveredWords(text) {
71
+ const paragraphs = text.split("\n\n");
72
+ const at = (i) => paragraphs[i].trim();
73
+ const n = paragraphs.length;
74
+ const note = n > 1 && NOTES.includes(at(n - 1)) ? at(n - 1) : null;
75
+ const end = note === null ? n : n - 1;
76
+ const words = (from, to) => paragraphs.slice(from, to).join("\n\n").trim();
77
+ const head = lineIn(HEAD_LINES, at(0));
78
+ if (head && words(1, end)) return { text: words(1, end), via: head[1], delivered: true };
79
+ const start = note === null ? lineIn(START_LINES, at(n - 1)) : null;
80
+ if (start && words(0, n - 1)) return { text: words(0, n - 1), via: start[1], delivered: true };
81
+ const asks = note === null || ASK_NOTES.includes(note);
82
+ const theirs = asks && (lineIn(ASK_LINES, at(end - 1)) && words(0, end - 1) || lineIn(HANDOFF_LINES, at(0)) && words(1, end));
83
+ if (theirs) return { text: note === null ? text : words(0, end), via: null, delivered: true };
84
+ const marked2 = asks ? lineIn(MARKED_ASK_LINES, at(end - 1)) : null;
85
+ if (marked2 && words(0, end - 1))
86
+ return { text: `${words(0, end - 1)}${frameAsk("", marked2[1])}`, via: null, delivered: true };
87
+ return { text, via: null, delivered: false };
88
+ }
2
89
 
3
90
  // src/control/verify.ts
4
91
  var GRANT_MAX_MS = 24 * 36e5;
@@ -545,6 +632,7 @@ export {
545
632
  CMD_MAX,
546
633
  COMMAND_TYPE,
547
634
  CONTROL_KINDS,
635
+ COPY_WHYS,
548
636
  DEVICE_CONTEXT,
549
637
  DEVICE_SIG_FIELDS,
550
638
  ES256,
@@ -557,6 +645,7 @@ export {
557
645
  GRANT_STR_MAX,
558
646
  GRANT_TYPE,
559
647
  HOME_RELATIVE_RE,
648
+ IN_PLACE_ANSWER,
560
649
  KEY_STORE_SESSION,
561
650
  KEY_TYPES,
562
651
  MACHINE_MAX_AGE_MS,
@@ -570,18 +659,28 @@ export {
570
659
  POOL_ORDER_MAX,
571
660
  POOL_SESSION,
572
661
  RECEIVER_MAX_AGE_MS,
662
+ RE_ARM,
573
663
  RS256,
574
664
  SKEW_MS,
575
665
  START_PERMISSIONS,
576
666
  TEXT_MAX,
577
667
  b64url,
668
+ copyNote,
669
+ deliveredWords,
578
670
  derToRaw,
579
671
  deviceKeyId,
580
672
  deviceMessage,
581
673
  filesProblem,
674
+ frame,
675
+ frameAsk,
676
+ frameHandoff,
677
+ frameMarked,
678
+ frameStart,
582
679
  fromB64url,
680
+ hostOf,
583
681
  keyUsable,
584
682
  levelRank,
683
+ messageMark,
585
684
  narrower,
586
685
  originAllowed,
587
686
  pairChallenge,
@@ -592,6 +691,7 @@ export {
592
691
  sameBytes,
593
692
  sessionIdOk,
594
693
  sha256,
694
+ unframe,
595
695
  verifyAssertion,
596
696
  verifyCommand,
597
697
  verifyEnrollment
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/control/verify.ts"],
4
- "sourcesContent": ["// SPDX-License-Identifier: Apache-2.0\n// The one verifier for signed control commands (spec/CONTROL.md \u00A74\u2013\u00A75). A machine\n// runs a command only when this says yes; a receiver MAY run it too, as an early\n// word, so a bad command is refused where the person can see why.\n//\n// WebCrypto only (globalThis.crypto.subtle), no node: imports, no clock of its own\n// (`now` is passed in): the same file runs in a Cloudflare Worker, in a browser and\n// in Node \u2265 20, and conformance/control-vectors/ pins its answers.\n//\n// Every signature covers the EXACT bytes as transmitted (`cmd`, `grant.str`): the\n// strings are parsed to read their fields and never re-serialized to verify.\n\n/** One passkey tap vouches for a day key for at most this long. */\nexport const GRANT_MAX_MS = 24 * 3600_000;\n/** Clock slack between the signing device, the receiver and the machine. */\nexport const SKEW_MS = 5 * 60_000;\n/** The machine judges a command by when it was SIGNED: a message sent while the lid\n * was shut still runs when the machine wakes, up to a day later. */\nexport const MACHINE_MAX_AGE_MS = 24 * 3600_000;\n/** A receiver's early check is stricter: a command is carried within ten minutes of\n * being signed, or not at all. */\nexport const RECEIVER_MAX_AGE_MS = 10 * 60_000;\n/** How long a machine remembers a nonce: longer than MACHINE_MAX_AGE_MS + SKEW_MS. */\nexport const NONCE_TTL_MS = 25 * 3600_000;\n/** The whole command string, in UTF-16 code units (what JSON.parse sees). */\nexport const CMD_MAX = 48_000;\n/** A prompt's text, in characters. */\nexport const TEXT_MAX = 20_000;\n/** A permission answer's note. */\nexport const NOTE_MAX = 2_000;\n/** A grant string. */\nexport const GRANT_STR_MAX = 2_000;\n/** Attachments on a `start` or a `prompt` (CONTROL.md \u00A73.1): at most this many files, */\nexport const FILES_MAX = 10;\n/** each at most this many bytes, */\nexport const FILE_SIZE_MAX = 26_214_400;\n/** together at most this many, */\nexport const FILES_TOTAL_MAX = 52_428_800;\n/** with a name of at most this many characters (after NFC) */\nexport const FILE_NAME_MAX = 200;\n/** and a media type of at most this many. */\nexport const FILE_TYPE_MAX = 100;\n/** A start's `model` (CONTROL.md \u00A73.1): at most this many characters, */\nexport const MODEL_MAX = 100;\n/** in this alphabet, never starting with a dash, so it can never read as a flag. */\nexport const MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\\]-]*$/;\n/** The levels a start may ask for (CONTROL.md \u00A73.1), narrowest first. Closed: no level\n * is wider than `auto`, and none turns a harness's approval off. */\nexport const START_PERMISSIONS = [\"read\", \"safe\", \"edits\", \"auto\"] as const;\nexport type StartPermission = (typeof START_PERMISSIONS)[number];\n/** What a run is started with: a level, or `inherit` (CONTROL.md \u00A72) \u2014 no permission\n * flag at all, so the harness starts in its own default on the machine. Only a machine\n * the person put in inherit mode runs that way; a command never names it. */\nexport type RunLevel = StartPermission | \"inherit\";\n\n/** Where a level sits, narrowest (0) to widest; -1 for anything that isn't one. */\nexport const levelRank = (l: unknown): number => START_PERMISSIONS.indexOf(l as StartPermission);\n/** The narrower of two levels: what a later run of a session gets, its recorded level\n * against what the machine's mode allows now. */\nexport const narrower = (a: StartPermission, b: StartPermission): StartPermission =>\n levelRank(a) <= levelRank(b) ? a : b;\n\nexport const COMMAND_TYPE = \"sessionpipe.control\";\nexport const GRANT_TYPE = \"sessionpipe.grant\";\nexport const CONTROL_KINDS = [\"prompt\", \"permission.answer\", \"cancel\", \"start\", \"key.add\", \"pool\"] as const;\n/** The session a `key.add` names: the machine's own key store, not a coding session. */\nexport const KEY_STORE_SESSION = \"sessionpipe:keys\";\n/** The session a `pool` names: the machine's account pool (CONTROL.md \u00A76 `pool`). */\nexport const POOL_SESSION = \"sessionpipe:pool\";\nexport type ControlKind = (typeof CONTROL_KINDS)[number];\n/** What a `pool` command may ask (CONTROL.md \u00A76 `pool`). Closed: an action added later\n * is refused by every machine that predates it. */\nexport const POOL_ACTIONS = [\n \"on\",\n \"off\",\n \"enable\",\n \"disable\",\n \"remove\",\n \"order\",\n \"refresh\",\n \"login.begin\",\n \"login.complete\",\n] as const;\nexport type PoolAction = (typeof POOL_ACTIONS)[number];\n/** A pooled account's id (or a pending sign-in's): what `session.account_id` carries. */\nexport const POOL_ACCOUNT_RE = /^[A-Za-z0-9._:-]{1,128}$/;\n/** An `order` names at most this many accounts: the pool holds no more. */\nexport const POOL_ORDER_MAX = 20;\n/** The pasted `code#state` of a sign-in, in characters, on one line. */\nexport const POOL_CODE_MAX = 512;\n\n/** COSE algorithm ids a trusted key may use. */\nexport const ES256 = -7;\nexport const RS256 = -257;\n\n/** The kinds of key a machine may trust (CONTROL.md \u00A72): a WebAuthn passkey, or a\n * device key an app holds in hardware where it has no passkey to offer. */\nexport const KEY_TYPES = [\"passkey\", \"device\"] as const;\nexport type KeyType = (typeof KEY_TYPES)[number];\n\n/** A key enrolled on the machine: a passkey's credential id, COSE alg and SPKI\n * (base64url); a device key also says so (`type`), and what its app declared about\n * it (`store`, `uv`). The machine verifies by its own record, never by a message. */\nexport interface TrustedKey {\n id: string;\n alg: number;\n spki: string;\n /** Present only on a device key (CONTROL.md \u00A72 \"Device keys\"). */\n type?: \"device\";\n store?: \"hardware\";\n uv?: \"each-use\";\n}\n/** A WebAuthn assertion: credential id, authenticatorData, clientDataJSON, signature (base64url). */\nexport interface Assertion {\n cred: string;\n ad: string;\n cd: string;\n sig: string;\n}\n/** A device key's signature (CONTROL.md \u00A73.2): its id and 64 bytes r\u2016s over deviceMessage(). */\nexport interface DeviceSignature {\n cred: string;\n sig: string;\n}\n/** The day's grant: a passkey's assertion over sha256(str), where str names the day key. */\nexport interface GrantBundle extends Assertion {\n str: string;\n}\n/** The day's grant from a device key: its signature over deviceMessage(\"grant\", rp, sha256(str)). */\nexport interface DeviceGrant extends DeviceSignature {\n str: string;\n}\n/** What a receiver carries for one command (control.json minus the receiver's envelope). */\nexport interface SignedCommand {\n cmd: string;\n csig?: string | null;\n grant?: GrantBundle | DeviceGrant | null;\n confirm?: Assertion | DeviceSignature | null;\n}\nexport interface Grant {\n v: 1;\n t: typeof GRANT_TYPE;\n pub: string;\n iat: number;\n exp: number;\n rp: string;\n}\n/** One file attached to a `start`: the bytes live on the receiver, named by their hash. */\nexport interface ControlFile {\n /** The file name the machine writes (NFC). */\n name: string;\n /** Media type, `type/subtype`. */\n type: string;\n /** Bytes. */\n size: number;\n /** SHA-256 of the bytes, base64url without padding (43 characters). */\n sha256: string;\n}\nexport interface ControlCommand {\n v: 1;\n t: typeof COMMAND_TYPE;\n machine: string;\n kind: ControlKind;\n /** `<harness>:<id>`, e.g. `claude-code:8c132906-\u2026`. */\n session: string;\n text?: string;\n for?: string;\n decision?: \"allow\" | \"deny\";\n note?: string;\n cwd?: string;\n /** key.add only: the key to trust from now on, as the receiver holds it (a passkey,\n * or a device key with its `type`, `store` and `uv`). */\n key?: TrustedKey;\n /** key.add only: what the person calls the device (\"iPhone\"), for `control keys`. */\n name?: string;\n /** start, prompt: files to download, verify and write into the session's folder. */\n files?: ControlFile[];\n /** start only: the level the new session runs at (never above the machine's mode). */\n permission?: StartPermission;\n /** start only: the model the harness runs, as the harness names it. */\n model?: string;\n /** pool only (session `sessionpipe:pool`): what to do with the account pool. */\n pool_action?: PoolAction;\n /** pool: the account (enable, disable, remove, refresh), or the pending sign-in's id\n * (login.complete). */\n pool_account?: string;\n /** pool, order: every account of the pool once, in the order wanted. */\n pool_order?: string[];\n /** pool, login.complete: the `code#state` the person pasted back. */\n pool_code?: string;\n /** start, prompt: `<harness>:<id>` of a session on the same machine that gets the\n * answer as a message once the turn ends (CONTROL.md \u00A76, a handoff). */\n reply_to?: string;\n nonce: string;\n iat: number;\n}\n\n/** Why a command was refused. Stable: conformance vectors and acks name these. */\nexport type RefusalCode =\n | \"malformed\"\n | \"not_a_command\"\n | \"wrong_machine\"\n | \"unknown_kind\"\n | \"bad_session\"\n | \"bad_fields\"\n | \"bad_nonce\"\n | \"too_old\"\n | \"future\"\n | \"unsigned\"\n | \"untrusted_key\"\n | \"bad_assertion\"\n | \"bad_grant\"\n | \"outside_grant\"\n | \"bad_csig\"\n | \"replay\"\n | \"nonce_store\";\n\nexport type Verdict =\n | { ok: true; cmd: ControlCommand; via: \"grant\" | \"confirm\"; key: string }\n | { ok: false; code: RefusalCode; why: string };\n\nexport interface VerifyOptions {\n /** This machine's id; a command for another machine is refused. */\n machine: string;\n /** The receiver's WebAuthn rp id the keys were enrolled for (well-known control.signing.rp_id). */\n rpId: string;\n /** The keys enrolled on this machine (or, for a receiver's early check, the account's). */\n trusted: TrustedKey[];\n /** Epoch ms. */\n now: number;\n /** MACHINE_MAX_AGE_MS on a machine (default), RECEIVER_MAX_AGE_MS on a receiver. */\n maxAgeMs?: number;\n /** Called once, last, only for a command whose signatures all verified: answer\n * true when the nonce was seen before, otherwise record it (durably) and answer\n * false. A throw refuses the command (`nonce_store`): never run what could not\n * be remembered. Omit it on a receiver's early check. */\n seenNonce?: (nonce: string) => boolean | Promise<boolean>;\n}\n\nconst NONCE_RE = /^[A-Za-z0-9_-]{22,64}$/;\nconst MACHINE_RE = /^m_[A-Za-z0-9_-]{16,40}$/;\nconst HARNESS_RE = /^[a-z][a-z0-9-]{0,39}$/;\nconst SESSION_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst B64URL_RE = /^[A-Za-z0-9_-]*$/;\nconst SHA256_RE = /^[A-Za-z0-9_-]{43}$/;\nconst MEDIA_TYPE_RE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i;\n// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters are exactly what it rejects\nconst BAD_NAME_CHAR_RE = /[/\\\\\\x00-\\x1f\\x7f]/;\nconst UP = 0x01;\nconst UV = 0x04;\n\n/** The fields each kind carries besides the common ones (CONTROL.md \u00A73.1). The set is\n * closed: a field it doesn't list is `bad_fields`, so a field added later fails closed\n * on a machine that predates it instead of being ignored. */\nconst COMMON_FIELDS: readonly string[] = [\"v\", \"t\", \"machine\", \"kind\", \"session\", \"nonce\", \"iat\", \"note\"];\nconst FIELDS: Record<ControlKind, readonly string[]> = {\n prompt: [\"text\", \"files\", \"reply_to\"],\n \"permission.answer\": [\"for\", \"decision\"],\n cancel: [],\n start: [\"text\", \"cwd\", \"files\", \"permission\", \"model\", \"reply_to\"],\n \"key.add\": [\"key\", \"name\"],\n pool: [\"pool_action\", \"pool_account\", \"pool_order\", \"pool_code\"],\n};\n/** The fields only a `pool` carries. */\nconst POOL_FIELDS = FIELDS.pool;\n\n// --- encodings ------------------------------------------------------------------\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nexport function b64url(bytes: Uint8Array | ArrayBuffer): string {\n const u = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n let s = \"\";\n for (const b of u) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** base64url without padding, strictly; null for anything else. */\nexport function fromB64url(s: unknown): Uint8Array | null {\n if (typeof s !== \"string\" || !B64URL_RE.test(s) || s.length % 4 === 1) return null;\n try {\n const bin = atob(s.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((s.length + 3) % 4));\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n } catch {\n return null;\n }\n}\n\nexport async function sha256(data: string | Uint8Array): Promise<Uint8Array> {\n const bytes = typeof data === \"string\" ? enc.encode(data) : data;\n return new Uint8Array(await crypto.subtle.digest(\"SHA-256\", bytes as Uint8Array<ArrayBuffer>));\n}\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n const out = new Uint8Array(a.length + b.length);\n out.set(a, 0);\n out.set(b, a.length);\n return out;\n}\n\n/** Equal, compared whole: a mismatch costs the same as a match. */\nexport function sameBytes(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let d = 0;\n for (let i = 0; i < a.length; i++) d |= (a[i] as number) ^ (b[i] as number);\n return d === 0;\n}\n\n/** A DER ECDSA signature (what WebAuthn returns) as the 64-byte r\u2016s WebCrypto verifies.\n * Strict: every length must match the bytes, so one signature has one encoding (a\n * P-256 signature is at most 72 bytes, so the short length form is the only one). */\nexport function derToRaw(der: Uint8Array): Uint8Array | null {\n if (der[0] !== 0x30 || der.length < 8 || der[1] !== der.length - 2) return null;\n let i = 2;\n const part = (): Uint8Array | null => {\n if (der[i] !== 0x02) return null;\n const len = der[i + 1] as number;\n if (len < 1 || len > 33) return null;\n let v = der.slice(i + 2, i + 2 + len);\n if (v.length !== len) return null;\n // Minimal integers only: no needless leading zero, and never negative.\n if ((v[0] as number) & 0x80) return null;\n if (len > 1 && v[0] === 0 && !((v[1] as number) & 0x80)) return null;\n i += 2 + len;\n while (v.length > 32 && v[0] === 0) v = v.slice(1);\n if (v.length > 32) return null;\n const out = new Uint8Array(32);\n out.set(v, 32 - v.length);\n return out;\n };\n const r = part();\n const s = part();\n return r && s && i === der.length ? concat(r, s) : null;\n}\n\nconst u8 = (b: Uint8Array) => b as Uint8Array<ArrayBuffer>;\n/** WebCrypto's key type, named without the DOM lib (Workers, Node and browsers all have it). */\ntype CryptoKey = Awaited<ReturnType<typeof crypto.subtle.importKey>>;\n\nasync function importKey(key: TrustedKey): Promise<CryptoKey | null> {\n const spki = fromB64url(key.spki);\n if (!spki?.length) return null;\n try {\n if (key.alg === ES256)\n return await crypto.subtle.importKey(\"spki\", u8(spki), { name: \"ECDSA\", namedCurve: \"P-256\" }, false, [\"verify\"]);\n if (key.alg === RS256) {\n const k = await crypto.subtle.importKey(\"spki\", u8(spki), { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-256\" }, false, [\n \"verify\",\n ]);\n const bits = (k.algorithm as { modulusLength?: number }).modulusLength as number; // every RSA key has one\n return bits >= 2048 ? k : null;\n }\n } catch {}\n return null;\n}\n\nconst PASSKEY_FIELDS = new Set([\"id\", \"alg\", \"spki\"]);\nconst DEVICE_FIELDS = new Set([\"id\", \"alg\", \"spki\", \"type\", \"store\", \"uv\"]);\n/** The only fields a device signature has (`str` only in a grant). */\nexport const DEVICE_SIG_FIELDS = [\"cred\", \"sig\", \"str\"] as const;\n\n/** A device key's id: the SHA-256 of its SPKI bytes, base64url without padding. */\nexport async function deviceKeyId(spki: string): Promise<string> {\n return b64url(await sha256(fromB64url(spki) ?? new Uint8Array()));\n}\n\n/** A device key's declared rules, ignoring any bookkeeping fields a store keeps beside\n * them (a name, when it was added): ES256, hardware, verified each use, named by its\n * SPKI's hash, and a P-256 key WebCrypto can import. */\nasync function deviceRecordValid(key: TrustedKey): Promise<boolean> {\n if (key.type !== \"device\" || key.alg !== ES256 || key.store !== \"hardware\" || key.uv !== \"each-use\") return false;\n if (typeof key.id !== \"string\" || !SHA256_RE.test(key.id) || key.id !== (await deviceKeyId(key.spki))) return false;\n return !!(await importKey(key));\n}\n\n/** Can this key be enrolled at all? A machine refuses to trust a key it could never\n * verify with. The key's fields are closed: a passkey is `id`, `alg`, `spki` and\n * nothing else; a device key adds exactly `type`, `store` and `uv` (CONTROL.md \u00A72),\n * so a kind added later is refused by every machine that predates it. */\nexport async function keyUsable(key: TrustedKey): Promise<boolean> {\n if (!key || typeof key !== \"object\" || Array.isArray(key)) return false;\n const fields = Object.keys(key).filter((k) => (key as unknown as Record<string, unknown>)[k] !== undefined);\n if (\"type\" in key && key.type !== undefined) {\n if (!fields.every((f) => DEVICE_FIELDS.has(f))) return false;\n return deviceRecordValid(key);\n }\n if (!fields.every((f) => PASSKEY_FIELDS.has(f))) return false;\n return (\n typeof key.id === \"string\" &&\n /^[A-Za-z0-9_-]{16,1400}$/.test(key.id) &&\n (key.alg === ES256 || key.alg === RS256) &&\n !!(await importKey(key))\n );\n}\n\n// --- WebAuthn -------------------------------------------------------------------\n\n/** A WebAuthn origin a key may have been used from: https on the rp id or a subdomain;\n * for rp id `localhost`, plain http on any port too. A bare origin, as a browser writes it. */\nexport function originAllowed(origin: unknown, rpId: string): boolean {\n if (typeof origin !== \"string\" || !rpId) return false;\n let u: URL;\n try {\n u = new URL(origin);\n } catch {\n return false;\n }\n if (u.origin !== origin) return false;\n if (u.hostname !== rpId && !u.hostname.endsWith(`.${rpId}`)) return false;\n if (u.protocol === \"https:\") return true;\n return u.protocol === \"http:\" && rpId === \"localhost\";\n}\n\ntype Check = { ok: true } | { ok: false; why: string };\nconst no = (why: string): Check => ({ ok: false, why });\n\n// --- device keys (CONTROL.md \u00A73.2) ------------------------------------------------\n\n/** The first line of every message a device key signs. */\nexport const DEVICE_CONTEXT = \"sessionpipe.device.v1\";\n/** What a device signature is for: one use can never answer for another. */\nexport type DevicePurpose = \"grant\" | \"confirm\" | \"pair\";\n\n/** The exact bytes a device key signs: the context, the purpose, the rp id as the\n * machine recorded it, and the base64url (no padding) of the 32-byte challenge a\n * passkey would sign for that purpose, joined by single line feeds, none at the end. */\nexport function deviceMessage(purpose: DevicePurpose, rpId: string, challenge: Uint8Array): Uint8Array {\n return enc.encode(`${DEVICE_CONTEXT}\\n${purpose}\\n${rpId}\\n${b64url(challenge)}`);\n}\n\n/** One device signature over `challenge` for `purpose`, made with the device key `key`. */\nasync function verifyDeviceSignature(\n challenge: Uint8Array,\n a: object,\n key: TrustedKey,\n rpId: string,\n purpose: DevicePurpose,\n): Promise<Check> {\n const sig = a as Record<string, unknown>;\n for (const f of Object.keys(sig)) {\n if (f === \"str\" && purpose === \"grant\") continue;\n if (f !== \"cred\" && f !== \"sig\") return no(`a device key's signature carries no ${f}`);\n }\n if (sig.cred !== key.id) return no(\"signed with a key this machine doesn't trust\");\n if (!(await deviceRecordValid(key))) return no(\"a trusted key is unusable\");\n const raw = fromB64url(sig.sig);\n if (raw?.length !== 64) return no(\"a device key's signature is 64 bytes, r\u2016s\");\n const pub = await importKey(key);\n /* v8 ignore next -- deviceRecordValid imported it already */\n if (!pub) return no(\"a trusted key is unusable\");\n let good = false;\n try {\n good = await crypto.subtle.verify(\n { name: \"ECDSA\", hash: \"SHA-256\" },\n pub,\n u8(raw),\n u8(deviceMessage(purpose, rpId, challenge)),\n );\n } catch {\n /* v8 ignore next -- WebCrypto answers false for a bad signature; a throw is a broken runtime */\n good = false;\n }\n return good ? { ok: true } : no(\"the device key's signature doesn't verify\");\n}\n\n/** One signature over `challenge` by `key`, chosen from the machine's own record of\n * the key, never from the signature: a device key's signature for `purpose`\n * (CONTROL.md \u00A73.2), or a passkey assertion made by a verified person. */\nexport async function verifyAssertion(\n challenge: Uint8Array,\n a: Assertion | DeviceSignature | null | undefined,\n key: TrustedKey,\n rpId: string,\n purpose: DevicePurpose,\n): Promise<Check> {\n if (!a || typeof a !== \"object\") return no(\"no passkey signature\");\n if (key.type !== undefined) {\n if (key.type !== \"device\") return no(\"a trusted key is unusable\");\n return verifyDeviceSignature(challenge, a, key, rpId, purpose);\n }\n return verifyPasskeyAssertion(challenge, a as Assertion, key, rpId);\n}\n\nasync function verifyPasskeyAssertion(\n challenge: Uint8Array,\n a: Assertion,\n key: TrustedKey,\n rpId: string,\n): Promise<Check> {\n if (a.cred !== key.id) return no(\"signed with a key this machine doesn't trust\");\n const cdBytes = fromB64url(a.cd);\n const ad = fromB64url(a.ad);\n const sig = fromB64url(a.sig);\n if (!cdBytes || !ad || !sig?.length) return no(\"the passkey signature is malformed\");\n let cd: { type?: unknown; challenge?: unknown; origin?: unknown; crossOrigin?: unknown };\n try {\n cd = JSON.parse(dec.decode(cdBytes));\n } catch {\n return no(\"the passkey signature is malformed\");\n }\n if (!cd || typeof cd !== \"object\") return no(\"the passkey signature is malformed\");\n if (cd.type !== \"webauthn.get\") return no(\"the passkey answered a different question\");\n if (cd.challenge !== b64url(challenge)) return no(\"the passkey signed something else\");\n if (!originAllowed(cd.origin, rpId)) return no(`the passkey was used on another site, not ${rpId}`);\n if (cd.crossOrigin === true) return no(\"the passkey was used inside another site's frame\");\n if (ad.length < 37) return no(\"the passkey signature is malformed\");\n if (!sameBytes(ad.slice(0, 32), await sha256(rpId))) return no(`the passkey belongs to another site, not ${rpId}`);\n const flags = ad[32] as number;\n if (!(flags & UP) || !(flags & UV)) return no(\"the passkey signed without verifying the person\");\n const pub = await importKey(key);\n if (!pub) return no(\"a trusted key is unusable\");\n const signed = concat(ad, await sha256(cdBytes));\n let good = false;\n try {\n if (key.alg === ES256) {\n const raw = derToRaw(sig);\n good = !!raw && (await crypto.subtle.verify({ name: \"ECDSA\", hash: \"SHA-256\" }, pub, u8(raw), u8(signed)));\n } else {\n good = await crypto.subtle.verify({ name: \"RSASSA-PKCS1-v1_5\" }, pub, u8(sig), u8(signed));\n }\n } catch {\n good = false;\n }\n return good ? { ok: true } : no(\"the passkey signature doesn't verify\");\n}\n\n/** What a passkey signs to enroll on a machine: bound to that machine and that pairing code. */\nexport function pairChallenge(machine: string, code: string): Promise<Uint8Array> {\n return sha256(`sessionpipe.pair:${machine}:${code}`);\n}\n\n/** The enrollment proof: the new key's assertion over pairChallenge(machine, code).\n * Only after this verifies does the key go into the machine's trust store. */\nexport async function verifyEnrollment(o: {\n machine: string;\n code: string;\n key: TrustedKey;\n proof: Assertion | DeviceSignature;\n rpId: string;\n}): Promise<Check> {\n if (!(await keyUsable(o.key))) return no(\"the key is in a shape this machine can't use\");\n return verifyAssertion(await pairChallenge(o.machine, o.code), o.proof, o.key, o.rpId, \"pair\");\n}\n\n// --- the command ------------------------------------------------------------------\n\ntype Parsed = { ok: true; cmd: ControlCommand } | { ok: false; code: RefusalCode; why: string };\nconst bad = (code: RefusalCode, why: string): { ok: false; code: RefusalCode; why: string } => ({\n ok: false,\n code,\n why,\n});\nconst isNum = (n: unknown): n is number => typeof n === \"number\" && Number.isFinite(n);\n\n/** The session id rule per harness (ADAPTERS.md): Claude Code ids are UUIDs. */\nexport function sessionIdOk(harness: string, id: string): boolean {\n if (!SESSION_ID_RE.test(id)) return false;\n if (harness === \"claude-code\") return UUID_RE.test(id);\n return true;\n}\n\n/** Split `<harness>:<id>`; null when either half breaks its rule. */\nexport function parseSessionRef(ref: unknown): { harness: string; id: string } | null {\n if (typeof ref !== \"string\") return null;\n const i = ref.indexOf(\":\");\n if (i <= 0) return null;\n const harness = ref.slice(0, i);\n const id = ref.slice(i + 1);\n return HARNESS_RE.test(harness) && sessionIdOk(harness, id) ? { harness, id } : null;\n}\n\n/** Read the command's fields from the exact string. Structure only; no clock, no keys. */\nexport function parseCommand(str: unknown): Parsed {\n if (typeof str !== \"string\" || !str || str.length > CMD_MAX)\n return bad(\"malformed\", \"the command is missing or too long\");\n let c: Record<string, unknown>;\n try {\n c = JSON.parse(str);\n } catch {\n return bad(\"malformed\", \"the command is not JSON\");\n }\n if (!c || typeof c !== \"object\" || Array.isArray(c)) return bad(\"malformed\", \"the command is not an object\");\n if (c.v !== 1 || c.t !== COMMAND_TYPE) return bad(\"not_a_command\", \"not a sessionpipe control command\");\n if (typeof c.machine !== \"string\" || !MACHINE_RE.test(c.machine)) return bad(\"malformed\", \"which machine?\");\n if (!CONTROL_KINDS.includes(c.kind as ControlKind))\n return bad(\"unknown_kind\", \"the command asks for something unknown\");\n const ref = parseSessionRef(c.session);\n if (!ref) return bad(\"bad_session\", \"which session?\");\n if (typeof c.nonce !== \"string\" || !NONCE_RE.test(c.nonce))\n return bad(\"bad_nonce\", \"the nonce is missing or malformed\");\n if (!isNum(c.iat) || !Number.isInteger(c.iat) || c.iat <= 0)\n return bad(\"malformed\", \"the command has no signing time\");\n const has = (k: string) => c[k] !== undefined;\n const text = () => typeof c.text === \"string\" && c.text.trim().length > 0 && c.text.length <= TEXT_MAX;\n // A handoff's reply_to (CONTROL.md \u00A76): a session ref by the adapter's rule, never the\n // command's own session. Whether that session is on this machine is the daemon's to say.\n const replyToProblem = (): string | null => {\n if (!has(\"reply_to\")) return null;\n if (!parseSessionRef(c.reply_to)) return \"a reply_to names a session on this machine, <harness>:<id>\";\n if (c.reply_to === c.session) return \"a reply_to can't name the command's own session\";\n return null;\n };\n switch (c.kind as ControlKind) {\n case \"prompt\": {\n if (!text()) return bad(\"bad_fields\", `a prompt needs text, at most ${TEXT_MAX} characters`);\n if (has(\"for\") || has(\"decision\") || has(\"cwd\"))\n return bad(\"bad_fields\", \"a prompt carries text, files and a reply_to\");\n if (has(\"files\")) {\n const why = filesProblem(c.files);\n if (why) return bad(\"bad_fields\", why);\n }\n const why = replyToProblem();\n if (why) return bad(\"bad_fields\", why);\n break;\n }\n case \"start\": {\n if (!text()) return bad(\"bad_fields\", `a new session needs text, at most ${TEXT_MAX} characters`);\n if (typeof c.cwd !== \"string\" || !(isAbsolute(c.cwd) || HOME_RELATIVE_RE.test(c.cwd)) || c.cwd.length > 1024)\n return bad(\"bad_fields\", \"a new session needs an absolute folder, or one under ~\");\n if (has(\"for\") || has(\"decision\"))\n return bad(\"bad_fields\", \"a start carries text, cwd, files, a permission, a model and a reply_to\");\n if (has(\"files\")) {\n const why = filesProblem(c.files);\n if (why) return bad(\"bad_fields\", why);\n }\n const why = replyToProblem();\n if (why) return bad(\"bad_fields\", why);\n if (has(\"permission\") && levelRank(c.permission) < 0)\n return bad(\"bad_fields\", `a permission is one of ${START_PERMISSIONS.join(\", \")}`);\n if (has(\"model\") && (typeof c.model !== \"string\" || c.model.length > MODEL_MAX || !MODEL_RE.test(c.model)))\n return bad(\"bad_fields\", `a model is 1 to ${MODEL_MAX} letters, digits and ._:/@[]-, not starting with a dash`);\n break;\n }\n case \"permission.answer\":\n if (typeof c.for !== \"string\" || !c.for || c.for.length > 128) return bad(\"bad_fields\", \"which attention?\");\n if (c.decision !== \"allow\" && c.decision !== \"deny\") return bad(\"bad_fields\", \"allow or deny?\");\n if (has(\"text\") || has(\"cwd\")) return bad(\"bad_fields\", \"a permission answer carries for, decision and note\");\n break;\n case \"cancel\":\n if (has(\"text\") || has(\"for\") || has(\"decision\") || has(\"cwd\"))\n return bad(\"bad_fields\", \"a cancel carries nothing\");\n break;\n case \"key.add\": {\n // A device the machine already trusts vouches for a new one (CONTROL.md \u00A72): the\n // new key's id, algorithm and public key (and, for a device key, its type, store\n // and uv) are inside the signed bytes.\n if (c.session !== KEY_STORE_SESSION) return bad(\"bad_session\", `a key.add names ${KEY_STORE_SESSION}`);\n const k = c.key as Record<string, unknown> | undefined;\n const b64 = (v: unknown, min: number, max: number) =>\n typeof v === \"string\" && v.length >= min && v.length <= max && /^[A-Za-z0-9_-]+$/.test(v);\n if (\n !k ||\n typeof k !== \"object\" ||\n Array.isArray(k) ||\n !b64(k.id, 16, 1400) ||\n (k.alg !== -7 && k.alg !== -257) ||\n !b64(k.spki, 40, 2000)\n )\n return bad(\"bad_fields\", \"a key.add carries the key: id, alg (-7 or -257) and spki\");\n if (k.type !== undefined) {\n if (k.type !== \"device\") return bad(\"bad_fields\", \"a key's type is device, or absent for a passkey\");\n if (k.alg !== ES256 || k.store !== \"hardware\" || k.uv !== \"each-use\" || !SHA256_RE.test(k.id as string))\n return bad(\"bad_fields\", \"a device key is ES256, store hardware, uv each-use, named by its SPKI's hash\");\n if (Object.keys(k).some((f) => !DEVICE_FIELDS.has(f)))\n return bad(\"bad_fields\", \"a device key carries id, alg, spki, type, store and uv\");\n } else if (Object.keys(k).some((f) => !PASSKEY_FIELDS.has(f)))\n return bad(\"bad_fields\", \"a passkey carries id, alg and spki: store and uv belong to a device key\");\n if (has(\"name\") && (typeof c.name !== \"string\" || !c.name.trim() || c.name.length > 80))\n return bad(\"bad_fields\", \"a device name is text, at most 80 characters\");\n if (has(\"text\") || has(\"for\") || has(\"decision\") || has(\"cwd\"))\n return bad(\"bad_fields\", \"a key.add carries the key and a name\");\n break;\n }\n case \"pool\": {\n if (c.session !== POOL_SESSION) return bad(\"bad_session\", `a pool command names ${POOL_SESSION}`);\n const why = poolProblem(c);\n if (why) return bad(\"bad_fields\", why);\n break;\n }\n }\n if (c.kind !== \"key.add\" && (has(\"key\") || has(\"name\"))) return bad(\"bad_fields\", \"only a key.add carries a key\");\n if (c.kind !== \"pool\" && POOL_FIELDS.some(has)) return bad(\"bad_fields\", \"only a pool command carries pool fields\");\n if (c.kind !== \"start\" && c.kind !== \"prompt\" && has(\"files\"))\n return bad(\"bad_fields\", \"only a start or a prompt carries files\");\n if (c.kind !== \"start\" && c.kind !== \"prompt\" && has(\"reply_to\"))\n return bad(\"bad_fields\", \"only a start or a prompt carries a reply_to\");\n if (c.kind !== \"start\" && (has(\"permission\") || has(\"model\")))\n return bad(\"bad_fields\", \"only a start carries a model or a permission\");\n const allowed = FIELDS[c.kind as ControlKind];\n for (const k of Object.keys(c))\n if (!COMMON_FIELDS.includes(k) && !allowed.includes(k))\n return bad(\"bad_fields\", `the command carries a field its kind doesn't have: ${k.slice(0, 40)}`);\n if (has(\"note\") && (typeof c.note !== \"string\" || c.note.length > NOTE_MAX))\n return bad(\"bad_fields\", `a note is text, at most ${NOTE_MAX} characters`);\n return { ok: true, cmd: c as unknown as ControlCommand };\n}\n\n/** Why a `pool` command's fields break the rules (CONTROL.md \u00A76 `pool`), or null when\n * they don't. Each action takes exactly its own fields: an account where the action\n * names one, the whole order for `order`, the pasted code for `login.complete`. The\n * machine still checks the account exists and the order is complete (`refused`). */\nexport function poolProblem(c: Record<string, unknown>): string | null {\n const action = c.pool_action;\n if (!POOL_ACTIONS.includes(action as PoolAction))\n return `a pool command's action is one of ${POOL_ACTIONS.join(\", \")}`;\n const a = action as PoolAction;\n const account = c.pool_account !== undefined;\n const order = c.pool_order !== undefined;\n const code = c.pool_code !== undefined;\n const needsAccount = a === \"enable\" || a === \"disable\" || a === \"remove\" || a === \"login.complete\";\n if (needsAccount && !account) return `${a} names an account`;\n if (account && !needsAccount && a !== \"refresh\") return `${a} names no account`;\n if (account && (typeof c.pool_account !== \"string\" || !POOL_ACCOUNT_RE.test(c.pool_account)))\n return \"an account id is 1 to 128 letters, digits and ._:-\";\n if (order !== (a === \"order\")) return a === \"order\" ? \"order carries the whole order\" : `${a} carries no order`;\n if (order) {\n const o = c.pool_order;\n if (!Array.isArray(o) || o.length < 1 || o.length > POOL_ORDER_MAX)\n return `an order is a list of 1 to ${POOL_ORDER_MAX} account ids`;\n if (!o.every((id) => typeof id === \"string\" && POOL_ACCOUNT_RE.test(id)))\n return \"an account id is 1 to 128 letters, digits and ._:-\";\n if (new Set(o).size !== o.length) return \"an order names each account once\";\n }\n if (code !== (a === \"login.complete\"))\n return a === \"login.complete\" ? \"login.complete carries the pasted code\" : `${a} carries no code`;\n if (\n code &&\n (typeof c.pool_code !== \"string\" ||\n !c.pool_code ||\n c.pool_code.length > POOL_CODE_MAX ||\n /[\\r\\n]/.test(c.pool_code))\n )\n return `a code is 1 to ${POOL_CODE_MAX} characters on one line`;\n return null;\n}\n\n/** Why a start's or a prompt's `files` breaks the rules (CONTROL.md \u00A73.1), or null when it doesn't. */\nexport function filesProblem(files: unknown): string | null {\n if (!Array.isArray(files) || files.length < 1 || files.length > FILES_MAX)\n return `files is a list of 1 to ${FILES_MAX} files`;\n const seen = new Set<string>();\n let total = 0;\n for (const f of files as unknown[]) {\n if (!f || typeof f !== \"object\" || Array.isArray(f)) return \"each file is an object\";\n const { name, type, size, sha256: hash } = f as Record<string, unknown>;\n const why = fileNameProblem(name);\n if (why) return why;\n const key = (name as string).normalize(\"NFC\").toLowerCase();\n if (seen.has(key)) return \"two files have the same name\";\n seen.add(key);\n if (typeof type !== \"string\" || type.length > FILE_TYPE_MAX || !MEDIA_TYPE_RE.test(type))\n return \"a file's type isn't a media type\";\n if (!isNum(size) || !Number.isInteger(size) || size < 1 || size > FILE_SIZE_MAX)\n return `a file is 1 byte to ${FILE_SIZE_MAX / 1_048_576} MiB`;\n total += size;\n if (typeof hash !== \"string\" || !SHA256_RE.test(hash)) return \"a file's sha256 isn't 43 base64url characters\";\n }\n if (total > FILES_TOTAL_MAX) return `the files add up to more than ${FILES_TOTAL_MAX / 1_048_576} MiB`;\n return null;\n}\n\n/** A file name the machine can write inside the session's folder and nowhere else. */\nfunction fileNameProblem(name: unknown): string | null {\n if (typeof name !== \"string\") return \"a file has no name\";\n const n = name.normalize(\"NFC\");\n if (n.length < 1 || n.length > FILE_NAME_MAX) return `a file name is 1 to ${FILE_NAME_MAX} characters`;\n if (BAD_NAME_CHAR_RE.test(n)) return \"a file name has a slash, a backslash or a control character\";\n if (n.startsWith(\".\")) return \"a file name can't start with a dot\";\n return null;\n}\n\n/** A `start`'s `cwd` under the machine's home: `~` alone, or `~/\u2026` (`~\\\u2026`). Never `~user`. */\nexport const HOME_RELATIVE_RE = /^~(?:[\\\\/]|$)/;\n\nfunction isAbsolute(p: string): boolean {\n return p.startsWith(\"/\") || /^[A-Za-z]:[\\\\/]/.test(p);\n}\n\n/** The grant's fields, read from its exact string. */\nexport function parseGrant(str: unknown, rpId: string): { ok: true; grant: Grant } | { ok: false; why: string } {\n if (typeof str !== \"string\" || !str || str.length > GRANT_STR_MAX)\n return { ok: false, why: \"the grant is missing or too long\" };\n let g: Record<string, unknown>;\n try {\n g = JSON.parse(str);\n } catch {\n return { ok: false, why: \"the grant is not JSON\" };\n }\n if (!g || typeof g !== \"object\" || Array.isArray(g)) return { ok: false, why: \"the grant is not an object\" };\n if (g.v !== 1 || g.t !== GRANT_TYPE) return { ok: false, why: \"not a sessionpipe grant\" };\n if (g.rp !== rpId) return { ok: false, why: `the grant is for another site, not ${rpId}` };\n if (typeof g.pub !== \"string\" || !fromB64url(g.pub)?.length || !isNum(g.iat) || !isNum(g.exp))\n return { ok: false, why: \"the grant is missing parts\" };\n if (g.exp <= g.iat) return { ok: false, why: \"the grant ends before it starts\" };\n if (g.exp - g.iat > GRANT_MAX_MS + SKEW_MS) return { ok: false, why: \"the grant lasts longer than a day\" };\n return { ok: true, grant: g as unknown as Grant };\n}\n\nasync function verifyCsig(cmd: string, csig: unknown, pub: string): Promise<boolean> {\n const sig = fromB64url(csig);\n const spki = fromB64url(pub);\n if (sig?.length !== 64 || !spki) return false;\n try {\n const key = await crypto.subtle.importKey(\"spki\", u8(spki), { name: \"ECDSA\", namedCurve: \"P-256\" }, false, [\n \"verify\",\n ]);\n return await crypto.subtle.verify({ name: \"ECDSA\", hash: \"SHA-256\" }, key, u8(sig), u8(enc.encode(cmd)));\n } catch {\n return false;\n }\n}\n\n/**\n * Is this a command this machine may run? The checks, in the spec's order\n * (CONTROL.md \u00A75): the exact bytes parse; the command is for this machine; it is\n * signed \u2014 a grant from a trusted key plus the day key's signature, or a confirm\n * over sha256(cmd); it was signed within the age window; its nonce is new.\n * The folder and open-attention checks that follow are the daemon's (they read\n * the machine, not the command).\n */\nexport async function verifyCommand(s: SignedCommand | null | undefined, o: VerifyOptions): Promise<Verdict> {\n if (!s || typeof s !== \"object\") return bad(\"malformed\", \"nothing to verify\");\n const p = parseCommand(s.cmd);\n if (!p.ok) return p;\n const c = p.cmd;\n if (c.machine !== o.machine) return bad(\"wrong_machine\", \"the command is for another machine\");\n\n let via: \"grant\" | \"confirm\" | null = null;\n let keyId = \"\";\n let refusal: { code: RefusalCode; why: string } | null = null;\n if (s.grant && s.csig) {\n const r = await checkGrantPath(s, c, o);\n if (r.ok) {\n via = \"grant\";\n keyId = r.key;\n } else refusal = r;\n }\n if (!via && s.confirm) {\n const key = o.trusted.find((k) => k && k.id === s.confirm?.cred);\n if (!key) refusal = { code: \"untrusted_key\", why: \"confirmed with a key this machine doesn't trust\" };\n else {\n const a = await verifyAssertion(await sha256(s.cmd), s.confirm, key, o.rpId, \"confirm\");\n if (a.ok) {\n via = \"confirm\";\n keyId = key.id;\n } else refusal = { code: \"bad_assertion\", why: a.why };\n }\n }\n if (!via) {\n if (refusal) return bad(refusal.code, refusal.why);\n return bad(\"unsigned\", \"the command isn't signed\");\n }\n // The key a key.add brings must be one this machine could ever verify with: an\n // SPKI it can import for that alg, and every rule of a device key (CONTROL.md \u00A72).\n if (c.kind === \"key.add\" && !(await keyUsable(c.key as TrustedKey)))\n return bad(\"bad_fields\", \"the key a key.add brings is in a shape this machine can't use\");\n if (c.iat > o.now + SKEW_MS) return bad(\"future\", \"the command is dated in the future (is this clock right?)\");\n if (o.now - c.iat > (o.maxAgeMs ?? MACHINE_MAX_AGE_MS)) return bad(\"too_old\", \"the command was signed too long ago\");\n if (o.seenNonce) {\n let seen: boolean;\n try {\n seen = await o.seenNonce(c.nonce);\n } catch {\n return bad(\"nonce_store\", \"the nonce couldn't be recorded, so the command didn't run\");\n }\n if (seen) return bad(\"replay\", \"this command was already used once\");\n }\n return { ok: true, cmd: c, via, key: keyId };\n}\n\nasync function checkGrantPath(\n s: SignedCommand,\n c: ControlCommand,\n o: VerifyOptions,\n): Promise<{ ok: true; key: string } | { ok: false; code: RefusalCode; why: string }> {\n const bundle = s.grant as GrantBundle | DeviceGrant;\n const g = parseGrant(bundle.str, o.rpId);\n if (!g.ok) return bad(\"bad_grant\", g.why);\n const key = o.trusted.find((k) => k && k.id === bundle.cred);\n if (!key) return bad(\"untrusted_key\", \"the grant was signed with a key this machine doesn't trust\");\n const a = await verifyAssertion(await sha256(bundle.str), bundle, key, o.rpId, \"grant\");\n if (!a.ok) return bad(\"bad_assertion\", a.why);\n if (!(await verifyCsig(s.cmd, s.csig, g.grant.pub)))\n return bad(\"bad_csig\", \"the command's signature doesn't verify \u2014 it was changed after it was signed\");\n if (c.iat < g.grant.iat - SKEW_MS || c.iat > g.grant.exp)\n return bad(\"outside_grant\", \"the command was signed outside its grant's window\");\n return { ok: true, key: key.id };\n}\n"],
5
- "mappings": ";;;AAaO,IAAM,eAAe,KAAK;AAE1B,IAAM,UAAU,IAAI;AAGpB,IAAM,qBAAqB,KAAK;AAGhC,IAAM,sBAAsB,KAAK;AAEjC,IAAM,eAAe,KAAK;AAE1B,IAAM,UAAU;AAEhB,IAAM,WAAW;AAEjB,IAAM,WAAW;AAEjB,IAAM,gBAAgB;AAEtB,IAAM,YAAY;AAElB,IAAM,gBAAgB;AAEtB,IAAM,kBAAkB;AAExB,IAAM,gBAAgB;AAEtB,IAAM,gBAAgB;AAEtB,IAAM,YAAY;AAElB,IAAM,WAAW;AAGjB,IAAM,oBAAoB,CAAC,QAAQ,QAAQ,SAAS,MAAM;AAQ1D,IAAM,YAAY,CAAC,MAAuB,kBAAkB,QAAQ,CAAoB;AAGxF,IAAM,WAAW,CAAC,GAAoB,MAC3C,UAAU,CAAC,KAAK,UAAU,CAAC,IAAI,IAAI;AAE9B,IAAM,eAAe;AACrB,IAAM,aAAa;AACnB,IAAM,gBAAgB,CAAC,UAAU,qBAAqB,UAAU,SAAS,WAAW,MAAM;AAE1F,IAAM,oBAAoB;AAE1B,IAAM,eAAe;AAIrB,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,kBAAkB;AAExB,IAAM,iBAAiB;AAEvB,IAAM,gBAAgB;AAGtB,IAAM,QAAQ;AACd,IAAM,QAAQ;AAId,IAAM,YAAY,CAAC,WAAW,QAAQ;AA8I7C,IAAM,WAAW;AACjB,IAAM,aAAa;AACnB,IAAM,aAAa;AACnB,IAAM,gBAAgB;AACtB,IAAM,UAAU;AAChB,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,gBAAgB;AAEtB,IAAM,mBAAmB;AACzB,IAAM,KAAK;AACX,IAAM,KAAK;AAKX,IAAM,gBAAmC,CAAC,KAAK,KAAK,WAAW,QAAQ,WAAW,SAAS,OAAO,MAAM;AACxG,IAAM,SAAiD;AAAA,EACrD,QAAQ,CAAC,QAAQ,SAAS,UAAU;AAAA,EACpC,qBAAqB,CAAC,OAAO,UAAU;AAAA,EACvC,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,QAAQ,OAAO,SAAS,cAAc,SAAS,UAAU;AAAA,EACjE,WAAW,CAAC,OAAO,MAAM;AAAA,EACzB,MAAM,CAAC,eAAe,gBAAgB,cAAc,WAAW;AACjE;AAEA,IAAM,cAAc,OAAO;AAI3B,IAAM,MAAM,IAAI,YAAY;AAC5B,IAAM,MAAM,IAAI,YAAY;AAErB,SAAS,OAAO,OAAyC;AAC9D,QAAM,IAAI,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;AACpE,MAAI,IAAI;AACR,aAAW,KAAK,EAAG,MAAK,OAAO,aAAa,CAAC;AAC7C,SAAO,KAAK,CAAC,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC1E;AAGO,SAAS,WAAW,GAA+B;AACxD,MAAI,OAAO,MAAM,YAAY,CAAC,UAAU,KAAK,CAAC,KAAK,EAAE,SAAS,MAAM,EAAG,QAAO;AAC9E,MAAI;AACF,UAAM,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,IAAI,MAAM,OAAO,EAAE,SAAS,KAAK,CAAC,CAAC;AAC1F,UAAM,MAAM,IAAI,WAAW,IAAI,MAAM;AACrC,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,IAAK,KAAI,CAAC,IAAI,IAAI,WAAW,CAAC;AAC9D,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,OAAO,MAAgD;AAC3E,QAAM,QAAQ,OAAO,SAAS,WAAW,IAAI,OAAO,IAAI,IAAI;AAC5D,SAAO,IAAI,WAAW,MAAM,OAAO,OAAO,OAAO,WAAW,KAAgC,CAAC;AAC/F;AAEA,SAAS,OAAO,GAAe,GAA2B;AACxD,QAAM,MAAM,IAAI,WAAW,EAAE,SAAS,EAAE,MAAM;AAC9C,MAAI,IAAI,GAAG,CAAC;AACZ,MAAI,IAAI,GAAG,EAAE,MAAM;AACnB,SAAO;AACT;AAGO,SAAS,UAAU,GAAe,GAAwB;AAC/D,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,MAAM,EAAE,CAAC,IAAgB,EAAE,CAAC;AAC/D,SAAO,MAAM;AACf;AAKO,SAAS,SAAS,KAAoC;AAC3D,MAAI,IAAI,CAAC,MAAM,MAAQ,IAAI,SAAS,KAAK,IAAI,CAAC,MAAM,IAAI,SAAS,EAAG,QAAO;AAC3E,MAAI,IAAI;AACR,QAAM,OAAO,MAAyB;AACpC,QAAI,IAAI,CAAC,MAAM,EAAM,QAAO;AAC5B,UAAM,MAAM,IAAI,IAAI,CAAC;AACrB,QAAI,MAAM,KAAK,MAAM,GAAI,QAAO;AAChC,QAAI,IAAI,IAAI,MAAM,IAAI,GAAG,IAAI,IAAI,GAAG;AACpC,QAAI,EAAE,WAAW,IAAK,QAAO;AAE7B,QAAK,EAAE,CAAC,IAAe,IAAM,QAAO;AACpC,QAAI,MAAM,KAAK,EAAE,CAAC,MAAM,KAAK,EAAG,EAAE,CAAC,IAAe,KAAO,QAAO;AAChE,SAAK,IAAI;AACT,WAAO,EAAE,SAAS,MAAM,EAAE,CAAC,MAAM,EAAG,KAAI,EAAE,MAAM,CAAC;AACjD,QAAI,EAAE,SAAS,GAAI,QAAO;AAC1B,UAAM,MAAM,IAAI,WAAW,EAAE;AAC7B,QAAI,IAAI,GAAG,KAAK,EAAE,MAAM;AACxB,WAAO;AAAA,EACT;AACA,QAAM,IAAI,KAAK;AACf,QAAM,IAAI,KAAK;AACf,SAAO,KAAK,KAAK,MAAM,IAAI,SAAS,OAAO,GAAG,CAAC,IAAI;AACrD;AAEA,IAAM,KAAK,CAAC,MAAkB;AAI9B,eAAe,UAAU,KAA4C;AACnE,QAAM,OAAO,WAAW,IAAI,IAAI;AAChC,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,MAAI;AACF,QAAI,IAAI,QAAQ;AACd,aAAO,MAAM,OAAO,OAAO,UAAU,QAAQ,GAAG,IAAI,GAAG,EAAE,MAAM,SAAS,YAAY,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;AAClH,QAAI,IAAI,QAAQ,OAAO;AACrB,YAAM,IAAI,MAAM,OAAO,OAAO,UAAU,QAAQ,GAAG,IAAI,GAAG,EAAE,MAAM,qBAAqB,MAAM,UAAU,GAAG,OAAO;AAAA,QAC/G;AAAA,MACF,CAAC;AACD,YAAM,OAAQ,EAAE,UAAyC;AACzD,aAAO,QAAQ,OAAO,IAAI;AAAA,IAC5B;AAAA,EACF,QAAQ;AAAA,EAAC;AACT,SAAO;AACT;AAEA,IAAM,iBAAiB,oBAAI,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AACpD,IAAM,gBAAgB,oBAAI,IAAI,CAAC,MAAM,OAAO,QAAQ,QAAQ,SAAS,IAAI,CAAC;AAEnE,IAAM,oBAAoB,CAAC,QAAQ,OAAO,KAAK;AAGtD,eAAsB,YAAY,MAA+B;AAC/D,SAAO,OAAO,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI,WAAW,CAAC,CAAC;AAClE;AAKA,eAAe,kBAAkB,KAAmC;AAClE,MAAI,IAAI,SAAS,YAAY,IAAI,QAAQ,SAAS,IAAI,UAAU,cAAc,IAAI,OAAO,WAAY,QAAO;AAC5G,MAAI,OAAO,IAAI,OAAO,YAAY,CAAC,UAAU,KAAK,IAAI,EAAE,KAAK,IAAI,OAAQ,MAAM,YAAY,IAAI,IAAI,EAAI,QAAO;AAC9G,SAAO,CAAC,CAAE,MAAM,UAAU,GAAG;AAC/B;AAMA,eAAsB,UAAU,KAAmC;AACjE,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,EAAG,QAAO;AAClE,QAAM,SAAS,OAAO,KAAK,GAAG,EAAE,OAAO,CAAC,MAAO,IAA2C,CAAC,MAAM,MAAS;AAC1G,MAAI,UAAU,OAAO,IAAI,SAAS,QAAW;AAC3C,QAAI,CAAC,OAAO,MAAM,CAAC,MAAM,cAAc,IAAI,CAAC,CAAC,EAAG,QAAO;AACvD,WAAO,kBAAkB,GAAG;AAAA,EAC9B;AACA,MAAI,CAAC,OAAO,MAAM,CAAC,MAAM,eAAe,IAAI,CAAC,CAAC,EAAG,QAAO;AACxD,SACE,OAAO,IAAI,OAAO,YAClB,2BAA2B,KAAK,IAAI,EAAE,MACrC,IAAI,QAAQ,SAAS,IAAI,QAAQ,UAClC,CAAC,CAAE,MAAM,UAAU,GAAG;AAE1B;AAMO,SAAS,cAAc,QAAiB,MAAuB;AACpE,MAAI,OAAO,WAAW,YAAY,CAAC,KAAM,QAAO;AAChD,MAAI;AACJ,MAAI;AACF,QAAI,IAAI,IAAI,MAAM;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,EAAE,WAAW,OAAQ,QAAO;AAChC,MAAI,EAAE,aAAa,QAAQ,CAAC,EAAE,SAAS,SAAS,IAAI,IAAI,EAAE,EAAG,QAAO;AACpE,MAAI,EAAE,aAAa,SAAU,QAAO;AACpC,SAAO,EAAE,aAAa,WAAW,SAAS;AAC5C;AAGA,IAAM,KAAK,CAAC,SAAwB,EAAE,IAAI,OAAO,IAAI;AAK9C,IAAM,iBAAiB;AAOvB,SAAS,cAAc,SAAwB,MAAc,WAAmC;AACrG,SAAO,IAAI,OAAO,GAAG,cAAc;AAAA,EAAK,OAAO;AAAA,EAAK,IAAI;AAAA,EAAK,OAAO,SAAS,CAAC,EAAE;AAClF;AAGA,eAAe,sBACb,WACA,GACA,KACA,MACA,SACgB;AAChB,QAAM,MAAM;AACZ,aAAW,KAAK,OAAO,KAAK,GAAG,GAAG;AAChC,QAAI,MAAM,SAAS,YAAY,QAAS;AACxC,QAAI,MAAM,UAAU,MAAM,MAAO,QAAO,GAAG,uCAAuC,CAAC,EAAE;AAAA,EACvF;AACA,MAAI,IAAI,SAAS,IAAI,GAAI,QAAO,GAAG,8CAA8C;AACjF,MAAI,CAAE,MAAM,kBAAkB,GAAG,EAAI,QAAO,GAAG,2BAA2B;AAC1E,QAAM,MAAM,WAAW,IAAI,GAAG;AAC9B,MAAI,KAAK,WAAW,GAAI,QAAO,GAAG,gDAA2C;AAC7E,QAAM,MAAM,MAAM,UAAU,GAAG;AAE/B,MAAI,CAAC,IAAK,QAAO,GAAG,2BAA2B;AAC/C,MAAI,OAAO;AACX,MAAI;AACF,WAAO,MAAM,OAAO,OAAO;AAAA,MACzB,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,MACjC;AAAA,MACA,GAAG,GAAG;AAAA,MACN,GAAG,cAAc,SAAS,MAAM,SAAS,CAAC;AAAA,IAC5C;AAAA,EACF,QAAQ;AAEN,WAAO;AAAA,EACT;AACA,SAAO,OAAO,EAAE,IAAI,KAAK,IAAI,GAAG,2CAA2C;AAC7E;AAKA,eAAsB,gBACpB,WACA,GACA,KACA,MACA,SACgB;AAChB,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO,GAAG,sBAAsB;AACjE,MAAI,IAAI,SAAS,QAAW;AAC1B,QAAI,IAAI,SAAS,SAAU,QAAO,GAAG,2BAA2B;AAChE,WAAO,sBAAsB,WAAW,GAAG,KAAK,MAAM,OAAO;AAAA,EAC/D;AACA,SAAO,uBAAuB,WAAW,GAAgB,KAAK,IAAI;AACpE;AAEA,eAAe,uBACb,WACA,GACA,KACA,MACgB;AAChB,MAAI,EAAE,SAAS,IAAI,GAAI,QAAO,GAAG,8CAA8C;AAC/E,QAAM,UAAU,WAAW,EAAE,EAAE;AAC/B,QAAM,KAAK,WAAW,EAAE,EAAE;AAC1B,QAAM,MAAM,WAAW,EAAE,GAAG;AAC5B,MAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,OAAQ,QAAO,GAAG,oCAAoC;AACnF,MAAI;AACJ,MAAI;AACF,SAAK,KAAK,MAAM,IAAI,OAAO,OAAO,CAAC;AAAA,EACrC,QAAQ;AACN,WAAO,GAAG,oCAAoC;AAAA,EAChD;AACA,MAAI,CAAC,MAAM,OAAO,OAAO,SAAU,QAAO,GAAG,oCAAoC;AACjF,MAAI,GAAG,SAAS,eAAgB,QAAO,GAAG,2CAA2C;AACrF,MAAI,GAAG,cAAc,OAAO,SAAS,EAAG,QAAO,GAAG,mCAAmC;AACrF,MAAI,CAAC,cAAc,GAAG,QAAQ,IAAI,EAAG,QAAO,GAAG,6CAA6C,IAAI,EAAE;AAClG,MAAI,GAAG,gBAAgB,KAAM,QAAO,GAAG,kDAAkD;AACzF,MAAI,GAAG,SAAS,GAAI,QAAO,GAAG,oCAAoC;AAClE,MAAI,CAAC,UAAU,GAAG,MAAM,GAAG,EAAE,GAAG,MAAM,OAAO,IAAI,CAAC,EAAG,QAAO,GAAG,4CAA4C,IAAI,EAAE;AACjH,QAAM,QAAQ,GAAG,EAAE;AACnB,MAAI,EAAE,QAAQ,OAAO,EAAE,QAAQ,IAAK,QAAO,GAAG,iDAAiD;AAC/F,QAAM,MAAM,MAAM,UAAU,GAAG;AAC/B,MAAI,CAAC,IAAK,QAAO,GAAG,2BAA2B;AAC/C,QAAM,SAAS,OAAO,IAAI,MAAM,OAAO,OAAO,CAAC;AAC/C,MAAI,OAAO;AACX,MAAI;AACF,QAAI,IAAI,QAAQ,OAAO;AACrB,YAAM,MAAM,SAAS,GAAG;AACxB,aAAO,CAAC,CAAC,OAAQ,MAAM,OAAO,OAAO,OAAO,EAAE,MAAM,SAAS,MAAM,UAAU,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,MAAM,CAAC;AAAA,IAC1G,OAAO;AACL,aAAO,MAAM,OAAO,OAAO,OAAO,EAAE,MAAM,oBAAoB,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,MAAM,CAAC;AAAA,IAC3F;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,OAAO,EAAE,IAAI,KAAK,IAAI,GAAG,sCAAsC;AACxE;AAGO,SAAS,cAAc,SAAiB,MAAmC;AAChF,SAAO,OAAO,oBAAoB,OAAO,IAAI,IAAI,EAAE;AACrD;AAIA,eAAsB,iBAAiB,GAMpB;AACjB,MAAI,CAAE,MAAM,UAAU,EAAE,GAAG,EAAI,QAAO,GAAG,8CAA8C;AACvF,SAAO,gBAAgB,MAAM,cAAc,EAAE,SAAS,EAAE,IAAI,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM;AAC/F;AAKA,IAAM,MAAM,CAAC,MAAmB,SAAgE;AAAA,EAC9F,IAAI;AAAA,EACJ;AAAA,EACA;AACF;AACA,IAAM,QAAQ,CAAC,MAA4B,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AAG9E,SAAS,YAAY,SAAiB,IAAqB;AAChE,MAAI,CAAC,cAAc,KAAK,EAAE,EAAG,QAAO;AACpC,MAAI,YAAY,cAAe,QAAO,QAAQ,KAAK,EAAE;AACrD,SAAO;AACT;AAGO,SAAS,gBAAgB,KAAsD;AACpF,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,IAAI,IAAI,QAAQ,GAAG;AACzB,MAAI,KAAK,EAAG,QAAO;AACnB,QAAM,UAAU,IAAI,MAAM,GAAG,CAAC;AAC9B,QAAM,KAAK,IAAI,MAAM,IAAI,CAAC;AAC1B,SAAO,WAAW,KAAK,OAAO,KAAK,YAAY,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;AAClF;AAGO,SAAS,aAAa,KAAsB;AACjD,MAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,IAAI,SAAS;AAClD,WAAO,IAAI,aAAa,oCAAoC;AAC9D,MAAI;AACJ,MAAI;AACF,QAAI,KAAK,MAAM,GAAG;AAAA,EACpB,QAAQ;AACN,WAAO,IAAI,aAAa,yBAAyB;AAAA,EACnD;AACA,MAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,EAAG,QAAO,IAAI,aAAa,8BAA8B;AAC3G,MAAI,EAAE,MAAM,KAAK,EAAE,MAAM,aAAc,QAAO,IAAI,iBAAiB,mCAAmC;AACtG,MAAI,OAAO,EAAE,YAAY,YAAY,CAAC,WAAW,KAAK,EAAE,OAAO,EAAG,QAAO,IAAI,aAAa,gBAAgB;AAC1G,MAAI,CAAC,cAAc,SAAS,EAAE,IAAmB;AAC/C,WAAO,IAAI,gBAAgB,wCAAwC;AACrE,QAAM,MAAM,gBAAgB,EAAE,OAAO;AACrC,MAAI,CAAC,IAAK,QAAO,IAAI,eAAe,gBAAgB;AACpD,MAAI,OAAO,EAAE,UAAU,YAAY,CAAC,SAAS,KAAK,EAAE,KAAK;AACvD,WAAO,IAAI,aAAa,mCAAmC;AAC7D,MAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,UAAU,EAAE,GAAG,KAAK,EAAE,OAAO;AACxD,WAAO,IAAI,aAAa,iCAAiC;AAC3D,QAAM,MAAM,CAAC,MAAc,EAAE,CAAC,MAAM;AACpC,QAAM,OAAO,MAAM,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,KAAK,EAAE,SAAS,KAAK,EAAE,KAAK,UAAU;AAG9F,QAAM,iBAAiB,MAAqB;AAC1C,QAAI,CAAC,IAAI,UAAU,EAAG,QAAO;AAC7B,QAAI,CAAC,gBAAgB,EAAE,QAAQ,EAAG,QAAO;AACzC,QAAI,EAAE,aAAa,EAAE,QAAS,QAAO;AACrC,WAAO;AAAA,EACT;AACA,UAAQ,EAAE,MAAqB;AAAA,IAC7B,KAAK,UAAU;AACb,UAAI,CAAC,KAAK,EAAG,QAAO,IAAI,cAAc,gCAAgC,QAAQ,aAAa;AAC3F,UAAI,IAAI,KAAK,KAAK,IAAI,UAAU,KAAK,IAAI,KAAK;AAC5C,eAAO,IAAI,cAAc,6CAA6C;AACxE,UAAI,IAAI,OAAO,GAAG;AAChB,cAAMA,OAAM,aAAa,EAAE,KAAK;AAChC,YAAIA,KAAK,QAAO,IAAI,cAAcA,IAAG;AAAA,MACvC;AACA,YAAM,MAAM,eAAe;AAC3B,UAAI,IAAK,QAAO,IAAI,cAAc,GAAG;AACrC;AAAA,IACF;AAAA,IACA,KAAK,SAAS;AACZ,UAAI,CAAC,KAAK,EAAG,QAAO,IAAI,cAAc,qCAAqC,QAAQ,aAAa;AAChG,UAAI,OAAO,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,GAAG,KAAK,iBAAiB,KAAK,EAAE,GAAG,MAAM,EAAE,IAAI,SAAS;AACtG,eAAO,IAAI,cAAc,wDAAwD;AACnF,UAAI,IAAI,KAAK,KAAK,IAAI,UAAU;AAC9B,eAAO,IAAI,cAAc,wEAAwE;AACnG,UAAI,IAAI,OAAO,GAAG;AAChB,cAAMA,OAAM,aAAa,EAAE,KAAK;AAChC,YAAIA,KAAK,QAAO,IAAI,cAAcA,IAAG;AAAA,MACvC;AACA,YAAM,MAAM,eAAe;AAC3B,UAAI,IAAK,QAAO,IAAI,cAAc,GAAG;AACrC,UAAI,IAAI,YAAY,KAAK,UAAU,EAAE,UAAU,IAAI;AACjD,eAAO,IAAI,cAAc,0BAA0B,kBAAkB,KAAK,IAAI,CAAC,EAAE;AACnF,UAAI,IAAI,OAAO,MAAM,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,SAAS,aAAa,CAAC,SAAS,KAAK,EAAE,KAAK;AACtG,eAAO,IAAI,cAAc,mBAAmB,SAAS,yDAAyD;AAChH;AAAA,IACF;AAAA,IACA,KAAK;AACH,UAAI,OAAO,EAAE,QAAQ,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,SAAS,IAAK,QAAO,IAAI,cAAc,kBAAkB;AAC1G,UAAI,EAAE,aAAa,WAAW,EAAE,aAAa,OAAQ,QAAO,IAAI,cAAc,gBAAgB;AAC9F,UAAI,IAAI,MAAM,KAAK,IAAI,KAAK,EAAG,QAAO,IAAI,cAAc,oDAAoD;AAC5G;AAAA,IACF,KAAK;AACH,UAAI,IAAI,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,UAAU,KAAK,IAAI,KAAK;AAC3D,eAAO,IAAI,cAAc,0BAA0B;AACrD;AAAA,IACF,KAAK,WAAW;AAId,UAAI,EAAE,YAAY,kBAAmB,QAAO,IAAI,eAAe,mBAAmB,iBAAiB,EAAE;AACrG,YAAM,IAAI,EAAE;AACZ,YAAM,MAAM,CAAC,GAAY,KAAa,QACpC,OAAO,MAAM,YAAY,EAAE,UAAU,OAAO,EAAE,UAAU,OAAO,mBAAmB,KAAK,CAAC;AAC1F,UACE,CAAC,KACD,OAAO,MAAM,YACb,MAAM,QAAQ,CAAC,KACf,CAAC,IAAI,EAAE,IAAI,IAAI,IAAI,KAClB,EAAE,QAAQ,MAAM,EAAE,QAAQ,QAC3B,CAAC,IAAI,EAAE,MAAM,IAAI,GAAI;AAErB,eAAO,IAAI,cAAc,0DAA0D;AACrF,UAAI,EAAE,SAAS,QAAW;AACxB,YAAI,EAAE,SAAS,SAAU,QAAO,IAAI,cAAc,iDAAiD;AACnG,YAAI,EAAE,QAAQ,SAAS,EAAE,UAAU,cAAc,EAAE,OAAO,cAAc,CAAC,UAAU,KAAK,EAAE,EAAY;AACpG,iBAAO,IAAI,cAAc,8EAA8E;AACzG,YAAI,OAAO,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,cAAc,IAAI,CAAC,CAAC;AAClD,iBAAO,IAAI,cAAc,wDAAwD;AAAA,MACrF,WAAW,OAAO,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,eAAe,IAAI,CAAC,CAAC;AAC1D,eAAO,IAAI,cAAc,yEAAyE;AACpG,UAAI,IAAI,MAAM,MAAM,OAAO,EAAE,SAAS,YAAY,CAAC,EAAE,KAAK,KAAK,KAAK,EAAE,KAAK,SAAS;AAClF,eAAO,IAAI,cAAc,8CAA8C;AACzE,UAAI,IAAI,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,UAAU,KAAK,IAAI,KAAK;AAC3D,eAAO,IAAI,cAAc,sCAAsC;AACjE;AAAA,IACF;AAAA,IACA,KAAK,QAAQ;AACX,UAAI,EAAE,YAAY,aAAc,QAAO,IAAI,eAAe,wBAAwB,YAAY,EAAE;AAChG,YAAM,MAAM,YAAY,CAAC;AACzB,UAAI,IAAK,QAAO,IAAI,cAAc,GAAG;AACrC;AAAA,IACF;AAAA,EACF;AACA,MAAI,EAAE,SAAS,cAAc,IAAI,KAAK,KAAK,IAAI,MAAM,GAAI,QAAO,IAAI,cAAc,8BAA8B;AAChH,MAAI,EAAE,SAAS,UAAU,YAAY,KAAK,GAAG,EAAG,QAAO,IAAI,cAAc,yCAAyC;AAClH,MAAI,EAAE,SAAS,WAAW,EAAE,SAAS,YAAY,IAAI,OAAO;AAC1D,WAAO,IAAI,cAAc,wCAAwC;AACnE,MAAI,EAAE,SAAS,WAAW,EAAE,SAAS,YAAY,IAAI,UAAU;AAC7D,WAAO,IAAI,cAAc,6CAA6C;AACxE,MAAI,EAAE,SAAS,YAAY,IAAI,YAAY,KAAK,IAAI,OAAO;AACzD,WAAO,IAAI,cAAc,8CAA8C;AACzE,QAAM,UAAU,OAAO,EAAE,IAAmB;AAC5C,aAAW,KAAK,OAAO,KAAK,CAAC;AAC3B,QAAI,CAAC,cAAc,SAAS,CAAC,KAAK,CAAC,QAAQ,SAAS,CAAC;AACnD,aAAO,IAAI,cAAc,sDAAsD,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE;AACnG,MAAI,IAAI,MAAM,MAAM,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,SAAS;AAChE,WAAO,IAAI,cAAc,2BAA2B,QAAQ,aAAa;AAC3E,SAAO,EAAE,IAAI,MAAM,KAAK,EAA+B;AACzD;AAMO,SAAS,YAAY,GAA2C;AACrE,QAAM,SAAS,EAAE;AACjB,MAAI,CAAC,aAAa,SAAS,MAAoB;AAC7C,WAAO,qCAAqC,aAAa,KAAK,IAAI,CAAC;AACrE,QAAM,IAAI;AACV,QAAM,UAAU,EAAE,iBAAiB;AACnC,QAAM,QAAQ,EAAE,eAAe;AAC/B,QAAM,OAAO,EAAE,cAAc;AAC7B,QAAM,eAAe,MAAM,YAAY,MAAM,aAAa,MAAM,YAAY,MAAM;AAClF,MAAI,gBAAgB,CAAC,QAAS,QAAO,GAAG,CAAC;AACzC,MAAI,WAAW,CAAC,gBAAgB,MAAM,UAAW,QAAO,GAAG,CAAC;AAC5D,MAAI,YAAY,OAAO,EAAE,iBAAiB,YAAY,CAAC,gBAAgB,KAAK,EAAE,YAAY;AACxF,WAAO;AACT,MAAI,WAAW,MAAM,SAAU,QAAO,MAAM,UAAU,kCAAkC,GAAG,CAAC;AAC5F,MAAI,OAAO;AACT,UAAM,IAAI,EAAE;AACZ,QAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,SAAS;AAClD,aAAO,8BAA8B,cAAc;AACrD,QAAI,CAAC,EAAE,MAAM,CAAC,OAAO,OAAO,OAAO,YAAY,gBAAgB,KAAK,EAAE,CAAC;AACrE,aAAO;AACT,QAAI,IAAI,IAAI,CAAC,EAAE,SAAS,EAAE,OAAQ,QAAO;AAAA,EAC3C;AACA,MAAI,UAAU,MAAM;AAClB,WAAO,MAAM,mBAAmB,2CAA2C,GAAG,CAAC;AACjF,MACE,SACC,OAAO,EAAE,cAAc,YACtB,CAAC,EAAE,aACH,EAAE,UAAU,SAAS,iBACrB,SAAS,KAAK,EAAE,SAAS;AAE3B,WAAO,kBAAkB,aAAa;AACxC,SAAO;AACT;AAGO,SAAS,aAAa,OAA+B;AAC1D,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,MAAM,SAAS;AAC9D,WAAO,2BAA2B,SAAS;AAC7C,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,QAAQ;AACZ,aAAW,KAAK,OAAoB;AAClC,QAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC5D,UAAM,EAAE,MAAM,MAAM,MAAM,QAAQ,KAAK,IAAI;AAC3C,UAAM,MAAM,gBAAgB,IAAI;AAChC,QAAI,IAAK,QAAO;AAChB,UAAM,MAAO,KAAgB,UAAU,KAAK,EAAE,YAAY;AAC1D,QAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,SAAK,IAAI,GAAG;AACZ,QAAI,OAAO,SAAS,YAAY,KAAK,SAAS,iBAAiB,CAAC,cAAc,KAAK,IAAI;AACrF,aAAO;AACT,QAAI,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO;AAChE,aAAO,uBAAuB,gBAAgB,OAAS;AACzD,aAAS;AACT,QAAI,OAAO,SAAS,YAAY,CAAC,UAAU,KAAK,IAAI,EAAG,QAAO;AAAA,EAChE;AACA,MAAI,QAAQ,gBAAiB,QAAO,iCAAiC,kBAAkB,OAAS;AAChG,SAAO;AACT;AAGA,SAAS,gBAAgB,MAA8B;AACrD,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,QAAM,IAAI,KAAK,UAAU,KAAK;AAC9B,MAAI,EAAE,SAAS,KAAK,EAAE,SAAS,cAAe,QAAO,uBAAuB,aAAa;AACzF,MAAI,iBAAiB,KAAK,CAAC,EAAG,QAAO;AACrC,MAAI,EAAE,WAAW,GAAG,EAAG,QAAO;AAC9B,SAAO;AACT;AAGO,IAAM,mBAAmB;AAEhC,SAAS,WAAW,GAAoB;AACtC,SAAO,EAAE,WAAW,GAAG,KAAK,kBAAkB,KAAK,CAAC;AACtD;AAGO,SAAS,WAAW,KAAc,MAAuE;AAC9G,MAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,IAAI,SAAS;AAClD,WAAO,EAAE,IAAI,OAAO,KAAK,mCAAmC;AAC9D,MAAI;AACJ,MAAI;AACF,QAAI,KAAK,MAAM,GAAG;AAAA,EACpB,QAAQ;AACN,WAAO,EAAE,IAAI,OAAO,KAAK,wBAAwB;AAAA,EACnD;AACA,MAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,IAAI,OAAO,KAAK,6BAA6B;AAC3G,MAAI,EAAE,MAAM,KAAK,EAAE,MAAM,WAAY,QAAO,EAAE,IAAI,OAAO,KAAK,0BAA0B;AACxF,MAAI,EAAE,OAAO,KAAM,QAAO,EAAE,IAAI,OAAO,KAAK,sCAAsC,IAAI,GAAG;AACzF,MAAI,OAAO,EAAE,QAAQ,YAAY,CAAC,WAAW,EAAE,GAAG,GAAG,UAAU,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,GAAG;AAC1F,WAAO,EAAE,IAAI,OAAO,KAAK,6BAA6B;AACxD,MAAI,EAAE,OAAO,EAAE,IAAK,QAAO,EAAE,IAAI,OAAO,KAAK,kCAAkC;AAC/E,MAAI,EAAE,MAAM,EAAE,MAAM,eAAe,QAAS,QAAO,EAAE,IAAI,OAAO,KAAK,oCAAoC;AACzG,SAAO,EAAE,IAAI,MAAM,OAAO,EAAsB;AAClD;AAEA,eAAe,WAAW,KAAa,MAAe,KAA+B;AACnF,QAAM,MAAM,WAAW,IAAI;AAC3B,QAAM,OAAO,WAAW,GAAG;AAC3B,MAAI,KAAK,WAAW,MAAM,CAAC,KAAM,QAAO;AACxC,MAAI;AACF,UAAM,MAAM,MAAM,OAAO,OAAO,UAAU,QAAQ,GAAG,IAAI,GAAG,EAAE,MAAM,SAAS,YAAY,QAAQ,GAAG,OAAO;AAAA,MACzG;AAAA,IACF,CAAC;AACD,WAAO,MAAM,OAAO,OAAO,OAAO,EAAE,MAAM,SAAS,MAAM,UAAU,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,IAAI,OAAO,GAAG,CAAC,CAAC;AAAA,EACzG,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,eAAsB,cAAc,GAAqC,GAAoC;AAC3G,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO,IAAI,aAAa,mBAAmB;AAC5E,QAAM,IAAI,aAAa,EAAE,GAAG;AAC5B,MAAI,CAAC,EAAE,GAAI,QAAO;AAClB,QAAM,IAAI,EAAE;AACZ,MAAI,EAAE,YAAY,EAAE,QAAS,QAAO,IAAI,iBAAiB,oCAAoC;AAE7F,MAAI,MAAkC;AACtC,MAAI,QAAQ;AACZ,MAAI,UAAqD;AACzD,MAAI,EAAE,SAAS,EAAE,MAAM;AACrB,UAAM,IAAI,MAAM,eAAe,GAAG,GAAG,CAAC;AACtC,QAAI,EAAE,IAAI;AACR,YAAM;AACN,cAAQ,EAAE;AAAA,IACZ,MAAO,WAAU;AAAA,EACnB;AACA,MAAI,CAAC,OAAO,EAAE,SAAS;AACrB,UAAM,MAAM,EAAE,QAAQ,KAAK,CAAC,MAAM,KAAK,EAAE,OAAO,EAAE,SAAS,IAAI;AAC/D,QAAI,CAAC,IAAK,WAAU,EAAE,MAAM,iBAAiB,KAAK,kDAAkD;AAAA,SAC/F;AACH,YAAM,IAAI,MAAM,gBAAgB,MAAM,OAAO,EAAE,GAAG,GAAG,EAAE,SAAS,KAAK,EAAE,MAAM,SAAS;AACtF,UAAI,EAAE,IAAI;AACR,cAAM;AACN,gBAAQ,IAAI;AAAA,MACd,MAAO,WAAU,EAAE,MAAM,iBAAiB,KAAK,EAAE,IAAI;AAAA,IACvD;AAAA,EACF;AACA,MAAI,CAAC,KAAK;AACR,QAAI,QAAS,QAAO,IAAI,QAAQ,MAAM,QAAQ,GAAG;AACjD,WAAO,IAAI,YAAY,0BAA0B;AAAA,EACnD;AAGA,MAAI,EAAE,SAAS,aAAa,CAAE,MAAM,UAAU,EAAE,GAAiB;AAC/D,WAAO,IAAI,cAAc,+DAA+D;AAC1F,MAAI,EAAE,MAAM,EAAE,MAAM,QAAS,QAAO,IAAI,UAAU,2DAA2D;AAC7G,MAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,oBAAqB,QAAO,IAAI,WAAW,qCAAqC;AACnH,MAAI,EAAE,WAAW;AACf,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,EAAE,UAAU,EAAE,KAAK;AAAA,IAClC,QAAQ;AACN,aAAO,IAAI,eAAe,2DAA2D;AAAA,IACvF;AACA,QAAI,KAAM,QAAO,IAAI,UAAU,oCAAoC;AAAA,EACrE;AACA,SAAO,EAAE,IAAI,MAAM,KAAK,GAAG,KAAK,KAAK,MAAM;AAC7C;AAEA,eAAe,eACb,GACA,GACA,GACoF;AACpF,QAAM,SAAS,EAAE;AACjB,QAAM,IAAI,WAAW,OAAO,KAAK,EAAE,IAAI;AACvC,MAAI,CAAC,EAAE,GAAI,QAAO,IAAI,aAAa,EAAE,GAAG;AACxC,QAAM,MAAM,EAAE,QAAQ,KAAK,CAAC,MAAM,KAAK,EAAE,OAAO,OAAO,IAAI;AAC3D,MAAI,CAAC,IAAK,QAAO,IAAI,iBAAiB,4DAA4D;AAClG,QAAM,IAAI,MAAM,gBAAgB,MAAM,OAAO,OAAO,GAAG,GAAG,QAAQ,KAAK,EAAE,MAAM,OAAO;AACtF,MAAI,CAAC,EAAE,GAAI,QAAO,IAAI,iBAAiB,EAAE,GAAG;AAC5C,MAAI,CAAE,MAAM,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG;AAC/C,WAAO,IAAI,YAAY,kFAA6E;AACtG,MAAI,EAAE,MAAM,EAAE,MAAM,MAAM,WAAW,EAAE,MAAM,EAAE,MAAM;AACnD,WAAO,IAAI,iBAAiB,mDAAmD;AACjF,SAAO,EAAE,IAAI,MAAM,KAAK,IAAI,GAAG;AACjC;",
6
- "names": ["why"]
3
+ "sources": ["../src/control/frames.ts", "../src/control/verify.ts"],
4
+ "sourcesContent": ["// SPDX-License-Identifier: Apache-2.0\n// The words sessionpipe puts around a message it delivers to a session (spec/CONTROL.md\n// \u00A76, \u00A78, \u00A713), in one place: whoever delivers a message builds them here, and the\n// transcript readers take them off again with `deliveredWords`, so the `user` side of a\n// turn is what the person wrote and never what the machine wrote around it. The shapes\n// `deliveredWords` knows are made from the builders themselves, so a sentence changed\n// here is recognised in the same edit. No `node:` imports: this is part of\n// sessionpipe-core/control, which a Worker or a browser may import.\n\n/** The receiver's own host, for a line a person reads; the whole string when it is not\n * a URL. */\nexport function hostOf(receiver: string): string {\n try {\n return new URL(receiver).host;\n } catch {\n return receiver;\n }\n}\n\n/** Said with every message that reaches a live session: the answer goes back by itself\n * (the daemon reads it from the transcript), so the model never invents a command to\n * reply with (a real session tried `sessionpipe send`, 2026-10-01). */\nexport const IN_PLACE_ANSWER =\n \"Answer it right here, as you would answer the person at the keyboard: your reply is sent back to them automatically. Don't run a command or call a tool just to send it.\";\n\n/** What held a session when a copy of it was made instead (CONTROL.md \u00A76, What counts as\n * held), as the machine found it: a window the person has it open in, another process's\n * run of it, or only a write to its transcript too recent to account for. */\nexport const COPY_WHYS = [\"open\", \"running\", \"recent\"] as const;\nexport type CopyWhy = (typeof COPY_WHYS)[number];\n\n/** Said with a message a copy of a session is answering, so the model knows its reply is\n * the only part the person sees, while its work is as real as the original's. The first\n * note said \"nothing you do here appears in it \u2026 don't carry on the original's work\", and\n * a copy asked to change a page answered that a copy can't publish (2026-11-05). Why\n * there is a copy is said as the machine found it: an original that is open in a\n * window, or one that something else is using. */\nexport const copyNote = (why: CopyWhy): string =>\n `You are a copy of that session, started because the original is ${why === \"open\" ? \"open in a window\" : \"in use by another process\"}, and you have its conversation, folder and tools. Do what the message asks the way the original would: edits, commands and publishing all take effect for real. Only this conversation stays here, so the person reads your reply and not the original's${why === \"open\" ? \" window\" : \"\"}: say what you did, then stop.`;\n\n/** Said after a message `sessionpipe wait` prints (CONTROL.md \u00A78), so the session starts\n * the waiter again. */\nexport const RE_ARM =\n \"(Answer it right here, as you would answer the person at the keyboard: your reply is sent back to them automatically, so don't run a command or call a tool just to send it. Then, without mentioning it, start `sessionpipe wait` again as a background command, with the longest timeout your harness allows, to keep receiving messages.)\";\n\n/** The framed text a session receives (CONTROL.md \u00A76): the model and the person can tell\n * where it came from. */\nexport function frame(text: string, receiver: string): string {\n return `Message via ${hostOf(receiver)} (sessionpipe control, signed on the sender's device):\\n\\n${text}`;\n}\n\n/** A message id a mark is made of: a whole ULID, which is what a receiver gives a\n * message (HTTP.md \u00A73). */\nconst WHOLE_ID = /^[0-9A-HJKMNP-TV-Z]{26}$/;\n\n/** What the daemon looks for in the row the session's transcript records for a message\n * handed to it in place (\u00A76 `prompt`, \u00A76.3): a turn is only proof of delivery when it is\n * THIS message's turn, and only what the session wrote after that row is its answer.\n *\n * Null for an id that is not a whole ULID. The id is the receiver's and nobody signed\n * it: its characters go into a header that says \"signed on the sender's device\" and\n * into the session's context, so nothing but the alphabet of a ULID is ever taken from\n * it (an id of `x\\n\\nrm -rf ~` put those words in the header), and a short one (\"42\")\n * would make a mark anybody's words contain. */\nexport const messageMark = (id: string): string | null => (WHOLE_ID.test(id) ? `message ${id.slice(-12)}` : null);\n\nconst marked = (text: string, host: string, mark: string): string =>\n `Message via ${host} (sessionpipe control, signed on the sender's device; ${mark}):\\n\\n${text}`;\n\n/** The framed text for a message handed to a live session in place \u2014 at its Stop, to\n * its waiter, into its inbox: `frame()` with that mark in it, since the session answers\n * in a turn of its own and the daemon has to recognise the message again in the\n * transcript to know which words are its answer. A message whose id makes no mark gets\n * `frame()` as it is: nothing of the id reaches the session. */\nexport function frameMarked(text: string, receiver: string, id: string): string {\n const mark = messageMark(id);\n return mark ? marked(text, hostOf(receiver), mark) : frame(text, receiver);\n}\n\n/** A new session's first message: the person's words first, where they come from after\n * (Claude Code names a session from the start of its first message). */\nexport function frameStart(text: string, receiver: string): string {\n return `${text}\\n\\n(Sent via ${hostOf(receiver)} with sessionpipe control, signed on the sender's device.)`;\n}\n\n/** What a session another session asked receives (CONTROL.md \u00A713), new or not: the task\n * first, then who asked and that the answer travels by itself. Handed to a live session\n * in place it names the message too (`mark`), which is how the daemon finds the answer\n * in that session's transcript. */\nexport function frameAsk(text: string, from: string | undefined, mark?: string | null): string {\n return `${text}\\n\\n(Asked by ${from ?? \"another session\"} on this machine with sessionpipe ask${mark ? `, ${mark}` : \"\"}. Answer here in full: your reply is returned to that session automatically. Don't run a command or call a tool just to send it.)`;\n}\n\n/** A handoff's answer as it lands in `reply_to` (CONTROL.md \u00A76). `from` is the session\n * that answered, as a person reads it (`<harness>:<short id>`). */\nexport function frameHandoff(from: string, receiver: string, reply: string): string {\n return `Answer from ${from} (sessionpipe handoff, asked via ${hostOf(receiver)}):\\n\\n${reply}`;\n}\n\n/** A line a builder above writes, as a test for it: the builder is run with a mark where\n * the host, the id or the asker goes, and the mark becomes `hole`. */\nconst HOLE = \"\\u0000\";\n/** Where a message's own mark goes (`messageMark`), in a line that has another hole too. */\nconst MARK = \"\\u0001\";\nconst lineOf = (built: string, hole = \"(\\\\S+)\"): RegExp =>\n new RegExp(\n `^${built\n .trim()\n .replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replaceAll(HOLE, hole)\n .replaceAll(MARK, \"message \\\\S+\")}$`,\n );\n\n// Where each builder puts its own line in the text a session gets, which is the only\n// place `deliveredWords` looks for it. A new wording (an ask that names its message,\n// another note to a copy) is one more entry in the list its builder belongs to.\n/** The first paragraph, the words after it: `frame`, `frameMarked`. */\nconst HEAD_LINES = [lineOf(frame(\"\", HOLE)), lineOf(marked(\"\", HOLE, MARK))];\n/** The last paragraph, the words before it: `frameStart`. */\nconst START_LINES = [lineOf(frameStart(\"\", HOLE))];\n/** The last paragraph, the task before it: `frameAsk`. Whoever asked is named as it gave\n * itself, a session reference or \"another session\". */\nconst ASK_LINES = [lineOf(frameAsk(\"\", HOLE), \"[^\\\\n]{1,300}\")];\n/** The same paragraph where the ask was handed to a live session in place and names its\n * message: who asked is captured, so the plain paragraph can be put back. */\nconst MARKED_ASK_LINES = [lineOf(frameAsk(\"\", HOLE, MARK), \"([^\\\\n]{1,300}?)\")];\n/** The first paragraph, the answer after it: `frameHandoff`. */\nconst HANDOFF_LINES = [lineOf(frameHandoff(HOLE, HOLE, \"\"))];\n/** What a copy was told by 0.13.0 to 0.13.2. Nothing says it now (told \"nothing you do\n * here appears in it\", a copy asked to change a page answered that a copy can't publish,\n * 2026-11-05); every copy those versions made has it in its transcript, after the\n * person's words, so a reader still takes it off. Read, never written. */\nconst COPY_NOTE_0_13_0 =\n \"You are a copy of that session, started to answer this message: the conversation above is a replay, the original session is somewhere else, and nothing you do here appears in it. Answer the message and stop; don't carry on the original's work.\";\n/** What a copy is told, in each of its forms (two today: an original open in a window,\n * and one something else is using), and what one was told before. */\nconst COPY_NOTES = [...new Set(COPY_WHYS.map(copyNote)), COPY_NOTE_0_13_0];\n/** What the daemon may add, once, after the words of a message a receiver sent\n * (control/daemon.ts: a Stop hook's block and the inbox, a copy, a waiter). */\nconst NOTES = [IN_PLACE_ANSWER, ...COPY_NOTES, RE_ARM];\n/** And after an ask or a handoff's answer, whose own lines already say how the answer\n * travels: a copy, a waiter. */\nconst ASK_NOTES = [...COPY_NOTES, RE_ARM];\n\nconst lineIn = (lines: RegExp[], line: string): RegExpExecArray | null => {\n for (const l of lines) {\n const m = l.exec(line);\n if (m) return m;\n }\n return null;\n};\n\n/** `frame()` and `frameMarked()` undone, and nothing else: the person's words, and the\n * host they were sent from. A channel shows the words as the message and says where\n * they came from beside it (CONTROL.md \u00A78). Text neither of them made comes back whole,\n * from nowhere. */\nexport function unframe(text: string): { text: string; via: string | null } {\n const cut = text.indexOf(\"\\n\\n\");\n const head = cut < 0 ? null : lineIn(HEAD_LINES, text.slice(0, cut));\n return head ? { text: text.slice(cut + 2), via: head[1] as string } : { text, via: null };\n}\n\n/** A delivered message with sessionpipe's own words taken off: the line that says where\n * it came from (before the words for a message into a session that exists, after them\n * for a new session's first), and the one note to the model that may follow the words\n * (how the answer travels, that the session is a copy, to start the waiter again).\n *\n * Only where sessionpipe puts them. A sentence is taken off in the place and the form\n * the daemon builds it in, never because a paragraph somewhere reads like one: a person\n * can type or paste any of these words, and a prompt that is nothing but one of them is\n * what the person wrote. So a note comes off only after the words of a text that carries\n * a frame, a frame's line only with words beside it, and text in no shape the daemon\n * builds comes back exactly as it was, `delivered: false`. `via` is the receiver's host\n * when the frame names one.\n *\n * Kept, because they say whose words these are and not how they travelled: the\n * paragraph `frameAsk` puts after a task (`(Asked by <session> on this machine with\n * sessionpipe ask. \u2026)`, in that plain form whether or not the ask named its message on\n * the way), and the first line of a handoff's answer. The task is an\n * agent's and the answer is an agent's; a receiver tells them from the person's own\n * words by exactly those lines, so a text without them would be drawn as something the\n * person wrote. Kept too, because it is what the message says: the list of attached\n * files. */\nexport function deliveredWords(text: string): { text: string; via: string | null; delivered: boolean } {\n const paragraphs = text.split(\"\\n\\n\");\n const at = (i: number): string => (paragraphs[i] as string).trim();\n const n = paragraphs.length;\n // The words end before a note, when the text ends with one and is more than the note.\n const note = n > 1 && NOTES.includes(at(n - 1)) ? at(n - 1) : null;\n const end = note === null ? n : n - 1;\n const words = (from: number, to: number) => paragraphs.slice(from, to).join(\"\\n\\n\").trim();\n const head = lineIn(HEAD_LINES, at(0));\n if (head && words(1, end)) return { text: words(1, end), via: head[1] as string, delivered: true };\n const start = note === null ? lineIn(START_LINES, at(n - 1)) : null;\n if (start && words(0, n - 1)) return { text: words(0, n - 1), via: start[1] as string, delivered: true };\n // An agent's task and an agent's answer keep the line that says so; a note after it goes.\n const asks = note === null || ASK_NOTES.includes(note);\n const theirs =\n asks && ((lineIn(ASK_LINES, at(end - 1)) && words(0, end - 1)) || (lineIn(HANDOFF_LINES, at(0)) && words(1, end)));\n if (theirs) return { text: note === null ? text : words(0, end), via: null, delivered: true };\n // An ask handed to a live session in place names its message as well. Who asked is\n // kept, in the paragraph every ask has; the mark said how the task travelled, and goes.\n const marked = asks ? lineIn(MARKED_ASK_LINES, at(end - 1)) : null;\n if (marked && words(0, end - 1))\n return { text: `${words(0, end - 1)}${frameAsk(\"\", marked[1] as string)}`, via: null, delivered: true };\n return { text, via: null, delivered: false };\n}\n", "// SPDX-License-Identifier: Apache-2.0\n// The one verifier for signed control commands (spec/CONTROL.md \u00A74\u2013\u00A75). A machine\n// runs a command only when this says yes; a receiver MAY run it too, as an early\n// word, so a bad command is refused where the person can see why.\n//\n// WebCrypto only (globalThis.crypto.subtle), no node: imports, no clock of its own\n// (`now` is passed in): the same file runs in a Cloudflare Worker, in a browser and\n// in Node \u2265 20, and conformance/control-vectors/ pins its answers.\n//\n// Every signature covers the EXACT bytes as transmitted (`cmd`, `grant.str`): the\n// strings are parsed to read their fields and never re-serialized to verify.\n\n/** One passkey tap vouches for a day key for at most this long. */\nexport const GRANT_MAX_MS = 24 * 3600_000;\n/** Clock slack between the signing device, the receiver and the machine. */\nexport const SKEW_MS = 5 * 60_000;\n/** The machine judges a command by when it was SIGNED: a message sent while the lid\n * was shut still runs when the machine wakes, up to a day later. */\nexport const MACHINE_MAX_AGE_MS = 24 * 3600_000;\n/** A receiver's early check is stricter: a command is carried within ten minutes of\n * being signed, or not at all. */\nexport const RECEIVER_MAX_AGE_MS = 10 * 60_000;\n/** How long a machine remembers a nonce: longer than MACHINE_MAX_AGE_MS + SKEW_MS. */\nexport const NONCE_TTL_MS = 25 * 3600_000;\n/** The whole command string, in UTF-16 code units (what JSON.parse sees). */\nexport const CMD_MAX = 48_000;\n/** A prompt's text, in characters. */\nexport const TEXT_MAX = 20_000;\n/** A permission answer's note. */\nexport const NOTE_MAX = 2_000;\n/** A grant string. */\nexport const GRANT_STR_MAX = 2_000;\n/** Attachments on a `start` or a `prompt` (CONTROL.md \u00A73.1): at most this many files, */\nexport const FILES_MAX = 10;\n/** each at most this many bytes, */\nexport const FILE_SIZE_MAX = 26_214_400;\n/** together at most this many, */\nexport const FILES_TOTAL_MAX = 52_428_800;\n/** with a name of at most this many characters (after NFC) */\nexport const FILE_NAME_MAX = 200;\n/** and a media type of at most this many. */\nexport const FILE_TYPE_MAX = 100;\n/** A start's `model` (CONTROL.md \u00A73.1): at most this many characters, */\nexport const MODEL_MAX = 100;\n/** in this alphabet, never starting with a dash, so it can never read as a flag. */\nexport const MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\\]-]*$/;\n/** The levels a start may ask for (CONTROL.md \u00A73.1), narrowest first. Closed: no level\n * is wider than `auto`, and none turns a harness's approval off. */\nexport const START_PERMISSIONS = [\"read\", \"safe\", \"edits\", \"auto\"] as const;\nexport type StartPermission = (typeof START_PERMISSIONS)[number];\n/** What a run is started with: a level, or `inherit` (CONTROL.md \u00A72) \u2014 no permission\n * flag at all, so the harness starts in its own default on the machine. Only a machine\n * the person put in inherit mode runs that way; a command never names it. */\nexport type RunLevel = StartPermission | \"inherit\";\n\n/** Where a level sits, narrowest (0) to widest; -1 for anything that isn't one. */\nexport const levelRank = (l: unknown): number => START_PERMISSIONS.indexOf(l as StartPermission);\n/** The narrower of two levels: what a later run of a session gets, its recorded level\n * against what the machine's mode allows now. */\nexport const narrower = (a: StartPermission, b: StartPermission): StartPermission =>\n levelRank(a) <= levelRank(b) ? a : b;\n\nexport const COMMAND_TYPE = \"sessionpipe.control\";\nexport const GRANT_TYPE = \"sessionpipe.grant\";\nexport const CONTROL_KINDS = [\"prompt\", \"permission.answer\", \"cancel\", \"start\", \"key.add\", \"pool\"] as const;\n/** The session a `key.add` names: the machine's own key store, not a coding session. */\nexport const KEY_STORE_SESSION = \"sessionpipe:keys\";\n/** The session a `pool` names: the machine's account pool (CONTROL.md \u00A76 `pool`). */\nexport const POOL_SESSION = \"sessionpipe:pool\";\nexport type ControlKind = (typeof CONTROL_KINDS)[number];\n/** What a `pool` command may ask (CONTROL.md \u00A76 `pool`). Closed: an action added later\n * is refused by every machine that predates it. */\nexport const POOL_ACTIONS = [\n \"on\",\n \"off\",\n \"enable\",\n \"disable\",\n \"remove\",\n \"order\",\n \"refresh\",\n \"login.begin\",\n \"login.complete\",\n] as const;\nexport type PoolAction = (typeof POOL_ACTIONS)[number];\n/** A pooled account's id (or a pending sign-in's): what `session.account_id` carries. */\nexport const POOL_ACCOUNT_RE = /^[A-Za-z0-9._:-]{1,128}$/;\n/** An `order` names at most this many accounts: the pool holds no more. */\nexport const POOL_ORDER_MAX = 20;\n/** The pasted `code#state` of a sign-in, in characters, on one line. */\nexport const POOL_CODE_MAX = 512;\n\n/** COSE algorithm ids a trusted key may use. */\nexport const ES256 = -7;\nexport const RS256 = -257;\n\n/** The kinds of key a machine may trust (CONTROL.md \u00A72): a WebAuthn passkey, or a\n * device key an app holds in hardware where it has no passkey to offer. */\nexport const KEY_TYPES = [\"passkey\", \"device\"] as const;\nexport type KeyType = (typeof KEY_TYPES)[number];\n\n/** A key enrolled on the machine: a passkey's credential id, COSE alg and SPKI\n * (base64url); a device key also says so (`type`), and what its app declared about\n * it (`store`, `uv`). The machine verifies by its own record, never by a message. */\nexport interface TrustedKey {\n id: string;\n alg: number;\n spki: string;\n /** Present only on a device key (CONTROL.md \u00A72 \"Device keys\"). */\n type?: \"device\";\n store?: \"hardware\";\n uv?: \"each-use\";\n}\n/** A WebAuthn assertion: credential id, authenticatorData, clientDataJSON, signature (base64url). */\nexport interface Assertion {\n cred: string;\n ad: string;\n cd: string;\n sig: string;\n}\n/** A device key's signature (CONTROL.md \u00A73.2): its id and 64 bytes r\u2016s over deviceMessage(). */\nexport interface DeviceSignature {\n cred: string;\n sig: string;\n}\n/** The day's grant: a passkey's assertion over sha256(str), where str names the day key. */\nexport interface GrantBundle extends Assertion {\n str: string;\n}\n/** The day's grant from a device key: its signature over deviceMessage(\"grant\", rp, sha256(str)). */\nexport interface DeviceGrant extends DeviceSignature {\n str: string;\n}\n/** What a receiver carries for one command (control.json minus the receiver's envelope). */\nexport interface SignedCommand {\n cmd: string;\n csig?: string | null;\n grant?: GrantBundle | DeviceGrant | null;\n confirm?: Assertion | DeviceSignature | null;\n}\nexport interface Grant {\n v: 1;\n t: typeof GRANT_TYPE;\n pub: string;\n iat: number;\n exp: number;\n rp: string;\n}\n/** One file attached to a `start`: the bytes live on the receiver, named by their hash. */\nexport interface ControlFile {\n /** The file name the machine writes (NFC). */\n name: string;\n /** Media type, `type/subtype`. */\n type: string;\n /** Bytes. */\n size: number;\n /** SHA-256 of the bytes, base64url without padding (43 characters). */\n sha256: string;\n}\nexport interface ControlCommand {\n v: 1;\n t: typeof COMMAND_TYPE;\n machine: string;\n kind: ControlKind;\n /** `<harness>:<id>`, e.g. `claude-code:8c132906-\u2026`. */\n session: string;\n text?: string;\n for?: string;\n decision?: \"allow\" | \"deny\";\n note?: string;\n cwd?: string;\n /** key.add only: the key to trust from now on, as the receiver holds it (a passkey,\n * or a device key with its `type`, `store` and `uv`). */\n key?: TrustedKey;\n /** key.add only: what the person calls the device (\"iPhone\"), for `control keys`. */\n name?: string;\n /** start, prompt: files to download, verify and write into the session's folder. */\n files?: ControlFile[];\n /** start only: the level the new session runs at (never above the machine's mode). */\n permission?: StartPermission;\n /** start only: the model the harness runs, as the harness names it. */\n model?: string;\n /** pool only (session `sessionpipe:pool`): what to do with the account pool. */\n pool_action?: PoolAction;\n /** pool: the account (enable, disable, remove, refresh), or the pending sign-in's id\n * (login.complete). */\n pool_account?: string;\n /** pool, order: every account of the pool once, in the order wanted. */\n pool_order?: string[];\n /** pool, login.complete: the `code#state` the person pasted back. */\n pool_code?: string;\n /** start, prompt: `<harness>:<id>` of a session on the same machine that gets the\n * answer as a message once the turn ends (CONTROL.md \u00A76, a handoff). */\n reply_to?: string;\n nonce: string;\n iat: number;\n}\n\n/** Why a command was refused. Stable: conformance vectors and acks name these. */\nexport type RefusalCode =\n | \"malformed\"\n | \"not_a_command\"\n | \"wrong_machine\"\n | \"unknown_kind\"\n | \"bad_session\"\n | \"bad_fields\"\n | \"bad_nonce\"\n | \"too_old\"\n | \"future\"\n | \"unsigned\"\n | \"untrusted_key\"\n | \"bad_assertion\"\n | \"bad_grant\"\n | \"outside_grant\"\n | \"bad_csig\"\n | \"replay\"\n | \"nonce_store\";\n\nexport type Verdict =\n | { ok: true; cmd: ControlCommand; via: \"grant\" | \"confirm\"; key: string }\n | { ok: false; code: RefusalCode; why: string };\n\nexport interface VerifyOptions {\n /** This machine's id; a command for another machine is refused. */\n machine: string;\n /** The receiver's WebAuthn rp id the keys were enrolled for (well-known control.signing.rp_id). */\n rpId: string;\n /** The keys enrolled on this machine (or, for a receiver's early check, the account's). */\n trusted: TrustedKey[];\n /** Epoch ms. */\n now: number;\n /** MACHINE_MAX_AGE_MS on a machine (default), RECEIVER_MAX_AGE_MS on a receiver. */\n maxAgeMs?: number;\n /** Called once, last, only for a command whose signatures all verified: answer\n * true when the nonce was seen before, otherwise record it (durably) and answer\n * false. A throw refuses the command (`nonce_store`): never run what could not\n * be remembered. Omit it on a receiver's early check. */\n seenNonce?: (nonce: string) => boolean | Promise<boolean>;\n}\n\nconst NONCE_RE = /^[A-Za-z0-9_-]{22,64}$/;\nconst MACHINE_RE = /^m_[A-Za-z0-9_-]{16,40}$/;\nconst HARNESS_RE = /^[a-z][a-z0-9-]{0,39}$/;\nconst SESSION_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst B64URL_RE = /^[A-Za-z0-9_-]*$/;\nconst SHA256_RE = /^[A-Za-z0-9_-]{43}$/;\nconst MEDIA_TYPE_RE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i;\n// biome-ignore lint/suspicious/noControlCharactersInRegex: control characters are exactly what it rejects\nconst BAD_NAME_CHAR_RE = /[/\\\\\\x00-\\x1f\\x7f]/;\nconst UP = 0x01;\nconst UV = 0x04;\n\n/** The fields each kind carries besides the common ones (CONTROL.md \u00A73.1). The set is\n * closed: a field it doesn't list is `bad_fields`, so a field added later fails closed\n * on a machine that predates it instead of being ignored. */\nconst COMMON_FIELDS: readonly string[] = [\"v\", \"t\", \"machine\", \"kind\", \"session\", \"nonce\", \"iat\", \"note\"];\nconst FIELDS: Record<ControlKind, readonly string[]> = {\n prompt: [\"text\", \"files\", \"reply_to\"],\n \"permission.answer\": [\"for\", \"decision\"],\n cancel: [],\n start: [\"text\", \"cwd\", \"files\", \"permission\", \"model\", \"reply_to\"],\n \"key.add\": [\"key\", \"name\"],\n pool: [\"pool_action\", \"pool_account\", \"pool_order\", \"pool_code\"],\n};\n/** The fields only a `pool` carries. */\nconst POOL_FIELDS = FIELDS.pool;\n\n// --- encodings ------------------------------------------------------------------\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nexport function b64url(bytes: Uint8Array | ArrayBuffer): string {\n const u = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n let s = \"\";\n for (const b of u) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** base64url without padding, strictly; null for anything else. */\nexport function fromB64url(s: unknown): Uint8Array | null {\n if (typeof s !== \"string\" || !B64URL_RE.test(s) || s.length % 4 === 1) return null;\n try {\n const bin = atob(s.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((s.length + 3) % 4));\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n } catch {\n return null;\n }\n}\n\nexport async function sha256(data: string | Uint8Array): Promise<Uint8Array> {\n const bytes = typeof data === \"string\" ? enc.encode(data) : data;\n return new Uint8Array(await crypto.subtle.digest(\"SHA-256\", bytes as Uint8Array<ArrayBuffer>));\n}\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n const out = new Uint8Array(a.length + b.length);\n out.set(a, 0);\n out.set(b, a.length);\n return out;\n}\n\n/** Equal, compared whole: a mismatch costs the same as a match. */\nexport function sameBytes(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let d = 0;\n for (let i = 0; i < a.length; i++) d |= (a[i] as number) ^ (b[i] as number);\n return d === 0;\n}\n\n/** A DER ECDSA signature (what WebAuthn returns) as the 64-byte r\u2016s WebCrypto verifies.\n * Strict: every length must match the bytes, so one signature has one encoding (a\n * P-256 signature is at most 72 bytes, so the short length form is the only one). */\nexport function derToRaw(der: Uint8Array): Uint8Array | null {\n if (der[0] !== 0x30 || der.length < 8 || der[1] !== der.length - 2) return null;\n let i = 2;\n const part = (): Uint8Array | null => {\n if (der[i] !== 0x02) return null;\n const len = der[i + 1] as number;\n if (len < 1 || len > 33) return null;\n let v = der.slice(i + 2, i + 2 + len);\n if (v.length !== len) return null;\n // Minimal integers only: no needless leading zero, and never negative.\n if ((v[0] as number) & 0x80) return null;\n if (len > 1 && v[0] === 0 && !((v[1] as number) & 0x80)) return null;\n i += 2 + len;\n while (v.length > 32 && v[0] === 0) v = v.slice(1);\n if (v.length > 32) return null;\n const out = new Uint8Array(32);\n out.set(v, 32 - v.length);\n return out;\n };\n const r = part();\n const s = part();\n return r && s && i === der.length ? concat(r, s) : null;\n}\n\nconst u8 = (b: Uint8Array) => b as Uint8Array<ArrayBuffer>;\n/** WebCrypto's key type, named without the DOM lib (Workers, Node and browsers all have it). */\ntype CryptoKey = Awaited<ReturnType<typeof crypto.subtle.importKey>>;\n\nasync function importKey(key: TrustedKey): Promise<CryptoKey | null> {\n const spki = fromB64url(key.spki);\n if (!spki?.length) return null;\n try {\n if (key.alg === ES256)\n return await crypto.subtle.importKey(\"spki\", u8(spki), { name: \"ECDSA\", namedCurve: \"P-256\" }, false, [\"verify\"]);\n if (key.alg === RS256) {\n const k = await crypto.subtle.importKey(\"spki\", u8(spki), { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-256\" }, false, [\n \"verify\",\n ]);\n const bits = (k.algorithm as { modulusLength?: number }).modulusLength as number; // every RSA key has one\n return bits >= 2048 ? k : null;\n }\n } catch {}\n return null;\n}\n\nconst PASSKEY_FIELDS = new Set([\"id\", \"alg\", \"spki\"]);\nconst DEVICE_FIELDS = new Set([\"id\", \"alg\", \"spki\", \"type\", \"store\", \"uv\"]);\n/** The only fields a device signature has (`str` only in a grant). */\nexport const DEVICE_SIG_FIELDS = [\"cred\", \"sig\", \"str\"] as const;\n\n/** A device key's id: the SHA-256 of its SPKI bytes, base64url without padding. */\nexport async function deviceKeyId(spki: string): Promise<string> {\n return b64url(await sha256(fromB64url(spki) ?? new Uint8Array()));\n}\n\n/** A device key's declared rules, ignoring any bookkeeping fields a store keeps beside\n * them (a name, when it was added): ES256, hardware, verified each use, named by its\n * SPKI's hash, and a P-256 key WebCrypto can import. */\nasync function deviceRecordValid(key: TrustedKey): Promise<boolean> {\n if (key.type !== \"device\" || key.alg !== ES256 || key.store !== \"hardware\" || key.uv !== \"each-use\") return false;\n if (typeof key.id !== \"string\" || !SHA256_RE.test(key.id) || key.id !== (await deviceKeyId(key.spki))) return false;\n return !!(await importKey(key));\n}\n\n/** Can this key be enrolled at all? A machine refuses to trust a key it could never\n * verify with. The key's fields are closed: a passkey is `id`, `alg`, `spki` and\n * nothing else; a device key adds exactly `type`, `store` and `uv` (CONTROL.md \u00A72),\n * so a kind added later is refused by every machine that predates it. */\nexport async function keyUsable(key: TrustedKey): Promise<boolean> {\n if (!key || typeof key !== \"object\" || Array.isArray(key)) return false;\n const fields = Object.keys(key).filter((k) => (key as unknown as Record<string, unknown>)[k] !== undefined);\n if (\"type\" in key && key.type !== undefined) {\n if (!fields.every((f) => DEVICE_FIELDS.has(f))) return false;\n return deviceRecordValid(key);\n }\n if (!fields.every((f) => PASSKEY_FIELDS.has(f))) return false;\n return (\n typeof key.id === \"string\" &&\n /^[A-Za-z0-9_-]{16,1400}$/.test(key.id) &&\n (key.alg === ES256 || key.alg === RS256) &&\n !!(await importKey(key))\n );\n}\n\n// --- WebAuthn -------------------------------------------------------------------\n\n/** A WebAuthn origin a key may have been used from: https on the rp id or a subdomain;\n * for rp id `localhost`, plain http on any port too. A bare origin, as a browser writes it. */\nexport function originAllowed(origin: unknown, rpId: string): boolean {\n if (typeof origin !== \"string\" || !rpId) return false;\n let u: URL;\n try {\n u = new URL(origin);\n } catch {\n return false;\n }\n if (u.origin !== origin) return false;\n if (u.hostname !== rpId && !u.hostname.endsWith(`.${rpId}`)) return false;\n if (u.protocol === \"https:\") return true;\n return u.protocol === \"http:\" && rpId === \"localhost\";\n}\n\ntype Check = { ok: true } | { ok: false; why: string };\nconst no = (why: string): Check => ({ ok: false, why });\n\n// --- device keys (CONTROL.md \u00A73.2) ------------------------------------------------\n\n/** The first line of every message a device key signs. */\nexport const DEVICE_CONTEXT = \"sessionpipe.device.v1\";\n/** What a device signature is for: one use can never answer for another. */\nexport type DevicePurpose = \"grant\" | \"confirm\" | \"pair\";\n\n/** The exact bytes a device key signs: the context, the purpose, the rp id as the\n * machine recorded it, and the base64url (no padding) of the 32-byte challenge a\n * passkey would sign for that purpose, joined by single line feeds, none at the end. */\nexport function deviceMessage(purpose: DevicePurpose, rpId: string, challenge: Uint8Array): Uint8Array {\n return enc.encode(`${DEVICE_CONTEXT}\\n${purpose}\\n${rpId}\\n${b64url(challenge)}`);\n}\n\n/** One device signature over `challenge` for `purpose`, made with the device key `key`. */\nasync function verifyDeviceSignature(\n challenge: Uint8Array,\n a: object,\n key: TrustedKey,\n rpId: string,\n purpose: DevicePurpose,\n): Promise<Check> {\n const sig = a as Record<string, unknown>;\n for (const f of Object.keys(sig)) {\n if (f === \"str\" && purpose === \"grant\") continue;\n if (f !== \"cred\" && f !== \"sig\") return no(`a device key's signature carries no ${f}`);\n }\n if (sig.cred !== key.id) return no(\"signed with a key this machine doesn't trust\");\n if (!(await deviceRecordValid(key))) return no(\"a trusted key is unusable\");\n const raw = fromB64url(sig.sig);\n if (raw?.length !== 64) return no(\"a device key's signature is 64 bytes, r\u2016s\");\n const pub = await importKey(key);\n /* v8 ignore next -- deviceRecordValid imported it already */\n if (!pub) return no(\"a trusted key is unusable\");\n let good = false;\n try {\n good = await crypto.subtle.verify(\n { name: \"ECDSA\", hash: \"SHA-256\" },\n pub,\n u8(raw),\n u8(deviceMessage(purpose, rpId, challenge)),\n );\n } catch {\n /* v8 ignore next -- WebCrypto answers false for a bad signature; a throw is a broken runtime */\n good = false;\n }\n return good ? { ok: true } : no(\"the device key's signature doesn't verify\");\n}\n\n/** One signature over `challenge` by `key`, chosen from the machine's own record of\n * the key, never from the signature: a device key's signature for `purpose`\n * (CONTROL.md \u00A73.2), or a passkey assertion made by a verified person. */\nexport async function verifyAssertion(\n challenge: Uint8Array,\n a: Assertion | DeviceSignature | null | undefined,\n key: TrustedKey,\n rpId: string,\n purpose: DevicePurpose,\n): Promise<Check> {\n if (!a || typeof a !== \"object\") return no(\"no passkey signature\");\n if (key.type !== undefined) {\n if (key.type !== \"device\") return no(\"a trusted key is unusable\");\n return verifyDeviceSignature(challenge, a, key, rpId, purpose);\n }\n return verifyPasskeyAssertion(challenge, a as Assertion, key, rpId);\n}\n\nasync function verifyPasskeyAssertion(\n challenge: Uint8Array,\n a: Assertion,\n key: TrustedKey,\n rpId: string,\n): Promise<Check> {\n if (a.cred !== key.id) return no(\"signed with a key this machine doesn't trust\");\n const cdBytes = fromB64url(a.cd);\n const ad = fromB64url(a.ad);\n const sig = fromB64url(a.sig);\n if (!cdBytes || !ad || !sig?.length) return no(\"the passkey signature is malformed\");\n let cd: { type?: unknown; challenge?: unknown; origin?: unknown; crossOrigin?: unknown };\n try {\n cd = JSON.parse(dec.decode(cdBytes));\n } catch {\n return no(\"the passkey signature is malformed\");\n }\n if (!cd || typeof cd !== \"object\") return no(\"the passkey signature is malformed\");\n if (cd.type !== \"webauthn.get\") return no(\"the passkey answered a different question\");\n if (cd.challenge !== b64url(challenge)) return no(\"the passkey signed something else\");\n if (!originAllowed(cd.origin, rpId)) return no(`the passkey was used on another site, not ${rpId}`);\n if (cd.crossOrigin === true) return no(\"the passkey was used inside another site's frame\");\n if (ad.length < 37) return no(\"the passkey signature is malformed\");\n if (!sameBytes(ad.slice(0, 32), await sha256(rpId))) return no(`the passkey belongs to another site, not ${rpId}`);\n const flags = ad[32] as number;\n if (!(flags & UP) || !(flags & UV)) return no(\"the passkey signed without verifying the person\");\n const pub = await importKey(key);\n if (!pub) return no(\"a trusted key is unusable\");\n const signed = concat(ad, await sha256(cdBytes));\n let good = false;\n try {\n if (key.alg === ES256) {\n const raw = derToRaw(sig);\n good = !!raw && (await crypto.subtle.verify({ name: \"ECDSA\", hash: \"SHA-256\" }, pub, u8(raw), u8(signed)));\n } else {\n good = await crypto.subtle.verify({ name: \"RSASSA-PKCS1-v1_5\" }, pub, u8(sig), u8(signed));\n }\n } catch {\n good = false;\n }\n return good ? { ok: true } : no(\"the passkey signature doesn't verify\");\n}\n\n/** What a passkey signs to enroll on a machine: bound to that machine and that pairing code. */\nexport function pairChallenge(machine: string, code: string): Promise<Uint8Array> {\n return sha256(`sessionpipe.pair:${machine}:${code}`);\n}\n\n/** The enrollment proof: the new key's assertion over pairChallenge(machine, code).\n * Only after this verifies does the key go into the machine's trust store. */\nexport async function verifyEnrollment(o: {\n machine: string;\n code: string;\n key: TrustedKey;\n proof: Assertion | DeviceSignature;\n rpId: string;\n}): Promise<Check> {\n if (!(await keyUsable(o.key))) return no(\"the key is in a shape this machine can't use\");\n return verifyAssertion(await pairChallenge(o.machine, o.code), o.proof, o.key, o.rpId, \"pair\");\n}\n\n// --- the command ------------------------------------------------------------------\n\ntype Parsed = { ok: true; cmd: ControlCommand } | { ok: false; code: RefusalCode; why: string };\nconst bad = (code: RefusalCode, why: string): { ok: false; code: RefusalCode; why: string } => ({\n ok: false,\n code,\n why,\n});\nconst isNum = (n: unknown): n is number => typeof n === \"number\" && Number.isFinite(n);\n\n/** The session id rule per harness (ADAPTERS.md): Claude Code ids are UUIDs. */\nexport function sessionIdOk(harness: string, id: string): boolean {\n if (!SESSION_ID_RE.test(id)) return false;\n if (harness === \"claude-code\") return UUID_RE.test(id);\n return true;\n}\n\n/** Split `<harness>:<id>`; null when either half breaks its rule. */\nexport function parseSessionRef(ref: unknown): { harness: string; id: string } | null {\n if (typeof ref !== \"string\") return null;\n const i = ref.indexOf(\":\");\n if (i <= 0) return null;\n const harness = ref.slice(0, i);\n const id = ref.slice(i + 1);\n return HARNESS_RE.test(harness) && sessionIdOk(harness, id) ? { harness, id } : null;\n}\n\n/** Read the command's fields from the exact string. Structure only; no clock, no keys. */\nexport function parseCommand(str: unknown): Parsed {\n if (typeof str !== \"string\" || !str || str.length > CMD_MAX)\n return bad(\"malformed\", \"the command is missing or too long\");\n let c: Record<string, unknown>;\n try {\n c = JSON.parse(str);\n } catch {\n return bad(\"malformed\", \"the command is not JSON\");\n }\n if (!c || typeof c !== \"object\" || Array.isArray(c)) return bad(\"malformed\", \"the command is not an object\");\n if (c.v !== 1 || c.t !== COMMAND_TYPE) return bad(\"not_a_command\", \"not a sessionpipe control command\");\n if (typeof c.machine !== \"string\" || !MACHINE_RE.test(c.machine)) return bad(\"malformed\", \"which machine?\");\n if (!CONTROL_KINDS.includes(c.kind as ControlKind))\n return bad(\"unknown_kind\", \"the command asks for something unknown\");\n const ref = parseSessionRef(c.session);\n if (!ref) return bad(\"bad_session\", \"which session?\");\n if (typeof c.nonce !== \"string\" || !NONCE_RE.test(c.nonce))\n return bad(\"bad_nonce\", \"the nonce is missing or malformed\");\n if (!isNum(c.iat) || !Number.isInteger(c.iat) || c.iat <= 0)\n return bad(\"malformed\", \"the command has no signing time\");\n const has = (k: string) => c[k] !== undefined;\n const text = () => typeof c.text === \"string\" && c.text.trim().length > 0 && c.text.length <= TEXT_MAX;\n // A handoff's reply_to (CONTROL.md \u00A76): a session ref by the adapter's rule, never the\n // command's own session. Whether that session is on this machine is the daemon's to say.\n const replyToProblem = (): string | null => {\n if (!has(\"reply_to\")) return null;\n if (!parseSessionRef(c.reply_to)) return \"a reply_to names a session on this machine, <harness>:<id>\";\n if (c.reply_to === c.session) return \"a reply_to can't name the command's own session\";\n return null;\n };\n switch (c.kind as ControlKind) {\n case \"prompt\": {\n if (!text()) return bad(\"bad_fields\", `a prompt needs text, at most ${TEXT_MAX} characters`);\n if (has(\"for\") || has(\"decision\") || has(\"cwd\"))\n return bad(\"bad_fields\", \"a prompt carries text, files and a reply_to\");\n if (has(\"files\")) {\n const why = filesProblem(c.files);\n if (why) return bad(\"bad_fields\", why);\n }\n const why = replyToProblem();\n if (why) return bad(\"bad_fields\", why);\n break;\n }\n case \"start\": {\n if (!text()) return bad(\"bad_fields\", `a new session needs text, at most ${TEXT_MAX} characters`);\n if (typeof c.cwd !== \"string\" || !(isAbsolute(c.cwd) || HOME_RELATIVE_RE.test(c.cwd)) || c.cwd.length > 1024)\n return bad(\"bad_fields\", \"a new session needs an absolute folder, or one under ~\");\n if (has(\"for\") || has(\"decision\"))\n return bad(\"bad_fields\", \"a start carries text, cwd, files, a permission, a model and a reply_to\");\n if (has(\"files\")) {\n const why = filesProblem(c.files);\n if (why) return bad(\"bad_fields\", why);\n }\n const why = replyToProblem();\n if (why) return bad(\"bad_fields\", why);\n if (has(\"permission\") && levelRank(c.permission) < 0)\n return bad(\"bad_fields\", `a permission is one of ${START_PERMISSIONS.join(\", \")}`);\n if (has(\"model\") && (typeof c.model !== \"string\" || c.model.length > MODEL_MAX || !MODEL_RE.test(c.model)))\n return bad(\"bad_fields\", `a model is 1 to ${MODEL_MAX} letters, digits and ._:/@[]-, not starting with a dash`);\n break;\n }\n case \"permission.answer\":\n if (typeof c.for !== \"string\" || !c.for || c.for.length > 128) return bad(\"bad_fields\", \"which attention?\");\n if (c.decision !== \"allow\" && c.decision !== \"deny\") return bad(\"bad_fields\", \"allow or deny?\");\n if (has(\"text\") || has(\"cwd\")) return bad(\"bad_fields\", \"a permission answer carries for, decision and note\");\n break;\n case \"cancel\":\n if (has(\"text\") || has(\"for\") || has(\"decision\") || has(\"cwd\"))\n return bad(\"bad_fields\", \"a cancel carries nothing\");\n break;\n case \"key.add\": {\n // A device the machine already trusts vouches for a new one (CONTROL.md \u00A72): the\n // new key's id, algorithm and public key (and, for a device key, its type, store\n // and uv) are inside the signed bytes.\n if (c.session !== KEY_STORE_SESSION) return bad(\"bad_session\", `a key.add names ${KEY_STORE_SESSION}`);\n const k = c.key as Record<string, unknown> | undefined;\n const b64 = (v: unknown, min: number, max: number) =>\n typeof v === \"string\" && v.length >= min && v.length <= max && /^[A-Za-z0-9_-]+$/.test(v);\n if (\n !k ||\n typeof k !== \"object\" ||\n Array.isArray(k) ||\n !b64(k.id, 16, 1400) ||\n (k.alg !== -7 && k.alg !== -257) ||\n !b64(k.spki, 40, 2000)\n )\n return bad(\"bad_fields\", \"a key.add carries the key: id, alg (-7 or -257) and spki\");\n if (k.type !== undefined) {\n if (k.type !== \"device\") return bad(\"bad_fields\", \"a key's type is device, or absent for a passkey\");\n if (k.alg !== ES256 || k.store !== \"hardware\" || k.uv !== \"each-use\" || !SHA256_RE.test(k.id as string))\n return bad(\"bad_fields\", \"a device key is ES256, store hardware, uv each-use, named by its SPKI's hash\");\n if (Object.keys(k).some((f) => !DEVICE_FIELDS.has(f)))\n return bad(\"bad_fields\", \"a device key carries id, alg, spki, type, store and uv\");\n } else if (Object.keys(k).some((f) => !PASSKEY_FIELDS.has(f)))\n return bad(\"bad_fields\", \"a passkey carries id, alg and spki: store and uv belong to a device key\");\n if (has(\"name\") && (typeof c.name !== \"string\" || !c.name.trim() || c.name.length > 80))\n return bad(\"bad_fields\", \"a device name is text, at most 80 characters\");\n if (has(\"text\") || has(\"for\") || has(\"decision\") || has(\"cwd\"))\n return bad(\"bad_fields\", \"a key.add carries the key and a name\");\n break;\n }\n case \"pool\": {\n if (c.session !== POOL_SESSION) return bad(\"bad_session\", `a pool command names ${POOL_SESSION}`);\n const why = poolProblem(c);\n if (why) return bad(\"bad_fields\", why);\n break;\n }\n }\n if (c.kind !== \"key.add\" && (has(\"key\") || has(\"name\"))) return bad(\"bad_fields\", \"only a key.add carries a key\");\n if (c.kind !== \"pool\" && POOL_FIELDS.some(has)) return bad(\"bad_fields\", \"only a pool command carries pool fields\");\n if (c.kind !== \"start\" && c.kind !== \"prompt\" && has(\"files\"))\n return bad(\"bad_fields\", \"only a start or a prompt carries files\");\n if (c.kind !== \"start\" && c.kind !== \"prompt\" && has(\"reply_to\"))\n return bad(\"bad_fields\", \"only a start or a prompt carries a reply_to\");\n if (c.kind !== \"start\" && (has(\"permission\") || has(\"model\")))\n return bad(\"bad_fields\", \"only a start carries a model or a permission\");\n const allowed = FIELDS[c.kind as ControlKind];\n for (const k of Object.keys(c))\n if (!COMMON_FIELDS.includes(k) && !allowed.includes(k))\n return bad(\"bad_fields\", `the command carries a field its kind doesn't have: ${k.slice(0, 40)}`);\n if (has(\"note\") && (typeof c.note !== \"string\" || c.note.length > NOTE_MAX))\n return bad(\"bad_fields\", `a note is text, at most ${NOTE_MAX} characters`);\n return { ok: true, cmd: c as unknown as ControlCommand };\n}\n\n/** Why a `pool` command's fields break the rules (CONTROL.md \u00A76 `pool`), or null when\n * they don't. Each action takes exactly its own fields: an account where the action\n * names one, the whole order for `order`, the pasted code for `login.complete`. The\n * machine still checks the account exists and the order is complete (`refused`). */\nexport function poolProblem(c: Record<string, unknown>): string | null {\n const action = c.pool_action;\n if (!POOL_ACTIONS.includes(action as PoolAction))\n return `a pool command's action is one of ${POOL_ACTIONS.join(\", \")}`;\n const a = action as PoolAction;\n const account = c.pool_account !== undefined;\n const order = c.pool_order !== undefined;\n const code = c.pool_code !== undefined;\n const needsAccount = a === \"enable\" || a === \"disable\" || a === \"remove\" || a === \"login.complete\";\n if (needsAccount && !account) return `${a} names an account`;\n if (account && !needsAccount && a !== \"refresh\") return `${a} names no account`;\n if (account && (typeof c.pool_account !== \"string\" || !POOL_ACCOUNT_RE.test(c.pool_account)))\n return \"an account id is 1 to 128 letters, digits and ._:-\";\n if (order !== (a === \"order\")) return a === \"order\" ? \"order carries the whole order\" : `${a} carries no order`;\n if (order) {\n const o = c.pool_order;\n if (!Array.isArray(o) || o.length < 1 || o.length > POOL_ORDER_MAX)\n return `an order is a list of 1 to ${POOL_ORDER_MAX} account ids`;\n if (!o.every((id) => typeof id === \"string\" && POOL_ACCOUNT_RE.test(id)))\n return \"an account id is 1 to 128 letters, digits and ._:-\";\n if (new Set(o).size !== o.length) return \"an order names each account once\";\n }\n if (code !== (a === \"login.complete\"))\n return a === \"login.complete\" ? \"login.complete carries the pasted code\" : `${a} carries no code`;\n if (\n code &&\n (typeof c.pool_code !== \"string\" ||\n !c.pool_code ||\n c.pool_code.length > POOL_CODE_MAX ||\n /[\\r\\n]/.test(c.pool_code))\n )\n return `a code is 1 to ${POOL_CODE_MAX} characters on one line`;\n return null;\n}\n\n/** Why a start's or a prompt's `files` breaks the rules (CONTROL.md \u00A73.1), or null when it doesn't. */\nexport function filesProblem(files: unknown): string | null {\n if (!Array.isArray(files) || files.length < 1 || files.length > FILES_MAX)\n return `files is a list of 1 to ${FILES_MAX} files`;\n const seen = new Set<string>();\n let total = 0;\n for (const f of files as unknown[]) {\n if (!f || typeof f !== \"object\" || Array.isArray(f)) return \"each file is an object\";\n const { name, type, size, sha256: hash } = f as Record<string, unknown>;\n const why = fileNameProblem(name);\n if (why) return why;\n const key = (name as string).normalize(\"NFC\").toLowerCase();\n if (seen.has(key)) return \"two files have the same name\";\n seen.add(key);\n if (typeof type !== \"string\" || type.length > FILE_TYPE_MAX || !MEDIA_TYPE_RE.test(type))\n return \"a file's type isn't a media type\";\n if (!isNum(size) || !Number.isInteger(size) || size < 1 || size > FILE_SIZE_MAX)\n return `a file is 1 byte to ${FILE_SIZE_MAX / 1_048_576} MiB`;\n total += size;\n if (typeof hash !== \"string\" || !SHA256_RE.test(hash)) return \"a file's sha256 isn't 43 base64url characters\";\n }\n if (total > FILES_TOTAL_MAX) return `the files add up to more than ${FILES_TOTAL_MAX / 1_048_576} MiB`;\n return null;\n}\n\n/** A file name the machine can write inside the session's folder and nowhere else. */\nfunction fileNameProblem(name: unknown): string | null {\n if (typeof name !== \"string\") return \"a file has no name\";\n const n = name.normalize(\"NFC\");\n if (n.length < 1 || n.length > FILE_NAME_MAX) return `a file name is 1 to ${FILE_NAME_MAX} characters`;\n if (BAD_NAME_CHAR_RE.test(n)) return \"a file name has a slash, a backslash or a control character\";\n if (n.startsWith(\".\")) return \"a file name can't start with a dot\";\n return null;\n}\n\n/** A `start`'s `cwd` under the machine's home: `~` alone, or `~/\u2026` (`~\\\u2026`). Never `~user`. */\nexport const HOME_RELATIVE_RE = /^~(?:[\\\\/]|$)/;\n\nfunction isAbsolute(p: string): boolean {\n return p.startsWith(\"/\") || /^[A-Za-z]:[\\\\/]/.test(p);\n}\n\n/** The grant's fields, read from its exact string. */\nexport function parseGrant(str: unknown, rpId: string): { ok: true; grant: Grant } | { ok: false; why: string } {\n if (typeof str !== \"string\" || !str || str.length > GRANT_STR_MAX)\n return { ok: false, why: \"the grant is missing or too long\" };\n let g: Record<string, unknown>;\n try {\n g = JSON.parse(str);\n } catch {\n return { ok: false, why: \"the grant is not JSON\" };\n }\n if (!g || typeof g !== \"object\" || Array.isArray(g)) return { ok: false, why: \"the grant is not an object\" };\n if (g.v !== 1 || g.t !== GRANT_TYPE) return { ok: false, why: \"not a sessionpipe grant\" };\n if (g.rp !== rpId) return { ok: false, why: `the grant is for another site, not ${rpId}` };\n if (typeof g.pub !== \"string\" || !fromB64url(g.pub)?.length || !isNum(g.iat) || !isNum(g.exp))\n return { ok: false, why: \"the grant is missing parts\" };\n if (g.exp <= g.iat) return { ok: false, why: \"the grant ends before it starts\" };\n if (g.exp - g.iat > GRANT_MAX_MS + SKEW_MS) return { ok: false, why: \"the grant lasts longer than a day\" };\n return { ok: true, grant: g as unknown as Grant };\n}\n\nasync function verifyCsig(cmd: string, csig: unknown, pub: string): Promise<boolean> {\n const sig = fromB64url(csig);\n const spki = fromB64url(pub);\n if (sig?.length !== 64 || !spki) return false;\n try {\n const key = await crypto.subtle.importKey(\"spki\", u8(spki), { name: \"ECDSA\", namedCurve: \"P-256\" }, false, [\n \"verify\",\n ]);\n return await crypto.subtle.verify({ name: \"ECDSA\", hash: \"SHA-256\" }, key, u8(sig), u8(enc.encode(cmd)));\n } catch {\n return false;\n }\n}\n\n/**\n * Is this a command this machine may run? The checks, in the spec's order\n * (CONTROL.md \u00A75): the exact bytes parse; the command is for this machine; it is\n * signed \u2014 a grant from a trusted key plus the day key's signature, or a confirm\n * over sha256(cmd); it was signed within the age window; its nonce is new.\n * The folder and open-attention checks that follow are the daemon's (they read\n * the machine, not the command).\n */\nexport async function verifyCommand(s: SignedCommand | null | undefined, o: VerifyOptions): Promise<Verdict> {\n if (!s || typeof s !== \"object\") return bad(\"malformed\", \"nothing to verify\");\n const p = parseCommand(s.cmd);\n if (!p.ok) return p;\n const c = p.cmd;\n if (c.machine !== o.machine) return bad(\"wrong_machine\", \"the command is for another machine\");\n\n let via: \"grant\" | \"confirm\" | null = null;\n let keyId = \"\";\n let refusal: { code: RefusalCode; why: string } | null = null;\n if (s.grant && s.csig) {\n const r = await checkGrantPath(s, c, o);\n if (r.ok) {\n via = \"grant\";\n keyId = r.key;\n } else refusal = r;\n }\n if (!via && s.confirm) {\n const key = o.trusted.find((k) => k && k.id === s.confirm?.cred);\n if (!key) refusal = { code: \"untrusted_key\", why: \"confirmed with a key this machine doesn't trust\" };\n else {\n const a = await verifyAssertion(await sha256(s.cmd), s.confirm, key, o.rpId, \"confirm\");\n if (a.ok) {\n via = \"confirm\";\n keyId = key.id;\n } else refusal = { code: \"bad_assertion\", why: a.why };\n }\n }\n if (!via) {\n if (refusal) return bad(refusal.code, refusal.why);\n return bad(\"unsigned\", \"the command isn't signed\");\n }\n // The key a key.add brings must be one this machine could ever verify with: an\n // SPKI it can import for that alg, and every rule of a device key (CONTROL.md \u00A72).\n if (c.kind === \"key.add\" && !(await keyUsable(c.key as TrustedKey)))\n return bad(\"bad_fields\", \"the key a key.add brings is in a shape this machine can't use\");\n if (c.iat > o.now + SKEW_MS) return bad(\"future\", \"the command is dated in the future (is this clock right?)\");\n if (o.now - c.iat > (o.maxAgeMs ?? MACHINE_MAX_AGE_MS)) return bad(\"too_old\", \"the command was signed too long ago\");\n if (o.seenNonce) {\n let seen: boolean;\n try {\n seen = await o.seenNonce(c.nonce);\n } catch {\n return bad(\"nonce_store\", \"the nonce couldn't be recorded, so the command didn't run\");\n }\n if (seen) return bad(\"replay\", \"this command was already used once\");\n }\n return { ok: true, cmd: c, via, key: keyId };\n}\n\nasync function checkGrantPath(\n s: SignedCommand,\n c: ControlCommand,\n o: VerifyOptions,\n): Promise<{ ok: true; key: string } | { ok: false; code: RefusalCode; why: string }> {\n const bundle = s.grant as GrantBundle | DeviceGrant;\n const g = parseGrant(bundle.str, o.rpId);\n if (!g.ok) return bad(\"bad_grant\", g.why);\n const key = o.trusted.find((k) => k && k.id === bundle.cred);\n if (!key) return bad(\"untrusted_key\", \"the grant was signed with a key this machine doesn't trust\");\n const a = await verifyAssertion(await sha256(bundle.str), bundle, key, o.rpId, \"grant\");\n if (!a.ok) return bad(\"bad_assertion\", a.why);\n if (!(await verifyCsig(s.cmd, s.csig, g.grant.pub)))\n return bad(\"bad_csig\", \"the command's signature doesn't verify \u2014 it was changed after it was signed\");\n if (c.iat < g.grant.iat - SKEW_MS || c.iat > g.grant.exp)\n return bad(\"outside_grant\", \"the command was signed outside its grant's window\");\n return { ok: true, key: key.id };\n}\n"],
5
+ "mappings": ";;;AAWO,SAAS,OAAO,UAA0B;AAC/C,MAAI;AACF,WAAO,IAAI,IAAI,QAAQ,EAAE;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,IAAM,kBACX;AAKK,IAAM,YAAY,CAAC,QAAQ,WAAW,QAAQ;AAS9C,IAAM,WAAW,CAAC,QACvB,mEAAmE,QAAQ,SAAS,qBAAqB,2BAA2B,4PAA4P,QAAQ,SAAS,YAAY,EAAE;AAI1Z,IAAM,SACX;AAIK,SAAS,MAAM,MAAc,UAA0B;AAC5D,SAAO,eAAe,OAAO,QAAQ,CAAC;AAAA;AAAA,EAA6D,IAAI;AACzG;AAIA,IAAM,WAAW;AAWV,IAAM,cAAc,CAAC,OAA+B,SAAS,KAAK,EAAE,IAAI,WAAW,GAAG,MAAM,GAAG,CAAC,KAAK;AAE5G,IAAM,SAAS,CAAC,MAAc,MAAc,SAC1C,eAAe,IAAI,yDAAyD,IAAI;AAAA;AAAA,EAAS,IAAI;AAOxF,SAAS,YAAY,MAAc,UAAkB,IAAoB;AAC9E,QAAM,OAAO,YAAY,EAAE;AAC3B,SAAO,OAAO,OAAO,MAAM,OAAO,QAAQ,GAAG,IAAI,IAAI,MAAM,MAAM,QAAQ;AAC3E;AAIO,SAAS,WAAW,MAAc,UAA0B;AACjE,SAAO,GAAG,IAAI;AAAA;AAAA,YAAiB,OAAO,QAAQ,CAAC;AACjD;AAMO,SAAS,SAAS,MAAc,MAA0B,MAA8B;AAC7F,SAAO,GAAG,IAAI;AAAA;AAAA,YAAiB,QAAQ,iBAAiB,wCAAwC,OAAO,KAAK,IAAI,KAAK,EAAE;AACzH;AAIO,SAAS,aAAa,MAAc,UAAkB,OAAuB;AAClF,SAAO,eAAe,IAAI,oCAAoC,OAAO,QAAQ,CAAC;AAAA;AAAA,EAAS,KAAK;AAC9F;AAIA,IAAM,OAAO;AAEb,IAAM,OAAO;AACb,IAAM,SAAS,CAAC,OAAe,OAAO,aACpC,IAAI;AAAA,EACF,IAAI,MACD,KAAK,EACL,QAAQ,uBAAuB,MAAM,EACrC,WAAW,MAAM,IAAI,EACrB,WAAW,MAAM,cAAc,CAAC;AACrC;AAMF,IAAM,aAAa,CAAC,OAAO,MAAM,IAAI,IAAI,CAAC,GAAG,OAAO,OAAO,IAAI,MAAM,IAAI,CAAC,CAAC;AAE3E,IAAM,cAAc,CAAC,OAAO,WAAW,IAAI,IAAI,CAAC,CAAC;AAGjD,IAAM,YAAY,CAAC,OAAO,SAAS,IAAI,IAAI,GAAG,eAAe,CAAC;AAG9D,IAAM,mBAAmB,CAAC,OAAO,SAAS,IAAI,MAAM,IAAI,GAAG,kBAAkB,CAAC;AAE9E,IAAM,gBAAgB,CAAC,OAAO,aAAa,MAAM,MAAM,EAAE,CAAC,CAAC;AAK3D,IAAM,mBACJ;AAGF,IAAM,aAAa,CAAC,GAAG,IAAI,IAAI,UAAU,IAAI,QAAQ,CAAC,GAAG,gBAAgB;AAGzE,IAAM,QAAQ,CAAC,iBAAiB,GAAG,YAAY,MAAM;AAGrD,IAAM,YAAY,CAAC,GAAG,YAAY,MAAM;AAExC,IAAM,SAAS,CAAC,OAAiB,SAAyC;AACxE,aAAW,KAAK,OAAO;AACrB,UAAM,IAAI,EAAE,KAAK,IAAI;AACrB,QAAI,EAAG,QAAO;AAAA,EAChB;AACA,SAAO;AACT;AAMO,SAAS,QAAQ,MAAoD;AAC1E,QAAM,MAAM,KAAK,QAAQ,MAAM;AAC/B,QAAM,OAAO,MAAM,IAAI,OAAO,OAAO,YAAY,KAAK,MAAM,GAAG,GAAG,CAAC;AACnE,SAAO,OAAO,EAAE,MAAM,KAAK,MAAM,MAAM,CAAC,GAAG,KAAK,KAAK,CAAC,EAAY,IAAI,EAAE,MAAM,KAAK,KAAK;AAC1F;AAuBO,SAAS,eAAe,MAAwE;AACrG,QAAM,aAAa,KAAK,MAAM,MAAM;AACpC,QAAM,KAAK,CAAC,MAAuB,WAAW,CAAC,EAAa,KAAK;AACjE,QAAM,IAAI,WAAW;AAErB,QAAM,OAAO,IAAI,KAAK,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI;AAC9D,QAAM,MAAM,SAAS,OAAO,IAAI,IAAI;AACpC,QAAM,QAAQ,CAAC,MAAc,OAAe,WAAW,MAAM,MAAM,EAAE,EAAE,KAAK,MAAM,EAAE,KAAK;AACzF,QAAM,OAAO,OAAO,YAAY,GAAG,CAAC,CAAC;AACrC,MAAI,QAAQ,MAAM,GAAG,GAAG,EAAG,QAAO,EAAE,MAAM,MAAM,GAAG,GAAG,GAAG,KAAK,KAAK,CAAC,GAAa,WAAW,KAAK;AACjG,QAAM,QAAQ,SAAS,OAAO,OAAO,aAAa,GAAG,IAAI,CAAC,CAAC,IAAI;AAC/D,MAAI,SAAS,MAAM,GAAG,IAAI,CAAC,EAAG,QAAO,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,KAAK,MAAM,CAAC,GAAa,WAAW,KAAK;AAEvG,QAAM,OAAO,SAAS,QAAQ,UAAU,SAAS,IAAI;AACrD,QAAM,SACJ,SAAU,OAAO,WAAW,GAAG,MAAM,CAAC,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC,KAAO,OAAO,eAAe,GAAG,CAAC,CAAC,KAAK,MAAM,GAAG,GAAG;AACjH,MAAI,OAAQ,QAAO,EAAE,MAAM,SAAS,OAAO,OAAO,MAAM,GAAG,GAAG,GAAG,KAAK,MAAM,WAAW,KAAK;AAG5F,QAAMA,UAAS,OAAO,OAAO,kBAAkB,GAAG,MAAM,CAAC,CAAC,IAAI;AAC9D,MAAIA,WAAU,MAAM,GAAG,MAAM,CAAC;AAC5B,WAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,GAAG,SAAS,IAAIA,QAAO,CAAC,CAAW,CAAC,IAAI,KAAK,MAAM,WAAW,KAAK;AACxG,SAAO,EAAE,MAAM,KAAK,MAAM,WAAW,MAAM;AAC7C;;;ACjMO,IAAM,eAAe,KAAK;AAE1B,IAAM,UAAU,IAAI;AAGpB,IAAM,qBAAqB,KAAK;AAGhC,IAAM,sBAAsB,KAAK;AAEjC,IAAM,eAAe,KAAK;AAE1B,IAAM,UAAU;AAEhB,IAAM,WAAW;AAEjB,IAAM,WAAW;AAEjB,IAAM,gBAAgB;AAEtB,IAAM,YAAY;AAElB,IAAM,gBAAgB;AAEtB,IAAM,kBAAkB;AAExB,IAAM,gBAAgB;AAEtB,IAAM,gBAAgB;AAEtB,IAAM,YAAY;AAElB,IAAM,WAAW;AAGjB,IAAM,oBAAoB,CAAC,QAAQ,QAAQ,SAAS,MAAM;AAQ1D,IAAM,YAAY,CAAC,MAAuB,kBAAkB,QAAQ,CAAoB;AAGxF,IAAM,WAAW,CAAC,GAAoB,MAC3C,UAAU,CAAC,KAAK,UAAU,CAAC,IAAI,IAAI;AAE9B,IAAM,eAAe;AACrB,IAAM,aAAa;AACnB,IAAM,gBAAgB,CAAC,UAAU,qBAAqB,UAAU,SAAS,WAAW,MAAM;AAE1F,IAAM,oBAAoB;AAE1B,IAAM,eAAe;AAIrB,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,kBAAkB;AAExB,IAAM,iBAAiB;AAEvB,IAAM,gBAAgB;AAGtB,IAAM,QAAQ;AACd,IAAM,QAAQ;AAId,IAAM,YAAY,CAAC,WAAW,QAAQ;AA8I7C,IAAM,WAAW;AACjB,IAAM,aAAa;AACnB,IAAM,aAAa;AACnB,IAAM,gBAAgB;AACtB,IAAM,UAAU;AAChB,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,gBAAgB;AAEtB,IAAM,mBAAmB;AACzB,IAAM,KAAK;AACX,IAAM,KAAK;AAKX,IAAM,gBAAmC,CAAC,KAAK,KAAK,WAAW,QAAQ,WAAW,SAAS,OAAO,MAAM;AACxG,IAAM,SAAiD;AAAA,EACrD,QAAQ,CAAC,QAAQ,SAAS,UAAU;AAAA,EACpC,qBAAqB,CAAC,OAAO,UAAU;AAAA,EACvC,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,QAAQ,OAAO,SAAS,cAAc,SAAS,UAAU;AAAA,EACjE,WAAW,CAAC,OAAO,MAAM;AAAA,EACzB,MAAM,CAAC,eAAe,gBAAgB,cAAc,WAAW;AACjE;AAEA,IAAM,cAAc,OAAO;AAI3B,IAAM,MAAM,IAAI,YAAY;AAC5B,IAAM,MAAM,IAAI,YAAY;AAErB,SAAS,OAAO,OAAyC;AAC9D,QAAM,IAAI,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;AACpE,MAAI,IAAI;AACR,aAAW,KAAK,EAAG,MAAK,OAAO,aAAa,CAAC;AAC7C,SAAO,KAAK,CAAC,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC1E;AAGO,SAAS,WAAW,GAA+B;AACxD,MAAI,OAAO,MAAM,YAAY,CAAC,UAAU,KAAK,CAAC,KAAK,EAAE,SAAS,MAAM,EAAG,QAAO;AAC9E,MAAI;AACF,UAAM,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,IAAI,MAAM,OAAO,EAAE,SAAS,KAAK,CAAC,CAAC;AAC1F,UAAM,MAAM,IAAI,WAAW,IAAI,MAAM;AACrC,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,IAAK,KAAI,CAAC,IAAI,IAAI,WAAW,CAAC;AAC9D,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,OAAO,MAAgD;AAC3E,QAAM,QAAQ,OAAO,SAAS,WAAW,IAAI,OAAO,IAAI,IAAI;AAC5D,SAAO,IAAI,WAAW,MAAM,OAAO,OAAO,OAAO,WAAW,KAAgC,CAAC;AAC/F;AAEA,SAAS,OAAO,GAAe,GAA2B;AACxD,QAAM,MAAM,IAAI,WAAW,EAAE,SAAS,EAAE,MAAM;AAC9C,MAAI,IAAI,GAAG,CAAC;AACZ,MAAI,IAAI,GAAG,EAAE,MAAM;AACnB,SAAO;AACT;AAGO,SAAS,UAAU,GAAe,GAAwB;AAC/D,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,MAAM,EAAE,CAAC,IAAgB,EAAE,CAAC;AAC/D,SAAO,MAAM;AACf;AAKO,SAAS,SAAS,KAAoC;AAC3D,MAAI,IAAI,CAAC,MAAM,MAAQ,IAAI,SAAS,KAAK,IAAI,CAAC,MAAM,IAAI,SAAS,EAAG,QAAO;AAC3E,MAAI,IAAI;AACR,QAAM,OAAO,MAAyB;AACpC,QAAI,IAAI,CAAC,MAAM,EAAM,QAAO;AAC5B,UAAM,MAAM,IAAI,IAAI,CAAC;AACrB,QAAI,MAAM,KAAK,MAAM,GAAI,QAAO;AAChC,QAAI,IAAI,IAAI,MAAM,IAAI,GAAG,IAAI,IAAI,GAAG;AACpC,QAAI,EAAE,WAAW,IAAK,QAAO;AAE7B,QAAK,EAAE,CAAC,IAAe,IAAM,QAAO;AACpC,QAAI,MAAM,KAAK,EAAE,CAAC,MAAM,KAAK,EAAG,EAAE,CAAC,IAAe,KAAO,QAAO;AAChE,SAAK,IAAI;AACT,WAAO,EAAE,SAAS,MAAM,EAAE,CAAC,MAAM,EAAG,KAAI,EAAE,MAAM,CAAC;AACjD,QAAI,EAAE,SAAS,GAAI,QAAO;AAC1B,UAAM,MAAM,IAAI,WAAW,EAAE;AAC7B,QAAI,IAAI,GAAG,KAAK,EAAE,MAAM;AACxB,WAAO;AAAA,EACT;AACA,QAAM,IAAI,KAAK;AACf,QAAM,IAAI,KAAK;AACf,SAAO,KAAK,KAAK,MAAM,IAAI,SAAS,OAAO,GAAG,CAAC,IAAI;AACrD;AAEA,IAAM,KAAK,CAAC,MAAkB;AAI9B,eAAe,UAAU,KAA4C;AACnE,QAAM,OAAO,WAAW,IAAI,IAAI;AAChC,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,MAAI;AACF,QAAI,IAAI,QAAQ;AACd,aAAO,MAAM,OAAO,OAAO,UAAU,QAAQ,GAAG,IAAI,GAAG,EAAE,MAAM,SAAS,YAAY,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;AAClH,QAAI,IAAI,QAAQ,OAAO;AACrB,YAAM,IAAI,MAAM,OAAO,OAAO,UAAU,QAAQ,GAAG,IAAI,GAAG,EAAE,MAAM,qBAAqB,MAAM,UAAU,GAAG,OAAO;AAAA,QAC/G;AAAA,MACF,CAAC;AACD,YAAM,OAAQ,EAAE,UAAyC;AACzD,aAAO,QAAQ,OAAO,IAAI;AAAA,IAC5B;AAAA,EACF,QAAQ;AAAA,EAAC;AACT,SAAO;AACT;AAEA,IAAM,iBAAiB,oBAAI,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AACpD,IAAM,gBAAgB,oBAAI,IAAI,CAAC,MAAM,OAAO,QAAQ,QAAQ,SAAS,IAAI,CAAC;AAEnE,IAAM,oBAAoB,CAAC,QAAQ,OAAO,KAAK;AAGtD,eAAsB,YAAY,MAA+B;AAC/D,SAAO,OAAO,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI,WAAW,CAAC,CAAC;AAClE;AAKA,eAAe,kBAAkB,KAAmC;AAClE,MAAI,IAAI,SAAS,YAAY,IAAI,QAAQ,SAAS,IAAI,UAAU,cAAc,IAAI,OAAO,WAAY,QAAO;AAC5G,MAAI,OAAO,IAAI,OAAO,YAAY,CAAC,UAAU,KAAK,IAAI,EAAE,KAAK,IAAI,OAAQ,MAAM,YAAY,IAAI,IAAI,EAAI,QAAO;AAC9G,SAAO,CAAC,CAAE,MAAM,UAAU,GAAG;AAC/B;AAMA,eAAsB,UAAU,KAAmC;AACjE,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,EAAG,QAAO;AAClE,QAAM,SAAS,OAAO,KAAK,GAAG,EAAE,OAAO,CAAC,MAAO,IAA2C,CAAC,MAAM,MAAS;AAC1G,MAAI,UAAU,OAAO,IAAI,SAAS,QAAW;AAC3C,QAAI,CAAC,OAAO,MAAM,CAAC,MAAM,cAAc,IAAI,CAAC,CAAC,EAAG,QAAO;AACvD,WAAO,kBAAkB,GAAG;AAAA,EAC9B;AACA,MAAI,CAAC,OAAO,MAAM,CAAC,MAAM,eAAe,IAAI,CAAC,CAAC,EAAG,QAAO;AACxD,SACE,OAAO,IAAI,OAAO,YAClB,2BAA2B,KAAK,IAAI,EAAE,MACrC,IAAI,QAAQ,SAAS,IAAI,QAAQ,UAClC,CAAC,CAAE,MAAM,UAAU,GAAG;AAE1B;AAMO,SAAS,cAAc,QAAiB,MAAuB;AACpE,MAAI,OAAO,WAAW,YAAY,CAAC,KAAM,QAAO;AAChD,MAAI;AACJ,MAAI;AACF,QAAI,IAAI,IAAI,MAAM;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,EAAE,WAAW,OAAQ,QAAO;AAChC,MAAI,EAAE,aAAa,QAAQ,CAAC,EAAE,SAAS,SAAS,IAAI,IAAI,EAAE,EAAG,QAAO;AACpE,MAAI,EAAE,aAAa,SAAU,QAAO;AACpC,SAAO,EAAE,aAAa,WAAW,SAAS;AAC5C;AAGA,IAAM,KAAK,CAAC,SAAwB,EAAE,IAAI,OAAO,IAAI;AAK9C,IAAM,iBAAiB;AAOvB,SAAS,cAAc,SAAwB,MAAc,WAAmC;AACrG,SAAO,IAAI,OAAO,GAAG,cAAc;AAAA,EAAK,OAAO;AAAA,EAAK,IAAI;AAAA,EAAK,OAAO,SAAS,CAAC,EAAE;AAClF;AAGA,eAAe,sBACb,WACA,GACA,KACA,MACA,SACgB;AAChB,QAAM,MAAM;AACZ,aAAW,KAAK,OAAO,KAAK,GAAG,GAAG;AAChC,QAAI,MAAM,SAAS,YAAY,QAAS;AACxC,QAAI,MAAM,UAAU,MAAM,MAAO,QAAO,GAAG,uCAAuC,CAAC,EAAE;AAAA,EACvF;AACA,MAAI,IAAI,SAAS,IAAI,GAAI,QAAO,GAAG,8CAA8C;AACjF,MAAI,CAAE,MAAM,kBAAkB,GAAG,EAAI,QAAO,GAAG,2BAA2B;AAC1E,QAAM,MAAM,WAAW,IAAI,GAAG;AAC9B,MAAI,KAAK,WAAW,GAAI,QAAO,GAAG,gDAA2C;AAC7E,QAAM,MAAM,MAAM,UAAU,GAAG;AAE/B,MAAI,CAAC,IAAK,QAAO,GAAG,2BAA2B;AAC/C,MAAI,OAAO;AACX,MAAI;AACF,WAAO,MAAM,OAAO,OAAO;AAAA,MACzB,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,MACjC;AAAA,MACA,GAAG,GAAG;AAAA,MACN,GAAG,cAAc,SAAS,MAAM,SAAS,CAAC;AAAA,IAC5C;AAAA,EACF,QAAQ;AAEN,WAAO;AAAA,EACT;AACA,SAAO,OAAO,EAAE,IAAI,KAAK,IAAI,GAAG,2CAA2C;AAC7E;AAKA,eAAsB,gBACpB,WACA,GACA,KACA,MACA,SACgB;AAChB,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO,GAAG,sBAAsB;AACjE,MAAI,IAAI,SAAS,QAAW;AAC1B,QAAI,IAAI,SAAS,SAAU,QAAO,GAAG,2BAA2B;AAChE,WAAO,sBAAsB,WAAW,GAAG,KAAK,MAAM,OAAO;AAAA,EAC/D;AACA,SAAO,uBAAuB,WAAW,GAAgB,KAAK,IAAI;AACpE;AAEA,eAAe,uBACb,WACA,GACA,KACA,MACgB;AAChB,MAAI,EAAE,SAAS,IAAI,GAAI,QAAO,GAAG,8CAA8C;AAC/E,QAAM,UAAU,WAAW,EAAE,EAAE;AAC/B,QAAM,KAAK,WAAW,EAAE,EAAE;AAC1B,QAAM,MAAM,WAAW,EAAE,GAAG;AAC5B,MAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,OAAQ,QAAO,GAAG,oCAAoC;AACnF,MAAI;AACJ,MAAI;AACF,SAAK,KAAK,MAAM,IAAI,OAAO,OAAO,CAAC;AAAA,EACrC,QAAQ;AACN,WAAO,GAAG,oCAAoC;AAAA,EAChD;AACA,MAAI,CAAC,MAAM,OAAO,OAAO,SAAU,QAAO,GAAG,oCAAoC;AACjF,MAAI,GAAG,SAAS,eAAgB,QAAO,GAAG,2CAA2C;AACrF,MAAI,GAAG,cAAc,OAAO,SAAS,EAAG,QAAO,GAAG,mCAAmC;AACrF,MAAI,CAAC,cAAc,GAAG,QAAQ,IAAI,EAAG,QAAO,GAAG,6CAA6C,IAAI,EAAE;AAClG,MAAI,GAAG,gBAAgB,KAAM,QAAO,GAAG,kDAAkD;AACzF,MAAI,GAAG,SAAS,GAAI,QAAO,GAAG,oCAAoC;AAClE,MAAI,CAAC,UAAU,GAAG,MAAM,GAAG,EAAE,GAAG,MAAM,OAAO,IAAI,CAAC,EAAG,QAAO,GAAG,4CAA4C,IAAI,EAAE;AACjH,QAAM,QAAQ,GAAG,EAAE;AACnB,MAAI,EAAE,QAAQ,OAAO,EAAE,QAAQ,IAAK,QAAO,GAAG,iDAAiD;AAC/F,QAAM,MAAM,MAAM,UAAU,GAAG;AAC/B,MAAI,CAAC,IAAK,QAAO,GAAG,2BAA2B;AAC/C,QAAM,SAAS,OAAO,IAAI,MAAM,OAAO,OAAO,CAAC;AAC/C,MAAI,OAAO;AACX,MAAI;AACF,QAAI,IAAI,QAAQ,OAAO;AACrB,YAAM,MAAM,SAAS,GAAG;AACxB,aAAO,CAAC,CAAC,OAAQ,MAAM,OAAO,OAAO,OAAO,EAAE,MAAM,SAAS,MAAM,UAAU,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,MAAM,CAAC;AAAA,IAC1G,OAAO;AACL,aAAO,MAAM,OAAO,OAAO,OAAO,EAAE,MAAM,oBAAoB,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,MAAM,CAAC;AAAA,IAC3F;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,OAAO,EAAE,IAAI,KAAK,IAAI,GAAG,sCAAsC;AACxE;AAGO,SAAS,cAAc,SAAiB,MAAmC;AAChF,SAAO,OAAO,oBAAoB,OAAO,IAAI,IAAI,EAAE;AACrD;AAIA,eAAsB,iBAAiB,GAMpB;AACjB,MAAI,CAAE,MAAM,UAAU,EAAE,GAAG,EAAI,QAAO,GAAG,8CAA8C;AACvF,SAAO,gBAAgB,MAAM,cAAc,EAAE,SAAS,EAAE,IAAI,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM;AAC/F;AAKA,IAAM,MAAM,CAAC,MAAmB,SAAgE;AAAA,EAC9F,IAAI;AAAA,EACJ;AAAA,EACA;AACF;AACA,IAAM,QAAQ,CAAC,MAA4B,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AAG9E,SAAS,YAAY,SAAiB,IAAqB;AAChE,MAAI,CAAC,cAAc,KAAK,EAAE,EAAG,QAAO;AACpC,MAAI,YAAY,cAAe,QAAO,QAAQ,KAAK,EAAE;AACrD,SAAO;AACT;AAGO,SAAS,gBAAgB,KAAsD;AACpF,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,IAAI,IAAI,QAAQ,GAAG;AACzB,MAAI,KAAK,EAAG,QAAO;AACnB,QAAM,UAAU,IAAI,MAAM,GAAG,CAAC;AAC9B,QAAM,KAAK,IAAI,MAAM,IAAI,CAAC;AAC1B,SAAO,WAAW,KAAK,OAAO,KAAK,YAAY,SAAS,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;AAClF;AAGO,SAAS,aAAa,KAAsB;AACjD,MAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,IAAI,SAAS;AAClD,WAAO,IAAI,aAAa,oCAAoC;AAC9D,MAAI;AACJ,MAAI;AACF,QAAI,KAAK,MAAM,GAAG;AAAA,EACpB,QAAQ;AACN,WAAO,IAAI,aAAa,yBAAyB;AAAA,EACnD;AACA,MAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,EAAG,QAAO,IAAI,aAAa,8BAA8B;AAC3G,MAAI,EAAE,MAAM,KAAK,EAAE,MAAM,aAAc,QAAO,IAAI,iBAAiB,mCAAmC;AACtG,MAAI,OAAO,EAAE,YAAY,YAAY,CAAC,WAAW,KAAK,EAAE,OAAO,EAAG,QAAO,IAAI,aAAa,gBAAgB;AAC1G,MAAI,CAAC,cAAc,SAAS,EAAE,IAAmB;AAC/C,WAAO,IAAI,gBAAgB,wCAAwC;AACrE,QAAM,MAAM,gBAAgB,EAAE,OAAO;AACrC,MAAI,CAAC,IAAK,QAAO,IAAI,eAAe,gBAAgB;AACpD,MAAI,OAAO,EAAE,UAAU,YAAY,CAAC,SAAS,KAAK,EAAE,KAAK;AACvD,WAAO,IAAI,aAAa,mCAAmC;AAC7D,MAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,UAAU,EAAE,GAAG,KAAK,EAAE,OAAO;AACxD,WAAO,IAAI,aAAa,iCAAiC;AAC3D,QAAM,MAAM,CAAC,MAAc,EAAE,CAAC,MAAM;AACpC,QAAM,OAAO,MAAM,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,KAAK,EAAE,SAAS,KAAK,EAAE,KAAK,UAAU;AAG9F,QAAM,iBAAiB,MAAqB;AAC1C,QAAI,CAAC,IAAI,UAAU,EAAG,QAAO;AAC7B,QAAI,CAAC,gBAAgB,EAAE,QAAQ,EAAG,QAAO;AACzC,QAAI,EAAE,aAAa,EAAE,QAAS,QAAO;AACrC,WAAO;AAAA,EACT;AACA,UAAQ,EAAE,MAAqB;AAAA,IAC7B,KAAK,UAAU;AACb,UAAI,CAAC,KAAK,EAAG,QAAO,IAAI,cAAc,gCAAgC,QAAQ,aAAa;AAC3F,UAAI,IAAI,KAAK,KAAK,IAAI,UAAU,KAAK,IAAI,KAAK;AAC5C,eAAO,IAAI,cAAc,6CAA6C;AACxE,UAAI,IAAI,OAAO,GAAG;AAChB,cAAMC,OAAM,aAAa,EAAE,KAAK;AAChC,YAAIA,KAAK,QAAO,IAAI,cAAcA,IAAG;AAAA,MACvC;AACA,YAAM,MAAM,eAAe;AAC3B,UAAI,IAAK,QAAO,IAAI,cAAc,GAAG;AACrC;AAAA,IACF;AAAA,IACA,KAAK,SAAS;AACZ,UAAI,CAAC,KAAK,EAAG,QAAO,IAAI,cAAc,qCAAqC,QAAQ,aAAa;AAChG,UAAI,OAAO,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,GAAG,KAAK,iBAAiB,KAAK,EAAE,GAAG,MAAM,EAAE,IAAI,SAAS;AACtG,eAAO,IAAI,cAAc,wDAAwD;AACnF,UAAI,IAAI,KAAK,KAAK,IAAI,UAAU;AAC9B,eAAO,IAAI,cAAc,wEAAwE;AACnG,UAAI,IAAI,OAAO,GAAG;AAChB,cAAMA,OAAM,aAAa,EAAE,KAAK;AAChC,YAAIA,KAAK,QAAO,IAAI,cAAcA,IAAG;AAAA,MACvC;AACA,YAAM,MAAM,eAAe;AAC3B,UAAI,IAAK,QAAO,IAAI,cAAc,GAAG;AACrC,UAAI,IAAI,YAAY,KAAK,UAAU,EAAE,UAAU,IAAI;AACjD,eAAO,IAAI,cAAc,0BAA0B,kBAAkB,KAAK,IAAI,CAAC,EAAE;AACnF,UAAI,IAAI,OAAO,MAAM,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,SAAS,aAAa,CAAC,SAAS,KAAK,EAAE,KAAK;AACtG,eAAO,IAAI,cAAc,mBAAmB,SAAS,yDAAyD;AAChH;AAAA,IACF;AAAA,IACA,KAAK;AACH,UAAI,OAAO,EAAE,QAAQ,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,SAAS,IAAK,QAAO,IAAI,cAAc,kBAAkB;AAC1G,UAAI,EAAE,aAAa,WAAW,EAAE,aAAa,OAAQ,QAAO,IAAI,cAAc,gBAAgB;AAC9F,UAAI,IAAI,MAAM,KAAK,IAAI,KAAK,EAAG,QAAO,IAAI,cAAc,oDAAoD;AAC5G;AAAA,IACF,KAAK;AACH,UAAI,IAAI,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,UAAU,KAAK,IAAI,KAAK;AAC3D,eAAO,IAAI,cAAc,0BAA0B;AACrD;AAAA,IACF,KAAK,WAAW;AAId,UAAI,EAAE,YAAY,kBAAmB,QAAO,IAAI,eAAe,mBAAmB,iBAAiB,EAAE;AACrG,YAAM,IAAI,EAAE;AACZ,YAAM,MAAM,CAAC,GAAY,KAAa,QACpC,OAAO,MAAM,YAAY,EAAE,UAAU,OAAO,EAAE,UAAU,OAAO,mBAAmB,KAAK,CAAC;AAC1F,UACE,CAAC,KACD,OAAO,MAAM,YACb,MAAM,QAAQ,CAAC,KACf,CAAC,IAAI,EAAE,IAAI,IAAI,IAAI,KAClB,EAAE,QAAQ,MAAM,EAAE,QAAQ,QAC3B,CAAC,IAAI,EAAE,MAAM,IAAI,GAAI;AAErB,eAAO,IAAI,cAAc,0DAA0D;AACrF,UAAI,EAAE,SAAS,QAAW;AACxB,YAAI,EAAE,SAAS,SAAU,QAAO,IAAI,cAAc,iDAAiD;AACnG,YAAI,EAAE,QAAQ,SAAS,EAAE,UAAU,cAAc,EAAE,OAAO,cAAc,CAAC,UAAU,KAAK,EAAE,EAAY;AACpG,iBAAO,IAAI,cAAc,8EAA8E;AACzG,YAAI,OAAO,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,cAAc,IAAI,CAAC,CAAC;AAClD,iBAAO,IAAI,cAAc,wDAAwD;AAAA,MACrF,WAAW,OAAO,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,eAAe,IAAI,CAAC,CAAC;AAC1D,eAAO,IAAI,cAAc,yEAAyE;AACpG,UAAI,IAAI,MAAM,MAAM,OAAO,EAAE,SAAS,YAAY,CAAC,EAAE,KAAK,KAAK,KAAK,EAAE,KAAK,SAAS;AAClF,eAAO,IAAI,cAAc,8CAA8C;AACzE,UAAI,IAAI,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,UAAU,KAAK,IAAI,KAAK;AAC3D,eAAO,IAAI,cAAc,sCAAsC;AACjE;AAAA,IACF;AAAA,IACA,KAAK,QAAQ;AACX,UAAI,EAAE,YAAY,aAAc,QAAO,IAAI,eAAe,wBAAwB,YAAY,EAAE;AAChG,YAAM,MAAM,YAAY,CAAC;AACzB,UAAI,IAAK,QAAO,IAAI,cAAc,GAAG;AACrC;AAAA,IACF;AAAA,EACF;AACA,MAAI,EAAE,SAAS,cAAc,IAAI,KAAK,KAAK,IAAI,MAAM,GAAI,QAAO,IAAI,cAAc,8BAA8B;AAChH,MAAI,EAAE,SAAS,UAAU,YAAY,KAAK,GAAG,EAAG,QAAO,IAAI,cAAc,yCAAyC;AAClH,MAAI,EAAE,SAAS,WAAW,EAAE,SAAS,YAAY,IAAI,OAAO;AAC1D,WAAO,IAAI,cAAc,wCAAwC;AACnE,MAAI,EAAE,SAAS,WAAW,EAAE,SAAS,YAAY,IAAI,UAAU;AAC7D,WAAO,IAAI,cAAc,6CAA6C;AACxE,MAAI,EAAE,SAAS,YAAY,IAAI,YAAY,KAAK,IAAI,OAAO;AACzD,WAAO,IAAI,cAAc,8CAA8C;AACzE,QAAM,UAAU,OAAO,EAAE,IAAmB;AAC5C,aAAW,KAAK,OAAO,KAAK,CAAC;AAC3B,QAAI,CAAC,cAAc,SAAS,CAAC,KAAK,CAAC,QAAQ,SAAS,CAAC;AACnD,aAAO,IAAI,cAAc,sDAAsD,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE;AACnG,MAAI,IAAI,MAAM,MAAM,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,SAAS;AAChE,WAAO,IAAI,cAAc,2BAA2B,QAAQ,aAAa;AAC3E,SAAO,EAAE,IAAI,MAAM,KAAK,EAA+B;AACzD;AAMO,SAAS,YAAY,GAA2C;AACrE,QAAM,SAAS,EAAE;AACjB,MAAI,CAAC,aAAa,SAAS,MAAoB;AAC7C,WAAO,qCAAqC,aAAa,KAAK,IAAI,CAAC;AACrE,QAAM,IAAI;AACV,QAAM,UAAU,EAAE,iBAAiB;AACnC,QAAM,QAAQ,EAAE,eAAe;AAC/B,QAAM,OAAO,EAAE,cAAc;AAC7B,QAAM,eAAe,MAAM,YAAY,MAAM,aAAa,MAAM,YAAY,MAAM;AAClF,MAAI,gBAAgB,CAAC,QAAS,QAAO,GAAG,CAAC;AACzC,MAAI,WAAW,CAAC,gBAAgB,MAAM,UAAW,QAAO,GAAG,CAAC;AAC5D,MAAI,YAAY,OAAO,EAAE,iBAAiB,YAAY,CAAC,gBAAgB,KAAK,EAAE,YAAY;AACxF,WAAO;AACT,MAAI,WAAW,MAAM,SAAU,QAAO,MAAM,UAAU,kCAAkC,GAAG,CAAC;AAC5F,MAAI,OAAO;AACT,UAAM,IAAI,EAAE;AACZ,QAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,SAAS;AAClD,aAAO,8BAA8B,cAAc;AACrD,QAAI,CAAC,EAAE,MAAM,CAAC,OAAO,OAAO,OAAO,YAAY,gBAAgB,KAAK,EAAE,CAAC;AACrE,aAAO;AACT,QAAI,IAAI,IAAI,CAAC,EAAE,SAAS,EAAE,OAAQ,QAAO;AAAA,EAC3C;AACA,MAAI,UAAU,MAAM;AAClB,WAAO,MAAM,mBAAmB,2CAA2C,GAAG,CAAC;AACjF,MACE,SACC,OAAO,EAAE,cAAc,YACtB,CAAC,EAAE,aACH,EAAE,UAAU,SAAS,iBACrB,SAAS,KAAK,EAAE,SAAS;AAE3B,WAAO,kBAAkB,aAAa;AACxC,SAAO;AACT;AAGO,SAAS,aAAa,OAA+B;AAC1D,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,MAAM,SAAS;AAC9D,WAAO,2BAA2B,SAAS;AAC7C,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,QAAQ;AACZ,aAAW,KAAK,OAAoB;AAClC,QAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC5D,UAAM,EAAE,MAAM,MAAM,MAAM,QAAQ,KAAK,IAAI;AAC3C,UAAM,MAAM,gBAAgB,IAAI;AAChC,QAAI,IAAK,QAAO;AAChB,UAAM,MAAO,KAAgB,UAAU,KAAK,EAAE,YAAY;AAC1D,QAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,SAAK,IAAI,GAAG;AACZ,QAAI,OAAO,SAAS,YAAY,KAAK,SAAS,iBAAiB,CAAC,cAAc,KAAK,IAAI;AACrF,aAAO;AACT,QAAI,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO;AAChE,aAAO,uBAAuB,gBAAgB,OAAS;AACzD,aAAS;AACT,QAAI,OAAO,SAAS,YAAY,CAAC,UAAU,KAAK,IAAI,EAAG,QAAO;AAAA,EAChE;AACA,MAAI,QAAQ,gBAAiB,QAAO,iCAAiC,kBAAkB,OAAS;AAChG,SAAO;AACT;AAGA,SAAS,gBAAgB,MAA8B;AACrD,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,QAAM,IAAI,KAAK,UAAU,KAAK;AAC9B,MAAI,EAAE,SAAS,KAAK,EAAE,SAAS,cAAe,QAAO,uBAAuB,aAAa;AACzF,MAAI,iBAAiB,KAAK,CAAC,EAAG,QAAO;AACrC,MAAI,EAAE,WAAW,GAAG,EAAG,QAAO;AAC9B,SAAO;AACT;AAGO,IAAM,mBAAmB;AAEhC,SAAS,WAAW,GAAoB;AACtC,SAAO,EAAE,WAAW,GAAG,KAAK,kBAAkB,KAAK,CAAC;AACtD;AAGO,SAAS,WAAW,KAAc,MAAuE;AAC9G,MAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,IAAI,SAAS;AAClD,WAAO,EAAE,IAAI,OAAO,KAAK,mCAAmC;AAC9D,MAAI;AACJ,MAAI;AACF,QAAI,KAAK,MAAM,GAAG;AAAA,EACpB,QAAQ;AACN,WAAO,EAAE,IAAI,OAAO,KAAK,wBAAwB;AAAA,EACnD;AACA,MAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,IAAI,OAAO,KAAK,6BAA6B;AAC3G,MAAI,EAAE,MAAM,KAAK,EAAE,MAAM,WAAY,QAAO,EAAE,IAAI,OAAO,KAAK,0BAA0B;AACxF,MAAI,EAAE,OAAO,KAAM,QAAO,EAAE,IAAI,OAAO,KAAK,sCAAsC,IAAI,GAAG;AACzF,MAAI,OAAO,EAAE,QAAQ,YAAY,CAAC,WAAW,EAAE,GAAG,GAAG,UAAU,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,GAAG;AAC1F,WAAO,EAAE,IAAI,OAAO,KAAK,6BAA6B;AACxD,MAAI,EAAE,OAAO,EAAE,IAAK,QAAO,EAAE,IAAI,OAAO,KAAK,kCAAkC;AAC/E,MAAI,EAAE,MAAM,EAAE,MAAM,eAAe,QAAS,QAAO,EAAE,IAAI,OAAO,KAAK,oCAAoC;AACzG,SAAO,EAAE,IAAI,MAAM,OAAO,EAAsB;AAClD;AAEA,eAAe,WAAW,KAAa,MAAe,KAA+B;AACnF,QAAM,MAAM,WAAW,IAAI;AAC3B,QAAM,OAAO,WAAW,GAAG;AAC3B,MAAI,KAAK,WAAW,MAAM,CAAC,KAAM,QAAO;AACxC,MAAI;AACF,UAAM,MAAM,MAAM,OAAO,OAAO,UAAU,QAAQ,GAAG,IAAI,GAAG,EAAE,MAAM,SAAS,YAAY,QAAQ,GAAG,OAAO;AAAA,MACzG;AAAA,IACF,CAAC;AACD,WAAO,MAAM,OAAO,OAAO,OAAO,EAAE,MAAM,SAAS,MAAM,UAAU,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,IAAI,OAAO,GAAG,CAAC,CAAC;AAAA,EACzG,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,eAAsB,cAAc,GAAqC,GAAoC;AAC3G,MAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO,IAAI,aAAa,mBAAmB;AAC5E,QAAM,IAAI,aAAa,EAAE,GAAG;AAC5B,MAAI,CAAC,EAAE,GAAI,QAAO;AAClB,QAAM,IAAI,EAAE;AACZ,MAAI,EAAE,YAAY,EAAE,QAAS,QAAO,IAAI,iBAAiB,oCAAoC;AAE7F,MAAI,MAAkC;AACtC,MAAI,QAAQ;AACZ,MAAI,UAAqD;AACzD,MAAI,EAAE,SAAS,EAAE,MAAM;AACrB,UAAM,IAAI,MAAM,eAAe,GAAG,GAAG,CAAC;AACtC,QAAI,EAAE,IAAI;AACR,YAAM;AACN,cAAQ,EAAE;AAAA,IACZ,MAAO,WAAU;AAAA,EACnB;AACA,MAAI,CAAC,OAAO,EAAE,SAAS;AACrB,UAAM,MAAM,EAAE,QAAQ,KAAK,CAAC,MAAM,KAAK,EAAE,OAAO,EAAE,SAAS,IAAI;AAC/D,QAAI,CAAC,IAAK,WAAU,EAAE,MAAM,iBAAiB,KAAK,kDAAkD;AAAA,SAC/F;AACH,YAAM,IAAI,MAAM,gBAAgB,MAAM,OAAO,EAAE,GAAG,GAAG,EAAE,SAAS,KAAK,EAAE,MAAM,SAAS;AACtF,UAAI,EAAE,IAAI;AACR,cAAM;AACN,gBAAQ,IAAI;AAAA,MACd,MAAO,WAAU,EAAE,MAAM,iBAAiB,KAAK,EAAE,IAAI;AAAA,IACvD;AAAA,EACF;AACA,MAAI,CAAC,KAAK;AACR,QAAI,QAAS,QAAO,IAAI,QAAQ,MAAM,QAAQ,GAAG;AACjD,WAAO,IAAI,YAAY,0BAA0B;AAAA,EACnD;AAGA,MAAI,EAAE,SAAS,aAAa,CAAE,MAAM,UAAU,EAAE,GAAiB;AAC/D,WAAO,IAAI,cAAc,+DAA+D;AAC1F,MAAI,EAAE,MAAM,EAAE,MAAM,QAAS,QAAO,IAAI,UAAU,2DAA2D;AAC7G,MAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,oBAAqB,QAAO,IAAI,WAAW,qCAAqC;AACnH,MAAI,EAAE,WAAW;AACf,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,EAAE,UAAU,EAAE,KAAK;AAAA,IAClC,QAAQ;AACN,aAAO,IAAI,eAAe,2DAA2D;AAAA,IACvF;AACA,QAAI,KAAM,QAAO,IAAI,UAAU,oCAAoC;AAAA,EACrE;AACA,SAAO,EAAE,IAAI,MAAM,KAAK,GAAG,KAAK,KAAK,MAAM;AAC7C;AAEA,eAAe,eACb,GACA,GACA,GACoF;AACpF,QAAM,SAAS,EAAE;AACjB,QAAM,IAAI,WAAW,OAAO,KAAK,EAAE,IAAI;AACvC,MAAI,CAAC,EAAE,GAAI,QAAO,IAAI,aAAa,EAAE,GAAG;AACxC,QAAM,MAAM,EAAE,QAAQ,KAAK,CAAC,MAAM,KAAK,EAAE,OAAO,OAAO,IAAI;AAC3D,MAAI,CAAC,IAAK,QAAO,IAAI,iBAAiB,4DAA4D;AAClG,QAAM,IAAI,MAAM,gBAAgB,MAAM,OAAO,OAAO,GAAG,GAAG,QAAQ,KAAK,EAAE,MAAM,OAAO;AACtF,MAAI,CAAC,EAAE,GAAI,QAAO,IAAI,iBAAiB,EAAE,GAAG;AAC5C,MAAI,CAAE,MAAM,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG;AAC/C,WAAO,IAAI,YAAY,kFAA6E;AACtG,MAAI,EAAE,MAAM,EAAE,MAAM,MAAM,WAAW,EAAE,MAAM,EAAE,MAAM;AACnD,WAAO,IAAI,iBAAiB,mDAAmD;AACjF,SAAO,EAAE,IAAI,MAAM,KAAK,IAAI,GAAG;AACjC;",
6
+ "names": ["marked", "why"]
7
7
  }