Skip to content

fix(purchase): skip scheduled purchases due more than 24h ago in the fire and process sweeps - #1549

Merged
IAmJSD merged 2 commits into
LeanerCloud:mainfrom
IAmJSD:fix/issue-712-age-cap
Oct 28, 2026
Merged

IAmJSD merged 2 commits into
LeanerCloud:mainfrom
IAmJSD:fix/issue-712-age-cap

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

Root cause

fire_scheduled_purchases and process_scheduled_purchases ship opt-in on most platforms (maintainer decision on #712). When a deployment first schedules one, the first tick finds every row that became due while it was off. Both sweeps bought all of them right away, on whatever recommendations and prices each row was created with. Nothing limited how late a row could be. The maintainer's scope comment on #712 says flipping fire/process on by default waits on this due-row age cap.

Change: item 4 of the issue only

  • Where the check runs. Both sweeps check the row their running claim returned, immediately before the provider call. The fire sweep does this in fireOneDue. The process sweep uses a new claimAndExecuteWithin (claimAndExecute with a cap), called from processOneExecution.
  • Due time. For a delayed purchase (status = scheduled) the row is due at scheduled_execution_at. For every other row it is due at scheduled_date.
  • What happens to an old row. A row due more than the cap ago is refused through the existing failClaimedBeforeSubmission path, the same one the post-claim step-cap and authorization refusals use. No new status is added. The row ends:
  • Why failed and not expired. expired means "approval lapsed before anyone acted". It is written only for pending/notified rows, carries no reason and sends no alert. It also cannot be retried. Every existing "refused before submission" path ends failed + no_submission. Ladder reconcile, ramp-blocked health and the retry flow already treat that state as "nothing bought".
  • Config. The cap defaults to 24h, the same as config.LadderFireGrace, the ladder's limit for firing its own overdue tranches. Override it with SCHEDULED_PURCHASE_MAX_OVERDUE, a Go duration such as 36h. A malformed or non-positive value fails startup, like DEFAULT_TERM. When the field is unset, or the Manager is built directly, the default cap applies; there is no "no cap" setting.
  • Counts. FireResult.Stale and ProcessResult.Stale report refused rows, and the handler log lines print them.

Concurrency (money path)

The check runs after the claim CAS (scheduled -> running, or approved/pending/notified -> running), so whichever sweep wins the claim alone decides the row:

  • Overlapping sweeps (fire/fire, process/process, fire/process). Exactly one claims the row. The losers count a race loss and neither buy nor report.
  • Revoke racing the fire sweep. If the revoke CAS (scheduled -> canceled) wins, the sweep loses the claim. If the sweep wins, the revoke gets its existing 410 and the row then ends failed, unbought.
  • Approve or run-now racing the process sweep. Same CAS. A human who wins buys normally; the cap only applies to the sweeps.
  • Boundary timing. The age is measured with time.Now() after the claim. A row cannot age past the cap between a stale listing and the purchase, and a listed row read inside the cap is re-checked after its claim.
  • Terminal save fails after a refusal. Nothing was bought. The fire sweep counts the row as Errored, not Stale, and the row stays running for the reaper (existing behavior for every post-claim refusal).

The integration test runs 4 process sweeps and 4 fire sweeps at once over a backlog of overdue and fresh rows on real Postgres.

What the cap does not touch

  • Rows awaiting a human. That means manual plans, web-sourced rows, a recommendation-bearing pending row, or a row while the kill switch is off. The scheduler gate skips these before any claim, so an overdue notified row on a manual plan stays approvable by email or session until its own token TTL.
  • SQS execute_purchase. It shares claimAndExecute but passes no cap. It is not one of the two sweeps, and it executes an explicitly enqueued row.
  • Stranded-approval re-drive and the reaper. RecoverStrandedApprovals handles human-approved rows that were interrupted mid-run, with its own threshold.

Verification

All evidence below is unit tests with mock store and provider, plus integration tests on real Postgres (testcontainers) with the ramp-step fixture's counting fake provider. No cloud API was called and nothing was purchased.

Environment: AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true, Go 1.26.9.

go build ./... && go vet ./... && go vet -tags integration ./...                       # ok
go test ./internal/purchase/ -count=1                                                  # ok
go test ./internal/server/ -count=1                                                    # ok
go test ./internal/mocks/ -count=1                                                     # ok
golangci-lint run --allow-parallel-runners --build-tags integration ./internal/purchase/ ./internal/server/   # 0 issues
gocyclo -over 10 -ignore "_test\.go" <changed non-test files>                         # none over 10
go test -tags integration -count=1 -timeout 20m -run 'TestDueRowAgeCap_' ./internal/purchase/                       # PASS
go test -tags integration -count=1 -timeout 20m -run '^TestPostCASSave_ClaimLostBeforeStampBuysNothing$' ./internal/purchase/   # PASS (call site updated)

Fail-before / pass-after (Postgres integration). The new due_age_cap_integration_test.go uses only APIs that exist on main. It was copied unchanged into a clean worktree of main at 7b86e386 and run there:

  • TestDueRowAgeCap_OverlappingSweepsBuyOnlyRowsInsideTheCap
    • main: FAIL. Purchases expected 2, got 4. Fire-sweep Fired expected 1, got 2. Both overdue rows (notified 48h late, scheduled 48h late) ended completed with no reason and no failure email.
    • branch: PASS. Only the two fresh rows were bought, each once. Both overdue rows are failed with no_submission and the reason, and each got exactly one failure email across 8 overlapping sweeps.
  • TestDueRowAgeCap_RefusedRowsStayRefused
    • main: FAIL. 2 overdue rows were bought.
    • branch: PASS. Two further ticks buy nothing and send no second email.

Unit tests (due_age_cap_test.go; they use the new symbols, so they don't compile on main). They cover:

  • the age boundaries (23h, exactly 24h and 24h+1s; not yet due);
  • scheduled_execution_at overriding scheduled_date;
  • the default cap applying when unset, and a configured cap (narrower and wider than the default) being honored;
  • fire and process refusals: failed, no_submission, reason, suppressions released, one email, provider never reached, no approved_by stamp;
  • a failed save on refusal counting as Errored;
  • a manual-plan overdue row never being claimed;
  • no cap on the SQS funnel.

internal/server/app_test.go covers the env var: default, 36h, and rejection of 24, 1d, 0s and -1h.

Combined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree: queue/platform @ 400d256b = origin/main 7b86e386 plus these heads, merged in this order:

Branch Tested head
fix/issue-243-rest 007c163
fix/issue-1206 312072f
fix/issue-111-email 6d422e2
fix/issue-111-fail-closed 3817e29
fix/issue-712-age-cap a91120b
fix/issue-712-fargate aeaf0d4
fix/issue-613-legacy-ids 350e837
fix/issue-120-guards 6f38ebc
fix/issue-120-shared-predicate cb25735
fix/issue-151-account-update-keep d20cf01
fix/issue-713-upcoming-filter e73fdfb
fix/issue-263-bastion-opts 7861f32
fix/issue-43-fixture-ids 8cee203
fix/issue-177-makefile-walk 365a9d6
fix/issue-27-seller-not-registered d845f66
fix/issue-313-user-columns 6d759dd
fix/issue-353-mock-panics aac2edf
fix/issue-8-config-stamps b548ae5
fix/issue-30-monthly-totals f637921

Linux, Go 1.26.9, AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers. The full suite ran on f7c48020, which differs from this tree only by test-only commits on fix/issue-30, fix/issue-713 and fix/issue-263 that replace placeholder account-number literals with named constants:

  • Passed: go build ./..., go vet ./..., go vet -tags integration ./... and the full go test -race -short -count=1 ./....
  • Static checks: golangci-lint v2.10.1 found 0 issues on every changed package, with and without --build-tags integration. gocyclo -over 10 is silent on the changed non-test files.
  • Integration tests, every changed package that has them, one package at a time (-tags integration -count=1): internal/analytics, internal/api, internal/auth, internal/config, internal/database/postgres/migrations, internal/purchase, internal/scheduler and internal/server all pass. Migrations 000149, 000150 and 000151 apply in order on this tree.
  • Frontend: the changed jest files (approval-details, settings-accounts, api) pass (221 tests).
  • Secrets: the first run flagged 9 new fixture lines (placeholder account numbers next to an account key); fixed with named constants. Re-run on this tree: git-secrets, using the repo's scripts/setup-git-secrets.sh patterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged. scripts/test-git-secrets-allowlist.sh: 183 PASS, 0 FAIL.
  • Terraform .tftest.hcl files were not run locally (CI runs them). Nothing was applied to any cloud.

Out of scope (deliberately left out)

  • Terraform/CloudFormation schedules for any platform (other PRs), and flipping the fire/process defaults to on. Once this lands, that flip is unblocked from the age-cap side.
  • No IaC env wiring for SCHEDULED_PURCHASE_MAX_OVERDUE; the 24h default applies everywhere until a deployment sets it.
  • No test pins that NewApplication passes ScheduledPurchaseMaxOverdue into purchase.ManagerConfig (the field is unexported on the Manager). The two wiring lines are in internal/server/app.go.
  • The SQS execute_purchase path has no due-date or age check at all. That is a pre-existing gap: it is only reached by explicitly enqueued rows.

Frontend follow-up for the UI owner: none required. History already shows a failed row's error text and offers Retry. An optional later nicety would be a distinct badge for "skipped: overdue" failures.

Refs #712

Maintainers: please mirror #712's triage labels onto this PR: priority/p1, severity/high, urgency/this-sprint, impact/many, effort/m, type/bug, triaged.

🤖 Generated with Claude Code

…fire and process sweeps

The fire and process sweeps ship opt-in on most platforms, so the first tick
after one is scheduled can find a long backlog of overdue rows. Until now
every one of them was bought on that tick, on whatever recommendations and
prices the row was created with.

Both sweeps now check the row their "running" claim returned, right before
the provider call. A row due more than the cap ago (scheduled_execution_at
for a delayed purchase, scheduled_date otherwise) is refused before
submission through the existing failClaimedBeforeSubmission path. It ends
failed with no_submission set and the reason in its error, its suppressions
are released, and operators get the purchase-failed alert once. It can be
retried from History. The check runs after the claim, so overlapping sweeps,
a revoke or an approve can't buy or double-report a row the cap refused.
Rows still awaiting human approval are never claimed by the sweep, so the
cap doesn't touch them. The SQS execute_purchase path keeps no cap.

The cap defaults to 24h (matching LadderFireGrace) and can be set with
SCHEDULED_PURCHASE_MAX_OVERDUE (a Go duration). A malformed or non-positive
value fails startup. FireResult and ProcessResult report the refused rows
as stale.

Refs LeanerCloud#712

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added urgency/this-sprint Within the current sprint triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm impact/many Affects most users effort/m Days type/bug Defect labels Oct 28, 2026
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

govulncheck in the Docker image build now fails on these two advisories,
both fixed in x/net v0.62.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD

IAmJSD commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE be5cf131852bd83aa3aa2b3b31ca2ace6acbfe8d

Blocking:

  • none

Non-blocking:

  • internal/server/app.go loadScheduledPurchaseMaxOverdue: any positive duration is accepted, including values shorter than the sweep cadence. With SCHEDULED_PURCHASE_MAX_OVERDUE=30m and the default process_scheduled_purchases_schedule = rate(1 hour), a row that comes due just after a tick is 30-60 minutes late at the next one, so most auto-purchase rows would be failed instead of bought. The same thing happens with the 24h default if an operator sets the process sweep to rate(1 day): a row due just after a tick is about 24h plus jitter late. This fails safe (nothing is bought), but it is an easy misconfiguration. Consider a floor such as >= 1h, or a sentence in docs/DEPLOYMENT.md next to the schedule variables. The env var is currently documented only in .env.example.
  • A deployment that enables the sweeps on top of a long backlog sends one purchase-failed email per overdue row on the first tick. That matches the "notify once per row" design, but it can be a burst of emails. Worth a release-note line.
  • Product note: an overdue auto-purchase row is now terminal. The only way back is Retry from History, which creates a web-sourced row that needs approval. This is what fix(iac): several scheduled tasks are not scheduled; delayed purchases never fire on GCP/Azure #712's scope comment asks for. Flagging it so it is a conscious choice.

Attacked:

  • Head moved from a91120b to be5cf13 while I was reviewing. The new commit is the x/net v0.62.0 bump, byte-identical to fix(deps): bump golang.org/x/net to v0.62.0 (GO-2026-6860, GO-2026-6866) #1564 (go.mod/go.sum only). I reviewed and tested the full change at a91120b, then re-ran go build ./... and the purchase/server -race -short tests at be5cf13: ok.
  • Money-path ordering: both checks run on the row returned by the running claim CAS. fireOneDue checks it after TransitionExecutionStatus(scheduled -> running) and before stampClaimedAudit and the provider. The process sweep checks it in claimedRowRefusal before the step-cap recheck and executeAndFinalize. The claim's RETURNING includes scheduled_execution_at and scheduled_date (NOT NULL), so dueAt reads the claimed values. A failed terminal save surfaces as ErrAuditLoss and is counted as Errored, not Stale. The SQS path (claimAndExecute -> cap 0) and RecoverStrandedApprovals are untouched. The only production NewManager call sites (app.go:521, :902) both pass the cap.
  • Wrongly refused fresh rows: I traced every place scheduled_date is set. Plan-step attach uses max(NextExecutionDate, now+NotificationDaysBefore). Root rows are only attached outside the veto window. Retry and direct rows are cudly-web and skipped by the gate. Delayed rows use scheduled_execution_at = approve+delay. None of them is born more than 24h overdue under the default cadences (fire 15m, process 1h).
  • Fail-before: I reverted manager.go and scheduled_fire.go to origin/main, removed due_age_cap.go and its unit test, and kept due_age_cap_integration_test.go. TestDueRowAgeCap_OverlappingSweepsBuyOnlyRowsInsideTheCap then fails: purchases 4 instead of 2, fired 2 instead of 1, overdue rows completed with no reason or email. After restoring the PR code, both tests pass.
  • Ran: go build ./.... go vet on purchase and server, with and without -tags integration. go test -race -short -count=1 ./internal/purchase/ ./internal/server/: ok. go test -race -tags integration -count=1 ./internal/purchase/ (ok, 107s) and ./internal/server/ (ok, 182s). golangci-lint v2.10.1 on both packages, with and without the integration tag: 0 issues. gocyclo -over 10 on the changed non-test files: silent.

Body-stale:

  • none

Reviewer: rG3. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths.

@IAmJSD

IAmJSD commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE be5cf131852bd83aa3aa2b3b31ca2ace6acbfe8d

Blocking:

  • none

Non-blocking:

  • internal/purchase/manager.go:994 (processOneExecution): if the terminal save after an age refusal fails, failClaimedBeforeSubmission returns ErrAuditLoss wrapping the refusal, so errors.Is(execErr, ErrDueRowTooOld) still matches and the row counts as Stale even though it stays running for the reaper. The fire sweep counts the same case as Errored (scheduled_fire.go:119). Only the log counters are affected; it still counts as Failed and nothing is bought.
  • internal/purchase/due_age_cap.go:37 (dueAt): this uses scheduled_execution_at whenever it is non-nil, not only for status = scheduled as the PR body says. That is fine today, because only the delayed-approve path sets it. A status check, or a comment saying the two must stay paired, would stop a future writer from silently moving the due time of a pending row.
  • Behaviour the owner should know about (as designed, not a defect): a delayed purchase that a human approved also gets refused if the fire sweep was down for more than 24h. The same goes for every auto-plan row once the global auto-purchase kill switch is turned back on after more than 24h off. Each refused row sends its own failure email.

Attacked:

  • Traced both sweeps' claim paths. The check runs on the row returned by the CAS (the RETURNING scan carries scheduled_date and scheduled_execution_at; the integration test's fresh rows complete, which proves it). Human approve and run-now CAS straight to running (approvals.go), so a human-approved row never shows up in the process sweep's approved claim window. RecoverStrandedApprovals and SQS stay uncapped. The ladder's ClaimDueLadderTranches has no production caller, so ladder rows cannot hit the cap. The IaC cadences (fire 15m, process 1h) sit well inside 24h.
  • Ran go build ./..., go vet (with and without -tags integration) and go test -race -short -count=1 on ./internal/purchase and ./internal/server: all OK. golangci-lint v2.10.1 found 0 issues with and without the integration tag, and gocyclo -over 10 is silent on the changed files. go test -race -tags integration -run 'TestDueRowAgeCap_|TestPostCASSave': PASS.
  • Fail-before: reverted manager.go, scheduled_fire.go, internal/server and the call-site test edits to origin/main, removed due_age_cap{,_test}.go, and ran the two TestDueRowAgeCap_ integration tests. Both FAIL: 4 purchases instead of 2, Fired=2, both overdue rows completed with no reason and no email, and in the second test 2 overdue rows were bought. Restored afterwards.

Body-stale:

Reviewer: rH1. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths.

@IAmJSD

IAmJSD commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification at be5cf131852bd83aa3aa2b3b31ca2ace6acbfe8d merged onto main (bbf0a839), together with #1552, #1553, #1551, #1246, #1548, #1549, #1554, #1560 in that order (combined tree 7f9817d4, Linux, Go 1.26.10): go build ./..., go vet with and without -tags integration, the full go test -race -short -count=1 ./..., and go test -race -tags integration -count=1 on every changed package, against Docker Postgres. All pass. CI Success and pre-commit are green on this head, with 2 approving independent review(s) on it. Nothing was run against any cloud.

@IAmJSD
IAmJSD marked this pull request as ready for review October 28, 2026 03:14
@IAmJSD
IAmJSD requested a review from cristim as a code owner October 28, 2026 03:14
@IAmJSD
IAmJSD merged commit 8c575f7 into LeanerCloud:main Oct 28, 2026
41 checks passed
@IAmJSD

IAmJSD commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 2 approving independent review(s) on be5cf131 (two required: money, auth or security path), green CI, and the combined-tree verification above. Squash-merged at exactly be5cf131852bd83aa3aa2b3b31ca2ace6acbfe8d.

IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 28, 2026
…llection

Conflict in internal/server/app_test.go (TestLoadApplicationConfig): kept
both RECOMMENDATION_SCHEDULE (this branch) and main's
SCHEDULED_PURCHASE_MAX_OVERDUE (LeanerCloud#1549) in the env list, setters and
assertions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 28, 2026
…in gateHarness

Main's TestProcessScheduledPurchases_CapNeverTouchesRowsAwaitingApproval
(LeanerCloud#1549) drives gateHarness with an AutoPurchase=false plan. The gate skips a
manual plan before it reads the global config, so the required
GetGlobalConfig expectation failed under assertMocksOnCleanup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit that referenced this pull request Oct 28, 2026
…1570)

* test(purchase): assert mock expectations across the purchase tests

Every internal/purchase test that registers a required .On(...) now fails
when that call never happens. A shared assertMocksOnCleanup(t, mocks...)
helper registers AssertExpectations as a t.Cleanup at mock construction (or
in the shared harness: newApproveManager, armedHarness, newDetachFixture,
attachManager callers, tokenCapture, runFanOutCapturingOpts and others).

Expectations that turned out never to fire were stubs for a path the test
does not take, not missed production calls. They are now .Maybe() with a
comment, or made conditional on the case that takes the path:
- newPinManager: per-path entry reads (cron lists vs SQS GetExecutionByID)
- gate tests: the kill-switch read for a missing plan (refused first)
- traps the test asserts NotCalled (plan lookup for "", the running CAS,
  the failed-email for non-failed statuses, non-alerting writers)
- shared stubs on paths that never reach them (optionalCalls helper)

No production code changes.

Refs #35

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(purchase): make the kill-switch read optional for a manual plan in gateHarness

Main's TestProcessScheduledPurchases_CapNeverTouchesRowsAwaitingApproval
(#1549) drives gateHarness with an AutoPurchase=false plan. The gate skips a
manual plan before it reads the global config, so the required
GetGlobalConfig expectation failed under assertMocksOnCleanup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit that referenced this pull request Oct 28, 2026
…ons at 02:00 UTC (#1580)

* fix(iac): fire delayed purchases by default; collect AWS recommendations at 02:00 UTC

Owner decision D33 (#712): the fire_scheduled_purchases sweep is now on by
default on every platform. enable_fire_scheduled_purchases_schedule defaults
to true in the aws, azure and gcp environment roots and in the Lambda,
Fargate, Cloud Run, GKE, Container Apps and AKS modules, and the
CloudFormation EnableFireScheduledPurchasesSchedule parameter defaults to
"true". The sweep only executes purchases already approved for a later
time, and the 24h due-row age cap (#1549) refuses and alerts on anything
older, so the first run cannot buy a stale backlog. The process sweep and
every other opt-in toggle stay off.

The Go policy test now treats fire as default-on (it would flag a committed
AWS tfvars that turns it off), and the module terraform tests expect the
fire job by default and cover the opt-out.

Owner decision D32 (#756): the AWS recommendation_schedule default moves
from rate(1 day) to cron(0 2 * * ? *), 02:00 UTC like GCP and Azure, in
the env root, the Lambda and Fargate modules, the github-*.tfvars and the
example tfvars. A rate() schedule has no known phase, so the freshness
endpoint's next_collection_at was always null on AWS. A new test reads
every default and committed value and checks the API's NextCollectionAt
returns the next 02:00 UTC for each.

Refs #712
Refs #756

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(changelog): note that bastion target roles must trust the bastion

#1556 assumes a bastion account's target role with the bastion's own
credentials and no longer falls back to the CUDly host's, so a target role
that trusts the host instead of the bastion now gets AccessDenied until its
trust policy names the bastion.

Refs #712

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* revert(cfn): leave the CloudFormation install stack to its retirement

#1432 (D29) deletes cloudformation/stacks/CUDly/ and
scheduled_task_cfn_test.go, so the CloudFormation edits in this branch
would only cause modify/delete conflicts. Restore both files to main and
drop CloudFormation from the CHANGELOG entry. The fire sweep's default
changes on the Terraform platforms only.

Until #1432 lands, TestCloudFormationSchedulesEveryRequiredTask and the
"opt-in task on by default" negative in that file fail on this branch
alone, because the policy now treats fire as default-on and the retiring
template still defaults it off; #1432 removes both tests.

Refs #712

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(iac): give the schedule-default test its own default regex

TestRecommendationScheduleDefaultsReportNextCollection borrowed
tfStringDefaultRe from scheduled_task_cfn_test.go, which #1432 deletes;
the combined tree would not compile. Declare a local copy instead.

Refs #712
Refs #756

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/many Affects most users priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant