diff --git a/.checkov.yml b/.checkov.yml
new file mode 100644
index 0000000..02f3a56
--- /dev/null
+++ b/.checkov.yml
@@ -0,0 +1,13 @@
+block-list-secret-scan: []
+compact: true
+directory:
+ - .
+download-external-modules: false
+evaluate-variables: true
+framework:
+ - all
+output:
+ - cli
+quiet: true
+soft-fail: true
+summary-position: top
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..03c5f79
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,19 @@
+# Managed by tfroot-github (gh-dependabot.tf); local edits are overwritten.
+"updates":
+- "directory": "/"
+ "groups":
+ "github-actions":
+ "patterns":
+ - "*"
+ "package-ecosystem": "github-actions"
+ "schedule":
+ "interval": "daily"
+- "directory": "/"
+ "groups":
+ "opentofu":
+ "patterns":
+ - "*"
+ "package-ecosystem": "opentofu"
+ "schedule":
+ "interval": "daily"
+"version": 2
diff --git a/.github/workflows/dependabot-notify.yml b/.github/workflows/dependabot-notify.yml
new file mode 100644
index 0000000..de27250
--- /dev/null
+++ b/.github/workflows/dependabot-notify.yml
@@ -0,0 +1,15 @@
+---
+# Managed by tfroot-github (gh-dependabot.tf); local edits are overwritten.
+name: dependabot-notify
+
+on:
+ pull_request:
+ types: [opened, reopened]
+
+permissions: {}
+
+jobs:
+ notify:
+ if: github.actor == 'dependabot[bot]'
+ uses: makeitworkcloud/shared-workflows/.github/workflows/_dependabot-notify.yml@main
+ secrets: inherit
diff --git a/.github/workflows/opentofu.yml b/.github/workflows/opentofu.yml
new file mode 100644
index 0000000..098bcca
--- /dev/null
+++ b/.github/workflows/opentofu.yml
@@ -0,0 +1,23 @@
+name: opentofu
+
+on:
+ pull_request:
+ branches:
+ - main
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: write
+ id-token: write
+ pull-requests: write
+
+jobs:
+ opentofu:
+ if: >-
+ github.event_name != 'pull_request' ||
+ github.event.pull_request.head.repo.full_name == github.repository
+ uses: makeitworkcloud/shared-workflows/.github/workflows/opentofu.yml@main
+ secrets:
+ CHART_UPDATER_GITHUB_APP_PRIVATE_KEY: ${{ secrets.CHART_UPDATER_GITHUB_APP_PRIVATE_KEY }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ca178e3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+**/*.sw[po]
+
+# Do not commit OpenTofu state, lock files, or working directories.
+**/.terraform.lock.hcl
+**/.terraform
+
+**/.vscode
+**/.DS_Store
+
+plan-output.txt
+
+# Generated canonical pre-commit configuration
+/.pre-commit-config.yaml
+/.pre-commit-config.yaml.tmp
diff --git a/.gitleaks.toml b/.gitleaks.toml
new file mode 100644
index 0000000..925bb96
--- /dev/null
+++ b/.gitleaks.toml
@@ -0,0 +1,6 @@
+[extend]
+useDefault = true
+
+[[allowlists]]
+description = "Generated OpenTofu working data is ignored and never committed"
+paths = ['''(^|/)\.terraform(?:/.*)?$''']
diff --git a/.sops.yaml b/.sops.yaml
new file mode 100644
index 0000000..99901db
--- /dev/null
+++ b/.sops.yaml
@@ -0,0 +1,3 @@
+---
+creation_rules:
+ - kms: arn:aws:kms:us-west-2:332355796717:key/0a45c0f6-71dc-4d54-ab33-9df4de1a9e91
diff --git a/.terraform-docs.yml b/.terraform-docs.yml
new file mode 100644
index 0000000..c11fd54
--- /dev/null
+++ b/.terraform-docs.yml
@@ -0,0 +1,20 @@
+formatter: "markdown"
+
+output:
+ file: "README.md"
+ mode: replace
+ template: |
+
+ {{ .Content }}
+
+
+settings:
+ color: false
+ lockfile: false
+
+sort:
+ enabled: true
+ by: name
+
+recursive:
+ enabled: false
diff --git a/.tflint.hcl b/.tflint.hcl
new file mode 100644
index 0000000..062eb57
--- /dev/null
+++ b/.tflint.hcl
@@ -0,0 +1,12 @@
+plugin "terraform" {
+ enabled = true
+ preset = "recommended"
+}
+
+rule "terraform_required_providers" {
+ enabled = false
+}
+
+rule "terraform_required_version" {
+ enabled = false
+}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..47f6717
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,9 @@
+# Agent Instructions
+
+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 encryption material. Those runtime concerns belong to `kustomize-cluster`.
+
+This bootstrap contains no Twilio resources, provider configuration, backend credentials, encrypted secrets, 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 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
new file mode 100644
index 0000000..a8476b8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,50 @@
+SHELL := /bin/bash
+TERRAFORM := $(shell which tofu)
+
+.PHONY: clean init plan apply test pre-commit-config pre-commit-check-deps pre-commit-install-hooks
+
+clean:
+ @find . -name .terraform -type d | xargs -r rm -rf
+
+# Bootstrap deliberately uses no remote backend until the encrypted backend
+# contract and least-privilege CI access are established in a later PR.
+init: clean
+ @${TERRAFORM} init -backend=false -upgrade -input=false
+
+plan: init
+ @${TERRAFORM} plan -refresh=false -input=false -lock=false -compact-warnings
+
+# There are intentionally no provider configurations or Twilio resources in
+# this bootstrap, so the main-branch apply has no provider-side effect.
+apply: init
+ @${TERRAFORM} apply -auto-approve -refresh=false -input=false -lock=false -compact-warnings
+
+test: pre-commit-config pre-commit-install-hooks
+ @pre-commit run -a
+
+pre-commit-config:
+ @curl --fail --silent --show-error --location \
+ --output .pre-commit-config.yaml.tmp \
+ https://raw.githubusercontent.com/makeitworkcloud/images/main/tfroot-runner/pre-commit-config.yaml
+ @if cmp -s .pre-commit-config.yaml.tmp .pre-commit-config.yaml; then \
+ rm -f .pre-commit-config.yaml.tmp; \
+ else \
+ mv .pre-commit-config.yaml.tmp .pre-commit-config.yaml; \
+ fi
+
+DEPS_PRE_COMMIT=$(shell which pre-commit || echo "pre-commit not found")
+DEPS_TERRAFORM_DOCS=$(shell which terraform-docs || echo "terraform-docs not found")
+DEPS_TFLINT=$(shell which tflint || echo "tflint not found")
+DEPS_CHECKOV=$(shell which checkov || echo "checkov not found")
+DEPS_JQ=$(shell which jq || echo "jq not found")
+pre-commit-check-deps:
+ @echo "Checking for pre-commit and its dependencies:"
+ @echo " pre-commit: ${DEPS_PRE_COMMIT}"
+ @echo " terraform-docs: ${DEPS_TERRAFORM_DOCS}"
+ @echo " tflint: ${DEPS_TFLINT}"
+ @echo " checkov: ${DEPS_CHECKOV}"
+ @echo " jq: ${DEPS_JQ}"
+ @echo ""
+
+pre-commit-install-hooks: pre-commit-config pre-commit-check-deps
+ @pre-commit install --install-hooks --hook-type pre-commit --hook-type commit-msg
diff --git a/README.md b/README.md
index 3dcf5aa..bf81611 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,28 @@
-# tfroot-twilio
\ No newline at end of file
+
+## Requirements
+
+| Name | Version |
+| ---- | ------- |
+| [terraform](#requirement\_terraform) | > 1.3 |
+| [twilio](#requirement\_twilio) | 0.27.1 |
+
+## Providers
+
+No providers.
+
+## Modules
+
+No modules.
+
+## Resources
+
+No resources.
+
+## Inputs
+
+No inputs.
+
+## Outputs
+
+No outputs.
+
diff --git a/docs/bootstrap.md b/docs/bootstrap.md
new file mode 100644
index 0000000..eaf9785
--- /dev/null
+++ b/docs/bootstrap.md
@@ -0,0 +1,25 @@
+# Twilio root bootstrap
+
+## Scope
+
+This initial root validates the OpenTofu toolchain and provider installation only. It creates, imports, updates, or deletes **no** Twilio resources. In particular, it does not purchase or configure phone numbers, webhooks, credentials, messaging services, or runtime routing.
+
+The evaluated provider is `RJPearson94/twilio` `0.27.1`. Registry documentation confirms it supports Twilio phone-number resources and inbound `messaging` webhook fields, but it is community-maintained and requires existing account credentials. The bootstrap intentionally declares no provider configuration, so CI receives no Twilio credentials and performs no provider-side API call.
+
+## Ownership boundary
+
+The future root may own only Twilio phone-number inventory and inbound messaging-webhook configuration. `kustomize-cluster` remains the owner of the OpenCode bridge workload, workload `TunnelBinding`/DNS, fixed number-to-agent map, approved-source allowlist, runtime secrets, and state-encryption inputs. The root must never create a bridge-worker API key or duplicate a runtime owner.
+
+## Backend and credential contract
+
+The checked-in `.sops.yaml` identifies the approved encryption recipient but no encrypted secret file exists yet. A later, separately reviewed change must:
+
+1. establish least-privilege GitHub Actions OIDC access for this exact repository to decrypt SOPS material;
+2. add an encrypted backend configuration for a dedicated Twilio state object; and
+3. define an existing Twilio credential delivery path that cannot expose a token in source, CI logs, or OpenTofu state.
+
+Only after those prerequisites pass pull-request validation may a later root change add provider configuration or Twilio inventory. Any actual Twilio provisioning or webhook update still 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`.
diff --git a/main.tf b/main.tf
new file mode 100644
index 0000000..adbd32e
--- /dev/null
+++ b/main.tf
@@ -0,0 +1,3 @@
+# Bootstrap intentionally contains no Twilio resources, data sources, provider
+# configuration, or secret material. The next root change must first establish
+# the encrypted backend and credential-delivery contract described in docs/bootstrap.md.
diff --git a/providers.tf b/providers.tf
new file mode 100644
index 0000000..e580aec
--- /dev/null
+++ b/providers.tf
@@ -0,0 +1,18 @@
+terraform {
+ required_version = "> 1.3"
+
+ required_providers {
+ twilio = {
+ source = "RJPearson94/twilio"
+ version = "0.27.1"
+ }
+ }
+}
+
+# The permanent S3 backend is intentionally omitted until its encrypted
+# contract and least-privilege CI access are reviewed in a follow-up PR.
+# The bootstrap Makefile initializes with -backend=false.
+
+# No provider block is declared during bootstrap. The candidate provider is
+# installed and syntax-validated without receiving credentials or managing
+# Twilio resources.