Skip to content

Commit 072d08d

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-21464-s-metric
2 parents 1ca2bfe + 37442d4 commit 072d08d

37 files changed

Lines changed: 1616 additions & 381 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/runtime': minor
4+
'@objectstack/cli': minor
5+
---
6+
7+
fix(spec,runtime,cli)!: the in-memory (mingo) engine is no longer a boot store — every boot door refuses it and names SQLite instead (#21492, #21572)
8+
9+
Clause-②: yes (narrowing)
10+
11+
<!-- adr-0087: not-required (no-migration-prescription) no metadata body, authorable key or stored shape moves: the driver table keeps `memory`, `mingo`, `in-memory` and `inmemory` on its config-contract face, so `resolveDriverId` answers them exactly as before and a stored `datasource.driver: memory` still parses against `MemoryConfigSchema`; what narrows is the boot selection (`--database-driver`, `OS_DATABASE_DRIVER`, `databaseDriver`, a `memory://` or `mingo://` database URL, and a project's default datasource declared on the engine), which is host configuration that `objectstack migrate meta` does not rewrite — and no rewrite would be truthful, since the only replacement is a different engine the operator has to choose. The other categories are closed on facts: all three packages publish (not `unpublished`); no ADR-0087 id covers a boot selection (not `registered` / `already-registered`); and the change is runtime behaviour plus exported constant values, not an interface or a type alone (not `runtime-interface-only` / `type-surface-only`). -->
12+
13+
**BREAKING**: the in-memory (mingo) engine can no longer be selected as the store a server, a migration or an embedded stack boots on. It refuses every tenant-scoped read by design, so a boot on it signed a user in and then answered `503` to every data request; there was nothing working to keep. The retirement is made at the declaration: `@objectstack/spec`'s driver table withdrew `memory`, `mingo` and `in-memory` from its selection face (they stay on the config-contract face beside `inmemory`), and every boot door refuses the engine with one sentence that names the replacement.
14+
15+
- **`@objectstack/spec`** — `DATABASE_DRIVER_SELECTION_ALIASES` no longer lists `memory`, `mingo` or `in-memory`; `DATABASE_DRIVER_SELECTION_IDS` no longer lists `memory`; `resolveDatabaseDriverId` answers `undefined` for all four spellings. `resolveDriverId`, `DRIVER_ID_ALIASES`, `BUILTIN_DRIVER_IDS` and the `memory` config contract are unchanged.
16+
- **`@objectstack/cli`** — `--database-driver memory` is refused while the flags parse (`os dev`, `os start`); `OS_DATABASE_DRIVER=memory` / `mingo` / `in-memory` is refused before `os dev` or `os start` prints its Database row; `os serve`'s legacy path refuses the spellings and the `memory://` / `mingo://` schemes as a fatal boot error. The help no longer offers `memory://`.
17+
- **`@objectstack/runtime`** — `createStandaloneStack`, `createDefaultHostConfig` and `resolveStandaloneDatabase` (every ordinary `os dev` / `os start` / `os serve` boot and every `os migrate` subcommand) refuse the spellings, the `memory://` and `mingo://` schemes, and a project whose default datasource is declared with `driver: 'memory'`. `resolveProjectDatabaseUrl` refuses a retired driver selection ahead of every rung, and its `ProjectDatabaseUrlSource` type no longer has the `'memory-driver'` member. `ResolvedStandaloneDatabase.driver` never names `memory`. Two exports are added for hosts that refuse the engine themselves: `namesRetiredMemoryEngine` and `retiredMemoryEngineMessage`.
18+
- **Unchanged:** the `@objectstack/driver-memory` package; a declared non-default datasource with `driver: 'memory'` and a directly constructed `InMemoryDriver`, both still built; SQLite's dev step-down, whose last rung is still this driver.
19+
20+
Migration — one flag change:
21+
22+
- FROM `os dev --database-driver memory` (or `OS_DATABASE_DRIVER=memory`) TO `os dev --fresh` for a throwaway database deleted on exit.
23+
- FROM `OS_DATABASE_URL=memory://…` / `--database memory://…` / `databaseUrl: 'memory://…'` TO `:memory:` (SQLite's own in-memory database), e.g. `OS_DATABASE_URL=:memory:`.
24+
- FROM a default datasource declared `{ driver: 'memory' }` TO a SQLite one, e.g. `{ driver: 'sqlite', config: { filename: ':memory:' } }`.
25+
26+
No shipped example selects the engine. It ships as `minor` under the launch-window convention for accept-set narrowings.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
An expanded view of a stored view container is reported as tenant-authored on an unscoped kernel, as it already was on an environment-scoped one: not resettable, and with no `code` layer
6+
7+
Clause-②: no
8+
9+
On an unscoped (control-plane) kernel, registry hydration registers each view a stored environment-wide container expands, under that view's own name. The container was registered with the tenant-authorship marker (`_provenance: 'org'`), and its expansions were not. An expansion of a container bound to a package therefore carried that package's id and no marker, and the registry's artifact lookup took it for a view the package ships. For such a name `getMetaItem` (`GET /api/v1/meta/view/NAME`) answered `resettable: true`, and `getMetaItemLayered` (`/layers`) answered the stored container's expansion as the `code` layer. The `code` layer was also wrong for an expansion of a package-less container. An environment-scoped kernel registers nothing, and answered `resettable: false` and `code: null`.
10+
11+
Each registered expansion now carries its container's marker, applied before the expansion's own artifact envelope, in the same order the container gets it. Where the container's own package ships a view of that name, that artifact's envelope still wins (ADR-0010 §3.3). Both kernels now give the same answer for every expanded name. Studio's reset affordance and its code-versus-overlay diff are drawn from these two values.
12+
13+
The save door is unchanged: it accepts a write by an expanded name on both kernels, as before, and the stored row then answers that name.

‎.github/workflows/release.yml‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,10 @@ jobs:
790790
# `contents: write` is for GitHub Releases, never for refs: this job runs
791791
# no `git push` of any kind.
792792
contents: write
793+
# `actions: read` is the Releases backfill's guard: the audit reads this
794+
# workflow's runs and their jobs to ask whether the same version's
795+
# publish is in flight in another run. Reads only, never a cancel.
796+
actions: read
793797
outputs:
794798
# "the docker job must build" — set only when npm ALREADY has this
795799
# version's WHOLE fixed group and its runtime image is missing.
@@ -1066,9 +1070,33 @@ jobs:
10661070
;;
10671071
esac
10681072
1073+
# ── the publish in flight, before any Releases backfill ───────────
1074+
# npm settles BEFORE the publish job reaches its own "Create GitHub
1075+
# Releases" step, so a landing audited in that window found the group
1076+
# complete and wrote the same Releases beside it: on 17.6.0 the
1077+
# publish of run 36955885276 wrote them 03:03:47Z -> 03:05:42Z, this
1078+
# backfill in run 36958423332 started at 03:04:37Z, and five tags got
1079+
# two Release objects each. The two writers are in different runs,
1080+
# so no `needs:` can order them; the publish's own run creates its
1081+
# Releases and D4 asset, and a landing after it finishes backfills
1082+
# whatever it did not. scripts/release-pending-publish.mjs `in-flight`
1083+
# (its --self-test, run by lint.yml, holds the rule) answers
1084+
# `in-flight` on anything it cannot read, so nothing is backfilled off
1085+
# a guess. It only ever leaves `releases-missing` unset: this step
1086+
# stays green and the image request below is not its business.
10691087
if [ "$releases_ok" = true ]; then
10701088
echo "GitHub Releases + ADR-0087 D4 asset are present for ${version}."
1089+
elif ! flight=$(GITHUB_TOKEN="$GH_TOKEN" node scripts/release-pending-publish.mjs in-flight --version "$version" --version-commit "$version_commit" --head "$SHA" --workflow release.yml); then
1090+
echo "::warning::${version}'s GitHub Releases or ADR-0087 D4 asset are incomplete, and whether its publish is still in flight could not be asked (reason above). Nothing is backfilled off a guess; the next landing reads again."
1091+
elif [ "$(jq -r '.state' <<<"$flight")" != 'clear' ]; then
1092+
jq -c . <<<"$flight"
1093+
if [ "$(jq -r '.reason' <<<"$flight")" = 'publish-in-flight' ]; then
1094+
echo "::notice::${version}'s GitHub Releases or ADR-0087 D4 asset are incomplete, but its publish is still in flight ($(jq -r '.detail' <<<"$flight")). That run creates them; nothing is backfilled beside it. A landing after it finishes backfills whatever it did not."
1095+
else
1096+
echo "::warning::${version}'s GitHub Releases or ADR-0087 D4 asset are incomplete, and whether its publish is still in flight could not be read ($(jq -r '.detail' <<<"$flight")). Nothing is backfilled off a guess; the next landing reads again."
1097+
fi
10711098
else
1099+
jq -c . <<<"$flight"
10721100
echo "::warning::${version} is on npm but its GitHub Releases or the ADR-0087 D4 asset are incomplete (#4900) — backfilling."
10731101
echo "releases-missing=true" >> "$GITHUB_OUTPUT"
10741102
fi

‎content/docs/data-modeling/drivers.mdx‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ libSQL data stayed untouched. Pass the token with `--database-auth-token`
7979
| **SQLite (WASM)** | `@objectstack/driver-sqlite-wasm` | `SqliteWasmDriver` | `sqlite-wasm` \| `wasm-sqlite` \| `wasm` |
8080
| **MongoDB** | `@objectstack/driver-mongodb` | `MongoDBDriver` | `mongodb` \| `mongo` (single-tenant only — see [below](#multi-tenancy-not-supported)) |
8181
| **Turso / libSQL** | `@objectstack/driver-turso` (optional install — see the callout above) | `TursoDriver` | `turso` \| `libsql` |
82-
| **Memory** | `@objectstack/driver-memory` | `InMemoryDriver` | `memory` |
82+
| **Memory** | `@objectstack/driver-memory` | `InMemoryDriver` | none — not a boot store (see [Memory Driver](#memory-driver)) |
8383

8484
> All SQL flavours (PostgreSQL / MySQL / SQLite) are served by a single
8585
> `@objectstack/driver-sql` package — there is no separate
@@ -101,7 +101,7 @@ driver ships that contract as a zod schema, exported from `@objectstack/spec/dat
101101
| `sqlite` \| `sqlite3` | `SqliteConfigSchema` | `filename`, `autoMigrate` |
102102
| `sqlite-wasm` \| `wasm-sqlite` | `SqliteWasmConfigSchema` | `filename`, `persist` |
103103
| `mongo` \| `mongodb` | `MongoConfigSchema` | `url`, `host`, `port`, `database`, `username`, `password`, `authSource`, `options` |
104-
| `memory` \| `in-memory` | `MemoryConfigSchema` | `initialData`, `strictMode`, `persistence` |
104+
| `memory` \| `in-memory` \| `mingo` | `MemoryConfigSchema` | `initialData`, `strictMode`, `persistence` |
105105

106106
An unrecognised key is rejected with its correction, at authoring time and in the
107107
Setup → Datasources wizard alike:
@@ -798,13 +798,25 @@ exits unless persistence is explicitly requested.
798798
It is the **last-resort fallback** in dev mode: `objectstack dev` prefers native
799799
SQLite (`better-sqlite3`), falls back to the pure-JS WASM SQLite driver if the
800800
native binary is unavailable, and only drops to the in-memory driver if WASM also
801-
fails to load. Set `OS_DATABASE_DRIVER=memory` to select it explicitly.
801+
fails to load.
802+
803+
<Callout type="warn">
804+
**It cannot be selected as a boot store.** The driver has no row-level tenant
805+
isolation and refuses every tenant-scoped call, so a server booted on it signs a
806+
user in and then answers `503` to every data request. Every boot door refuses it,
807+
naming the replacement: `OS_DATABASE_DRIVER=memory` / `mingo` / `in-memory`,
808+
`--database-driver memory`, a `memory://` or `mingo://` database URL, and a
809+
project whose **default** datasource is declared with `driver: 'memory'`. For a
810+
throwaway database use `os dev --fresh`; for SQLite's own in-memory database use
811+
the URL `:memory:` (`OS_DATABASE_URL=:memory:`). The driver package itself is
812+
unchanged: the declared datasources and direct construction below still build it.
813+
</Callout>
802814

803815
**Both ways of building the driver are ephemeral by default** (#4083, #4065):
804816

805817
| How it is built | Persistence |
806818
| :--- | :--- |
807-
| A **declared datasource** — `{ driver: 'memory' }` in a stack/app config | **Ephemeral.** Nothing is written to disk unless the declaration sets `config.persistence` — and when it does, the destination is scoped per datasource, so two memory datasources never share one file. |
819+
| A **declared datasource** — `{ driver: 'memory' }` in a stack/app config (not as the project's default datasource, which the boot refuses) | **Ephemeral.** Nothing is written to disk unless the declaration sets `config.persistence` — and when it does, the destination is scoped per datasource, so two memory datasources never share one file. |
808820
| `new InMemoryDriver()` **constructed directly** | **Ephemeral.** Pass `persistence` to opt in (see below). Note the scoping above is the *factory's* job: two directly-constructed `'auto'` drivers in one process still share the single default path. |
809821

810822
```typescript

‎content/docs/deployment/cli.mdx‎

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ predicate/schema/binding mistakes that fail silently at runtime), and `os dev --
6969
</Callout>
7070

7171
<Callout type="tip">
72-
`os dev --ui` starts a dev server with the bundled Console UI, auto-loads ObjectQL, an in-memory driver when appropriate, and the Hono HTTP server.
72+
`os dev --ui` starts a dev server with the bundled Console UI, auto-loads ObjectQL, a SQLite database (a persistent project file by default, a throwaway one with `--fresh`), and the Hono HTTP server.
7373
</Callout>
7474

7575
## Commands
@@ -182,8 +182,8 @@ os dev --database file:./data/test.db --auth-secret $(openssl rand -hex 32)
182182
| Flag | Env equivalent | Purpose |
183183
|---|---|---|
184184
| `-a, --artifact <path\|url>` | `OS_ARTIFACT_URL` / `OS_ARTIFACT_PATH` | Boot a pre-built artifact directly; **skips auto-compile** |
185-
| `-d, --database <url>` | `OS_DATABASE_URL` | `file:…` / `libsql://` / `postgres://` / `mongodb://` / `memory://` |
186-
| `--database-driver <kind>` | `OS_DATABASE_DRIVER` | Force `sqlite` \| `sqlite-wasm` \| `turso` \| `postgres` \| `mysql` \| `mongodb` \| `memory` |
185+
| `-d, --database <url>` | `OS_DATABASE_URL` | `file:…` / `:memory:` / `libsql://` / `postgres://` / `mongodb://` |
186+
| `--database-driver <kind>` | `OS_DATABASE_DRIVER` | Force `sqlite` \| `sqlite-wasm` \| `turso` \| `postgres` \| `mysql` \| `mongodb` |
187187
| `--database-auth-token <t>` | `OS_DATABASE_AUTH_TOKEN` | libsql/Turso token |
188188
| `--auth-secret <s>` | `OS_AUTH_SECRET` | Override the dev-fallback secret |
189189
| `--environment-id <id>` | `OS_ENVIRONMENT_ID` | Environment identifier (default `env_local`) |
@@ -472,8 +472,8 @@ os start
472472
|---|---|---|
473473
| `-a, --artifact <path\|url>` | `OS_ARTIFACT_PATH` | File path or `http(s)://` URL to the compiled artifact |
474474
| — | `OS_ARTIFACT_URL` | Boot a published artifact **by reference**, optionally content-hash pinned via a `#sha256=` fragment. See [Artifact-pinned boot](/docs/deployment/self-hosting#artifact-pinned-boot-os_artifact_url) |
475-
| `-d, --database <url>` | `OS_DATABASE_URL` | `file:…` / `libsql://` / `postgres://` / `mongodb://` / `memory://` |
476-
| `--database-driver <kind>` | `OS_DATABASE_DRIVER` | Force `sqlite` \| `sqlite-wasm` \| `turso` \| `postgres` \| `mysql` \| `mongodb` \| `memory` when the URL is ambiguous |
475+
| `-d, --database <url>` | `OS_DATABASE_URL` | `file:…` / `:memory:` / `libsql://` / `postgres://` / `mongodb://` |
476+
| `--database-driver <kind>` | `OS_DATABASE_DRIVER` | Force `sqlite` \| `sqlite-wasm` \| `turso` \| `postgres` \| `mysql` \| `mongodb` when the URL is ambiguous |
477477
| `--database-auth-token <token>` | `OS_DATABASE_AUTH_TOKEN` | Auth token for libsql/Turso |
478478
| `--auth-secret <secret>` | `OS_AUTH_SECRET` / `AUTH_SECRET` | Secret for `@objectstack/plugin-auth`. If neither the flag nor the env var is set, `os start` **auto-generates one** and persists it at `<home>/auth-secret` |
479479
| `--home <dir>` | `OS_HOME` | Home directory for persistent state (default `<cwd>/.objectstack` when an `objectstack.config.ts` is present, otherwise `~/.objectstack`) |
@@ -502,10 +502,24 @@ The fall-through applies to the **conventional** locations only. Remote
502502
(`http(s)://`) sources cannot be checked up front and are validated when fetched.
503503
</Callout>
504504
505+
<Callout type="warn">
506+
**The in-memory (mingo) engine is not a boot store.** It refuses every
507+
tenant-scoped read, so a server booted on it signed you in and then answered
508+
`503` to every data request. Every boot command — `os dev`, `os start`,
509+
`os serve` and every `os migrate` subcommand — refuses it: the
510+
`--database-driver memory` flag value (rejected while the flags parse),
511+
`OS_DATABASE_DRIVER=memory` / `mingo` / `in-memory`, and a `memory://` or
512+
`mingo://` database URL, each with a message naming the replacement. Use SQLite
513+
instead: `os dev --fresh` for a throwaway database that is deleted on exit, or
514+
`--database :memory:` (`OS_DATABASE_URL=:memory:`) for SQLite's own in-memory
515+
database. A datasource you declare with `driver: 'memory'` still validates;
516+
as the project's **default** datasource it is refused at boot the same way.
517+
</Callout>
518+
505519
**What it boots:**
506520
- Reads the artifact's `manifest`, `objects`, `views`, `flows`, …
507521
- Auto-registers the platform services declared in `requires: [...]` (e.g. `ai`, `automation`, `analytics`, `auth`, `ui`). Declaring a **service** capability (`automation`, `analytics`, `ai`, `audit`, …) is a *requirement*: if its provider package isn't installed, boot **fails fast** with a clear error instead of silently starting without a capability you asked for. (`auth` and `ui` are tier-gated with their own opt-in rules — `auth`'s secret-gated skip is described below.)
508-
- Auto-detects the driver from the database URL scheme (`memory://` → in-memory, `libsql://`/`https://*.turso.*` → Turso — via the optional `@objectstack/driver-turso` package, and a loud failure with the install command when it is missing rather than a fallback to sqlite —, `postgres[ql]://`/`pg://` → pg, `mongodb[+srv]://` → MongoDB, otherwise sqlite)
522+
- Auto-detects the driver from the database URL scheme (`libsql://`/`https://*.turso.*` → Turso — via the optional `@objectstack/driver-turso` package, and a loud failure with the install command when it is missing rather than a fallback to sqlite —, `postgres[ql]://`/`pg://` → pg, `mongodb[+srv]://` → MongoDB, otherwise sqlite; `:memory:` is SQLite's own in-memory database). `memory://` and `mingo://` are refused — see the callout below.
509523
- Runs standalone boot mode with one active environment.
510524
511525
**Authentication:**

0 commit comments

Comments
 (0)