Repository navigation
docs(cli): the README states the global flags and the os plugin group that the built os registers - #21354
Conversation
…o command rows as os does The README listed -v/-h as global short flags (both exit 2), said there is no os plugin command group (build/sign/publish are registered), described os init as always using the current directory, and os dev as hot reload. Claude-Session: https://claude.ai/code/session_018gA1pE6eJtwHhqx72G8U9X Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check
What this run could not see
Coarse fallback — 26 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Review: REWORK, patch round 1, PR #21354 (head
|
…lags, and os serve --ui as the help does Patch round 1. The Cloud section said every cloud command reads os cloud login's session or --token/OS_CLOUD_API_KEY and --server/OS_CLOUD_URL; os environments * take -u/--url and -t/--token (env OS_TOKEN) and use the os login session instead. os serve --ui enables the bundled Console portal, not "Studio UI". The changeset now counts five false claims. Claude-Session: https://claude.ai/code/session_018gA1pE6eJtwHhqx72G8U9X Co-authored-by: Claude <noreply@anthropic.com>
Review: ACCEPT, patch round 1, PR #21354 (head
|
Contract reviewServed-tier: Scope read, at 2026-10-02T07:26Z: card #21310 (body and all three comments: the Claim 5945814810, the round-0 report 5946785715, the round-1 report 5947221232); PR #21354 (body, the two-file list, all three comments: docs-drift 5946760338, REWORK 5946805321, ACCEPT 5947260207); the net three-dot diff against ① Derived judgmentsThe diff touches no schema, no command, no flag, no env var and no exit code, so it implies no accept-set change. The one public-surface change it implies is the text of
② Semver level
Clause-②: no — correct; no accept set widens or narrows. ③ Boundary flagsDev flags from the two round reports (comments 5946785715 and 5947221232), each answered:
Check-runs on the head: all 34 grouped names have concluded and none failed ( Implemented-by: VERDICT: PASS Generated by Claude Code |
…one shared resolver (objectstack-ai#21400) Fixes objectstack-ai#21360 Clause-②: yes (widening) ## What changes The five `os environments` subcommands (`list`, `show`, `create`, `bind`, `switch`) built their client with `createApiClient`, which reads only `~/.objectstack/credentials.json` (the `os login` session). With only `~/.objectstack/cloud.json`, which is the state after `os cloud login`, all five exited 1 with `Authentication required` before sending a request. Meanwhile `os login --help` sends hosted users to `os cloud login`, so the documented hosted flow looped. This follows triage's ruling `5947754514` (shape 1). All five now choose their session in **one** shared resolver, `createControlPlaneApiClient` in `packages/cli/src/utils/api-client.ts`, which picks in this order: 1. `credentials.json`'s session where it targets the server the command talks to. With no `--url` / `OS_CLOUD_URL`, it names the server itself, so a user with an `os login` session sees no change. 2. Otherwise `cloud.json`'s session, on the same terms. Its server is its recorded url, or `https://cloud.objectos.ai` when it records none. 3. Otherwise, when an explicit url names neither file's server, `credentials.json`'s session as before. `cloud.json`'s token is never sent to a url other than its own. Explicit flags and env vars (`--url` / `OS_CLOUD_URL`, `--token` / `OS_TOKEN`, `OS_ENVIRONMENT_ID`) still win field by field, as in `createApiClient`. Other details: - The active environment sent as `X-Environment-Id` comes from the chosen session's file. - The result carries `session: 'credentials' | 'cloud'`. `switch` and `create --activate` read it and skip the `credentials.json` write when they ran on the cloud session. Without that skip, an id from cloud.json's server would land in a file that names a different server, and every later `os data` / `os meta` call would send it there. The `cloud.json` write still goes through the existing url gate in `active-environment.ts`. - With no session at all, the refusal (`requireControlPlaneAuth`) names `os cloud login` as well as `os login`. The `Authentication required` prefix is unchanged. - Unchanged: `createApiClient`, `requireAuth`, and their `data` / `meta` / `datasource` / `whoami` callers. ### Where the resolver lives, and why `os package publish` does not use it - **Location: `api-client.ts`, beside `createApiClient`.** The resolver needs both stores and the url gate `isSameControlPlane` from `active-environment.ts`. Putting it in `cloud-config.ts` would create an import cycle, because `active-environment.ts` imports `cloud-config.ts`. - **`os package publish` keeps its own lane.** It reads only `cloud.json`, by design: its source says it deliberately does not fall back to `credentials.json`, and `content/docs/deployment/cli.mdx` documents that. The ruling's order puts `credentials.json` first. Moving publish onto this resolver would change which token publish sends whenever `credentials.json` names the same server, so `publish.ts` is untouched. ## Measured: the five subcommands, spawned from source Setup: HOME holds only `cloud.json`, pointed at a local echo control plane. The CLI runs through `bin/run-dev.js`, with `packages/cli/dist` absent so the source is what runs. | subcommand | before (`51550933db`) | after (`97c52b5ce7`) | |---|---|---| | `list` | exit 1, 0 requests, `Authentication required. Please run os login ...` | exit 0, `GET /api/v1/cloud/environments` with `Bearer cloud_tok` | | `show env_1` | exit 1, 0 requests | exit 0, `GET .../environments/env_1` with `Bearer cloud_tok` | | `create --org org_1 --name Dev` | exit 1, 0 requests | exit 0, `POST .../environments` + `POST .../env_new/activate` with `Bearer cloud_tok`; id recorded in `cloud.json` | | `bind env_1 --artifact ...` | exit 1, 0 requests | exit 0, `GET` + `PATCH .../environments/env_1` with `Bearer cloud_tok` | | `switch env_1` | exit 1, 0 requests | exit 0, `GET` + `POST .../env_1/activate` with `Bearer cloud_tok`; id recorded in `cloud.json` | ## Pins: `packages/cli/src/commands/environments/cloud-session.test.ts` The file has 49 cases. They run in-process through `Command.run` against two real `node:http` echo control planes on 127.0.0.1, with HOME redirected to a temp directory. Every group runs over all five subcommands: - **Only `cloud.json`:** the cloud bearer and the cloud active environment go to the cloud url. A `--url` that `cloud.json` does not name gets exit 1 and zero requests. - **Only `credentials.json` (the control):** the runtime bearer and active environment go to the runtime url, including with an explicit `--url`, as before. - **Both stores:** - `credentials.json` wins where both name the same server, with and without `--url`. - With no `--url`, `credentials.json` still picks the server. - A `--url` naming cloud.json's server selects the cloud session. - **Writes:** `switch` and `create` on the cloud session leave `credentials.json` alone. With only `cloud.json`, they record the id in `cloud.json`. - **No session:** the refusal names `os cloud login`. **Red before the fix**, measured with the pin commit `eb9dcdaeb4` on top of `51550933db`: 19 failed, 25 passed. The failures were the cloud-only bearer (5), the `--url` naming cloud.json's server (5), the four write cases, and the no-session remedy (5). The 25 passing cases were the controls, the both-stores ordering cases and the foreign-url guard. These hold before and after the change by design. ### Ablations All 15 ablations ran at `97c52b5ce7` through `scripts/ablation-replace.mjs` in WRAP mode, with the fix already committed. The subject is reached by relative import into `packages/cli/src`, so no dist is on the path. Every leg's anchor hit exactly once, and the mutation was proven on disk by anchor count and blob change. Every restore was proven by blob == HEAD and an empty `git diff HEAD`. Each leg went red exactly where predicted: | leg | mutation | red | |---|---|---| | L1 | drop the cloud.json candidate | 14: cloud-only bearer x5, `--url` cloud x5, the 4 write cases | | L2 | no-url order swapped to cloud first | 10: both-same-server x5, no-url picks credentials x5 | | L3 | delete the credentials url match | 5: credentials wins on the shared `--url` server | | L4 | ungated cloud fallback for a foreign url | 5: cloud token never sent to a foreign `--url` | | L5 | drop the legacy credentials leg | 5: control with explicit `--url` | | L6 | cloud active environment dropped | 10: cloud-only x5, `--url` cloud x5 | | L7 | runtime active environment dropped | 10: control x5, both-same-server x5 | | L8 / L9 | `switch` / `create` runtime write ungated | 1 each: leaves credentials.json alone | | L10 | remedy reverted to the old sentence | 5: no-session names `os cloud login` | | L11-L15 | each subcommand back on `createApiClient` | that subcommand's cases only (2, 2, 4, 2, 4) | ## Tests and gates, at `97c52b5ce7` - `pnpm --filter @objectstack/cli typecheck`: exit 0, which covers `tsc --noEmit` over `src` (the new pin is under `src`) plus `check:test-typecheck` OK. - `pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2`: - First run: 245 of 247 files passed (3509 passed, 29 skipped). - The two failing files, `published-subpath-console.pin` and `published-subpath-hook-body.pin`, refused because `packages/cli/dist` was absent, a build prerequisite. - After the workspace build, those two files passed (29 tests). - Integration tier: **declared to CI.** The diff touches no integration-tier file and no spawn entry. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 65 commands, and every one was run with its exit code recorded before any pipe. - Four gates first exited 3 (PREREQUISITE NOT MET, no dist): `check:dual-build-cjs-loads`, `check:i18n`, `check:i18n-coverage` and `check:i18n-walk-parity`. After `turbo run build` they exit 0. - `--ran`: `65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN`. - Lint, a declared narrowing of `pnpm lint`. ESLint ran with `--no-inline-config --format json` on the 10 changed `.ts` files. - The JSON reports 10 files linted, none ignored, 0 errors and 0 warnings. - The repo's single `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, no typed rules; it says so itself). Its only cross-file inputs are two baseline JSON files this diff does not touch, so no untouched file's verdict can move. ## Acceptance notes - `packages/cli/src/utils/active-environment.ts` header says "`os environments` authenticating as the runtime identity is deliberate". That sentence is now false. The file is outside this card's claimed surface, so it is not edited here. It is a one-sentence comment change. - `package/publish.ts` and `plugin/publish.ts` each carry their own copy of the `cloud.json`-only resolution. That duplication predates this card, and this card does not touch it. - `content/docs/deployment/cli.mdx` has no `os environments` sentence that this change makes false, so it is not edited. - The legacy leg (an explicit url that neither file names gets `credentials.json`'s session) is kept so the control holds byte for byte. Narrowing it would be its own decision. - In the README Cloud section, the table row and the paragraph and code comment that PR objectstack-ai#21354 added are rewritten to state the new behaviour. - The gate derivation ran on a tree 8 commits behind `origin/main`. Its one stale input is `scripts/sdui-manifest.record.json`, which this diff does not touch. Upstream, only `packages/cli/test/json-stdout-purity.e2e.test.ts` changed under `packages/cli`. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #21310
Clause-②: no
What changes
packages/cli/README.mdnow says what the builtosdoes. Every claim below was read off the built entry (packages/cli/bin/run.js,@oclif/core5.1.2), run from an empty cwd:os --help,os plugin --help, every topic's--helpand each documented command's--help. The credential sources were also measured against a local echo server.### Globallists--versionand--helponly. It says there is no short form:os -handos -vexit 2. It also names the commands where-vis the command's own flag.### Plugin Managementdrops "There is noos plugincommand group in v1". It lists the registered group instead:os plugin build,os plugin signandos plugin publish. It notes that the group has noinstall(per ADR-0025's status line), and thatos pluginis unrelated toos plugins.os init [name]andos dev [package]. Both are rewritten.os cloud login's session, or--token/OS_CLOUD_API_KEYand--server/OS_CLOUD_URL. A new#### Credentials and server URLtable states, per command, the server-URL flag, the token flag and the stored session it uses. The typical publish flow now says that itsos environments createstep does not read theos cloud loginsession.os serve --ui(patch round 1) now uses the--helpwording: "Enable the bundled Console portal", in place of "Enable Studio UI"..changeset/21310-cli-readme-flags.mdis apatchfor@objectstack/cli, becauseREADME.mdis in the package'sfiles. It now counts five false claims.No code, flag, environment variable, exit code or help page changes.
packages/cli/package.jsonis untouched.Short flags: the README route (docs follow the implementation)
Readings on the built entry from an empty cwd. "Before" is at
1caa60373, the branch point after #21167 landed. "After" is at9bdb092ca, this head.os -hcommand -h not foundcommand -h not found(unchanged by design)os -vcommand -v not foundcommand -v not found(unchanged by design)os --help1855676fe5a2bb87aa5871fc1bed196fos --version@objectstack/cli/17.6.0 linux-x64 node-v22.22.0The ruling's check. The ruling: "show that no command already uses
-h/-vas its own flag. If one does, choose the README route and say why."Six commands already own
-v. Found bygit grepforchar: 'v'inpackages/cli/src, then read back in each command's--help:-vis--verboseonos dev(dev.ts:212),os serve(serve.ts:1209),os start(start.ts:93) andos doctor(doctor.ts:1905).-vis--version VALUEonos package publish(package/publish.ts:315) andos package install(package/install.ts:57).No command owns
-h. So the ruling sends-vdown the README route.-hwas eligible on its own, and it is dropped too, for the reasons in the four axes below.What the alternative would have done. These rows come from oclif's own predicates,
versionAdditionandhelpAdditionin@oclif/core5.1.2lib/main.js. They were evaluated in memory on this package's loadedConfig, withadditionalVersionFlags: ["-v"]andadditionalHelpFlags: ["-h"]set on it. No file was written.-v servecommand -v not foundserve -v--verbose--verbose(oclif checks only argv[0] for a version flag)serve -hNonexistent flag: -hThe four axes.
git grepforos -h,os -vandobjectstack -h|-vover the whole tree (content/docs, skills, examples, packages, scripts) finds no occurrence. This README's### Globalwas the only text that named the short forms.-valready has two meanings inside this CLI: verbose on four commands and a package version on two. Adding a third that applies only at argv[0] (print the CLI version) makes the flag's meaning depend on where it appears. Making the docs follow the implementation removes the false claim with no runtime change.os -v servefails loudly with exit 2. With the key set, it would print a version line, exit 0 and start nothing, so a loud failure would become a silent one. The README now says the short forms do not exist, so an agent reading it uses--helpand--version, which work in every position.-hwork would also leave### Globalasymmetric, with no measured user who needs it.os plugin— the commands, verbatimos plugin --helpat9bdb092ca, exit 0. The output is byte-identical at1caa60373.Usage lines:
os plugin build [DIR] [-e VALUE] [-o VALUE] [--minify],os plugin sign ARTIFACT -k VALUE [--key-id VALUE] [-o VALUE]andos plugin publish [ARTIFACT] …. There is noinstall, and that matches ADR-0025's status line, which says the code-plugin install half is unimplemented.Every README command-table row against
--helpPlaceholders are spelled in capitals here (TYPE, NAME, ID).
os init [name]os init --helpsays: "When provided, a new directory with this name is created; otherwise the current directory is used." The Quick Start's ownos init my-appwas a counterexample.os dev [package]os dev --helpsays: "watch sources, rebuild the artifact, and restart the server on change".dev.tsrecords that the old "server will auto-reload" line "advertised a hot reload the runtime only partially performs".os serve [config]serve.ts:11importsisHostConfig/shouldBootWithLibraryfromutils/plugin-detection.ts, which detect a host config that carries instantiated plugins. The row leaves out the artifact fallback that--helpleads with, but that is an omission, not a false claim.os compile [config]-odefaults todist/objectstack.json.os validate [config]--helpalso mentions CEL expressions and widget bindings, which the row leaves out.os info [config]info.ts:117prints agents.os generate TYPE NAMEbcd68a29fbefore this branch's base). It is also already true:--helpmarks NAME optional, butgenerate.tsrefuses a metadata type without a name ("Missing required argument"). NAME is optional only for thetypes,clientandmigrationroutes.os create TYPE [name]--helpsays "Create a new standalone kernel code plugin from a built-in template", with TYPE = plugin.os cloud login-e/--emailand-p/--passwordskip the browser flow, and credentials go to~/.objectstack/cloud.json.os cloud whoami/os cloud logoutos cloud --help.os environments create --org ID --name Nprojectstopic inos --help.os environments list/show IDos package publish [artifact]dist/objectstack.json.os test [files],os doctor,os lint [config],os diff [before] [after]os explain [schema]### Globalos pluginsandos help(not commands)os pluginsexits 2 withcommand plugins not found, andos helpexits 2 withcommand help not found.package.jsonhas nooclif.pluginsand no@oclif/plugin-*dependency.os cloud login, or--token/OS_CLOUD_API_KEYand--server/OS_CLOUD_URLos package publishandos plugin publish. The new per-command table is below.os cloud login, thenos environments createos cloud loginsession present,os environments createexits 1 withAuthentication required. Please run os login or set OS_TOKEN environment variable.The flow now says so at that step and names what the step reads instead.OS_CLOUD_URL(or--server)"os cloud login,os package publishandos environmentsreadOS_CLOUD_URL. The flag is--serveronos package publishand--urlon the other two.os cloud whoami/logoutread neither.### os serve--ui--helpwording: "Enable the bundled Console portal at /_console/ when @object-ui/console is installed (default: true)".Cloud commands: flags, env vars and stored session, per command
Read off each command's
--helpat9bdb092ca. The "stored session" column was measured, not taken from the help:HOMEpointed at a temp dir holding only acloud.json, or only acredentials.json, whose URL was a local echo server that logged each request's path and bearer.os cloud login-u, --url(OS_CLOUD_URL, defaulthttps://cloud.objectos.ai)-e, --email/-p, --password, or the browser device flow~/.objectstack/cloud.jsonos cloud whoami,os cloud logout--jsononly)cloud.json(cloud/whoami.ts:26,cloud/logout.ts:29,39)os package publish-s, --server(OS_CLOUD_URL, defaulthttps://cloud.objectos.ai; with neither set, the URL incloud.json)-t, --token(OS_CLOUD_API_KEY, thenOS_TOKEN)cloud.json. With onlycredentials.json: exit 1, "Not logged in to ObjectStack Cloud. Run os cloud login first", and 0 requests. With onlycloud.json: the request goes to its URL with its bearer. WithOS_CLOUD_API_KEYorOS_TOKEN: the request carries that bearer.os plugin publish-s, --server(OS_CLOUD_URL)-t, --token(OS_CLOUD_API_KEY)cloud.json, by the same precedence code as package publish (plugin/publish.ts:170-178). Code-read only; not run, because it needs a built.osplugin.os environments list/show/create/bind/switch-u, --url(OS_CLOUD_URL); else the URL incredentials.json; elsehttp://localhost:3000-t, --token(OS_TOKEN)credentials.json, theos loginsession. With onlycloud.json, all five exit 1 withAuthentication required, before any request. With onlycredentials.json,listsendsGET /api/v1/cloud/environmentswith its bearer.OS_TOKENworks, andOS_CLOUD_API_KEYalone does not.os package install(a runtime command, not a cloud one)-r, --runtime(OS_RUNTIME_URL, defaulthttp://localhost:3000)--email/--password(OS_RUNTIME_EMAIL/OS_RUNTIME_PASSWORD)os whoami,os data *,os meta list/get/register/delete-u, --url(OS_CLOUD_URL)-t, --token(OS_TOKEN)credentials.json, through the samecreateApiClient(code-read)os datasource introspect/list-tables/validate-u, --url(OS_CLOUD_URL, elsehttp://localhost:3000)-t, --token(OS_TOKEN)datasource/introspect.ts:10-13, code-read).os login/os register-u, --url(OS_RUNTIME_URLfor login,OS_CLOUD_URLfor register; defaulthttp://localhost:3000)credentials.jsonAcceptance notes
These are out of scope. The last one is filed as #21360; the others are not filed.
build,start,verify,login,logout,register,whoami,migrate,data,datasource,db,i18n,meta,secret,storage,package installandenvironments bind/switchhave no row. These are omissions, not mismatches: the README does not claim to be complete, andcontent/docs/deployment/cli.mdxis the full reference.os cloud login,os environments createrefuses and says to runos login.os login --helpsays "For the hosted package registry, useos cloud logininstead." This round changes no code, so the README states the gap rather than closing it. The seat filed it as cli:os environments *never read theos cloud loginsession, whileos login --helpsends hosted users toos cloud login— the documented cloud flow loops #21360.Verification
pnpm turbo run build --filter=!@objectstack/docs --concurrency=2at9bdb092ca: 72/72 tasks, verify-lockVERDICT command-exit 0.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat9bdb092cagives 52 commands; the mergedmainaddedcheck-dts-emitted.mjs --self-test. All 52 exited 0 at9bdb092ca, each exit code captured before any pipe. The--ranreconciliation reads "52 derived, 52 run, 0 NOT-MEASURED, 0 UNRUN", and that zero is derived from recorded exit codes.mainmoved again after the last merge. That happened while the gates ran:96b12b589(a pm-roster step inlint.yml) and23365eaed(spec). Neither touchespackages/clior this changeset. The merge queue rebuilds the PR on currentmain.os --helpis byte-identical at9bdb092caand at1caa60373: exit 0, 3712 bytes, md51855676fe5a2bb87aa5871fc1bed196f.os -handos -vstill exit 2.pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 244 files and 3461 tests passed,VERDICT command-exit 0, at4e7e91fd2. Since then,git diff 4e7e91fd2 9bdb092ca -- packages/clitouches onlypackages/cli/README.md, and no CLI test reads that file. The tests that mention a README read the README thatos createemits. The integration tier is left to CI.pnpm --filter @objectstack/cli typecheck: exit 0 at4e7e91fd2.Generated by Claude Code