Skip to content

🐛 fix(agent): stop agent reports from clearing controller-set update policy overrides - #624

Merged
scttbnsn merged 2 commits into
dev/v1.6from
fix/v1.6-565-agent-policy-overrides
Jul 28, 2026
Merged

🐛 fix(agent): stop agent reports from clearing controller-set update policy overrides#624
scttbnsn merged 2 commits into
dev/v1.6from
fix/v1.6-565-agent-policy-overrides

Conversation

@scttbnsn

@scttbnsn scttbnsn commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Problem

Agent-managed containers lose their controller-set update policy overrides (maturity mode/min-age days, skip lists, snooze) on every agent report. The agent resolves its own declarative (env/label) policy but never learns controller-side runtime overrides, so every container report it sends carries an explicit updatePolicyOverrides: {} layer. The controller persisted that layer verbatim, and the store's updateContainer treats any present updatePolicyOverrides key as authoritative — even when empty. Any manual recheck or periodic agent sync wiped the settings.

This is the settings-deletion half of #565. The soak-clock resets in that thread were real but separate mechanisms, fixed in #566/#568 and the rc.7 identity batch (#607). The reporter runs the controller-agent architecture (confirmed in their own words in #496, reconfirmed in #623), which is why the symptom survived all four prior fixes: those fixed the clock, this deletes the actual override values — matching the original before/after screenshots.

The insert/recreate path has had exactly this protection since #496/#497 (restoreRetainedUpdatePolicy: "an empty layer carries no controller intent"); the same-id updateContainer path never got it.

Fix (two layers)

  1. AgentClient.buildContainerReport reapplies the controller's stored runtime overrides onto the incoming agent report before persistence. Covers all five agent ingestion call sites (handshake, single-container, pending watcher-cycle, bulk reports, recheck) — they all funnel through this one method.
  2. storeContainer.updateContainer now encodes the invariant directly, mirroring the insert-path rule: a present-but-empty override layer carries no intent and falls back to the stored overrides, unless the caller passes authoritativeEmptyOverrides: true. The update-policy PATCH handler passes it, so deliberate clears from the UI still stick.

Safety was verified adversarially before landing: overrides can only be set via the controller's PATCH endpoint, there is no controller→agent policy push that could echo stale values back, expired snoozes can't be resurrected (evaluated live at read time), and the local (non-agent) watcher path already preserves overrides by construction (it mutates the live store doc).

Tests

  • Regression: mature/5-day override survives an agent report with an empty override layer (fails pre-fix)
  • skipTags/skipDigests/snoozeUntil survive the same scenario
  • Inverse: cleared overrides stay cleared on subsequent agent reports (no resurrection)
  • Store guard: authoritative empty clears; non-empty always authoritative; declarative-absent empty preserved; first-write empty stays empty
  • PATCH handler passes clear authority to the store

Full app suite: 394 files / 12,395 tests, 100% coverage. Full lefthook pre-push gate green.

Refs #565

Changelog

  • 🐛 Fixed agent reports clearing controller-owned update-policy overrides.
  • 🔧 Reapplied stored maturity, minimum-age, skip-list, and snooze overrides during agent report normalization.
  • 🔧 Treated empty override layers as non-authoritative by default during container updates.
  • ✨ Added explicit authoritativeEmptyOverrides support for intentional clears from update-policy PATCH requests.
  • ✨ Added regression coverage for override preservation, clearing, and PATCH behavior.
  • 🔧 Forwarded update options through container API handlers.

Validation

  • Full suite passes: 394 files, 12,395 tests, 100% coverage.

…policy overrides

Agent watcher normalization stamps updatePolicyOverrides={} on every
container it reports; the controller persisted that empty layer verbatim,
wiping controller-set runtime overrides (maturity mode/min-age, skip
lists, snooze) on every agent sync or manual recheck.

- 🐛 fix(agent): reapply the controller's stored overrides in
  buildContainerReport before persisting agent reports
- 🐛 fix(store): treat a present-but-empty updatePolicyOverrides layer as
  carrying no intent in updateContainer unless the caller passes
  authoritativeEmptyOverrides (update-policy PATCH clears still stick)
- ✅ test: regression coverage for maturity/skip/snooze preservation,
  clear-then-report stickiness, and PATCH clear authority

Refs: #565
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
drydock-website Ready Ready Preview, Comment Jul 28, 2026 12:36pm
drydockdemo-website Ready Ready Preview, Comment Jul 28, 2026 12:36pm

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 176f5086-8984-4604-9f9b-fc447111f855

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Container override merging now distinguishes authoritative empty layers from empty agent reports, preserving stored controller overrides unless explicit authority is provided. The container API propagates this option, and unsnooze marks cleared layers as authoritative. Agent report ingestion reapplies existing controller-owned policy overrides before persistence. Tests cover maturity, skip/snooze, stale agent values, authoritative clearing, undefined layers, and first writes.

Possibly related PRs

  • CodesWhat/drydock#512: Also changes updateContainer handling of empty updatePolicyOverrides layers and stored controller overrides.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/v1.6-565-agent-policy-overrides

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/store/container.ts (1)

1142-1150: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated "empty-layer is non-authoritative" rule — consider a shared predicate.

The same rule ("a present-but-empty updatePolicyOverrides layer carries no controller intent") is now implemented twice: here and in restoreRetainedUpdatePolicy (Lines 1044-1049), which even comments that it "mirrors the rule ... above." Two independent implementations of the same policy invite drift if one is tweaked later without the other (which is exactly the class of bug #565 fixes).

♻️ Suggested consolidation
+function isAuthoritativeOverrideLayer(
+  overrides: unknown,
+  authoritativeEmptyOverrides: boolean,
+): boolean {
+  return authoritativeEmptyOverrides || Object.keys(overrides ?? {}).length > 0;
+}

Then use isAuthoritativeOverrideLayer(container.updatePolicyOverrides, options.authoritativeEmptyOverrides === true) in both updateContainer and restoreRetainedUpdatePolicy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/store/container.ts` around lines 1142 - 1150, Extract the duplicated
empty-layer authority check into a shared `isAuthoritativeOverrideLayer`
predicate, accepting the overrides layer and the authoritative-empty flag.
Replace the inline logic in both `updateContainer` and
`restoreRetainedUpdatePolicy`, while preserving the distinction between absent,
non-empty, and explicitly authoritative empty overrides.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/store/container.ts`:
- Around line 1142-1150: Extract the duplicated empty-layer authority check into
a shared `isAuthoritativeOverrideLayer` predicate, accepting the overrides layer
and the authoritative-empty flag. Replace the inline logic in both
`updateContainer` and `restoreRetainedUpdatePolicy`, while preserving the
distinction between absent, non-empty, and explicitly authoritative empty
overrides.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d8279893-d99f-4cb1-a633-6666d797801a

📥 Commits

Reviewing files that changed from the base of the PR and between 997a263 and daf2471.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !CHANGELOG.md
📒 Files selected for processing (7)
  • app/agent/AgentClient.test.ts
  • app/agent/AgentClient.ts
  • app/api/container.ts
  • app/api/container/update-policy.test.ts
  • app/api/container/update-policy.ts
  • app/store/container.test.ts
  • app/store/container.ts

…ween update and insert paths

CodeRabbit review: the '#565 empty layer carries no intent' rule was
implemented independently in updateContainer and restoreRetainedUpdatePolicy;
extract isAuthoritativeOverrideLayer so the two paths cannot drift.
@scttbnsn
scttbnsn merged commit 196fb29 into dev/v1.6 Jul 28, 2026
25 checks passed
@scttbnsn
scttbnsn deleted the fix/v1.6-565-agent-policy-overrides branch July 28, 2026 13:31
scttbnsn added a commit that referenced this pull request Jul 28, 2026
Wholesale-tree sync bringing main tree-identical to dev/v1.6 at 3331000: #624 (#565 override-wipe fix), #625 (#623 action-trigger allow-list fix), #626 (rc.8 prep), #627 (docs), #628 (GA promotion workflow fix).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant