diff --git a/AGENTS.md b/AGENTS.md index e8e9fed..bc14c13 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,6 @@ OpenTofu root for Make IT Work Cloud Twilio control-plane infrastructure. This root owns only Twilio phone-number inventory and inbound messaging-webhook configuration. It must not own the OpenCode SMS bridge workload, Cloudflare workload route or DNS, number-to-agent mapping, approved-source allowlist, bridge credentials, or runtime encryption material. Those runtime concerns belong to `kustomize-cluster`. -The root owns the SOPS-encrypted `secrets/secrets.yaml` provider credential file and no other secret material. It contains only the Twilio provider environment-variable names. The root configures the Twilio provider exclusively from those process-scoped environment variables, but contains no Twilio resources, state, or number identifiers. Use GitHub MCP and pull-request CI plans as validation authority. `main` is an environment-gated apply path; use scoped branches and pull requests, never direct pushes. Do not run OpenTofu, SOPS, state, import, or apply commands from this server. +The root owns the SOPS-encrypted `secrets/secrets.yaml` provider credential file and no other secret material. It contains only the Twilio provider environment-variable names. The root configures the Twilio provider exclusively from those process-scoped environment variables and passes the account SID to declared resources only as `TF_VAR_account_sid` inside the same SOPS `exec-env` child. `numbers.tf` declares the four pilot phone-number resources; purchased number values live only in remote state and outputs. Use GitHub MCP and pull-request CI plans as validation authority. `main` is an environment-gated apply path; use scoped branches and pull requests, never direct pushes. Do not run OpenTofu, SOPS, state, import, or apply commands from this server. The shared workflow is owned by `shared-workflows`; the runner image and canonical pre-commit configuration are owned by `images/tfroot-runner`. Keep any future SOPS data encrypted and never expose credentials, decrypted values, state, private keys, or sensitive plans. diff --git a/Makefile b/Makefile index e5cce01..c5594f8 100644 --- a/Makefile +++ b/Makefile @@ -21,18 +21,19 @@ init: clean # Verify ciphertext and required keys without printing values. plan and apply run # OpenTofu inside a separate SOPS exec-env child so the provider can authenticate. +# The account SID reaches OpenTofu only as TF_VAR_account_sid inside that child. credentials-check: @test -f "${TWILIO_CREDENTIALS}" @sops filestatus "${TWILIO_CREDENTIALS}" | jq -e '.encrypted == true' >/dev/null @sops exec-env "${TWILIO_CREDENTIALS}" 'test -n "$$TWILIO_ACCOUNT_SID" && test -n "$$TWILIO_API_KEY" && test -n "$$TWILIO_API_SECRET"' plan: init credentials-check - @sops exec-env "${TWILIO_CREDENTIALS}" '${TERRAFORM} plan -refresh=false -input=false -compact-warnings' + @sops exec-env "${TWILIO_CREDENTIALS}" 'TF_VAR_account_sid="$$TWILIO_ACCOUNT_SID" ${TERRAFORM} plan -refresh=false -input=false -compact-warnings' -# This root has no Twilio resources. Provider authentication may perform its -# own read-only validation, but apply has no Twilio resource action to perform. +# The four twilio_phone_number.agent resources purchase pilot numbers on apply. +# No inbound webhook field is set until the bridge is deployed and healthy. apply: init credentials-check - @sops exec-env "${TWILIO_CREDENTIALS}" '${TERRAFORM} apply -auto-approve -refresh=false -input=false -compact-warnings' + @sops exec-env "${TWILIO_CREDENTIALS}" 'TF_VAR_account_sid="$$TWILIO_ACCOUNT_SID" ${TERRAFORM} apply -auto-approve -refresh=false -input=false -compact-warnings' test: pre-commit-config pre-commit-install-hooks @pre-commit run -a diff --git a/README.md b/README.md index bf81611..8f060fc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ ## Providers -No providers. +| Name | Version | +| ---- | ------- | +| [twilio](#provider\_twilio) | 0.27.1 | ## Modules @@ -16,13 +18,19 @@ No modules. ## Resources -No resources. +| Name | Type | +| ---- | ---- | +| [twilio_phone_number.agent](https://registry.terraform.io/providers/RJPearson94/twilio/0.27.1/docs/resources/phone_number) | resource | ## Inputs -No inputs. +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| [account\_sid](#input\_account\_sid) | Twilio account SID, supplied only from SOPS exec-env through TF\_VAR\_account\_sid. | `string` | n/a | yes | ## Outputs -No outputs. +| Name | Description | +| ---- | ----------- | +| [agent\_phone\_numbers](#output\_agent\_phone\_numbers) | Purchased pilot number for each primary agent. | diff --git a/docs/bootstrap.md b/docs/bootstrap.md index fe08a96..3982513 100644 --- a/docs/bootstrap.md +++ b/docs/bootstrap.md @@ -2,13 +2,15 @@ ## Scope -This root validates the OpenTofu toolchain, remote state backend, encrypted provider-credential delivery, and Twilio provider authentication only. It creates, imports, updates, or deletes **no** Twilio resources. In particular, it does not purchase or configure phone numbers, webhooks, messaging services, or runtime routing. +This root validates the OpenTofu toolchain, remote state backend, encrypted provider-credential delivery, and Twilio provider authentication. It now also owns the pilot phone-number inventory: exactly one purchased US local SMS/MMS-capable number per primary agent (`lawnmowerman`, `grillmaster`, `homesteader`, `homerepair`), declared in `numbers.tf`. -The evaluated provider is `RJPearson94/twilio` `0.27.1`. It is community-maintained and requires existing account credentials. The provider is intentionally configured with no static attributes: OpenTofu receives its credentials only in a SOPS `exec-env` child. Its authentication validation may perform a read-only Twilio API request; no resource is declared or changed by this root. +It configures **no** inbound messaging webhook. The `messaging` block on each number is added only in a separately reviewed change after the `kustomize-cluster` bridge is reconciled, healthy, and explicitly approved for functional testing. The root does not own the bridge workload, Cloudflare workload route or DNS, number-to-agent mapping, approved-source allowlist, bridge credentials, or runtime encryption material; those runtime concerns belong to `kustomize-cluster`. + +The evaluated provider is `RJPearson94/twilio` `0.27.1`. The provider is intentionally configured with no static attributes: OpenTofu receives its credentials only in a SOPS `exec-env` child, and the resource `account_sid` reaches OpenTofu only as `TF_VAR_account_sid` inside that same child process. The purchased number values live only in remote state and the `agent_phone_numbers` output. ## Intended message flow -The owner’s intended outcome is one Twilio phone number for each primary agent: `lawnmowerman`, `grillmaster`, `homesteader`, and `homerepair`. Each number will initially accept inbound SMS/MMS only from the one owner-approved source phone number, then route the request to its corresponding agent for a timely reply using that agent’s existing context, knowledge base, and MCP integrations. The sender allowlist is intentionally extensible: adding a further approved source number after the pilot is a separate runtime-configuration change, not a Twilio-root redesign. +The owner's intended outcome is one Twilio phone number for each primary agent. Each number will initially accept inbound SMS/MMS only from the one owner-approved source phone number, then route the request to its corresponding agent for a timely reply using that agent's existing context, knowledge base, and MCP integrations. The sender allowlist is intentionally extensible: adding a further approved source number after the pilot is a separate runtime-configuration change, not a Twilio-root redesign. `kustomize-cluster` exclusively owns the bridge workload, number-to-agent map, sender allowlist and its later expansion, runtime secrets, `TunnelBinding`/DNS, and public route. This root may later own only the Twilio number inventory and the inbound messaging-webhook fields pointing at the already-healthy bridge. It must not duplicate any bridge/runtime configuration. @@ -18,8 +20,8 @@ The owner’s intended outcome is one Twilio phone number for each primary agent The checked-in `.sops.yaml` identifies the approved encryption recipient. `secrets/secrets.yaml` is the only accepted provider credential file. It holds the encrypted `TWILIO_ACCOUNT_SID`, `TWILIO_API_KEY`, and `TWILIO_API_SECRET` values; it must never contain bridge runtime inputs. `make plan` and `make apply` first require SOPS to recognize the file as encrypted, then run OpenTofu inside `sops exec-env`. They never write plaintext files, set static provider attributes, or use GitHub Actions secrets. -This provider-only change must pass pull-request validation before later Twilio inventory or webhook work is proposed. Any actual Twilio provisioning or webhook update still requires explicit owner confirmation before merge because `main` invokes the environment-gated apply path. +Any change that purchases numbers or updates a webhook requires explicit owner confirmation before merge because `main` invokes the environment-gated apply path. ## Central generated files -`.github/dependabot.yml` and `.github/workflows/dependabot-notify.yml` are seeded here with the exact content generated by `tfroot-github`. They are not locally owned. This PR-based seed resolves the branch-protection bootstrap conflict; after merge, the central `github_repository_file` resources can adopt and continue managing them with `overwrite_on_create = true`. +`.github/dependabot.yml` and `.github/workflows/dependabot-notify.yml` are centrally generated by `tfroot-github`. They are not locally owned; do not hand-edit them. diff --git a/numbers.tf b/numbers.tf new file mode 100644 index 0000000..ac3117c --- /dev/null +++ b/numbers.tf @@ -0,0 +1,39 @@ +# One pilot number per primary agent. Purchased US local numbers with SMS +# and MMS capability; no inbound webhook is configured here. The messaging +# webhook is added only after the kustomize-cluster bridge is healthy. + +variable "account_sid" { + type = string + description = "Twilio account SID, supplied only from SOPS exec-env through TF_VAR_account_sid." +} + +locals { + agent_numbers = { + lawnmowerman = "opencode-sms lawnmowerman" + grillmaster = "opencode-sms grillmaster" + homesteader = "opencode-sms homesteader" + homerepair = "opencode-sms homerepair" + } +} + +resource "twilio_phone_number" "agent" { + for_each = local.agent_numbers + + account_sid = var.account_sid + friendly_name = each.value + + search_criteria { + iso_country = "US" + type = "local" + + capabilities { + sms_enabled = true + mms_enabled = true + } + } +} + +output "agent_phone_numbers" { + description = "Purchased pilot number for each primary agent." + value = { for name, number in twilio_phone_number.agent : name => number.phone_number } +}