Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a5c605f
docs(devlog): add docs-first roadmap and PRDs for ocx-child ephemeral…
lidge-jun Nov 6, 2026
07f5007
feat(link): add ephemeral-child contract helpers and golden conforman…
lidge-jun Nov 6, 2026
072d491
feat(ocx-child): add standalone lightweight Rust Child link relay and…
lidge-jun Nov 6, 2026
a49f471
feat(link): integrate ephemeral ocx-child discovery, enrollment, and …
lidge-jun Nov 6, 2026
fec8761
docs(link): document ephemeral ocx-child in structure SSOT and docs-site
lidge-jun Nov 6, 2026
2cce483
fix(ocx-child): persist write-ahead journal, track multiline TOML, an…
lidge-jun Nov 6, 2026
f8a5ba8
fix(link): harden ocx-child Windows ACLs, strict wire/settings bounds…
lidge-jun Nov 6, 2026
bd7a68c
fix(link): preserve interrupted-sync digests, rollback failed reattac…
lidge-jun Nov 6, 2026
5e26d66
fix(link): fail fast on unreadable attach snapshots, preserve feature…
lidge-jun Nov 6, 2026
c4d4d5d
fix(link): verify restored credentials before relay restart and clari…
lidge-jun Nov 6, 2026
580898d
Merge remote-tracking branch 'origin/dev' into codex/maintainer-8237
lidge-jun Nov 7, 2026
e40ab37
style(ocx-child): remove trailing whitespace
lidge-jun Nov 7, 2026
28a6886
fix(ocx-child): preserve recovery and verify shutdown
lidge-jun Nov 7, 2026
1aa8880
fix(ocx-child): harden recovery identity and shutdown
lidge-jun Nov 7, 2026
3907546
fix(ocx-child): preserve local state during detach and sync
lidge-jun Nov 7, 2026
87e1501
Merge origin/dev into lane/8237-blockers
lidge-jun Nov 8, 2026
768a058
test(ocx-child): add env-gated test barrier and shared mock Home harness
lidge-jun Nov 8, 2026
fb6d5d5
fix(ocx-child): serialize and own the shared CODEX_HOME
lidge-jun Nov 8, 2026
1612d7a
style(ocx-child): format lifecycle assertion message
lidge-jun Nov 8, 2026
27712f5
fix(ocx-child): recheck home identity around every restoration step
lidge-jun Nov 8, 2026
62aab7a
fix(ocx-child): commit a daemon generation before it serves
lidge-jun Nov 8, 2026
02f8376
fix(ocx-child): let shutdown end in-flight parity work and a busy pool
lidge-jun Nov 8, 2026
86f199a
test(ocx-child): cover removal of an unchanged injected web_search on…
lidge-jun Nov 8, 2026
e459d86
docs(link): state the single-owner CODEX_HOME rule for ocx-child
lidge-jun Nov 8, 2026
00c11d6
fix(ocx-child): let Home catalog slugs win in models_cache sync after…
lidge-jun Nov 8, 2026
4422013
fix(ocx-child): commit a rollback-restarted relay after removing the …
lidge-jun Nov 8, 2026
8808f98
fix(ocx-child): bound each control-mode request head with a deadline
lidge-jun Nov 8, 2026
6c917b3
fix(ocx-child): cap the cancellable parity connect at one second
lidge-jun Nov 8, 2026
f9d1b54
fix(ocx-child): keep both catalogs in the backup across the cache write
lidge-jun Nov 8, 2026
62d9e2f
fix(ocx-child): remove the padding inside the relay restart hint
lidge-jun Nov 8, 2026
0f4b36d
fix(ocx-child): compile the test barrier out of release builds
lidge-jun Nov 8, 2026
0d0f17d
fix(ocx-child): open lock files without following links
lidge-jun Nov 8, 2026
3e6dfec
fix(ocx-child): wipe the shutdown secret and token-bearing request heads
lidge-jun Nov 8, 2026
6ceb5ed
docs(link): state Child sync serialization and the restore window pre…
lidge-jun Nov 8, 2026
ff6f9e5
test(link): find the ocx-child binary under CARGO_TARGET_DIR
lidge-jun Nov 8, 2026
79e4a18
fix(ocx-child): stop printing the link key id after attach
lidge-jun Nov 8, 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
116 changes: 116 additions & 0 deletions devlog/_plan/261107_ocx_child_ephemeral_link/000_roadmap.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# `261107_ocx_child_ephemeral_link` — Audit Disclosures & Phase Status Ledger

> Reader contract: Maintainer and reviewer ledger tracking PABCD audit rounds, model-inheritance disclosures (`REVIEW-DECORRELATE-01`), and per-phase verification evidence (`LOOP-PESSIMIST-01`).

## 1. Model Inheritance Disclosure (`REVIEW-DECORRELATE-01`)

Per explicit user directive (`"너 상속 으로 해서"`), all PABCD subagents (`explorer`, `architect`, `reviewer`) inherit the coordinator's model (`google-antigravity/gemini-4-argon`) without mutating shared `.codexclaw` subagent configuration files. Cross-family decorrelation was therefore intentionally waived by user instruction and compensated by fresh zero-conclusion prompts and real command-backed verification.

## 2. `wp0` (Docs-Only Roadmap Cycle) Audit & Closure

- **Architect consultation**: Handle `01a1b18b-a2f3-7b53-971a-9b2c9cccecf7` returned `ALIGNED` on `ARCH-01..ARCH-07`; `Gap-1` (`models_cache.json` snapshot/restore in `ChildJournalV1`) and `Gap-2` (Windows owner-only DACL via `Win32_Security_Authorization`) folded into `020_wp2_rust_ocx_child.md`.
- **Independent A-gate review**: Handle `01a1b197-8e50-7722-bf0d-d13bb303f767` Round 1 returned `VERDICT: GO-WITH-FIXES (blockers=2)`; all 5 findings (omitting `restartCapability` on `ocx-child` client `/healthz`, `ephemeralChild`-specific `apply()` failure recovery string + fail-closed detached `restart`, `010` header dependencies + exact `isAllowedRequestOrigin` loopback Host/Origin check, `sync --json` stderr redirection + attached `serve` start rationale, and clean `corrupt_managed_span_refused` vector) were folded into `010`, `020`, and `030`. Round 2 re-audit returned `VERDICT: PASS` (`blocking_issues: []`).
- **`LOOP-PESSIMIST-01` notes (`wp0`)**:
- *Dead hypothesis*: Upstream socket pooling across relayed requests (`ARCH-02` alternative) died against `src/server/index/link-relay-sessions.ts:105` (`consume(nonce)` is strictly single-use), confirming one-shot `SingleSocketUpstream` per relayed request.
- *Dead hypothesis*: Advertising `restartCapability: "v1"` on `ocx-child` `GET /healthz` died in Round-1 audit because `ocx-child` does not serve `POST /api/system/restart` and `isChildHealth(..., "client", port)` does not require it.
- *Refutation trigger for `wp1..wp3`*: Any divergence between TypeScript and Rust outputs on `tests/fixtures/link/ocx-child-conformance.json`, or Windows encoded PowerShell exceeding `8000` chars.

## 3. Work-Phase Status Table

| Work Phase | Status | Summary |
|---|---|---|
| `wp0` | Completed | Docs-first roadmap (`000_roadmap.md`, `001_audit_and_status.md`, `010..040` decade PRDs) audited and locked with zero production edits |
| `wp1` | Completed | Shared golden conformance fixture (`ocx-child-conformance.json`), `src/link/ephemeral-child.ts`, and `tests/clients/link-ocx-child-conformance.test.ts` (65 pass, 0 fail; typecheck clean) |
| `wp2` | Completed | Standalone Rust crate `native/ocx-child` (stripped release binary; conformance, relay, and process-level lifecycle suites in `native/ocx-child/tests/`; `clippy -D warnings` & `fmt --check` clean). Current test counts are in the PR #8237 Verification section |
| `wp3` | Completed | Home discovery (`ssh-argv.ts`, `remote-windows.ts`), detached enrollment preflight (`link-enrollment.ts`), `package.json` scripts/files, and focused + real-binary E2E suites (`link-ocx-child-e2e.test.ts` 811ms pass) |
| `wp4` | Completed | `structure/manifest.json` + `structure/remote-link.md` + `structure/overview.md` (`INV-LINK-CHILD-01`) + `structure/INDEX.md`, `docs-site/` build (641 pages, 93,153 links), full gate matrix, and PR targeting `dev` |
| `wp5` | In Progress | PR #8237 review rounds: write-ahead recovery journal, multiline TOML structural scanner, overwritten user `[features]` surgical restoration, shared `CODEX_HOME` ownership, per-step home identity rechecks during restoration, committed daemon generations, bounded relay shutdown, and `models_cache.json` slug ownership across sync and detach, each with shared golden or Rust regression tests |
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# `wp1` — Shared Golden Conformance Vectors & Ephemeral Child Contract Helpers

> Continuity handoff (`LOOP-CONTINUITY-01` from `wp0` D at `a5c605fafc`): `wp0` locked the docs-first roadmap with zero production edits (`VERDICT: PASS` / `GO-WITH-FIXES (blockers=0)`). Revalidated `010_wp1_conformance_contract.md` against current HEAD: `isAllowedRequestOrigin` (`src/server/auth-cors.ts:118`) is added to `Header dependencies` and `routeCases` so DNS-rebinding `Host` and cross-origin `Origin` `403` rejections are locked in the shared golden fixture alongside route admission, smuggling, HMAC v2, OCX-6 digests, child-parity, and `#1798` journal round-trips. Architect consultation: covered by `ARCH-06` & `ARCH-07` (`ALIGNED` on handle `01a1b18b-a2f3-7b53-971a-9b2c9cccecf7`, `000_roadmap.md` §3.1).

- **Phase ID**: `wp1` (depends on `wp0`; satisfies `c-2`)
- **Header dependencies**:
- Existing types/functions consumed: `linkRouteAllowed` (`src/link/routes.ts:15`), `linkManagementRouteAllowed` (`src/link/management-routes.ts:72`), `isAllowedRequestOrigin` (`src/server/auth-cors.ts:118`), `computeLinkRelayProof`, `computeLinkConnectionProof`, `verifyLinkRelayProof`, `verifyLinkConnectionProof` (`src/link/relay-auth.ts:45-95`), `HomeCodexSettings`, `codexSettingsDigestPayload`, `codexSettingsDigest` (`src/link/codex-settings.ts:30-182`), `parseLinkChildParityReport` (`src/link/child-parity.ts:67`), `validateHubRelayRequestHeaders` (`src/client/hub-relay.ts:49`), `classifySyncResult` (`src/cli/sync-fanout.ts:52`), `externalCodexModelProvider` & `ensureRootWebSearchDisabled` (`src/codex/inject/config-toml.ts:30,366`), and `stripJournaledOpenaiBaseUrl` (`src/codex/injected-marker.ts:106`).
- Shared types exported/validated in this phase: `EphemeralChildConfigProjection`, `isDetachedEphemeralChildConfig`, `isEphemeralChildHealth` (`src/link/ephemeral-child.ts`), and `ConformanceSuite` (`tests/fixtures/link/ocx-child-conformance.json`).
- **Scope**:
- **IN**: `src/link/ephemeral-child.ts` (NEW), `tests/fixtures/link/ocx-child-conformance.json` (NEW), `tests/clients/link-ocx-child-conformance.test.ts` (NEW — named `link-*` to satisfy `seeds.json` vs `cli` prefix rule), `scripts/test-layout/layout-clients.json` (MODIFY), `tests/fixtures/test-layout-expected.json` (MODIFY).
- **OUT**: `native/ocx-child/**` (`wp2`), `src/server/management/**` (`wp3`), `structure/**` & `docs-site/**` (`wp4`).

## 1. Exact File Changes

### 1.1 NEW `src/link/ephemeral-child.ts`

Pure, zero-side-effect TypeScript contract helper module in `src/link/` (starts no timers, sockets, or processes on import; unreachable from `src/router.ts`, `src/server/lifecycle.ts`, and `src/server/responses/core.ts`):

```ts
/**
* Contract predicates for the lightweight `ocx-child` ephemeral Remote Link Child runtime.
*
* Kept pure inside `src/link/` so enrollment and diagnostics can distinguish a clean detached
* `ocx-child` host from a full standalone `ocx` daemon without changing `LinkStore` on disk.
*/

export interface EphemeralChildConfigProjection {
readonly port: number;
readonly runtimeRole: "standalone" | "client";
readonly ephemeralChild: true;
}

function isValidPort(value: unknown): value is number {
return typeof value === "number" && Number.isInteger(value) && value >= 1024 && value <= 65535;
}

/**
* True when `config show --json` came from a clean, detached `ocx-child` binary ready for
* Home-initiated enrollment (`runtimeRole: "standalone"`, `ephemeralChild: true`, valid port).
* An already-attached `ocx-child` reports `runtimeRole: "client"` and returns `false`.
*/
export function isDetachedEphemeralChildConfig(value: unknown): value is EphemeralChildConfigProjection {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const record = value as Record<string, unknown>;
return record.ephemeralChild === true
&& (record.runtimeRole ?? "standalone") === "standalone"
&& isValidPort(record.port);
}

/** True when a `/healthz` payload identifies a live `ocx-child` client relay on `expectedPort`. */
export function isEphemeralChildHealth(value: unknown, expectedPort: number): boolean {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const record = value as Record<string, unknown>;
return record.service === "opencodex"
&& record.role === "client"
&& record.ephemeralChild === true
&& record.port === expectedPort
&& typeof record.pid === "number"
&& Number.isInteger(record.pid)
&& record.pid > 0
&& typeof record.startedAt === "string"
&& Number.isFinite(Date.parse(record.startedAt));
}
```

### 1.2 NEW `tests/fixtures/link/ocx-child-conformance.json`

Deterministic JSON document (`"schema": "ocx.child.conformance/1"`) containing 6 golden vector arrays shared verbatim between `bun test` and `cargo test`:

1. `routeCases` (24 vectors):
- Every allowed `POST` data-plane route (`/v1/responses`, `/v1/responses/compact`, `/v1/messages`, `/v1/messages/count_tokens`, `/v1/chat/completions`, `/v1/audio/transcriptions`, `/v1/alpha/search`, `/v1/images/generations`, `/v1/images/edits`, `/v1/live`, `/v1/realtime/calls`, plus all 10 `CONTEXT_PATHS` under `/v1/alpha/{history,notes}/v2/*`) -> `expectedAction: "relay_data"`, `expectedAuthHeader: "bearer"`.
- `GET /v1/catalog`, `HEAD /v1/catalog`, `GET /v1/hub-state`, `HEAD /v1/hub-state`, `GET /v1/models`, `GET /v1/opencodex/artifacts/item-1` -> `expectedAction: "relay_data"`, `expectedAuthHeader: "bearer"`.
- `GET /v1/usage` -> `expectedAction: "relay_data"`, `expectedAuthHeader: "x-opencodex-api-key"`.
- `GET /v1/link-management/codex-settings` (empty query -> `relay_management`; `?foo=1` -> `reject_404`), `GET /v1/link-management/api/settings` (clean query -> `relay_management`; `?refresh=1` -> `reject_404`).
- `GET /healthz` -> `local_healthz`; `GET /readyz` -> `local_readyz`; `HEAD /readyz` -> `reject_404`.
- `GET /v1/responses` with `Upgrade: websocket` (loopback `Origin`) -> `reject_426_websocket`; `GET /v1/responses` with `Upgrade: websocket` + `Origin: https://evil.example` -> `reject_403_origin`; `POST /v1/responses` with `Host: evil.example:10100` -> `reject_403_origin`; `POST /v1/responses` with `Origin: https://evil.example` -> `reject_403_origin`; `GET /v1/catalog` with `Upgrade: h2c` -> `reject_404`; `GET /api/config` & `DELETE /v1/responses` -> `reject_404`.
2. `framingCases` (10 vectors):
- Valid `Content-Length: 42` with caller credentials (`Authorization: Bearer sk-user`, `x-api-key: ant-user`, `x-goog-api-key: g-user`, `api-key: az-user`, `x-opencodex-api-key: ocx-user`, `chatgpt-account-id: acc-1`, `cookie: sid=1`, `Connection: keep-alive, x-custom-secret`, `x-custom-secret: drop-me`, `x-request-trace: keep-me`) -> `allow` and exact `strippedHeaderNames` leaving only `x-request-trace`.
- Valid `Transfer-Encoding: chunked` without `Content-Length` -> `allow`.
- Simultaneous `Content-Length: 10` + `Transfer-Encoding: chunked` -> `reject_400_smuggling`.
- Duplicate `Content-Length` (`10`, `10` or `10`, `20`) -> `reject_400_smuggling`.
- Non-digit `Content-Length: +10` / `10abc` -> `reject_400_smuggling`.
- Non-chunked `Transfer-Encoding: gzip, chunked` -> `reject_400_smuggling`.
- `Upgrade: websocket` in relay header validator -> `reject_400_smuggling`.
- Header value containing `\r` or `\n` -> `reject_400_invalid`.
3. `relayAuthCases` (3 deterministic vectors using synthetic test-only tokens like `"ocx_data_0000000000000000000000000000000000000001"` so `privacy:scan` sees obvious zero-entropy test fixtures):
- Locks `tokenSha256Hex`, `expectedCallerProof`, `expectedListenerProof`, and `expectedConnectionProof` computed over raw 32-byte `Buffer.from(tokenSha256Hex, "hex")`.
4. `settingsDigestCases` (4 vectors):
- All-null defaults (`fastMode: null, multiAgentMode: null, webSearchSidecarEnabled: true, subagentDefaults: null`).
- Populated `fastMode: true, multiAgentMode: "v1", webSearchSidecarEnabled: false, subagentDefaults: { model: "gpt-5.6-sol", reasoningEffort: "high" }`.
- Populated `fastMode: false, multiAgentMode: "v2", webSearchSidecarEnabled: true, subagentDefaults: { model: "anthropic/claude-sonnet-4-5", reasoningEffort: null }`.
- `multiAgentMode: "default"` with `subagentDefaults: null`.
5. `childParityCases` (6 vectors):
- Valid pre-apply (`lastApplyAt: null, appliedCodexSettingsDigest: null`), valid post-apply, mismatched nullability (`lastApplyAt: null` with non-null digest -> invalid), extra unknown property -> invalid, out-of-range timestamp -> invalid, negative `sequence` -> invalid.
6. `journalRoundtripCases` (8 vectors):
- `clean_absent_config`: `preAttachToml: null` -> attach -> unchanged detach -> `expectedFinalToml: null` (file unlinked), `expectedDetachOutcome: "exact_restore"`.
- `preserve_comments_and_crlf`: CRLF `config.toml` with `# user comment` and `[mcp_servers.git]` -> attach -> sync with updated `fastMode` + `subagentDefaults` -> unchanged detach -> byte-exact CRLF restoration (`"exact_restore"`).
- `app_rewrite_1798_comment_strip`: attach -> Codex App strips all `# Auto-injected by opencodex...` comments and adds `model = "gpt-5.6-sol"` + `[projects."/work"]` -> detach -> strips `openai_base_url`, `experimental_realtime_ws_base_url`, `model_catalog_json`, and managed settings by exact journaled value while preserving `[projects."/work"]` (`"surgical_restore"`).
- `web_search_disabled_and_restored`: user had `web_search = "live"` -> attach with `webSearchSidecarEnabled: false` writes `web_search = "disabled"` -> `#1798` strips marker + user adds `approval_policy = "never"` -> detach restores `web_search = "live"` and keeps `approval_policy = "never"` (`"surgical_restore"`).
- `user_changed_base_url_preserved`: attach -> user edits `openai_base_url = "https://custom.gateway.example/v1"` -> detach preserves user's custom `openai_base_url` while removing remaining owned keys (`"surgical_restore"`).
- `corrupt_managed_span_refused`: attach wrote Design B root keys -> external edit corrupts `config.toml` with an unterminated quoted assignment on a managed root key (`openai_base_url = "http://127.0.0.1:10100/v1`) -> detach refuses (`expectedDetachOutcome: "refuse_conflict"`) leaving `config.toml` and `codex-journal.json` untouched.
- `conflict_tampered_catalog_refused`: attach wrote `opencodex-catalog.json` -> user hand-edits `opencodex-catalog.json` -> detach refuses (`expectedDetachOutcome: "refuse_conflict"`) rather than deleting user-modified catalog.
- `refuse_external_model_provider_on_attach`: `preAttachToml` has `model_provider = "openrouter"` + `[model_providers.openrouter]\nbase_url = "https://openrouter.ai/api/v1"` -> `expectedAttachOk: false`, `expectedDetachOutcome: "not_attached"`.

### 1.3 NEW `tests/clients/link-ocx-child-conformance.test.ts` & Test-Layout Shards

- Resolves fixture via `fixturePath("link", "ocx-child-conformance.json")` from `../helpers/repo-root`.
- Verifies every vector in `routeCases`, `framingCases`, `relayAuthCases`, `settingsDigestCases`, `childParityCases`, and `journalRoundtripCases` against OpenCodex's live TypeScript implementations (`linkRouteAllowed`, `linkManagementRouteAllowed`, `validateHubRelayRequestHeaders`, `computeLinkRelayProof`, `computeLinkConnectionProof`, `codexSettingsDigestPayload`, `codexSettingsDigest`, `parseLinkChildParityReport`, `externalCodexModelProvider`, `stripJournaledOpenaiBaseUrl`, `ensureRootWebSearchDisabled`), plus unit tests for `isDetachedEphemeralChildConfig` and `isEphemeralChildHealth`.
- Registers `"link-ocx-child-conformance.test.ts": "clients"` in BOTH:
- `scripts/test-layout/layout-clients.json`
- `tests/fixtures/test-layout-expected.json`

## 2. Conditional-Path Activation Scenarios (`C-ACTIVATION-GROUNDING-01`)

1. **WebSocket `Upgrade` vs generic `Upgrade` rejection**: Driven in `routeCases` (`Upgrade: websocket` on `/v1/responses` -> `reject_426_websocket`; `Upgrade: h2c` on `/v1/catalog` -> `reject_404`) and asserted against `linkRouteAllowed` + ingress rules.
2. **Smuggling header combinations (`CL+TE`, duplicate `CL`)**: Driven in `framingCases` and asserted to return `reason: "smuggling"` from `validateHubRelayRequestHeaders`.
3. **Raw 32-byte vs ASCII-hex HMAC key**: Driven in `relayAuthCases` and asserted against `computeLinkRelayProof` / `verifyLinkRelayProof` (plus negative assertion that HMAC over ASCII hex differs).
4. **Detached vs already-attached `ocx-child` config**: Driven against `isDetachedEphemeralChildConfig` (`runtimeRole: "client"` returns `false`; `ephemeralChild` missing returns `false`; port `< 1024` returns `false`).

## 3. Phase Verifiers

- `bun test tests/clients/link-ocx-child-conformance.test.ts tests/test-layout.test.ts tests/test-layout-tooling.test.ts tests/lab/core-link-boundary.test.ts tests/lab/core-lab-boundary.test.ts` (exit `0`)
- `bun run typecheck` (exit `0`)
- `bun run privacy:scan` (exit `0`)
Loading
Loading