Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
aef76f4
fix(update): preserve service-home authority during recovery
luvs01 Oct 15, 2026
29f3414
fix(update): allow guarded package-only updates under initial uncerta…
luvs01 Oct 15, 2026
9897f45
test(update): relocate service-home registrations to avoid merge conf…
luvs01 Oct 15, 2026
2d8102d
test(windows): follow extracted update teardown helper
luvs01 Oct 15, 2026
1da3dfb
test(update): isolate fallback fixtures from host service managers
luvs01 Oct 16, 2026
c238a67
Merge current dev and keep service-home test registration within the …
github-actions[bot] Oct 21, 2026
31ee7fe
Merge dev and repair update recovery checks
luvs01 Oct 22, 2026
08f0494
test(update): recognize wrapped npm registry queries in lease fixtures
luvs01 Oct 22, 2026
48c8da4
fix(update): preserve authority through delegated recovery
luvs01 Oct 22, 2026
c61bedc
test(update): follow delegated tray handoff helper
luvs01 Oct 22, 2026
4ca5868
fix(update): preserve tray recovery state when handoff rejects
luvs01 Oct 22, 2026
302668c
fix(update): preserve service-home authority during recovery
luvs01 Oct 15, 2026
21e46e7
fix(update): allow guarded package-only updates under initial uncerta…
luvs01 Oct 15, 2026
773a904
test(update): relocate service-home registrations to avoid merge conf…
luvs01 Oct 15, 2026
915dcc0
test(windows): follow extracted update teardown helper
luvs01 Oct 15, 2026
2219f90
test(update): isolate fallback fixtures from host service managers
luvs01 Oct 16, 2026
236b738
test(update): recognize wrapped npm registry queries in lease fixtures
luvs01 Oct 22, 2026
bb7c840
fix(update): preserve authority through delegated recovery
luvs01 Oct 22, 2026
7600a84
test(update): follow delegated tray handoff helper
luvs01 Oct 22, 2026
d5cb4ca
fix(update): preserve tray recovery state when handoff rejects
luvs01 Oct 22, 2026
1178f3e
fix(update): restore merge-resolution content dropped by the flat rebase
luvs01 Oct 23, 2026
eadd533
fix(update): re-check service-home authority after replacement-time a…
luvs01 Oct 23, 2026
8244012
fix(update): gate reclaim mutations and shim probe on live authority
luvs01 Oct 23, 2026
234fced
fix(update): preserve reclaim authority refusals across transient rec…
luvs01 Oct 23, 2026
c84a322
merge: preserve original service-home authority history
luvs01 Oct 23, 2026
315502d
Merge origin/dev into update service-home authority
luvs01 Oct 23, 2026
9e5a917
fix(test): promote Bun 1.4.4 with independent runner role verification
luvs01 Oct 23, 2026
1deb67d
docs: align Docker Bun comments with unified role defaults
luvs01 Oct 23, 2026
41f08ff
test: use a draining TLS origin for early upload replies
luvs01 Oct 23, 2026
a9c3d12
test: confine draining origin to buffered HTTP/1 upload
luvs01 Oct 23, 2026
f011a94
merge: align test-runner role branch with dev 2.93.0 dependency baseline
luvs01 Oct 24, 2026
8b4c34c
Merge branch 'dev' into codex/test-runner-role-verification-20261023
lidge-jun Oct 24, 2026
629fc35
Merge commit '26598df2c4c07d781e344ac6119953fed77ecb27' into codex/pr…
luvs01 Oct 24, 2026
2430fd7
Merge commit '8b4c34c7e6103314dcb061f0f82a8eaafdb4d386' into codex/pr…
luvs01 Oct 24, 2026
5d1dee3
Merge commit '8ebe46b2076445d100cef1e52af4d98d78f8f1e1' into codex/71…
luvs01 Oct 25, 2026
5b6358c
Merge origin/dev c2f3d8b42b into codex/update-service-home-authority-…
lidge-jun Oct 26, 2026
3b23d9a
fix(update): gate ghost recovery and handle retained ownership leases
lidge-jun Oct 26, 2026
ad87532
fix(update): gate pinned-start preparation mutations
lidge-jun Oct 26, 2026
4cb19fd
fix(update): recheck recovery home authority after ownership awaits
lidge-jun Oct 26, 2026
655cec3
Merge remote-tracking branch 'origin/dev' into la-7154-fix3
lidge-jun Oct 26, 2026
3f2ed2d
test(update): tie post-stop home transition to stopped runtime
lidge-jun Oct 26, 2026
f173a0a
test(update): preserve async Node updater in production-body fixture
lidge-jun Oct 26, 2026
8c480f5
test(update): inject Windows async package transaction in home fixture
lidge-jun Oct 26, 2026
367e7d0
fix(update): revalidate staged swap and foreground recovery authority
lidge-jun Oct 26, 2026
2aebb3d
Merge current origin/dev into la-7154-fix4
lidge-jun Oct 26, 2026
5e981a5
test(update): cover delegated staging and final liveness home checks
lidge-jun Oct 26, 2026
8e13310
fix(update): retain home refusal across service stop and ownership ve…
lidge-jun Oct 26, 2026
23f10d5
test(update): preserve absent-service foreground stop control
lidge-jun Oct 26, 2026
e4b8335
fix(update): authorize every recovery mutation and terminal success
lidge-jun Oct 26, 2026
714ea24
Merge current origin/dev into PR 7154 round-six guard fix
lidge-jun Oct 26, 2026
cd4f2d2
fix(update): preserve fresh wrapper and scanner authority
lidge-jun Oct 26, 2026
b99de55
Merge round-7 update authority fixes with current origin/dev
lidge-jun Oct 26, 2026
0c46a8e
test(update): reject blocking mutation argument evaluation
lidge-jun Oct 26, 2026
c92bf59
test(update): cover computed names and formatting accessors
lidge-jun Oct 26, 2026
f62b968
test(update): refresh extracted restart source contracts
lidge-jun Oct 26, 2026
c0c8e08
test(update): require inert guarded mutation expressions
lidge-jun Oct 26, 2026
7305bee
Merge current origin/dev into la-7154-fix9
lidge-jun Oct 26, 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
222 changes: 183 additions & 39 deletions bin/ocx.mjs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ and, afterwards, a folder next to the package that cannot be deleted because `bu
The updater stops the proxy, installs the new version into a separate staging folder, checks it,
and only then swaps it in. When the install step fails, the swap never happens: the previous version
stays installed and runnable, and the updater restarts the previous background service (or the
previous proxy) before it exits. You do not need to reinstall to get back to a working proxy.
previous proxy) before it exits, when ownership and service-home checks authorize recovery.

If the initial service-home check is inconclusive, for example because Bun is missing or a service
query fails, the updater can replace an idle package without stopping or restarting the proxy.
A successful package-only update prints a manual restart hint and explains that service ownership
could not be verified. Check `ocx service status` and the registered installation before restarting.
Changes in authority, unknown durable runtime ownership, Desktop supervision, a live package
runtime or an unresolved teardown receipt still block package replacement; an inconclusive check is not permission to control them.

Check that with:

Expand Down
13 changes: 13 additions & 0 deletions scripts/test-layout/layout-update-home.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"update-windows-wrapper-authority.test.ts": "update",
"update-mutation-scanner.test.ts": "update",
"update-mutation-boundaries.test.ts": "update",
"update-mutation-guard.test.ts": "update",
"update-home-launcher.test.ts": "update",
"update-home-recovery.test.ts": "update",
"update-recovery-authority.test.ts": "update",
"update-recovery-boundaries.test.ts": "update",
"update-home-worker.test.ts": "update",
"update-service-home-policy.test.ts": "update",
"update-service-home.test.ts": "update"
}
2 changes: 1 addition & 1 deletion scripts/test-layout/layout.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": 1,
"root": "tests",
"explicitShards": ["layout-clients.json", "layout-codex-collision.json", "layout-agent-assertion.json", "layout-chat.json", "layout-replay.json", "layout-adapters.json", "layout-jev.json", "layout-workbuddy.json", "layout-combos.json", "layout-credit-renewal.json"],
"explicitShards": ["layout-clients.json", "layout-codex-collision.json", "layout-update-home.json", "layout-agent-assertion.json", "layout-chat.json", "layout-replay.json", "layout-adapters.json", "layout-jev.json", "layout-workbuddy.json", "layout-combos.json", "layout-credit-renewal.json"],
"explicit": { "combo-description.test.ts": "routing", "combo-description-catalog.test.ts": "codex-integration", "codex-provider-routing.test.ts": "codex-integration", "config-native-reasoning-retention.test.ts": "config", "server-native-reasoning-retention-merge.test.ts": "server", "server-native-reasoning-retention.test.ts": "server", "responses-retained-reasoning-stream-recovery.test.ts": "responses", "openai-native-hosted-search-history.test.ts": "adapters/openai", "responses-native-compact-search-history.test.ts": "responses", "config-hosted-search-projection.test.ts": "config", "gcp-credential-boundary.test.ts": "adapters/google", "gcp-sa-marker.test.ts": "adapters/google", "gcp-sa-adapter.test.ts": "adapters/google", "management-gcp-credential-input.test.ts": "server", "codex-preserve-signed-in.test.ts": "codex-integration", "cli-account-preservation.test.ts": "cli", "cli-provider-list-active.test.ts": "cli", "service-state-isolation.test.ts": "service", "compaction-history-framing.test.ts": "responses", "catalog-native-max-context.test.ts": "codex-integration", "protocol-argon-thought-boundary.test.ts": "responses", "service-log.test.ts": "service", "messages-native-empty-inline.test.ts": "claude-integration", "catalog-auto-review-override.test.ts": "codex-integration", "management-auto-review-settings.test.ts": "server", "management-auto-review-auth.test.ts": "server", "prefer-model-alias-ids.test.ts": "server", "responses-mimo-envelope.test.ts": "responses", "responses-call-id-linkage.test.ts": "responses", "bridge-terminal-refusal.test.ts": "adapters", "loop-terminal-refusal.test.ts": "images", "web-search-terminal-refusal.test.ts": "web-search", "combo-terminal-refusal.test.ts": "routing", "server-terminal-refusal-wiring.test.ts": "server", "incomplete-reason-normalization.test.ts": "responses", "advisor-retention.test.ts": "advisor", "responses-native-key-cooldown.test.ts": "responses", "responses-run-turn-key-readmission.test.ts": "responses", "qoder-spawn-readmission.test.ts": "adapters", "api-key-selection-cooldown.test.ts": "providers", "api-key-auto-pause-distribution.test.ts": "providers", "explicit-key-pause-oauth-boundary.test.ts": "providers", "server-chat-native-key-selection.test.ts": "server", "compact-key-selection.test.ts": "responses", "messages-native-key-selection.test.ts": "claude-integration", "coding-agent-windows-dispatch.test.ts": "adapters", "coding-agent-native-empty-argv.test.ts": "windows",
"service-proxy-env.test.ts": "service", "openai-chat-standalone-bare-block.test.ts": "adapters/openai",
"owner-registry-isolation.test.ts": "config",
Expand Down
17 changes: 17 additions & 0 deletions src/server/port-reclaim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ export type ReclaimListenPortOptions = WaitForPortOptions & {
killFn?: (pid: number) => void;
dropTcpFn?: (port: number) => number | { dropped: number; skippedIpv6: number };
isAvailableFn?: (port: number, hostname?: string) => Promise<boolean>;
/**
* Synchronous authority gate re-checked after each availability await and
* immediately before every destructive action (killFn / dropTcpFn). Returning
* false denies the mutation and ends the reclaim: the port is reported
* not-freed without touching processes or TCP rows. A throwing gate denies too.
*/
mayMutateFn?: () => boolean;
sleepMs?: (ms: number) => Promise<void>;
};

Expand Down Expand Up @@ -516,6 +523,8 @@ export async function ownsIpv4LoopbackListener(
* revalidates immediately before each kill.
* Never overrides a rejected ocx verifier result. Never drops TCP rows while a
* rejected live or protected ocx listener owns the port, or when the scan failed.
* A denied `mayMutateFn` ends the wait: no kill or TCP-row drop may run under
* an authority that no longer holds.
*/
export async function reclaimListenPort(
port: number,
Expand All @@ -539,13 +548,18 @@ export async function reclaimListenPort(
const dropTcpFn = opts.dropTcpFn ?? dropWindowsTcpRowsForLocalPort;
const isAvailableFn = opts.isAvailableFn ?? isPortAvailable;
const sleep = opts.sleepMs ?? ((ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms)));
const mayMutate = (): boolean => {
try { return opts.mayMutateFn?.() !== false; } catch { return false; }
};

const deadline = Date.now() + timeoutMs;
let lastScan = 0;
const killed = new Set<number>();

for (;;) {
if (await isAvailableFn(port, hostname)) return true;
// The authority may have changed while the availability probe was in flight.
if (!mayMutate()) return false;
if (Date.now() >= deadline) return false;

if (Date.now() - lastScan >= scanIntervalMs) {
Expand Down Expand Up @@ -585,6 +599,7 @@ export async function reclaimListenPort(
}
if (!killed.has(pid)) {
// Revalidate immediately before termination.
if (!mayMutate()) return false;
if (isAliveFn(pid) && verifyOcxFn(pid) === pid && mayKillThis) {
try {
killFn(pid);
Expand Down Expand Up @@ -619,6 +634,8 @@ export async function reclaimListenPort(
// Reset those IPv4 TCBs (and ghost LISTEN rows) so the configured port can bind again —
// without killing the browser process. Only safe when no live foreign/protected listener remains.
if (dropTcpRows) {
// Same gate as the kill path: denied authority never runs SetTcpEntry.
if (!mayMutate()) return false;
try {
dropTcpFn(port);
} catch {
Expand Down
Loading
Loading