Skip to content

docs(runtime): hand-audit the README's 30 unread call sites — 1 fabricated member, 2 wrong call shapes (#10368) - #10816

Queued
claude[bot] wants to merge 2 commits into
mainfrom
claude/issue-10368-runtime-readme-audit
Queued

docs(runtime): hand-audit the README's 30 unread call sites — 1 fabricated member, 2 wrong call shapes (#10368)#10816
claude[bot] wants to merge 2 commits into
mainfrom
claude/issue-10368-runtime-readme-audit

Conversation

@claude

@claudeclaudeBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#10368

Commissioned as a measurement. The deliverable is a true defect count with its denominator; the repairs are the by-product. Both are below.


⚠️ The denominator in the ruling does not reproduce

The card states packages/runtime/README.md holds 47 unread sites on 18 receivers. Derived from the gate's own population, it holds 30 on 17.

how it was countedcallsreceivers
the gate today (readable = import-bound + extractLocalBindings derivations)3017
import-bound only, i.e. the pre-PR-#10365 gate3520
raw occurrences, no X.y dedup44
whole file instead of fenced blocks only3017
the card's figure4718

No counting convention in scripts/check-published-readme-exports.mjs yields 47/18, and the document has not been edited since #10226 — well before the card was written, and the card quotes today's NOT read: 120 … 72 line verbatim, so it was written against this same tree. 47is the number printed one line above it on the same green output — 47 name(s) built from one, i.e. derivedReceivers, a whole-repo quantity. That is the likely provenance; I cannot prove it.

The card's argument survives intact: this document is still by far the largest concentration, and still ~3× the next one (30 vs packages/metadata/README.md's 10). Only the arithmetic moved. #10815 is filed on the underlying cause — the gate emits no per-document breakdown, so a reader has to transcribe from whole-repo scalars.

How the site list was derived

Not by grepping the README. The population had to be the gate's, so the derivation reuses the gate's own exports — publishedDocs(), extractImports, splitSpecifier, resolveTypesEntry, extractLocalBindings, readFences, countUnreadCalls. Only two things are re-stated: analyzeDocument's readable map (it is built internally and not exported) and countUnreadCalls' body (it returns cardinalities, not the pairs).

The re-statement is checksummed against the gate rather than trusted: summed over all 60 published documents it must reproduce the printed pair, and it does — TOTAL unread calls=120 receivers=72, equal to the gate's own NOT read: 120 … on 72 receiver(s). After the repairs it tracks the move too: TOTAL unread calls=119 receivers=71. A drift in the derivation would move that total. (It is a whole-run checksum: it catches a systematic disagreement, not one document's worth. That limitation is #10815.)

The script is not committed — this card is a read, and scripts/check-published-readme-exports.mjs is fenced. It is reproduced in full, with its per-document output over all 60 published documents, in a comment on #10368.


The verdict table — all 30 sites

Line numbers are origin/main at 112a8c673, i.e. the population as commissioned.

#receiverdocumented memberline(s)receiver's real typemember exists?verdict
1ctxregisterService(…)148, 191, 300, 327, 353, 405, 445, 513PluginContext — annotated at the parameteryes
2ctxgetService(…)323, 467, 532, 552, 562PluginContextyes
3ctxtrigger(…)347PluginContextyes
4ctxhook(…)364PluginContextyes
5engineinsert(…)204IDataEnginectx.getService<IDataEngine>(…)yes
6enginefind(…)205IDataEngineyes
7engineupdate(…)206IDataEngineyes — wrong call shape🔧 repaired
8enginedelete(…)207IDataEngineyes — wrong call shape🔧 repaired
9zobject(…)384zodyes
10zstring(…)385, 386zodyes
11znumber(…)387zodyes
12thischeckDatabase(…)536HealthCheckPlugin, same fenceyes — declared line 550
13thischeckCache(…)537HealthCheckPluginyes — declared line 560
14thischeckDiskSpace(…)538HealthCheckPluginyes — declared line 570
15serverget(…)161, 534IHttpServeryes
16serveruse(…)470IHttpServeryes
17resjson(…)162IHttpResponse — 2nd param of RouteHandleryes
18resstatus(…)543IHttpResponseyes
19dbinsert(…)344none — free variable, never bound in the fenceunanswerable❓ ambiguous
20dbping(…)553nonectx.getService('database'), the README's own illustrative connectionunanswerable❓ ambiguous
21consolelog(…)72global Consoleyes
22auditLogwrite(…)367none — free variableunanswerable❓ ambiguous
23ConfigSchemaparse(…)395z.object({…}) — a zod schemayes
24connclose(…)439nonefor (const conn of this.connections.values()), values from an illustrative createConnectionunanswerable❓ ambiguous
25Datenow(…)471, 480global DateConstructoryes
26factorygetInstance(…)519the object literal registered at 503–511yes
27Promiseall(…)535global PromiseConstructoryes
28checksevery(…)541await Promise.all([…]) — an arrayyes
29cacheping(…)563nonectx.getService('cache'), reader-owned exactly like database aboveunanswerable — see note❓ ambiguous
30replycode(…)610none — free variable; the spelling is Fastify'snofabricated — repaired

Note on #29.cache is also a reserved core service slot (CoreServiceContracts.cache: ICacheService), and ICacheService ships get/set/delete/has/clear/stats — no ping. But the README's sibling example registers 'database' as the reader's own connection object, and this fence's dependencies = ['http-server', 'database', 'cache'] names plugins, not slots. Both readings are available and the receiver carries no type. Counted as ambiguous rather than pushed into the defect bucket, per the card's instruction.

The count and the denominator, plainly

Against the 1-in-70 prior: on the like-for-like question this is 1 in 30 (or 1 in 25 over adjudicable sites) versus 1 in 70. One instance either way; on a sample of 30 that is not a material difference, and it does not support re-opening the five-document sweep on member existence. The number that is new is the wrong-call-shape class: 2 more in the same document, invisible to the gate by construction, which no measurement so far has priced. Whether that re-opens anything is triage's call — it is a different sweep than the one #9870 proposed. The other decision-relevant fact for the remaining 73 sites: 1 in 6 of this document's unread sites is unadjudicable by anyone, so a hand read of the residual buys less coverage than its site count suggests.


The repairs, and the evidence for each

Six false claims, all in packages/runtime/README.md.

1–2. IDataEngine.update / .delete — real members, wrong call shape (lines 206–207)

- await engine.update('user', user.id, { name: 'Jane' });- await engine.delete('user', user.id);+ await engine.update('user', { id: user.id, name: 'Jane' });+ await engine.delete('user', { where: { id: user.id } });

IDataEngine (packages/spec/dist/analytics.zod-CjlbASro.d.ts:891, published through @objectstack/spec/contracts@objectstack/core@objectstack/runtime) declares:

update(objectName: string,data: any,options?: EngineUpdateOptions&WriteObservabilityOptions): Promise<any>;delete(objectName: string,options?: EngineDeleteOptions): Promise<any>;

There is no id parameter on either. The normative rule for identifying one row is resolveEngineUpdateDispatch (packages/metadata-core/src/engine-update-dispatch.ts, module header): a truthy scalardata.id wins, else options.where.id, else options.multi, else the engine rejects. The delete twin is assertEngineDeleteDispatch in the same package. As written, the README's user.id landed in the options bag — a call the engine rejects rather than one that silently does the wrong thing, but a reader following the page still cannot compile-and-run it.

3. The IDataEngine Interface Methods bullet list restated both wrong signatures (lines 215–216) — corrected in the same edit. A repaired example beside a bullet list that still contradicts it is not a repair. The two bullets that were incomplete rather than false (insert / find omit their optional trailing options?) are deliberately left alone: an omission is not a false statement, and widening the diff past the adjudicated defects is not this card.

4. reply.code(429).send({…}) (line 610) — the one fabricated member.

-if (!decision.allowed) reply.code(429).send({ retryAfterMs: decision.retryAfterMs });+if (!decision.allowed) res.status(429).json({ retryAfterMs: decision.retryAfterMs });

reply.code() is Fastify. This package's HTTP contract is IHttpResponse (packages/spec/dist/contracts/index.d.ts), which spells the step status(code: number): IHttpResponse and whose send takes string | Uint8Array | ArrayBuffer — so the original line was wrong twice: a member that does not exist, handed an argument the real send would reject. status(…).json(…) is the spelling this same README already uses at lines 543–546, and the docs/HARDENING.md#rate-limiting recipe the same section links to answers 429 through the framework's own JSON responder (return c.json({ error: 'Too many requests' }, 429)). decision.allowed and decision.retryAfterMs are both real (RateLimitDecision, packages/runtime/dist/index.d.ts:1384) and are untouched.

5. status: res.statusCode (line 484) — IHttpResponse has no statusCode. Dropped from the logged object. A response's status is observed through IHttpServer.afterResponseHttpResponseObservation.status, not read off the response inside middleware. Outside the gate's population (a property access, not a call), found in the same read.

6. PluginContext's declared block (lines 250–251) — logger: Console and getKernel?(): any. The real contract (packages/core/dist/index.d.ts:403) is logger: Logger (spec's Logger, not the global Console) and a requiredgetKernel(): ObjectKernel. The block's omissionsregisterServiceFactory, replaceService, getServiceScoped, getServices — are left as they are, for the same reason as the bullets above.

Changeset, not skip-changeset

packages/runtime is published, and its files array carries README.md — a README repair ships on npm, so this owes a real changeset. .changeset/runtime-readme-unread-call-site-audit.md, patch. (PR #9906 is the precedent; PR #10365 made the same call.) The diff ships user-visible published content, so the measurement-only branch does not apply here.


Gate evidence

All gate results are the gate's own verdict line, with the exit code captured before any pipe (cmd > file 2>&1; ec=$?).

pnpm check:published-readme-exports, before (exit 0):

 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.

after (exit 0, at 63549ad2ad):

 NOT read: 119 documented `X.y(…)` call(s) on 71 receiver(s) with no type this gate can reach
(free variables, parameters, globals, non-workspace imports). Visibility, not a verdict — see #9870.

−1 call and −1 receiver, and that is the whole expected movement. The reply receiver is gone; its replacement res was already a receiver in this document with res.status and res.json already counted, and countUnreadCalls dedupes X.y per document. Nothing else in the diff touches a call site. The other five repairs are a bullet list, an interface block, a property access and two calls whose member names are unchanged — all invisible to this count. Per-document: 30/17 → 29/16.

node scripts/pm/dispatch-gates.mjs derived 10 families from the change set (.changeset/runtime-readme-unread-call-site-audit.md, packages/runtime/README.md). All re-run at the final commit 63549ad2ad:

gateexit
pnpm check:changeset-gate-self-tests0
pnpm check:objectui-changeset0
pnpm check:slot-lookup0
pnpm check:test-source-alias0
pnpm check:type-source-resolution0
node scripts/check-adr-0087-registration.mjs0
node scripts/check-changeset-no-major.mjs0
node scripts/check-empty-changeset.mjs0
node scripts/check-plugin-teardown-shape.mjs0
node scripts/docs-audit/check-affected-docs.mjs0
pnpm check:published-readme-exports (the card's own gate)0
pnpm check:nul-bytes0

Selected verdict lines, as the gates printed them:

✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new, and every file in the population parsed.
baseline key set verified against 112a8c6: no files added.
✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).
✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).
check-nul-bytes: OK (scanned 6252 text file(s) -- 6252 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).

Both changeset gates were re-run after the commit: run against an uncommitted changeset they report 0 declaring changeset(s) and pass vacuously, which is the reading that would have been wrong.

No tests run and none added: the diff is one Markdown file and one changeset entry.

Fences honoured

scripts/check-published-readme-exports.mjs is untouched — this is a read, not a recognizer change. #9870's option D (receivers bound by property access off a typed value) is not attempted; it still wants a census and its own card. #9911 and #10367 remain open and are not folded in here.

Findings filed (unassigned, no pm:queue)

Generated by Claude Code


Generated by Claude Code

… README's unread call sites (#10368)
Hand-adjudicated every call site in `packages/runtime/README.md` that
`check:published-readme-exports` reports under its `NOT read:` line —
receivers bound to free variables, parameters and globals, which neither
the gate nor a human reader can type by looking.
Population, derived from the gate's own `countUnreadCalls` over the same
`publishedDocs()` it runs on: 30 sites on 17 receivers (the whole-repo
line reads `NOT read: 120 ... on 72 receiver(s)`).
Verdicts: 24 sites resolvable and correct, 5 genuinely unadjudicable
(receiver is a reader-owned illustrative object with no type anywhere),
1 fabricated member — `reply.code(429)`, which is Fastify, against a
package whose HTTP contract spells the step `IHttpResponse.status(code)`.
Two further sites name a real member with the wrong call shape
(`engine.update` / `engine.delete`), a defect class member-existence
cannot see, and three defects outside the call population turned up in
the same read (`res.statusCode`, `PluginContext.logger`,
`PluginContext.getKernel`).
`NOT read` moves 120 -> 119 calls and 72 -> 71 receivers: the `reply`
receiver is gone, and `res.status` / `res.json` were already counted in
this document.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
@claudeclaudeBot added documentation Improvements or additions to documentation domain:devx tooling labels Aug 21, 2026
@github-actions

github-actionsBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/runtime/README.md) — pages documenting those are invisible to this run

Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a7ea3289eb605c6681ca6bc904e4ab5f799d6504packageMentionDocs.

@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 13:18
@os-zhuang
os-zhuang added this pull request to the merge queueAug 21, 2026
Any commits made after this event will not be merged.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationdomain:devxsize/stooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hand-audit packages/runtime/README.md's 47 unread call sites — the one surface where a per-document read is priced to pay

1 participant

@claude