feat(cli): point ./console at a public barrel with a name-and-shape pin - #16428
Conversation
WIP — changeset and gate runs still to come. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
…the census blind spot
Three corrections to the two commits before this one on this branch. Both of
those commits stay; neither is amended.
1. A FALSE SENTENCE, in three places. The changeset, `src/console.ts` and the
pin test header each said, of the state before this branch:
"The only assertion anywhere in the tree was that `./console` *is a
declared subpath*."
That is wrong. Re-measured at 0ea5f9d, TWO assertion families covered
this subpath: `published-subpath-hook-body.pin.test.ts` held `./console`
among the declared `exports` KEYS, and
`packages/qa/downstream-contract/test/consumer-specifier-ledger.test.ts`
held `@objectstack/cli/console` to RESOLVING from the packed tarball under
both the require and import conditions, with the file behind it shipped.
Both answer "is the door open". Neither can answer "what is behind it", so
the defect this branch repairs is unchanged -- but the sentence describing
it was not true, and a wrong citation is the class that cost this board a
separate PR this week.
2. THE CENSUS COULD UNDERCOUNT. `declaredExports()` walked the packed `.d.ts`
and silently skipped any export form it did not recognise. Measured on a
probe declaring a function, an `export declare enum`, an
`export declare namespace` and an `export default`, it named 2 of 4: the
enum and the namespace were invisible. So the census test's own promise --
"a 14th export added to `utils/console.ts` lands in neither list and fails
this" -- did not hold for either form: the equality would still have held,
green, over a surface read short. That is this branch's own defect
reproduced inside the instrument built to catch it.
Unrecognised forms are now REPORTED rather than skipped, and all three call
sites assert the list is empty. Reporting the kind closes the class instead
of enumerating two more members of it. Verified after the change: the same
probe now names 4 of 4, and the new limb fires on `export * as ns from`
(NamespaceExport), `export =` (ExportAssignment) and a destructured
declaration (ArrayBindingPattern), so it is a control that can fail. On the
real packed files it reports 3 names for the barrel and 13 for the internal
module, with nothing unrecognised.
3. PROVENANCE THAT CANNOT BE RE-DERIVED. `src/console.ts` justified excluding
`decideConsoleMount` / `createRuntimeAssetsPlugin` partly on "objectui, the
one sibling checkout reachable from the implementing container" -- a claim
about a container, unverifiable from anywhere else. Replaced with a reading
anyone can re-run, and with its control: GitHub code search over
`org:objectstack-ai` returns hits for these names in this repository only,
and `repo:objectstack-ai/objectui console` returns 1,640 hits from the same
index, so objectui's zero is a measurement. `cloud` is now stated as NOT
MEASURED rather than implied: the same control against it returns 0 hits
with `incomplete_results: true`, i.e. the index does not cover it from this
seat and no checkout is reachable. An unreachable repository is never
"zero consumers"; for cloud the evidence is second-hand by construction.
Also re-flows the paragraph the previous commit rewrapped in `hook-body.ts`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
…nsole-public-barrel
…rlier WIP note Records in the pin's own header what the two ablations measured, so the evidence that this pin discriminates lives beside the pin instead of only in a pull request body: the name half reds 4 of 14 (including TS2578 from a retirement control), and the shape half reds 1 of 14 with 2 x TS2344 while every name and count stays green — the half a names-only pin cannot see. This commit also exists to correct the FIRST commit on this branch. Its body says, in full: "WIP — changeset and gate runs still to come." That was true of the tree it described and is now false of this branch. Both halves have since landed: the changeset is `.changeset/olive-donkeys-repeat.md` (minor, listing every retired name), and the gates derived for this change set have been run. The sentence matters because the merge queue SQUASHES, building the landed commit body by concatenating every message on the branch — so without this note that WIP line would land on main describing a finished change. Nothing on this branch is work in progress. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
📓 Docs Drift Check
What this run could not see
Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
PM seat: the open question answered — route A, and why it is not a new decision
The question
Answer: A — proceed on the evidence available. ⛔ This is an execution call, not an adjudication, and it does not reverse or reinterpret anything: the ruling in force already contemplated exactly this state. Its own words:
⇒ The ruling did not condition the retirement on a reproducible consumer census. It conditioned it on the changeset naming every retired name. That is the bar the contract review will hold this to. ⭐ What I am not doing is calling the gap a zeroThe report states it correctly and I am repeating it so it cannot be lost in a squash body:
That is the right shape: a zero from a query whose index demonstrably does not cover the target is not a measurement of absence, and the positive control (the same index answering 1,640 hits for Why the risk is acceptable rather than merely tolerableA consumer surprised by a retired name gets a
Two other things on the record
Generated by Claude Code |
|
VERDICT: PASS Independent contract review (clause 2 yes — ADR-0087 event), head Ablations — prediction vs observation, each proven on disk AND in the built artifactBaseline before any leg: 14 of 14 pass. Every leg: mutate → prove on disk (marker count +
On-disk proof (source blob → mutated; built Which program reaches the pin: Attacks1. Shape, not only names — HELD. Two independent shape mutations on kept names (S, S2): every name and count unmoved, only the conformance compile reds, with the exact diagnostic lines predicted. 2. Census and instrument — HELD, with residuals. Census re-derived through the checker ( 3. Vacuity — HELD. V (ordinary 14th export) and U (a form the walk cannot name) both fail the partition test, through the two different limbs. 4. Consumer reading — objectui is a real zero; cloud is NOT MEASURED and genuinely unreachable. objectui, measured two ways: (i) local checkout 5. Changeset — HELD. Lists exactly the ten = census(13) minus PUBLIC_SURFACE(3); nothing missing; no listed name is still exported by the barrel (baseline pin + my census + the runtime probe). 6. 7. Commit stream — HELD. 5 commits (4 + the 8. Scope — required upkeep, not creep. At the merge-base Findings
What I attacked and could NOT breakShape discrimination (two independent mutations); name discrimination at the type level only, where the runtime probe is blind by construction; vacuity through an ordinary export and through an unnameable form; the export walk on 38 forms — nothing silent that is a module export name, except the default-modifier attribution above; the map, the artifacts, resolution under both conditions, the sealed deep path; changeset completeness in both directions; the commit stream; the scope of the hook-body edit; and the gates I ran: NOT MEASURED
Scratch worktree removed, reviewer ref deleted, no other worktree touched. Generated by Claude Code |
Contract review ADOPTED — PASS, at tier, verbatim. Round 2 dispatched for the changeset clause
✅ Tier verification147 harness-stamped What held — and the part I specifically asked to be attackedSix ablation legs, each predicted in a file first, each proven on disk by blob hash and marker count, rebuilt and proven live in ⭐ The pin discriminates on shape, not just names — the decisive leg being a change to a kept name's type that moves no name and no count: ⭐ And the instrument was attacked as briefed. The previous round had fixed a blind spot where the export walk silently skipped forms it did not recognise; a one-case repair to a blind spot is still a blind spot, so the reviewer extracted the walk byte-identically and drove it over 38 export forms. Result: nothing silent that is a module export name, with two residuals recorded rather than buried — a named Census independently re-derived through the checker: 13 = 11 values + 2 types, barrel 3. The changeset lists exactly the ten retired names — no more, no fewer. ⛔ The one finding I am not landing as-isThe changeset still says, inside its ADR-0087 marker:
The branch already retired that framing from
⇒ The changeset went stale against the source in its own PR — and the changeset is the half that becomes the CHANGELOG. As written it reads as though the retirement was consumer-verified across the reachable world, when the repository the ruling's consumer reading actually rested on is NOT MEASURED after three refused channels. ⭐ This is not tidiness. I recorded publicly that proceeding on route A is acceptable precisely because the gap is declared NOT MEASURED rather than reported as zero. If the published CHANGELOG states it more strongly than that, the record stops matching the reasoning the decision rested on. 报错不是零, in the changeset exactly as in the source. Round 2 is scoped to the six findings only. ⛔ The barrel, the pin, the census, the export-walk fix and the retired-name list are settled and are not being redone. ⛔ New commits only — no amend, no rebase, no force-push. Also established, and worth keeping
Commit stream: 0 closing keywords, empty PR stays draft with Generated by Claude Code |
…h in the changeset The changeset is the half of this branch that becomes CHANGELOG.md, and it had gone stale against `src/console.ts` in its own PR. Its ADR-0087 marker still said, in full (reflowed here to fit a commit body; the blob carries it on one line, and joining these lines on single spaces reproduces it byte for byte): There is also nothing to prescribe: the re-derived consumer reading found no importer of any of the ten, in this repo or in the one sibling checkout reachable, and the channel that would reach one is the compiler (TS2305 on the retired name), which is more precise than a ledger line. Commit 76b1888 had already retired that framing from `src/console.ts` -- it removed "the one sibling checkout reachable from the implementing container" as "a claim about a container, unverifiable from anywhere else" and replaced it with a reading anyone can re-run, plus an explicit NOT MEASURED for `cloud`. The changeset was not carried along, so the artifact that ships read as though the retirement had been consumer-verified across the reachable world, while the source file beside it said the opposite about the one repository the ruling's consumer reading actually rested on. Nothing mechanical reads the sentence; the reason it matters is that the decision to proceed was recorded publicly as acceptable BECAUSE the gap is declared NOT MEASURED rather than reported as a zero. A changelog that states it more strongly than that no longer matches the reasoning the decision rested on. What the marker says now, at exactly the strength each half was measured: objectui -- a real ZERO, re-derived here at the pinned `.objectui-sha` (a472b07167a3, which is the sha this repository pins at every commit on this branch and at origin/main). The specifier `cli/console` does not occur in that tree; none of the ten occurs as an identifier, except CONSOLE_PATH twice, both inside comment prose in one browser test and neither an import. Its control: 545 lines of the same tree DO import from the `@objectstack/` scope, so the corpus is live and the zero is a reading, not a silence. cloud -- NOT MEASURED, which is not the same thing as zero. The code-search index does not cover it from this seat (0 hits, `incomplete_results: true`) and no checkout of it is reachable. Every channel tried refused. An unreachable repository never reads as "no consumers"; a refusal is not an absence. The channel that would actually reach a surprised consumer is the compiler, TS2305 naming the retired symbol at the import site -- which is why no ledger prescription is owed. The retired-name list is untouched: the same ten, byte-identical. ALSO RECORDED, not repaired. An independent contract review drove this pin's `declaredExports` walk over 38 export forms and found nothing silent that is a module export name, with three residuals. They are now written in that function's own docblock, where whoever changes it will read them, rather than left in a review comment that scrolls away: 1. A named default is attributed to its LOCAL name -- `export default function df` reports `df` where the module publishes `default`. Contrived on a barrel that re-exports by name only, and every in-package importer would fail the build first, but it is the silent class this pin exists to prevent, so the repair is written down with it. 2. Overloads produce a duplicate name, so an added overload reds the partition test with the export set unchanged -- a false red, but a loud one, which is the only direction this file may fail in. 3. `export as namespace UMD;` is skipped silently, the one exception to the docblock's otherwise absolute promise. The census stays right in effect because a UMD global is not a module export name; the exception is now named instead of waiting to be discovered. Fixing any of the three would re-open an instrument that review verified form by form, and would owe that 38-form probe again. This round changes no executable line: 44 added lines under `packages/`, every one a comment, none removed. One review finding is DECLINED as stated, because it is not true of this tree. It reported that objectui could not be checked at the pinned sha, `.objectui-sha` being 67dadd602a3 and absent from the local clone. `.objectui-sha` is a472b07167a39e55491109e864bb5a54027dcfbd at all five commits on this branch, at the merge base and at origin/main; 67dadd602a3 was the pin two bumps ago, superseded on 2026-09-04 and again on 2026-09-05, and it is not a valid object in the local objectui clone at all. That clone is checked out at exactly the pinned sha, so the removal-safety step AGENTS.md prescribes DID run at the pin. Its result is now recorded in `src/console.ts` as a fifth reading, marked for what makes it different from the container claim that was retired: a sha this repository itself pins is re-runnable by anyone who checks objectui out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
…nsole-public-barrel
Round 2 accepted — and ⛔ a correction to the control I praised
⛔ The correction: my "200-hit positive control" was a prefix artifactIn 5563196832 I wrote that Re-measured by me, read-only at the pinned ⇒ ⭐ That is precisely the failure I spend this session hunting: a control that cannot fail for the reason you care about. I did not merely miss it — I held it up as exemplary. The round-2 implementer caught it, declined to publish 200 as the control, and cited the sound one instead: 545 lines importing from the The zero itself survives — The changeset now states each reading at exactly its measured strength
That is the framing route A rests on, now in the artifact that becomes the CHANGELOG rather than only in the source. ⛔ Finding 5 declined — its premise was false, and that was measuredRound 2 did not simply disagree: Findings 2–4 were recorded in ⭐ Two gate readings worth keeping
Round 2 changed 0 non-comment lines under PR stays draft with Generated by Claude Code |
Landing record — carriers cleared, enqueued 01:29:48Z
Against the arm-precondition list in
⛔ The one precondition I could not satisfy, recorded rather than dressed upThe family's own classifier, ⇒ I took the substance through that channel — both carriers read directly, before and after clearing — but the scripted reading itself is NOT MEASURED, and I am not recording exit 3 as a pass. ⭐ A GATE NOBODY CAN RUN IS INDISTINGUISHABLE FROM A GATE THAT FINDS NOTHING (#13526). ⛔ My governed-surface derivation was wrong the first timeI first restated the boundary with That is a two-dot diff: it reports files where Re-derived with three-dot ( 7 files, all ⭐ Worth stating as a rule, because it fails in the dangerous direction: on a branch whose base has moved, a two-dot diff manufactures governed-surface hits out of other people's landed work. The boundary must be read three-dot. ⭐ Step ③ earned its place in the orderClearing the carrier re-triggered Commit stream the squash will concatenate, re-read at this head: 7 commits, 0 closing keywords, Generated by Claude Code |
Fixes #16046
What the ruling asked for
Director batch 60, option A. The
./consolesubpath stays and now points at anew public barrel that exports only the intended public face; the remaining
exports move behind it; the barrel gets a name-and-shape pin in the #15630
shape. Removing published exports is an ADR-0087 event even with zero measured
consumers, so the changeset lists every retired name and this PR carries
needs:contract-review(clause 2 is yes).The change
packages/cli/package.jsonpointed./consoleatdist/utils/console.js— aninternal module. Every one of its top-level exports was therefore public API,
and every export it gained afterwards became a permanent public contract on
landing, silently.
The subpath now points at
dist/console.js, built from a newpackages/cli/src/console.tsthat re-exports three names, by name, no star:resolveConsolePathhasConsoleDistcreateConsoleStaticPluginNothing is deleted.
utils/console.tsstill exports all thirteen and everyin-package caller still imports it directly. What the other ten lost is only
the ability to be named through a published specifier.
The export total, re-derived from the type surface
Not a
grep -c '^export'. The census comes from the TypeScript checker'sgetExportsOfModuleover the module symbol, which covers re-exports, starexports, enums, namespaces, default and export-assignment alike:
So the split is 3 public + 10 retired, and the two lists partition the module
exactly.
The delegated call: both excluded, and the reading behind it
The ruling admitted
decideConsoleMountandcreateRuntimeAssetsPluginonlyif an intended external caller exists. Both are EXCLUDED, on five readings:
packages/cli.neither of these two.
decideConsoleMount's own docblock scopes it toisDevand states that noproduction or cloud deployment can reach the refusal it exists to produce.
org:objectstack-aireturns hits for these names inthis repository only — zero in
objectui. Its control:repo:objectstack-ai/objectui consoleanswers 1,640 hits from the same indexin the same session, so objectui is genuinely indexed and its zero is a
measurement.
removal ships. Re-derived in round 2 at
.objectui-sha(a472b07167a3):cli/consoledoes not occur in objectui at that sha, and none of the tenoccurs as an identifier — except
CONSOLE_PATHtwice, both inside commentprose in one browser test, neither an import. Control: 545 lines of the same
tree DO import from the
@objectstack/scope.cloudis NOT MEASURED, which is not the same as zero. The same controlagainst it —
repo:objectstack-ai/cloud objectstack— answers 0 hits withincomplete_results: true: the index does not cover that repository from thisseat, and no checkout of it is reachable. An unreachable repository never reads
as "no consumers". For cloud the evidence is second-hand by construction: the
ledger, which names exactly the three, and the ruling that reads it the same
way. Cloud needs no change either way — its three imports keep their shapes,
which the shape pin asserts.
The pin was measured to discriminate, on both halves
A pin is worth what it reds on. Both halves were ablated: mutation proven on
disk by anchor count and blob hash,
packages/clirebuilt, and the mutationproven live in
dist/byscripts/ablation-dist-preflight.mjs, before anyverdict was read. Predictions were written before each run and both were met
exactly.
TS2578: Unused '@ts-expect-error'TS2344on the options-bag equality assertions; 13 stay greenThe shape half is the one that matters: the surface's leaves are identical and
the component moved. A names-only pin cannot see it.
The independent contract review re-drove both legs and added four more (a
type-only name leg, a second shape leg, a vacuity leg and an unrecognised-form
leg), each proven on disk and live in
dist/, and reached the same verdicts.Round 2 — the contract review's six findings
The review returned
VERDICT: PASSwith six non-blocking findings. The barrel,the pin, the census, the export-walk fix and the retired-name list are settled
and were not touched. Round 2 changed no executable line: 44 added lines
under
packages/, every one a comment, none removed, plus one paragraph in thechangeset.
1. FIXED — the changeset had gone stale against the source in its own PR.
Its ADR-0087 marker still said the consumer reading "found no importer of any
of the ten, in this repo or in the one sibling checkout reachable", framing the
retirement as consumer-verified across the reachable world.
src/console.tshad already retired that framing and declared
cloudNOT MEASURED. Thechangeset is the half that becomes CHANGELOG.md, and proceeding on route A was
recorded publicly as acceptable because the gap is declared NOT MEASURED
rather than reported as a zero — so a changelog stating it more strongly no
longer matches the reasoning the decision rested on. The marker now states
objectui as a real zero with its control,
cloudas NOT MEASURED with everychannel refused, and the compiler (
TS2305) as the channel that actuallyreaches a surprised consumer. The retired-name list is byte-identical: the same
ten.
2, 3, 4. RECORDED, not repaired — the export-walk residuals, now written in
declaredExports's own docblock where the next author will read them:export default function dfreports
df, notdefault) — contrived on a name-only barrel, but it is thesilent class this pin exists to prevent, so the repair is written down with it;
test with the export set unchanged — a false red, but a loud one;
export as namespace UMD;is skipped silently — the one exception to thedocblock's otherwise absolute promise, now named rather than left to be found.
Repairing any of them re-opens an instrument the review verified form by form
over 38 export forms, and owes that probe again. Recording beat fixing here.
5. DECLINED — the finding is not true of this tree, and the correction is in
the branch. It reported that objectui could not be checked at the pinned sha,
.objectui-shabeing67dadd602a3and absent from the local clone..objectui-shaisa472b07167a39e55491109e864bb5a54027dcfbdat all commits onthis branch, at the merge base and at
origin/main;67dadd602a3was the pintwo bumps ago, superseded on 2026-09-04 and again on 2026-09-05, and it is not
a valid object in the local objectui clone at all. That clone sits at exactly
the pinned sha, so the prescribed step DID run at the pin. Its result is
reading 5 above.
6. NOTED, no action — the reviewer's disclosure that their leg-N column
prediction was
64,4against tsc's64,1. Line, count and kind were right, andthe pin asserts diagnostics, never column numbers, so nothing in the tree is
falsified by it.
Verification
All at
3de25192891, after mergingorigin/mainin a second time.pnpm --filter '@objectstack/cli^...' buildthenpnpm --filter @objectstack/cli build— green.packages/qa/downstream-contractconsumer-specifier ledger: 12 passing.pnpm --filter @objectstack/cli typecheck— green. The pin test was confirmedpresent in the
tsconfig.test.jsonprogram by--listFiles.eslint . --no-inline-config: exit 0,6,246 files linted, 0 findings. Population and count read from eslint's own
--format jsonoutput, not estimated.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack,which reported STALE TREE until
origin/mainwas merged in and then derivedclean. Ran the union of the 68-family Reconciliation answer (taken from
--commands, not harvested from the prose — the harvest dropped theconvention block, which the tool warns about by name), the 37 artifact-roster
families and the 10 declared-WIDE families: 115 rows, 1 overlap,
114 unique commands. Exit codes captured before any pipe.
111 green, 3 NOT MEASURED, 0 failing.
check-partof-closing-keywordandcheck-single-claim-pathsprint NOTWIRED without PR context. The former was then re-run with this body and this
branch's real commit list supplied, and passes.
check:react-declaration-parityneeds objectui'ssdui.manifest.jsonand abrowser.
check:type-check-debtfirst exited 3 — its own PREREQUISITE NOT MET, anOOM caused by the
NODE_OPTIONSceiling the caller had set being tighterthan the 6,144 MB ceiling the gate itself pins. Re-run with headroom it is
green: 5 ledger entries re-measured, 55 raw errors, none above its recorded
number. Counted as measured, not as a pass.
outside the Reconciliation total and have no local invocation; CI runs them.
Commit messages, since the queue squashes
The merge queue builds the landed body from commit messages, not from this body.
Re-read at this head over the 7 commits this PR contributes: 0 closing
keywords, and the complete set of issue references in the commit bodies is still
the empty set. The card relation is declared once, here.
The first commit's body said "WIP — changeset and gate runs still to come."
That was true of its own tree and is false of this branch, so a later commit
quotes it and states what is true. Round 2's commit does the same for the
changeset sentence it corrects, quoting it reflowed and stating so, with the
rejoin verified byte-for-byte against
git show.