Idunn/docs/watchdog-reporting-postmortem.md
Meta Vens 9a0a6a2ce9 Body map after reincarnation cut 1; cut 1 installed on Yggdrasil
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:36:21 +02:00

11 KiB
Raw Blame History

Watchdog reporting postmortem

Campaign idunn-watchdog, 2026-09-30 to 2026-10-01. This was the first Eureka campaign whose state was typed in Huginn's mind (instance eureka) rather than kept in a prose map. Document ids below are idunn-watchdog:<kind>:<local>. Read them with view; this page does not restate them.

Summary

Idunn is now a watchdog that reaches the operator on Discord.

  • Idunn opens a durable incident at the site where a continuity budget is exhausted (since idunn-reincarnation cut 1 this is a paging threshold only; continuity keeps restarting). The incident is written to incidents.cc, a world-readable file in its projection directory with a private 0600 lock, and closes when the target runs again and its restart window is no longer exhausted.
  • Bifrost's watchdog-notice reader runs from a one-minute timer. It sends one opening DM and at most one closure DM per incident, keeps its own journal, and retries a failed send forever with capped backoff.
  • Scale: 8 cuts across Idunn, Bifrost and gamecult-ops: 13 spec revisions, 17 Hands reports, 17 Soul verdicts and 55 findings. Measured against the in-force estimates:
Repo Estimate (lines) Landed (lines)
Idunn +930/−101 +2618/−354
Bifrost +246/−335 +1182/−429
gamecult-ops +280/−35 +505/−104
  • Installed on Yggdrasil:
    • Idunn a909f93 at 2026-09-30 23:21 UTC, then cb60965 at 2026-10-01 02:02 UTC.
    • The Bifrost reader at 160fac20 with CultLib 36ea08d3, at 2026-10-01 08:04 UTC.
  • First live notice: the incident's first real input was raven-muninn, which exhausted its budget minutes after the incident cut was installed. Its opening DM was delivered at 08:04:41 UTC, and the operator confirmed it.
  • Still owed: the closure DM for that same incident (follow_up:closure-dm-observed). It depends on raven-muninn recovering, and raven-muninn's own fault lies outside this campaign.

Scope and invariants

Target r4. Its invariants are:

  • incident-owner
  • record-before-delivery
  • delivery-owner
  • survival-independent
  • one-per-incident
  • no-sensitive-egress
  • tick-lock-private

The operator directions are ruling:operator-watchdog-direction and ruling:operator-bifrost-bridge. The second made Bifrost the bridge to Discord, so Idunn holds no credential and speaks no Discord API.

Excluded:

  • Odin's store condition and B5 OperatorRequired. Neither can page yet. Odin's O2 cut is unbuilt, and B5 has no operator exit (follow_up:b5-operator-required-no-exit).
  • VoidBot's notify_owner.
  • The Persona Discord crossing.
  • Eve lowering.

The earlier attempt was Bifrost c063f27 (2026-09-04): an alarm was a command written into Bifrost's store. It never ran, because no pump was deployed and the deployed pump lacked the DM verb. It was also reversed by ruling: ruling:delivery-crossing has Bifrost read Idunn's projection, so no write crosses an owner boundary.

Timeline

Cut Reports Soul passes Notable finding
ops-retire-alarm h1 1 none
bifrost-retire-alarm h1–h2 2 silent-skip: a stray discord-dm command sat pending forever
bifrost-suite h1 1 suite-cultcache-scope (pre-existing): 9 of 17 tests failed at base on a CultLib rename
bifrost-notice-reader h1–h3 3 prune-keyed-on-presence, bridge-exit-is-failed: duplicate DMs and blocked closures
bifrost-notice-retry h1–h2 2 future-stamp-stalls-retry
ops-notice-deploy h1–h3 3 retry-window-five-minutes: a 5-minute outage dropped a notice for good
idunn-incident h1–h3 3 lock-stalls-tick (Blocker); lock-acl-world-openable (High)
idunn-topology-lock h1–h2 2 contended-publish-aborts; the live stall it fixed was pre-existing

Structural delta

The landed code was about 2.5× its estimate, and the misses were not explained cut by cut as the skill asks.

  • idunn-incident: +1813 against +700. Its fix batches added the lock primitive, load-independent lock tests, and per-operation fault keys.
  • bifrost-notice-reader: +891, against r3's estimate of +140. That r3 figure described only the revision's own delta, not the cut, so r3 carried the wrong baseline.

Deleted:

  • Bifrost's publish-idunn-alarm and the pump's discord-dm verb.
  • gamecult-ops' dead --operator-alarm-command scripts.
  • Idunn's publish_projection_mode and its 0644-lock test.
  • The reader's prune-on-absence logic and its WATCHDOG_NOTICE_NOW_MS production clock seam.

Added:

  • idunn.operator_incident.v1 (incidents.cc) and bifrost.watchdog_notice_execution.v1 (the journal).
  • bifrost-watchdog-notice.{service,timer}.

Nothing was parked.

What Soul caught

Green suites passed every defect below.

  • Survival and locking.
    • Incident writes took a blocking flock inside Idunn's scheduler tick, on a world-readable lock. Any local uid could freeze continuity for every target, and WatchdogUSec=0 meant nothing would restart Idunn (s1.lock-stalls-tick).
    • After the fix, the live directory's default ACL still made the lock 0644, so any uid could stop incidents opening. The test ran in a temp directory with no ACL (s2.lock-acl-world-openable).
    • The same probe exposed a pre-existing live hazard: topology.cc.lock 0644 with a blocking write in the tick, measured at 5.07 s at base (s2.topology-lock-stalls-tick). The operator ruled it into the campaign; after the topology cut the tick takes 0.09 s.
  • Split authority.
    • The reader pruned its "already sent" journal whenever a key was absent from Idunn's store. Restoring a backup would have re-sent old DMs.
    • The reader decided subject validity with its own grammar, narrower than Idunn's.
    • Retention was decided by two predicates in Idunn.
  • Lifecycle.
    • One running observation closed an exhaustion incident. A release that crashes after each restart would have sent about 12 DMs an hour (s1.flap-reopens).
    • A bridge that died after Discord accepted a message was retried up to 5 times, and the closure was then blocked forever.
    • Five attempts on a one-minute timer dropped a notice after a 5-minute Discord outage.
  • Decorative checks.
    • A compare-exchange expected None on a key that embeds now, so it guarded nothing (s1.cas-decorative).
    • A 2-second wall-clock bound on a lock test flaked under load and counted equivalent mutants as caught.
  • The verifier itself. Soul's s3 found that the stopgap forced one CARGO_TARGET_DIR on every job, so cargo-mutants' parallel workers tested each other's binaries. Two runs of the same two mutants disagreed (s3.mutants-share-target-dir). Every parallel mutation count reported through the stopgap before 2026-10-01 is unproven, in both sessions' work.

Operator corrections

  • "Bifrost is indeed intended to be the bridge to external services like Discord." The agents had found two delivery paths: VoidBot's notify_owner and Bifrost's command store. The ownership boundary was missing from both repos' summaries.
  • "Stewardship is currently universal…" Self had filed a gap treating follow-up repos as needing stewardship. It was withdrawn; the rule was not written in the skill.
  • "…we seem to be saving some tokens… but we're very very slow." Self blamed cold builds, measured, and was wrong. Cold builds took about 51 s and warm builds 29 s. The cost was slot queueing, repeated verify jobs, a test that hung a slot for 39 minutes, and a 4-hour job from the other session holding a fifth of capacity.
  • "Yep, fixing Mind issues is highest priority, immediately once this campaign closes" (ruling:mind-fixes-next).

Incidents

  • Network outages (ENOTFOUND). Two outages killed three agents mid-pass. Each was resumed by SendMessage with context intact, and nothing was lost, because each agent's state was in commits and the mind.
  • A shared stopgap target directory made mutation evidence noise. It is fixed in Eureka a755a60.
  • Self's own brief inflated runtime. It told Hands to mutate serially to dodge a flake, and was corrected to skipping the flaky test by name.
  • A hung test held a verify slot for 39 minutes (s2.route-connect-test-hangs). It is fixed.
  • A Hands test helper overwrote the container's /usr/bin/node through a symlink. It was caught by the agent's own baseline and then confined (s2.test-clobbers-usr-bin-node).
  • A worktree directory stays locked by a finished process (F:\Projects\gamecult-ops-watchdog-deploy, empty). Removing it is left to the operator.

What worked

  • Soul attacks the layer where the rule fails, not the test. Every High/Blocker finding came from a probe the shipped tests could not see: a separate-process lock holder, the live directory's ACL, or a restored store.
  • Point Soul at the load-bearing invariant in the brief. Naming "can the lock hang the tick" produced the Blocker on the first pass.
  • Read before touching live state. Self installed only after checking the installed revision, the in-flight transactions and the unit diff. The repo unit carried a BOM the live one did not.
  • One verify job per pass. Once briefs required it, a reader fix batch took 11 minutes, where the first reader Hands took 38.

What to change in the pipeline

Already changed in the skill:

  • The substrate gaps moved from a table to the mind.
  • Imagination owns the model page.
  • Soul pays for each rerun once.
  • The rust image uses sccache.
  • The stopgap no longer forces CARGO_TARGET_DIR.

Still to change:

  • Explain every ledger miss in the next report's deviations. This campaign ran at about 2.5× its estimate and nobody explained it per cut.
  • Make a revision's estimate the whole cut. r3's estimate covered only its own delta.
  • Verify the merged tree of a long-lived branch before pushing. Self did this once, for the incident cut over B5, and it should be the rule.
  • Briefs name the standard sibling build command. The stopgap cannot provision siblings (follow_up:gap-verify-no-sibling-repos), and one hand-rolled build produced four false failures.

Open follow-ups

Fifty follow-ups are in force. Twenty are substrate gaps (gap-*): the work the operator ruled next. Query them with the substrate-gaps recipe in the skill. Seven findings remain unresolved:

  • cut-idunn-incident.s3.mutants-share-target-dir. It is fixed in the stopgap but awaits a Soul mutation run under the fixed script.
  • Six findings whose resolution keys exceed 64 bytes (follow_up:gap-unresolvable-finding-key). Their substance is recorded in follow_up:notice-retry-hardening, follow_up:ops-refusal-test-hardening, follow_up:bifrost-pump-input-hygiene and follow_up:contended-publish-bounds.

Operational follow-ups that matter most:

  • closure-dm-observed
  • notice-unknown-settle
  • contended-publish-bounds
  • idunn-load-flaky-tests
  • cultcache-rs-lock-mode
  • cultcache-ts-readonly-pull