Repository navigation
Conversation
Step 2 of 2, and the one that starts the soak. Sets
LIFECYCLE_ROLLOUT_MODE=observe on the shepherd CWFT, which runs the
comparison.
LAST of three, deliberately: the worker flipped first so the writer would
be live, then the bootstrap imported the pull requests already in flight.
Starting the comparison before either measures the migration rather than
the contract -- every entity the old mechanism already drives reads
store-permits-old-forbids, the one dangerous class.
Observe means the store is read alongside the old DevLoop liveness probe,
the two answers are compared and logged, and the OLD one still decides. A
test in mctl-agents asserts the filter's decisions AND the tick's own
output are identical with the compare on and off.
This is the only rollout control. Not workflow.patched("lifecycle-
ownership"), which is a history marker and can never be one, and not
LIFECYCLE_OWNERSHIP_REQUIRED, which is the break-glass inside enforce. To
stop the soak, set this back to off -- the remedy the dangerous-divergence
alert names.
LifecycleShadowSilent ships here and not earlier: it fires on the ABSENCE
of the comparison counters, and until this commit that absence is correct
behaviour. It is the alert for the failure that reads as a clean soak.
It requires all three counters absent, not just `agree`. A compare
emitting only store-unknown is BROKEN -- a 403 on the admin-gated batch
route answers that for every id -- and it is loud;
LifecycleShadowStoreUnknownHigh is the alert for it. Keying on `agree`
alone would raise both for one fault, naming different causes.
Refs: mctlhq/.github#57
Claude-Session: https://claude.ai/code/session_01RUQTbQvUQjwbDFd7Nq9bvC
The comments claimed increase() "is 0" for a counter Promtail creates at 1. That is true of Prometheus and false of the engine these rules run on: vmalert evaluates them against VictoriaMetrics, which counts the first point of a new series -- confirmed end-to-end in gitops#1171. So increase() would have fired in production. The real objection is narrower and better: `promtool test rules` is Prometheus, so increase() here would be an expression whose correctness depends on who evaluates it, and the only way to make its unit test pass is to seed a zero the pipeline never writes -- a test agreeing with itself rather than with production. max_over_time is correct under both engines and testable under the one we have. The expression does not change; the justification stops being wrong about the thing it names. Claude-Session: https://claude.ai/code/session_01RUQTbQvUQjwbDFd7Nq9bvC
mashkovd
force-pushed
the
feat/lifecycle-observe-compare
branch
from
September 14, 2026 13:32
2c5ea8d to
783cc90
Compare
This branch has not been deployed
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.
Part of mctlhq/.github#57 — step 2 of 2 of the observe rollout, and the one that starts
the soak.
Draft on purpose. Merge only after the bootstrap has been applied.
Order, and why this is last
dry_run=true, report reviewed row by rowdry_run=falseStarting the comparison before steps 1–3 measures the migration rather than the contract:
every entity the old mechanism already drives reads
store-permits-old-forbids, the onedangerous class, and the first week of data is the import.
What observe means
The store is read alongside the old DevLoop liveness probe, the two answers are compared
and logged, and the old one still decides. A test in mctl-agents asserts the filter's
decisions and the tick's own output are identical with the compare on and off.
This is the only rollout control — not
workflow.patched("lifecycle-ownership"), ahistory marker that can never be one, and not
LIFECYCLE_OWNERSHIP_REQUIRED, thebreak-glass inside
enforce. Each is read in exactly one place.To stop the soak: set this back to
off. That is the remedyLifecycleShadowDangerousDivergencenames.LifecycleShadowSilentships hereIt fires on the absence of the comparison counters, and until this commit that absence
is correct behaviour. It is the alert for the failure that reads as a clean soak.
It requires all three counters absent, not just
agree: a compare emitting onlystore-unknownis broken — a 403 on the admin-gated batch route answers that for everyid — and it is loud, with
LifecycleShadowStoreUnknownHighalready on it. Keying onagreealone would raise both alerts for one fault, naming different causes.Expect a burst; do not start the clock on it
store-permits-old-forbidsis non-zero by construction after the flip even with thebootstrap applied. Executions started after the
shepherd-in-looppatch but beforelifecycle-ownershipnever write a row —patched()replays False forever — and they stayRunning for up to the 14-day merge deadline.
Depends on
mctl-agents#378 merged, released, and the shepherd CWFT's
agent_imageon that release.The mode means nothing on an image whose shepherd has no compare in it — which is exactly
what
LifecycleShadowSilentwould then report.Verification
Then:
lifecycle_shadow_*_totalappear withnamespace="argo-workflows", noexported_prefix, and
store-unknownis not the whole volume.🤖 Generated with Claude Code
https://claude.ai/code/session_01RUQTbQvUQjwbDFd7Nq9bvC