Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
52ea6f6
feat(grok): add fail-closed stream compatibility
CompleteDotTech Jul 26, 2026
7fb62a3
ci(grok): require signed registry trust material
CompleteDotTech Jul 26, 2026
9b63024
fix(grok): resolve compatibility module in Node tests
CompleteDotTech Jul 26, 2026
14d081e
fix(grok): anchor cached schema revisions
CompleteDotTech Jul 26, 2026
62df71f
test(grok): cover compatibility route wiring
CompleteDotTech Jul 26, 2026
57b36cc
fix(grok): harden compatibility routing
CompleteDotTech Jul 26, 2026
d18c0d9
fix(grok): quarantine malformed compatibility frames
CompleteDotTech Jul 26, 2026
1e1136b
fix(grok): preserve fail-closed fallback integrity
CompleteDotTech Jul 26, 2026
36acd29
fix(grok): scrub proxy credentials from probes
CompleteDotTech Jul 26, 2026
6bdd2fe
fix(grok): preserve trusted registry cache invariants
CompleteDotTech Jul 26, 2026
48afb1d
fix(grok): redact fallback protocol failures
CompleteDotTech Jul 26, 2026
1d5b88b
fix(grok): bind tool schemas to verified revisions
CompleteDotTech Jul 26, 2026
f3f28e2
test(grok): cover structured fallback route wiring
CompleteDotTech Jul 26, 2026
64bf2d8
fix(grok): preserve safe plain fallback turns
CompleteDotTech Jul 26, 2026
e45d311
test(grok): update shared routing contracts
CompleteDotTech Jul 26, 2026
2b962aa
test(grok): persist shared compatibility diagnostics
CompleteDotTech Jul 26, 2026
7c33223
fix(grok): fail closed after schema expiry
CompleteDotTech Jul 26, 2026
244d290
fix(grok): bound registry refresh trust
CompleteDotTech Jul 26, 2026
463f894
fix(grok): preserve reordered combined tool completion
CompleteDotTech Jul 26, 2026
605596c
test(grok): cover preflight-backed capability probing
CompleteDotTech Jul 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,26 @@ jobs:
echo "NEXT_PUBLIC_COVEN_OPENCODE_SCHEMA_REGISTRY_CHECKPOINT=$NEXT_PUBLIC_COVEN_OPENCODE_SCHEMA_REGISTRY_CHECKPOINT"
} >> "$GITHUB_ENV"

- name: Require signed Grok compatibility registry
shell: bash
env:
NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_URL: ${{ secrets.GROK_SCHEMA_REGISTRY_URL }}
NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEY: ${{ secrets.GROK_SCHEMA_REGISTRY_PUBLIC_KEY }}
NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEYS: ${{ secrets.GROK_SCHEMA_REGISTRY_PUBLIC_KEYS }}
NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_CHECKPOINT: ${{ secrets.GROK_SCHEMA_REGISTRY_CHECKPOINT }}
run: |
node scripts/check-grok-registry-release.mjs
{
echo "NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_URL=$NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_URL"
echo "NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEY<<COVEN_GROK_KEY_EOF"
printf '%s\n' "$NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEY"
echo "COVEN_GROK_KEY_EOF"
echo "NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEYS<<COVEN_GROK_KEYS_EOF"
printf '%s\n' "$NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEYS"
echo "COVEN_GROK_KEYS_EOF"
echo "NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_CHECKPOINT=$NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_CHECKPOINT"
} >> "$GITHUB_ENV"

# Decode the App Store Connect API key onto the runner for the custom
# macOS release script. The path is exported into the env for later
# steps. Runs only on the macOS leg.
Expand Down
15 changes: 15 additions & 0 deletions docs/grok-compatibility-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Grok Build compatibility registry

Grok Build's built-in profile is limited to the xAI-documented `text`, `thought`, `end`, and `error` `streaming-json` frames. It contains no tool-event aliases. A tool schema is enabled only when the exact locally resolved launcher advertises the value-bearing `--output-format streaming-json` option and a selected Ed25519-signed bundle explicitly names every envelope field and lifecycle event **and pins those aliases to exact locally probed Grok Build versions**. Help/version probes never receive credential-bearing environment variables and run no model request.

Release configuration is public verification material, never a signing key:

- `GROK_SCHEMA_REGISTRY_URL` — canonical credential-free HTTPS bundle URL.
- `GROK_SCHEMA_REGISTRY_PUBLIC_KEY`, or `GROK_SCHEMA_REGISTRY_PUBLIC_KEYS` — PEM Ed25519 trust anchor(s), with one to four key IDs for rotation. A bundle signed against a multi-key keyring must carry its exact `keyId`.
- `GROK_SCHEMA_REGISTRY_CHECKPOINT` — JSON `{ "sequence": number, "payloadHash": "<lowercase sha256>" }` that anchors first use and rollback resistance.

The release maps these to `NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_*`; production reads only those packaged anchors. Development may use `COVEN_GROK_SCHEMA_REGISTRY_*`, which production deliberately ignores. Registry downloads reject redirects, credentials, oversized or stalled bodies, malformed bundles, invalid signatures, checkpoint regressions, and cache-anchor rollbacks. The per-user cache is bounded, atomically replaced under a short writer lock, and keeps a bounded immutable high-water journal so a resumed stale writer cannot lower the accepted sequence. It is always reverified; an unknown or malformed selected event quarantines that schema in-process and future turns fall back to plain text. If a newer remote contract was previously accepted, its cache expires, or its anchor is missing/corrupt, Cave does not revive the older compiled parser; it waits in plain chat for a verified refresh. The compiled baseline also expires rather than parsing future output indefinitely. Do not publish a Grok tool schema until its precise stdout envelope is source-verified and captured from an approved non-production fixture. Never store a private key in this repository, app configuration, or release secrets.

## Evidence

Verified on 2026-07-26 from xAI's [Grok Build overview](https://docs.x.ai/build/overview), which documents headless `grok -p ... --output-format streaming-json`, and the upstream [headless-mode source documentation](https://github.com/xai-org/grok-build/blob/47348d13ec4508dcfe440e34c6d511bb02998fb2/crates/codegen/xai-grok-pager/docs/user-guide/14-headless-mode.md) at Grok Build revision [`47348d13ec4508dcfe440e34c6d511bb02998fb2`](https://github.com/xai-org/grok-build/tree/47348d13ec4508dcfe440e34c6d511bb02998fb2). Those sources establish the baseline text/thought/end/error transport only; they do **not** document tool lifecycle envelope names. No live Grok capture is stored or required. Future signed schemas need separately recorded source evidence for every added event and field before release owners publish them.
29 changes: 29 additions & 0 deletions scripts/check-grok-registry-release.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Public verification material is embedded in a desktop release; this guard
// makes a missing trust anchor a release failure rather than an unsafe default.
import { createPublicKey } from "node:crypto";

const url = process.env.NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_URL;
const publicKey = process.env.NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEY;
const publicKeys = process.env.NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEYS;
const checkpoint = process.env.NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_CHECKPOINT;
const fail = (message) => { console.error(`::error::${message}`); process.exitCode = 1; };
let keyring;
try { keyring = publicKeys ? JSON.parse(publicKeys) : publicKey ? { legacy: publicKey } : null; } catch { keyring = null; }
if (!url || !keyring || typeof keyring !== "object" || Array.isArray(keyring) || !checkpoint) {
fail("Grok compatibility registry URL, Ed25519 public key/keyring, and immutable sequence checkpoint must be configured for every desktop release.");
} else {
try {
const parsedUrl = new URL(url);
if (parsedUrl.protocol !== "https:" || parsedUrl.username || parsedUrl.password) throw new Error("registry URL must use HTTPS without credentials");
const entries = Object.entries(keyring);
if (!entries.length || entries.length > 4) throw new Error("registry keyring must contain one to four keys");
for (const [id, pem] of entries) {
if (!/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(id) || typeof pem !== "string") throw new Error("registry keyring has an invalid key id");
if (createPublicKey(pem).asymmetricKeyType !== "ed25519") throw new Error("registry key must be Ed25519");
}
const parsedCheckpoint = JSON.parse(checkpoint);
if (!parsedCheckpoint || typeof parsedCheckpoint !== "object" || Array.isArray(parsedCheckpoint)
|| Object.keys(parsedCheckpoint).length !== 2 || !Number.isSafeInteger(parsedCheckpoint.sequence) || parsedCheckpoint.sequence < 1
|| typeof parsedCheckpoint.payloadHash !== "string" || !/^[a-f0-9]{64}$/.test(parsedCheckpoint.payloadHash)) throw new Error("registry checkpoint must contain a sequence and SHA-256 payload hash");
} catch (error) { fail(`Invalid Grok compatibility registry configuration: ${error instanceof Error ? error.message : "unknown error"}`); }
}
14 changes: 14 additions & 0 deletions scripts/check-grok-registry-release.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import assert from "node:assert/strict";
import { generateKeyPairSync } from "node:crypto";
import { spawnSync } from "node:child_process";
import { readFile } from "node:fs/promises";

const workflow = await readFile(new URL("../.github/workflows/release.yml", import.meta.url), "utf8");
const guard = await readFile(new URL("./check-grok-registry-release.mjs", import.meta.url), "utf8");
assert.match(workflow, /Require signed Grok compatibility registry[\s\S]*?NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_URL[\s\S]*?check-grok-registry-release\.mjs/);
assert.match(guard, /registry URL must use HTTPS without credentials/);
const { publicKey } = generateKeyPairSync("ed25519");
const result = spawnSync(process.execPath, ["scripts/check-grok-registry-release.mjs"], { cwd: new URL("..", import.meta.url), encoding: "utf8", env: { ...process.env, NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_URL: "https://publisher:secret@registry.example/grok.json", NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_PUBLIC_KEY: publicKey.export({ type: "spki", format: "pem" }).toString(), NEXT_PUBLIC_COVEN_GROK_SCHEMA_REGISTRY_CHECKPOINT: JSON.stringify({ sequence: 1, payloadHash: "a".repeat(64) }) } });
assert.notEqual(result.status, 0);
assert.doesNotMatch(result.stderr, /secret/);
console.log("check-grok-registry-release.test.mjs: ok");
4 changes: 4 additions & 0 deletions scripts/run-tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const SUITES = {
"scripts/eslint/design-system-plugin.test.mjs",
"scripts/bundle-budget.test.mjs",
"scripts/check-opencode-registry-release.test.mjs",
"scripts/check-grok-registry-release.test.mjs",
"src/components/open-coven-tools-update.test.ts",
"src/lib/opencoven-tools-state.test.ts",
"src/lib/opencoven-install-job-observer.test.ts",
Expand Down Expand Up @@ -1013,6 +1014,7 @@ export const SUITES = {
"src/lib/grok-bin.test.ts",
"src/lib/grok-build.test.ts",
"src/lib/runtime-availability.test.ts",
"src/lib/grok-compatibility.test.ts",
"src/app/api/harnesses/route.test.ts",
"src/lib/copilot-stream.test.ts",
"src/lib/copilot-bin.test.ts",
Expand All @@ -1034,6 +1036,7 @@ export const SUITES = {
"src/app/api/chat/send/chat-send-models.test.ts",
"src/app/api/chat/send/chat-send-capabilities.test.ts",
"src/app/api/chat/send/route-opencode.integration.test.ts",
"src/app/api/chat/send/route-grok-compatibility.integration.test.ts",
"src/app/api/chat/send/route-runtime-availability.integration.test.ts",
"src/app/api/chat/send/offline-queue.test.ts",
"src/app/api/chat/send/first-turn-stub.test.ts",
Expand Down Expand Up @@ -1306,6 +1309,7 @@ const ALIAS_LOADER = new Set([
"src/app/api/chat/send/chat-send-models.test.ts",
"src/app/api/chat/send/chat-send-capabilities.test.ts",
"src/app/api/chat/send/route-opencode.integration.test.ts",
"src/app/api/chat/send/route-grok-compatibility.integration.test.ts",
"src/app/api/chat/send/route-runtime-availability.integration.test.ts",
"src/lib/familiar-workspace-sessions.test.ts",
"src/lib/use-projects-scope-transition.test.ts",
Expand Down
28 changes: 24 additions & 4 deletions src/app/api/chat/send/harness-routing-copilot-jsonl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ assert.match(
);
assert.match(
chatRoute,
/if \(grokDirect\) \{\s*handleGrokLine\(line, isJson\);\s*return;/,
"Grok stdout routes through the native JSONL parser, never generic stream-json parsing",
/if \(grokDirect\) \{[\s\S]*?handleGrokLine\(line, isJson \|\| [\s\S]*?line\.trimStart\(\)\)\);\s*return;/,
"Grok stdout routes through the native parser, rejecting whitespace-prefixed object and array envelopes before plain fallback persistence",
);
assert.match(
chatRoute,
Expand Down Expand Up @@ -255,6 +255,26 @@ assert.match(
/Grok Build chats currently run on this Cave host/,
"SSH Grok must fail explicitly instead of falling back to coven run",
);
assert.match(
chatRoute,
/const grokCapabilities = grokDirect[\s\S]*?probeReadyLocalRuntimeCapability\([\s\S]*?runner: "grok",[\s\S]*?probe: \(\) => probeGrokRunCapabilities\([\s\S]*?command: localRuntimePlan!\.command,[\s\S]*?fixedArgs: localRuntimePlan!\.fixedArgs[\s\S]*?localRuntimePlan!\.env[\s\S]*?const grokCompatibility = grokCapabilities[\s\S]*?resolveGrokCompatibility\(grokCapabilities\)/,
"Grok must probe the exact preflight-approved launcher and environment before selecting a structured schema",
);
assert.match(
chatRoute,
/outputFormat: grokCompatibility\?\.mode === "structured" \? "streaming-json" : null/,
"an unverified Grok client must use plain output rather than an assumed JSON protocol",
);
assert.match(
chatRoute,
/case "tool_start":[\s\S]*?envelopeToolUse[\s\S]*?consumePendingEnvelopeProgress[\s\S]*?consumePendingEnvelopeResult/,
"selected Grok schemas must reconcile reordered progress and terminal tool results through the shared tracker",
);
assert.match(
chatRoute,
/case "unknown":[\s\S]*?quarantineGrokSchema[\s\S]*?redactedGrokEventFingerprint/,
"unknown selected-schema events must quarantine future structured launches with a redacted diagnostic",
);

assert.match(
chatRoute,
Expand Down Expand Up @@ -324,8 +344,8 @@ assert.match(
);
assert.match(
chatRoute,
/\(openCodeDirect \|\| copilotStream\) && code !== 0[\s\S]*?is_error: true/,
"a nonzero direct Copilot process exit persists the turn as an error even without a final result frame",
/\(openCodeDirect \|\| copilotStream \|\| grokDirect\) && code !== 0[\s\S]*?is_error: true/,
"a nonzero direct Copilot or Grok process exit persists the turn as an error even without a final result frame",
);
assert.match(
chatRoute,
Expand Down
12 changes: 6 additions & 6 deletions src/app/api/chat/send/harness-routing-opencode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ assert.match(
);
assert.match(
route,
/child\.on\("close", \(code\) => \{[\s\S]*?if \(\(openCodeDirect \|\| copilotStream\) && code !== 0\)[\s\S]*?is_error: true/,
"a non-zero direct OpenCode or Copilot exit cannot be treated as a successful run when no JSON error arrives",
/child\.on\("close", \(code\) => \{[\s\S]*?if \(\(openCodeDirect \|\| copilotStream \|\| grokDirect\) && code !== 0\)[\s\S]*?is_error: true/,
"a non-zero direct OpenCode, Copilot, or Grok exit cannot be treated as a successful run when no JSON error arrives",
);
assert.match(
route,
Expand All @@ -179,8 +179,8 @@ assert.match(
);
assert.match(
route,
/const tailBlock = !openCodeDirect && tailSource\.length/,
"OpenCode stderr never becomes assistant-visible or persisted empty-response diagnostics",
/const tailBlock = !openCodeDirect && !grokDirect && tailSource\.length/,
"OpenCode and Grok stderr never become assistant-visible or persisted empty-response diagnostics",
);
assert.match(
route,
Expand Down Expand Up @@ -219,8 +219,8 @@ assert.match(
);
assert.match(
route,
/persistedOpenCodeDiagnostics[\s\S]*?id === "opencode-compatibility"[\s\S]*?progress: persistedOpenCodeDiagnostics/,
"safe OpenCode compatibility diagnostics persist with the completed assistant turn",
/persistedCompatibilityDiagnostics[\s\S]*?id === "opencode-compatibility" \|\| id === "grok-compatibility"[\s\S]*?progress: persistedCompatibilityDiagnostics/,
"safe OpenCode and Grok compatibility diagnostics persist with the completed assistant turn",
);
assert.match(
route,
Expand Down
Loading
Loading