Skip to content

v0.2.13 follow-ups: profit control as upstream, one reasoning recovery switch, retired settings and options deleted - #235

Merged
HTExplicit merged 39 commits into
mainfrom
chore/v0213-followups
Oct 4, 2026
Merged

HTExplicit merged 39 commits into
mainfrom
chore/v0213-followups

Conversation

@HTExplicit

Copy link
Copy Markdown
Owner

Ships together with the upstream v0.2.13 integration (#232) as v0.2.13-codexrip.1. These are the follow-ups the owner decided on 2026-10-03. .downstream/upstream-review-v0.2.13.md describes the resulting tree.

What changes for administrators

  • Group profit control is upstream's again. The group create and edit dialogs have upstream's profit control section (profit_control_enabled, profit_min_margin, profit_safety_buffer), off by default. The compile-time kill switch, the 400 on profit_* fields, the constants in the API-key auth snapshot and the read-only "retired" block are removed. The profit code and its tests are byte-identical to upstream v0.2.13; the Codex models manifest stays outside the gate, as upstream.
  • New page 推理恢复 (Reasoning Recovery) under 扩展功能: one switch and Save, no step-up. It decides for all OpenAI accounts whether a rejected reasoning ciphertext is recovered. GET and PUT /api/v1/admin/reasoning-recovery with {"enabled": boolean}; stored as the settings row reasoning_recovery_config, on while no row exists, read from memory on the forwarding path, saves audited.
  • Account dialogs. The two per-account reasoning options are gone from create, edit and bulk edit. For TypeSafe accounts the hints and placeholders name TypeSafe instead of Anthropic. An upstream model sync is offered and started only where the backend can list models (one shared rule): no failing sync after creating a TypeSafe account, and no button for Vertex service accounts or Antigravity API-key accounts without an /antigravity base URL.
  • System prompt binding only where a prompt can be injected. Accounts on a platform without an insertion point (TypeSafe) get no binding control in the edit dialog and row menu, are skipped by the binding endpoint, and are left out of the usage counts; the bulk bar offers the action only when a selected account can take a prompt.
  • 系统设置: the read-only block 已弃用设置 is removed.
  • Ops error detail: the continuation diagnostics have no text for policy_unavailable any more, and the text for disabled names the switch.

What changes for API users

  • Chat reasoning replay is deleted: a Chat Completions request to a Responses upstream is the plain conversion of the client's messages, as in upstream v0.2.13.
  • Recovery of a rejected reasoning ciphertext works as before but follows the global switch. An account that had it switched off individually is recovered again after the upgrade.
  • In a group with profit control enabled, accounts that fail the margin check are skipped; when none is left the request is answered with 503. A group whose stored profit_control_enabled is already true is gated from the first start.
  • Every cached API key is reloaded once (auth snapshot version 28).

Removed

  • Chat reasoning replay: engine, recorder hooks, replay rules, the batch half of the reasoning state cache.

  • The two per-account reasoning options: stored keys, validation, import merge, bulk-edit preflight, scheduler cache projection, UI component, specs, locale keys.

  • The retired settings openai_apikey_alpha_search_responses_bridge_enabled and openai_apikey_prompt_cache_key_normalization_enabled: code, the deprecated_settings field of GET /admin/settings, the UI block, the stored rows.

  • The profit control kill switch and its "dormant" tests.

  • The stop reason policy_unavailable.

  • CI: .github/scripts/test_reasoning_replay.sh is now test_reasoning_recovery.sh; a spec deleted in fix: rework connection tests, context capacity, system prompts, Cindy and UI foundation #206 left the Makefile critical list.

  • The dated live reasoning diagnostic (owner decision of 2026-10-04): the live test, its offline control test, its support file and the second command of the gate script. Its broker scripts outside this repository are deleted as well; the study's ledger on the server is not touched.

Migrations

On a database that already ran v0.2.11-codexrip.9, the first start applies six files in this order: the two upstream 241 files and 265 (already on main with #232), then the three of this PR:

  • 266_purge_retired_openai_apikey_compat_settings.sql: deletes the two retired settings rows.
  • 267_purge_account_reasoning_options.sql: removes the two per-account reasoning option keys from accounts.extra; a stored value is not carried over to the global switch.
  • 268_purge_system_prompt_bindings_without_insertion_point.sql: removes extra.system_prompt from accounts on a platform without an insertion point.

Each is a single statement with lock and statement timeouts and a no-op when run again. None has a down path; an older image would treat every account as default-on for replay and recovery.

Verification

Run on this branch:

  • go build ./... (windows); go vet with the unit and the integration tag on the 14 touched packages and with unit on the 4 packages whose tests import them; gofmt -l empty for the 102 changed Go files; golangci-lint 2.13 with 0 issues on the 14 packages.

  • 120 Go tests by name with the unit tag in ten packages, all passing: upstream's profit tests (43 in service, 10 in handler, 2 in handler/dto, 3 in cmd/profit-preview), the settings tests the purge changed, the system prompt binding tests, the recovery route and audit tests, TestAPIContracts, both TypeSafe migration tests.

  • .github/scripts/test_reasoning_recovery.sh as CI runs it: 60 tests.

  • Migrations 266, 267 and 268: their integration tests pass against a disposable local PostgreSQL 18.1 after the harness applied all 349 migration files to the empty database.

  • Frontend: vue-tsc --noEmit; ESLint on the 48 changed files; vitest for 13 spec files with 279 tests (the 11 changed specs, locale key completeness, the diagnostics component). These ran against a local dependency tree with axios 1.18.1 and dompurify 3.3.1, while the lockfile pins 1.20.0 and 3.4.14.

  • test_upstream_automation_contract.py, test_upstream_risk.py, test_release_preflight.py.

  • After the last changes (live diagnostic deleted, the frozen harness helper loads the recovery switch, two zh texts back to the approved wording): go build ./..., go vet on service with the unit and the reasoning_fidelity tag, the gate script, the two offline fidelity tests, vue-tsc --noEmit, ESLint on the two changed frontend files and vitest for six files with 20 tests.

  • Production, read only through the admin API on 2026-10-04: all 12 groups hold the profit defaults (off, 0, 0); none of the 862 accounts holds either reasoning option key or a system prompt binding, so 267 and 268 change no row there; both retired settings rows exist and 266 deletes them.

Not run locally: the full unit, integration, race and e2e suites, the linux build and the candidate image, whole-project ESLint and the production frontend build, the production migration path against a database in the state of v0.2.11-codexrip.9, a browser preview of the assembled tree, and real model calls.

🤖 Generated with Claude Code

Codex and others added 30 commits October 4, 2026 09:02
Group profit control (upstream Wei-Shaw#4925) was switched off downstream by a
compile-time kill switch, a 400 on any profit_* field in group create and
update, and constant false/0/0 values in the API-key auth snapshot. The
owner decided on 2026-10-03 that the feature is upstream's again: editable
per group, off by default.

- openai_profit_control.go, gateway_profit_control.go, group.go,
  admin_group_duplicate.go and handler/admin/group_handler.go are
  byte-identical to v0.2.13 again, and the profit-preview command with
  service/profit_preview.go is back.
- The auth snapshot carries the stored group values. Its version becomes
  28: version 27 entries hold the constants instead of the stored values,
  and the gate is built from the cached group without a database read, so
  they have to be reloaded.
- The legacy selection failure summary counts profit vetoes again instead
  of counting a vetoed account as eligible.
- Upstream's comments at the gate call sites are restored where the fork
  had shortened or dropped them.
- Tests: upstream's profit tests replace the rewrites that pinned the
  switched-off state; profit_control_dormant_regression_test.go and
  TestGroupHandlerRejectsLegacyProfitControlWrites are removed; the
  simple-mode normalization tests send the profit fields again.

No migration: the three columns come from 192, and the invalidation
trigger function as last defined by 260 has the body of 193.

A group that leaves profit control off gets no gate, so scheduling is
unchanged for it. The profit code still differs from v0.2.13 only where a
downstream contract wraps it: the runtime-breaker probe on selection
results, the single repository read of the previous-response owner, Codex
quality runs, the WebSocket local close error, the scheduler-based token
count selection and the (bool, reason) eligibility helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ialogs

The create and edit dialogs get upstream's editable section back: the
switch, the minimum gross margin and the safety buffer in percent, and the
validation before submit, with groupsProfitControl.ts, its spec, the
profitControl texts of both locales and the request types. The read-only
block of the edit dialog, its retiredProfitControl texts and the comment
that called the fields deprecated are removed.

The console theme needs no rule and no hook: the section has the markup of
the peak-rate block above it, and its divider, checkbox, label, hint, field
labels and inputs resolve to the same computed styles as their neighbours
in light and dark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v0.2.13 review listed the profit control kill switch among the
deviations kept. It now states the restoration, the files that are
identical to upstream again, the auth snapshot version, why no migration
is needed, what a group without a gate still sees differently (the
snapshot reload and the two counters in the legacy selection summary), the
profit lines that still differ inside downstream mechanisms and the checks
that were run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… options

Invalid reasoning ciphertext recovery becomes one global switch on its own
page under 扩展功能; the two per-account options leave the account dialogs.
This is the frontend half: the backend for the endpoint below and the removal
of the per-account backend code follow separately.

- /admin/reasoning-recovery (sidebar: 扩展功能, after Codex 运行设置): one
  switch with Reload and Save, no step-up. It reads and writes
  GET/PUT /api/v1/admin/reasoning-recovery, body and answer
  {"enabled": boolean}. No value is shown before one is loaded, and an answer
  without a boolean counts as a failed request.
- Create, edit and bulk edit of an account no longer show or send
  openai_chat_reasoning_replay_enabled and
  openai_reasoning_signature_recovery_enabled. OpenAIReasoningPolicyFields,
  utils/openaiReasoningPolicy, their specs and locale keys are deleted, and
  buildOpenAIExtra starts from a plain copy again.
- oauthPassthroughDesc now names the global switch instead of the option that
  used to sit below it.
- Console theme: the page takes the 系统提示词 rules through its own data-ui
  hook; no new rule set.
- downstream-verify selects the new view spec in place of the two deleted
  specs, which test_upstream_automation_contract requires to exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
openai_apikey_alpha_search_responses_bridge_enabled and
openai_apikey_prompt_cache_key_normalization_enabled lost their gates and
write fields on 2026-09-01 (cce166e). Since then the values were only
loaded, shown in 系统设置 as 已弃用设置(只读,不生效) and written back
unchanged on every save. They now go completely.

- Frontend: the block, its state, the deprecated_settings field of the
  settings API type and its locale keys are removed, together with the
  alphaSearchMode* and promptCacheKeyMode* account locale keys that no
  component used and a stylesheet comment that named the block.
- Backend: GET /admin/settings answers upstream's plain document again
  (no deprecated_settings); saving no longer carries the two values; the
  setting keys, their defaults, parse and update code, the audit comparison
  and the two never-read fields of OpenAIRefusalRecoveryRuntime are deleted.
- Migration 266 deletes the two settings rows; an integration test restores
  them next to data that must stay and applies the migration twice.
- Tests: the cases that only covered the two values are deleted. The ordinary
  API-key Alpha Search tests keep every assertion and lose the retired switch
  from their fixtures, so two are renamed after what they assert:
  TestForwardAlphaSearchOrdinaryAPIKeyUsesDirectAlphaSearchEndpoint and
  TestAlphaSearchOrdinaryAPIKeyToolErrorDoesNotSwitchAccounts. The handler
  fixture passes no settings service again, as before the switch existed.

The account option openai_prompt_cache_key_mode and the PAT-only Alpha Search
bridge are live and not touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v0.2.13 review was last written for the profit control follow-up alone.
With the settings purge and the reasoning options change on the same tree,
three of its statements were no longer true: the first start applies four
migrations, not three (266 is new); two settings rows are deleted on
upgrade; and the per-account reasoning options are not untouched.

- Integration decisions: the deletion of the two retired API-key
  compatibility settings, and the frontend half of the reasoning options
  change, with what this tree still lacks (the endpoint and the removal of
  the per-account backend code) and the save error that follows for an
  account whose stored value is not a boolean.
- Migrations: 266.
- Validation: the checks run on the combined tree, why upstream-risk.json
  needs no regeneration, and an intro line that fits the follow-up bullets.
- Release target: four migrations; no setting takes a new value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream selects the account for the Codex models manifest with
SelectAccountForModelWithExclusions, which installs no profit gate. The
downstream handler selects through the scheduler, so the scheduler's defensive
gate applied to the manifest as soon as a group enabled profit control: every
account over the threshold was filtered, and a group with none left was
answered with 503 "No available OpenAI accounts" where upstream serves the
manifest. A veto after the slot would have ended as an empty 200, because this
handler has no veto branch.

CodexModels now marks its request context with
WithOpenAIProfitControlSuppressed before the selection loop, as token counting
does. No gate is installed for the request, so neither the filter nor the veto
can occur there.

TestCodexModelsIgnoresGroupProfitControl: a group with profit control on whose
only account is over the threshold still gets its manifest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Reasoning options: create and bulk edit never send the two keys; the edit
  dialog no longer sets them but still sends them inside the stored extra it
  passes through. The lead sentence said that all three dialogs no longer send
  them, against its own sub-bullet. Name what the backend half has to remove
  for the pass-through to end; no strip code returns to the dialog.
- Profit control inside downstream mechanisms: two differences from v0.2.13
  were missing. The Codex models manifest selects through the scheduler under
  the handler's suppress marker, and a same-account retry gets a selection
  without a gate, so its check after the slot uses the request-level gate.
- Validation: the settings purge and the reasoning options change share three
  files (the two admin/accounts.ts locale files and
  pages/settings-ops-public.css, in separate hunks); only profit control
  shares none with the others. List the checks run for the review fixes.
- settings-ops-public.css: the hook table names the current SettingsView.vue
  lines of settings-dlg-foot (6386, 8035, 8088) and settings-list-box (7836).
  The removed deprecated-settings block moved them up by 27 lines, and three
  of the four numbers were 12 lines off before that. Comment only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner decisions after the preview of 2026-10-03.

- The page has the switch and Save only. The Reload button, its locale key in
  both locales and its spec case are gone. The value is read once when the
  page is opened: load() no longer has a second caller, so the reset of the
  error before a read and the two guards that only a second read needed
  (loading while a draft exists) went with the button.
- A failed read shows its error and no switch, with Save disabled, until the
  page is opened again. That is how the pages without a reload button behave
  (Codex 运行设置, 系统设置); no control was added for it.
- Sidebar: the entry used the shield of 安全审计 and 操作日志. It now has
  RecoveryIcon, the turn-back arrow (Heroicons arrow-uturn-left), defined like
  the other inline icons and mapped for the console theme to the new Icon.vue
  name undo, whose Lucide glyph is undo-2 (verbatim from lucide-static
  0.469.0). No other entry uses the icon, the path or the name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…again

- The account-level Alpha Search and prompt_cache_key mode selectors left the
  dialogs long ago; three cases only asserted that their test ids are absent.
  The edit and bulk edit cases are deleted. The create case also checks that
  the Responses mode stays on auto when the Laxa base URL is typed instead of
  taken from the preset, which sets force_responses; that part stays under a
  name that says so.
- admin.accounts.bulkEdit.noFieldsSelected lost its only assertion with the
  reasoning policy cases. A new bulk edit case selects the Responses routing
  field, changes the target to a platform that hides it and submits: nothing
  is sent and the message is shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sync

Two defects of the TypeSafe account dialogs that are upstream's (v0.2.13),
fixed by owner decision of 2026-10-03.

Wording. A TypeSafe account showed the generic API-key texts, which name
Anthropic. The Base URL and API Key hints now come from the per-platform keys
admin.accounts.typesafe.baseUrlHint / apiKeyHint (zh, en), through the
baseUrlHint and apiKeyHint computeds that already serve OpenAI and Gemini.
The edit dialog's placeholders are https://api.typesafe.ai and ts-..., the
values the create dialog already had.

Model sync. The create dialog started 同步上游支持的模型 after a create for
every API-key account with a concrete mapping target. A TypeSafe account
always has one (jev-latest), and System One has no model list, so every
create ended with a 400 from the sync and the warning toast 同步上游模型失败.
The button itself was decided by a platform set inside ModelWhitelistSelector,
a second rule that knew no account type.

utils/upstreamModelSync.ts now holds the one rule,
supportsUpstreamModelSync(platform, type, credentials): the pairs that
fetchUpstreamModelList and its request builders (service/upstream_models.go)
accept before any upstream call, with the two that also depend on credentials
(an Antigravity API-key account needs a base URL ending in /antigravity; a
Gemini OAuth account with a project is Code Assist, which has no list).

- Create dialog: the preview credentials exist, and the sync after a create
  starts, only where the rule holds. A preview also needs the API-key form,
  whose key authenticates it; a key left there no longer offers a preview in
  the OAuth and Bedrock sections.
- Edit dialog: the model selectors get the account ID, and the Antigravity
  button shows, only where the rule holds for the saved account.
- ModelWhitelistSelector offers the button for the target it is given; its
  platform set is gone. Bulk edit gives it no target, as before.

Besides TypeSafe this ends three offers the backend answered with 400: the
button for a Vertex service-account account, and the button and the sync
after a create for an Antigravity API-key account whose base URL does not end
in /antigravity. The backend is unchanged.

The rule was compared with the backend by running both over the eleven
platforms, the six account types and four credential variants: 264 cells, no
difference. That comparison is not part of the repository.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A system prompt reaches an account only through the gateway that forwards
its platform: the prompt service is called on the final body by
GatewayService (Messages), OpenAIGatewayService (Responses, Chat Completions,
Messages), GeminiMessagesCompatService and AntigravityGatewayService. A
TypeSafe account is forwarded by ForwardSystemOne, which relays the request
as it is, and System One has no system or instructions field. Still the
binding endpoint wrote a binding to it, the account dialogs offered the
control, and 系统提示词 counted it as inheriting the default (owner decision
of 2026-10-03: do not offer it, and skip it in bulk).

The rule is one list, service.SystemPromptPlatforms, the ten platforms those
four gateways forward. An account on any other platform takes no binding.

Backend
- SetBindings (PUT /admin/system-prompts/bindings, used by the edit dialog,
  the row menu and the bulk bar alike) loads the selected accounts and writes
  only to those on the list. The others are skipped the way IDs that match no
  account already were: no error, and the answer counts the accounts written.
- The usage counts of 系统提示词 are taken over the accounts on the list.
- Migration 268 removes extra.system_prompt from every account on another
  platform, soft-deleted rows included. Such a value could exist (the endpoint
  wrote it whatever the platform, and 259 turned an old prompt_skills "off"
  into one), so it is removed once instead of being ignored in code. One
  UPDATE, a no-op when run again; no trigger writes another row for it.

Frontend
- takesSystemPrompt (utils/systemPromptBinding.ts) is the copy of the list.
  The account edit dialog and the row menu offer 系统提示词 only where it
  holds.
- The bulk bar keeps its button: a selection is a list of IDs that can span
  pages. Its dialog now says that TypeSafe accounts are skipped, and reports
  the number actually updated.
- The page texts no longer promise every platform: the description, the hint
  under the default prompt and the notes name the TypeSafe exception.

Tests: SetBindings skips such accounts and writes nothing for a selection of
them only; the edit dialog and the row menu offer the control for OpenAI and
Antigravity accounts and not for a TypeSafe one. TestMigration268... (tag
integration) is added next to the tests of 263 and 266 and, like them, needs
a database: it was compiled here, not run.

Not closed here: account create, bulk update, data import and duplicate store
extra as given on every platform, so a direct API caller can still put the
key on such an account. Nothing reads it there and no dialog sends it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The intro of 系统提示词 named the TypeSafe exception too. In the console
theme that made the Chinese text wrap, with two characters left on a second
line and the head's buttons pushed down (seen in the preview capture). The
exception is stated right below it, under the default prompt and in the
notes, so the intro only loses its "on every platform" claim: accounts that
inherit the default receive it. Both locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v0.2.13 review described the tree before the owner's preview of
2026-10-03. Several of its statements are no longer true, and three
downstream differences from official were not written down.

Corrected
- 推理恢复 has the switch and Save, not Reload and Save; what a failed read
  leaves on the page; the entry's own sidebar icon.
- The two TypeSafe defects of the account dialogs are no longer "upstream
  warts kept as they are"; only the missing badge colour is.
- The open owner question about the binding selector of TypeSafe accounts is
  answered and removed.
- The first start applies five migrations, not four (268 is new), and the
  release carries two downstream migrations besides the tail one.
- System prompts for TypeSafe: not only "not applied".

Recorded
- TypeSafe in downstream account flows: the binding rule
  (service.SystemPromptPlatforms), what the endpoint, the counts, the dialogs
  and the page texts do with it, and what stays open to a direct API caller.
- Deliberate downstream deviations: the TypeSafe wording of the dialogs, and
  the one rule for the upstream model sync with the set it mirrors and the
  offers it ends besides TypeSafe.
- Migrations: 268.
- Validation: the checks of this change and what was not run.

gpt6-routing-and-prompts.md said that inheriting accounts use the site
default on every platform; it now states the rule and the TypeSafe exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e a prompt

The bulk bar offered 系统提示词 for every selection of 1 to 1000 accounts.
For a selection of TypeSafe accounts only, one selected row included, the
control had no effect: the binding request skips such an account. Its
dialog showed no skip hint for a single account, stated 当前生效:站点默认
for the selection and ended with 已更新 0 个账号, while the row menu of the
same account has no such entry (owner decision 5 of 2026-10-03).

Bar
- AccountsView gives the bar the selected rows the list holds (selAccounts,
  which selPlatforms and selTypes are now derived from).
- The button is absent when every selected account is among them and none
  takes a prompt. A selection is a set of IDs that can also name accounts of
  other pages, of every result or of an import; such a selection keeps the
  button, and the request skips the accounts that take none.

Dialog
- The bar tells the dialog how many of the selected accounts it knows to
  take a prompt. The edit dialog and the row menu, which offer the binding
  only for such an account, pass nothing.
- The skip hint no longer depends on the selection holding more than one
  account: it is shown whenever a selected account may be skipped, with its
  own wording for a single account the list has not loaded. A selection
  known to hold only accounts that take a prompt gets the plain count
  sentence again, and none for one account.
- What the binding puts in effect is stated only when a selected account is
  known to take a prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SetBindings skips an account that can never receive a prompt and migration
268 removes the bindings such accounts already held, but three other paths
still stored a given extra.system_prompt on any platform:

- account creation (buildAccountForCreate), which data import and duplicate
  go through with a stored extra: an export holding a binding re-created it
  on a TypeSafe account;
- bulk update (BulkUpdateAccounts), which merges the given keys into extra;
- UpdateAccountExtra, which re-authorization reaches with the caller's extra.

Only UpdateAccount protected the key. A binding stored that way could not be
removed again: the dialogs hide the control for such an account and the
endpoint skips it. So 268 was not final, and its header's "this release
writes no binding to these accounts" was not true.

The rule "an account outside SystemPromptPlatforms holds no binding" is now
kept where extra is written:

- account creation drops a given binding unless the platform takes a prompt;
- bulk update and UpdateAccountExtra drop the key on every platform, like
  the other keys that enter through their own endpoint.

On an existing account the binding endpoint is the only writer of the key;
an account edit keeps the stored value, as before. The header of 268 now
names these writers among the ways such a row came to hold a binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v0.2.13 review said two things about system prompt bindings that the
review of the post-preview changes found false or left open.

Corrected
- The bulk bar does not keep its button for every selection: it offers
  系统提示词 while the selection can hold an account that takes a prompt, and
  not when every selected account is loaded and none of them does. What the
  bulk dialog says about skipped accounts and about the effect is described
  as it is now.
- "Not closed: account create, bulk update, data import and duplicate store
  extra as given ... no dialog sends it" is removed. The data import dialog
  did send it, and the three writers are closed: creation drops a given
  binding unless the platform takes a prompt, bulk update and
  UpdateAccountExtra write the key on no platform.
- Migrations, 268: the writers that could leave such a binding are listed in
  full, and why the file is a single correction.

Recorded
- Validation: the checks of these fixes and what was not run.

gpt6-routing-and-prompts.md states the same rule for the bulk bar, account
creation and the two key-level writers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chat-to-Responses behaves as official v0.2.13 again where replay was the
fork's only difference: the request is the plain conversion of the client's
messages, tool call ids are normalized by the Codex transform, and nothing
is recorded from the response. Signature recovery stays on that path; it
now rebuilds each attempt from the clean body like the native path does.

Deleted: the replay engine and its recorder hooks in the Chat and Messages
handlers, the replay rules, the batch half of the reasoning state cache
(Redis keys, Lua branches, bounds, interface methods), the rejected-cipher
callback that only replay used, the request builder variant that existed
for replay preparation, ChatMessageResponsesInput, and the Chat cases of
the live diagnostic harness.

The harness and the CI gate are named for what they cover now: reasoning
recovery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Chat-to-Responses conversion always asks for reasoning.encrypted_content
in `include`, so the assertion that the second request never contains the
string "encrypted_content" failed. It now names what must not come back: the
ciphertext the upstream returned in the first turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every lookup and every write of the rejection memory built its own Redis
client, dialed, ran one script and closed the connection, so a request
carrying reasoning ciphertext paid a TCP dial and a handshake on its way
upstream. The dedicated client existed because the shared client does not
apply a context deadline to socket I/O.

The script now runs on the shared client through WithTimeout: the same
connection pool, with the remaining I/O budget as read and write timeout.
Waiting for a pooled connection, a new dial and a retry backoff end with the
operation's context, and the shared options stay untouched.

A Redis that stops answering still costs a request no more than its 100 ms
budget, and the connection that timed out leaves the pool. An unreachable
Redis now costs the remaining budget instead of failing at the first refused
dial, because the pool retries the dial while the operation waits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signature recovery was switched per account through accounts.extra and read
again before every send and before the retry decision. It is now one settings
row, reasoning_recovery_config = {"enabled": <bool>}, enabled while no row is
stored, served like the system prompt configuration: an in-memory value that
a save replaces at once and a background refresh renews after 60 seconds, so
the forwarding path never queries the database. A forwarding attempt reads
the switch once, when its recovery state is created. A save and a refresh
are serialized, so a read that began before a save cannot publish the older
value after it. A failed read at startup stops the start; a failed refresh
keeps the value in use.

GET and PUT /api/v1/admin/reasoning-recovery read and save
{"enabled": <bool>} beside the system prompt endpoints: admin
authentication, no step-up, the save audited as
admin.reasoning_recovery.update.

Removed with the account option: both extra key constants and their getters,
the validation and preservation in the admin service, the handler checks and
the import merge that hid stored values, the bulk-edit target preflight, the
scheduler cache projection, and their tests.

Cleaned after the dispatch layer removal: the policy calls took a context
they never used and returned a nil error, so the unavailable-policy state,
its stop reason and those operands are gone, and nothing produces
policy_unavailable any more. The request-shape helpers of
plugin_codex_recovery.go live in openai_reasoning_recovery_policy.go, and the
rejection is parsed by the one policy function.

The recovery gate selects the tests of the switch and no longer names the
deleted scheduler projection tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removes openai_chat_reasoning_replay_enabled and
openai_reasoning_signature_recovery_enabled from accounts.extra. Nothing
reads them: Chat reasoning replay is deleted and recovery has one global
switch. A stored value is not carried over to that switch. Rows that hold
neither key are not written, and a second run writes nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the global switch, its endpoint, storage and read path to the
current-state document, and names reasoning signature recovery where the
v0.2.13 review listed the per-account reasoning policy switches, which no
longer exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rejection memory ran on the shared Redis client through WithTimeout.
The shared client does not apply a context deadline to socket I/O, so each
write and read got the remaining budget as a timeout of its own, counted
from the moment it started: after a slow pool turn or dial, a stalled reply
could hold a request for about twice its 100 ms budget. An unreachable
Redis cost the whole budget, because the shared pool retries a refused dial
with a backoff while the operation waits.

The memory now runs on one long-lived client of its own, built with the
gateway cache from the shared options. It applies the context deadline, so
every write and read ends at the operation's deadline however long the
connection took to acquire. It sends a command once and dials once, so a
refused dial fails the operation at once. Connections are opened on demand
and reused, so an operation still pays no dial, and a connection that timed
out leaves this client's pool, not the shared one. The shared client's
options are untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing produces policy_unavailable since recovery became a direct call:
its producers went with the policy lookup. The continuation diagnostic
allow-list still accepted it, which only served rows stored by earlier
releases. The list now holds exactly the reasons recovery can emit; a
stored row carrying the retired value reads as an empty reason, like any
unknown value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The harness was the fork side of one sealed, dated study of Chat reasoning
replay and signature recovery (run reasoning-replay-recovery-20260907). Its
broker lives outside this repository: it runs one pinned binary once, a
stored ledger refuses a second run, and it passes only when the Chat replay
cases complete. With Chat replay deleted the remaining recovery cases
cannot be launched by it under any name, the bootstrap still required the
study's replay run id, and the harness gateway never read the global
recovery switch.

Deleted: the live test, its offline control test and its support file. The
CI gate keeps the offline unit contracts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReasoningFidelityGatewayForTest builds its gateway from a frozen settings
snapshot but installed no ReasoningRecoveryService, so a harness gateway
ran with recovery enabled whatever the snapshot stored. Before the switch
became global the option came from the frozen account row. The helper now
loads the switch from the same snapshot: a missing row keeps the enabled
default, a stored "off" stays off, and an invalid row fails the bootstrap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whether the dated live recovery diagnostic and its broker outside this
repository are deleted or kept and aligned is not decided. Until it is,
the harness stays as it was reviewed: the live test, its offline control
test, its support file and the second command of the gate are back, and
the frozen gateway helper they share with the fidelity harness has its
previous wiring again. This undoes b0a6e85ea and 664eed548.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cache

The client the rejection memory runs on is built with the gateway cache,
and nothing in the application closed it. The cache now releases it in
Close, and application cleanup calls that before it closes the shared
Redis client, which stays with its owner.

The blocked-transport test took its snapshot of the shared options after
the cache had been built, so it compared those options with themselves.
It takes the snapshot first now: a constructor that edits the shared
options in place fails it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nostics

The backend neither produces nor accepts the stop reason policy_unavailable
any more, so the Ops error detail keeps no text for it. The text was inline
in the component; no locale key belonged to it.

The text for "disabled" still described a per-account scope. Recovery has
one global switch now: a forwarding attempt reports "disabled" when the
switch is off, when the account type is not covered, or on a request path
recovery does not cover (Messages, or a final request that is not
/responses or /responses/compact). The text says that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex and others added 9 commits October 4, 2026 09:49
FRONTEND_CRITICAL_VITEST still named
src/features/cindy-group-split/__tests__/CindyGroupAuditDialog.spec.ts,
which #206 deleted. Vitest ignores a file filter that matches nothing, so
`make test-frontend-critical` selects the same specs as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v0.2.13 review was written by the integration and extended by two
follow-up branches, each describing its own tree: the frontend branch
called the recovery backend "not in this tree", the backend branch knew
no page, and both listed checks of trees that no longer exist. It now
describes the one tree that ships as v0.2.13-codexrip.1.

- Reasoning recovery is one entry: the global switch, its endpoint and
  page, the deleted Chat reasoning replay and per-account options, the
  rejected-cipher memory client, and the live diagnostic that stays as it
  is until the owner decides.
- Migrations: the six files of this release in the order the runner
  applies them, the production path (read) and the new-database path
  (executed), migration 267, and what an older image reads after the
  three purges.
- The profit control notes drop the Codex quality run, which #233
  removed; the TypeSafe check in buildAccountForCreate is described as it
  stands after #233.
- Validation states what was run on the assembled tree and what was not.
- Release target lists what changes with the upgrade before an
  administrator acts.

gpt6-models-and-prompts.md names the 推理恢复 page, the envelope and the
400 of the endpoint, and compact; it refers to migration 267 for the two
removed keys instead of repeating their names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four packages the follow-ups do not touch have tests that import the
changed ones (accounttools/policy, codexruntime/profile, handler/quotaview,
securityaudit). Their tests were type-checked on the assembled tree with
the unit tag; the Validation section says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both headers named git 06e865f as the previous release and cited line
numbers in it. 06e865f is v0.2.11-codexrip.8; the release before
v0.2.13-codexrip.1 is v0.2.11-codexrip.9 (291d25e, #233), where the
reader of the two account keys is service/plugin_codex_recovery.go:20-27
and the deprecated_settings block is handler/admin/setting_handler.go:513-519.
The other cited lines are the same in both commits.

Comment lines only; the statements are unchanged. The runner pins a
file's checksum from its first application on, and the only database
that applied these files was the disposable one of the validation run,
so the headers are corrected before the release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestOpenAIReasoningRecoveryChatToolRoundKeepsNoReasoningState ended by
checking that no gin context key contains "replay". The only such keys
were set by openai_chat_reasoning_replay.go, which is deleted, so the
loop asserted the absence of removed code. The test keeps its contract:
the second turn is the plain conversion of the client's messages, without
the first turn's ciphertext or reasoning id, and the reasoning state
store is neither read nor written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The zh hint of the switch said 被拒 reasoning next to 新推理, and the
scope note kept the English Chat-only. They now say 被拒推理项, the term
the English text and the downstream document use, and 只支持原生 Chat 的上游.

The recovery line of the Ops continuation diagnostics was labelled
推理签名恢复 while the reason for "disabled" in the same panel says
推理恢复, the name of the page. The label is 推理恢复 too. The English
texts already used one name and are unchanged.

Text only. The owner has not previewed these strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Release target names the stored data of the removed replay that the
  upgrade does not delete: the batches the previous image wrote to Redis
  under openai_reasoning_state:v1:{openai_reasoning_batches}:. No code of
  this release names the prefix; every key carries the 24-hour expiry
  the previous image set, so the last one is gone 24 hours after that
  image stored its last batch.
- The live recovery diagnostic was described as kept as it was. Against
  the previous release it is renamed from replay to recovery (files,
  build tag, tests, environment variables, user agent) and has lost its
  Chat half; the run id, the bootstrap contract and the recovery cases
  are unchanged. Deleting it or aligning it with the broker stays the
  owner's open decision.
- The upstream risk gate also runs on a manual dispatch for a pull
  request number, not for sync/upstream-* branches only. It recomputes
  from the commit the manifest records, so a dispatched run reproduces
  the manifest and asks for the upstream-reviewed label.
- Validation lists the four changes made after its runs (the headers of
  266 and 267, one assertion of the Chat tool round test, three zh
  strings), what was run again and what was not: ESLint, vue-tsc and
  vitest for the two frontend files, and an owner preview of the zh
  texts written after 2026-10-03.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The harness was the fork side of one sealed study of Chat reasoning replay
and signature recovery (run reasoning-replay-recovery-20260907). Its broker
outside this repository ran one pinned binary once and passed only when the
Chat replay cases completed; with Chat replay deleted it cannot run under
any name. The owner decided on 2026-10-04 to delete the whole tool.

Deleted: the live test, its offline control test, its support file and the
second command of the gate script. The frozen gateway helper it shared with
the reasoning fidelity harness now loads the recovery switch from the same
frozen settings snapshot, so that harness sees a stored off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hint and the scope note are the wording the owner approved in the
preview of 2026-10-03. The review document records the deleted live
diagnostic and what was run on the final tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HTExplicit
HTExplicit merged commit 2de8891 into main Oct 4, 2026
18 checks passed
@HTExplicit
HTExplicit deleted the chore/v0213-followups branch October 4, 2026 03:16
HTExplicit added a commit that referenced this pull request Oct 4, 2026
…ion-repair diagnostics (#238)

Owner decisions of 2026-10-04: the last dated one-shot diagnostics leave
the repository. The reasoning replay/recovery diagnostic went with #235.

Deleted: the reasoning fidelity diagnostic (four test files of service
under the reasoning_fidelity build tag, 2,124 lines) and the
continuation-repair diagnostic (two test files of handler under the
continuation_repair_live build tag, 1,953 lines). No other file carried
either tag, no workflow or script named them, and every production
function they called has other callers, so no production code changes.

Test-only files under build tags that no build uses: no runtime change and
no release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant