Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/30857-image-secrets-refuse-start.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion scripts/check-skills-token-ratchet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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],

Expand Down
17 changes: 17 additions & 0 deletions skills/objectstack-platform/evals/config-plugins-ops.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
}
]
}
51 changes: 10 additions & 41 deletions skills/objectstack-platform/references/operations.md
Original file line number Diff line number Diff line change
@@ -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 |
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <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 |

> **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

Expand Down Expand Up @@ -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<any>('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
Expand All @@ -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

Expand All @@ -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 <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. |
Loading