From 12ec9df1c22b76cc95b1913ee93050339cbaadf2 Mon Sep 17 00:00:00 2001 From: Richard Xiong Date: Mon, 27 Jul 2026 16:22:14 +0800 Subject: [PATCH 1/3] chore(harn): reconcile pnpm approvals after PR #15 --- ...rovals-span-pinned-and-current-config.yaml | 13 +++++ .../reconcile-pr15-pnpm-build-approvals.yaml | 51 +++++++++++++++++++ pnpm-workspace.yaml | 2 + scripts/release-audit.mjs | 21 ++++++++ 4 files changed, 87 insertions(+) create mode 100644 .harn/assumptions/pnpm-build-approvals-span-pinned-and-current-config.yaml create mode 100644 .harn/plans/reconcile-pr15-pnpm-build-approvals.yaml diff --git a/.harn/assumptions/pnpm-build-approvals-span-pinned-and-current-config.yaml b/.harn/assumptions/pnpm-build-approvals-span-pinned-and-current-config.yaml new file mode 100644 index 00000000..0b4aa86e --- /dev/null +++ b/.harn/assumptions/pnpm-build-approvals-span-pinned-and-current-config.yaml @@ -0,0 +1,13 @@ +id: pnpm-build-approvals-span-pinned-and-current-config +hash: a-b3a8a1a7c479 +title: Required build scripts are approved by pinned and current pnpm config +state: active +statement: The pinned pnpm 10.9 package.json onlyBuiltDependencies list and the + pnpm 10.26+/11 pnpm-workspace allowBuilds map must remain additive and approve + the same better-sqlite3, esbuild, sodium-native, and udx-native build scripts, + with the release audit rejecting any divergence, because either supported pnpm + generation must produce usable native bindings and generated binaries without + silently ignoring a required install script. +depends_on: + - workspace-gates-cover-all-buildable-projects +created_by: reconcile-pr15-pnpm-build-approvals diff --git a/.harn/plans/reconcile-pr15-pnpm-build-approvals.yaml b/.harn/plans/reconcile-pr15-pnpm-build-approvals.yaml new file mode 100644 index 00000000..d9d3daed --- /dev/null +++ b/.harn/plans/reconcile-pr15-pnpm-build-approvals.yaml @@ -0,0 +1,51 @@ +id: reconcile-pr15-pnpm-build-approvals +title: Record and enforce post-merge pnpm build-approval compatibility from PR 15 +notes: "This is an honest post-merge reconciliation of contributor PR #15. The + contributor commits were merged unchanged before this plan existed, so this + plan does not claim to have governed or predated them. Strict package.json + cannot carry Harn comment markers without unsupported syntax, so the current + pnpm-workspace declaration is source-anchored and the existing release audit + gains a second anchor with an executable equivalence check that reads both + package.json and pnpm-workspace.yaml. The four approvals and pnpm 10.9 + packageManager pin remain unchanged. Disposable clean installs and recursive + builds must pass under pinned pnpm 10.9.0 and current pnpm 11 with only the + alternate-version guard explicitly bypassed for the latter probe." +assumptions: + create: + - id: pnpm-build-approvals-span-pinned-and-current-config + title: Required build scripts are approved by pinned and current pnpm config + statement: The pinned pnpm 10.9 package.json onlyBuiltDependencies list and the + pnpm 10.26+/11 pnpm-workspace allowBuilds map must remain additive and + approve the same better-sqlite3, esbuild, sodium-native, and udx-native + build scripts, with the release audit rejecting any divergence, because + either supported pnpm generation must produce usable native bindings and + generated binaries without silently ignoring a required install script. + reason: pnpm 11 ignores the legacy package.json field while the pinned pnpm 10.9 + predates allowBuilds, so compatibility depends on both equivalent + declarations remaining present and executable drift detection. + depends_on: + - workspace-gates-cover-all-buildable-projects + reviewed: + - id: workspace-gates-cover-all-buildable-projects + outcome: unchanged + reason: "PR #15 adds build approval configuration below the existing workspace + glob anchor without changing which projects recursive gates cover." + - id: fresh-clone-install-proven-by-script + outcome: unchanged + reason: The clean-clone install and build contract is unchanged; the additive + current-pnpm map makes the same required scripts available to newer pnpm + generations. +files: + - pnpm-workspace.yaml + - scripts/release-audit.mjs +anchors: + pnpm-build-approvals-span-pinned-and-current-config: + pnpm-current-build-approvals: + action: change + reason: Anchor the merged pnpm 10.26+/11 allowBuilds declaration. + pnpm-build-approval-equivalence-audit: + action: change + reason: Read both config files and fail the release audit unless the enabled + allowBuilds keys exactly equal package.json onlyBuiltDependencies. +applied: + applied_at: 2026-07-27T08:22:13.767Z diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 094b66b9..7ec0b2ec 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,6 +7,7 @@ packages: - relay # harn:end workspace-gates-cover-all-buildable-projects +# harn:assume pnpm-build-approvals-span-pinned-and-current-config ref=pnpm-current-build-approvals # pnpm >= 10.26 reads build-script approvals from this map instead of the "pnpm.onlyBuiltDependencies" # field in package.json, which pnpm 11 ignores outright. Kept in addition to that field, not in # place of it: package.json's "packageManager" pin stays at pnpm@10.9.0, which predates @@ -16,3 +17,4 @@ allowBuilds: esbuild: true sodium-native: true udx-native: true +# harn:end pnpm-build-approvals-span-pinned-and-current-config diff --git a/scripts/release-audit.mjs b/scripts/release-audit.mjs index 319f27ff..a58b3595 100644 --- a/scripts/release-audit.mjs +++ b/scripts/release-audit.mjs @@ -123,6 +123,27 @@ assert.equal((firstChannelSource.match(/headingLevel=\{2\}/g) ?? []).length, 2); const selfHost = await readFile(new URL('../docs/SELF-HOST.md', import.meta.url), 'utf8'); const setupGuide = await readFile(new URL('../docs/SETUP.md', import.meta.url), 'utf8'); const rootManifest = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')); +const workspaceManifest = await readFile(new URL('../pnpm-workspace.yaml', import.meta.url), 'utf8'); + +// harn:assume pnpm-build-approvals-span-pinned-and-current-config ref=pnpm-build-approval-equivalence-audit +const requiredBuildApprovals = ['better-sqlite3', 'esbuild', 'sodium-native', 'udx-native']; +const legacyBuildApprovals = [...(rootManifest.pnpm?.onlyBuiltDependencies ?? [])].sort(); +const allowBuilds = workspaceManifest.match(/^allowBuilds:\n((?:^ [^\n]+\n?)*)/m); +assert.ok(allowBuilds, 'pnpm-workspace.yaml must declare allowBuilds'); +const currentBuildApprovals = [...allowBuilds[1].matchAll(/^ ([^:#\n]+): true$/gm)] + .map((match) => match[1]) + .sort(); +assert.deepEqual( + legacyBuildApprovals, + requiredBuildApprovals, + 'package.json pnpm.onlyBuiltDependencies must approve every required build script', +); +assert.deepEqual( + currentBuildApprovals, + legacyBuildApprovals, + 'pnpm-workspace.yaml allowBuilds must match package.json pnpm.onlyBuiltDependencies', +); +// harn:end pnpm-build-approvals-span-pinned-and-current-config // harn:assume public-npx-install-is-primary-install ref=release-install-audit for (const [name, body] of [['README', readme], ['self-host guide', selfHost], ['setup guide', setupGuide]]) { From f199d107186cd112fd23147a3f6d5d1e795b2f1e Mon Sep 17 00:00:00 2001 From: Richard Xiong Date: Mon, 27 Jul 2026 16:24:35 +0800 Subject: [PATCH 2/3] chore(harn): reconcile install disclosures after PR #9 --- ...cs-disclose-build-approval-boundaries.yaml | 14 +++ ...ocs-disclose-certificate-transparency.yaml | 13 +++ ...cile-pr9-install-tailscale-disclosure.yaml | 100 ++++++++++++++++++ README.md | 4 + docs/PRIVACY.md | 2 + docs/SELF-HOST.md | 6 ++ 6 files changed, 139 insertions(+) create mode 100644 .harn/assumptions/pnpm-install-docs-disclose-build-approval-boundaries.yaml create mode 100644 .harn/assumptions/tailscale-serve-docs-disclose-certificate-transparency.yaml create mode 100644 .harn/plans/reconcile-pr9-install-tailscale-disclosure.yaml diff --git a/.harn/assumptions/pnpm-install-docs-disclose-build-approval-boundaries.yaml b/.harn/assumptions/pnpm-install-docs-disclose-build-approval-boundaries.yaml new file mode 100644 index 00000000..80ac196c --- /dev/null +++ b/.harn/assumptions/pnpm-install-docs-disclose-build-approval-boundaries.yaml @@ -0,0 +1,14 @@ +id: pnpm-install-docs-disclose-build-approval-boundaries +hash: a-50784d52f1bb +title: pnpm install guidance discloses execution and build-approval boundaries +state: active +statement: Public install documentation must direct pnpm users away from the + tested pnpm dlx path, explain that it did not receive required workspace + package extensions and native build approvals, and name the compatible legacy + onlyBuiltDependencies and current allowBuilds settings, because an apparently + successful install without required native scripts produces an unusable + runtime. +depends_on: + - public-npx-install-is-primary-install + - pnpm-build-approvals-span-pinned-and-current-config +created_by: reconcile-pr9-install-tailscale-disclosure diff --git a/.harn/assumptions/tailscale-serve-docs-disclose-certificate-transparency.yaml b/.harn/assumptions/tailscale-serve-docs-disclose-certificate-transparency.yaml new file mode 100644 index 00000000..d28c9cb6 --- /dev/null +++ b/.harn/assumptions/tailscale-serve-docs-disclose-certificate-transparency.yaml @@ -0,0 +1,13 @@ +id: tailscale-serve-docs-disclose-certificate-transparency +hash: a-3ed3f05350e7 +title: Tailscale Serve docs disclose HTTPS authorization and public CT issuance +state: active +statement: Public and privacy documentation must explain that tailnet HTTPS + enablement authorizes certificate issuance, running tailscale serve issues a + certificate whose device ts.net name enters the irreversible public + Certificate Transparency record, and localhost avoids that disclosure, because + disabling Serve or HTTPS later cannot withdraw an already published + certificate record. +depends_on: + - setup-resolves-and-capability-probes-tailscale-serve +created_by: reconcile-pr9-install-tailscale-disclosure diff --git a/.harn/plans/reconcile-pr9-install-tailscale-disclosure.yaml b/.harn/plans/reconcile-pr9-install-tailscale-disclosure.yaml new file mode 100644 index 00000000..cdcdc3bc --- /dev/null +++ b/.harn/plans/reconcile-pr9-install-tailscale-disclosure.yaml @@ -0,0 +1,100 @@ +id: reconcile-pr9-install-tailscale-disclosure +title: Anchor post-merge install and Tailscale disclosure truth from PR 9 +notes: "This plan honestly reconciles contributor PR #9 after its original + commits were merged unchanged. It does not claim to have governed or predated + that documentation. The implementation adds Harn markers only: pnpm guidance + must retain the tested pnpm dlx limitation and compatible build-approval + names, while Tailscale guidance must distinguish HTTPS authorization, + certificate issuance, irreversible public Certificate Transparency disclosure, + and the localhost alternative. Setup behavior, trust policy, and contributor + prose remain unchanged." +assumptions: + create: + - id: pnpm-install-docs-disclose-build-approval-boundaries + title: pnpm install guidance discloses execution and build-approval boundaries + statement: Public install documentation must direct pnpm users away from the + tested pnpm dlx path, explain that it did not receive required workspace + package extensions and native build approvals, and name the compatible + legacy onlyBuiltDependencies and current allowBuilds settings, because + an apparently successful install without required native scripts + produces an unusable runtime. + reason: "Contributor PR #9 records a tested packaging limitation and the + configuration operators need to avoid a silent native-binding failure." + depends_on: + - public-npx-install-is-primary-install + - pnpm-build-approvals-span-pinned-and-current-config + - id: tailscale-serve-docs-disclose-certificate-transparency + title: Tailscale Serve docs disclose HTTPS authorization and public CT issuance + statement: Public and privacy documentation must explain that tailnet HTTPS + enablement authorizes certificate issuance, running tailscale serve + issues a certificate whose device ts.net name enters the irreversible + public Certificate Transparency record, and localhost avoids that + disclosure, because disabling Serve or HTTPS later cannot withdraw an + already published certificate record. + reason: Operators must be able to distinguish reversible access configuration + from the public disclosure caused by certificate issuance. + depends_on: + - setup-resolves-and-capability-probes-tailscale-serve + reviewed: + - id: public-npx-install-is-primary-install + outcome: unchanged + reason: The primary npx install command and setup alias remain unchanged; the + pnpm note documents a separate tested execution boundary. + - id: fresh-clone-install-proven-by-script + outcome: unchanged + reason: The self-host guide remains the clean-clone contract; new markers only + anchor merged prerequisite and disclosure prose. + - id: setup-resolves-and-capability-probes-tailscale-serve + outcome: unchanged + reason: Runtime resolution, capability probing, retry, and localhost fallback + behavior are untouched by documentation anchors. + - id: tailnet-auto-pairing-explicit-trust + outcome: unchanged + reason: Certificate disclosure is independent from the existing opt-in + identity-header trust and pairing boundary. + - id: operator-launches-serve-web-next + outcome: unchanged + reason: README anchor nesting does not change which supported browser runtime + setup launches. +files: + - README.md + - docs/SELF-HOST.md + - docs/PRIVACY.md +anchors: + pnpm-install-docs-disclose-build-approval-boundaries: + readme-pnpm-install-disclosure: + action: change + reason: Anchor the concise public warning against the tested pnpm dlx path. + selfhost-pnpm-build-approval-disclosure: + action: change + reason: Anchor both pnpm generations' required approval setting names. + selfhost-pnpm-dlx-disclosure: + action: change + reason: Anchor the detailed tested pnpm dlx configuration limitation. + tailscale-serve-docs-disclose-certificate-transparency: + readme-tailscale-certificate-disclosure: + action: change + reason: Anchor the public HTTPS prerequisite, issuance, CT, and localhost note. + selfhost-tailscale-certificate-disclosure: + action: change + reason: Anchor the detailed reversible configuration versus irreversible CT + truth. + privacy-tailscale-certificate-disclosure: + action: change + reason: Anchor the privacy tier's public device-name disclosure. + public-npx-install-is-primary-install: + readme-primary-install: + action: change + reason: A nested pnpm disclosure anchor is added inside the existing install + region. + fresh-clone-install-proven-by-script: + selfhost-guide: + action: change + reason: Nested anchors are added throughout the canonical self-host guide. + operator-launches-serve-web-next: + readme-current-web-client: + action: change + reason: Nested install and Tailscale anchors are added inside the README runtime + region. +applied: + applied_at: 2026-07-27T08:24:34.533Z diff --git a/README.md b/README.md index 2cb6db41..8a484e0c 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,13 @@ The interactive setup checks this computer, prepares private files, asks how the connect, starts Codor, verifies the daemon, and prints a QR, URL, eight-character pairing code, and expiry. It never sends channel data through a Codor-hosted service. + > [!NOTE] > On pnpm, install into a project rather than running `pnpm dlx @richhardry/codor install`. A tested > `pnpm dlx` install did not pick up the workspace `packageExtensions` and native build-approval > settings this install needs—see [Self-host: Prerequisites](docs/SELF-HOST.md#prerequisites) for > the settings. + Preview without changing the host: @@ -124,6 +126,7 @@ Tailscale lets you open Codor privately from your phone, tablet, or another comp putting it on the public internet. [Install Tailscale](https://tailscale.com/download) and sign in on both devices with the same account. + > [!IMPORTANT] > Before this works, HTTPS certificates must be enabled for your tailnet at > [the admin console](https://console.tailscale.com/admin/dns). Until then, `tailscale serve` waits @@ -134,6 +137,7 @@ on both devices with the same account. > [Certificate Transparency](https://tailscale.com/docs/how-to/set-up-https-certificates) log—a > disclosure that can't be undone, even if you disable HTTPS again later. If you'd rather skip this, > choose "On this computer only" during setup—you can add Tailscale access later. + `codor install` can publish Codor privately over Tailscale automatically. If you skipped that step, run: diff --git a/docs/PRIVACY.md b/docs/PRIVACY.md index 9289b68c..f3c87727 100644 --- a/docs/PRIVACY.md +++ b/docs/PRIVACY.md @@ -33,6 +33,7 @@ no cloud ever holds content, encrypted or not. ### Tier 0 — tailnet only (recommended default) + Switchboard binds its Tailscale IP; web/iPhone connect over WireGuard (`tailscale serve` gives HTTPS + certs once your tailnet has HTTPS certificates enabled—a one-time authorization step at [the admin console](https://console.tailscale.com/admin/dns); running `tailscale serve` then issues @@ -41,6 +42,7 @@ Transparency log, irreversibly; a Tailscale app connector adds custom-domain acc for teams). No Codor-related traffic leaves the tailnet; transport encryption is WireGuard's. **Zero third-party infrastructure.** Limits: watch has no tailnet of its own (phone relays via WatchConnectivity), and no push when the phone app is cold (see §push). + ### Tier 1 — serverless P2P (the walkie tier) diff --git a/docs/SELF-HOST.md b/docs/SELF-HOST.md index 129ce0da..9ff2d622 100644 --- a/docs/SELF-HOST.md +++ b/docs/SELF-HOST.md @@ -14,6 +14,7 @@ machine. No hosted Codor component is required. - pnpm 10.9.0, selected by the repository's `packageManager` field through Corepack. - Optional: Tailscale for private HTTPS access from phones and other machines. + On pnpm 10.1–10.25, `better-sqlite3`'s native build script needs approval via `onlyBuiltDependencies: [better-sqlite3]` in `pnpm-workspace.yaml` or the `package.json` `pnpm` field. On pnpm 10.26 and newer (including pnpm 11), that setting is `allowBuilds: { better-sqlite3: @@ -21,6 +22,7 @@ true }` in `pnpm-workspace.yaml`—`pnpm approve-builds` writes it interactively `pnpm install` reports `Ignored build scripts: better-sqlite3` and the service fails to start with no native binding. See [pnpm's settings reference](https://pnpm.io/settings) for the current option names. + Never expose port 8137 directly to the public internet. The browser token is a bearer credential; use loopback plus Tailscale Serve, another authenticated private tunnel, or a hardened reverse @@ -43,11 +45,13 @@ side-effect-free preview. Unattended mutation requires both `--yes` and `npx @richhardry/codor setup` remains available as a backward-compatible alias. + On pnpm, install into a project (`pnpm add @richhardry/codor`) rather than `pnpm dlx @richhardry/codor install`. A tested `pnpm dlx` install did not pick up the workspace `packageExtensions` and native build-approval settings this install needs, including the `better-sqlite3` approval above—`pnpm dlx` does honor some workspace configuration (for example catalogs), but not this. + For source development, clone a stable ref and use the checkout installer: @@ -225,6 +229,7 @@ For development diagnostics only, the single repository-relative fallback is For the common single-operator setup, keep Codor on loopback and let Tailscale terminate HTTPS. + **Prerequisite:** HTTPS certificates must be enabled for your tailnet at [the admin console](https://console.tailscale.com/admin/dns) before `tailscale serve` will work non-interactively. Until that's done, `tailscale serve` blocks waiting for you to complete @@ -239,6 +244,7 @@ disclosure is not. See [Tailscale's HTTPS certificate docs](https://tailscale.com/docs/how-to/set-up-https-certificates) for the full mechanism. Choosing localhost-only access during `codor install` skips Tailscale entirely. + ```sh tailscale serve --bg http://127.0.0.1:8137 From 10e7a46bc9f05fcb77920060f06246e3623c9197 Mon Sep 17 00:00:00 2001 From: Richard Xiong Date: Mon, 27 Jul 2026 16:32:52 +0800 Subject: [PATCH 3/3] chore(harn): reconcile Grok truth after PR #7 --- .harn/assumptions/grok-capability-truth.yaml | 13 ++ ...ok-cli-resolves-windows-command-shims.yaml | 13 ++ ...-distinguishes-cache-and-context-tier.yaml | 12 + .harn/plans/reconcile-pr7-grok-truth.yaml | 207 ++++++++++++++++++ packages/adapters/grok/NOTES.md | 2 + packages/adapters/grok/src/adapter.spec.ts | 10 + packages/adapters/grok/src/adapter.ts | 16 +- packages/adapters/grok/src/fixtures.spec.ts | 2 + packages/adapters/grok/src/index.spec.ts | 4 + packages/switchboard/src/pricing.spec.ts | 7 + packages/switchboard/src/pricing.ts | 4 +- 11 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 .harn/assumptions/grok-capability-truth.yaml create mode 100644 .harn/assumptions/grok-cli-resolves-windows-command-shims.yaml create mode 100644 .harn/assumptions/grok-price-table-distinguishes-cache-and-context-tier.yaml create mode 100644 .harn/plans/reconcile-pr7-grok-truth.yaml diff --git a/.harn/assumptions/grok-capability-truth.yaml b/.harn/assumptions/grok-capability-truth.yaml new file mode 100644 index 00000000..80edfa1f --- /dev/null +++ b/.harn/assumptions/grok-capability-truth.yaml @@ -0,0 +1,13 @@ +id: grok-capability-truth +hash: a-492e4c7d3a14 +title: Grok streaming events normalize into truthful bounded turn evidence +state: active +statement: Grok streaming-json and Responses-style events accepted by the + translator must normalize into bounded text, reasoning, and tool WireEvents; + retain a discovered session identifier; normalize reported input and output + usage without inventing absent usage; and emit exactly one truthful terminal + completion or failure, because downstream run evidence must reflect the + observable native stream rather than adapter guesses. +depends_on: + - normalized-run-item-payload-contract +created_by: reconcile-pr7-grok-truth diff --git a/.harn/assumptions/grok-cli-resolves-windows-command-shims.yaml b/.harn/assumptions/grok-cli-resolves-windows-command-shims.yaml new file mode 100644 index 00000000..d1cb4c90 --- /dev/null +++ b/.harn/assumptions/grok-cli-resolves-windows-command-shims.yaml @@ -0,0 +1,13 @@ +id: grok-cli-resolves-windows-command-shims +hash: a-83ce68825360 +title: Grok direct turns resolve Windows command shims +state: active +statement: The Grok direct CLI turn launcher must use cross-spawn so grok + command and shebang shims resolve on Windows while preserving exact argv, cwd, + inherited session environment, process supervision, interruption, output + translation, and terminal cleanup, because native Node spawn does not + consistently resolve Windows command shims. +depends_on: + - adapter-process-lifecycle-supervised + - adapter-children-inherit-session-env +created_by: reconcile-pr7-grok-truth diff --git a/.harn/assumptions/grok-price-table-distinguishes-cache-and-context-tier.yaml b/.harn/assumptions/grok-price-table-distinguishes-cache-and-context-tier.yaml new file mode 100644 index 00000000..e616787b --- /dev/null +++ b/.harn/assumptions/grok-price-table-distinguishes-cache-and-context-tier.yaml @@ -0,0 +1,12 @@ +id: grok-price-table-distinguishes-cache-and-context-tier +hash: a-0375d79ad051 +title: Grok 4.5 advisory pricing distinguishes cache and context tier +state: active +statement: The advisory Grok 4.5 price table must calculate uncached input, + cached input, and output separately at the cited public standard rates of $2, + $0.30, and $6 per million tokens below 200,000 input tokens and $4, $0.60, and + $12 per million tokens at or above that threshold, because the operator + estimate must not charge cached short-context input at the uncached rate. +depends_on: + - adapters-own-their-model-catalog +created_by: reconcile-pr7-grok-truth diff --git a/.harn/plans/reconcile-pr7-grok-truth.yaml b/.harn/plans/reconcile-pr7-grok-truth.yaml new file mode 100644 index 00000000..58578888 --- /dev/null +++ b/.harn/plans/reconcile-pr7-grok-truth.yaml @@ -0,0 +1,207 @@ +id: reconcile-pr7-grok-truth +title: Reconcile Grok truth and cached-input pricing after contributor PR 7 +notes: "This plan honestly reconciles contributor PR #7 after its original + commits were merged unchanged. It does not claim to have governed or predated + that feature. The merged dangling translator anchor becomes a narrow + streaming-event contract; Grok command-shim resolution and Grok 4.5 pricing + remain independent review obligations. Invalid Grok markers are removed from + assumptions whose statements enumerate only older adapters. Existing general + adapter obligations are reviewed and receive only scoped Grok anchors. The + only behavioral correction is the published $0.30/M short-context cached-input + rate and one focused regression." +assumptions: + create: + - id: grok-capability-truth + title: Grok streaming events normalize into truthful bounded turn evidence + statement: Grok streaming-json and Responses-style events accepted by the + translator must normalize into bounded text, reasoning, and tool + WireEvents; retain a discovered session identifier; normalize reported + input and output usage without inventing absent usage; and emit exactly + one truthful terminal completion or failure, because downstream run + evidence must reflect the observable native stream rather than adapter + guesses. + reason: "Contributor PR #7 added this translator and a dangling anchor; the + post-merge reconciliation establishes only the observable translation + contract enclosed by that anchor." + depends_on: + - normalized-run-item-payload-contract + - id: grok-cli-resolves-windows-command-shims + title: Grok direct turns resolve Windows command shims + statement: The Grok direct CLI turn launcher must use cross-spawn so grok + command and shebang shims resolve on Windows while preserving exact + argv, cwd, inherited session environment, process supervision, + interruption, output translation, and terminal cleanup, because native + Node spawn does not consistently resolve Windows command shims. + reason: "Grok is outside the existing Windows assumption that explicitly + enumerates Copilot, Gemini, and OpenCode, but contributor PR #7 adopted + the same observable compatibility requirement." + depends_on: + - adapter-process-lifecycle-supervised + - adapter-children-inherit-session-env + - id: grok-price-table-distinguishes-cache-and-context-tier + title: Grok 4.5 advisory pricing distinguishes cache and context tier + statement: The advisory Grok 4.5 price table must calculate uncached input, + cached input, and output separately at the cited public standard rates + of $2, $0.30, and $6 per million tokens below 200,000 input tokens and + $4, $0.60, and $12 per million tokens at or above that threshold, + because the operator estimate must not charge cached short-context input + at the uncached rate. + reason: "Contributor PR #7 recorded the long-context cached rate but omitted the + published short-context cached-input rate." + depends_on: + - adapters-own-their-model-catalog + reviewed: + - id: canonical-spawn-controls-enforced + outcome: unchanged + reason: Grok's policy and thinking argv mapping satisfies the existing + every-adapter control contract; scoped source and regression anchors are + added without changing behavior. + - id: harness-declares-what-a-policy-becomes + outcome: unchanged + reason: Grok declares all three canonical policy outcomes and the declaration + matches its argv mapping; scoped anchors are added. + - id: harness-declares-supported-thinking-levels + outcome: unchanged + reason: Grok declares and enforces its exact low, medium, and high levels; + scoped anchors are added. + - id: adapter-process-lifecycle-supervised + outcome: unchanged + reason: Grok observes spawn, error, stderr, close, EOF, interruption, and + cleanup under the existing general lifecycle contract; malformed merged + marker nesting is repaired. + - id: adapter-children-inherit-session-env + outcome: unchanged + reason: Grok merges session.env over process.env at its only child launch; a + scoped source anchor is added. + - id: adapters-own-their-model-catalog + outcome: unchanged + reason: Grok returns a curated adapter-owned catalog backed by its cited + provider model source; scoped code and documentation anchors are added. + - id: adapter-registry-sole-harness-source + outcome: unchanged + reason: The existing registry-owned built-in composition already includes Grok + and needs no source change. + - id: adapter-catalog-distinguishes-installed-and-configurable + outcome: unchanged + reason: Existing registry tests already prove Grok is a native executable + detected on PATH; no catalog behavior changes. + - id: attach-custody-lease-tracks-child-pid + outcome: unchanged + reason: The focused CLI regression confirms Grok resolves to its native resume + argv, while the shared attach custody implementation is unchanged. + - id: live-inbox-capability-is-evidence-backed-v2 + outcome: unchanged + reason: Its immutable statement does not enumerate Grok, so the invalid + contributor marker is removed rather than broadening the assumption. + - id: remaining-cli-adapters-use-supervised-subprocesses + outcome: unchanged + reason: Its immutable statement enumerates only Copilot, Gemini, and OpenCode, + so the invalid Grok marker is removed. + - id: windows-cli-adapters-resolve-command-shims + outcome: unchanged + reason: Its immutable statement enumerates only Copilot, Gemini, and OpenCode; + Grok receives a separate narrow assumption instead. + - id: codex-price-table-models-cache-and-aliases + outcome: unchanged + reason: Nested Grok-specific pricing anchors isolate the new obligation; all + Codex prices, aliases, and behavior remain unchanged. +files: + - packages/adapters/grok/NOTES.md + - packages/adapters/grok/src/adapter.ts + - packages/adapters/grok/src/adapter.spec.ts + - packages/adapters/grok/src/fixtures.spec.ts + - packages/adapters/grok/src/index.spec.ts + - packages/switchboard/src/pricing.ts + - packages/switchboard/src/pricing.spec.ts +anchors: + grok-capability-truth: + grok-event-translation: + action: keep + reason: Preserve the exact merged translator region while establishing its + previously missing narrow assumption truth. + grok-event-translation-regression: + action: change + reason: Anchor the focused native fixture regression for translation, + completion, session-id, and terminal de-duplication behavior. + grok-cli-resolves-windows-command-shims: + grok-windows-cli-spawn-provider: + action: change + reason: Anchor only the Grok cross-spawn provider at the direct turn launch. + grok-windows-cli-launch-regression: + action: change + reason: Anchor the existing focused executable launch and translated-turn + regression. + grok-price-table-distinguishes-cache-and-context-tier: + grok-price-table: + action: change + reason: Add the missing $0.30/M standard cached-input rate without changing + other rates or thresholds. + grok-price-regression: + action: change + reason: Add one short-context cached-input calculation regression. + canonical-spawn-controls-enforced: + grok-spawn-control-mapping: + action: change + reason: Repair and scope the merged marker around Grok argv construction. + grok-spawn-control-regression: + action: change + reason: Anchor the existing focused policy/thinking argv regression. + harness-declares-what-a-policy-becomes: + grok-policy-declarations: + action: change + reason: Anchor Grok's exact three-policy declaration. + grok-policy-regression: + action: change + reason: Anchor the existing capability snapshot and argv regression. + harness-declares-supported-thinking-levels: + grok-thinking-level-declaration: + action: change + reason: Anchor Grok's exact supported thinking levels. + grok-thinking-level-regression: + action: change + reason: Anchor the existing capability snapshot and argv regression. + adapter-process-lifecycle-supervised: + grok-cli-process-supervision: + action: change + reason: Repair the malformed merged marker and scope it to deliver, + interruption, and process-group signaling. + grok-cli-process-supervision-regression: + action: change + reason: Anchor the existing successful and missing-command lifecycle tests. + adapter-children-inherit-session-env: + grok-child-environment: + action: change + reason: Anchor the exact environment merge at the only Grok child launch. + adapters-own-their-model-catalog: + grok-model-catalog: + action: change + reason: Anchor the curated Grok model result owned by the adapter. + grok-model-catalog-notes: + action: change + reason: Anchor the provider model documentation cited by the curated catalog. + live-inbox-capability-is-evidence-backed-v2: + grok-live-inbox-capability: + action: remove + reason: Remove the invalid Grok marker from an assumption whose statement + explicitly enumerates other built-ins. + remaining-cli-adapters-use-supervised-subprocesses: + grok-cli-subprocess-driver: + action: remove + reason: Remove the invalid Grok marker from the Copilot/Gemini/OpenCode-only + assumption. + windows-cli-adapters-resolve-command-shims: + windows-cli-spawn-provider: + action: remove + reason: Remove the invalid Grok marker from the Copilot/Gemini/OpenCode-only + assumption and replace it with the separate Grok obligation. + codex-price-table-models-cache-and-aliases: + codex-price-table: + action: change + reason: Add a nested Grok price anchor inside the existing table region without + changing Codex truth. + codex-price-regression: + action: change + reason: Add a nested Grok regression anchor inside the existing suite without + changing Codex coverage. +applied: + applied_at: 2026-07-27T08:32:49.167Z diff --git a/packages/adapters/grok/NOTES.md b/packages/adapters/grok/NOTES.md index 5612587a..c844a8c8 100644 --- a/packages/adapters/grok/NOTES.md +++ b/packages/adapters/grok/NOTES.md @@ -5,7 +5,9 @@ specification is based on the xAI CLI documentation checked on 2026-07-23: - - + - + - ## Invocation diff --git a/packages/adapters/grok/src/adapter.spec.ts b/packages/adapters/grok/src/adapter.spec.ts index ac5af1c2..725fa266 100644 --- a/packages/adapters/grok/src/adapter.spec.ts +++ b/packages/adapters/grok/src/adapter.spec.ts @@ -23,6 +23,9 @@ afterEach(() => { }); describe('Grok subprocess and capability conformance', () => { + // harn:assume canonical-spawn-controls-enforced ref=grok-spawn-control-regression + // harn:assume harness-declares-what-a-policy-becomes ref=grok-policy-regression + // harn:assume harness-declares-supported-thinking-levels ref=grok-thinking-level-regression it('maps documented effort and approval controls', () => { expect(grokApprovalArgs('read-only')).toEqual([]); expect(grokApprovalArgs('workspace-write')).toEqual([]); @@ -34,7 +37,12 @@ describe('Grok subprocess and capability conformance', () => { '--model', 'grok-4.5', '--effort', 'high', '--always-approve', '--resume', 'session', ]); }); + // harn:end harness-declares-supported-thinking-levels + // harn:end harness-declares-what-a-policy-becomes + // harn:end canonical-spawn-controls-enforced + // harn:assume adapter-process-lifecycle-supervised ref=grok-cli-process-supervision-regression + // harn:assume grok-cli-resolves-windows-command-shims ref=grok-windows-cli-launch-regression it('passes headless arguments and translates a streaming turn', async () => { const command = executable(` const fs = require('node:fs'); @@ -58,6 +66,7 @@ console.log(JSON.stringify({type:'response.completed',status:'completed',usage:{ }); expect(session.session_ref).toBe('22222222-2222-4222-8222-222222222222'); }); + // harn:end grok-cli-resolves-windows-command-shims it('discovers UUID sessions from ~/.grok/sessions', () => { const home = mkdtempSync(join(tmpdir(), 'codor-grok-home-')); @@ -74,4 +83,5 @@ console.log(JSON.stringify({type:'response.completed',status:'completed',usage:{ new GrokAdapter().spawn({ cwd: process.cwd() }), 'hello')) events.push(event); expect(events.at(-1)).toMatchObject({ type: 'run.completed', status: 'failed' }); }); + // harn:end adapter-process-lifecycle-supervised }); diff --git a/packages/adapters/grok/src/adapter.ts b/packages/adapters/grok/src/adapter.ts index 3258e22d..c01af3a2 100644 --- a/packages/adapters/grok/src/adapter.ts +++ b/packages/adapters/grok/src/adapter.ts @@ -35,6 +35,7 @@ export function grokArgs(session: Session, payload: string): string[] { if (session.session_ref !== undefined) args.push('--resume', session.session_ref); return args; } +// harn:end canonical-spawn-controls-enforced const SESSION_REF = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; @@ -48,18 +49,20 @@ export class GrokAdapter implements HarnessAdapter { ask: false, approvals: 'spawn-time', extensions: false, + // harn:assume harness-declares-supported-thinking-levels ref=grok-thinking-level-declaration thinking: true, thinking_levels: ['low', 'medium', 'high'] as const, - // harn:assume live-inbox-capability-is-evidence-backed-v2 ref=grok-live-inbox-capability + // harn:end harness-declares-supported-thinking-levels live_inbox: false, // Grok exposes --always-approve, but its CLI does not document a native // read-only/workspace-write mapping. Do not imply either tier is enforced. + // harn:assume harness-declares-what-a-policy-becomes ref=grok-policy-declarations policies: { 'read-only': null, 'workspace-write': null, 'full-access': '--always-approve', }, - // harn:end live-inbox-capability-is-evidence-backed-v2 + // harn:end harness-declares-what-a-policy-becomes } as const; private readonly children = new WeakMap(); @@ -87,16 +90,16 @@ export class GrokAdapter implements HarnessAdapter { }; } + // harn:assume adapters-own-their-model-catalog ref=grok-model-catalog listModels(): Promise { return Promise.resolve({ models: ['grok-4.5'], source: 'curated' }); } + // harn:end adapters-own-their-model-catalog attach(session_ref: SessionRef): Session { return { harness: this.id, session_ref, cwd: process.cwd() }; } - // harn:assume windows-cli-adapters-resolve-command-shims ref=windows-cli-spawn-provider - // harn:assume remaining-cli-adapters-use-supervised-subprocesses ref=grok-cli-subprocess-driver // harn:assume adapter-process-lifecycle-supervised ref=grok-cli-process-supervision async *deliver( session: Session, @@ -104,12 +107,16 @@ export class GrokAdapter implements HarnessAdapter { hooks: AdapterTurnHooks = {}, ): AsyncIterable { const args = grokArgs(session, payload); + // harn:assume grok-cli-resolves-windows-command-shims ref=grok-windows-cli-spawn-provider + // harn:assume adapter-children-inherit-session-env ref=grok-child-environment const child = spawn(this.command, args, { cwd: session.cwd, stdio: ['ignore', 'pipe', 'pipe'], detached: true, env: { ...process.env, ...session.env }, }); + // harn:end adapter-children-inherit-session-env + // harn:end grok-cli-resolves-windows-command-shims this.children.set(session, child); let stderr = ''; @@ -182,6 +189,7 @@ export class GrokAdapter implements HarnessAdapter { child.kill(signal); } } + // harn:end adapter-process-lifecycle-supervised respondInteraction(): Promise { return Promise.reject( diff --git a/packages/adapters/grok/src/fixtures.spec.ts b/packages/adapters/grok/src/fixtures.spec.ts index ba93479f..30a19283 100644 --- a/packages/adapters/grok/src/fixtures.spec.ts +++ b/packages/adapters/grok/src/fixtures.spec.ts @@ -5,6 +5,7 @@ import { describe, expect, it } from 'vitest'; import { createTurnTranslator } from './translate.js'; describe('native Grok streaming-json fixture', () => { + // harn:assume grok-capability-truth ref=grok-event-translation-regression it('translates text, thought, session id, and end events', () => { const translator = createTurnTranslator(); const lines = readFileSync(new URL('../fixtures/streaming-native.jsonl', import.meta.url), 'utf8') @@ -20,4 +21,5 @@ describe('native Grok streaming-json fixture', () => { expect(translator.sessionId()).toBe('22222222-2222-4222-8222-222222222222'); expect(translator.end()).toEqual([]); }); + // harn:end grok-capability-truth }); diff --git a/packages/adapters/grok/src/index.spec.ts b/packages/adapters/grok/src/index.spec.ts index c34bada8..bc8890b3 100644 --- a/packages/adapters/grok/src/index.spec.ts +++ b/packages/adapters/grok/src/index.spec.ts @@ -3,6 +3,8 @@ import { describe, expect, it } from 'vitest'; import { GrokAdapter } from './index.js'; describe('@codor/adapter-grok barrel', () => { + // harn:assume harness-declares-what-a-policy-becomes ref=grok-policy-regression + // harn:assume harness-declares-supported-thinking-levels ref=grok-thinking-level-regression it('exposes the conservative native capability contract', () => { const adapter = new GrokAdapter(); expect(adapter.id).toBe('grok'); @@ -23,4 +25,6 @@ describe('@codor/adapter-grok barrel', () => { }, }); }); + // harn:end harness-declares-supported-thinking-levels + // harn:end harness-declares-what-a-policy-becomes }); diff --git a/packages/switchboard/src/pricing.spec.ts b/packages/switchboard/src/pricing.spec.ts index e5b4f9f3..82926203 100644 --- a/packages/switchboard/src/pricing.spec.ts +++ b/packages/switchboard/src/pricing.spec.ts @@ -54,11 +54,17 @@ describe('estimateCostUsd', () => { })).toBeCloseTo(0.980004, 9); }); + // harn:assume grok-price-table-distinguishes-cache-and-context-tier ref=grok-price-regression it('uses the published Grok 4.5 token rates', () => { expect(estimateCostUsd('grok-4.5', { input_tokens: 100_000, output_tokens: 10_000, })).toBeCloseTo(0.26, 9); + expect(estimateCostUsd('grok-4.5', { + input_tokens: 100_000, + cached_input_tokens: 40_000, + output_tokens: 10_000, + })).toBeCloseTo(0.192, 9); expect(estimateCostUsd('grok-4.5', { input_tokens: 200_000, output_tokens: 10_000, @@ -69,6 +75,7 @@ describe('estimateCostUsd', () => { output_tokens: 10_000, })).toBeCloseTo(0.58, 9); }); + // harn:end grok-price-table-distinguishes-cache-and-context-tier it('retains the retired Gemini Pro spelling only as stored-history compatibility', () => { const usage = { input_tokens: 10_000, output_tokens: 1_000 }; diff --git a/packages/switchboard/src/pricing.ts b/packages/switchboard/src/pricing.ts index 86c7d6fa..5bd7aa43 100644 --- a/packages/switchboard/src/pricing.ts +++ b/packages/switchboard/src/pricing.ts @@ -82,8 +82,9 @@ export const MODEL_PRICES: Readonly> = { 'gemini-2.5-flash': { standard: { inputPerMTok: 0.3, outputPerMTok: 2.5 }, }, + // harn:assume grok-price-table-distinguishes-cache-and-context-tier ref=grok-price-table 'grok-4.5': { - standard: { inputPerMTok: 2, outputPerMTok: 6 }, + standard: { inputPerMTok: 2, cachedInputPerMTok: 0.3, outputPerMTok: 6 }, longContext: { aboveInputTokens: 200_000, inclusive: true, @@ -92,6 +93,7 @@ export const MODEL_PRICES: Readonly> = { outputPerMTok: 12, }, }, + // harn:end grok-price-table-distinguishes-cache-and-context-tier }; const MODEL_ALIASES: Readonly> = {