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>
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>
Eureka idunn-watchdog cut bifrost-retire-alarm, attempts 1-2. Soul verdict
idunn-watchdog:verdict:cut-bifrost-retire-alarm.s2 holds; the pump refuses
any verb but discord-post with a failed receipt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two fixture tests forced a workstation-only target dir
(C:\Users\Meta\.cargo-target-codex); the child now inherits process.env.
Follow-up to 868df354, which missed this deletion. Builds.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
Claude Code reads CLAUDE.md, not AGENTS.md, so this repo's doctrine was
invisible to it. This one-line import makes the existing AGENTS.md load
without duplicating or renaming it. Codex is unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>