Skip to content

bon-333: W2.3 events transfer — EventBus + 4 consumers#10

Merged
Antawari merged 2 commits into
v0.1from
antawari/bon-333-events-transfer
Apr 18, 2026
Merged

bon-333: W2.3 events transfer — EventBus + 4 consumers#10
Antawari merged 2 commits into
v0.1from
antawari/bon-333-events-transfer

Conversation

@Antawari
Copy link
Copy Markdown
Contributor

Summary

Wave 2.3 of the public v0.1 transfer. Lifts bonfire.events — the typed async publish-subscribe bus that wires every stage of the pipeline. Lands simultaneously with BON-332 (protocols) as the second half of Wave 2's transfer of foundation infrastructure.

EventBus (129 LOC, bus.py)

  • Two subscriber tiers: typed (subscribe(EventType, handler)) + global (subscribe_all)
  • C7 ordering: typed fires before global, regardless of registration order
  • Within each tier: registration order
  • Sequential await (correctness > performance)
  • Consumer isolation: each handler in try/except, errors logged, never re-raised
  • Sequence stamping: monotonic counter, stamped via model_copy(update={"sequence": n})
  • Exact-type filter (type(event) is event_type, NOT isinstance — subclasses don't fire parent handlers)

4 Consumers

  • CostTracker(budget_usd, bus) — subscribes to DispatchCompleted, latched emits of CostBudgetWarning (≥80%) + CostBudgetExceeded (≥100%)
  • DisplayConsumer(callback, persona=None) — 4 typed subscriptions, sync/async detect, swallows callback exceptions
  • SessionLoggerConsumer(persistence) — global subscribe_all, positional append_event(session_id, event), swallows persistence exceptions
  • VaultIngestConsumer(backend, project_name) — 4 typed subscriptions (stage/dispatch/session events), in-file stub (deep semantics deferred to future vault wave)

wire_consumers factory (keyword-only)

wire_consumers(*, bus, persistence, cost_tracker, display_callback, vault_backend) -> None

TDD provenance

Phase Branch SHA Scope
Knight-A (distributed-systems) antawari/bon-333-knight-a e08506a 94 tests — concurrency, ordering violations, consumer isolation under exception chains
Knight-B (fidelity) antawari/bon-333-knight-b 0aa66d2 83 tests — mirror private V1 test_event_bus.py + consumer tests
Sage synthesis antawari/bon-333-sage f15b132 112 canonical tests (42 bus + 70 consumers), 3 structural divergences resolved
Warrior (conservative) antawari/bon-333-warrior 0c72276 448 LOC / 7 files, V1 verbatim modulo adaptations

Three structural divergences — Sage adjudications

1. Snapshot vs live iteration — DROPPED Knight-A's snapshot test

Knight-A asserted "handler registered mid-emit only seen by next emit" (snapshot semantics). V1's bus.py:108 iterates self._typed.get(type(event), []) — a list reference (live). Zero docstring/test evidence of snapshot intent in 616 LOC of V1 tests. Decision: match V1's live iteration. The canonical does NOT assert snapshot behavior.

2. VaultIngestConsumer — surface only (Knight-A direction)

Knight-A: re-export only (V1's vault_ingest.py is 5 LOC). Knight-B: full class with store semantics. V1 source confirms re-export; deep class lives in bonfire.vault.consumer with VaultEntry + content_hash dependencies absent in public v0.1. Decision: surface-only contract (importable, 4 typed subscriptions, register method). Warrior chose Option A (minimal in-file stub).

3. wire_consumers factory — in scope (Knight-B direction)

V1 grep confirms wire_consumers at events/consumers/__init__.py:30-56 with 4 V1 tests. Public v0.1 drops persona + cost_ledger_path kwargs (absent). Decision: lock the 5-kwarg public signature.

Three adaptations from private V1

  1. Private refs scrubbed.
  2. BonfireEvent imports from bonfire.models.events (present post-BON-331).
  3. wire_consumers signature trimmed to public v0.1 surface.

Verification

  • Full suite: 374 passing (262 existing + 112 new) in ≤1.5s
  • Ruff: scoped + full-tree clean on 42 files

Gate tier

Infra (per docs/release-gates.md).

Sage decisions doc: docs/audit/sage-decisions/bon-333-sage-20260418T004958Z.md.

Dual-lens pre-merge review follows.

Closes BON-333.

🤖 Generated with Claude Code

Antawari and others added 2 commits April 17, 2026 20:29
…rs factory

Lift bonfire.events from private V1 into public v0.1. Typed async
publish-subscribe bus (C7 ordering: typed before global, sequential
await, consumer isolation, monotonic sequence stamping via model_copy)
plus 4 consumer implementations + wire_consumers factory. Infra-tier.

Modules:
- events/bus.py — EventBus (129 LOC, V1 verbatim)
- events/__init__.py — re-exports EventBus + BonfireEvent
- events/consumers/cost.py — CostTracker (latched warning/exceeded)
- events/consumers/display.py — DisplayConsumer (sync+async callback)
- events/consumers/logger.py — SessionLoggerConsumer (positional append)
- events/consumers/vault_ingest.py — VaultIngestConsumer (in-file stub)
- events/consumers/__init__.py — wire_consumers(*, bus, persistence,
  cost_tracker, display_callback, vault_backend) factory

TDD provenance (dual-Knight + Sage synthesis):
- Knight-A antawari/bon-333-knight-a @ e08506a: 94 tests, distributed-
  systems adversarial lens (concurrency, consumer isolation, ordering).
- Knight-B antawari/bon-333-knight-b @ 0aa66d2: 83 tests, fidelity lens
  mirroring private V1 test_event_bus.py + consumer tests.
- Sage antawari/bon-333-sage @ f15b132: 112 canonical tests (42 bus +
  70 consumers), resolved 3 structural divergences:
    1. Snapshot vs live iteration: DROPPED Knight-A's snapshot test —
       V1 uses live iteration (self._typed.get(type(event), [])), zero
       docstring evidence of snapshot intent.
    2. VaultIngestConsumer: SURFACE ONLY (Knight-A direction) — V1's
       vault_ingest.py is a 5-LOC re-export; deep vault semantics out
       of W2.3 scope. Warrior chose Option A (in-file stub).
    3. wire_consumers: IN SCOPE (V1 grep confirmed, Knight-B direction)
       — dropped persona + cost_ledger_path kwargs (absent in public).
- Warrior antawari/bon-333-warrior @ 0c72276: 448 LOC across 7 files,
  V1 verbatim modulo three adaptations.

Three adaptations from private V1:
1. Private refs scrubbed (no BON-NNN, no bonfire-beta#113).
2. BonfireEvent imports from bonfire.models.events (public post-BON-331).
3. wire_consumers dropped persona + cost_ledger_path (public v0.1 scope).

Sage decisions doc: docs/audit/sage-decisions/bon-333-sage-20260418T004958Z.md

Verification: 374 tests passing (262 existing + 112 new). Ruff clean.

Closes BON-333.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Antawari
Copy link
Copy Markdown
Contributor Author

Pre-merge dual-lens gate — cleared

Both lenses ran in parallel on dedicated review worktrees. Full convergence: both recommend MERGE.

Reviewer Verdict Findings
Wizard (fidelity/design) APPROVE 0 blocker / 0 high / 0 medium / 0 low / 1 nit / 1 info
superpowers:code-reviewer (engineering) APPROVE 0 critical / 0 important / 2 suggestion / 6 info-confirmations

Invariants confirmed

  • tests_green: 374/374
  • ruff_clean: scoped + full-tree
  • scrub_clean: zero BON-NNN / bonfire-beta / constraint-index.md in src
  • C7 ordering verified: 4 tests (including global-registered-first acid test)
  • Consumer isolation verified: 7 tests; emit never raises; counter advances on handler failure
  • wire_consumers keyword-only: positional invocation raises TypeError
  • VaultIngestConsumer Option A: minimal in-file stub, 4 typed subs, _safe_store swallows backend exceptions, no bonfire.vault.* imports
  • CostTracker threshold latching: 100%-check BEFORE 80%-check — single huge dispatch fires BOTH events
  • bus.py + cost.py byte-identical to V1; display.py/logger.py adapted only for absent protocol types (PersonaProtocol/SessionPersistenceAny)

Follow-ups (non-blocking)

  • W-001 (nit): bus.py:73 docstring cites type(event) is event_type but code uses .get(...) — behavior equivalent, V1-inherited, optional cleanup
  • W-002 (info): CostTracker reentrancy into bus.emit — outer stamp frozen at entry, correctness preserved, worth a future doc note
  • R-001 (code-reviewer): DisplayConsumer retains V1 persona branches though wire_consumers drops the kwarg — dead code via public v0.1 entry point, leave intact for V1 fidelity
  • R-002: CostBudgetWarning.percent can exceed 100 under a huge single dispatch — V1-identical, awkward display string, display-polish wave

Audit trail committed to this PR

  • docs/audit/retrospective/wizard-*.{json,md}
  • docs/audit/retrospective/code-reviewer-*.{json,md}
  • docs/audit/sage-decisions/bon-333-sage-*.md

Gate tier: Infra cleared. Ready to squash-merge into v0.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant