You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b2e964f
Browse filesBrowse the repository at this point in the historyBrowse files
fix(cli)!: refuse a production os serve whose @objectstack/plugin-auth fails to load, naming the load error (#25231)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuNsiqYysBbcDRE7iuEMwM2A)_Fixes#25158
Clause-②: yes (narrowing)
Metadata-protocol: none
Card thread of the `domain:cli` lane (seat post #6024, 由Sales账户的项目负责),
session `session_01YD4v2ErCfZk8sfaZe8AYVu`, claim 6341648045. The rule
is #25133's (ADR-0131 D9, ruling 6328987873's reasoning); this is the
third composition that reaches the same state. Review tier:
`CONTRACT_REVIEW_TIER`, because the diff declares a narrowing. The
branch is `claude/project-thread-m47h05`, not `claude/issue-25158-*`:
this session can push only that branch, as the claim says.
## What changes
`packages/cli/src/commands/serve.ts`, the `5d` AuthPlugin
auto-registration:
- **Before.** The `auth` tier is on and a secret resolved, so
`resolvePlatformAuthComposition` composes AuthPlugin. The block's broad
`try` wraps the `@objectstack/plugin-auth` import, the construction and
the `kernel.use`. When any of them threw, the catch warned (or said
nothing, for `ERR_MODULE_NOT_FOUND`) and the boot went on with no
AuthPlugin, so with no organization object.
- **After.** On a production boot (no `--dev`) whose rule composed
AuthPlugin, a throw before AuthPlugin is mounted is recorded
(`authPluginLoadFailure`). It is refused beside the two existing
ADR-0131 D9 refusals, after composition and before anything starts,
whether or not `--server`. The refusal names the load error verbatim and
the remedy (reinstall the deployment's dependencies;
`@objectstack/plugin-auth` is a dependency of `@objectstack/cli`). An
`authPluginMounted` flag keeps a throw that happens after the mount on
the old path.
- `const productionBoot = !isDev` moved up from the no-auth refusal so
the catch can read it. That refusal's condition is unchanged.
- **Unchanged:**
- `--dev` keeps the warn-and-boot path;
- the host kernel: its rule composes nothing (`host-kernel`), so the
catch keeps its old handling;
- a production boot with no secret, or with no auth: still refused by
the existing two refusals;
- programmatic embedding, which never runs this command.
`scripts/check-cli-test-child-env.mjs`: its self-test pins the exact set
of test files that spawn the built `bin/run.js`, and the new pin joins
that set ("fourteen" becomes "fifteen"). The entry carries the same kind
of reason as `serve-no-auth-secret-refusal`'s. This is the one line
outside `packages/cli`; `scripts/` gates anchor to `domain:spec`, so it
is declared here as a cross-lane touch.
## Pins
`packages/cli/test/serve-plugin-auth-load-failure-refusal.integration.test.ts`,
4 legs, next to `serve-no-auth-secret-refusal.integration.test.ts`. All
legs spawn `bin/run.js` with `NODE_ENV` unset and `OS_AUTH_SECRET` set.
The load failure is stubbed with
`packages/cli/test/fixtures/plugin-auth-load-failure.hook.mjs`, a `node
--import` resolve hook. It denies only the package's root entry; the
subpaths `@objectstack/runtime` imports stay reachable. In both modes
node produces its own error:
- `?mode=missing` resolves to a path that does not exist (`Cannot find
module '…'`);
- `?mode=broken` resolves to a module that cannot link (`The requested
module 'node:path' does not provide an export named …`, the shape of the
publish-smoke specimen).
The legs:
- **Refused, the database never opened, the load error named after `The
load error: `, and no `⚠ AuthPlugin failed to load` line:**
- a missing install, with `--server`;
- a broken install, with `--no-server`.
- **Boots, reaching ready and opening the database:**
- the same fixture and secret with no stub (`--no-server`);
- the broken stub under `--dev --no-server`, which asserts the warn line
still carries node's sentence.
The cause is asserted right after `The load error: ` because, with the
stub on, oclif also prints a `ModuleLoadError` warning for the `verify`
command: `@objectstack/verify` imports the root entry too. The hook's
header says so.
## Proof
Local, on this branch:
- **The pin file:** 4/4, three runs at 13.3 s, 13.1 s and 12.4 s. It
declares `test-budget: 20` (13.3 s × 1.25), with no CI reading yet.
- **Ablation:** the recording condition was turned off (`if (false &&
productionBoot …`) and the cli rebuilt; the marker is in
`dist/commands/serve.js`. Result: 2 failed and 2 passed. Both refused
legs are red with "the boot was not refused", and the broken leg shows
the old `⚠ AuthPlugin failed to load:` line. The controls stay green.
Restored with `git restore --source=HEAD`, blob `47ae01cd` == HEAD,
rebuilt, and the marker is absent from `dist`.
- **The sibling pin
`serve-no-auth-secret-refusal.integration.test.ts`:** 10/10.
- **cli typecheck:** exit 0.
- **cli unit layer:** `vitest run --project unit` reports 324/324 files
and 4777/4777 tests.
- **`node scripts/pm/dispatch-gates.mjs --commands`:** 101 families,
each exit code captured before any pipe. The first sweep had one real
finding and four prerequisite refusals:
- `check:cli-test-child-env` (exit 1): its self-test pins the set of
test files that spawn the built `bin/run.js`, and the new pin joins it.
The new file is named there with the reason it needs production posture,
the same way `serve-no-auth-secret-refusal` is.
- exit 3, not measured: `check-plugin-teardown-shape --self-test` (a
shallow clone), and `check:skill-examples`, `check:dual-build-cjs-loads`
and `check:i18n-coverage` (packages outside the cli closure were not
built). All four re-ran at exit 0 after the pinned fixture commit was
fetched and `pnpm build` ran.
- The gate-script edit derives 20 more families (121 in all). They ran
too, all at exit 0. `--ran` accounts for 121 of 121, with 0
NOT-MEASURED.
- **`pnpm lint`:** exit 0.
- **`check-governed-merges --branch`:** exit 0, NOT governed (0 of 7
paths), +335 / -10.
## Docs
- `content/docs/deployment/cli.mdx`: the auth-tier paragraph names the
third refusal.
- `content/docs/upgrading.mdx`: the 18.0 auth-secret paragraph covers a
failed `@objectstack/plugin-auth` load, `os start` included. "Either
refusal" becomes "any of these refusals", and the `os start` sentence is
scoped to the missing-secret refusal.
- `content/docs/releases/**` is not touched.
## Acceptance notes
- **The refusal also covers a throw from the AuthPlugin construction,
before the mount, not only from the import.** Either way no AuthPlugin
and no organization object, and the error is named. The text says
"failed to load", which reads a little wide for a constructor throw. I
kept one sentence rather than branching the text on the stage.
- **`os start`'s `serve` child is a production boot**, so a broken
install under `os start` is now refused too. It used to boot without
auth.
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(cli)!: a production `os serve` whose `@objectstack/plugin-auth` fails to load is refused, naming the load error, instead of booting with no organization object
6
+
7
+
Clause-②: yes (narrowing)
8
+
Metadata-protocol: none
9
+
10
+
<!-- adr-0087: not-required (no-migration-prescription) A boot-time refusal in `os serve`: no key of any metadata schema is removed, renamed or re-shaped, so there is nothing for `objectstack migrate meta` to rewrite and no tombstone. No export, type, flag or error code is removed or renamed. The remedy is a deployment one (repair the install), stated below. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers this boot (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
11
+
12
+
**BREAKING** boot-refusal narrowing (ADR-0131 D9), the third composition beside the two the previous change refused. It changes which production boots start, not what a metadata author can write, so it ships as `patch` with this banner.
13
+
14
+
- FROM: a production `os serve` or `os start` (no `--dev`) whose stack enables the `auth` tier and resolves an auth secret, but whose `@objectstack/plugin-auth` fails to load — a missing install (silently) or a broken one (one `⚠ AuthPlugin failed to load: …` warning) — booted with no `AuthPlugin`. It composed no organization object, so every row the boot wrote landed with no organization: stored ownerless on an 18.0.0-born store (nullable columns, MongoDB), refused at NOT NULL on a fresh SQL table.
15
+
- TO: that boot is refused before any plugin starts, with or without `--server`, and the database is never opened. The refusal names the load error. The same holds when the AuthPlugin construction itself throws before it is mounted.
16
+
17
+
**Unchanged:**`os dev` and `--dev` (a development boot keeps the warning and boots without auth); the cloud runtime's host kernel (`ObjectOSEnvironmentPlugin`), whose auth is each project's; a production boot with no auth secret or with no auth at all, which is still refused by the existing refusals; and an embedded kernel, which never runs this command.
18
+
19
+
**The remedy.** Repair the install the error names: `@objectstack/plugin-auth` is a dependency of `@objectstack/cli`, so reinstall the deployment's dependencies (e.g. `npm ci` or `pnpm install --frozen-lockfile`). Running kernel-only (`--no-server`) does not avoid the refusal.
0 commit comments