Skip to content

docs(core): repair four copy-fails in PHASE2_IMPLEMENTATION.md - #18712

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-18620-phase2-doc-copy-fails
Sep 17, 2026
Merged

huangyiirene merged 1 commit into
mainfrom
claude/issue-18620-phase2-doc-copy-fails

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #18620

Clause-②: no

packages/core/PHASE2_IMPLEMENTATION.md carried four copy-fails that the edit boundary of PR #18615 (the seven retired key spellings) deliberately left alone. Triage refused to split the card, so all four land here in one sweep of one file. packages/spec was read-only for this work: the schemas were read to learn the right spelling, never edited.

Every reading below was re-taken on this branch's head. The card's line numbers moved after PR #18615 landed, so each anchor was located by content.

The four items are NOT one species

Exactly one of them is a silently dropped key. The other three are a compile failure and stale prose.

1. blockedHosts — a key the runtime silently drops, in a security example

The sandbox example wrote blockedHosts. SandboxConfigSchema (packages/spec/src/kernel/plugin-security-advanced.zod.ts:392) declares deniedHosts, and sandbox-runtime.ts:261-262 reads config.network.deniedHosts. The network object is a plain z.object, not .strict(), and there is no retiredKey tombstone for blockedHosts — so the parse succeeds and the key is stripped with no diagnostic at all.

Measured against the schema source, the documented block beside a positive control:

--- DOCUMENTED blockedHosts ---
  success: true
  parsed network keys: ["mode","allowedHosts","maxConnections"]
  network.deniedHosts: undefined
--- CONTROL deniedHosts ---
  success: true
  parsed network keys: ["mode","allowedHosts","deniedHosts","maxConnections"]
  network.deniedHosts: ["malicious.com"]

A reader who copied that block got a sandbox that did not deny the host it named, and was told nothing. blockedHosts occurred exactly once repo-wide before this PR (that documentation line) and occurs zero times after it. The Features bullet above the block said "allowed/blocked hosts" and now says "allowed/denied hosts", matching the filesystem bullet beside it.

Species: a silently dropped key. This is the only one of the four.

2. kernel.logger — a compile failure by member visibility, not a dropped key

The Integration with Kernel block passed kernel.logger to five constructors. ObjectKernel declares private logger: ObjectLogger (packages/core/src/kernel.ts:107), KernelBase declares protected logger: Logger (packages/core/src/kernel-base.ts:36), and neither class has a public getter. The block never compiled, so nothing about it ever reached a runtime that could drop anything.

Measured with tsc --noEmit --strict, the documented line against the repaired one:

documented:  exit 2
  error TS2341: Property 'logger' is private and only accessible within class 'ObjectKernel'.
repaired:    exit 0, no output

The repair builds the ObjectLogger those five constructors take with createLogger — already exported from @objectstack/core through the logger.js barrel — from the same config the kernel is given, rather than reaching into the kernel. The whole repaired block is what the exit-0 run above typechecked.

Species: an example that fails when copied, by member visibility. No metadata key is involved.

3. Prose advertising a capability no runtime delivers

The intro claimed "auto-recovery" and the Features list claimed "Auto-restart with backoff strategies (fixed, linear, exponential)". packages/core/src/health-monitor.ts:160 states the opposite in as many words: "Monitors plugin health status. It REPORTS; it does not act on what it finds." The three keys that once configured a restart were retired under ADR-0049 and deleted from this file's examples by PR #18615, which is how the prose became conspicuous.

The intro now says the monitor reports and that acting on a report belongs to the host, and the untrue Features bullet is replaced by the true integration point the example already demonstrates — polling getHealthStatus and getHealthReport. The adjacent bullets were checked rather than assumed: the recovering status is real and asserted by health-monitor.test.ts, so that bullet stands.

Species: stale prose, Prime Directive #10. No key.

4. Three observations in the same file

  • MICROKERNEL_IMPROVEMENT_PLAN.md was linked from the References section. Repo-wide the name occurred once — that link — and no such file exists at the repo root or anywhere else, while its sibling ARCHITECTURE.md does. The dead link is removed.
  • npm test in a pnpm workspace is now pnpm --filter @objectstack/core test, which is what the package's own test script answers to. The three test files the section lists all exist.
  • "File watching integration points" was listed as a hot-reload feature. HotReloadConfig.watchPatterns was retired in [finding] HotReloadManager.startWatching watches nothing and logs "File watching started" at info; watchPatterns has no reader and watchHandles is never populated #12428 because no watcher was ever constructed, and this file's own body already tells the reader that file watching is the host's job. The bullet is folded into the debounce bullet, which now names scheduleReload and says the host runs the watcher.

Species: stale prose and one dead reference. No keys.

Changeset: measured, not guessed

packages/core/PHASE2_IMPLEMENTATION.md sits at a package root, outside every fast-track path, so whether it ships is a property of @objectstack/core's files[], which is ["dist", "README.md", "CHANGELOG.md"].

The reading, taken after a real build of @objectstack/core and its dependency closure:

  • npm pack --dry-run --json in packages/core lists 16 tarball entries. The non-dist/ entries are CHANGELOG.md, LICENSE, README.md and package.json. PHASE2_IMPLEMENTATION.md is not among them.
  • Grepping the published paths (dist, README.md, CHANGELOG.md, package.json, LICENSE) for symbols unique to this diff: PHASE2_IMPLEMENTATION 0 files, MICROKERNEL_IMPROVEMENT_PLAN 0 files, Auto-restart with backoff strategies 0 files, blockedHosts 0 files.
  • Positive control on the same grep: PluginSandboxRuntime hits 4 published files (dist/index.cjs, dist/index.js, dist/index.d.cts, dist/index.d.ts), so the grep was capable of finding something.

Verdict forced by that reading: this diff publishes nothing from any released package, so it takes skip-changeset rather than a patch changeset.

Clause-②: no stands after the diff was written: the schema is untouched, no accept set moves, and no surface is published. scripts/pm/check-widening-tells.mjs --declaration no exits 0 on this diff but reports it NOT MEASURED — no declared surface covers a Markdown file — so that exit is recorded as evidence about no surface rather than as a clearance.

Verification

  • Gate families derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack from the tool's own change set: 39 families. 37 exited 0. Reconciled with --ran carrying every exit code: 39 derived, 37 run, 2 NOT MEASURED, 0 UNRUN.
  • The 2 NOT MEASURED are check:dual-build-cjs-loads and check:lean-entry-closure, both exit 3 PREREQUISITE NOT MET: they load built output for every package in the tree and 74 or more packages have no dist/. A whole-tree pnpm build is CI's run, not this card's; declared as a narrowing rather than reported as a pass.
  • pnpm --filter @objectstack/core typecheck exit 0 (three legs: tsc --noEmit, tsconfig.examples.json, check:test-typecheck).
  • pnpm --filter @objectstack/core test exit 0 — 51 test files, 1316 tests passed.
  • pnpm --filter '@objectstack/core...' build exit 0 (core plus its dependency closure), run before every gate and measurement that reads dist/.
  • pnpm lint narrowed to a measurement rather than skipped. ESLint's universe read from eslint.config.mjs itself: every config block's files is a {ts,tsx,mts,cts,js,jsx,mjs,cjs} glob and no block names Markdown. ESLint's own count over the changed file, from --format json: 1 result, 0 errors, and the one message is "File ignored because no matching configuration was supplied." Invariance over untouched files: the config enables no type-aware linting anywhere — no parserOptions.project, no typed rules, as the config's own recorded measurement at line 328 states — so this diff cannot move any other file's verdict.
  • pnpm check:nul-bytes exit 0, plus a direct control-character scan of the changed file, which found none.
  • All heavy runs went through scripts/pm/os-verify-lock.sh with a stable slot; every verdict above is read from its VERDICT command-exit line.

Measurements taken at b6606cdc2d, which is the only commit on this branch.

Acceptance notes

  • The ## References section still links [Protocol Definitions](../spec/src/system/), while the Phase 2 components this file documents are Kernel-domain schemas under packages/spec/src/kernel/. The link resolves to a directory that exists, so it is not dead and nothing fails when copied — a documentation nit, none of the three filable classes. Noted, not filed. No PR and no person is queued on this file today, so there is no carrier for it beyond the next sweep of the document.
  • needs:contract-review is the dispatching seat's label. This PR neither hangs it, removes it, nor waits on it; its presence and the --pair exit code are reported to the seat as readings.

Generated by Claude Code

The sandbox example wrote `blockedHosts`, a key `SandboxConfigSchema` does
not declare: `safeParse` succeeds and silently strips it, so a reader who
copies that security block blocks nothing and is told nothing. Both the
schema and `sandbox-runtime.ts` spell it `deniedHosts`.

The `Integration with Kernel` block passed `kernel.logger` to five
constructors; that field is `private` on `ObjectKernel` and `protected` on
`KernelBase`, with no public getter, so the block never compiled. Build the
`ObjectLogger` those constructors take with `createLogger` instead.

Drop the auto-recovery / auto-restart-with-backoff claims: `health-monitor.ts`
reports and does not act, and the keys that configured a restart were retired
under ADR-0049. Also repair the hot-reload feature bullet, the `npm test`
line in a pnpm workspace, and the dead MICROKERNEL_IMPROVEMENT_PLAN.md link.

Claude-Session: https://claude.ai/code/session_01CqmCgU5RGDoJYhHUMVp2af
Co-authored-by: Claude <noreply@anthropic.com>
@huangyiirene huangyiirene added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 17, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/core/PHASE2_IMPLEMENTATION.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/core/PHASE2_IMPLEMENTATION.md) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 24 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 51297e9ee17db23f50d38c1431f5a4dcc47632ff → packageMentionDocs.

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 17, 2026
@huangyiirene
huangyiirene marked this pull request as ready for review September 17, 2026 16:34
@huangyiirene
huangyiirene added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 02e19a7 Sep 17, 2026
40 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-18620-phase2-doc-copy-fails branch September 17, 2026 16:53
huangyiirene pushed a commit that referenced this pull request Sep 17, 2026
A census instrument, not a gate: it exits 0 on every census outcome, is
wired into no CI job and no package test/lint chain.

It extracts fenced ts/typescript/tsx blocks from Markdown at the root of
every package under packages/, compiles them in one tsc program against a
`paths` map generated from each package's own exports map, and reports the
number two ways — raw (upper bound) and under a tolerance rule that forgives
only what a legitimately partial block can produce (lower bound).

Two controls run on every census. GREEN_CONTROL must compile, so an unbuilt
workspace refuses instead of reporting a catastrophe made of nothing.
FIRING_CONTROL reconstructs the pre-#18712 `kernel.logger` block and must be
caught with TS2341 — and, because that block ends in an elision marker, it is
also the standing proof that an exclusion-style elision rule would have hidden
the one defect in this family anyone has measured.

Claude-Session: https://claude.ai/code/session_01CqmCgU5RGDoJYhHUMVp2af
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…bjectstack-ai#18751)

Part of objectstack-ai#18715 — this PR answers the card's own "⛔ Not measured"
question with a number. It deliberately does **not** close the blind
spot: nothing here gates anything, so the half left open is the wiring
decision, which is a new required gate and therefore the maintainer's
floor (live precedent: the PR parked in the decision box for that
reason, referenced in the dispatch). objectstack-ai#18715 stays open on merge.

Clause-②: no

## What landed

One file: `scripts/measure-markdown-ts-blocks.mjs`, 1086 lines, a member
of the existing `scripts/measure-*.mjs` family (8 prior members). It is
a measurement instrument, not a gate:

- it exits 0 on **every** census outcome;
- it is wired into no CI job, no `package.json` script, no package
`test`/`lint` chain;
- it exits non-zero for exactly one reason — **the instrument itself
could not run**. "Could not run" is a failure, not a skip.

Usage: `node scripts/measure-markdown-ts-blocks.mjs` (census, 12s on a
warm build) · `--json` · `--only PATH` · `--controls-only` ·
`--self-test` (30 batteries, no build needed).

## The census

Population: `*.md` at the root of every directory under `packages/` that
carries a `package.json` — 76 package roots, 141 Markdown files, **1002
fenced `ts`/`typescript`/`tsx` blocks**. `content/docs/**` is a
different population with its own gates and is out of scope.

Three numbers, because two would hide the answer inside the convention
(see below). RAW counts a block failing on any diagnostic; TOLERANT
counts it failing on any diagnostic **outside** the forgiven set;
WELL-FORMED AND WRONG counts blocks that parse as a TypeScript module
and still fail — the class the repaired `kernel.logger` block belonged
to.

| stratum | files | blocks | RAW | TOLERANT | WELL-FORMED AND WRONG |
|:--|--:|--:|--:|--:|--:|
| **HAND-WRITTEN** (repairable by an author) | 54 | 282 | **195**
(69.1%) | **76** (27.0%) | **58** (20.6%) |
| **CHANGELOG.md** (release-owned history) | 46 | 720 | 646 (89.7%) |
356 (49.4%) | 109 (15.1%) |
| sub-stratum: the literal `packages/*/*.md` glob the card names | 42 |
654 | 574 (87.8%) | 314 (48.0%) | 131 (20.0%) |

RAW is an **upper bound** — it counts fragments that were never meant to
stand alone. TOLERANT is a **lower bound**, and that is the direction
that matters: a forgiven "cannot find name" leaves that name typed
`any`, so every downstream use of it goes unchecked. Real defects hide
behind an elision; they never appear because of one. The honest sentence
is "between TOLERANT and RAW".

⚠️ The two strata are reported separately and never summed.
`CHANGELOG.md` is compiled by `changeset version` from `.changeset/*.md`
and is release-owned: its blocks are before/after migration snippets
that **deliberately** document removed APIs (57 of its counted
diagnostics are TS2305 "has no exported member"). A failing block there
is frequently correct documentation, and it cannot be repaired in a code
PR at all.

Diagnostic composition over the whole corpus:

```
  forgiven  elided-name                 1297 diagnostics in  527 blocks   TS2304x1275 TS2503x20 TS2552x2
  COUNTED   syntax                       772 diagnostics in  265 blocks   TS1005x370 TS1128x170 TS1109x115 TS1127x73
  COUNTED   semantic                     332 diagnostics in  141 blocks   TS2305x57 TS2300x56 TS2451x49 TS18004x47
  COUNTED   implicit-any                  65 diagnostics in   42 blocks   TS7006x54 TS7031x8 TS7008x2 TS7023x1
  forgiven  doc-local-path                12 diagnostics in    9 blocks   TS2307x12
  COUNTED   unpublished-subpath            8 diagnostics in    7 blocks   TS2307x8
  forgiven  missing-ambient-environment     1 diagnostic  in    1 block    TS2593x1
```

Per-file breakdown (blocks / RAW fail / TOLERANT fail), every file
carrying at least one TOLERANT failure:

```
   259  229  141  packages/spec/CHANGELOG.md                        4    4    4  packages/plugins/plugin-auth/ARCHITECTURE.md
    33   29   23  packages/services/service-automation/CHANGELOG.md 7    7    4  packages/plugins/plugin-auth/CHANGELOG.md
    37   35   22  packages/runtime/CHANGELOG.md                     9    9    4  packages/plugins/plugin-sharing/CHANGELOG.md
    50   46   19  packages/objectql/CHANGELOG.md                    6    4    4  packages/services/service-analytics/CHANGELOG.md
    25   23   15  packages/metadata-protocol/CHANGELOG.md           7    6    4  packages/services/service-datasource/CHANGELOG.md
    18   16   12  packages/lint/CHANGELOG.md                        4    4    4  packages/triggers/trigger-record-change/CHANGELOG.md
    20   18   11  packages/cli/CHANGELOG.md                         4    4    3  packages/client/README.md
    27   25   11  packages/client/CHANGELOG.md                      7    7    3  packages/core/CHANGELOG.md
    23   23   10  packages/rest/CHANGELOG.md                        4    3    3  packages/drivers/driver-memory/README.md
    14   14    9  packages/client-react/README.md                   8    8    3  packages/plugins/plugin-hono-server/CHANGELOG.md
    17   15    9  packages/drivers/driver-mongodb/CHANGELOG.md      3    3    3  packages/rest/README.md
    10   10    9  packages/metadata/CHANGELOG.md                   15   13    3  packages/spec/DEVELOPMENT_PLAN.md
    10   10    8  packages/platform-objects/CHANGELOG.md
    20   20    8  packages/runtime/README.md                       ... 2 each: client-react/CHANGELOG, metadata-core/CHANGELOG,
    33   27    7  packages/drivers/driver-sql/CHANGELOG.md             plugin-audit/CHANGELOG, plugin-auth/README,
    10    9    6  packages/core/ADVANCED_FEATURES.md                   plugin-reports/CHANGELOG, service-i18n/CHANGELOG,
    12   12    6  packages/plugins/plugin-security/CHANGELOG.md        service-job/README, service-queue/CHANGELOG,
    11    7    5  packages/services/service-automation/README.md       service-realtime/README, service-sms/CHANGELOG,
     7    5    5  packages/spec/V3_MIGRATION_GUIDE.md                  service-storage/CHANGELOG, spec/PLUGIN_STANDARDS,
    14   12    4  packages/drivers/driver-memory/CHANGELOG.md          trigger-record-change/README, trigger-schedule/CHANGELOG,
                                                                      trigger-schedule/README
 ... 1 each: cli/README, driver-mongodb/README, driver-turso/README, mcp/CHANGELOG, mcp/README, observability/README,
             knowledge-ragflow/README, service-cache/README, service-cluster/CHANGELOG, service-i18n/README,
             service-package/README, service-queue/README, service-settings/CHANGELOG, service-storage/README,
             spec/README, spec/REST_API_PLUGIN, spec/ZOD_SCHEMA_AUDIT_REPORT, types/CHANGELOG, types/README,
             verify/CHANGELOG

  33 file(s) with TS blocks and zero TOLERANT failures.
```

The full per-block record, with every diagnostic, is `node
scripts/measure-markdown-ts-blocks.mjs --json`.

## The elision convention — the design question, and why the obvious
answer is disqualified

The card says the convention is the real design question, not the
extraction. It is, and the obvious answer fails.

⛔ **Exclusion is disqualified, and this PR proves it rather than
asserting it.** The obvious rule is: a block carrying an elision marker
(`// ...`) is skipped. Run that rule against the one failure this
repository has already measured — the pre-objectstack-ai#18712 `kernel.logger` block
in `packages/core/PHASE2_IMPLEMENTATION.md`, `tsc --noEmit --strict`
exit 2 with TS2341 — and it is **excluded**, because that block ends
with the line `// ... plugin registration code ...`. An exclusion rule
would have hidden the only defect in this family anyone has ever
measured. The instrument reports the exclusion reading on every run,
labelled disqualified, so the number cannot hide inside the convention:

| reading | hand-written | CHANGELOG |
|:--|--:|--:|
| [disqualified] EXCLUSION | 188 fail of 272 considered, 10 blocks
skipped | 642 fail of 716 considered, 4 blocks skipped |

✅ **The rule used is a TOLERANCE rule.** Every block is compiled; what a
legitimately partial block can *produce* is forgiven and nothing else
is:

- forgiven — TS2304 / TS2552 / TS2503: a name or namespace the block
elided.
- forgiven — TS2307 on a **relative** specifier (`./my-kernel`,
`./objectstack.config.js`): the reader's own file.
- forgiven — "cannot find name `it` / `describe` / `process`", and a
third-party module that ships no declarations: the reader's ambient
environment.
- counted — everything else, including TS2341 (private member), TS2339,
TS2345, TS2305 / TS2724, and **TS2307 on an `@objectstack/*`
specifier**. The `paths` map is generated from each package's own
`exports` map, so an unresolved one means the document tells a reader to
import a subpath the package does not publish.
- counted — syntax. A tolerance rule that forgave TS1xxx would forgive a
typo.

**The forward convention, for what tolerance cannot absolve.** A block
that is a bare fragment — a method-signature listing, half an object
literal — produces syntax errors, and those need an explicit
declaration. Proposed spelling: an info-string tag, ` ```ts partial `.
Zero blocks carry it today, so it contributes nothing to this census;
the instrument reads it, and counts how many blocks would need one: **18
hand-written**, 239 in CHANGELOGs.

## The firing control

An instrument that finds zero failures and was never shown capable of
finding one has measured nothing. Two controls run on **every** census,
through the identical extractor, normalisation and compiler path as
corpus blocks, and the run **refuses to print a census** if either
misbehaves:

- `GREEN_CONTROL` must compile. Its failure means the workspace is not
built — the state in which every other block would report TS2307 and the
census would read as a catastrophe made of nothing.
- `FIRING_CONTROL` is the pre-objectstack-ai#18712 `kernel.logger` block,
reconstructed line-for-line from that PR's diff. It must be reported
failing, must carry TS2341, must survive the tolerance rule, and must
still carry its elision marker.

Observed, every run:

```
  GREEN_CONTROL  compiles clean — `@objectstack/*` resolves through the built exports maps.
  FIRING_CONTROL reports 5 diagnostic(s) including TS2341 at block line 13 — the pre-objectstack-ai#18712
                 `kernel.logger` block, reconstructed from that PR's diff, is caught.
  FIRING_CONTROL also carries an elision marker, so the EXCLUSION reading SKIPS it.
```

Five TS2341, at block lines 13–17: one per constructor the block passed
`kernel.logger` to.

⭐ **The firing control earned its keep during this task.** The
instrument was first written against `ts.createProgram` +
`getPreEmitDiagnostics`, then rewritten onto the `tsc` binary because
`check:parse-guard` correctly forbids the parser API outside
`scripts/ts-parse.mjs` (and `createProgramChecked` is the wrong tool
here — it exits 3 on the first syntactic diagnostic, and blocks that do
not parse are this census's subject). The first binary version reported
**zero** diagnostics for the firing control, and the control refused the
run. Cause, a measured property of the CLI: **when any file in a program
has a syntax error, tsc reports the syntax errors and never type-checks
ANY file.** One pass over this corpus returned 772 TS1xxx and not one
semantic diagnostic. Hence two passes: pass 1 finds the blocks that do
not parse, pass 2 re-runs over the ones that do. Without the firing
control this would have shipped as "the corpus has no type errors".

Cross-check: over all 1002 corpus blocks, the binary implementation and
the compiler-API one it replaced agree on **every** block's verdict —
raw, tolerant and well-formed-and-wrong — with zero disagreements.

## Wiring options, with costs, for the maintainer's letter

Nothing below is done in this PR. What it would take for the number to
reach zero is stated first, because it decides everything else.

**Reaching zero, hand-written stratum:** 76 blocks across 31 files — 58
repairs (a block that parses and is wrong) plus 18 explicit `partial`
tags (a block that is a signature listing or a fragment). **Reaching
zero, CHANGELOG stratum: impossible in principle.** Those files are
release-owned, compiled from changesets, and deliberately show removed
APIs. Any gate whose population includes `CHANGELOG.md` is a gate that
can never be green without rewriting published release history. ⇒ **The
only gate-able population is hand-written package-root Markdown.**

1. **Leave it unwired (what this PR does).** Cost: zero CI time. The
number is known only when someone runs it — the declared-not-enforced
shape this family of cards is about, mitigated only by the instrument
being cheap (12s) and carrying its own controls.
2. **Advisory job, printing the census on every PR.** Cost: it resolves
through built `.d.ts`, so it must piggyback on a job that has already
built (or pay a full build). Nothing blocks. Risk: an advisory red rides
`main`'s merge ref into every later PR until someone stanches it.
3. **Shrink-only ratchet with a checked-in per-file baseline.** Cost: a
required job (maintainer's floor) plus a baseline artefact and its
merge-driver discipline. Opening number to hold: 76 hand-written
TOLERANT failures. Buys: the number can only go down, and grandfathered
debt is visible rather than forgotten.
4. **Gate at zero over hand-written package-root Markdown.** Cost: the
76-block entry price above, paid before the gate can be armed, plus the
`partial` tag convention landing first. Buys: the strongest guarantee,
and the only one that makes the card's class genuinely closed.
5. **Diff-scoped gate — only blocks a PR adds or edits.** Cost: block
identity across edits; today's debt is grandfathered on day one. Buys:
catches the PR-objectstack-ai#18712 class at the moment it is written, at near-zero
entry price. ⚠️ Grandfathering is exactly the failure mode objectstack-ai#15931
demonstrated, so this option is only honest if the standing debt number
is recorded somewhere that gets re-read — which is what option 3 is for.
3 + 5 together is the combination with no known hole.

## Verification

- `node scripts/measure-markdown-ts-blocks.mjs --self-test` — 30 cases
pass, 30 batteries at or above the pinned floor of 30.
Roster-and-handshake shape copied from
`scripts/check-agent-model-declared.mjs`; needs no build.
- `node scripts/measure-markdown-ts-blocks.mjs` — exit 0, both controls
fire, 12s under the shared verify lock.
- All 26 gates `scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derives for this diff: **26/26 exit 0**. Two
were red on the first draft and drove real corrections —
`check:parse-guard` (the parser-API rule) and `check:entry-guard` (the
import guard).
- `eslint . --no-inline-config --format json` — the **union**, not a
narrowing: 6830 files governed by eslint's own config, 0 errors, 0
warnings, at `3c64a03c4e`.
- Control-character self-scan over the new file: no match.
- `skip-changeset`, measured with a positive control rather than
assumed: the new file's basename has 0 hits anywhere under any package's
built `dist/`; the positive control `ObjectKernel` has 4 hits in
`packages/core/dist`; no package's `files[]` names a path outside its
own directory, so a repo-root `scripts/` file cannot enter any tarball.

## Acceptance notes

- **Not repaired, by instruction.** 58 hand-written blocks parse and are
wrong. A sample, all real: `packages/runtime/README.md` — TS2339,
`Property 'use' does not exist` on the promise the kernel builder
returns, because the front-page example chains `.use(...)` without
awaiting it (the type in the diagnostic is a Promise parameterised by
ObjectKernel; spelled out here because a raw angle-bracket fragment does
not survive this surface); `packages/plugins/plugin-auth/README.md` —
`'plugins' does not exist in type 'ObjectKernelConfig'`;
`packages/services/service-job/README.md` — `'timeout' does not exist in
type 'JobScheduleOptions'. Did you mean 'timeoutMs'?`;
`packages/drivers/driver-mongodb/README.md` — `'driver' does not exist
in type 'ObjectStackDefinitionInput'`;
`packages/services/service-cache/README.md` — the documented `MyCache`
class does not implement `ICacheService`.
- **The sharpest instance found, not filed by me:**
`packages/spec/V3_MIGRATION_GUIDE.md` tells a reader to import
`@objectstack/spec/hub`, `@objectstack/core/errors` and
`@objectstack/core/plugin`. None of the three is in its package's
`exports` map, so the import fails for anyone who copies it. This is the
`check-published-readme-exports` family arriving through a document that
gate's population does not reach — `V3_MIGRATION_GUIDE.md` is not in
`@objectstack/spec`'s `files[]`. Reported to the dispatching seat with
dedupe words rather than filed here, to keep this PR a census.
- **Noted, not filed:** the census counts `packages/*/CHANGELOG.md`
because they are package-root Markdown, and they dominate the raw number
(720 of 1002 blocks). They are structurally unrepairable in a code PR.
Successor: whoever writes the wiring letter — the stratum split exists
precisely so that reader does not have to re-derive it.
- **No census snapshot is committed.** A number checked into a file is a
number that rots; the instrument regenerates it in 12 seconds. The
census lives in this PR body and in the report comment on objectstack-ai#18715.
- Population note: the card's example glob is `packages/*/*.md`, which
misses nested package roots such as `packages/plugins/plugin-auth/`. The
instrument measures every package root and reports the card's literal
glob as a named sub-stratum, so both readings are available and neither
is assumed.

<sub>⚠️ **Line count corrected by the dispatching seat, not by the
author.** This body first read 「918 lines」, which was the count at the
first commit, before the two-pass `tsc` rewrite; the landed file at
`3c64a03c4e` is **1086** lines (`git show … | wc -l`, taken by the
seat). The author found the slip after the single permitted body write
and reported it rather than patching, because the role file allows a dev
exactly one body write in the `POST /pulls` stroke and routes later
changes through its report to the seat. ⛔ Nothing else in this body was
changed, and everything else in it was re-verified by the author at the
landed head.</sub>

---
_Generated by [Claude Code](https://claude.ai/code)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants