Commit Graph
263 Commits
Author SHA1 Message Date
jschoubben fb42fb956b sonarr, radarr: full nox modules — clients, tools and events (ADR 0044/0046)
The Servarr apps (TV, movies), each self-contained from hal's shared arr
client. Tools: status, library, search, queue, calendar. Events by polling the
download queue: a new item emits module.<app>.<thing>.grabbed, an item that
leaves as completed emits module.<app>.download.completed — the exact key plex
consumes to rescan. A grab that left as failed/warning is not reported as a
completion. Both typecheck; manifests parse.
2026-09-04 02:33:32 +02:00
jschoubben 259c3721b5 gitea: full nox module — client, tools and events (ADR 0044/0046)
Git hosting. 15 tools (repos, issues, PRs, labels, api passthrough) moved out
of the shared sdk. Emits repo.created (from a light poll, catching repos born
of git push or the web UI), issue.opened and pull.merged (from the tools at the
moment of the action) — the poll owns repo.created alone so it is never
announced twice. Typechecks; manifest parses.
2026-09-04 02:30:40 +02:00
jschoubben 4d98da18a0 keycloak: full nox module — client, tools and events (ADR 0044/0046)
Identity provider. 22 admin tools (realms, users, clients + secrets, groups,
roles) over the admin API, moved out of the shared sdk. Emits user created/
deleted, password reset, client/group/role created — from the write tools
themselves, since Keycloak's value is the changes it makes, not pollable
state. Consumes nothing: it is upstream of everything that authenticates
against it. Typechecks; manifest parses.
2026-09-04 02:30:40 +02:00
jschoubben 1498a22c94 mailu: full nox module — client, tools and events (ADR 0044/0046)
Email server. Users/aliases/domains over the admin REST API, mail read via
doveadm in the imap container; ten tools. Emits user/alias created/deleted by
polling and diffing the admin API, so a change in the web admin is announced
as readily as one via a tool. Consumes nothing — deliberately, since mutating
mail accounts off another module's event could silently lose mail. Typechecks
against the sdk; manifest parses.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-04 02:29:38 +02:00
jschoubben 2526955712 plex: full nox module — client, tools and events (ADR 0044/0046)
Moves plex's API client and tools out of the shared hal sdk into the module,
so a Plex API change rebuilds only plex. Tools: status, search, sessions,
recently-added, refresh. And a real event design: it emits playback
started/stopped and item.added by watching the server, and consumes
module.*.download.completed to rescan so a downloader's fetch becomes a
visible item. Typechecks against the sdk; manifest parses with its emits/
consumes and broker own-secret.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-04 02:23:23 +02:00
jschoubben c2c26a29a9 audit-logger: the container names its image directly (a container has no artifact)
A container resource takes a digest-pinned image, not a build artifact — the
host refuses 'artifact' on a container. Verified: the mesh assigns it and the
host runs it in the lab.
2026-09-04 02:13:01 +02:00
jschoubben 8f0994fcdc audit-logger: the assigned-module manifest (ADR 0048)
Now a real assigned module, not just a handler: consumes '#', declares its
broker own-secret, and runs the runtime image as a container that mounts the
sealed credential and its trail. own-secrets:{broker} is the file the mesh
seals it (module issue); the container reads MESH_BROKER_FILE from the mount
and takes its node/module identity from the credential. Parses against the
catalogue schema.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-04 01:56:36 +02:00
jschoubben 43625ec03f audit-logger: record the event's own x-event-id (ADR 0047)
The trail's id is now the event's x-event-id — the handle a reader dedups
the at-least-once stream on — not the type@time placeholder the first cut
used. Carries causation/schema through when present.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-04 00:26:10 +02:00
jschoubben f77745d7c0 audit-logger: a module that records every event on the mesh
The universal consumer from ADR 0046 — no privilege, just a module that
consumes '#' and writes each event to an append-only trail. Its whole code
is on('#', record); the manifest declares consumes:['#'] and a log dir.
Records module.*, mesh.* and node.* events alike (ADR 0047's namespace).

Type-checks against @novox/mesh-sdk; the handler test records a module
event and a node event with their metadata; the manifest parses against
the consumes-enabled schema.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-03 23:50:55 +02:00
jschoubben e8061191c7 umami: complete, on the sdk — the first fully converted module
umami is now a whole module, not a manifest: its own API client, its
tools, and its provisioner all live in the module and build on
@novox/mesh-sdk.

- client.ts — umami's API client, moved out of the shared sdk into the
  module (ADR 0044); umami's tools and provisioner both import it.
- tools/ — umami_create_site / umami_delete_site on the sdk tool harness
  (registerModuleTools); the tool logic and client are the module's.
- provisioner/ — the adapter making umami a provider of the mesh
  'analytics' interface: a consumer contributes {domain}, receives
  {siteId, snippet, dashboard}. ~20 lines, because the watch/seal/grant
  loop is the sdk harness's.

Type-checks against the real mesh-sdk (tsc --noEmit clean); the manifest
parses against internal/catalogue. Remaining to actually run: build and
publish the module + its mesh-provision-umami-analytics image (the
placeholder digest), which is the pipeline's job.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-03 23:05:20 +02:00
jschoubben f4fc5b3be8 umami: the reference module — a provider as well as a consumer
umami was only half a module: it required postgres but did not provide
what it exists to offer. It now provides the mesh 'analytics' interface
(ADR 0045) — a webapp requires analytics and umami's provisioner creates
its site and returns the grant — mirroring the postgres provider pattern
(serves/receives/grants + a provisioner container that adapts umami's API
to the mesh contract).

Also: split the listen (one port, two surfaces — the mesh-gated dashboard
and the public collection endpoint browsers POST to, hence from:anywhere),
and an admin own-secret for the provisioner to drive umami's API.

Parses against internal/catalogue. Still to build: the provisioner image
(mesh-provision-umami-analytics — the adapter, real code like
postgres-provisioner; placeholder digest for now) and umami's tools/client
in the module (ADR 0044), which need the sdk harness.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-03 22:53:07 +02:00
jschoubben c5efbd53ca A module is a package, not a bare manifest
Each module becomes modules/<name>/ holding module.json, with room for
the rest of what a module is — its tools, health checks, provisioning,
lifecycle — which the conversion from hal still has to bring across.
The flat <name>.json was only the resource-declaration half.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-03 00:47:14 +02:00
jschoubben 86ea181c76 The catalogue moves to its own repository
Thirty modules the mesh builds, provisions and runs, as manifests — one
per module, flat under modules/. They were in mesh-control/examples/,
which framed the mesh's real modules as illustrations of a control-plane
package; they are neither examples nor the control plane's. The engine
that reads them stays in mesh-control; the data lives here, consumed as
a build source.

Answers the tier-4 question novox/hq ADR 0030 left open — where the
catalogue lives — in favour of one flat repository, which the drop of
domain grouping (seats, claims and tags instead) makes the right shape.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-02 23:51:53 +02:00