From 65974429c0127b53992349d7c3adecc9ff2e2366 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Mon, 26 Oct 2026 19:08:59 +0100 Subject: [PATCH] chore(comments): delete comments that say what, not why, in src/contract --- src/contract/operations.test.ts | 22 +--- src/contract/operations.ts | 134 ++------------------- src/contract/protocol.ts | 2 +- src/contract/schemas.ts | 205 ++------------------------------ 4 files changed, 27 insertions(+), 336 deletions(-) diff --git a/src/contract/operations.test.ts b/src/contract/operations.test.ts index 17267d3fc..8cc811d19 100644 --- a/src/contract/operations.test.ts +++ b/src/contract/operations.test.ts @@ -12,11 +12,6 @@ import { PUSH_SCHEMAS } from "./pushes.js"; import type { Role } from "./roles.js"; import { platformCatalogSchema } from "./schemas.js"; -/** - * The ADR §3 operation matrix, name -> role. A table-driven test against this makes - * "forgetting a role" (or getting one wrong) a test failure, not just a type-level one -- - * `doctor.run` gets two rows since its role is a function of `fix`. - */ const ROLE_MATRIX: ReadonlyArray<{ readonly name: OperationName; readonly input: unknown; @@ -61,16 +56,13 @@ const ROLE_MATRIX: ReadonlyArray<{ { name: "worker.undrain", input: { workerId: "wrk_1" }, role: "admin" }, { name: "worker.remove", input: { workerId: "wrk_1" }, role: "admin" }, { name: "worker.update", input: { workerId: "wrk_1" }, role: "admin" }, - // ADR 0010 §6: an operator's consent to a download. { name: "component.install", input: { platform: "ios", version: "26.4" }, role: "admin" }, { name: "component.list", input: {}, role: "agent" }, - // ADR 0010 §7: the same consent, given for a set of workers through their gateway. { name: "worker.install-component", input: { platform: "ios", version: "26.4", workers: "all" }, role: "admin", }, - // ADR 0010 §8: removal is destructive, so admin-only. { name: "component.remove", input: { platform: "ios", version: "26.4" }, role: "admin" }, { name: "device.add", input: { id: "00008110-001A2B3C4D5E", platform: "ios" }, role: "admin" }, { name: "device.remove", input: { id: "00008110-001A2B3C4D5E" }, role: "admin" }, @@ -454,7 +446,7 @@ describe("operation input/output round trips", () => { }); it("lease.request/lease.renew: a non-positive ttlMs is a schema-level BAD_REQUEST", () => { - // The upper bound (`lease.maxTtlMs`) is the dispatcher's to enforce -- it is a daemon + // why: The upper bound (`lease.maxTtlMs`) is the dispatcher's to enforce -- it is a daemon // config value this module cannot see -- but "a TTL is a positive number" is shape. expect(() => OPERATIONS["lease.request"].input.parse({ @@ -1047,7 +1039,6 @@ describe("operation input/output round trips", () => { Object.fromEntries(names(count).map((name) => [name, ["a"]])); const classedModels = (count: number) => Object.fromEntries(names(count).map((name) => [name, "phone"])); - // Each pair is the largest value accepted and the smallest one refused. const bounds = [ [ { modelAliases: { ["x".repeat(256)]: ["a"] } }, @@ -1092,12 +1083,12 @@ describe("operation input/output round trips", () => { }); it("worker.remove reports whether there was a view to forget; drain never lies", () => { - // `remove` is idempotent: forgetting a worker the gateway has already forgotten is done, + // why: `remove` is idempotent: forgetting a worker the gateway has already forgotten is done, // not an error -- so `false` is a legitimate outcome, the same shape `token.revoke` uses. expect(OPERATIONS["worker.remove"].output.parse({ workerId: "wrk_1", removed: false })).toEqual( { workerId: "wrk_1", removed: false }, ); - // Drain and undrain each report the state they establish, and only that state: an + // why: Drain and undrain each report the state they establish, and only that state: an // `undrain` answering `drained: true` would be a contradiction rather than an outcome. expect(OPERATIONS["worker.drain"].output.parse({ workerId: "wrk_1", drained: true })).toEqual({ workerId: "wrk_1", @@ -1119,7 +1110,7 @@ describe("operation input/output round trips", () => { }); it("device.exec: parses a command, keeps stdin optional, and refuses anything else", () => { - // ADR 0005 §19a. `.strict()` is doing real work here: a client that sent `input` or `env` + // why: ADR 0005 §19a. `.strict()` is doing real work here: a client that sent `input` or `env` // hoping either would reach the child must be told no, not have it silently dropped. expect( OPERATIONS["device.exec"].input.parse({ @@ -1134,7 +1125,7 @@ describe("operation input/output round trips", () => { OPERATIONS["device.exec"].input.parse({ args: [], leaseId: "lse_1", tool: "simctl" }), ).toEqual({ args: [], leaseId: "lse_1", tool: "simctl" }); - // `tool` is an open string, exactly as `driver.passthrough`'s is: which wrappers exist is + // why: `tool` is an open string, exactly as `driver.passthrough`'s is: which wrappers exist is // the drivers' answer, so a name none of them wraps parses fine here and comes back as // `UNKNOWN_PASSTHROUGH_TOOL` from the driver catalog rather than as a malformed body. expect( @@ -1157,8 +1148,6 @@ describe("operation input/output round trips", () => { }); it("the output push carries a frame id, a stream, and a chunk", () => { - // The `output` family is request-scoped exactly like `progress` (ADR 0005 §19a), which is - // the property that lets one connection run more than one command at a time. expect(PUSH_SCHEMAS.output.parse({ chunk: "hello", requestId: 7, stream: "stdout" })).toEqual({ chunk: "hello", requestId: 7, @@ -1183,7 +1172,6 @@ describe("operation input/output round trips", () => { } }); - // Each refused push beside the nearest one that is accepted, so the refusal is the bound's. it.each([ ["a percent above 100", { percent: 101 }, { percent: 100 }], ["a percent below 0", { percent: -1 }, { percent: 0 }], diff --git a/src/contract/operations.ts b/src/contract/operations.ts index 4f0c381aa..3a30de277 100644 --- a/src/contract/operations.ts +++ b/src/contract/operations.ts @@ -1,12 +1,3 @@ -/** - * Every daemon operation, declared once (ADR 0003 §1). Name, role, input schema, output - * schema, optional `authorize` hook. Public TypeScript types are inferred from the zod - * schemas, never hand-written in parallel -- see each operation's `Input`/`Output` export. - * - * `defineOperation` returning a typed record whose `role` may be a plain `Role` or a function - * of the (already-validated) input is what lets `doctor.run` model an input-dependent role - * honestly (see its declaration below) without a second gating mechanism next to `authorize`. - */ import { z } from "zod"; import { CONSOLE_URL_MAX_LENGTH } from "./console-url.js"; @@ -68,15 +59,9 @@ export interface OperationDefinition< > { readonly name: Name; readonly role: Role | ((input: z.infer) => Role); - /** Whether a call changes state. The daemon log records every `write` at its default level - * and a `read` only at `debug`. Never sent on the wire. Like `role`, it may depend on the - * already-validated input. */ readonly effect: Effect | ((input: z.infer) => Effect); readonly input: InputSchema; readonly output: OutputSchema; - /** Ownership/ownership-adjacent gating a session-aware dispatcher would run after the role - * check (ADR §1's `ownsLease` example). Declared for typing purposes only in this PR -- - * nothing sets it, and nothing calls it; PR 2 adds both. */ readonly authorize?: (input: z.infer, context: AuthorizeContext) => boolean; } @@ -90,8 +75,6 @@ function defineOperation< return definition; } -// ---- catalog.get ------------------------------------------------------------------------------ - // fallow-ignore-next-line unused-export -- consumed only through the OPERATIONS registry, not by name; still public contract surface. export const catalogGet = defineOperation({ name: "catalog.get", @@ -101,31 +84,23 @@ export const catalogGet = defineOperation({ output: z.object({ platforms: z.array(platformCatalogSchema) }), }); -// ---- status.get --------------------------------------------------------------------------- - export const statusGet = defineOperation({ name: "status.get", role: "agent", effect: "read", input: z.object({}), output: z.object({ - // `devices`, `leases`, `capacity` and `queueDepth` are absent while `daemon.health` is + // why: `devices`, `leases`, `capacity` and `queueDepth` are absent while `daemon.health` is // `starting`: the registry has not been checked yet, so a reader sees a daemon that says // it is starting and what host it runs on, and nothing it could mistake for an empty fleet. devices: z.array(statusDeviceSchema).optional(), leases: z.array(statusLeaseSchema).optional(), capacity: statusCapacitySchema.optional(), - /** - * The warm pool as its last pass left it: whether it is on, the reserve, and each kind of device - * the targets name with how many are ready and booting and why it is short. Absent only while `daemon.health` - * is `starting`, like the fields above: a started worker and a gateway always send it. - */ warmPool: statusWarmPoolSchema.optional(), capabilities: capabilityListSchema.optional(), daemon: z.object({ health: daemonHealthSchema, mode: daemonModeSchema, - // Bounded: a gateway parses every worker's answer (safety rule 10). consoleUrl: z.string().max(CONSOLE_URL_MAX_LENGTH).optional(), ...daemonUpdateFields, }), @@ -136,7 +111,7 @@ export const statusGet = defineOperation({ physicalQueueDepth: z.number().optional(), installs: statusInstallsSchema.optional(), waiting: z.array(waitingRequestSchema).optional(), - /** Absent, not empty, from a worker: an empty array would read as a gateway with no workers. */ + /** why: Absent, not empty, from a worker: an empty array would read as a gateway with no workers. */ workers: z.array(workerViewSchema).optional(), }), }); @@ -172,7 +147,7 @@ const leaseRequestBaseSchema = z model: z.string().min(1).optional(), class: deviceClassSchema.optional(), platform: platformSchema, - // A plain string, checked by `refuseBadOsVersion`: a refined field would be checked by the MCP SDK + // why: A plain string, checked by `refuseBadOsVersion`: a refined field would be checked by the MCP SDK // before the handler, and its error would stand in for `BAD_REQUEST`. osVersion: z.string().optional(), mode: z.enum(["slim", "full"]).optional(), @@ -182,35 +157,11 @@ const leaseRequestBaseSchema = z allowDownload: z.boolean().optional(), noWait: z.boolean().optional(), timeoutMs: z.number().finite().positive().optional(), - /** Capped by the dispatcher's handler, not here: the cap is daemon config this module cannot see. */ + /** why: Capped by the dispatcher's handler, not here: the cap is daemon config this module cannot see. */ ttlMs: z.number().finite().positive().optional(), owner: z.string().optional(), - /** - * Makes this request repeatable: the daemon stores it under `(requesterId, idempotencyKey)`, - * and the same request sent again returns the stored result instead of a second lease -- - * the way a client that lost its answer, to a disconnect or a daemon restart, gets it back. - * The same key naming a different device, or a different `leaseId` (a missing one on either - * side counts as different), is `IDEMPOTENCY_CONFLICT`; a repeat from another - * principal is `FORBIDDEN`. Optional: a request without one is still stored, it just cannot - * be repeated. Bounded because the daemon stores it. - */ idempotencyKey: z.string().min(1).max(200).optional(), - /** - * ADR 0020: the ID the granted lease gets, instead of one simlock generates. The requester - * guarantees it is unique for all time; simlock refuses one an active lease or a waiting - * request holds (`LEASE_ID_TAKEN`) and keeps no record of IDs already used. A request - * without one works as before. Like `idempotencyKey` it is an option of the request, not - * part of the device it names. Anything outside the pattern is `BAD_REQUEST`. - */ leaseId: leaseIdSchema.optional(), - /** - * ADR 0021: the gateway's own request id, sent on every dispatch to a worker. A request that - * carries it is a probe: the worker declines it (`lease.declined`) rather than rejecting it, - * never queues it, and names the id on the request's lease events. Only the gateway's uplink - * session may send it, and only with `noWait: true`; the dispatcher refuses it otherwise - * (`FORBIDDEN`, then `BAD_REQUEST`). Not part of the MCP lease tool or the HTTP lease body. - * Bounded as `idempotencyKey` is, because the worker stores it. - */ fleetRequestId: z.string().min(1).max(200).optional(), requires: z .array(capabilityNameSchema) @@ -237,7 +188,7 @@ export function refuseModelWithClass( }; } -/** `allowDownload: false` passes because the CLI always sends it. */ +/** why: `allowDownload: false` passes because the CLI always sends it. */ function refusePhysicalWithImageFields(): < T extends { readonly physical?: boolean | undefined; @@ -327,7 +278,7 @@ export const leaseRequest = defineOperation({ effect: "write", input: leaseRequestInputSchema, output: leaseGrantSchema, - // No `authorize` hook on purpose: any agent session may request under an arbitrary + // why: No `authorize` hook on purpose: any agent session may request under an arbitrary // `requesterId`, and `owner` is gated by role in the handler (the resource does not exist yet). }); @@ -367,7 +318,7 @@ export const leaseCancel = defineOperation({ input: z.object({ requesterId: z.string().optional(), platform: platformSchema.optional() }), output: z.object({ result: z.enum(["cancelled", "not-found", "not-cancellable"]) }), /** - * ADR §9: "cancels this principal's pending request by requester id". Gated on the pending + * why: ADR §9: "cancels this principal's pending request by requester id". Gated on the pending * request's *recorded owner* (`pendingRequestOwner`, ADR §4's `ownerId` -- always the * creating session's principal, never the caller-suppliable `requesterId`), not on comparing * `requesterId` to the principal directly -- that comparison would forbid exactly the case @@ -391,8 +342,6 @@ export const leaseCancel = defineOperation({ }, }); -// ---- lease.renew ---------------------------------------------------------------------------- - /** * ADR 0004 §1: the only thing that keeps a lease alive, on every transport. An omitted `ttlMs` * re-applies the lease's own stored width -- never `lease.defaultTtlMs` -- so a lease granted @@ -410,8 +359,6 @@ export const leaseRenew = defineOperation({ authorize: ownsLease((input) => input.leaseId), }); -// ---- lease.release -------------------------------------------------------------------------- - export const leaseRelease = defineOperation({ name: "lease.release", role: "agent", @@ -421,8 +368,6 @@ export const leaseRelease = defineOperation({ authorize: ownsLease((input) => input.leaseId), }); -// ---- driver.passthrough (ADR 0001, decision 7) ----------------------------------------------- - /** * Builds the scoped command behind `simlock simctl` / `simlock adb`. It lives in the daemon * rather than the CLI because only the driver that proved a device root knows which flag points @@ -444,8 +389,6 @@ export const driverPassthrough = defineOperation({ output: passthroughCommandSchema, }); -// ---- device.exec (ADR 0005 §19a) ------------------------------------------------------------- - /** * Runs `simctl`, `adb` or `devicectl` **on the machine that owns the device** and streams the output back * (ADR 0005 §19a). The difference from `driver.passthrough` is where the process runs, not @@ -494,7 +437,7 @@ export const deviceExec = defineOperation({ args: z.array(z.string()), stdin: z.string().optional(), /** - * Who this command is being run *for* -- the requester the lease is attributed to (ADR + * why: Who this command is being run *for* -- the requester the lease is attributed to (ADR * 0003 §4), not the connection's principal. Read **only on an admin session**, where it * is the gateway case: a gateway holds one admin session over its uplink and proxies * many agents through it, each under its own namespaced requester id (ADR 0005 @@ -508,14 +451,14 @@ export const deviceExec = defineOperation({ }) .strict(), /** - * Only the exit code: every byte the command wrote already left as `output` pushes while it + * why: Only the exit code: every byte the command wrote already left as `output` pushes while it * ran (see `pushes.ts`), so an answer that also carried the output would mean the daemon had * buffered all of it -- which ADR §19e forbids, and which is why there is no size cap here * to speak of either. */ output: z.object({ exitCode: z.number() }), /** - * `ownsLease` for an agent, and something stricter for an admin. + * why: `ownsLease` for an agent, and something stricter for an admin. * * An **agent** session is gated exactly like `lease.renew`: its principal against the * lease's `ownerId`. It may not supply `requesterId` at all -- round 4, F4: this used to be @@ -552,8 +495,6 @@ export const deviceExec = defineOperation({ }, }); -// ---- lease.list (new, ADR §9) ----------------------------------------------------------------- - /** * ADR 0005 §20: the element carries an optional `workerId` -- absent from a worker's own * answer, set by a gateway, which lists the leases its worker views report. Additive, so a @@ -578,12 +519,10 @@ export const leaseGet = defineOperation({ output: z.object({ lease: statusLeaseSchema }), }); -// ---- doctor.run ------------------------------------------------------------------------------- - const doctorRunInputSchema = z.object({ fix: z.boolean().optional(), /** - * Destroys every orphan the run finds (ADR 0001, decision 5) -- the single opt-in exception + * why: Destroys every orphan the run finds (ADR 0001, decision 5) -- the single opt-in exception * to registry-only destruction (safety rule 1). Its own flag rather than part of `fix`, so a * `doctor --fix` already running unattended in CI does not acquire a destructive behaviour on * upgrade. @@ -595,7 +534,7 @@ const doctorRunInputSchema = z.object({ export const doctorRun = defineOperation({ name: "doctor.run", /** - * The role genuinely depends on the input: `fix: false` (read-only, shells out per device) + * why: The role genuinely depends on the input: `fix: false` (read-only, shells out per device) * is agent-visible; `fix: true` (applies safe fixes to registry-owned devices) is admin * only -- see ADR §3's matrix, which lists `doctor.run` twice for exactly this reason. A * function-of-input role, rather than an `authorize` hook, is the right tool here: `authorize` @@ -614,8 +553,6 @@ export const doctorRun = defineOperation({ output: doctorReportSchema, }); -// ---- lease.release-all ------------------------------------------------------------------------ - // fallow-ignore-next-line unused-export -- consumed only through the OPERATIONS registry, not by name; still public contract surface. export const leaseReleaseAll = defineOperation({ name: "lease.release-all", @@ -641,8 +578,6 @@ export const listGet = defineOperation({ ]), }); -// ---- cleanup.run -------------------------------------------------------------------------- - // fallow-ignore-next-line unused-export -- consumed only through the OPERATIONS registry, not by name; still public contract surface. export const cleanupRun = defineOperation({ name: "cleanup.run", @@ -652,8 +587,6 @@ export const cleanupRun = defineOperation({ output: z.array(proposalSchema), }); -// ---- nuke.run ----------------------------------------------------------------------------- - // fallow-ignore-next-line unused-export -- consumed only through the OPERATIONS registry, not by name; still public contract surface. export const nukeRun = defineOperation({ name: "nuke.run", @@ -663,8 +596,6 @@ export const nukeRun = defineOperation({ output: nukeReportSchema, }); -// ---- config.get --------------------------------------------------------------------------- - // fallow-ignore-next-line unused-export -- consumed only through the OPERATIONS registry, not by name; still public contract surface. export const configGet = defineOperation({ name: "config.get", @@ -674,13 +605,6 @@ export const configGet = defineOperation({ output: configSchema, }); -// ---- daemon.stop -------------------------------------------------------------------------- -// -// Part of the closed OPERATIONS registry (ADR §3); `daemon.stop` is handled as a -// frozen-exception special case in `DaemonServer#dispatchLine` rather than through the normal -// dispatch switch, so nothing imports this declaration by name yet. Declared here because the -// contract must still describe its input/output/role. - // fallow-ignore-next-line unused-export -- see the comment above. export const daemonStop = defineOperation({ name: "daemon.stop", @@ -720,8 +644,6 @@ export const daemonUpdate = defineOperation({ }), }); -// ---- events.replay ------------------------------------------------------------------------ - // fallow-ignore-next-line unused-export -- consumed only through the OPERATIONS registry, not by name; still public contract surface. export const eventsReplay = defineOperation({ name: "events.replay", @@ -747,8 +669,6 @@ export const usageGet = defineOperation({ output: usageOutputSchema, }); -// ---- events.subscribe --------------------------------------------------------------------- - // fallow-ignore-next-line unused-export -- consumed only through the OPERATIONS registry, not by name; still public contract surface. export const eventsSubscribe = defineOperation({ name: "events.subscribe", @@ -758,8 +678,6 @@ export const eventsSubscribe = defineOperation({ output: z.object({ subscribed: z.literal(true), subscriptionId: z.string() }), }); -// ---- events.unsubscribe ------------------------------------------------------------------- - // fallow-ignore-next-line unused-export -- consumed only through the OPERATIONS registry, not by name; still public contract surface. export const eventsUnsubscribe = defineOperation({ name: "events.unsubscribe", @@ -769,8 +687,6 @@ export const eventsUnsubscribe = defineOperation({ output: z.object({ subscribed: z.literal(false) }), }); -// ---- token.create / token.list / token.revoke -------------------------------------------- - // why: ADR §11 makes the daemon the only owner of the tokens, and that move has landed: both // dispatchers handle these three, the CLI reaches them as admin round-trips from // `src/cli/commands/token.ts`, and no CLI code reads the token store itself. The declarations live @@ -803,17 +719,6 @@ export const tokenRevoke = defineOperation({ output: z.object({ revoked: z.boolean() }), }); -// ---- worker.list / worker.drain / worker.undrain / worker.remove (ADR 0005 §8, §23) --------- -// -// They act on the gateway's worker registry. A worker answers them as a fleet of one (ADR -// 0012): `worker.list` returns one view, the worker itself, and `worker.drain`, -// `worker.undrain` and `worker.remove` answer `UNSUPPORTED_IN_WORKER_MODE`, since a single host -// has no gateway to drain it or forget it. -// -// `admin` throughout: these are operator tools (drain a machine for maintenance, forget a -// machine that is gone), and ADR 0003 §3's matrix puts every daemon-wide administrative -// operation at that role. - // fallow-ignore-next-line unused-export -- consumed only through the OPERATIONS registry, not by name; still public contract surface. export const workerList = defineOperation({ name: "worker.list", @@ -885,8 +790,6 @@ export const workerUpdate = defineOperation({ output: daemonUpdate.output.extend({ workerId: z.string() }), }); -// ---- component.install (ADR 0010 §6) -------------------------------------------------------- - /** * Installs one iOS simulator runtime or Android system image without leasing a device (ADR 0010 * §6). Admin-only: the command is an operator's consent to a multi-GB download, which @@ -908,8 +811,6 @@ export const componentInstall = defineOperation({ }), }); -// ---- component.list (ADR 0010 §8) ----------------------------------------------------------- - /** * Every installed iOS simulator runtime and Android system image on this machine, ordered by * platform, then version, then variant. A read for any session. `installedBySimlock` is true only @@ -941,9 +842,6 @@ export const componentList = defineOperation({ }), }); -// ---- worker.install-component (ADR 0010 §7) ------------------------------------------------- - -/** The longest worker id a gateway accepts at the uplink, and so the longest one it can name. */ const WORKER_ID_MAX_LENGTH = 128; /** The longest error message a relayed result carries. A worker's message is a claim, cut to @@ -959,14 +857,6 @@ const workerInstallTargetsSchema = z.union([ .refine((ids) => new Set(ids).size === ids.length, "worker ids must be distinct"), ]); -/** - * One worker's answer to a relayed install. `installed` and `already-installed` are the worker's - * own outcome, with the version it installed or found. `refused` is the worker's - * `DOWNLOADS_DISABLED`; `failed` is any other error the worker answered; `skipped` is a worker - * the gateway did not ask; `unknown` is a worker whose uplink closed, or that did not answer - * within its own `downloads.timeoutMs` plus a minute. Every outcome but the first two carries - * `error`. - */ const workerInstallResultSchema = z.object({ workerId: z.string(), label: z.string().optional(), diff --git a/src/contract/protocol.ts b/src/contract/protocol.ts index 54aa285c7..0095990df 100644 --- a/src/contract/protocol.ts +++ b/src/contract/protocol.ts @@ -48,7 +48,7 @@ export function mapLegacyProtocolMismatch( export const helloRequestSchema = z .object({ clientVersion: z.string(), - /** Legacy exact version, sent alongside `protocolRange` so an old protocol-2 daemon (which + /** why: Legacy exact version, sent alongside `protocolRange` so an old protocol-2 daemon (which * only ever compares this field) still answers intelligibly. */ protocolVersion: z.number().int().optional(), protocolRange: protocolRangeSchema.optional(), diff --git a/src/contract/schemas.ts b/src/contract/schemas.ts index 54c7a1b5c..0f2431a2d 100644 --- a/src/contract/schemas.ts +++ b/src/contract/schemas.ts @@ -1,22 +1,3 @@ -/** - * Zod schemas re-declaring today's wire shapes for the contract module (ADR 0003 §1). - * - * These schemas deliberately do NOT import their "real" counterparts from `src/core` -- - * `DeviceRecord`, `LeaseRecord`, `Config`, `DoctorFinding`, `Proposal`, `EventEnvelope`, and so - * on are core-private types. Re-declaring their fields here, by hand, is exactly what keeps - * private domain records off the public package surface (the daemon maps its own records onto - * these shapes in exactly one place -- `DaemonDispatcher#dispatch`'s `#parseOutput` call in - * `src/daemon/dispatcher.ts`, which parses every handler's result through the operation's - * output schema before it reaches a transport, stripping any field (such as `DeviceRecord`'s - * quarantine/foreign/recovery bookkeeping) that a narrower output schema like - * `grantedDeviceSchema` does not declare). If a core type's shape changes - * without a matching edit here, that is a compile-time or (for shapes structurally compatible - * but semantically different) a runtime output-validation failure at the daemon boundary -- - * see `DaemonServer`'s output parsing -- not a silent drift. - * - * This module must never import from src/core, src/daemon, src/drivers, src/http, src/cli, - * src/mcp, or src/ports -- enforced by `pnpm lint` (`.oxlintrc.json`). - */ import { z } from "zod"; export const platformSchema = z.enum(["ios", "android"]); @@ -95,7 +76,6 @@ function checkDeviceType( } } -/** The longest image tag a lease request may name. */ const IMAGE_TAG_MAX = 64; /** @@ -109,11 +89,7 @@ export const imageTagSchema = z .max(IMAGE_TAG_MAX) .regex(/^[A-Za-z0-9_.-]+$/, "imageTag may contain only letters, digits, '_', '.' and '-'"); -/** A device's spec as responses show it. The spec's planned mode is not shown (ADR 0007 §9): the - * device's `mode` is the one mode a response reports. `imageTag` is shown on a device whose - * request named one. */ const deviceSpecSchema = z.object({ - /** `true` for an enrolled physical device, `false` for a simulator or emulator. */ physical: z.boolean(), platform: platformSchema, model: z.string(), @@ -220,7 +196,6 @@ export const deviceRecordSchema = z address: z.string().optional(), absentReason: absentReasonSchema.optional(), physical: z.boolean(), - /** Present exactly when `physical` is `false`. */ mode: deviceModeSchema.optional(), class: physicalClassSchema.optional(), leaseIdentity: leaseIdentitySchema.optional(), @@ -258,7 +233,7 @@ export const statusDeviceSchema = z workerId: z.string().optional(), }) .superRefine((device, context) => checkDeviceType(device, context, true)) - // `list.get` records carry a virtual device's driver address, and a worker view parses them + // why: `list.get` records carry a virtual device's driver address, and a worker view parses them // through this schema: it drops the address of a device whose canonical ID is not shown. .transform((device): typeof device => { if (device.physical || device.driverDeviceId === undefined) return device; @@ -279,11 +254,8 @@ export const grantedDeviceSchema = z * nothing here ever guesses at a value it wasn't told. */ address: z.string().optional(), - /** Always present: see `checkDeviceType`. */ physical: z.boolean(), - /** The mode the device actually has (see `DeviceRecord.mode`); absent exactly when `physical`. */ mode: deviceModeSchema.optional(), - /** Carried by a physical device, but a grant does not require it (see `checkDeviceType`). */ class: physicalClassSchema.optional(), }) .superRefine((device, context) => checkDeviceType(device, context, false)); @@ -311,20 +283,7 @@ export const leaseRecordSchema = z.object({ ttlMs: z.number(), ttlDeadline: z.number(), lastRenewedAt: z.number(), - /** - * ADR 0020: whether the requester chose `id` (`lease.request`'s `leaseId`) rather than simlock - * generating it. A gateway rebuilding its routing table reads it to name such a lease bare. - */ idChosenByRequester: z.boolean(), - /** - * ADR 0005 §18: "the lease object gains `worker: { id, label }` (additive) so a client and - * the console can tell where the device lives. A worker's network address is never exposed." - * Additive and gateway-only -- a worker granting its own local lease has no second machine to - * name and never sets this; a gateway stamps it on every lease it forwards a grant/renew for, - * using the same worker id `id` already encodes (§16: a gateway lease id is - * `.`) plus the worker's display `label`, when it has one, for a - * console to render without a second lookup. - */ worker: z.object({ id: z.string(), label: z.string().optional() }).optional(), }); @@ -335,19 +294,12 @@ const leaseTimingSchema = z.object({ estimatedReadyMs: z.number(), }); -/** - * Scoping variables a lease holder needs to reach the device it was just granted -- the - * device-set path on iOS, the adb server port on Android (ADR 0001, decision 7). Built by the - * driver that owns the device root and forwarded verbatim; no key here means anything to the - * core or to this contract, which is what lets a third driver contribute its own scoping - * without an edit here (architecture rule 2). - */ const leaseEnvironmentSchema = z.record(z.string(), z.string()); export const leaseGrantSchema = z.object({ device: grantedDeviceSchema, /** - * Always present, `{}` at the least: containment cuts both ways, and a grant that did not + * why: Always present, `{}` at the least: containment cuts both ways, and a grant that did not * carry it would hand back a `driverDeviceId` no documented workflow can address. */ environment: leaseEnvironmentSchema, @@ -479,8 +431,6 @@ export const passthroughCommandSchema = z.object({ export const leaseProgressSchema = z.discriminatedUnion("stage", [ z.object({ stage: z.literal("queued"), queuePosition: z.number() }), - // ADR 0010 §3: the request waits on a component download. Bounded here so a gateway checks a - // worker's push before relaying it (safety rule 10). z.object({ stage: z.literal("downloading"), component: z.string().max(64), @@ -499,11 +449,6 @@ const runningCapacityEntrySchema = z.object({ overLimit: z.boolean(), }); -/** - * One platform's entry. `atRamBudget`: creating one more full device of the platform would be - * refused for RAM -- the check the planner makes, so a gateway can pass such a worker over - * (ADR 0009 §7). Always `false` under a strategy that keeps no RAM budget. - */ const platformCapacityEntrySchema = runningCapacityEntrySchema.extend({ limit: z.number(), warm: z.number(), @@ -533,7 +478,6 @@ export const WARM_POOL_NAME_MAX = 256; /** why: the configured targets plus the learned kinds, 32 per platform, the pool keeps. */ const WARM_POOL_ENTRIES_MAX = WARM_POOL_TARGETS_MAX + 64; -/** Why a warm target is short, in the order the first that applies is the one reported. */ const warmPoolShortSchema = z.enum([ "disabled", "no-driver", @@ -610,17 +554,11 @@ export const daemonUpdateFields = { latestVersion: updateVersionSchema.nullable(), }; -/** - * Limits on the catalog's other names and images. A worker's catalog is a claim the gateway - * parses off the wire, so every string and list in these fields has a bound well above any - * real host (an SDK ships some 70 device profiles and a few dozen images). - */ const CATALOG_NAME_MAX = 256; const CATALOG_ALIASES_PER_MODEL_MAX = 32; const CATALOG_ALIASED_MODELS_MAX = 4096; const CATALOG_IMAGE_FIELD_MAX = 128; const CATALOG_IMAGES_MAX = 1024; -/** `customModels` is a subset of `models`; it takes the bound already on a per-model list. */ const CATALOG_CUSTOM_MODELS_MAX = CATALOG_ALIASED_MODELS_MAX; /** @@ -640,22 +578,14 @@ const catalogImageSchema = z.object({ abi: z.string().max(CATALOG_IMAGE_FIELD_MAX), }); -/** - * ADR 0008 §5: what machine a daemon runs on. Every string and the `tools` list are bounded, - * because a gateway stores what each worker reports in that worker's view (safety rule 10). - */ const MAX_HOST_STRING_LENGTH = 128; const MAX_HOST_TOOLS = 32; const hostStringSchema = z.string().min(1).max(MAX_HOST_STRING_LENGTH); export const hostFactsSchema = z.object({ - /** Operating system product name (`macOS`), or the kernel name where there is none. */ os: hostStringSchema, osVersion: hostStringSchema, - /** CPU architecture as Node names it (`arm64`, `x64`). */ arch: hostStringSchema, - /** The platform tools each driver uses, with the version installed. Empty on a gateway, which - * runs no drivers. */ tools: z .array( z.object({ @@ -668,7 +598,6 @@ export const hostFactsSchema = z.object({ .max(MAX_HOST_TOOLS), }); -/** The shape `fitHostFacts` reads: the core's own host facts type and this schema's both fit. */ interface HostFactsShape { readonly arch: string; readonly os: string; @@ -754,18 +683,7 @@ export const platformCatalogSchema = z.object({ models: z.array(z.string()), runtimes: z.array(z.string()), defaultRuntime: z.string().optional(), - /** - * ADR 0008 §1: for each name in `models`, the installed runtimes it pairs with -- every pair - * listed here can be leased. An empty list means no installed runtime pairs with that model. - * On a gateway this is the union of each connected worker's own list for the model, never - * the cross product of fleet models and fleet runtimes. - */ modelRuntimes: z.record(z.string(), z.array(z.string())), - /** - * ADR 0008 §1: for a name in `models`, the other names a lease request may use for it, in any - * letter case -- a device id a driver accepts beside a display name. Only models that have another - * name appear. On a gateway it is the union per model of what each worker lists. - */ modelAliases: z .record( z.string().max(CATALOG_NAME_MAX), @@ -774,38 +692,14 @@ export const platformCatalogSchema = z.object({ .refine((aliases) => Object.keys(aliases).length <= CATALOG_ALIASED_MODELS_MAX, { message: `modelAliases lists more than ${CATALOG_ALIASED_MODELS_MAX} models`, }), - /** - * ADR 0015 §3: for a name in `models`, its class, when the platform's tools report one. A model - * the tools say nothing usable about has no entry. On a gateway it is the union over the - * connected workers; when two workers class a model differently, the first worker in id order - * wins. - */ modelClasses: z .record(z.string().max(CATALOG_NAME_MAX), deviceClassSchema) .refine((classes) => Object.keys(classes).length <= CATALOG_ALIASED_MODELS_MAX), - /** - * ADR 0015 §4: for a class, the model Simlock would create for it on this host -- of the - * names on the class's preference list that `models` lists and that are of the class, the - * first that pairs with an installed runtime, else the first of them. A class in which no - * listed model counts has no entry. On a gateway a class's entry is kept only when every - * connected worker reports the same model for it, and that worker lists it itself. - */ classDefaults: z.partialRecord(deviceClassSchema, z.string().max(CATALOG_NAME_MAX)), - /** - * ADR 0008 §1: every installed image, present only for a platform whose driver has images - * (Android). `runtime` is a value from `runtimes`; an image of an ABI the host cannot run - * natively is listed too. On a gateway it is the union by runtime, tag, and ABI. - */ images: z.array(catalogImageSchema).max(CATALOG_IMAGES_MAX).optional(), - /** - * Names from `models` that exist because of something on this machine rather than the - * platform's tools -- on Android, a custom profile from Android Studio's `devices.xml`. - * Absent when there are none. On a gateway a model is listed when any worker that lists it - * marks it custom. - */ customModels: z.array(z.string().max(CATALOG_NAME_MAX)).max(CATALOG_CUSTOM_MODELS_MAX).optional(), /** - * ADR 0005 §21: on a gateway, `models`/`runtimes` are the *union* over the fleet, and these + * why: ADR 0005 §21: on a gateway, `models`/`runtimes` are the *union* over the fleet, and these * two maps say which workers each entry came from (`{"iPhone 16": ["wrk_a", "wrk_b"]}`). * Additive and gateway-only: a worker's own catalog needs no attribution, and a renderer * that predates the fleet still reads `models`/`runtimes` unchanged. Kept as annotations @@ -862,7 +756,7 @@ export const proposalSchema = z.object({ export const cleanupRuleSummarySchema = z.object({ name: z.string() }); -/** Mirrors `DriverDevice` (src/core/driver.ts) -- `driverData` stays opaque `unknown` on +/** why: Mirrors `DriverDevice` (src/core/driver.ts) -- `driverData` stays opaque `unknown` on * purpose, exactly as the core treats it. */ const driverDeviceSchema = z.object({ deviceId: z.string(), @@ -962,16 +856,6 @@ export const nukeReportSchema = z.object({ releasedLeaseIds: z.array(z.string()), }); -/** - * Mirrors `EventEnvelope` (src/bus/index.ts) at the envelope level only. `event` stays a plain - * string and `payload` stays `z.unknown()` rather than redeclaring all ~30 `EventMap` payload - * shapes here -- see docs/internal/EVENTS.md for the authoritative catalog. This is a deliberate, - * documented simplification: re-declaring every business event's payload shape a second time, - * next to `events.md`'s existing documentation requirement, is a lot of near-duplicate surface - * for a channel this PR does not change the routing of. Tightening this (e.g. a discriminated - * union keyed on `event`) is left as follow-up work; flagged in the PR description. - */ -/** The largest magnitude, in ms, a JavaScript `Date` holds. */ const MAX_DATE_MS = 8.64e15; export const historyStatusSchema = z.object({ @@ -985,18 +869,14 @@ export const historyStatusSchema = z.object({ export const EVENT_ID_PATTERN = /^evt_[A-Za-z0-9_-]{1,64}$/; export const eventEnvelopeSchema = z.object({ - /** Wire input is a claim (safety.md): a pushed `id` is bounded, not trusted. */ id: z.string().regex(EVENT_ID_PATTERN), seq: z.number(), - /** A claim, bounded to the range a JavaScript date holds so the console can render it. */ timestamp: z.number().min(-MAX_DATE_MS).max(MAX_DATE_MS), event: z.string(), payload: z.unknown().optional(), module: z.string(), }); -// ---- config.get ----------------------------------------------------------------------------- - const platformCapacityOptionsSchema = z.object({ maxDevices: z.number().optional(), maxRunning: z.number().optional(), @@ -1030,19 +910,11 @@ const resourceCapacityConfigSchema = z.object({ }), }); -/** - * Mirrors `CapacityConfig` (src/core/capacity/strategies/index.ts), discriminated on - * `strategy` exactly as the core type is. Adding a third strategy needs a matching edit here -- - * there is no way around that with a hand-declared contract; it is the same trade-off the ADR - * accepts for every other core-private shape. - */ const capacityConfigSchema = z.discriminatedUnion("strategy", [ fixedCapacityConfigSchema, resourceCapacityConfigSchema, ]); -/** Mirrors `Config` (src/core/config.ts) field for field. */ -/** ADR 0015 §4: `ios.defaultModels` and `android.defaultModels`, a list of model names per class. */ const defaultModelsSchema = z.partialRecord(deviceClassSchema, z.array(z.string().min(1)).min(1)); const proxyAuthSchema = z.object({ @@ -1054,9 +926,6 @@ const proxyAuthSchema = z.object({ }); export const configSchema = z.object({ - /** ADR 0005 §1. `config.get` is how a caller learns which mode the daemon it is talking to - * runs in without inferring it from behaviour; `status.get`'s `daemon.mode` is the - * agent-role answer to the same question. */ mode: z.enum(["worker", "gateway"]), capabilities: z.record( capabilityNameSchema, @@ -1115,7 +984,7 @@ export const configSchema = z.object({ level: z.enum(["debug", "info", "warn", "error"]), rotateBytes: z.number(), }), - // Optional: a newer gateway parses this from an older worker's `config.get`. + // why: Optional: a newer gateway parses this from an older worker's `config.get`. eventLog: z .object({ retention: z.number().optional(), @@ -1154,29 +1023,13 @@ export const configSchema = z.object({ bootAnimation: z.boolean(), }), }), - /** ADR 0005 §3/§6. `url`/`token`/`label` are the worker's half, `disconnectedRetentionMs` - * the gateway's; a daemon carries the whole block whichever mode it runs in, and simply - * reads the half that applies (see `Config["gateway"]`). */ gateway: z.object({ url: z.string().optional(), token: z.string().optional(), label: z.string().optional(), disconnectedRetentionMs: z.number(), execTimeoutMs: z.number(), - /** ADR 0005 §29/P2: the forwarded `lease.request`'s own bound. Round 6 review: this was - * declared on `Config`, validated by `loadConfig`, and documented in CONFIGURATION.md, but - * missing here -- and since this schema is `config.get`'s declared output, zod stripped it, - * so an operator inspecting the effective config saw every other gateway key and concluded - * this one did not exist. `config.test.ts` now parses a loaded config through this schema - * and compares key sets, so the next such omission fails rather than disappears. */ leaseRequestTimeoutMs: z.number(), - /** ADR 0005 §13/Decision 7: which routing policy dispatch uses. Gateway-side, read once at - * construction like `capacity.strategy` -- never a per-request choice. The set of names is - * re-declared here rather than imported from `src/gateway/routing.ts`'s own registry, the - * same way `capacityConfigSchema` above re-declares "fixed"/"resource" instead of importing - * `src/core/capacity`'s: this module describes wire shapes and imports no engine (its own - * header comment), gateway or worker alike. `"warm-then-free"` is the only policy in v1; - * adding a second means adding it to both registries, exactly as a capacity strategy does. */ routing: z.enum(["warm-then-free"]), /** * why: `format` and `events` are re-declared, not imported from core; a test checks `format` against core's list. `secret` is optional so a `slack` destination parses. @@ -1199,8 +1052,6 @@ export const configSchema = z.object({ }), }); -// ---- tokens ----------------------------------------------------------------------------------- - /** * `TokenRole` (agent/operator/worker token store roles, src/auth/tokens.ts) is a * deliberately different vocabulary from the daemon session `Role` (agent/admin, see @@ -1226,8 +1077,6 @@ export const tokenRecordSchema = z.object({ workerId: z.string().max(128).optional(), }); -// ---- modes and worker views (ADR 0005) -------------------------------------------------------- - /** * Which mode a daemon runs in (ADR 0005 §1). A worker owns devices -- drivers, registry, * capacity, leases -- and is what every simlock daemon has been until now, so it is the @@ -1236,49 +1085,31 @@ export const tokenRecordSchema = z.object({ */ export const daemonModeSchema = z.enum(["worker", "gateway"]); -/** Mirrors `ProtocolRange` (protocol.ts). Re-declared rather than imported so this file stays - * the one place a wire *shape* is written down; `protocol.ts` owns the negotiation logic. */ const protocolRangeShapeSchema = z.object({ min: z.number().int(), max: z.number().int() }); -/** - * ADR 0005 §6: the uplink is the reachability signal. `connected` means the worker's uplink is - * open and its session negotiated a protocol version; `disconnected` means it closed and the - * view is the last-known one (kept until `gateway.disconnectedRetentionMs` elapses or an - * operator removes it); `incompatible` means the uplink is open but `hello` found no - * overlapping protocol version (§31) -- the view then carries both ranges and nothing else the - * gateway would have had to ask for over a protocol it cannot speak. - */ const workerConnectionStateSchema = z.enum(["connected", "disconnected", "incompatible"]); -// ---- installs in progress (ADR 0010 §3) --------------------------------------------------- - const MAX_LISTED_INSTALLS = 16; const MAX_INSTALL_COMPONENT_LENGTH = 64; /** - * One component install a daemon has waiting or running. A worker's list reaches the gateway as + * why: One component install a daemon has waiting or running. A worker's list reaches the gateway as * a claim (safety rule 10), so the bounds cut rather than refuse: an over-long list keeps its * first (oldest) 16 entries and an over-long `component` its first 64 characters, and the view * stays usable instead of failing its whole refresh over one field. */ const installInProgressSchema = z.object({ platform: platformSchema, - /** As the daemon's driver named it: a version, or a word such as "newest". */ component: z.string().transform((value) => value.slice(0, MAX_INSTALL_COMPONENT_LENGTH)), - /** `waiting` behind another install on its platform, or `downloading`. */ state: z.enum(["waiting", "downloading"]), - /** When the first call for this install arrived. */ since: z.number(), - /** How many calls are joined to it. */ waiters: z.number().int().nonnegative(), }); -/** A list of at most `MAX_LISTED_INSTALLS` entries; a longer one is cut to its first ones. */ function boundedInstalls(entry: Entry) { return z.array(entry).transform((installs) => installs.slice(0, MAX_LISTED_INSTALLS)); } -/** A worker view's installs: the worker's own `status.get` list, copied (ADR 0010 §7). */ const workerInstallsSchema = boundedInstalls(installInProgressSchema); /** `status.get`'s installs. On a gateway each entry names the worker it runs on. */ @@ -1347,50 +1178,33 @@ export function capabilityReason(text: string): string { export const workerViewSchema = z.object({ capabilities: capabilityListSchema.optional(), load: workerLoadSchema.optional(), - /** The worker's own instance identity (`instance.json`), ADR 0005 §3a: stable across - * restarts, unique by construction, opaque to clients. Never a host name or an address. */ id: z.string(), - /** `gateway.label` from the worker's config -- display only, need not be unique, and never - * used to route (§13: "label is display-only"). */ label: z.string().optional(), connection: workerConnectionStateSchema, - /** ADR 0005 §9: a drained worker keeps its leases and receives no new dispatches. */ drained: z.boolean(), - /** When the gateway last had this worker's uplink open, or last refreshed its view. */ lastSeenAt: z.number(), health: daemonHealthSchema.optional(), - /** The worker daemon's own version string, as reported by its `hello` reply. */ version: z.string().optional(), - /** Set only for an `incompatible` worker (§31), naming both ranges so an operator can see - * which side to upgrade. */ protocol: z .object({ gateway: protocolRangeShapeSchema, worker: protocolRangeShapeSchema }) .optional(), capacity: statusCapacitySchema.optional(), - /** The worker's warm pool as its last `status.get` read it; absent until one was read. */ warmPool: statusWarmPoolSchema.optional(), - /** - * The worker's effective `downloads.policy`, read once with `config.get` when the uplink - * connects. Display only: routing counts installed runtimes and never reads it, and the - * gateway forwards every request with `allowDownload: false` (ADR 0009 §3). Absent for a - * worker whose `config.get` the gateway could not read (an incompatible one, or a call that - * failed). - */ downloads: z .object({ policy: z.enum(["never", "on-request", "always"]), - /** Optional so a gateway on an older build still parses a view. */ + /** why: Optional so a gateway on an older build still parses a view. */ timeoutMs: z.number().optional(), }) .optional(), - /** A worker whose own cap is lower still refuses a request the gateway accepted; this lets the gateway notice. */ + /** why: A worker whose own cap is lower still refuses a request the gateway accepted; this lets the gateway notice. */ lease: z.object({ maxTtlMs: z.number() }).optional(), queueDepth: z.number().optional(), physicalQueueDepth: z.number().optional(), leases: z.array(leaseRecordSchema).optional(), devices: z.array(statusDeviceSchema).optional(), catalog: z.array(platformCatalogSchema).optional(), - /** Cleared on connect, so a just-connected worker has none: it tells a catalog read and found empty from one not yet arrived. */ + /** why: Cleared on connect, so a just-connected worker has none: it tells a catalog read and found empty from one not yet arrived. */ catalogReadAt: z.number().optional(), host: hostFactsSchema.optional(), installs: workerInstallsSchema.optional(), @@ -1401,7 +1215,6 @@ export const workerViewSchema = z.object({ }); export const statusLeaseSchema = leaseRecordSchema.extend({ - /** Which worker holds this lease; absent on a worker's own answer, set by a gateway. */ workerId: z.string().optional(), // why: the part of a gateway lease id after `.`, so a relayed event naming the worker's lease can be matched; set only by a gateway's `lease.list`, `lease.get` and `list.get kind=leases`. workerLeaseId: z.string().optional(),