A wasm plugin.build now folds an optional root manifest.yaml into the
DESCRIBE-derived manifest.pb exactly as BuildCodeless does, so a reduced
"mixed" plugin can keep a minimal guest for genuine logic while shipping the
full declarative surface set host-rendered: theme_presets, bundled_fonts,
master_pages, system/page templates, template_overrides, email_wrappers, css,
required_icon_packs (and the referenced root JSON is embedded into manifest.pb).
applyManifestYAML now only overwrites a scalar/bytes key when manifest.yaml
actually declares it, so folding onto a guest-populated manifest supplements
and overrides but never WIPES a guest DESCRIBE field. A repo with no
manifest.yaml is a no-op — existing pure-wasm plugins build byte-for-byte as
before. Codeless builds are unaffected (empty manifest → identical result).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
manifest.yaml gains template_overrides [{template, block}] and
email_wrappers [system keys]; sources by convention at
templates/overrides/<template>/<block>.ninjatpl and
templates/email/<system>.ninjatpl (validated at build). Populates the
existing manifest fields BlockTemplateOverrides / EmailWrapperSystemKeys —
no proto change. Unblocks full theme-catalog codeless conversion: every
theme in the fleet registers overrides + an email wrapper.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
manifest.yaml gains system_templates + page_templates declarations; each page
template's .ninjatpl source lives at templates/<system>/<key>.ninjatpl and is
validated at pack time. Verify no longer rejects declared system/page
templates on codeless manifests (guest template_keys stay rejected) — the cms
host registers them source-tracked and renders the files through the same
host pongo pipeline powered blocks use. Blog/system/normal page layouts are
now fully expressible with zero code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
`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>
When the orchestrator imports `block/core/internal/api/orchestrator/v1`
transitively through other core packages and also generates its own
bindings for the same files (accounts.proto etc.), proto registration
panics at startup: "file ... is already registered". Tests in the
orchestrator confirmed this.
Fix:
- buf generate now uses --path to limit core's output to
proto/orchestrator/v1/plugin_registry.proto (see new `make proto`).
- Adds a minimal MyAccount message and PluginAuthService.ListMyAccounts
RPC to plugin_registry.proto (already pushed to block/proto) so the
CLI's account picker no longer needs accounts.proto generated.
- CLI switches back to cli.Auth.ListMyAccounts; orchclient.Client drops
the Account field.
Side effect: every previously-generated orchestrator/v1 binding besides
plugin_registry is removed from this module.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove core's local proto/ fork and pull the canonical block/proto repo in
as a submodule at the same path. buf.yaml now sources from the submodule's
orchestrator/v1 namespace; everything outside that (blockninja, helpdesk)
is excluded from generation.
This brings the orchestrator's local-only RPCs (PluginScopeService.ListMyPlugins,
PluginRegistryService.SubmitForReview, the full PluginModerationService) into
core's bindings — harmless surface area for the CLI, prerequisite for the
orchestrator to also stop forking the proto.
Side effect: the CLI's account picker now uses the canonical
AccountService.ListMyAccounts in accounts.proto rather than the duplicate
PluginAuthService.ListMyAccounts that lived only in core's fork. The
existing Account message uses Name (no DisplayName / Role), so the picker
output collapses to "slug — Name".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ninja login forces account selection (interactive when >1); creds now
carry ActiveAccountID/Slug. New `ninja account` group.
- ninja plugin list / delete / delete-version split public vs active-account
@private sections; `publish --private` is sticky in plugin.mod.
- GetPluginRequest gains active_account_id so @private resolution works
alongside the public (scope, name) path.
- publish auto-commits a dirty plugin.mod (path-scoped, leaves other staged
paths alone) so the bump→publish loop never trips the dirty check.
--allow-dirty is replaced with --strict (default now ships dirty trees
via stash-create).
- bump auto-commits its plugin.mod write with `bump to X.Y.Z`; --no-commit
opts out.
- Design doc updated to match the new defaults.
The dirty-tree branch (where git stash create captures uncommitted tracked
changes) was untested. Add two cases: one asserting the archive contains
the dirty working-copy contents (not HEAD) and the working tree is not
mutated; another asserting untracked files are excluded — the contract
the --allow-dirty publish warning relies on.
Pre-existing CLI improvements ahead of the tarball-publish refactor:
- New top-level `ninja scope` command (create, list, set-default).
- `init` accepts no --scope: prompts from ListMyScopes or uses creds default.
- Plugin name prompted if not provided.
- `plugin bump <major|minor|patch>` writes the bumped version into plugin.mod.
- `plugin version` prints the current plugin.mod version.
- `login` prints a URL with ?user_code= so the link is one click.
- creds: HostCreds gains optional default_scope.
- plugin/version: ParseBaseSemver + BumpVersion helpers, with tests.