From 9db88a290a3a61d1f31339dd2abcbb9e774ae49b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 07:51:52 +0000 Subject: [PATCH] docs(skills): the platform skill's operations reference says the official image refuses to start without its database and secrets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Docker / Kubernetes row and a Common ops pitfalls row teach what `os start` does under the image's `OS_DEPLOYMENT_DURABILITY_CHECK_ENABLED=true` preset (exit 1 naming the unset ones before any database file or secret is written; an explicit `file:` URL on a mounted volume passes) and how an upgrade keeps the values an old container generated under `/home/node/.objectstack`. Paid inside the file: the vitest block that plugin-development.md § Testing Plugins / § Using Plugins carries, the scaffolded-project spelling every domain skill's Verify section carries, the test-seed bullet the `os data` row and objectstack-data seeds.md carry, the cloud-connection plugin roster, and the intro sentence. The ratchet row moves down to the landed count. Eval 16 covers the upgrade path. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01L9qibyPEPCmwvSgLKLnUeg --- .../30857-image-secrets-refuse-start.md | 8 +++ scripts/check-skills-token-ratchet.mjs | 2 +- .../evals/config-plugins-ops.json | 17 +++++++ .../references/operations.md | 51 ++++--------------- 4 files changed, 36 insertions(+), 42 deletions(-) create mode 100644 .changeset/30857-image-secrets-refuse-start.md diff --git a/.changeset/30857-image-secrets-refuse-start.md b/.changeset/30857-image-secrets-refuse-start.md new file mode 100644 index 00000000000..9066d616c9c --- /dev/null +++ b/.changeset/30857-image-secrets-refuse-start.md @@ -0,0 +1,8 @@ +--- +'@objectstack/skills': patch +--- + +Clause-②: no +Metadata-protocol: none + +The platform skill's operations reference now says what the official image does on `main`: it refuses to start unless `OS_DATABASE_URL`, `OS_AUTH_SECRET` and `OS_SECRET_KEY` are set at runtime (an explicit `file:` URL on a mounted volume passes), and a Common ops pitfalls row tells an upgrade to keep the values an old container generated (`auth-secret`, `dev-crypto-key`, `data/objectstack.db` under `/home/node/.objectstack`) rather than set fresh ones, which would leave every saved credential unreadable. diff --git a/scripts/check-skills-token-ratchet.mjs b/scripts/check-skills-token-ratchet.mjs index 2d2a9b19146..913f78533cb 100644 --- a/scripts/check-skills-token-ratchet.mjs +++ b/scripts/check-skills-token-ratchet.mjs @@ -1014,7 +1014,7 @@ export const CEILINGS = new Map([ // 花板,怎么付?」, tap A, verbatim and untranslated: 「A:先削重复,余额按实测提高」. ['skills/objectstack-data/references/seeds.md', 1566], ['skills/objectstack-platform/references/bootstrap.md', 1351], - ['skills/objectstack-platform/references/operations.md', 3119], + ['skills/objectstack-platform/references/operations.md', 3108], ['skills/objectstack-platform/references/plugin-development.md', 3137], ['skills/objectstack-upgrade/references/examples-upgrade.md', 1197], diff --git a/skills/objectstack-platform/evals/config-plugins-ops.json b/skills/objectstack-platform/evals/config-plugins-ops.json index 5211203bd1b..d039d8f65c3 100644 --- a/skills/objectstack-platform/evals/config-plugins-ops.json +++ b/skills/objectstack-platform/evals/config-plugins-ops.json @@ -270,6 +270,23 @@ "PUT /api/v1/meta/action/crm_close_deal" ] } + }, + { + "id": 16, + "prompt": "We run the official image on one host with a volume at /home/node/.objectstack and never set any OS_* variable. After pulling the new tag the container exits at once: OS_DATABASE_URL, OS_AUTH_SECRET and OS_SECRET_KEY are not set. Get us back up without losing the logins and the stored credentials.", + "expected_output": "The image presets `OS_DEPLOYMENT_DURABILITY_CHECK_ENABLED=true`, so `os start` exits before it writes a database file or a secret while any of the three is unset; set all three on the run command, never in the image. Keep the values the old container generated, read from its `/home/node/.objectstack` (`docker exec` or the volume): `OS_AUTH_SECRET` is the contents of `auth-secret`, `OS_SECRET_KEY` the contents of `dev-crypto-key`, and `OS_DATABASE_URL=file:/home/node/.objectstack/data/objectstack.db` on the kept volume (or migrate that file to a database). Fresh secrets would leave every saved credential unreadable and sign everyone out.", + "files": [], + "assertions": { + "must_contain": [ + "auth-secret", + "dev-crypto-key", + "objectstack.db", + "/home/node/.objectstack" + ], + "must_not_contain": [ + "OS_DEV_CRYPTO_KEY" + ] + } } ] } diff --git a/skills/objectstack-platform/references/operations.md b/skills/objectstack-platform/references/operations.md index e058cd453b9..57944ff920b 100644 --- a/skills/objectstack-platform/references/operations.md +++ b/skills/objectstack-platform/references/operations.md @@ -1,8 +1,5 @@ # Part 3 — Operations: CLI, Testing, Deployment -Every project gets the same `os` command surface — `npm install` does not need -to be re-run when commands are added. - ## Daily-loop commands | Command | What it does | @@ -49,8 +46,7 @@ Both run the same gates: 3. **Widget-binding integrity (ADR-0021)** — every dashboard widget's `dataset` / `dimensions` / `values` resolves to a declared dataset/field. -Both exit non-zero with a located, corrective message. In a scaffolded project -these are `npm run validate` / `npm run build`. +Both exit non-zero with a located, corrective message. **Rule of thumb: never report a metadata change as done until `os validate` passes.** (`os lint` is a *separate* style/convention pass and does not @@ -104,12 +100,9 @@ Port resolution is the same for `os dev` and `os start` (both spawn `os serve`): | `os package publish [dist/objectstack.json] [--env … --install --visibility org]` | Upload the compiled artifact as a versioned package to the cloud catalog (ADR-0008 P3). Listing file, README, review and every screen the customer's administrator meets: [Marketplace handbook](https://objectstack.ai/docs/deployment/marketplace-handbook) | | `os package install [--version │ -u/--url http://localhost:3000]` | Install a package into a **running** runtime. Catalog mode (by manifest id) or air-gapped local-artifact mode. Auths as a **target runtime** user (`-t/--token` or `--email/--password`; env `OS_RUNTIME_TOKEN` or `OS_RUNTIME_EMAIL`/`OS_RUNTIME_PASSWORD`), not the cloud login | -> **Cloud connection & marketplace (`@objectstack/cloud-connection`, ADR-0008/0009).** -> The open runtime-side cloud client. Its plugins — -> `CloudConnectionPlugin`/`createCloudConnectionPlugin`, `MarketplaceProxyPlugin`, -> `MarketplaceInstallLocalPlugin`, `RuntimeConfigPlugin` — expose the install-local -> endpoint that `os package install` targets, ship the **Installed Apps** page and -> marketplace Setup nav as plugin metadata. +> **Cloud connection & marketplace (`@objectstack/cloud-connection`, ADR-0008/0009):** +> the open runtime-side cloud client; its `MarketplaceInstallLocalPlugin` serves the +> install-local endpoint `os package install` targets. ## Mobile push @@ -143,36 +136,11 @@ Authoring stops at `kernel.use(plugin)`; these three make it distributable. Use `LiteKernel` for unit / integration tests — it skips the cloud bits and plugin discovery, so tests run in milliseconds. Assemble the same plugins the CLI would auto-register (`use()` is synchronous and chainable): +`new ObjectQLPlugin()`, `new DriverPlugin(new InMemoryDriver())` and +`new AppPlugin(stack)` (`AppPlugin`: `@objectstack/runtime`; `stack`: the +config's default export). The vitest shape: [Testing Plugins](./plugin-development.md#testing-plugins); +the other imports: [Using Plugins](./plugin-development.md#using-plugins). -```typescript -import { describe, it, expect } from 'vitest'; -import { LiteKernel } from '@objectstack/core'; -import { ObjectQLPlugin } from '@objectstack/objectql'; -import { DriverPlugin, AppPlugin } from '@objectstack/runtime'; -import { InMemoryDriver } from '@objectstack/driver-memory'; -import stack from '../objectstack.config'; - -describe('stack boot', () => { - it('registers the task object', async () => { - const kernel = new LiteKernel({ logger: { level: 'silent' } }); - kernel - .use(new ObjectQLPlugin()) - .use(new DriverPlugin(new InMemoryDriver())) - .use(new AppPlugin(stack)); - await kernel.bootstrap(); - - const ql = kernel.getService('objectql'); - // query / mutate through the engine — see objectstack-query for the API - expect(ql).toBeDefined(); - - await kernel.shutdown(); - }); -}); -``` - -- **Seed in tests:** declare fixtures in the stack's `data:` collection — - they load at boot. See **objectstack-data** for env-scoped fixtures - (`env: ['test']`). - **Reset between tests:** create a fresh `LiteKernel` per test — with the in-memory driver a full bootstrap is cheap, and there is no `reset()`. - **HTTP-level tests:** mount `createHonoApp({ kernel })` from @@ -186,7 +154,7 @@ describe('stack boot', () => { | Edge (Cloudflare Workers, Vercel Edge) | `driver-turso` (**cloud / EE only**) | `@objectstack/hono` | Cold-start friendly; LiteKernel only | | Serverless (Lambda, Vercel functions) | `driver-sql` (`pg` with pooler) / `driver-mongodb` | `@objectstack/hono` | Mind cold-start: prefer LiteKernel | | Browser / WebContainer | `driver-sqlite-wasm` | none (in-process) | Playground, demos | -| Docker / Kubernetes | any | any | Use `os start` as the entrypoint; pin `PORT` and `EXPOSE` it (see [Ports & networking](#ports--networking)) | +| Docker / Kubernetes | any | any | Use `os start` as the entrypoint; pin `PORT` and `EXPOSE` it (see [Ports & networking](#ports--networking)). The official image (`ghcr.io/objectstack-ai/objectstack`) refuses to start unless `OS_DATABASE_URL`, `OS_AUTH_SECRET` and `OS_SECRET_KEY` are set at runtime, never baked in; an explicit `OS_DATABASE_URL=file:/path/on/a/mounted/volume/app.db` passes, so SQLite on a volume still works | ## Health & observability @@ -210,3 +178,4 @@ describe('stack boot', () => { | Hot reload misses new objects | Barrel `src/objects/index.ts` not re-exporting — check the file | | Login works but **Setup / Studio missing** | The logged-in user isn't a platform admin. Setup/Studio are gated by `setup.access` / `studio.access` on `admin_full_access`, auto-granted only to the first registered **human** (`bootstrapPlatformAdmin`). The `usr_system` seed identity is skipped, so it can't steal the grant. Either sign up first (`--seed-admin`/`--fresh` does this) or check `sys_user_permission_set` for a cross-tenant (`organization_id = NULL`) `admin_full_access` link on your user. Don't edit nav code first. | | A permission set is declared but grants nobody anything | Declaring a set is not assigning it — see "Assigning a permission set to a user" in **objectstack-data**. | +| The official image exits at start naming `OS_DATABASE_URL` / `OS_AUTH_SECRET` / `OS_SECRET_KEY` | The image presets `OS_DEPLOYMENT_DURABILITY_CHECK_ENABLED=true`: with any of the three unset, `os start` exits 1 before it writes a database file or a secret. Set the named ones on the run command. Upgrading a container that ran on the image's defaults? **Keep its generated values**, read from the old container's `/home/node/.objectstack` (`docker exec cat `, or its volume): `OS_AUTH_SECRET` is the contents of `auth-secret`, `OS_SECRET_KEY` the contents of `dev-crypto-key`, and the data is `data/objectstack.db` (`OS_DATABASE_URL=file:…` on the kept volume, or migrate it). Fresh values leave every saved credential unreadable and sign everyone out. |