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 publish --bnp <file>` uploads a .bnp built by `ninja plugin
build` instead of a source archive. The orchestrator verifies the .bnp
layout/ABI/name/version server-side and records its abi_version. Without
--bnp, publish keeps shipping a source archive for backwards compatibility
until the CMS install path is wasm-native.
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>
Pre-existing strict-lint failures in the publish warning helpers were
the only thing keeping check-safety's Go lint lane red for the module;
discard the writer errors explicitly (warnings are best-effort output).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds TestMutateTags_AddRmSetClear (covers add/rm/set/clear operations
including dedupe and normalisation) and TestMutateTags_RejectsInvalidNoWrite
(ensures validation failures don't mutate plugin.mod).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds ninja plugin tags (show), add, rm, set, and clear subcommands that
read and mutate the local plugin.mod tag list via NormalizeTags. The bare
tags command stubs ListTags with a TODO(tags): marker for Task 12.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add promptTagsWithDefault helper (mirrors promptCategoriesWithDefault) and
wire it into the init flow so upsertPluginMod receives real user-entered tags
instead of nil. ListTags RPC stub left with TODO(tags) marker for Task 12.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extend upsertPluginMod signature to accept tags parameter (positional arg 7,
between categories and private). Update the single call site to pass nil.
Add tags serialization in writeMod, mirroring the categories pattern.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When core/plugin imported core/internal/api/orchestrator/v1 for the
PluginVisibility enum, every consumer of core/plugin (including the
orchestrator) transitively pulled in core's generated bindings — and
those bindings register the same proto descriptors as the orchestrator's
own bindings, panicking at startup.
Move the label helper into the CLI's cmd package where it belongs;
core/plugin no longer references the proto package at all.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tracks the shared proto rename that resolves the message-name collision
between PluginAuthService and AccountService.
Co-Authored-By: Claude Opus 4.7 <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.
Add private-plugin RPCs (ListPrivatePlugins, DeletePrivatePlugin,
DeletePrivatePluginVersion, ListPrivatePluginInstallSites) and
ListMyAccounts to the proto/generated stubs; introduce PluginVisibility
enum replacing the loose string field; add ModPlugin.Private + Coords()
routing to @private/<name>@<version>; update ninja CLI to use
VisibilityLabel helper; bump go directive to 1.26.4 for ABI alignment.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Proves the publish command's warning surface end-to-end: tracked-yet-
gitignored files, declared submodules, untracked files on --allow-dirty,
and that the dirty-tree abort suppresses the untracked warning.
Pulls the three warning calls and the dirty-tree check out of the
publish RunE closure into a single helper so a refactor that drops one
warning can be caught by a fixture-based test.
Adds coverage for two git-edge cases: commits land correctly on a
detached HEAD with the prior commit as parent, and an empty PATH
produces a git-mentioning error rather than a panic.
Add fires/no-op pairs for gitignoredTrackedWarning, untrackedFilesWarning,
and submoduleWarning. The submodule case writes a hand-crafted .gitmodules
file rather than wiring real submodules — submodulePaths reads the file
directly so that's sufficient.
Pull the three inline warning blocks in newPluginPublishCmd —
gitignoredTrackedWarning, untrackedFilesWarning, submoduleWarning — into
package-private helpers that take a repo dir and an io.Writer. Output is
byte-identical to the previous inline code; this just makes them unit-
testable without driving the whole cobra command.
autoCommitPluginMod runs `git status --porcelain plugin.mod` then commits if
dirty. Add two cases: dirty plugin.mod produces an "Add plugin.mod" commit,
and a clean state leaves HEAD unchanged. Uses t.Chdir to scope CWD to the
temp repo without polluting parent state.
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.
`git archive` does not recurse into submodules, so a plugin shipping
vendored code via submodule produced a tarball where the submodule path
existed but was empty — silent failure. Now publish reads .gitmodules
and lists submodule paths to stderr with guidance to vendor or pack
them separately. The publish still proceeds, since the developer may
not actually need the submodule contents in the archive.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`git stash create` only captures tracked content, so a developer using
--allow-dirty after creating new files (but forgetting to `git add`)
would ship a tarball missing them with no indication. Now publish lists
the untracked, non-ignored files to stderr and suggests `git add` when
--allow-dirty is in play.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously a brand-new repo (git init, no commits) surfaced `git stash
create: exit status 128: You do not have the initial commit yet` from
deep inside the archive helper. Now the publish flow detects this case
via `git rev-parse --verify HEAD` up front and prints "no commits in
repository; run `git add . && git commit` before publishing". Also
updates the init flow's hint to mention `git init && git commit` so
users aren't misled into thinking `git init` alone is enough.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.