Repository navigation
anomaly: backlog signal for late-delivered observations - #591
Conversation
Add an opt-in export flag that marks the events of an observer burst delivering transmissions other observers reported minutes earlier: a reconnect queue stamped at delivery. It sets the existing ingest_backlog boolean, so the rate rule's exclude_ingest_backlog guard reads it unchanged; the library, replay and goldens are untouched. A row is already seen when another observer heard its transmission at least -late-gap before it and none within the gap. An observer's bin is a late burst when its window (the bin and both neighbours) holds at least -late-min-rows rows, at least -late-share of them already seen, with those delays spread over at least the gap. Unlike the #473 flag it needs no silence, so a real storm that an often-quiet observer hears first is not flagged. Four explicit flags, no defaults, exclusive with -backlog-*. Only the boolean leaves the export; the output is byte-identical under observer renaming. Relates to #538 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Relates to #538 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ivacy test Relates to #538 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rapport — CS-Minimax PR#591 #538 — head fca73b3Status: Draft. All requirements are implemented and tested, and every CI job is green (no reruns). The draft pre-registration is in the PR description, not posted. Evidence: [T] test or command output, [A] analysis or measurement, [K] code or config read. Requirements
Invariants
Local runs
CI per job (head fca73b3)
No reruns were needed; #271 did not occur. [T] Remaining
|
Review — CS-pve-agent3 PR#591 — head fca73b3Dom: REQUEST CHANGES. The code is sound: it is opt-in, key-free and byte-identical without the flags. The changes requested are to the DRAFT pre-registration (F1, F2) and to the stated limitations (F3, F5), plus one boundary test (F6). No functional code change is required. This is an independent, read-only review. Evidence: [T] test or command I ran, [A] analysis, [K] code, config or issue read. Findings
Answers to the review points
Invariants
Tests and mutantsMerged tree
Reviewer mutants, applied to
The reproductions for F3, F4, F5 and F7 and the random-permutation privacy check are a review-only test file. It is not pushed. Not verified
|
…#538) Review round 2 of #591 (F3-F6). The tests pin behaviour the definition already has, so a change to it is a visible decision: - a common-cause outage (two or three observers draining the same outage together) is never flagged, while one draining observer is; - recall of a 40-GRP_TXT FIFO queue depends on the shared rows older than the gap that come with it (0/40 after 3-10 min, 40/40 after 20-40 min at 6 shared rows a minute, 0/40 at 1 a minute); - two clocks behind by different offsets flag 13 of 40 events of a live storm at share 0.5; - a peer exactly at t + gap is not within the gap (kills the reviewer's mutant X1, < to <=). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 2 of #591 (F3-F5, F7, F8): the README and the code comment state that a common-cause outage is never flagged, that recall falls for large queues after short outages, that no share protects against two clocks skewed by different offsets, that outages shorter than about two gaps are never flagged, and that the share bound is compared in floating point (exact for 0.5). No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rapport — CS-pve-agent1 PR#591 runde-2 — head a643321Review feedback addressed (commit This round answers the review "Review — CS-pve-agent3 PR#591 — head fca73b3". The detector's behaviour is unchanged: Evidence: [T] test or command I ran, [A] analysis, [K] code, config or issue read.
No finding was rejected. TestsThe new tests (
F1, F2, F7 and F8 changed only text, so no mutant applies to them. A candidate MF5′ ( Local runs (head a643321)
CI per job (head a643321)Run 40182335343, attempt 1, conclusion success [T]:
No reruns were needed. Neither #271 nor #301 occurred. Invariants
Not verified
|
Review — CS-pve-agent3 PR#591 — head a643321Dom: APPROVE med nits This is a re-review of round 2 against my review of This is an independent, read-only review. Evidence: [T] test or command I ran, [A] analysis, [K] code, config or issue read. Findings
Answers to the re-review points
Invariants
Tests and mutantsThe merged tree is
Mutants were applied to
Not verified
|
Relates to #538
Summary
This adds an opt-in late-delivery flag to
corescope-anomaly export, forcmd/anomalyonly. It marks the events of an observer burst that delivers transmissions the rest of the network reported minutes earlier. That is the shape of a queue sent on reconnect and stamped at delivery.The #473 flag ("first observations within 2 h after a 10-min silence of the observer") marked about 75 % of the events in two of three real GRP_TXT storms (#473 issuecomment-6380316698). The new flag needs no silence, and the synthetic real storms in the tests do not trigger it.
"ingest_backlog": true, so the rate rule'sexclude_ingest_backlogguard reads it unchanged.internal/anomaly, replay and the replay goldens are not touched (git diff origin/master -- internal/is empty).-late-window,-late-min-rows,-late-shareand-late-gap. There are no defaults. The four must be given together, and they cannot be combined with-backlog-*. Without them the output is byte-identical to master.observations.observer_idxinto memory only, a line gains the boolean and nothing else, and the output stays byte-identical when observers are renamed.Plan (this PR)
Candidates evaluated
observations.timestampis the envelope receive time, or the ingest time whereresolveRxTimefalls back to it. The only proxy is row-id order: "lag = max timestamp of earlier-inserted rows − own timestamp". On the e2e fixture that proxy puts 487 of 500 rows over 5 min, because the fixture's ids are not in ingest order [A]. A copied or rebuilt database breaks the proxy silently. It is also blind to the case that matters: a queue stamped at delivery has lag ≈ 0, and the #457 artifact definition (a storm whose transmissions were first heard by one observer that was silent before it) implies timestamps bunched at the reconnect. A queue that keeps its receive times does not form a timestamp-space storm at all.-late-min-rowsconditionDefinition
t,eis the latest observation of the same transmission by another observer beforet + gap, over the whole database. The row is already seen when such aneexists andd = t − e ≥ gap. That means others heard it at leastgapbefore, and none of them withingapof it.t + gap" rather than "earliest other". Otherwise one observer whose clock runs behind would make every live peer look late. The test scenario and mutant M6 cover this.windowwide. O's bin k is a late burst when O's rows in bins k−1, k and k+1 meet all three conditions:min-rows;shareof them are already seen;gap.Evaluation (synthetic data and e2e fixture only; no staging data)
Scenario fixture (
TestExportLateDeliveryFlagsTheDrainBacklogOnly): window 1m, min rows 10, share 0.5, gap 2m. The #473 flag uses 10m/2h [T].End to end (
TestExportThenGuardedReplayIgnoresTheLateBacklog), with a GRP_TXT rate rule (threshold 20 / 5 min) [T]:Randomised sweep (
TestLateDeliverySyntheticSweep, 20 seeds, through the real export) [T]. Each seed has:A "near" event is a live event first heard by the draining observer within two windows of its delivery.
shareof the burst, for example a short outage with many transmissions only the draining observer heard, is not flagged. The sweep mixes small queues (10–40) with sparse background (about one shared row per minute of outage). A busier network raises recall only after long outages: see the large-queue limit below. 55 % is only a synthetic figure.e2e fixture (
TestExportLateDeliveryE2EFixture): over the whole fixture day the late-delivery flag marks 0 of 498 events and finds 0 rows already seen. That is expected, because the fixture holds one observation per transmission, and it shows only that real single-observer traffic is not flagged. For contrast, the #473 flag (10m/30m, the only lookback that fits the fixture) marks 140 of 222 events in 17:00–18:00 of ordinary traffic [A] [T].Limitations (stated before any validation data)
The first three are pinned in
late_limits_538_test.goat window 1m, min rows 10, share 0.5 and gap 2m [T].A common-cause outage is never flagged. When two or more observers lose the same uplink or broker and drain the same outage together, each queued row has a peer, the other draining observer, seconds away. That peer is the latest other observation before
t + gap, sod ≈ 0, no row is already seen and nothing is flagged. This is the flip side of refinement 1 ("latest", not "earliest"). Synthetic, FIFO queue delivered at 1 row/s (TestLateDeliveryMissesACommonCauseOutage):Recall is low for a large queue after a short outage. The share's denominator holds the queue-only rows and the observer's own live rows after reconnect. A queue large enough to trip the GRP_TXT rule on its own (≥ ~33 queue-only rows) is therefore flagged only when at least as many already-seen rows, older than the gap, come with it in the same 3-min window. 40 queue-only GRP_TXT, FIFO, delivered at 5 rows/s (
TestLateDeliveryRecallOfALargeQueue):Share 0.5 is no protection against skewed clocks. Two observers whose clocks run behind by different offsets of at least
gapgive another observer's rows two different delays, which can pass the spread condition at any share. A real storm of 40 GRP_TXT heard by O (correct clock), a third of it also by an observer 3 min behind and another third by one 7 min behind, and by no one else: 13 of 40 events are flagged at 1m/10/0.5/2m (TestLateDeliveryTwoSkewedClocksDefeatTheShare). In the sweep, 0.5 happened to flag no live event and 0.3 one; that is a property of the sweep's networks, not a guarantee.An outage shorter than about 2·
gap(4 min) is never flagged: already-seen delays are at leastgap, and their spread must be at leastgap, so the oldest queued row must be at least 2·gapold [A].A queue from an observer whose coverage overlaps no other observer has no already-seen rows, so it is not flagged.
A queue that keeps its receive times is not flagged. It forms no timestamp-space storm either.
An observer's own live first observations within one window of its delivery are flagged too, because the flag marks whole bursts.
The share bound is compared in floating point (
seen >= share * rows). It is exact for shares that are multiples of 1/2^k, such as 0.5, but misses some decimal bounds: at share 0.07, 7 of 100 rows seen is not a burst, because 0.07 · 100 = 7.000000000000001 [T]. Documented, not changed.gapmust exceed propagation plus upload jitter. 2 min is 4× the 30 s settle window that the export already treats as "live evidence".Privacy
As in #477/#473:
lateQueryreads onlyobserver_idx,timestamp,idandtransmission_id, with no join (TestLateQueryReadsOnlyActivity).ingest_backlog, whose only value istrue(TestExportLateDeliveryAddsOnlyTheBoolean).TestExportLateDeliveryIsInvariantUnderObserverRenaming).late_deliveryholds counts only: rows, rows without an observer, rows already seen and events flagged.Cost
This is an offline tool, not a server hot path. The flag adds one index range scan on
observations.timestampover[start − settle − 2·window, end + settle + 2·window), under the same-max-rowscap, with one index search per row over its transmission's observations.TestLateQueryPlanIsBoundedasserts no SCAN and no TEMP B-TREE. Memory is one entry per (observer, bin) with rows, capped at 4,194,304; one more fails the export (TestLateTrackerIsBounded). No performance claim is made.Tests and mutants
Red before: on
origin/masterthe new tests do not build, becauseexportOptionshas noLate*fields andexportSummaryhas noLateDelivery[T]. Each requirement also has assertion-level mutants. A mutant is applied to the committed source, thecmd/anomalytests run, and the file is restored withgit checkout[T].cmd/anomaly)TestExportLateDeliveryFlagsTheDrainBacklogOnly,TestLateTrackerBurst,TestLateTrackerBinsAndSeen,TestExportThenGuardedReplayIgnoresTheLateBacklog,TestLateDeliverySyntheticSweep,TestExportLateDeliverySummaryCountsTheScand > gap· M4 strict share · M5 window = own bin only · M6 earliest instead of latest other observer · M7 not + gapbound · M8 own rows count as "another observer" · M18 NULL observer flaggedTestExportLateDeliveryAddsOnlyTheBoolean,TestExportLateDeliveryIsInvariantUnderObserverRenaming,TestLateQueryReadsOnlyActivitysnr-backlog-*; plain export and replay goldens unchangedTestExportLateDeliveryIsOptIn,TestExportCommandLateDeliveryFlags, all existing export, replay and golden tests (unchanged and green)late()ignores-late-share· M12 no exclusion with-backlog-*· M13 share bound 2 · M14 a default-late-window(kills 6 existing command tests)TestLateQueryPlanIsBounded,TestLateTrackerIsBounded,TestExportLateDeliveryScanIsCappedByMaxRows,TestExportLateDeliverySummaryCountsTheScan-max-rowsoff by one · M17 scan margin without the 2 windowsTestExportLateDeliveryE2EFixture(e2e fixture, unchanged file hash)t + gapbound (review round 2)TestLateDeliveryMissesACommonCauseOutage,TestLateDeliveryRecallOfALargeQueue,TestLateDeliveryTwoSkewedClocksDefeatTheShare,TestLateQueryPeerAtExactlyTheGapIsNotWithinp.timestamp <= o.timestamp − gap) · MF4 queue-only rows leave the share's denominator · MF5 spread must exceed 2·gap · MF6t + gapbound inclusive (the reviewer's X1)late()") was equivalent and is dropped:late()implies the flag is set.internal/anomaly,cmd/server,cmd/ingestor,public/and.github/are unchanged.DRAFT pre-registration for a validation round (not posted; the coordinator posts it)
Rules of the round
Tool.
cmd/anomalyfrom this PR at head<HEAD SHA>, built withgo build -trimpath; the binary sha256 is reported.<HEAD SHA>is the head commit of PR #591 that the last review of the PR reviewed: a commit onfeat/538-backlog-late-delivery-signal, not a merge commit and notmaster. If that commit cannot be built, the round is void.1. Data and runs
[2026-11-02T00:00Z, 2026-11-09T00:00Z): 7 whole UTC days. V is disjoint from #474 round 4, whose V ends at the latest on 2026-11-01T00:00Z, and starts one day after it, so the export's scan margin (V.start − 30 s − 2 min) reads no row of that round's V either. The snapshot must cover V plus the settle margin plus the scan margin of 2 windows: it is taken afterV.end + 30 s + 2 min. There is no shorter fallback. If this text is not posted before 2026-11-02T00:00Z, or the coordinator has seen data from V before posting, the round is void; a new round needs a new posting with a new V.[2026-11-02T00:00Z, 2026-11-03T00:00Z), the first 24 h of V. It is used only for the rate thresholds (§3).[2026-11-03T00:00Z, 2026-11-09T00:00Z), V minus C, 6 days. All metrics count only episodes activated in E and storms that start in E.scripts/backup-sqlite.shcopy used as-is when no-wal/-journalfile sits beside it, or elsesqlite3 -readonly <source> "VACUUM INTO …". Read-only, with-immutable.corescope-anomaly export -db <snap>/meshcore.db -immutable -start 2026-11-02T00:00:00Z -end 2026-11-09T00:00:00Z -settle 30s -max-rows 50000000 -late-window 1m -late-min-rows 10 -late-share 0.5 -late-gap 2m -out events.jsonlcorescope-anomaly replay -config round538A.json(orround538B.json)-input events.jsonl -start 2026-11-02T00:00:00Z -end 2026-11-09T00:00:00Z -max-line-bytes 4096 -out reportA.json(orreportB.json)EventID. Activation is its first transition toactive; its end is the last transition toended(V.end if none).-max-rows 50000000. If either export scan fails withmore than -max-rows, it runs once more with-max-rows 100000000, the tool's maximum (maxExportRows); no other value is used. If that run also fails withmore than -max-rows, the round is void. A failure for more than 4,194,304 (observer, bin) entries also voids the round. Every run and its outcome is reported.2. The late-delivery flag (fixed values)
The definition is the one in this PR:
observations.timestampas stored, the time base of the anomaly phase 3: replay CLI, read-only export and blind validation (periodic, rate, new-stream) #457 labels.Why these values. All of them come from data before V, and none from staging after 2026-10-24:
Limitations, stated before the data (synthetic figures from this PR's tests):
3. Rule configs
T_ADVERTandT_GRPTXTare computed by the anomaly phase 3: replay CLI, read-only export and blind validation (periodic, rate, new-stream) #457 §3 procedure on C:calibrate.py, unchanged, over all events of C, flagged ones included. Both arms use the same two values, and the coordinator posts them before either replay runs.round473A.jsonfrom anomaly round 2: rate rule with an ingest-backlog guard, blind validation on fresh data #473 §3, unchanged: the two round-1 rate rules (p457_rate_advert,p457_rate_grptxt) with the samelimitsandexpected."exclude_ingest_backlog": trueon both rules and the new namesp538_rate_advert_guardandp538_rate_grptxt_guard. Nothing else differs.ParseReplayConfigandNewon the pinned head, with the placeholders set to 20.4. Ground truth
The #457 round-2 §4 rate labels, unchanged, computed with
label457.py. Only V and E change, to the values in §1, and only the rate part is used.count ≥ max(3·B(p,h), B(p,h)+30), and runs separated by one bin are merged.(payload type, start, stop, artifact)tuples only. Its sha256 is posted before either replay runs.5. Matching and metrics
Per arm, as in #457 round 2 §5 (rate column), unchanged. The recount for arm B counts only lines without
"ingest_backlog": true.Comparative metrics (they decide, §6). An episode is on a storm, and a storm is detected by an arm, under the #457 round 2 §5 matching rule, unchanged. Over the storms that start in E:
N_artandN_real: the number of artifact and non-artifact storms;EA_artandEB_art: the number of arm A's and arm B's episodes that are on an artifact storm;L: the number of non-artifact storms that arm A detects and arm B does not.Also reported, outside the bars:
late_delivery.events_flaggedandrows_already_seen, in V and in E;6. Pass bars and decision
All of these must hold, otherwise no-go:
Comparative criterion, also required for a go (§5 metrics; the comparator is arm A, the tolerance is zero storms):
N_art ≥ 1andEB_art < EA_art. Arm B has strictly fewer episodes on artifact storms than arm A.L = 0. Arm B detects every non-artifact storm that arm A detects. This implies that arm B's recall is at least arm A's.How the result is read:
N_art ≥ 1andEB_art ≥ EA_art.N_art = 0), or arm A with no episode on an artifact storm (EA_art = 0), leaves the guard's benefit unmeasured: the round is inconclusive, which counts as a no-go for any claim. C2 and the bars are still reported, as a safety result only.<HEAD SHA>, a failed export at-max-rows 100000000, or the bin cap) has no results.7. Files
events.jsonl, both reports and the label file stay with the coordinator. Only aggregates and(payload type, start, stop, artifact)tuples are published.🤖 Generated with Claude Code