Commit eb34082
Daily platform report with day-over-day finding diffs (#8)
* feat: daily platform report with day-over-day finding diffs
Closes #6.
The platform was accumulating a narrative and discarding it. ops.monitor_results
has carried run_at since Task 18 and nothing ever read it back. Worse, the one
artifact that looked like a history was destroying one: incident slugs are
deliberately stable so a recurring finding rewrites one file rather than
spawning many, which means write_incident OVERWRITES and yesterday is gone.
Nothing could answer "when did this start?" or "did yesterday's problem clear?"
Two append-only Iceberg tables:
ops.finding_log every finding on every run, keyed on sensors.finding_key
ops.agent_runs one row per run, findings or not
The second exists because a clean run writes no finding rows, so without it
"ran and found nothing" and "never ran" are the same empty table -- and a report
would render the second as a clean bill of health. Same empty-delta blind spot
this codebase has now closed four times (quarantine_rate SELECT 0.0, range on an
all-NULL column, freshness on no values, an enum watch on an empty column).
src/ops/report.py assembles seven sections. build_report() takes a dataclass and
returns a string: it is handed no engine, so it CANNOT compute a metric even by
accident, which is what makes "the report recomputes nothing" a property rather
than a comment. A test asserts the module never references monitors.evaluate,
sensors.detect or validator.validate. A report that recomputes can disagree with
the monitor that raised the alert, and the report is the one people believe.
Absences are reported as absences: no run recorded says so instead of rendering
clean; no monitor results is "an absence of evidence, not a pass"; no previous
run says "nothing to diff against" instead of calling every standing finding new;
a monitor with no prior value prints an em dash, never 0. An unknown finding kind
routes to Errors rather than vanishing.
drift-demo --day N applies one scheduled change instead of all four, so the agent
can run between them and the log accumulates a real timeline. Bare drift-demo is
unchanged. reports/ holds a four-day sequence from one continuous history: clean,
+column, rename, then the rest plus a volume collapse -- 0, 1, 2, 8 findings.
Fixed a latent bug found on the way in: incident_slug() hashed
table|kind|column|change with no monitor name, so EVERY monitor_breach on a table
hashed identically. With one breach firing that was invisible; with two the
second incident file silently overwrote the first, and this feature's diff would
have reported a finding as "cleared" while it was still breaching. Extracted
sensors.finding_key() as the single definition of finding identity, shared by the
incident filename and the log row -- they must agree or "first seen" describes
something other than the file on disk. Incident filenames also stopped rendering
"None" for breaches carrying no column. The committed incident slugs change
accordingly; this is a one-time migration.
Also fixed: the new tests reached graph.run()'s act node and wrote into the
repo's real docs/incidents/. test_agent_graph and test_bronze already guard
against this; test_report now uses the same autouse fixture. The suite no longer
dirties the committed artifact set.
Reviewed: 275 tests pass (250 before), ruff clean, four-day sequence re-run end
to end at full scale and every committed report and incident regenerated from it.
Day four runs one day past the end of the data on purpose, which is why three
arrival SLAs breach; stated in the README rather than left to look accidental.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0199ePw5w34FyfGAB41mkv3G
* fix: review round 1 -- re-running the agent doubled findings; report render was unstable
Two defects found by probing the shipped code rather than re-reading the diff.
1. `make agent` twice on one date -- an ordinary thing to do -- appended the
same logical finding to ops.finding_log twice, and the report counted rows
rather than findings. One open finding rendered as "2 still open". A diff
whose counts are wrong is worse than no diff, because the counts are read as
a measurement. `_latest_per_key` collapses to one row per finding_key per
run; `make monitor` gets the same treatment.
2. The diff sections were rendered in set-iteration order, so regenerating a
report reshuffled its lines with no content change. Reports are committed
artifacts: an unstable render puts noise in every git diff, and a diff that
is usually noise stops being read. Sorted by finding key, with a test that
renders twice and compares.
Also added a test that an unescaped pipe in detail text cannot split a Markdown
table cell -- it was already handled, but nothing pinned it.
Reviewed: 279 tests pass (275 before), ruff clean. reports/daily-2026-07-01.md
re-rendered under the stable ordering; counts are unchanged (6 new, 0 cleared,
2 still open), only line order. Verified byte-stable across two regenerations.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0199ePw5w34FyfGAB41mkv3G
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>1 parent 5fbe51e commit eb34082
21 files changed
Lines changed: 1630 additions & 31 deletions
File tree
- docs
- ai-sdlc/decisions
- incidents
- reports
- src
- agent
- lakehouse
- ops
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
50 | 54 | | |
51 | 55 | | |
52 | 56 | | |
| |||
170 | 174 | | |
171 | 175 | | |
172 | 176 | | |
| 177 | + | |
173 | 178 | | |
174 | 179 | | |
175 | 180 | | |
176 | 181 | | |
177 | 182 | | |
178 | | - | |
| 183 | + | |
179 | 184 | | |
180 | 185 | | |
181 | 186 | | |
| |||
436 | 441 | | |
437 | 442 | | |
438 | 443 | | |
439 | | - | |
440 | | - | |
| 444 | + | |
| 445 | + | |
441 | 446 | | |
442 | 447 | | |
443 | 448 | | |
444 | 449 | | |
445 | 450 | | |
446 | 451 | | |
447 | 452 | | |
| 453 | + | |
448 | 454 | | |
449 | 455 | | |
450 | 456 | | |
| |||
548 | 554 | | |
549 | 555 | | |
550 | 556 | | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
551 | 655 | | |
552 | 656 | | |
553 | 657 | | |
| |||
Lines changed: 119 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
Lines changed: 19 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
| 5 | + | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
| 5 | + | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| |||
Lines changed: 19 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
Lines changed: 19 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
0 commit comments