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:
+37
-37
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user