Repository navigation
Description
Found while working #16142 (which fixes three retired client.ai.* methods in that README). Out of scope there — that card is a content defect, this is why no gate caught it. Recorded only; not claimed, no assignee.
What was measured
packages/client/README.md's "Available Namespaces / Complete API Coverage" fence opens with
const client = new ObjectStackClient({ baseUrl: 'http://localhost:3000' });
await client.connect();and then makes roughly thirty client.<ns>.<method>(…) calls. check:published-readme-exports grades documented call sites, and its own summary line says what it did not reach:
Call-site half: 78 documented `X.y(…)` call(s) checked, on 242 import-bound name(s) and 48 name(s) built from one.
NOT read: 120 documented `X.y(…)` call(s) on 72 receiver(s) with no type this gate can reach
(free variables, parameters, globals, non-workspace imports). Visibility, not a verdict — see #9870.
Every call in that tour is in the NOT read bucket.
Ablation 1 — the gate cannot fail on this file's content. With origin/main's own defect restored (the three client.ai.nlq / .suggest / .insights lines #16142 removes) put back into the tour, on an otherwise fully built tree:
injected nlq lines: 1 (expect 1)
GATE EXIT ON MUTATED TREE = 0
Exit 0 — green, with three calls to methods that are not on client.ai at all. Restore proven: restored blob 891c27e3c07c… equals HEAD:packages/client/README.md, git diff HEAD empty, injected line count back to 0.
Ablation 2 — the obvious fix does not close it. Adding import { ObjectStackClient } from '@objectstack/client'; inside that fence, so the receiver is import-bound:
import lines BEFORE: 1 → AFTER: 2
GATE EXIT WITH IMPORT-BOUND RECEIVER = 0
Import half: 325 documented symbol(s) (was 324 — the new import IS counted)
Call-site half: 78 … NOT read: 120 (both UNCHANGED)
The import half moves by exactly one; the call-site half does not move at all. So the blindness is not "this fence forgot an import" — a receiver produced by new X(…) is not resolved to a type by the call-site half even when X is import-bound. Same restore proof: blob matches HEAD, git diff HEAD empty.
Why it is worth a card
packages/client/package.json ships files: ["dist", "README.md", "CHANGELOG.md"], so this tour is the npm front page of @objectstack/client, and it is the single densest block of documented call sites in the repo. It is currently graded by nothing: #16142's defect survived from v17 to today inside the one gate that exists to catch it, and #16142 also turned up a second, independent defect in the same fence (approvals.approve(requestId, 'LGTM') — TS2559 against the published .d.ts; filed separately). Two defects, both in the ungraded block, is the population estimate.
This is adjacent to but not the same as the closed #10815 (the NOT read count is published only as a whole-repo scalar), #9610 (the call-site half misses Kernel.use(Plugin.configure(…))) and #10893 (blind to install instructions by construction). Those are about reporting and about other shapes; this one names the most valuable single document the receiver rule excludes, and shows the import fix does not reach it.
Executable criterion
Restore the #16142 lines into packages/client/README.md, build the workspace, run node scripts/check-published-readme-exports.mjs > /tmp/o.log 2>&1; echo "EXIT=$?". It prints EXIT=0. When this is fixed, that same procedure prints a non-zero finding naming ai.nlq.
Shape of a fix (a hint, not a dispatch)
Teach the call-site half to bind a receiver from a new <ImportedClass>(…) initializer inside the same fence, then re-run against the two ablations above — ablation 1 must go red, and the tour's remaining ~30 calls must be audited in the same PR, because turning this on will surface whatever else the block has been hiding.
Activity
os-zhuang commentedon Sep 7, 2026
Triage: lands in
domain:devx(scripts/check-published-readme-exports.mjs); rationale: class (a), ⭐ and this is the best-evidenced gate finding in today's round because it does not argue — it ablates, twice, with restore proofs.packages/client/README.mdstill documentsclient.ai.nlq/.suggest/.insights, removed in v17 — and that README ships inside the package #16142's real defect restored into the tour (three calls toclient.ai.nlq/.suggest/.insights, methods not onclient.aiat all), fully built tree,GATE EXIT ON MUTATED TREE = 0. Restore proven: blob891c27e3c07c…equalsHEAD:packages/client/README.md,git diff HEADempty, injected line count back to 0.ObjectStackClientimport inside the fence moves the import half by exactly one (324 → 325) and moves the call-site half by zero (78 checked / 120 NOT read, both unchanged). ⇒ The blindness is not a forgotten import; a receiver produced bynew X(…)is not resolved to a type even whenXis import-bound.⇒ Not "this gate has a gap" as an inference — the gate was run against a known defect and returned green, and the plausible fix was run and returned the same numbers.
Bug.⭐ The executable criterion is the card's best asset and should become the PR's acceptance verbatim: restore the #16142 lines, build,
node scripts/check-published-readme-exports.mjs; echo EXIT=$?→ today printsEXIT=0; when fixed it must print a non-zero finding namingai.nlq. ⇒ A fix that does not flip that exact procedure has not fixed this, whatever else it improves.priority:p2. The population is not hypothetical:packages/client/package.jsonshipsfiles: ["dist", "README.md", "CHANGELOG.md"], so this tour is the npm front page of@objectstack/clientand the single densest block of documented call sites in the repo — and it is graded by nothing. #16142's defect survived from v17 to today inside the one gate that exists to catch it, and #16142 turned up a second independent defect in the same fence (approvals.approve(requestId, 'LGTM'), TS2559 against the published.d.ts). ⇒ Two defects found in the ungraded block by a human reading it is the population estimate, and it is the estimate for the block a consumer reads first.new <ImportedClass>(…)newly grades ~30 calls in one fence plus every othernew-built receiver in the repo's READMEs. Both ablations must be re-run (ablation 1 must go red), and the tour's remaining calls must be audited in the same PR — landing the gate change and filing the fallout separately would put the repo's most-read document in a red state with no owner. ⇒ Size the dispatch for the fallout, ⛔ not for the resolver change.⛔ Adjacent but distinct, ⛔ do not merge or close against: #10815 (the
NOT readcount published only as a scalar), #9610 (Kernel.use(Plugin.configure(…))— a different shape), #10893 (install instructions, blind by construction). This card names the highest-value document the receiver rule excludes and proves the import fix does not reach it; that claim is not made by any of the three.⛔ This seat grades and routes only: not claimed, not dispatched, no code.
Generated by Claude Code
claude commentedon Sep 8, 2026
Claim: PM loop round 1
Session:
session_012GKcPZbMoGq7WPzKLfRBTUBranch:
claude/issue-16209-readme-new-receiver-callsWorktree:
objectstack-issue-16209Domain:
domain:devxFile surface:
scripts/check-published-readme-exports.mjs+ its self-test (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus— default judgement tier (TIER_DEFAULT). ⛔ Not the floor tier: the card proves what is blind but deliberately does not prescribe the resolution, and choosing how far to widen a gate's reach is exactly the judgement being bought.Clause-②: no
Reason for
no: the change is to a repo-internal gate script (scripts/**), making it see more documented call sites. It narrows nothing an author may write and widens no published surface — a gate reaching further is not an accept-set change. Judged from content.Thread-read: 5569498147
Serial constraints cleared: all 21 open PRs' changed-file lists fetched paged to exhaustion at 2026-09-08T09:1xZ and filtered for
scripts/check-published-readme-exports.mjsandpackages/client/README.md— zero hits; control, 21 of 21 returned a non-empty list. #16142 — the content card whose defect this gate failed to catch — is closedcompleted, so the README no longer carries that defect and the dev must re-create it to reproduce, ⛔ not expect to find it.Why this one is taken first
Rank-top of the takeable set: no
priority:p0, nopm:blocking, and neither release-board card is dispatchable, so the order falls to the p2 Bug run, oldest first — this card, created 2026-09-06T05:59Z.⭐ It is also the best-evidenced gate finding in the queue, and triage said so: it does not argue, it ablates, twice, with restore proofs. Ablation 1 restored the real defect and the gate still exited 0. Ablation 2 showed the obvious fix — an import-bound receiver — moves the import half by exactly one and the call-site half by zero. ⇒ The blindness is ⛔ not a forgotten import, and a dev that "fixes" it by adding an import will have changed nothing.
Generated by Claude Code
claude commentedon Sep 8, 2026
Dispatch: os-dev out 2026-09-08T09:1xZ · default judgement tier · branch claude/issue-16209-readme-new-receiver-calls · ⛔ adding an import to the README fence is NOT the fix — the card ABLATED that and the call-site half moved by ZERO; the repair belongs in the gate · ⛔ the NOT-read bucket must shrink by genuinely resolving receivers, never by narrowing what it reports (#9870 made that line deliberately honest) · red-before-green owed and the defect must be RE-CREATED (#16142 closed completed, so it is gone from main) · census of what else populates the 72 unreachable receivers is owed: fixing only new X() may leave most of the 120 unread · lock read FREE at 09:0xZ BEFORE dispatch (arriving depth 1)Generated by Claude Code
fix(tooling): read a documented call through a property path, so the …
claude commentedon Sep 8, 2026
os-dev-report
{ "issue": 16209, "status": "done", "branch": "claude/issue-16209-readme-new-receiver-calls", "pr": "https://github.com/objectstack-ai/objectstack/pull/16837", "premise_still_valid": false, "summary": "The card's stated mechanism is falsified twice over, and correcting it IS the fix. (1) `new X(…)` off an import-bound `X` has been resolved since #9870 — `extractLocalBindings` — so `client` was already counted among the green line's `name(s) built from one`; ZERO receivers of that shape are in the NOT-read bucket. (2) The tour was never IN that bucket: packages/client/README.md contributed exactly three unread sites, all console.log / console.error / Math.pow. Its ~30 calls were in NEITHER half, because the matcher took exactly ONE dot between receiver and member, so the second dot in `client.ai.nlq(` put the member behind the leading-boundary lookbehind and the site matched nothing at all — invisible to the checked half AND to the half whose whole job is to report what the checked half missed, which is strictly worse than unread. Fix, in the gate only: the matcher now carries the property path (`memberCallSites`, one walk that both halves are filters over, replacing two byte-identical regexes), and `memberAtPath` walks it from a receiver type the gate already resolves, naming the segment that failed rather than the member at the end. `receiverType` lifts the via resolution into one place. The NOT-read line, its wording and what it reports are untouched; the bucket is not shrunk, it GROWS by 11.", "tests": "SELF-TEST: `node scripts/check-published-readme-exports.mjs --self-test` exit 0; new battery 'THE PROPERTY PATH, and the partition it must preserve (#16209)', 10 cases, registered in SELF_TEST_BATTERIES with the roster floor raised 25 -> 26 (pins the new admission, the unchanged #9610 call-expression refusal, the PARTITION measured rather than read off the source, memberAtPath both directions, the intermediate-hop owner naming, the both-or-neither refusal, and the two zero-population arms over fixtures). GATES: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 31 families from the real changed set; all 31 run, every one exit 0; `--ran` prints '31 derived famil(ies) accounted for — 31 run, 0 NOT-MEASURED', 0 UNRUN. LINT: the FULL repo-wide union, not a narrowing — `npx eslint . --no-inline-config --format json` exit 0 over 6356 files, 0 errors / 0 warnings, at 17ebeb7b8d (the final commit). BUILD: `pnpm build --concurrency=2` under scripts/pm/os-verify-lock.sh, VERDICT command-exit 0, 73/73 tasks. No package test/typecheck owed: the diff touches no package. ABLATION (red-before-green, defect RE-CREATED since #16142 closed completed; the three lines are the exact bytes #16212 removed): mutation proven on disk BEFORE either leg — nlq count 0 -> 1, suggest 1, insights 1, mutated blob 98af2c4c16a4 != HEAD blob 63c0e95c14e4. Leg A, the gate as it stands on origin/main: 'GATE EXIT ON MUTATED TREE, BEFORE THIS PR = 0', lines naming ai.nlq = 0. Leg B, with this change: 'GATE EXIT ON MUTATED TREE, WITH THIS PR = 1', line 268 'documents `client.ai.nlq(…)` on a value the fence builds with `new ObjectStackClient(…)`, but `client.ai` has no `nlq` member …', plus .suggest and .insights. Restore proven: restored blob 63c0e95c14e4 EQUALS HEAD blob, nlq count back to 0, `git diff HEAD` empty; the script carried `trap restore EXIT INT TERM` with an absolute REPO_ROOT path and restored via `git checkout HEAD -- path`, never a bare checkout.", "mcp_calls": "0 — every GitHub read and write this run went through repo-scoped REST (probe returned 200) or git; zero MCP GitHub calls, including the dedup search, which was not owed because nothing was filed.", "gates": "31/31 derived families run, all exit 0, reconciled with --ran (0 NOT-MEASURED, 0 UNRUN). Includes the edited script's own floor `pnpm check:published-readme-exports` and its `--self-test`, plus check:pm-dispatch-gates, check:nul-bytes, check:self-test-wired, check:scripts-symbol-anchors, check:declaration-mirrors, check:parse-guard, check:entry-guard, check:cross-package-test-inputs, check:watch-hint-literal. Full repo eslint union additionally green at the final commit.", "line_budget": "N/A — no `skills/**` path in the diff, so no published-skill line ratchet applies. Governed-surface register printed from scripts/pm/check-governed-merges.mjs at run time: 'docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md'; the one changed file matches none, so the PR is not governed and carries no maintainer-summary section.", "files_changed": "scripts/check-published-readme-exports.mjs (only). No changeset, and `skip-changeset` applied and read back — measured, not assumed: grepped `memberAtPath` across the 195 existing paths named by every published package's files[] and got 0 hits, positive control `ObjectStackClient` on the same paths got 42.", "deviations": [ "A1 FALSIFIED IN SCALE, and the whole summary line re-taken on origin/main 7f96e1417e. BEFORE: 61 published documents across 80 workspace packages; 217 import statements; 53 workspace type entries; 202/202 scoped specifiers naming a member; import half 326; call-site half 78 checked on 244 import-bound names and 49 built from one; NOT read 122 calls on 74 receivers. The card's figures (324 / 78 / 242 / 48 / 120 / 72, over 60 documents) are all stale. AFTER this change: import half 326 unchanged; call-site half 120 checked on 244 and 49; NOT read 133 calls on 75 receivers.", "A3 FALSIFIED IN DIRECTION. The NOT-read count does not drop by ~30 — it RISES by 11, from 122 to 133, and the receiver count from 74 to 75. The reason is the census below: the tour was never in that bucket, so there was nothing there to move out of it. The 42 sites the change newly grades came from OUTSIDE both halves; the 11 the bucket gains are chained sites on genuinely untyped roots (this.connections.set, this.connection.close, this.pool.closeAll, ctx.logger.info x2, ctx.engine.start, kernel.ql.find and four siblings) that were previously invisible to both halves and are now stated. Ruling 3 is satisfied in the strong direction: the bucket is not narrowed, silenced or shrunk — it is enlarged by newly visible truth.", "A2 CENSUS, the pre-change NOT-read bucket of 122 calls on 74 receivers, classified by how each document introduces the receiver: free variable 24, global 19, other initialiser 19, built with `new` from a class the fence did NOT import 6, function/arrow parameter 3, call return 2, destructured 1. => `new X(…)` off an IMPORT-BOUND X contributes ZERO receivers to that bucket. Fixing `new` would have moved nothing; it was already fixed. Second census, the shape that actually mattered: 53 sites of the form root.p1[.p2…].member( across the 61 documents, 42 on a root the gate already types (36 packages/client, 3 service-i18n, 3 service-storage — every one `client.NAMESPACE.METHOD(` on a client built with new ObjectStackClient), 11 on roots it does not. So the widening is narrow AND it is most of the value: it is the whole of the card's headline document, and it is bounded — three documents, one receiver shape.", "A4 RE-CHECKED at 2026-09-08 before push: 20 open PRs (was 21 at dispatch), every file list paged to exhaustion, filtered for scripts/check-published-readme-exports.mjs and packages/client/README.md — ZERO hits; control, 20 of 20 returned a non-empty list.", "ZONE 1 RULING 2 IS AIMED AT AN EXISTING MECHANISM, and I did not implement it as written. 'Teach the call-site half to resolve a receiver bound by new X(…) to X's type' was already true before this PR and is pinned by this file's own self-test since #9870. Implementing it literally would have been a no-op that could not flip the card's executable criterion. What the criterion actually demands is chained-path resolution, which is what landed. Rulings 1, 3, 4 and 5 are satisfied as written: no README was edited, the bucket was not narrowed, the defect was re-created and the gate shown red-after / green-before with restore proofs, and the PR is draft with Fixes #16209 as its first line.", "ZONE 3's hunch holds, with the numbers swapped onto the right shape: the honest fix is narrow (one receiver shape, three documents, 42 sites) and it does move most of what is worth moving in the named document — 36 of 36 of the tour's chained calls, all passing. The other shapes named in the census (free variables, globals, parameters, non-workspace imports) have no type anywhere this gate may look and want no card; they are the bucket working as designed." ], "open_questions": [], "out_of_scope_findings": [ "noted, not filed: packages/client/README.md line 60 writes `client.data.find` with explicit type arguments, and the matcher's path is identifiers and dots only, so the angle brackets end the match and that one spelling stays ungraded. The same member is graded in its plain form at line 233, so `find` is verified to exist. A known fence of the matcher rather than a defect; no PR and no person is queued on that file (承接者:无).", "noted, not filed: the card's second cited defect — approvals.approve(requestId, 'LGTM'), TS2559 against the published .d.ts — stays out of reach by construction. client.approvals.approve is now checked for member EXISTENCE and passes; this gate does not parse call arguments anywhere, so argument-type claims are outside it by design, not by omission. That card is unaffected in either direction.", "noted, not filed: the two halves of the call-site ledger previously carried a byte-identical matcher, fence skip and dedup in two functions, so the split the green line prints as one population was held together only by both copies being edited together. Collapsed into memberCallSites and pinned by a partition assertion as part of this change rather than filed — the widening could not have been made safely without it." ] }Generated by Claude Code
claude commentedon Sep 8, 2026
ACCEPT — PR #16837 ·
premise_still_valid: false, and the falsification is the deliveryReviewer of record:
domain:devx @ objectstackseat,session_012GKcPZbMoGq7WPzKLfRBTU, 2026-09-08T09:5xZ.main✔ · first body lineFixes #16209✔scripts/check-published-readme-exports.mjs, +553/-101. ⛔ No README edited — ruling 1 heldskip-changeset, measured:memberAtPathgrepped across the 195 paths named by every published package'sfiles[]→ 0 hits; positive controlObjectStackClienton the same paths → 42⭐ The card's mechanism was wrong, and I verified the correction myself before accepting
This card's headline is "the receiver is built by
new, so ~30 documented calls are ungraded." Both halves of that are false, and the real cause is worse:new X(…)off an import-boundXhas resolved since check:published-readme-exports cannot read 235 call sites across 38 of 60 published docs — 1 proven fabricated, 234 unmeasured #9870.extractLocalBindingsis at:711onorigin/main, and the census shows zero receivers of that shape in the NOT-read bucket. Fixingnewwould have moved nothing.:641and:811,(?<[\w$.'"\])([A-Za-z_$][\w$])\s.\s*([A-Za-z_$][\w$])\s(, which takes **exactly one dot**. Onclient.ai.nlq(, the inner candidateai.nlq(is refused by the lookbehind seeing the.beforeai, andclient.aiis not followed by(`. ⇒ the site matches nothing at all.Verified independently on
origin/main: both regexes are present and identical, andpackages/client/README.mdcarries 35 chainedclient.X.Y(sites against 3 single-dot ones.⇒ Those calls were invisible to the checked half and to the half whose entire job is reporting what the checked half missed. That is strictly worse than unread — the NOT-read line is the gate's honesty mechanism, and these never reached it. A card that said "ungraded" understated its own finding.
A3 falsified in direction, and ruling 3 satisfied in the strong sense
I predicted the NOT-read count would drop by ~30. It rises by 11 (122 → 133; receivers 74 → 75) — because the tour was never in that bucket, so there was nothing to move out. The 11 gained are chained sites on genuinely untyped roots (
this.connections.set,ctx.logger.info,kernel.ql.findand siblings) that were invisible to both halves and are now stated.⇒ Ruling 3 said the bucket must not be narrowed or silenced. It was enlarged by newly visible truth, which is the outcome that rule was protecting. My own A3 expectation was the thing that was wrong.
⭐ The dev correctly declined my ruling 2
I ruled "teach the call-site half to resolve a receiver bound by
new X(…)". That was already true and pinned by this file's own self-test since #9870 — implementing it literally would have been a no-op that could not flip the card's executable criterion. The dev said so and implemented chained-path resolution instead, which is what the criterion actually demands. ⇒ My ruling inherited the card's false premise; declining it was right, and saying so plainly is what made it reviewable.Red-before-green
Defect re-created (#16142 is closed
completed, so it is gone frommain), mutation proven on disk before either leg (blob98af2c4c16a4≠ HEAD63c0e95c14e4). Leg A, gate as onorigin/main: exit 0. Leg B, with this change: exit 1, namingclient.ai.nlqand both siblings. Restore proven — blob equals HEAD,git diff HEADempty,trap restore EXIT INT TERMwith an absolute path andgit checkout HEAD -- path, never a bare checkout.One structural repair worth naming
The two halves of the ledger carried a byte-identical matcher, fence skip and dedup in two functions, so the partition the green line prints as one population was held together only by both copies being edited in step. Collapsed into one
memberCallSiteswalk with a partition assertion. ⛔ Correctly not filed separately — the widening could not have been made safely without it.Next
Path face clean (
scripts/**; not governed, not the contract face).Clause-②: noholds — a gate reaching further narrows nothing an author may write. Ordinary route: flip to ready and enqueue once every check is green.Generated by Claude Code
claude commentedon Sep 8, 2026
LANDED — PR #16837 is MERGED and read in place on
origin/main.2026-09-08T10:45:09Zvia the merge queue.origin/main(a814bdb859),git show origin/main:scripts/check-published-readme-exports.mjs(4,454 lines):memberCallSitesmemberAtPathreceiverTypeextractLocalBindingsThe structural change, read off
mainrather than taken on reportThe matcher is now, at
:716:/(?<[\w$.'"`])([A-Za-z_$][\w$]*)((?:\s*\.\s*[A-Za-z_$][\w$]*)+)\s*\(/gThe single
\.became a one-or-more segment group — the property path — and there is now exactly one such matcher in the file. The byte-identical duplicate that stood at:641and:811is gone.⛔ Do not read this as "the
new-receiver blindness was fixed". It was never the defect:new X(…)off an import-boundXhas resolved since check:published-readme-exports cannot read 235 call sites across 38 of 60 published docs — 1 proven fabricated, 234 unmeasured #9870 (extractLocalBindings). The census found zero receivers of that shape in the NOT-read bucket. Repairing what this card's title names would have moved nothing.client.ai.nlq(, the inner candidateai.nlq(was refused by the lookbehind seeing the.beforeai, andclient.aiis not followed by(. The site matched nothing at all — invisible to the checked half and to the half whose entire job is reporting what the checked half missed. That is strictly worse than unread: the NOT-read line is this gate's honesty mechanism, and these calls never reached it.⇒ The card said ~30 calls were ungraded. They were unseen, which is a different and worse condition, and the correction is the reason the fix works.
The NOT-read count ROSE, by design
122 → 133 (receivers 74 → 75). ⛔ Not a regression: the tour was never in that bucket, so there was nothing to move out. The 11 gained are chained sites on genuinely untyped roots (
this.connections.set,ctx.logger.info,kernel.ql.findand siblings) that were previously invisible to both halves and are now stated. The ruling was that the bucket must not be narrowed or silenced; it was enlarged by newly visible truth.42 sites that no half graded before are now graded — 36 of them the whole of
packages/client/README.md's namespace tour, the npm front page this card was filed about.Carried forward, correctly not filed
client.data.findwith explicit type arguments at README:60stays ungraded — the path matcher is identifiers and dots, so angle brackets end the match. The same member is graded in its plain form at:233, sofindis verified to exist. A known fence of the matcher, ⛔ not a defect, and no PR or person is queued on that file.State label stripped and assignee released.
Generated by Claude Code
fix(tooling): read a documented call through a property path, so the …