diff --git a/.changeset/dev-fresh-claim-scope.md b/.changeset/dev-fresh-claim-scope.md new file mode 100644 index 0000000000..79768520df --- /dev/null +++ b/.changeset/dev-fresh-claim-scope.md @@ -0,0 +1,31 @@ +--- +'@objectstack/cli': patch +--- + +`os dev --fresh` states the isolation it actually delivers (#5594) + +The `--fresh` block promised its tempdir "owns ALL persistent state for this +run". After #4968 that is true of everything the CLI itself places — the dev +SQLite DB (`OS_HOME` → `/data/dev.db`, published as `OS_DATABASE_URL`), +the uploads root (published on the settings service's own name +`OS_STORAGE_LOCAL_ROOT`), and any plugin state keyed off `OS_HOME` — but it was +never true of state an **app** reaches by a relative path it declares itself. +Such a path is resolved by its own consumer against the process working +directory, which `--fresh` does not move, so the file lands in the project tree +and is still there after the run exits. + +The live specimen is deliberate authoring, not a bug: the showcase's +`showcase-external` datasource declares +`filename: '.objectstack/data/showcase_external.db'` and documents that the path +resolves against the project cwd — so a `--fresh` showcase run leaves that file +(plus `-wal`/`-shm`) behind. + +No behaviour changed. The `--fresh` flag help, the source comments, and the +`os dev` flag table in the CLI docs now name the covered surface +(`OS_HOME`-keyed state plus the env channels the CLI publishes) and state +plainly what falls outside it, with a docs note on declaring an absolute path +when a datasource should follow `--fresh`. + +Re-anchoring app-declared relative paths on `OS_HOME` is a behaviour change +resting on an open contract question ("relative to cwd" vs "relative to this +run's home") and is deliberately not taken here. diff --git a/content/docs/deployment/cli.mdx b/content/docs/deployment/cli.mdx index 05f3266a90..a51f601a5c 100644 --- a/content/docs/deployment/cli.mdx +++ b/content/docs/deployment/cli.mdx @@ -138,7 +138,7 @@ os dev --database file:./data/test.db --auth-secret $(openssl rand -hex 32) | `-p, --port ` | `OS_PORT` / `PORT` | Listen port (default `3000`). In dev a busy port auto-hops to the next free one; the banner shows the actual port. | | `--ui` | — | Force Console UI on (already on by default in dev) | | `--compile` | — | Force compiling `objectstack.config.ts` → `dist/objectstack.json` before starting (auto when the artifact is missing; ignored with `--artifact`) | -| `--fresh` | — | Ephemeral `OS_HOME` in the OS tempdir (clean DB, uploads, storage), auto-deleted on exit; implies `--seed-admin` | +| `--fresh` | — | Ephemeral `OS_HOME` in the OS tempdir (clean DB, uploads root, and other `OS_HOME`-keyed state), auto-deleted on exit; implies `--seed-admin`. See the scope note below | | `--seed-admin` / `--no-seed-admin` | — | Seed a dev admin (`admin@objectos.ai` / `admin123`) on an empty DB — default on; override with `--admin-email` / `--admin-password` | | `-v, --verbose` | — | Verbose output | @@ -146,6 +146,21 @@ By default `os dev` keeps your data between restarts in a project-local SQLite file at `.objectstack/data/dev.db` (created on first run). Pass `--database`, set `OS_DATABASE_URL`, or use `--fresh` for a throwaway run. + +`--fresh` isolates the state **the CLI places for the run**: everything keyed +off the ephemeral `OS_HOME` (the dev SQLite DB, the uploads root, plugin state +under `OS_HOME`) plus the env channels `os dev` publishes for it — +`OS_DATABASE_URL` and `OS_STORAGE_LOCAL_ROOT`. That tempdir is deleted on exit. + +It does **not** relocate state your app reaches by a **relative path it +declares itself** — for example a datasource with +`config: { filename: '.objectstack/data/my.db' }`. Such a path is resolved by +its own consumer against the process working directory, which `--fresh` does +not change, so the file is written into your project tree and is still there +after the run ends. Declare an absolute path (or one derived from `OS_HOME`) +when you want a datasource to follow `--fresh`. + + With a file-backed SQLite database, dev also provisions a sibling `.telemetry.` file registered as the `telemetry` datasource — lifecycle-classed system data (activity streams, job runs, notifications, diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index ef84961288..1870e5ae3a 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -97,15 +97,18 @@ export default class Dev extends Command { }), // ── Ephemeral / fresh-environment helpers ──────────────────────── - // `--fresh` creates an isolated tempdir for OS_HOME / DB / uploads - // so every run starts from a clean slate. Combine with `--seed-admin` + // `--fresh` creates an isolated tempdir for OS_HOME / DB / uploads, so + // every run starts from a clean slate for the OS_HOME-keyed state the + // CLI itself places (see the block in `run()` for the exact covered + // surface, and for what an app-declared relative path escapes — #5594). + // Combine with `--seed-admin` // (default-on when --fresh) to also provision a logged-in admin // account, so backend debugging never blocks on first-run wizards. // The seeded admin uses FIXED, well-known credentials by default // (admin@objectos.ai / admin123) so tooling never has to guess them — // override with --admin-email / --admin-password when needed. fresh: Flags.boolean({ - description: 'Start with an ephemeral OS_HOME under the OS tempdir (clean DB, uploads, storage); auto-deletes on exit. Implies --seed-admin (admin@objectos.ai / admin123) unless --no-seed-admin is given.', + description: 'Start with an ephemeral OS_HOME under the OS tempdir — clean DB, uploads root and other OS_HOME-keyed state for this run, auto-deleted on exit. State an app reaches by its own cwd-relative path (e.g. a datasource `filename: .objectstack/...`) is NOT covered and survives exit. Implies --seed-admin (admin@objectos.ai / admin123) unless --no-seed-admin is given.', default: false, }), 'seed-admin': Flags.boolean({ @@ -180,10 +183,29 @@ export default class Dev extends Command { const environmentId = flags['environment-id'] ?? process.env.OS_ENVIRONMENT_ID ?? 'env_local'; // ── --fresh: ephemeral OS_HOME under the OS tempdir ───────────── - // Creates a unique scratch dir that owns ALL persistent state for - // this run: the SQLite DB (via OS_HOME → /data/...), the - // storage-service uploads root (OS_STORAGE_LOCAL_ROOT), and any other - // state plugins keyed off OS_HOME. Auto-deleted on exit. + // Creates a unique scratch dir that owns the state this command can + // actually place, and nothing more. What it covers, exactly: + // - the dev SQLite DB the CLI resolves for the run + // (OS_HOME → /data/dev.db, published as OS_DATABASE_URL), + // - the storage-service uploads root, published on the settings + // service's own env name OS_STORAGE_LOCAL_ROOT (#4968), + // - any other state a plugin keys off OS_HOME. + // In one sentence: framework-owned, OS_HOME-keyed state plus the env + // channels this block publishes below. Auto-deleted on exit. + // + // NOT covered — state reached by an app-declared RELATIVE path (#5594). + // Such a path is resolved by its own consumer against the process cwd, + // which `--fresh` does not move, so it is written into the project tree + // and survives exit. The live specimen is deliberate authoring, not a + // bug: examples/app-showcase's `showcase-external` datasource declares + // `filename: '.objectstack/data/showcase_external.db'` and says in its + // own comment that the path resolves against the project cwd — so a + // `--fresh` run of the showcase leaves that file (+ -wal/-shm) behind. + // Re-anchoring app-declared relative paths on OS_HOME would be a + // behaviour change resting on an open contract question ("relative to + // cwd" vs "relative to this run's home"); it is deliberately NOT taken + // here, and this comment states the covered surface instead of an + // unqualified promise a reader would rely on for isolation. // // The uploads root MUST be published under the name the settings // service derives for it — `envKeyOf('storage','local_root')` (#4968).