Skip to content

fix(spec,runtime,cli)!: the in-memory engine is no longer a boot store — every boot door refuses it and names SQLite instead - #21598

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-21492-retire-memory-boot
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-21492-retire-memory-boot

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21492
Fixes #21572

Clause-②: yes (narrowing)

What this lands

One landing, as triage ruled (5969348039, (ii)): #21572's spec row and #21492's runtime and CLI halves. The in-memory (mingo) engine is retired as a boot store, at its declaration, on both hosts. It refuses every tenant-scoped read by design, so a boot on it signed the seeded admin in and then answered 503 to every data read. Every boot door now refuses it with one sentence that names the replacement: os dev --fresh for a throwaway database, or the URL :memory: for SQLite's own in-memory database.

  • spec (packages/spec/src/data/driver/config-registry.zod.ts, the row from d480ef0d70 plus its stale doc comments): memory, mingo and in-memory move from the selection face to contractOnlyAliases, beside inmemory. DATABASE_DRIVER_SELECTION_ALIASES, DATABASE_DRIVER_SELECTION_IDS, the --database-driver allowlist and --help drop them by derivation. resolveDriverId is unchanged, so a stored datasource.driver: memory still parses. No artifact moves (check:generated: all 15 up to date).
  • runtime:
    • resolveProjectDatabaseUrl: the memory-driver rung becomes a refusal that stands ahead of every rung. ProjectDatabaseUrlSource loses 'memory-driver'.
    • resolveStandaloneDatabase: the URL detection recognises memory:// and mingo://, and refuseRetiredMemoryEngine refuses them. It names the source: the typed URL, OS_DATABASE_URL, or the project's declared default datasource.
    • unsupportedDriverMessage gives a spelling of the engine the retirement sentence, not the typo list.
    • StandaloneDatabaseDriverSchema now enumerates the selection face. The dispatch loses its memory arm, and ResolvedStandaloneDatabase.driver can no longer name it.
    • The sentence lives in one place, retiredMemoryEngineMessage, with the predicate namesRetiredMemoryEngine. Both are exported for the CLI.
  • cli:
    • inferDriverTypeFromUrl refuses memory:// and mingo:// with UnsupportedDriverError (recognized: false).
    • resolveStorageDefinition refuses the spellings with the same sentence. The memory definition arm is gone.
    • The --database help of dev and start offers :memory: instead of memory://.
    • The storage-driver.ts cli: OS_DATABASE_DRIVER=memory is advertised but serve.ts has no dispatch branch (falls through to SQLite) #3276 note and its module header are rewritten: the header now says which boots reach that file (legacy only). The parity pin's header, the database-driver-flag.ts doc and serve's comments are rewritten too.
  • docs: deployment/cli.mdx, deployment/environment-variables.mdx, data-modeling/drivers.mdx, plugins/index.mdx.
  • QA (docs/qa/platform-checklist/areas/cli.json, revisions bumped with history):
    • cli.dev-boot-contract: the memory tier becomes a REFUSED variant, with a negative and the :memory: variant added.
    • cli.migrate-duplicates-inventory: the no_sql_seam negative cites the seam-double pin.
    • devx's rider 5967974683: cli.build-own-contract's six live strings name this.exit(2). Its two dated history entries keep their wording, because they record what was true when written.
  • ledger: scripts/driver-memory-census.ledger.json's CLI row why no longer claims that a boot spelling routes to the driver.
  • one changeset with the narrowing kit: fix(...)!, BREAKING, minor for the three packages, and the ADR-0087 marker not-required (no-migration-prescription).

The driver-memory package is unchanged. A declared non-default driver: 'memory' datasource and a directly constructed InMemoryDriver still build: per the ruling, neither is a boot spelling.

A1: reach, measured on both trees

Before, on origin/main 901e7cf13a with examples/app-crm (closure build exit 0, 60/60 tasks):

invocation result
os dev --fresh --database-driver memory sign-in 200; GET /api/v1/data/crm_lead 503 SERVICE_UNAVAILABLE; the banner shows Database: file:/tmp/objectstack-dev-…/data/objectstack.db beside Driver: _InMemoryDriver → (in-memory) (the banner finding)
os migrate plan --database-url memory://qa exit 0, datasource 'default': connected (driver=memory …)
os dev --fresh (control) sign-in 200; crm_lead 200; SqlDriver(better-sqlite3)

After, on this branch (133871a251; the refusal code has not changed since):

door result
os dev --fresh --database-driver memory exit 2 at flag parse (oclif: Expected --database-driver=memory to be one of: sqlite, sqlite-wasm, postgres, mysql, mongodb, turso)
os start --database-driver memory exit 2, same
OS_DATABASE_DRIVER=memory os dev --fresh (and mingo) exit 1 before any Database: row, naming --fresh and :memory:
OS_DATABASE_DRIVER=in-memory os start exit 1, same sentence
os dev --fresh --database memory://x the parent prints Database: memory://x; the serve child refuses, exit 1
os migrate plan --database-url memory://qa / mingo://qa exit 1, [StandaloneStack] The database URL "…" selects the in-memory (mingo) engine …
os migrate duplicates --database-url memory://qa exit 1, {"error":"boot_failed", …} with the sentence
OS_MODE=legacy OS_DATABASE_DRIVER=memory os serve --dev exit 1 (legacy CLI host)
OS_MODE=legacy OS_DATABASE_URL=mingo://x os serve --dev exit 1 (legacy CLI host, inference)
OS_ARTIFACT_PATH=… OS_DATABASE_URL=memory:// os serve --dev (artifact path, empty cwd) exit 1
os dev --fresh (control) crm_lead 200, SqlDriver(better-sqlite3)
os dev --database :memory: (the replacement) crm_lead 200, Database: :memory:, SqlDriver(better-sqlite3) → :memory:

A2: which doors reach which resolver (symbol walk)

  • Runtime host (resolveStandaloneDatabase, which calls resolveProjectDatabaseUrl), reached by:
    • os serve over a config (createStandaloneStack) and over an artifact (createDefaultHostConfig, then createStandaloneStack); these are the serve children of os dev and os start;
    • every bootSchemaStack user: os migrate apply, plan, meta, duplicates, account-issuer, audit-metadata-bodies, files-to-references, multi-value-columns, recorded-by, resume, summary-nulls and value-shapes, plus os meta resync, os secret orphans / rewrap and os storage orphans;
    • probeMigrationTarget, the pre-boot probe of seven migrate subcommands;
    • every embedder.
  • The shared resolver alone (resolveProjectDatabaseUrl), reached by the os dev and os start parents (resolveDevDatabase, resolveStartDatabase) and by os db clean.
  • CLI host (resolveDriverType with resolveStorageDefinition), reached by os serve's legacy path only: OS_MODE=off|none|legacy, bootMode: 'off', or a host config (shouldBootWithLibrary).
  • One refusal per host, at the resolver. No command carries a copy.

A3: refusal shape and code

The refusals reuse each host's existing shape for an unrecognised driver or scheme: the runtime's plain Error, prefixed [StandaloneStack] inside the standalone stack, and the CLI's UnsupportedDriverError. No ledgered error code is involved, and the error-code ledger is not edited. The CLI flag door is oclif's own allowlist refusal, derived from the spec (see Acceptance notes).

A4: parity pin

driver-vocabulary-parity.test.ts is the pin, with no exemption:

  • The accept rows still iterate the selection face, so the three spellings leave them by derivation.
  • A new row asserts which spellings the table withdrew: contract face yes, selection face no, derived from DRIVER_ID_ALIASES.
  • New refuse rows: both hosts refuse memory / mingo / in-memory, in dev and in prod, and both name --fresh and :memory:.
  • New URL-door rows: both hosts refuse memory://, memory://named, mingo:// and MINGO://upper with no driver named.
  • A control: both hosts accept :memory: as sqlite.
  • The mingo:// cross-host divergence (the CLI accepted it, the runtime called it an unknown scheme) is closed: both hosts now recognise and refuse it.

A5: the census ledger row

scripts/driver-memory-census.ledger.json, gated by scripts/check-driver-memory-census.mjs. The file is under scripts/: it is not packages/spec and not a governed surface. The CLI manifest row's why is rewritten, its axis is unchanged, and check:driver-memory-census is green.

A6: reverse verification

Both legs were run through scripts/ablation-replace.mjs on the committed tree (b0b835ba75), in WRAP mode, with a trap that restores from HEAD by absolute path.

  • Runtime leg. Three sites were planted, each behind an OS_ABLATION_RUNTIME_* env marker: the rung refusal, refuseRetiredMemoryEngine, and the unsupportedDriverMessage branch. Each anchor went 1 to 0, and each blob changed.
    • Runtime rebuilt (exit 0); ablation-dist-preflight found all three markers in dist/ (exit 0 each).
    • Runtime pins: 14 red of 124, in 4 files.
    • CLI: dev-default-db 2 red; the parity pin 7 red, each failing on its runtime-half lines (:231, :254).
    • storage-driver.test.ts (the CLI host): green.
    • Restore: blob equals HEAD, git diff HEAD empty. Runtime rebuilt; preflight --absent exit 0 for all three markers.
  • CLI leg. Two sites were planted in storage-driver.ts, the inference refusal and the spelling refusal; the anchors went 1 to 0. The tests import that file from source, so no build was involved.
    • storage-driver.test.ts: 9 red. The parity pin: 7 red, each failing on its CLI-half lines (:225, :248).
    • The other host stays green: runtime pins 124/124, and dev-default-db (runtime-backed).
    • Restore: blob equals HEAD, 0 markers left, git diff HEAD empty.

Tests and gates (at b0b835ba75)

  • Full suites:
    • @objectstack/runtime (all projects): 319 files, 5210 passed and 19 skipped.
    • @objectstack/cli --project unit: 254 files, 3742 passed.
    • @objectstack/spec: 657 files, 18834 passed and 1 todo.
  • CLI integration, the 19 integration-tier files that boot os migrate / bootSchemaStack or spawn serve with a store flag or URL: 19 files, 210 passed and 1 skipped. duplicates.null-seam.test.ts now boots nothing, so the tier predicate files it under unit.
  • Typecheck: runtime, cli and spec typecheck all exit 0, test-typecheck debt held.
  • Lint: the full pnpm lint exits 0.
  • Spec artifacts: @objectstack/spec check:generated exits 0.
  • Derived gates: dispatch-gates --commands (no paths) derived 121 for the 31 changed paths, and all 121 exit 0. --ran reports 121 run and 0 NOT-MEASURED. Three of them (check:skill-examples, check:dual-build-cjs-loads, check:i18n-coverage) first exited 3 for a missing build. They were re-run after a full turbo run build --filter=!@objectstack/docs (exit 0) and exit 0.

Acceptance notes

  • The flag door names no replacement. --database-driver memory is refused by oclif's derived allowlist, in oclif's wording. It lists the legal kinds, sqlite among them, but does not name --fresh or :memory:. Making it do so would mean dropping the derived options: or special-casing a value at the consumer. Both are forbidden by database-driver-flag.ts and by the ruling. Every other door names the replacement.
  • The URL door refuses after the parent's banner. os dev --database memory://x prints the parent's Database: memory://x row, and the serve child then refuses, naming OS_DATABASE_URL (the channel it received the URL on). This is the same shape every unsupported scheme already has. The driver spelling is refused before the banner. The banner finding (a file: row beside an in-memory child) cannot occur any more.
  • The CLI's @objectstack/driver-memory dependency now serves no CLI-owned path. The ledger row says so. It is kept, because removing a published dependency is a packaging decision the ruling did not take. Carrier: none.
  • duplicates.ts's header still quotes the original memory://qa measurement. That is a dated record of the defect, not an instruction.

Generated by Claude Code

claude added 4 commits October 3, 2026 11:10
…tion face (A1 red-set probe)

Spec row edit only, for the dispatch's A1 red-set measurement.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM
…door

The spec row (d480ef0) withdrew memory, mingo and in-memory from the
driver vocabulary's selection face. This commit lands both hosts' halves:

- runtime: the memory-driver rung of resolveProjectDatabaseUrl becomes a
  refusal ahead of every rung; the standalone stack's URL detection
  recognises memory:// and mingo:// and refuses them, naming the source;
  StandaloneDatabaseDriverSchema enumerates the selection face; the
  dispatch loses its memory arm. One sentence, retiredMemoryEngineMessage,
  names the SQLite replacements (os dev --fresh, or :memory:).
- cli: inferDriverTypeFromUrl refuses memory:// and mingo://;
  resolveStorageDefinition refuses the spellings with the same sentence;
  the memory definition arm is gone; help, notes and the flag doc follow.
- tests: the red pins rewritten to pin the refusals; the parity pin gains
  the withdrawn-spelling and URL-door rows; the runtime boots move to
  SQLite :memory:; the no_sql_seam test drives a seam double.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
…r the in-memory engine's retirement

- docs: cli.mdx, environment-variables.mdx, data-modeling/drivers.mdx and
  plugins/index.mdx stop offering the engine as a boot store and name the
  SQLite replacements; the spec row's stale doc comments are rewritten.
- QA: cli.dev-boot-contract and cli.migrate-duplicates-inventory follow the
  retirement (the no_sql_seam negative cites the seam-double pin);
  cli.build-own-contract's exit-2 wording names this.exit(2).
- scripts/driver-memory-census.ledger.json: the CLI row's why no longer
  claims a boot spelling routes to the driver.
- one changeset with the narrowing kit.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/runtime, @objectstack/spec, touching 21 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/cli/src/utils/database-driver-flag.ts, packages/runtime/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

18 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json eb9ef791bdc0034559924924bfffd3f762b531d6.

⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/cli/src/utils/database-driver-flag.ts, packages/runtime/src/index.ts) — pages documenting those are invisible to this run
  • 2 anchor(s) matched too much of the corpus to be a work list: os dev (command, 32 pages), os serve (command, 30 pages)
  • 4 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 — 148 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 eb9ef791bdc0034559924924bfffd3f762b531d6 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 5bf5a5550351bc49be3c944f5220f6cc1e116bb4 — the merge of head b0b835ba75c9bb42ba7299874859065c9f3ef68f into base eb9ef791bdc0034559924924bfffd3f762b531d6, 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 5bf5a5550351bc49be3c944f5220f6cc1e116bb4 && git checkout 5bf5a5550351bc49be3c944f5220f6cc1e116bb4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin eb9ef791bdc0034559924924bfffd3f762b531d6 b0b835ba75c9bb42ba7299874859065c9f3ef68f && git checkout -B drift-repro eb9ef791bdc0034559924924bfffd3f762b531d6 && git merge --no-ff b0b835ba75c9bb42ba7299874859065c9f3ef68f

node scripts/docs-audit/affected-docs.mjs --json eb9ef791bdc0034559924924bfffd3f762b531d6

⚠️ 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 eb9ef791bdc0034559924924bfffd3f762b531d6 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b0b835ba75c9bb42ba7299874859065c9f3ef68f
Local-runs: none

Read: card #21492 (body and every comment, triage's first grade 5963328372, its lane amendment 5963850458, the reversal to retire 5964828718, the A-on-both-hosts ruling 5967968291, and the one-landing restaging 5969357294), card #21572 (body and every comment, including triage's 5969348039), PR #21598's body and 31-file list, the net diff against main at this head (merge base 901e7cf13a), and the check-runs on this head. Nothing was built, run or re-run locally.

① Derived judgments

Governing text: triage 5967968291 (A: retire at the declaration on both hosts; Q2 a: os migrate and a programmatic createStandaloneStack refuse the schemes too; driver-memory not retired; a direct construction is not a boot spelling), restaged by 5969348039 / 5969357294 as one landing carrying #21572's row commit d480ef0d70.

Spec (packages/spec/src/data/driver/config-registry.zod.ts, the #21572 row) — right.

Runtime (domain:cli's half) — right.

  • resolveProjectDatabaseUrl: the memory-driver rung is replaced by a refusal that stands ahead of every rung (namesRetiredMemoryEngine reads the CONTRACT face, so the four spellings and inmemory are refused as the retirement, not as a typo). Pinned ahead of an explicit file: URL and of an env memory:// URL, with a sqlite/sqlite-wasm control. The banner finding rides here: resolveDevDatabase passes --fresh's file as explicitUrl and the refusal precedes it, so os dev prints no Database: row for a store the child would refuse (pinned in dev-default-db.test.ts).
  • resolveStandaloneDatabase: detectDriverFromUrl now classifies (memory|mingo):// character-for-character as the CLI does, and refuseRetiredMemoryEngine refuses it naming the source (typed URL, OS_DATABASE_URL, or the declared DEFAULT datasource by name). unsupportedDriverMessage gives the engine's spellings the retirement sentence on both the env door and the zod databaseDriver config door. StandaloneDatabaseDriverSchema now enumerates the selection face, so the enum no longer offers a value its own doors refuse (the spec(data/driver): withdraw memory / mingo / in-memory from the driver vocabulary's selection face, contract-only kept (the declaration half of #21492's retirement) #21572 report's red pair in standalone-stack.mysql.test.ts is closed by that, not by a test rewrite). The memory arm leaves createStandaloneStack's dispatch and BootDriverKind keeps the never check honest. Q2 a holds: every bootSchemaStack user and every embedder resolves through this seam; probeMigrationTarget catches and steps aside (not_applicable) so the boot owns the loud refusal, pinned in sqlite-occupancy.test.ts.
  • Published surface, judged: narrowings — ProjectDatabaseUrlSource loses 'memory-driver'; ResolvedStandaloneDatabase.driver narrows to exclude memory; resolveProjectDatabaseUrl, resolveStandaloneDatabase, createStandaloneStack, createDefaultHostConfig throw where they accepted. Widenings — two new exports from the package index, namesRetiredMemoryEngine and retiredMemoryEngineMessage, so the CLI's legacy resolver cannot word the retirement differently. Both halves are declared in the changeset. Right.
  • The 5 runtime boots on memory:// move to SQLite :memory: as ruled; standalone-stack.test.ts keeps a memory:// case as a refusal pin and adds the :memory: round-trip as its control.

CLI — right.

  • inferDriverTypeFromUrl throws UnsupportedDriverError (recognized: false) for both schemes rather than returning '' (which would be the cli: OS_DATABASE_DRIVER=memory is advertised but serve.ts has no dispatch branch (falls through to SQLite) #3276 silent fall-through to SQLite) or a kind (which would leave the refusal to a message naming a variable the operator never set). resolveStorageDefinition refuses the spellings with the shared sentence and drops the memory definition arm. The storage-driver module header now says, measured, which boots reach it (legacy os serve only) — the observation the dev's first report flagged, discharged.
  • The --database-driver allowlist and --help drop memory by derivation (database-driver-allowlist.pin.test.ts pins the parse-time refusal for all three spellings). database-driver-flag.ts's "no driver is withheld today" doc is corrected. dev/start --database help offers :memory: instead of memory://.
  • The parity pin (driver-vocabulary-parity.test.ts): no exemption. A derived row asserts the withdrawn set equals exactly the four memory spellings; refuse rows for the three spellings on both hosts in dev and prod naming both replacements; URL-door rows for memory://, memory://named, mingo://, MINGO://upper on both hosts; a :memory: control accepted as sqlite on both. The mingo:// cross-host divergence (spec(data/driver): withdraw memory / mingo / in-memory from the driver vocabulary's selection face, contract-only kept (the declaration half of #21492's retirement) #21572 report, class b) is closed.
  • duplicates.null-seam.test.ts moves to a seam double per Q2 a (vi.hoisted mock of bootSchemaStack, no boot on the engine), keeps the real command on the refusal path, asserts the probes reached the seam and the stack was shut down, and adds the answering-seam control. Both halves pinned.

Docs, QA, ledger — right, with one nit.

  • Four docs pages advertise the retirement and the two replacements; drivers.mdx says plainly that a declared non-default driver: 'memory' datasource and direct construction still build, which keeps the claim as narrow as the enforcement. content/docs/releases/** untouched.
  • Nit (wrong, non-blocking): content/docs/deployment/cli.mdx line 522 ends "memory:// and mingo:// are refused — see the callout below", but the memory callout is the one inserted ABOVE "What it boots" (lines 505–517); the two callouts below it are about os start vs os serve and os secret orphans. One word, a docs-only follow-up or a rider on the next edit of the page.
  • cli.json: cli.dev-boot-contract revision 4 turns the memory tier into a REFUSED variant with a negative naming where each spelling is refused, adds the -d :memory: variant, and corrects the source note; cli.migrate-duplicates-inventory revision 4 cites the seam-double pin and keeps the memory:// run as a boot_failed probe; devx's rider 5967974683 rewrites the six live this.error() strings and leaves the two dated history entries, which is the right reading of a history ledger. Each bump carries its history entry.
  • scripts/driver-memory-census.ledger.json: the CLI row's why no longer claims a boot spelling routes to the driver; axis unchanged.

Not in the diff, as ruled: @objectstack/driver-memory untouched; no packages/spec path beyond the declared row; no ADR touched. ADR grep for the surface: ADR-0115 names driver-memory in plugin-dev's zero-config assembly, which is a direct construction (dev-plugin.ts still does new InMemoryDriver(...)) and so outside the ruling's "boot spelling" — not contradicted; no scripts/adr-anchors/ entry anchors any touched file.

② Semver level

.changeset/21492-retire-memory-boot-store.md: @objectstack/spec, @objectstack/runtime, @objectstack/cli at minor; title fix(spec,runtime,cli)!:; a **BREAKING** paragraph; a FROM → TO migration for each of the three doors (flag/env to --fresh, URL to :memory:, default datasource to a SQLite one); exactly one ADR-0087 marker, not-required (no-migration-prescription), with the other categories closed on facts.

  • The three packages that publish a change are exactly the three in the changeset; the files outside them (docs, QA, the census ledger) publish nothing.
  • The level is right under this repo's narrowing kit: an accept-set narrowing is BREAKING and ships as minor under the launch-window convention (check:changeset-no-major green, and the same shape as 713b0fa76d which the spec(data/driver): withdraw memory / mingo / in-memory from the driver vocabulary's selection face, contract-only kept (the declaration half of #21492's retirement) #21572 card names).
  • The ADR-0087 marker is right: no authorable key, metadata body or stored shape moves — datasource.driver: memory still parses against MemoryConfigSchema — so there is nothing a conversion could rewrite, and a rewrite to a different engine would not be truthful. No retiredKey() tombstone is owed because no authorable key is removed. Check Changeset green on this head.
  • The two added runtime exports are a widening inside a narrowing changeset; the dominant arm is the narrowing, the widening is declared in the body, and the Clause-② line carries one arm as the rule requires.

Clause-②: yes (narrowing) — right. The spec's published selection face drops three spellings and both hosts refuse boot selections they accepted; the PR body's line-initial Clause-②: yes (narrowing) matches the changeset's.

③ Boundary flags

Dev flags (report 5970391589, PR Acceptance notes) and the seat's ACCEPT (5970431837), each answered:

  1. Scope question raised to triage — a declared NON-default driver: 'memory' datasource still builds through service-datasource's factory. Judged: the PR's refusal scope matches the rulings' text as the text was derived. 5967968291's sentence "a stored datasource.driver: memory parses and is refused at boot" descends from the spec(data/driver): withdraw memory / mingo / in-memory from the driver vocabulary's selection face, contract-only kept (the declaration half of #21492's retirement) #21572 report's declared_datasource_door reading, which is the DEFAULT-datasource door (datasourceUrlOf translating the declaration to memory:// on the runtime's URL ladder); that door is refused here by name. The same ruling keeps driver-memory unretired and says a direct construction is not a boot spelling, and no ruling names a non-default declared datasource. The question is correctly escalated, not decided by the seat, and it does not block: the ruling's enumerated doors (flag, env, memory:// / mingo:// on both hosts, os migrate, programmatic createStandaloneStack, the default datasource) are all closed and pinned, the service-datasource door is domain:services, and the docs claim exactly what is enforced. Carrier: triage's answer on os dev --database-driver memory boots, 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; a follow-up card in domain:services only if triage rules the door in.
  2. The flag door refuses in oclif's wording, naming no replacement. Answered: the ruling itself says the flag and --help drop the spelling by derivation and forbids consumer-side subtraction, so the parse-time refusal in oclif's words is the ruling's own consequence; the next door the operator reaches (env, URL, config) names both replacements. Right; noted in the PR, not filed.
  3. os dev --database memory://x prints the parent's Database: row before the serve child refuses. Answered: the parent prints the URL it was handed and the child refuses that same URL, so one boot no longer names two stores; this is the shape every unsupported scheme has. The banner finding proper (a file: row beside an in-memory child) is refused before any row and pinned. Right.
  4. The CLI's @objectstack/driver-memory dependency kept. Answered: dropping a published dependency is a packaging decision the ruling did not take; the ledger row says why it stays. Right.
  5. Merge commit 6727c656c6 with git's default message, no trailer pair. Non-blocking: the landing squashes, so the merge commit's message does not reach main; the authored commits carry the model-free pair per AGENTS.md.
  6. devx rider: six live strings rewritten, two history entries kept. Right — a history entry records what was true when written.
  7. QA no_sql_seam negative moved to the seam double, memory:// run kept as a boot_failed probe. Matches Q2 a verbatim.
  8. open_questions: [] on the final report; A1–A6 all measured and the ablation turned each host's own pins red and left the other green, which is the reverse verification the parity pin promises.

Observations, not blocking, none filed (carrier none):

  • The dev SQLite step-down's last rung still constructs the engine (declared "Unchanged" in the changeset and named in drivers.mdx). It is not a spelling and the ruling does not reach it; it remains the one non-spelling path to a store that refuses tenant-scoped reads.
  • Console Pin Gate is skipped on this head (path filter). No spec export is removed and no api-surface shard moves, so a pinned-sibling import cannot break on a removed symbol; the one removed union member (ProjectDatabaseUrlSource.'memory-driver') is a backend runtime type. The landing seat still owes the sibling grep Post-Task step 4 names.
  • environment-variables.mdx's OS_DATABASE_POOL_MAX row still lists memory among pool-less drivers; still true on the contract face (a declared datasource), so not a defect.

Governance: not a governed-surface PR (no docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, docs/NORTH-STAR.md in the file list; Governed Surface Queue Guard green). Same-repo head. 1,376 changed lines, under the 5,000 human-merge threshold. Security posture: the change removes a boot door to a store with no tenant isolation; no role, permission or policy surface moves.

Gates on this head (latest run per name, read 2026-10-03T15:34Z after the last two concluded): every one of the seven required contexts is success — Lint & Repo Gates (111228240772, concluded 15:33Z), TypeScript Type Check (111231304620), Test Core (111233671067; shards 1–6 all success, 1/6 concluded 15:33Z), Dogfood Regression Gate (111229963643; 1–3 all success), Build Core (111228304857), Temporal Conformance (live PG + MySQL) (111228304871), Governed Surface Queue Guard (111228240740). Also success: Build Docs, Check Changeset (111229651411), Check Documentation Links, Dogfood Verify CLI, Flag docs affected by code changes, Spec property liveness, the three claim/single-writer/part-of guards, the four Type Check sub-jobs. skipped (path-filtered, not a verdict): Auto Label, Check PR Size re-run (its first run 111228240696 was success), Console Pin Gate, Packed-tarball smoke. No run on this head is unfinished and none failed.

Implemented-by: claude/issue-21492-retire-memory-boot
Reviewed-by: session_016GiHYRmLSNWTfbX9gVQkpz

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment