Skip to content

Commit fe2b763

Browse files
codexlidge-jun
authored andcommitted
docs(devlog): fold two audit rounds into the phase 2 kernel plan
1 parent e1cc655 commit fe2b763

1 file changed

Lines changed: 83 additions & 20 deletions

File tree

devlog/_plan/260911_account_pool_unification/020_phase2_shared_kernel.md

Lines changed: 83 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,22 @@ are not:
2222
file in the round with four open PRs, which is also why the wp4b call-site
2323
wiring could not follow #4277 immediately.
2424

25-
This cycle therefore takes the kernel plus the generic consumer and leaves the
26-
Codex and Anthropic import swaps to a later layer. That ordering is not a
27-
concession: a kernel that nothing imports yet is still verifiable through the
28-
generic kind, and it keeps the contended files out of this PR entirely.
25+
This cycle takes the kernel, the Anthropic import swap and the generic consumer.
26+
Only the CODEX import swap is deferred, and it is deferred for free: once
27+
`pool-rotation.ts` re-exports the kernel, `src/codex/` keeps its existing import
28+
path and needs no edit at all. So the contended files stay out of this PR without
29+
the kernel being an orphan.
30+
31+
Two kinds of change are moving here and they carry different risk, which is why
32+
only one of them is behind the flag:
33+
34+
- **Relocation** is behaviour-preserving. Moving the state and primitives into
35+
`pool-kernel.ts` and re-exporting them changes no selection outcome, so it is
36+
not flagged. `git` history and a green existing suite are its proof.
37+
- **Behaviour** is flagged. The generic kind consuming `strategy` and
38+
`autoSwitchThreshold`, and the DTO reporting `inert: false`, only happen when
39+
`pool.kernel` is on. Flag off restores today's outcomes exactly, because the
40+
pre-kernel path is the same code reached through the shim.
2941

3042
Anchors confirmed present on `origin/dev`: `selectPriorityTier` :86,
3143
`pickRoundRobinAccount` :189, `notePoolRotationSuccess` :213,
@@ -69,29 +81,73 @@ and stamps `inert: true` on the generic DTO (`:40-54`, `:57-67`).
6981
## Change surface
7082

7183
NEW `src/oauth/pool-kernel.ts`
72-
- move `SelectionState`, `pickRoundRobinAccount`, `peekRoundRobinAccount`,
73-
`seedPoolRotationAccount`, `notePoolRotationSuccess`, `notePoolRotationFailure`,
74-
`selectPriorityTier`, and the strategy/sticky normalizers
84+
- move the WHOLE private `selectionState` map together with
85+
`pickRoundRobinAccount`, `peekRoundRobinAccount`, `seedPoolRotationAccount`,
86+
`notePoolRotationSuccess`, `notePoolRotationFailure`, `clearPoolRotationState`,
87+
`selectPriorityTier`, the priority parsers, `POOL_KEY_*` and the strategy and
88+
sticky normalizers. Moving a function subset while leaving the map behind would
89+
split one piece of state across two modules.
90+
- the move is safe: `pool-rotation.ts` imports only two TYPES,
91+
`OcxAccountPoolRotationStrategy` from `../types` and `GenerationContext` from
92+
`../lib/state-store-sweeper`. Neither creates a cycle into `src/oauth`.
7593
- add `genericPoolKey(provider) => \`generic:\${provider}\``
76-
- lift fill-first to `pickFillFirst(ids, afterId, hasHeadroom)` so both existing
77-
copies call one implementation
78-
- extend the reconcile sweep to `generic:*` keys, which `:270-276` currently skips
94+
- add a fill-first helper with the signature
95+
`pickFillFirst(ids, afterId, hasHeadroom, stableAll)`. The earlier three-argument
96+
shape was rejected by the audit: both existing copies walk a STABLE FULL roster
97+
and not the eligible subset, so dropping `stableAll` changes the wrap order
98+
whenever an ineligible id sits between two eligible ones.
99+
- extend the reconcile sweep to `generic:*`. `buildGenerationContext` already fills
100+
`oauthAccountKeys` from `listLiveOAuthAccountKeys` as `provider\0id` for every
101+
live OAuth provider, so the sweep needs no new field and no Codex dependency;
102+
today those keys are simply skipped as `valid === null`.
103+
104+
NOT moved, deliberately: the Codex fill-first copy in `src/codex/routing.ts` stays
105+
where it is. Deleting it is the only thing that would force an edit to a file lane
106+
L3 owns, and the audit flagged that as a blocker against this unit's own freeze.
107+
Only `anthropic-routing.ts` and the generic kind switch to the kernel helper, and
108+
the Anthropic caller keeps its weekly `exhausted5h` pre-filter rather than pushing
109+
that rule into the shared helper.
79110

80111
MODIFY `src/codex/pool-rotation.ts` — re-export the kernel so existing importers
81112
and `tests/codex-integration/codex-pool-rotation.test.ts` keep working unchanged.
82113

83-
MODIFY `src/oauth/generic-account-failover.ts` — route selection through the kernel
84-
by strategy: `quota` keeps `rankAccountsByHeadroom`, `round-robin` calls
85-
`pickRoundRobinAccount(genericPoolKey(name), ...)`, `fill-first` calls the lifted
86-
helper; seed on manual selection; note success and failure. Keep the presence
114+
MODIFY `src/oauth/generic-account-failover.ts` — branch BOTH paths on strategy, not
115+
just the proactive one. `preferredInitialAccount` currently no-ops when the active
116+
account is healthy and requires `hasHeadroomEvidence`, and the 429 path always ends
117+
in `rankAccountsByHeadroom`; leaving either unbranched keeps the strategy inert in
118+
practice even after the DTO says otherwise. `quota` keeps
119+
`rankAccountsByHeadroom`, `round-robin` calls
120+
`pickRoundRobinAccount(genericPoolKey(name), ...)`, and `fill-first` uses the
121+
kernel helper with `autoSwitchThreshold` as its headroom test. Keep the presence
87122
quorum, the `EXCLUDED_PROVIDERS` guard and the per-provider `health` cooldown.
88123

89-
MODIFY `src/oauth/pool-settings-capability.ts` — drop `inert: true`, add
90-
`stickyLimit`. MODIFY `src/types/provider.ts:512-518` comments and
91-
`oauth-account-routes.ts:395` to accept `stickyLimit`.
92-
93-
MODIFY `src/codex/routing.ts` and `src/oauth/anthropic-routing.ts` — import from
94-
the kernel instead of holding their own copies.
124+
MODIFY `src/server/management/oauth-account-routes.ts` — a manual account selection
125+
must seed the cursor, or the operator's pick immediately loses to sticky
126+
round-robin. Today that PUT calls only `forgetGenericFailoverRoster`, which clears
127+
the presence cache and not the rotation state. Add
128+
`seedPoolRotationAccount(genericPoolKey(provider), accountId)` beside it, mirroring
129+
what `resetAnthropicRoutingForManualSelection` already does for Anthropic.
130+
`clearGenericFailoverHealth` is the wrong map and `clearPoolRotationState` wipes
131+
where seeding is wanted.
132+
133+
MODIFY `src/oauth/pool-settings-capability.ts` — report `inert` from the flag rather
134+
than as a type literal. While `pool.kernel` is off the generic DTO must keep saying
135+
`inert: true`, because nothing consumes the strategy yet and the reversibility rule
136+
below requires the old behaviour to be exactly restorable. The literal becomes a
137+
computed field and only turns false once the kernel is on.
138+
139+
Known readers of that field, all of which move in the same PR:
140+
`src/cli/account-extended.ts` (forces generic auto-switch inactive),
141+
`tests/server/account-pool-management-api.test.ts` and
142+
`tests/cli/cli-account-pool-verbs.test.ts`. The GUI does not read it.
143+
Also lift the `stickyLimit` rejection at `oauth-account-routes.ts:395` and update
144+
`src/types/provider.ts:512-518` comments.
145+
146+
MODIFY `src/oauth/anthropic-routing.ts` — import from the kernel. `src/codex/`
147+
keeps importing `./pool-rotation`, which is now a re-export, so this layer needs
148+
no edit inside lane L3's files at all. The audit confirmed the shim is sufficient:
149+
`routing.ts`, `auth-api.ts`, `account-priority.ts` and
150+
`state-store-registrations.ts` all keep their existing import path.
95151

96152
## Reversibility (audit blocker, mandatory)
97153

@@ -109,6 +165,13 @@ the kernel instead of holding their own copies.
109165

110166
## Tests
111167

168+
Audit record: the A-phase reviewer returned PASS-WITH-FINDINGS with two blockers,
169+
both folded above. The first was that lifting fill-first out of its Codex copy
170+
would have forced an edit inside lane L3's freeze. The second was that dropping
171+
`inert: true` unconditionally contradicts this document's own reversibility rule,
172+
which requires `pool.kernel` to default off and the old behaviour to be exactly
173+
restorable.
174+
112175
- `tests/codex-integration/codex-pool-rotation.test.ts` — unchanged behaviour
113176
through the re-export (`pickRoundRobinAccount` `:270`, `selectPriorityTier` `:111`)
114177
- `tests/oauth/generic-oauth-failover.test.ts` — a configured strategy changes the

0 commit comments

Comments
 (0)