Menu

#102 feat(cost): CostObserver plugin seam (CB.2)

closed
nobody
None
2026-07-30
2026-07-14
Anonymous
No

Originally created by: QY-25123

Pull Request

Slice

  • Repo: doberman-core
  • Feature / Slice: CB.2 — CostObserver plugin seam
  • Plan reference: doberman_implementation_plan.md

What this PR does

This PR adds the CostObserver plugin seam to the existing CB.1 cost meter.

After a CostEvent is successfully written to the local cost_events ledger, Doberman discovers and notifies plugins registered under the doberman.cost_observers entry-point group.

The implementation follows the existing DriftObserver and AuditSink plugin patterns:

  • adds a CostObserver protocol with on_cost(event)
  • adds structural validation for observer-shaped plugins
  • adds discover_cost_observers() to the plugin registry
  • notifies observers only after a successful database commit
  • logs and skips invalid or raising observers
  • keeps observer execution outside the authorization decision path

A misbehaving observer cannot:

  • prevent a cost event from being written
  • raise into the execution path
  • change a PASS, AUTH, or BLOCK verdict
  • affect the existing cost-meter behavior when no plugin is installed

Files changed:

  • src/doberman/storage/cost.py
  • adds CostObserver
  • adds _looks_like_cost_observer
  • adds notify_cost_observers
  • wires observer notification into record_cost_event

  • src/doberman/engine/registry.py

  • adds COST_OBSERVER_GROUP = "doberman.cost_observers"
  • adds discover_cost_observers()

  • tests/unit/test_cost_observer.py

  • adds unit tests for observer discovery, fan-out, isolation, and cost-meter integration

  • README.md

  • updates the cost-observability roadmap for CB.2
  • keeps CB.3, the raise-only loop-anomaly detector, as the next slice

Tests added (run in CI)

  • tests/unit/test_cost_observer.py
  • verifies the CostObserver protocol shape
  • verifies _looks_like_cost_observer with valid, missing, and non-callable on_cost attributes
  • verifies observer fan-out
  • verifies per-observer event-copy behavior
  • verifies invalid observers are skipped
  • verifies a raising observer does not stop remaining observers
  • verifies no-op behavior when no observers are installed
  • verifies discover_cost_observers() returns [] when no plugin is registered
  • verifies record_cost_event() notifies observers after a successful ledger write
  • verifies observer failures do not prevent the cost row from being written
  • verifies record_cost_event() does not raise when an observer fails
  • verifies observers are not called when the database write fails

  • Existing tests/unit/test_cost_meter.py tests were also run to confirm that CB.1 behavior remains unchanged.

Local checks run:

ruff check .
ruff format --check .
lint-imports
pytest tests/unit/test_cost_observer.py tests/unit/test_cost_meter.py -v
pytest --cov=doberman --cov-report=term-missing -q

Public-release safety (doberman-core only)

  • [x] Contains nothing from the "not allowed" list: no enterprise/hosted code, no proprietary detection, no customer data, no secrets, no commercial-license code
  • [x] Core still builds/tests/runs with NO enterprise package installed

Security checklist

  • [x] Fails closed on error / uncertainty
  • [x] No secret, full file, or unredacted prompt logged or committed
  • [x] Any guardrail/learning change is raise-only (no silent loosening)
  • [x] Every BLOCK/AUTH carries reason codes + a human explanation
  • [x] doberman-core does not import doberman_enterprise

Edge cases covered / Deviations from plan / Risks introduced

  • Observers are notified only after a successful database commit.
  • Observers are not called when the cost-event write fails.
  • No installed observers is treated as a normal no-op.
  • A malformed plugin without a callable on_cost method is logged and skipped.
  • A raising observer is isolated and does not stop remaining observers.
  • A raising observer cannot prevent the ledger write or raise into the execution path.
  • No new dependencies were added.
  • No database schema changes were made.
  • CB.3 loop-anomaly detection remains out of scope.
  • No deviations from the implementation plan.

The main risk is third-party observer behavior. This is mitigated through defensive discovery, structural validation, exception isolation, and by keeping observers outside the authorization decision path.

AI assistance

AI assistance was used to:

  • understand the existing CostEvent and plugin architecture
  • compare the implementation with the existing DriftObserver and AuditSink patterns
  • help prepare the implementation plan and testing checklist
  • help review and organize this Pull Request description

I reviewed the implementation, fixed test failures, ran the checks, and validated the submitted changes.

Related

Tickets: #143
Tickets: #231

Discussion

  • Anonymous

    Anonymous - 2026-07-14

    Ticket changed by: QY-25123

    • status: open --> closed
     
  • Anonymous

    Anonymous - 2026-07-14

    Ticket changed by: QY-25123

    • status: closed --> open
     
  • Anonymous

    Anonymous - 2026-07-14

    Ticket changed by: QY-25123

    • status: open --> closed
     
  • Anonymous

    Anonymous - 2026-07-14

    Ticket changed by: QY-25123

    • status: closed --> open
     
  • Anonymous

    Anonymous - 2026-07-16

    Originally posted by: fu351

    Thank you — this is a clean, well-isolated plugin seam, consistent with the existing registry/discovery precedent, and I appreciate the thorough tests. One tiny thing before merge: the docstring in storage/cost.py says each observer 'receives its own copy of the event,' but observers actually get the same frozen CostEvent instance (your own test_notify_passes_copy_not_original documents this correctly) — could you tweak that one line of wording? Once that's in and CI has run green I'll merge. Thanks for contributing, and apologies for the slow turnaround!

     
  • Anonymous

    Anonymous - 2026-07-18

    Ticket changed by: fu351

    • status: open --> closed
     
  • Anonymous

    Anonymous - 2026-07-18

    Originally posted by: fu351

    Thank you for the CostObserver seam — you mirrored the DriftObserver/AuditSink pattern almost exactly, and didn't just implement isolation but tested it (a raising/malformed observer is logged & skipped, never blocks the ledger or decision), ~195 test lines. Appreciated the transparency about AI assistance + personal validation. One thing needed fixing before merge: a docstring said each observer 'receives its own copy' when the code passes the same frozen CostEvent — you fixed it fast (cf6fe3a). Solid; hope to see more.

     
  • Anonymous

    Anonymous - 2026-07-30

    Originally posted by: fu351

    The CostObserver plugin seam is clean work — the protocol, the structural validation, discovery, and the isolation guarantee (a raising observer can't touch the ledger write or the verdict) are exactly the shape the other extension seams use. You've now built one seam end-to-end, so [#200] (plugins+docs: complete the doberman.detectors extension seam, level-9) is the same shape applied to a different seam if you want to do it again. Issues are labelled level-1 through level-10 — a comment on one claims it.

     

    Related

    Tickets: #200


Log in to post a comment.