44 Commits

Author SHA1 Message Date
Alex Dunmow
b6e9fdd531 test(caps): golden coverage for every WO-WZ-019 method (full provisioner family + jobs.progress)
The cms host parity gate requires one golden per registered handler; this
completes the set so TestGoldenParity's count check holds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 10:17:49 +08:00
Alex Dunmow
9e39119555 feat(abi): injection-complete capability surface — provisioner family, content authoring, 4 callback hooks (WO-WZ-019)
Dynamic families added to the ABI + guest SDK:
- provisioner.* (14 methods, 1:1 plugin.Provisioner): wasm provisioning was
  silently dead (RegisterWithProvisioner got a noopProvisioner and the cms
  loader ignored has_provisioner). Now a LOAD-TIME capability via the new
  CoreServices.Provisioner field; EnsureEmbed rejects RenderFunc-only embeds.
- content.* writes (content.Author + CoreServices.ContentAuthor):
  create_page, set_page_blocks, publish_page, set_page_seo, upsert_post.
- settings.update_plugin_settings (settings.Updater grows the method).
- bridge.invoke + plugin.BridgeInvokable: opaque-payload cross-plugin calls
  (typed GetService still returns nil across the sandbox by design).
- jobs.progress: HOOK_JOB handlers' progress() now crosses (was discarded).

New host→guest hooks: HOOK_AI_TOOL_CALL (executes recorded ai.ToolDefinition
handlers — registers tools in Register so every pooled instance has them),
HOOK_BRIDGE_CALL, HOOK_DIRECTORY_PANEL_SECTION, HOOK_DIRECTORY_PIN_DECORATOR.
The ai/bridge stubs now record handlers/values locally in addition to
forwarding names.

buf breaking clean (additive within ABI major 1); golden round-trips added
for the new families (cms host replays the same goldens).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 10:02:21 +08:00
Alex Dunmow
6c52e3077c fix: resolve check-safety Go lint failures in ninja bnp
- build.go: lowercase the capitalized error string (staticcheck ST1005)
- verify.go: rewrite range-over-strings.Split loop as slices.Contains,
  breaking a Go 1.26 `go fix` stringsseq/slicescontains conflict loop
- driver_test.go: check deferred db.Close() error (errcheck)
- dbvalue.go: reflect.TypeOf -> reflect.TypeFor (applied by golangci --fix)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 09:12:38 +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
e663b77b47 content: add Content.ListPosts capability for blog listing
Adds ListPosts(ctx, ListPostsParams) to the content.Content capability so
wasm plugins can list posts with bodies — the per-plugin Postgres role
denies direct public.blog_posts reads (42501) and GetPost only fetches a
single post's metadata. Extends PostInfo with Body, AuthorName, AuthorSlug
and PublishedAt (additive; existing get_post golden unchanged). Wires all
layers: interface, ABI PostInfo/ContentListPosts{Request,Response} messages
(buf breaking clean), guest stub, and a deterministic content_list_posts
golden replayed by the cms host parity test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 01:00:14 +08:00
Alex Dunmow
314a25353d feat(abi,auth): first-class uuid[] DbValue + trusted identity headers (WZ-016)
Two shared wasm-boundary fixes surfaced by the messenger port (WZ-013):

1. uuid[] DbValue variant. bnwasm had no uuid-array bind/scan, so every
   ANY($1::uuid[]) query broke in the guest ("unsupported argument type
   []uuid.UUID") and messenger worked around it with a ::text[]::uuid[] cast.
   Adds a dedicated DbValue.uuid_array_value (abiv1.UuidArray) — distinct from
   text[] so the host binds a native uuid[] param (queries keep ::uuid[]) and
   scans a uuid[] column straight into []uuid.UUID. Guest toDbValue marshals
   []uuid.UUID; naturalValue/assign parse the canonical strings back into
   []uuid.UUID (nil→NULL, empty stays empty). Pinned by the uuid_array entry in
   the shared DbValueFixtures contract (round-trip + driver-value tests green).

2. Trusted identity headers (auth/trustedheaders.go). Context does not cross
   the ABI, so guests cannot see the host's verified principal. The SECURE
   contract: the host runs its RBAC guard against the signature-verified JWT,
   strips any client-supplied copy of the X-Bn-Verified-* headers, and sets
   them itself from auth.Get{Public,}UserFromContext; the guest reconstructs
   context via auth.TrustedHeaderMiddleware and trusts ONLY those headers.
   Guests MUST NOT decode a client cookie/Bearer token for identity — that is a
   privilege-escalation bug (a verified public user forging an admin JWT the
   guest would honour on a RolePublic method). Documented in docs/wasm-abi.md,
   replacing the ambiguous "auth context reaches the guest via HttpRequest
   headers" line that invited the insecure decode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 00:43:13 +08:00
Alex Dunmow
b9e8fe4bf1 feat(wasmguest): HostServices() — per-instance CoreServices accessor
Block/template render funcs receive only ctx+content over the ABI, no
services. Load runs on a single pooled instance, so a DB-backed block on
any other pooled instance had a nil pool. HostServices() exposes the
CoreServices bound at _initialize on every instance (live db.* Pool +
capability stubs) so render funcs resolve their pool per-instance. Zero
value in native builds (Serve never called); callers nil-check Pool.

Enables the symposium wasm port (WO-WZ-012).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 21:35:16 +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
5880aa21ee test: modernize idiom in wasmguest tests (slices.Contains, any, new(v))
- driver_test.go: use slices.Contains instead of a hand-rolled loop
- sqlcgen_test.go: interface{} -> any in the generated-style DBTX shim
- caps_roundtrip_test.go: new(idParent.String()) instead of proto.String
  for the pointer literal

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 17:51:08 +08:00
Alex Dunmow
40ba4ee9de bnwasm: nil []string→NULL, fatten DbValue fixtures, doc text[] NULL-element limit
Close three WO-WZ-004 review gaps in the guest db driver:

- toDbValue: nil []string now marshals to DbValue_Null (matching []byte /
  json.RawMessage); empty-but-non-nil stays a non-NULL empty text[].
- DbValueFixtures: add edge entries (zero time.Time, negative + very-large
  numeric strings, empty text[], and text[] elements forcing encodePgTextArray
  quoting/escaping). Covered automatically by the table-driven round-trip and
  driver-value tests; new dbvalue_test.go covers the toDbValue nil convention.
- Document that TextArray cannot represent a NULL array element (repeated
  string has no per-element NULL) in the fixtures file and docs/wasm-abi.md,
  a contract limit the WO-WZ-007 host executor must also honor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:49:49 +08:00
Alex Dunmow
081bacf2ab feat(wasmguest): wire bnwasm Pool into guest CoreServices (WO-WZ-004)
Bind the db.* driver over the same transport as the capability stubs so
deps.Pool keeps working for plugin sqlc code:

- dispatch.go: g.services.Pool = bnwasm.NewPool(capTransport) (nil on
  native/DESCRIBE → fails cleanly, matching the caps stubs).
- hostcalls.go (wasip1): also bind the "bnwasm" database/sql driver's
  process-global transport to CallHost.
- docs/wasm-abi.md: document the implemented guest driver + pgx-primary
  rationale under the Pool disposition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:41:56 +08:00
Alex Dunmow
7881aeee05 feat(wasmguest): guest DB driver bnwasm (WO-WZ-004)
Guest-side database access over the db.* host calls (abiv1 db.proto),
two surfaces sharing one injected transport:

- database/sql driver registered as "bnwasm" (driver.go) — QueryContext/
  ExecContext/BeginTx over db.query/db.exec/db.tx_*; named args rejected.
- plugin.Pool (pool.go) handing out a pgx.Tx-shaped value (tx.go), so the
  pgx-flavored sqlc DBTX every current plugin generates against
  (sql_package: pgx/v5) is satisfied with no source edits. This is the
  primary path: their DBTX needs pgconn.CommandTag/pgx.Rows/pgx.Row, which
  database/sql cannot produce.

DbValue↔Go mapping (dbvalue.go) covers all 11 oneof arms both directions;
DbError surfaces as *pgconn.PgError (SQLSTATE preserved for errors.As);
nested tx/savepoints rejected with a clear error (no fleet plugin uses
them). The scan contract is pinned in the exported DbValueFixtures table
(dbvalue_fixtures.go) that the WO-WZ-007 host executor mirrors.

Tests: driver_test.go (fake host — every DbValue variant round-trips with
correct scan types, exec rows-affected, ordered host-call assertions for
tx commit/rollback sequences, post-rollback autocommit carries no handle)
and sqlcgen_test.go (vendored sqlc-style Queries + WithTx run against the
fake host). Native + wasip1 builds green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:41:45 +08:00
Alex Dunmow
bec3a43f55 feat(wasmguest): guest capability stubs over host calls (WO-WZ-003)
Every CoreServices interface (core/plugin/deps.go) now has a guest-side stub
that marshals to the WO-WZ-001 capability messages and dispatches through the
generic host_call transport, so plugin service code compiles and runs
unchanged against content.Content, settings.Settings, plugin.PluginBridge, etc.

- core/plugin/wasmguest/caps/: one file per family (17 families, 38 methods),
  a var _ <iface> = (*stub)(nil) compile proof each, and NewCoreServices(call)
  assembling them. The transport is injected (CallFunc) so marshaling is
  natively testable; the wasm shim binds it to CallHost, DESCRIBE probes pass
  nil (capability calls fail cleanly instead of nil-panicking).
- Error mapping wraps AbiError with <family>.<method> context and maps
  DEADLINE_EXCEEDED onto context.DeadlineExceeded.
- RAGService.RegisterContentFetcher stays guest-side (RAGStub) for
  HOOK_RAG_FETCH dispatch; Query/OnContentChanged marshal out. dispatch.go and
  describe.go now source fetchers from the caps RAG stub.
- caps_roundtrip_test.go: fake transport + 76 deterministic golden payloads
  (family_method_{req,resp}.pb) covering 100% of families, plus error-mapping,
  deadline, nil-transport, and guest-side-fetcher tests. WO-WZ-006 replays the
  same goldens to prevent host/guest drift.
- Acceptance: testdata/fixture Load hook calls deps.Content/Settings/Bridge
  unchanged (compiles for wasip1); caps_wasmhost_test.go drives it end-to-end
  through a real wazero module + fake host_call table.
- Disposition table in docs/wasm-abi.md: every member stub | host-side
  (Pool, Interceptors, AppURL/MediaPath, CoreServiceBindings host-side).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:23:58 +08:00
Alex Dunmow
5f4fa9db0f fix(wasmguest): frame empty InvokeResponse as (ptr,0), not packed 0
A successful hook whose response message has no set fields (LoadResponse,
UnloadResponse) proto-marshals to zero bytes. bn_invoke's `len==0 → return 0`
shortcut collided with the "callee could not produce an envelope" sentinel, so
every successful empty-response hook — notably HOOK_LOAD — looked like an
INTERNAL failure and got the instance discarded. Frame the empty case as
(ptr, 0) with a real 1-byte-backed pointer instead; the host reads zero bytes
into a valid empty InvokeResponse.

Surfaced by the WO-WZ-003 end-to-end capability test (first exercise of LOAD).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 14:23:36 +08:00
Alex Dunmow
9bbc793563 test(wasmguest): reactor fixture plugin + throwaway wazero host (WO-WZ-002)
testdata/fixture is the acceptance fixture: one block, one template, one
admin page, plus the two-line reactor boilerplate main. wasmhost_test.go
compiles it with GOOS=wasip1 GOARCH=wasm -buildmode=c-shared (15.7 MiB,
the WO-WZ-012 memory-budget baseline) and drives it through wazero
exactly per wasm-abi.md: _initialize as the start function, request
bytes through bn_alloc, DESCRIBE returning a decodable manifest,
same-instance block+template renders, a panicking BlockFunc surfacing as
ABI_ERROR_CODE_INTERNAL with the instance still callable, and a
non-bn_alloc request pointer rejected as DECODE.

wazero v1.12.0 joins go.mod as a test-only dependency (approved).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 13:53:39 +08:00
Alex Dunmow
c35199f0bc feat(wasmguest): guest runtime shim for the wasm plugin ABI (WO-WZ-002)
The wasip1 half of the ABI: bn_alloc/bn_invoke/bn_free exports with a
live-pin map so the GC never frees host-visible buffers, the generic
`blockninja.host_call` import (single import decided over per-family
symbols; recorded in wasm-abi.md), and a dispatch table adapting an
unmodified plugin.PluginRegistration to all nine v1 hooks. Panics inside
plugin hooks come back as ABI_ERROR_CODE_INTERNAL — the instance stays
callable; traps stay reserved for runtime corruption.

DESCRIBE builds the PluginManifest from the registration's static funcs
plus a capture-only Register pass (block metas via the same
PluginBlockRegistry prefixing the .so loader applies, template/system/
page-template/email-wrapper keys), probes JobHandlers/ServiceHandlers/
Load with capture-only services for job types, RBAC roles, core-service
bindings, and RAG fetcher types. RenderContext values are rehydrated
through the exact core/blocks context keys, so existing block code
reading from ctx works unchanged.

Plugins build in REACTOR mode (go build -buildmode=c-shared): init()
calls wasmguest.Serve (non-blocking), main is never called, and the host
runs _initialize before any bn_invoke. Command mode deadlocks or exits
(verified against wazero v1.12.0) — documented prominently in
wasm-abi.md, which also now reconciles the import module namespace to
`blockninja` and requires bn_alloc'd buffers on both directions.

Dispatch/describe/context logic is buildable on every GOOS; only
exports.go and hostcalls.go carry the wasip1 tag. dispatch_test.go
covers describe, hook routing, envelope mismatch, decode failures,
template-override resolution, panic recovery, and lifecycle hooks
natively.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 13:53:24 +08:00
Alex Dunmow
d9788abe4c feat(plugin): media deposit SDK surface — Media, MediaDeposit, EnsureMedia
Add the SDK surface for plugins to deposit images into the CMS media library.
MediaDeposit / MediaResult types and a Media interface on CoreServices for
runtime deposits; EnsureMedia on the Provisioner interface for idempotent
seed-time deposits under a plugin-chosen, template-referable UUID. The CMS
implements both against one internal depositor; seeded templates reference
the chosen UUID directly via {% img "<id>" %}.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 13:14:31 +08:00
Alex Dunmow
d53c3d8325 feat(plugin): add Tags field to ModPlugin
TDD approach: added failing tests in mod_test.go that check parsing and
null-coalescing of tags, then added the []string Tags field to ModPlugin
struct with TOML tag "tags,omitempty".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 15:23:05 +08:00
Alex Dunmow
ed365f9030 feat(plugin): add NormalizeTags helper and slug rules
Add NormalizeTags, TagMinLen, TagMaxLen, TagMaxCount constants and
tagSlugRe to plugin/mod.go. Full TDD: 8 tests covering happy path,
trim/lowercase, case-insensitive dedupe, empty-drop, bad-slug rejection,
boundary acceptance, cap enforcement, and nil input.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 15:20:24 +08:00
Alex Dunmow
ba87684696 feat(plugin): add RequiredIconPacks to PluginRegistration and ModPlugin
Lets plugins declare icon-pack dependencies (e.g. "tabler", "phosphor")
in plugin.mod and PluginRegistration. The CMS loader auto-installs
declared packs from the bundled registry before the plugin loads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 15:14:15 +08:00
Alex Dunmow
af7f44c34d fix(plugin): drop VisibilityLabel and its proto import
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>
2026-06-04 20:58:09 +08:00
Alex Dunmow
264116f44e feat(core): private-plugin SDK, PluginVisibility enum, and Go 1.26.4 bump
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>
2026-06-04 01:00:50 +08:00
Alex Dunmow
041a7c2e3f feat(core): plugin display_name + description; CLI prompts; SDK fields 2026-06-03 11:31:23 +08:00
Alex Dunmow
20a7b35e50 docs(sdk): document Coords scope normalisation and accept-both contract
Adds a doc comment to ModFile.Coords explaining the leading-@ trim and a
note on ModPlugin.Scope clarifying that consumers should trim "@" before
comparing. Locks in the contract with a test asserting both call shapes
produce the same display string.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 08:55:46 +08:00
Alex Dunmow
139d9b8543 fix(sdk): Coords trims leading @ from scope so callers can store either form 2026-06-03 07:03:19 +08:00
Alex Dunmow
08be22ec34 feat(sdk): add Kind and Categories to ModPlugin; writeMod emits them 2026-06-03 01:41:56 +08:00
Alex Dunmow
2a76b30c51 feat(cli): scope subcommand, interactive scope prompt, bump+version helpers
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.
2026-06-03 01:18:11 +08:00
Alex Dunmow
7ff326ef25 feat(sdk): ModFile struct, ParseModFull, and tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 22:45:26 +08:00
Alex Dunmow
7eb3e27053 feat: converge BlockNote renderer, add datasources bridge, rename ServiceDeps to CoreServices
SDK renderer now has full feature parity with the host: text alignment,
checkListItem, toggleListItem, video, audio, file, statement blocks,
and text/background color inline styles. New datasources.Datasources
interface lets plugins resolve buckets directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 10:18:32 +08:00
Alex Dunmow
9c62780246 feat: add context-aware BlockNote SDK bridge 2026-05-03 08:36:08 +08:00
Alex Dunmow
b2c968af41 feat: add SymposiumSeeder and MessengerSeeder bridge interfaces
Defines cross-plugin seeder interfaces in the SDK so template plugins
can seed Symposium/Messenger content via PluginBridge without importing
their database packages directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-03 00:16:46 +08:00
Alex Dunmow
601718a309 feat: add Load/Unload lifecycle hooks to PluginRegistration
Enables runtime plugin disable/enable without CMS restart.
Load is called after registration succeeds; Unload on disable
with a 30-second context deadline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-02 22:28:49 +08:00
Alex Dunmow
334d79b4bf fix: EnsureSetting accepts any value type for JSON-serializable settings
Plugins store arrays and scalars as settings, not just maps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-02 12:13:39 +08:00
Alex Dunmow
a2a56f642c fix: UpdateDataTableRowField accepts any value type, not just maps
The provisioner just marshals the value to JSON regardless of type.
Restricting to map[string]any prevented plugins from setting scalar
field values (strings, numbers, booleans).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-02 12:10:36 +08:00
Alex Dunmow
62c25a7b3a feat: add settings.Updater interface to ServiceDeps 2026-05-02 11:03:40 +08:00
Alex Dunmow
2c89ce4d42 feat: add BadgeRefresher interface and badge types 2026-05-02 11:03:09 +08:00
Alex Dunmow
1ede7d50be feat: add ReviewSubmitter interface to ServiceDeps 2026-05-02 11:02:28 +08:00
Alex Dunmow
a174eb943d feat: add CoreServiceBindings interface to ServiceDeps 2026-05-02 11:02:08 +08:00
Alex Dunmow
d270bc8582 feat: extend EmbeddingService and RAGService interfaces for .so plugins
Add EmbedContent/IsAvailable to EmbeddingService and
RegisterContentFetcher/OnContentChanged to RAGService so .so plugins
can use embedding and RAG capabilities through SDK interfaces instead
of type-asserting CMS concrete types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-02 09:25:44 +08:00
Alex Dunmow
917eee13a2 feat: add menus, subscriptions, public users, and plugin bridge to SDK
New capability interfaces:
- menus.Menus: menu/nav access (GetMenuByName, GetMenuItems)
- subscriptions.Subscriptions: tier/plan access (GetUserTierLevel, GetTierBySlug, ListActivePlans)
- auth.PublicUsers: public user profiles (GetByUsername, GetByID)
- plugin.PluginBridge: inter-plugin service registry with typed GetServiceAs[T] helper

All added to ServiceDeps for plugin consumption.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-01 12:36:13 +08:00
Alex Dunmow
43deff21f7 feat: tighten types — MasterPageBlock JSON tags + *string, BlockNote []map[string]any
- Add JSON struct tags to MasterPageBlock and MasterPageDefinition
- Change MasterPageBlock.HtmlContent from string to *string (nullable)
- Change BlockNote renderer signatures from []any to []map[string]any
- Move type assertions to JSON boundary in blocksFromRaw/inlineContentFromRaw

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-01 11:37:41 +08:00
Alex Dunmow
13d741979a fix: rename module path from ninja/core to block/core
Matches the actual Gitea repo location at block/core.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-01 09:39:10 +08:00
Alex Dunmow
7c20538a4e feat: WO-PS-009 SDK plugin types
- plugin/types.go: Pool, EmailSender, JobHandlerFunc, Dependency,
  AdminPage, AIAction, MasterPageDefinition, status/source constants,
  MediaAnalyzedEvent, ModerationDecisionEvent, MediaHooksProvider,
  DirectoryExtensions
- plugin/deps.go: ServiceDeps with typed capability interfaces
  (Content, Settings, Gating, Crypto, ToolRegistry, JobRunner,
  EmbeddingService, RAGService)
- plugin/registration.go: PluginRegistration, RegisterFunc
- plugin/service.go: ConnectServiceBinding with generics, ServiceMount,
  ServiceRegistration
- plugin/provisioner.go: Provisioner interface with all config types
- plugin/css_manifest.go: CSSManifest, MergedCSSManifest, MergeCSSManifests
- plugin/version.go: ParseModVersion, CompareVersions
- plugin/topo_sort.go: TopologicalSort (Kahn's algorithm)
- plugin/block_registry.go: PluginBlockRegistry (auto-prefixing wrapper)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-30 22:41:52 +08:00
Alex Dunmow
0991b791b1 feat: WO-PS-001 initialize SDK repo
Initialize git.dev.alexdunmow.com/ninja/core with Go 1.26 module,
package directory structure, and README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-30 22:25:52 +08:00