Skip to content

finding(app-shell): two console/home HomePage tests tear their fetch stub down BEFORE RTL cleanup, and the stub is a blanket sink rather than a router #8033

Description

@baozhoutao

Filed unassigned by the domain:devx @ objectui dev seat while doing batch 4 of #7307 (PR #8032). ⛔ No domain:*, type, grade or label set — triage's to produce. Observation, not a red: nothing fails today, and neither file is on the network-escape ledger.

Duplicate check before filing: read the full open set updated since 2026-09-06T09:19Z over repo-scoped REST (23 cards: #8031 #8030 #8029 #8028 #8027 #8026 #8024 #8021 #8018 #8015 #7990 #7839 #7750 #7711 #7582 #7401 #7310 #7307 #7058 #6892 #5748 #5560 #5174), on top of the PM seat's full open domain:devx read at 09:19Z. #7996 is the nearest neighbour and is a different instance — it is about an explain double answering a key no hook reads; this is about teardown ORDER and about a stub that answers everything. #7439 is the card that named the ordering hazard; it is closed as completed and fixed exactly one file (RecordDetailView.approvalDeclaredActions.test.tsx). No open card covers these two files.

What was measured

Two files in packages/app-shell/src/console/home/__tests__/ install a fetch stub and tear it down like this:

HomePage.marketplaceDisabled.test.tsx:107   vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true, status: 200, json: async () => body })));
HomePage.marketplaceDisabled.test.tsx:136   afterEach(() => {
HomePage.marketplaceDisabled.test.tsx:137     vi.unstubAllGlobals();

HomePage.aiStudioDisabled.test.tsx:135      vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true, status: 200, json: async () => body })));
HomePage.aiStudioDisabled.test.tsx:168      afterEach(() => {
HomePage.aiStudioDisabled.test.tsx:169        vi.unstubAllGlobals();

Neither file imports cleanup, and neither calls it before vi.unstubAllGlobals().

Two distinct things are off, both named verbatim by the network-escape guard's own Fix: text in vitest.setup.network-escape-guard.ts.

1. The teardown order is the #7439 order, inverted

Vitest runs afterEach hooks in REVERSE registration order, so a teardown written in a test file runs BEFORE the root setup's RTL cleanup(). Unstubbing there restores the real fetch while the tree is still mounted, so any read that cleanup()'s act-flush triggers reaches a real socket. That is the whole content of #7439, and the guard's error text spells out the remedy: "call cleanup() BEFORE vi.unstubAllGlobals()". Batches 1 to 4 of #7307 have installed exactly that pairing in 16 files; these two sit next door to batch 4's four and do the opposite.

Why it is green today rather than flaky: HomePage mounts PendingDraftsBanner, whose usePendingDrafts fetches only from its mount effect and issues nothing on unmount, so the window the inverted order opens is currently empty. That is a property of today's HomePage, not of these files — it is exactly the kind of thing that changes under someone else's PR, and #7439 was found because it cost an unrelated PR its enqueue window.

2. The stub is a sink, not a router

bootOn() answers every URL with the runtime-config body:

const serverConfig = (cloudUrl, marketplace) => ({ cloudUrl, singleEnvironment: true, features: {...}, branding: {...} });

HomePage reaches at least two endpoints during these renders — GET /api/v1/runtime/config (what the stub is for) and GET /api/v1/meta/_drafts (usePendingDrafts.ts:48, measured in #7307 batch 4 as the escape route of the four sibling files in this directory). The _drafts reader parses the runtime-config body, finds no drafts key, and yields [] — inert, so nothing fails. But it means the double documents a request/response pairing that does not exist, and, more importantly, a future new escape from HomePage would be silently answered instead of going red: the sink has no record of what it was handed and no assertion that it only served what it meant to serve. The router shape #5225 settled on, and that #7307's batches use, fails on any URL outside the served set.

Why it is worth a line rather than a fix here

It is out of scope for #7307 batch 4, which owns four named files and two ledger literals; neither of these two files is on the ledger, so touching them would be a scope breach with no gate to prove the change. ⛔ Not fixed in PR #8032.

The fix, when someone takes it, is mechanical and is the same one landed sixteen times already: import cleanup, call it before vi.unstubAllGlobals(), and turn bootOn's stub into a recording router over the two routes with an afterEach assertion that nothing else was requested.

Activity

  1. added theissue type on Sep 8, 2026
  2. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    Contributor

    Triage routing

    Labels tests tooling finding domain:devx pm:queue priority:p3 · Type Task

    Lane — domain:devx. The subject is test-harness discipline — teardown ordering and the network-escape guard — not any product surface. The two files live in packages/app-shell, but the gate's subject sets the lane, not the symptom's address.

    Re-measured on 83d4fda16 — confirmed. Line numbers have drifted by 2-3.

    card says actual today
    HomePage.marketplaceDisabled.test.tsx:107 / :137 :109 stub / :139 vi.unstubAllGlobals()
    HomePage.aiStudioDisabled.test.tsx:135 / :169 :137 stub / :171 vi.unstubAllGlobals()

    And the load-bearing negative verifies: grepping both files for cleanup returns zero — neither imports it, neither calls it before vi.unstubAllGlobals(). The same query finds vi.unstubAllGlobals and vi.stubGlobal in both files, so the pattern is live and the zero is a reading.

    ⚠️ One addition the card does not carry: each file has a second stub site — marketplaceDisabled:199 and aiStudioDisabled:263, both vi.stubGlobal('fetch', … status: 404 …). So there are four stub installations across the two files, not two. Whoever takes this must convert all four, not just bootOn's.


    Grade p3, and the card is right that it is an observation rather than a red

    ⭐ Nothing fails today, and the reason it doesn't is the reason to file it. HomePage mounts PendingDraftsBanner, whose usePendingDrafts fetches only from its mount effect and issues nothing on unmount, so the window the inverted order opens is currently empty. That is a property of today's HomePage, not of these files — exactly the kind of thing that changes under someone else's PR. And there is precedent for the cost: objectui#7439 was found because it cost an unrelated PR its enqueue window.

    Re-grade to p2 the moment HomePage (or anything it mounts) gains an unmount-time or cleanup-triggered read — that closes the "currently empty" escape and turns this into a live flake on someone else's branch.

    The two defects are separate and both should land together

    1. Teardown order is #7439's, inverted. Vitest runs afterEach hooks in reverse registration order, so a teardown written in a test file runs before the root setup's RTL cleanup(). Unstubbing there restores the real fetch while the tree is still mounted, so any read cleanup()'s act-flush triggers reaches a real socket. The remedy is spelled out verbatim in the guard's own Fix: text in vitest.setup.network-escape-guard.ts: "call cleanup() BEFORE vi.unstubAllGlobals()". Batches 1-4 of #7307 have installed exactly that pairing in 16 files; these two sit next door to batch 4's four and do the opposite.

    2. The stub is a sink, not a router. bootOn() answers every URL with the runtime-config body. HomePage reaches at least two endpoints — GET /api/v1/runtime/config (what the stub is for) and GET /api/v1/meta/_drafts (usePendingDrafts.ts:48, measured in #7307 batch 4 as the escape route of the four sibling files in this directory). The _drafts reader parses the runtime-config body, finds no drafts key, and yields [] — inert, so nothing fails.

    ⭐ Defect 2 is the one worth the card even though defect 1 has the precedent. A sink has no record of what it was handed and no assertion that it only served what it meant to serve, so a future new escape from HomePage would be silently answered instead of going red. That is a test double that cannot fail — the same shape this repo keeps carding (#8492 / #8535 / #8410 / #8465), here in the harness rather than in a gate. The router shape #5225 settled on, and that #7307's batches use, fails on any URL outside the served set.

    ⛔ Do not fix defect 1 alone. Ordering without routing leaves the sink; routing without ordering leaves the socket window. The card's own summary is the acceptance criterion: import cleanup, call it before vi.unstubAllGlobals(), and turn bootOn's stub into a recording router over the two routes with an afterEach assertion that nothing else was requested.

    ⛔ Correctly not fixed in PR #8032. #7307 batch 4 owns four named files and two ledger literals; neither of these two files is on the network-escape ledger, so touching them would be a scope breach with no gate to prove the change. ⚠️ Note the consequence for whoever claims this: there is still no gate over these two files, so the PR must carry its own evidence — the afterEach assertion in defect 2 is that evidence, which is another reason not to split the two halves.

    Dedup accepted and unusually well done — the full open set updated since 2026-09-06T09:19Z read over repo-scoped REST (23 cards enumerated), on top of a full open domain:devx read. The two near-misses are correctly distinguished: #7996 is an explain double answering a key no hook reads (different instance, same family); #7439 named the ordering hazard, is closed as completed, and fixed exactly one file (RecordDetailView.approvalDeclaredActions.test.tsx). No open card covers these two files.

    Triage seat only — no claim, no dispatch, no code.


    Generated by Claude Code

  3. claude commented on Sep 8, 2026

    @claude
    Contributor

    Claim: PM loop round R49 — domain:devx @ objectui execution seat.
    Session: session_01FhBNJcLRZLe8M87VcUgpKr
    Branch: claude/issue-8033-homepage-fetch-stub-router
    Worktree: /home/user/objectui-issue-8033
    Domain: domain:devx
    Dispatch: one os-dev subagent of this session (mode:subagent, model: opus); the dispatch note follows. Labels pm:queue → pm:dispatched and assignee set in one write, read back and diffed against the union at 2026-09-08T20:18:40Z.

    Why this card

    Lane at 2026-09-08T20:18Z: 60 open, 21 dispatchable. No priority:p0, ⛔ no pm:blocking anywhere, no target: items; the type-Bug level holds only objectui#8380, which is already fixed by 83d4fda16 and close-requested (5587718694) ⇒ the live level is the rest, oldest first, and this is the oldest dispatchable card in the lane. ⭐ priority:p3 is not what ranks it — below p0 the priority label does not rank.

    ⛔ Both halves land together — carried from triage verbatim

    ⛔ Do not fix defect 1 alone. Ordering without routing leaves the sink; routing without ordering leaves the socket window.

    ⇒ the acceptance criterion is the card's own sentence: import cleanup, call it before vi.unstubAllGlobals(), and turn bootOn's stub into a recording router over the two routes with an afterEach assertion that nothing else was requested.

    ⚠️ Four stub sites, ⛔ not two — triage's addition, and the easiest thing to miss: marketplaceDisabled:199 and aiStudioDisabled:263 each install a second vi.stubGlobal('fetch', … status: 404 …).

    ⚠️ There is no gate over these two files — neither is on the network-escape ledger, which is why objectui#7307 batch 4 correctly did not touch them. ⇒ the PR must carry its own evidence, and the afterEach assertion is that evidence. ⭐ Another reason the two halves cannot be split.

    Concurrency and file surface

    Three in flight (batch:3), surfaces pairwise disjoint:

    this card     : packages/app-shell/src/console/home/__tests__/HomePage.{marketplaceDisabled,aiStudioDisabled}.test.tsx
    objectui#8099 : .github/workflows/** (the corepack setup step)
    objectui#8316 : packages/lint gate configuration + the source-text pin over packages/core/src/registry/Registry.ts
    

    Generated by Claude Code

  4. claude commented on Sep 8, 2026

    @claude
    Contributor

    Dispatch — round R49, domain:devx @ objectui execution seat, 2026-09-08T20:20Z

    One os-dev subagent of session session_01FhBNJcLRZLe8M87VcUgpKr (mode:subagent, model: opus), branch claude/issue-8033-homepage-fetch-stub-router, worktree /home/user/objectui-issue-8033. Three in flight (batch:3), surfaces pairwise disjoint with objectui#8099 and objectui#8316.

    Adjudication section

    ⛔ No maintainer ruling applies. The constraints are the card's and triage's (5583438716).

    ⛔ Binding fences

    ⛔ Do not fix defect 1 alone. Ordering without routing leaves the sink; routing without ordering leaves the socket window.

    ⚠️ Four stub sites, ⛔ not two — marketplaceDisabled:199 and aiStudioDisabled:263 each install a second vi.stubGlobal('fetch', … 404 …). All four convert.

    ⚠️ No gate covers these two files (neither is on the network-escape ledger — which is why objectui#7307 batch 4 correctly left them alone). ⇒ the PR carries its own evidence, and the afterEach assertion is it.

    ⭐ The router shape is objectui#5225's, already used by objectui#7307's batches: it fails on any URL outside the served set. The dev reads one of batch-4's four files and matches it — ⛔ it does not invent a third shape.

    Preconditions falsified against the tree first

    ⚠️ Anchored on text, ⛔ not the recorded line numbers (triage measured them already drifted 2-3): both files unstub in a locally-registered afterEach and neither imports cleanup — ⚠️ with a control in the same run, since that negative is the card's centre · four stub sites · usePendingDrafts really reads /api/v1/meta/_drafts from its mount effect · neither file is on the ledger · the reference router shape exists and is named.

    ⭐ The demonstration this dispatch will not accept a PR without

    A scratch mutation adding an unexpected fetch must turn the new assertion red, restored afterwards with the restore proven by blob hash (⛔ not by an exit code). ⭐ If the router cannot fail, it is a sink with extra steps — which is the defect, not the fix.

    Changeset

    ⚠️ The dev picks the form from AGENTS.md §9 itself; ⛔ this brief names no bump level.


    Generated by Claude Code

  5. baozhoutao commented on Sep 8, 2026

    @baozhoutao
    ContributorAuthor

    os-dev-report

    {
      "issue": 8033,
      "status": "done",
      "branch": "claude/issue-8033-homepage-fetch-stub-router",
      "worktree": "/home/user/objectui-issue-8033",
      "pr": "https://github.com/objectstack-ai/objectui/pull/8661",
      "pr_number": 8661,
      "pr_draft": true,
      "head_sha": "d8a971b7240772d9a91e183192360468ec553ae2",
      "base": "4dc80d0fc",
      "premise_still_valid": true,
      "summary": "Both defects landed together in both files. Teardown: both now import `cleanup` from @testing-library/react and call it BEFORE `vi.unstubAllGlobals()` in the single file-level afterEach. Routing: the blanket sink is replaced by one module-scope recording router (`installHomeRouter`, registered by a module-scope beforeEach so it covers every describe block) serving exactly `GET /api/v1/runtime/config` and `GET /api/v1/meta/_drafts`, 404ing and RECORDING everything else, with an afterEach assertion that nothing outside the served set was requested. All FOUR stub sites converted, not two. 14 tests passed before and after; no assertion changed. Empty-frontmatter changeset added (test-only). Claim verified: newest Claim comment 5591323696 names this branch. Assignee untouched, no second claim posted, no pm:* label touched.",
      "preconditions": {
        "1_no_cleanup_with_control": "CONFIRMED. `grep -n cleanup` exit 1 in both files. Control in the same run: `vi.unstubAllGlobals` found at marketplaceDisabled:139 and aiStudioDisabled:171; `vi.stubGlobal` found at marketplaceDisabled:109+:199 and aiStudioDisabled:137+:263. So the zero is a reading, not a bad pattern.",
        "2_four_stub_sites": "CONFIRMED. Exactly four, at the four lines above - triage's re-measured numbers reproduce unchanged on 4dc80d0fc (the card's original 107/136/135/168 are the drifted ones).",
        "3_second_route_is_real": "CONFIRMED. HomePage.tsx:209 calls usePendingDrafts({}) inside PendingDraftsBanner; the banner is mounted at HomePage.tsx:401 and :487. fetchPendingDrafts hits `/api/v1/meta/_drafts` at usePendingDrafts.ts:48 with the GLOBAL fetch (no apiFetch seam) from the mount effect at :116 via refresh at :90. Measured live: leg A of the ablation enumerated both routes in all 14 cases.",
        "4_not_on_ledger": "CONFIRMED, and stronger - there is no ledger. KNOWN_ESCAPES was retired on 4b9fe9819 when objectui#7307 reached zero; scripts/__tests__/network-escape-ledger.test.ts now pins its ABSENCE. Neither filename appears anywhere outside its own directory (grep exit 1); control query `HomePage.approvalsTarget` does return a hit, so the zero is a reading.",
        "5_reference_shape": "CONFIRMED. Copied from packages/app-shell/src/console/home/__tests__/HomePage.approvalsTarget.test.tsx (objectui#7307 batch 4, commit 78a9b74a6, PR #8032). No third shape invented."
      },
      "stub_sites": [
        "HomePage.marketplaceDisabled.test.tsx:109 (bootOn) - blanket ok:200 sink answering the runtime-config body for EVERY url -> the shared recording router; bootOn now only sets `configAnswer` then awaits initRuntimeConfig()",
        "HomePage.marketplaceDisabled.test.tsx:199 (fail-open case) - second blanket sink, ok:false status:404 for EVERY url -> `await bootOnNoConfig()`, driving the SAME router whose /runtime/config leg answers 404",
        "HomePage.aiStudioDisabled.test.tsx:137 (bootOn) - blanket ok:200 sink -> the shared recording router",
        "HomePage.aiStudioDisabled.test.tsx:263 (fail-open case) - second blanket 404 sink -> `await bootOnNoConfig()`"
      ],
      "router_shape": "Batch-4 shape verbatim, widened from one route to two: module-scope `let fetchCalls: string[]`, `routeOf(url) = url.split('?')[0]` (pathname match, because usePendingDrafts appends a ?packageId= scope), `installHomeRouter()` in a module-scope beforeEach doing `vi.stubGlobal('fetch', vi.fn(...))` that pushes every url then routes; afterEach asserts `fetchCalls.filter(url => !SERVED_ROUTES.includes(routeOf(url)))` is empty, then cleanup(), then vi.unstubAllGlobals(), then resetRuntimeConfigForTesting(). The one addition batch 4 did not need: a per-case `configAnswer` for /runtime/config, reset by installHomeRouter to a 404 so a case that never boots cannot inherit the previous case's payload. Each file now holds exactly ONE vi.stubGlobal.",
      "failure_demonstration": {
        "leg_a_non_vacuity": "Mutated the afterEach to demand the RECORDED set be empty. On-disk proof: injected anchor count 1, removed anchor `SERVED_ROUTES.includes` count 0; blobs 1e547fa8 -> 0d97c54b and ec59bf4f -> ed63ecfc. OBSERVED RED, exit 1, Test Files 2 failed (2), Tests 14 failed (14), each case enumerating exactly ['/api/v1/runtime/config', '/api/v1/meta/_drafts']. Proves the served set equals the requested set and neither route is decoration.",
        "leg_b_escape_probe": "Injected `void fetch('/api/v1/ABLATION/unexpected-endpoint')` into each file's renderHome(). On-disk proof: injected anchor count 1 in each; blobs 1e547fa8 -> 62ccca59 and ec59bf4f -> 39de5a39. Predicted red before running. OBSERVED RED, exit 1, Test Files 2 failed (2), Tests 14 failed (14), each case naming the unexpected url. Under the old sink the same probe is answered ok:true with the runtime-config body and stays green.",
        "restore_proof": "By blob hash, not exit code. After each leg both paths' `git hash-object` equal their HEAD blobs (1e547fa8... and ec59bf4f...) and `git diff HEAD --name-only` is empty. Script carried `trap <restore-from-HEAD> EXIT INT TERM` with absolute paths derived from `git rev-parse --show-toplevel`; both legs ran on the COMMITTED tree (2aeecf4f8). No dist/preflight leg owed: these are the vitest projects' own test files, transformed from source."
      },
      "files_touched": [
        "packages/app-shell/src/console/home/__tests__/HomePage.marketplaceDisabled.test.tsx",
        "packages/app-shell/src/console/home/__tests__/HomePage.aiStudioDisabled.test.tsx",
        ".changeset/8033-homepage-fetch-router.md"
      ],
      "tests": "All exit codes captured by redirect-then-capture, never through a pipe. BASELINE on 4dc80d0fc: `pnpm exec vitest run <the two files>` exit 0, Test Files 2 passed (2), Tests 14 passed (14). FINAL on d8a971b72 (tree clean): `pnpm exec vitest run packages/app-shell/src/console/home/ scripts/__tests__/network-escape-ledger.test.ts` exit 0, Test Files 13 passed (13), Tests 75 passed (75), and zero lines matching `Network escape` or `ECONNREFUSED` in the whole run. `pnpm --filter @object-ui/app-shell run type-check` exit 0 (tsc --noEmit plus tsc -p tsconfig.test.json); NON-VACUITY proven with `tsc -p tsconfig.test.json --listFiles`, which names each edited file exactly once. Its FIRST reading was exit 2 with TS2307 on @object-ui/* - a missing prerequisite, not a red gate; `pnpm --filter '@object-ui/app-shell^...' run build` exit 0 is what it was owed. eslint over both edited files exit 0; --format json reports 2 files linted, 0 errors, 2 warnings, both the pre-existing no-explicit-any on the useObjectLabel mock at marketplaceDisabled:53 and aiStudioDisabled:74 - unmoved lines strictly before every insertion point, so no warning is mine. `node scripts/check-changeset-presence.mjs` exit 1 before the changeset, exit 0 after. `node scripts/check-control-bytes.mjs` exit 0 (6851 tracked text files) plus a `grep -naP` control-range self-scan over all three changed paths, no hits. `node scripts/check-governed-queue-guard.mjs --test` over all changed paths exit 0, NOT GOVERNED, 3 paths against 5 governed surfaces. NOT RUN LOCALLY, declared to CI: the repo-wide `turbo run lint` and `turbo run type-check`, and the full app-shell package suite.",
      "mcp_calls": "6 - issue_read get, issue_read get_comments, pull_request_read #8032 (to match the PR-body footer convention), create_pull_request, pull_request_read #8661 (body read-back), add_issue_comment (this one). All card/commit reading went through git and the local tree.",
      "open_questions": [],
      "out_of_scope_findings": [],
      "notes": [
        "Nothing was escalated to the maintainer floor: no accepted set widened, no contract changed, no gate weakened, no runtime dependency added. Test files only.",
        "The afterEach asserts BEFORE cleanup(), matching batch 4's 16-file precedent verbatim. Asserting after cleanup() would additionally catch an escape raised by the act-flush, but that would be a third shape and the brief fences it; the root guard's own afterEach still runs after this file's teardown and reds any REAL socket in that window.",
        "The repo is a shallow clone (`git rev-parse --is-shallow-repository` = true). No ancestry negative was relied on: every reading above is a text or blob reading on the checked-out tree, and `git show` of 78a9b74a6 / 4b9fe9819 resolved inside the shallow window.",
        "Kept in draft per the dispatch. No ready flip, no auto-merge, no merge, no label written, no assignee written."
      ]
    }

    Generated by Claude Code

  6. claude commented on Sep 8, 2026

    @claude
    Contributor

    LANDED — objectui#8033 via PR objectui#8661, merged 5798df42a

    merged_at 2026-09-08T21:17:37Z; merged and removed_from_merge_queue share that timestamp with merged: true ⇒ that removal is the merge, ⛔ not a dequeue.

    Landing probe on a re-fetched origin/main (tip 5798df42a)

    ⚠️ Read as LINE ORDER, ⛔ never presence — both cleanup and vi.unstubAllGlobals were present before this PR too, and the order was the defect. Comment lines excluded from the scan.

    marketplaceDisabled aiStudioDisabled
    vi.stubGlobal sites 1 (:163) 1 (:191)
    cleanup() :237 :269
    vi.unstubAllGlobals() :238 :270
    ⭐ order: cleanup BEFORE unstub ✅ ✅
    imports cleanup ✅ ✅
    firing control — SERVED_ROUTES 2 refs 2 refs

    Nonsense control: ✅ exit 1.

    ⭐ One vi.stubGlobal per file is the count that matters: there were four sinks across the two files, and a fix that converted only bootOn's would have left two behind while looking finished. Four collapsed into two routers, one per file.

    What is now true that was not

    Teardown order. Vitest runs afterEach hooks in reverse registration order, so a teardown written in the test file ran before the root setup's RTL cleanup() — restoring the real fetch while the tree was still mounted, so any read that cleanup()'s act-flush triggered reached a real socket. From 5798df42a both files call cleanup() first, matching the pairing objectui#7307's batches installed in 16 files.

    The sink is gone. bootOn() used to answer every URL with the runtime-config body. ⭐ A sink has no record of what it was handed and no assertion that it served only what it meant to — so a future new escape from HomePage would have been silently answered instead of going red. Now a recording router serves exactly /api/v1/runtime/config and /api/v1/meta/_drafts, records everything else, and an afterEach asserts nothing outside that set was requested.

    ⭐ The evidence, because there is still no gate over these two files

    Precondition 4 came back stronger than the card stated: not only are these files not on the network-escape ledger — ⭐ there is no ledger. KNOWN_ESCAPES was retired when objectui#7307 reached zero, and a test now pins its absence. ⇒ nothing external can prove this change, which is why the PR had to carry its own proof:

    • Leg A (non-vacuity) — demand the recorded set be empty ⇒ 14/14 red, each case enumerating exactly the two served routes. ⇒ the router really records, and really sees both.
    • Leg B (escape probe) — inject an unexpected fetch ⇒ 14/14 red, each naming the URL. ⭐ Under the old sink that same probe is answered ok: true and stays green. ⇒ the defect reproduced and closed in one measurement.

    Both proven on disk by anchor count and blob hash, restored under a trap, restoration proven by blob equality against HEAD — ⛔ not by an exit code.

    One design note kept on the record

    The afterEach asserts before cleanup(), matching batch 4's 16-file precedent verbatim. Asserting after would additionally catch an escape raised by the act-flush — ⭐ but inventing a third shape in this family is the disease this lane keeps carding, and the root guard's own afterEach still runs after this file's teardown and reds any real socket in that window.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repofindingpriority:p3teststooling

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions