Conversation
…7-39) Task 37 — Sidecars as sibling AppServiceSpec: - Remove interfaces.KeySidecars from doUnsupportedCanonicalKeys; sidecars now advertised in SupportedCanonicalKeys(). - sidecarsFromConfig() maps each canonical sidecar to a godo.AppServiceSpec: image (ParseImageRef), run_command, env_vars/env_vars_secret, and first public port → HTTPPort. - buildAppSpec appends sidecar specs to spec.Services after the primary svc. - Tests: TestBuildAppSpec_Sidecars (2 sidecars, secret env, port mapping), TestBuildAppSpec_Sidecars_Empty (no sidecars → only main service). - provider_test.go: "sidecars" added to supported key list; negative assertion removed. Task 38 — appHealthResult: already fully implemented in v0.6.x (no-op). Task 39 — DatabaseDriver.trusted_sources: - DatabaseClient interface gains UpdateFirewallRules(). - trustedSourceCreateRulesFromConfig() → []*DatabaseCreateFirewallRule for Create. - trustedSourceFirewallRulesFromConfig() → []*DatabaseFirewallRule for Update. - Create wires Rules field; Update calls UpdateFirewallRules when present. - Tests: Create_WithTrustedSources, Update_WithTrustedSources, Update_NoTrustedSources_SkipsFirewall. - mockDatabaseClient extended with lastCreateReq/lastFirewallReq captures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…roplet) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds v0.7.0 support for canonical sidecars in App Platform build spec generation and wires canonical trusted_sources into the managed database driver’s create/update flows.
Changes:
- Map canonical
sidecarsto siblinggodo.AppServiceSpecentries inbuildAppSpec, including env vars/secrets and first public port →HTTPPort. - Advertise
sidecarsinSupportedCanonicalKeys()(and remove it from the unsupported key list). - Add
trusted_sources→ database firewall rule mapping forCreate, and conditionalUpdateFirewallRulesbehavior forUpdate(plus tests/mocks).
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/provider_test.go | Updates SupportedCanonicalKeys expectation to include sidecars. |
| internal/provider.go | Removes sidecars from the unsupported canonical keys set. |
| internal/drivers/database_test.go | Extends the mock DB client and adds tests for trusted_sources create/update behavior. |
| internal/drivers/database.go | Extends DB client interface; maps trusted_sources to create rules and (conditionally) update firewall rules. |
| internal/drivers/app_platform_buildspec_test.go | Adds coverage for sidecars mapping (services count, secret env var, public port → HTTPPort). |
| internal/drivers/app_platform_buildspec.go | Implements sidecarsFromConfig and includes sidecars as sibling services in the AppSpec. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if err == nil { | ||
| svc.Image = img | ||
| } |
There was a problem hiding this comment.
If a sidecar specifies an image but ParseImageRef fails, the code silently omits svc.Image, which can lead to a confusing downstream API error. Consider surfacing the parse error (e.g., by changing sidecarsFromConfig to return an error and plumbing it up through buildAppSpec) so misconfigured sidecar images fail fast with a clear message.
| if err == nil { | |
| svc.Image = img | |
| } | |
| if err != nil { | |
| panic(fmt.Errorf("invalid sidecar image for %q: %q: %w", name, imgStr, err)) | |
| } | |
| svc.Image = img |
There was a problem hiding this comment.
Fixed in 56d2fc1: sidecarsFromConfig now returns ([]*godo.AppServiceSpec, error). An invalid image ref returns fmt.Errorf("sidecar %q: %w", name, err) and buildAppSpec surfaces it immediately, so misconfigured sidecar images fail fast rather than being silently dropped.
| // Sync firewall rules when trusted_sources is specified. | ||
| if rules := trustedSourceFirewallRulesFromConfig(spec.Config); rules != nil { | ||
| _, err = d.client.UpdateFirewallRules(ctx, ref.ProviderID, &godo.DatabaseUpdateFirewallRulesRequest{ | ||
| Rules: rules, | ||
| }) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("database update firewall %q: %w", ref.Name, WrapGodoError(err)) |
There was a problem hiding this comment.
Update currently only calls UpdateFirewallRules when trustedSourceFirewallRulesFromConfig returns a non-nil slice. Since the helper returns nil when trusted_sources is present but an empty list, callers cannot clear existing firewall rules by setting trusted_sources: []. Consider distinguishing “key absent” vs “key present but empty” (e.g., return (rules, present) or return an empty slice when the key exists) so updates can intentionally clear rules.
There was a problem hiding this comment.
Fixed in 56d2fc1: Update now calls trustedSourceFirewallRulesFromConfig which returns ([]*DatabaseFirewallRule, bool). The bool gates the UpdateFirewallRules call: false = key absent → leave existing rules unchanged; true = key present → sync rules (including clearing when the slice is empty).
| // []*godo.DatabaseFirewallRule for use in a DatabaseUpdateFirewallRulesRequest. | ||
| func trustedSourceFirewallRulesFromConfig(cfg map[string]any) []*godo.DatabaseFirewallRule { | ||
| raw, ok := cfg["trusted_sources"].([]any) | ||
| if !ok || len(raw) == 0 { |
There was a problem hiding this comment.
trustedSourceFirewallRulesFromConfig treats an empty trusted_sources list the same as the key being absent (returns nil). If the intended semantics are “present ⇒ sync firewall, absent ⇒ leave unchanged”, the helper should be able to return an empty slice when the key exists but is empty so the caller can clear rules.
| if !ok || len(raw) == 0 { | |
| if !ok { |
There was a problem hiding this comment.
Fixed in 56d2fc1: trustedSourceFirewallRulesFromConfig now returns ([]*godo.DatabaseFirewallRule, bool). bool=false = key absent (no-op for existing rules); bool=true = key present, return whatever rules were parsed (empty slice = clear all). The type assertion on the raw value uses blank-assign so a nil/non-list value still produces true, [] (present but empty), which is correct.
| func TestDatabaseDriver_Update_NoTrustedSources_SkipsFirewall(t *testing.T) { | ||
| mock := &mockDatabaseClient{db: testDatabase()} | ||
| d := drivers.NewDatabaseDriverWithClient(mock, "nyc3") | ||
|
|
||
| _, err := d.Update(context.Background(), interfaces.ResourceRef{ | ||
| Name: "my-db", ProviderID: "db-123", | ||
| }, interfaces.ResourceSpec{ | ||
| Name: "my-db", | ||
| Config: map[string]any{"size": "db-s-2vcpu-4gb"}, | ||
| }) | ||
| if err != nil { | ||
| t.Fatalf("Update: %v", err) | ||
| } | ||
| if mock.lastFirewallReq != nil { | ||
| t.Error("UpdateFirewallRules should not be called when trusted_sources is absent") | ||
| } | ||
| } |
There was a problem hiding this comment.
There’s no test asserting the “trusted_sources present but empty” behavior (clearing firewall rules). Adding a test for trusted_sources: [] would lock in the intended semantics and prevent regressions when adjusting the nil-vs-empty handling.
There was a problem hiding this comment.
Fixed in 56d2fc1: added TestDatabaseDriver_Update_EmptyTrustedSources_ClearsFirewall — sets trusted_sources: []any{} (key present, empty), asserts UpdateFirewallRules is called and the request carries 0 rules.
| // DO App Platform has no native sidecar model; each sidecar is mapped as an independent | ||
| // service in the same app, sharing the app's network namespace via the platform routing. |
There was a problem hiding this comment.
The comment claims sidecar services “share the app’s network namespace”, which is misleading for DO App Platform (components run as separate services). Consider rewording to describe that sidecars are modeled as separate services within the same app and can communicate via platform networking, without implying a shared network namespace.
| // DO App Platform has no native sidecar model; each sidecar is mapped as an independent | |
| // service in the same app, sharing the app's network namespace via the platform routing. | |
| // DO App Platform has no native sidecar model; each sidecar is mapped as a separate | |
| // service within the same app and can communicate with other app components via the | |
| // platform's networking and routing features. |
There was a problem hiding this comment.
Fixed in 56d2fc1: comment updated to: "DO App Platform has no native sidecar concept; each sidecar becomes an independent service component in the same app. Components communicate via the app's internal networking (platform-managed DNS/routing), not via a shared Linux network namespace."
1. sidecarsFromConfig returns ([]*godo.AppServiceSpec, error); invalid image ref now propagates as an error instead of being silently dropped. buildAppSpec plumbs the error up so misconfigured sidecar images fail fast. 2+3. trustedSourceFirewallRulesFromConfig returns ([]*DatabaseFirewallRule, bool) where bool=false means "key absent" (leave existing rules unchanged) and bool=true means "key present" (sync rules, including clearing when empty). Update() now uses the bool to distinguish the two cases. 4. Add TestDatabaseDriver_Update_EmptyTrustedSources_ClearsFirewall: verifies that trusted_sources present but empty causes UpdateFirewallRules to be called with an empty rule set (clearing all existing rules). 5. Fix misleading comment in sidecarsFromConfig: replace "sharing the app's network namespace via the platform routing" with accurate description: "Components communicate via the app's internal networking (platform-managed DNS/routing), not via a shared Linux network namespace." Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t + workflow perms) 3/3 findings addressed: #12 (validate_plan.go:16) — uuidPattern was lowercase-only; UUIDs are case-insensitive in practice (operator clipboards, templating engines, mixed-case API responses). Added (?i) flag so upper-case and mixed-case VPC UUIDs also classify as deferred-to-apply, not as plain resource names that would trigger false dangling-reference diagnostics. New test: TestDOProvider_ValidatePlan_VPCRefAsUpperCaseUUIDIsDeferred. #13 (provider_conformance_test.go:86) — switched p.Initialize(context.Background(), ...) to p.Initialize(t.Context(), ...) so live-cloud Initialize is interrupted promptly when the test is canceled or hits its deadline. Removed the now-unused "context" import. #14 (codemod-report.yml:12) — dropped the unused pull-requests:write permission. The workflow only creates/updates an issue comment (PR comments are issues at the GitHub API layer) so the surviving issues:write is sufficient. Inline doc-comment captures the reasoning so future maintainers don't restore the broader grant. Aligns with ci.yml's contents:read-only baseline. Verified locally: go test ./... -count=1 -race PASS, go test -tags=conformance ./internal/ -run TestConformance PASS, go vet clean, codemod-report.yml YAML valid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lyPlan) (#61) * chore(deps): bump workflow to e2c582b (W-7 conformance + W-8 codemod) Pseudo-version v0.20.6-0.20260505011403-e2c582bece90, the workflow main HEAD that includes: - W-7: iac/conformance scenario suite (12 scenarios) and DO smoke gate - W-8: cmd/iac-codemod 4-mode AST tool Required for TP1-TP5 of PR P-DO (IaC conformance plan §P-DO). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(plugin): codemod-report workflow uploads dry-run output as artifact + sticky PR comment summary PR P-DO TP1: per pull_request, runs the iac-codemod refactor-apply mode in -dry-run against the plugin source, uploads the full Markdown report as a 90-day retention GitHub Actions artifact, and posts/updates a sticky PR comment with the top-30 lines of the report so drive-by reviewers see the key findings without downloading the artifact. Supply-chain note: actions/github-script SHA-pinned per workflow security policy (Renovate tracks upstream releases via .github/renovate.json). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(provider): collapse Apply to wfctlhelpers.ApplyPlan PR P-DO TP2: replace the in-Apply per-action switch (create/update/replace/ delete + upsert recovery + nil-out diagnostic) with a single dispatch to wfctlhelpers.ApplyPlan. The helper handles: - ErrResourceAlreadyExists upsert recovery via interfaces.UpsertSupporter (DO drivers AppPlatform, VPC, Firewall, Database already implement SupportsUpsert() bool, so they satisfy the canonical interface without code change — the local upsertSupporter declaration is now removed). - JIT ${MODULE.id} / ${VAR} substitution (W-5). - Replace decomposition + ReplaceIDMap propagation (W-3a/W-3b). - Input-drift postcondition (W-3a). - Per-action context cancellation between iterations. The DO-plugin-specific deferred-update flush (DatabaseDriver type=app trusted_sources referencing apps created later in the plan; regression gated by provider_deferred_test.go and CHANGELOG entry for staging-deploy- blockers Blocker 2) is preserved by wrapping ApplyPlan with the second- pass loop that calls FlushDeferredUpdates on any deferredUpdater driver. The wrapper deviates from the codemod's canonical 'return wfctlhelpers.ApplyPlan(ctx, p, plan)' single-statement shape; the deviation is documented and marked with // wfctl:skip-iac-codemod so AssertApplyDelegatesToHelper recognizes the intentional shape. When wfctlhelpers grows a deferred-update lifecycle hook, the wrapper can collapse and the marker can drop. The provider_apply_test.go DeleteAction_MissingCurrent regression test was a v1 pre-flight defense (synthesize an error when action.Current is nil before calling Delete). Under v2 dispatch the contract is 'driver is the authority on what an empty ProviderID means' (per wfctlhelpers/ apply.go::doUpdate's analogous comment). The test was rewritten to lock the new contract: dispatch IS made with an empty-ProviderID ref; real drivers like FirewallDriver surface the diagnostic via their typed validation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(provider): ValidatePlan for App Platform region-VPC constraints PR P-DO TP3: implement interfaces.ProviderValidator (W-4) on DOProvider to surface DO region constraints at `wfctl infra align` time before any cloud API call. Per the W-4 design, R-A10 invokes ValidatePlan via type-assertion; providers that do not implement it continue to work unchanged. The first pass covers three constraint families: 1. App Platform infra.container_service requires a region GROUP slug (nyc, ams, fra, sfo, sgp, syd, tor, blr, lon). Zone slugs (nyc1, sfo3 …) are rejected with PlanDiagnosticError. This is the "copy-pasted nyc3 from a Droplet config" defense. 2. Zone-bound resources (infra.vpc, infra.droplet, infra.volume) MUST use a zone slug. Bare group slugs are rejected with PlanDiagnosticError. Inverse of (1). 3. Cross-resource: an App Platform with vpc_ref pointing to a VPC in the same plan must have a region group whose zones include the referenced VPC's region. This locks the recurring 'App Platform in nyc cannot reach VPC in sfo3' production bug class (root-cause issue D from the conformance design). Cross-resource resolution looks at desired spec first, falls back to action.Current's Outputs["region"] for unchanged-VPC scenarios. vpc_ref pointing to a name not in the plan emits a Severity=Warning so non-strict align tolerates external VPCs while --strict escalates. ValidatePlan is read-only and makes no remote calls per the W-4 contract. Compile-time interface assertion lives at the bottom of validate_plan.go. 11 TDD tests in validate_plan_test.go cover: nil/empty plan, group-slug accepted, zone-slug rejected for AP, zone-slug accepted for VPC/Droplet/Volume, group-slug rejected for VPC, mismatch error (flagship), happy-path match, unknown-vpc_ref Warning, current-state fallback, delete-action skipped, compile-time assertion. Future extensions (deferred follow-ups): database/cache zone slugs, load balancer zone matching against attached droplets, registry regional restrictions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(plugin): opt into computePlanVersion: v2 PR P-DO TP4: declare iacProvider.computePlanVersion=v2 at the TOP LEVEL of plugin.json so wfctl's runtime dispatcher (cmd/wfctl/deploy_providers.go ::iacPluginManifest) routes Apply through wfctlhelpers.ApplyPlan instead of the legacy in-provider switch. Schema note: the SDK manifest schema (plugin/sdk/manifest_schema.json) expects iacProvider.computePlanVersion at the top level of plugin.json. The existing capabilities.iacProvider sub-block (name, resourceTypes, configSchema) is a DIFFERENT consumer (plugin discovery + capability declaration); the two structures coexist in the same file. The runtime loader unmarshals both into one struct (one Capabilities.IaCProvider.Name field plus one top-level IaCProvider.ComputePlanVersion field) so a single plugin.json read serves both code paths. Validated via three checkers: - go run ./cmd/wfctl plugin validate --file plugin.json --strict-contracts (OK) - JSON-schema validation against plugin/sdk/manifest_schema.json (OK) - sdk.ParseManifest decode confirms EffectiveComputePlanVersion()==v2 Backward compat: wfctl < v0.21.0 ignores the new field; the legacy v1 dispatch (provider.Apply switch, now wrapping wfctlhelpers.ApplyPlan) continues to work for all existing callers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(provider): add conformance test + extend ValidatePlan for database vpc_ref + bump to v0.10.0 PR P-DO TP5: ship the conformance test entry point, extend ValidatePlan to satisfy the conformance suite's cross-resource constraint scenario, and bump plugin.json + CHANGELOG to v0.10.0. provider_conformance_test.go (new, behind 'conformance' build tag): - Invokes iac/conformance.Run against a freshly-constructed and initialized DOProvider. Initialize is always called with a stub token so the driver registry is populated for the non-cloud scenarios that probe ResourceDriver lookups (structpb-roundtrip, cross-module resolution, etc.) — these exercise read-only or pure-data paths that don't hit DO's API. - LiveCloud (CONFORMANCE_LIVE_CLOUD=1 + DIGITALOCEAN_ACCESS_TOKEN) swaps the stub for the real token before driver instantiation. - SmokeOnly = testing.Short() limits to Smoke=true scenarios for the per-PR smoke gate's narrow contract. ValidatePlan extension (validate_plan.go): - Added appendDatabaseDiagnostics: infra.database vpc_ref pointing to a name not in the plan emits a Severity=Error diagnostic. Closes the conformance Scenario_CrossResourceConstraintRejection assertion that 'at least one Severity=Error diagnostic' fires for a dangling cross-resource reference. The assertion was failing before this change (database vpc_ref was previously unhandled by ValidatePlan). - Two new TDD tests: dangling-vpc_ref → Error (mirrors the conformance contract in-tree); in-plan vpc_ref → no diagnostic (happy path). plugin.json: - version 0.9.0 → 0.10.0 - download URL paths v0.9.0 → v0.10.0 (per-OS/arch) CHANGELOG.md: - New v0.10.0 section catalogues TP1-TP5 changes (ValidatePlan, computePlanVersion: v2 opt-in, Apply collapse, conformance test, codemod-report workflow), the Apply-delete v2-contract change, and the workflow dep bump. - Migrates the previously-Unreleased infra.vpc id-output fix into the v0.10.0 Fixed section (it ships in this release). Test results: - go test -tags=conformance ./internal/ -run TestConformance: 6/6 non-cloud scenarios PASS (CrossResourceConstraintRejection, DiffSurvivesGRPCRoundTrip, InfraOutputCrossModuleResolution, PlanStaleDiagnostic, ProtectedReplaceWithOverride, ProtectedReplaceWithoutOverride). Upsert-on-already-exists + grpc-roundtrip skip when their opt-in driver types are absent (DO does not expose infra.compute). - go test ./... -count=1 -race: ALL packages PASS. - go run ./cmd/wfctl plugin validate --strict-contracts: OK. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: Copilot review round 1 — 5 substantive findings 5/5 findings addressed: #1 (validate_plan.go:52) — byName index must exclude delete-action resources. Previously a vpc_ref pointing to a VPC scheduled for deletion in the same plan would silently 'resolve' as if live; now delete-targets are excluded from the index so cross-resource references to them surface as Severity=Error dangling references. New regression test: TestDOProvider_ValidatePlan_DatabaseVPCRefToDeleteTargetIsDangling. #2 (validate_plan.go:91) — appendDatabaseDiagnostics docstring incorrectly said 'Warning when missing'; the implementation has always emitted PlanDiagnosticError, and the conformance scenario + TDD tests both REQUIRE Error severity. Doc rewritten to match the implemented contract. #3 (validate_plan.go:225) — zonesInGroup docstring promised sorted output but returned the raw underlying slice unsorted. Now copies + sorts (lexicographic) so diagnostic messages are deterministic and the returned slice is owned by the caller (safe to mutate). nil for unknown groups; the caller's strings.Join still works. #4 (provider_conformance_test.go:70) — comment described a 'stub-then-real swap' with two Initialize calls; the implementation has always made one call with the right token chosen up-front. Doc rewritten to match the actual single-call flow. #5 (codemod-report.yml:73) — fork PRs run with a read-only GITHUB_TOKEN per GitHub's pull_request workflow security model, so issues:create-comment + issues:update-comment fail 403 and would block CI. Gate the comment step on github.event.pull_request.head.repo.fork == false. The artifact upload step still runs unconditionally so the report remains reachable from the Actions tab. Verified locally: go test ./... -count=1 -race PASS, go test -tags=conformance ./internal/ -run TestConformance PASS, go vet clean, codemod-report.yml YAML valid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: Copilot review round 2 — 2 vpc_ref type-mismatch findings 2/2 findings addressed (both legitimate type-safety improvements): #6 (validate_plan.go:123) — appendDatabaseDiagnostics validated that vpc_ref name resolved in the plan but did NOT validate that the target type is actually infra.vpc. A vpc_ref pointing to a Droplet or App Platform resource silently passed prior validation. Now surfaces a typed Severity=Error diagnostic when target.spec.Type != "infra.vpc". New regression test: TestDOProvider_ValidatePlan_DatabaseVPCRefToNonVPCType. #7 (validate_plan.go:189) — same bug in appendAppPlatformDiagnostics: vpc_ref pointing to a non-VPC resource would silently bypass the region-match check (target.spec.Config["region"] would be a region GROUP for an App Platform target, not a zone for a VPC) and the operator would never see a clear diagnostic. Same fix: typed Error when target.spec.Type != "infra.vpc". New regression test: TestDOProvider_ValidatePlan_AppPlatformVPCRefToNonVPCType. Both diagnostics carry the offending type in the message body so the operator immediately knows whether they typo'd a name (resolves to nothing) vs. typo'd a TYPE in a name (resolves to wrong resource). Verified locally: go test ./... -count=1 -race PASS, go test -tags=conformance ./internal/ -run TestConformance PASS, go vet clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: Copilot review round 3 — 4 deeper findings (vpc_ref UUID/template + classifyRegion + delete-test comment) 4/4 findings addressed (deeper architectural correctness): #8/#9 (validate_plan.go:137,205) — vpc_ref's accepted DO API shapes are: (a) a VPC UUID literal, OR (b) a wfctl JIT template like ${vpc.id} that resolves to a UUID at apply time via wfctlhelpers.ApplyPlan's jitsubst.ResolveSpec. The prior validator unconditionally treated vpc_ref as an in-plan resource Name and would have rejected production configs that use the canonical UUID shape (godo.AppVpcSpec{ID: vpcID} consumes it directly per internal/drivers/app_platform_buildspec.go:674). New looksLikeResourceName() helper detects UUID literals (RFC-4122 pattern) and JIT templates (${...} / $(...)), and the in-plan- name lookup is skipped for both. Only plain-name vpc_ref values trigger the dangling-reference + non-VPC-type checks. Diagnostic messages updated to call out the 'plain resource name' branch explicitly so an operator who hit the diagnostic understands it does not apply to UUID/template forms. Two new TDD tests cover the UUID-literal + JIT-template paths for both database and App Platform. #10 (validate_plan.go:307) — classifyRegion emitted 'a zone slug in group ""' for legacy zones (nyc2, ams2) that intentionally map to an empty group. Now special-cases the empty-group case to emit 'a zone slug not in any App Platform region group'. New TDD test exercises the path via an App Platform action with region=nyc2. #11 (provider_apply_test.go:94) — the test comment claimed real drivers reject empty-ProviderID deletes via typed validation; FirewallDriver.Delete actually resolves by name when ProviderID is empty. Comment rewritten to reflect that v2 dispatch contract is 'driver knows what an empty ProviderID means for its resource shape', not 'all drivers reject empty ProviderID'. Net new test count: 3 (UUID-deferred, JIT-template-deferred, classify-empty-group). All existing tests still pass. Verified locally: go test ./... -count=1 -race PASS, go test -tags=conformance ./internal/ -run TestConformance PASS, go vet clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: Copilot review round 4 — 3 polish findings (UUID case + t.Context + workflow perms) 3/3 findings addressed: #12 (validate_plan.go:16) — uuidPattern was lowercase-only; UUIDs are case-insensitive in practice (operator clipboards, templating engines, mixed-case API responses). Added (?i) flag so upper-case and mixed-case VPC UUIDs also classify as deferred-to-apply, not as plain resource names that would trigger false dangling-reference diagnostics. New test: TestDOProvider_ValidatePlan_VPCRefAsUpperCaseUUIDIsDeferred. #13 (provider_conformance_test.go:86) — switched p.Initialize(context.Background(), ...) to p.Initialize(t.Context(), ...) so live-cloud Initialize is interrupted promptly when the test is canceled or hits its deadline. Removed the now-unused "context" import. #14 (codemod-report.yml:12) — dropped the unused pull-requests:write permission. The workflow only creates/updates an issue comment (PR comments are issues at the GitHub API layer) so the surviving issues:write is sufficient. Inline doc-comment captures the reasoning so future maintainers don't restore the broader grant. Aligns with ci.yml's contents:read-only baseline. Verified locally: go test ./... -count=1 -race PASS, go test -tags=conformance ./internal/ -run TestConformance PASS, go vet clean, codemod-report.yml YAML valid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: Copilot review round 5 — 2 findings (Initialize-ctx comment + unknown-region forward-compat) 2/2 findings addressed: #15 (provider_conformance_test.go:83) — the prior comment claimed t.Context() makes Initialize cancelable, but DOProvider.Initialize today constructs its godo client with its own context.Background() and ignores the passed-in ctx. Comment rewritten to reflect that the change is forward-prep (so any future rev of Initialize that honors ctx picks up the test-scoped cancellation/deadline) rather than an immediate behavior fix. Tracked as a follow-up to thread ctx through the godo client construction. #16 (validate_plan.go:334) — the hardcoded region/zone allowlists would Severity=Error any brand-new DO region the plugin hasn't caught up to, blocking apply until the plugin is bumped. Severity is now two-bucket: - Documented misconfig (group-where-zone-required, zone-where- group-required) → Error (the original anti-pattern stays loud). - Unknown slug (neither known group nor known zone, e.g. a hypothetical 'atl' or 'atl1') → Warning so non-strict align lets operators on bleeding-edge DO regions proceed; --strict still escalates for cautious operators. New regression test: TestDOProvider_ValidatePlan_UnknownRegionSlugWarnsNotErrors covers both VPC zone and AP group unknown-slug paths. Net new test count: 1. All existing tests still PASS (the documented misconfig branches use known specific slugs that hit the Error path). Verified locally: go test ./... -count=1 -race PASS, go test -tags=conformance ./internal/ -run TestConformance PASS, go vet clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
sidecarslist to siblingAppServiceSpecentries inbuildAppSpec. Each sidecar carriesimage,run_command,env_vars/env_vars_secret, and first publicport→HTTPPort.sidecarsis now advertised inSupportedCanonicalKeys()(removed fromdoUnsupportedCanonicalKeys).appHealthResultwas already fully implemented in v0.6.x — no-op.trusted_sourcesinDatabaseDriver:CreatesetsRulesviatrustedSourceCreateRulesFromConfig;UpdatecallsUpdateFirewallRuleswhentrusted_sourcesis present and skips it when absent.DatabaseClientinterface extended withUpdateFirewallRules.Test plan
TestBuildAppSpec_Sidecars— 2 sidecars, secret env var (TS_AUTH_KEY), public port → HTTPPortTestBuildAppSpec_Sidecars_Empty— no sidecars → only main serviceTestDatabaseDriver_Create_WithTrustedSources— 2 firewall rules in create requestTestDatabaseDriver_Update_WithTrustedSources—UpdateFirewallRulescalled with 1 ruleTestDatabaseDriver_Update_NoTrustedSources_SkipsFirewall—UpdateFirewallRulesnot calledGOWORK=off go test -race ./... -count=1— all green🤖 Generated with Claude Code