The snapshot is regenerated; EF also reordered two pre-existing properties
(BridgeAction.TargetSurface, one ProjectId) into its canonical order.
Migration discovery now lists thirteen ids.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ConceptAppraisalService is the only appraisal writer; tallies are derived from
rows. VoterIdentity.For decides voter key and class; NetworkPrefix is the only
prefix computation (HMAC tag). NetworkTagSecret is required in production.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
concept-mill-votes:cut_report:cut-1.h1 and h2, held by Soul
(concept-mill-votes:verdict:cut-1.s2). Production refuses to start without
DataProtection:KeysPath; the deploy (Cut 0b) must set it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UseForwardedHeaders runs first with KnownProxies exactly loopback and
ForwardLimit 1; Data Protection always uses application name "Bifrost";
the startup validator refuses production without DataProtection:KeysPath.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Concept Mill votes Cut 0a (concept-mill-votes:cut_report:cut-0a.h1), held by
Soul (concept-mill-votes:verdict:cut-0a.s1). daedcc4, the live release, is now
an ancestor of main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imagination pass that admitted cut specs 0a through 7. Facts re-probed at
Bifrost bf83a93 and on the host; the from-memory prior-art list is replaced
by a citation of docs/research/anonymous-vote-stuffing-prior-art.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Served concept-mill-votes Cut 4 (anti-stuffing defence). Facts with source
pointers on PoW captchas, device cookies, network buckets, ePrivacy status of
anti-abuse cookies, and after-the-fact purge at HN, Stack Exchange and Reddit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Imagination map of 2026-10-08, committed as the target document of the
concept-mill-votes campaign. Typed invariants, rulings and cut specs live in
the eureka mind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eureka idunn-watchdog cut bifrost-notice-retry, attempts 1-2. Soul verdict
idunn-watchdog:verdict:cut-bifrost-notice-retry.s2 holds. Retries with the
same nonce after 1,2,4..32 then every 60 minutes; the unit fails from the
third attempt while retrying (ruling notice-retry-policy).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The finally-sends test expected six attempts on all three journal rows, but the
closure entry is first tried in the run that completes its opening, so it has
one. Test-only fix; the previous commit's test failed on this line.
The previous commit (69e12e3867) builds; its
suite has one failing test, fixed here.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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>
Eureka idunn-watchdog cut bifrost-notice-reader, attempts 1-3 (spec r3).
Soul verdict idunn-watchdog:verdict:cut-bifrost-notice-reader.s3 holds.
One opening and at most one closure DM per incident, own journal, nonce
with enforce_nonce, unknown never re-sent. Requires CultLib >= 8cb3b728.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
The egress test searched for the substring CANARY, which the recipient double
also carries, so it failed on its own input.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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>