diff --git a/docs/offscale-plugin.md b/docs/offscale-plugin.md index 082c4e56..715fd990 100644 --- a/docs/offscale-plugin.md +++ b/docs/offscale-plugin.md @@ -465,32 +465,20 @@ from nothing includes what nothing needs and misses what is awkward. The field s is the floor, not the ceiling. ```ts +// plugins/offscale/manifest.ts export const manifest = { - // ── Identity ───────────────────────────────────────────────────────────── - /** THE id. Route segment, table prefix, sidecar suffix, install key. One name, everywhere. */ - appName: 'offscale', - /** Who published it. The ONLY input that decides the mount prefix — see `mountPrefix()`. */ + /** Constant today. The one input to `mountPrefix()`, and the seam third parties hang off later. */ publisher: 'officerdev', /** The plugin's own semver. Updates compare against this. */ version: '1.0.0', /** Which platforms this build is good for. Refused at install when it does not match. */ platform: '>=1.0.0 <2.0.0', - // ── Presentation ───────────────────────────────────────────────────────── label: 'Offscale', summary: 'Your tailnet — machines, users, pre-auth keys and access policy', icon: 'Network', color: '#818cf8', - // ── Backend ────────────────────────────────────────────────────────────── - sidecar: { - /** PM2 process name. Always `officer-`, but written out because PM2 is told it verbatim. */ - process: 'officer-offscale', - /** `bun` or `node`. A field rather than an assumption: officer-pty needs node for node-pty's ABI. */ - runtime: 'bun', - script: 'sidecar/index.ts', - }, - // Named `permissions`, NOT `capabilities`. That word already means three different things here — the // permission registry, the officer-items store, and the sidecar's routing keys — and a fourth would be // one too many. `permissions` is accurate and free: the old table of that name went in 044aacf4. @@ -503,32 +491,44 @@ export const manifest = { ownerOnly: true, }, ], - - // ── Data ───────────────────────────────────────────────────────────────── - /** Drizzle schema. Every table must be prefixed `offscale_`; the installer enforces it. */ - schema: 'db/schema.ts', - - // ── Frontend ───────────────────────────────────────────────────────────── - frontend: { - /** Default export is mounted at `/*` by the generated Plugins.tsx. */ - router: 'web/Router.tsx', - /** `appRegistryMetas` — the panel apps this plugin contributes. */ - panels: 'web/panels.ts', - dock: { label: 'Offscale', to: '/offscale' }, - title: 'Offscale', - }, - - // ── Optional dependencies ──────────────────────────────────────────────── - // Informational, not enforced: both are service dependencies that already degrade. They exist so the - // store can say "the Console section needs the terminal plugin" instead of the section silently not - // working, which is the difference between a missing feature and a broken one. - dependsOn: [ - { appName: 'anthropic-proxy', optional: true, reason: 'the ACL drafting assistant' }, - { appName: 'pty', optional: true, reason: 'the SSH console section' }, - ], } as const; ``` +### Everything the tree can say, the tree says + +The manifest holds only what a directory listing genuinely cannot tell you: an identity fact, or something +a human chose. Everything structural is convention, and **presence is the declaration**: + +| Path | Means | +| -------------------- | --------------------------------------------------------------------------------------------- | +| _the directory name_ | `appName` — `plugins/offscale/` **is** the id, so it cannot disagree with where the code sits | +| `sidecar/index.ts` | there is a sidecar; PM2 gets an entry. `.mjs` instead means node — see below | +| `api/router.ts` | there is a backend router, mounted at `mountPrefix(manifest)` | +| `db/schema.ts` | there are tables; pushed on install, every name prefixed `offscale_` | +| `web/Router.tsx` | there is a frontend; its default export mounts at `/*` | +| `web/panels.ts` | it contributes panels; exports `appRegistryMetas` | + +The dock tile and the page title need no fields either — the tile is `{ label, icon, color, to: +mountPrefix(manifest) }` and the title is `label`, all of which are already above. Writing them again was +duplication that could only ever drift. + +**The runtime is the file extension.** `sidecar/index.mjs` runs under node, `sidecar/index.ts` under bun. +Implicit, but it is the rule this repo already follows — `officer-pty` is `pty/index.mjs` under node +because node-pty is a native module built against Node's ABI, and everything else is bun. Better than a +field that can contradict the file it describes. + +### Dropped from the first draft + +- **`dependsOn`** — nothing read it and nothing enforced it. Both of offscale's dependencies already + explain themselves where it matters (`assistant_unavailable`; "no SSH host configured"). A field whose + only job is to be displayed, that nothing displays, is stale the first time anyone looks at it. Add it + when something consumes it. +- **`kind`** — see below. +- **`sidecar` / `schema` / `frontend` objects** — all convention now. + +`[open]` A plugin with a frontend that should NOT get a dock tile has no way to say so: `web/` present +means a tile. Fine for offscale; add a flag the first time something needs it. + ### `admin` has to be allowed, and the pilot proved it immediately The earlier rule here was "a plugin may declare `app`, and nothing else". **That is wrong, and offscale is