diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1dcd92bde2..86691d202c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -405,7 +405,7 @@ jobs: bun install --frozen-lockfile - name: Typecheck - run: bun x tsc --noEmit + run: bun x tsc --noEmit -p tests/tsconfig.doctor-service-memory-contract.json - name: GUI tests run: cd gui && bun test tests diff --git a/src/server/local-management-read-client.ts b/src/server/local-management-read-client.ts index abcaa7acf2..50629090db 100644 --- a/src/server/local-management-read-client.ts +++ b/src/server/local-management-read-client.ts @@ -21,6 +21,9 @@ export interface LocalManagementReadDeps { readRuntime?: (pid: number) => RuntimePortState | null; createNonce?: () => string; now?: () => number; +} + +export interface LocalManagementReadRequestDeps extends LocalManagementReadDeps { timeoutMs?: number; } @@ -34,7 +37,7 @@ export interface LocalManagementReadDeps { export async function fetchBoundLocalManagementRead( target: LiveProxy, path: LocalManagementReadPath, - deps: LocalManagementReadDeps = {}, + deps: LocalManagementReadRequestDeps = {}, ): Promise { if ( target.source !== "runtime" diff --git a/structure/05_gui-and-management-api.md b/structure/05_gui-and-management-api.md index 098d77898a..a9a9239705 100644 --- a/structure/05_gui-and-management-api.md +++ b/structure/05_gui-and-management-api.md @@ -25,18 +25,17 @@ a fourth credential class. A management credential that equals any configured da does not enable management access. The data plane may continue to start, but `/api/*` remains closed. CLI health collection follows the same boundary without transporting the reusable management credential. Its local-read HMAC capability is an additional single-use, route-scoped admission -mechanism, not a reusable credential class. `ocx status` and `ocx doctor` derive these capabilities -from the protected -`runtime-port.json` secret for exactly two read-only GETs: `/api/codex-auth/accounts` and -`/api/system/memory`. Each capability is bound to its method, path, nonce, proxy PID, and port. A -short expiry is part of the HMAC, and the server consumes each capability once. A capability cannot -authorize another management route or survive process replacement. These probes connect directly -to the selected listener instead of delegating local identity to an environment HTTP proxy. Their -output distinguishes -a missing proxy, rejected local capability, and an unexpected management response so a reachable -`401` cannot be reported as "proxy not running." Legacy or configured-port-only listeners still -satisfy ordinary liveness, but their detailed CLI health remains unavailable until restarted with -an attested runtime record and capability-aware server. +mechanism, not a reusable credential class. `ocx doctor` and OAuth health derive these capabilities +from the protected `runtime-port.json` secret for exactly two read-only GETs: +`/api/codex-auth/accounts` and `/api/system/memory`. Each capability is bound to its method, path, +nonce, proxy PID, and port. A short expiry is part of the HMAC, and the server consumes each +capability once. A capability cannot authorize another management route or survive process +replacement. These probes connect directly to the selected listener instead of delegating local +identity to an environment HTTP proxy. Their output distinguishes a missing proxy, rejected local +capability, and an unexpected management response so a reachable `401` cannot be reported as +"proxy not running." Legacy or configured-port-only listeners still satisfy ordinary liveness, but +their detailed CLI health remains unavailable until restarted with an attested runtime record and +capability-aware server. [Decision Log] - 목적과 의도: Keep a lower-privileged local process from collecting the management bearer by impersonating `/healthz` on an unused port. diff --git a/tests/doctor-service-memory-contract.test.ts b/tests/doctor-service-memory-contract.test.ts new file mode 100644 index 0000000000..21b854764e --- /dev/null +++ b/tests/doctor-service-memory-contract.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, test } from "bun:test"; +import { fetchServiceMemory } from "../src/cli/doctor"; + +type ServiceMemoryDeps = NonNullable[1]>; +type TimeoutOptionIsHidden = "timeoutMs" extends keyof ServiceMemoryDeps ? false : true; + +const timeoutOptionIsHidden: TimeoutOptionIsHidden = true; + +describe("doctor service memory contract", () => { + test("owns its timeout instead of accepting a caller override", () => { + expect(timeoutOptionIsHidden).toBe(true); + }); +}); diff --git a/tests/tsconfig.doctor-service-memory-contract.json b/tests/tsconfig.doctor-service-memory-contract.json new file mode 100644 index 0000000000..647db8a9f4 --- /dev/null +++ b/tests/tsconfig.doctor-service-memory-contract.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": ".." + }, + "include": [ + "../src", + "./doctor-service-memory-contract.test.ts" + ] +}