Merge pull request #733 from nwparker/create-speed/sc-typecheck

fix(cloud): sandbox-control typecheck to zero
This commit is contained in:
Neil
2026-11-13 11:18:45 -08:00
committed by GitHub
14 changed files with 178 additions and 15 deletions
+15
View File
@@ -0,0 +1,15 @@
# sandbox-control
The Orca Cloud control plane Worker: workspace lifecycle, the egress credential proxy, environment builds and the golden image. Its routes are listed at the top of `src/index.ts`.
## Typecheck
`pnpm typecheck` runs `scripts/typecheck.mjs`. It fails on any TypeScript diagnostic in this package's own files, and on any diagnostic without a file (a config error).
**Why a raw `tsc --noEmit` shows about 247 diagnostics.** The Worker imports desktop code from `src/shared` (wire types, parsers), so tsc checks those files under this package's options:
- `noUncheckedIndexedAccess`, which the desktop doesn't use, accounts for about 239;
- the Workers globals, which lack the desktop's `window` and `MessageChannel`, account for the rest.
TypeScript can't apply options per file. Those files are type-checked by the root (desktop) typecheck under their own config. So the script counts them, and prints the count on every run so a jump is visible, but doesn't fail on them.
**Why there's an xterm stub.** Desktop settings types import `ITheme` from `@xterm/xterm`, whose typings pull in the DOM library. DOM's `Headers`, streams and `crypto` clash with `@cloudflare/workers-types`. `tsconfig.json` `paths` maps that import to `types/xterm-theme.d.ts`, the theme shape only.
@@ -189,7 +189,7 @@ describe('Worker-embedded image scripts', () => {
it('installs every VM script and makes zsh read the session env hook once', () => {
const provision = readFileSync(fileURLToPath(new URL('./provision-golden.sh', import.meta.url).href), 'utf8')
const loop = provision.match(/^for name in (.+); do$/m)?.[1].split(' ') ?? []
const loop = provision.match(/^for name in (.+); do$/m)?.[1]?.split(' ') ?? []
expect(new Set(loop)).toEqual(new Set(VM_SCRIPTS))
expect(provision).toContain(
"grep -q zz-orca-session-env /etc/zsh/zshenv ||\n echo '[ -r /etc/profile.d/zz-orca-session-env.sh ] && . /etc/profile.d/zz-orca-session-env.sh' | sudo tee -a /etc/zsh/zshenv"
+1 -1
View File
@@ -7,7 +7,7 @@
"dev": "wrangler dev -c wrangler.jsonc -c wrangler.build-runner.jsonc --port 8791 --ip 127.0.0.1",
"db:migrate:local": "wrangler d1 migrations apply SANDBOX_DB --local",
"golden-image": "tsx image/build-golden-image.ts",
"typecheck": "tsc --noEmit",
"typecheck": "node scripts/typecheck.mjs",
"test": "vitest run"
},
"dependencies": {
@@ -0,0 +1,57 @@
// SANDBOX-CONTROL-TYPECHECK: `pnpm typecheck` for this Worker. tsc checks every file the program reaches, and
// the Worker imports desktop code from src/shared (wire types, parsers). Those files are written for the
// desktop's compiler options (no noUncheckedIndexedAccess) and globals (window, MessageChannel), and the
// desktop's own typecheck covers them; TypeScript has no per-file options. So this fails on any diagnostic in
// this package's own files, or one tsc reports without a file (a config error), and counts the rest.
// node scripts/typecheck.mjs
import { spawnSync } from 'node:child_process'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
/**
* Splits tsc --pretty false output into this package's diagnostics (with their detail lines) and a count of others.
* The split is by path prefix, and nothing else: tsc prints paths relative to this package, so a path that starts
* with `..` (or is absolute) is an imported file: desktop src/shared and node_modules. Every other located
* diagnostic is own: src/, test/, image/, scripts/, types/ (the xterm stub) and tsconfig.json itself, including
* any file added later. A diagnostic with no file (a config or option error) is own too.
*/
export function splitDiagnostics(output) {
const own = []
let foreign = 0
let keep = false
for (const line of output.split('\n')) {
const located = /^(.+?)\(\d+,\d+\): error TS\d+/.exec(line)
if (located) {
// Paths are relative to this package: anything reached through `..` is not its own.
keep = !located[1].startsWith('..') && !located[1].startsWith('/')
if (keep) {
own.push(line)
} else {
foreign++
}
} else if (/^error TS\d+/.test(line)) {
keep = true
own.push(line)
} else if (keep && line.startsWith(' ') && line.trim()) {
own.push(line)
} else if (line.trim()) {
keep = false
}
}
return { own, foreign }
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const tsc = createRequire(import.meta.url).resolve('typescript/bin/tsc')
const run = spawnSync(process.execPath, [tsc, '--noEmit', '-p', '.', '--pretty', 'false'], { encoding: 'utf8' })
const { own, foreign } = splitDiagnostics(`${run.stdout ?? ''}${run.stderr ?? ''}`)
if (own.length > 0) {
process.stdout.write(`${own.join('\n')}\n`)
}
// Always printed, so a jump in imported diagnostics shows in every run.
process.stdout.write(`imported desktop files: ${foreign} diagnostic(s), checked by the root (desktop) typecheck, not here\n`)
const ownErrors = own.filter((line) => / error TS\d+/.test(line) || /^error TS\d+/.test(line)).length
process.stdout.write(`sandbox-control typecheck: ${ownErrors} error(s) in its own files\n`)
// A crash, or a non-zero exit with nothing parsed, is never a pass.
process.exit(ownErrors > 0 || run.status === null || (run.status !== 0 && foreign === 0) ? 1 : 0)
}
@@ -69,7 +69,7 @@ export function providerEnabled(env: Pick<Env, 'AGENT_ACCOUNTS_CLAUDE' | 'AGENT_
return { claude: states.claude === 'on', codex: states.codex === 'on' }
}
export function credentialState(row: Pick<CredentialRow, 'state' | 'expires_at'> | null, now: number): CredentialState {
export function credentialState(row: Pick<CredentialRow, 'state' | 'expires_at' | 'suspect_at'> | null, now: number): CredentialState {
if (!row) {
return 'none'
}
+2
View File
@@ -16,6 +16,8 @@ export type SandboxStatus =
export type SandboxRow = {
id: string
/** What made a machine-made workspace (a routine run: its routine's id); null for desktop creates. */
origin_ref?: string | null
account_id: string
user_id: string
workspace_id: string
+1 -1
View File
@@ -63,6 +63,6 @@ export async function tarGzipFile(input: { dirs: string[]; path: string; bytes:
new Uint8Array(padding),
new Uint8Array(BLOCK * 2) // end-of-archive marker
]
const stream = new Blob(parts as BlobPart[]).stream().pipeThrough(new CompressionStream('gzip'))
const stream = new Blob(parts).stream().pipeThrough(new CompressionStream('gzip'))
return new Response(stream).arrayBuffer()
}
+4 -4
View File
@@ -307,7 +307,7 @@ export async function listWorkspaces(db: D1Database, caller: SandboxCaller, url:
/** sandbox id -> its routine's name, for routine runs being listed. */
async function routineNamesFor(db: D1Database, accountId: string, rows: readonly SandboxRow[]): Promise<Map<string, string>> {
const refs = [...new Set(rows.flatMap((row) => ((row as { origin_ref?: string | null }).origin_ref ? [(row as { origin_ref: string }).origin_ref] : [])))]
const refs = [...new Set(rows.flatMap((row) => (row.origin_ref ? [row.origin_ref] : [])))]
if (refs.length === 0) {
return new Map()
}
@@ -318,7 +318,7 @@ async function routineNamesFor(db: D1Database, accountId: string, rows: readonly
const byRoutine = new Map(results.map((r) => [r.id, r.name]))
const out = new Map<string, string>()
for (const row of rows) {
const name = byRoutine.get((row as { origin_ref?: string | null }).origin_ref ?? '')
const name = byRoutine.get(row.origin_ref ?? '')
if (name) {
out.set(row.id, name)
}
@@ -438,8 +438,8 @@ export async function patchWorkspace(
}
const ownerChanged = transferring
const wentPrivate = row.visibility === 'team' && next.visibility === 'private'
const archived = patch.archived === true && row.archived_at === null
return { row: next, revoke: ownerChanged ? 'all_but_new_owner' : wentPrivate || archived ? 'non_owner' : 'none' }
// Archiving goes through the lifecycle (archive_via_lifecycle above); the team route revokes on archive itself.
return { row: next, revoke: ownerChanged ? 'all_but_new_owner' : wentPrivate ? 'non_owner' : 'none' }
}
export const PresenceSchema = z
@@ -214,7 +214,7 @@ describe('public API: attachments', () => {
const header = Buffer.from(tar.subarray(block * 512, block * 512 + 512))
const stored = parseInt(field(block, 148, 8), 8)
header.fill(0x20, 148, 156)
expect(header.reduce((sum, b) => sum + b, 0)).toBe(stored)
expect(header.reduce((sum: number, b: number) => sum + b, 0)).toBe(stored)
}
expect(tar.subarray(3 * 512, 3 * 512 + 5).toString()).toBe('hello')
await expect(tarGzipFile({ dirs: [], path: '../x', bytes, mtime: 0 })).rejects.toThrow('tar_bad_path')
@@ -67,7 +67,10 @@ export type FakeVercel = SandboxProviderApi & GoldenImageProviderApi & {
snapshots: Map<string, ProviderSnapshot & { sandboxName: string; creationMethod: string; createdAt: number }>
commands: { sessionId: string; command: string; args?: string[]; env: Record<string, string> }[]
/** Answers commands the fake does not know (in-VM orca CLI, routine runners); undefined falls through. */
commandHandler?: (params: RunCommandParams, sessionId: string) => { exitCode: number; stdout: string; stderr: string } | undefined
commandHandler?: (
params: RunCommandParams,
sessionId: string
) => { exitCode: number; stdout: string; stderr: string } | Promise<{ exitCode: number; stdout: string; stderr: string }> | undefined
failStart: boolean
failEnvRefresh: boolean
/** Exit code orca-ssh-keys answers with (0 = success; 3 = image without sshd). */
@@ -124,7 +124,7 @@ describe('SEC-6c: secret-handling code logs only through the redacting sink', ()
it('the secret-handling modules never call console directly', () => {
for (const file of FILES) {
const source = readFileSync(fileURLToPath(new URL(`../src/${file}`, import.meta.url)), 'utf8')
const source = readFileSync(fileURLToPath(new URL(`../src/${file}`, import.meta.url).href), 'utf8')
expect({ file, direct: /\bconsole\s*\.\s*[a-z]+\s*\(/.test(source) }).toEqual({ file, direct: false })
}
})
@@ -0,0 +1,38 @@
// SANDBOX-CONTROL-TYPECHECK: the package typecheck fails on this package's own diagnostics (and config errors),
// and only counts those in imported desktop files.
import { describe, expect, it } from 'vitest'
// @ts-expect-error -- plain .mjs script; no type declarations
import { splitDiagnostics } from '../scripts/typecheck.mjs'
describe('scripts/typecheck.mjs', () => {
it('keeps own diagnostics with their detail lines, counts imported desktop ones', () => {
const out = [
"src/a.ts(1,2): error TS2322: Type 'x' is not assignable to type 'y'.",
" Type 'x' is missing properties.",
"../../../src/shared/b.ts(3,4): error TS2532: Object is possibly 'undefined'.",
" detail of a foreign error",
"test/c.test.ts(5,6): error TS7006: Parameter 'p' implicitly has an 'any' type.",
''
].join('\n')
const { own, foreign } = splitDiagnostics(out)
expect(own).toEqual([
"src/a.ts(1,2): error TS2322: Type 'x' is not assignable to type 'y'.",
" Type 'x' is missing properties.",
"test/c.test.ts(5,6): error TS7006: Parameter 'p' implicitly has an 'any' type."
])
expect(foreign).toBe(1)
})
it('the split is by path prefix: own package paths (src, test, image, scripts, types stub, tsconfig) fail; `..` and absolute paths are imported', () => {
const line = (path: string) => `${path}(1,1): error TS2322: x`
const own = ['src/a.ts', 'test/b.test.ts', 'image/c.ts', 'scripts/d.mjs', 'types/xterm-theme.d.ts', 'tsconfig.json', 'new-dir/added-later.ts']
const imported = ['../../../src/shared/e.ts', '../../node_modules/f/index.d.ts', '/abs/node_modules/g.d.ts']
const { own: kept, foreign } = splitDiagnostics([...own, ...imported].map(line).join('\n'))
expect(kept).toEqual(own.map(line))
expect(foreign).toBe(imported.length)
})
it('a diagnostic without a file (config error) is its own', () => {
expect(splitDiagnostics("error TS2688: Cannot find type definition file for 'x'.").own).toHaveLength(1)
})
})
+18 -4
View File
@@ -3,12 +3,26 @@
"target": "es2022",
"module": "es2022",
"moduleResolution": "bundler",
"lib": ["es2022"],
"types": ["@cloudflare/workers-types", "@types/node"],
"lib": [
"es2024"
],
"types": [
"@cloudflare/workers-types",
"@types/node"
],
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmit": true,
"skipLibCheck": true
"skipLibCheck": true,
"paths": {
"@xterm/xterm": [
"./types/xterm-theme.d.ts"
]
}
},
"include": ["src", "test", "image"]
"include": [
"src",
"test",
"image"
]
}
+34
View File
@@ -0,0 +1,34 @@
// SANDBOX-CONTROL-TYPECHECK: desktop shared settings types reach `import type { ITheme } from '@xterm/xterm'`, and
// xterm's typings pull in `/// <reference lib="dom" />`. DOM's Headers, streams and crypto then clash with
// @cloudflare/workers-types across this Worker. tsconfig `paths` maps the import here: the theme shape only (copied
// from @xterm/xterm's ITheme), no DOM.
export interface ITheme {
foreground?: string
background?: string
cursor?: string
cursorAccent?: string
selectionBackground?: string
selectionForeground?: string
selectionInactiveBackground?: string
scrollbarSliderBackground?: string
scrollbarSliderHoverBackground?: string
scrollbarSliderActiveBackground?: string
overviewRulerBorder?: string
black?: string
red?: string
green?: string
yellow?: string
blue?: string
magenta?: string
cyan?: string
white?: string
brightBlack?: string
brightRed?: string
brightGreen?: string
brightYellow?: string
brightBlue?: string
brightMagenta?: string
brightCyan?: string
brightWhite?: string
extendedAnsi?: string[]
}