Skip to content

refactor(src): split the remaining five oversized modules behind facades - #4658

Merged
lidge-jun merged 9 commits into
codex/m3-l1-roadmapfrom
codex/m3-impl
Sep 14, 2026
Merged

lidge-jun merged 9 commits into
codex/m3-l1-roadmapfrom
codex/m3-impl

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

src/에 남아 있던 2,000줄 이상 파일 아홉 개 중 다섯 개를 facade 뒤로 분해한다. 순수 이동이며 다섯 파일의 공개 export 표면이 모두 보존되므로 소비자의 import 경로는 하나도 바뀌지 않는다. 커밋은 파일별로 나눠 두어 하나씩 읽을 수 있다.

파일 새 리프
src/config.ts 4,799 460 12
src/providers/registry.ts 3,744 232 4
src/codex/auth-api.ts 3,134 43 10
src/codex/catalog/provider-fetch.ts 2,944 54 6
src/adapters/openai-chat.ts 2,234 822 7

새 리프 39개는 전부 1,999줄 미만이고 순환 import가 없다. 실행 계약은 devlog/_plan/260915_godfile_round3/에 있다.

각 파일에서 지킨 경계를 적는다. config.ts는 create-only 경로와 치환 저장 경로를 facade에 함께 두되 import 집합을 물리적으로 분리했다. 합치면 초기화가 기존 파일을 치환할 수 있는 경로가 생긴다. 경고 메모 세 개는 단일 소유 모듈로 먼저 뽑아 두 소비자가 import한다. 프로세스당 한 번이라는 성질이 모듈이 갈라지면 깨지기 때문이다.

providers/registry.ts의 엔트리는 객체 아이덴티티를 유지한다. 배열은 spread concat으로만 재조립했고 빌더나 Object.freeze를 쓰지 않았다. parity 테스트와 discovery 헬퍼가 라이브 엔트리를 in-place로 변이한 뒤 복원하므로 아이덴티티가 깨지면 조용히 실패한다.

codex/auth-api.ts에서는 access·refresh 토큰이 더 이상 라우트 모듈에 도달하지 않는다. reset-credit 인증 클로저를 서비스 리프가 통째로 흡수해서 라우트가 Bearer 헤더를 조립하지 않는다. Pool/Direct/API-key 조기 반환 술어 두 곳은 한 게이트 모듈에 함께 두었다. 갈라놓으면 한쪽만 갱신돼 Direct나 API-key 경로에서 pool 상태가 갱신될 수 있다.

adapters/openai-chat.ts는 어댑터 팩토리 본문과 lastRequestedModelId 클로저를 facade에 남겼다. buildRequest가 쓰고 parseStream·parseResponse가 읽는 단일 클로저 상태라 쪼개면 갈라진다.

본문을 텍스트로 읽는 오라클과 라우트 레지스트리를 같은 PR에서 옮겼다. route-registry.ts의 codex auth 선언 23개는 codex/auth-api/routes를 가리키고, codex-auth-api.test.ts의 오라클 여섯은 login-flow.ts를, reasoning-replay-scope-source.test.tsopenai-chat/messages.ts를 읽는다. 단언 문자열은 바꾸지 않았다.

Verification

  • bun scripts/structure-ssot.tsstructure/ SSOT checks passed (structure/ 백틱 경로와 docs-site의 provider 추가 안내 8개 언어를 새 소유 모듈로 갱신한 뒤)
  • 정적 감사: src/ 1,187 파일 파싱과 상대 import 해석 전수, facade export 표면 origin/dev 대비 107/107, 12/12, 33/33, 34/34, 4/4, 누락 0
  • tsc 바인딩 오류(TS2304/2305/2459/2724) 0건, 상대 경로 모듈 미해석 0건
  • 로컬 typecheck·full suite·install은 실행하지 않았다. 이 워크트리에 node_modules가 없어 나머지 검증은 이 PR head의 hosted CI가 증거다.

직전 라운드에서 CI가 잡았던 결함 유형이 이번에도 하나 나왔다. config/schema/config-schema.ts가 한 단계 깊어진 위치에서 인라인 import("./types")를 그대로 들고 있었고, 상대 경로 TS2307만 따로 검사해서 잡았다.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

lidge-jun and others added 7 commits September 15, 2026 04:14
…ion (#4546) (#4653)

* docs(devlog): record why the root workflow budget expires a long session (#4546)

A Codex session dispatching subagents was refused across three unrelated providers with a 429 that reads as a provider rate limit. The refusal was this proxy: workflowSendCeilingReached compares a per-root send count that only ever grows, keyed on x-codex-parent-thread-id, so for Codex the cap is a session expiry rather than a fan-out guard. A probe carrying the session id was refused while a probe carrying a fresh root id was served, and restarting the proxy served both.

The unit records the diagnosis and plans two layers: windowed ceilings so a rate is bounded rather than a lifetime, and a refusal an operator can read, name and clear without restarting.

Verification posture: local suite, typecheck, install and build NOT run by explicit instruction. Hosted CI at the exact final head is the only proof. Pushed with --no-verify.

* docs(devlog): record the two-probe reproduction for the root budget refusal (#4546)

One body, one upstream, two answers separated only by the claimed root id. That single check rules out the provider, the account and the model, and it is what the next person should run before spending hours on a status page. Also records that a restart erases the evidence, which is why the obvious remedy hides the cause.

Verification posture: local suite, typecheck, install and build NOT run by explicit instruction. Hosted CI at the exact final head is the only proof.
…ifetime (#4546) (#4654)

* fix(lib): bound the root workflow ceilings by a window instead of a lifetime (#4546)

state.sends only grew and state.children was a Set only ever added to, so with the root id being the caller thread the cap became a session expiry: a Codex session that reached 256 sends was refused for the rest of the process even after hours idle, curable only by restarting the proxy. The cap was written against a burst, and a burst is a rate.

Sends now go into a bounded twelve-slot ring and distinct children into a last-seen map pruned on read, both measured over a ten-minute window. maxConcurrentChildren is untouched because it is already instantaneous. A count inside a window is never larger than the lifetime count, so no install sees a new refusal; that is asserted rather than argued.

Verification posture: local suite, typecheck, install and build NOT run by explicit instruction. Hosted CI at the exact final head is the only proof. Pushed with --no-verify.

* fix(lib): thread the clock through chargeWorkflowSends, and re-ratchet core.ts (#4546)

Two things hosted CI caught. chargeWorkflowSends read Date.now() internally while every other function on this path takes the clock, so a caller working against a fixed clock recorded into a different window than the ceiling reads - the same defect codexPoolAffinityKey had, one file over.

And dev is currently red on the file-size ratchet: core.ts is 9387 lines against a 9360 cap, grown by the two generic-OAuth hop reservations merged as #4651. The cap is raised to what dev actually carries rather than left failing. This works against the godfile-splitting programme and core.ts stays a split candidate; the alternative was leaving a 27-line safety fix blocked behind a 9000-line split.

Verification posture: local suite, typecheck, install and build NOT run by explicit instruction. Hosted CI at the exact final head is the only proof. Pushed with --no-verify.

* fix(lib): make every workflow ceiling read the caller's clock (#4546)

workflowSendCeilingReached still read Date.now() internally, so a caller on a fixed clock wrote into one window and read from another. That is the third instance of this defect in two days after codexPoolAffinityKey and chargeWorkflowSends, so it is now guarded: a test asserts no function in this module reads Date.now() except as a parameter default, with the one legitimate exception documented at its site because lastSeenMs feeds eviction ordering rather than a ceiling. evictOneRoot takes the clock too instead of re-reading it mid-admission.

Verification posture: local suite, typecheck, install and build NOT run by explicit instruction. Hosted CI at the exact final head is the only proof. Pushed with --no-verify.

* fix(lib): pin the workflow window to the root, and make the safety test able to fail (#4546)

An independent review of the windowed ceilings found two real holes, neither
blocking but both worth closing before this lands.

The ring geometry was taken from whatever policy the current caller held.
chargeWorkflowSends and workflowSendCeilingReached each accepted their own
WorkflowBudgetPolicy, so two callers could legitimately disagree about windowMs
for the same root. Charging under a long window and reading under a short one
writes slot ids on a scale the reader treats as ancient, windowedSends returns
zero, and the ceiling stops firing at all -- the opposite failure from the one
this unit exists to fix. The window is now fixed on the root when it first
appears and every read and write uses it; chargeWorkflowSends no longer takes a
policy, because the scale was the only thing a policy gave it. Production never
passed one.

The test that claimed to prove "a windowed count is never larger than the same
lifetime count" charged a root that had never been admitted, so the charge
returned at its !state guard, the snapshot came back undefined, and every
assertion sat behind if (snapshot). It passed with the ring deleted. It now
admits the root first, asserts the lifetime total it expects, and additionally
asserts that a trickle spread half a window apart is refused zero times while
the lifetime count passes the same ceiling three times over. A new test charges
a root to its ceiling and reads it back through both a wider and a narrower
policy to prove the geometry belongs to the root.

Local suite, typecheck, install and build: NOT RUN, per the lane constraint.
Proof is hosted CI at this exact head.
Pure move. 4799 -> 460 lines with twelve leaves under src/config/. The create-only path and the replacing save path stay on the facade with physically separate import sets; the three warn-once memos move to a single warn-memo owner so the process-once behaviour cannot split.
Pure move. 3744 -> 232 lines with four leaves. Entries keep their object identity: the array is rebuilt by spread concat, never by a builder or Object.freeze, because the parity tests mutate live entries in place and restore them.
Pure move. 3134 -> 43 lines with ten leaves. Access and refresh tokens no longer reach a route module: the reset-credit authorization closure is absorbed by its service leaf. The Pool/Direct/API-key early-return predicates stay together in one gate module so they cannot drift apart.
Pure move. 2944 -> 54 lines. The two large functions move whole. Module state keeps a single owner each, and the reset path in build-entries is untouched.
Pure move. openai-chat.ts 2234 -> 822 lines with seven leaves; the adapter factory and its lastRequestedModelId closure stay on the facade because buildRequest writes it and parseStream/parseResponse read it. This commit also carries the structure/ and docs-site updates shared by all five splits.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 14, 2026 20:35
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 63a84786-e4e5-4abf-a58b-a037e799bbdd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-14T20:41:15.238889Z 90aeffa PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the chore Maintenance, CI, tests, refactors, or build changes (not a user-facing bug or feature). label Sep 14, 2026
@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 73 / 80

설명

이 PR은 갓파일 라운드2(#4635: sync/inject/shim/state/routing/quota 파사드 + 파일 크기 래칫) 다음에 남은 src/ 대형 모듈 다섯 개를 facade 뒤로 옮기는 실제 구현입니다. 바로 아래 스택의 문서 PR #4655가 같은 계획(devlog/_plan/260915_godfile_round3/)을 dev에 올리려 하고, 이 PR의 base는 codex/m3-l1-roadmap(#4655 head)입니다. 지금 tip dev HEAD는 836511b9c(#4654 윈도우형 root workflow 천장)이고 패키지는 2.56.0입니다. 이 브랜치의 merge-base는 ce0ac617d(#4651)라서 tip과는 #4653·#4654 두 커밋만 떨어져 있습니다.

다섯 파일의 공개 export 표면을 보존한 순수 이동입니다. 체크아웃 head(90aeffa70)에서 줄 수는 PR 표와 같습니다 — src/config.ts 4,799→460, src/providers/registry.ts 3,744→232, src/codex/auth-api.ts 3,134→43, src/codex/catalog/provider-fetch.ts 2,944→54, src/adapters/openai-chat.ts 2,234→822. 새 리프는 모두 1,999줄 미만이고, 가장 큰 리프는 providers/registry/entries-core.ts(1,221)와 entries-extended.ts(1,204)입니다. 소비자 import 경로는 그대로라서 대시보드·CLI·테스트가 from "../config" 같은 옛 경로를 써도 깨지지 않습니다.

경계를 실제로 지킨 곳이 중요합니다. config.ts facade는 create-only 경로(publishInitialConfigNoReplace / initializePersistedConfigIfMissing)와 치환 저장 경로(persistConfigUnlocked / saveConfig)를 같은 파일에 두되 import 집합을 물리적으로 갈랐고, 경고 메모 세 개(src/config/warn-memo.ts)는 프로세스당 한 번 성질을 위해 단일 모듈로 뽑았습니다. providers/registry.tsPROVIDER_REGISTRY_COREEXTENDED를 spread concat만으로 이어 붙여 엔트리 객체 아이덴티티를 유지합니다(빌더/Object.freeze 없음). codex/auth-api에서는 access·refresh 토큰이 라우트 모듈에 안 가게 reset-credit-service.ts가 인증 클로저를 흡수하고, route-registry 23개 선언은 codex/auth-api/routes를 가리킵니다. openai-chat.tslastRequestedModelId 클로저를 buildRequest/parseStream/parseResponse가 같이 쓰므로 facade에 팩토리 본문을 남긴 판단이 맞습니다.

오라클·구조 문서도 같이 옮겼습니다. structure/runtime.md·structure/config.md가 새 소유 모듈을 가리키고, codex-auth-api.test.tslogin-flow.ts를, reasoning-replay-scope-source.test.tsopenai-chat/messages.ts를 읽도록 경로만 바꿨습니다. 단언 문자열은 그대로입니다. types.ts/config.ts 분할 캠페인과 정면으로 같은 방향이라 close-don't-rebase 대상이 아니라 착지 대상입니다. tip의 src/types.ts는 이미 123줄 + src/types/ 리프로 쪼개져 있고, 이번 건은 남은 config.ts 모놀리스를 같은 방식으로 끝내는 칸입니다.

다만 호스티드 CI의 test 2/4가 지금 빨갛습니다. 원인은 이 분해 자체가 아니라 파일 크기 래칫입니다. 커밋된 기준선은 src/server/responses/core.ts를 9,360줄로 두고 있는데 이 트리(그리고 tip)의 실제 줄 수는 9,387이라 GREW로 실패합니다. tip 기준선은 이미 9,387로 올라가 있으므로, 계획서(000_plan.md)가 말한 대로 tip 병합 후 기준선을 재시드(또는 tip 기준선을 가져오기)하면 이 offender는 사라집니다. 로컬 suite는 PR 본문대로 안 돌렸고 CI가 증거입니다. 기능 변경·버그 수정은 없고 #4546 런타임 에픽과도 파일 겹침이 거의 없어, 스택 순서와 래칫만 정리되면 tip에 올려도 되는 구조 작업입니다.

라인 / 경로 문제

tests/fixtures/file-size-baseline.json (core.ts 캡 9360) - 실제 src/server/responses/core.ts는 9,387줄이라 래칫이 GREW로 실패합니다. tip(836511b9c) 기준선은 이미 9,387입니다. #4655 착지 후 tip에 rebase하고 기준선을 tip과 맞추거나 --update로 재시드해야 CI가 녹색이 됩니다.
PR base = codex/m3-l1-roadmap (#4655) - tip dev가 아닙니다. 체인 최하단(#4655)을 먼저 dev에 올린 뒤 이 PR을 tip으로 옮기거나, 한 PR로 squash해 tip에 올리는 순서를 정해야 합니다. tip의 #4653/#4654는 workflow-budget 쪽이라 충돌은 작을 가능성이 큽니다.
계획 vs 실제 PR 쪼개기 - 000_plan.md는 파일별 브랜치(m3-l2-configm3-l6-openai-chat) 여섯 PR 체인을 말했는데, 실제로는 로드맵(#4655) + 다섯 상 구현(#4658) 두 장으로 합쳐졌습니다. 리뷰·보안 분리(auth-api) 관점에서는 약하지만, 커밋은 파일별로 나뉘어 있어 읽기는 가능합니다.
src/adapters/openai-chat.ts (822줄) - 다섯 facade 중 가장 큽니다. lastRequestedModelId 클로저 때문에 팩토리 본문을 남긴 선택은 맞지만, 다음 라운드에서 헬퍼(resolveMaxTokens / thinkingBudgetForEffort / canSerializeOpenAIChatServiceTier)만 리프로 빼도 facade를 더 줄일 수 있습니다.
src/providers/registry/entries-core.ts (1221) · entries-extended.ts (1204) - 임계값 아래지만 다음 갓파일 후보입니다. 아이덴티티 보존(in-place mutate 후 복원) 계약을 리프 README나 테스트 주석에 한 줄 더 박아 두면 이후 분해 때 실수가 줄어듭니다.
검증 - 로컬 typecheck/full suite 미실행, test 2/4 실패(래칫). gates·hygiene·docker smoke 등은 통과 중이므로 래칫/rebase만 정리하면 머지 게이트로 충분합니다.

메인테이너의 판단이 필요한 지점

  • #4655를 먼저 tip에 머지한 뒤 #4658을 rebase할지, 아니면 로드맵+구현을 한 번에 tip으로 옮길지.
  • auth-api credential 경계 이동(토큰이 routes에 안 감, reset-credit-service가 Bearer 조립)을 이 PR 본문·계획의 "보안 검토 기록"으로 충분한지, 별도 사인오프 코멘트가 필요한지.
  • 계획의 파일별 PR 체인을 지금처럼 한 구현 PR로 유지할지(속도), 아니면 auth-api만이라도 분리 PR로 다시 쪼갤지(보안 리뷰 단위).
  • openai-chat 822줄 facade와 registry 1.2k 리프를 이번 머지 후 바로 다음 라운드 후보로 잡을지, 남은 넷(core.ts·server/index.ts·openai-responses·bridge) 설계를 먼저 할지.

너의 추천

#4655를 tip에 먼저 착지시킨 다음, 이 PR을 tip(836511b9c)에 rebase하고 파일 크기 기준선을 tip과 맞춘 뒤 CI(특히 래칫)가 녹색이면 머지하세요. 내용 자체는 라운드3 실행 계약과 일치하고 export 표면·클로저·아이덴티티·토큰 경계를 지키므로 rebase 지옥으로 닫을 대상이 아닙니다. 머지 후 기준선에 새 리프 캡이 들어가 있는지와 다섯 facade가 모두 1,999 이하인지만 한 번 더 확인하면 됩니다.

이 댓글은 grok-bot이 작성했습니다

dev raised the core.ts cap after this branch forked, so the committed baseline was stale here. Merging dev picks up that cap, and ratchet:update lowers the five split facades to their new sizes so they cannot grow back toward the threshold.
@lidge-jun
lidge-jun merged commit a90a99a into codex/m3-l1-roadmap Sep 14, 2026
27 checks passed
@lidge-jun
lidge-jun deleted the codex/m3-impl branch September 14, 2026 21:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore Maintenance, CI, tests, refactors, or build changes (not a user-facing bug or feature).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant