diff --git a/.gitignore b/.gitignore index 00394f7dee..1973231f05 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,6 @@ tests/**/.tmp-* # `git add` three separate times and reached `dev` once — see # tests/ci-workflows/repo-hygiene.test.ts, which fails if any path here becomes tracked again. go/ + +# Rust native helpers keep their reproducible sources and lockfile in git, never local artifacts. +native/**/target/ diff --git a/.npmignore b/.npmignore index acf3a0c4d0..cfbe1d3750 100644 --- a/.npmignore +++ b/.npmignore @@ -19,6 +19,7 @@ gui/eslint.config.* gui/bun.lock # misc +native/remote-workspace-helper/target/ *.test.ts *.map .DS_Store diff --git a/devlog/_plan/260912_accounts/000_plan.md b/devlog/_plan/260912_accounts/000_plan.md index df54929c99..ed3e4574c1 100644 --- a/devlog/_plan/260912_accounts/000_plan.md +++ b/devlog/_plan/260912_accounts/000_plan.md @@ -46,3 +46,5 @@ Two design follow-ups encountered inherited-model capacity errors; one same-hand ## Roadmap cycle outcome Independent design reflection and A re-audit passed with the source restrictions in 001_roadmap_audit.md. B freezes the contracts as documentation only. C checks document paths/numbering and git whitespace; local product suites NOT RUN. D next direction: execute 010_callback.md independently, then the remaining dependency-ordered cycles. Runtime behavior has not improved yet; the rejected hypotheses were native history identity by sentinel alone, attempt timing inferred from untimed attempts, and one-shot implying one physical request through a retrying primitive. + +History P split:048_history_identity.md supplies stable publication identity and fenced writer capture before050 history. This is a new foundation cycle, registered in the same goalplan; intended manual chain history-identity → history → capacity. It is independent of reset-first. Staged login samples are omitted until a fenced post-publication observation; native history remains nondurable and excluded from capacity. diff --git a/devlog/_plan/260912_accounts/020_eligibility.md b/devlog/_plan/260912_accounts/020_eligibility.md index 94719a4f58..734bc42d89 100644 --- a/devlog/_plan/260912_accounts/020_eligibility.md +++ b/devlog/_plan/260912_accounts/020_eligibility.md @@ -17,3 +17,5 @@ Field chain: existing excludedPlans config create/save/load → same normalized MODIFY existing `tests/codex-integration/codex-pool-plan-exclusion.test.ts`: replace last-account soft fallback test with none/preview none; test normalized plan update and explicit fixed route. Extend account API/CLI and card tests for reason and renewal clearing. Sync ownership docs and providers configuration pages that describe the old soft exception. Retain source attribution of #4238; no recarry of already-landed commits. Local tests/build/typecheck NOT RUN. Hosted CI plus rendered artifact from final tip supplies execution proof. Exclusion reason derives from the routing config plan, not a display-only freshly observed plan if persistence failed. This preserves truth between selection and explanation. + +P revalidation on dev d6fb87197a: keep exported existing predicate in routing.ts; pass runtimeConfig into both poolAccountDto calls. Alongside closed selectionExcludedReason include selectionExcludedPlan from the same routing config when excluded, so a display-only fresh WHAM tier cannot mislabel the reason. CLI/card render this policy plan. Exact GUI type owner is hooks/useCodexAccountPool.ts; component type file re-exports it. Docs source is guides/codex-integration.md in every locale; revise all-excluded fallback paragraphs there. Callback D delivered PR4352 and left hosted acceptance open; this cycle is independent from current dev. diff --git a/devlog/_plan/260912_accounts/021_eligibility_delivery.md b/devlog/_plan/260912_accounts/021_eligibility_delivery.md new file mode 100644 index 0000000000..ed4405ac17 --- /dev/null +++ b/devlog/_plan/260912_accounts/021_eligibility_delivery.md @@ -0,0 +1,9 @@ +# Plan exclusion completion + +Built on already-landed #4238, independently from current dev d6fb87197a. Existing normalized predicate is shared with the account DTO; both preview and real automatic fallback reject excluded plans when no eligible account remains. Explicit account-qualified routes retain normal auth, pause and entitlement checks. Native main remains exempt. + +CLI and dashboard display the policy's routing-plan reason separately from credential health and a possibly newer display-only plan. The automatic Set-as-next action is suppressed for excluded rows because pinning does not bypass this policy; explicit account-qualified routes remain available. All nine UI locale catalogs and eight affected integration guides are synchronized. Source ownership docs link the canonical plan-exclusion contract. + +Regression sources cover all-excluded preview/resolve, renewal, explicit route with pause/reauth, API reasons, CLI normalization and card display/renewal. No new test file or dependency. Local suites/build/typecheck/install: NOT RUN. Hosted CI and rendered preview remain pending. Source searches: isCodexAccountPlanExcluded, getPoolAccountPlan, poolAccountDto, CodexAccountEntry, selection guards and excludedPlans docs; reused the existing predicate rather than a parallel policy. + +Prior callback cycle delivered PR4352 and remains pending hosted verification. This is an independent dev PR, with no callback code and no manual chain dependency. diff --git a/devlog/_plan/260912_accounts/022_eligibility_ci_repair.md b/devlog/_plan/260912_accounts/022_eligibility_ci_repair.md new file mode 100644 index 0000000000..eb33329f4c --- /dev/null +++ b/devlog/_plan/260912_accounts/022_eligibility_ci_repair.md @@ -0,0 +1,7 @@ +# Eligibility hosted regression repair + +Exact-head run34680496052 at a1f24df5ed90848f32d2499303b22620d91eed42 failed in Linux test4/4 job103523074988 and macOS2/2 job103523074889. The reset-ticket source oracle still required the old next-session guard without plan exclusion. The implementation correctly retained all health guards and added plan exclusion. + +The oracle now also requires the plan-exclusion guard, preserving ticket co-render and all health checks. A rendered regression fixture confirms eligible accounts show next-session and tickets together; excluded accounts retain tickets and omit next-session. No production code changed. No assertion was removed or loosened. Local suites/build/typecheck/install NOT RUN; remote final-head verification follows. + +Other failures in these runs concern Cline registry/localization/asset/test-layout and native history restoration. They are recorded in task scratch with exact job logs for owner integration; no other-lane files were changed. The parent-updated branch was fast-forwarded without rebase or merge commit. Hostgoal remains blocked and FSMB is unchanged; no new completed PABCD cycle is claimed for this source repair. diff --git a/devlog/_plan/260912_accounts/030_reset.md b/devlog/_plan/260912_accounts/030_reset.md index 875a6dccc5..be34c48373 100644 --- a/devlog/_plan/260912_accounts/030_reset.md +++ b/devlog/_plan/260912_accounts/030_reset.md @@ -14,3 +14,11 @@ Additional MODIFY `src/oauth/pool-settings-capability.ts` and `src/server/manage Field chain: CLI/GUI strategy creation → canonical PUT parser → config.accountPoolStrategy write → config load + canonical GET parser → pool rotation/preview/failover, CLI and GUI display. Audit every existing strategy comparison/default, not just the union. No schema migration or new dependency. Exact contributor diff remains `.tmp/accounts-20260912/pr4080.diff` during planning; changes are adapted to current callers before B. Extend regression sources for canonical PUT/GET/save/reload, legacy endpoint, non-Codex rejection, tied/missing/elapsed resets, threshold zero, priorities, affinity and failover. Existing #4080 test cases are retained/adapted. Update all source ownership docs; screenshot of final rendered strategy control is included with PR. Local suites/build/typecheck/install NOT RUN; final head hosted CI supplies proof. #3376 remains partial until history/capacity; monthly/Anthropic/latest-first scope is reported separately. + +P revalidation: #4080 head unchanged. Current pool-rotation.ts is a compatibility facade, so Codex parser/normalizer live in existing src/oauth/pool-kernel.ts leaf and are reexported. Canonical GET DTO and PUT parser use Codex-specific parser only for kind=codex. Use existing resetAtToMs for both seconds/milliseconds before comparing future deadlines. Existing manualPreferenceBlocks remains at promotion; reset-first affinity calls mayRebindAffinityForQuota so pool.cacheAffinity retains a healthy bound account until genuine exhaustion. Current config parser preserves accountPoolStrategy through passthrough, so canonical save/reload regression is required. User limits unchanged; previous eligibility D delivered PR4361 with hosted/render pending, reset-first remains independent. + +A1 accepted: independent spark/reserve quota scopes use the existing quota strategy consistently for initial selection, preview, affinity and alternates; shared 5h/weekly reset timestamps are not their evidence. Add private `accountPoolStrategyForScope(config, quotaScope)` in routing.ts: normalize the configured Codex strategy, then return quota when reset-first and isIndependentCodexQuotaScope(scope), otherwise the normalized strategy. Use it in pickUnboundStrategyAccount, pickAlternateCodexAccount, previewReusableAffinityAccount and reevaluateAffinityQuota. Shared promotion remains scope-guarded and uses configured normalized strategy. Config remains reset-first, DTO shows configured value and docs explain effective independent-scope fallback. Tests oppose shared reset versus usage order, include scoped cooldown and unchanged shared cursor. + +Config decision: retain existing passthrough compatibility rather than add an unrelated disk-validation policy in this carry. Canonical/legacy management writes validate through Codex parser, and all runtime consumers normalize malformed direct config values to quota as before. Explicit invalid parser/API and save/reload tests verify this boundary; no whole-config reset is introduced. + +C source audit found threshold=0/cacheAffinity=true could still rebind at100%. Accepted and fixed with early disabled-threshold return before reset-first affinity evaluation; new preview/resolve/all100 fixtures cover both cache settings. Failure recovery stays separate. Local suites NOT RUN; source re-audit and hosted CI pending. diff --git a/devlog/_plan/260912_accounts/031_reset_delivery.md b/devlog/_plan/260912_accounts/031_reset_delivery.md new file mode 100644 index 0000000000..4b20ad2950 --- /dev/null +++ b/devlog/_plan/260912_accounts/031_reset_delivery.md @@ -0,0 +1,13 @@ +# Reset-first carry follows the current pool contract + +Adapts #4080 ecf6b4e48a4c2992c296fada2caf6a8132313eaa by Terry Tan. The Codex parser now lives in the existing shared kernel leaf, canonical and legacy settings round-trip the configured strategy, and the GUI offers it only on Codex. Existing runtime priority, manual preference and cache-affinity behavior is preserved. Mixed reset units are normalized before ordering; independent model quota scopes retain existing quota selection. + +Regression sources include original reset-first cases plus mixed units, cacheAffinity on/off, scoped fallback/health/shared cursor, canonical and legacy persistence, non-Codex rejection and GUI empty-response normalization. UI hints reflect current cache-affinity and scope semantics. Local tests/build/typecheck/install: NOT RUN. git diff --check is whitespace evidence only; independent source review and hosted final-tip CI/render evidence follow. + +Source search: accountPoolStrategy, normalizeAccountPoolStrategy, resetAtToMs, pool/settings, mayRebindAffinityForQuota, manualPreferenceBlocks and all strategy consumers. Existing pool-kernel and routing owners extended; no new dependency or separate pool implementation. Config passthrough behavior preserved deliberately; write routes validate through the Codex-specific parser. + +Co-authored-by: Terry Tan + +C source audit found threshold=0/cacheAffinity=true could still rebind at100%. Accepted and fixed with early disabled-threshold return before reset-first affinity evaluation; new preview/resolve/all100 fixtures cover both cache settings. Failure recovery stays separate. Local suites NOT RUN; source re-audit and hosted CI pending. + +Independent C re-audit PASS at eddc8c7b08; nonblocking oracle improvement accepted: assert actual alternate b, excluding null as a false recovery result. Hosted/runtime acceptance remains pending. diff --git a/devlog/_plan/260912_accounts/048_history_identity.md b/devlog/_plan/260912_accounts/048_history_identity.md new file mode 100644 index 0000000000..d8273fab4e --- /dev/null +++ b/devlog/_plan/260912_accounts/048_history_identity.md @@ -0,0 +1,19 @@ +# Bind quota history to credential publication identity + +New foundation cycle history-identity, C4 credential metadata, before history and capacity. Current source saveCodexAccountCredential publishes a new generation, while normal refresh CAS also increments generation and preserves replacedAt. Neither generation equality nor a millisecond timestamp alone establishes durable quota-history continuity. Reuse the credential store and its mutation lock; no new store or token-derived fingerprint. + +MODIFY src/types/accounts.ts CodexAccountCredentialRecord: optional private quotaHistoryIdentity UUID, not credential material and never projected to API/CLI. MODIFY src/codex/account-store.ts: every explicit save creates a fresh UUID; saveCodexAccountCredentialIfGeneration and commitRefreshedCodexCredentialWithAliases preserve each record's own UUID, including aliases. Deletes retain no old history identity. Existing credential projection excludes metadata automatically. + +Add PoolQuotaWriter type in dependency-free src/codex/quota-types.ts: +```ts +export interface PoolQuotaWriter { accountId: string; credentialGeneration: number; historyIdentity: string } +``` +Add capturePoolQuotaWriter(accountId, dispatched:{accessToken,chatgptAccountId,generation}) in account-store.ts. Under existing withCredentialMutationLockSync, read record and require exact dispatched credential and generation, live/nondeleted state. For a legacy/malformed missing UUID initialize one once and persist under that lock without changing credential generation; do not mint on normal reads. A mismatch returns undefined. Lock/persistence failures at this optional evidence boundary return undefined, never fail the request. Credentials remain transient and never enter returned proof. Existing valid UUID capture needs only read matching record, no mutation lock or rewrite; legacy slow path rechecks under lock. + +Add isPoolQuotaWriterLive(writer): compare current live record's UUID and generation. Add poolQuotaHistoryIdentity(accountId): read valid current UUID only, never initialize or mutate. These separate append admission from retention, which matches UUID across ordinary refresh. Both are narrow production interfaces for the next history layer, not public management capabilities. + +Tests extend existing codex-account-store.test.ts: new saves unique; same-millisecond explicit replacement changes UUID; refresh preserves; alias refresh preserves distinct destination identities; stale dispatched access/generation/account cannot capture; legacy initialization stable and does not advance generation; metadata omitted from getCodexAccountCredential/load compatibility projection; delete/recreate invalidates old writer. Local tests/build/typecheck/install NOT RUN. Hosted cumulative history/capacity tip verifies these regression sources. Source security review separate from runtime proof. + +Field chain: explicit save/legacy capture creates UUID → existing atomic credential record serialization → existing read with UUID validity checked at history boundary → capture/live/retention helpers → next cycle's auth-context/WHAM/header history admission. All explicit record reconstructions are enumerated: save at161, validation spreads186/234 preserve, refresh279/338 preserve, alias366 preserves its own, deletion387 drops. Source ownership docs updated with private metadata semantics. No credential/token/string values enter docs or log output. + +A implementation checks accepted: legacy tag init uses plain persist, preserving both generation and credentialMutationEpoch. UUID validation stays at history boundary; malformed optional metadata never discards usable credentials. Catch read/hardening failures as well as lock/write failures and return no optional proof. Capture excludes the reserved native-main sentinel. If a CAS caller supplies a different upstream account identity, rotate the history UUID instead of treating that as ordinary same-account refresh. diff --git a/devlog/_plan/260912_accounts/049_history_identity_delivery.md b/devlog/_plan/260912_accounts/049_history_identity_delivery.md new file mode 100644 index 0000000000..6af02c6026 --- /dev/null +++ b/devlog/_plan/260912_accounts/049_history_identity_delivery.md @@ -0,0 +1,7 @@ +# Quota history identity foundation + +Adds a private random publication UUID to pool credential records. Explicit saves rotate it, refresh CAS preserves it for the same upstream account, and aliases retain distinct identities. Captured writer proofs require exact dispatched credential generation and access/account pairing; legacy identity initialization occurs under the existing lock without changing the credential generation or mutation epoch. Read/lock/write failure yields no optional proof. Metadata never enters credential-only projection. + +Regression sources cover refresh versus same-time replacement, aliases, deletion/recreation, legacy stable initialization, stale capture, malformed metadata, secret-free proof and identity-changing CAS. The latter rotates owner history and does not propagate the new identity into old aliases. No new test file/dependency. Local suites/build/typecheck/install NOT RUN. Source checks are not runtime proof; hosted final cumulative history/capacity tip remains required. + +Structural decision: proof type stays in quota-types.ts (type-only), credential record/lock/persistence stay in account-store.ts, future pure history leaf consumes plain evidence. Rejected generation-only retention because ordinary refresh increments it; rejected timestamp identity because publication can share a millisecond. The small foundation is the first ordinary manual-chain PR, then history, then capacity. No merge. diff --git a/devlog/_plan/260912_accounts/050_history.md b/devlog/_plan/260912_accounts/050_history.md index fab9e603be..1e870236fc 100644 --- a/devlog/_plan/260912_accounts/050_history.md +++ b/devlog/_plan/260912_accounts/050_history.md @@ -20,4 +20,30 @@ Field chain: creation is guarded quota commit; serialization is existing atomic Reflection REF-04: fixed aggregate bounds: 64 account identities, 4096 rows, 2 MiB serialized history payload and 4 MiB whole cache read bound. During append/hydrate evict oldest observed rows, tie-break account key; prune accounts absent from authoritative roster. Never include dynamic raw account identities in logs. History retains actual per-window provenance (response-header or WHAM where available), reset boundary and window family; partial inherited values do not count. Overlarge/malformed cache read fails to empty history without blocking newest quota. Tests include many-account overflow, byte overflow, deterministic ties and remove/restart. -A1 accepted: native main history is deliberately NOT hydrated from disk in this slice. It can be sampled in-process only after identity observation and cleared on identity change; persistence omits __main__. Pool history envelopes bind stable configured account identity and stored credential generation, pruning mismatches on hydrate. This avoids attributing offline identity replacements to an old main label. Acceptance explicitly covers main replacement while stopped and account-id reuse. Main cross-restart history remains a documented limitation; bounded durable history is provided for stored pool accounts. +A1 accepted: native main history is deliberately NOT hydrated from disk in this slice. It can be sampled in-process only after identity observation and cleared on identity change; persistence omits __main__. Pool history envelopes bind a stable private publication UUID; hydration prunes identity mismatches, while ordinary generation changes on refresh retain prior observations. This avoids attributing offline identity replacements to an old main label. Acceptance explicitly covers main replacement while stopped and account-id reuse. Main cross-restart history remains a documented limitation; bounded durable history is provided for stored pool accounts. + +P refinement depends on new048 history-identity cycle. Adopt HIST-01..06: generation gates each physical sample; private random publication UUID persists through refresh and changes on explicit save. Capture PoolQuotaWriter before upstream calls, refresh it after replay token resolution, and forward through every WHAM/WS/HTTP/compact/warmup path. Omit staged login/reauth samples until first post-publication fenced observation; do not retrofit ambient provenance. Native main is excluded from durable endpoint/capacity in this slice. Raw QuotaObservation carries observedAt, wham|response-header source, bounded windows with account|spark family and short|weekly|monthly name, percentage/resetAtMs/duration/primary provenance; no arbitrary upstream label. Envelope private identity binds samples but is omitted from read DTO. Retain best-effort single-writer atomic cache semantics; no multi-process merge/durability claim. Read endpoint GET /api/codex-auth/quota/history?accountId=&limit=<1..200>; CLI ocx account history openai [--limit N] [--json]. Unknown/deleted404, invalid/duplicate selector400, emptyhistory200. No upstream call on reads. + +## Executable history child contract after identity foundation D + +Parent PR4375/e9007429c5 provides PoolQuotaWriter and store capture/live/retention helpers. This child depends on that branch; the capacity child follows this one. Previous D delivered only identity and deferred hosted proof. + +NEW src/codex/quota-history.ts, pure leaf (imports quota types and pure account-id only): closed HistoryWindow family account|spark, window short|weekly|monthly, usedPercent, optional resetAtMs/windowSeconds/monthlyIsPrimaryWindow; HistorySample observedAt/source/credentialGeneration/windows; private envelope identity/samples. CodexQuotaHistory owns append/hydrate/read/clear/reconcile/serialize. Keep 200 samples/account,30days,64accounts,4096samples,2MiB conservative serialized-byte budget; max5 windows/sample. Track per-sample byte costs incrementally, evict by observedAt then accountId and insertion order. Hydration rejects an over-limit envelope before admitting rows (>64 accounts, >200 rows/account, >4096 total samples or >2MiB serialized payload); accepted rows are validated and sorted by timestamp before age retention. Unknown fields/labels never survive. Read returns deep copies; private identity never reaches API. No filesystem/config/store import in the leaf. + +MODIFY quota.ts: own the history instance and optional history:{version:1,accounts:{...}} in existing quota-cache version1. Hydrate history before latest-quota six-hour TTL filtering; native-main never hydrates/records in this durable layer. Replace unbounded file allocation with a local fd/readSync loop capped at4MiB+1; oversized/corrupt cache is a cache miss, never an inference failure. Keep latest in-memory state untouched. Existing debounced atomic persistence serializes bounded history, so no new timer/store and no multi-process merge claim. Clear and roster reconcile remove history-only identities too; read compares current store UUID before returning, even after offline replacement. + +setAccountQuotaFromParsed gains optional sixth QuotaObservationEvidence {writer,observedAt,source,raw}. After config/main write guards, append only when writer.accountId matches and isPoolQuotaWriterLive. Convert only fresh raw percentages into closed history windows, normalizing resets with resetAtToMs. Account short/weekly/monthly map directly; Spark uses existing short label plus a new canonical weekly-label constant shared with the WHAM parser. No arbitrary custom labels. Credits-only/metadata-only updates append nothing. The legacy latest-snapshot merge remains unchanged. applyAccountQuotaFromUpstreamHeaders options adds poolWriter; builds evidence from original parse result BEFORE custom-window carry. Missing writer/evidence preserves latest cache but appends no trusted sample. + +MODIFY auth-context.ts pool union with poolQuotaWriter?:PoolQuotaWriter, capture immediately after getValidCodexToken before dispatch. MODIFY core.ts WS closure, rejected-first response, ordinary HTTP, and refreshedAuthCtx to forward/re-capture exact serving writer; compact.ts refresh/rejection follows same rule. MODIFY quota-auto-refresh.ts pool warmup captures before I/O. MODIFY auth-api.ts WHAM initial and refreshed replay capture before fetch, commitPoolQuotaResponse carries writer and sends raw parsed result with observedAt after JSON read; keep all current generation/mayPublish checks. Staged login quota writes intentionally omit history until a post-publication observation; native main and legacy updateAccountQuota omit it. No token material is added to response objects/logs. + +GET /api/codex-auth/quota/history?accountId=&limit=<1..200> is read-only cached data, no upstream/auth refresh/warmup. Add before existing /quota handler; registry entry+capability map. Validate exactly one accountId, optional single numeric limit and no unknown query fields. Invalid/main400, unknown configured pool404, known account200 even empty. DTO: {accountId,observations:[{observedAt,source,windows}],retention:{maxObservations:200,maxAgeDays:30},truncated:boolean}; omit UUID and credential generation. Public array follows ascending observed time, limit chooses newest rows. Capacity is added only in next child. + +NEW src/cli/account-history.ts exports cmdAccountHistory(args,deps). Shape `ocx account history openai [--limit N] [--json]`; reject other provider/main/extraargs before any network. Use resolveBaseUrl/apiJson/apiError/proxyUnreachable from account-api owner. JSON prints DTO; human output prints observed time/source/window/percent/reset and no-observation state. Wire lazy dispatcher and help/capabilities; source-only skill surface generator allowed (not product suite). + +Tests: new pure codex-quota-history.test.ts (register both layout maps), existing quota-store integration hydration harness for raw-vs-carried, writer mismatch/refresh/replacement, stage omission, native omission, clear/reconcile and disk limits; authenticated server route tests+CLI transport fixture. No local runtime execution. All touched source-area ownership docs and English+Korean account command docs synchronized; other translations must not contradict additions. + +Read unavailability refinement: undefined current identity (legacy/missing/unreadable) returns empty/unavailable evidence without deleting a retained envelope. Only a confirmed different UUID or authoritative roster removal clears it; this avoids transient permission/read errors destroying history. Restored matching identity may expose retained valid rows again. Cache eviction/expiry remains bounded. + +Deferred history-plan review findings (actual A entry was refused because persisted active work phase is tun): reuse pre-clamp invalid-percentage checking for all WHAM primary/secondary/tertiary and additional Spark windows, and response-header raw usage fields; any invalid numeric/nonfinite/out-of-range percentage omits the ENTIRE trusted observation while leaving legacy display behavior unchanged. Add before-clamp history parser/evidence guard so clamped values cannot masquerade as measured percentages. Hydration rejects an entire over-limit history payload (>64accounts,>200rows/account,>4096rows,>2MiB) instead of slicing by lexical key/array position; bounded accepted rows are sorted by observedAt before retention. Tests include65th-newestaccount and unordered rows. These need fresh independent A review when history resumes. + +Implementation review HIST-IMPL-01 accepted: compact final response now records actualoutcomeCtx poolwriter beforebuffering, coveringordinary/401replay/alternate; rejectedfirstaccount retains its separateexistingwrite, so everyresponse contributesonce. Add compactregression withquotaheaders onoriginalsuccess andA429→Bsuccess. This sourcework is user-authorized whilehostgoal remainsblocked; no FSM A/B/C/D advancement claimed. diff --git a/devlog/_plan/260912_accounts/051_history_delivery.md b/devlog/_plan/260912_accounts/051_history_delivery.md new file mode 100644 index 0000000000..0e38a408c8 --- /dev/null +++ b/devlog/_plan/260912_accounts/051_history_delivery.md @@ -0,0 +1,9 @@ +# Bounded raw quota history implementation + +Extends publication identity foundation #4375 with a pure bounded history leaf, existing quota-cache persistence, fenced WHAM/HTTP/WS/compact/warmup producers, a management read route and account history CLI. Invalid upstream percentages never become trusted samples after display clamping. Native-main, staged-login and legacy unproven setters are omitted. + +Regression sources cover chronological retention, limits/corrupt disk, private-field stripping, generation/identity changes, raw-versus-carried windows, cached API auth/validation, CLI argument rejection and compact serving-account attribution. Independent source review identified missing compact final-response capture; it was added with ordinary/alternate regressions. CLI skill surface regenerated by its source-only generator, not a product build or suite. Local suites/build/typecheck/install NOT RUN. + +This child targets the existing history-identity branch at19cbe826d8. The pending plan-only commit was rebased onto the parent-updated branch; foundation product bytes were unchanged. Host goal remains blocked; actual FSMB(tun) remains untouched under explicit user instruction. These are authorized source implementation and independent reviews, not a claimed new persisted PABCD cycle. Complete hosted verification belongs to the eventual cumulative history/capacity tip; no merge or issue closure. + +Review corrections: human CLI formats out-of-range dates as unknown; byte-limit fixtures now carry valid populated data and exercise append-byte eviction before row limits; authenticated API returns a populated sanitized history; WHAM refresh/replay, HTTP/WS and real warmup producer fixtures assert history including stale WS replacement rejection. Local suites remain NOT RUN. diff --git a/devlog/_plan/260912_accounts/060_capacity.md b/devlog/_plan/260912_accounts/060_capacity.md index 931e0b6646..d222802811 100644 --- a/devlog/_plan/260912_accounts/060_capacity.md +++ b/devlog/_plan/260912_accounts/060_capacity.md @@ -2,17 +2,31 @@ Cycle capacity depends on history. Source: `src/usage/log.ts` already persists accountLogLabel, timestamp, reported/estimated usage and per-attempt attribution; `src/codex/account-label.ts` owns safe labels. Use those existing records instead of storing credentials or duplicating request attribution. -NEW `src/codex/quota-capacity.ts`: a pure estimator receives copied raw history and account-attributed reported usage observations. For each short/weekly/monthly window, pair adjacent fresh percentage observations only when reset identity matches, time increases and percentage delta is positive. Sum reported token usage in that interval, count per-attempt records once, exclude estimated/local/unattributed usage and reset/refund crossings. Estimate tokens per full window as observedTokens * 100 / percentageDelta; aggregate defensible intervals with median and report sampleCount plus observed-token lower-bound caveat. No valid interval returns null, never zero or a fabricated capacity. Bounded scan is invoked on management request, never routing; estimation is informational and does not overrule live quota. +NEW `src/codex/quota-capacity.ts`: a pure estimator receives copied raw history and account-attributed reported usage observations. For each short/weekly/monthly window, pair adjacent fresh percentage observations only when reset identity matches, time increases and percentage delta is positive. Sum reported token usage in that interval, count per-attempt records once, exclude estimated/local/unattributed usage and reset/refund crossings. Estimate tokens per full window as observedTokens * 100 / percentageDelta; aggregate defensible intervals with median and report sampleCount plus an explicit low-confidence inference caveat. No valid interval returns null, never zero or a fabricated capacity. Bounded scan is invoked on management request, never routing; estimation is informational and does not overrule live quota. ```ts export type CodexCapacityEstimate = { window: "short" | "weekly" | "monthly"; estimatedTokens: number; sampleCount: number; - confidence: "observed-lower-bound"; + confidence: "low"; }; ``` MODIFY history read API/CLI projection to attach per-window estimates with sample count and caveat; expose an existing account-card detail surface only if it can be honestly rendered and verified. Field chain: pure estimator creation; API JSON serialization; existing typed CLI/client deserialization; explicit informational display consumers. No persisted estimate schema needed. Tests feed independently hand-calculated intervals, 0% delta, reset rollover, missing timestamps/identity, cross-account records, retries, estimated usage, and extreme numeric input. Sync quota/usage ownership docs and user configuration guidance. Full closure of #3376 requires both history and meaningful capacity; reset-first alone stays partial. Local suites NOT RUN; hosted final cumulative tip is the verifier. A2 accepted: use readUsageSnapshotForManagement; if truncatedPrefixBytes>0, entriesTruncated, entriesDropped>0, missing revision, or invalid timing then return insufficient-evidence with no estimate. Treat each request as interval [timestamp, timestamp+durationMs] (request-log.ts:1039/1072); include only requests wholly contained in a quota-observation interval. Boundary-spanning requests contribute nothing. For included requests count reported physical attempts matching the exact pool label once; do not count both request total and attempts. Without attempts accept request-level reported usage only with matching label and no recovery ambiguity. Native main is excluded from token capacity because its historical label cannot establish identity after replacement. Current pool logLabel must be unique; legacy fallback labels/id reuse require insufficient evidence unless continuity is proven by history generation. Same-reset positive deltas only. Hand-worked boundary-spanning, truncation, missing identity and retry rows are mandatory regression fixtures. + +P future refinement from history sidecar: do not call estimate a mathematical lower bound. It is an observed effective token estimate under rounded/delayed quota and local coverage assumptions. Admit only single-send reported nonestimated attempts; present-but-empty attempt arrays cannot fall back to parent totals. Deduplicate requestId+ordinal and reject conflicting duplicates. Use interval (left,right] with whole request containment to avoid zero-duration double counting. Existing parser can skip malformed rows without a rejected counter: report retained-valid-ledger-only assumption explicitly or add rejected-row metadata before claiming complete coverage. Loglabel alone is not history identity; history publication UUID and current stable unique configured label must bind sample period. All source tests remain hosted-only. + +## Resumed capacity contract + +Depends on history PR4404/0d98205fcd. Add pure quota-capacity.ts estimator receiving public sanitized observations, validated usage rows and the current explicit unique random pool logLabel; no native-main/fallback labels. Per account short/weekly/monthly, pair adjacent raw observations only with same source/reset boundary, increasing localtime and percentage delta>=1. Count only whole requests within (left,right], single-send reported nonestimated nonlocal attempts matching that label. Presence of an empty attempts array never falls back to request totals. Deduplicate requestId; conflicting duplicates yield insufficient evidence. No inferred absolute attempt start. Exclude boundary-spanning requests and unknown/multisend usage; no valid pair yields insufficient-evidence. + +Use reported totalTokens or input+output exactly once, not reasoning/cache detail additions. Median effective tokens per100percentage over defensible intervals, sampleCount explicit. Output confidence low and assumptions array: rounded/delayed quota, only retained valid proxy ledger rows, label continuity assumed inside the observation interval, external usage not observed. This is an observed effective estimate, never a provider token limit or proven lower bound. The private credential publication UUID must match before/after async ledger read; current explicit logLabel and uniqueness must still match config. Any mismatch yields insufficient-evidence, not mixed identity. No estimate is used for scheduling. + +Extend existing history GET result with capacity:{status:estimated|insufficient-evidence,estimates:[{window,estimatedTokens,sampleCount,confidence:low}],reason?,assumptions}. Cached history remains visible on ledger read failure. Use readUsageSnapshotForManagement; reject truncatedPrefixBytes/entriesTruncated/entriesDropped, missingrevision and >10000 retainedrows before estimator scan. This deliberately does not attest missing/rejected historical ledger lines; assumptions state that limitation. CLI history humanoutput renders estimates and sample counts/caveat; JSON carries fullobject. No new config, timer, persistence, GUI surface or inference call. + +Tests handcomputed10→20% plus1000reportedtokens→10000estimate; mixed sources/reset/refund/0delta/rounding/timestampintervals, duplicate request IDs, absent-vs-empty attempts, multisend, local/estimated/unattributed tokens, nonfiniteoutput, truncatedledger andidentitychangedawait. Sample storage/read provides current publication evidence; retrospective label continuity is explicitly low-confidence inference, not independently verified identity. This clarification replaces earlier mathematically unprovable lower-bound wording without reducing raw-data/identity fences. Local suites/build/typecheck/install NOTRUN. Independent source design/review plus final cumulative tip hostedCI required; hostFSMblockedB remains unchanged. + +Implementation refinements: reject absent physicalattempts, deduplicate ordinals, countonlyaccountfamily/sharedmodelscope and matchingwindowduration/primaryprovenance withresetnotelapsed. Preserve locallyAnswered duringexistingusagenormalization so capacitycanexcludeit. CaptureUUIDbeforehistoryread and recheckbefore/afterasyncledgerread; usefullboundedhistory forestimationindependentofdisplaylimit. Labels re-read fromcurrentruntimeconfig. diff --git a/devlog/_plan/260912_accounts/061_capacity_delivery.md b/devlog/_plan/260912_accounts/061_capacity_delivery.md new file mode 100644 index 0000000000..da434e3e12 --- /dev/null +++ b/devlog/_plan/260912_accounts/061_capacity_delivery.md @@ -0,0 +1,7 @@ +# Informational effective quota capacity + +This child of #4404 estimates observed reported tokens per100percentage from bounded raw observation intervals. It preserves private publication UUID checks and requires an explicit unique pool log label. The estimate is low-confidence with disclosed rounding, retained-valid-row, external-usage and label-continuity assumptions; it is not a provider limit or scheduling policy. + +Regression sources cover a hand-computed1000tokens/10points=10000, duplicates, single-send evidence, provenance/reset/interval/independent-model conditions, numeric overflow, bounded ledger rejection, populated API/CLI output and identity replacement during async usage read. Existing local-answer provenance now survives attempt normalization. No local suite/build/typecheck/install was run. Independent design source audit passed; implementation source review and final cumulative hostedCI remain pending. Actual hostgoal blocked/FSMB untouched; no persisted capacity PABCD cycle is claimed. + +Source review corrections: API accepts only explicit shared quota scope, excluding blank/undefined model identity through an actual populated API regression. CLI prints insufficient-evidence reasons through the closed reason parser, with estimated/insufficient human+JSON fixtures. A positive fraction that rounds to zero yields no estimate. Local suites remain NOTRUN. diff --git a/devlog/_plan/260912_accounts/070_tun.md b/devlog/_plan/260912_accounts/070_tun.md index d37cd09632..77f46a02cc 100644 --- a/devlog/_plan/260912_accounts/070_tun.md +++ b/devlog/_plan/260912_accounts/070_tun.md @@ -21,3 +21,9 @@ MODIFY GUI `components/provider-workspace/types.ts`, `hooks/useProviderAccountPo Field chain: private probe→transient cache→account results→authenticated API JSON→enum-normalized client/CLI→current/all-account quota text. Ranking/health/history do not consume it. Tests: each enum trigger, summary failure/fallback success, final-attempt precedence, stale bars, recovery, cross-account isolation, stale-config, late response, unknown wire code, and secret-free projection. Existing provider account quota fixtures supply transport injection; new files require both layout entries. Fix inventory's stale IPv6 proxy-only sentence and update every touched area owner. Local suites/build NOT RUN; hosted backend/GUI checks and rendered final-tip artifact. Authenticated TUN observation remains unmet until an authorized operator supplies exact SHA, proxy/TUN mode and sanitized successful refresh; no network/account changes here. Reflection TUN-R01/R02 accepted. HTTP 300–399→redirect_blocked, 401/403→access_denied, 429→rate_limited, other non-2xx→upstream_error; success with unusable quota→response_unusable. Keep providerRedirectError cancellation and discard its message. Neither status establishes plan or reauth. fetchAntigravityQuota may reuse the private probe preserving null/rejection and success source; ProviderQuota/ProviderQuotaReport gain no diagnostic field, report-only views remain generic. getCachedProviderAccountQuota returns last-good quota only. + +P revalidation on489af939: parent added explicit account readers and Combo quota evidence. Classification stays limited to Antigravity account probe/cache; provider report remains its existing report(...) projection with no inference authority or diagnosticfield. Current functions moved but contract unchanged. Active tun cursor honored after history A mismatch; history source implementation remains pending on its own branch. Local tests/build/typecheck/install still NOT RUN; authenticated field acceptance untouched. + +A1 credential-currentness accepted. Reuse the existing private explicitQuotaIdentity hash recipe via a pure quotaCredentialIdentity(provider,id,capturedCredential,target) helper; existing explicit readers keep byte-identical hashes. Antigravity diagnostic capture uses its fixed canonical target and the credential whose access token matches the resolved probe token, captured before I/O. Preparation failure may use a pre-resolution identity only if it still matches. Record an epoch-bound private isQuotaFailureCurrent callback; recheck before cache publication, cached reads, result assembly and API projection. A changed/missing/unreadable identity omits the diagnostic only, preserving current last-good quota/unavailable semantics. No private digest or callback serialized. Add optional nonenumerable quotaFailureIsCurrent to the internal ProviderAccountQuota result instead of using isCurrent, whose existing API branch invalidates the entire quota row. Cache/result quotaFailure appears only while unavailable and current. Tests reauth during delayed probe and cached failure after same-id replacement; stale diagnosis is omitted, not attached to replacement credentials. + +TUN-D source proposal labels refer to the read-only Faraday design outputs recorded in this task. The executable source of truth is this document's full type/flow contract, not an absent external file. No inference-wide authority is added. diff --git a/devlog/_plan/260912_accounts/071_tun_delivery.md b/devlog/_plan/260912_accounts/071_tun_delivery.md new file mode 100644 index 0000000000..cc5de963c4 --- /dev/null +++ b/devlog/_plan/260912_accounts/071_tun_delivery.md @@ -0,0 +1,9 @@ +# Safe Antigravity quota diagnostics + +The existing canonical transport stays intact. A private probe result preserves exported null/rejection compatibility and classifies the final attempted endpoint. Account failures carry only an allowlisted code, bound to the probed credential/project and invalidated independently from last-good bars. API, CLI and both dashboard quota views consume it; unknown values and local management failures stay generic. All nine locale dictionaries updated. + +Regression sources exercise status/typed transport categories, fallback recovery, old bars, same-id replacement during and after a probe, private-value serialization, API projection, GUI normalization/recovery and CLI code filtering. Local suites/build/typecheck/install: NOT RUN. Text whitespace checks only. Backend source reviewer Rawls found no material scoped defect; full consumer/security review and final hosted CI/render proof remain pending. Authenticated TUN field acceptance remains open for #3781; no live credential or network configuration was changed. + +Resume binding verified: session01a093dc-e20d-79a1-8d46-231ced9ee05b, currentaccounts-tun branch489af939 baseline, actual FSMB. Host goal is blocked and has not been edited or described as active. Existing successful callback CI34673984380/fa4226a9ba is retained and not rerun. Other original PRs have changed heads and require current evidence; no merged PR is recreated. + +Full source review found a diagnostic-specific merge regression gap. Added initial-roster-failure clearing, roster-only same-mode retention/mode-change clearing, and delayed failure/recovery after newer selection with surviving membership. No product behavior changed in this correction. Local suites NOT RUN. diff --git a/devlog/_plan/260912_accounts/screenshots/010_usage-incomplete.png b/devlog/_plan/260912_accounts/screenshots/010_usage-incomplete.png new file mode 100644 index 0000000000..a1b96ec48d Binary files /dev/null and b/devlog/_plan/260912_accounts/screenshots/010_usage-incomplete.png differ diff --git a/devlog/_plan/260912_accounts/screenshots/011_usage-custom-range.png b/devlog/_plan/260912_accounts/screenshots/011_usage-custom-range.png new file mode 100644 index 0000000000..ed3589cb20 Binary files /dev/null and b/devlog/_plan/260912_accounts/screenshots/011_usage-custom-range.png differ diff --git a/devlog/_plan/260912_accounts/screenshots/020_codex-plan-exclusion-reset-first.png b/devlog/_plan/260912_accounts/screenshots/020_codex-plan-exclusion-reset-first.png new file mode 100644 index 0000000000..63d7c9229a Binary files /dev/null and b/devlog/_plan/260912_accounts/screenshots/020_codex-plan-exclusion-reset-first.png differ diff --git a/devlog/_plan/260912_operations/030_totals.md b/devlog/_plan/260912_operations/030_totals.md index 71d2615772..e3baa47109 100644 --- a/devlog/_plan/260912_operations/030_totals.md +++ b/devlog/_plan/260912_operations/030_totals.md @@ -2,10 +2,14 @@ Class C3; dependency roadmap. Adopt public #4111 final diff (2f07acb58b3e73f48cea38334f301b430a8634cd) after current-base and latest-review audit; preserve luvs01 credit. Source diff and metadata are in ignored .tmp/operations/pr-4111.diff/json, fetched directly from GitHub. -MODIFY src/server/management/usage-aggregate-cache.ts: replace four oversizedRows throws with retained usageIncomplete boolean, set on full scan, OR on append, preserve in resultFrom; cache API-key snapshots with diagnostic. MODIFY api-key-usage.ts: keep readable accumulator output and attach usageIncomplete:true / usageIncompleteReason:oversized_rows instead of throwing. MODIFY logs-usage-routes.ts: serialize diagnostics on filtered and unfiltered summaries. MODIFY usage-summary-cache.ts CachedUsageSummary and oauth-account-routes.ts GET /api/keys to retain/serialize flags. Other IO/mutation errors still fail. +MODIFY src/server/management/usage-aggregate-cache.ts: replace four oversizedRows throws with retained usageIncomplete boolean, set on full scan, OR on append, preserve in resultFrom; cache API-key snapshots with diagnostic. MODIFY api-key-usage.ts: keep readable accumulator output and attach usageIncomplete:true / usageIncompleteReason:oversized_rows instead of throwing. MODIFY logs-usage-routes.ts: serialize diagnostics on filtered and unfiltered summaries. MODIFY usage-summary-cache.ts CachedUsageSummary and oauth-account-routes.ts GET /api/keys to retain/serialize flags. Preserve existing non-oversized failure behavior: API-key rollups still return their existing zero fallback on IO failure, and /api/usage retains error:read_failed. MODIFY src/cli/usage-report.ts: warnings precede totals/no-match branch; incomplete no-match says skipped records may match. MODIFY gui/src/usage-summary-resource.ts shared optional diagnostic type; NEW components/usage-incomplete-notice.tsx; extend consumers Usage, dashboard overview, Models, AddProviderModal, ProviderWorkspaceShell, ApiKeysWorkspace/ListPanel and ApiKeys. Incomplete keys do not claim inactivity; warnings survive consumer caching. Add all locale keys. Full field chain: scanner oversizedRows -> retained aggregate boolean/API key snapshot -> route JSON/cache -> shared GUI/CLI input types -> every totals/ranking/key activity consumer. -MODIFY existing tests/cli/cli-usage-report.test.ts, tests/server/api-usage.test.ts, tests/server/api-key-attribution.test.ts, tests/usage/usage-aggregate-cache.test.ts and GUI usage/custom-range/model-picker/key-workspace tests; NEW gui/tests/usage-incomplete-consumers.test.tsx. Activation: good + oversized + good rows yields readable sums and warning; append oversized sticky flag, full clean rewrite clears it, missing filter matches stays uncertain, loading/stale consumers retain warning. Do not turn IO errors into zero totals. +MODIFY existing tests/cli/cli-usage-report.test.ts, tests/server/api-usage.test.ts, tests/server/api-key-attribution.test.ts, tests/usage/usage-aggregate-cache.test.ts and GUI usage/custom-range/model-picker/key-workspace tests; NEW gui/tests/usage-incomplete-consumers.test.tsx. Activation: good + oversized + good rows yields readable sums and warning; append oversized sticky flag, full clean rewrite clears it, missing filter matches stays uncertain, loading/stale consumers retain warning. Do not introduce any new IO-error fallback. MODIFY structure/gui-and-management-api.md and relevant mapped contract pointers; public management API, CLI agents and web-dashboard guides in all existing translated paths from original diff. Hosted full CI and dashboard evidence certify final tip; local suites/build/typecheck NOT RUN. This does not implement hub client-scoped CLI usage (#4205). + +Design reflection OPS-TOTALS01..05 accepted: positive-only flags do not prove completeness when absent; invalidRows is not sticky. Models rejects most-used ordering before PUT when usage is incomplete and keeps other modes available. Include dashboard-shared.ts as type carrier, keys-first/usage-first seeding, unfinished suffix without duplicates, empty/no-attribution and incomplete-to-clean recovery cases. Reuse existing Notice warn presentation and all locale modules, without new visual tokens or motion (ops dashboard, variance2/motion1, existing density). Remote build preview supplies rendered evidence; local product build/tests NOT RUN. + +A amendment: replace the newly carried fixed25ms wait in gui/tests/usage-incomplete-consumers.test.tsx with bounded condition-driven completion (act and event-loop turns, asserting rendered expected condition before return). No timer delay is accepted as proof of rendering. diff --git a/devlog/_plan/260912_operations/040_client_usage.md b/devlog/_plan/260912_operations/040_client_usage.md index 389c303f8b..e582767c44 100644 --- a/devlog/_plan/260912_operations/040_client_usage.md +++ b/devlog/_plan/260912_operations/040_client_usage.md @@ -8,6 +8,22 @@ MODIFY existing client/hub API owner only where the read contract requires it; e MODIFY public connected-client/CLI usage guide and structure/runtime.md / gui-and-management-api.md canonical scope. Any pre-disclosure details stay in scratch. Hosted regressions only; local execution NOT RUN. -Accepted design OPS-USAGE-02/03/04. NEW tests/server/hub-usage.test.ts and tests/clients/hub-usage.test.ts with entries in scripts/test-layout/layout.json explicit and tests/fixtures/test-layout-expected.json; NEW tests/cli/cli-usage-hub.test.ts. Tests use two client keys, loopback and remote admissions, invalid state, custom window, unsupported endpoint, bad response, expired/revoked credentials. Full implementation follows source confirmation before B. +Accepted design OPS-USAGE-02/03/04. NEW tests/server/hub-usage.test.ts and tests/clients/client-hub-usage.test.ts with entries in scripts/test-layout/layout.json explicit and tests/fixtures/test-layout-expected.json; NEW tests/cli/cli-usage-hub.test.ts. Tests use two client keys, loopback and remote admissions, invalid state, custom window, unsupported endpoint, bad response, expired/revoked credentials. Full implementation follows source confirmation before B. Reflection amendments: getFilteredUsageAggregate in src/server/management/usage-aggregate-cache.ts is the aggregation owner. Client DTO preserves #4111 incomplete flags; CLI suppresses advice to remove filters for account totals because that scope never exports accounts. Public files: docs-site/src/content/docs/guides/remote-hub.md and reference/cli/agents.md. All three new tests register in scripts/test-layout/layout.json and tests/fixtures/test-layout-expected.json. + +P revalidation after totals28c13d0c09: this slice depends on its usageIncomplete aggregate contract, so publish an ordinary child PR based on operations-totals. Retire4343 review follow-up was prioritized by explicit user steering; it is now source-reviewed and resolved. + +Concrete DTO: version1/source hub/scope client; range/surface/since/until/customWindow/generatedAt, numeric summary fields consumed by CLI, provider/model/day cost rows, and provider/model/matched/comboOverlap filter echo. No accounts, raw entries, apiKeyId or arbitrary spread fields cross the wire. NEW remote/hub-usage.ts owns a stripping Zod schema, capped arrays/string sizes and1MiB response bound; server projects through it and client parses through it. Incomplete flags retain positive-only semantics. No persistence or cache on client. Existing getFilteredUsageAggregate owns server cache keyed by authenticated key; no route-global cache. + +Handler accepts GET/v1/usage only, requires dedicated data key and configured admission even for loopback; no management/API env key. Reject unknown or duplicate query keys, caller apiKeyId, invalid range/surface/window, and noncanonical (blank/padded) authenticated key IDs before aggregation because the existing filtered cache trims IDs. Check origin and hub role. Recheck matched current credential/key identity after awaited scan before returning; revocation/rotation changes cannot publish a stale authority response. Serialize bounded allowlist DTO or explicit error, never a partial silent result. + +CLI reads connection state and matching service token fingerprint, sends only data credential to configured serverUrl via existing fetchBounded/boundedText helpers, retains redirect refusal and deadlines. Invalid/mismatched connection/token fails with no local fallback. Confirm owner remains the same after the read before printing. Human header names hub source/client scope and suppresses account-total advice; standalone runtimeRequest remains unchanged. + +Hosted tests: actual server two keysA/B and loopback auth; caller keyID rejected; absent/environment/admin/bad keys rejected; unknown/duplicate params and invalid window; provider filters/custom window; malformed response/too-large/redirect/oldHub/offline; CLI connected versus standalone and token mismatch. Test paths in this doc register in both layout files. No local tests. Unpublished security analysis remains .tmp/operations/040_client_usage_private.md. + +Reflection closure: post-read CLI validates both owner triple AND current connection/file token fingerprints; sameClientConnectionOwner alone omits fingerprint. Every nested DTO object strips unknown fields; the1MiB check uses serialized UTF-8 bytes in addition to array/string caps. + +B scope refinement: reuse resolveDataPlaneAdmissionSecret directly; no resolver logic change necessary; auth-cors.ts AUTH_MATRIX gets the new endpoint row and tests/server/api-key-attribution.test.ts drives its real GET cells. Client test basename is client-hub-usage.test.ts to avoid the registry basename collision with server/hub-usage.test.ts. + +Follow-up100: new fetchHubUsage requires HTTPS or supported loopback HTTP before credential headers, uses request cache:no-store, and retains server cache-control:no-store. diff --git a/devlog/_plan/260912_operations/100_usage_transport.md b/devlog/_plan/260912_operations/100_usage_transport.md new file mode 100644 index 0000000000..ac09181261 --- /dev/null +++ b/devlog/_plan/260912_operations/100_usage_transport.md @@ -0,0 +1,7 @@ +# Client usage credential transport + +User intake4353 plan comments3995195164 and3995195169. The new usage source is in4373, not in4353. Previous listener-diagnosticD directs this separate repair before pairing. Class C4; same scoped HOTL/tool/write bounds, no resource cap, no local suites/build/typecheck/install. Do not change unrelated hub-client operations or publish speculative analysis. + +MODIFY src/client/hub-client.ts fetchHubUsage only: after normalizeHubOrigin, use existing isPairingTransportPermitted before constructing credential headers or invoking fetchBounded. Accept HTTPS and supported numeric/localhost loopback HTTP; reject plain HTTP remote origins with an actionable static error. Add cache:no-store on the authenticated request. The server already sends cache-control:no-store; preserve it. + +MODIFY tests/clients/client-hub-usage.test.ts: remote HTTP and credentials-bearing malformed origins fail before injected transport invocation; HTTPS and each supported loopback form remain accepted; request cache mode is no-store. Existing real route test proves response header. Modify040 and publichub usage guide to state transport constraint. Independent source re-audit, reply/resolve the two authorized plan threads with4373 exacthead. Hosted cumulativetip acceptance remainsOPEN. diff --git a/devlog/_plan/260912_remote_workspace_carry/020_executor_runtime.md b/devlog/_plan/260912_remote_workspace_carry/020_executor_runtime.md index 550b2a5bb5..e20200c7c3 100644 --- a/devlog/_plan/260912_remote_workspace_carry/020_executor_runtime.md +++ b/devlog/_plan/260912_remote_workspace_carry/020_executor_runtime.md @@ -82,3 +82,25 @@ REMOTE-ARCH-003: Separate persisted enrollment capabilities from current connect REMOTE-ARCH-006: Use existing required private-file/Windows ACL primitives for new identity and bearer stores. Check permission setup failures and refuse loading/saving secrets when enforcement fails. Do not change global config-store behavior. Record exact selected existing helper in phase-2 P after reading the owner; no best-effort function is accepted as proof. REMOTE-ARCH-007: Codex real App Server tests depend on OCX_CODEX_BIN; Claude real integration on OCX_CLAUDE_BIN; Pi on OCX_PI_BIN. The Linux confinement case can return without execution unless OCX_REQUIRE_LINUX_REMOTE_WORKSPACE_CONFINEMENT=1 or bwrap is available. Current generic CI alone does not prove those paths. Mock tests prove lifecycle and tool-routing contracts only; native Hub isolation and executor confinement stay explicit final acceptance gaps when not activated. For each adapter separately record denied local tools, inherited plugins/hooks/config, offline refusal and teardown; inspect source plus hosted mocks, no claims of live CLI confinement from flags alone. + +## Phase-2 revalidation and exact owner choices + +Previous D: wp1 inactive foundation source cycle complete at 726ddc7fc0; final hosted proof remains wp4. Continue in child branch codex/260912-60plus-remote-runtime. Existing public exports and added host-negative coverage are retained. + +REMOTE-ARCH-004: storage modules import atomicWriteFile directly from src/config/atomic-write.ts and getConfigDir from src/config/paths.ts, avoiding the broad config.ts barrel. Device CLI orchestration retains explicit runner construction because it computes actual availability after root approval; no import-time probe exists. This is intentional sequential coupling. Server seams in phase 3 use narrow structural connection/session interfaces rather than pulling concrete remote classes into shared request types. No remote module imports server surfaces. + +REMOTE-ARCH-006 exact helpers: NEW src/remote-control/workspace-secret-store.ts owns prepareWorkspaceSecretDirectory(directory) and hardenWorkspaceSecretFile(path). On POSIX use chmodSync with propagated failure and lstat directory/file identity/type checks. On Windows call existing src/lib/windows-secret-acl.ts hardenSecretDir/hardenSecretPath with required:true. Reject symlink state targets. All three stores use this before reads and before atomicWriteFile. Existing atomic-write.ts already creates an empty private descriptor, hardens before writing bytes, and scrubs failures; retain it. Tests: NEW tests/clients/remote-workspace-secret-store.test.ts covers owner-only POSIX file mode, unexpected path types/symlinks and failed reads; hosted Windows ACL owner tests remain applicable. No global config behavior changes. + +src/lib/windows-atomic-replace.ts change is the new ReplacePublisher literal remote-workspace (the function is already exported). Use existing counter serialization/consumers unchanged: creation at executor write, diagnostic key serialization, dynamic record readers; no closed switch to extend. + +NEW tests/clients/remote-workspace-session-binding.test.ts covers session/device/root/capability mismatches with zero execution and a valid positive control, using encrypted messages and independent fixtures. MODIFY agent-wire, hub, sessions and device tests to assert subset negotiation and presence intersection. Platform runner source retains existing fail-closed native paths; remove stale comment claiming supported macOS commands. + +### Audit amendment: store-level failure propagation + +Hub/Device/Session file-store constructors accept an optional narrow permissions dependency containing prepareDirectory and hardenFile, defaulting to the required production helper. Load returns null for absent files; existing files require directory and file checks before secret reads. Save prepares directory, hardens an existing target, then invokes the existing private atomic writer. For each store, injected directory/file hardening throws must propagate, preserve existing bytes and prevent secret IO. New-state first-run controls return null then save/load valid fixtures. Add all three store cases to remote-workspace-secret-store.test.ts; this injection observes caller ordering rather than relying on ACL-owner tests alone. + +### Native containment amendment + +Independent source review requires a protected Linux bubblewrap executable outside writable roots, with identity revalidation before use. Custom executable files and their parent chain must not be writable by group/other; canonical system symlinks are resolved before checking. Workspace roots cannot contain the executable; every invocation rechecks. Add source/runner regression fixtures without claiming a local run. + +Windows command availability remains disabled in this carry: nativeRemoteWorkspaceCommandRunnerAvailable returns false before invoking the helper, and the official Windows helper rejects public probe/run without allocating OS resources. The candidate Windows implementation remains in original PR history; do not retain callable unverified entrypoints. This matches the fail-closed macOS policy and preserves independently authorized file tools. Update native denial tests and docs; Windows working-command acceptance stays OPEN. A future lifecycle owner and hosted cancellation/cleanup evidence are required before re-enablement. This is a safety limitation, not completion of Windows commands. diff --git a/devlog/_plan/260912_remote_workspace_carry/030_integration.md b/devlog/_plan/260912_remote_workspace_carry/030_integration.md index 5c126f5987..4cf079534d 100644 --- a/devlog/_plan/260912_remote_workspace_carry/030_integration.md +++ b/devlog/_plan/260912_remote_workspace_carry/030_integration.md @@ -67,3 +67,23 @@ Local tests/build/typecheck/install NOT RUN by user instruction. Text comparison NEW src/remote-control/workspace-activation.ts exports a side-effect-free guard requiring runtimeRole=hub AND process.env.OCX_REMOTE_WORKSPACE_ENABLED === "1". This guard imports only the config type. Pair and agent branches call it before dynamic import; disabled requests return 404. Management namespace returns a disabled status before importing runtime. Shutdown uses already retained workspace references or initialized-only lazy import only when explicitly enabled; a disabled Hub never creates identity or probes model CLIs. CLI pairing remains explicit Executor-local authorization and never modifies server environment. Document the opt-in variable and require an explicit environment choice to enable the feature. Test disabled Hub, non-Hub with flag, and enabled Hub, with no ambient inheritance in fixtures. Existing-file conflicts observed by git apply --check: management-api.ts, management/context.ts and ws-bridge.ts. Port the namespace-dispatch addition into current management handler, append only type/dependency seam fields after current imports, and extend current WebSocket discriminator/handlers without replacing newer fields. The check was text applicability only, not a product test. + +## Phase-3 revalidation + +Previous D: runtime source cycle closed at a3182185f0 after corrected whitespace receipt. Final executable/native proof remains open; Windows commands unsupported. Continue integration from that exact parent. Carry current React resource/Select/Notice/icon conventions with no dependency additions. All locales inherit original translations with the unavailable-state opt-in message added consistently. + +Server adaptation: preserve current quota-reset and Grok coupon lazy dispatch. Add remote namespace handler before normal configuration routes. It answers disabled GET status with available:false and empty collections before loading workspace runtime; mutations when disabled refuse. Pair/agent paths require explicit guard before lazy imports and existing Origin/device-token validation. WebSocket data stores only structural receive/open/close callbacks; no concrete Hub class imports in ws-bridge. Upgrade closure owns hub/device association and close cleanup. Management dependency seams use structural Pick projections of only public Hub/session operations; all are import type and erased at runtime. Runtime modules use narrow config imports from phase 2, eliminating the prior broad runtime cycle. + +Shutdown: a promise-local initialized workspace module reference is set only on actual workspace route activation; shutdown calls initialized service getters only when that reference exists. It never dynamically imports remote runtime merely because runtimeRole is hub. Management-only activation also needs lifecycle-owned shutdown registration or a retained optional shutdown callback; resolve before B and test both paths. + +NEW tests/clients/remote-workspace-activation.test.ts covers hub+flag guard, disabled management status without store writes and unauthorized principal refusal before dependency construction. Existing server tests get explicit isolated flag setup/restore; no real devices. CLI capabilities list pair/agent/status, no Hub-status automation introduced. Regenerate skills/ocx reference surface through its existing generator (documentation only). Docs state OCX_REMOTE_WORKSPACE_ENABLED=1 opt-in, default read-only sessions, Linux conditional exec and both desktop native helpers refusing commands. + +Rendering: this worktree has no node_modules or gui/node_modules. Do not install or run a local build. Prefer final hosted package artifacts for a local static render with synthetic API responses; if no artifact exists, retain rendering as unmet acceptance and attach no historical screenshot as current evidence. + +### Awaited per-server cleanup decision + +The existing optional-shutdown registry is synchronous best-effort and cannot prove awaited Remote Workspace shutdown. Reuse server.stop's existing runListenerShutdown array instead. Add a per-server retained shutdown callback and a ManagementApiDeps onRemoteWorkspaceShutdown callback setter. Workspace management resolves its already-loaded services then registers an initialized-only cleanup closure through that setter; pair/agent loader registers the same kind of closure. server.stop calls the retained callback if present. No callback means no remote import/work. Keep registration idempotent and closure references scoped to the current config/server; tests cover management-only initialization and explicit stop. Do not change the global optional-shutdown API. + +In-flight initialization refinement: management checks per-server stopping before and after module import, creates Hub/session services synchronously in one turn, then registers initialized-only teardown. Pair/upgrade paths check stopping after lazy load. SessionService rejects create/resume after shutdown even when an availability promise completes later; a regression holds availability across shutdown. This prevents request initialization from creating resources after stop. + +Source-audit follow-up: pending creation promises and late handle cleanup are owned by shutdown, which settles all session cleanups before propagating failure. Pair-body completion rechecks stop admission. UI derives enrollment commands from the actual shared Hub origin, keeps drafts per session, preserves newer input on failure, shares submission eligibility between keyboard/button, warns on stale snapshots, and permits Stop during pairing. Controlled runtime/body/draft/Stop regressions accompany these adaptations; execution remains hosted-only. diff --git a/devlog/_plan/260912_remote_workspace_carry/041_resume_ci_repair.md b/devlog/_plan/260912_remote_workspace_carry/041_resume_ci_repair.md new file mode 100644 index 0000000000..0b8bcdec90 --- /dev/null +++ b/devlog/_plan/260912_remote_workspace_carry/041_resume_ci_repair.md @@ -0,0 +1,23 @@ +# Resumed final-tip verification + +The integration branch was fast-forwarded from 5c462fec1a to existing remote head 4ad1d1ce54 without rewriting the six intervening commits. PRs #4344, #4362 and #4372 remain open. CI run 34683454076 at 4ad1d1ce54359ee25fbb078344a22bdb16f7e304 failed; cancelled and skipped jobs are not passing evidence. + +## Scoped repair + +MODIFY tests/clients/remote-workspace-activation.test.ts: provide the HTTP Host precondition that managementRequestOrigin requires. Preserve 200/404 expectations and assert the admin-token denial message so a generic origin failure cannot satisfy the consent regression. + +MODIFY tests/clients/remote-workspace-sessions.test.ts: observe pending rejection using native Promise settlement, release the held start/availability gate, then assert rejection. Keep late runtime cleanup, one-stop/one-close checks, terminal failure state, and assert zero model prompts. Production cleanup code is unchanged. The source dependency order is verified; Bun matcher scheduling remains a hypothesis until repaired-head hosted execution confirms it. + +The host goal is observed blocked and the native cycle remains C; no direct goal/FSM edits or reset were performed. This repair continues the incomplete integration Check under explicit resume authorization. It is not claimed as an additional completed cycle. Local suites/build/typecheck/install are NOT RUN. Independent inherited-model review and final cumulative-tip hosted CI remain required. + +## Render provenance + +Dashboard artifact 10294424393 from run 34683454076 records merge-ref commit 5270d422166eb56692d1fde1513d5ff4f40533d4 and GUI tree 69b1bce039ee18cecea2586bc484c2afe0523a13, equal to candidate 4ad1d1ce54:gui. It is rendered with synthetic example state on a loopback static server; mutations are disabled. This proves the captured layout only, not real pairing or runtime execution. + +Observed screenshots: assets/041_desktop_read_only.png and assets/042_desktop_files_only.png at 1440x913 CSS pixels. The actual access picker changes from Read only to Edit files only and displays the command-unavailable notice. No live enrollment or model operation was invoked. A requested390px native-window resize stopped at500px; that is not390px proof, and no narrower layout claim is made. The installed browser CLI does not expose the documented script command; no driver was installed to work around it. + +## Windows subprocess fixtures + +Run34693137770 at d1a922ae reports three remote-owned fixture failures on Windows: two native response cases cannot execute a POSIX shebang file, and the cwd case times out while starting PowerShell. The intended contracts are bounded stdin/response decoding and real child cwd; neither requires a shell. Replace only test subprocess fixtures with the exact current Bun executable, preserve helper-path checks, timeout/output bounds and success/error assertions. Native Windows command support remains disabled. Local suites remain NOT RUN; independent source review and repaired-head hosted proof are required. + +The Windows native fixture keeps descriptor keys separate from its injected spawn function, verifies all pipe options before forwarding them unchanged, and never executes authority from the input JSON. Cwd checks compare decoded stdout with the canonical executor directory and exclude the canonical coordinator directory. An exact exit-code/empty-stderr assertion accompanies the unchanged deadline. At predecessor d1a922ae, hosted Windows2 job103552067478 passed the corrected disabled management status case; that narrow result is not an all-suite pass. diff --git a/devlog/_plan/260912_remote_workspace_carry/assets/041_desktop_read_only.png b/devlog/_plan/260912_remote_workspace_carry/assets/041_desktop_read_only.png new file mode 100644 index 0000000000..9075e975be Binary files /dev/null and b/devlog/_plan/260912_remote_workspace_carry/assets/041_desktop_read_only.png differ diff --git a/devlog/_plan/260912_remote_workspace_carry/assets/042_desktop_files_only.png b/devlog/_plan/260912_remote_workspace_carry/assets/042_desktop_files_only.png new file mode 100644 index 0000000000..c99e88e29f Binary files /dev/null and b/devlog/_plan/260912_remote_workspace_carry/assets/042_desktop_files_only.png differ diff --git a/devlog/_plan/260912_remote_workspace_carry/screenshots/remote-workspace-hub-dashboard.png b/devlog/_plan/260912_remote_workspace_carry/screenshots/remote-workspace-hub-dashboard.png new file mode 100644 index 0000000000..98972b8e75 Binary files /dev/null and b/devlog/_plan/260912_remote_workspace_carry/screenshots/remote-workspace-hub-dashboard.png differ diff --git a/devlog/_plan/260912_remote_workspace_carry/screenshots/remote-workspace-pairing-code.png b/devlog/_plan/260912_remote_workspace_carry/screenshots/remote-workspace-pairing-code.png new file mode 100644 index 0000000000..3fc3c97e08 Binary files /dev/null and b/devlog/_plan/260912_remote_workspace_carry/screenshots/remote-workspace-pairing-code.png differ diff --git a/devlog/_plan/260912_unimplemented_trio_stack/000_plan.md b/devlog/_plan/260912_unimplemented_trio_stack/000_plan.md new file mode 100644 index 0000000000..611dbac17c --- /dev/null +++ b/devlog/_plan/260912_unimplemented_trio_stack/000_plan.md @@ -0,0 +1,108 @@ +# Trio stack: WS stage instrumentation, native-main device reauth, paginated history recovery + +Unit 260912_unimplemented_trio_stack. HOTL loop goalplan slug +`implement-three-unimplemented-opencodex-backlog` (session +01a09616-38e6-72e0-b5bf-99eb10ce58a6). Bottom-up manual stacked-PR chain +against `dev` (lidge-jun/opencodex). No merges, no GitHub native-stack +registration. Every push uses `git push --no-verify`; local product +suite/build/typecheck/install NOT RUN; each PR relies on hosted exact-head +CI and says so in its Verification section. + +## Objective + +Close the three implementable unimplemented backlog items identified in the +2026-09-12 inventory: + +1. Issue #4191 — WS 1006 / response-prelude-timeout diagnosis has no durable + content-free evidence. Ship stage instrumentation only; no fix, no + auto-retransmit fallback. +2. Issue #3898 — headless hub cannot reauth native `__main__` because + deviceauth is pool-only. Ship the native-main device reauth API/CLI, then + the main-card Re-login GUI on top of it. +3. Issue #4311 residual — paginated history still has no writer support and + no recovery for ordinal-corrupted rollouts. Ship the offline recovery + tool with preservation proofs; live writes stay refused. + +## Sources + +- #4191 body: content-free stage diagnostics list; A/B evidence that the + failure is proxy-path-specific; related #2471, #4083, #3976. +- #3898 body: suggested contract (reuse OpenAI deviceauth, persist to native + main slot, keep `__main__` out of `/api/codex-auth/login`, no codex + binary/keyring requirement, secret-free DTOs). +- #4311 body: ordinal-0 clone defect (now guarded), incident recovery by + ordinal-digit rewrite while Codex was closed, prohibition of N+1 guessing + and live rewrites. +- devlog/_plan/260912_accounts/080_reauth_api.md and 090_reauth_ui.md — + accepted Accounts-lane design drafts this unit adopts for L2/L3. +- devlog/_plan/260912_history_containment/ — refusal contract this unit + must preserve. + +## Constraints (hard) + +- L1 logs stay content-free: create-frame byte count, send completion, + close code (numeric), elapsed/first-frame timings, frame counters, OCX and + Bun versions. No conversation text, no headers, no close-reason text, no + account identifiers in the new records. +- L1 adds no `responseCommitted === false` auto-retransmit: turn + duplication risk is documented in #4191 discussion. +- L2 keeps `/api/codex-auth/login` rejecting `__main__` (400), keeps pool + Add/Re-login semantics unchanged, and must not route the native flow + through `startLoginFlow("chatgpt")` (scratch-slot overwrite + pool + singleflight collision, src/oauth/index.ts:1899-1973). +- L2 commit to `$CODEX_HOME/auth.json` only under an exclusive claim with + path/hash/inode assertion and same-identity verification; never retains + old identity token beside new credentials; fails safe + (`native_main_unavailable`) when no fence can be established. +- L3 must not reuse `AddCodexAccountModal` or `openReauth("__main__")`; + dedicated hook and dedicated backend namespace only (the pool login route + rejects `__main__` at src/codex/account-id.ts:15-20). +- L4 must not invent last-ordinal+1, must not write to a live rollout, must + not weaken `history_paginated_requires_native_writer` refusal in + preflight/apply/restore paths, and must preserve every non-ordinal byte. +- All layers: focused tests land with the layer; every new test file gets + layout.json `explicit` + tests/fixtures/test-layout-expected.json + entries in the same PR. +- structure/ ownership: any owned source area changed by a layer updates + its structure doc in the same PR (structure/AGENTS.md). + +## Work-phase map (dependency order = stack order, bottom first) + +| WP | Layer | Branch | PR base | Decade doc | +|----|-------|--------|---------|------------| +| wp2 | L1 #4191 WS stage instrumentation | codex/260912-ws-stage-instrumentation | dev | 010 | +| wp3 | L2 #3898 native-main reauth API/CLI | codex/260912-native-main-reauth-api | wp2 branch | 020 | +| wp4 | L3 #3898 main-card Re-login GUI | codex/260912-native-main-reauth-ui | wp3 branch | 030 | +| wp5 | L4 #4311 paginated history recovery | codex/260912-native-paginated-writer | wp4 branch | 040 | + +Dependency logic: L2 and L3 are one feature split at the API/UI seam +(030 depends on 020's route). L1 is independent but touches the shared +request-log schema, so it sits at the bottom where later layers rebase onto +a stable log contract. L4 is the riskiest (user data) and rides on top so +lower layers can land without waiting for it. There is no functional +dependency between L1/L2 and L4; the chain exists to serialize review. + +## Verification policy per layer + +- Red-first focused tests, then implementation, then green. +- `bun test tests//` (or `cd gui && bun test tests/` + for L3) fresh at C, captured via `cxc receipt test`. +- Full local suite/build/typecheck/install: NOT RUN (standing rule); each + PR Verification section labels this and names the hosted exact-head CI + run as the integration evidence. Cancelled/skipped CI never counts as + passing. +- L4 additionally: privacy-relevant paths (rollout bytes) stay in tests + with synthetic fixtures only. + +## Open decisions carried to audit + +1. L2 hub fence: on a headless hub the native owner never activates + (src/server/index.ts:1026-1046 + src/codex/desired-state.ts:79-81). + 020 resolves how commit fencing works there without weakening the + exclusive-claim contract; audit must confirm the chosen fence. +2. L4 scope: true live-write support needs a Codex-owned writer API that + does not exist in this tree. This unit ships the offline recovery tool + and keeps live refusal; the PR description must say so explicitly. +3. L3 screenshot evidence: obtained from hosted CI artifacts or recorded + exemption, per repo PR gate (title/body mentions of gui need a + screenshot). diff --git a/devlog/_plan/260912_unimplemented_trio_stack/001_baseline_revalidation.md b/devlog/_plan/260912_unimplemented_trio_stack/001_baseline_revalidation.md new file mode 100644 index 0000000000..327dc4285c --- /dev/null +++ b/devlog/_plan/260912_unimplemented_trio_stack/001_baseline_revalidation.md @@ -0,0 +1,22 @@ +# Baseline revalidation (wp1 B-phase) + +Independent main-session spot check of the citations folded in by the wp1 +audit (0268727f82), re-run against the working tree at B. Every folded +reference was opened and read; results below. All verified TRUE. + +| Claim | Where verified | Result | +|-------|----------------|--------| +| `assertNativeMainOwner` throws without a held owner entry | src/codex/native-main-owner.ts:302-314 — throws NATIVE_MAIN_OWNER_UNAVAILABLE/BUSY (503) unless snapshot held | TRUE | +| Exclusive claim is owner-independent (FS/SQLite lock) | src/codex/native-main-claim.ts:167 — `withNativeMainExclusiveClaim(context, operation, options)`, claim/release around operation, no owner lookup | TRUE | +| `shouldSyncCodexOnStart` is composed at desired-state.ts:130 | src/codex/desired-state.ts:130 — exported function; comment names the hub rule | TRUE | +| Pool login writes `isMain: false` | src/codex/auth-api.ts:2934,2939 — both update and add paths set `isMain: false` | TRUE | +| Paginated guard throws the structured reason | src/codex/history-provider.ts:1172 — `CodexHistoryIntegrityError("history_paginated_requires_native_writer")` on `ordinal` key or `history_mode === "paginated"` | TRUE | +| State DB resolution | src/codex/paths.ts:106-109 — `resolveCodexStateDbPath` joins sqlite root + state_5.sqlite | TRUE | +| `startLoginFlow` location | src/oauth/index.ts:1899 — export begins | TRUE | +| GUI modal state | gui/src/components/CodexAccountPool.tsx:75 (`showAdd`), :94 (`reauthId`), :651-654 (modal mount) | TRUE | + +Consequence for implementation cycles: 020's hub-fence resolution stands +as amended — the native-main reauth fence MUST NOT call +`assertNativeMainOwner`; the owner-independent exclusive claim plus +path/hash/inode and recovery/admission rechecks is the whole fence, and +claim/admission failure alone maps to `native_main_unavailable`. diff --git a/devlog/_plan/260912_unimplemented_trio_stack/010_l1_ws_stage_instrumentation.md b/devlog/_plan/260912_unimplemented_trio_stack/010_l1_ws_stage_instrumentation.md new file mode 100644 index 0000000000..72cfd8c3c2 --- /dev/null +++ b/devlog/_plan/260912_unimplemented_trio_stack/010_l1_ws_stage_instrumentation.md @@ -0,0 +1,142 @@ +# L1: content-free Codex WS upstream stage instrumentation (#4191) + +Class C2. Stack bottom, base `dev`. Branch +`codex/260912-ws-stage-instrumentation`. Diagnosis instrumentation only: +no behavior change to success paths, no retry/fallback change. +Second revision: folds the wp2 A-audit FAIL (2 blockers, 2 majors, 1 +minor) into the design. First revision's `recordCodexWsStage`-on-context +design is retracted — the exchange has no `RequestLogContext` +(codex-ws-exchange.ts:11-18,85) and cannot get one without inverting +layers. + +## Problem + +#4191 fails as WS 1006 or "response prelude timed out" only through the +proxy. The content-free stage record already exists as +`CodexWsFailureStage` (src/server/responses/codex-ws-wire.ts:100-144) and +`failureStage()` (src/server/responses/codex-ws-exchange.ts:148-159), but +it is only interpolated into failure message strings. Durable logs keep +neither the message nor a typed code: the eager relay collapses stream +errors to `upstream_reset` + `streamAborted` (wire.ts:218-229 comment; +relay.ts:1417-1430), and the 504 pre-response JSON path never reaches the +relay at all. `/api/logs` and usage.jsonl are explicit per-field copies, +so a field added only to `RequestLogContext` is dropped on write and on +restart hydrate. + +## Contract (from #4191 + maintainer bounds) + +Record, per upstream exchange: create-frame bytes, send completion, +close code (numeric only), elapsed ms and first-frame ms, frame counters +(upstream/control/relayed, pings/pongs), pool reuse boolean, OCX version, +Bun runtime version. Never record conversation text, headers, close-reason +text, or account identifiers. No `responseCommitted === false` +auto-retransmit fallback. Client CLI version is not on the handshake +(`user-agent` is not in FORWARD_HEADERS, +src/adapters/openai-responses.ts:43-61) — the limitation is documented in +the PR, not worked around by parsing `frameText`. + +## Changes + +MODIFY `src/server/responses/codex-ws-wire.ts` +- New exported type `CodexWsStageRecord = + Omit & { + requestBytes: number | null; closeCode: number | null; reused: boolean; + ocxVersion: string; bunVersion: string }` (Omit, not an intersection — + an intersection cannot widen `requestBytes`). Extend the privacy + comment: numeric/boolean/semver fields only; close-reason text stays out + of every durable record. +- New `markCodexWsStage(response, record)` / `readCodexWsStage(response)` + over a `WeakMap` — the same + Response-marker seam `markCodexWsResponse` already uses. +- `ocxVersion` comes from a module-local package.json IIFE, the exact + pattern already duplicated in management-api.ts:87-93, gui-static.ts:6-9, + client/machine-listener.ts:21, update/index.ts:147. Do NOT import + management-api (layer inversion + cycle). + +MODIFY `src/server/responses/codex-ws-exchange.ts` +- `ExchangeOptions` gains optional `bunVersion?: string` and nothing + else; no context, no callback registry. +- Snapshot once in `failStream` (the funnel every failure site already + calls: armSilence :206, connect-deadline :256, onClose :426, onError + :437, and the onMessage sites :330-402) and once in `commitResponse` + (:160). After the existing settle decision, call + `markCodexWsStage(response, record)` on the Response being resolved — + both the SSE 200 and the `codexWsPreResponseFailure` JSON paths resolve + a Response, so one marker covers success and failure. +- `requestBytes`: computed at failure time only (current deferred + behavior). On the committed-success record it is `null` — the happy + path must not byte-count megabyte replay frames (the deferral comment at + :143-147 is the contract). +- `closeCode` is captured in `onClose` from the event (numeric only) and + carried into the `failStream` call it makes; other sites pass `null`. +- `reused` is `session.reused`; `bunVersion` from the new option. +- No control-flow change at any site: emissions happen after the settle + decision, never instead of it. + +MODIFY `src/server/responses/ws-upstream.ts` +- Pass `bunVersion: typeof runtime === "string" ? runtime : runtime.version` + (the gate input at :62-64 may be a plain string) through + `codexWsUpstreamFetch` into `codexWsExchange`. Signature gain is one + optional field. + +MODIFY `src/server/responses/core.ts` +- Adopt the stage onto the attempt at the handleResponses send path, not + only at `retryCodexPoolOnAlternateAccount` (:1532-1556 is the pool + retry, not the primary send): `readCodexWsStage(upstreamResponse)`; + when present assign `logCtx.activeAttempt.codexWsStage`. Apply at every + adopted `upstreamResponse`: the primary send (:5304-5320), the + post-retry assignment (:5824) — or once on the final response after the + ladder (~5758); B picks the single funnel that covers every adopted + response and tests it. This covers the 504/502 pre-response JSON path + that never reaches relay.ts, and needs no relay.ts change: the relay + collapse only sets `streamAborted` alongside the stage. (First + revision's relay.ts MODIFY is retracted.) + +MODIFY `src/usage/log.ts` +- `PersistedUsageAttempt` gains `codexWsStage?: CodexWsStageRecord` + with a comment naming #4191 and the content-free invariant. +- Attempt serializer allowlist (:445-480 region): carry `codexWsStage` + through a `normalizeCodexWsStageRecord` guard (numeric fields via + isNonNegativeFiniteNumber-style checks, booleans strictly, versions as + capped semver strings, `requestBytes: number | null`) so a hand-edited + row cannot inject strings into the DTO. +- `normalizeUsageEntry` (:527-612) carries it via the attempts + normalization above; no entry-level copy (stage is per-attempt). + +MODIFY `src/server/request-log.ts` +- `RequestLogEntry` needs no new field: `attempts` already projects. + `requestLogEntryFromPersistedUsage` (:280-330) keeps copying + `attempts` wholesale. Verify `addFinalRequestLog` (:1037-1086) passes + the attempt objects (with the stage) into `addLog` — if it re-derives + attempt rows field-by-field, add `codexWsStage` there instead. B + confirms which of the two attempt paths is authoritative and tests it. + +## Tests (red-first) + +MODIFY `tests/responses/ws-upstream.test.ts` +- Through `handleResponses` (the :399-408 pattern — the only path that + owns a logCtx): upstream 1006 persists `codexWsStage` on the logged + attempt with `closeCode: 1006` and `sent: true`; prelude-timeout + persists `firstFrameMs: null`, `upstreamFrames: 0`; a committed + success records exactly one stage with `requestBytes: null`. +MODIFY `tests/responses/ws-failure-stage.test.ts` +- Record carries closeCode/reused/versions; the serialized record never + contains reason text, header names, or body substrings. +NEW `tests/usage/usage-log-ws-stage.test.ts` +- Round trip: `normalizeUsageEntry` + attempt serializer keep a valid + stage; corrupt stage shapes (string frames, object closeCode) are + dropped, not passed through. layout.json explicit + + tests/fixtures/test-layout-expected.json entries (domain `usage`). + +## Docs / ownership + +L1 touches owned `src/server/responses/*`, `src/usage/log.ts`, and +`src/server/request-log.ts`: sync structure/transports/responses.md and +structure/runtime.md in this PR (structure:check must stay green). + +## Out of scope + +Any WS behavior fix, SSE-fallback policy change, prelude-timeout tuning +(#3976/#4083), pool policy, inbound client-socket metrics +(codexWebSocketAdmissionMetrics is the client side — do not touch), +auto-retransmit on `responseCommitted === false`. diff --git a/devlog/_plan/260912_unimplemented_trio_stack/020_l2_native_main_reauth_api.md b/devlog/_plan/260912_unimplemented_trio_stack/020_l2_native_main_reauth_api.md new file mode 100644 index 0000000000..bde091a235 --- /dev/null +++ b/devlog/_plan/260912_unimplemented_trio_stack/020_l2_native_main_reauth_api.md @@ -0,0 +1,138 @@ +# L2: native-main device reauth API/CLI (#3898) + +Class C4 (auth boundary). Stack layer 2, base the L1 branch. Branch +`codex/260912-native-main-reauth-api`. Adopts the accepted Accounts-lane +design devlog/_plan/260912_accounts/080_reauth_api.md; this doc is the +diff-level revalidation of that draft against current `dev` plus the +deltas the code map surfaced. 080 remains the contract source; anything +here overrides stale details of 080, not its invariants. + +## Problem + +Headless hub (`runtimeRole=hub`, `oauthOpenBrowser: false`, no codex +binary, no keyring) cannot reauth native `__main__`: +`/api/codex-auth/login` is pool-only and rejects `__main__` +(src/codex/account-id.ts:15-20; src/codex/auth-api.ts:2733-2748); +`ocx account main add` requires official `codex login` + OS keyring +(src/cli/account-main.ts:73-90,214-260). WHAM `token_revoked` on the main +grant is then unrecoverable from the hub. + +## Changes (080 contract, revalidated) + +MODIFY `src/oauth/chatgpt-device.ts` +- Factor the private grant exchange so a native-only result retains the + raw validated token payload: new `loginChatGPTNativeDevice` returns + `{ credential, idToken }` in-process only; reject missing + access/refresh/id token or mismatched account identity. Existing + `loginChatGPTDevice` behavior unchanged (still projects + OAuthCredentials, no id_token). +- Delta from 080 (explorer-confirmed gap): the usercode/poll/token fetches + (84-90, 121-127, 152-163) have no per-request timeout — only the 15-min + poll deadline and abort. Add a service-owned per-fetch deadline (fetch + + body) so a stuck TCP cannot hold the flow until TTL. This is the Kuhn + blocker "poll timer does not bound fetch/body deadlines". + Audit-folded: one FRESH 30s timeout per fetch attempt inside the poll + loop (AbortSignal.any([ctrl.signal, AbortSignal.timeout(30_000)]), the + main-account.ts:239-241 pattern) — a single 30s signal across the whole + poll would kill the 15-minute grant. Abort-timeout maps to + device_authorization_failed. The shared helper also bounds hung POOL + device logins at 30s per fetch — an intended improvement, called out in + the PR. + +MODIFY `src/codex/main-account.ts` +- New `beginNativeMainReauth`: captures the existing + `MainAuthJsonCredential` snapshot (103-136) into a private closure; + returned commit accepts complete native device tokens and, only after + human authorization, acquires `withNativeMainExclusiveClaim` + (src/codex/native-main-claim.ts:167), rechecks recovery/admission fence, + asserts original path/hash/inode before atomic rename, requires same + chatgpt account identity, writes access+refresh+id token + account_id + together, advances the mutation epoch, and reconciles runtime/quota + state. Old identity token is never retained beside new credentials. No + claim held during human polling. + Audit-folded: do NOT reuse persistRefreshedMainAuthJson (:190-195) — it + spreads expected.tokens and never writes id_token, so the old identity + token would survive beside the new grant. The commit uses a SIBLING + persist that sets access_token/refresh_token/id_token/account_id + together and overwrites any prior id_token (adding the key is safe: + readMainAuthJsonCredential :122 tolerates it and + native-profile-store.ts:476-481 expects it). + +NEW `src/codex/main-device-reauth.ts` +- One process-owned active flow (opaque UUID, AbortController, bounded + terminal retention 5 min, grant deadline 15 min). Start/status/cancel + return only flowId, status, verificationUrl, deviceCode, and closed safe + failure codes per the 080 `MainDeviceReauthStatus` union. Injectable + login/commit dependencies for tests. Superseded/cancelled completions + never publish. No tokens/emails/raw account ids in DTO/log/error. +- Dedicated abort controller and direct `loginChatGPTNativeDevice` call: + MUST NOT use `startLoginFlow("chatgpt")` (would overwrite the chatgpt + scratch slot and 409 against pool logins, src/oauth/index.ts:1899-1973). + +NEW `src/codex/main-device-reauth-api.ts` +- `POST/GET/DELETE /api/codex-auth/main/reauth-device` with exact opaque + flow query, strict request keys, safe 400/404/409. Registered at the + management dispatch boundary (src/server/management-api.ts:385-407 + region); existing management auth/origin/session controls stay + authoritative. No CLI direct account-file write. + +MODIFY `src/cli/account-main.ts` +- `ocx account main reauth --device [--no-wait]`, + `reauth status --flow `, `reauth cancel --flow ` via the + management API; reject extra args before start. Register capability/help; + regenerate skill surface with `bun run skill:surface` if the capability + registry changes (tests/ci-workflows/skill-ocx.test.ts gates this). + Audit-folded: the native-main CLI branch point is account-main.ts (:181 + region, beside add/switch) with USAGE in src/cli/account.ts:64; the + management route-registry (src/server/management/route-registry.ts + MANAGEMENT_ROUTES) must gain the POST/GET/DELETE rows or + management-route-registry.test.ts and the capabilities ratchet go red — + do NOT grow UNDECLARED_ROUTES_2026_08_28. + +## Hub fence resolution (open decision 1, resolved here for audit) + +On a headless hub the native owner lifecycle is a no-op +(src/server/index.ts:1026-1046 binds the no-op when +`shouldSyncCodexOnStart` is false; the gate is composed at +src/codex/desired-state.ts:130 — :79-81 is `localClientSyncAllowed`). +The reauth commit therefore MUST NOT depend on owner activation and MUST +NOT widen `shouldSyncCodexOnStart` (that gate covers client-config sync, +not credential rewrite). + +Audit-folded correction to 080: 080's `assertNativeMainOwner` at +preparation/commit is RETRACTED for this layer. That assert throws without +a held owner entry (src/codex/native-main-owner.ts:302-314), which would +make hub reauth always fail. The exclusive claim is owner-independent +(src/codex/native-main-claim.ts:167, FS/SQLite lock only). The fence is +pinned to: `withNativeMainExclusiveClaim` + in-process admission fence + +path/hash/inode assertion + recovery/admission snapshot recheck, exactly +as on workstations. Only claim/admission failure maps to +`native_main_unavailable`; no write occurs without the full fence — an +unfenced write is a C4 violation, not a fallback. + +## Tests (red-first; domain tests/codex-integration, tests/oauth, tests/cli) + +NEW `tests/codex-integration/main-device-reauth.test.ts` — same-account +success without codex/keyring; wrong identity refused; missing token +fields; cancelled/superseded late result cannot publish; concurrent file +replace/refresh/profile switch; atomic write failure; claim unavailable → +native_main_unavailable with zero writes; no pool-row mutation; DTO/log +secret scan. +NEW `tests/codex-integration/main-device-reauth-api.test.ts` — route +contract: strict keys, 400/404/409 shapes, unauthorized rejected, +`__main__` still refused by `/api/codex-auth/login`. +MODIFY `tests/oauth/chatgpt-device-auth.test.ts` — native result retains +idToken in-process; per-fetch deadline fires on a hung stub fetch. +Audit-folded: native-main CLI tests land in +tests/cli/cli-native-profile.test.ts (native-main CLI); the pool +cli-account.test.ts keeps only the __main__ login rejection cases. +MODIFY `tests/cli/cli-native-profile.test.ts` — reauth --device surface, +status, cancel, arg rejection. +All NEW files: layout.json explicit + expected-fixture entries. + +## Docs / ownership + +structure/ ownership docs for src/codex, src/oauth, src/cli, src/server +synced in this PR (structure:check must stay green). Headless recovery +instructions updated (docs-site) in the same PR. Security draft stays in +scratch; only the implementation + regression diff is published. diff --git a/devlog/_plan/260912_unimplemented_trio_stack/030_l3_main_card_relogin_ui.md b/devlog/_plan/260912_unimplemented_trio_stack/030_l3_main_card_relogin_ui.md new file mode 100644 index 0000000000..41c1196409 --- /dev/null +++ b/devlog/_plan/260912_unimplemented_trio_stack/030_l3_main_card_relogin_ui.md @@ -0,0 +1,104 @@ +# L3: main-card Re-login with device code (#3898 GUI) + +Class C3 (auth-adjacent GUI). Stack layer 3, base the L2 branch. Branch +`codex/260912-native-main-reauth-ui`. Adopts +devlog/_plan/260912_accounts/090_reauth_ui.md, revalidated against current +`dev` by the GUI code map. Depends on L2's +`/api/codex-auth/main/reauth-device` contract. + +## Problem + +The main card is a locked App-login identity: expired state shows only +`codexAuth.mainTokenExpired` ("sign in again via Codex App login", +gui/src/components/codex-account-pool-main-card.tsx:183-185) and no +Re-login control (props at 21-56 have no `onReauth`). Pool rows have the +full device-code modal; the main card has nothing. + +## Constraints (090 + code map) + +- MUST NOT reuse `AddCodexAccountModal` / `openReauth("__main__")` / + `reauthAccountId=__main__`: the pool login route rejects `__main__` + (src/codex/account-id.ts:15-20; src/codex/auth-api.ts:221-224,2736-2748) + and a successful pool login writes `isMain: false` rows + (src/codex/auth-api.ts:2934-2939) — wrong credential store. +- DTO field chain: backend DTO → hook-validated state → main card only; + no device code in browser storage; verification URL accepted only from + the backend contract, never from arbitrary payloads. +- New copy lands in ALL locale files (en, de, fr, ja, ko, ru, tr, zh, + zh-TW) per gui/AGENTS.md "Text and i18n". +- `tests/gui/provider-workspace-auth.test.ts:248` currently requires + `codexAuth.mainTokenExpired` on the main card; updating that copy is + part of this layer. + +## Changes + +NEW `gui/src/components/use-main-device-reauth.ts` +- Dedicated hook mirroring the pool OAuth hook's start/poll/cancel shape + (gui/src/components/use-add-codex-account-oauth.ts:27) against + `/api/codex-auth/main/reauth-device`: `start()` POST, `poll(flowId)` + with visibility polling (2s tick, 10s per-tick timeout, stop on terminal + status), `cancel(flowId)` DELETE, unmount/abort cleanup. +- Normalizes closed status/error payloads; ignores late responses from a + replaced flow (flowId ownership); never accepts token/account-id fields; + renders only verificationUrl + deviceCode + status. + +MODIFY `gui/src/components/codex-account-pool-main-card.tsx` +- New optional `onReauthDevice` prop. When `showReauth` (83) is true, + render a "Re-login with device code" CTA beside the existing copy; after + start, show verification URL + human code + pending status + cancel; + success triggers the existing parent refresh. +- Layout stays consistent with the current card; pool Add/Re-login and the + native profile picker are untouched. + +MODIFY `gui/src/components/CodexAccountPool.tsx` +- Own main-reauth modal state separate from `showAdd`/`reauthId` + (75,94; openReauth at 189-192); wire `onReauthDevice` at the main-card render (515-533); + pause pool refresh while the main flow is active, same as the existing + modal pause (174-178). + +MODIFY `gui/src/i18n/{en,de,fr,ja,ko,ru,tr,zh,zh-TW}.ts` +- New `codexAuth.*` keys: CTA label, pending status, cancel, terminal + failure copy (actionable, safe; no auto-retry wording). Revise + `mainTokenExpired` so it no longer claims App login is the only path. + +## Audit folds (wp4 A) + +- Start POSTs an EMPTY body (the route rejects any body with 400); poll + immediately until verificationUrl/deviceCode arrive (they are empty in the + start response), and keep the last url/code through the committing state. +- Map the full MainDeviceReauthStatus union + HTTP error shapes: committing + (no url/code), failed.code (identity_mismatch, credential_changed, + native_main_unavailable, device_authorization_failed, + publication_failed, reconciliation_failed), 409 flow_in_progress, 503 + native_main_unavailable; when credentialUpdated is true the copy never + claims the file was unchanged; the verification URL is allowlisted to + https://auth.openai.com/codex/device. +- structure claim lands in structure/gui-and-management-api.md (the + Codex-accounts row :312), not overview.md. + +## Tests (red-first) + +NEW `gui/tests/main-device-reauth.test.tsx` — happy-dom mount per +gui/tests convention: CTA starts the dedicated route (never +`/api/codex-auth/login`), code/URL display, cancel ownership, stale-poll +ignore, success refresh, keyboard and error states. +MODIFY `tests/gui/provider-workspace-auth.test.ts` — main-card contract +updated for the new CTA + copy. +MODIFY `tests/gui/codex-auth-modal-status.test.ts` if locale-key +assertions enumerate codexAuth keys. +The happy-dom file lives under `gui/tests/`, outside the `tests/` layout +map — layout.json explicit + expected-fixture entries are needed only for +any NEW `tests/gui/*` source-contract file, not for `gui/tests/*`. + +## Docs / ownership + +L3 touches owned `gui/`: sync structure/overview.md and +structure/gui-and-management-api.md in this PR (structure:check must stay +green). + +## Verification + +`cd gui && bun test tests/main-device-reauth.test.tsx` plus the touched +suites; `bun run lint:i18n` for copy. Local GUI build NOT RUN; PR +screenshot evidence comes from hosted CI built artifacts, or an explicit +recorded exemption (repo gate: gui-mentioning PRs need a screenshot). diff --git a/devlog/_plan/260912_unimplemented_trio_stack/040_l4_native_paginated_writer.md b/devlog/_plan/260912_unimplemented_trio_stack/040_l4_native_paginated_writer.md new file mode 100644 index 0000000000..0b6e879460 --- /dev/null +++ b/devlog/_plan/260912_unimplemented_trio_stack/040_l4_native_paginated_writer.md @@ -0,0 +1,91 @@ +# L4: paginated history — offline ordinal recovery, live refusal preserved (#4311) + +Class C4 (user data). Stack top, base the L3 branch. Branch +`codex/260912-native-paginated-writer`. + +## Problem and scope decision (open decision 2, resolved here for audit) + +#4311's live defect (ordinal-0 `session_meta` clone) is already guarded: +`updateSessionMeta` throws for paginated records before writing +(throw at src/codex/history-provider.ts:1172), and preflight refuses +`history_paginated_requires_native_writer` +(structured field src/codex/inject.ts:899; preflight closure +src/codex/inject.ts:1182-1194). The residual acceptance is +(a) native paginated writer support and (b) corrupted-rollout recovery. + +(a) needs a Codex-owned writer API/IPC. None exists in this tree: Codex +owns ordinals and the live projection cursor +(structure/codex-home.md:232-234), `appendRolloutLine` deliberately does +not allocate ordinals (src/codex/history-provider.ts:77,248), and H +serializes only OpenCodex writes (src/codex/history-lock.ts; +src/codex/internal/history-writer.ts:86,107). Inventing N+1 is explicitly +forbidden by the issue (concurrent native writer / stale cursor). This +layer therefore ships (b) the offline recovery tool, keeps (a) refused +with the same structured reason, and says so in the PR. A follow-up +native-writer integration needs a Codex-side write API first — reported, +not faked. + +## Changes + +NEW `src/codex/history-ordinal-recovery.ts` +- Offline repairer for the #4311 corruption shape: an unprojected suffix + whose ordinals regress (projector error `expected N, got 0`). +- Preconditions, all enforced before any write: + - Codex fully closed (no running Codex process holds the home; detect + via the same process/home inspection the service uses, fail safe when + undecidable). + - Target resolution follows `resolveCodexStateDbPath` and + `threads.rollout_path` (src/codex/paths.ts:107-108; the column is + read through history-provider, not paths.ts) — never assume + `~/.codex/sessions`. + - Suffix shape verified: ordinals strictly increase before the boundary, + regress at the boundary, and the suffix parses cleanly. Anything else + refuses. + - Byte-identical backup written before mutation (manifest beside the + existing backup convention, src/codex/history-provider.ts:30). +- Rewrite: only ordinal digits in the unprojected suffix, renumbered to + continue the pre-boundary sequence; message text, ids, timestamps, and + all earlier bytes preserved. Exact readback verification before + reporting success. Dry-run (verify-only) is the default; `--write` + applies. + +MODIFY `src/cli/` (doctor/dispatch surface per existing conventions) +- `ocx doctor history repair-paginated-ordinals [--thread ]` + [--write]: runs the recovery, prints boundary, counts, backup path, and + readback result. Register capability/help; regenerate skill surface if + the registry changes. + +MODIFY `structure/codex-home.md` +- Record the recovery tool's ownership of offline ordinal repair and + restate that live paginated writes stay refused (structure:check gate). + +Explicitly unchanged (regression-tested, not edited): +`preflightCodexHistoryInjection` (history-provider.ts:307), +`appendRolloutLine` (77), `updateSessionMeta` paginated guard (1172), +inject pre/postflight (inject.ts:1182,1295,1332), catalog-only sync +(src/codex/sync.ts:216). + +## Tests (red-first; tests/codex-integration) + +NEW `tests/codex-integration/history-ordinal-recovery.test.ts` +- Synthetic fixture: session_meta ordinal 0 followed by event ordinal 1 + (the issue's minimal shape) behind a healthy increasing prefix. +- Dry-run reports and writes nothing (byte-identical file). +- Applied repair renumbers only the suffix; every non-ordinal byte + identical; readback passes; backup exists and matches the original. +- Refusals: Codex process detected / undecidable; suffix shape mismatch + (no regression, gap, unparsable line); missing backup space; absolute + rollout_path outside CODEX_HOME via sqlite_home. +- Preservation invariants red-first: run the preservation assertions + against the unimplemented command first (red), then implement (green). +MODIFY `tests/codex-integration/codex-history-provider.test.ts` +- Assert preflight refusal reason unchanged for paginated rows (the + recovery tool must not become a live writer). +NEW files: layout.json explicit + expected-fixture entries. + +## Out of scope + +Live paginated writes, ordinal allocation, native-writer IPC, any change +to the authless/compaction relabel fork (inject.ts:1098), provider-table +lifetime policy (separate #4311 sub-thread, tracked by containment unit), +in-app repair while Codex runs. diff --git a/docs-site/astro.config.mjs b/docs-site/astro.config.mjs index b25586f7a6..b76a01cdd5 100644 --- a/docs-site/astro.config.mjs +++ b/docs-site/astro.config.mjs @@ -86,6 +86,7 @@ export default defineConfig({ translations: { fr: "Guides", ko: "가이드", "zh-CN": "指南", "zh-TW": "指南", ru: "Руководства", ja: "ガイド", tr: "Kılavuzlar" }, items: [ { label: "Remote Hub Deployment", translations: { fr: "Déploiement Remote Hub", ko: "Remote Hub 배포", "zh-CN": "Remote Hub 部署", "zh-TW": "Remote Hub 部署", ru: "Развёртывание Remote Hub", ja: "Remote Hub のデプロイ", tr: "Remote Hub Dağıtımı" }, slug: "guides/remote-hub" }, + { label: "Remote Workspace", translations: { fr: "Espace de travail distant", ko: "원격 워크스페이스", "zh-CN": "远程工作区", "zh-TW": "遠端工作區", ru: "Удалённая рабочая область", ja: "リモートワークスペース", tr: "Uzak Çalışma Alanı" }, slug: "guides/remote-workspace" }, { label: "Providers", translations: { fr: "Fournisseurs", ko: "프로바이더", "zh-CN": "提供商", "zh-TW": "供應商", ru: "Провайдеры", ja: "プロバイダー", tr: "Sağlayıcılar" }, slug: "guides/providers" }, { label: "Factory Droid Bridge", translations: { fr: "Pont Factory Droid", ko: "Factory Droid 브리지" }, slug: "guides/factory-droid" }, { label: "Cursor Private Inference", translations: { ko: "Cursor Private Inference" }, slug: "guides/cursor-private-inference" }, diff --git a/docs-site/src/content/docs/fr/guides/codex-integration.md b/docs-site/src/content/docs/fr/guides/codex-integration.md index 57b5b63fde..4f07ffaced 100644 --- a/docs-site/src/content/docs/fr/guides/codex-integration.md +++ b/docs-site/src/content/docs/fr/guides/codex-integration.md @@ -399,8 +399,7 @@ ocx config set codexPool '{"excludedPlans":["free"]}' C'est une politique de sélection, pas un blocage. Un compte écarté conserve ses identifiants, son historique de quota et son affinité de thread, reste visible dans la liste des comptes et demeure joignable par sélection explicite comme `work/gpt-5.5`. Seule la rotation automatique cesse de le choisir, y compris lorsqu'il est déjà le compte actif ou déjà lié à un thread — l'état exact que laisse un abonnement expiré. -Deux limites volontaires. Le compte Codex principal n'est jamais écarté par forfait, car le routage en mode sélection seule ne lit pas son forfait dans les identifiants natifs protégés ; une règle le couvrant se contredirait. Et lorsqu'il ne reste aucun compte non écarté, le compte écarté répond quand même au lieu d'échouer : mettre tous les comptes en pause reste le moyen d'arrêter complètement le service. Il n'existe pas de `minimumPlan`, car classer les forfaits ChatGPT entre eux exige un ordre total qui n'existe pas ici. - +Le compte Codex principal reste exempt de l’exclusion par forfait : le routage en mode sélection seule ne lit pas ses identifiants natifs protégés. Si tous les comptes éligibles du pool sont exclus, la sélection automatique ne renvoie aucun compte. Les routes désignant explicitement un compte restent disponibles, avec les contrôles de pause, d’authentification et de droits du modèle. La carte et le CLI affichent le forfait exclu séparément de l’état des identifiants. Il n’existe pas de réglage `minimumPlan`, faute d’ordre total des forfaits. ## Restauration de Codex natif `ocx stop` arrête le proxy et le service d'arrière-plan installé, puis tente de restaurer Codex natif. OpenCodex retire les éléments de routage dont il peut vérifier la propriété et signale une restauration incomplète si les fichiers de configuration ne peuvent pas être récupérés en toute sécurité. diff --git a/docs-site/src/content/docs/fr/guides/web-dashboard.md b/docs-site/src/content/docs/fr/guides/web-dashboard.md index 2e333e39d8..2d4d753bb5 100644 --- a/docs-site/src/content/docs/fr/guides/web-dashboard.md +++ b/docs-site/src/content/docs/fr/guides/web-dashboard.md @@ -57,6 +57,8 @@ gestionnaire de mots de passe. | **Stockage** | Consultez en lecture seule la répartition du disque de CODEX_HOME — sessions, archives, bases de données et pièces jointes. Pour le nettoyage facultatif des archives, prévisualisez les N % les plus anciennes, puis placez-les en quarantaine dans `CODEX_HOME/.trash` (par défaut) ou supprimez-les définitivement après avoir coché une case explicite. **La stratégie de nettoyage automatique** est facultative et **désactivée par défaut** (`storageCleanupPolicy.enabled`) ; configurez son seuil, sa cible, sa planification et son mode sur la page **Stockage**, ou lancez **Exécuter maintenant**. Les entrées mises en quarantaine peuvent être restaurées depuis cette page (JSONL et fils). Les sessions actives restent en lecture seule. Le nettoyage et la restauration sont refusés tant que Codex verrouille le fichier `state_*.sqlite` le plus récent ou actif. | | **Arrêter** | Arrêtez proprement le proxy et le service d'arrière-plan installé, restaurez Codex natif et quittez (`POST /api/stop`). Sur Windows avec le backend Planificateur de tâches, le tableau de bord refuse et vous demande d'exécuter `ocx stop` : le wrapper peut relancer le proxy après la fin de la tâche, et seul un stop exécuté hors du proxy peut vérifier cette fenêtre de redémarrage avant de restaurer votre configuration client. Rien n'est modifié en cas de refus. | +Les vues Utilisation, Tableau de bord, Fournisseurs, Catalogue des fournisseurs et Clés API signalent les enregistrements exclus, même sans résultat lisible. Les décomptes, les dates et les classements reposent uniquement sur les lignes lisibles. L’enregistrement de l’ordre des modèles par utilisation est refusé si l’historique est incomplet : choisissez un autre ordre ou réparez l’historique avant de réessayer. + ### Filtrer les requêtes Les filtres combinent interface, requêtes interceptées, fournisseur, modèle exact, statut, période, vitesse et identifiant de conversation dans le journal chargé. Les choix incluent les tentatives de repli ; les modèles ignorent la casse et les espaces externes, sans correspondance partielle. Un choix disparu revient à Tous. diff --git a/docs-site/src/content/docs/fr/reference/cli/agents.md b/docs-site/src/content/docs/fr/reference/cli/agents.md index 0d781042d9..69d4ac39d0 100644 --- a/docs-site/src/content/docs/fr/reference/cli/agents.md +++ b/docs-site/src/content/docs/fr/reference/cli/agents.md @@ -98,6 +98,8 @@ Inspectez les requêtes de proxy, l’utilisation, le stockage, la mémoire et l ocx observe usage --range 30d --json ``` +Si certains enregistrements ne peuvent pas être inclus, la sortie lisible affiche un avertissement, même sans ligne lisible. Les totaux affichés ne reflètent que les enregistrements lisibles. Si un filtre ne trouve aucune correspondance lisible, la sortie affiche l'avertissement et des indications au lieu des lignes de totaux ; les enregistrements ignorés peuvent contenir des correspondances. `--json` préserve le diagnostic `usageIncomplete` et sa raison. + ### `ocx debug ` Lisez ou modifiez les remplacements de débogage d'exécution via la gestion du proxy en cours d'exécution API. diff --git a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md index b28642e0ca..b531e1b307 100644 --- a/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/fr/reference/cli/providers-accounts.md @@ -315,6 +315,9 @@ ocx account main doctor [--json] ocx account main list [--json] ocx account main register