Resync hq ADR references (0044-0054 -> 0039-0049) #5

Merged
jschoubben merged 1 commits from feat/adr-ref-resync into main 2026-09-05 10:49:51 +00:00
140 changed files with 179 additions and 179 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
// The audit handler — appends one line per event to an append-only audit log. It is the whole of
// the module's code: an audit logger is not a privileged component, only a module that listens to
// everything and writes it down (novox/hq ADR 0046).
// everything and writes it down (novox/hq ADR 0041).
import { appendFile, mkdir } from "node:fs/promises";
import { dirname } from "node:path";
@@ -12,7 +12,7 @@ export function auditLogPath(env: NodeJS.ProcessEnv = process.env): string {
}
/** Append an event to the trail as one JSON line, keeping the metadata an audit needs first. The id
* is the event's own x-event-id (ADR 0047) — the handle a reader dedups the at-least-once trail on. */
* is the event's own x-event-id (ADR 0042) — the handle a reader dedups the at-least-once trail on. */
export async function record(event: Event, path: string): Promise<void> {
const line =
JSON.stringify({
+2 -2
View File
@@ -1,4 +1,4 @@
// The Bazarr API client — bazarr's own code, living in the module (novox/hq ADR 0044). Bazarr
// The Bazarr API client — bazarr's own code, living in the module (novox/hq ADR 0039). Bazarr
// manages subtitles for a Sonarr/Radarr library: it tracks which episodes and movies are still
// missing subtitles, searches providers for them, and records what it downloaded. This client
// talks its /api surface (keyed by an X-API-KEY header); bazarr's tools and events import it.
@@ -36,7 +36,7 @@ export interface HistoryEntry {
description?: string;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -3,7 +3,7 @@
// the missing-subtitle list, and when it succeeds a subtitle appears in its history. That is worth
// announcing to the mesh.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.bazarr.subtitle.downloaded — a subtitle was fetched for an episode or movie
//
// Bazarr has nothing on the mesh it usefully reacts to (a download completing is Sonarr/Radarr's
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-bazarr",
"version": "0.1.0",
"description": "bazarr — subtitle management. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "bazarr — subtitle management. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// bazarr's tools — its own code (novox/hq ADR 0044), importing bazarr's client. They return
// bazarr's tools — its own code (novox/hq ADR 0039), importing bazarr's client. They return
// structured data; the mesh serves them through the sdk's tool harness.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
+1 -1
View File
@@ -1,4 +1,4 @@
// The Bookshelf API client — bookshelf's own code, living in the module (novox/hq ADR 0044).
// The Bookshelf API client — bookshelf's own code, living in the module (novox/hq ADR 0039).
// Ported from the shared hal `arr` client, but self-contained: in nox each Servarr app owns its own
// copy, so a change to Bookshelf's API rebuilds only bookshelf and nothing else. Both this module's
// tools and its events entrypoint import it, and nothing outside bookshelf does.
+1 -1
View File
@@ -2,7 +2,7 @@
// download queue and turns its comings and goings into mesh events — the same mechanism radarr uses,
// applied to a Servarr book manager.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.bookshelf.book.grabbed — a release entered the queue (Bookshelf grabbed it)
// module.bookshelf.download.completed — a release left the queue, imported. This routing key is
// what the plex module consumes (module.*.download.completed)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-bookshelf",
"version": "0.1.0",
"description": "bookshelf — ebook/audiobook management (Readarr fork). Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "bookshelf — ebook/audiobook management (Readarr fork). Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// bookshelf's tools — ported from the shared hal `arr` sdk (novox/hq ADR 0044), importing
// bookshelf's tools — ported from the shared hal `arr` sdk (novox/hq ADR 0039), importing
// bookshelf's own client. They return structured data (not the pre-formatted text hal returned); the
// mesh serves them through the sdk's tool harness. Bookshelf has no calendar endpoint, so there is
// no calendar tool — matching hal, which excluded it from its calendar-capable apps.
+3 -3
View File
@@ -1,5 +1,5 @@
// cloudflare-dns's own code (novox/hq ADR 0044). It provides the mesh `public-dns` interface
// (ADR 0049): a public name that resolves to the mesh's public ingress. Cloudflare is one registrar
// cloudflare-dns's own code (novox/hq ADR 0039). It provides the mesh `public-dns` interface
// (ADR 0044): a public name that resolves to the mesh's public ingress. Cloudflare is one registrar
// behind the neutral interface — a consumer names `public-dns`, never Cloudflare — so this file is
// the only place Cloudflare's API appears, and swapping registrars swaps only this module.
@@ -24,7 +24,7 @@ export class CloudflareClient {
static fromEnv(env: NodeJS.ProcessEnv = process.env): CloudflareClient {
// Which zone, domain and ingress are a mesh's own facts, not this module's — so they are
// settings, merged into a config file the mesh manages (novox/hq ADR 0051), read here. The
// settings, merged into a config file the mesh manages (novox/hq ADR 0046), read here. The
// token is the one secret and stays an own-secret. Env is honoured as a fallback for a
// hand-run instance, but the deployed path is the config file settings fill.
const config = readConfig(env.MESH_CLOUDFLARE_CONFIG_FILE);
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-cloudflare-dns",
"version": "0.1.0",
"description": "cloudflare-dns — a public-dns provider (ADR 0049): registers public names at Cloudflare.
"description": "cloudflare-dns — a public-dns provider (ADR 0044): registers public names at Cloudflare.
"type": "module",
"private": true,
"dependencies": {
+3 -3
View File
@@ -1,14 +1,14 @@
// cloudflare-dns's provisioner — the adapter making it a provider of the mesh `public-dns` interface
// (novox/hq ADR 0049). The reconcile loop and the contributions file are the sdk harness's; this
// (novox/hq ADR 0044). The reconcile loop and the contributions file are the sdk harness's; this
// writes only the per-registrar half: register a consumer's public name at Cloudflare, pointing it
// at the mesh's ingress, and remove it when the consumer is withdrawn (ADR 0053).
// at the mesh's ingress, and remove it when the consumer is withdrawn (ADR 0048).
//
// The `public-dns` interface hands a consumer { fqdn, target, ttl } — a name that resolves publicly
// and what it resolves to. Like umami's analytics it is a *data* provision, not a credential one:
// nothing the mesh mints is set here (a DNS record is public, and the only secret is this module's
// own Cloudflare token, which never leaves). So the password the harness carries is unused; the name
// is derived from the login the mesh gave the consumer, which the consumer can derive too. Delivering
// the record back to the consumer is the data-provision return path ADR 0053 leaves out of scope.
// the record back to the consumer is the data-provision return path ADR 0048 leaves out of scope.
import { runProvisioner, type Provision } from "@novox/mesh-sdk/provisioner";
import { emit } from "@novox/mesh-sdk/events";
+1 -1
View File
@@ -1,4 +1,4 @@
// dnsmasq's own code, living in the module (novox/hq ADR 0044). dnsmasq here is a pure resolver: it
// dnsmasq's own code, living in the module (novox/hq ADR 0039). dnsmasq here is a pure resolver: it
// answers the mesh's generated wildcard names (<service>.<node>.<suffix>) and forwards nothing. So
// its code reads what it was told to answer, and can resolve through itself to prove that it does.
+1 -1
View File
@@ -3,7 +3,7 @@
// announces, from the resolver's own vantage, that a name became (or stopped being) resolvable on
// this node: DNS having actually propagated here, distinct from the mesh's own node.* events.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.dnsmasq.name.added — this node's resolver now answers a machine's name
// module.dnsmasq.name.removed — it no longer does
+1 -1
View File
@@ -1,5 +1,5 @@
// dnsmasq's tools — a resolver's two useful questions: what does it answer, and does it answer a
// given name. Moved into the module (novox/hq ADR 0044); the mesh serves them through the sdk.
// given name. Moved into the module (novox/hq ADR 0039); the mesh serves them through the sdk.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
import { DnsmasqClient } from "../client.js";
+1 -1
View File
@@ -1,4 +1,4 @@
// fail2ban's own code, in the module (novox/hq ADR 0044). The jails and the daemon are declared
// fail2ban's own code, in the module (novox/hq ADR 0039). The jails and the daemon are declared
// resources — the mesh writes /etc/fail2ban/jail.d/* and keeps fail2ban.service running (see
// module.json). This code exists only to read and steer the *live* state the daemon owns at
// runtime: which IPs are banned right now, and the manual ban/unban an operator reaches for. That
+2 -2
View File
@@ -1,5 +1,5 @@
// The firewall's own code, in the module (novox/hq ADR 0044). The mesh computes this node's whole
// rule set from every module's `listens` and writes it to /etc/nftables.conf (novox/hq ADR 0050);
// The firewall's own code, in the module (novox/hq ADR 0039). The mesh computes this node's whole
// rule set from every module's `listens` and writes it to /etc/nftables.conf (novox/hq ADR 0045);
// the module loads it (the nftables service, reloaded whenever the rules change). This code exists
// only to read back what is actually enforced — the enforcement itself is declarative.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-firewall",
"version": "0.1.0",
"description": "firewall — applies the mesh-computed packet filter (ADR 0050). Its diagnostic tool lives here.
"description": "firewall — applies the mesh-computed packet filter (ADR 0045). Its diagnostic tool lives here.
"type": "module",
"private": true,
"dependencies": {
+2 -2
View File
@@ -1,4 +1,4 @@
// The Gitea API client — gitea's own code, living in the module (novox/hq ADR 0044). Moved out of
// The Gitea API client — gitea's own code, living in the module (novox/hq ADR 0039). Moved out of
// the shared hal sdk, where a change to Gitea's API rebuilt everything; here it rebuilds only
// gitea. Both this module's tools and its events entrypoint import it, and nothing outside gitea
// does.
@@ -44,7 +44,7 @@ export interface GiteaLabel {
name: string;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,7 +1,7 @@
// gitea's events. The tool runtime imports this once the broker is bound. It watches the forge and
// emits what appeared.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.gitea.repo.created — a repository appeared, however it was made (push, web UI, or tool)
//
// issue.opened and pull.merged are emitted from the tools (tools/index.ts), at the instant the mesh
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-gitea",
"version": "0.1.0",
"description": "gitea — git hosting. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "gitea — git hosting. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+2 -2
View File
@@ -1,7 +1,7 @@
// gitea's tools — moved here from the shared sdk (novox/hq ADR 0044), importing gitea's own client.
// gitea's tools — moved here from the shared sdk (novox/hq ADR 0039), importing gitea's own client.
// They return structured data; the mesh serves them through the sdk's tool harness.
//
// Two tools emit an event at the natural point of the action they take (novox/hq ADR 0046/0047):
// Two tools emit an event at the natural point of the action they take (novox/hq ADR 0041/0042):
// create-issue emits issue.opened, merge-pull-request emits pull.merged — the mesh's own hand on
// the forge, announced the instant it moves. repo.created is deliberately NOT emitted here: repos
// are far more often born from a `git push` or the web UI than from this tool, so the events
+2 -2
View File
@@ -1,4 +1,4 @@
// Grafana's API client — grafana's own code, living in the module (novox/hq ADR 0044). Ported from
// Grafana's API client — grafana's own code, living in the module (novox/hq ADR 0039). Ported from
// the shared hal sdk, where a change here rebuilt everything; here it rebuilds only grafana. Both
// this module's tools and its events entrypoint import it, and nothing outside grafana does.
@@ -37,7 +37,7 @@ export interface GrafanaAlert {
activeAt?: string;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,7 +1,7 @@
// grafana's events. The tool runtime imports this once the broker is bound. It watches unified
// alerting and announces when an alert instance starts firing.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.grafana.alert.firing — an alert instance entered the Alerting state
//
// A Grafana with no alerting configured simply never has a firing alert, so this observes nothing
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-grafana",
"version": "0.1.0",
"description": "grafana — monitoring dashboards. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "grafana — monitoring dashboards. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// grafana's tools — moved here from the shared hal sdk (novox/hq ADR 0044), importing grafana's own
// grafana's tools — moved here from the shared hal sdk (novox/hq ADR 0039), importing grafana's own
// client. They return structured data; the mesh serves them through the sdk's tool harness.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
+2 -2
View File
@@ -1,5 +1,5 @@
// The Home Assistant API client — home-assistant's own code, living in the module (novox/hq
// ADR 0044). Both this module's tools and its events entrypoint import it, and nothing outside
// ADR 0039). Both this module's tools and its events entrypoint import it, and nothing outside
// home-assistant does. Talks to the HA REST API (/api) with a long-lived access token.
import { readFileSync } from "node:fs";
@@ -20,7 +20,7 @@ export interface HAConfig {
state?: string;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -2,7 +2,7 @@
// entities whose state changing is a real signal — a door opening, a lock turning, a switch
// flipping — and emits when one does.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.home-assistant.state.changed — a watched entity's state value changed
//
// Bounded on purpose. Home Assistant has hundreds of entities and many (temperature, humidity,
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-home-assistant",
"version": "0.1.0",
"description": "home-assistant — home automation platform. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "home-assistant — home automation platform. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// home-assistant's tools — its own code (novox/hq ADR 0044), importing its own client. They return
// home-assistant's tools — its own code (novox/hq ADR 0039), importing its own client. They return
// structured data; the mesh serves them through the sdk's tool harness.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
+2 -2
View File
@@ -1,4 +1,4 @@
// Icecast's API client — icecast's own code, living in the module (novox/hq ADR 0044). Icecast is an
// Icecast's API client — icecast's own code, living in the module (novox/hq ADR 0039). Icecast is an
// audio streaming server: sources push mountpoints in, listeners pull them out. Its `/status-json.xsl`
// endpoint reports the live mountpoints and their listener counts — the one thing worth watching, and
// the basis for both the status tool and the stream started/stopped events.
@@ -35,7 +35,7 @@ interface RawSource {
server_type?: string;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,7 +1,7 @@
// icecast's events. The tool runtime imports this once the broker is bound. It watches the streaming
// server and announces when a mountpoint goes live or drops.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.icecast.stream.started / .stopped — a mountpoint appeared or disappeared
//
// A mountpoint exists only while a source is connected, so the set of mounts diffed over time is
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-icecast",
"version": "0.1.0",
"description": "icecast — audio streaming server. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "icecast — audio streaming server. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// icecast's tools (novox/hq ADR 0044), importing icecast's own client.
// icecast's tools (novox/hq ADR 0039), importing icecast's own client.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
import { IcecastClient } from "../client.js";
+2 -2
View File
@@ -1,4 +1,4 @@
// The InfluxDB API client — influxdb's own code, living in the module (novox/hq ADR 0044). Only
// The InfluxDB API client — influxdb's own code, living in the module (novox/hq ADR 0039). Only
// this module's tools import it. Talks to the InfluxDB 2.x HTTP API (/api/v2) with a token.
import { readFileSync } from "node:fs";
@@ -17,7 +17,7 @@ export interface InfluxBucket {
retentionSeconds?: number;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-influxdb",
"version": "0.1.0",
"description": "influxdb — time-series database. Its API client and tools live here (novox/hq ADR 0044).",
"description": "influxdb — time-series database. Its API client and tools live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// influxdb's tools — its own code (novox/hq ADR 0044), importing its own client. They return
// influxdb's tools — its own code (novox/hq ADR 0039), importing its own client. They return
// structured data; the mesh serves them through the sdk's tool harness. Read-only: health, bucket
// listing, and Flux queries — no write path is exposed.
+2 -2
View File
@@ -1,4 +1,4 @@
// The Jackett API client — jackett's own code, living in the module (novox/hq ADR 0044). Jackett is
// The Jackett API client — jackett's own code, living in the module (novox/hq ADR 0039). Jackett is
// an indexer proxy: it normalises many torrent trackers behind one Torznab surface. This client
// talks its /api/v2.0 REST API, and only jackett's tools import it.
@@ -24,7 +24,7 @@ export interface JackettResult {
link?: string;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-jackett",
"version": "0.1.0",
"description": "jackett — indexer proxy. Its API client and tools live here (novox/hq ADR 0044).",
"description": "jackett — indexer proxy. Its API client and tools live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// jackett's tools — its own code (novox/hq ADR 0044), importing jackett's client. Jackett has
// jackett's tools — its own code (novox/hq ADR 0039), importing jackett's client. Jackett has
// nothing worth watching (an indexer proxy answers queries; it has no timeline of its own), so it
// is a tools-only module: no events entrypoint, no broker. What is useful is asking it things.
+2 -2
View File
@@ -1,11 +1,11 @@
// The Keycloak admin API client — keycloak's own code, living in the module (novox/hq ADR 0044).
// The Keycloak admin API client — keycloak's own code, living in the module (novox/hq ADR 0039).
// Moved out of the shared hal sdk, where a change to Keycloak's admin API rebuilt everything; here
// it rebuilds only keycloak. Both this module's tools and its events entrypoint import it, and
// nothing outside keycloak does.
import { readFileSync } from "node:fs";
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,6 +1,6 @@
// keycloak's events. Keycloak's worth to the mesh is in what it changes — an identity created, a
// client registered, a password reset — so its events are emitted from the admin actions themselves
// (novox/hq ADR 0046/0047), not scraped back by polling. This module is the single vocabulary for
// (novox/hq ADR 0041/0042), not scraped back by polling. This module is the single vocabulary for
// them: every keycloak event goes through one of the helpers here, and the tools call them at the
// point the change succeeds.
//
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-keycloak",
"version": "0.1.0",
"description": "keycloak — identity and access. Its admin API client, tools and events live here (novox/hq ADR 0044).",
"description": "keycloak — identity and access. Its admin API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// keycloak's tools — moved here from the shared sdk (novox/hq ADR 0044), importing keycloak's own
// keycloak's tools — moved here from the shared sdk (novox/hq ADR 0039), importing keycloak's own
// client. They return structured data (not the hal MCP `{content:[...]}` shape); the mesh serves
// them through the sdk's tool harness. Write actions announce themselves through the module's event
// surface at the point they succeed.
+1 -1
View File
@@ -1,4 +1,4 @@
// The Lidarr API client — lidarr's own code, living in the module (novox/hq ADR 0044). Ported from
// The Lidarr API client — lidarr's own code, living in the module (novox/hq ADR 0039). Ported from
// the shared hal `arr` client, but self-contained: in nox each Servarr app owns its own copy, so a
// change to Lidarr's API rebuilds only lidarr and nothing else. Both this module's tools and its
// events entrypoint import it, and nothing outside lidarr does.
+1 -1
View File
@@ -1,7 +1,7 @@
// lidarr's events. The tool runtime imports this once the broker is bound. It watches the download
// queue and turns its comings and goings into mesh events.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.lidarr.album.grabbed — a release entered the queue (Lidarr grabbed it)
// module.lidarr.download.completed — a release left the queue, imported. The download.completed
// routing key matches what a media consumer subscribes to
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-lidarr",
"version": "0.1.0",
"description": "lidarr — music management. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "lidarr — music management. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// lidarr's tools — ported from the shared hal sdk (novox/hq ADR 0044), importing lidarr's own
// lidarr's tools — ported from the shared hal sdk (novox/hq ADR 0039), importing lidarr's own
// client. They return structured data (not pre-formatted text as hal did); the mesh serves them
// through the sdk's tool harness.
+2 -2
View File
@@ -1,4 +1,4 @@
// The Mailu API client — mailu's own code, living in the module (novox/hq ADR 0044). Moved out of
// The Mailu API client — mailu's own code, living in the module (novox/hq ADR 0039). Moved out of
// the shared hal sdk, where a change to Mailu's surface rebuilt everything; here it rebuilds only
// mailu. Both this module's tools and its events entrypoint import it, and nothing outside mailu does.
//
@@ -45,7 +45,7 @@ export interface MailMessage {
// The fields we ask doveadm for, once — kept together so read and search stay identical in shape.
const FETCH_FIELDS = "date.received hdr.subject hdr.from body.snippet";
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -2,7 +2,7 @@
// server's accounts and announces what changed, so the rest of the mesh can react to a mailbox
// appearing or an alias being pointed somewhere new.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.mailu.user.created / .deleted — a mailbox appeared or was removed
// module.mailu.alias.created / .deleted — an alias was added or removed
//
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-mailu",
"version": "0.1.0",
"description": "mailu — mail server. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "mailu — mail server. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// mailu's tools — moved here from the shared sdk (novox/hq ADR 0044), importing mailu's own client.
// mailu's tools — moved here from the shared sdk (novox/hq ADR 0039), importing mailu's own client.
// They return structured data; the mesh serves them through the sdk's tool harness. Deletions are
// guarded by an explicit `confirm`, since removing a mailbox destroys its mail and cannot be undone.
+2 -2
View File
@@ -1,4 +1,4 @@
// The MinIO admin client — minio's own code, living in the module (novox/hq ADR 0044). Ported out
// The MinIO admin client — minio's own code, living in the module (novox/hq ADR 0039). Ported out
// of the shared hal sdk, where a change to MinIO's surface rebuilt everything; here it rebuilds only
// minio. This module's tools, its provisioner and its events entrypoint import it; nothing outside
// minio does.
@@ -207,7 +207,7 @@ export class MinioClient {
/**
* Create a service account scoped to one bucket, under a given access key and secret key, and
* return the pair. The secret key is the mesh's — the mesh mints one password per consumer and
* hands a copy to both ends (novox/hq ADR 0053), so minio sets that as the secret rather than
* hands a copy to both ends (novox/hq ADR 0048), so minio sets that as the secret rather than
* generating one the consumer could never learn. The MinIO admin REST API encrypts this request
* with a key derived (Argon2) from the root secret, which node built-ins cannot reproduce — so, as
* hal did, the module drives the `mc` CLI, which the runtime image bundles.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-minio",
"version": "0.1.0",
"description": "minio — S3-compatible object store. Its admin client, tools, provisioner and events live here (novox/hq ADR 0044).",
"description": "minio — S3-compatible object store. Its admin client, tools, provisioner and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+2 -2
View File
@@ -1,13 +1,13 @@
// minio's provisioner — the adapter that makes minio a provider of the mesh `s3-bucket` interface
// (the name in module.json's `provides`). The reconcile loop, the contributions file, and reading
// the mesh's minted secret are the sdk harness's; this writes only the per-service half: how minio
// creates and removes a consumer's bucket and its scoped access key (novox/hq ADR 0044/0045/0053).
// creates and removes a consumer's bucket and its scoped access key (novox/hq ADR 0039/0040/0048).
//
// The `s3-bucket` interface: a consumer connects to an S3 endpoint with an access key confined to
// its own bucket. It depends on `s3-bucket`, not on minio, so any S3-compatible provider could serve
// it.
//
// **The access key and its secret are the mesh's, not the provisioner's (ADR 0053).** The mesh
// **The access key and its secret are the mesh's, not the provisioner's (ADR 0048).** The mesh
// derives the login (the access-key id) and hands it to both ends, and mints the secret key. minio
// creates the service account under exactly that access key with exactly that secret — a credential
// the provisioner invented is one the consumer could never present. The bucket is derived from the
+1 -1
View File
@@ -1,4 +1,4 @@
// minio's tools — ported here from the shared sdk (novox/hq ADR 0044), importing minio's own client.
// minio's tools — ported here from the shared sdk (novox/hq ADR 0039), importing minio's own client.
// They return structured data; the mesh serves them through the sdk's tool harness. These are the
// read/inspect operations useful to an operator; creating storage for a consumer is the provisioner's
// job, not a tool's.
+1 -1
View File
@@ -1,4 +1,4 @@
// mongodb's admin client — mongodb's own code, living in the module (novox/hq ADR 0044). Both this
// mongodb's admin client — mongodb's own code, living in the module (novox/hq ADR 0039). Both this
// module's tools and its provisioner import it, and nothing outside mongodb does.
//
// Commands run through `mongosh`, not a wire-protocol driver: the module may take NO npm dependency
+1 -1
View File
@@ -1,6 +1,6 @@
// mongodb's events entrypoint, loaded by the per-node tool host (the provisioner container runs
// ./provisioner separately). The database lifecycle events are EMITTED from the provisioner, where
// the lifecycle actually happens (novox/hq ADR 0046/0047):
// the lifecycle actually happens (novox/hq ADR 0041/0042):
// module.mongodb.database.provisioned — a consumer's database + owning user was created
// module.mongodb.database.deprovisioned — that database was removed
// Here in the tool host we react to them, keeping a lightweight audit trail of who was granted a
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-mongodb",
"version": "0.1.0",
"description": "mongodb — provides the mesh mongodb-database interface. Its client, provisioner, tools and events live here (novox/hq ADR 0044).",
"description": "mongodb — provides the mesh mongodb-database interface. Its client, provisioner, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+2 -2
View File
@@ -1,12 +1,12 @@
// mongodb's provisioner — the adapter that makes mongodb a provider of the mesh `mongodb-database`
// interface. The reconcile loop, the contributions file, and reading the mesh's minted password are
// the sdk harness's; this writes only the per-service half: how mongodb creates and removes a
// consumer's database + owning user (novox/hq ADR 0044/0045/0053).
// consumer's database + owning user (novox/hq ADR 0039/0040/0048).
//
// The `mongodb-database` interface: a consumer connects to a database it alone owns, as `as` with the
// password the mesh minted, authenticating against that same database.
//
// **The user name and password are the mesh's, not the provisioner's (ADR 0053).** The mesh derives
// **The user name and password are the mesh's, not the provisioner's (ADR 0048).** The mesh derives
// the login and hands it to both ends, and mints the password. mongodb creates a user and a
// same-named database under exactly that login — a name the consumer cannot learn is a database it
// cannot reach.
+1 -1
View File
@@ -1,4 +1,4 @@
// mongodb's tools — mongodb's own code (novox/hq ADR 0044), importing mongodb's own client. They
// mongodb's tools — mongodb's own code (novox/hq ADR 0039), importing mongodb's own client. They
// return structured data; the mesh serves them through the sdk's tool harness. Both call through
// MongoClient.evalJs(), the module's one execution boundary (see client.ts).
+1 -1
View File
@@ -1,4 +1,4 @@
// mosquitto's admin client — mosquitto's own code, living in the module (novox/hq ADR 0044). Both
// mosquitto's admin client — mosquitto's own code, living in the module (novox/hq ADR 0039). Both
// this module's tools and its provisioner import it, and nothing outside mosquitto does.
//
// Client, role and ACL administration is driven through `mosquitto_ctrl dynsec`, not a hand-rolled
+1 -1
View File
@@ -1,6 +1,6 @@
// mosquitto's events entrypoint, loaded by the per-node tool host (the provisioner container runs
// ./provisioner separately). The topic lifecycle events are EMITTED from the provisioner, where the
// lifecycle actually happens (novox/hq ADR 0046/0047):
// lifecycle actually happens (novox/hq ADR 0041/0042):
// module.mosquitto.topic.provisioned — a consumer's client + scoped role was created
// module.mosquitto.topic.deprovisioned — that client was removed
// Here in the tool host we react to them, keeping a lightweight audit trail of who was granted a
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-mosquitto",
"version": "0.1.0",
"description": "mosquitto — provides the mesh mqtt-topic interface. Its admin client, provisioner, tools and events live here (novox/hq ADR 0044).",
"description": "mosquitto — provides the mesh mqtt-topic interface. Its admin client, provisioner, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+2 -2
View File
@@ -1,13 +1,13 @@
// mosquitto's provisioner — the adapter that makes mosquitto a provider of the mesh `mqtt-topic`
// interface. The reconcile loop, the contributions file, and reading the mesh's minted password are
// the sdk harness's; this writes only the per-service half: how mosquitto creates and removes a
// per-consumer MQTT client (novox/hq ADR 0044/0045/0053).
// per-consumer MQTT client (novox/hq ADR 0039/0040/0048).
//
// The `mqtt-topic` interface: a consumer connects as `as` with the password the mesh minted, and
// publishes and subscribes under `<as>/#`, isolated from every other consumer by a Dynamic Security
// role scoped to exactly that subtree.
//
// **The login and password are the mesh's, not the provisioner's (ADR 0053).** The mesh derives the
// **The login and password are the mesh's, not the provisioner's (ADR 0048).** The mesh derives the
// login and hands it to both ends so they agree, and mints the password and delivers a copy to each.
// mosquitto creates exactly that client with exactly that password — a name or password the
// provisioner invented is one the consumer could never present.
+1 -1
View File
@@ -1,4 +1,4 @@
// mosquitto's tools — mosquitto's own code (novox/hq ADR 0044), importing mosquitto's own admin
// mosquitto's tools — mosquitto's own code (novox/hq ADR 0039), importing mosquitto's own admin
// client. They return structured data; the mesh serves them through the sdk's tool harness.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
+3 -3
View File
@@ -1,4 +1,4 @@
// mssql's admin client — mssql's own code, living in the module (novox/hq ADR 0044). Both this
// mssql's admin client — mssql's own code, living in the module (novox/hq ADR 0039). Both this
// module's tools and its provisioner import it, and nothing outside mssql does.
//
// SQL is executed through `sqlcmd`, not a wire-protocol driver: the module may take NO npm
@@ -86,7 +86,7 @@ export class MssqlClient {
// `-h -1` drops the column-header rule; `-y 0`/`-Y 0` lift the display-width cap so a long
// JSON document is not truncated; `-W` trims trailing whitespace so the JSON chunks rejoin
// cleanly. sqlcmd from the mssql-tools ships in the runtime container, the way `psql` ships
// with postgres's — the module owns its own code (ADR 0044) and shells out to it.
// with postgres's — the module owns its own code (ADR 0039) and shells out to it.
const { stdout } = await run(
"sqlcmd",
[
@@ -109,7 +109,7 @@ export class MssqlClient {
/**
* Create a login and a database it owns (mapped as a db_owner user), idempotently. The login,
* the database and the user all carry the consumer's minted name, so the consumer owns exactly
* its own database — a name it cannot learn is a database it cannot reach (ADR 0053).
* its own database — a name it cannot learn is a database it cannot reach (ADR 0048).
*/
async createDatabaseAndLogin(database: string, login: string, password: string): Promise<void> {
const logins = await this.query(
+1 -1
View File
@@ -1,6 +1,6 @@
// mssql's events entrypoint, loaded by the per-node tool host (the provisioner container runs
// ./provisioner separately). The database lifecycle events are EMITTED from the provisioner, where
// the lifecycle actually happens (novox/hq ADR 0046/0047):
// the lifecycle actually happens (novox/hq ADR 0041/0042):
// module.mssql.database.provisioned — a consumer's database + login/user was created
// module.mssql.database.deprovisioned — that database was removed
// Here in the tool host we react to them, keeping a lightweight audit trail of who was granted a
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-mssql",
"version": "0.1.0",
"description": "mssql — provides the mesh mssql-database interface. Its client, provisioner, tools and events live here (novox/hq ADR 0044).",
"description": "mssql — provides the mesh mssql-database interface. Its client, provisioner, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+2 -2
View File
@@ -1,12 +1,12 @@
// mssql's provisioner — the adapter that makes mssql a provider of the mesh `mssql-database`
// interface. The reconcile loop, the contributions file, and reading the mesh's minted password
// are the sdk harness's; this writes only the per-service half: how mssql creates and removes a
// consumer's database + login/user with the mesh-minted credential (novox/hq ADR 0044/0045/0053).
// consumer's database + login/user with the mesh-minted credential (novox/hq ADR 0039/0040/0048).
//
// The `mssql-database` interface: a consumer connects to a database it alone owns, as `as` with
// the password the mesh minted.
//
// **The login name and password are the mesh's, not the provisioner's (ADR 0053).** The mesh
// **The login name and password are the mesh's, not the provisioner's (ADR 0048).** The mesh
// derives the login and hands it to both ends, and mints the password. mssql creates a login, a
// same-named database, and a db_owner user under exactly that login — a name the consumer cannot
// learn is a database it cannot reach.
+1 -1
View File
@@ -1,4 +1,4 @@
// mssql's tools — mssql's own code (novox/hq ADR 0044), importing mssql's own client. They return
// mssql's tools — mssql's own code (novox/hq ADR 0039), importing mssql's own client. They return
// structured data; the mesh serves them through the sdk's tool harness. Both call through
// MssqlClient, the module's one pending execution boundary (see client.ts): the tool shapes are
// fixed and correct, and surface the work honestly through that boundary.
+2 -2
View File
@@ -1,4 +1,4 @@
// Nextcloud's client — nextcloud's own code, living in the module (novox/hq ADR 0044). Both this
// Nextcloud's client — nextcloud's own code, living in the module (novox/hq ADR 0039). Both this
// module's tools and its events entrypoint import it, and nothing outside nextcloud does.
//
// Nextcloud is administered two ways, and this client speaks both:
@@ -25,7 +25,7 @@ export interface NextcloudShare {
owner: string;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,7 +1,7 @@
// nextcloud's events. The tool runtime imports this once the broker is bound. It watches the user
// list and the share list and announces new arrivals.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.nextcloud.user.created — a user account appeared (occ user:list)
// module.nextcloud.share.created — a share appeared (OCS shares)
//
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-nextcloud",
"version": "0.1.0",
"description": "nextcloud — file sync and share. Its client, tools and events live here (novox/hq ADR 0044).",
"description": "nextcloud — file sync and share. Its client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// nextcloud's tools — importing nextcloud's own client (novox/hq ADR 0044). occ runs inside the
// nextcloud's tools — importing nextcloud's own client (novox/hq ADR 0039). occ runs inside the
// container; shares come over OCS. They return structured data; the mesh serves them through the
// sdk's tool harness.
+2 -2
View File
@@ -1,4 +1,4 @@
// Node-RED's admin-API client — nodered's own code, living in the module (novox/hq ADR 0044). Only
// Node-RED's admin-API client — nodered's own code, living in the module (novox/hq ADR 0039). Only
// this module's tools import it; nodered has nothing to poll, so there is no events entrypoint.
//
// Node-RED exposes a runtime admin API under its base URL: GET/POST /flows for the whole flow
@@ -20,7 +20,7 @@ export interface NodeRedNodeModule {
types: string[];
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-nodered",
"version": "0.1.0",
"description": "nodered — flow-based automation. Its client and tools live here (novox/hq ADR 0044).",
"description": "nodered — flow-based automation. Its client and tools live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+2 -2
View File
@@ -1,7 +1,7 @@
// nodered's tools — importing nodered's own admin-API client (novox/hq ADR 0044). They return
// nodered's tools — importing nodered's own admin-API client (novox/hq ADR 0039). They return
// structured data; the mesh serves them through the sdk's tool harness.
//
// The deploy tool is nodered's one event source (novox/hq ADR 0046/0047): a successful deploy
// The deploy tool is nodered's one event source (novox/hq ADR 0041/0042): a successful deploy
// emits module.nodered.flows.deployed. nodered has nothing to observe on a timer, so there is no
// separate events entrypoint — the emit rides the action that causes it. The emit is best-effort:
// if no broker is bound, the deploy still succeeds and the announcement is simply skipped.
+2 -2
View File
@@ -1,4 +1,4 @@
// The NZBGet API client — nzbget's own code, living in the module (novox/hq ADR 0044). Ported from
// The NZBGet API client — nzbget's own code, living in the module (novox/hq ADR 0039). Ported from
// hal's shared nzbget tools, but self-contained: a change to NZBGet's JSON-RPC now rebuilds only
// nzbget and nothing else. Both this module's tools and its events entrypoint import it, and
// nothing outside nzbget does. NZBGet speaks JSON-RPC at /jsonrpc, behind HTTP Basic auth.
@@ -41,7 +41,7 @@ export interface NzbgetHistoryItem {
success: boolean;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,7 +1,7 @@
// nzbget's events. The tool runtime imports this once the broker is bound. It watches the download
// queue and the history and turns their comings and goings into mesh events.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.nzbget.download.added — an NZB entered the queue
// module.nzbget.download.completed — an NZB finished successfully (left the queue, landed in
// history as SUCCESS). This exact routing key is what the
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-nzbget",
"version": "0.1.0",
"description": "nzbget — Usenet download client. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "nzbget — Usenet download client. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// nzbget's tools — ported from the shared hal sdk (novox/hq ADR 0044), importing nzbget's own
// nzbget's tools — ported from the shared hal sdk (novox/hq ADR 0039), importing nzbget's own
// client. They return structured data (not the pre-formatted text hal returned); the mesh serves
// them through the sdk's tool harness.
+2 -2
View File
@@ -1,4 +1,4 @@
// The Ombi API client — ombi's own code, living in the module (novox/hq ADR 0044). Ombi is the
// The Ombi API client — ombi's own code, living in the module (novox/hq ADR 0039). Ombi is the
// request front-end: viewers ask for movies and shows, and an operator approves them. This client
// talks its /api/v1 REST API (keyed by an ApiKey header); ombi's tools and events import it.
@@ -22,7 +22,7 @@ export interface RequestCounts {
available: number;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -3,7 +3,7 @@
// are worth announcing — the mesh can notify on a new request, and act on an approval (that is when
// a downloader should start looking).
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.ombi.request.created — a viewer filed a new request
// module.ombi.request.approved — a request was approved
//
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-ombi",
"version": "0.1.0",
"description": "ombi — media requests. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "ombi — media requests. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// ombi's tools — its own code (novox/hq ADR 0044), importing ombi's client. They return structured
// ombi's tools — its own code (novox/hq ADR 0039), importing ombi's client. They return structured
// data; the mesh serves them through the sdk's tool harness.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
+2 -2
View File
@@ -1,4 +1,4 @@
// The photo app's API client — photos' own code, living in the module (novox/hq ADR 0044). The
// The photo app's API client — photos' own code, living in the module (novox/hq ADR 0039). The
// module packages a self-hosted photo library (immich-shaped: a REST API under `/api`, authenticated
// by an API key sent as the `x-api-key` header). The client speaks only what the tools and the
// item-added event need: server version and statistics, albums, and recent assets.
@@ -26,7 +26,7 @@ export interface PhotosAsset {
createdAt?: string;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,7 +1,7 @@
// photos' events. The tool runtime imports this once the broker is bound. It watches the library and
// announces newly added assets.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.photos.item.added — a new asset appeared in the library
//
// New assets are found by diffing the recent-assets slice by asset id. Primed silently on the first
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-photos",
"version": "0.1.0",
"description": "photos — self-hosted photo library. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "photos — self-hosted photo library. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// photos' tools (novox/hq ADR 0044), importing photos' own client.
// photos' tools (novox/hq ADR 0039), importing photos' own client.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
import { PhotosClient } from "../client.js";
+1 -1
View File
@@ -1,4 +1,4 @@
// The Plex API client — plex's own code, living in the module (novox/hq ADR 0044). Moved out of the
// The Plex API client — plex's own code, living in the module (novox/hq ADR 0039). Moved out of the
// shared hal sdk, where a change to Plex's API rebuilt everything; here it rebuilds only plex. Both
// this module's tools and its events entrypoint import it, and nothing outside plex does.
+1 -1
View File
@@ -1,7 +1,7 @@
// plex's events. The tool runtime imports this once the broker is bound, and it does two things:
// it watches the server and emits what happened, and it reacts to the mesh's media events.
//
// Emits (novox/hq ADR 0046/0047):
// Emits (novox/hq ADR 0041/0042):
// module.plex.playback.started / .stopped — someone began or ended watching
// module.plex.item.added — a new item appeared in a library
// Consumes:
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-plex",
"version": "0.1.0",
"description": "plex — media server. Its API client, tools and events live here (novox/hq ADR 0044).",
"description": "plex — media server. Its API client, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// plex's tools — moved here from the shared sdk (novox/hq ADR 0044), importing plex's own client.
// plex's tools — moved here from the shared sdk (novox/hq ADR 0039), importing plex's own client.
// They return structured data; the mesh serves them through the sdk's tool harness.
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
+2 -2
View File
@@ -1,4 +1,4 @@
// The Portainer API client — portainer's own code, living in the module (novox/hq ADR 0044).
// The Portainer API client — portainer's own code, living in the module (novox/hq ADR 0039).
// portainer is tools-only: its "events" would really be the underlying containers' lifecycle,
// which the host owns and emits — so this module reads Portainer's own resources (endpoints,
// stacks, containers) and exposes them, and stops there.
@@ -29,7 +29,7 @@ export interface PortainerContainer {
status: string;
}
/** The settings-merged config the mesh delivers (novox/hq ADR 0051): { url, apiKey, token, password, user, ... }. */
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
function meshConfig(file?: string): Record<string, string> {
if (!file) return {};
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-portainer",
"version": "0.1.0",
"description": "portainer — container management UI. Its API client and tools live here (novox/hq ADR 0044).",
"description": "portainer — container management UI. Its API client and tools live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {
+1 -1
View File
@@ -1,4 +1,4 @@
// portainer's tools — its own code (novox/hq ADR 0044), importing portainer's own client. They
// portainer's tools — its own code (novox/hq ADR 0039), importing portainer's own client. They
// return structured data; the mesh serves them through the sdk's tool harness. portainer is
// tools-only (no events entrypoint): a container starting or stopping is the host's signal to emit,
// not Portainer's to re-announce.
+2 -2
View File
@@ -1,4 +1,4 @@
// postgres's admin client — postgres's own code, living in the module (novox/hq ADR 0044). Both this
// postgres's admin client — postgres's own code, living in the module (novox/hq ADR 0039). Both this
// module's tools and its provisioner import it, and nothing outside postgres does.
//
// SQL is executed through `psql`, not a wire-protocol driver: the module may take NO npm dependency
@@ -60,7 +60,7 @@ export class PostgresClient {
async query(sql: string, database = "postgres"): Promise<QueryResult> {
// Executed through `psql`, the way minio drives itself through `mc` and mailu through doveadm:
// node has no postgres wire client without an npm dependency, and the module owns its own code
// (ADR 0044), so it shells out to the client the postgres tools ship. CSV so the rows come back
// (ADR 0039), so it shells out to the client the postgres tools ship. CSV so the rows come back
// structured; ON_ERROR_STOP so a failed statement is an error here, not a success with a warning.
const { stdout } = await run(
"psql",
+1 -1
View File
@@ -1,6 +1,6 @@
// postgres's events entrypoint, loaded by the per-node tool host (the provisioner container runs
// ./provisioner separately). The database lifecycle events are EMITTED from the provisioner, where
// the lifecycle actually happens (novox/hq ADR 0046/0047):
// the lifecycle actually happens (novox/hq ADR 0041/0042):
// module.postgres.database.provisioned — a consumer's database + owning role was created
// module.postgres.database.deprovisioned — that database was removed
// Here in the tool host we react to them, keeping a lightweight audit trail of who was granted a
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@novox/module-postgres",
"version": "0.1.0",
"description": "postgres — provides the mesh postgres-database interface. Its client, provisioner, tools and events live here (novox/hq ADR 0044).",
"description": "postgres — provides the mesh postgres-database interface. Its client, provisioner, tools and events live here (novox/hq ADR 0039).",
"type": "module",
"private": true,
"dependencies": {

Some files were not shown because too many files have changed in this diff Show More