Skip to content

Commit 4180067

Browse files
authored
docs(devlog): close wp2 planning after four audit rounds and record the wp3 review (#3036)
wp2 (#3023) is planned to an implementable state and closed as a planning cycle. It does not claim an implementation, because there is none; that is registered as wp6. Four audit rounds, four correctness holes, all in the same place: what a deduplicated ensure operation is allowed to answer for. The flight key grew one term per round, each because a reviewer produced a concrete cross-answering sequence. Round 8's is the one worth remembering. Candidate set, client version, mutation epoch and identities can all be unchanged while an entry expires mid-flight, so a second caller computes an identical key, joins, and the resolver's now is fixed from the flight's start, meaning the account it came to refresh stays a cache hit. ocx export, the surface #3023 actually reported, then returns short rows having refreshed nothing. Final key: (candidate set, client version, mutation epoch, identity vector, workset). The identity vector exists because a mutation epoch cannot fence external auth.json writers, so a caller holding a new identity could otherwise join an old-identity flight. Also corrected: the negative-memo TTL is pinned at 5000 ms, its publication is fenced on the captured identity vector, and expiry is measured from the absence observation rather than settlement, since a flight that spent 30s in a credential refresh must not hand out evidence treated as 5s fresh. Three regressions the draft claimed as red are already green and no longer claimed. "Zero credential validation" is restated as zero token refresh and zero network, because a pure cache read needs a cross-process credential-generation signal that does not exist. Wait policy settled: one whole-ensure flight, caught fail-closed and never aborted by a management timeout, sidecar joining at 0 ms, management surfaces waiting up to 3000 ms from the flight's start. MODEL_ROSTER_TIMEOUT_MS is not a total bound. Records the wp1 shipped outcome (#3035, merged as 4bdc0f6) and the wp3 review, which returned FAIL: the drain, budget split and ordering are right, but supersession reached the state tracking and not the writer, so an abandoned writer could still publish to the filesystem and orphan a temp. Still in repair, not part of this change. No source change.
1 parent 4bdc0f6 commit 4180067

4 files changed

Lines changed: 425 additions & 43 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# 008 — audit round 5: wp2 plan re-audit after wp1 landed
2+
3+
Auditor: sol-high subagent, read-only, run against `codex/prio70-train-260831` at
4+
`1b6b36b96` (wp1 merged into the branch). Verdict **FAIL** — five substantive
5+
corrections plus a stale-citation sweep. All are folded into `020`.
6+
7+
## Findings
8+
9+
1. **Citations shifted by wp1** (+45 lines or so in `model-entitlements.ts`).
10+
`:223``:323-325`; `:455``:524-526`, `:542-552`; `:500-506``:556-563`;
11+
`:539-550``:391-419`, `:592-602`; `:630``:686-693`; expiry `:509``:673`.
12+
Also `auth-api.ts:2015``:2016`, `server/index.ts:1155``:1158-1164`,
13+
`export-command.ts:169``:181`, `sidecar/candidates.ts:29``:35-40`.
14+
Still valid: `account-store.ts:131`, `model-rows.ts:50`,
15+
`model-routes.ts:352-354`, `Models.tsx:402-417`.
16+
17+
2. **"Zero credential validation in the steady state" is unachievable as written.**
18+
The cache key is `(accountId, clientVersion)` (`:323-325`), but freshness also
19+
compares `credentialIdentity` (`:517-522`), and obtaining the current identity
20+
reads `auth.json` or the account store (`:378-389`). A pure cache read would need
21+
a cross-process credential-generation signal that does not exist. Redefined
22+
target: **zero token refresh and zero network**, via an identity-only read, with
23+
the full `accountCredentialSnapshot` (`:391-419`) reserved for entries that are
24+
missing, stale, or identity-mismatched.
25+
26+
3. **The invalidation hook list was incomplete**, and one entry was wrong.
27+
`auth-api.ts:2015` is the branch, not the save (`:2016`), and hooking it misses
28+
new-account login at `:546`. Also required: pool CAS refresh
29+
(`account-store.ts:195-218`, called at `:705`, `:790`), owner/alias refresh
30+
(`:252-309`, called at `:858`), deletion/tombstone (`:312-320` via
31+
`account-lifecycle.ts:158`), and main-token refresh
32+
(`main-account.ts:133-163`, `:219`). External replacement of
33+
`~/.codex/auth.json` cannot be hooked at all, so a bounded identity probe has to
34+
remain. Active-account switch (`auth-api.ts:1630-1641`) is config-only and needs
35+
no clear. One cycle-free mutation epoch at every successful commit and tombstone,
36+
rather than a hook per call site.
37+
38+
4. **Awaiting in `listManagementModelRows` is structurally safe; rejecting is not.**
39+
The function is already async, every production caller awaits it, and
40+
`nativeModelRows` stays synchronous (`src/codex/catalog/metadata.ts:414`). But a
41+
rejection degrades sidecar to auth slots (`src/sidecar/candidates.ts:35-61`) and
42+
makes client-config answer 503 (`model-routes.ts:393-409`). The ensure must be
43+
non-throwing at its boundary, with a regression for each of those two outcomes.
44+
45+
5. **The 8-second figure is not a total bound.** `MODEL_ROSTER_TIMEOUT_MS` starts
46+
inside the roster fetch (`:438-445`); credential refresh can spend 30 seconds
47+
first (`main-account.ts:188-190`, `account-store.ts:743-746`). And flight dedup
48+
begins only *after* credential enumeration (`:524-552`), so abandoning a wait
49+
avoids duplicate roster fetches but not repeated credential work. The whole
50+
ensure operation has to be deduplicated, with the deadline anchored to the
51+
flight's start.
52+
53+
6. **Three of the four regression claims were false reds.** Fresh-zero-refetch,
54+
TTL+1-single-fetch, and unconfirmed-failure-caching are already green through
55+
`:517-552`, and the empty/all-filtered 15s behaviour is already covered by wp1's
56+
own tests (`tests/codex-model-entitlements.test.ts:672-749`). The genuine wp2
57+
reds are the **credential-read count** and the **logged-out negative memo**. The
58+
`/api/models` and client-config assertions are red only if the fixture supplies a
59+
usable credential and upstream roster — and client-config's expected fetch count
60+
of 1 is red because the current count is **0**, which is the defect itself. The
61+
CLI case is red only after its stub (`tests/cli-export-command.test.ts:51-57`) is
62+
replaced with the real handler. Baseline across the four files: 93 pass, 0 fail.
63+
64+
7. wp1 removed the concept the plan still named: there is no "confirmed-empty
65+
entry" any more (`:465-478`). The correct noun is "an empty or all-filtered
66+
**unconfirmed** entry within `MODEL_ROSTER_FAILURE_TTL_MS`".
67+
68+
## Open question — settled
69+
70+
The plan's default (wait out the refresh) is wrong, and my proposed flat 3s
71+
per-waiter deadline was also wrong: sequential sidecar calls
72+
(`src/server/management/config-routes.ts:589-593`) would accumulate up to 6s.
73+
74+
**Decision.** One whole-ensure flight carrying `{ startedAt, promise }`, caught
75+
fail-closed, and **never aborted by a management timeout** — the upstream work
76+
always runs to completion so the cache is populated for the next caller.
77+
78+
- Sidecar candidate paths join with a **0 ms** wait. They never stall; they read
79+
whatever is already cached.
80+
- `/api/models`, `/api/client-config`, integrations and `ocx export` wait up to
81+
**3000 ms**, measured from the flight's original `startedAt` rather than from each
82+
waiter's own arrival.
83+
84+
That keeps the useful one-shot window for `ocx export` — which is the surface
85+
#3023 actually reported — while a dashboard poll never stalls and converges within
86+
one 5s cycle.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# 009 — audit rounds 6-8: wp2 plan, and the wp3 implementation review
2+
3+
Two independent sol-high reviewers, both read-only. Recorded together because they
4+
ran concurrently.
5+
6+
## wp2 plan — rounds 6, 7 and 8 (all FAIL, all folded into `020`)
7+
8+
**Round 6.**
9+
10+
1. The whole-ensure flight was underspecified: a bare `{ startedAt, promise }` can
11+
cross-answer different candidate sets, client versions or credential epochs, while
12+
the per-entry dedup it wraps is keyed by account, identity and version
13+
(`:524-552`). Fixed by giving the flight a real key.
14+
2. The mutation-hook set was still incomplete: `native-profile-manager.ts:1307`,
15+
`:1335`, `:1449` write canonical `auth.json` during switch, rollback and recovery
16+
(transitions at `:1318`, `:1453`). These are OpenCodex's own writes, so they belong
17+
in the epoch.
18+
3. The two wait policies could not be expressed by `ensureCodexEntitlementFreshness(config)`
19+
as written, because sidecar reaches the same `listManagementModelRows`
20+
(`src/sidecar/candidates.ts:40`). Needs an options path.
21+
4. The credential-read regression was **false-green**: "zero token refreshes and zero
22+
fetches" is already satisfied today while the full credential snapshot still runs
23+
(`:592-601`). Must assert zero `accountCredentialSnapshot` calls.
24+
5. The negative-memo TTL was never pinned, and "converges within one 5s cycle"
25+
contradicted the acknowledged 30s credential refresh.
26+
27+
**Round 7.**
28+
29+
1. The epoch alone cannot fence external `auth.json` writers — the document says so
30+
itself — so a caller holding a new identity could still join an old-identity
31+
flight. The flight key now carries the identity vector
32+
`(accountId, credentialIdentity | null)`.
33+
2. The negative memo could still be published against a login that landed mid-flight.
34+
Publication is now fenced on the captured identity vector, and expiry is measured
35+
from the **absence observation**, not from settlement — a flight that spent 30s in
36+
a credential refresh must not hand out evidence that is treated as 5s fresh.
37+
38+
**Round 8 (final).**
39+
40+
The key still omitted the **workset**. Candidate set, version, epoch and identities
41+
can be unchanged while an entry expires mid-flight: a flight that started when B was
42+
fresh refreshes only A, B expires, a second caller computes the same key and joins,
43+
and the resolver's `now` is fixed from flight start (`:586`) so B stays a cache hit
44+
(`:517-522`). `ocx export` — the surface #3023 actually reported — then returns short
45+
rows having refreshed nothing. The key now includes normalized
46+
`needsRefreshAccountIds`, or joining requires a subset relationship.
47+
48+
wp2 is planned but **not implemented**. The document is now implementable; the work
49+
itself is the next cycle.
50+
51+
## wp3 implementation review — FAIL, one high finding
52+
53+
Reviewed commit `135f22932` (on top of Ingwannu's `aec717722`).
54+
55+
**Confirmed correct, no change needed:** drain ordering before the 2 MiB snapshot
56+
exclusion (`state.ts:1236-1247` vs `:1133`); the stable-fixed-point loop with no bare
57+
`Promise.race` (`:443-450`); the budget split `B=5000`/`R=4000` with the fallback
58+
receiving `R`; the shared ACL deadline reaching both the directory and temp hardeners
59+
(`spill-store.ts:197-227`, `:440-452`); test 2c's six `icacls` calls and its
60+
max-timeout assertion, which would genuinely detect a fresh 30s window. No
61+
MUST-NOT-CHANGE violation, no payload or token logging.
62+
63+
**HIGH — the abandoned writer can still publish, and can orphan a temp.** The async
64+
writer creates and fills its temp *before* awaiting file ACL hardening
65+
(`spill-store.ts:505-511`). At cap expiry the fallback marks the job cancelled and
66+
drops its tracking (`state.ts:417-422`) but takes no ownership of that temp; the drain
67+
returns (`:446-448`) and shutdown exits (`management-api.ts:278-280`,
68+
`cli/index.ts:365-370`). A resuming writer publishes *first* (`spill-store.ts:515-529`)
69+
and only then hits the cancellation check (`state.ts:257-260`). State overwrite
70+
prevention is airtight; filesystem publication and cleanup are merely narrow. The
71+
commit's own test encoded the violation as expected, asserting **two** publications
72+
(`tests/responses-state.test.ts:999-1000`).
73+
74+
**MEDIUM — the fixed point has no regression behind it.** The shutdown tests queue a
75+
single job (`:823-891`), so a one-shot tail await would pass them. The loop is right;
76+
nothing proves it stays right.
77+
78+
Both sent back for repair before wp3 lands.
79+
80+
## Residual risk carried
81+
82+
A real Windows host is still needed to prove NTFS inheritance, `icacls` timeout
83+
behaviour, and unlink semantics while `icacls` holds a path. Everything above was
84+
exercised through the repository's injected Windows/ACL runners.

0 commit comments

Comments
 (0)