Skip to content

feat(skills): add OpenTofu IaC release management skill with findings ledger - #28

Merged
jredgard merged 1 commit into
mainfrom
fm/fm-opentofu-iac-skill
Oct 5, 2026
Merged

jredgard merged 1 commit into
mainfrom
fm/fm-opentofu-iac-skill

Conversation

@jredgard

@jredgard jredgard commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Intent

Said on 2026-10-05 after a day of failed QA infrastructure releases and a scout audit of them: "Whatever we learn, we should make into a finding, add it to our fm tooling so that fm can turn it into a skill for managing opentofu iac." Context: the audit found the releases failed because secret, grant, image, database, realm and recovery prerequisites lived outside the executable OpenTofu release; mocked tests stayed green while Azure refused creates and left orphan shells. Turn those lessons into a reusable firstmate skill for managing OpenTofu infrastructure-as-code releases, with the findings recorded as a citable ledger.

What Changed

  • Adds the internal opentofu-iac-management skill. It covers scoping and briefing a release, a single protected orchestration (exclusive lock, an executable prerequisite manifest, a live prerequisite gate that fails closed, and immutable saved plans for each phase), private Key Vault and identity boundaries, image publication and executed bootstrap receipts, scoped recovery after failed creates, and layered verification. The skill gives no authority to mutate or destroy infrastructure.
  • Adds references/findings.md, a citable ledger of 14 findings (IAC-001 to IAC-014) from the failed QA infrastructure releases. Topics include prerequisites that were never executable, grants that must exist before consumers, preview runs that don't verify anything, orphan shells left by failed Azure creates, mocked tests that pass while real releases fail, and destroy/rebuild as the acceptance boundary.
  • Adds a load trigger for the skill to agent-skill-trigger-index and marks both new files with the agent-runtime audience in docs/documentation-audiences.json.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: A documentation-only change that adds an agent-only skill, its findings ledger and their index and audience entries, following existing patterns, with no executable code changed.

Testing

I ran the repo's real documentation-audience checker on the change. It passes. In a throwaway clone I broke the change two ways: removing its inventory entries and breaking its link to the ledger. The checker failed closed both times. I then started a real Claude Code primary in a disposable lab home on a private tmux socket. Given two OpenTofu tasks it had not been told the skill name for (scoping a destroy-and-rollout brief, and diagnosing an orphan failed-create shell), it loaded opentofu-iac-management by itself both times and answered with the skill's rules: zero human actions after destroy except approvals, no unmocked tofu test against the real release root, and no hand deletion of the shell without receipted ownership proof followed by a fresh approved plan. Every finding ID it cited (IAC-003/004/008/009/011/012/014) exists in the ledger. The lab was torn down afterwards. All scenarios passed.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
The new skill and ledger are classified in the documentation-audience inventory, and the repo's audience checker accepts them with every local link resolving ✅ pass live doc-audience-check.txt (bin/fm-doc-audience-check.sh exit 0, surfaces=124 local_links=734)
Adversarial: dropping the inventory entries, or breaking the SKILL.md link to the findings ledger, makes the audience checker fail closed ✅ pass live doc-audience-adversarial.txt (both mutations in a throwaway clone exit 1 with specific unclassified and unresolved-link errors)
A firstmate Claude primary asked to scope and brief a QA OpenTofu destroy-and-rollout loads opentofu-iac-management on its own and answers with its acceptance rule, its unmocked-test ban and the corre… ✅ pass live lab-primary-scope-brief-transcript.txt (Skill(opentofu-iac-management) loaded; ledger read; IAC-003 and IAC-012 cited and checked against the ledger)
Adversarial guard: a primary asked whether to hand-delete an orphan failed Azure create shell loads the skill, refuses the ad hoc deletion, gives the recovery conditions and requires a fresh guarded p… ✅ pass live lab-primary-orphan-shell-recovery-transcript.txt (Skill loaded after /clear; refuses az delete; cites IAC-009/011/012/014/004/008, all present in the ledger)
Evidence: Doc audience checker passes on target commit

Source: Doc audience checker passes on target commit

fm-doc-audience-check: ok surfaces=124 local_links=734 exit=0

$ bin/fm-doc-audience-check.sh
fm-doc-audience-check: ok surfaces=124 local_links=734
exit=0
Evidence: Doc audience checker rejects missing inventory entries and broken ledger link

Source: Doc audience checker rejects missing inventory entries and broken ledger link

unclassified: .agents/skills/opentofu-iac-management/SKILL.md, .../references/findings.md exit=1 unresolved local link in .agents/skills/opentofu-iac-management/SKILL.md: references/findings.md exit=1

## Adversarial 1: drop the two new inventory entries
fm-doc-audience-check: unclassified: .agents/skills/opentofu-iac-management/SKILL.md, .agents/skills/opentofu-iac-management/references/findings.md
exit=1
## Adversarial 2: break the SKILL.md -> references/findings.md link
fm-doc-audience-check: unresolved local link in .agents/skills/opentofu-iac-management/SKILL.md: references/findings.md
exit=1
Evidence: Lab Claude primary transcript: scope/brief destroy-and-rollout loads the skill

Source: Lab Claude primary transcript: scope/brief destroy-and-rollout loads the skill


 ▐▛███▛█   Claude Code v2.1.289
▝▜██████▀  Fable 5.1 with high effort · API Usage Billing
 ▝▝   ▝▝   ~/.no-mistakes/worktrees/551ff26a6b1c/01M45QG5WQ4B8R4F58HTS4V7M9


❯ Read-only question, do not run any scripts or mutate anything: I need to scope and brief a crewmate for a destroy-and-rollout of a QA OpenTofu release on Azure with a private Key Vault. Load the
  firstmate skill whose trigger covers this, then answer in 4 short bullets: (1) the exact skill name you loaded, (2) the acceptance criterion after destroy, (3) whether the crewmate may run unmocked
  tofu test against the real release root and why, (4) the finding ID from its ledger that explains why opening the vault firewall for seeding is forbidden.

● Captain, I'll load the opentofu-iac-management skill and answer from its contents without running anything.

● Skill(opentofu-iac-management)
  ⎿  Successfully loaded skill

● I need the ledger for bullet 4, so I'll read the referenced findings file.

  Read 1 file

● Captain, here are the four answers, read-only, nothing run or changed.

  - Skill loaded: opentofu-iac-management, whose trigger explicitly covers scoping and briefing an OpenTofu destroy-and-rollout with a private Key Vault.
  - Acceptance after destroy: zero human actions other than approvals. One real protected release must pass every phase, then prove a no-op rerun. Any hand-seeded secret, workstation grant, manual
    image import, SQL/realm/job command, or shell deletion fails acceptance even if the final UI works. Destroy itself stays the explicitly authorized operator step, never the crewmate's.
  - Unmocked tofu test on the real release root: no. Unmocked native tests create and automatically destroy resources outside the release's saved-plan and approval controls. The crewmate may run
    mocked contract tests anywhere, and unmocked tests only against a separately authorized disposable fixture. Real-environment acceptance comes only through the protected release lifecycle. The
    ledger's IAC-012 also names this as an anti-pattern.
  - Ledger finding for the vault firewall: IAC-003, "Private bootstrap must not open the vault". Prior seeding temporarily changed public access even though ARM metadata already worked privately. The
    rule is control-plane secret and key creation via Microsoft.KeyVault/vaults/secrets and /keys, with in-network jobs for any data-plane step. Opening a firewall or adding peering to accommodate a
    hosted agent or workstation is the recorded anti-pattern.

✻ Cooked for 21s · done 11:52
























────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯ Draft the crewmate brief for the destroy-and-rollout
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents
Evidence: Lab Claude primary transcript: orphan-shell diagnosis loads the skill and refuses hand deletion

Source: Lab Claude primary transcript: orphan-shell diagnosis loads the skill and refuses hand deletion


 ▐▛███▛█   Claude Code v2.1.289
▝▜██████▀  Fable 5.1 with high effort · API Usage Billing
 ▝▝   ▝▝   ~/.no-mistakes/worktrees/551ff26a6b1c/01M45QG5WQ4B8R4F58HTS4V7M9


❯ /clear

❯ Read-only, do not run any commands or touch Azure: our QA OpenTofu apply just failed - Azure refused a Container App create and left a live failed shell that is not in tofu state, so the next plan
  collides with it. Using the firstmate guidance that applies to diagnosing this, answer briefly: may I just az-delete that shell by hand to unblock the rerun? Under what exact conditions may it be
  removed, what must be refused, and what happens to the previously approved plan afterwards? Name the skill you loaded and cite ledger finding IDs.

● Captain, loading the OpenTofu release-management skill that governs this diagnosis before answering.

● Skill(opentofu-iac-management)
  ⎿  Successfully loaded skill

  Read 1 file

● Captain, short answer: no, not by hand and not yet. I loaded the opentofu-iac-management skill, and its "Failed-create recovery" section plus the findings ledger govern this exactly.

  When the shell may be removed (all conditions together, per IAC-009):

  - It is a release-owned, failed, revisionless, stateless Container App shell with no healthy revision.
  - Pre-apply receipts prove it was absent before that exact release-owned create, so provenance is established, not inferred.
  - Live type, name, scope, provisioning status and revisions have been reconciled against that run and the state file.
  - Removal happens under the same exclusive release lock and an explicitly authorized recovery contract, with the deletion verified and a redacted recovery receipt retained.
  - Before touching anything, the Azure refusal itself is classified from stage-level evidence as a provisioning failure rather than admission, authorization, image-pull or identity-resolution trouble
    (IAC-014, IAC-004, IAC-008).

  What must be refused:

  - Any shell that is pre-existing, of unknown provenance, has a healthy revision, is stateful, or belongs to an unrelated successful resource.
  - Recovery being stretched into generic import, adoption, taint or deletion authority. An ad hoc az delete is exactly the hand-delete of an inconvenient collision that IAC-009 names as the
    anti-pattern.
  - If ownership or recovery authority cannot be proved, the rule is stop and escalate. The skill grants no infrastructure mutation or recovery authority on its own.

  The previously approved plan is dead afterwards. A failed apply is never success, and changed live inventory or state requires a fresh guarded saved plan with its own approval (IAC-011 and the
  orchestration step 4 rule). You cannot edit or reuse the old approved plan, and mock-green or zero-drift evidence cannot stand in for the new gate (IAC-012).

  Net consequence for the QA rerun: the only compliant path is receipted reconciliation and a locked, authorized cleanup of that one shell, then a new plan and approval. Nothing here was run or
  touched.

✻ Brewed for 34s · done 11:53












────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯ 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents

Pipeline

Updates from git push no-mistakes

  • ✅ intent - passed
  • ✅ rebase - passed
  • ✅ review - passed
  • ✅ test - passed
  • ⚠️ document - 1 info
  • ✅ lint - passed
  • ✅ push - passed
  • ⚠️ pr - running
  • ⚠️ ci - pending

@jredgard
jredgard merged commit ee05232 into main Oct 5, 2026
36 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant