Commit graph Bifrost/tools
Author SHA1 Message Date
7adc7315a3 send gate: re-check the brake after the gate wait, before the PUT
A send queued at the room gate passed the brake check at entry only, so a
brake placed while it waited still let the PUT through. The check now also
runs inside the gate, just before the PUT, and answers a typed Brake refusal.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 10:37:22 +00:00
Meta Vens
a76c70cc39 persona matrix mouth: a v2 send is refused if the room moved past her basis; check and PUT are one step per room across every Persona
Removes the bare PUT; the one PUT now lives inside the room gate. v1 stays served unchecked through the same gate.
Wire: chat_send_request.v2 (basis) and chat_send_receipt.v2 (newer); RoomMoved is v2 only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 11:01:44 +01:00
Meta Vens
c29c5d1a4e persona matrix mouth: a delivery's context is read from Matrix, not remembered
Some checks failed
Publish Container / Build and publish Bifrost image (push) Has been cancelled
The per-room in-memory history map is gone: a restart emptied it and /sync resumed from the
cursor, so every room (a DM first) lost its past. When a delivery is built the mouth now reads
the room's timeline from the homeserver as that Persona (GET /messages dir=b, or the thread's
root plus /relations m.thread for a threaded message), keeps m.room.message from everyone
including her own, oldest first, ending before the delivered event. Newest lines are kept to a
16 KiB byte budget (each clipped to limits.contextBytes), at most the 20 lines the delivery
tuple carries. One fetch per room or thread per sync round. A failed fetch gives empty context
and a redacted log line; the delivery still goes out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:10:57 +01:00
Meta Vens
98689b787d persona matrix mouth: each Persona's rooms are the shared MATRIX_ROOMS plus her own MATRIX_ROOMS_<SLUG>
Some checks failed
Publish Container / Build and publish Bifrost image (push) Has been cancelled
SLUG is the Persona slug upper-cased with "." and "-" turned into "_". Lists are
deduplicated; an absent variable gives shared rooms only; a malformed project alias,
or two Personas mapping to one variable, is refused at start. The send path's
UnknownRoom check already reads the mouth's own list, so another Persona's project
room is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:12:31 +01:00
Meta Vens
3bff0d779c persona matrix mouth: log the many-peer server's errors and keep serving
Some checks failed
Publish Container / Build and publish Bifrost image (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:51:57 +01:00
Meta Vens
b892c64f00 persona matrix mouth: one session per client on the many-peer RUDP server; every request answered on the connection it arrived on
Two Persona turns running at once no longer take each other's session. Requests run in order per client, not across clients.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:51:57 +01:00
Meta Vens
cc76f0f2d0 mouth-all-personas: one Matrix mouth serves every Persona; deliveries carry mentions and is_direct, refusals are typed; same-server DM invites accepted; the ring is deleted (Persona turns are the heartbeat's) (squash of eureka-body/mouth-all-personas 7b63031..5b628e2; Soul s1-s3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 02:49:59 +01:00
Meta Vens
7b630312e1 persona-mouth: Matrix ear and mouth for Personas (brake-refusing sends, token only in the Authorization header, cursor after ack, one ring per idle window); delete the Discord posting actuator (squash of eureka-body/persona-mouth 1894f65..ab2bdc6; Soul s1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:41:30 +01:00
Meta Vens
0312e8bbac Watchdog notice reader: a future stamp is eligible now; no production clock seam
A failed entry stamped ahead of the clock (host clock stepped forward then
corrected) is eligible now instead of waiting out the skew; the next attempt
rewrites the stamp. The WATCHDOG_NOTICE_NOW_MS env var is deleted: the reader
reads Date.now(), and tests fix it with a --import preload (tests/fake-clock.mjs).
The unknown-entry tests now rerun far past the backoff so a retried unknown fails.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 02:19:01 +02:00
Meta Vens
69e12e3867 Watchdog notice reader: a failed send is retried forever with capped backoff
Ruling notice-retry-policy. The five-attempt cap and its terminal
"exhausted" state are gone. A failed entry is eligible again once
min(2^(attempts-1), 60) minutes have passed since its last journal
update; a run inside the delay skips it without spawning the bridge.
Every attempt keeps the same nonce. The exit status is non-zero while
any entry is unknown or has failed 3 or more times, and retries go on.
status shows nextEligibleAt for failed entries.

The clock is one now() that WATCHDOG_NOTICE_NOW_MS can override, like
the bridge timeout override, so tests drive the backoff without sleeping.
The process summary field "exhausted" is renamed "failing". A journal
entry whose updatedAt is not a date is now refused as
journal-entry-invalid, since the delay is measured from it.

All commits build.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 01:48:44 +02:00
Meta Vens
ff087a554b Watchdog notice reader: pin timeout, spawn-error, 5xx/429 and guard behaviour; say what non-2xx is
Tests only, plus wording. No change to what the reader or bridge does.

- A spawn timeout and a spawn error (ENOBUFS from a bridge that floods
  stdout) record unknown, are never re-posted, and block the closure.
  WATCHDOG_NOTICE_BRIDGE_TIMEOUT_MS overrides the 60 s timeout so the test
  reaches that branch; the default is unchanged.
- A 500/502/503/429 answer to the message POST exits 75 from the bridge, and
  through the reader is retried with the same nonce until it completes.
- allowed_mentions {parse: []} is asserted in the posted body; status exits
  non-zero while an entry is unknown and zero when none is.
- The bridge comment and docs/watchdog-notice.md no longer call a non-2xx
  answer "definite": it is retried with the same nonce, relying on
  enforce_nonce inside Discord's window, and may double-send if Discord acted
  on a 5xx outside it.

Builds. Two tests fail on Windows (SIGKILL status), as before; they pass on
Yggdrasil.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 22:56:27 +02:00
Meta Vens
1f001e11d2 Watchdog notice reader: the journal owns "sent"; only exit 75 is retried
Fix batch for Soul pass s1 (spec r3):
- Delete prune-on-absence and its test. Absence from Idunn's store no longer
  deletes a journal entry, so an empty or restored store never re-sends.
- discord-dm exits 75 only when no message can have been sent (arguments,
  token, DM channel open, non-2xx on the message POST). The reader records
  failed for 75 only; any other non-success without a message id is unknown
  and is never re-posted.
- Subject grammar is Idunn's require_id; the notice puts it in backticks.
- Times are bounded to the Date range; notice text is built before the
  running entry is written.

Builds and passes on Linux. On Windows the two tests that SIGKILL the reader
cannot pass (process.kill does not deliver a signal there).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 21:54:23 +02:00
Meta Vens
ef867f3e28 Watchdog notice reader: start the verb after the module finishes initialising
The top-level await ran main() before the const declarations below it, so every
process run died in the temporal dead zone with unexpected-error.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 21:19:42 +02:00
Meta Vens
417454c28f Watchdog notice reader: Idunn incidents become owner DMs through Bifrost's own journal
tools/watchdog-notice.mjs is a one-shot reader. It reads Idunn's incident
store unlocked with SingleFileMessagePackBackingStore.pullAll, sends one opening
and one closing DM per incident through bifrost-bridge.mjs, and keeps a
bifrost.watchdog_notice_execution.v1 journal: a running entry becomes unknown
and is never re-posted, a failed one is retried with the same nonce up to five
times, terminal entries are pruned once Idunn retires the incident. The exit
status is the delivery-failure signal.

discord-dm accepts --nonce (at most 25 chars) and sends nonce with
enforce_nonce so a retry inside Discord's window returns the original message.

The tests run the CLI against the store bytes Idunn wrote (fixture from
GameCult/Idunn 7f7528b3) with a counting bridge double, and once through the
real bridge with fetch stubbed. docs/watchdog-notice.md states the CultLib
>= 8cb3b728 floor.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 21:15:18 +02:00
Meta Vens
4152676ebb Merge watchdog bifrost-suite: the node suite loads against current CultLib
Eureka idunn-watchdog cut bifrost-suite. Soul verdict
idunn-watchdog:verdict:cut-bifrost-suite.s1 holds: 24 tests, 22 pass,
0 fail, 2 skipped with reason. Bifrost now requires CultLib >= 8cb3b728.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:40:35 +02:00
Meta Vens
868df35449 Bifrost suite: load @gamecult/cultcache-ts; skip Rust fixture tests with a stated reason
CultLib 8cb3b728 renamed only cultcache-ts to @gamecult/cultcache-ts; the
suite and three tools still required the bare name. Rename the require
string in tests/persona-feedback-cli, persona-discord-delivery,
persona-discord-crossing-rust-smoke, tools/persona-feedback (:114),
agent-transport and governance-threads. No fallback: a pre-8cb3b728 CultLib
fails loudly at import. operator-notification.mjs is untouched (retire cut).

The two Rust fixture tests skip, naming the missing producer (Epiphany
removed both fixture bins in 387afe49), and drop the hard-coded
CARGO_TARGET_DIR override. Builds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 17:16:39 +02:00
Meta Vens
9c954e7502 Bifrost pump: one verb, one path; refuse anything else with a failed receipt
With discord-dm gone the verb set, listCommandRecords filter, resolveBridgeVerb
throw and the verb variable decided nothing, and the filter hid non-discord-post
commands so they stayed pending with no receipt. Delete all of it: a command
whose verb is not discord-post now reaches processOneCommand and is refused
with a failed receipt (error names no input value). Payload staging moves
inside the try so a refusal cannot escape as a thrown error.

Adds tests/cultmesh-bridge-commands.test.mjs: discord-dm refused with failed
receipt and non-ok result, canary-free error, discord-post still reaches the
actuator.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 16:59:51 +02:00
Meta Vens
4619939e64 Retire publish-idunn-alarm and the pump's discord-dm verb
Delivery-crossing ruling: Bifrost reads Idunn's incident projection with its
own delivery journal, so Idunn no longer publishes alarms into Bifrost.

- Delete tools/operator-notification.mjs (259 lines); its only callers were
  gamecult-ops notify-idunn-operator-alarm scripts.
- cultmesh-bridge-commands.mjs: drop discord-dm from supportedBridgeVerbs and
  the recipient-id arm; --channel-id is always passed.
- bifrost-bridge.mjs discord-dm is unchanged (docs/bridge.md, BridgeCliTests).

Builds: yes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 16:13:43 +02:00
Meta Vens
42728a677a Remove unused postBridgeJson/postDispatchRunJson and their ensureTrailingSlash copies
REC-099: `postBridgeJson` (tools/bifrost-bridge.mjs) and
`postDispatchRunJson` (tools/dispatch-agent-requests.mjs) each occur
exactly once in their file: their own definition, with no caller.
Each was the sole caller of that file's private `ensureTrailingSlash`
copy, which becomes dead in the same cut. The `ensureTrailingSlash`
copies in agent-transport.mjs and governance-threads.mjs are separate,
still-called definitions and were left untouched.

Kill attempts that failed: none.
- `rg -n "postBridgeJson" tools/*.mjs` -> only its own (now removed)
  definition.
- `rg -n "postDispatchRunJson" tools/*.mjs` -> only its own (now
  removed) definition.
- `rg -n "ensureTrailingSlash" tools/bifrost-bridge.mjs` and the same
  against tools/dispatch-agent-requests.mjs -> confirmed each file's
  copy had exactly one caller (the function just removed) before
  cutting it.

Checks run: `node --check tools/bifrost-bridge.mjs` and
`node --check tools/dispatch-agent-requests.mjs` -> both syntax OK. No
package.json/test suite exists for these standalone tool scripts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 14:41:21 +02:00
Meta Vens
26a40f4943 Read the established DISCORD_OWNER_ID rather than a new spelling
The rewrite invented IDUNN_OPERATOR_DISCORD_ID, BIFROST_OPERATOR_DISCORD_ID,
and OWNER_DISCORD_ID for a value VoidBot config already names DISCORD_OWNER_ID.
That is the same parallel-vocabulary mistake this tool was just repaired for,
in a smaller place. Read DISCORD_OWNER_ID first and keep one alias.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 04:06:24 +02:00
Meta Vens
c063f27014 Route Idunn operator alarms through the Bifrost Discord gate
operator-notification.mjs invented gamecult.operator_dm_request.v1 and
hand-rolled its document definitions, while the gate's own vocabulary and an
exported definition helper sat in the same tools/ directory. Nothing consumed
the invented type, so a published alarm sat pending forever with no receipt and
no way to notice. Two command vocabularies for one crossing, one of them
unreadable.

Publish bifrost.bridge.discord_post_command.v1 instead, via
discordPostCommandDefinition, as a discord-dm command carrying the operator
recipient and the alarm text. gamecult.operator_dm_request is gone, along with
the four unrelated Eve/VoidBot document definitions the publisher registered to
open somebody else's store. The default store is now the gate's own
.bifrost/provider-store.cc, which is what the bridge command pump reads.
Per AGENTS.md, Bifrost owns the Discord crossing; this tool only asks.

The bridge command route was discord-post only, so teach it the verb the
command document already carries:

- resolveBridgeVerb accepts discord-post and discord-dm and rejects anything
  else before the command is marked running;
- discord-dm passes --recipient-id, discord-post keeps --channel-id, and the
  persona/reply options stay post-only;
- listCommandRecords selects on the supported verb set instead of hardcoding
  discord-post, so a discord-dm command is actually picked up;
- receipts record the verb that ran rather than a literal "discord-post".

Two changes exist because of how this bug hid rather than what it was:

- --dry-run now builds the runtime and definitions before returning. The old
  dry run skipped CultMesh entirely, which is why a publisher that could never
  publish looked healthy in every check anyone ran.
- A receipt reporting a failed crossing sets a non-zero exit code. Printing
  ok:false and exiting 0 would hand Idunn a success it would act on.

--wait-seconds is opt-in; without it the tool returns at publish and says the
delivery is unconfirmed rather than implying it landed.

Verified end to end against a throwaway store: the command is published, the
pump claims it, dispatches discord-dm, and writes back a typed receipt. With no
bot token present the receipt reads "Set BIFROST_DISCORD_BOT_TOKEN or
DISCORD_BOT_TOKEN before sending a Discord DM" and the publisher exits 1. The
missing credential is now a reported failure instead of silence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 02:49:42 +02:00
Meta Vens
c289c820c6 Stop double-wrapping the operator notification schema
publishIdunnAlarm has never successfully published. Every non-dry run died
with "definition.schema.parse is not a function" inside CultMeshNode.put.

parseObjectDocument already returns a schema-shaped object:

  function parseObjectDocument(label) { return { parse(input) { ... } }; }

but all five document definitions wrapped it a second time:

  schema: { parse: parseObjectDocument("Operator DM request") }

so definition.schema.parse was an object, and CultMesh called it as a
function. Pass the helper's result straight through instead.

This was present in the file's only prior commit, so the alarm path has never
worked. The dry-run branch returns before touching CultMesh, which is why the
publisher looked healthy: --dry-run prints a well-formed request and exits 0
without ever exercising put.

Verified end to end against a throwaway store: exit 0, ok:true, the .cc is
written, and the document reads back as
{"alarm":"verify-fix","svc":"probe-daemon","cmd":"owner.dm.send","status":"pending"}.
CultMesh creates missing store directories, so an absent .voidbot tree is not
a blocker.

Not exercised here: real delivery of the DM by VoidBot's worker, which needs
the operator's say-so rather than a probe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 01:54:21 +02:00
Meta Vens
8b9ab65b3f Delete Epiphany operator bridge 2026-08-22 06:16:39 +02:00
Meta Vens
cb3239aaac persona: receipt Discord bridge commands 2026-08-08 21:26:13 +01:00
Meta Vens
514c2a6cb9 persona: align permit bodies with Rust wire tuples 2026-08-08 21:15:57 +01:00
Meta Vens
573bbd3968 persona: normalize Rust permit byte tuples 2026-08-08 20:55:35 +01:00
Meta Vens
fe845db18e persona: admit first boot of typed delivery stores 2026-08-08 20:39:53 +01:00
Meta Vens
34eb1e59e6 persona: split receipt and permit requester identities 2026-08-08 20:22:22 +01:00
Meta Vens
dd79b8b991 persona: separate delivery listener daemon 2026-08-08 20:17:22 +01:00
Meta Vens
b8deaa9a8c persona: add typed cross-host delivery nerve 2026-08-08 20:10:23 +01:00
Meta Vens
da716c95f2 Prove signed health transport identity 2026-07-22 02:53:44 +01:00
Meta Vens
da537ad8dc Use canonical daemon health publisher role 2026-07-22 02:49:25 +01:00
Meta Vens
a7647a1660 Follow portable CultNet provider signature bytes 2026-07-22 02:15:35 +01:00
Meta Vens
d74b61f4b7 Encode provider health timestamps as canonical u64 2026-07-22 02:07:17 +01:00
Meta Vens
8b115b08b4 Encode provider health signatures canonically 2026-07-22 02:01:07 +01:00
Meta Vens
5f519cb5ca Keep Discord phase out of provider health 2026-07-22 00:59:30 +01:00
Meta Vens
548dd9397f Own native Persona Discord transport 2026-07-22 00:34:04 +01:00
Meta Vens
0010f7da29 Add exact Epiphany Status v2 transport 2026-07-19 22:54:44 +01:00
Meta Vens
671168fefc Sign Persona feedback provider health 2026-07-19 21:41:50 +01:00
Meta Vens
6491f449cf Unify Epiphany operator request v1 2026-07-19 18:43:49 +01:00
Meta Vens
3824d42b6d Add typed operator identity enrollment 2026-07-19 15:18:13 +01:00
Meta Vens
dabf7aa320 Align Epiphany operator bridge v1 2026-07-19 15:09:26 +01:00
Meta Vens
e0e612c69c Complete authenticated Epiphany operator bridge 2026-07-19 13:12:04 +01:00
Meta Vens
82dd058d42 Add signed Epiphany operator admission boundary 2026-07-19 12:00:50 +01:00
Meta Vens
c65cbfa527 Gate organizational Persona feedback 2026-07-18 20:32:10 +01:00
Meta Vens
3bd250f811 Gate deployments with typed release authority 2026-07-16 18:28:13 +01:00
Meta Vens
03c6f353a8 Load CultLib entrypoints without workspace links 2026-07-15 22:20:03 +01:00
Meta Vens
6104f6c514 Load typed transport from packaged CultLib 2026-07-15 22:18:59 +01:00
Meta Vens
cde3a78874 Move public crossings onto typed Bifrost transport 2026-07-15 22:16:07 +01:00
Meta Vens
2a382fb4a8 Publish Bifrost provider respect to Odin 2026-06-23 23:09:16 +01:00