Repository navigation
feat: a window says how long a state held (ADR 0088) - #132
Merged
Merged
Conversation
ADR 0087 gave the two agent states a start instant and named what it did not do: a state that ended is absent from the next payload by design, because a state field describes the present and the kind supersedes. A watch that failed for nineteen minutes and recovered left the backend's row clean. `agent_states` is the other half — one record per (window, state, subject), hour-aligned, carrying how much of the hour the state held, how many episodes that was, and how much of the hour the agent was running to watch at all. The relationship to `collection_coverage` is `container_restarts` to `restart_counters`: one is a reading, the other a window, and the contract carries the sentence that keeps them apart. Occupancy is an advance rather than a reading, so resuming it is where this could go silently wrong. Accrual moves forward from `accrued_to` and is never recomputed from the episode's start; `open_since` is what tells a resumed process the episode it finds open is the one already counted. Both directions are covered by tests that fail on a number, not on an error. Sampling at the pass loses nothing, and that is measured rather than assumed: a pass runs every minute and a new run of watch failures needs two minutes of quiet to begin, so two episodes cannot both begin and end between two passes. The counter deltas are deliberately not here — subtracting a previous reading is a different mechanism with its own failure modes (ADR 0062 priced them). go test -race ./... all packages ok go vet -tags e2e ./... clean GOOS=linux golangci-lint run 0 issues Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeZECJXTDHL6Nts95eKMJA
kfeofantov
force-pushed
the
feat/a-state-says-when-it-began
branch
from
September 18, 2026 14:35
39d1328 to
5c4cb36
Compare
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.
Slice B of the "when" work, and the answer to "а если упало, но потом
восстановилось". Builds on #131.
The half that was left
ADR 0087 gave the two agent states a start instant, and named what it did not
do. A state field describes the present,
collection_coveragesupersedes, andthe backend is entitled to keep one row — so a state that ended leaves nothing:
agent_statesis the other half: one record per (window, state, subject),hour-aligned, superseding by
(window start, window length)like the sixwindow kinds beside it.
Three numbers, and why each is there
occupied_nanos— how much of the hour the state held.entries— how many episodes that was. Independent of the duration onpurpose: one nineteen-minute outage and three six-minute ones are different
diagnoses that a single number merges.
observed_nanos— how much of the hour the agent was running to watch atall. Without it, zero occupancy means both "a healthy hour" and "an hour
nobody watched", which is ADR 0054's founding defect in a new shape.
A record is written for every watched subject, including a clear one — a
subject with no record is one nobody watched.
The relationship to
collection_coverageiscontainer_restartstorestart_counters: one is a reading, the other a window, and the contract nowcarries the same sentence keeping them apart.
Where this could have gone silently wrong
Occupancy is an advance, not a reading. The five journals beside this one resume
safely because a sample read twice is the same value; that property does not
transfer. So:
Write
now - open_sinceonce and a resumed window counts the whole pre-restartspan a second time — with no error, only a wrong number.
open_sinceis themarker that tells a resumed process the episode it finds open is the one already
counted. Both directions have tests that fail on the number.
One assumption, measured rather than assumed
Sampling states at the flush pass could in principle miss an episode that began
and ended between two passes. It cannot here: a pass runs every minute
(
everyPass) and a new run of watch failures begins only after two minutes ofquiet (
watchStreakGap), so two episodes cannot both begin and end between twopasses. The halt cannot flap at all — it latches until the process ends.
What it still cannot say
In ADR 0088 under its own heading, not buried in consequences: a restart
crossing a window boundary adds one entry to a continuing episode (detectable —
sincemoves at the same moment); an episode that closed between two passesundercounts by up to one pass, because guessing when it closed would be the
payload's only invented number; and the marker survives a process restart, not a
pod replacement, because the spool is an
emptyDir.Not in this PR
The counter deltas —
filter.excluded_*,shipping.deferred, the puller'soutcomes, the intake rejections. They have the same blind spot for the same
reason, but subtracting a previous reading is a different mechanism with its own
failure modes, which ADR 0062 already priced. Named as out of scope in ADR 0088
rather than left to look finished.
Evidence
🤖 Generated with Claude Code
https://claude.ai/code/session_01KeZECJXTDHL6Nts95eKMJA