Skip to content

fix(usage): reject session rebinding in Codex rollouts - #7566

Merged
huangruiteng merged 1 commit into
loopx-project:mainfrom
shangzh0:codex/usage-session-identity
Oct 28, 2026
Merged

huangruiteng merged 1 commit into
loopx-project:mainfrom
shangzh0:codex/usage-session-identity

Conversation

@shangzh0

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

A bound rollout containing session A's counters followed by session B's session_meta currently returns A's totals under B's snapshot id. Booking that file after an earlier A booking can count the full total again as a new session. Repeating A's own metadata with a later start time also changes the duration of an otherwise unchanged snapshot and breaks strict replay.

Bind cumulative usage to the first identified session metadata, reject missing/conflicting ids and counters preceding that metadata, and retain the original start time on same-id repeats. The existing typed usage error refuses the booking before record/index publication. Updating the synthetic rollout back to its original session then recovers as a zero delta.

  • Outcome basis: reproduced defects in the existing Codex usage adapter.
  • Roadmap/RFC: S7 usage attribution and replay accounting; goal-usage-token-cost-v0.md Capture Contract and its existing docs/status-data-contract.md usage-summary contract.
  • Base: loopx-project/loopx:main at 90b26f2fa26835d668f4f1fb77dea87c81fda755.

Author Declaration

  • Written by: model_agent (OpenAI Codex), at the contributor's request.
  • Implemented against: the source-binding, same-snapshot replay and no-partial-append requirements in docs/status-data-contract.md at the base revision. This PR makes the single-session validation explicit there.
Criterion Disposition Owner / evidence
One identified session owns the accepted counters implemented codex_session_usage.py; conflicting ids before/after counters, missing id and late metadata regressions
Repeated same-id metadata preserves snapshot duration implemented reader tests cover both native id spellings and retain the first clock
Failed input publishes no usage; repaired retry remains idempotent implemented real File/SQLite refresh_state_run, exact index-byte and aggregate comparisons, then zero-delta recovery

Scope And Continuation

Complete within this scope. The existing Python host-source adapter remains the parser; the ledger, booking lock and typed usage seam retain their authority. Existing interleaved-session, concurrency and legacy model-binding coverage passes. The future-facing pass keeps the identity check at this existing owner instead of adding a second reader or state store. The self-repair catalog adds one local diagnostic pattern, not a runtime protocol vocabulary. No live benchmark benefit is claimed.

Validation

  • Published head: cdcb6d14084fe0e64c6019cc45eb8c1aec229359. The tested tree is unchanged from local commit d7d9b16; publication normalized only commit dates to UTC. Remote blobs, complete tree and commit hash match local objects.
  • Run state: finished. Input classes: synthetic.
Check kind Result Evidence / limitation
unit / real_backend passed pytest -q tests/control_plane/test_codex_session_usage.py tests/control_plane/test_usage_summary.py: 101 passed; actual File and SQLite authority with disposable fixtures
integration passed pytest -q tests/control_plane/test_run_index_jsonl_framing.py tests/test_state_refresh_projections.py: 19 passed
regression_parity passed Original reader loaded in isolation under the same qualified runtime: 92 existing cases pass and all 9 new regressions fail; candidate passes all 101
real_entrypoint passed Built Chat assets and wheel; installed wheel in a clean environment and exercised the installed reader/booking API outside the checkout, including unchanged same-id replay and cross-session refusal without partial record mutation
static passed Ruff, mypy on adapter, semantic diff advisory (no supported new vocabulary), bilingual RFC gate (no paired RFC changed), whitespace and public-boundary scans; new self-repair pattern is retrievable by id
unit not_run Full repository suite, native Windows and live Codex/model execution; required CI and review remain outstanding

Environment boundary: the system Node 24.14 / SQLite 3.51.2 is rejected by main's existing WAL qualification gate. The initial unmodified run had 13 prerequisite failures. All baseline/candidate backend results above use isolated Node 24.22 / SQLite 3.53.4; the qualification gate and global runtime were unchanged. Public-boundary checks report two expected warnings for the disposable checkout's absent registry.

Frontend / Visual Evidence

UI impact: none. This repairs the existing usage capture source and documents its validation. Screenshots and first-viewport evidence: N/A.

Boundary Checklist

  • Scoped bug fix with regression tests and contract/self-repair documentation.
  • No credentials, private sessions, raw trajectories, verifier output, internal links or machine paths in this PR.
  • No live benchmark jobs, model calls or global CLI configuration changes.
  • No paired RFC changed; bilingual check passed.
  • DCO sign-off is present; shared-authority promotion fixture impact is N/A.

Signed-off-by: shangzh0 <97216392+shangzh0@users.noreply.github.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent; OpenAI gpt-6.1-sol; runtime_reported; reasoning_effort=max

本次精确 head 未发现阻塞性问题,建议 APPROVE。结论针对显式用量导入的身份与重放修复。

动机 / Motivation

显式导入 Codex 会话用量的 CLI 用户和读取累计成本的后续工作。

同一文件先有 A 的计数、后有 B 的 metadata,旧 reader 会把 A 的总数放到 B 名下再记一次;重复 A 的头部又会改变已记 snapshot 的时长。当前 head 在记账前拒绝身份变化,并保持首次会话时钟。

真实 File/SQLite CLI 拒绝混合和晚到身份且 index 不变,修复后同 snapshot 零增量,继续增长与 A/B 交错各按自身基准累计。

不自动发现会话、不迁移或改写历史用量,不关闭 Goal 全部成本/模型采用或科学收益验收。

改动思路 / Approach

在原 streaming 用量 reader 先验证身份,再复用同一 collector、booking lock 和 ledger;无需新增状态或授权 owner。

本 PR 完成显式会话用量身份与首次时钟修复,历史账本、自动发现及完整模型成本归因保持原边界。

用量导入需要完整扫描调用方指定的文件;活动观测 cursor 只回答最近是否执行,不能替代完整记账。将校验放在既有 _read_codex_session_usage 内,能够在生成 observation、读取累计基准和 append 之前阻止错误归属。另建 snapshot 文件或事后修账都会增加恢复窗口;要求用户重新选择另一会话还会破坏原显式绑定。

输入 immutable base 当前 head
A 计数后追加 B metadata 按 B 接受并可能重复记全量 拒绝,index 字节保持
同 A 重复较晚开始时间 时长改变 首次时钟保持
计数在身份之前 后来的身份回填计数 拒绝
修复原文件,再增长 可能重放冲突 原 snapshot 零增量,增长只记差额
不传会话用量参数 未采集 仍未采集

没有新增用户准备或确认轮次。错误源被修正后可以沿原命令继续,不修改历史结果来制造成功。

具体改动 / Changes

规格依据为改动前 docs/status-data-contract.md#usage-summary,完整 revision 90b26f2fa26835d668f4f1fb77dea87c81fda755,另核对 Accepted goal-usage-token-cost-v0.md。按既有未编号要求映射四项:session-owned-capture 是显式会话和模型归属;snapshot-replay-integrity 是相同 snapshot 零增量;atomic-refusal-and-recovery 是拒绝不 append、修复沿原 ledger;streaming-private-boundary 是完整 opening extent、aggregate-only 和 unknown 保留。四项均有实际路径证据;没有用本 PR 新增说明反过来定义验收。

关键代码讲解

  1. _read_codex_session_usage:每条 session_meta 先解析现有两种 id 拼写。缺身份或与首次身份不同立即抛原 typed error;只在首次绑定时记录开始时间。重复同 id 不再覆盖时钟,计数必须发生在已识别身份之后。
  2. book_codex_session_usage:仍先完成 source read,再从原 run index 找本 session 的基准,交既有 collector 生成 delta。旧 model-binding 兼容、snapshot identity 与历史行格式未改,没有第二份账本。
  3. refresh_state_run:真实 CLI 仍在原 booking lock 下执行读基准和 append。未提供该参数继续走原 unknown 或显式 typed measurement 分支,没有扩大自动采集范围。

全 diff 四路径 +115/-5:生产一文件 +18/-4,回归一文件 +90,合同和可达的 repair-pattern 两处 +7/-1。修复指南要求检查原绑定 source、保留 streaming/lock、拒绝不 append 和修复后零增量,并明确不发现别的会话、不重写历史;它没有授予新任务或执行权限。现有 Python host-source adapter 的位置合理,未复制共享 TypeScript ledger 语义。

对主干的风险 / Risks

最强风险是收紧校验后误拒正常 metadata 重复,或者仅在 reader unit test 拒绝、实际 CLI 仍先写了账。独立测试直接启动正式 loopx.cli.main,使用隔离真实 File/SQLite:正常 A 记 1200,污染后拒绝并比较完整 index bytes,修复重放全部零,增长只记 400,再导入 B600、重放 A0,最终 input2200/output510。首次时钟和计数先于身份也单独断言。

同一八例在当前 head 8 PASS;用 immutable base 的唯一改动生产模块替换 reader,其余生产代码经完整 diff 确认相同,得到 6 FAIL / 2 PASS。六个失败精确落在混合身份、晚到身份和首次时钟;两个不传参数的控制例两版均通过。不是从相同测试数量推断 parity,也未把失败输出裁成成功。

四个相关模块 120 PASS / 0 SKIP,含并发、interleaved sessions、严格 snapshot replay、模型归属、非有限数字、损坏/截断 source、流式边界和真实汇总读回。改动 Python 的 Ruff PASS;standard premerge 6 direct +19 selected PASS,无 manual hold。首次私有 probe 使用不存在的 python -m loopx,八例都未到 CLI;改为 console script 的同一正式入口后重跑,没有改变断言,原失败保留。未查询、轮询或等待远端 CI。

语义与 CI 对齐

身份以精确 id 比较,状态不由标题或 prose substring 判断。更严格的是明确 source 的一致性;同 id 重复合法、optional cost 未知不补零,原先模型先行绑定、完整扫描、torn final record 和 booking lock 保持。新增错误在 source adapter 内,不向通用 quota 注入 Codex 专用工作义务。修复指南属于可执行校验的解释,未把硬拒绝称为建议。原历史 model correction 仍有实际兼容消费者,当前没有新增版本 decoder 或 authority。

我的整体评价 / Verdict

problem_context=goal_achieved,指这项可独立验收的身份/时钟修复。long_horizon=preserved:后续记账、重试、增长和跨会话交错有原账本及完整负例;user_experience=improved:拒绝原因明确,修复原 source 后无需新增操作轮次。repository_reuse=reused,code_volume=necessary,change_proportionality=proportionate,default_off_isolation=isolated,authority_semantics/semantic_alignment=aligned。未发现需要另建框架或扩大切片的理由。

检查全部4路径:session 用量 reader、测试与两处文字,没有渲染页面或布局变化。

未验证正式安装态、真实模型采用或完整成本净收益。

本结论仅绑定 cdcb6d14084fe0e64c6019cc45eb8c1aec229359。代码批准与旧 review/thread closeout、native readiness、维护者合并授权分别判断,不能用本次局部通过结清父 Goal。

English verdict: APPROVE cdcb6d1; real CLI identity/refusal/replay cases pass on both backends and distinguish the old defect, with existing ledger behavior retained.

xyx2002OvO pushed a commit to xyx2002OvO/loopx that referenced this pull request Oct 28, 2026
@huangruiteng
huangruiteng merged commit 1d6993d into loopx-project:main Oct 28, 2026
28 of 40 checks passed
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.

3 participants