Commit Graph
903 Commits
Author SHA1 Message Date
mesh-admin e61fc6aede Merge pull request 'Ask the check of a delivery that waits for one nobody asked (hq issue 290)' (#101) from fix/a-delivery-waiting-gets-its-check into main 2026-10-07 11:20:18 +00:00
jochen d1de0edd4a Ask the check of a delivery that waits for one nobody asked (hq issue 290)
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
A pull request's head announced again from the bus's history at
mesh-delivery's first start was proposed with no verdict and nothing
ever asked its check: the controller had taken that announcement long
before, and stalled raised it after an hour for the operator.

A proposed delivery with no verdict and no check asked is now asked
through delivery-check once it has waited past a grace longer than a
check takes; an announcement carrying its head's decided gate status
takes it. The proposed bound runs from the ask, and H2's close may
re-ask once (a table row) before the delivery is the operator's.
2026-10-07 11:44:21 +02:00
mesh-admin 46481baab7 Merge pull request 'Keep secrets off command lines the runtime records (hq issue 282)' (#100) from fix/no-secret-on-a-command-line into main 2026-10-06 23:40:53 +00:00
jochen 13b7562c47 Keep secrets off command lines the runtime records (hq issue 282)
mesh/merge-gate pass: builds docker, keycloak, minio, mosquitto → ace, g14, novox, shanks; no bus step; 2 wait(s) for a person; every machine composes with…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mosquitto passed the broker's admin password to mosquitto_ctrl as -P on
every docker exec, and the container runtime keeps every exec's command
line in its event stream, where docker_events returned it. The admin
credentials now reach mosquitto_ctrl as a 0600 options file fed on
stdin, client passwords at its own prompt, and an argv carrying a secret
is refused before it runs. The admin secret says it is taken at start:
the bootstrap re-runs when the mesh replaces it and re-keys the broker
online from the value it last applied, so it can be rotated.

docker_events redacts what an exec's command line carried, and
docker_secrets_in_events names such secrets by name. keycloak's repair
hands kcadm its passwords through KC_CLI_PASSWORD; minio gives mc its
root alias through MC_HOST_mesh.
2026-10-07 01:37:21 +02:00
mesh-admin 950e52ff64 Merge pull request 'mesh-delivery: the owner of deliveries and delivery groups; the forge's note, view and status tools (hq ADR 0239)' (#99) from feat/mesh-delivery into main 2026-10-06 23:00:36 +00:00
jochen b6f0bc309b Add mesh-delivery, the owner of deliveries and delivery groups (hq ADR 0239)
mesh/merge-gate fail: a manifest the change touches fails the module check: modules/mesh-delivery/module.json: this manifest cannot be used:
mesh/delivery delivered
mesh/delivery-group group feat/mesh-delivery delivered: every member is delivered
One module answers 'did my change go out' for a commit and orders a
cross-repository change: one compiled state table, its state on the bus,
every transition said, noted on the commit and shown on the pull request.
The forge's holder gains the note, view and status tools it asks with, and
says closed pull requests and a merge's head and statuses.
2026-10-06 23:59:54 +02:00
mesh-admin 88135ad0e7 Merge pull request 'gitea: graph-driven announce, both check statuses, the change plan, branch-protection tools (hq ADR 0238)' (#98) from feat/the-graph-decides-what-is-checked into main 2026-10-06 21:00:27 +00:00
jochen 81641a4b22 Test the modules the planner says the change reaches (hq ADR 0238)
mesh/merge-gate pass: the merge check passed
mesh/delivery delivered
mesh/delivery-group group feat/the-graph-decides-what-is-checked delivered: every member is delivered
2026-10-06 22:54:52 +02:00
jochen cc1305a354 gitea: post a pull request's change plan with its verdict (hq ADR 0238)
mesh/merge-gate pass: the merge check passed
mesh/delivery superseded: a newer head of the same pull request
The gate's status says what the change does and how it was judged; a change that builds
something gets its plan as a comment — the tiers, what each machine receives, and what
is not an ordinary send.
2026-10-06 22:50:32 +02:00
jochen dba71a97a8 gitea: tell the controller which files a pull request deletes; say the dependents a merge would build (hq ADR 0238)
The controller asks its planner what a pull request reaches, as if merged: a module whose
manifest the change deletes is one the merge removes, and the plan's dependents are said
on the gate's status beside the modules it moves.
2026-10-06 22:34:53 +02:00
jochen bd7b757dd9 gitea: give the controller what it maps a pull request onto the graph with; set both check statuses; protect a branch by tool (hq ADR 0237)
The controller now decides what a pull request's check runs from the mesh's module
graph, so the announcement carries the changed directories that hold a module at the
head and whether the head has a merge-check.sh. A verdict sets mesh/merge-gate (the
gate, with the modules it judged) and mesh/repo-check (the repository's own tests).
gitea_branch_protection_get/set let the operator's agent make those statuses required.

The catalogue's merge-check.sh leaves the gate to the build seat and keeps its own layer:
every manifest through module check, and the touched Go modules' tests.
2026-10-06 21:56:04 +02:00
mesh-admin b792bf4ba2 Merge pull request 'Phase 5: check every pull request before it merges, and show the verdict on it (hq ADR 0237)' (#97) from feat/merge-gate into main
mesh/delivery delivered
2026-10-06 19:35:57 +00:00
jochen 9951718623 Check every pull request before it merges, and show the verdict on it (hq ADR 0237, to-be 45 §9)
The forge's announcer announces each new head of an open pull request as pull.updated, once,
and marks the head pending; the controller asks the build seat to check it against every
machine of the mesh's facts, and says the verdict as checked, which the forge's holder sets as
the head commit's status mesh/merge-gate - an error never as a success - with the check's own
account as a comment when it is not a pass. merge-check.sh is the catalogue's check: every
manifest through the running controller's module check and merge gate, and the Go tests of each
module the change touches, a module whose dependencies cannot be fetched said as not tested.
2026-10-06 21:16:32 +02:00
mesh-admin 1ba2c0513f Merge pull request 'gitea: say which changed directories hold a module at the merge commit (hq issue 278)' (#96) from fix/a-module-directory-is-never-shared-code into main
mesh/delivery delivered
2026-10-06 18:28:48 +00:00
jochen 1178db279e gitea: say which changed directories hold a module at the merge commit (hq issue 278)
The controller read a changed file as shared code unless its directory was a module it holds or
the merge also changed that directory's manifest. A merge touching modules/showcase/index.ts -
the catalogue's reference module, held by no machine - therefore rebuilt all 103 modules built
from this repository on 2026-10-06, 88 of them byte-identical, with the build agent first only
because everything else is built by it.

Whether a directory is a module is a fact of the repository at the commit, so the announcer now
looks it up: every directory above a changed file (never the root) is asked for its module.json
at the merge commit, and the ones that have one go out as module_dirs, with module_dirs_said.
Not said when the file list is cut, past 300 directories, or when the forge cannot be asked;
the controller then keeps its old rule, which rebuilds too much rather than too little.

modules/showcase stays: TestTheShowcaseModuleIsAValidManifest in mesh-controller parses it and
hq to-be 18 and 20 name it as the reference module.
2026-10-06 19:55:17 +02:00
mesh-admin 9d407248b3 Merge pull request 'Back up the bus by the server's own snapshot of each stream, not its live files (hq ADR 0235)' (#93) from feat/bus-snapshot into main
mesh/delivery superseded: a newer delivery to the same trunk took over its walk
2026-10-06 17:33:13 +00:00
mesh-admin 6e4d12e5a4 Merge pull request 'Say which modules wait for a person's push; announce a merge's deleted files (hq ADR 0236)' (#95) from feat/core-upgrades-that-roll-back into main
mesh/delivery delivered
2026-10-06 17:32:58 +00:00
jochen 1fd2914ae1 Say which modules wait for a person's push, and announce the files a merge deleted (hq ADR 0236)
With a gate on the first machine and a rollback after it, a module's build rolls out by
default. The ones kept back say why: the network path a rollback could not cross, the
providers every consumer on a machine drops with, and the stores holding the photos.
A merge's deleted files are announced, so a module whose manifest went is forgotten
rather than asked to build (the public-acme plan failure).
2026-10-06 18:39:13 +02:00
mesh-admin 7f99fb4a85 Merge pull request 'audit-logger, model-usage: retry a failed write and never lose the event (hq issue 276)' (#94) from fix/audit-and-usage-never-lose-an-event into main
mesh/delivery delivered
2026-10-06 16:38:06 +00:00
jochen 08265a70ca audit-logger, model-usage: retry a failed write and never lose the event (hq issue 276)
Both caught a failed write and took the event, losing it silently; the SDK's rule is to throw when
the work was not done. A failed write now throws so the bus offers the event again, and is spooled
on disk at once; on its last delivery the spooled event is taken, and a background pass replays the
spool once writing works. The runtime does not pass the delivery count, so the spool counts failed
deliveries itself, across restarts. Over its bound (1000 events or 30 minutes) the last delivery is
no longer taken, so the bus gives it up and the controller raises max-deliveries - the one existing
condition that names a consumer which cannot keep up - while the spool still holds it.

Writes are idempotent by event: the trail skips an id it already wrote; the usage upsert keeps the
reading observed latest (migration 2), so a late replay never overwrites a newer one. Each module has
a status tool for the spool, declared as valuable data (ADR 0233). model-usage moves to the bundle
shape (ADR 0198) with its schema in a prepare step and numbered migrations; its old container shape
had no image. Both on mesh-sdk 0.1.13.

The log-only handlers of redis, mssql, mosquitto, mongodb, mesh-vault, showcase and the catalogue no
longer throw a TypeError on an event without a body.
2026-10-06 18:36:16 +02:00
jochen 419e82cded Back up the bus by the server's own snapshot of each stream, not its live files (hq ADR 0235)
The restic holder copied JetStream's store while the server wrote it; such a
copy may not restore. The nats image now carries mesh-nats-snapshot, run by
the declared dump under the module's own bus account (snapshot API only):
every stream one at a time, flow-controlled, into one tar with a manifest of
counts, sequences and checksums. Restore builds a new store beside the live
one with the bus's own server; a person swaps it in. Proven against
throwaway nats 2.11 servers being written to during the snapshot.
2026-10-06 18:20:51 +02:00
mesh-admin f144f6eee8 Merge pull request 'Every module declares its data; the backup holder measures it (hq ADR 0233)' (#92) from feat/a-module-declares-the-data-it-holds into main
mesh/delivery delivered
2026-10-06 15:05:51 +00:00
jochen 7524390cad Bound the holder's measuring; dump the database platform (hq ADR 0233)
Walks run at most daily and stop after ten minutes or two million files; datasets are read from
their counters and large items from their top level only. The database platform's tables are
dumped with pg_dumpall rather than copied as live files.
2026-10-06 17:00:23 +02:00
jochen 685cb1cb1b Declare every module's data; the backup holder measures it (hq ADR 0233)
Backup lines are derived from each module's data section instead of written by hand; the holder
measures declared items, reads the array under them, and deletes a retired item only after a last
restore point; the Go providers say each held consumer's size so an empty replacement is seen.
2026-10-06 16:47:49 +02:00
mesh-admin 8f5a75ab9b Merge pull request 'Fold public-acme into route-proxy; drop dhcpcd and cloudflare-dns (hq ADR 0226)' (#84) from feat/route-proxy-names-its-public-issuer into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-06 13:04:29 +00:00
jochen 57d524f4ef Fold public-acme into route-proxy; drop dhcpcd and cloudflare-dns (hq ADR 0226)
public-acme ran nothing and had one consumer. The proxy now states the issuer itself, byte for byte
what the binding rendered, so its account directory and every certificate stay put. dhcpcd and
cloudflare-dns are assigned nowhere and nothing requires what they provide.
2026-10-06 14:59:43 +02:00
mesh-admin 368fa2f45e Merge pull request 'postgres, keycloak: retire a consumer, delete only on a person's word (hq ADR 0230)' (#91) from feat/retired-consumers into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-06 12:51:35 +00:00
jochen 2c15074a0b Retire only once the same answer has held ten minutes as well as five passes
Five passes are twenty-five seconds, shorter than a controller restart, a store
reconnecting or a file half written; the operator asked for both (hq ADR 0230).
2026-10-06 14:28:21 +02:00
jochen e68ef88333 Retire a consumer the mesh stops asking for, and delete only on a person's word (hq ADR 0230)
The hourly release of ADR 0229's brake still ended in the mesh acting alone on
a mistake. A consumer now stays active until the same unasked set holds for
five passes, waits for a person past three or half of those held, is disabled
and marked rather than withdrawn, comes back as it was when asked again, and is
deleted only through the provider's delete tool. The backend keeps the mark, so
a restart forgets nothing and finds what was withdrawn before.
2026-10-06 13:54:10 +02:00
mesh-admin 4554e18279 Merge pull request 'Brake a withdrawal larger than its bound (hq to-be 45 Phase 2, ADR 0227 rule 4)' (#90) from feat/a-core-that-cannot-fail-silently-phase-2 into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-06 10:43:11 +00:00
jochen 4238dd8616 Name the SDK harness the Go loop follows: 0.1.11, with the withdrawal brake 2026-10-06 12:29:21 +02:00
jochen 2c151e7c21 Brake a withdrawal larger than its bound (hq to-be 45 Phase 2, ADR 0227 rule 4)
Issue 241 withdrew seven consumers in one pass on one misread file; its fix
refuses a file it cannot read, and a file read whole that names nobody
still withdraws everybody. A pass that would withdraw more than one
consumer at once, or more than half of those it holds, now withdraws
nothing: each consumer it kept is announced provisioner.failing with the
class withdrawal-braked, so the controller raises it as a condition and
the operator is told, and one is let go each hour while the mesh goes on
not asking for them. A consumer asked for again is kept and said recovered.
Postgres and keycloak carry the harness identically.
2026-10-06 12:29:21 +02:00
mesh-admin 12569ed085 Merge pull request 'Mark the OpenAI keys letta and supabase hold as issued outside the mesh' (#89) from feat/a-given-secret-lives-until-the-first-good-start into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-06 10:27:21 +00:00
jochen b1bd5c861e Mark the OpenAI keys letta and supabase hold as issued outside the mesh
The controller now replaces a given at-start secret after the module's
first good start and on rotate (hq ADR 0228); a key only OpenAI can
issue must say so, or a fresh random value would take its place.
2026-10-06 12:13:57 +02:00
mesh-admin 8c2c9ace0e Merge pull request 'messenger: history is never news — read the open conditions first, coalesce bursts (hq issue 271)' (#88) from fix/messenger-history-is-never-news into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-06 10:06:27 +00:00
jochen a4b92c1306 messenger: never say history as news — read the open conditions first, coalesce bursts
A consumer made on 2026-10-06 was handed three hours of raised-and-cleared
conditions at once and the holder said each as new: 20 desktop notifications
in a second. What is said is now decided by the controller's open set and by
an event's own time, never by its arrival; bursts are one message, the
desktop gets warnings at most every 15 min, the cap is said once, and the
first minute after start says only the urgent conditions still open.
Replays of that morning's 96 events are tests. Also drops two committed
binaries. (novox/hq issue 271)
2026-10-06 12:03:19 +02:00
mesh-admin aabc8aa039 Merge pull request 'supabase: make logflare's stored key and backends follow its environment' (#87) from fix/supabase-logflare-reconcile into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-06 09:50:28 +00:00
jochen 908d45864a supabase: make logflare's stored key and backends follow its environment
logflare 1.4.0 copies LOGFLARE_API_KEY into its default user and
POSTGRES_BACKEND_URL into every source's backend once, when it creates
them, and never reads either again. On ace every source still points at
db:5432, which resolves nowhere, so analytics stored no logs; and the key
that leaked into the log before #85 could not be replaced, because the
mesh had to mark it "applied".

The start script now runs logflare's migrations and then reconcile.exs
through `logflare eval`, before logflare starts: it uses logflare's own
Users and Backends contexts to set the default user's key to
LOGFLARE_API_KEY (clearing old_api_key) and to point each postgres
source backend at POSTGRES_BACKEND_URL. It changes nothing that already
matches, says what it changed without printing the key or a password,
and stops the start when it cannot finish.

With the key taken at every start, the secret is "at-start" and `secret
rotate` works. analytics restarts on its env file and studio on its env
file too, so a rotated key reaches every reader.
2026-10-06 11:48:57 +02:00
mesh-admin c12d364a68 Merge pull request 'The operator-channel and the watcher's watcher (hq to-be 45 phase 1)' (#86) from feat/the-mesh-says-when-it-is-wrong into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-06 08:37:48 +00:00
jochen 0ae7933d54 Tell the operator what the mesh finds wrong, and watch the watcher (hq to-be 45 phase 1)
The mesh noticed 48 core failures in six days and told nobody (ADR 0227).
messenger holds the operator-channel seat: it consumes the controller's
condition events and sends them to Telegram and the desktop notifier,
deduplicated by key, reminded once, edited on clear, capped at 20 an hour
with the rest folded, and refusing anything carrying an address, a path or
a secret. mesh-watcher, on a machine other than the control node, sends to
Telegram directly when the self-check heartbeat or the bus goes silent.
2026-10-06 09:35:55 +02:00
mesh-admin 495bb88111 Merge pull request 'supabase: keep logflare's API key out of its log (hq issue 268)' (#85) from fix/supabase-logflare-key-out-of-logs into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-06 00:34:13 +00:00
jochen b87dc29706 supabase: keep logflare's API key out of its log (hq issue 268)
vector handed logflare its API key as ?api_key= in every sink URL, and
logflare 1.4.0 prints a failed request's whole URL in its Plug.Cowboy
error report. Its ingest fails on every request here, so the key was in
the analytics log about every ten seconds. The report is an error, so no
log level hides it.

Every sink now sends the key in the x-api-key header, which logflare
reads first. A start script refuses to start logflare while the vector
config it is given still puts the key in a URL.

The key is marked "applied", not "at-start": logflare writes it into its
default user once and never updates it, so the mesh must not rotate it
by restarting.
2026-10-06 02:33:32 +02:00
mesh-admin e5cb7b071c Merge pull request 'State each provision's identity bound (hq issue 263, ADR 0225)' (#83) from fix/263-identity-bounds-per-provision into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-06 00:29:56 +00:00
mesh-admin 7fad19a764 Merge pull request 'letta: keep its passwords out of its log; docker: find and hide secrets a container printed (hq issue 268)' (#82) from fix/letta-secrets-out-of-logs into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-06 00:25:32 +00:00
jochen 4769dadf63 State each provision's identity bound (hq issue 263)
Consumers were held to an S3 access key's 20 characters whatever they
required. Each provider now says what its backend keeps: minio 20,
PostgreSQL and MongoDB and DNS 63, Gitea 40, a mailbox 64, SQL Server 128,
Keycloak 255, unbounded where the store has no limit, and none for the
resolver and route provisions, which keep no name of their consumers.
Needs the controller that reads the field (mesh-controller, ADR 0225).
2026-10-06 02:16:27 +02:00
jochen bbb67e41a0 docker: find and hide secrets a container printed into its log (hq issue 268)
letta printed two passwords into its log for weeks and nothing noticed,
and docker_logs handed them to whoever asked. docker_secrets_in_logs
compares each container's recent lines with the secret-named values of
its environment, the passwords in its URIs, and any URI carrying a
password, and names what it found by container, module and variable -
never the value. docker_logs redacts the same values before answering.
2026-10-06 02:13:42 +02:00
jochen f9f27d4878 letta: keep its database and server passwords out of its log (hq issue 268)
letta 0.6.8 prints LETTA_PG_URI whole (startup.sh, alembic, server.py)
and its server password when it starts in secure mode, so both were in
the container's log on every one of its restarts. Newer letta still
prints both, and neither is a log level.

The URI now names no password: libpq reads it from a mounted pgpass
file (PGPASSFILE). The one print of the server password is rewritten by
a start script before the server starts, and the script refuses to start
letta if that print, or a password in the URI, is still there - a letta
that does not start says why; one that leaks says nothing.

Both own secrets say they are read at start, so `rotate` can replace the
server password the mesh made.
2026-10-06 02:13:42 +02:00
mesh-admin cc2f19123a Merge pull request 'nats: run 2.11.17, which hands a consumer with several filters every message (hq issue 266)' (#81) from fix/nats-multi-filter-consumers-skip into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-05 23:40:11 +00:00
jochen 0275c2eeac nats: run 2.11.17, which hands a consumer with several filters every message (hq issue 266)
On 2.10.29 such a consumer was moved past a message now and then without
handing it over; the controller's events consumer has seven filters, and a
merge on the stream never reached it. The test reproduces the skip on 2.10.29
and keeps the image's release equal to the server it tests.
2026-10-06 01:29:20 +02:00
mesh-admin 78328d4ab2 Merge pull request 'keycloak: port to Go and repair a refused admin; providers announce a failing consumer' (#80) from feat/identity-provider-admin-safety-nets into main
mesh/delivery held for a person: merged without a passing check: only a person decides that it goes on
2026-10-05 22:39:02 +00:00