Manual Test Runbook — F0: Cloud-Agnostic Module Contracts
Owner: Sagar | Time: ~15 min | Sandbox: not required
What this runbook proves
- Every
modules/_contracts/<domain>/sub-module is a valid Terraform module carrying no providers and no resources. - F1 (
modules/azure/baseline/) emitsidentity_contractandobservability_contractoutputs whose shapes compile against the corresponding contracts. - F6 (
modules/azure/state-backend/) emits anobject_store_contractoutput whose shape compiles against theobject_store/contract. - The contract modules mechanically REJECT bad literal candidates — proving the gate has teeth, not just that compatible shapes happen to compile.
Apply-time conformance (the catch for drift in module outputs wired from
un-applied resources) is exercised by the F1 + F6 runbooks against the sandbox
subscription — see F1.md Part B and F6.md Part B.
Prerequisites
- Local tooling:
terraform >= 1.6,go >= 1.22 - Working directory: repo root
- No cloud credentials needed — this runbook is 100% offline
Steps
Part A — every contract module validates standalone (~2 min)
- Walk every contract sub-module and confirm
terraform fmt -check,terraform init -backend=false, andterraform validateall succeed:
for c in identity observability object_store network cluster registry kv; do
echo "=== _contracts/$c ==="
terraform -chdir=modules/_contracts/$c fmt -check && \
terraform -chdir=modules/_contracts/$c init -backend=false -input=false >/dev/null && \
terraform -chdir=modules/_contracts/$c validate || { echo "FAIL: $c"; exit 1; }
done
echo "ALL CONTRACTS VALIDATED"
- Expect to see
Success! The configuration is valid.seven times followed byALL CONTRACTS VALIDATED.
Part B — F1 and F6 retrofit still validate (~2 min)
- Confirm the F1 retrofit (added
identity_contract+observability_contractoutputs and theazurerm_client_configdata source) hasn't broken F1:
terraform -chdir=modules/azure/baseline fmt -check && \
terraform -chdir=modules/azure/baseline init -backend=false -input=false >/dev/null && \
terraform -chdir=modules/azure/baseline validate
- Same for F6 (added
object_store_contract):
terraform -chdir=modules/azure/state-backend fmt -check && \
terraform -chdir=modules/azure/state-backend init -backend=false -input=false >/dev/null && \
terraform -chdir=modules/azure/state-backend validate
Part C — positive conformance tests pass (~30 s after init cache warms)
- Run the positive-path tests. They wire F1 + F6's contract outputs through
the matching contract modules and assert
terraform validatesucceeds:
cd tests/terratest
go test -v -timeout 5m -run 'TestF1ContractConformance|TestF6ObjectStoreContractConformance' ./modules/azure/
- Expect:
--- PASS: TestF1ContractConformance (...s)
--- PASS: TestF6ObjectStoreContractConformance (...s)
PASS
Part D — negative conformance tests prove the gate has teeth (~30 s)
- Run the negative-path tests. They pipe deliberately-malformed literal
candidates through each contract and assert
terraform validateFAILS with the expected diagnostic substring:
- Expect four sub-tests to pass:
--- PASS: TestContractsRejectBadLiterals/identity-missing-scope-type
--- PASS: TestContractsRejectBadLiterals/identity-bad-enum
--- PASS: TestContractsRejectBadLiterals/observability-missing-name
--- PASS: TestContractsRejectBadLiterals/object-store-locked-without-enabled
--- PASS: TestContractsRejectBadLiterals
Each scenario triggers a different rejection path:
- identity-missing-scope-type — missing required field rejected by type.
- identity-bad-enum — scope_type outside closed set rejected by validation block.
- observability-missing-name — missing required field rejected by type.
- object-store-locked-without-enabled — cross-field rule rejected by validation block.
Part E — full Terratest suite stays green (~30 s)
- Run the entire repo's Terratest suite to confirm nothing else regressed:
- Expect all tests to pass, including the pre-existing
TestBaselineValidate,TestStateBackendValidate,TestNoopHarness,TestSandboxValidate.
Part F — spot-check the contract output shape in F1 / F6 source (~3 min)
-
Read
modules/azure/baseline/outputs.tfand confirm the two new outputs are present and that every contract field has a value:identity_contract— one of two shapes depending on whether an MG is in scope. Both includescope_id,scope_type,parent_scope_id,tenant_id,display_name.observability_contract—workspace_id,workspace_name,customer_id,region,retention_days,ingest_endpoint.
-
Read
modules/azure/state-backend/outputs.tfand confirmobject_store_contractincludes every field listed inmodules/_contracts/object_store/variables.tf. -
Read
modules/_contracts/README.mdand confirm the seven-contract table matches what's on disk.
Pass criteria
- All 7 contract sub-modules:
fmt -checkclean,init -backend=falseOK,validateSuccess. - F1 + F6 still validate clean after retrofit.
-
TestF1ContractConformance+TestF6ObjectStoreContractConformancePASS. -
TestContractsRejectBadLiteralsPASS with all 4 sub-scenarios green. - Full
go test ./...suite remains green (5 prior tests + 3 new = 8 top-level). - F1 + F6 contract outputs visually match the corresponding contract
variables.tfshapes. -
modules/_contracts/README.mdtable matchesls modules/_contracts/.
Teardown
Nothing to clean up — no cloud resources are created by this runbook. The
.terraform/ and .terraform.lock.hcl artifacts left in each contract module
directory may be removed if you want a pristine tree:
find modules/_contracts -type d -name .terraform -exec rm -rf {} +
find modules/_contracts -name .terraform.lock.hcl -delete
find tests/terratest/fixtures/baseline-contract tests/terratest/fixtures/state-backend-contract tests/terratest/fixtures/contracts-negative -type d -name .terraform -exec rm -rf {} +
find tests/terratest/fixtures/baseline-contract tests/terratest/fixtures/state-backend-contract tests/terratest/fixtures/contracts-negative -name .terraform.lock.hcl -delete
Sign-off
- Tester: ____ | Date: ______ | Result: PASS / FAIL / N/A
- Notes: