Skip to content

[finding] A dev report's "declared narrowing" sentence asserts a completeness turbo ls --affected cannot have — 73 cross-package test readers are invisible to it, and 5 of the last 30 merged objectui PRs cited the affected set with an off-graph reader outside it #16631

Description

@os-zhuang

Routed here by the triage seat (session session_01SwJQDFKe8tVit3BXQ9EfR5) from objectstack-ai/objectui#8195, whose census is delivered and whose remaining deliverable lands on this repo's skills surface rather than in objectui.

Filed ungraded and unstated on purpose. Per SKILL.md 「skills 车道 finding 由该席自分诊,全仓轮跳过」 the skills seat self-triages this; the whole-repo round routes only. domain:skills is routing, ⛔ not a grade.

Not claimed, not dispatched, no code written. The measurement below is not this seat's — it was produced by the domain:devx @ objectui seat (report 5567340399 on objectui#8195) and released to triage at 5567428260. Attributed rather than inherited silently.

The defect, in one sentence

Dev reports routinely narrow a gate claim with a sentence of the form "this diff cannot move gate X, because package P is not in the affected set" — and the affected set is derived from the package dependency graph, which structurally cannot see a test that reads another package's source off disk. So the sentence asserts a completeness the instrument does not have, and when it is wrong it is wrong silently and green.

Measured (objectui, base b6f821a8)

reading value
candidate test files that read source off disk 295
class (a) — cross-package readers the graph cannot see 73
… of those, point holes (a test reading one named foreign file) 44
… of those, workspace- or repo-wide sweeps (edge to every package at once) 17
… of those, already covered because the reader happens to depend on what it reads 12
root scripts/__tests__ + eslint-rules outside the instrument entirelyturbo ls lists 46 workspace packages and the root package is absent

turbo ls --affected measured blind at two concrete points, with the instrument's own positive control satisfied in the same read (the touched package and its graph dependents are listed, so it could have named the missing package had an edge existed):

  • touching packages/plugin-view/src/ObjectView.tsx → affected set of 7, missing @object-ui/types, which pins that exact path in object-kanban-group-by-limit-7322.test.ts.
  • touching packages/app-shell/src/console/ai/ConversationsSidebar.tsx → affected set of 4, missing @object-ui/i18n, which pins that exact path at console-namespace-3546.test.tsx:277.

And the claim is actually being made: of the 30 most recently updated merged PRs, 5 cite the affected set (#8211, #8212, #8217, #8237, #8245) — and all 5 had at least one off-graph reader outside their affected set. Sharpest instance: PR #8217 touched packages/app-shell/src/views/metadata-admin/i18n.ts, which packages/i18n/src/__tests__/perm-home-namespace-3546.test.tsx:464 reads by exact path, while an app-shell touch's affected set is 4 packages and @object-ui/i18n is not among them.

⚠️ Stated as the reporting seat stated it: sweep-class matches are in-scope edges, not proof the assertion would have moved. Only the exact-path matches are pinned reads.

Why the fix is a sentence, not a gate

CI runs the full farm (ci.yml: pnpm test --shard=N/4; no --affected anywhere in .github/workflows, scripts/ or AGENTS.md). ⇒ No user-visible defect exists today, and nothing here ships a capability. The only consumer of turbo ls --affected in that repo is the narrowing sentence in dev reports. So the thing that is actually wrong is a sentence — and the recommended direction is the only one of four aimed at it.

The four directions, with the reporting seat's four-axis verdict:

  • D1 / D3 — a declaration file plus a gate that maps off-disk reads to packages. ⛔ Rejected. Both introduce a hand-maintained declaration mirroring a fact the graph cannot derive; when the mirror drifts the affected set is silently wrong again — the same failure with one more file to keep in sync, i.e. the declared-but-unenforced shape this org keeps filing cards about. ⭐ And there is reflexive evidence: this very census's mechanical query needed four iterations and still under-measured until three false-negative families were found by hand (segmented path joins, workspace-group sweeps, repo-root walks, git grep shell-outs). A declaration written by the same process would be incomplete the same way — and green.
  • D2 — where a ledger can assert on an imported value instead of source text, do that. ✅ Contract-first and correct where it applies (an import is an edge the graph can see), but it does not generalise: most of these ledgers assert on source text, on the absence of a symbol, on JSDoc prose, or on a repo-wide git grep — facts with no importable value. Keep as opportunistic, applied when a ledger is next edited; ⛔ no migration sweep.
  • D4 — the standing rule (recommended). A declared narrowing must name the instrument's blind spot: that the affected set is graph-derived and blind to file reads, and that scripts/__tests__ is outside it entirely. The sentence stops asserting completeness it cannot have, so it can no longer be silently wrong. Cost: one sentence in the report/dispatch template, zero repo files, zero CI cost.

The open question this card carries — and why it is the skills seat's plus a maintainer's

Where does the D4 sentence live?

  • A — the dispatch/report template only (.claude/skills/pm-dispatch/**). Zero AGENTS.md diff, effective immediately, but invisible to a human reading AGENTS.md.
  • B — an AGENTS.md paragraph as well as the template (the reporting seat's recommendation): the census is durable evidence and AGENTS.md is where this repo already records instrument caveats; it is binding on every seat, not just PM ones.

⚠️ Both land on the governed surface (.claude/**, and AGENTS.md for B) ⇒ draft PR + human merge by a GOVERNED_APPROVERS account. That is a defined path, not a blocker — and the maintainer's merge is the decision between A and B, so ⛔ this does not need a separate decision card.

⚠️ Cross-repo note: the census was taken in objectui, and its AGENTS.md is a different file from this repo's. Whoever takes this should decide explicitly whether the rule is stated once here (agent instruction files being cross-repo judged) or in both — ⛔ do not assume either.

A second question, recorded and answered

12 of the 73 cross-package readers are safe only because the reader currently depends on the package it reads. Should they be recorded so a future dependency removal does not silently turn them into holes? The reporting seat's answer, which this seat concurs with: not at this stage — no defect today, and recording them is new bookkeeping against the startup-focus axis. The list is preserved in report 5567340399 if it is ever wanted.

Dedupe

One targeted semantic search over the affected-set / narrowing-claim / graph-blind-spot vocabulary returned 22 substantive results, including the closed #15323 (domain:skills, "A dev's local suite list is chosen as 'packages I changed', but CI asks turbo ls --affected") ⇒ the channel reaches this exact neighbourhood. ⚠️ #15323 is not this card: it is the same instrument in the opposite direction (a dev under-running locally against CI's affected set), and it is closed. Nothing open names the affected set's own blind spot. ⛔ Not upgraded to exhaustive.

Refs: objectstack-ai/objectui#8195 (the census and its full report) · #15323 (adjacent, closed).

Activity

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions