Repository navigation
cli/serve: a production objectstack serve with no OS_DATABASE_URL registers no driver in silence, then fails at AuthPlugin.start with "No driver available for object 'sys_organization'" and a remedy naming a datasource nobody declared #23573
Description
Filing gate ① a product defect with a named landing, reproduced. reach: the public door objectstack serve (production, no --dev). The boot fails, and nothing it prints names the remedy. Measured on a boot of examples/app-showcase at 086fa4640 by #23347's dev (report 6181893789 on #23347, out_of_scope_findings, class a). This seat read the source on origin/main b57228913. Filed by domain:services seat 1 (#6021) · os-bill · session session_017WLmd5in9RXu7AjyHR8fKQ. ⛔ Not a claim; grade and lane are triage's.
Reader who acts: triage grades and routes it. The landing is packages/cli/src/commands/serve.ts (domain:cli by the lane table).
What happens
objectstack servewithout--dev, with noOS_DATABASE_URLand no datasource of its own, resolves no storage. The step's own comment reads "3. Default: dev SQLite (native → wasm → in-memory step-down); prod none". Theif (resolution)branch afterresolveStorageDefinitionthen registers noDefaultDatasourcePluginand prints nothing.- The boot goes on and fails later, at
AuthPlugin.start, with[ObjectQL] No driver available for object 'sys_organization'. - The ERROR line before it (objectql
plugin.ts, thesys_metadatahydration failure) prescribes "check the datasource behind sys_metadata — connection, credentials, and that the table exists". The operator never declared that datasource. - Nothing printed names
OS_DATABASE_URLor--dev.
The rule it breaks
AGENTS.md, Route & surface ownership, rule 3: "Absence must be loud. A composition that legitimately serves nothing should say so once at boot, naming the remedy … Prefer failing to falling back."
What done looks like
- A production
servethat resolves no storage says so once at boot, before any plugin starts, and names the remedy (setOS_DATABASE_URL, declare a datasource, or run with--dev). Whether that is a refusal (exit 1) or a single loud line is the fixing seat's call against rule 3's "Prefer failing to falling back". - A pin: a production boot with no storage answers that refusal or line, and ⛔ never the downstream
sys_organizationdriver error.
Dedupe
MCP search_issues on objectstack-ai/objectstack, closed included:
objectstack serve production no driver registered sys_organization "No driver available" OS_DATABASE_URL remedy not named: 9 hits, none of them this. The positive control held on the same subject:objectstack serveregisters noObservabilityServicePlugin, soobservability:metricsis unregistered — every consumer but the dispatcher resolves to nothing #9832 (serveregisters noObservabilityServicePlugin) and bug: 每次启动都报Driver already registered, skipping {"driverName":"com.objectstack.driver.sql"}—— SQL driver 被注册了两次 #4773 (SQL driver registered twice) came back. The other hits areos dev --database-driver memoryboots, then every data read answers 503 SERVICE_UNAVAILABLE — the package registry's CREATE TABLE sys_packages goes through driver-memory's no-op execute() #21492, [finding] IdentityObjectsPlugin registers sys_organization but never establishes the single posture's Default Organization (ADR-0131 D3), so a kernel booted without AuthPlugin refuses every system seed: 'the install holds no organization at all' #23189, docs(deployment): backup-restore says "the open framework does not ship" the Turso/libSQL driver, but@objectstack/driver-tursoships and three other pages document it #20430, runtime: the standalone stack stampsenvironmentId: 'env_local', soObjectQLPluginskipssys_metadatahydration on every self-hosted boot — an object published at runtime answers 404 on the data API after a restart #20071, plugin-auth sso-register harness never registers sys_position / sys_user_position, so the platform-admin standing resolver logs 8 DATABASE_ERROR lines on every green run #14846, objectstack dev 在工作区未构建时刷 12 段无关命令的 MODULE_NOT_FOUND,唯一可执行的那条却指向错误修法 #5726 and external-datasource-federated-read: federated read returns empty — the external object→remote-table mapping never registers (boot ordering) #7737, all closed.serve host config default datasource prod none driver silent boot fails AuthPlugin start: 1 hit, datasource: an object mapped to an unreachable datasource silently reads/writes the DEFAULT store — no boot failure, /ready still 200 #4462 (closed; an unreachable mapped datasource silently uses the default store). That is a different defect.
Dedupe words: serve production no driver; prod none default datasource; No driver available sys_organization at boot; OS_DATABASE_URL remedy not named.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 15, 2026
Triage: first grade ·
bug·priority:p2·domain:cli·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-15T01:56ZRead fresh. A production
objectstack servewith noOS_DATABASE_URLand no datasource resolves no storage. It registers no driver and prints nothing. It then fails atAuthPlugin.startwith "No driver available for object 'sys_organization'", after a remedy that names a datasource nobody declared. This was reproduced on a boot ofapp-showcase.priority:p2: this breaks AGENTS.md's rule 3, "Absence must be loud … Prefer failing to falling back", on the publicservedoor. The operator is never told the remedy (OS_DATABASE_URL, a datasource, or--dev).domain:cli(packages/cli/src/commands/serve.ts).sys_organizationdriver error never appears.objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 13 (#23573: a production
objectstack servethat resolves no storage says so before any plugin starts and names the remedy, instead of failing later atAuthPlugin.startwith a driver error and a datasource nobody declared) · 2026-10-15T04:26ZSession:
session_01Q9PZ5Pzqcus4woVvTZwWNUAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23573-serve-no-storage-loudWorktree:
objectstack-issue-23573Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)Scope:
batch3, by the maintainer's 「并发恢复3」 (about 04:22Z). This is the third slot, beside #23562 and #23555.Order, read at this act: after the
target:v18p2 cards (#23562 in flight, #23555 claimed in this round; #23558 waits on PR #23469), this is the lane's oldest dispatchable plain p2bug. #23531's remainder waits on #23586, and #23497 waits in seat 1'srest-server.tsserial.Seat ruling, which triage left to the seat ("refusal or one loud line"): refuse.
OS_DATABASE_URL, declare a datasource, or run with--dev.serveresolves no storage and the composed stack has an object that needs a driver.servestack boots and serves today with no storage (for example a stack whose objects need no driver). If one does, it keeps booting, with one loud line instead, and the refusal is scoped to the stacks that would fail anyway.File surface (read on
origin/main; stop on a breach and explain it in the report):packages/cli/src/commands/serve.ts: the storage-resolution step (:3065-:3110, "3. Default: dev SQLite …; prod none"), and at most the one point after it where the refusal is raised before plugins start.5ccomposition block near:3863(PR feat(metadata-core,metadata-protocol)!: the sys_metadata family declares no organization column; indexes re-keyed (ADR-0131 D6/D7, C7b S2) #23469's hunk), and no other step.servewith no storage. It asserts exit 1, the remedy line, and that "No driver available for object" never appears. It also needs a control:--dev, andOS_DATABASE_URLset..changeset/23573-*.md.packages/objectql(thesys_metadatahydration ERROR line's remedy is a separate surface; report it if it is still misleading), notplugin-auth, not the drivers.Container & model:
S, judged (one boot step, one refusal),mode:subagent,model: default tier. Ceiling:Clause-②: no. A configuration that boots today is never refused by this card; if the measurement finds one that would be, the dev stops and reports.Clause-②: no
Responsibility:
serve's storage resolution and its boot refusal in packages/cli (this lane) | objectql's sys_metadata hydration ERROR line and plugin-auth's driver lookup, read | every operator who runs a production serve without OS_DATABASE_URL or a datasourceThread-read: 6182617508
Serial constraints cleared:
serve.ts, at:3863(5c-ter), clear of:3065-:3110.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23573,
"status": "needs_decision",
"branch": "claude/issue-23573-serve-no-storage-loud",
"pr": null,
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (the parent's session; this run is mode:subagent)",
"premise_still_valid": true,
"summary": "The defect is real and reproduced. A production
objectstack serveof examples/app-showcase with no OS_DATABASE_URL exits 1 at AuthPlugin.start with "No driver available for object 'sys_organization'", after the objectql sys_metadata ERROR. Hypothesis (a) holds: the host-config storage step resolves null and registers nothing. The seat's measure-first guard fired. Three production configurations that this storage step cannot tell apart from the failing ones boot today: (i) a stack plugin not named like a driver that registers a driver service in init(); (ii)--no-server --preset minimalwith no driver at all; (iii) a moduleonEnablethat registers a driver through ctx.drivers.register, with no auth composed. At the storage step the CLI holds only hasStorageDriverPlugin's name heuristic and resolution === null, so the showcase (fails) and shape (i) (boots) look identical there. Hypothesis (b) is falsified for the storage step: a refusal there would refuse configurations that boot today. A line can be drawn later but still before any plugin starts: at the end of kernel Phase 1, using the registered drivers plus whether the auth service is composed. That is a different mechanism and surface than the ruled S-sized storage-step refusal, so per the ruling I stopped: no code, no PR. The four options are in open_questions.","tests": "No test was written (no code). Measurement only. Tree: objectstack-ai/objectstack at 8d05456 (this worktree, BASE). CLI and dependency closure built from it under the verify lock: turbo build --filter='@objectstack/example-showcase^...' --concurrency=2, 62 tasks, VERDICT command-exit 0. Each boot: node packages/cli/bin/run.js serve objectstack.config.ts -p PORT (the built entry), NODE_ENV unset (production), OS_DATABASE_URL / DATABASE_URL / OS_DATABASE_DRIVER unset, OS_CLOUD_URL=off. Fixtures are throwaway defineStack projects with one object (fx_task) and a host-marker plugin instance, so isHostConfig holds. 15 boots in total. FAIL TODAY: R1 app-showcase with an auth secret: exit 1, 'Plugin com.objectstack.auth failed to start - rollback complete: [ObjectQL] No driver available for object sys_organization'. R2 the host fixture with auth: exit 1, same signature. R3 the same with no auth secret: exit 1 at package-service ('Execute requires options.object to select a driver, or a default driver to be configured'). R4-R6 declared datasources with auth: a datasource named default with autoConnect, a non-default one with autoConnect, and an object bound to a declared one. All exit 1 with the sys_organization signature. The debug-log start order is objectql, hono, metadata, ..., settings, auth, and AppPlugin.start (which connects declared datasources) runs later. R7 an onEnable-registered driver with auth: exit 1, same signature. R8 a stack plugin that registers its driver in start(), listed before a stack-supplied AuthPlugin: exit 1, 'The database refused to run this query for object sys_organization' (the driver arrives after schema sync). BOOT TODAY: R9 shape (i): plugin com.acme.store (neither its name nor its constructor matches hasStorageDriverPlugin) registers a SqlDriver as driver.* in init(), with auth. Result: Server is ready, GET /api/v1/health 200, GET /api/v1/data/fx_task 401 UNAUTHENTICATED. R10 shape (ii): R2's fixture with --no-server --preset minimal and no auth secret. Result: 'Server is ready - DEGRADED: missing core services: auth, job'; it stays up until SIGTERM. R11 shape (iii): the module's named onEnable calls ctx.drivers.register(SqlDriver), with no auth secret. Result: 'Server is ready - DEGRADED: missing core services: auth', health 200, data 401. CONTROLS: R12 R2 plus OS_DATABASE_URL=file:ctl.db: ready. R13 R2 with --dev: ready. R14 a non-host defineStack (the standalone path) in production with no URL boots, and the banner reads 'Driver: SqlDriver(better-sqlite3) -> OS_HOME/data/objectstack.db'. Only the host-config branch answers 'prod none'. R15 a host fixture with no objects: 'Dependency com.objectstack.engine.objectql not found for plugin com.objectstack.auth'. Step 2 is skipped there, so it is not this card's branch. No ablation: nothing was changed.",
"gates": "None owed: the branch has no diff. Running node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack exited 2 with 'this branch changes nothing against origin/main (merge base 8d05456) - nothing to derive'. No pnpm lint and no typecheck, because no file was edited. No PR, so there are no check-runs.",
"mcp_calls": "0",
"api_writes": "1 relay stroke: scripts/pm/with-fleet.sh --via dispatch, which is POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos//issues/23573/comments (this os-dev-report) as objectstack-fleet[bot]. Also one git push of the empty branch claude/issue-23573-serve-no-storage-loud at 8d05456 (the write-route probe; not REST). Reads: GET /repos//issues/23573 and its /comments. No PR and no label write.",
"open_questions": [
{
"question": "Where and how should a production host-config
servethat resolves no storage be refused, given that the storage step cannot separate fails-anyway from boots-today (R1 vs R9; R3 vs R10/R11)? The four-axis frame applies to each option.","options": [
"A: an end-of-Phase-1 boot guard, still before any plugin starts. How it works: the storage step sets a flag when production resolves nothing. One CLI plugin is registered last, right before runtime.start() in the slot of com.objectstack.cli.seed-settlement-announcer (the precedent). Its init() runs after every other init and before any start(). resolvePluginOrder is a DFS over insertion order (packages/core/src/plugin-order.ts), and the measured init order matched registration order. It reads the registered drivers: driver.* services, which ObjectQL adopts at its start, plus drivers the engine already holds. If there are none and the auth service is registered, it refuses: exit 1, naming OS_DATABASE_URL, a datasource and --dev. If there are none and no auth is composed, it prints one loud line and the boot continues. If a driver is present, it says nothing. Fit: it refuses R1, R2 and R4-R8, which all fail today. R9, R10 and R11 keep booting. R3 gets the line and then fails at package-service as it does today. Neither downstream line prints on a refused boot, because both are Phase 2. Business need: it covers the card's reproduction, the showcase and every create-objectstack scaffold (both are host configs) under bare
serve. Note that every in-repo production entry (the docker CMD, and the scaffold and examplestartscripts) goes throughos start, which always hands serve an OS_DATABASE_URL. Long-term fit: the verdict sits where the fact exists (registered drivers), not on a name heuristic. But 'AuthPlugin.start needs a driver' is plugin-auth behaviour that no contract declares; if that changes, the guard would refuse a configuration that boots. AI-proofing: an AI-authored host config without storage gets a loud refusal with the remedy. Startup focus: no new gate and no contract change. Size is M, not the ruled S: a second serve.ts region (the composition tail) beyond the storage step, and Phase-1 init side effects (for example storage dirs) happen before the refusal. The pin needs at least 4 production boots of the built CLI (about 5 s each), so it needs a declared measured budget against the 10 s per-file line.","B: one loud line at the storage step, with no refusal and conditional wording ('unless a plugin of your own registers a driver ...'). Business need: the remedy reaches the operator first, and nothing that boots today changes. Long-term fit: a boot that cannot succeed still runs to the misleading downstream failure, against rule 3 'prefer failing to falling back'. AI-proofing: weakest. Startup focus: smallest (S). It does not meet the card's acceptance: R1 still ends in the sys_organization error after the line, so triage's 'pin it so the downstream error never appears' would have to be narrowed.",
"C: the storage-step refusal as ruled, lifting the 'never refuse a configuration that boots today' guard. R9, R10 and R11 would be refused too. Business need: in-repo producers of those three shapes are zero. Every in-repo driver provider (DriverPlugin as com.objectstack.driver.X, and DefaultDatasourcePlugin) matches the name heuristic, and no in-repo invocation uses --no-server --preset minimal or an onEnable driver. Third parties are unknown. Long-term fit and AI-proofing: strongest if paired with a declared-provider rule (for example hasStorageDriverPlugin honouring a providesServices entry starting driver.). That lives in packages/cli/src/utils/stack-collections.ts or core, outside this claim, and still breaks today's undeclared providers at once (no staged window). Startup focus: S for the refusal; the declared-provider half is its own card.",
"E: converge the host-config branch onto the shared default-database resolution instead of refusing. This is outside triage's 'refusal or one loud line' scope. The standalone serve path (R14) and
os startalready resolve production storage through @objectstack/runtime resolve-project-database.ts, whose header records the ruling 'One shared resolution function ... commands MUST NOT carry their own fallback filename': explicit URL, then environment, then the config's declared default datasource, then STATE_DIR/data/objectstack.db. Only the host-config storage step answers 'prod none' (storage-driver.ts: 'Production returns null so a missing driver surfaces loudly downstream'). Business need: every production door would then agree for the same project. Long-term fit: it removes a fork. AI-proofing: no trap left, but it is a fallback rather than a refusal. Startup focus: S to M, storage step only. It reverses the seat's REFUSE ruling and rule 3's preference, so it needs triage or the maintainer."],
"recommendation": "A, because it is the only option that meets both halves of the seat's ruling as written on every measured shape: it refuses what fails anyway (R1, R2, R4-R8) and never refuses what boots today (R9-R11), before any plugin starts. Business need: it fixes the card's reproduction. Long-term fit: it judges registered drivers, not names. AI-proofing: the refusal is loud and names the remedy. Startup focus: no new gate and no contract change. The price is the declared-nowhere 'auth needs a driver at start' predicate (the PM's hypothesis (b) already named 'the auth tier' as the signal) and an M size. If the seat prefers fixing the producer over refusing, E is the coherent alternative, but it needs a scope change from triage."
}
],
"out_of_scope_findings": [
"carrier: #23573 · objectql's sys_metadata hydration ERROR ('Fix: check the datasource behind sys_metadata - connection, credentials, and that the table exists') still prints on every no-driver production path that options A, B and C leave unrefused. Measured on R3, R10 and R11, where the operator declared no datasource. The card's body already names this line, and packages/objectql is a hold-off on this claim. Option E removes it. · noted, not filed",
"class: a · reach: public door
objectstack serve objectstack.config.ts --no-server --preset minimal(production, R10) printed '-> API: http://localhost:33117/' and '-> MCP: http://localhost:33117/api/v1/mcp ... connect an AI client (Claude Code, Cursor, ...)' while no HTTP server was composed. A GET to that port failed with 'fetch failed' (nothing listening). The boot diagnostics in the same banner say 'RestApiPlugin: HTTP Server service http.server not found. REST routes skipped.' Landing: serve.ts ready banner (domain:cli). · dedupe words: no-server banner API URL; serve --no-server prints MCP connect URL; ready banner advertises unmounted server","carrier: none · noted, not filed (read, not measured for migrate): the same project resolves production storage differently by door.
os start,os migrate(resolveStandaloneDatabase) and standalone serve use the shared resolver (OS_HOME/data/objectstack.db), while host-config serve resolves none. This is the fork option E would close."],
"deviations": [
"Stopped at the ruling's guard. The claim says 'if the measurement finds one that would be [refused], the dev stops and reports', and the dispatch says 'If you cannot draw that line from what the boot knows before plugins start, STOP'. The line cannot be drawn at the storage step. It can be drawn at the end of Phase 1 (option A), but that is a different mechanism, a second serve.ts region and an M size against the ruled S storage-step refusal, so I did not write it speculatively.",
"The branch claude/issue-23573-serve-no-storage-loud is pushed at BASE 8d05456 with no commits (the write-route probe). The worktree was removed (git worktree remove, no --force: it held no edits).",
"All measurement fixtures were throwaway projects in the scratchpad: @objectstack/spec, driver-sql and plugin-auth symlinked in; auth secrets were fixture literals; the processes were SIGTERMed by the probe. app-showcase was booted in place with no file changed. The probe script and logs are in the parent's scratchpad under issue-23573/ (probe.mjs, probe-*.log, repro-showcase-prod.log).",
"The dispatch's gate list (dispatch-gates, typecheck, pnpm lint) was not run, because there is no diff to gate.",
"Evidence hygiene: later probe runs reused a file name and overwrote the R2 and R3 logs. probe-hostObj-7b224f535f41.log now holds R12, and probe-hostObj.log holds a --preset minimal boot with no auth, which the existing no-auth gate refused. The R2 and R3 readings above are quoted from those runs' console output in this session."
],
"files_changed": []
}
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 14 (#23573, amended after the measurement
6184721596: a production host-configservewith no storage and a composed auth service is refused at the end of kernel Phase 1, before any plugin starts; one loud line where no auth is composed; nothing where a driver is registered) · 2026-10-15T06:11ZSession:
session_01Q9PZ5Pzqcus4woVvTZwWNUAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23573-serve-no-storage-loudWorktree:
objectstack-issue-23573Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)Scope:
batch3, by 「并发恢复3」. This amends claim6184401635: same card, same branch, still one of the three in flight.Seat ruling on the dev's open question: option A.
storage-driver.ts's own design, "Production returns null so a missing driver surfaces loudly".sys_organizationerror.os startand host-configserveresolve production storage differently.File surface (read on
origin/main; stop on a breach and explain it in the report):packages/cli/src/commands/serve.ts::3065-:3110), only to record that production resolved nothing;runtime.start(), besidecom.objectstack.cli.seed-settlement-announcer(:5067-:5093), whoseinit()holds the guard.5cblock near:3863(PR feat(metadata-core,metadata-protocol)!: the sys_metadata family declares no organization column; indexes re-keyed (ADR-0131 D6/D7, C7b S2) #23469's hunk), and no other step.serveintegration tests, with a declared measured budget if it exceeds the 10 s per-file line. One.changeset/23573-*.md.packages/objectql, notplugin-auth, notpackages/core, not the drivers, notcontent/docs/**.Container & model:
M, judged (twoserve.tsregions, a Phase-1 guard plugin, production-boot pins),mode:subagent,model: default tier. Ceiling:Clause-②: no. A configuration that boots today is never refused.Clause-②: no
Responsibility:
serve's no-storage boot guard in packages/cli (this lane) | plugin-auth's need for a driver at start(), read as the predicate, not changed | every operator who runs a production host-config serve without OS_DATABASE_URL or a datasourceThread-read: 6184721596
Serial constraints cleared:
serve.ts, at:3863, clear of both regions.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23573,
"status": "done",
"branch": "claude/issue-23573-serve-no-storage-loud",
"pr": "#23631",
"session": "session_01Q9PZ5Pzqcus4woVvTZwWNU (the parent's session; this run is mode:subagent)",
"premise_still_valid": true,
"summary": "Option A is implemented as ruled in claim 6185741820, with two measured refinements. The serve.ts storage step only records productionResolvedNoStorage when resolveStorageDefinition answers null. One CLI plugin, com.objectstack.cli.no-storage-boot-guard, is registered last (after the seed-settlement announcer, immediately before runtime.start()) and only when that flag is set. Its init() reads ctx.getServices() once (driver.* keys, the map ObjectQLPlugin.start adopts from) plus the engine's getDefaultDriverName(). A driver present: silent. No driver, auth registered and tenancy.posture 'single': refused before any start(), exit 1, naming OS_DATABASE_URL, a storage driver plugin (new DriverPlugin(driver)) and --dev. No driver otherwise: one line naming the same fixes, and the boot goes on. Refinement 1: the refusal also requires tenancy.posture === 'single', because R20c (auth registered, group posture, no driver anywhere) boots today and AuthPlugin.start() reads sys_organization only under 'single'. Refinement 2: 'declare a datasource' is spelled 'mount a storage driver in the stack's plugins', because R4 (a declared default datasource with autoConnect) still fails: config datasources connect in AppPlugin.start(), after AuthPlugin.start(). Every shape that booted still boots (R9, R9e, R10, R11, R20c, R12, R13). Every measured sys_organization failure is refused with neither downstream line (R1 showcase, R2, R4, R8, R8b, R8c). PR #23631 is a draft assigned to sunhaolin. Its body reads back byte-identical with one session-URL footer.",
"tests": "Final head 650e28a (merged with origin/main 7c6b7f4). The CLI closure was rebuilt first (turbo build --filter='@objectstack/cli...', 62 tasks, VERDICT command-exit 0). (1) The new pin test/serve-no-storage-boot-guard.integration.test.ts (5 production boots of bin/run.js with NODE_ENV unset: refusal; line before any start() with --no-server --preset minimal; a plugin registering driver.* in init() is silent; OS_DATABASE_URL control; --dev control) ran with 8 neighbouring serve tests (serve-config-boot-flat-docs, serve-config-boot-residual, serve-host-config-metadata-door, serve-migrate-exit-schema-parity, serve-view-container-read, serve-package-declared-capabilities, serve-runtime-state-project-key, serve-host-config): 'Test Files 9 passed (9) / Tests 61 passed (61)', VERDICT command-exit 0. The pin alone took 23.58 s, 24.37 s and 24.68 s over three runs. (2) pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 'Test Files 290 passed (290) / Tests 4323 passed (4323)', VERDICT command-exit 0. The rest of the integration layer is declared to CI. (3) pnpm --filter @objectstack/cli typecheck: exit 0, 'check:test-typecheck: OK'. The pin is in the tsconfig.test.json program (tsc --listFilesOnly, 1 hit). (4) Ablation of the refusal, done on the committed fix through scripts/ablation-replace.mjs with its restore trap, before the merge, which touches no line of this diff. The condition got 'false &&' prefixed: anchor x1 -> x0, blob c016f7b009c1 -> 139994049e2d. After the CLI build, ablation-dist-preflight found the marker in 1 built file. The pin read 'Tests 1 failed | 4 passed': the refusal leg failed on 'no refusal was printed', and the boot it read printed '[ObjectQL] No driver available for object sys_organization' and 'ERROR sys_metadata could NOT be read at boot'. Restore leg: blob == HEAD c016f7b009c1 and git diff HEAD empty. Then a rebuild and preflight --absent ('absent from all 632 built files', 'working tree clean against HEAD'), and the pin read 'Tests 5 passed (5)'. (5) Probe matrix with throwaway host-config fixtures and the built CLI. Before = base 9f81260 or report 6184721596 (8d05456); after = this branch, with R2, R3, R9, R10, R11 and R20c re-measured on 650e28a. R1 showcase+secret: refused, 0 downstream lines. R2: refused, the fixture's init marker present and its start marker absent. R3 (no secret): the line, then the same package-service failure as before. R4, R8, R8b, R8c: failed before, refused now. R9, R9e: boot, silent. R10, R11, R20c: boot before; line + boot now. R12 OS_DATABASE_URL and R13 --dev: boot, silent.",
"gates": "Local, on 650e28a. (a) node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 86 commands (19 of them from the scripts/check-cli-test-child-env.mjs edit). All 86 were run with exit codes captured before any pipe: 86 exit 0. --ran over the 'CMD :: exit N' record: '86 derived famil(ies) accounted for, 86 run, 0 NOT-MEASURED (a DERIVED zero)', exit 0. An earlier 67-command run on the pre-merge head had two reds, both since resolved. check:cli-test-child-env exited 1 on two self-test pins: the it.each env, and the new bin/run.js spawner missing from the pinned population. That was fixed by literal env legs and the roster entry. check:dual-build-cjs-loads exited 3 with PREREQUISITE NOT MET. Both are green in the final run. (b) The edited gate's own suite: node scripts/check-cli-test-child-env.mjs --self-test ('152 cases pass') and its audit, both exit 0. node scripts/check-ratchet-remedy-authority.mjs and its --self-test, which name it, exit 0. (c) Read though not derived: pnpm check:startup-registry-verdict exit 0, pnpm check:init-service-contract exit 0. (d) Lint narrowed per the lane order: eslint --no-inline-config --format json over serve.ts, the pin and check-cli-test-child-env.mjs, exit 0, 0 messages. Three pieces of evidence: the root lint script is 'eslint . --no-inline-config' (same flags); eslint.config.mjs enables no type-aware linting for any file, so verdicts are per file; --print-config resolves active rules for all three files (5, 5, 2), so none is ignored. Repo-wide pnpm lint is CI's. (e) Local-only families check:dev-prereqs and check:skill-frame-freshness are not owed: no packages/spec build input and no four-axis frame file is in the diff. (f) CI on PR #23631 head 650e28a, read once at open: 23 check-runs, 21 in_progress, 1 success, 1 skipped. The required five (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard) are in_progress. Not waited on, per the role.",
"mcp_calls": "0",
"api_writes": "2 relay strokes, both through scripts/pm/with-fleet.sh --via dispatch (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]). (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) then POST /repos//issues/23631/assignees ['sunhaolin']; read back 11736 bytes sent, 11736 stored. (2) This os-dev-report: POST /repos//issues/23573/comments. 0 label writes: the dispatch named none, and skip-changeset does not apply because a changeset ships. Not REST: git push of the branch, 7 pushes, all fast-forward with no force. The first moved the empty branch 8d05456 -> 9f81260 as the write probe; the last pushed the merge 650e28a. Reads: GET issue 23573 and its comments, GET pulls/23631, GET check-runs.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door OS_DATABASE_URL=redis://127.0.0.1:6399/0 objectstack serve objectstack.config.ts --dev (host fixture, auth secret, at 650e28a) printed '✓ Server is ready' with banner 'Driver: SqlDriver(better-sqlite3) → :memory:'. No line says the URL was ignored, so the operator's named database is silently replaced by an in-memory store. · evidence: inferDriverTypeFromUrl (packages/cli/src/utils/storage-driver.ts) answers '' for an unrecognized scheme, and resolveStorageDefinition falls through to the dev ':memory:' default. An unrecognized OS_DATABASE_DRIVER is refused there ('Booting on the SQLite default instead would silently ignore the driver you asked for'), but the URL arm has no such refusal. In production the same URL now reaches this PR's no-storage refusal ('no recognized OS_DATABASE_URL'), so the silent half is dev only. Landing: domain:cli. · dedupe words: unrecognized OS_DATABASE_URL scheme ignored; serve --dev unknown database URL falls back to memory; inferDriverTypeFromUrl empty string silent SQLite default",
"class: a · reach: public door objectstack serve objectstack.config.ts (production) of a host config declaring datasources [{ name: 'default', driver: 'sqlite', config: { filename }, autoConnect: true }] with OS_AUTH_SECRET set. Base 9f81260: exit 1, 'No driver available for object sys_organization' (R4). With this PR it is refused by the no-storage guard. The declared default datasource never serves the boot, because AppPlugin.start() connects config datasources after AuthPlugin.start() needs a driver (debug start order measured in 6184721596). · evidence: probe-base-R4 and probe-new-R4 logs in the PM scratchpad issue-23573/b. Landing: packages/runtime AppPlugin datasource connect timing (domain owner to route). · dedupe words: host config declared default datasource connects after auth start; datasources autoConnect default production serve sys_organization; AppPlugin start connects datasources too late",
"class: a · reach: public door objectstack serve objectstack.config.ts --no-server --preset minimal (production, no storage, R10 at 650e28a), and likewise R3, R11 and R20c. objectql prints 'ERROR sys_metadata could NOT be read at boot ... Fix: check the datasource behind sys_metadata - connection, credentials, and that the table exists - then restart.' No datasource was declared and no driver is registered. This PR's '⚠ No storage:' line now precedes it with the real fixes, but the ERROR's own remedy still names a datasource nobody declared. 6184721596 carried this on #23573, which this PR closes, so it needs its own card. Landing: packages/objectql/src/plugin.ts hydration ERROR (a hold-off on this claim). · dedupe words: sys_metadata hydration error remedy no driver; objectql DB hydration skipped no datasource declared; misleading fix datasource behind sys_metadata",
"carrier: none (承接者:无) · noted, not filed: R3 (production host config, no auth secret, default preset) still dies at package-service start. Its error 'Execute requires options.object to select a driver, or a default driver to be configured. Configure datasourceMapping with default: true ...' now follows the new line with the real fixes. Refusing it was outside the ruling's predicate. In the PR's Acceptance notes."
],
"deviations": [
"Refusal predicate refined from 'auth service registered' to 'auth registered AND tenancy.posture === single'. R20c boots today with auth registered and no driver anywhere: stack AuthPlugin({ membershipPolicy: 'invite-only' }) + OrganizationsPlugin, OS_TENANCY_POSTURE=group, OS_PLATFORM_OWNER_EMAIL, --no-server --preset minimal. The ruled predicate would have refused it, against the claim's 'a configuration that boots today is never refused'. AuthPlugin.start() reads sys_organization only when this.tenancy.posture === 'single' (auth-plugin.ts start()). R16-R21 were probed to find R20c.",
"Remedy 'declare a datasource' is spelled 'mount a storage driver in the stack's plugins (e.g. new DriverPlugin(driver) from @objectstack/runtime)'. R4 declares a default datasource with autoConnect and still fails, because config datasources connect after AuthPlugin.start(). That defect is filed above as out-of-scope.",
"File surface: scripts/check-cli-test-child-env.mjs (+7/-1). It pins the exact set of test files that spawn bin/run.js and requires a file joining it to be named in the PR. The roster entry has its reason, and 'seven' became 'eight'. The ordered pin (production boots of the built CLI) cannot avoid joining that population. Not a hold-off, and not a governed surface. Declared here as a breach of the claim's listed surface.",
"The pin is named .integration.test.ts, not .e2e.test.ts. The .e2e name moves a file to the nightly run on main (OS_TEST_TIERS, scripts/nightly-tiers.mjs), and this pin guards a boot order any serve.ts edit can break. Its budget is a header line, '// test-budget: 40 — spawns the real producer once per single-cause fixture: …' (ruling record 6181973363, read back through readTestBudget): the slower of two local readings, 24.4 s on a shared container, x 1.25, rounded up to 40. No CI reading exists yet.",
"The no-auth line is worded as of the end of plugin init ('… so objects have no store unless a plugin registers one when it starts'), not as a verdict. A start() can still register a driver: R11's onEnable runs in AppPlugin.start() after the guard's init(). AGENTS.md 'Startup registry reads' governs this. R11 therefore gets the line and boots, as hypothesis (b) expected.",
"Hypotheses (a) and (b) hold: both are read from the kernel registry, with no CLI-side auth flag. 'auth' and 'tenancy' are direct registerService entries made unconditionally in AuthPlugin.init(). Drivers are the 'driver.' keys plus the engine's getDefaultDriverName(). Hypothesis (c): a refused boot left no disk side effects (no .objectstack in the fixture or in examples/app-showcase, OS_HOME empty). The only Phase-1 output is two init() log lines: the SDUI-manifest notice and the StorageServicePlugin local-driver warning.",
"Conflict noted. The dispatch's CLI lane always adds 'pnpm lint', while the role file says repo-wide scans are CI's and never owed. I took the dispatch's own alternative, the proven narrowing with three pieces of evidence (gates d).",
"The harness's attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Commits carry the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude), and the PR body ends with the session-URL footer, because AGENTS.md takes precedence. The merge commit 650e28a carries git's default message and no trailers.",
"The empty branch was fast-forwarded from 8d05456 to origin/main 9f81260 by the first push, with no force. origin/main 7c6b7f4 was merged before the PR opened (AGENTS §10). It brought PR #23469's 5c-ter OrganizationOwnershipBootGuardPlugin and objectql changes. The probe matrix rows marked * and the whole test and gate set were re-run after it.",
"Cleanup is the step after this comment: rm -rf the worktree's node_modules, then git worktree remove ../objectstack-issue-23573 with no --force. Every commit is pushed and the branch head equals the remote's 650e28a.",
"Extra builds for measurement only: @objectstack/organizations, and the examples/app-showcase closure for R1. Nothing from them is committed. Probe fixtures and logs are in the PM scratchpad, issue-23573/b (probe2.mjs, probe-.log). Logs are tagged per run, so none is overwritten."
],
"files_changed": [
".changeset/23573-serve-no-storage-loud.md",
"packages/cli/src/commands/serve.ts",
"packages/cli/test/serve-no-storage-boot-guard.integration.test.ts",
"scripts/check-cli-test-child-env.mjs"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #23631 comment 6187623746 · 2026-10-15T08:14Z
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed: PR #23631 →
edac2183c0· 2026-10-15T08:55Zdomain:cliseat 2 (#22648) ·sunhaolin· sessionsession_01Q9PZ5Pzqcus4woVvTZwWNU· holder of amended claim6185741820, released by this landing. Review: the seat record6187623746on the PR (pointer6187636323on this card). No contract review was owed.merged: true,merged_at2026-10-15T08:54Z, through the merge queue (added_to_merge_queue2026-10-15T08:18Z)gh-readonly-queue/main/pr-23631-*git ls-remote, 0 refs)origin/mainedac2183c0is an ancestor; one parent (b513343ac1); 4 files, +403 / −1, as reviewed at650e28a731Fixes #23573onlydomain:clicards (read at this stamp), #23573 no longer among themState change: #23573 closed
completedby the merge;pm:dispatchedremoved in this act.Follow-ups, filed from the build report:
--devignores an unrecognisedOS_DATABASE_URLscheme.datasourcesaccepts a datasource nameddefault, which every boot refuses as host-reserved —defineStack/os validate/ publish pass a config that boots in no posture #23634: a declared default datasource connects afterAuthPlugin.start. Once that lands, this guard's remedy can name "declare a default datasource" again.sys_metadataERROR names a datasource nobody declared.Option E (host-config
servefalling back to the shared resolver's default file) was not taken; it is the maintainer's to rule.Generated by Claude Code