the manifest holds only what the tree cannot say

lean it to identity facts and human choices: publisher, version, platform range,
the four presentation fields, and permissions. everything structural becomes
convention, where presence is the declaration — sidecar/, api/router.ts,
db/schema.ts, web/Router.tsx, web/panels.ts. appName comes from the directory
name, so the id cannot disagree with where the code sits.

the dock tile and page title needed no fields at all: the tile is label + icon +
color + mountPrefix, and the title is label. writing them again was duplication
that could only drift.

runtime is the file extension. index.mjs is node, index.ts is bun — implicit,
but already the rule here, since officer-pty runs under node for node-pty's abi
and everything else is bun. better than a field that can contradict the file.

dependsOn is gone; nothing read it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 19:32:07 +00:00
co-authored by Claude Opus 5
parent 7ebc4d0ccd
commit 7befaf032a
+37 -37
View File
@@ -465,32 +465,20 @@ from nothing includes what nothing needs and misses what is awkward. The field s
is the floor, not the ceiling. is the floor, not the ceiling.
```ts ```ts
// plugins/offscale/manifest.ts
export const manifest = { export const manifest = {
// ── Identity ───────────────────────────────────────────────────────────── /** Constant today. The one input to `mountPrefix()`, and the seam third parties hang off later. */
/** 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()`. */
publisher: 'officerdev', publisher: 'officerdev',
/** The plugin's own semver. Updates compare against this. */ /** The plugin's own semver. Updates compare against this. */
version: '1.0.0', version: '1.0.0',
/** Which platforms this build is good for. Refused at install when it does not match. */ /** Which platforms this build is good for. Refused at install when it does not match. */
platform: '>=1.0.0 <2.0.0', platform: '>=1.0.0 <2.0.0',
// ── Presentation ─────────────────────────────────────────────────────────
label: 'Offscale', label: 'Offscale',
summary: 'Your tailnet — machines, users, pre-auth keys and access policy', summary: 'Your tailnet — machines, users, pre-auth keys and access policy',
icon: 'Network', icon: 'Network',
color: '#818cf8', color: '#818cf8',
// ── Backend ──────────────────────────────────────────────────────────────
sidecar: {
/** PM2 process name. Always `officer-<appName>`, 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 // 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 // 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. // 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, 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 `<prefix>/*` 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; } 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 `<prefix>/*` |
| `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 ### `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 The earlier rule here was "a plugin may declare `app`, and nothing else". **That is wrong, and offscale is