mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* [comment audit] Align legacy failure-read counts with cached display * [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci] * [comment audit] Condense shared generation, path and preview comments [skip ci] * [comment audit] Condense host, file and Git contract rationale [skip ci] * [comment audit] Tighten Git lifecycle and provider contract comments [skip ci] * [comment audit] Clarify shared host authority and launch isolation [skip ci] * [comment audit] simplify local access mobile and native chat contracts [skip ci] * [comment audit] simplify native chat presentation and state contracts [skip ci] * [comment audit] simplify native runtime and provider contracts [skip ci] * [comment audit] simplify migration and orchestration contracts [skip ci] * [comment audit] simplify plugin trust and process capture rationale [skip ci] * [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci] * [comment audit] clarify remote recovery and launch wire rationale [skip ci] * [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci] * [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci] * [comment audit] clarify sidebar, skill and SSH invariants [skip ci] * [comment audit] condense structured chat delivery and timing rationale [skip ci] * [comment audit] condense terminal replay and telemetry invariants [skip ci] * [comment audit] clarify launch permission and terminal delivery rationale [skip ci] * [comment audit] clarify workspace ownership and layout contracts [skip ci] * [comment audit] wait for fitted and painted SSH terminal links * [comment audit] clarify layout persistence and session ownership [skip ci] * [comment audit] clarify host routing and workspace lifecycle contracts [skip ci] * [comment audit] clarify startup persistence and app lifetime comments [skip ci] * [comment audit] clarify recovery and activity boundaries [skip ci] * [comment audit] condense activity and artifact invariants [skip ci] * [comment audit] condense automation authority contracts [skip ci] * [comment audit] simplify automation scheduling and history contracts [skip ci] * [comment audit] clarify browser chrome and annotation lifecycles [skip ci] * [comment audit] clarify browser routing and retained guest ownership [skip ci] * [comment audit] clarify browser recovery and document ownership comments [skip ci] * [comment audit] restore hosted runners required by native IME fixtures * [comment audit] shorten preview, palette and dashboard invariants [skip ci] * [comment audit] clarify terminal preview, dashboard and retention comments [skip ci] * [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci] * [comment audit] shorten rich-text and combined-diff ownership comments [skip ci] * [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci] * [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci] * [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci] * [comment audit] clarify rich Markdown source and selection safeguards [skip ci] * [comment audit] clarify editor loading and emulator stream lifetimes [skip ci] * [comment audit] trim emulator and feature tour narration [skip ci] * [comment audit] clarify floating workspace and GitHub cache ownership [skip ci] * [comment audit] trim provider controls and Linear filter explanations [skip ci] * [comment audit] clarify pairing and chat resume lifetimes [skip ci] * [comment audit] clarify chat delivery and transcript lifetimes [skip ci] * [comment audit] clarify session options and host-owned status [skip ci] * [comment audit] clarify draft durability and live-status precedence [skip ci] * [comment audit] Trim chat send and model ownership explanations [skip ci] * [comment audit] Shorten transcript ownership and send recovery comments [skip ci] * [comment audit] Simplify native chat composer and transcript hook comments [skip ci] * [comment audit] Shorten native chat scroll and structured session explanations [skip ci] * [comment audit] Trim workspace picker and onboarding commentary [skip ci] * [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci] * [comment audit] Trim sidebar and session history explanations [skip ci] * [comment audit] Condense session resume and review checks comments [skip ci] * [comment audit] Shorten checks state and file explorer refresh comments [skip ci] * [comment audit] Trim plugin bridge and source-control action rationale [skip ci] * [comment audit] Trim source-control listing and review rationale [skip ci] * [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci] * [comment audit] Condense Settings account and appearance rationale [skip ci] * [comment audit] Trim pairing and repository Settings explanations [skip ci] * [comment audit] Condense Settings search SSH and terminal safeguards [skip ci] * [comment audit] Shorten Settings voice and provider ownership comments [skip ci] * [comment audit] Condense Settings draft persistence and host-state rationale [skip ci] * [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci] * [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci] * [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci] * [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci] * [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci] * [comment audit] Prune focus-read baseline after comment cleanup * [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci] * [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci] * [comment audit] Clarify viewport geometry and skill update safeguards [skip ci] * [comment audit] Tighten skill inventory, usage and host status comments [skip ci] * [comment audit] Clarify usage ownership and resource kill safeguards [skip ci] * [comment audit] Tighten tab selection, rename and search contracts [skip ci] * [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci] * [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci] * [comment audit] Tighten provider loading and terminal recovery comments [skip ci] * [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci] * [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci] * [comment audit] Tighten PTY startup and replay explanations [skip ci] * [comment audit] Clarify PTY identity and delivery ownership [skip ci] * [comment audit] Shorten remote reconnect and input replay rationale [skip ci] * [comment audit] Condense terminal parking clipboard and file link comments [skip ci] * [comment audit] Clarify IME ownership and trim trace history [skip ci] * [comment audit] Shorten pane recovery and parked watcher explanations [skip ci] * [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci] * [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci] * [comment audit] Trim renderer lifecycle and menu rationale [skip ci] * [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci] * [comment audit] Clarify renderer hook evidence and source ownership [skip ci] * [comment audit] Tighten IPC ownership and reconnect explanations [skip ci] * [comment audit] Trim renderer focus, paste and attention comments [skip ci] * [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci] * [comment audit] Trim agent launch, resume and ownership commentary [skip ci] * [comment audit] Shorten account, browser and editor comments [skip ci] * [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci] * [comment audit] Condense launch recovery and editor tokenization comments [skip ci] * [comment audit] Condense workspace selection and terminal pane rationale [skip ci] * [comment audit] Condense terminal fitting and rendering recovery comments [skip ci] * [comment audit] Condense terminal input and host routing explanations [skip ci] * [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci] * [comment audit] Condense structured launch and terminal link explanations [skip ci] * [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci] * [comment audit] Condense session persistence and workspace activation rationale [skip ci] * [comment audit] Condense workspace routing and browser ownership rationale [skip ci] * [comment audit] Condense mirror recovery and layout authority rationale [skip ci] * [comment audit] Condense runtime transport and file safety rationale [skip ci] * [comment audit] Condense runtime graph and structured session rationale [skip ci] * [comment audit] Condense browser creation and remote tab recovery rationale [skip ci] * [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci] * [comment audit] Condense frame, startup and provider cache rationale [skip ci] * [comment audit] Condense host twin and agent lifecycle rationale [skip ci] * [comment audit] Condense browser authority and editor cleanup rationale [skip ci] * [comment audit] Condense editor buffer identity and review cache rationale [skip ci] * [comment audit] Condense provider lifetime and repository ownership rationale [skip ci] * [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci] * [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci] * [comment audit] Condense cleanup ownership and listing admission rationale [skip ci] * [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci] * [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci] * [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci] * [comment audit] Condense cloud rollout and assignment safety rationale [skip ci] * [comment audit] Condense relay session and database recovery rationale [skip ci] * [comment audit] Condense relay placement and protocol compatibility rationale [skip ci] * [comment audit] Condense build and packaging constraints [skip ci] * [comment audit] Condense CI gates and validation isolation rationale [skip ci] * [comment audit] Condense catalog identity and browser validation rationale [skip ci] * [comment audit] Condense mobile browser and frame-budget rationale [skip ci] * [comment audit] Shorten mobile route and browser test explanations [skip ci] * [comment audit] Condense editor and session seam test rationale [skip ci] * [comment audit] Trim session and terminal closure history [skip ci] * [comment audit] Shorten terminal browser and census explanations [skip ci] * [comment audit] Preserve root-only E2E diagnostic baseline * [comment audit] Condense mobile seam and HTML preview test rationale [skip ci] * [comment audit] Condense preview instrumentation and render harness rationale [skip ci] * [comment audit] Condense bundle contracts and native-build constraints [skip ci] * [comment audit] Shorten native packaging and CI routing rationale [skip ci] * [comment audit] Condense release tooling and benchmark constraints [skip ci] * [comment audit] Condense packaging and platform gate rationale [skip ci] * [comment audit] Shorten mobile route and history rationale [skip ci] * [comment audit] Condense mobile browser and component rationale [skip ci] * [comment audit] Shorten drawer and workspace-form rationale [skip ci] * [comment audit] Trim mobile review and editor-document explanations [skip ci] * [comment audit] Condense editor lifecycle and Markdown explanations [skip ci] * [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci] * [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci] * [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci] * [comment audit] Condense bridge stream, native request and audio rationale [skip ci] * [comment audit] Reduce bridge schema and page-client commentary [skip ci] * [comment audit] Tighten mobile page bridge contracts [skip ci] * [comment audit] Clarify mobile cache and session constraints [skip ci] * [comment audit] Condense mobile route and lifecycle rationale [skip ci] * [comment audit] Tighten mobile shell hook timing rationale [skip ci] * [comment audit] Clarify route handoff and notification compatibility [skip ci] * [comment audit] Clarify push registration and dictation cleanup constraints [skip ci] * [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci] * [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci] * [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci] * [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci] * [comment audit] Tighten stream replay and queued session ownership rationale [skip ci] * [comment audit] Condense mobile composer and reply-schema constraints [skip ci] * [comment audit] Clarify mobile transcript recovery and session ownership [skip ci] * [comment audit] Clarify mobile queue and source-control compatibility [skip ci] * [comment audit] Condense storage and task launch constraints [skip ci] * [comment audit] Clarify task schemas and terminal document ownership [skip ci] * [comment audit] Tighten terminal input and viewport rationale [skip ci] * [comment audit] Shorten terminal and RPC recorder contracts [skip ci] * [comment audit] Condense recorder engine and connection contracts [skip ci] * [comment audit] Shorten host credential and relay recovery comments [skip ci] * [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci] * [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci] * [comment audit] Condense catalog and relay entry contracts [skip ci] * [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci] * [comment audit] Condense WSL relay and E2E regression rationale [skip ci] * [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci] * [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci] * [comment audit] Shorten layout oracle and regression explanations [skip ci] * [comment audit] Condense paired recovery and ownership regression contracts [skip ci] * [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci] * [comment audit] Condense terminal rendering and IME regression rationale [skip ci] * [comment audit] Shorten terminal recovery and benchmark contracts [skip ci] * [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci] * [comment audit] Condense workflow routing and release rationale [skip ci] * [comment audit] Condense mobile security and installer contracts [skip ci] * [comment audit] Shorten native rationale and stylesheet workarounds [skip ci] * [comment audit] Shorten Windows visibility probe rationale [skip ci] * [comment audit] Condense embedded release and signing rationale [skip ci] * [comment audit] Retain current main test behavior in final reconciliation [skip ci] * [comment audit] Complete final comment review across current source inventory * [comment audit] Handle consolidated audit reports before final CI * [comment audit] Preserve credential storage census detail [skip ci] * [comment audit] Scale broad changed E2E selections with verified timing shards * [comment audit] Align E2E assertions with browser close and settled session metadata * [comment audit] Match existing E2Es to current resource and windowing contracts * [comment audit] Observe the shared server outage notice in reconnect coverage * [comment audit] Make existing renderer probes measure settled tab and PTY work * [comment audit] Align palette E2E selector with task search * [comment audit] Refresh remaining palette search selectors * [comment audit] Keep selector correction within selected audit specs * [comment audit] Await emoji fixture settings inside renderer evaluation * [comment audit] Cancel fixture fit retries between terminal tests --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
449 lines
14 KiB
JavaScript
449 lines
14 KiB
JavaScript
#!/usr/bin/env node
|
|
// Compare stdout/stderr/exit byte-for-byte across two real CLI emits: current tree and selected files restored from baseline (default HEAD~).
|
|
// Full-tree compilation stays outside fast Vitest invariants (runtime-client-deferral.test.ts).
|
|
// Usage: node config/scripts/cli-runtime-client-deferral-equivalence.mjs [--baseline <rev>]
|
|
import { execFileSync, spawnSync } from 'node:child_process'
|
|
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync, readFileSync } from 'node:fs'
|
|
import { join, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const REPO = fileURLToPath(new URL('../..', import.meta.url))
|
|
|
|
// Restore only these baseline files; remove baseline-absent files (e.g. cli-error.ts), then restore the working tree.
|
|
const TOUCHED = [
|
|
'src/cli/args.ts',
|
|
'src/cli/cli-error.ts',
|
|
'src/cli/dispatch.ts',
|
|
'src/cli/flags.ts',
|
|
'src/cli/format.ts',
|
|
'src/cli/index.ts',
|
|
'src/cli/runtime/client.ts',
|
|
'src/cli/selectors.ts'
|
|
]
|
|
|
|
// Fail if real deferral call shapes change or baseline/current could become identical and pass vacuously.
|
|
function assertMarkersFresh() {
|
|
const index = readFileSync(join(REPO, 'src/cli/index.ts'), 'utf8')
|
|
const client = readFileSync(join(REPO, 'src/cli/runtime/client.ts'), 'utf8')
|
|
const checks = [
|
|
['src/cli/index.ts', index, 'await loadRuntimeClientClass()'],
|
|
['src/cli/index.ts', index, "await import('./runtime-client.js')"],
|
|
['src/cli/index.ts', index, "import type { RuntimeClient } from './runtime-client'"],
|
|
['src/cli/runtime/client.ts', client, 'await loadSendWebSocketRequest()'],
|
|
['src/cli/runtime/client.ts', client, "await import('./websocket-transport.js')"]
|
|
]
|
|
for (const [file, source, marker] of checks) {
|
|
// Match calls so prose mentions cannot satisfy the guard.
|
|
if (!source.includes(marker)) {
|
|
throw new Error(
|
|
`${file} no longer contains \`${marker}\` — cli-runtime-client-deferral-equivalence.mjs is stale`
|
|
)
|
|
}
|
|
}
|
|
const repointed = ['src/cli/args.ts', 'src/cli/flags.ts', 'src/cli/dispatch.ts']
|
|
for (const file of repointed) {
|
|
const source = readFileSync(join(REPO, file), 'utf8')
|
|
if (!source.includes("import { RuntimeClientError } from './runtime/types'")) {
|
|
throw new Error(`${file} no longer repoints RuntimeClientError at ./runtime/types — stale`)
|
|
}
|
|
}
|
|
}
|
|
|
|
function buildTree(label, baselineRev) {
|
|
// Build inside repo so emitted modules resolve its node_modules.
|
|
const outDir = join(REPO, `.equiv-out-${label}`)
|
|
rmSync(outDir, { recursive: true, force: true })
|
|
const restored = []
|
|
try {
|
|
if (baselineRev) {
|
|
for (const file of TOUCHED) {
|
|
const path = join(REPO, file)
|
|
restored.push([path, existsSync(path) ? readFileSync(path) : null])
|
|
const old = spawnSync('git', ['show', `${baselineRev}:${file}`], {
|
|
cwd: REPO,
|
|
maxBuffer: 64 * 1024 * 1024
|
|
})
|
|
if (old.status === 0) {
|
|
writeFileSync(path, old.stdout)
|
|
} else {
|
|
rmSync(path, { force: true })
|
|
}
|
|
}
|
|
}
|
|
execFileSync(
|
|
'npx',
|
|
[
|
|
'tsc',
|
|
'-p',
|
|
'config/tsconfig.cli.json',
|
|
'--outDir',
|
|
outDir,
|
|
'--composite',
|
|
'false',
|
|
'--incremental',
|
|
'false'
|
|
],
|
|
{ cwd: REPO, stdio: 'inherit' }
|
|
)
|
|
} finally {
|
|
for (const [path, contents] of restored) {
|
|
if (contents === null) {
|
|
rmSync(path, { force: true })
|
|
} else {
|
|
writeFileSync(path, contents)
|
|
}
|
|
}
|
|
}
|
|
return join(outDir, 'cli/index.js')
|
|
}
|
|
|
|
// Every case must exit itself; bound spawnSync so reaching a blocking command fails instead of stalling forever.
|
|
const RUN_TIMEOUT_MS = 30_000
|
|
|
|
function run(entry, argv, env) {
|
|
const result = spawnSync(process.execPath, [entry, ...argv], {
|
|
cwd: REPO,
|
|
env: { ...process.env, ...env },
|
|
encoding: 'buffer',
|
|
timeout: RUN_TIMEOUT_MS,
|
|
killSignal: 'SIGKILL'
|
|
})
|
|
if (result.error) {
|
|
if (result.error.code === 'ETIMEDOUT') {
|
|
throw new Error(
|
|
`orca ${argv.join(' ')} did not exit within ${RUN_TIMEOUT_MS} ms — it reached a blocking command`
|
|
)
|
|
}
|
|
throw result.error
|
|
}
|
|
return {
|
|
status: result.status,
|
|
stdout: result.stdout.toString('utf8'),
|
|
stderr: result.stderr.toString('utf8')
|
|
}
|
|
}
|
|
|
|
// Require identical bytes; empty user-data dirs make runtime-dependent cases deterministically report not running.
|
|
function buildCases(isolatedUserData) {
|
|
const isolated = { ORCA_USER_DATA_PATH: isolatedUserData }
|
|
const cases = [
|
|
// Paths that must never load the runtime client at all.
|
|
[[], {}],
|
|
[['--help'], {}],
|
|
[['help'], {}],
|
|
[['help', 'worktree'], {}],
|
|
[['help', 'browser'], {}],
|
|
[['worktree', '--help'], {}],
|
|
[['help', 'no-such-command'], {}],
|
|
[['no-such-command'], {}],
|
|
[['no-such-command'], { ORCA_PAIRING_CODE: 'garbage' }],
|
|
[['wrktree', 'list'], {}],
|
|
[['agent-context'], {}],
|
|
[['agent-context', '--json'], {}],
|
|
// Flag validation must still fire before any runtime lookup.
|
|
[['worktree', 'list', '--nonexistent-flag'], {}],
|
|
[['worktree', 'list', '--nonexistent-flag', '--json'], {}],
|
|
[['browser', 'snapshot', '--nonexistent-flag'], {}],
|
|
// resolveRemotePairing throws from the RuntimeClient CONSTRUCTOR.
|
|
[['status', '--pairing-code', 'x', '--environment', 'y'], isolated],
|
|
[['status', '--pairing-code', 'x', '--environment', 'y', '--json'], isolated],
|
|
[['status', '--pairing-code', 'not-a-pairing-code'], isolated],
|
|
[['status', '--pairing-code', 'not-a-pairing-code', '--json'], isolated],
|
|
[['status', '--pairing-code', 'orca://pair?code=zzzz'], isolated],
|
|
[['status', '--environment', 'no-such-environment'], isolated],
|
|
[['status', '--environment', 'no-such-environment', '--json'], isolated],
|
|
[['worktree', 'list', '--environment', 'no-such-environment', '--json'], isolated],
|
|
// The env-var fallback must stay live for non-suppressed commands...
|
|
[['status', '--json'], { ...isolated, ORCA_PAIRING_CODE: 'not-a-pairing-code' }],
|
|
[['status', '--json'], { ...isolated, ORCA_REMOTE_PAIRING: 'not-a-pairing-code' }],
|
|
[['status', '--json'], { ...isolated, ORCA_ENVIRONMENT: 'no-such-environment' }],
|
|
// Keep suppression for local-only groups. Do not invoke agent hooks on/off: they modify real ~/.claude configuration outside ORCA_USER_DATA_PATH.
|
|
// Constructor-argument assertions in runtime-client-deferral.test.ts cover null/undefined suppression without those side effects.
|
|
[['environment', 'list', '--json'], { ...isolated, ORCA_ENVIRONMENT: 'no-such-environment' }],
|
|
[['environment', 'list', '--json'], { ...isolated, ORCA_PAIRING_CODE: 'not-a-pairing-code' }],
|
|
[['agent-context', '--json'], { ...isolated, ORCA_PAIRING_CODE: 'not-a-pairing-code' }],
|
|
// Runtime-unavailable reporting (RuntimeClientError formatting).
|
|
[['status'], isolated],
|
|
[['status', '--json'], isolated],
|
|
[['worktree', 'list', '--json'], isolated],
|
|
[['terminal', 'list', '--json'], isolated]
|
|
]
|
|
// Seed fuzzing for reproducibility; all pool tokens must be safe to execute, checked against UNSAFE_TOKENS before running any case.
|
|
const tokens = [
|
|
'worktree',
|
|
'list',
|
|
'status',
|
|
'browser',
|
|
'snapshot',
|
|
'terminal',
|
|
'environment',
|
|
'agent',
|
|
'agent-context',
|
|
'vm',
|
|
'--json',
|
|
'--help',
|
|
'--pairing-code',
|
|
'--environment',
|
|
'--worktree',
|
|
'orca://pair?code=!!!',
|
|
'',
|
|
'-',
|
|
'--',
|
|
'--=',
|
|
'a'.repeat(300),
|
|
'näme-ünicode',
|
|
'💥',
|
|
'../..',
|
|
'x\ty'
|
|
]
|
|
let seed = 0x9e3779b9
|
|
const next = () => {
|
|
seed ^= seed << 13
|
|
seed ^= seed >>> 17
|
|
seed ^= seed << 5
|
|
return (seed >>> 0) / 0x100000000
|
|
}
|
|
// Check entire pool, not just sampled cases: serve once survived review here and escaped the per-case scan by chance.
|
|
assertTokensSafe(tokens, 'fuzz token pool')
|
|
assertFuzzPoolDeclaredReadOnly(tokens)
|
|
for (let index = 0; index < 400; index += 1) {
|
|
const length = 1 + Math.floor(next() * 4)
|
|
const argv = []
|
|
for (let part = 0; part < length; part += 1) {
|
|
argv.push(tokens[Math.floor(next() * tokens.length)])
|
|
}
|
|
cases.push([argv, isolated])
|
|
}
|
|
for (const [argv] of cases) {
|
|
assertTokensSafe(argv, `argv ${JSON.stringify(argv)}`)
|
|
}
|
|
return cases
|
|
}
|
|
|
|
// Cases run real CLI with developer HOME/PATH: deny foreground commands, surviving children, config/registry writes and real UI input.
|
|
// Deny mixed read/write groups (capture/intercept/label/relation) wholesale; fuzzing cannot distinguish their subcommands.
|
|
const FOREGROUND_TOKENS = ['serve', 'open', 'claude-teams', 'exec', 'eval', 'launch', 'attach']
|
|
const MUTATING_TOKENS = [
|
|
// persistent config and registry state
|
|
'on',
|
|
'off',
|
|
'hooks',
|
|
'create',
|
|
'remove',
|
|
'rm',
|
|
'delete',
|
|
'add',
|
|
'edit',
|
|
'set',
|
|
'set-value',
|
|
'set-base-ref',
|
|
'setup-clone',
|
|
'setup-create',
|
|
'setup-update',
|
|
'setup-delete',
|
|
'setup-existing-folder',
|
|
'install',
|
|
'uninstall',
|
|
'reinstall',
|
|
'update',
|
|
'clone',
|
|
'apply',
|
|
'write',
|
|
'reset',
|
|
'clear',
|
|
'enable',
|
|
'disable',
|
|
'use-default',
|
|
'permissions',
|
|
// process and pane lifecycle
|
|
'run',
|
|
'run-stop',
|
|
'start',
|
|
'stop',
|
|
'kill',
|
|
'shutdown',
|
|
'close',
|
|
'split',
|
|
'switch',
|
|
'rename',
|
|
'focus',
|
|
// orchestration writes
|
|
'send',
|
|
'reply',
|
|
'dispatch',
|
|
'task-create',
|
|
'task-update',
|
|
'gate-create',
|
|
'gate-resolve',
|
|
// issue-tracker writes
|
|
'save-issue',
|
|
'comment',
|
|
'label',
|
|
'relation',
|
|
'assignee',
|
|
'priority',
|
|
'estimate',
|
|
'due-date',
|
|
// browser / computer / emulator input and navigation
|
|
'goto',
|
|
'back',
|
|
'forward',
|
|
'reload',
|
|
'click',
|
|
'dblclick',
|
|
'hover',
|
|
'fill',
|
|
'type',
|
|
'type-text',
|
|
'select',
|
|
'select-all',
|
|
'uncheck',
|
|
'keypress',
|
|
'press-key',
|
|
'inserttext',
|
|
'hotkey',
|
|
'paste-text',
|
|
'perform-secondary-action',
|
|
'drag',
|
|
'scroll',
|
|
'scrollintoview',
|
|
'wheel',
|
|
'move',
|
|
'up',
|
|
'down',
|
|
'tap',
|
|
'gesture',
|
|
'button',
|
|
'rotate',
|
|
'upload',
|
|
'download',
|
|
'dismiss',
|
|
'accept',
|
|
'highlight',
|
|
'viewport',
|
|
'geolocation',
|
|
'headers',
|
|
'credentials',
|
|
'offline',
|
|
'media',
|
|
'device',
|
|
'capture',
|
|
'intercept'
|
|
]
|
|
|
|
const UNSAFE_TOKENS = new Map([
|
|
...FOREGROUND_TOKENS.map((token) => [token, 'runs in the foreground or spawns a process']),
|
|
...MUTATING_TOKENS.map((token) => [token, 'can write outside ORCA_USER_DATA_PATH'])
|
|
])
|
|
|
|
// Deny lists miss new verbs (serve once escaped); require explicit read-only allowlisting for every pool token too.
|
|
const READ_ONLY_FUZZ_TOKENS = new Set([
|
|
// command tokens: every path they can form is a list/show or a parse error
|
|
'agent',
|
|
'agent-context',
|
|
'browser',
|
|
'environment',
|
|
'list',
|
|
'snapshot',
|
|
'status',
|
|
'terminal',
|
|
'vm',
|
|
'worktree',
|
|
// global flags and hostile strings, which reach no handler at all
|
|
'--json',
|
|
'--help',
|
|
'--pairing-code',
|
|
'--environment',
|
|
'--worktree',
|
|
'orca://pair?code=!!!',
|
|
'',
|
|
'-',
|
|
'--',
|
|
'--=',
|
|
'a'.repeat(300),
|
|
'näme-ünicode',
|
|
'💥',
|
|
'../..',
|
|
'x\ty'
|
|
])
|
|
|
|
function assertTokensSafe(tokens, context) {
|
|
for (const token of tokens) {
|
|
const reason = UNSAFE_TOKENS.get(token)
|
|
if (reason) {
|
|
throw new Error(`Refusing to run ${context}: "${token}" ${reason}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reject neither/both-list membership before any case runs; sampling cannot prove pool safety.
|
|
function assertFuzzPoolDeclaredReadOnly(tokens) {
|
|
for (const token of tokens) {
|
|
if (!READ_ONLY_FUZZ_TOKENS.has(token)) {
|
|
throw new Error(
|
|
`Fuzz token ${JSON.stringify(token)} is not declared in READ_ONLY_FUZZ_TOKENS — declare it read-only or drop it`
|
|
)
|
|
}
|
|
}
|
|
for (const token of READ_ONLY_FUZZ_TOKENS) {
|
|
if (UNSAFE_TOKENS.has(token)) {
|
|
throw new Error(
|
|
`Token ${JSON.stringify(token)} is declared both read-only and unsafe — the two lists disagree`
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
const baselineIndex = process.argv.indexOf('--baseline')
|
|
const baselineRev = baselineIndex === -1 ? 'HEAD' : process.argv[baselineIndex + 1]
|
|
|
|
assertMarkersFresh()
|
|
|
|
const isolatedUserData = mkdtempSync(join(REPO, '.equiv-userdata-'))
|
|
mkdirSync(join(isolatedUserData, 'empty'), { recursive: true })
|
|
|
|
let oldEntry
|
|
let newEntry
|
|
try {
|
|
// Build cases and check safety before expensive tsc compiles so unsafe tokens fail immediately.
|
|
const cases = buildCases(isolatedUserData)
|
|
|
|
console.log(`Building baseline (${baselineRev}) …`)
|
|
oldEntry = buildTree('old', baselineRev)
|
|
console.log('Building working tree …')
|
|
newEntry = buildTree('new', null)
|
|
|
|
console.log(`Comparing ${cases.length} invocations byte for byte …`)
|
|
let mismatches = 0
|
|
for (const [argv, env] of cases) {
|
|
const before = run(oldEntry, argv, env)
|
|
const after = run(newEntry, argv, env)
|
|
if (
|
|
before.status !== after.status ||
|
|
before.stdout !== after.stdout ||
|
|
before.stderr !== after.stderr
|
|
) {
|
|
mismatches += 1
|
|
console.error(`\nMISMATCH argv=${JSON.stringify(argv)} env=${JSON.stringify(env)}`)
|
|
console.error(` exit before=${before.status} after=${after.status}`)
|
|
if (before.stdout !== after.stdout) {
|
|
console.error(` stdout before=${JSON.stringify(before.stdout.slice(0, 400))}`)
|
|
console.error(` after =${JSON.stringify(after.stdout.slice(0, 400))}`)
|
|
}
|
|
if (before.stderr !== after.stderr) {
|
|
console.error(` stderr before=${JSON.stringify(before.stderr.slice(0, 400))}`)
|
|
console.error(` after =${JSON.stringify(after.stderr.slice(0, 400))}`)
|
|
}
|
|
}
|
|
}
|
|
if (mismatches > 0) {
|
|
throw new Error(`${mismatches} of ${cases.length} invocations differ`)
|
|
}
|
|
console.log(`\nAll ${cases.length} invocations byte-identical (stdout, stderr, exit code).`)
|
|
} finally {
|
|
rmSync(isolatedUserData, { recursive: true, force: true })
|
|
for (const label of ['old', 'new']) {
|
|
rmSync(resolve(REPO, `.equiv-out-${label}`), { recursive: true, force: true })
|
|
}
|
|
}
|