Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
2a93532
feat(lab): CL-10 public evidence trust core
Wibias Aug 13, 2026
5ad7201
fix(lab): bound private file stage cleanup
Wibias Aug 13, 2026
9336afe
test(lab): cover expired private file stages
Wibias Aug 13, 2026
57ff10f
fix(lab): bound strict public JSON input (#1641)
Wibias Aug 13, 2026
88627a8
fix(lab): harden private stages before publication
Wibias Aug 14, 2026
9c39523
fix(lab): protect publisher key before final link
Wibias Aug 14, 2026
75f8a02
test(lab): reject publisher key publication on ACL failure
Wibias Aug 14, 2026
bc4ab53
test(lab): prepare private stages before secret writes
Wibias Aug 14, 2026
78740d6
fix(lab): harden private stages before writing secrets
Wibias Aug 14, 2026
f26f397
fix(lab): harden publisher stage before key bytes
Wibias Aug 14, 2026
d01a619
fix(lab): address public evidence review findings
Wibias Aug 14, 2026
5e4e9aa
test(lab): tighten review regressions
Wibias Aug 14, 2026
b669369
fix(lab): address public evidence review findings
Wibias Aug 14, 2026
a69b19a
fix(lab): close remaining POSIX path privacy gaps
Wibias Aug 14, 2026
2a709df
feat(lab): CL-10 public evidence operator and community integration
Wibias Aug 14, 2026
6655307
fix(lab): scope public purge test faults
Wibias Aug 14, 2026
5d5a239
test(lab): cover actionable CodeRabbit regressions
Wibias Aug 14, 2026
7a1e066
fix(lab): bound mutation lock PID recovery
Wibias Aug 14, 2026
6c0db16
fix(lab): align public origin quota accounting
Wibias Aug 14, 2026
73d2cc2
fix(lab): report incomplete purge provenance
Wibias Aug 14, 2026
b5d35ab
fix(lab): fail incomplete public purge classification
Wibias Aug 14, 2026
4c2f84f
fix(lab): bound public verdict lookup and canonical ordering
Wibias Aug 14, 2026
cda04fe
fix(lab): make revocation ordering locale independent
Wibias Aug 14, 2026
34d0445
fix(cli): classify public verification failures correctly
Wibias Aug 14, 2026
6206798
test(lab): exercise community revocation conflict path
Wibias Aug 14, 2026
7983c7c
test(lab): make duplicate-key regression effective
Wibias Aug 14, 2026
fe295bc
test(lab): make private-key leak canary effective
Wibias Aug 14, 2026
99faa65
test(lab): describe provenance failure accurately
Wibias Aug 14, 2026
124d1e0
test(lab): remove duplicate source-shape regressions
Wibias Aug 14, 2026
af73ffc
test(lab): harden provenance recovery regressions
Wibias Aug 14, 2026
d5d7e59
test(lab): derive wire fixture key path
Wibias Aug 14, 2026
67ed25e
test(lab): keep temp path helper import
Wibias Aug 14, 2026
25b4c24
test(lab): isolate corrupt-origin purge provenance
Wibias Aug 14, 2026
f887c2a
test(lab): align origin regressions with corrected semantics
Wibias Aug 14, 2026
8e77cd6
test(lab): cover deferred export purge branches
Wibias Aug 14, 2026
730aa0d
fix CL-01 negative-control accounting
Wibias Aug 14, 2026
3ecd63f
test CL-01 harness failure isolation
Wibias Aug 14, 2026
d3886c1
refactor(lab): share community bundle filename contract
Wibias Aug 14, 2026
fa7846a
docs(lab): fix sensitive purge heading level
Wibias Aug 14, 2026
de9b5ff
fix CL-01 negative-control execution identity
Wibias Aug 14, 2026
28cf62a
test: report unexpected CL-01 negative-control classifications
Wibias Aug 14, 2026
6d471f3
test(lab): restore purge fault in finally
Wibias Aug 14, 2026
d5ea3a3
fix(gui): allow HTTP protocol literal in i18n lint
Wibias Aug 14, 2026
1c59fb1
fix CL-01 tool-result negative control
Wibias Aug 14, 2026
b69934c
test(lab): cover ledger mutation locking
Wibias Aug 14, 2026
1ee2d51
fix(lab): serialize ledger mutations
Wibias Aug 14, 2026
c0187ef
fix(lab): lock sensitive purge transactions
Wibias Aug 14, 2026
164881e
fix(lab): keep artifact publication in ledger transactions
Wibias Aug 14, 2026
6ec2dea
fix(lab): lock live artifact persistence
Wibias Aug 14, 2026
b1c8e37
fix(lab): lock conformance artifact persistence
Wibias Aug 14, 2026
aba59bb
fix(lab): lock fabric artifact persistence
Wibias Aug 14, 2026
52c92e8
test(lab): cover atomic artifact publication
Wibias Aug 14, 2026
1fe1eb5
fix(lab): make stale ledger recovery ownership-safe
Wibias Aug 14, 2026
1df92c2
test(lab): cover ledger lock lifecycle guards
Wibias Aug 14, 2026
d348130
test(lab): assert persistence lock release
Wibias Aug 14, 2026
981b868
fix(lab): clean up uninitialised ledger locks
Wibias Aug 14, 2026
d9655f3
fix(lab): preserve live transport failure classes
Wibias Aug 14, 2026
ecdbbda
fix(lab): preserve legacy pinned HTTP idle timeout
Wibias Aug 14, 2026
8c1f497
fix(lab): validate management read filters
Wibias Aug 14, 2026
118c90f
test(lab): cover invalid management read filters
Wibias Aug 14, 2026
c715534
fix(lab): propagate pinned body failures
Wibias Aug 14, 2026
6e23061
test(lab): cover compatibility follow-up regressions
Wibias Aug 14, 2026
5f59416
fix(lab): surface partial compatibility reads
Wibias Aug 14, 2026
a4cdf38
test(lab): verify load-more retry recovery
Wibias Aug 14, 2026
ae5498e
test(lab): avoid hardcoded retry error text
Wibias Aug 14, 2026
a13ebd8
fix(lab): harden CL-07 outcome validation
Wibias Aug 14, 2026
381ce11
test(lab): cover fractional CL-07 timestamps
Wibias Aug 14, 2026
05fcadf
fix(gui): allowlist HTTP protocol token
Wibias Aug 14, 2026
e66f864
fix(providers): recover static model discovery
Wibias Aug 14, 2026
a919667
docs(devlog): plan the 260815 dual-lane release train
lidge-jun Aug 14, 2026
690eeb3
fix lab automation runtime rebind
Wibias Aug 14, 2026
8acdb23
test lab activation runtime ownership
Wibias Aug 14, 2026
991e5ba
fix(lab): align passive signal API limit
Wibias Aug 15, 2026
b665724
fix(lab): validate passive subject IDs in CLI
Wibias Aug 15, 2026
04d6b08
test(lab): cover passive read surface validation
Wibias Aug 15, 2026
19af689
test(lab): restore passive test environment
Wibias Aug 15, 2026
8119b3c
feat(models): per-custom-model reasoning effort in the Models dashboard
RobinBially Aug 14, 2026
64272d7
docs: add custom-model reasoning steps dialog screenshot
RobinBially Aug 14, 2026
709b2ff
fix(models): enforce ladder invariants and constrain Pi effort levels
RobinBially Aug 14, 2026
4467a4b
docs: refresh custom-model dialog screenshot to final reasoning-effor…
RobinBially Aug 14, 2026
925fa93
docs: rename screenshot for a cache-free URL (old filename was CDN-ca…
RobinBially Aug 14, 2026
f88d69c
docs: hi-res screenshot of the reasoning-effort dialog (none..max)
RobinBially Aug 14, 2026
74a1a7b
docs: use the user's browser screenshot of the reasoning-effort dialog
RobinBially Aug 14, 2026
fbda693
feat(models): add none/minimal as declared reasoning efforts; fix rev…
RobinBially Aug 15, 2026
cce2995
fix(gui): localize the V2 mode-switch failure messages
lidge-jun Aug 15, 2026
8ccca16
fix(models): address CodeRabbit round 3 + carry-over findings
RobinBially Aug 15, 2026
a42dfe5
fix(models): CodeRabbit round 4 — init-ref seeding, capability-aware …
RobinBially Aug 15, 2026
a087d2a
fix(gui): localize reasoning-effort labels in the custom-model dialog
RobinBially Aug 15, 2026
ccdc7bc
refactor(adapters): centralize runtime adapter authority
Wibias Aug 15, 2026
ca0a512
test(adapters): derive routed tool conformance from registry
Wibias Aug 15, 2026
3b05a27
test(adapters): cover buffered freeform restoration
Wibias Aug 15, 2026
7a5fa85
fix(xai): merge root tool unions into one object schema
jonathanli12 Aug 15, 2026
98d37c2
docs(devlog): plan the dashboard-driven Codex app-server restart
lidge-jun Aug 15, 2026
f1ab210
feat(server): add a management route that restarts stale Codex app-se…
lidge-jun Aug 15, 2026
931ad7a
test(codex): use a non-home-shaped path in the redaction fixture
lidge-jun Aug 15, 2026
8c450a4
feat(gui): pair stop-proxy with restart-Codex in the sidebar
lidge-jun Aug 15, 2026
4f248f8
fix(gui): describe the restart honestly and reject contradictory resp…
lidge-jun Aug 15, 2026
0751839
test(codex): pin every service response against the contract guard
lidge-jun Aug 15, 2026
d8a9916
fix(gui): finish the restart-copy rename and drop a duplicated interface
lidge-jun Aug 15, 2026
12d0c34
feat(gui): surface Codex staleness on the models tab
lidge-jun Aug 15, 2026
1da366e
feat(codex): give Windows a real termination path for app-servers
lidge-jun Aug 15, 2026
02abe0a
fix(gui): stop a code comment from registering a phantom endpoint
lidge-jun Aug 15, 2026
5bc5fda
Merge PR #1708 into dev (triage 260815)
lidge-jun Aug 15, 2026
56522c6
Merge PR #1709 into dev (triage 260815)
lidge-jun Aug 15, 2026
0827bc6
Merge PR #1710 into dev (triage 260815)
lidge-jun Aug 15, 2026
43c5f9e
Merge PR #1712 into dev (triage 260815)
lidge-jun Aug 15, 2026
7c20f8c
Merge PR #1715 into dev (triage 260815)
lidge-jun Aug 15, 2026
e7651fc
Merge PR #1717 into dev (triage 260815)
lidge-jun Aug 15, 2026
a2f01ac
Merge PR #1719 into dev (triage 260815)
lidge-jun Aug 15, 2026
f0daa72
Merge PR #1720 into dev (triage 260815)
lidge-jun Aug 15, 2026
8c88f56
Merge PR #1705 into dev (triage 260815)
lidge-jun Aug 15, 2026
d8479bd
Merge PR #1706 into dev (triage 260815)
lidge-jun Aug 15, 2026
447b776
Merge PR #1714 into dev (triage 260815)
lidge-jun Aug 15, 2026
feeec96
Merge PR #1721 into dev (triage 260815)
lidge-jun Aug 15, 2026
13429be
test(adapters): use canonical mimo-free endpoint in registry authorit…
lidge-jun Aug 15, 2026
859bce8
Merge PR #1722 into dev (triage 260815)
lidge-jun Aug 15, 2026
ca5c10f
test(adapters): canonical mimo-free chat endpoint in tool conformance…
lidge-jun Aug 15, 2026
9edfecb
Merge PR #1723 into dev (triage 260815)
lidge-jun Aug 15, 2026
d1491ab
test(adapters): canonical mimo-free chat endpoint in buffered conform…
lidge-jun Aug 15, 2026
13672f2
Merge PR #1716 into dev (triage 260815)
lidge-jun Aug 15, 2026
57097a1
docs(devlog): 260815 open-PR triage plan, matrix, and execution record
lidge-jun Aug 15, 2026
ba20ce1
fix(lab): type-safe deferred export error in merged purge flow
lidge-jun Aug 15, 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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# CL-10 V1 revocation anchor clarification

Status: normative clarification to `010_cl10_public_evidence_export.md` section 17.

`PublicEvidenceRevocationV1` uses exactly one already-verified target bundle as its authority anchor. The revocation may contain between 1 and 256 sorted unique targets, but every target must resolve inside that one anchor bundle:

- a `bundle` target must equal the anchor bundle ID;
- a `record` target must name a record contained by the anchor bundle;
- mixed bundle/record targets are allowed only when they all resolve inside the same anchor bundle;
- multiple distinct bundle IDs in one V1 revocation are not supported and must be rejected;
- targets spread across multiple bundles are not supported even when those bundles use the same publisher key.

The publisher algorithm, key ID and exact public key in the revocation must match the already-verified anchor bundle before the revocation signature is authoritative. V1 therefore has no cross-key, key-rotation or multi-bundle authority bootstrap.

The phrase "one or more bundle/record IDs" in section 17 describes the bounded target list, not multiple independent bundle authority contexts. Where that wording could be read as authorizing a single V1 revocation across multiple bundles, this clarification is authoritative.

Supporting multi-bundle revocation requires a separately reviewed contract/schema revision that defines how all target bundles are supplied, verified, bounded and bound to the signing authority before persistence or application.
94 changes: 94 additions & 0 deletions devlog/_plan/260815_dual_lane_release_train/000_research.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# 260815 dual-lane release train — research

## Question

Is `origin/dev` @ `81ada7cd0` ready to be promoted into both `main` (npm
`latest`) and `preview` (npm `preview`), and what version numbers do the two
lanes take?

## Branch state at audit time

| ref | sha | package.json version | relation to dev |
|-----|-----|----------------------|-----------------|
| `origin/dev` | `81ada7cd0` | `2.18.0` | — |
| `origin/main` | `af737adb5` | `2.18.2` | 17 behind, 3 ahead (release bumps only) |
| `origin/preview` | `a0f5897d4` | `2.17.1-preview.20260814` | 50 behind, 1 ahead (release bump only) |

`main` is "ahead" of `dev` only by `release: v2.18.0/1/2`, three commits that
each change one line of `package.json`. No product code on `main` is missing
from `dev`.

## Why npm says 2.18.2 but the tree says 2.18.0

`gh run list --workflow release.yml` shows three consecutive failures before the
success:

- `7602613f4` (v2.18.0) — failed three times
- `cbc594f3c` (v2.18.1) — failed
- `af737adb5` (v2.18.2) — success, published, GitHub release `v2.18.2` exists

The workflow refuses to publish a version whose git tag already exists, so each
retry needed a fresh version number. The consequence: npm `latest` = 2.18.2
carries the v2.18.0 code, and every commit on `dev` after `ea96fbd5d`
(`release: v2.18.0`) is unpublished. That is 16 product commits.

## Promotion range content (`origin/main..origin/dev`)

21 files, +651/-73:

- `src/server/responses/input-admission.ts` — new module (169 lines): refuse
input that cannot fit the model context window
- `src/responses/state.ts` (+162) — stop `previous_response_id` replay from
compounding history
- `src/codex/shim.ts` (+105) — restore an unprobeable launcher on shim rollback
- `src/providers/registry.ts` — MiMo Free reasoning-effort cap
- `src/lib/errors.ts` — Windows ACL hardening failures classified 503 not 401
- `src/vision/index.ts`, `src/adapters/*`, `src/chat/inbound.ts`,
`gui/src/pages/*` — vision modality detection, GUI V2 mode-switch errors,
container-query stat rows

## Security-boundary scan

`git diff --name-only origin/main..origin/dev` filtered for
`auth|token|oauth|credential|workflow|release|secret` returns only
`package.json`. No authentication path, no credential handling, no
`.github/workflows/` change, no `scripts/release.ts` change. Per `AGENTS.md`
section "Review guidelines" this promotion needs no separate security review.

## Gate evidence on the exact dev head

- `ci.yml` Cross-platform CI — completed/success @ `81ada7cd0`
- CodeQL — completed/success @ `81ada7cd0`
- `service-lifecycle.yml` — last dev run success @ `875bb70c3`; the release
bump commit itself touches `package.json`, which is a service-gate trigger
path, so a fresh run on the release SHA is required and will be produced by
the promotion push.
- Local: `bun run typecheck` exit 0; `bun test` 11965 pass / 8 skip / 0 fail,
56382 expects, 739 files, 525.96s; `bun run privacy:scan` passed.

## Release-workflow constraints that shape the plan

From `.github/workflows/release.yml`:

1. Must dispatch from `main` or `preview`; any other ref is rejected.
2. `main` requires a stable semver version plus dist-tag `latest`; `preview`
requires a `-preview.` prerelease plus dist-tag `preview`.
3. The CI gate accepts only a push-event `ci.yml` success on the release
branch for the exact release SHA. A PR run for the same SHA does not
qualify.
4. `expected-sha` is mandatory and must equal the resolved branch head at
dispatch time.
5. Publishing refuses a version whose `v<version>` tag already exists.

`scripts/release.ts` automates bump, commit, push, wait for both workflows,
dispatch, and watch, and is mutating even without `--publish`.

## Version decision

- Stable lane: 2.19.0. The range adds a new admission module and new
provider-registry capability fields, which is feature-bearing, not a patch.
Precedent on this repository: `2.17.0` and `2.16.0` were cut for comparable
fix-plus-capability ranges.
- Preview lane: 2.19.0-preview.20260815, matching the established
`x.y.z-preview.YYYYMMDD` train (`2.17.1-preview.20260814`,
`2.15.1-preview.20260814`).
49 changes: 49 additions & 0 deletions devlog/_plan/260815_dual_lane_release_train/010_stable_lane.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 010 — stable lane: dev to main, npm latest 2.19.0

Work-phase wp2. Depends on nothing but the wp1 audit.

## Diff-level steps

1. Use the dedicated release worktree `/Users/jun/.codex/worktrees/rel-main`.
It currently has `preview-rel` checked out; the primary worktree holds
`dev` and must not move. Create or reuse a worktree that can hold `main`
without disturbing `/Users/jun/Developer/new/700_projects/opencodex`
(`dev`), `/Users/jun/.codex/worktrees/260728-preview/opencodex`
(`preview`), or `.tmp/rev3` (detached).
2. `git fetch origin`, `git checkout main`, `git reset --hard origin/main`
inside that worktree only.
3. `git merge --no-ff origin/dev -m "Merge dev into main: v2.19.0"`. Expect a
clean merge: `main` carries only version-bump commits, so the only possible
conflict is `package.json` version. Resolve to the dev content; the bump
happens in step 5.
4. `bun install` and `cd gui && bun install` in the fresh worktree before any
gate runs — otherwise typecheck fails with `Cannot find type definition file
for 'bun-types'`.
5. `bun scripts/release.ts 2.19.0 --publish`. It runs the clean-tree check,
`audit:high`, typecheck, `bun test --isolate tests`, `privacy:scan`, bumps
`package.json` to 2.19.0, commits `release: v2.19.0`, pushes `main`, waits
for `ci.yml` and `service-lifecycle.yml` on the release SHA, re-reads the
live remote head, then dispatches `release.yml` with `expected-sha` and
watches it.

## Failure recovery

If the helper exits after the bump was already pushed, do not rerun it blindly.
Rerun the failed workflow once, confirm the remote SHA did not move, then
dispatch manually:

```sh
gh workflow run release.yml --ref main \
-f version=2.19.0 -f tag=latest -f dry-run=false -f expected-sha=<sha>
```

If the `v2.19.0` tag already exists at a different commit, the publish is
refused; take the next patch number rather than deleting a tag.

## Exit criteria

- `git merge-base --is-ancestor 81ada7cd0 origin/main` exits 0
- `release.yml` run on `main` concluded success with the dispatched
`expected-sha`
- `npm view @bitkyc08/opencodex dist-tags --json` shows `latest: 2.19.0`
- GitHub release `v2.19.0` exists
39 changes: 39 additions & 0 deletions devlog/_plan/260815_dual_lane_release_train/020_preview_lane.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 020 — preview lane: dev to preview, npm preview 2.19.0-preview.20260815

Work-phase wp3. Runs after wp2 so the preview train never publishes content the
stable lane has not already accepted, and so `release.yml` concurrency
(group `release`, `cancel-in-progress: false`) does not queue two publishes.

## Diff-level steps

1. Work in a worktree holding `preview`.
`/Users/jun/.codex/worktrees/260728-preview/opencodex` already has
`preview` checked out at `6a8c74965`, which is stale relative to
`origin/preview` (`a0f5897d4`). Confirm ownership before touching it; if in
doubt, use a fresh worktree instead of resetting someone else's.
2. `git reset --hard origin/preview`, then
`git merge --no-ff origin/dev -m "Merge dev into preview: v2.19.0 train"`.
Also merge `origin/main` so the preview line carries the stable release
commit, following the precedent
`6a8c74965 Merge main into preview: v2.14.2 release (preview keeps its
prerelease version)`. On any `package.json` conflict, keep the preview
prerelease version; step 4 sets the final value.
3. `bun install` and `cd gui && bun install`.
4. `bun scripts/release.ts 2.19.0-preview.20260815 --publish`. The helper
derives dist-tag `preview` from the branch and rejects a non-prerelease
version on `preview`.

## Guard rails

- `release.yml` rejects a `preview` dispatch whose version lacks `-preview.`
and any dist-tag other than `preview`.
- The preview changelog baseline is lineage-relative: the newest prior release
of either channel reachable from HEAD. Merging `main` first keeps that
baseline at `v2.19.0` so the preview notes do not restate the stable range.

## Exit criteria

- `git merge-base --is-ancestor 81ada7cd0 origin/preview` exits 0
- `release.yml` run on `preview` concluded success with its `expected-sha`
- `npm view @bitkyc08/opencodex dist-tags --json` shows
`preview: 2.19.0-preview.20260815`
25 changes: 25 additions & 0 deletions devlog/_plan/260815_dual_lane_release_train/030_convergence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 030 — convergence verification and local restore

Work-phase wp4.

## Checks

```sh
git fetch origin --tags
git merge-base --is-ancestor 81ada7cd0 origin/main
git merge-base --is-ancestor 81ada7cd0 origin/preview
git log --oneline origin/main..origin/dev # expect empty
npm view @bitkyc08/opencodex dist-tags --json # latest 2.19.0, preview 2.19.0-preview.20260815
gh release view v2.19.0 --json tagName,publishedAt
gh run list --workflow release.yml --limit 4 # both runs success
```

## Restore

- Primary worktree `/Users/jun/Developer/new/700_projects/opencodex` must end
on `dev` with the same clean status recorded at wp1 start.
- Every other worktree keeps the branch it held before this unit:
`260728-preview` -> `preview`, `rel-main` -> `preview-rel`, `45a0` ->
`preview-dev`, plus the four `codex/*` feature worktrees and
`.tmp/rev3` (detached).
- Move this unit to `devlog/_fin/` once the terminal outcome is recorded.
146 changes: 146 additions & 0 deletions devlog/_plan/260815_gui_codex_restart/000_research.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# 000 — Research: dashboard-driven Codex app-server restart

Unit: `260815_gui_codex_restart`
Class: C4 (new management endpoint + cross-platform process control + GUI surface)
Trigger: SSH remote workspaces showed a stale model picker for ten days while the
on-disk catalog was current. The manual recovery was a hand-run
`ocx sync --restart-codex` on every host.

## 1. The defect this unit addresses

Codex builds a static model manager from the catalog once at app-server startup and
never rereads the file (`src/codex/app-server-processes.ts:764-770`). An app-server
that booted before a catalog write keeps serving the old roster forever. Every check
a user can run reads the file; the picker renders memory.

Detection already exists and already fired. The observed hosts printed
`WARNING: N Codex app-server process(es) still running` on each sync. Nobody read it:
the warning goes to stderr, and the process that keeps an SSH workspace's app-server
alive is the Codex app, not a human at a terminal.

## 2. What exists today

### 2.1 Two unrelated restart systems

| System | Entry | What it restarts | Where |
|---|---|---|---|
| Proxy self-restart | `POST /api/system/restart`, `ocx restart` | the **ocx proxy process** | `src/server/management/system-routes.ts:110` -> `acceptSystemRestart` (`src/server/management/system-restart.ts:344`) |
| Codex app-server stop | `ocx sync --restart-codex` (CLI only) | **Codex app-server / code-mode-host** children | `src/cli/dispatch.ts:199` -> `afterCatalogWriteHandleAppServers` (`src/codex/app-server-processes.ts:731`) -> `restartCodexAppServers` (`:662`) |

The load-bearing finding: **no management endpoint reaches
`restartCodexAppServers`.** The dashboard can restart the proxy and cannot touch a
stale app-server. A GUI button therefore needs a new backend route; it cannot be a
relabelled call to `/api/system/restart`.

### 2.2 Staleness classifier (already shipped)

`collectCodexAppServerCatalogState` (`src/codex/app-server-processes.ts:581`) returns
`fresh | stale | not_running | unknown` by comparing each app-server start time
against the catalog mtime, with a 5s memo when every io field is defaulted. Its only
consumer today is `GET /api/subagent-models`
(`src/server/management/agent-settings-routes.ts:601`).

Two deliberate conservatisms to preserve:

- Enumeration failure yields `unknown`, never `not_running` (#857) — a failed
enumeration must not read as "nothing running".
- The comparison is `<=`, because `ps lstart` is second-granularity. The observed
sujihome case had a 9-second gap and would otherwise have been misread as fresh.

### 2.3 Termination semantics

`restartCodexAppServers` re-resolves each pid immediately before signaling and
requires the same pid+command-line identity, so a recycled pid is never killed
(`:676-682`). It sends `SIGTERM` only, shares one ~2s exit deadline across all
targets, and never escalates to `SIGKILL` (`:706-714`).

The name overpromises: it stops processes and returns
`{ requested, stopped, surviving, failed }`. It never spawns a replacement. Whoever
owns the app-server (the Codex app, an SSH bootstrap) re-launches it on next use.

### 2.4 Platform matrix (verified against source)

| | Enumerate | Start time | Terminate |
|---|---|---|---|
| macOS | `/bin/ps -u <uid> -o pid=,command=` (`:274-279`) | `ps -o pid=,lstart=` batch (`:494-500`) | `process.kill(pid, "SIGTERM")` |
| Linux | `/proc/<pid>/status` uid + `/proc/<pid>/cmdline` (`:244-268`); missing `/proc` throws `procfs_unavailable` | `/proc/<pid>/stat` field 22 + `/proc/stat` btime (`:426-438`) | `process.kill(pid, "SIGTERM")` |
| Windows | trusted System32 PowerShell `Get-CimInstance Win32_Process` + `GetOwner` owner filter (`:330-352`) | `CreationDate` via CIM (`:516-523`) | `process.kill(pid, "SIGTERM")` |

`defaultListSnapshots` (`:375`) routes `win32` and `darwin` explicitly and sends
**every other platform** down the procfs path, so Linux is genuinely supported
rather than incidentally tolerated.

**The Windows gap.** On Windows `process.kill(pid, "SIGTERM")` is not a graceful
signal — it is `TerminateProcess`. The repository already knows this and already has
the correct ladder for the proxy: `src/lib/process-control.ts:150-165` uses
`taskkill /PID <pid> /T /F` on Windows and `SIGTERM`-then-`SIGKILL` elsewhere. That
ladder is **not** applied to app-servers. Consequences:

- No process-tree termination, so an app-server's own children can be orphaned.
- A target that ignores the request is only reported as `surviving`, with prose
telling the user to stop it by hand.

This matters more on Windows than anywhere else, because Windows has no Ctrl+Q quit
affordance for the Codex app: the user closes the window and the app-server keeps
running in the background holding its catalog snapshot.

### 2.5 Startup vs manual sync (#1046)

| | Classifier | Signals? | Silent when |
|---|---|---|---|
| `ocx sync` (no flag) | none — warns if any matching process is merely running (`:734-741`) | no | no write happened |
| `ocx sync --restart-codex` | none | yes, all matches | no write happened |
| startup / service | `collectCodexAppServerCatalogState` (`:790-800`) | **never** | `fresh`, `not_running`, `unknown` |

The startup path deliberately refuses to signal: killing an app-server on an
unattended boot would interrupt an in-flight turn, and "a human typing
`ocx sync --restart-codex` is consenting to that; a login is not" (`:772-780`).
That consent boundary is the design constraint for this unit — a dashboard click
**is** consent, which is exactly why the action belongs in the GUI.

### 2.6 GUI surfaces

- Sidebar foot: `gui/src/App.tsx:250` (`.sidebar-foot`) holds locale select, theme
toggle, stop button (`:267`), then `SidebarGithubRow`.
- Mobile stop button: `gui/src/App.tsx:205`.
- Stop handler `handleStop` with `confirm()` + pending state: `gui/src/App.tsx:172`;
transport `gui/src/stop-proxy.ts:41`.
- Circular satellite pattern `.sidebar-orb` (28x28, pill radius):
`gui/src/styles.css:336`; row container `.sidebar-github-row` `:333`.
- Icons are inline SVG with no library: `gui/src/icons.tsx:1`; `IconRefresh` `:23`,
`IconPower` `:35`.
- Existing restart UX to imitate (confirm -> draining -> reconnecting -> error):
`gui/src/components/MemoryObservabilityCard.tsx:351`.
- Models page head (already `justify-content: space-between`):
`gui/src/pages/Models.tsx:1716`, style `gui/src/styles.css:436`.
- i18n: `gui/src/i18n/en.ts` is the source of truth and defines `TKey` (`:2055`);
the other seven locales are `Record<TKey, string>`, so a missing key fails the
build rather than falling back silently.

## 3. Constraints carried into the phase docs

1. A new endpoint is required; do not overload `/api/system/restart`.
2. Never signal without an explicit user action. The endpoint is the consent
boundary and must not be invoked by polling or on render.
3. Preserve the pid+identity re-resolution and the `unknown`-on-enumeration-failure
conservatism; do not "simplify" either.
4. Do not quit or relaunch the Codex desktop app. No such code exists in this
repository and this unit does not add it.
5. Stay off the Lab core path (`src/router.ts`, `src/server/lifecycle.ts`,
`src/server/responses/core.ts`).
6. Locale parity is a build gate; all eight locales change together.

## 4. Work-phase map (dependency-ordered)

| Phase | Doc | Consumes |
|---|---|---|
| 1 | `010_phase1_backend_endpoint.md` | existing process-control primitives |
| 2 | `020_phase2_gui_sidebar.md` | phase 1's endpoint + response contract |
| 3 | `030_phase3_models_tab.md` | phase 2's client helper and i18n keys |
| 4 | `040_phase4_platform_hardening.md` | phases 1-3 landed and green |

Ordering is structural, not effort-based: the response shape must exist before a
client can render it, the client helper must exist before a second surface reuses
it, and platform termination behavior is hardened last because it changes the
meaning of a result the earlier phases already display.

Loading
Loading