Repository navigation
Commit 790d9cf
fix(cli,runtime): the ownership refusal os start prints exports the auth secret it keeps in its home (#29913)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBXyqsnJCqZ3DaKgxUVGuVM4)_
domain:server#6 · os-support-ai · session_01WGufzj1WJNGVejm8mrJ71y
Fixes #29882
## What changes
- **`@objectstack/runtime`.** `OrganizationOwnershipBootGuardOptions`
gains an optional `authSecretFile`: the file a production boot read its
auth secret from because the operator set none. On a boot that is not a
development boot, the refusal names both ceremony commands with that
secret exported from the file. This covers every refusal that names the
ceremony: no marker, an outdated generation, an unreadable marker, an
unverified marker, a column read that failed, and a birth marker that
could not be written. A path a shell would split or expand becomes one
double-quoted word. One sentence says why the commands carry the secret.
The `--dev` spelling does not change.
```
Run 'OS_AUTH_SECRET="$(cat /srv/app/.objectstack/auth-secret)" os
migrate organization-ownership --out PLAN' to write and review its plan,
take a backup, then run 'OS_AUTH_SECRET="$(cat
/srv/app/.objectstack/auth-secret)" os migrate organization-ownership
--apply --plan-file PLAN --backup-taken', and start again. Both commands
export the auth secret this server read from
/srv/app/.objectstack/auth-secret: the ceremony composes the security
plugin only with the secret the deployment serves with, and without it
the apply can stop at its authored-conversion step.
```
- **`@objectstack/cli`.** `os start` now tells its `serve` child where
the secret came from. It uses a new internal channel,
`OS_INTERNAL_AUTH_SECRET_FILE`
(`utils/internal-auth-secret-channel.ts`), which follows the same naming
and ownership rule as `OS_INTERNAL_ARTIFACT_PATH` and is deliberately
undocumented. The parent sets the variable only when the secret came
from `<home>/auth-secret`, and only when that file really holds the
secret (a generated secret whose write failed lives in the process
alone). Otherwise it deletes the variable, so an inherited copy never
speaks for the parent. `os serve` passes the file to the guard.
- **Docs.** `content/docs/deployment/cli.mdx` › `os migrate
organization-ownership` gains one bullet: run the ceremony with the auth
secret the deployment serves with, and what an `os start` refusal names.
- **Clause-② yes (widening).** `OrganizationOwnershipBootGuardOptions`
is re-exported from `@objectstack/runtime`, so the optional
`authSecretFile` widens the package's public surface. It is not a
protocol change, so the changeset stays `patch`.
## Why
On a v17-shaped database, `os start` refused the boot and named the
ceremony bare. When the operator had set no secret, `os start` was
serving with the secret it persists in its home, and that secret is not
in the operator's shell. Run exactly as printed, the ceremony composed
no security plugin: the plan was written and the apply stopped at
`authored-conversion` ("Did not compose the security plugin…"), and
nothing named the secret or where it lived. That is the card's
measurement at `589049b0`. #23871 fixed the same gap for `os dev` / `os
serve --dev`. This PR fixes the `os start` branch.
**Release read (asked by the maintainer):** not blocking for 18.3.0. The
text has read this way since 18.0.0, so it is no regression. The stopped
apply resumes from its checkpoint with no data lost once
`OS_AUTH_SECRET` is exported. The changeset says that for operators who
already hit it.
**Out of scope, noted:** a boot whose secret the operator passed as `os
start --auth-secret X`, with no matching export, still names the bare
commands. The refusal has no file to point at, and the secret must never
be printed. The changeset says to run them with that secret in
`OS_AUTH_SECRET`. The database URL is also not named: `os start -d URL`
needs `OS_DATABASE_URL`/`--database-url` on the ceremony, as the card's
own reproduction set it.
## Verification
Measured on `objectstack-ai/objectstack@00f72f9` (main at branch point)
plus this branch.
- `packages/runtime` `src/organization-ownership-boot-guard.test.ts`:
23/23 pass. A new case covers the file-sourced spelling in all four
refusal states, quoting of a path with a space and `$`, and the `--dev`
spelling winning over a file.
- New
`packages/cli/test/start-organization-ownership-refusal.integration.test.ts`
(built CLI, `bin/run.js`, `NODE_ENV` unset): 5/5 pass, file wall time
about 29 s locally. It boots a fresh `os start --home H`, stages the
17.x shape (`sys_secret.organization_id` plus no marker), and checks
four things:
- the refused boot names both commands with `OS_AUTH_SECRET="$(cat
H/auth-secret)"`, read from the printed line, and the secret itself is
never printed;
- a boot under the operator's `OS_AUTH_SECRET` names the bare commands;
- the two commands, run **through bash exactly as printed** in a shell
with no secret, write the plan and complete the apply ("The ceremony is
complete", "Security Plugin initialized", no "Did not compose the
security plugin");
- the next `os start` serves the database.
- **Reverse check:** with the channel write ablated in `start.ts` (CLI
rebuilt), 2 of the 5 cases go red: the refusal names the bare commands,
and the apply run as printed never initializes the security plugin.
Restored from the commit, all 5 are green again.
- `pnpm typecheck` for `@objectstack/runtime` and `@objectstack/cli`
(including `check:test-typecheck`): pass. `eslint` on the 6 touched
source and test files: clean.
- start.ts source pins (`port-contract-single-source`,
`console-route-ledger.conformance`, `child-env-source-loader.pin`,
`start-port-forwarding-channel.pin`, `artifact-child-env.pin`): 69/69
pass.
- `pnpm lint` (repo-wide): exit 0.
- `dispatch-gates.mjs --commands` for this change set, plus the 5
roster-adjacent gates it marks ⛔: 67 commands, 64 exit 0 on the first
sweep. 3 exited 3 on a missing prerequisite, not on a finding.
`check-plugin-teardown-shape --self-test` needed its pinned fixture
commit fetched into the shallow clone; once fetched it exits 0 (48
cases). `check:dual-build-cjs-loads` and `check:i18n-coverage` needed a
full `pnpm build`; after it, both exit 0. The 31 docs-family gates the
`cli.mdx` edit adds to the derived set also exit 0.
Clause-②: yes (widening)
Metadata-protocol: none
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No spec change.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [ ] 4. One issue, one branch off an up-to-date `main`, one pull
request. The branch is the harness-pinned
`claude/project-thread-7lmys5`, not `claude/issue-29882-*`. It is off
current `main`, with one issue and one PR.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (Not touched.)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzj1WJNGVejm8mrJ71y
<!-- signed: domain:server#6 · os-support-ai ·
session_01WGufzj1WJNGVejm8mrJ71y -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8ed47cb commit 790d9cf
8 files changed
Lines changed: 473 additions & 15 deletions
File tree
- .changeset
- content/docs/deployment
- packages
- cli
- src
- commands
- utils
- test
- runtime/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1839 | 1839 | | |
1840 | 1840 | | |
1841 | 1841 | | |
| 1842 | + | |
| 1843 | + | |
| 1844 | + | |
| 1845 | + | |
| 1846 | + | |
| 1847 | + | |
| 1848 | + | |
| 1849 | + | |
| 1850 | + | |
1842 | 1851 | | |
1843 | 1852 | | |
1844 | 1853 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| |||
4194 | 4195 | | |
4195 | 4196 | | |
4196 | 4197 | | |
4197 | | - | |
4198 | | - | |
| 4198 | + | |
| 4199 | + | |
| 4200 | + | |
| 4201 | + | |
| 4202 | + | |
| 4203 | + | |
| 4204 | + | |
4199 | 4205 | | |
4200 | 4206 | | |
4201 | 4207 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
312 | 313 | | |
313 | 314 | | |
314 | 315 | | |
315 | | - | |
316 | | - | |
317 | | - | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
318 | 325 | | |
319 | 326 | | |
320 | 327 | | |
| |||
454 | 461 | | |
455 | 462 | | |
456 | 463 | | |
| 464 | + | |
457 | 465 | | |
458 | 466 | | |
459 | 467 | | |
| |||
601 | 609 | | |
602 | 610 | | |
603 | 611 | | |
| 612 | + | |
| 613 | + | |
604 | 614 | | |
605 | | - | |
| 615 | + | |
606 | 616 | | |
607 | 617 | | |
608 | 618 | | |
609 | | - | |
| 619 | + | |
610 | 620 | | |
611 | 621 | | |
612 | 622 | | |
613 | 623 | | |
614 | 624 | | |
615 | 625 | | |
616 | 626 | | |
| 627 | + | |
617 | 628 | | |
618 | 629 | | |
619 | 630 | | |
620 | | - | |
| 631 | + | |
621 | 632 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
0 commit comments