Skip to content

feat(database): deferred trusted_sources flush for type=app refs on first deploy#44

Merged
intel352 merged 2 commits intomainfrom
feat/db-trusted-sources-deferred-update
May 2, 2026
Merged

feat(database): deferred trusted_sources flush for type=app refs on first deploy#44
intel352 merged 2 commits intomainfrom
feat/db-trusted-sources-deferred-update

Conversation

@intel352
Copy link
Copy Markdown
Contributor

@intel352 intel352 commented May 2, 2026

Summary

  • DatabaseDriver.Create and .Update now defer firewall updates when a trusted_sources entry of type: app references an app that does not yet exist (first-deploy ordering), instead of failing with a resource-not-found error.
  • DOProvider.Apply runs a second pass after the main action loop, calling FlushDeferredUpdates on any driver with pending updates — the full rule set (including resolved app UUIDs) is applied once apps are provisioned.
  • ErrAppNotFound sentinel distinguishes "app not yet created" (deferral-eligible) from API-level failures (rate-limit, transient, auth) which are never silently deferred. Flush failures are fatal and surface in ApplyResult.Errors.

Fixes GoCodeAlone/core-dump#154 (R4 first-deploy ordering finding).
See docs/plans/2026-05-02-staging-deploy-blockers-design.md (Blocker 2).

TDD Regression Invariant Proof

With fix reverted (Create propagates error on ErrAppNotFound without deferring):

$ GOWORK=off go test -v -run "TestDatabaseDriver_Create_AppNotYetExisting_DefersAndSucceeds|TestDOProvider_Apply_FlushesDeferred_AfterAllCreates" ./internal/...
--- FAIL: TestDatabaseDriver_Create_AppNotYetExisting_DefersAndSucceeds
    Create should succeed even when app ref is not yet resolvable; got: database create "my-db": trusted_sources app(s) "coredump-staging": trusted_sources app: resource not found
--- FAIL: TestDOProvider_Apply_FlushesDeferred_AfterAllCreates
    Apply result errors: [{Resource:coredump-staging-db Action:create Error:database create "coredump-staging-db": trusted_sources app(s) "coredump-staging": trusted_sources app: resource not found}]
FAIL

With fix restored:

$ GOWORK=off go test -v -run "TestDatabaseDriver_Create_AppNotYetExisting_DefersAndSucceeds|TestDOProvider_Apply_FlushesDeferred_AfterAllCreates" ./internal/...
--- PASS: TestDatabaseDriver_Create_AppNotYetExisting_DefersAndSucceeds
--- PASS: TestDOProvider_Apply_FlushesDeferred_AfterAllCreates
PASS

Test plan

  • TestDatabaseDriver_Create_AppNotYetExisting_DefersAndSucceeds — app absent → Create succeeds, no app rules, deferred queued
  • TestDatabaseDriver_Create_AppNotYetExisting_MixedRules — ip_addr+app, app absent → ip_addr rule in create request, app deferred
  • TestDatabaseDriver_Create_AppAPIFailure_NotDeferred — API error → Create fails, NOT deferred, error is not ErrAppNotFound
  • TestDatabaseDriver_Update_AppNotYetExisting_DefersAndAppliesPartialRules — Update with unresolvable app → partial rules applied, deferred queued
  • TestDatabaseDriver_FlushDeferredUpdates_FullRulesApplied — after deferred create, flush calls UpdateFirewallRules with app UUID
  • TestDatabaseDriver_FlushDeferredUpdates_APIError_ReturnsError — flush propagates UpdateFirewallRules error
  • TestDatabaseDriver_HasDeferredUpdates_FalseWhenNone — fresh driver returns false
  • TestDatabaseDriver_FlushDeferredUpdates_NoopWhenEmpty — flush on empty driver returns nil
  • TestDOProvider_Apply_FlushesDeferred_AfterAllCreates — end-to-end: provider Apply calls flush after creates, UpdateFirewallRules receives app UUID
  • All existing tests pass (GOWORK=off go test -count=1 ./internal/...)

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 2, 2026 15:35
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements a deferred-apply mechanism for infra.database trusted_sources rules of type: app so first-deploy ordering (DB created before app) no longer fails; instead, firewall rules are finalized after all plan actions complete.

Changes:

  • Add ErrAppNotFound sentinel and queue/flush logic in DatabaseDriver to defer unresolvable app-name trusted sources.
  • Add a second-pass in DOProvider.Apply to call FlushDeferredUpdates on drivers with pending deferred updates.
  • Add TDD-style regression tests covering deferral behavior and provider-level flushing, plus a changelog entry.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/drivers/database.go Adds ErrAppNotFound, defers app-name firewall rules, and implements deferred flush queueing/processing.
internal/provider.go Adds deferred-updater interface and a second-pass flush in Apply.
internal/drivers/database_deferred_test.go New tests covering create/update deferral and flush behavior (success + error cases).
internal/drivers/database_test.go Updates existing test to reflect new deferral behavior instead of failing on missing app.
internal/provider_deferred_test.go New end-to-end test ensuring Apply performs the deferred flush pass.
CHANGELOG.md Documents the new deferred trusted_sources behavior and its failure semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/provider.go
}
if flushErr := du.FlushDeferredUpdates(ctx); flushErr != nil {
result.Errors = append(result.Errors, interfaces.ActionError{
Resource: action.Resource.Name,
Comment on lines +598 to +602
ruleType := strFromConfig(m, "type", "")
ruleValue := strFromConfig(m, "value", "")
if ruleType == "" || ruleValue == "" || ruleType == "app" {
continue // skip app-type entries; they will be applied in the deferred pass
}
Comment on lines +630 to +634
ruleType := strFromConfig(m, "type", "")
ruleValue := strFromConfig(m, "value", "")
if ruleType == "" || ruleValue == "" || ruleType == "app" {
continue // skip app-type entries; they will be applied in the deferred pass
}
Comment thread internal/drivers/database.go Outdated
Comment on lines +678 to +680
d.pendingFirewallUpdates = nil // clear queue after flush
if len(errs) > 0 {
return fmt.Errorf("%s", strings.Join(errs, "; "))
Comment thread internal/provider.go
Comment on lines +320 to +337
// resources exist. Each driver type is flushed at most once.
seen := make(map[string]struct{}, len(plan.Actions))
for _, action := range plan.Actions {
if _, done := seen[action.Resource.Type]; done {
continue
}
seen[action.Resource.Type] = struct{}{}
d, dErr := p.ResourceDriver(action.Resource.Type)
if dErr != nil {
continue
}
du, ok := d.(deferredUpdater)
if !ok || !du.HasDeferredUpdates() {
continue
}
if flushErr := du.FlushDeferredUpdates(ctx); flushErr != nil {
result.Errors = append(result.Errors, interfaces.ActionError{
Resource: action.Resource.Name,
intel352 and others added 2 commits May 2, 2026 12:06
…irst deploy

When a trusted_sources entry of type=app references an app that does not yet
exist (first-deploy ordering), DatabaseDriver.Create/Update now defers the
full firewall update instead of failing. The resolvable rule subset is applied
immediately; after all plan creates complete, DOProvider.Apply calls
FlushDeferredUpdates so the full rule set (including app UUIDs) is applied in
a second pass.

API-level failures (rate-limit, transient, auth) are never silently deferred —
only ErrAppNotFound (app absent from Apps.List) triggers the deferred path.
Flush failures are fatal and surface in ApplyResult.Errors.

Fixes GoCodeAlone/core-dump#154 (R4 first-deploy ordering finding).
See docs/plans/2026-05-02-staging-deploy-blockers-design.md (Blocker 2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FlushDeferredUpdates previously cleared the entire pending queue after flush
regardless of success. Combined with Diff not comparing trusted_sources, this
left any failed entry permanently unretriable — retry Apply would produce no
update action and the second-pass flush would never fire.

Now only successfully-flushed entries are removed. Failed entries remain in
pendingFirewallUpdates so a subsequent Apply automatically re-attempts them.

Includes updated test (renamed _APIError_RetainsQueue) and CHANGELOG note.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@intel352 intel352 force-pushed the feat/db-trusted-sources-deferred-update branch from e602031 to 706138a Compare May 2, 2026 16:08
Copilot AI review requested due to automatic review settings May 2, 2026 16:08
@intel352 intel352 merged commit b754b71 into main May 2, 2026
6 checks passed
@intel352 intel352 deleted the feat/db-trusted-sources-deferred-update branch May 2, 2026 16:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/provider.go
Comment on lines +341 to +367
// Second pass: flush deferred updates accumulated by drivers during the main
// action loop. These arise when a resource's config (e.g. DB trusted_sources
// with type=app) references another resource provisioned later in the same
// plan. By this point all plan creates have completed and the referenced
// resources exist. Each driver type is flushed at most once.
seen := make(map[string]struct{}, len(plan.Actions))
for _, action := range plan.Actions {
if _, done := seen[action.Resource.Type]; done {
continue
}
seen[action.Resource.Type] = struct{}{}
d, dErr := p.ResourceDriver(action.Resource.Type)
if dErr != nil {
continue
}
du, ok := d.(deferredUpdater)
if !ok || !du.HasDeferredUpdates() {
continue
}
if flushErr := du.FlushDeferredUpdates(ctx); flushErr != nil {
result.Errors = append(result.Errors, interfaces.ActionError{
Resource: action.Resource.Name,
Action: "deferred_update",
Error: flushErr.Error(),
})
}
}
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.

2 participants