Skip to content

[WRONG BRANCH] fix(tests): walk the Lab activation chain instead of its first hop (#4704) - #4754

Merged
lidge-jun merged 9 commits into
codex/rg1-destructive-home-guardfrom
codex/rg2-lab-sync-activation-guard
Sep 16, 2026
Merged

lidge-jun merged 9 commits into
codex/rg1-destructive-home-guardfrom
codex/rg2-lab-sync-activation-guard

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Summary

The synchrony guard followed the direct callees of startServer. That catches activateLab becoming async, but activateLab calls installLabAutomationRuntime and startAutomationIfEnabled without awaiting them (src/lib/lab-activation.ts:176,200), so making either one async with an await before its registration call leaves activateLab parsing as perfectly synchronous. Every assertion stayed green while startServer returned before Lab was registered — the one ordering the window exists to protect, and the reason AGENTS.md pins it.

walkActivationChain now follows the chain from activateLab to a bounded depth, failing on any node declared async or carrying a body-level await. At the current tip it reaches 61 nodes with zero failures.

The depth-one comment warned that a deeper walk would treat dynamic dispatch as startup callees and produce false positives. What avoids that is what this walk refuses to follow:

  • Nested functions are already skipped by collectBodyLevelCalls, so timer callbacks, shutdown hooks, promise continuations and the deferred route executor are not activation edges. They run later by construction.
  • Receiver calls are not followed. A method that turns async cannot suspend its caller unless the caller awaits it, and that await is already reported on the caller's own body. Following Map.get or record.staticDetach.push would add noise and catch nothing.
  • Names imported from outside this repository are classified automatically. Hand-listing every join and readFileSync in the chain would bury the six names that actually need human judgement, which are listed with a reason each and asserted to still be reachable.

inspectActivationDeclaration extends inspection to const arrows. The function-only inspector reported activationKey as missing, and a walk that read missing as fine would skip every const-arrow node in the chain. That needed its own return-type skipper: the existing skipReturnType treats a top-level => as part of a function-type annotation and keeps scanning for a body brace that a concise arrow never has.

The walk is bounded three ways: a 200-node ceiling that fails rather than running away, a visited set, and an eight-hop limit on re-export resolution.

Closes #4704

Verification

Local test suites, individual test files, bun run typecheck, bun run test, bun run test:changed, the dashboard build and dependency installation were NOT RUN — the maintainer forbids local suite execution in this lane, since a local test run previously deleted real ~/.opencodex data.

What was done instead:

  • The walker was driven against the real sources read-only and every assertion this PR adds was evaluated ahead of time: 16/16 match. That includes the exact failure strings, the classified-name equality, and the body: " a" value asserted for the arrow inspector.
  • All four mutation cases were exercised through the injected source loader against the real src/lib/lab-activation.ts and src/lab/automation/orchestrator.ts: a nested callee declared async, an await added inside that callee, a suspension three hops down in startLabAutomationScheduler, and the arrow forms. Each is reported; on the same mutated source the depth-one scan still returns { found: true, async: false, awaitLines: [] } for activateLab, which is the defect being fixed.
  • No false positives: the unmutated chain produces zero failures across all 61 nodes.
  • Transpile check of the changed file. No test was executed.
  • Hosted CI for this layer runs on the lane tip, which contains this commit; this layer's own commit carries [skip ci] under the maintainer-approved DEV-STACK-08 tip-only policy.

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.

…4704) [skip ci]

The synchrony guard followed the direct callees of startServer. That catches
activateLab becoming async, but activateLab calls installLabAutomationRuntime
and startAutomationIfEnabled without awaiting them, so making either async with
an await before its registration call left activateLab parsing as perfectly
synchronous. Every assertion stayed green while startServer returned before Lab
was registered, which is the one ordering the window exists to protect.

walkActivationChain now follows the chain from activateLab to a bounded depth,
failing on any node that is declared async or carries a body-level await.

What keeps a recursive walk from becoming the false-positive machine the
depth-one comment warned about is what it refuses to follow. Nested functions
are already skipped by collectBodyLevelCalls, so timer callbacks, shutdown
hooks, promise continuations and the deferred route executor are not activation
edges. Receiver calls are not followed: a method that turns async cannot suspend
its caller unless the caller awaits it, and that await is reported on the
caller's own body. Names imported from outside this repository are classified
automatically, so the six names that need human judgement are not buried under
every join and readFileSync in the chain.

inspectActivationDeclaration extends inspection to const arrows. The
function-only inspector reported activationKey as missing, and a walk that read
missing as fine would skip every const-arrow node. That needed its own
return-type skipper: the existing one treats a top-level => as part of a
function-type annotation and keeps scanning for a body brace a concise arrow
never has.

Four mutation cases run against the real sources through an injected loader: a
nested callee declared async, an await added inside that callee, a suspension
three hops down in startLabAutomationScheduler, and the arrow forms the previous
inspector could not see. The async case also asserts what the depth-one scan
reports on the same mutated source -- still green, which is the defect.

Closes #4704
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 16, 2026 02:17
@coderabbitai

coderabbitai Bot commented Sep 16, 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: 98afb933-ee2b-40dc-9c1f-8a6e752d3b32

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.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 16, 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-16T02:21:19.409887Z 21a3f18 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 github-actions Bot added the bug Something isn't working label Sep 16, 2026
…4706) [skip ci]

GENERATED_PATHS exempted twelve files from every size cap, and eleven of them
were hand-maintained: nine i18n catalogues, a hand-curated benchmarks snapshot,
and the model-metadata generator's INPUT. Its output, src/generated/model-
metadata.ts, is 108 lines and was scanned normally, so the one file the list was
named after was the one file it did not describe.

Exempting catalogues and data snapshots is a reasonable policy. Calling them
generated is what invites the next hand-written file onto the list, because a
name is a claim nothing checks.

The exemption is now three exact allowlists, each carrying its own reason.
GENERATED_PATHS holds only agent_pb.ts, which opens with a protoc-gen-es banner.
I18N_CATALOG_PATHS holds the nine locale catalogues, exempt because they grow by
one line per UI string in nine locales at once, so a cap would block every new
string in the GUI rather than any oversized module. DATA_SNAPSHOT_PATHS holds
the two records whose size tracks how much was recorded. EXEMPT_PATHS is their
union, and the verdict and baseline field are renamed from GENERATED to EXEMPT
to match. loadBaseline still accepts the old key so a branch written before the
rename loads instead of failing with a shape error that explains nothing.

The classification is now checkable against the files themselves: every path in
GENERATED_PATHS must carry a generator banner, and no path in the other two
lists may. The positive control is the generator's real output, which carries
the banner, is not exempt, and is scanned under a cap.

The exemption relies on --update never turning an exemption into a cap, so that
is asserted directly alongside the Math.min rule that only ever lowers one.

Closes #4706

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 21a3f18ace

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

];
while (queue.length > 0) {
const item = queue.shift()!;
if (nodes.has(item.name)) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Key visited activation nodes by declaration, not name

The visited check conflates same-named functions from different modules. The current graph already reaches both src/lab/automation/persistence.ts and config-persistence.ts, which each declare sleepLockRetry and pidDefinitelyDead; because the persistence declaration is visited first, changing the config-persistence sleepLockRetry to an async function with a body-level await still produces failures: []. Key nodes by resolved file plus declaration/exported name so every distinct activation callee is inspected.

AGENTS.md reference: AGENTS.md:L74-L82

Useful? React with 👍 / 👎.

…de splits (#4711)

The architecture pages and structure/runtime.md still described the module
ownership that existed before the facade splits of the last release train, so a
reader following them landed in a file that no longer contains the code.

Routing now lives in src/server/index/serve-options.ts, request preparation in
src/server/responses/request-prepare.ts, and bridge conversion in src/bridge/
sse.ts and src/bridge/response-json.ts. src/bridge.ts is seven lines of
re-exports. The pages named the pre-split files in every locale, so fixing only
the English source would have left seven translations contradicting it.

The facade paragraph also carried three counts -- seven, nine and five leaf
modules -- that were already 26, 42 and 53. Counts that must be recounted on
every split are a drift source rather than information, so they are gone; what
replaces them is the distinction the counts were standing in for. A facade is
the stable import path, not the implementation, and each step of the request
flow now names the module that owns the code.

structure/runtime.md gets the same correction on five ownership claims, and
points at structure/transports/responses.md, which already carries the
post-split owner inventory for the Responses surface.

Scope is deliberately narrow: only claims about which module owns which
responsibility change. No prose about behaviour is rewritten, and no locale
receives a translation it did not already have.

Closes #4711
@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 78 / 80

이 PR은 Lab 동기 활성화 가드가 startServer한 홉 직접 호출만 보던 구멍을 막습니다. 지금 dev와 부모 #4747(rg1) 위에서도 tests/lab/core-lab-boundary.test.tsactivateLab가 async가 되는지만 잡습니다. 그런데 실제 src/lib/lab-activation.ts에서 activateLabinstallLabAutomationRuntimestartAutomationIfEnabled를 await 없이 호출합니다(대략 176·200행). 그 안쪽이 async로 바뀌고 registration 전에 await가 생겨도 activateLab 시그니처는 동기처럼 보여 기존 단언이 전부 초록인데, startServer는 Lab 등록 전에 돌아갈 수 있습니다. AGENTS.md가 지키려는 바로 그 순서입니다. 이 PR은 walkActivationChain으로 유계 깊이까지 따라가며, async 선언이거나 body-level await가 있는 노드를 실패시킵니다. const 화살표도 inspectActivationDeclaration로 보고, nested/timer/shutdown/receiver 호출은 따라가지 않아 옛 depth-one 주석이 걱정한 오탐 기계를 피합니다. 파일은 테스트 하나뿐입니다.

베이스는 dev가 아니라 codex/rg1-destructive-home-guard(#4747)입니다. ratchet/lab 스택 tip(rg2)이라 혼자 dev에 올리면 안 됩니다. 부모 #4747이 먼저 랜딩된 뒤 tip을 올려야 하고, #4720 이후 dev HEAD b3035fe와의 관계는 부모가 머지될 때 함께 정리하면 됩니다. 이슈 #4704를 닫는다고 본문에 명시돼 있습니다.

tests/lab/core-lab-boundary.test.ts walkActivationChain - 유계 깊이·외부 import 자동 분류·수신자 호출 비추적. 현재 tip에서 61노드 0실패라고 본문에 적힘. 로컬에서 재현은 부모 체크아웃이 필요.
inspectActivationDeclaration - 예전 function-only 검사기는 const 화살표를 missing으로 보고 walk가 건너뜀. return-type skipper도 화살표 본문 없는 경우를 맞춤. 회귀 포인트.
뮤테이션 4종 - nested async, callee 내부 await, 3홉 아래 startLabAutomationScheduler 중단, 화살표 형태. depth-one이 같은 뮤테이션에 초록인 것도 assert해서 “구멍의 증명”을 남김.
스택 - #4747(rg1, base dev) → #4754(rg2 tip). 이후 rg3/rg4(#4766 등)와 이어질 수 있음. tip만 squash 머지 금지.

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

너의 추천
WAIT-FOR-STACK: 부모 #4747을 먼저 머지한 뒤 #4754를 머지하세요. 스테일 베이스로 혼자 dev에 올리지 마세요. 머지 후 #4704 클로즈. 내용은 KEEP급.

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

@lidge-jun

Copy link
Copy Markdown
Owner Author

Cascading downward. The lab synchronous-activation guard now walks the activation chain recursively instead of stopping one hop after startServer, with boundaries that keep it from reporting nested callbacks and receiver calls that cannot suspend the caller.

Evidence at the verified tip baaf691 (tree 3728219b8b5c1758a768108e4b69b648fa0828f6), from dispatch run 35053542913:

  • test 1-4/4, macos 1-2/2 and macos control all completed with conclusion success, confirmed through the check-runs API rather than the check rollup. windows 1/6 through 6/6 all succeeded, as did gates, storage policy, api usage, docker smoke, keyring and npm-global on three platforms, and the aggregate ci check.
  • A dispatch run is the evidence here rather than the pull-request run, and that is not incidental. This lane's tip layer changes only documentation, so the changes path filter skips every heavy job on a pull_request event while the aggregate check still reports green. Because the layers below carry [skip ci], the pull-request signal alone would have covered nothing at all. The dispatch runs against the tip tree, which contains all four layers.
  • The dispatch ran at commit c086fda, whose tree is byte-identical to this tip's tree. The lane re-absorbed dev from the bottom layer upward afterwards to keep each pull request's diff to its own layer (5 / 1 / 3 / 9 files); only the merge order changed, so the run remains valid evidence for this tree.
  • windows 5/6 failed once with EPERM ... rm ocx-management-auth-* at tests/helpers/remove-tree.ts:38 and passed on re-run. That is pre-existing Windows behaviour, not a regression from this lane: the guard added here runs once before the retry loop, line 38 is the existing rethrow-after-retries path, the raised error is rmSync's EPERM rather than a guard refusal, the identical code passed in run 35050601423, and src/config/paths.ts:57 already documents hardenConfigDir() leaving icacls.exe holding the directory. The retry budget being too small under six concurrent Windows shards is filed separately as [Bug]: the Windows removal retry budget is 2.5 seconds and is exceeded under shard load #4789.
  • git merge-tree --write-tree origin/dev <tip> reports a clean merge.
  • Ancestry verified so each layer closes as MERGED: rg1, rg2 and rg3 are all ancestors of this tip.

Chained-child stacks merge top-down, so this lands in the parent branch and cascades to dev.

Maintainer integration decision under MAINTAINERS.md / AGENTS.md: a maintainer with maintain or admin access may integrate into dev without a second maintainer approval, recording the decision and exact-head CI evidence.

@lidge-jun
lidge-jun merged commit 13f7573 into codex/rg1-destructive-home-guard Sep 16, 2026
7 checks passed
@lidge-jun
lidge-jun deleted the codex/rg2-lab-sync-activation-guard branch September 16, 2026 05:06
@github-actions github-actions Bot changed the title fix(tests): walk the Lab activation chain instead of its first hop (#4704) [WRONG BRANCH] fix(tests): walk the Lab activation chain instead of its first hop (#4704) Sep 16, 2026
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • wrong target branch (codex/rg1-destructive-home-guard); retarget to dev.

What to do

  • Retarget this PR to dev — all contributions go to dev.

Its title has been prefixed with [WRONG BRANCH].
Automatic draft conversion failed (token cannot change draft status). Please convert this pull request to a draft manually. The required enforce-target check will keep failing until every issue above is resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant