Skip to content

fix(cli): Console-dist warning names the remedy for where it runs; README states the Console step and command costs - #22224

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-22155-readme-contributor-path
Oct 8, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-22155-readme-contributor-path

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22155
Fixes #22164
Clause-②: no

One PR for the two-card bundle that edits the same README section. #22156 is serial after this PR and is not addressed here: its README items (the first curl, the pnpm floors) and CONTRIBUTING.md are untouched.

What changes

README, "Hack on the framework"

This covers #22164 and the README half of #22155.

  • The command block gains pnpm objectui:build as the Console step, between pnpm build and pnpm dev.

  • A paragraph under the block says what the step does:

    • it builds objectui at the commit pinned in .objectui-sha into the gitignored packages/console/dist;
    • it builds from a ../objectui checkout when one exists, otherwise from a shallow clone into .cache/;
    • it installs objectui's dependencies either way, so it needs network;
    • without it, pnpm dev still serves the REST API, but no Console;
    • rerun it when .objectui-sha moves.
      This matches content/docs/getting-started/examples.mdx and scripts/build-console.sh, both read on origin/main c6fe02d7ac.
  • A table gives each command's measured cost, quoted as the cards measured it on a fresh clone (4-vCPU container, Node 22.22, pnpm 10.31):

    Command Time
    pnpm install 16 s
    pnpm build 6 m 54 s (72 turbo tasks)
    pnpm objectui:build 10 m 36 s
    pnpm test 54 m 03 s (135 turbo tasks)

    These were not re-measured here.

  • For a contributor's own change, the section now points to pnpm turbo run test --affected and pnpm --filter @objectstack/PKG test. A note says that --affected compares the branch with the local main.

  • The pnpm install line's text is unchanged; only its comment column moved.

CLI boot warning

This is the warning half of #22155, cross-lane to domain:cli as declared on the claim.

packages/cli/src/commands/serve.ts used to print the same remedy wherever it ran: install @object-ui/console (already built) or run pnpm --filter @object-ui/console build in the objectui workspace. It now calls formatConsoleDistMissingWarning(consolePath), which is new in packages/cli/src/utils/console.ts beside the drift formatters. The function picks the remedy from one signal: whether the nearest package.json above the resolved console package declares an objectui:build script.

  • Framework repo (the root manifest declares the script). The warning reads Console dist not found at packages/console/dist, says pnpm build does not produce it, and says to build it with pnpm objectui:build in the root directory, which it names. The directory matters because pnpm dev runs in examples/app-showcase, where the root script does not resolve.
  • Anywhere else. The warning gives the dist path and says the Console ships prebuilt in @objectstack/console, a dependency of @objectstack/cli, to reinstall at the CLI's version. A scaffolded project depends on @objectstack/cli (packages/create-objectstack/src/templates/blank/package.json), and the CLI depends on @objectstack/console (packages/cli/package.json). The resolver reads @objectstack/console only. An installed @object-ui/console is never consulted.

Why this signal (Zone 2, item 3):

  • The script's presence is exactly the claim the message makes, so the command is named only where it exists.
  • The .objectui-sha pin that the drift guard keys on would also match another repository that carries a pin without the script.
  • A layout check such as packages/console or scripts/build-console.sh couples the CLI to this repo's file tree.
  • The walk stops at the nearest manifest. A project created inside a framework checkout therefore gets the install remedy for the console it actually resolved. The pnpm store layout's intermediate directories carry no manifest, so the walk reaches the project's own.

Only the text changes; mount and refuse decisions are untouched. There is a patch changeset for @objectstack/cli.

The new export is internal. test/published-subpath-console.pin.test.ts partitions every export of utils/console.ts into public and not-public lists, and it went red until the name was recorded there as INTERNAL_SINCE_RETIREMENT. The two "exactly three names" checks still hold it off the published ./console subpath. The helper's build-root walk is not exported.

Not done here

Verification

All runs below were on 0ef75bd7d9 unless noted.

  • Unit test, both branches with controls (packages/cli/test/console-resolve.test.ts, four new cases):

    • framework layout: names pnpm objectui:build and the root;
    • control: the same layout without the script gets the install remedy;
    • installed CLI, hoisted and pnpm-store layouts: names @objectstack/console;
    • a project inside a framework checkout gets the install remedy. Its control removes the project's manifest, and the walk then reaches the outer one and names the build.

    Every case asserts that @object-ui/console is absent.

  • Ablations, each landed on disk via scripts/ablation-replace.mjs and restored with blob equal to HEAD and an empty git diff HEAD:

    • the signal forced to null: 2 of 4 red (the framework case and the stop-rule control);
    • the walk made to keep climbing past the nearest manifest: 1 of 4 red (the stop-rule case).
  • Live boot, framework context: pnpm dev -- --fresh -p 38592 from this worktree, with no packages/console/dist and no ../objectui beside it. The boot printed:

    ⚠ Console dist not found at packages/console/dist — `pnpm build` does not produce it. Build it with `pnpm objectui:build` in /home/user/objectstack-issue-22155 (uses a ../objectui checkout if present, else clones objectui at the pinned commit — needs network).
    

    /api/v1/health answered 200 and /_console/ answered 404. The non-framework branch was verified by the unit test only; no scaffolded project was booted.

  • --affected, verified with pnpm turbo run test --affected --dry-run=json (turbo 2.11.5):

    • TURBO_SCM_BASE=origin/main before the change: 0 packages;
    • after the change: 8 packages, namely //, @objectstack/cli and its six dependents dogfood, downstream-contract and example-{crm,multi-package,showcase,todo};
    • against this checkout's stale local main (9cc2c7954a, 108 commits behind): 81 packages. That is why the README says a stale main widens the set.
  • @objectstack/cli:

    • build: exit 0;
    • typecheck, including check:test-typecheck: exit 0;
    • vitest run --project unit: 263 files and 3878 tests passed;
    • --project integration, run locally because serve.ts is on the boot path: 93 files, 897 tests passed and 2 skipped.
  • Gates: node scripts/pm/dispatch-gates.mjs --commands over this diff derived 68 commands. That is the dispatch's 53 plus 15 that the changeset and test files bring in. All 68 exited 0. --ran reconciliation: 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN.

  • Lint, a proven narrowing: eslint --no-inline-config --format json on the 4 changed TS files reported 4 files, 0 errors and 0 warnings. --print-config shows each file is in the config's population. eslint.config.mjs enables no type-aware linting (no parserOptions.project or projectService), so this diff cannot move a verdict on an untouched file. The repo-wide pnpm lint is left to CI.

Acceptance notes

  • os serve --help still describes --[no-]ui as Enable the bundled Console portal at /_console/ when @object-ui/console is installed. That is the same wrong package name, at serve.ts:1198 and mirrored in packages/cli/README.md:244. It is outside this claim's file surface, so it is reported to the PM rather than changed here.
  • packages/spec/scripts/publish-smoke-boot-failure.test.ts and a comment in scripts/publish-smoke.sh quote the old warning line. Those quotes are verbatim records of past CI runs, kept unchanged on purpose. The smoke's boot-failure predicate does not key on this line, and neither does the new wording.

Generated by Claude Code

claude added 2 commits October 8, 2026 05:40
…ADME states the Console step and each command's cost

The "Console dist not found" boot warning named @object-ui/console and an
objectui-workspace filter in every context. It now asks the nearest
package.json above the resolved console package: one that declares
`objectui:build` (the framework repo) gets `pnpm objectui:build` and the
directory to run it in; anywhere else gets the @objectstack/console install.

README "Hack on the framework" adds `pnpm objectui:build` as the Console
step, the measured cost of install/build/objectui:build/test, and the
`pnpm turbo run test --affected` path for a contributor's own change.

Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw
Co-authored-by: Claude <noreply@anthropic.com>
…e one new export in the ./console partition pin

The ./console subpath pin partitions every export of utils/console.ts into
public and not-public lists, so the new internal export is recorded there
(the two "exactly three names" checks keep it off the published subpath).

Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 4 documentable anchor(s).

12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/deployment/self-hosting.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/deployment/tenancy-modes.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/deployment/troubleshooting.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/getting-started/examples.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/getting-started/your-first-project.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/plugins/development.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/plugins/index.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/protocol/kernel/index.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/protocol/kernel/plugin-spec.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/protocol/objectql/schema.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/upgrading.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-3.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))
  • content/docs/releases/v17/17-4.mdx (via package.json (literal, a string literal in findConsoleBuildRoot))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: os serve (command, 31 pages)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 28 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 3b493184e1e395549d80723e267d2ff80ce33da4 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3788587880472349aea61099ba9fdcaa5de40422 — the merge of head 0ef75bd7d96b5d91b96e669f0617094947a1cfb0 into base 3b493184e1e395549d80723e267d2ff80ce33da4, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3788587880472349aea61099ba9fdcaa5de40422 && git checkout 3788587880472349aea61099ba9fdcaa5de40422
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3b493184e1e395549d80723e267d2ff80ce33da4 0ef75bd7d96b5d91b96e669f0617094947a1cfb0 && git checkout -B drift-repro 3b493184e1e395549d80723e267d2ff80ce33da4 && git merge --no-ff 0ef75bd7d96b5d91b96e669f0617094947a1cfb0

node scripts/docs-audit/affected-docs.mjs --json 3b493184e1e395549d80723e267d2ff80ce33da4

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 3b493184e1e395549d80723e267d2ff80ce33da4 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 8, 2026 07:44
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 8, 2026 07:44
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit 5accac4 Oct 8, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22155-readme-contributor-path branch October 8, 2026 09:01
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 9, 2026
…real showcase run (objectstack-ai#22241)

Fixes objectstack-ai#22232
Clause-②: no

Part A of the card: the README now opens on a thirty-second GIF recorded
from a really running app, rendered by a committed script. Part B
(repository topics and the About line) is the maintainer's click on the
repository settings page and is not in this PR.

## What changed

| File | Change |
|:---|:---|
| `docs/screenshots/readme-demo.gif` | new: 1200 x 675, 43 frames, 29.88
s, loops forever, **497,614 bytes** |
| `docs/screenshots/render-readme-demo.mjs` | new: the generator (boots
the app, makes the calls, screenshots the Console, renders the frames,
assembles the GIF) |
| `README.md` | top only: the GIF replaces the embedded cover image
under the chip row, a one-line caption names the three beats, the
"Watch: ObjectStack in 90 Seconds" link stays as text under it.
`docs/screenshots/hero-cover-dark.png` is byte-untouched (the docs
site's og:image). The architecture diagram and everything below it are
unchanged. |

No `packages/**`, `apps/docs`, `content/**`, `CHANGELOG.md` or
`package.json` change; nothing publishes.

## The recording

Example and object: `examples/app-showcase`, object `showcase_team`
(three fields, so the whole typed definition fits one frame without
scrolling).

1. **One definition, and the app runs.** The Team object exactly as
`examples/app-showcase/src/data/objects/team.object.ts` declares it
(comment lines omitted; the frame says so), then `pnpm dev:showcase --
--fresh -p PORT` booting it on a throwaway database, then the Console's
Team list (2 rows).
2. **An agent creates a record over MCP.** A JSON-RPC `tools/call` of
`create_record` against the running app's `/api/v1/mcp`, sent with the
dev admin's API key; the real response; then the Console list with the
new "Data Guild" row (3 rows) and its record page reading "Created by
Dev Admin".
3. **A read-only identity is refused.** The byte-identical call sent
with the Auditor persona's API key; the real refusal; then the Console
list, unchanged (3 rows).

Closing frame: "The ontology is the software." and the four chips
Executable · AI-writable · Agent-operable · You own it.

Measured in the run that rendered the committed GIF (port 39497):
`showcase_team` went 2 → 3 on the admin's call and stayed 3 on the
refused call (read back over REST after each call).

- Admin's call: `200`, record `QZu-Fcgx29Po-Sfn` "Data Guild",
`created_by` = the admin's user id.
- Read-only call, the real response bytes:

```json
{"result":{"content":[{"type":"text","text":"You do not have permission to perform this action. Contact your administrator if you need access."}],"isError":true},"jsonrpc":"2.0","id":1}
```

### Keys and identities

- Both keys are minted through the documented `POST /api/v1/keys`
(content/docs/ai/connect-mcp.mdx, "Headless: API keys"), each from a
session opened with `POST /api/v1/auth/sign-in/email`.
- Writer: the dev admin that `--fresh` seeds (`admin@objectos.ai`).
- Read-only identity: the showcase's own Auditor persona,
`auditor.demo@example.com` ("Ada Auditor (demo)"), which
`examples/app-showcase/src/security/seed-approval-demo.ts` provisions on
every dev boot. It holds only the `auditor` position, bound to
`showcase_auditor` (no Team grant), plus the member baseline
`showcase_member_default`, which grants `showcase_team` read and no
create. No code was changed to get a refusal. Before the GIF calls it
"read-only", the script proves it can read: its `query_records` on
`showcase_team` returned both rows.

### Honesty rules, enforced by the script rather than trusted

- Every browser frame is a screenshot of the real Console against the
app that run booted; the only additions are an outline around the new
row and around the record count.
- The request panel shows the bytes sent: the body is sent
pretty-printed, exactly as drawn; the API key is masked after its public
prefix.
- The response panel shows the bytes received, with one re-layout for
legibility: the escapes inside `result.content[0].text` are drawn as the
characters they encode.
- Terminal lines are lines that run's dev server printed, picked by
pattern in order, with a dim ellipsis at each gap. A pattern that stops
matching is a refusal (exit 5), not a stand-in line.
- No assistant chat transcript, no model identifier, no record the run
did not create; the caller is "an agent" / "any MCP client".
- The script also refuses (exit 5) when the admin's call does not create
the row, when the read-only identity cannot read the object, when its
create is NOT refused, or when the row count moves on the refusal; and
(exit 6) when the GIF is not 1200 px wide, runs outside 20-35 s, or
exceeds 4,000,000 bytes.

## Re-render

```bash
node docs/screenshots/render-readme-demo.mjs                 # rewrite the committed GIF
node docs/screenshots/render-readme-demo.mjs --out /tmp/demo  # preview: GIF, every frame and story.json, nothing committed touched
```

Prerequisites are the ones `pnpm dev` checks itself: a built workspace
(`pnpm build`) and a built Console (`pnpm objectui:build`), plus
`ffmpeg` on PATH. Playwright's Chromium is resolved from
`examples/app-showcase` (preinstalled binary under
`PLAYWRIGHT_BROWSERS_PATH`, never `playwright install`), `sharp` from
the docs app's Next dependency (it verifies screenshot and GIF
dimensions and duration), and ffmpeg assembles the GIF in two passes
(palettegen, then paletteuse with bayer dithering). Four end-to-end runs
in this container produced the same beats; byte-identity is not a goal
(ids, timestamps, port).

**Fonts:** the frames the script draws use Inter and IBM Plex Mono from
Google Fonts, the hero cover's pair; all eight faces loaded in the
committed render. Without them the script refuses to write the committed
GIF (exit 3), as `render-hero-cover.mjs` does; `--out` previews the
system fallback (Liberation Sans / DejaVu Sans Mono). The Console
screenshots carry the Console's own fonts. No font file is committed.

## Verification

Run on the final head `8336490e` (change set: the three files above, 662
changed lines, the binary counted 0).

- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 13 families. All 13 exited 0:
check-ci-filter-parity, check-closing-keyword-parity (and its
`--self-test`), check-comment-mask-corpus, `@objectstack/lint`
check:doc-formula-expressions, check:cross-package-test-inputs,
check:doc-anchors, check:doc-authoring, check:driver-memory-census,
check:gitlink-declared, check:nul-bytes, check:refd-timer-probe,
check:watch-hint-literal. `--ran` reconciliation: "13 derived, 13 run, 0
NOT-MEASURED, 0 UNRUN".
- `pnpm check:published-readme-links` exited 0: "221 outbound link(s)
across 101 published markdown file(s) ... 144/144 relative target(s)
found in the tree."
- Narrowed eslint, spelled as `pnpm lint` runs it
(`--no-inline-config`), over the three changed paths:
- population, read from the config: `eslint --print-config` answers a
config object for `render-readme-demo.mjs` and `undefined` for
`README.md` and the GIF;
- count, read from `--format json`: 3 results, 1 file linted with 0
errors and 0 warnings, 2 "File ignored because no matching configuration
was supplied";
- invariance: that config's `parserOptions` is `{ ecmaVersion,
sourceType }` with no `project` or `projectService` (type-aware linting
is off repo-wide), and nothing imports the new script, so this diff
cannot move any untouched file's verdict.
- Not run locally: the repo-wide `pnpm lint` and the rest of CI's farm,
which CI owns.

## Acceptance notes

- Serial constraint: objectstack-ai#22224 (the README contributor-path section, lower
in the file) is still open; `origin/main` at `3ae59661` has no README
change since this branch's base `7b926f76`, so no merge was needed. Both
PRs edit different hunks of README.md.
- Observed in a fresh `pnpm dev:showcase -- --fresh` boot whose full
output was read during this work, not investigated, not filed (outside
this card): the boot-diagnostics block prints a `sys_migration` "UNIQUE
constraint failed" insert warning, a pre-bind settings read warning, an
analytics `admitObjectRead` warning and a sharing-rule "expands to NO
recipients" warning. The terminal frame shows none of them because it
shows a pattern-picked subset of lines; nothing in the GIF depends on
them.
- The MCP refusal is an `isError: true` tool result carrying prose only,
with HTTP 200 and no machine-readable error code; the GIF shows it as
received.

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

---------

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