Files
didactyl/plans/fix_kind30078_agent_config_publish.md

5.6 KiB

Fix: Kind 30078 d=agent_config Publish Gaps

Problem

The agent stores its admin pubkey and DM protocol in a NIP-44 self-encrypted kind 30078 replaceable event (d=agent_config). This event is critical for the --nsec-only startup path (used by systemd services) to recover the admin identity.

Multiple startup paths fail to publish this event, causing:

  • Agents installed via wizard "Install Service" to lose their admin pubkey on restart
  • Agents recovering on new servers to use stale/old admin pubkeys from relays
  • Agents with genesis files to silently ignore newer kind 30078 values

Affected Files

  • src/setup_wizard.c — wizard flows
  • src/main.c — main startup logic

Fixes

Fix A: new_agent_flow — publish before service install

File: src/setup_wizard.c ~line 2184
Problem: When user chooses "Install systemd service" in new agent flow, persist_runtime_config_to_nostr_wizard_online is never called. The service starts with --nsec only and has no kind 30078 to recover from.
Fix: Call persist_runtime_config_to_nostr_wizard_online(cfg) before install_system_service_with_dedicated_user(). Fail the install if publish fails (same pattern as existing_agent_flow).

Fix B: new_agent_flow — publish before "boot now" return

File: src/setup_wizard.c ~line 2177
Problem: When user chooses "Boot now", the wizard returns SETUP_WIZARD_RC_BOOTSTRAP and relies on main() to publish later. This works but is fragile — if the bootstrap publish in main() fails, the kind 30078 is never created.
Fix: Call persist_runtime_config_to_nostr_wizard_online(cfg) before returning 0. This is belt-and-suspenders — main() will also publish, but the wizard ensures it happens at least once. If the wizard publish fails, log a warning but continue (non-fatal since main will retry).

Fix C: existing_agent_flow — always publish before service install

File: src/setup_wizard.c ~line 2368
Problem: persist_runtime_config_to_nostr_wizard_online is only called if (config_changed). If the user accepts the recovered config as-is, the kind 30078 is not re-published. The event may have been lost from relays.
Fix: Remove the config_changed gate. Always call persist_runtime_config_to_nostr_wizard_online(cfg) before installing the service. Kind 30078 is a replaceable event, so re-publishing is safe and idempotent.

Fix D: existing_agent_flow — always return BOOTSTRAP

File: src/setup_wizard.c ~line 2364
Problem: When user chooses "Boot now" and config_changed == 0, the flow returns SETUP_WIZARD_RC_EXISTING which means bootstrap_mode = 0 in main(). If first_run = 0 (kind 10002 exists), persist_runtime_config_to_nostr is skipped.
Fix: Always return SETUP_WIZARD_RC_BOOTSTRAP from the "boot now" path, regardless of config_changed. This ensures main() always re-publishes the kind 30078. The wizard has all the config in memory — it should always be treated as authoritative.

Fix E: persist_runtime_config_to_nostr_wizard_online — ensure relay delivery

File: src/setup_wizard.c ~line 1159
Problem: The function publishes the event and immediately calls nostr_handler_cleanup(). The event may not have been delivered to relays yet (fire-and-forget).
Fix: Add a brief poll loop (e.g., 2-3 seconds of nostr_handler_poll()) after the publish call and before nostr_handler_cleanup() to give the event time to propagate. This matches the pattern used elsewhere for relay operations.

Fix F: main() — always fetch kind 30078 and compare

File: src/main.c ~line 847 (recover_missing_runtime_config_from_nostr)
Problem: The recovery only runs when admin_config_is_complete() returns false. If a genesis file provides an admin pubkey, the kind 30078 is never checked, even if it has a different (newer) value.
Fix: Always fetch the kind 30078 d=agent_config regardless of whether the genesis already provided values. Compare the fetched admin_pubkey with the loaded one. If they differ, log a DEBUG_WARN with both values. Genesis wins (operator intent), but the warning makes the conflict visible in logs.

Fix G: main() — always re-publish kind 30078

File: src/main.c ~line 1152
Problem: persist_runtime_config_to_nostr only runs when bootstrap_mode || first_run. On subsequent runs, the kind 30078 is never refreshed. If relays purge the event, it's gone.
Fix: Move persist_runtime_config_to_nostr(&cfg) outside the if (bootstrap_mode || first_run) block so it runs on every startup. Kind 30078 is a replaceable event — re-publishing is safe, idempotent, and ensures relay persistence. Log the result but don't fail startup if it doesn't succeed.

Execution Order

  1. Fix E first (relay delivery) — this makes all other wizard publishes more reliable
  2. Fixes A + B (new_agent_flow) — the most critical bug
  3. Fixes C + D (existing_agent_flow) — second most critical
  4. Fix G (always re-publish in main) — ensures long-term relay persistence
  5. Fix F (conflict detection) — nice-to-have logging improvement

Testing

  • Wizard → New Agent → Install Service: verify kind 30078 exists on relays after service starts
  • Wizard → New Agent → Boot: verify kind 30078 exists on relays
  • Wizard → Existing Agent → Install Service (no changes): verify kind 30078 re-published
  • Wizard → Existing Agent → Boot (no changes): verify kind 30078 re-published
  • --nsec only restart: verify kind 30078 recovered and re-published
  • --config genesis.jsonc with different admin than kind 30078: verify warning logged, genesis wins, kind 30078 updated