Repository navigation
feat(cloud-agent-next): collect deduplicated outcome aggregates - #6213
Conversation
…egates Add a scheduled collector that computes deduplicated settled-turn outcomes from the Cloud Agent reporting tables and emits one small structured record per window into Axiom through the existing Logpush pipeline. - Half-open terminal-time windows (5/15/60 min) over the 90-day retained reporting population, joined to the parent session. - Splits by runtime generation (session-id prefix) and turn role (initial vs follow-up); platform/unknown failure shares are null, never zero, when the denominator is empty. - Reports failure_stage/failure_code composition and session-setup failures as a separate population; interrupted turns never enter settled. - Runs the three queries sequentially in one read-only REPEATABLE READ transaction so a window is a consistent snapshot; a failed collection emits a failed-status record instead of zeros. - Dispatches the Worker scheduled handler by exact cron identity so a new collection trigger can never run the daily 90-day retention sweep. Retention stays on 17 2 * * *; collection is provisionally */3 * * * *. - Adds a real-Postgres Vitest suite (test:postgres) with a CI Postgres service, covering dedup/merge, half-open boundaries, retention cutoff, absent responsibility, generation agreement, and snapshot coherence. The collector only exposes reported categories; it does not prove that a message never reached the model. Axiom monitor definitions, alert delivery, and the PR 3 freshness signal are out of scope.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryThe incremental change verifies cleanly: both null Files Reviewed (3 files)
Previous Review Summary (commit 498aacf)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 498aacf)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe scheduled collection and cron-dispatch changes are sound; the only issue is a null Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (12 files)
Verification Notes (click to expand)
Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0 Review guidance: REVIEW.md from base branch |
…r null codes The outcome aggregate coalesced a null failure_code to 'unknown', which is a valid failure_stage but is not a member of CloudAgentSessionRunFailureCode. Consumers keyed on the failure-code domain do not recognize it. The established sentinel for a null code is 'unclassified', as used by cloud-agent-queue-report.ts and admin-cloud-agent-next-router.ts. A failed run with both classification fields null is allowed, so the run-level fallback is reachable; the session-level fallback is currently unreachable because the sessions check constraint requires stage and code together with failure_at. Fixing both occurrences keeps the two coalesce expressions consistent. The unit fixture defaulted failureCode to 'unknown' too, so it mirrored the bug and could not catch it; the new PostgreSQL regression test asserts the emitted record and fails without the source fix.
Summary
Adds the first piece of Cloud Agent health monitoring: a scheduled collector that computes deduplicated settled-turn outcomes from the Cloud Agent reporting tables and emits one small structured record per window into Axiom via the existing Logpush pipeline.
Why: the only outcome view today is the admin health overview, which computes
failed / (completed + failed)over run rows with no runtime-generation split and no failure-stage breakdown. There was no deduplicated per-turn outcome metric, and no way to see which lifecycle stage failed (pre-dispatch, no-activity, after-activity).What it collects, per runtime generation and per turn role, over half-open
terminal_atwindows of 5/15/60 minutes on the 90-day retained population:settled, andplatformFailureShare/unknownClassificationShare(null, not 0, on an empty denominator)failure_stageand(failure_stage, failure_code)settledDesign notes:
collectionStatus: 'failed'record with no counts — never a zero snapshot.workspace_=> control, else legacy), with the prefix exported fromsession-plane.tsas the single owner.message_id = initial_message_id), not ordering or dispatch state.retentionCutoffowner, and the coverage label derives from the retention constant.Wiring:
scheduled()now dispatches by exact cron identity. Retention stays17 2 * * *; collection is provisionally*/3 * * * *. Unrecognized expressions warn and do nothing, so no fallback can run retention at the wrong cadence.test:postgres) plus a CI Postgres service; the existing suite mocks the database and therefore cannot verify the aggregate SQL.Out of scope: Axiom monitor/dashboard definitions, alert delivery, the PR 1 log-signal catalog, the PR 3 freshness signal, and PR 4 progress facts.
Verification
Manually exercised against a locally running Worker (real
wrangler dev --env dev --test-scheduledagainst a migrated local Postgres, serving the uncommitted changes):*/3 * * * *=> exactly one collection: one aggregate record per window (5/15/60),collectionStatus: 'complete', counts matching the seeded rows (completed 1, platformFailed 2, settled 3, distinctPlatformAffectedSessions 1, stage/code breakdown present, 90-day-expired session excluded).* * * * *expression now hits the unrecognized-cron branch (warn only, no collection, no retention), proving the running Worker uses the new constant.17 2 * * *=> retention only; the expired session is deleted and no aggregate record is emitted.Visual Changes
N/A
Reviewer Notes
scheduled()previously ran retention unconditionally. A drift test asserts the root andenv.devcron arrays equal exactly the exported constants. Rollback order matters: remove the collection trigger and keep the dispatcher deployed until cron deletion propagates, then revert code.post_dispatch_no_activitydoes not establish prior activity, so it must not be read as proof that a message never reached the model. The emittedlimitations[]labels carry this.environmentis null, so consumers must scope evaluations by the deployed Worker identity (dataset plus Worker name), not by the constantservicefield.o11ystack), and nothing pages after this PR.