Summary
Evaluate adding an opt-in force_destroy (default false) attribute to kernel_browser_pool, so terraform destroy can tear down a pool that has live/leased browser sessions instead of failing with pool_in_use.
Raised during PR #3 review: DeleteBrowserPool hardcodes Force: kernel.Bool(false) (client.go:223).
Why this is deferred, not just a missing knob
Force: false implements a deliberate non-goal, not an arbitrary default. docs/architecture.md lists "force-release/recovery operations" as out of scope (line 52) and bars internal/kernelclient from exposing SDK runtime methods like force-release (line 85), enforced as a compile-time boundary.
The reasoning: Terraform converges on durable state. A pool is durable infra; the browser sessions leased from it are runtime, owned by whoever is actively driving them. A force=true destroy reaches across and terminates that live work — exactly the runtime control the declarative model is meant to keep out. The pool_in_use error is the intended recoverable path: drain/release, then retry (resource.go:271).
So enabling force_destroy is an architecture amendment, requiring:
docs/architecture.md updated to carve out the opt-in delete exception
docs/release.md security-checklist line "Delete uses force=false" reworded
- reviewer sign-off (the boundary was an explicit decision)
Recommendation
Hold for v0. The failure is already safe and recoverable, the blast radius is other actors' live sessions (and default-false doesn't protect an operator who sets it once and forgets), and it's easy to add later but painful to remove once shipped (Hyrum's law). No demonstrated demand yet.
Revisit when a concrete workflow needs it — most likely ephemeral pools in CI hitting repeated stuck destroys. That use case also pins down the exact semantics (hard-kill vs drain-then-delete, polling, timeout) that we'd otherwise be guessing at.
Scope if approved (~80–150 lines across ~7 files, not a one-liner)
model.go: add ForceDestroy types.Bool
schema.go: Optional + Computed bool, default false (booldefault.StaticBool), description
client.go:223: add force bool param to DeleteBrowserPool — ripples through two consumer interfaces that redeclare it (resource.go:28, acctest.go:92) plus both call sites
resource.go delete handler: read state.ForceDestroy, thread it through, update the pool_in_use diagnostic
- Design wrinkle:
force_destroy is a client-only attribute the API never stores or returns (same class as project_id). Read can't populate it, so it must be preserved from prior state or it shows perpetual drift — handle like the AWS provider's force_destroy.
- Rewrite
TestDeleteBrowserPoolDoesNotForceRuntimeCleanup into default-false + true-when-set cases
- Update
schema_test.go expected-attributes map + bool-attribute assertion
- Regenerate Terraform docs (
scripts/check-docs.sh gate)
- Acceptance test for the force path
- Land as a new PR on top of the stack (the vertical slice is coherent at the tip), not threaded into existing stack commits
Precedent: AWS provider force_destroy on S3 buckets / IAM users (opt-in, default false).
Summary
Evaluate adding an opt-in
force_destroy(defaultfalse) attribute tokernel_browser_pool, soterraform destroycan tear down a pool that has live/leased browser sessions instead of failing withpool_in_use.Raised during PR #3 review:
DeleteBrowserPoolhardcodesForce: kernel.Bool(false)(client.go:223).Why this is deferred, not just a missing knob
Force: falseimplements a deliberate non-goal, not an arbitrary default.docs/architecture.mdlists "force-release/recovery operations" as out of scope (line 52) and barsinternal/kernelclientfrom exposing SDK runtime methods like force-release (line 85), enforced as a compile-time boundary.The reasoning: Terraform converges on durable state. A pool is durable infra; the browser sessions leased from it are runtime, owned by whoever is actively driving them. A
force=truedestroy reaches across and terminates that live work — exactly the runtime control the declarative model is meant to keep out. Thepool_in_useerror is the intended recoverable path: drain/release, then retry (resource.go:271).So enabling
force_destroyis an architecture amendment, requiring:docs/architecture.mdupdated to carve out the opt-in delete exceptiondocs/release.mdsecurity-checklist line "Delete usesforce=false" rewordedRecommendation
Hold for v0. The failure is already safe and recoverable, the blast radius is other actors' live sessions (and default-false doesn't protect an operator who sets it once and forgets), and it's easy to add later but painful to remove once shipped (Hyrum's law). No demonstrated demand yet.
Revisit when a concrete workflow needs it — most likely ephemeral pools in CI hitting repeated stuck destroys. That use case also pins down the exact semantics (hard-kill vs drain-then-delete, polling, timeout) that we'd otherwise be guessing at.
Scope if approved (~80–150 lines across ~7 files, not a one-liner)
model.go: addForceDestroy types.Boolschema.go:Optional + Computedbool, default false (booldefault.StaticBool), descriptionclient.go:223: addforce boolparam toDeleteBrowserPool— ripples through two consumer interfaces that redeclare it (resource.go:28,acctest.go:92) plus both call sitesresource.godelete handler: readstate.ForceDestroy, thread it through, update thepool_in_usediagnosticforce_destroyis a client-only attribute the API never stores or returns (same class asproject_id). Read can't populate it, so it must be preserved from prior state or it shows perpetual drift — handle like the AWS provider'sforce_destroy.TestDeleteBrowserPoolDoesNotForceRuntimeCleanupinto default-false + true-when-set casesschema_test.goexpected-attributes map + bool-attribute assertionscripts/check-docs.shgate)Precedent: AWS provider
force_destroyon S3 buckets / IAM users (opt-in, default false).