Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 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
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
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.
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.

119 changes: 119 additions & 0 deletions devlog/_plan/260815_gui_codex_restart/001_design_alternatives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# 001 — Design alternatives and rejected options

Survey and comparison material split out of the phase documents per LEXICO-SPLIT-01.
Phase docs carry decided invariants and executable diffs; the reasoning that produced
those decisions lives here.

## 1. Endpoint shape: new route vs flag on `/api/system/restart`

`/api/system/restart` computes its drain deadline at
`src/server/management/system-restart.ts:360` from `MEMORY_DRAIN_RESTART_MS`
(`src/lib/system-restart-contract.ts:12`, 60s) and terminates the process serving the
request. It is guarded by an HMAC capability bound to pid+port
(`src/lib/system-restart-contract.ts:44-57`) for exactly that reason.

Stopping a Codex app-server is a different action: bounded, synchronous, and it never
touches the proxy. A flag on the existing route would force one capability rule to
cover two blast radii — too strict for the new action or too loose for the old one.

**Decided:** a separate `POST /api/system/codex-restart`, plus a cheap
`GET /api/system/codex-app-server` for the state reading.

## 2. Where the staleness reading comes from

Three options were considered.

**A — reuse `GET /api/subagent-models`.** It already returns `catalogState` from the
same classifier (`src/server/management/agent-settings-routes.ts:601-605`). Rejected:
that route also assembles the full subagent model roster, so a models-page banner
would pay for work it does not use. It is also semantically owned by the subagents
page (`gui/src/pages/Subagents.tsx:116-125`).

**B — a dedicated read endpoint.** `GET /api/system/codex-app-server` returns the
classifier verdict and nothing else.

**C — poll on a timer.** Rejected outright: enumeration shells out to `ps`,
`/proc`, or PowerShell CIM. A dashboard timer that enumerates processes every few
seconds is the kind of hidden work the models workspace explicitly avoids
(`gui/src/pages/Models.tsx:325-330` gates even its catalog poll on tab activity).

**Decided:** B, fetched once on mount and on explicit refresh.

**Naming hazard found during audit:** `Models.tsx` already binds `catalogState`
(`gui/src/pages/Models.tsx:331`) to the `useDataSurface` resource state of
`/api/catalog`. It is an unrelated concept. The new value is named
`appServerState` everywhere to keep the collision from ever forming.

## 3. Sidebar layout: full-width rows vs icon satellites

The sidebar foot currently stacks full-width rows: locale, theme, stop
(`gui/src/App.tsx:250-270`). The GitHub row already demonstrates the alternative —
a labelled element with circular 28x28 satellites at the trailing edge
(`gui/src/styles.css:333-346`).

Rejected: adding a second full-width row for restart. Two adjacent full-width
buttons, one of which stops the proxy, invites a misclick and doubles the vertical
cost of a foot that already holds four rows.

Rejected: placing the new control inside `SidebarGithubRow`. That component owns
repository affordances; proxy lifecycle is not one of them.

**Decided:** a new `.sidebar-action-row` container reusing the existing
`.sidebar-orb` satellite class, with both actions as icons.

**Mobile constraint found during audit:** the mobile stop button is widened to a
44x44 touch target (`gui/src/styles.css:2115`). A bare 28x28 `.sidebar-orb` on
mobile would be a regression, so the mobile rule sizes both orbs to 44x44.

## 4. Windows termination: ladder vs leave-as-is

**A — apply the proxy's ladder.** `killProxy` uses
`%SystemRoot%\System32\taskkill.exe /PID <pid> /T /F` on Windows and
SIGTERM-then-SIGKILL elsewhere (`src/lib/process-control.ts:150-167`).

**B — keep SIGTERM-only and let the `partially_stopped` response carry the news.**

Windows `process.kill(pid, "SIGTERM")` is already `TerminateProcess`, so on that
platform option A is not an escalation — it adds child-process cleanup to a
termination that was hard either way. On Unix, SIGTERM-then-SIGKILL *is* a real
escalation, and a second harder signal to a process that may be mid-turn asks a
harsher consent than a restart click gives.

**Decided:** asymmetric. Windows gets `taskkill /T /F`; Unix stays SIGTERM-only.
The asymmetry is recorded in the function's doc comment so a later reader does not
"fix" it into symmetry.

**Resolver note found during audit:** `resolveTrustedWindowsTaskkillExe` does not
exist. `src/lib/windows-elevation.ts` has `resolveTrustedWindowsPowerShellExe`
(`:192`) and `resolveTrustedWindowsSchtasksExe` (`:206`), both anchored to a trusted
system directory with a test-override slot. The new resolver follows those, not the
looser `process.env.SystemRoot` string interpolation in `process-control.ts:157`.

## 5. Testability: dynamic imports vs an injectable service

The first draft had the route dynamic-import production modules and call them with
no arguments. That cannot be driven by a test: `ManagementApiDeps`
(`src/server/management/context.ts:11`) has no seam for sync, classification, or
termination, and the six planned route scenarios all require one.

Rejected: adding three seams to `ManagementApiDeps`. That type is already large and
these three belong together.

**Decided:** a service module with its own io interface. The route becomes a thin
adapter, and every branch is driven at the service level.

## 6. Delivery: direct push vs pull request

`src/AGENTS.md:20` classifies management-API changes as a security boundary, and
`MAINTAINERS.md:48-52` requires explicit security review for them; GUI changes need
a screenshot in the PR description (`MAINTAINERS.md:24-27`).

The repository owner directed a direct `--no-verify` push to `dev` for this unit.
That is the maintainer exercising their own merge authority, not an agent bypassing
review, so the instruction stands.

**Decided:** push directly as instructed, and compensate for the skipped local hook
by running the full gate set (typecheck, test, privacy scan, GUI lint/test/build)
before pushing, plus the Linux cross-check on `lidge`. Repository CI remains the
final enforcement layer on `dev`.

Loading
Loading