Repository navigation
fix(compliance): keep owner tests from re-arming the heartbeat schedule - #7691
therevoltingx wants to merge 4 commits into
Conversation
A full-suite owner_test published through recordComplianceRun advanced next_compliance_check_at by a full interval and cleared requeued_at, so it silently cancelled a pending requeue and could starve the independent heartbeat indefinitely. Gate the schedule_next CTE on triggered_by: owner_test runs still publish the verdict but leave the schedule untouched, and a first-ever owner test leaves the agent immediately eligible for a heartbeat. heartbeat, manual, and webhook runs keep advancing the cadence and consuming requeues. Closes adcontextprotocol#7680 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IPR Policy Agreement Required@therevoltingx — thanks for the contribution. Before this PR can be merged, the AgenticAdvertising.Org IPR Policy requires your agreement. To agree, post a new comment on this PR with the exact phrase: Your signature is recorded once and covers all contributions to AAO repositories. See |
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — server-only fix for #7680 with no blocking or medium findings.
What I checked:
- Scope is
server/**only (compliance-db.ts + integration test) — no protocol surface touched (static/schemas/source/**,docs/reference/**, task defs, dist artifacts). No changeset required; correct no-changeset call. - The fix gates the
schedule_nextCTE ontriggered_byso owner_test runs publish the verdict but never re-arm the heartbeat: no-op on existing rows, NULL-and-immediately-due on first insert. Heartbeat/manual/webhook paths unchanged. - NULL-safe predicate verified; no starvation or permanent-skip window.
- 10 tests pin every branch of the new conditional.
Gates: No no-auto-approve team match. gated_paths: false. high_risk: false. review_decision is REVIEW_REQUIRED, but row 2 only fires when gated_paths is true — it is not, so no hard gate applies. No critical/high/medium findings (rows 1, 4–8 do not fire). Falls through to row 9.
|
I have read the IPR Policy |
IPR Policy — signedThanks, @therevoltingx. Your agreement to the IPR Policy is recorded at |
|
I'd go with It is a bare The two failure shapes are not symmetric either. A wrong The divergence set is smaller than the comparison suggests. On the admin case specifically, I think it cuts the other way from the triage. An admin support run consuming an owner's pending requeue is the same defect shape as the one this issue is about: an action the owner did not initiate silently cancelling their explicit request. The owner-displacement framing is right, and an admin support run is not the registry's independent scheduled check either. Against that, the cost you are avoiding is one redundant heartbeat after an admin run, on 4 runs historically. One correction to the switching cost: it is two places, not one. The INSERT branch is keyed on the same literal: VALUES ($1, CASE WHEN $7 = 'owner_test' THEN NULL ELSE NOW() + INTERVAL '12 hours' END)
...
WHERE $7 != 'owner_test'Those are consistent as written. Flip only the VALUES ($1, CASE WHEN $7 = 'heartbeat' THEN NOW() + INTERVAL '12 hours' ELSE NULL END)
...
WHERE $7 = 'heartbeat'That said, this is a preference, not a blocker. The practical difference today is 4 runs, and your reasoning for the admin carve-out is coherent. If you keep The INSERT branch was the right call to handle here. I left it out of the issue and I was wrong to — the reasoning holds: no metadata row means Agreed on the floor being out of scope, and your read of why is right — |
|
The fail-closed reasoning is correct, and the two-place catch is important — thank you for both. Keeping On Two-place consistency is preserved as written: the CASE ( Generated by Claude Code |
|
You're right and my premise was wrong. The two lines I cited were read shapes, not the write path:
With a compile-time union the fail-open argument is much weaker, and keeping One factual note on scope, not an argument to change anything: Agreed that On the two-place point, I think we're agreeing: the I don't see |
…edicate Requested in adcontextprotocol#7691 review: the WHERE predicate on the schedule_next CTE relies on the TypeScript TriggeredBy union as its compile-time guard. A new union member opts into advancing the cadence and consuming requeues unless the predicate is updated alongside it, and the INSERT-branch CASE is keyed on the same literal and must move with it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
c9a9110
|
@bokelley — inline comment added in c9a9110 on the @EvgenyAndroid — |
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — subsequent pass on PR #7691, clean diff with no findings.
What I checked:
- Delta since the prior clean approve is comment-only: a five-line explanatory comment in
server/src/db/compliance-db.tsdocumenting the$7 = 'owner_test'scheduling predicate in theschedule_nextCTE. No executable code changed. - Reviewer surfaced no critical/high/medium findings.
- No protocol surface touched (
static/schemas/source/**,docs/reference/**,dist/**untouched) — no changeset, oneOf, or schema↔docs coherence concerns apply. high_risk: false;gated_paths: false(row 2 does not fire even thoughreview_decisionis REVIEW_REQUIRED — the gate is path-based and no gated path was touched).- Author is not on a no-auto-approve team.
- Prior decision was
approve, so no sticky-escalation logic applies.
No rows 1–8 fire; falls through to row 9 → approve.
|
@andybevan-scope3 — this needs a re-approve when you have a moment. Your approval from 19:05 on the 25th was auto-dismissed by the State as of now: all four checks green (GitGuardian, IPR signature, Worth flagging that it gates other work: #7693 wants to write |
Picks up adcontextprotocol#7861, which dropped the retired @adcp/sdk beta tag from published-versions.json; the stale tag failed check:published-compliance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he member profile The heartbeat due query now selects only agents with a registry source (adcontextprotocol#7844), so metadata-only fixture agents were never due regardless of the fix. Owner tests come from member-registered agents; register each fixture agent on the suite's member profile, as beforeAll already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Updated the branch with main. The Build Check failure was the published compliance check packing After the merge, three of the new tests failed because #7844 now only selects agents with a registry source, and the fixtures were metadata-only. I registered each fixture agent on the suite's member profile, as One interaction to flag: #7844 also added |
|
Leave it as-is — The streak is backoff state for the heartbeat's internal cadence. An owner test's conclusive verdict already reaches the owner through the public card and The alternative (let a conclusive owner test zero out the streak but not advance the schedule) requires splitting the WHERE into per-column conditions, which adds surface to an already nuanced predicate. The gain — one fewer heartbeat cycle in backoff after a conclusive owner test — doesn't justify the complexity here. If the owner-displacement framing later extends to "owner should be able to unstick the backoff without a full requeue," that scopes cleanly as a follow-up: a dedicated Generated by Claude Code |
Closes #7680
Problem
recordComplianceRunre-armed the heartbeat schedule on every authoritative publish, regardless of who triggered the run. A full-suite ("Recheck & retest")owner_testtherefore:next_compliance_check_at = NOW() + intervaland clearedrequeued_at, removing the agent from the due set before the Owners cannot obtain an authoritative compliance run: human refresh fenced with no ETA, and requeue cannot reach agents with a recent last_checked_at (heartbeat orders by last_checked_at ASC) #7632 ordering could apply. Nothing surfaced that the requeue was dropped.check_interval_hourskept pushing the heartbeat out, whilelast_checked_atstill looked fresh.Fix
Gate the
schedule_nextCTE ontriggered_by(passed as$7, defaulting to'heartbeat'to match the runs insert):owner_test, existing row:DO UPDATE ... WHERE $7 != 'owner_test'leavesnext_compliance_check_atandrequeued_atuntouched.owner_test, first-ever run: the metadata row is inserted withnext_compliance_check_at = NULL, so the agent is immediately eligible for an independent heartbeat instead of waiting 12h. This covers the INSERT-branch follow-up noted in triage.heartbeat/manual/webhook: unchanged. The cadence advances and requeues are consumed. The heartbeat that serves a requeue still clears it, so a requeue can never become permanent.Owner-test verdicts still publish to the public card,
last_checked_at, and badges as before. Only scheduling changes.This follows the
!= 'owner_test'predicate from the triage discussion. Both "Fix 2" variants (gate onrequeued_at IS NULL, or mirrornext_compliance_check_at > NOW()) were ruled out there because they break requeue consumption or the whole cadence.Tests
10 new cases in
server/tests/integration/compliance-run-publication.test.ts:owner_test→ requeue survives and the agent stays due; verdict still publishedheartbeat→ requeue consumed, next check about 12h outmanual/webhook→ cadence advances, requeue clearedowner_test→ not pushed further outowner_test→ still dueowner_test→ row created withNULL(immediately eligible)heartbeat→ default 12h cadenceowner_test→ scheduling untouchedWith the fix reverted, the 4 owner-test cases fail; the other 6 pin unchanged behavior. Also ran the
compliance-heartbeatandcompliance-db-last-write-winsunit suites (41/41) andtscwith no errors.No changeset: server-only, no protocol surface.
🤖 Generated with Claude Code
Disposition (2026-10-09): superseded by merged #7969 (
a4003bd7ac6b1889047ab828156af09dddf41f92). Its owner-test publication path preserves heartbeat scheduling, requeues and streaks, and initializes owner-only records without a heartbeat schedule. The existing replacement passed all 24 real PostgreSQL regressions, full normal local checks and all 56 remote checks with fresh final-head approval. The fallback is therefore no longer needed. This source branch is preserved; #7355 remains open for separate fleet/operator verification.