diff --git a/.changeset/30092-platform-skill-push-block.md b/.changeset/30092-platform-skill-push-block.md new file mode 100644 index 00000000000..8024a0470bc --- /dev/null +++ b/.changeset/30092-platform-skill-push-block.md @@ -0,0 +1,8 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): objectstack-platform teaches the stack's `push` block in `references/operations.md` (Mobile push): `transport: 'direct'` with `apns` / `fcm` blocks is the transport served today, each secret is named by `credentialRef` and never written inline, and the hosted relay (also what `{}` reads as) is refused at boot until its sender ships; the `requires` table points `push` at it, and the push eval case asserts the shape + +Clause-②: no +Metadata-protocol: none diff --git a/skills/objectstack-platform/SKILL.md b/skills/objectstack-platform/SKILL.md index c2b312bd1dc..b859d929f41 100644 --- a/skills/objectstack-platform/SKILL.md +++ b/skills/objectstack-platform/SKILL.md @@ -302,7 +302,7 @@ token through the `CAPABILITY_PROVIDERS` registry in | Token | Provider package | |:--|:--| | `automation` | `@objectstack/service-automation` — flows, and any declarative `connectors:` entry | -| `analytics` `cache` `storage` `queue` `job` `messaging` `realtime` `settings` `sms` `push` | `@objectstack/service-` + the token | +| `analytics` `cache` `storage` `queue` `job` `messaging` `realtime` `settings` `sms` `push` | `@objectstack/service-` + the token ([the `push` block](./references/operations.md#mobile-push)) | | `marketplace` | `@objectstack/service-package` | | `audit` `email` `sharing` `approvals` `webhooks` | `@objectstack/plugin-` + the token | | `pinyin-search` | `@objectstack/plugin-pinyin-search` | @@ -331,8 +331,7 @@ Five rules that change what you write: - **Declaring is a demand.** A capability YOU declared whose provider package is absent is a hard boot error; one the platform auto-injects for you stays best-effort (warn and continue). -- **`auth` implies `email`.** Auth callbacks (password reset, email - verification, magic link, invitation) need the mail service, so the CLI +- **`auth` implies `email`.** Auth callbacks need the mail service, so the CLI appends `email` whenever `auth` is required. - **Keep `automation` whenever `plugins:` lists a connector** — connector executors register their provider factories with it, and without it they have diff --git a/skills/objectstack-platform/evals/config-plugins-ops.json b/skills/objectstack-platform/evals/config-plugins-ops.json index 338f01b8faf..5211203bd1b 100644 --- a/skills/objectstack-platform/evals/config-plugins-ops.json +++ b/skills/objectstack-platform/evals/config-plugins-ops.json @@ -201,17 +201,20 @@ { "id": 12, "prompt": "Turn on push notifications for our `os serve` deployment: our own iOS and Android apps, signed with our APNs key and our Firebase project.", - "expected_output": "`requires: ['push']` on the stack root (the token mounts `@objectstack/service-push`) plus a top-level `push:` key on the stack definition for the transport; no host code, no `PushServicePlugin` in `plugins:`. The APNs signing key and the Firebase service-account JSON stay in the secrets layer (an environment variable in the open tier) and the config names them by reference (`credentialRef`): no `.p8` key, `privateKey` or service-account JSON written inline.", + "expected_output": "`requires: ['push']` on the stack root (the token mounts `@objectstack/service-push`) plus a top-level `push:` key on the stack definition; no host code, no `PushServicePlugin` in `plugins:`. Their own signed apps take `transport: 'direct'`, the transport served today, with an `apns` block (`teamId`, `keyId`, `topic`, `environment`, `credentialRef`) and an `fcm` block (`projectId`, `credentialRef`). The APNs signing key and the Firebase service-account JSON stay in the secrets layer (an environment variable in the open tier) and each `credentialRef` names one: no `.p8` key, `privateKey`, `serviceAccount` or service-account JSON written inline. Not the hosted relay, which the boot refuses until its sender ships.", "files": [], "assertions": { "must_contain": [ "requires", "'push'", - "push:" + "push:", + "transport: 'direct'", + "credentialRef" ], "must_not_contain": [ "BEGIN PRIVATE KEY", "privateKey:", + "serviceAccount:", "\"private_key\"" ] } diff --git a/skills/objectstack-platform/references/operations.md b/skills/objectstack-platform/references/operations.md index 0bfca5358c7..c1027960513 100644 --- a/skills/objectstack-platform/references/operations.md +++ b/skills/objectstack-platform/references/operations.md @@ -10,7 +10,7 @@ to be re-run when commands are added. | `os init` | Scaffold a new project (alternative to `npx create-objectstack`) | | `os dev` | Start the dev server with hot metadata reload. `--seed-admin` (default **on** for plain `os dev`) seeds a loginable dev admin **in-process via the runtime** (env vars `OS_SEED_ADMIN*`) on an **empty** DB only — idempotent, never overwrites an existing account (default `admin@objectos.ai` / `admin123`; override with `--admin-email` / `--admin-password`; disable with `--no-seed-admin`). `--fresh` = ephemeral clean OS_HOME/DB, implies `--seed-admin`. The seeded admin is promoted to **platform admin**, so Setup/Studio work on first login. | | `os dev --ui` | Also mount the bundled Console portal at `/_console/` (there is no separate `os studio` command) | -| `os validate` | Validate `objectstack.config.ts` — Zod protocol schema, CEL/predicate validation (`record.` existence), and widget-binding integrity. Same gates as `os build`, no artifact emitted. See [Verify your work](#verify-your-work). | +| `os validate` | Validate `objectstack.config.ts`: the `os build` gates, no artifact emitted — see [Verify your work](#verify-your-work) | | `os lint` | Style/convention lint on metadata files | | `os info` | Print a metadata summary of the config (objects, apps, and other collections; `--json`) | | `os doctor` | Diagnose common setup issues | @@ -39,7 +39,7 @@ os validate # Zod schema + CEL predicates + widget bindings — no artifact os build # the same three gates, plus emits dist/objectstack.json ``` -`os validate` and `os build` run the **same** structural + semantic gates: +Both run the same gates: 1. **Zod protocol schema** — the stack conforms to `@objectstack/spec`. 2. **CEL / predicate validation (ADR-0032)** — every `visible` / `disabled` / @@ -49,13 +49,12 @@ os build # the same three gates, plus emits dist/objectstack.json 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. Use `os validate` as the -fast inner-loop check after editing metadata and `os build` when you need -`dist/`. In a scaffolded project these are `npm run validate` / `npm run build`. +Both exit non-zero with a located, corrective message. In a scaffolded project +these are `npm run validate` / `npm run build`. **Rule of thumb: never report a metadata change as done until `os validate` -passes.** (`os lint` is a *separate* style/convention pass — naming, labels, -namespace prefixes — and does not replace `os validate`.) +passes.** (`os lint` is a *separate* style/convention pass and does not +replace `os validate`.) ## Ports & networking @@ -112,6 +111,22 @@ Port resolution is the same for `os dev` and `os start` (both spawn `os serve`): > endpoint that `os package install` targets, ship the **Installed Apps** page and > marketplace Setup nav as plugin metadata. +## Mobile push + +`requires: ['push']` reads the top-level `push` key (`PushServiceConfigSchema`). +Served today: `transport: 'direct'` only, one block per vendor, at least one: + +```ts +push: { transport: 'direct', + apns: { teamId, keyId, topic: 'com.acme.app', environment: 'production', credentialRef: 'APNS_P8_KEY' }, + fcm: { projectId, credentialRef: 'FCM_SA_JSON' } }, +``` + +`credentialRef` names each secret (the `.p8` key, the service-account JSON; an +env-var name in the open tier), never holds it: `privateKey`, `serviceAccount` or +a PEM/JSON value is refused. The relay transport (also what `{}` or no key reads +as) is refused at boot until its sender ships. + ## Shipping a plugin Authoring stops at `kernel.use(plugin)`; these three make it distributable. @@ -189,8 +204,6 @@ describe('stack boot', () => { | Symptom | Likely cause | |:--------|:-------------| | `os dev` hangs at "Loading metadata…" | Circular import in `objectstack.config.ts` — run `os validate` | -| `os start` exits with "Port N is already in use" | Intended: production never auto-shifts ports. Free the port or set `PORT=` — see [Ports & networking](#ports--networking) | -| better-auth `Invalid origin` 403 after a port/host change | Port or hostname out of sync with `OS_AUTH_URL` / `OS_TRUSTED_ORIGINS` — see [Ports & networking](#ports--networking) | | Migrations apply locally but not in cloud | `env` scoping on the dataset excludes the target environment | | Adapter 404s on auto-generated routes | `enable.apiEnabled: false` on the object, or missing `os build` | | LiteKernel test passes, ObjectKernel boot fails | Test missed a plugin the CLI auto-registers — compare your test's `use()` list against the `os dev` boot log |