Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .changeset/olive-donkeys-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
'@objectstack/cli': minor
---

feat(cli): point `@objectstack/cli/console` at a public barrel with a name-and-shape pin

**BREAKING**: `@objectstack/cli/console` publishes three names instead of thirteen. Ten names it used to resolve no longer resolve through that subpath.

The subpath pointed straight at `dist/utils/console.js` — an internal module — and carried no surface pin of any kind, neither names nor shapes. Two assertions did exist and neither is one: `./console` was held among the declared `exports` keys, and the specifier was held to resolving from the packed tarball. Both answer *is the door open*; neither can answer *what is behind it*. So all thirteen of that module's top-level exports were public API, and every export it gained afterwards became a permanent public contract the moment it landed, silently.

The subpath stays open and now points at a dedicated barrel, `dist/console.js`, which re-exports by name (no star) exactly the three helpers the one ledgered out-of-repo consumer uses to mount the Console SPA:

- `resolveConsolePath`
- `hasConsoleDist`
- `createConsoleStaticPlugin`

Those three keep their existing shapes exactly, so a consumer importing only them compiles unchanged.

These ten are no longer reachable through `@objectstack/cli/console`:

- `CONSOLE_PATH`
- `ConsoleShaDrift`
- `DRIFT_OVERRIDE_ENV`
- `ResolveConsoleOptions`
- `createRuntimeAssetsPlugin`
- `decideConsoleMount`
- `detectConsoleShaDrift`
- `formatConsoleShaDriftRefusal`
- `formatConsoleShaDriftWarning`
- `isConsoleVersionCompatible`

Nothing was deleted. `utils/console.ts` still exports all thirteen and every in-package caller still imports it directly; what these ten lost is only the ability to be named through a published specifier. `ResolveConsoleOptions` in particular is still `resolveConsolePath`'s parameter type, so the options object a caller passes keeps working structurally — only the type's name is no longer importable from this subpath.

`decideConsoleMount` and `createRuntimeAssetsPlugin` were retired on a measurement rather than by default: every reference to either name in this repo is inside `packages/cli`, the consumer-specifier ledger names neither, and `decideConsoleMount`'s own docblock scopes it to `isDev` and states that no published install can reach the refusal it exists to produce.

`packages/cli/test/published-subpath-console.pin.test.ts` now holds the packed `.d.ts` to exactly the three names and their shapes, compiled by a real consumer outside the workspace, with a control per retired name. Re-admitting any of the ten is a deliberate, reviewed, `minor`-bumped edit to that barrel and that pin.

<!-- adr-0087: not-required (no-migration-prescription) The ADR-0087 ledger serves metadata upgraders: its entries are the data source for `objectstack migrate meta`, `spec-changes.json` and the generated upgrade guide. All ten names are ordinary TypeScript values and types on a published subpath with no metadata surface whatsoever — no Zod schema, no `packages/spec` declaration, no stored representation — so `objectstack migrate meta` has nothing to reach and no ledger entry could carry anything. There is also nothing to prescribe, and the consumer reading behind that is stated here at exactly the strength it was measured. In this repo: no importer of any of the ten outside `packages/cli` itself. In `objectui`: a real zero, re-derived at the pinned `.objectui-sha` — the specifier `cli/console` does not occur, and none of the ten occurs as an identifier except `CONSOLE_PATH`, twice, both inside comment prose and neither an import — against a positive control of 545 lines that do import from the `@objectstack/` scope, so the corpus is live and the zero is a reading rather than a silence. In `cloud`: NOT MEASURED, which is not the same thing as zero — the code-search index does not cover that repository from this seat, answering 0 hits with `incomplete_results: true`, and no checkout of it is reachable either. Every channel tried refused, and an unreachable repository never reads as "no consumers"; a refusal is not an absence. So for `cloud` the evidence stays second-hand by construction: the consumer-specifier ledger, which names exactly the three kept helpers, and the ruling behind this card, which reads it the same way. The channel that would actually reach a surprised consumer is the compiler (TS2305 naming the retired symbol at the import site), which is more precise than a ledger line. -->
2 changes: 1 addition & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ resolve is exactly this map — a subpath is added here on purpose, with a
| Subpath | What it is for |
|:---|:---|
| `@objectstack/cli` | The command classes `bin/run.js` loads — the oclif entry. |
| `@objectstack/cli/console` | Console SPA resolution helpers (`resolveConsolePath`, `hasConsoleDist`, `createConsoleStaticPlugin` and the drift guards), consumed by cloud's `objectos-runtime` node server to mount the Console. |
| `@objectstack/cli/console` | Exactly three Console SPA mounting helpers `resolveConsolePath`, `hasConsoleDist`, `createConsoleStaticPlugin` consumed by cloud's `objectos-runtime` node server to mount the Console. The drift guards and the rest of `utils/console.ts` are **not** on this subpath (#16046). |
| `@objectstack/cli/hook-body` | The hook-body extractor `os build` and `os lint` apply, for an app harness that must run the **same** body-only lowering the build ships (below). |
| `@objectstack/cli/package.json` | The manifest itself, for the ordinary tooling idiom of reading a dependency's own version. |

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"default": "./dist/index.js"
},
"./console": {
"types": "./dist/utils/console.d.ts",
"default": "./dist/utils/console.js"
"types": "./dist/console.d.ts",
"default": "./dist/console.js"
},
"./hook-body": {
"types": "./dist/hook-body.d.ts",
Expand Down
104 changes: 104 additions & 0 deletions packages/cli/src/console.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

/**
* `@objectstack/cli/console` — the public entry for mounting the Console SPA.
*
* ## Why this entry exists (#16046)
*
* Until this file landed, `./console` pointed its subpath straight at
* `dist/utils/console.js` — an INTERNAL module with 13 top-level exports and no
* surface pin of any kind, neither names nor shapes. Two assertions did exist
* (re-measured at `0ea5f9d9f79`): `./console` is among the declared `exports`
* KEYS, and the specifier RESOLVES from the packed tarball. Both answer *is the
* door open*; neither can answer *what is behind it*. So
* every export that module gained was published the moment it landed: an
* accidental `export *` widening, or a symbol added for an internal reason,
* became public API silently, with nothing that would notice.
*
* That is a strictly weaker position than the one #15630 repaired on
* `./hook-body`, where a pin at least held the ratified names. This file is the
* same remedy applied one door over: the subpath stays — cloud's
* `objectos-runtime` node server depends on it, and sealing it would be #13662
* and #15325 a third time — but it now points at a barrel that re-exports the
* intended public face BY NAME, no star.
*
* `test/published-subpath-console.pin.test.ts` holds the packed `.d.ts` to
* exactly these names AND their shapes, so a widening is a deliberate,
* reviewed, `minor`-bumped act rather than a side effect of a refactor.
*
* ⛔ Do not add to this list to make something convenient reachable. A new name
* here is a new public contract on a published package.
*
* ## What the public face is, and why it is these three
*
* The one ledgered out-of-repo consumer
* (`packages/qa/downstream-contract/consumer-specifiers.ledger.json`) mounts the
* Console SPA through exactly `resolveConsolePath` / `hasConsoleDist` /
* `createConsoleStaticPlugin`. Those three are the face.
*
* ### The two the ruling left to a measurement, and how it came out
*
* The #16046 ruling admitted `decideConsoleMount` and `createRuntimeAssetsPlugin`
* "only if the implementer finds an intended external caller", stating that
* `commands/serve.ts`'s own use does not count because it is in-package. Both
* are EXCLUDED, on four readings taken at `0ea5f9d9f79`:
*
* 1. Every reference to either name in this repo is inside `packages/cli/`
* — `commands/serve.ts`, `utils/console.ts` itself, and two of this
* package's own tests — plus one historical `CHANGELOG.md` line. No
* caller outside the package exists to be intended.
* 2. The consumer-specifier ledger — this repo's owned, shrink-only record of
* what out-of-repo consumers import, and the only place such a claim is
* written down at all — names three functions for this specifier and
* neither of these two.
* 3. `decideConsoleMount`'s own docblock scopes it to `isDev` only and says
* "Published installs carry no pin, so no production or cloud deployment
* can reach the refusal." The sole ledgered consumer IS a cloud
* deployment, so the source says the external caller cannot reach the
* behaviour this function exists to produce.
* 4. GitHub code search over `org:objectstack-ai` (2026-09-06) returns, for
* `@objectstack/cli/console` and for the two names themselves, hits in
* THIS repository only — zero in `objectui`. That zero counts only
* because it carries a control: `repo:objectstack-ai/objectui console`
* answers 1,640 hits from the same index in the same session, so objectui
* is genuinely indexed and its zero is a measurement rather than a silence.
* 5. The PINNED sibling checkout — the reading AGENTS.md prescribes before a
* removal ships, taken at `.objectui-sha` (`a472b07167a3`) rather than at
* whatever that checkout's HEAD happens to be. The specifier
* `cli/console` does not occur in objectui at that sha, and none of the
* ten occurs as an identifier, except `CONSOLE_PATH` twice — both inside
* comment prose in one browser test, neither an import. Its control: 545
* lines of that same tree DO import from the `@objectstack/` scope, so the
* corpus is live and the zero is a reading. Note what this is NOT: an
* earlier draft of this docblock justified the exclusion on "the one
* sibling checkout reachable from the implementing container", and that
* was retired for being unverifiable from anywhere else. A sha this
* repository itself pins is re-runnable by anyone who checks objectui out.
*
* ⚠️ `cloud` is NOT MEASURED, which is a different thing from zero. The same
* control run against it — `repo:objectstack-ai/cloud objectstack` — answers 0
* hits with `incomplete_results: true`: the index does not cover that
* repository from this seat, and no checkout of it is reachable either. An
* unreachable repository never reads as "no consumers". So for `cloud` the
* evidence is second-hand BY CONSTRUCTION — the consumer-specifier ledger,
* which names exactly the three, and the #16046 ruling that reads it the same
* way. If a consumer of either name ever surfaces, the remedy is
* the one #13123's body prescribes and #13662 applied: re-open the name here
* deliberately, with a changeset, and ledger the consumer. ⛔ Not a deep
* `dist/` import, and not a local reimplementation.
*
* ## What retiring the other names does and does not do
*
* Nothing is deleted: `utils/console.ts` keeps all 13 exports and every
* in-package caller keeps importing it directly. What changes is only which of
* them a PUBLISHED specifier can name.
*
* One consequence is worth stating because it is invisible from the export list.
* `ResolveConsoleOptions` is retired, but it is still `resolveConsolePath`'s
* parameter type, so a consumer keeps passing the same options object — the
* shape stays reachable STRUCTURALLY through the signature — and loses only the
* ability to NAME the type through this subpath. The pin asserts both halves, so
* neither can drift silently.
*/

export { resolveConsolePath, hasConsoleDist, createConsoleStaticPlugin } from './utils/console.js';
16 changes: 9 additions & 7 deletions packages/cli/src/hook-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
*
* ## Why a dedicated file and not the internal module itself
*
* `./console` points its subpath straight at `dist/utils/console.js`, so every
* export that module ever gains is public the moment it lands. The card asks
* for four names, and that is what this file re-exports — by name, no star. An
* export `extract-hook-body.ts` grows tomorrow is NOT public until someone
* edits this list, and `test/published-subpath-hook-body.pin.test.ts` holds
* the packed `.d.ts` to exactly these four so the widening is a deliberate,
* reviewed, `minor`-bumped act rather than a side effect of a refactor.
* `./console` used to point its subpath straight at `dist/utils/console.js`, so
* every export that module gained was public the moment it landed — the defect
* #16046 then repaired by giving that subpath a barrel of its own, in the shape
* of this one. The card asks for four names, and that is what this file
* re-exports — by name, no star. An export `extract-hook-body.ts` grows
* tomorrow is NOT public until someone edits this list, and
* `test/published-subpath-hook-body.pin.test.ts` holds the packed `.d.ts` to
* exactly these four so the widening is a deliberate, reviewed,
* `minor`-bumped act rather than a side effect of a refactor.
*
* ⛔ Do not add to this list to make something convenient reachable. A new
* name here is a new public contract on a published package.
Expand Down
11 changes: 10 additions & 1 deletion packages/cli/src/utils/console.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

/**
* Console UI Integration Utilities
* Console UI Integration Utilities — an INTERNAL module (#16046).
*
* ⚠️ This module is not a published entry point. Until #16046 the `./console`
* subpath pointed straight at this file's build output, so all 13 of its
* top-level exports were public API and any export it gained became public on
* landing. The published face is now `src/console.ts`, which re-exports three
* of them by name; the other ten are reachable only from inside this package.
* Adding an export here no longer publishes anything, and ⛔ moving one into
* the public face means editing that barrel and its pin
* (`test/published-subpath-console.pin.test.ts`) on purpose, with a changeset.
*
* Mirrors `studio.ts` / `account.ts` but for the opinionated, fork-ready
* runtime console. The Console SPA is mounted at `/_console/` by every
Expand Down
Loading
Loading