docs: codeless-bnp.md — the declarative/logic split and artifact contract (WO-WZ-020)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7a82028618
commit
ca9332180d
96
docs/codeless-bnp.md
Normal file
96
docs/codeless-bnp.md
Normal file
@ -0,0 +1,96 @@
|
||||
# Codeless `.bnp` — Declarative Plugins (WO-WZ-020)
|
||||
|
||||
A **codeless `.bnp`** is a plugin artifact with **no `plugin.wasm`**: pure
|
||||
declaration the host runs. No wazero compile, no instance pool, no capability
|
||||
binding — zero guest code ever executes. This is the end-state of "injection,
|
||||
not reliance" for themes, content sites, and template-only block packs: the
|
||||
plugin is *data the host runs*, not a program that links a library. No Go, no
|
||||
`block/core` dependency, no toolchain beyond the `ninja` CLI.
|
||||
|
||||
## The classifier — exactly one rule
|
||||
|
||||
`ninja plugin build` classifies by repo shape:
|
||||
|
||||
- **No Go source at the repo root → codeless.** The manifest is synthesized
|
||||
from `plugin.mod` + the declarative files below; the artifact packs without
|
||||
a wasm. `--codeless` asserts this and fails if Go is present.
|
||||
- **Go source → wasm**, exactly as before. A converted repo DELETES its Go —
|
||||
partial conversions keep a smaller wasm and still ship `blocks/` +
|
||||
`seed/` alongside it (both artifact kinds carry the declarative dirs).
|
||||
|
||||
## The declarative / logic split
|
||||
|
||||
**Codeless-expressible** (declaration or host-rendered template):
|
||||
- Blocks: a `.ninjatpl` template + declared **data providers** (the CMS's own
|
||||
provider set — `posts`, `site`, `menus`, `authors`, … ADR 0018). The host
|
||||
fetches, the engine renders. Defined in `blocks/blocks.yaml` — the SAME
|
||||
manifest-FS layout core builtin block definitions use.
|
||||
- Master pages, theme presets, fonts, CSS manifest, icon packs, settings
|
||||
schema (`manifest.yaml`), assets (host-served), migrations (host-run Goose),
|
||||
seed data (`seed/seed.json`, applied via the WO-WZ-019 provisioner).
|
||||
|
||||
**Requires code (a wasm plugin):**
|
||||
- A block whose data no declared provider can produce; a *computing* tag or
|
||||
filter; HTTP handlers; background jobs; Load/Unload logic; RAG fetchers;
|
||||
media hooks; bridge services; AI tools; `data_dir`. A codeless manifest
|
||||
declaring any of these is rejected at build (`ninja plugin verify` /
|
||||
`CodelessHookViolation`) AND at load (cms reader `codelessHookViolation`).
|
||||
|
||||
## Repo layout
|
||||
|
||||
```
|
||||
my-theme/
|
||||
plugin.mod # name/version/kind (data_dir forbidden)
|
||||
manifest.yaml # optional: theme_presets/bundled_fonts/settings_schema
|
||||
# (JSON file refs), master_pages (JSON file),
|
||||
# required_icon_packs, css {...}, dependencies [...]
|
||||
blocks/
|
||||
blocks.yaml # key/title/category/schema/template/providers per block
|
||||
hero.schema.json
|
||||
hero.ninjatpl
|
||||
templates/ # reserved for host-rendered page templates (future)
|
||||
seed/
|
||||
seed.json # settings {merge/override/ensure}, media, pages, menu_items
|
||||
hero.jpg # media bytes referenced by seed.json entries
|
||||
assets/ # host-served statics
|
||||
migrations/ # Goose SQL, host-run under the plugin schema
|
||||
```
|
||||
|
||||
`ninja plugin build` → `<name>-<version>.bnp` (tar.zst, no `plugin.wasm`,
|
||||
`manifest.pb` with `codeless: true`). `ninja plugin verify` re-runs the
|
||||
loader's checks standalone.
|
||||
|
||||
## How the host runs it (cms `plugin/codeless_loader.go`)
|
||||
|
||||
- The `.bnp` reader accepts a missing `plugin.wasm` iff `manifest.codeless`,
|
||||
and rejects codeless manifests with computing-hook declarations.
|
||||
- Load: `blocks/` → `blocks.LoadManifest` → `Registry.RegisterDefinition` —
|
||||
rendering flows through the existing definition engine (providers +
|
||||
ninjatpl + layer fallback, WO-089..096). `seed/seed.json` → provisioner
|
||||
(`EnsureMedia`/`MergeSiteSettings`/`EnsureSetting`/`EnsurePage`/
|
||||
`EnsureMenuItem`), idempotent by construction. Presets/fonts/CSS/master
|
||||
pages/settings schema come from the manifest exactly as for wasm plugins.
|
||||
- Unload/disable: definitions unregister; seeded content stays (it is site
|
||||
data, not runtime state). Uninstall: normal teardown (schema/role drop,
|
||||
artifact removal).
|
||||
- Hot-swap: codeless→codeless swaps run migrations first, then re-register
|
||||
definitions and re-apply seed. Wasm↔codeless transitions require
|
||||
uninstall + reinstall.
|
||||
|
||||
## Seed schema (`seed/seed.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"settings": {"merge": {"k": "v"}, "override": {"k": "v"}, "ensure": {"k": "v"}},
|
||||
"media": [{"id": "<uuid>", "file": "hero.jpg", "alt": "", "folder": ""}],
|
||||
"pages": [{"slug": "/", "title": "Home", "template_key": "landing",
|
||||
"parent_slug": "", "reconcile_blocks": false,
|
||||
"blocks": [{"block_key": "hero", "title": "Hero",
|
||||
"content": {}, "slot": "main", "sort_order": 1}]}],
|
||||
"menu_items": [{"menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1}]
|
||||
}
|
||||
```
|
||||
|
||||
Media `id` is the deterministic, template-referable key (`media:<uuid>`,
|
||||
`{% img %}`); bytes live in `seed/` next to the JSON. Apply order:
|
||||
media → settings → pages → menu items.
|
||||
Loading…
x
Reference in New Issue
Block a user