feat(FM.1): ambient activity bus — ActivityEvent, SQLite bus, and the doberman.collectors seam - #512
Open
Maqbool61 wants to merge 1 commit into
Open
feat(FM.1): ambient activity bus — ActivityEvent, SQLite bus, and the doberman.collectors seam#512Maqbool61 wants to merge 1 commit into
Maqbool61 wants to merge 1 commit into
Conversation
…ors seam Closes DobermanCore#236 - Add ActivityEvent to models.py: frozen, extra='forbid', wraps a SecurityObject plus collector id, HMAC entity/session fingerprints, and string class metadata. Redaction is enforced by construction — no field can carry a raw secret. - Add activity_events + monitor_state tables to storage/db.py (schema v14, both CREATE TABLE IF NOT EXISTS — no migration needed). - Add storage/activity.py: emit_activity_event (best-effort, never raises, rejects and counts oversize events), cursor-based read_activity_events (no replay, no loss), purge_activity_events (bounded retention, refuses naive datetimes), save/load_cursor. - Add COLLECTOR_GROUP constant and discover_collectors() to engine/registry.py: doberman.collectors entry-point group, collect() contract enforced structurally, failures isolated like every other plugin group. - Add import-linter contract: policy core must not import doberman.monitor (bus stays off the decision path at import level). - Add 26 tests proving every spec requirement: secret never stored, oversize rejection, cursor resume, purge, stub/raising collector isolation, lint-imports green. 26/26 tests pass. Full suite: 3420 passed, 3 skipped. Coverage 91.51%.
Contributor
Author
|
The Windows job was cancelled by the runner at 40m — no test failures in the log, just a timeout. All three Ubuntu matrix runs (3.11, 3.12, 3.13) passed cleanly. Happy to re-run if needed. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #236
First slice of the ambient monitor: the data plane other agent surfaces
will report through.
What this adds
ActivityEventinmodels.pyFrozen,
extra="forbid", wraps aSecurityObjectplus a collector id,HMAC entity/session fingerprints, and string-valued class metadata
(
target_class,action_class). Redaction is enforced by construction —there is no field a raw command, argument, or secret could ride in.
Local SQLite bus —
storage/db.py+storage/activity.pyTwo additive
CREATE TABLE IF NOT EXISTStables (schema v14, nomigration needed on existing DBs):
activity_events— append-only bus log, one row perActivityEventmonitor_state— per-reader cursor so consumers resume withoutreplaying or losing rows
Three operations in
storage/activity.py:emit_activity_event— best-effort, never raises into the caller;oversize events (> 4 KiB serialized) are rejected and counted in
_oversized_countread_activity_events— cursor-based page read keyed on the integerprimary key, immune to clock skew and concurrent writers
purge_activity_events— bounded retention; refuses naive datetimesso a caller mistake can never silently delete all rows
save_cursor/load_cursorpersist each reader's position inmonitor_state.Plugin seam —
engine/registry.pyCOLLECTOR_GROUP = "doberman.collectors"anddiscover_collectors():loaded defensively via the same entry-point pattern as every other
plugin group. Each collector must expose
collect() -> Iterable[ActivityEvent]; a raising collector is isolated — its eventsare dropped for that tick, but the bus and every other collector
continue unaffected. Returns
[]on a core-only install.Import boundary —
pyproject.tomldoberman.monitorjoins the import-linter forbidden list alongsidedoberman.proxy,doberman.hosthooks,doberman.dash, anddoberman.turngate. The bus is off the decision path at the importlevel as well as the runtime level.
Test proof points (26 tests, all green)
verified at the byte level against the raw DB
extra="forbid"rejects any unknown field (including an accidentalraw_secretkwarg) at construction timeemitnever raiseseven on DB failure
paged reads and a mid-stream write
isolated — good collector still emits
lint-imports: 5/5 contracts keptCI results
26 passed in 1.58s # activity bus suite
3420 passed, 3 skipped # full suite
91.51% coverage # above the 80% threshold
ruff check . ✓
ruff format --check . ✓
lint-imports: 5 kept, 0 broken ✓
Invariant preserved
Nothing on this bus can suppress, lower, or alter an inline decision.
There is no code path from it into
combine(). The import-lintercontract makes this a CI-enforced invariant, not a review-only
convention.