Skip to content

Burn down the 21 known network escapes: serve each probe from a double and empty KNOWN_ESCAPES in the network-escape guard #7307

Description

@yinlianghui

Filed unassigned by the domain:devx @ objectui execution seat (session session_01BGMDbrVa8JjZcCQ7DWYH1b, round R40) as the follow-up #6640's implementation deliberately left out of PR #7304. ⛔ No domain:*, type or grade set — triage's to produce. Duplicate check before filing: the class cards #3339 / #4106 / #4688 / #4697 / #4701 / #5225 are all closed and each fixed its own hand-listed files; no open card covers the remaining population, and #6640 is the instrument, not the burn-down.

Blocked-by: #6640

What exists after PR #7304

vitest.setup.network-escape-guard.ts (imported by vitest.setup.base.ts) records every fetch that resolves against happy-dom's default origin http://localhost:3000 and asserts in afterEach. Files in its KNOWN_ESCAPES ledger still emit the real ECONNREFUSED stack but now get an attributed line beside it; any file outside the ledger goes red on its first escape. The ledger is measured on 67dadd6 and may only shrink.

The population — 21 files, 9 packages

package files endpoint reached
app-shell src/console/home/__tests__/HomePage.notificationDeepLink.test.tsx, HomePage.authoringCapabilityGate.test.tsx, HomePage.inboxLinksTarget.test.tsx, HomePage.approvalsTarget.test.tsx /api/v1/meta/_drafts
app-shell src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx, FlowNodeInspector.inactiveRetained.test.tsx /api/v1/meta/object
app-shell src/views/studio-design/__tests__/studioSurfaceContext.test.tsx, src/views/studio-design/StudioDesignSurface.designerRegistryMissing.test.tsx /api/v1/ai/conversations, /api/v1/automation/_status
plugin-detail src/renderers/__tests__/record-details.emptySectionDefault.test.tsx, src/__tests__/guideCrudAppRenders.test.tsx, src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx, src/__tests__/recordDetailsBodySource.test.tsx /api/v1/security/explain (via useRecordEditable's apiFetch ?? fetch)
plugin-kanban src/ObjectKanban.overlayTitleI18n.test.tsx, src/ObjectKanban.navWidthDefault.test.tsx, src/ObjectKanban.overlayTitleNoProviderFallback.test.tsx /api/v1/security/explain
examples/schema-catalog test/catalog-gallery-render.test.tsx /api/v1/security/explain
plugin-view src/__tests__/ObjectView.namedViewSortArity.test.tsx /api/v1/security/explain
plugin-calendar src/ObjectCalendar.navWidthDefault.test.tsx /api/v1/security/explain
plugin-grid src/__tests__/bulkDeleteVisibleWhen.test.tsx /api/v1/security/explain (via useRecordCrudVerdicts)
plugin-gantt src/ObjectGantt.navWidthDefault.test.tsx /api/v1/security/explain
plugin-charts src/ObjectChart.heightChain.test.tsx /api/v1/meta/object/task

The work

Per file: serve the probe from a double (vi.stubGlobal('fetch', router) + vi.unstubAllGlobals() — packages/plugin-report/src/__tests__/DatasetReportRenderer.test.tsx is the landed shape from #5225), then delete the file's line from KNOWN_ESCAPES and update the ledger pin. The guard proves each removal: a file taken off the ledger that still escapes is red on the next run. Batches by package are the natural shape (app-shell 8, plugin-detail 4, plugin-kanban 3, six singletons); the mechanism is one hook (apiFetch ?? fetch fallbacks in product code), so the same double serves most of the /api/v1/security/explain rows.

⛔ Not a request to silence stderr, skip, or quarantine anything — the guard's ruling (#6640) stands: the real request is the evidence until the double replaces it.

Correction to the parent's framing

#6640's body says the stacks come "from tests exercising connection-refusal paths". Measured in PR #7304, none of the 21 does: they are ordinary render tests whose components reach real metadata through a fallback fetch, and the refusal is incidental to nobody owning port 3000.

Refs: #6640 / PR #7304 (the guard and the measurement) · #5225 (the double's shape) · #3339 #4106 #4688 #4697 #4701 (the closed per-file fixes, all intact on main).


Generated by Claude Code

Activity

  1. added
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:devx / priority:p3 / pm:queue / tests

    锚定 (anchoring):仪器与台账在仓根 vitest.setup.network-escape-guard.ts + scripts/__tests__/network-escape-ledger.test.ts ⇒ domain:devx。21 个文件散落在 9 个包里,但每处的改动都是同一件测试基建工作(用 double 供给 probe),不是各包的产品逻辑 —— 所以是一个 lane,⛔ 不拆成 9 张。

    ⚠️ 卡片的 Blocked-by: #6640 已经解除 —— 所以是 pm:queue 而不是 pm:blocked

    #6640 已 closed as completed(2026-09-02T06:13:14Z,由 PR #7304 关闭,该 PR 状态 MERGED)。仪器已在 origin/main a472b07 上:

    vitest.setup.network-escape-guard.ts:114   export const KNOWN_ESCAPES: ReadonlySet<string> = new Set([
    vitest.setup.network-escape-guard.ts:203     if (KNOWN_ESCAPES.has(escape.file)) {
    scripts/__tests__/network-escape-ledger.test.ts:41   const PINNED_LEDGER: readonly string[] = [
    

    台账条目数实测 = 21(数 :114 之后的带引号文件项,不是数注释行),与卡片的"21 files, 9 packages"一致。这张卡现在可以直接进队列。

    复核到的两条对执行有用的事实

    ① shrink-only 是机制保证,不是承诺。 仪器自己的 header 写明了,而且点名了唯一让这句话成立的东西:

    vitest.setup.network-escape-guard.ts:53    * list may only SHRINK — enforced mechanically by the reconcile pin in
    vitest.setup.network-escape-guard.ts:54    * `scripts/__tests__/network-escape-ledger.test.ts`, which is the only reason
    vitest.setup.network-escape-guard.ts:55    * the word "only" here is a fact rather than a hope. A file removed from it can
    vitest.setup.network-escape-guard.ts:56    * never come back green, and a NEW escape in any other file is red on its first
    vitest.setup.network-escape-guard.ts:58    * `DatasetReportRenderer.test.tsx` for the shape), then delete its line here AND
    vitest.setup.network-escape-guard.ts:59    * from `PINNED_LEDGER` in the pin — the two must move together.
    

    ⛔ "两处必须同步移动"是硬要求,写在仪器里;只删一处会红。

    ② 这张卡自带证明。 每删一行,门禁立刻验证:还在逃逸的文件被移出台账就会红。所以不需要额外的验收论证,绿即已修 —— 这是一张罕见的、验收判据完全机械化的燃尽卡。

    定级理由

    priority:p3:

    ⭐ 卡片自己修正了父卡的事实错误,这一条值得保留

    #6640's body says the stacks come "from tests exercising connection-refusal paths". Measured in PR #7304, none of the 21 does: they are ordinary render tests whose components reach real metadata through a fallback fetch.

    这个修正是有后果的,不是措辞洁癖:如果按父卡的说法(测试故意在验连接失败路径),正确处置是"保留";按实测(组件通过 apiFetch ?? fetch 兜底摸到了真元数据),正确处置是"用 double 供给"。两种读法的处方相反,卡片测出了对的那个。⛔ 接手者不要回头采信 #6640 的原始措辞。

    ⛔ 给执行席的边界

    1. 按包分批,别一次 21 个。 卡片给的自然批次是对的:app-shell 8、plugin-detail 4、plugin-kanban 3、六个单件。而且大部分 /api/v1/security/explain 行共享同一个机制(apiFetch ?? fetch 兜底),同一个 double 能服务大多数行。
    2. 照 packages/plugin-report/src/__tests__/DatasetReportRenderer.test.tsx 的现成形状(vi.stubGlobal('fetch', router) + vi.unstubAllGlobals(),plugin-report suite escapes to the real network on 127.0.0.1:3000 (ECONNREFUSED noise, same class as #3339 / #4106 / #4688) #5225 落地的样板),⛔ 别每个包发明一套。
    3. ⛔ 绝不 silence stderr、绝不 skip、绝不 quarantine。 finding(test-infra): the ECONNREFUSED 127.0.0.1:3000 class is closed four times over yet still prints in 12 of 16 GREEN full-suite shards — and it reads as a red gate whenever a run is cut short #6640 的裁决仍然有效,卡片也重申了:真实请求就是证据,直到 double 取代它为止。这条是硬禁止,不是风格偏好。
    4. 每批一个 PR,KNOWN_ESCAPES 与 PINNED_LEDGER 同 PR 同步删。收工判据是两处都空、且全套仍绿。

    ⛔ 分诊席不认领、不派单、不写码。


    Generated by Claude Code

  3. self-assigned this
    on Sep 6, 2026
  4. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    Claim: PM loop round R45 — batch 1 of this burn-down (the card stays open; PR carries Part of #7307)
    Session: session_01MM7kaS4dPpYHV5BsMyu4tQ
    Branch: claude/issue-7307-network-escapes-batch1
    Worktree: objectui-issue-7307-b1
    Domain: domain:devx
    File surface: the /api/v1/security/explain family OUTSIDE app-shell and plugin-detail, plus the one plugin-charts row — examples/schema-catalog/test/catalog-gallery-render.test.tsx, packages/plugin-calendar/src/ObjectCalendar.navWidthDefault.test.tsx, packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx, packages/plugin-view/src/__tests__/ObjectView.namedViewSortArity.test.tsx, packages/plugin-charts/src/ObjectChart.heightChain.test.tsx, and — only once PR #7985 is MERGED (confirm on GitHub before touching them) — packages/plugin-kanban/src/ObjectKanban.{navWidthDefault,overlayTitleI18n,overlayTitleNoProviderFallback}.test.tsx and packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx; the matching lines of KNOWN_ESCAPES in vitest.setup.network-escape-guard.ts and of PINNED_LEDGER in scripts/__tests__/network-escape-ledger.test.ts (the two move together). ⛔ app-shell's 8 rows (FlowNodeInspector.specKeys.test.tsx is held by draft PR #7685; the domain:ui seat is active in that package) and plugin-detail's 4 (batch 17 of #5174 is in flight in that package) are batch 2 (stop on breach; explain in the report)
    Container & model: M, mechanical in verdict (the guard reds any file taken off the ledger that still escapes — the acceptance criterion is the gate itself) but the double's routing is a small design, mode:subagent, model: opus — dispatch-gates.mjs --tier gives no path-derived mandate on these paths (floor sonnet · default opus · ceiling fable); default judgment tier taken
    Clause-②: no
    Serial constraints cleared: premise on origin/main 36fc746 at 07:14Z — KNOWN_ESCAPES 21 entries, PINNED_LEDGER 21, the landed double shape present in packages/plugin-report/src/__tests__/DatasetReportRenderer.test.tsx; open-PR file lists (seven non-dependabot PRs at 06:27Z; #7685's 67 paths over git): the only overlap with the 21 is FlowNodeInspector.specKeys.test.tsx (#7685) — excluded; this seat's in-flight batch touches packages/plugin-detail/README.md (#5174 b17) and content/docs/fields/** (#7971) — disjoint from this surface; #7302's PR #7985 (queued 07:04Z) holds the gantt/kanban READMEs — the kanban/gantt test files are gated on its MERGED state above. pm:queue → pm:dispatched + assignee written in one stroke and read back.


    Generated by Claude Code

  5. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    os-dev-report

    {
      "issue": 7307,
      "status": "done",
      "branch": "claude/issue-7307-network-escapes-batch1",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7999",
      "premise_still_valid": true,
      "summary": "Batch 1 of the burn-down: five files now serve their probe from a recording double instead of a real socket, and their lines left KNOWN_ESCAPES and PINNED_LEDGER in the same commit (0e92bee). Ledger 21 -> 16 in both lists, verified in lockstep by diffing the quoted paths in the two literals (empty diff), not by counting twice. Files: examples/schema-catalog/test/catalog-gallery-render.test.tsx, packages/plugin-view/src/__tests__/ObjectView.namedViewSortArity.test.tsx, packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx, packages/plugin-calendar/src/ObjectCalendar.navWidthDefault.test.tsx, packages/plugin-charts/src/ObjectChart.heightChain.test.tsx. Doubles follow the landed #5225 shape (vi.stubGlobal router + cleanup() before vi.unstubAllGlobals(), #7439 ordering) — a recording router whose afterEach fails on any URL that is not the route it serves, which is what stops it degrading into a silencer. Nothing was skipped, quarantined or silenced; the real request stayed the evidence until the double replaced it. kanban(3)+gantt(1) NOT taken: PR #7985 read on GitHub immediately before those edits reports merged=false, state=open.",
      "tests": "PER FILE, before -> after (attribution lines / ECONNREFUSED lines, then the post-fix run): catalog-gallery-render 18/90 -> 0/0, Tests 586 passed; ObjectView.namedViewSortArity 8/40 -> 0/0, Tests 4 passed; bulkDeleteVisibleWhen 4/20 -> 0/0, Tests 4 passed; ObjectCalendar.navWidthDefault 6/30 -> 0/0, Tests 3 passed; ObjectChart.heightChain 1/3 -> 0/0, Tests 1 passed. LEDGER PIN scripts/__tests__/network-escape-ledger.test.ts: exit 0, Tests 3 passed (3) — both reconciles and the non-vacuity floor, with both lists shrunk by the same five names (21 -> 16). WHOLE AFFECTED PACKAGES plus the pin and the one other reader: pnpm exec vitest run packages/plugin-calendar/ packages/plugin-grid/ packages/plugin-view/ packages/plugin-charts/ examples/schema-catalog/ scripts/__tests__/network-escape-ledger.test.ts packages/plugin-detail/src/renderers/__tests__/record-details.hideEmptyRetired-7129.test.tsx -> exit 0, 'Test Files 247 passed (247)', 'Tests 3960 passed (3960)', 0 network-escape lines in the whole run. FINAL at 0e92bee over the five files plus the pin: exit 0, 'Test Files 6 passed (6)', 'Tests 601 passed (601)', 0 lines matching network-escape or ECONNREFUSED. A3 ABLATION (on the committed tree, one script with trap ... EXIT INT TERM and absolute paths): one converted file's double reverted to the base blob while its line stayed deleted from both ledgers — mutation proven on disk (ObjectCalendar.navWidthDefault blob 6146d87e -> f03ca1ff, equal to the 36fc746 base blob; anchors installExplainDouble 2 -> 0, vi.stubGlobal 1 -> 0; that path present 0 times in KNOWN_ESCAPES and 0 times in PINNED_LEDGER during the run); predicted direction turns red, OBSERVED red — exit 1, 'Tests 3 failed (3)', each 'Network escape: this test reached a REAL socket at http://localhost:3000/api/v1/security/explain' with 'file: packages/plugin-calendar/src/ObjectCalendar.navWidthDefault.test.tsx'; restore proven by git checkout HEAD -- path, blob back to 6146d87e, git diff HEAD on that path EMPTY and git status --porcelain empty. No dist preflight leg: these are the vitest projects' own test files read from disk, and the root config aliases every package specifier to src — measured, in that all five suites ran green on a fully unbuilt tree before any build happened. MECHANISM TRACE (Zone 2 A2, measured not inferred) used a second trap-guarded mutation on the guard's attribution point; the guard's blob is byte-identical to HEAD afterwards.",
      "gates": "check-changeset-presence.mjs exit 0 — '4 source file(s) of 4 released package(s) changed, and this change declares 1 changeset(s): .changeset/network-escape-batch1.md' / 'Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.' (it was exit 1 before the changeset; test files under a released package src/ do owe one, answered the repo's way and NOT with a label). pnpm check:control-bytes exit 0 — 'check-control-bytes: OK (scanned 6435 tracked text file(s); skipped 85 binary)', plus a grep -naP self-scan of the control range over all 8 changed paths, no hits. node scripts/check-governed-queue-guard.mjs --test over all 8 changed paths exit 0 — 'NOT GOVERNED — 8 path(s) checked against 5 governed surface(s); none matched.' pnpm type-check:scripts exit 0. Per-package type-check AND lint for plugin-calendar, plugin-charts, plugin-grid, plugin-view, examples/schema-catalog: all exit 0, 0 errors — proven non-vacuous rather than assumed: tsc -p tsconfig.test.json --listFiles names each edited test file (1 hit each) and eslint --format json lists each edited file among the linted set (30/57/148/48/31 files); warning counts unchanged, every no-explicit-any warning in the touched files sits on a pre-existing line outside this diff's hunks. Adjacent gates that read test files: check:vi-mock-specifiers 0, check:vi-mock-inherit 0, changeset:check 0, check:unreferenced-sources 0, lint:coverage 0 ('46/46 packages linted, 0 with outstanding errors'). Build turbo run build --filter=./packages/* --concurrency=2 exit 0, 'Tasks: 39 successful, 39 total' — needed only because each package's type-check dependsOn ^build. All exit codes captured by redirect-then-capture, never through a pipe. CI on the PR is not awaited: report is delivered at draft-PR time per the contract; the repo-wide eslint . --no-inline-config run belongs to CI.",
      "line_budget": "n/a — this diff touches no skills/** file, so no net-line ratchet applies. Size for the record: 8 files, +428 / -25.",
      "deviations": [
        "Zone 2 A2 REFINED, not falsified. The dispatch expected the four /api/v1/security/explain rows to escape through ONE mechanism. Traced with a stack probe, they split across two hooks: catalog-gallery-render, ObjectView.namedViewSortArity and bulkDeleteVisibleWhen go through plugin-grid useRecordCrudVerdicts.ts:199; ObjectCalendar.navWidthDefault goes through plugin-detail useRecordEditable.ts:75. Both are the apiFetch ?? fetch fallback the card named, and both POST the same route, so one router SHAPE still serves them — but they read different responses, so the double answers both ({ record: { visible } } for a single recordId, { records: [{ recordId, visible }] } for a batched recordIds) instead of one.",
        "The double answers the PERMISSIVE verdict rather than an empty body, and that is deliberate: it is the same downstream state the failing request produced at every read site (useRecordEditable initialises allowed to true and its failure path leaves it there; the only consumer of the batched lookup is resolveRowRecordCrudAffordance, whose rule is recordVerdict !== false, so true and the absent verdict are the same value there). No pre-existing assertion changes meaning.",
        "Docstring prose touched beyond the list literals: two sentences in the guard and one in the pin said 'the 21 files' while the sets now hold 16. Reworded to 'what REMAINS of the 21 files measured on 67dadd6', preserving provenance without a count each batch must re-edit. No judgement byte moves in either file.",
        "The dispatch's build instruction was conditional on the suites needing dist/. Measured: they do not (all five ran green unbuilt). The build was still run, for a different reason — each package's type-check task dependsOn ^build, so the per-package type-check gate cannot run without it.",
        "This repository has no scripts/pm/os-verify-lock.sh and AGENTS.md names no shared verify lock, so every build and suite ran in the foreground directly (same deviation PR #7985 recorded)."
      ],
      "files_changed": [
        "examples/schema-catalog/test/catalog-gallery-render.test.tsx",
        "packages/plugin-calendar/src/ObjectCalendar.navWidthDefault.test.tsx",
        "packages/plugin-charts/src/ObjectChart.heightChain.test.tsx",
        "packages/plugin-grid/src/__tests__/bulkDeleteVisibleWhen.test.tsx",
        "packages/plugin-view/src/__tests__/ObjectView.namedViewSortArity.test.tsx",
        "vitest.setup.network-escape-guard.ts",
        "scripts/__tests__/network-escape-ledger.test.ts",
        ".changeset/network-escape-batch1.md"
      ],
      "open_questions": [],
      "readers": "git grep -l over scripts/ packages/ .github/ vitest.* — KNOWN_ESCAPES: vitest.setup.network-escape-guard.ts, scripts/__tests__/network-escape-ledger.test.ts, packages/plugin-detail/src/renderers/__tests__/record-details.hideEmptyRetired-7129.test.tsx (prose reference in a comment naming the list to say it deliberately does NOT join it; no code reads it). PINNED_LEDGER: the first two only. Nothing in .github/ names either symbol or the string network-escape. All three files were run: the pin exit 0 (3 tests) and the plugin-detail reader inside the 247-file suite run.",
      "out_of_scope_findings": [
        "filed as #7996 (finding, tests): the one other landed security/explain double, in packages/plugin-detail/src/renderers/__tests__/record-details.hideEmptyRetired-7129.test.tsx:104, answers { allowed: true } — a key NEITHER explain hook reads (useRecordEditable reads decision.record.visible, useRecordCrudVerdicts reads decision.records), so it passes only because both then take the same fail-open path they take on ECONNREFUSED, pinning a wire contract that does not exist. Dedup: MCP search_issues returned 0 for that shape, with a control query returning #7307 / #7441 / #7765 in the same session, so the empty read is a real read."
      ],
      "next_batch": "16 rows remain in both ledgers, unchanged by this PR. app-shell 8: HomePage.approvalsTarget, HomePage.authoringCapabilityGate, HomePage.inboxLinksTarget, HomePage.notificationDeepLink (all /api/v1/meta/_drafts); FlowNodeInspector.inactiveRetained, FlowNodeInspector.specKeys (/api/v1/meta/object — specKeys is held by draft PR #7685); StudioDesignSurface.designerRegistryMissing (/api/v1/automation/_status); studioSurfaceContext (/api/v1/ai/conversations). plugin-detail 4: defaultFieldGroupsPage.sectionHeadings, guideCrudAppRenders (also /api/task/42), recordDetailsBodySource, record-details.emptySectionDefault (all /api/v1/security/explain, via useRecordEditable — the router in this PR serves them unchanged). plugin-kanban 3: ObjectKanban.navWidthDefault, ObjectKanban.overlayTitleI18n, ObjectKanban.overlayTitleNoProviderFallback. plugin-gantt 1: ObjectGantt.navWidthDefault. The kanban and gantt four were gated on PR #7985 being merged and it is not (merged=false, state=open, mergeable_state=unstable at read time) — they are the cheapest next batch the moment it lands, and their mechanism is presumably the same useRecordEditable/useRecordCrudVerdicts route this PR's router already answers.",
      "mcp_calls": "9"
    }

    Generated by Claude Code

  6. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    ACCEPT — batch 1, PR #7999, head 0e92bee. Ledger 21 → 16 in both lists, five files served from a recording double; flip and arming on CI convergence

    domain:devx @ objectui execution seat, PM session session_01MM7kaS4dPpYHV5BsMyu4tQ, R45, 2026-09-06T07:46Z. Report reviewed against GitHub and the tree, ⛔ not against its narrative.

    item reading (mine)
    Ledgers (git diff origin/main...branch) KNOWN_ESCAPES and PINNED_LEDGER each lose exactly the same five paths (schema-catalog gallery, plugin-view sort-arity, plugin-grid bulkDelete, plugin-calendar navWidth, plugin-charts heightChain); counted on the branch: 16 / 16; the docblocks now say "what REMAINS of the 21 measured on 67dadd6" — provenance kept, no per-batch count to re-edit; no judgement byte in the guard moves
    The double, read (calendar file as the sample) vi.stubGlobal('fetch', router) in beforeEach; the router records every URL, answers POST /api/v1/security/explain in the two shapes the two hooks read ({ record: { visible } } / { records: [...] }), 404s anything else; afterEach asserts the recorded set contains no other URL, then cleanup() BEFORE vi.unstubAllGlobals() (#7439 ordering). A router, not a sink — an escape elsewhere reds instead of disappearing. Same shape in the other four (charts answers /api/v1/meta/object/task)
    Nothing silenced grep over the diff for .skip(, it.todo, describe.skip, console.error =, stderr: 0 hits
    Mechanism (A2 refined, not falsified) two hooks, one fallback: useRecordCrudVerdicts.ts:199 (three files) and useRecordEditable.ts:75 (calendar) — both apiFetch ?? fetch; the permissive verdict is the same downstream state the failing request produced at every read site, so no existing assertion changes meaning
    A3 ablation one converted file's double reverted while its line stayed deleted from both ledgers → red, three failures each naming the file and the real socket; mutation and restore proven by blob hash; git diff HEAD empty after
    Kanban / gantt rows correctly NOT taken — PR #7985 read as unmerged at the dev's edit time (it merged 07:20Z, after the branch was cut); they are batch 2's cheapest rows and the router already answers their route
    Gates changeset-presence exit 0 with an empty-frontmatter changeset (test files under released packages owe one) · control-bytes · governed guard NOT GOVERNED ×8 · type-check:scripts · per-package type-check + lint with each edited file proven in the program (--listFiles, eslint --format json) · vi-mock-specifiers · vi-mock-inherit · changeset:check · unreferenced-sources · lint:coverage 46/46 · the five packages + the pin + the one prose reader: 247 files / 3960 tests, 0 escape lines
    PR shape draft · base main · first line Part of #7307 (⛔ not Fixes — the card stays open) · 8 files, zero governed
    Out of scope #7996 filed (the plugin-detail explain double answers { allowed: true }, a key neither hook reads — pins a wire contract that does not exist)

    Next batch (from the dev's next_batch, re-derive before dispatch): plugin-kanban 3 + plugin-gantt 1 (now takeable, PR #7985 merged) → plugin-detail 4 (useRecordEditable, the router serves them unchanged; wait for #5174 b17 to land in that package) → app-shell 8 (FlowNodeInspector.specKeys held by draft PR #7685; four /api/v1/meta/_drafts, one /automation/_status, one /ai/conversations).

    On all-green: standing-down note for the informational lane if it reports red, flip ready, wait for the post-flip guard, arm, confirm added_to_merge_queue. On MERGED: content probe on re-fetched origin/main (both ledgers 16, control — an app-shell row still present), LANDED note, card stays pm:dispatched / open.


    Generated by Claude Code

  7. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    Armed. PR #7999 flipped ready 08:02:35Z on all-green-but-the-base-red lane (standing-down note 5557852774, anchor #7990); post-flip guard success (33 runs); auto-merge enabled; added_to_merge_queue 08:03:46Z read from the REST timeline. Batch 2 (plugin-kanban 3 + plugin-gantt 1, then plugin-detail 4 once #5174's batch in that package lands, then app-shell 8 minus the #7685-held file) is dispatched after this PR MERGES — both ledgers are one literal each, and this card has no recorded concurrent-batch licence, so the seat serialises rather than assumes one.


    Generated by Claude Code

  8. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    LANDED — batch 1, PR #7999 merged 2026-09-06T08:19:08Z (queued 08:03:46Z). Verified on origin/main e1545cf by CONTENT, ⛔ not by the merge event.

    domain:devx @ objectui execution seat, PM session session_01MM7kaS4dPpYHV5BsMyu4tQ, R45, 2026-09-06T08:21Z.

    reading expected got
    KNOWN_ESCAPES entries in vitest.setup.network-escape-guard.ts 16 16
    PINNED_LEDGER entries in scripts/__tests__/network-escape-ledger.test.ts 16 16
    ObjectCalendar.navWidthDefault row in KNOWN_ESCAPES 0 0
    installExplainDouble in the calendar test ≥ 1 2
    control — HomePage.approvalsTarget row still present 1 1 ✅ probe fires

    Card stays open and pm:dispatched (Part of). Remaining 16 rows: app-shell 8 (FlowNodeInspector.specKeys held by draft PR #7685), plugin-detail 4, plugin-kanban 3, plugin-gantt 1. Batch 2 (kanban 3 + gantt 1 — cheapest, same router) is dispatched at the next free dev slot (three devs are in flight at 08:21Z); plugin-detail 4 after that, once no #5174 batch is in flight in that package.


    Generated by Claude Code

  9. baozhoutao commented on Sep 6, 2026

    @baozhoutao
    Contributor

    Claim: PM loop round R45 — batch 2 of this burn-down (the card stays open; PR carries Part of #7307)
    Session: session_01MM7kaS4dPpYHV5BsMyu4tQ
    Branch: claude/issue-7307-network-escapes-batch2
    Worktree: objectui-issue-7307-b2
    Domain: domain:devx
    File surface: packages/plugin-kanban/src/ObjectKanban.{navWidthDefault,overlayTitleI18n,overlayTitleNoProviderFallback}.test.tsx and packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx (the batch-1 recording-double shape, verbatim), plus the matching lines of KNOWN_ESCAPES in vitest.setup.network-escape-guard.ts and PINNED_LEDGER in scripts/__tests__/network-escape-ledger.test.ts (move together); ⛔ plugin-detail's 4 rows and app-shell's 8 are later batches (stop on breach; explain in the report)
    Container & model: S–M, mechanical in verdict (the guard reds a file taken off the ledger that still escapes), mode:subagent, model: opus — no path-derived tier mandate; default judgment tier taken because the router must be re-derived per file (batch 1 measured two hooks behind one route)
    Clause-②: no
    Serial constraints cleared: batch 1 (PR #7999) MERGED 08:19:08Z and probed (ledgers 16/16) — no concurrent batch on the two ledger literals; premise on origin/main e1545cf at 08:22Z: the four kanban / gantt rows present in KNOWN_ESCAPES; PR #7985 (#7302, the gantt / kanban READMEs) MERGED 07:20Z — no README overlap; #5174 b18 is in flight on ONE packages/*/README.md chosen by its census — if it picks gantt or kanban it says so in its PR body and this batch's dev must merge main before opening its PR (different files, same packages, one queue rebuild); draft PR #7685's 67 paths over git: 0 hits in plugin-kanban/src / plugin-gantt/src; this seat's other in-flight devs (#7413 in scripts/check-shell-escape-residue.mjs; #7362 delivered) are disjoint. The card is already assigned to this account and pm:dispatched — no label or assignee write.


    Generated by Claude Code

  10. 26 remaining items

  11. claude commented on Sep 7, 2026

    @claude
    Contributor

    Unblocked: pm:blocked → pm:queue, Blocked-by: #7122 line removed — domain:devx @ objectui seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, 2026-09-07T08:07Z. The block was on draft PR #7685 holding FlowNodeInspector.specKeys.test.tsx; that PR merged at 2026-09-07T07:31:44Z (639114c4d), so the file is released even though #7122 itself is still open. Read on origin/main fc32921aa: the FlowNodeInspector.specKeys row is still present in both vitest.setup.network-escape-guard.ts and scripts/__tests__/network-escape-ledger.test.ts (1 / 1), so batch 6 — remove the last row and retire the KNOWN_ESCAPES machinery with its pin, per the batch-5 LANDED note — is dispatchable from this queue when a dev slot frees (3/3 in flight at 2026-09-07T08:07Z). Fresh claim required.


    Generated by Claude Code

  12. claude commented on Sep 7, 2026

    @claude
    Contributor

    Claim: PM loop round R46 — batch 6 of this burn-down, the LAST row (PR carries Fixes #7307) — domain:devx @ objectui execution seat. Fresh claim after the 07:22Z release: labels pm:queue → pm:dispatched and assignee set in one write, read back at 2026-09-07T08:13Z.
    Session: session_01FhBNJcLRZLe8M87VcUgpKr
    Branch: claude/issue-7307-network-escapes-batch6
    Worktree: /home/user/objectui-issue-7307-b6
    Domain: domain:devx
    File surface: packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx (serve its /api/v1/meta/object probe from a double), vitest.setup.network-escape-guard.ts (delete the row; retire the KNOWN_ESCAPES burn-down list and its attribution branch — the guard's afterEach assertion for NEW escapes stays), scripts/__tests__/network-escape-ledger.test.ts (retire the pin together with the list, as its own non-vacuity floor instructs). ⛔ Not FlowNodeInspector.tsx or any product file.
    Dispatch: one os-dev subagent of this session (mode:subagent, model: opus); dispatch note follows.


    Generated by Claude Code

  13. claude commented on Sep 7, 2026

    @claude
    Contributor

    Dispatched: batch 6, the last row — domain:devx @ objectui execution seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-07T08:15Z. Devs in flight 3/3 (#8216, #5174 batch 36, this).

    Dev. One os-dev subagent of this session on branch claude/issue-7307-network-escapes-batch6, worktree /home/user/objectui-issue-7307-b6, base origin/main at fc32921aa or newer. Race re-read at 08:13Z: the newest Claim: on this card is 5567505884, naming that branch.

    Brief, in one paragraph. Serve FlowNodeInspector.specKeys.test.tsx's /api/v1/meta/object probe from the double batch 5 already wrote one file over (installMetaObjectDouble, FlowNodeInspector.inactiveRetained.test.tsx:69, beforeEach at :86), with the teardown order the guard's Fix: text prescribes (#7439); the file was touched by PR #7685 at 07:31Z, so it is read as it is on fc32921aa. Then, because the ledger reaches zero, retire the KNOWN_ESCAPES burn-down machinery together with its pin, as the pin's own non-vacuity floor instructs (network-escape-ledger.test.ts:92–97): the set, the attributed-stderr branch and the known/unknown split go; the recording wrapper and the afterEach that reds every escape stay (that is #6640's standing guard). The pin is retired by a deliberate choice between deleting it and replacing it with a pin that the machinery is gone; all non-governed mentions of the list updated; a governed mention (if any) reported, not edited. Measurements before (the probe escapes, attributed) and after (zero escapes, guard silent), a scratch escape proving the standing guard still reds with the Fix: text, an ablation on the retirement pin, gates by exit code. PR first line Fixes #7307 — this is the batch that closes the card.

    Premise check (read 08:13Z on fc32921aa): KNOWN_ESCAPES and PINNED_LEDGER both hold exactly one row (this file); the last changes on the three files are #7685 (test file, 12 lines) and batch 5 (65ce8c576); the batch-5 LANDED note's batch-6 shape still holds.


    Generated by Claude Code

  14. claude commented on Sep 7, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 7307,
      "batch": 6,
      "status": "done",
      "branch": "claude/issue-7307-network-escapes-batch6",
      "pr": "https://github.com/objectstack-ai/objectui/pull/8289",
      "base_sha": "fc32921aa204e2ce75acb436801b5ccbb3efbe82",
      "final_head": "e09bb3e507db3a9b4952e54310bafa07231ee75b",
      "premise_still_valid": true,
      "before": {
        "escape_line": "[network-escape - known - objectui#6640] packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx -> http://localhost:3000/api/v1/meta/object",
        "url": "http://localhost:3000/api/v1/meta/object",
        "occurrences": 3,
        "target_file_exit": 0,
        "econnrefused_stacks": 3,
        "base_pin_run": "pnpm exec vitest run scripts/__tests__/network-escape-ledger.test.ts -> exit 0, 3 passed, PINNED_LEDGER.length === 1",
        "call_path": "FlowNodeInspector -> FlowReferenceField -> useMetadataListOptions (FlowReferenceField.tsx:638) -> MetadataClient.list(type) -> GET /api/v1/meta/object over createAuthenticatedFetch's bare global fetch; happy-dom document URL defaults to http://localhost:3000"
      },
      "after": {
        "escapes": 0,
        "guard_afterEach_silent": true,
        "target_file": "exit 0, 5 passed, 0 escape lines, 0 ECONNREFUSED",
        "inspectors_directory": "exit 0, 67 files, 770 passed / 1 skipped, 0 escapes",
        "union_at_final_head": "vitest run inspectors/ scripts/__tests__/ -> exit 0, 183 files, 4194 passed / 1 skipped, 0 escapes, 0 ECONNREFUSED"
      },
      "retirement": {
        "guard_removed": [
          "export const KNOWN_ESCAPES set",
          "attributed-stderr branch for known escapes (with the now-dead writeStderr helper and StderrHost type it existed for)",
          "the unknown = seen.filter(!KNOWN_ESCAPES.has) split — every escape is now a defect on its first run",
          "the `Do NOT add this file to KNOWN_ESCAPES` line of the Fix: text",
          "header's burn-down section, rewritten to record that the list reached zero on this card"
        ],
        "guard_kept": [
          "the recording globalThis.fetch wrapper",
          "the afterEach that fails ANY escape in ANY file (the standing guard, objectui#6640)",
          "the Fix: remedy text (double + objectui#7439 teardown ordering)"
        ],
        "pin_decision": "b",
        "pin_path_kept": "scripts/__tests__/network-escape-ledger.test.ts",
        "reason": "Four axes. 实际业务需求: the pressure the old pin resisted (its own docstring: an author who hit the red could make it green by adding a line) did not end with the list, it went UP — every escape is now red with NO list to join, so re-creating the list is the cheapest wrong fix available to the next author; (a) leaves nothing resisting it. 项目长远合理性: contract-first — the standing guard is the contract 'no test reaches a socket', and this repo's own precedent is decisive: the guard said the list 'may only shrink' and that was not TRUE until a pin made it true; same reasoning one step later. 防 AI 写代码犯错: decisive axis — a re-added list is 宽容-as-cover, an AI turning a red green by declaring an exception instead of fixing the escape, repo-wide and silent; (b) makes re-introduction structurally loud, (a) makes it routine. 创业阶段不扩散需求: both retire IMMEDIATELY (no staged window; (b) asserts ABSENCE, it does not keep the mechanism alive); (b) is not capability expansion — zero product surface, zero published bytes, a file replaced rather than added in a directory of 116 such pins, file count flat. The mild line-count pull toward (a) is outweighed by axis 3. Path kept so an agent grepping 'network-escape-ledger' (because the guard's old text said the two move together) lands on the retirement pin. Absence checks read the guard through scripts/js-comment-mask.mjs — load-bearing, since the guard's header now explains at length that there is no KNOWN_ESCAPES and a naive text search would find the name in prose; a PROSE_ONLY control asserts the masking is actually working.",
        "pin_asserts": [
          "the guard module's namespace exports nothing (runtime, same instance the run is guarded by)",
          "no KNOWN_ESCAPES in the guard's CODE (comments masked) — catches an unexported list too",
          "no `new Set([` literal in the guard's code — the general re-introduction shape",
          "globalThis.fetch.name === 'guardedFetch' and the source still installs the wrapper",
          "the source still registers afterEach with the 'Network escape:' throw and the Fix: remedy",
          "non-vacuity floor: source length, a masking positive control (ESCAPE_ORIGIN present in code), a masking negative control (PROSE_ONLY phrase absent from code)"
        ],
        "pin_run": "exit 0, 5 passed"
      },
      "standing_guard_proof": {
        "scratch_file": "scripts/__tests__/zz-standing-guard-scratch.test.ts (never committed)",
        "fetched": "http://localhost:3000/api/v1/x",
        "result": "exit 1 — RED: 'Network escape: this test reached a REAL socket at http://localhost:3000/api/v1/x' naming file and test, with the full Fix: text and no trailing 'Do NOT add this file to KNOWN_ESCAPES' line",
        "guard_named_this_file": false,
        "scratch_removed": true,
        "tree_clean_after": "git status empty, git diff HEAD empty"
      },
      "ablation": {
        "subject": "the retirement pin reds if a KNOWN_ESCAPES export is re-added to the guard",
        "mutated_committed_tree": true,
        "mutation": "inserted `export const KNOWN_ESCAPES: ReadonlySet<string> = new Set([...])` above the ESCAPE_ORIGIN const in vitest.setup.network-escape-guard.ts",
        "on_disk_proof": "grep -c 'export const KNOWN_ESCAPES' 0 -> 1; git hash-object 94c4d099bb946c65de342ebc0482e106ed17bbaa -> 19226b8cba4ae827a1186ca6d77ce9f3b12caa4b (mutation is not the editor's exit code)",
        "predicted_direction": "RED, both halves: the namespace export test and the code-shaped list test",
        "observed": "exit 1 — 2 failed | 3 passed, exactly those two",
        "restore": "git checkout HEAD -- <abs path>, trap ... EXIT INT TERM, absolute paths",
        "restore_proof": "git hash-object back to 94c4d099bb946c65de342ebc0482e106ed17bbaa = the HEAD blob; git diff HEAD empty; git status clean"
      },
      "mentions_updated": [
        "packages/plugin-detail/src/renderers/__tests__/record-details.hideEmptyRetired-7129.test.tsx — its '⛔ Not KNOWN_ESCAPES: that list only shrinks' comment now says there is no list to join at all",
        "scripts/check-doc-example-shared-reader.mjs — its KNOWN_HAND_SPELLINGS header cited the ledger as 'the same shape'; now cites it in the past tense and points at what the file pins today"
      ],
      "mentions_deliberately_not_updated": [
        ".changeset/network-escape-batch1.md, batch2, batch3, batch4, .changeset/network-escapes-batch5.md — pending release notes describing what THOSE PRs did; accurate as written, and rewriting four other PRs' unreleased changesets is the third-party touch check-changeset-overwrite.mjs exists to discourage"
      ],
      "governed_mentions_found": [],
      "agents_md_names_the_list": false,
      "files_changed": [
        "packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.specKeys.test.tsx",
        "vitest.setup.network-escape-guard.ts",
        "scripts/__tests__/network-escape-ledger.test.ts",
        "scripts/check-doc-example-shared-reader.mjs",
        "packages/plugin-detail/src/renderers/__tests__/record-details.hideEmptyRetired-7129.test.tsx",
        ".changeset/network-escapes-batch6.md"
      ],
      "gates": {
        "vitest run FlowNodeInspector.specKeys.test.tsx": "0 (5 passed)",
        "vitest run packages/app-shell/src/views/metadata-admin/inspectors/": "0 (67 files, 770 passed / 1 skipped)",
        "vitest run inspectors/ + scripts/__tests__/ at final HEAD": "0 (183 files, 4194 passed / 1 skipped)",
        "vitest run record-details.hideEmptyRetired-7129.test.tsx": "0 (3 passed)",
        "pnpm lint:root": "0 (0 errors, 32 warnings, all pre-existing)",
        "pnpm --filter @object-ui/app-shell --filter @object-ui/plugin-detail run lint": "0 (0 errors, 2914 warnings, all pre-existing)",
        "pnpm --filter @object-ui/app-shell --filter @object-ui/plugin-detail run type-check": "0 (tsc --noEmit AND tsc -p tsconfig.test.json, so the test files are covered)",
        "pnpm check:control-bytes": "0 (6594 tracked text files scanned)",
        "pnpm check:entry-guard": "0",
        "node scripts/check-changeset-presence.mjs": "0 (1 changeset, empty frontmatter)",
        "node scripts/check-changeset-overwrite.mjs": "0 (no pre-existing changeset modified or deleted)",
        "node scripts/check-changeset-no-major.mjs": "0",
        "node scripts/check-governed-queue-guard.mjs --test (all 6 changed paths)": "0 — NOT GOVERNED, 6 paths against 5 governed surfaces, none matched",
        "prerequisite": "pnpm --filter '@object-ui/app-shell^...' --filter '@object-ui/plugin-detail^...' build — exit 0; without it type-check fails with 16 phantom TS2307s from absent dist/*.d.ts that read as 'your change broke the imports'"
      },
      "changeset": ".changeset/network-escapes-batch6.md — empty frontmatter (test-only; no product source touched, nothing to release). The dispatch expected 'likely none owed'; check-changeset-presence.mjs disagreed (2 released packages' source files changed: @object-ui/app-shell, @object-ui/plugin-detail) and was obeyed.",
      "mcp_calls": 0,
      "deviations": [
        "Surface: two files beyond the three declared on the claim were edited — packages/plugin-detail/.../record-details.hideEmptyRetired-7129.test.tsx and scripts/check-doc-example-shared-reader.mjs. Both are comment-only, and both are the dispatch's own instruction 'update every non-governed mention' from the git grep sweep. No product code touched anywhere.",
        "One changeset added, where the dispatch expected none. The script demanded it; obeyed with an empty frontmatter, the shape batches 1-5 used.",
        "Dependency closure had to be built before type-check would answer at all (see gates.prerequisite). Reported rather than silently absorbed, because the first type-check run's 16 TS2307s are indistinguishable from a real breakage."
      ],
      "out_of_scope_findings": [],
      "open_questions": []
    }

    Generated by Claude Code

  15. claude commented on Sep 7, 2026

    @claude
    Contributor

    LANDED — the burn-down is complete. PR #8289 (batch 6, the last row) merged at 2026-09-07T09:49:48Z as 4b9fe9819; content probe on re-fetched origin/main passed: the guard keeps its recording fetch wrapper and the afterEach that reds ANY escape (the #6640 standing guard), and carries no KNOWN_ESCAPES list any more; scripts/__tests__/network-escape-ledger.test.ts now pins that the list is GONE (and reds if one is re-added, proven by ablation); the last probe is served from a recording double.

    Six batches, 21 files, five PRs before this one (#7999 #8013 #8019 #8032 #8053) and this one — every escape now goes red on its first run with the Fix: remedy, and there is no list to join instead. Closed via Fixes; pm:dispatched stripped in the same stroke. — domain:devx @ objectui seat, PM session session_01FhBNJcLRZLe8M87VcUgpKr, 09:51Z.


    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

Labels

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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions