diff --git a/infrastructure/cloud-compose.mdx b/infrastructure/cloud-compose.mdx index 4dc212e..1438425 100644 --- a/infrastructure/cloud-compose.mdx +++ b/infrastructure/cloud-compose.mdx @@ -164,7 +164,7 @@ release-specific IAM instructions. Do not copy an unpublished foundation module from a development branch into an otherwise pinned production stack. -The [July 17, 2026 release-status snapshot](/infrastructure/current-release-status) records cloud-compose `1.5.0` and the signed PPB `0.5.1` image as released self-hosted dependencies. That does not make the separate managed shared-router/private-PPB integration generally available; its API, edge, and end-to-end promotion gates remain independent. +The [July 19, 2026 release-status snapshot](/infrastructure/current-release-status) records cloud-compose `1.5.0` and the signed PPB `0.5.1` image as released self-hosted dependencies. That does not make the separate managed shared-router/private-PPB integration generally available; its API, edge, and end-to-end promotion gates remain independent. The optional Cloud Run power-management ingress reaches the VM's private diff --git a/infrastructure/current-release-status.mdx b/infrastructure/current-release-status.mdx index af7669b..6cf4a81 100644 --- a/infrastructure/current-release-status.mdx +++ b/infrastructure/current-release-status.mdx @@ -7,7 +7,7 @@ description: "A dated availability record for the independently released artifac There is not yet a published, platform-wide known-good release set for the managed shared-router and private-PPB request path. The architecture pages describe the target contract, but they are not evidence that this integration is generally available. Do not change production DNS or infrastructure for that path until a later status record identifies every immutable artifact and its green end-to-end release gate. -This snapshot was reviewed on **July 17, 2026 at 02:44 UTC**. It is a release record, not a moving "latest" lookup. A later tag does not silently update the compatibility claims on this page. +This snapshot was reviewed on **July 19, 2026 at 01:15 UTC**. It is a release record, not a moving "latest" lookup. A later tag does not silently update the compatibility claims on this page. ## Status meanings @@ -24,10 +24,10 @@ This snapshot was reviewed on **July 17, 2026 at 02:44 UTC**. It is a release re | Surface | Dated reference | Status | What this record proves | | --- | --- | --- | --- | -| Last published generated `sitectl` documentation set | [Generated documentation dependency manifest](https://github.com/libops/sitectl-docs/blob/214049330081000a57f3179f9bcf3d151af03fb6/scripts/snippet-dependencies.json) | Released docs set | The documentation was generated from the exact commits in that manifest: core `sitectl` `v1.0.1`, `sitectl-isle` `v1.0.1`, the other self-hosted application plugins at `v1.0.0` including `sitectl-drupal`, and managed-platform `sitectl-libops` at `v1.3.0`. The core and ISLE patches canonicalize the displayed spelling of `superseded` while retaining legacy input and RPC compatibility; exact-main documentation CI passed. | +| Last published generated `sitectl` documentation set | [Generated documentation dependency manifest](https://github.com/libops/sitectl-docs/blob/bb890d50726a036938bcae0c451040472343d65d/scripts/snippet-dependencies.json) | Released docs set | The documentation was generated from the exact commits in that manifest: core `sitectl` `v1.0.2`, `sitectl-isle` `v1.0.1`, the other self-hosted application plugins at `v1.0.0` including `sitectl-drupal`, and managed-platform `sitectl-libops` at `v1.4.0`. Core emits the canonical `superseded` RPC spelling while retaining legacy input compatibility; the ISLE patch canonicalizes its displayed spelling. The Google domain lifecycle commands and recovery-safe shared publisher contract are included, and exact-main documentation CI passed. | | cloud-compose | [`1.5.0`](https://github.com/libops/cloud-compose/releases/tag/1.5.0) | Released dependency | Provider isolation, the GCP-only compatibility root, compiled CI helper, exact per-run smoke ownership, rollback-preserving Docker cleanup, and coordinated sitectl v1 presets passed Terraform/configuration lint plus hosted GCP, DigitalOcean, Linode, Ansible, Salt, and fresh-install gates. Its rootfs and checksum release assets were also published successfully. | | Self-hosted application baseline | [cloud-compose `1.5.0` template presets and released matrix](/templates/compose-projects#released-compatibility-baseline) | Published compatibility baseline | Every preset pins core `sitectl` and its independently released application plugin at `v1.0.0`. Application templates are `v1.0.0`, except ISLE `v1.1.0`; direct image identities are immutable where the template owns them, while explicitly component-controlled Islandora images use the catalog's reviewed `ISLANDORA_TAG=6.3.19` default. The matrix does not claim that all seven applications ran on all five infrastructure adapters in one aggregate test. | -| ISLE v1 releases | [`libops/isle` `v1.1.0`](https://github.com/libops/isle/tree/v1.1.0), core [`sitectl` `v1.0.1`](https://github.com/libops/sitectl/releases/tag/v1.0.1), [`sitectl-drupal` `v1.0.0`](https://github.com/libops/sitectl-drupal/releases/tag/v1.0.0), [`sitectl-isle` `v1.0.1`](https://github.com/libops/sitectl-isle/releases/tag/v1.0.1), and [cloud-compose `1.5.0`](https://github.com/libops/cloud-compose/releases/tag/1.5.0) | Released dependency | The template and DigitalOcean catalog smoke prove the cloud-compose preset recorded with core, Drupal, and ISLE plugins at `v1.0.0`. Core and ISLE subsequently published `v1.0.1` spelling-only compatibility patches with binary and package release gates; those patches do not rewrite the cloud-compose `1.5.0` compatibility baseline. The cloud catalog supplies the minimum supported Islandora image tag without overriding an explicit downstream value. | +| ISLE v1 releases | [`libops/isle` `v1.1.0`](https://github.com/libops/isle/tree/v1.1.0), core [`sitectl` `v1.0.2`](https://github.com/libops/sitectl/releases/tag/v1.0.2), [`sitectl-drupal` `v1.0.0`](https://github.com/libops/sitectl-drupal/releases/tag/v1.0.0), [`sitectl-isle` `v1.0.1`](https://github.com/libops/sitectl-isle/releases/tag/v1.0.1), and [cloud-compose `1.5.0`](https://github.com/libops/cloud-compose/releases/tag/1.5.0) | Released dependency | The template and DigitalOcean catalog smoke prove the cloud-compose preset recorded with core, Drupal, and ISLE plugins at `v1.0.0`. Core `v1.0.2` and ISLE `v1.0.1` subsequently published canonical-spelling compatibility patches with binary and package release gates; those patches do not rewrite the cloud-compose `1.5.0` compatibility baseline. The cloud catalog supplies the minimum supported Islandora image tag without overriding an explicit downstream value. | | Terraform Cloud Run v2 module | [`0.8.0`](https://github.com/libops/terraform-cloudrun-v2/releases/tag/0.8.0) | Released dependency | The reusable module release exists. That does not prove that cloud-compose or the managed control plane has promoted every module capability. | | PPB support image | [`0.5.1`](https://github.com/libops/ppb/releases/tag/0.5.1) at `sha256:249697fe2ce7e007053af270be2d5cb064ffa545572a035e405e2763298149bc` | Released dependency | GHCR and public GAR expose the same multi-platform manifest. Its keyless signature verified against the pinned shared publisher workflow identity. This proves the support image release, not the unreleased managed router integration. | | Buildkit images and Compose templates | The exact template checkout and each `tag@sha256:digest` it records | Unrecorded | Templates record immutable direct-image identities, but this snapshot does not contain a generated aggregate of every template commit, Buildkit digest, and hosted smoke-test result. | @@ -38,8 +38,8 @@ The table deliberately does not invent an image digest, template commit, signatu | Integration | Status | Gate that remains | | --- | --- | --- | -| Cloud DNS and Certificate Manager through the global load balancer, Cloud Armor, shared Cloud Run router, and private per-site PPB | Blocked / preview | PPB `0.5.1` is available, but the exact edge-controller and router images, DNS delegation, certificate-map lifecycle, API/router integration, managed Terraform pins, private-origin, client-IP, authorization-preservation, split timeout, Direct VPC, and hosted canaries still need promotion as one managed set. Cloud CDN remains disabled. | -| Organization Vault three-image runtime | Blocked / preview | The verified-publisher desired state is merged, but the public GAR writer migration has not passed a successful central Terraform apply and protected-workflow publication canary; the Vault Init GAR upload still fails closed. Apply and verify that central state, publish and verify the independently versioned `vault-server`, `vault-init`, and `vault-proxy` manifests, release the sitectl-admin digest resolver and exact tag-commit/signature gate, pin all three immutable GAR manifests, and pass the API, Terraform, initialization, recovery, and rollback gates. A source release, local candidate, or reserved version is not an aggregate runtime release. | +| Cloud DNS and Certificate Manager through the global load balancer, Cloud Armor, shared Cloud Run router, and private per-site PPB | Blocked / preview | PPB `0.5.1` is available, but the exact independently built site-router, edge-controller, and edge-provider-mutator image digests, ordered Pub/Sub mutation delivery, static Certificate Manager deny boundary, child-zone delegation lifecycle, two-phase organization DNS teardown with persisted TTL high-water and recursive-plus-parent-authority absence proof, exact-service-account state-gateway authentication, transactional observed-state outbox, managed Terraform pins, private-origin, client-IP, authorization-preservation, split timeout, Direct VPC, and hosted canaries still need promotion as one managed set. Cloud CDN remains disabled. | +| Organization Vault three-image runtime | Blocked / preview | The shared publisher and verified WIF selector have passed protected-main publication for `vault-server`, released `vault-init` `1.0.6`, and released `vault-proxy` `2.0.3` through the cleanup-safe shared workflow. The aggregate runtime is still blocked until sitectl-admin's digest resolver and exact tag-commit/signature gate are released, all three independently built GAR manifests are pinned by digest, and the hosted API, Terraform, initialization, recovery, and rollback gates pass. Independently green image publications are not an aggregate runtime release. | | Canonical API image set and production VM resolver | Blocked / preview | Merge the hosted post-CI publisher and `sitectl admin terraform api-compose-images`; publish the exact protected-main run to GHCR plus the appropriate private or public GAR repository; verify every digest's reusable-workflow identity, caller repository/ref/SHA, and caller-workflow annotation; and prove fresh VM bootstrap plus in-place refresh with the four verified private-GAR Compose images, the checkout detached at the publication commit, and legacy boot-disk discovery that fails on ambiguity. | | Separate request-serving API and `api-worker` Cloud Run services | Preview | Release the managed worker deployment and prove identity bootstrap, database connectivity, readiness, rollout, rollback, and removal of any temporary migration privilege. A process boundary in source or Compose is not proof of this Cloud Run topology. | | Platform-wide image, template, CLI, plugin, and infrastructure compatibility manifest | Blocked | Generate the aggregate record from release automation and attach hosted CI evidence for the exact references. Until then, each operator owns a deployment-specific record. | @@ -53,7 +53,11 @@ A future status entry can change a managed integration to released only when it - every template release or full commit; - core `sitectl` and each plugin's independent package version; - the cloud-compose release or full commit, reusable Terraform-module releases, and provider lockfiles; -- the API, worker, router, and PPB release identities; +- the API, worker, site-router, edge-controller, edge-provider-mutator, and PPB + release identities; +- hosted proof that organization DNS teardown removes provider resources, + child records, parent delegation, drained child zone, and foundation IAM in + that order without bypassing the full published TTL; and - the exact hosted smoke, upgrade, migration, canary, and rollback evidence. diff --git a/platform/adoption-model.mdx b/platform/adoption-model.mdx index dbc83dd..1146a67 100644 --- a/platform/adoption-model.mdx +++ b/platform/adoption-model.mdx @@ -141,28 +141,45 @@ Managed Terraform keeps long-lived ownership narrow: | State | Owner | | --- | --- | -| Foundation | Customer folder, organization project, protected state bucket, and Terraform runner identity/job | +| Foundation | Customer folder, organization project, protected state bucket, Terraform runner identity/job, and exact conditional IAM for that runner's NS record in the existing `libops.site` parent zone | | Organization | Shared organization network and services, Vault runtime, events, and model integrations | | Vault configuration | Mounts, policies, and workload-auth configuration | | Project | One customer project plus its complete runtime and site infrastructure inventory | -| Edge | Public hostname and edge-routing resources | +| Edge | Organization child Cloud DNS zone, parent NS delegation, child records, restricted controller IAM, and public hostname routing inputs | Creation follows foundation, organization, Vault configuration, project, then -edge; deletion reverses that order. Deletion is staged and terminal: children -must be retired before their parent, externally billed project capacity must be -removed idempotently before the local tombstone commits, and a project is not -marked decommissioned until its final infrastructure apply succeeds. An -organization delete does not silently cascade through projects; every retained -project must already be terminal and decommissioned, with billing in a terminal -`canceled` or `incomplete_expired` state (or no subscription). A site does not -receive an independent Terraform state, and one state -must not manage a resource owned by another root. +edge; deletion reverses that order. Foundation does not create the child DNS +zone or its delegation: it creates only the project, runner, and exact +conditional permission to change that organization's NS record in the +existing parent zone. The edge state is the sole Terraform owner of the child +zone, parent delegation, child records, and child-zone controller IAM. + +Deletion is staged and terminal. Edge provider resources are removed first. +The first destructive edge apply removes child records and then the parent NS +while retaining the empty child zone and controller IAM. The control plane +persists the successful removal time and highest published delegation TTL, +waits that full interval, and then requires the exact NS record to be absent +from both a trusted recursive view and every current parent authoritative +server. Only a later edge apply may delete the empty `force_destroy = false` +zone and controller IAM. Vault, organization, and foundation state remain +blocked until that final edge apply succeeds; foundation's parent-zone IAM is +removed last. + +Externally billed project capacity must likewise be removed idempotently before +the local tombstone commits, and a project is not marked decommissioned until +its final infrastructure apply succeeds. An organization delete does not +silently cascade through projects; every retained project must already be +terminal and decommissioned, with billing in a terminal `canceled` or +`incomplete_expired` state (or no subscription). A site does not receive an +independent Terraform state, and one state must not manage a resource owned by +another root. Downstream operators must not bypass a managed phase by manually +deleting the delegation, child zone, or foundation IAM. The shared-router/private-PPB integration is a target architecture and is not generally released in the [current status snapshot](/infrastructure/current-release-status). The ownership boundary remains valid, but the linked router identity, canonical client-IP, and timeout details must not be treated as the current production topology until their release gate is complete. -In that target path, controller and site delivery use private network traffic but remain authenticated. Shared project state owns the Cloud Run service identity, network-use permissions, and regional network capacity; runtime state owns instance-scoped power grants and VM firewall rules. The public site path still enters through Cloud DNS, the global external Application Load Balancer, Cloud Armor, and the shared router because Direct VPC provides Cloud Run **egress**, not service ingress. See [Security and Operations](/platform/security-operations) for the target router-to-PPB identity, canonical client-IP, subnet, startup, and timeout contracts. +In that target path, deployment `controller-ingress` and site PPB delivery are the only services that use Direct VPC egress to private VM addresses; both remain authenticated. Shared project state owns the Cloud Run service identity, network-use permissions, and regional network capacity; runtime state owns instance-scoped power grants and VM firewall rules. The Google edge-controller is not on this private path: it observes Certificate Manager read-only, publishes ordered mutation commands, calls the authenticated API state gateway over HTTPS, and has no database credential or VPC attachment. An internal IAM-authenticated edge-provider-mutator is the sole dynamic Certificate Manager writer and likewise has no DNS, database, or VPC access. The public site path still enters through Cloud DNS, the global external Application Load Balancer, Cloud Armor, and the shared router because Direct VPC provides Cloud Run **egress**, not service ingress. See [Security and Operations](/platform/security-operations) for the controller/mutator trust split and the target router-to-PPB identity, canonical client-IP, subnet, startup, and timeout contracts. ## Responsibility changes by adoption layer diff --git a/platform/automation-backplane.mdx b/platform/automation-backplane.mdx index 2f5197a..cb625bf 100644 --- a/platform/automation-backplane.mdx +++ b/platform/automation-backplane.mdx @@ -8,7 +8,7 @@ LibOps uses GitHub as an important collaboration surface, but the platform shoul LibOps is building a webhook-driven automation backplane for events that need to trigger work inside the platform: dependency scanning, CI runner orchestration, deployment follow-up, and repository maintenance. -The separate request-serving API and `api-worker` Cloud Run deployment described below is a **preview target**, not a released managed topology in the [July 17, 2026 status snapshot](/infrastructure/current-release-status). Process separation in source code or a Compose development stack does not prove that the worker service, identity migration, database path, rollout, and rollback have been promoted in production. +The separate request-serving API and `api-worker` Cloud Run deployment described below is a **preview target**, not a released managed topology in the [July 19, 2026 status snapshot](/infrastructure/current-release-status). Process separation in source code or a Compose development stack does not prove that the worker service, identity migration, database path, rollout, and rollback have been promoted in production. ## GitHub events in LibOps diff --git a/platform/custom-domains.mdx b/platform/custom-domains.mdx index 9f4dad4..08caa41 100644 --- a/platform/custom-domains.mdx +++ b/platform/custom-domains.mdx @@ -33,6 +33,22 @@ load-balancing, routing, Cloud Armor, and Google infrastructure DDoS protection; it does not cache application responses or provide offline origin availability. +## DNS ownership + +LibOps Cloud DNS is authoritative for managed names under `libops.site`. Each +organization receives a delegated child zone, and the managed edge stack owns +that child zone, its parent NS delegation, managed site aliases, CAA policy, +and declared SSH records. Customers do not copy or maintain those records. + +Your DNS provider remains authoritative for a customer-owned hostname. LibOps +returns three independent instructions as the binding advances: the one-time +LibOps ownership TXT, Google's permanent certificate-validation CNAME, and the +traffic record. Publish only the exact values returned for that binding. A +subdomain normally receives a CNAME to its organization edge name; a zone apex +receives the shared A and AAAA values because a standards-compliant apex cannot +use CNAME. LibOps does not return provider-specific flattening or proxy +settings. + ## Before you start You need: @@ -97,8 +113,19 @@ This validation CNAME must remain in DNS for certificate renewal. If the zone has restrictive CAA records, allow Google Trust Services with `pki.goog`. LibOps waits for the validation record, managed certificate, certificate-map -entry, and exact site route. Missing DNS remains a waiting state. A retry only -resumes a retryable failed reconciliation; it cannot skip validation. +entry, and exact site route. Missing DNS remains a waiting state. LibOps keeps +observing every active authorization, validation record, certificate, and map +entry so provider drift is repaired instead of leaving an apparently active +binding broken. If an active dependency disappears, LibOps blocks the route, +returns the binding to certificate preparation, and walks the full dependency +chain again. It does not keep serving through a stale map entry. + +Certificate Manager can report a terminal issuance failure when CAA does not +permit Google Trust Services. LibOps leaves that failed certificate in place +without repeatedly deleting and recreating it. After correcting CAA to allow +`pki.goog`, use **Retry** once. That explicit retry removes any dependent map +entry and requests one replacement certificate. Retry never skips ownership, +DNS, certificate, route, or traffic validation. ## 3. Publish traffic @@ -114,13 +141,18 @@ TTL: 300 or provider default Always use the value returned for the domain. Do not substitute a VM address, a Cloud Run `run.app` hostname, a remembered shared hostname, or an address -copied from another organization. +copied from another organization. Do not enable a DNS provider's HTTP proxy in +front of the returned record: the Google load balancer is the public edge and +must observe the customer's DNS and TLS directly. For an apex, use the exact A and AAAA values returned by LibOps. Do not create a CNAME at a standards-compliant apex. -LibOps marks the domain active only after public DNS resolves to the managed -edge and TLS serves the expected hostname. +LibOps marks the domain active only after every published A and AAAA address +exactly matches the managed edge and TLS serves the expected hostname at every +returned address. It verifies the relevant DNS data through public resolvers +and every assigned authoritative server, then continues checking DNS and TLS +after activation. ## Verify publication @@ -140,10 +172,20 @@ domain binding. ## Delete a domain -Deletion is asynchronous and fail-closed. LibOps blocks routing first, removes -the exact certificate-map entry, certificate, and authorization, and retains -the row while cleanup is in progress. The domain view then identifies the -traffic, validation, and proof records that can be removed. +Deletion is asynchronous and fail-closed. LibOps blocks routing first. Every +router revalidates a cached positive route on its next request; cleanup also +waits 65 seconds, longer than the enforced 60-second positive-cache ceiling, +before touching Google resources. LibOps then removes the exact +certificate-map entry, certificate, and authorization in dependency order and +confirms each resource is absent on a later reconciliation. The retained +domain view identifies the traffic, validation, and proof records to remove. + +Remove the customer traffic CNAME or A/AAAA records when the domain view says +they are safe to remove. Final deletion does not complete while public traffic +DNS still resolves the hostname to the LibOps edge. This prevents a released +hostname from continuing to send requests to a shared edge that no longer +owns its route. Keep the Google validation CNAME until LibOps reports that its +authorization has been removed. Do not attach the same hostname to another site until deletion and the hostname quarantine finish. A later attachment requires a new ownership proof. @@ -154,8 +196,8 @@ quarantine finish. A later attachment requires a new ownership proof. | --- | --- | | Waiting for ownership | Query the authoritative TXT name and compare it with the current one-time instruction. | | Waiting for certificate DNS | Confirm the exact Google CNAME name and target and leave it in place. | -| Certificate issuance failed | Check CAA and allow `pki.goog`; then retry only if LibOps marks the error retryable. | +| Certificate issuance failed | Check CAA and allow `pki.goog`; after the correction is authoritative, use the explicit retry action once. LibOps does not churn a terminal failed certificate automatically. | | Route ready, traffic not active | Publish the exact CNAME or A/AAAA values returned for this binding and wait for authoritative DNS. | | Browser sees the wrong site | Confirm the hostname is attached to the intended site and that DNS does not contain an older conflicting record. | | Visitors are blocked | Confirm the production site's firewall rules intentionally allow their IPv4 and, when enabled, IPv6 ranges. | -| Domain is deleting | Wait for server-owned cleanup; do not recreate it or remove validation records before the UI says they are safe to remove. | +| Domain is deleting | Remove traffic DNS when instructed so final cleanup can complete. Do not recreate the binding or remove the validation CNAME before the UI says it is safe. | diff --git a/platform/security-operations.mdx b/platform/security-operations.mdx index 8466050..0078728 100644 --- a/platform/security-operations.mdx +++ b/platform/security-operations.mdx @@ -10,7 +10,7 @@ For the full self-hosted checklist, including Terraform state, cloud IAM, secret -The shared Cloud Run router and private per-site PPB path below is a **target managed-platform contract**, not a generally available release in the [July 17, 2026 release-status snapshot](/infrastructure/current-release-status). It must not be used as a production DNS runbook until a later status record names the promoted image digests, publisher identities, infrastructure and API releases, and passing end-to-end canaries. +The shared Cloud Run router and private per-site PPB path below is a **target managed-platform contract**, not a generally available release in the [July 19, 2026 release-status snapshot](/infrastructure/current-release-status). It must not be used as a production DNS runbook until a later status record names the promoted image digests, publisher identities, infrastructure and API releases, and passing end-to-end canaries. ## Managed-platform defaults @@ -52,6 +52,41 @@ VM private IP and application port In the target contract, Cloud Armor applies the reviewed public customer policy at the load balancer. Customer and model backends use separate policies. Preconfigured WAF and rate-limit rules begin in preview and are enforced individually after false-positive review. The shared router accepts traffic only from the load balancer, and its default `run.app` URL is disabled so it cannot become an alternate public edge. Cloud CDN remains disabled. +The router's positive route cache is an availability optimization, not an +authorization cache. Every request conditionally revalidates its cached entry +against the API generation and ETag. A missing route, API failure, parent +deactivation, or generation rollback evicts the entry and fails closed before +origin access. Positive entries expire within 60 seconds even without a +request; destructive edge cleanup waits 65 seconds before removing Google +resources. + +The edge-controller continuously re-observes active DNS authorizations, +validation CNAMEs, certificates, map entries, public traffic DNS, and TLS at +every published IPv4 and IPv6 address. A missing Google resource resumes from +the missing dependency. A terminal failed certificate is preserved to avoid +provider churn; after CAA is corrected to allow `pki.goog`, an explicit retry +requests exactly one dependency-safe replacement. Deletion confirms absence +on a later reconciliation and does not release a vanity hostname while public +DNS still directs it to the shared edge. + +The controller is not the Certificate Manager writer. It has read-only +Certificate Manager access, conditioned permission for the exact validation +CNAMEs, and publish access to one ordered Pub/Sub topic. An internal +IAM-authenticated `edge-provider-mutator` is the sole dynamic Certificate +Manager writer. Commands are ordered by deterministic resource ID; the +mutator accepts only the configured edge project and map, matching dynamic +IDs, hostname, and dependency relationships. It has no DNS permission, API or +database credential, customer-secret access, or VPC attachment. Failed +deliveries retry and then reach an alerted dead-letter topic. + +Terraform-owned DNS authorizations, certificates, and certificate map use +[Resource Manager tags supported by Certificate +Manager](https://cloud.google.com/certificate-manager/docs/create-manage-tags) +plus an IAM deny boundary against both dynamic identities. Certificate-map +entries are not listed as taggable resources, so their static protection is +enforced by the mutator's exact rejection of platform names and IDs. It is not +represented as a tag guarantee. + The target per-site power and proxy backend (PPB) has a different URL decision. Its `run.app` hostname must remain addressable because the shared router uses that hostname as the origin, but it must not be public: Cloud Run invocation is granted only to the router service account. The VM application port remains private and is reached only through the runtime subnet firewall rule. Self-hosted operators must supply their own DNS, certificate lifecycle, trusted-proxy configuration, firewall policy, and abuse controls. Enabling Traefik in a template does not provide this surrounding edge service. @@ -69,15 +104,50 @@ The router must authenticate to each private PPB with a Google ID token for one | Layer | Owns | Must preserve | | --- | --- | --- | | Cloud DNS, Certificate Manager, and load balancer | Authoritative managed names, public TLS, SNI selection, stable addresses, and the declared public edge | Preserve exact DNS ownership, certificate-map readiness, and load-balancer-overwritten identity headers | -| Shared edge Terraform | Global load balancer, Certificate Manager map, Cloud Armor policy, router identity, regional router services, and timeout contract | Keep the router load-balancer-only, Cloud CDN disabled, and image digests pinned | +| Shared edge Terraform | Global load balancer, static Certificate Manager resources and deny boundary, Cloud Armor policy, router/controller/mutator identities and services, ordered Pub/Sub delivery, and timeout contract | Keep the router load-balancer-only, Cloud CDN disabled, and all three service images pinned to immutable digests | +| Edge-controller and API state gateway | Conditioned validation-CNAME mutation, read-only Certificate Manager observation, ordered mutation publication, API-owned leases, generation fences, observed-state transactions, and internal outbox events | Allow only the exact controller service account and audience; give the controller no database credential or VPC route and give the API no edge mutation IAM | +| Edge-provider-mutator | Sole dynamic Certificate Manager mutation after exact command validation | Accept only the IAM-authenticated Pub/Sub push identity/audience and one project/map; reject static/platform IDs; provide no DNS, API, database, secret, or VPC access | | Shared router | Exact active-host lookup, SNI/Host validation, canonical header replacement, and origin ID token | Reject ambiguous client identity, preserve application authorization, and route only to validated Cloud Run origins | -| Project foundation | Regional network and subnets, Cloud Run service identity, and network-use permissions | Keep Direct VPC capacity and IAM available before runtimes attach | +| Organization foundation Terraform | Organization project and runner plus exact conditional IAM for its NS RRset in the existing parent zone | Do not create or delete the child zone here; retain the parent-zone binding until edge teardown is complete | +| Organization edge Terraform | Child Cloud DNS zone, parent NS delegation, child records, child-zone controller IAM, and staged teardown | Keep one writer, use only the controller-released teardown phase, and leave `force_destroy = false` | +| Project/runtime foundation | Regional network and subnets, Cloud Run service identity, and network-use permissions | Keep Direct VPC capacity and IAM available before runtimes attach | | Runtime state | Per-site PPB and identity, router invoker grant, custom audience, VM start permission, and app-port firewall rule | Scope power permission to the runtime VM and destination traffic to its service account and declared ports | | Compose repository and application | Application ingress settings, trusted-proxy behavior, sessions, and application authorization | Treat forwarded identity as deployment configuration and retain application-level access controls | +### Organization DNS teardown + +Organization deletion is a durable sequence, not a single Terraform destroy: + +1. Block routes and remove dynamic Certificate Manager map entries, + certificates, validation CNAMEs, and authorizations. The edge-controller + stops at `provider_cleanup_complete`. +2. Run edge Terraform with `teardown_phase = "remove_delegation"`. It removes + stack-owned child records before the parent NS delegation, but retains the + empty child zone and restricted controller IAM. +3. Persist the first successful delegation-removal time and the highest TTL + ever published for that delegation. A retry or later lower TTL cannot + shorten the drain. +4. After the full high-water TTL, require the exact child NS record to be + absent from a trusted recursive resolver and from every current + authoritative server for the parent zone. One cached answer, stale + authority, non-authoritative response, or DNS error leaves teardown pending. + The child authorities are not queried because the retained zone correctly + continues answering its own apex during the drain. +5. Persist `delegation_absent` and schedule the exact final edge run. Only then + may `teardown_phase = "destroy"` delete the empty child zone and its IAM. + `force_destroy = false` makes any leftover record fail closed. +6. Release downstream Vault, organization, and foundation deletion only after + the final edge output records `complete`. Foundation's parent-zone IAM is + removed last. + +Do not substitute a manual DNS deletion, `time_sleep`, `local-exec`, a failed +apply used as a timer, or an assumed destroy order for these durable gates. +Downstream consumers must preserve the state ownership and sequencing even if +they customize the surrounding Terraform. + ## Private VM delivery on Google Cloud -In the target path, the site PPB reaches the application's private VM address through [Direct VPC egress](https://cloud.google.com/run/docs/configuring/vpc-direct-vpc) without a Serverless VPC Access connector. A separate Direct VPC attachment carries authenticated controller requests to the private rollout endpoint. Cloud Run services and jobs do not support Direct VPC ingress: external traffic still enters through Cloud DNS, the global load balancer, Cloud Armor, and the shared Cloud Run router. Worker-pool ingress is a separate capability that this design does not use. +In the target path, the site PPB reaches the application's private VM address through [Direct VPC egress](https://cloud.google.com/run/docs/configuring/vpc-direct-vpc) without a Serverless VPC Access connector. A separate Direct VPC attachment carries authenticated deployment `controller-ingress` requests to the private rollout endpoint. Those are the only two Direct VPC use cases in this edge design. The site-router reaches PPB through its IAM-protected Cloud Run origin; the edge-controller calls the GSA-authenticated API state gateway and Google APIs over HTTPS; and the edge-provider-mutator receives authenticated Pub/Sub push and calls Certificate Manager. Router, controller, and mutator have no VPC attachment or database route. Cloud Run services and jobs do not support Direct VPC ingress: external traffic still enters through Cloud DNS, the global load balancer, Cloud Armor, and the shared Cloud Run router. Worker-pool ingress is a separate capability that this design does not use. The target deployment sets Cloud Run egress to `PRIVATE_RANGES_ONLY`. Only traffic to private destination ranges uses the VPC attachment; other outbound traffic follows normal Cloud Run egress and does not traverse this VPC or its Cloud NAT. Changing the service to `ALL_TRAFFIC` creates a different routing, NAT, cost, and startup-latency contract and requires a separate review and hosted test.