Repository navigation
feat(cli): ratify ./console as a public subpath export — the out-of-repo consumer #13123 predicted - #13662
Merged
Conversation
…-repo consumer #13123 predicted #13123 sealed the CLI behind an `exports` map, and its own body names the remedy for a consumer that was reading `dist/` paths: ratify the subpath as public surface rather than keep the deep read. That consumer exists — cloud's `objectos-runtime` node server mounts the Console SPA through the CLI's self-contained static-serve util (`resolveConsolePath` / `hasConsoleDist` / `createConsoleStaticPlugin`, fs/path only, no oclif) via what was `@objectstack/cli/dist/utils/console.js`. Under the sealed map that specifier throws ERR_PACKAGE_PATH_NOT_EXPORTED at BOTH layers: tsup's DTS build refuses (TS2307), and at runtime the consumer's defensive catch silently degrades every boot to API-only — a working deployment loses its console with nothing but a warn line. `./console` maps to exactly the module that was being consumed (`dist/utils/console.{js,d.ts}`), so the surface grows by one deliberate door, not back to "everything under dist/". check:published-files' GATED invariant stays green (the map still names real entries), and the census floor is untouched. Gates run at this head: full 71-package build, full-repo lint, check:published-files, check-empty-changeset — all exit 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hotlong
enabled auto-merge
August 31, 2026 05:38
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 4, 2026
baozhoutao
pushed a commit
that referenced
this pull request
Sep 5, 2026
…e exports maps An `exports` map is a PACKAGING contract, and inside this monorepo nothing is sealed: every in-repo consumer reaches any file through a relative import, a vitest alias or a `paths` entry. So the repo could not observe a sealing regression at all, by construction — twice the seal 17.3.0 introduced (#13123) was found after publish, by the consumer, during an upgrade (#13662, #15325). `consumer-specifiers.ledger.json` imports that knowledge from outside: the bare specifiers named out-of-repo consumers import, each with the consumer, a date and the card that ratified it. The suite packs each ledgered package with `pnpm pack`, unpacks it into a throwaway `node_modules` outside the workspace, and resolves every specifier from there with Node's real resolver under both the `require` and `import` conditions — failing with a message that names the specifier AND the consumer it breaks. Option A of #15589 only. Option B (a changeset-gated seal declaration in `check:published-files`) is deliberately not here. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
This was referenced Sep 5, 2026
os-steve
pushed a commit
that referenced
this pull request
Sep 5, 2026
…to an already-published package GATED asserts an `exports` map exists; it is silent on the TRANSITION, and the transition is where the damage is. 17.3.0 added a map to `@objectstack/cli`, which had published without one, and every deep path a consumer reached through `dist/` stopped resolving at once -- found after publish, by the consumer, twice (#13662, #15325). ANNOUNCED is the second half of #15589, ruled B1 on #15715: a base-vs-HEAD comparison, the same shape `check-adr-0087-registration --base` already uses, and deliberately not `git log -S` archaeology. A `package.json` absent at the merge base -- or present but private/unnamed there -- is born-sealed and passes, because it has no published predecessor whose consumers could be deep-importing it. Measured when #15715 was filed: 69 publishable packages declare a map from 51 introducing commits, but 56 were born with it and only 13 retrofitted, from 7 commits. Gating all 51 would demand a consumer note 44 times from packages that had no consumers, every one of them on a new-package PR -- and a gate answered by boilerplate 44 times out of 51 has stopped being read by the 7th time it matters. What the announcement must say is checked against the head map rather than for a form of words: a `minor`-or-greater changeset on the package whose body names a deep specifier the new map does NOT resolve. Naming a path that still resolves tells a consumer nothing and does not satisfy it. Absence is never a pass (#4690). Every way the base read can fail -- no `origin/main`, no merge base, an unreadable `.changeset/` -- makes every package look born-sealed and the clause vacuously green, so each is a refusal that names itself, and the read carries its own census control (BASE_READ_FLOOR) exactly as GATED carries EXPORTS_CENSUS_FLOOR. In CI the base comes from `Lint & Repo Gates` checking out at `fetch-depth: 0`. Claude-Session: https://claude.ai/code/session_01PU9zBGbH2s2ZtxSyu963M3 Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 9, 2026
…hed exports maps (objectstack-ai#15717) * wip(qa): inherited draft of the consumer-specifier ledger Committed as-is from a dead session's worktree, before verification, so a container restart cannot lose it. Verified and rewritten in the commits that follow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * test(qa): ledger the out-of-repo consumer specifiers, held against the exports maps An `exports` map is a PACKAGING contract, and inside this monorepo nothing is sealed: every in-repo consumer reaches any file through a relative import, a vitest alias or a `paths` entry. So the repo could not observe a sealing regression at all, by construction — twice the seal 17.3.0 introduced (objectstack-ai#13123) was found after publish, by the consumer, during an upgrade (objectstack-ai#13662, objectstack-ai#15325). `consumer-specifiers.ledger.json` imports that knowledge from outside: the bare specifiers named out-of-repo consumers import, each with the consumer, a date and the card that ratified it. The suite packs each ledgered package with `pnpm pack`, unpacks it into a throwaway `node_modules` outside the workspace, and resolves every specifier from there with Node's real resolver under both the `require` and `import` conditions — failing with a message that names the specifier AND the consumer it breaks. Option A of objectstack-ai#15589 only. Option B (a changeset-gated seal declaration in `check:published-files`) is deliberately not here. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * test(qa): shrink SEALED_TODAY to empty — objectstack-ai#15611 landed and opened both doors The set-equality assertion did exactly what its header said it would. objectstack-ai#15611 ratified `./hook-body` and `./package.json` and merged while this branch was in flight; merging main turned the suite red naming the two lines to delete. They are deleted, so the ledger of losses has reached its floor: every specifier a named out-of-repo consumer imports now resolves from a packed tarball. The empty table is KEPT rather than removed. "Every ledgered specifier resolves" is the contract; "the sealed set is exactly empty" is the ratchet — the line an author has to edit, past a ⛔, to make a red green by recording a break instead of repairing it. Also separates two diagnoses the exports map cannot tell apart on its own: a specifier that fails with ERR_PACKAGE_PATH_NOT_EXPORTED is SEALED (the subject of this file), while one failing MODULE_NOT_FOUND is a declared door leading nowhere — the package is unbuilt, or `files` does not ship the path `exports` names. Now that all three doors are open, an unbuilt tree would otherwise have read as "hotcrm is broken". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --------- Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 9, 2026
…onto an already-published package (objectstack-ai#15715) (objectstack-ai#16082) * feat(devx): check:published-files gates an exports map retrofitted onto an already-published package GATED asserts an `exports` map exists; it is silent on the TRANSITION, and the transition is where the damage is. 17.3.0 added a map to `@objectstack/cli`, which had published without one, and every deep path a consumer reached through `dist/` stopped resolving at once -- found after publish, by the consumer, twice (objectstack-ai#13662, objectstack-ai#15325). ANNOUNCED is the second half of objectstack-ai#15589, ruled B1 on objectstack-ai#15715: a base-vs-HEAD comparison, the same shape `check-adr-0087-registration --base` already uses, and deliberately not `git log -S` archaeology. A `package.json` absent at the merge base -- or present but private/unnamed there -- is born-sealed and passes, because it has no published predecessor whose consumers could be deep-importing it. Measured when objectstack-ai#15715 was filed: 69 publishable packages declare a map from 51 introducing commits, but 56 were born with it and only 13 retrofitted, from 7 commits. Gating all 51 would demand a consumer note 44 times from packages that had no consumers, every one of them on a new-package PR -- and a gate answered by boilerplate 44 times out of 51 has stopped being read by the 7th time it matters. What the announcement must say is checked against the head map rather than for a form of words: a `minor`-or-greater changeset on the package whose body names a deep specifier the new map does NOT resolve. Naming a path that still resolves tells a consumer nothing and does not satisfy it. Absence is never a pass (objectstack-ai#4690). Every way the base read can fail -- no `origin/main`, no merge base, an unreadable `.changeset/` -- makes every package look born-sealed and the clause vacuously green, so each is a refusal that names itself, and the read carries its own census control (BASE_READ_FLOOR) exactly as GATED carries EXPORTS_CENSUS_FLOOR. In CI the base comes from `Lint & Repo Gates` checking out at `fetch-depth: 0`. Claude-Session: https://claude.ai/code/session_01PU9zBGbH2s2ZtxSyu963M3 Co-authored-by: Claude <noreply@anthropic.com> * fix(devx): only the changesets a change introduces can announce what it narrows Measured while ablating the clause: a real `./console` subpath removal went GREEN because an unrelated changeset already on main contained the string `./console` in a sentence about a different release. The pending stock is ~1300 files of prose about these same packages, so "some changeset mentions this path" is satisfied by accident constantly -- the boilerplate-answered gate this clause exists to avoid, reached from the other direction. The subject is now the diff: a changeset absent at the merge base, or one whose text differs from its base copy. The removal half also reads the changeset BODY rather than the whole file, since the frontmatter names the package on every changeset and would otherwise let the bump line answer the note. Nothing in the file is exported any more either. `check:entry-guard`'s second rule is that a `scripts/**` file exporting a binding can be imported for it, whereupon this gate's top-level dispatch -- and its `process.exit` -- runs inside the importer; the self-test is in the same module and needs no exports. Claude-Session: https://claude.ai/code/session_01PU9zBGbH2s2ZtxSyu963M3 Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
One deliberate door in the CLI's
exportsmap:"./console"→dist/utils/console.{js,d.ts}— the self-contained static-serve util (resolveConsolePath/hasConsoleDist/createConsoleStaticPlugin+ drift-guard helpers; fs/path only, no oclif).Why
#13123 sealed the CLI behind an
exportsmap and its own body names this exact remedy for an out-of-repo consumer: ratify the subpath as public surface rather than readdist/paths. That consumer exists — cloud'sobjectos-runtimenode server mounts the Console SPA through this util, formerly via@objectstack/cli/dist/utils/console.js. Under the sealed map that specifier now fails at both layers:ERR_PACKAGE_PATH_NOT_EXPORTED, which the consumer's defensive catch turns into a silent API-only boot — a working deployment loses its console with nothing but a warn line.Duplicating the util in cloud was rejected: it is 654 lines carrying the objectui-pin drift guards (#7752) and the mount-decision policy — a fork would drift by construction.
Scope
The surface grows by ONE named door, not back to "everything under
dist/".check:published-files' GATED invariant stays green (the map names real entries); census floor untouched. Changeset:@objectstack/climinor.Gates at this head
Full 71-package build, full-repo lint,
check:published-files,check-empty-changeset— all exit 0. Cloud-side consumer verified against a locally patched checkout of this exact map: runtime resolution returns all three functions, objectos-runtime DTS builds green, and cloud's five harness suites pass.🤖 Generated with Claude Code