Skip to content

feat(forensics): support epoch backfill lifecycle rows - #339

Open
agent-p1p wants to merge 2 commits into
masterfrom
pip/goggles-338
Open

feat(forensics): support epoch backfill lifecycle rows#339
agent-p1p wants to merge 2 commits into
masterfrom
pip/goggles-338

Conversation

@agent-p1p

@agent-p1p agent-p1p commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • sync Goggles' v2 audit schema byte-for-byte with MDK PR #1386 head 773402d3
  • add a five-row epoch-stall backfill lifecycle JSONL fixture covering armed, deferred, started, failed, and completed evidence
  • prove ingestion and agent-state export preserve every lifecycle kind field plus opaque context.operation_id, while account-scoped rows remain free of a fabricated top-level group_ref
  • keep lifecycle aggregate fields in raw kind/context evidence rather than adding unused normalized columns

Verification

  • just check — 256 tests passed; Django check, Ruff lint/format, and migration drift passed
  • just audit-dependencies — no known vulnerabilities
  • python manage.py validate_audit_schema fixtures/*.jsonl — 39 events across 4 fixtures passed
  • just ci — frozen sync and all 256 SQLite tests passed; local Postgres leg could not start because this worker has no access to /var/run/docker.sock (GitHub CI will run it)

Fixes #338

Summary by CodeRabbit

  • New Features

    • Expanded audit event coverage for relay registration, subscription rebuilds, sync draining, and epoch-stall backfill lifecycles.
    • Added detailed recipient and signature failure outcomes.
    • Added retry and processing-duration metrics to message state changes.
    • Added events for escalation and discarded convergence passes.
  • Documentation

    • Updated the audit log glossary with the new failure outcomes.
  • Tests

    • Added coverage for complete epoch-stall backfill lifecycles and preserved operational context.

Sync the canonical MDK v2 audit schema and cover all five epoch-stall backfill lifecycle rows through ingestion and agent-state export.

Fixes #338
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bb25ce2b-e293-43fb-a959-1e37f53f77d1

📥 Commits

Reviewing files that changed from the base of the PR and between dcb66b7 and fb54c47.

📒 Files selected for processing (3)
  • docs/audit-log-glossary.md
  • forensics/ingest.py
  • forensics/tests.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • forensics/tests.py

Walkthrough

The audit schema adds operational and epoch-stall backfill events. Fixtures and tests validate peeler outcomes and lifecycle ingestion, group association, field preservation, export shape, and operation ID normalization.

Changes

Audit lifecycle support

Layer / File(s) Summary
Shared audit definitions and operational events
docs/schemas/audit-log-event.v2.schema.json, forensics/ingest.py, forensics/tests.py, docs/audit-log-glossary.md
The schema adds relay registration, peeler outcomes, message retry and residence fields, subscription_rebuild, and sync_drain. Ingest validation, glossary text, and tests cover the new peeler outcomes.
Epoch-stall backfill event contract
docs/schemas/audit-log-event.v2.schema.json
The schema defines arming, start, completion, failure, deferral, escalation, and discarded convergence events with lifecycle metadata.
Lifecycle fixture ingestion and export
fixtures/epoch-stall-backfill-lifecycle.jsonl, forensics/tests.py
Tests validate five lifecycle events, fallback group association, preserved fields, export shape, and normalized operation IDs.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds all five lifecycle kinds, validates ingestion, preserves required fields during export, and maintains privacy and compatibility requirements [#338].
Out of Scope Changes check ✅ Passed The additional schema, ingestion, glossary, fixture, and regression-test changes support exact schema synchronization and the linked issue requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: adding support for epoch backfill lifecycle rows in forensics.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pip/goggles-338

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
forensics/tests.py (2)

5037-5046: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Assert the exported key set to cover the "no additional identifying data" objective.

The loop checks four named keys. It does not fail if the export adds a key such as an account pubkey or a device label. Comparing the full key set turns that objective into an enforced assertion.

🛡️ Proposed addition
         for event_type, expected in expected_by_type.items():
             with self.subTest(event_type=event_type):
                 exported = exported_by_type[event_type]
+                self.assertEqual(
+                    set(exported),
+                    {"event_type", "context", "kind", "group_ref", "normalized"},
+                )
                 self.assertEqual(exported["context"], expected["context"])

Adjust the expected key set to the full documented export contract if it contains more fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@forensics/tests.py` around lines 5037 - 5046, Update the assertions in the
expected_by_type iteration to compare the complete set of keys in each exported
record against the documented export contract, rejecting unexpected identifying
fields while including every legitimate documented field. Keep the existing
value assertions for context, kind, group_ref, and normalized.

4983-4983: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Anchor the fixture path to the repository root.

If the test runner starts outside the repository root, Path("fixtures/...") cannot find the committed fixture. Use Path(__file__).resolve().parent.parent / "fixtures" / "epoch-stall-backfill-lifecycle.jsonl".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@forensics/tests.py` at line 4983, Update the fixture_path assignment in the
test to resolve the fixture from the repository root using __file__. Preserve
the existing epoch-stall-backfill-lifecycle.jsonl fixture while constructing the
path through the parent directories and the fixtures directory instead of the
current working directory.
docs/schemas/audit-log-event.v2.schema.json (1)

1704-1786: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the shared completed/failed payload into a $def.

The epoch_stall_backfill_completed and epoch_stall_backfill_failed branches declare the same eight required fields and the same property set. Only the type const and the extra error_kind differ. A shared $def keeps the two branches from drifting when the producer adds a field.

♻️ Sketch of the shared definition
+    "epochBackfillOutcomeFields": {
+      "retry_ordinal": { "$ref": "`#/`$defs/u64" },
+      "duration_ms": { "$ref": "`#/`$defs/u64" },
+      "activation_outcome": { "$ref": "`#/`$defs/epochBackfillActivationOutcome" },
+      "deliveries": { "$ref": "`#/`$defs/u64" },
+      "local_epoch_before": { "$ref": "`#/`$defs/u64" },
+      "local_epoch_after": { "$ref": "`#/`$defs/u64" },
+      "group_advanced": { "type": "boolean" }
+    },

Then reference it from both branches with "$ref": "#/$defs/epochBackfillOutcomeFields" inside properties, keeping the per-branch type const and error_kind.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/schemas/audit-log-event.v2.schema.json` around lines 1704 - 1786,
Extract the shared fields and property definitions from the
epoch_stall_backfill_completed and epoch_stall_backfill_failed schema branches
into a new $defs.epochBackfillOutcomeFields definition. Replace the duplicated
fields in each branch with a reference to that definition inside properties,
while retaining each branch’s type const and the failed branch’s error_kind
property.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/schemas/audit-log-event.v2.schema.json`:
- Around line 695-705: Update the ingestion outcome allowlist in
forensics/ingest.py to accept the schema-defined invalid_signature and
wrong_recipient values, then add both outcomes to the corresponding glossary and
ingestion tests. Preserve the existing generic non-success error-tone behavior.

---

Nitpick comments:
In `@docs/schemas/audit-log-event.v2.schema.json`:
- Around line 1704-1786: Extract the shared fields and property definitions from
the epoch_stall_backfill_completed and epoch_stall_backfill_failed schema
branches into a new $defs.epochBackfillOutcomeFields definition. Replace the
duplicated fields in each branch with a reference to that definition inside
properties, while retaining each branch’s type const and the failed branch’s
error_kind property.

In `@forensics/tests.py`:
- Around line 5037-5046: Update the assertions in the expected_by_type iteration
to compare the complete set of keys in each exported record against the
documented export contract, rejecting unexpected identifying fields while
including every legitimate documented field. Keep the existing value assertions
for context, kind, group_ref, and normalized.
- Line 4983: Update the fixture_path assignment in the test to resolve the
fixture from the repository root using __file__. Preserve the existing
epoch-stall-backfill-lifecycle.jsonl fixture while constructing the path through
the parent directories and the fixtures directory instead of the current working
directory.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e2f7eecd-d600-474a-a38d-c44e6d2f888d

📥 Commits

Reviewing files that changed from the base of the PR and between 480857b and dcb66b7.

📒 Files selected for processing (3)
  • docs/schemas/audit-log-event.v2.schema.json
  • fixtures/epoch-stall-backfill-lifecycle.jsonl
  • forensics/tests.py

Comment thread docs/schemas/audit-log-event.v2.schema.json
@agent-p1p

Copy link
Copy Markdown
Contributor Author

Adversarial review — changes required

Reviewed exact head dcb66b74610111a39a8cb6cf1e6aaccb30c7e962.

Blocking (1)

Canonical invalid_signature and wrong_recipient peeler outcomes are accepted by the synced schema but rejected by actual ingestion.

The copied schema adds both values to $defs.peelerOutcomeKind (docs/schemas/audit-log-event.v2.schema.json:695-705), but normalize_kind() still uses the old hard-coded allowlist (forensics/ingest.py:765-772). I independently reproduced this with two otherwise-valid v2 peeler_outcome rows:

manage.py validate_audit_schema
Schema validation passed for 2 event(s) across 1 file(s).

 ingest_audit_log_bytes
file_status=invalid, valid=0, invalid=2
invalid_signature -> outcome must be a known peeler outcome
wrong_recipient   -> outcome must be a known peeler outcome

Impact: this PR makes Goggles' canonical validator advertise support for producer evidence that the upload path then quarantines and excludes from valid-event exports. This also violates the issue's existing-v2 compatibility requirement because the byte-for-byte schema sync necessarily brings these producer outcomes with it.

Required fix: extend the ingestion allowlist to match the schema, add regression coverage proving both rows remain AuditFile.STATUS_VALID / AuditEvent.STATUS_VALID, and update the peeler-outcome glossary entry that still enumerates the old values. Keep the generic non-success error tone; it already handles both outcomes correctly. CodeRabbit independently reported the same integration defect at #339 (comment).

Verified

  • Goggles schema is byte-for-byte identical to live MDK PR #1386 head 773402d3; SHA-256 8b082e70a41614ac72b6f61f96a912e95af2f8c24cd326a25081e80a07ebba54.
  • All five requested lifecycle rows validate and ingest; focused lifecycle tests pass 2/2.
  • Export equality checks preserve each row's complete kind, complete context, applicable top-level group_ref, and normalized opaque context_operation_id.
  • Account-scoped started/deferred rows retain an empty exported group_ref; no fabricated wire identity is introduced.
  • No model or migration changes were added.
  • just check: 256 tests passed; Django check, Ruff lint/format, and migration drift passed.
  • just audit-dependencies: no known vulnerabilities.
  • GitHub CI, including Postgres, is green. Local just ci repeated all 256 SQLite tests successfully and stopped only at the unavailable Docker socket.

No additional suggestions or nitpicks from this pass.

Align ingestion and documentation with the synced v2 schema, and add regression coverage for both newly supported outcomes and the lifecycle export contract.
@agent-p1p

Copy link
Copy Markdown
Contributor Author

Addressed review feedback in fb54c47: fixed the blocking peeler-outcome ingest mismatch, added valid-ingest regression coverage, updated the glossary, anchored the lifecycle fixture path to the repository, and asserted the complete documented export key set. The suggested schema deduplication was intentionally not applied: this file must remain byte-for-byte identical to the MDK producer schema, and it still matches producer head 773402d3 (SHA-256 8b082e70a41614ac72b6f61f96a912e95af2f8c24cd326a25081e80a07ebba54). Local just check passes 257 tests plus Django/Ruff/migration gates; dependency audit reports no known vulnerabilities. @coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== PR changed files =='
git diff --name-status HEAD^ HEAD

printf '%s\n' '== Relevant symbol map =='
ast-grep outline forensics/ingest.py --items all --match 'normalize_kind|peeler' || true
ast-grep outline forensics/tests.py --items all --match 'Peeler|EpochStallBackfillLifecycleTests' || true

printf '%s\n' '== Ingestion and tests =='
rg -n -C 8 'invalid_signature|wrong_recipient|peeler_outcome|normalize_kind' \
  forensics/ingest.py forensics/tests.py

printf '%s\n' '== Glossary references =='
rg -n -i -C 5 'peeler outcome|invalid_signature|wrong_recipient' \
  docs README.md . -g '!forensics/ingest.py' -g '!forensics/tests.py' \
  -g '!docs/schemas/audit-log-event.v2.schema.json' || true

printf '%s\n' '== Focused diff =='
git diff HEAD^ HEAD -- forensics/ingest.py forensics/tests.py docs README.md

Length of output: 19098


@agent-p1p The blocking peeler-outcome mismatch is addressed.

normalize_kind() now accepts invalid_signature and wrong_recipient. The regression test verifies valid file status, valid event status, zero invalid events, and preserved outcomes for both values. The glossary now lists both outcomes.

The repository-anchored fixture path and exact export-key assertion are also present. The schema deduplication should remain excluded because byte-for-byte producer-schema synchronization is a stated requirement.

I did not execute the test suite in this review environment. Static inspection found no remaining issue in this fix.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@agent-p1p
agent-p1p marked this pull request as ready for review August 11, 2026 23:46
@agent-p1p

Copy link
Copy Markdown
Contributor Author

Adversarial re-review — APPROVE / LGTM

Reviewed exact final head fb54c47ff5b11dc91d19842b6cbe9d2d41423175.

The prior blocking schema/ingest mismatch is resolved:

  • normalize_kind() now accepts both canonical invalid_signature and wrong_recipient outcomes. The focused regression passed and proves one valid AuditFile, two valid AuditEvent rows, zero invalid rows, preserved outcomes, and empty validation errors.
  • The Goggles schema remains byte-for-byte identical to live MDK PR #1386 head 773402d3 (SHA-256 8b082e70a41614ac72b6f61f96a912e95af2f8c24cd326a25081e80a07ebba54).
  • The glossary outcome list exactly matches the canonical schema enum.
  • Lifecycle regression coverage still validates all five rows and pins the complete agent-export top-level key set, complete raw context/kind, applicable group_ref, and only the intended normalized context_operation_id. Both lifecycle tests also pass when launched outside the repository working directory.
  • just check: 257 tests passed; Django checks, Ruff lint/format, and migration drift passed.
  • just audit-dependencies: no known vulnerabilities.
  • Live GitHub CI and CodeRabbit are green; the sole review thread is resolved.

Blocking findings: 0. No new blocker, sensitive path, suggestion, or nitpick found in the final diff.

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.

Support MDK epoch-stall backfill lifecycle audit rows

1 participant