Skip to content

Commit d0ebb8f

Browse files
docs(skills): the platform skill's operations reference says the official image refuses to start without its database and secrets (#30875)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoUSHs2PSwPeZKjinKYi84Wn)_ Fixes #30857 Clause-②: no Metadata-protocol: none Refs #30120 · PR #30720 (the `Skills-docs:` follow-up it named as owed). ## 维护者速读 - **改了什么**:平台技能的运维参考页。部署目标表的 Docker / Kubernetes 行写明官方镜像必须在运行时给出 `OS_DATABASE_URL`、`OS_AUTH_SECRET`、`OS_SECRET_KEY` 三项,否则拒绝启动,显式指向挂载卷的 `file:` 地址可以过;「Common ops pitfalls」新增一行:镜像启动即退出并点名这三项时怎么办,升级时沿用旧容器 `/home/node/.objectstack` 下已生成的值(`auth-secret`、`dev-crypto-key`、`data/objectstack.db`),不要换新。另加评测用例 16 和 changeset。 - **为什么改**:PR #30720 之后镜像的行为变了,技能还按旧文写,AI 照抄会写出镜像拒启的 `docker run`;更糟的是升级时换上新密钥,所有已保存的凭据都读不出来、所有人被登出。 - **风险与回滚**:只改技能文字与一个评测用例,不动 SKILL.md;token 额度靠删同文件里在别处有家的重复文字支付(3116 → 3108,上限 3119 → 3108 下调,不抬);回滚即撤这个 PR。 - **先看**:`skills/objectstack-platform/references/operations.md` 的 Docker / Kubernetes 行与「Common ops pitfalls」最后一行;其次被删的 Testing pattern 代码块(改为指向 plugin-development.md 的两节)。 - **两分钟验证**:`node scripts/check-skills-token-ratchet.mjs | grep operations.md` 应打印 `3108 / 3108`;对照 `docker/README.md`「What the image presets」与 `packages/cli/src/commands/start.ts` 的 `formatDurabilityRefusal`,三个变量名与三个文件名应一致。 - **需要你做的**:批准这个 Tier H PR(`skills/**` 需受管审批人 os-zhuang 或 hotlong 批准),之后席位落地。 ## What changes `skills/objectstack-platform/references/operations.md` (212 → 181 lines, 3116 → 3108 tokens, ceiling 3119 → 3108): - **Deployment targets, Docker / Kubernetes row:** 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. - **Common ops pitfalls, new last row:** symptom "the official image exits at start naming the three"; cause: the image presets `OS_DEPLOYMENT_DURABILITY_CHECK_ENABLED=true`, under which `os start` exits 1 before it writes a database file or a secret when any of the three is unset; fix: set them on the run command, and on an upgrade **keep the generated values** from the old container's `/home/node/.objectstack` (`docker exec CONTAINER cat FILE`, or its volume): `OS_AUTH_SECRET` is the contents of `auth-secret`, `OS_SECRET_KEY` the contents of `dev-crypto-key`, 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. Every sentence is read off `main`: `docker/README.md` ("What the image presets"), `content/docs/deployment/self-hosting.mdx` ("When the three are not set"), and the check itself in `packages/cli/src/commands/start.ts` (`readDurabilityCheckSignal`, `missingDurableVariables` — an explicit `file:` URL counts as chosen, `isDefaultedDatabase`; `formatDurabilityRefusal` prints the three file paths and exits 1 before `readOrCreateAuthSecret` runs). **Paid inside the file (the headroom was 3 tokens).** Each deletion, and where the content lives: 1. The intro sentence ("Every project gets the same `os` command surface — `npm install` does not need to be re-run when commands are added"): no rule and no author action; the command tables under it are the content. 2. "In a scaffolded project these are `npm run validate` / `npm run build`" in Verify your work: the Verify section of every domain skill carries the scaffolded spelling (objectstack-ai, -api, -automation, -data, -formula, -query, -ui `SKILL.md`). 3. The cloud-connection blockquote's plugin roster (`CloudConnectionPlugin`/`createCloudConnectionPlugin`, `MarketplaceProxyPlugin`, `RuntimeConfigPlugin`, "ship the Installed Apps page and marketplace Setup nav"): no author action; kept are the package, the ADRs and the one fact `os package install` needs — `MarketplaceInstallLocalPlugin` serves the install-local endpoint (also on the docs page `deployment/cli`, under `os package install`); `createCloudConnectionPlugin` stays in `plugin-development.md` § Well-Known Plugin Names. 4. The Testing pattern vitest block: the LiteKernel + vitest shape (`describe`/`it`, silent logger, `bootstrap()`, `getService`, `shutdown()`) is `plugin-development.md` § Testing Plugins, and the `ObjectQLPlugin` / `DriverPlugin` / `InMemoryDriver` imports are § Using Plugins. What was unique stays as prose: which three plugins a stack boot assembles, that `AppPlugin` is in `@objectstack/runtime` and `stack` is the config's default export, with links to both sections. 5. The "Seed in tests" bullet: the `os data` row already says the `data:` collection loads at boot, and objectstack-data `references/seeds.md` carries the `env: ['dev', 'test']` scoping. No line was re-wrapped to win room. The section headings are unchanged, so `SKILL.md` L402's section list still holds and no `SKILL.md` edit is needed. **`skills/objectstack-platform/evals/config-plugins-ops.json`:** case 16 (the platform evals cover deployment: case 6), spliced before the closing bracket so the file's formatting is untouched. Prompt: the official image on a kept volume, nothing set, exits after pulling the new tag; expected: set the three on the run command, carry `auth-secret`, `dev-crypto-key` and `data/objectstack.db` over from `/home/node/.objectstack`, never fresh secrets. `must_contain` the three file names and the home path; `must_not_contain` `OS_DEV_CRYPTO_KEY` (it does not count for `OS_SECRET_KEY`, PR #30720 acceptance notes). **`scripts/check-skills-token-ratchet.mjs`:** the operations.md row 3119 → 3108, pinned at the landed count (down only). **`.changeset/30857-image-secrets-refuse-start.md`:** `'@objectstack/skills': patch`, both declaration lines. ## Why Since PR #30720 (ruling A3 on #30120) an agent that follows the skill as written produces a `docker run` the image refuses, and on an existing deployment may set fresh secrets, which makes every encrypted saved credential unreadable. The triage audit (6679092587) asks for the text to say **keep** the existing secrets on an upgrade; the pitfall row says it in bold. ## Tests - `node scripts/check-skills-token-ratchet.mjs`: exit 0; `skills/objectstack-platform/references/operations.md 3108 / 3108 (+0)`; `--self-test` 97 cases pass. - `skills/objectstack-platform/evals/config-plugins-ops.json` parses; 16 cases, ids unique. - The derived gate battery and its `--ran` reconciliation: see Verification. ## Acceptance notes - The `os start` row ("It does **not** validate env or apply migrations") is unchanged: outside the image's preset that is still what `os start` does; the Docker row and the pitfall carry the preset's refusal. Left as is, scope. - Verify your work still says a bare field ref in a predicate "evaluates to `null` and silently hides an action/validation", while the formula skill's Verify section says the engine throws at runtime and the rule fails loudly. One of the two is stale; not this card's surface, noted for the skills seat. - `/home/node/.objectstack` is the image's home for the `node` user; the refusal prints the actual paths (and marks the ones present), so a custom `OS_HOME` is covered by reading the refusal. - The `OS_DEPLOYMENT_DURABILITY_CHECK_ENABLED=false` escape is not taught; neither `docker/README.md` nor `self-hosting.mdx` teaches it. - Whole-package readings (`skills/**`): the sum of every `SKILL.md` is 4523 lines before and after (no `SKILL.md` touched); the ratchet's shipped-bundle total is 227058 → 227374 tokens (+316: the eval fixture +324, operations.md −8). - Relay writes only: `git push`, `pr_create`, the `os-dev-report` comment. The harness's assignee / reviewer step is skipped (no MCP writes); the seat sets them at hand-over. ## Verification Head `bc38b673` (9db88a2 plus the merge of `main` cf478ea). Exit codes captured before any pipe, one `COMMAND :: exit CODE` line per command, then reconciled: - `bash scripts/pm/os-verify-lock.sh -c "pnpm build --filter='./packages/**' --concurrency=2"`: `VERDICT command-exit 0`, held the lock 402 s. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; the change set from the merge base): 62 commands, all 62 `:: exit 0` — among them `check:skills-token-ratchet` and its `--self-test` (97 cases), `check:skill-identifier-liveness`, `check:skill-frame-sync`, `check:skill-compatibility`, `check:skill-refs`, `check:pm-skill-id-lint`, `check:doc-authoring`, `check:empty-changeset`, `check:changeset-no-major`, `check:adr-0087-registration`, `check:doc-formula-expressions` (after the packages build), `check:comment-mask-corpus`, `check:pm-dispatch-gates`, `check:pm-governed-merges`. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran LIST`: "62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED"; changed lines 78 (+36 / −42) against the 3000 threshold. - `pnpm lint`, `test:repo` and the artifact-roster families are CI's run: the PR's check-runs. - `main` moved by four commits after the push (to 18870c9); none touches the four files and a `git merge-tree` of the two is clean. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (`packages/spec` untouched.) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` is CI's run. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (Not touched.) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_01L9qibyPEPCmwvSgLKLnUeg)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 514a422 commit d0ebb8f

4 files changed

Lines changed: 36 additions & 42 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
Clause-②: no
6+
Metadata-protocol: none
7+
8+
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.

‎scripts/check-skills-token-ratchet.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ export const CEILINGS = new Map([
10321032
// 花板,怎么付?」, tap A, verbatim and untranslated: 「A:先削重复,余额按实测提高」.
10331033
['skills/objectstack-data/references/seeds.md', 1566],
10341034
['skills/objectstack-platform/references/bootstrap.md', 1351],
1035-
['skills/objectstack-platform/references/operations.md', 3119],
1035+
['skills/objectstack-platform/references/operations.md', 3108],
10361036
['skills/objectstack-platform/references/plugin-development.md', 3137],
10371037
['skills/objectstack-upgrade/references/examples-upgrade.md', 1197],
10381038

‎skills/objectstack-platform/evals/config-plugins-ops.json‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,23 @@
270270
"PUT /api/v1/meta/action/crm_close_deal"
271271
]
272272
}
273+
},
274+
{
275+
"id": 16,
276+
"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.",
277+
"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.",
278+
"files": [],
279+
"assertions": {
280+
"must_contain": [
281+
"auth-secret",
282+
"dev-crypto-key",
283+
"objectstack.db",
284+
"/home/node/.objectstack"
285+
],
286+
"must_not_contain": [
287+
"OS_DEV_CRYPTO_KEY"
288+
]
289+
}
273290
}
274291
]
275292
}

‎skills/objectstack-platform/references/operations.md‎

Lines changed: 10 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
# Part 3 — Operations: CLI, Testing, Deployment
22

3-
Every project gets the same `os` command surface — `npm install` does not need
4-
to be re-run when commands are added.
5-
63
## Daily-loop commands
74

85
| Command | What it does |
@@ -49,8 +46,7 @@ Both run the same gates:
4946
3. **Widget-binding integrity (ADR-0021)** — every dashboard widget's
5047
`dataset` / `dimensions` / `values` resolves to a declared dataset/field.
5148

52-
Both exit non-zero with a located, corrective message. In a scaffolded project
53-
these are `npm run validate` / `npm run build`.
49+
Both exit non-zero with a located, corrective message.
5450

5551
**Rule of thumb: never report a metadata change as done until `os validate`
5652
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`):
104100
| `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) |
105101
| `os package install <manifest-id │ ./dist/objectstack.json> [--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 |
106102

107-
> **Cloud connection & marketplace (`@objectstack/cloud-connection`, ADR-0008/0009).**
108-
> The open runtime-side cloud client. Its plugins —
109-
> `CloudConnectionPlugin`/`createCloudConnectionPlugin`, `MarketplaceProxyPlugin`,
110-
> `MarketplaceInstallLocalPlugin`, `RuntimeConfigPlugin` — expose the install-local
111-
> endpoint that `os package install` targets, ship the **Installed Apps** page and
112-
> marketplace Setup nav as plugin metadata.
103+
> **Cloud connection & marketplace (`@objectstack/cloud-connection`, ADR-0008/0009):**
104+
> the open runtime-side cloud client; its `MarketplaceInstallLocalPlugin` serves the
105+
> install-local endpoint `os package install` targets.
113106
114107
## Mobile push
115108

@@ -143,36 +136,11 @@ Authoring stops at `kernel.use(plugin)`; these three make it distributable.
143136
Use `LiteKernel` for unit / integration tests — it skips the cloud bits and
144137
plugin discovery, so tests run in milliseconds. Assemble the same plugins the
145138
CLI would auto-register (`use()` is synchronous and chainable):
139+
`new ObjectQLPlugin()`, `new DriverPlugin(new InMemoryDriver())` and
140+
`new AppPlugin(stack)` (`AppPlugin`: `@objectstack/runtime`; `stack`: the
141+
config's default export). The vitest shape: [Testing Plugins](./plugin-development.md#testing-plugins);
142+
the other imports: [Using Plugins](./plugin-development.md#using-plugins).
146143

147-
```typescript
148-
import { describe, it, expect } from 'vitest';
149-
import { LiteKernel } from '@objectstack/core';
150-
import { ObjectQLPlugin } from '@objectstack/objectql';
151-
import { DriverPlugin, AppPlugin } from '@objectstack/runtime';
152-
import { InMemoryDriver } from '@objectstack/driver-memory';
153-
import stack from '../objectstack.config';
154-
155-
describe('stack boot', () => {
156-
it('registers the task object', async () => {
157-
const kernel = new LiteKernel({ logger: { level: 'silent' } });
158-
kernel
159-
.use(new ObjectQLPlugin())
160-
.use(new DriverPlugin(new InMemoryDriver()))
161-
.use(new AppPlugin(stack));
162-
await kernel.bootstrap();
163-
164-
const ql = kernel.getService<any>('objectql');
165-
// query / mutate through the engine — see objectstack-query for the API
166-
expect(ql).toBeDefined();
167-
168-
await kernel.shutdown();
169-
});
170-
});
171-
```
172-
173-
- **Seed in tests:** declare fixtures in the stack's `data:` collection —
174-
they load at boot. See **objectstack-data** for env-scoped fixtures
175-
(`env: ['test']`).
176144
- **Reset between tests:** create a fresh `LiteKernel` per test — with the
177145
in-memory driver a full bootstrap is cheap, and there is no `reset()`.
178146
- **HTTP-level tests:** mount `createHonoApp({ kernel })` from
@@ -186,7 +154,7 @@ describe('stack boot', () => {
186154
| Edge (Cloudflare Workers, Vercel Edge) | `driver-turso` (**cloud / EE only**) | `@objectstack/hono` | Cold-start friendly; LiteKernel only |
187155
| Serverless (Lambda, Vercel functions) | `driver-sql` (`pg` with pooler) / `driver-mongodb` | `@objectstack/hono` | Mind cold-start: prefer LiteKernel |
188156
| Browser / WebContainer | `driver-sqlite-wasm` | none (in-process) | Playground, demos |
189-
| Docker / Kubernetes | any | any | Use `os start` as the entrypoint; pin `PORT` and `EXPOSE` it (see [Ports & networking](#ports--networking)) |
157+
| 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 |
190158

191159
## Health & observability
192160

@@ -210,3 +178,4 @@ describe('stack boot', () => {
210178
| Hot reload misses new objects | Barrel `src/objects/index.ts` not re-exporting — check the file |
211179
| 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. |
212180
| 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**. |
181+
| 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 <container> cat <file>`, 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. |

0 commit comments

Comments
 (0)