4 Commits

Author SHA1 Message Date
Alex Dunmow
a3b261dbe4 feat(bnp): codeless .bnp artifacts — declarative plugins with no wasm (WO-WZ-020)
PluginManifest gains `codeless` (additive, buf-breaking clean): an artifact
with NO plugin.wasm that the host runs entirely. `ninja plugin build`
classifies by repo shape — no Go source → codeless (manifest synthesized from
plugin.mod + optional manifest.yaml: theme presets, fonts, settings schema,
master pages, CSS, icon packs, deps); Go source → wasm as always. New
`--codeless` flag asserts the expectation.

Both artifact kinds now pack the declarative dirs: blocks/ (the cms
blocks.yaml manifest-FS layout — definition-backed blocks), templates/, and
seed/ (seed.json: settings/media/pages/menu items, applied host-side via the
WO-WZ-019 provisioner). The packer structurally validates blocks.yaml and
seed.json (schema JSON validity, file presence, traversal safety); full
semantic validation stays host-side at install.

`ninja plugin verify`: plugin.wasm is required exactly when NOT codeless; a
codeless manifest declaring any computing hook (http/jobs/load/unload/media/
RAG/tags/filters/services/guest blocks/data_dir) is rejected —
CodelessHookViolation mirrors the cms reader check (lockstep duplication).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 11:30:00 +08:00
Alex Dunmow
04991295cf feat(abi,blocks): ninjatpl rendering as a host capability (powered blocks + tag/filter callbacks)
pongo2 stays host-side and is never compiled into a guest; plugins render
templates by handing the host a {template, data} pair, and the host calls
back into the free guest instance for plugin-declared tags/filters.

ABI (additive, buf-breaking clean):
- RenderBlockResponse gains a `powered` PoweredBlock{template, data_json};
  a block returns EITHER html OR powered.
- New hooks HOOK_RENDER_TAG (10) / HOOK_APPLY_FILTER (11) with
  RenderTag{Request,Response} and ApplyFilter{Request,Response}.
- PluginManifest gains repeated declared_tags / declared_filters (31/32).

Guest SDK (core/blocks, core/plugin/wasmguest):
- blocks.PoweredBlock(template, data) / DecodePoweredBlock: NUL-sentinel
  marker so BlockFunc's string signature is unchanged (smallest additive
  change — no ripple to existing blocks or the host guest-side).
- blocks.RegisterTag / RegisterFilter (+ RenderContext = context.Context)
  write a package-level registry; runRegister resets it per registration
  for deterministic DESCRIBE + dispatch.
- DESCRIBE emits declared_tags/filters; dispatch handles RENDER_TAG /
  APPLY_FILTER (fn errors → response.error; panics → AbiError INTERNAL,
  instance stays callable).

Docs: core/docs/wasm-abi.md gains the render-as-a-host-capability model,
the powered-block flow (re-entrancy-free), the plugin API, and the
HOST-SIDE CONTRACT the cms phase implements.

Tests: unit round-trips for powered/RENDER_TAG/APPLY_FILTER (dispatch +
error + unknown + panic + per-guest registry isolation) plus a real
wazero round-trip through the compiled fixture module. Verified no
guest-reachable package imports pongo2 (go list -deps on the wasip1
fixture build is clean).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 02:36:39 +08:00
Alex Dunmow
ad6d87bf06 feat(ninja): plugin build/verify → .bnp packer + ABI riders (WO-WZ-009)
`ninja plugin build` compiles a plugin to reactor-mode wasip1 wasm (Go >= 1.24
enforced), extracts manifest.pb by driving one HOOK_DESCRIBE over wazero with
failing host stubs, and packs a tar.zst .bnp (plugin.wasm, plugin.mod,
manifest.pb + migrations/schemas/assets/web-dist when present) with a summary
table. A describe-time capability call (e.g. db.* from Register) fails with an
actionable error naming the offending method. `ninja plugin verify` re-runs the
CMS reader's layout/name/abi/path-safety/size checks standalone (deliberate
duplication of cms backend/plugin/bnp/reader.go; kept in lockstep by WO-WZ-010).

ABI riders (additive; buf breaking clean):
- ABI_ERROR_CODE_TX_EXPIRED enum value + bnwasm guest mapping to a new
  bnwasm.ErrTxExpired sentinel (retryable tx expiry, distinct from real faults);
  the cms dbexec side adopts the emit separately.
- PluginManifest.data_dir bool + a first-class `data_dir` key on the plugin.mod
  parser (so writeMod's struct round-trip can't drop it); `plugin build` stamps
  it from plugin.mod into the manifest.

Docs: wasm-abi.md gains a Building & packing section, the error-code table row,
the manifest data_dir mapping, and the plugin.mod reference. Tests: CLI e2e
builds the WZ-002 fixture → verify + manifest block keys; a capfixture proves
the actionable describe-time error; verify rejects each malformed class;
bnwasm TX_EXPIRED classification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 18:34:17 +08:00
Alex Dunmow
704b046727 feat(abi): wasm plugin ABI protobuf schema v1 (WO-WZ-001)
Defines the host<->guest wire contract for the .so -> wazero migration as a
repo-local buf module (abi/proto/v1, generated Go in abi/v1):

- manifest.proto: PluginManifest mirroring every static field of
  plugin.PluginRegistration (abi_version, deps, block/template registrations,
  admin pages, settings schema, theme presets, fonts, master pages, AI
  actions, RBAC method roles, CSS manifest, icon packs, directory
  extensions, job types, RAG fetcher types, settings panel, hook flags,
  core service bindings)
- invoke.proto: bn_invoke envelope, Hook catalog (RENDER_BLOCK,
  RENDER_TEMPLATE, HANDLE_HTTP, JOB, LOAD, UNLOAD, RAG_FETCH, MEDIA_HOOK,
  DESCRIBE), AbiError, and job/lifecycle/rag/media/describe payloads
- render.proto: RenderBlock/RenderTemplate payloads + RenderContext
  enumerating every ctx value from blocks/context.go (incl. BlockContext 1:1)
- http.proto: buffered HttpRequest/HttpResponse for HANDLE_HTTP
- db.proto: guest sql-driver messages (DbValue oneof over pgx-mappable
  types, query/exec/tx with host-side tx handles, SQLSTATE-carrying DbError)
- capability.proto: HostCall envelope + one request/response pair per
  CoreServices interface method (content, settings+update, gating, crypto,
  menus, datasources, users, subscriptions, media.deposit, email.send,
  ai.text_call, ai.tools.register, bridge, jobs.submit, embeddings, rag,
  reviews, badges.refresh)

The abi/ buf module is deliberately separate from the repo-root config:
proto/ is the shared block/proto submodule (service API contracts); the ABI
is SDK-internal and versions in lockstep with the guest shim. New `make abi`
target runs buf lint + generate. docs/wasm-abi.md documents the ptr+len
calling convention (bn_alloc, bn_invoke, packed u64), hook catalog, error
semantics, abi_version evolution rules, and the full registration/
CoreServices coverage tables.

Verified: `cd abi && buf lint` exit 0; `go build ./...` exit 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:47:00 +08:00