Training module · Draft
Attribute-Driven Migration, Module 9: Deploy to Test, then Production
Take the kits past the chapters’ test-tenant steps with one settings file per stage, a pre-flight check, a smoke test that says go or no-go, a rollback written from a record of every change, and the evidence the ISSO keeps
Status — Draft
This page is still being written. Expect gaps and changes; it is not part of the workbook's production guidance. Last reviewed 2026-11-07.
Learning path: Building the Attribute-Driven Migration · Module 9 of 9 · 50 minutes · Gates 2 to 5 and 7 to 9, through the step-to-gate map · Workbook Chapter 10
Watch: the narrated deck for this module: Deck · ▶ Watch (about 14 minutes, captions) · MP4 · Script · Captions.
Chapter 10 waits for the owner’s review, and nobody has run its runbook against a live tenant, subscription or forest. Its five deploy scripts are tested against fixtures, and their dry-run output is real. Every other result in the chapter is an expectation, not an observation. This module’s Lab 1 runs only the dry runs.
Unit 1: Introduction
5 minutes, with the objectives and prerequisites
Each chapter’s Lab 2 changes the smallest thing it can, by hand, and backs it out. That proves one kit in isolation. A deployment runs all nine kits in a safe order, reads every value from one settings file per stage, records every change with its rollback, and ends with a go or no-go record that production’s pre-flight check verifies. The order is the guide’s: prove the attributes first, build groups and workflows paused or disabled, target by attribute, cut over by wave, then keep the attributes true. In this module you dry-run the scripts that enforce it.
The federal requirement. Every runbook step has a requirements table, and every step leaves an evidence record that names its controls and KSIs. From Chapter 10:
| Requirement | KSI | Evidence the ISSO keeps |
|---|---|---|
| CM-3(2): the settings, kits, identities and cloud are checked before any change (pre-flight) | KSI-CMT-VTD, KSI-CMT-LMC | preflight.json, dry and live, and its evidence record |
| IA-5 for non-user accounts; AC-6: no secret in the settings file, and three certificate identities with only what each step needs | KSI-IAM-SNU, KSI-IAM-ELP | preflight.json rows PF-02 and PF-05; the identities’ grants in the change record |
| CM-4(2) Verification of Controls; CA-7: after the change, the gates’ checks are read again and the answer is go or no-go (step 9) | KSI-CMT-LMC, KSI-CMT-VTD, KSI-MLA-EVC | smoke/smoke-report.json and the after run of the validation kit |
| AU-9; SI-7: one evidence package with a SHA-256 per file, verified (step 10) | KSI-MLA-OSM, KSI-SVC-VRI | The package, its SHA256SUMS and the verify output |
| CM-3: every change recorded with its rollback, written newest first and, in the test stage, run (step 11) | KSI-CMT-LMC, KSI-CMT-RVP | deploy-record.json, the rollback scripts and their transcripts |
KSIs are FedRAMP 20x indicators for cloud service providers. The agency authorizes its own system and lines its evidence up with what its providers report against them; it doesn’t report KSIs itself (Book 00, Appendix B). Chapter 10 uses only KSI IDs this workbook already maps to the same controls.
Learning objectives
In this module, you’ll:
- Explain the four ways a deployment differs from a chapter’s Lab 2.
- Describe the settings file, the placeholders and the three identities.
- Run the pre-flight check, and say which checks it runs dry and which live.
- Record changes and read the rollback plan they produce.
- Explain why a fixture smoke test can never promote, and what production’s pre-flight check verifies.
Prerequisites
- Modules 1 to 8, or at least their Lab 1s: this module doesn’t teach the kits again.
- Python 3.10 or later with pytest, and a POSIX shell with
sha256sum.
Unit 2: What the runbook adds
10 minutes
One settings file per stage, with no secrets, so the same commands run against test and production. Three identities with a certificate each, plus one named delegated step where Learn allows nothing else. Every change recorded, and the rollback written from the record. A go or no-go record before production, then waves. Read:
Unit 3: The stages and the promotion
8 minutes
The live test stage deploys every component in an order where nothing grants access before it has been checked, and drills the rollback. Production needs a live, passing test smoke report and evidence check whose hashes are in its settings file, and the CISO’s and identity engineering’s approvals. Then it runs in waves, leaver first, with a week of drift monitoring. Read:
- Stage 1: live test, its order table.
- Promotion go/no-go criteria.
- Stage 2: production and evidence the ISSO keeps.
Unit 4: Exercise: Lab 1 (fixtures, no tenant)
20 minutes
Every command in this lab, in order, from examples/drift-pipeline. Each step below says what it does and what to look for.
# Step 1: Start clean
rm -rf out
# Step 2: Run the test stage's pre-flight check
python deploy/preflight.py --settings deploy/tests/fixtures/test.json --other deploy/tests/fixtures/production.json
# Step 3: Record four changes the way the stage steps do, then write the rollback
S=deploy/tests/fixtures/test.json
python deploy/rollback.py add --settings $S --kind provisioning-job --id API2AAD.00000000000000000000000000000000.44444444-4444-4444-8444-444444444401
python deploy/rollback.py add --settings $S --kind dynamic-group --id 88888888-8888-4888-8888-888888888801 --name "FIN ledger employees"
python deploy/rollback.py add --settings $S --kind gpo-link-created --id FMP-Workstation-Baseline --name "OU=ADM-Test,DC=test,DC=contoso,DC=example"
python deploy/rollback.py add --settings $S --kind policy-assignment --id adm-mc-tls-audit --name /subscriptions/22222222-2222-4222-8222-222222222201/resourceGroups/rg-adm-test
python deploy/rollback.py plan --settings $S
# Step 4: Run the smoke test
E=out/deploy-evidence/test/CHG-TEST-0101
python deploy/smoke_test.py --settings $S --fixture --kit-run tests/fixtures/runs/current/2026-10-29-0617-fixture-runner-fixture \
--hcm-provenance tests/fixtures/provenance/hcm-entra/provenance.json --dynamic-rules tests/fixtures/provenance/dynamic-rules/check-results.json \
--mc-provenance tests/fixtures/provenance/machine-configuration/provenance.json --mc-mode Audit --deploy-record $E/deploy-record.json --out $E/smoke
# Step 5: Check the stage's evidence
python deploy/evidence.py check --settings $S
(cd $E/evidence && sha256sum -c SHA256SUMS)
# Step 6: Ask production's pre-flight check to promote
python deploy/preflight.py --settings deploy/tests/fixtures/production.json --other $S --test-smoke-report $E/smoke/smoke-report.json --test-evidence-check $E/evidence-check.json
# Step 7: Run the deploy scripts' tests
python -m pytest deploy/tests -q
# Step 8: Clean up
rm -rf outYou dry-run the five deploy scripts against the kits’ fixtures. Work in examples/drift-pipeline, in a POSIX shell, and start with no out folder. Nothing signs in and nothing is called. These are the commands of Chapter 10: try the scripts against fixtures first, which shows the full output.
Start clean.
rm -rf outRun the test stage’s pre-flight check.
python deploy/preflight.py --settings deploy/tests/fixtures/test.json --other deploy/tests/fixtures/production.jsonExpected:
PASSonPF-01toPF-10, the live probes printed asDRY, and the last two lines:preflight: 0 failed, 0 warning(s); dry run (nothing signed in, nothing called) evidence 001-preflight.json: preflight passRecord four changes the way the stage steps do, then write the rollback.
S=deploy/tests/fixtures/test.json python deploy/rollback.py add --settings $S --kind provisioning-job --id API2AAD.00000000000000000000000000000000.44444444-4444-4444-8444-444444444401 python deploy/rollback.py add --settings $S --kind dynamic-group --id 88888888-8888-4888-8888-888888888801 --name "FIN ledger employees" python deploy/rollback.py add --settings $S --kind gpo-link-created --id FMP-Workstation-Baseline --name "OU=ADM-Test,DC=test,DC=contoso,DC=example" python deploy/rollback.py add --settings $S --kind policy-assignment --id adm-mc-tls-audit --name /subscriptions/22222222-2222-4222-8222-222222222201/resourceGroups/rg-adm-test python deploy/rollback.py plan --settings $SExpected: each
addrecords its change and writes an evidence record. The plan lists the four undo steps newest first, then:rollback plan: 4 step(s), newest first: 3 cloud, 1 ad wrote out/deploy-evidence/test/CHG-TEST-0101/rollback-cloud.ps1 wrote out/deploy-evidence/test/CHG-TEST-0101/rollback-ad.ps1 wrote out/deploy-evidence/test/CHG-TEST-0101/rollback-manual.md evidence 006-rollback.json: rollback plan recorded nothing was run: review the scripts, then run them in a signed-in sessionRun the smoke test on the fixture validation kit run and the other kits’ fixture provenance:
E=out/deploy-evidence/test/CHG-TEST-0101 python deploy/smoke_test.py --settings $S --fixture --kit-run tests/fixtures/runs/current/2026-10-29-0617-fixture-runner-fixture \ --hcm-provenance tests/fixtures/provenance/hcm-entra/provenance.json --dynamic-rules tests/fixtures/provenance/dynamic-rules/check-results.json \ --mc-provenance tests/fixtures/provenance/machine-configuration/provenance.json --mc-mode Audit --deploy-record $E/deploy-record.json --out $E/smokeExpected: exit 2, and a last line that starts
smoke test: NO-GO (5 failed) for test/CHG-TEST-0101, fixture mode. The fixtures carry findings on purpose. The report’s hash changes with each run, because the report records when it ran.Check the stage’s evidence, and let
sha256sumconfirm the records:python deploy/evidence.py check --settings $S (cd $E/evidence && sha256sum -c SHA256SUMS)Expected: exit 2, with
OKonly forR-PF-1andR-PF-2, andevidence check: 2 of 17 requirement rows evidenced for test/CHG-TEST-0101; FAIL. Then every record readsOKundersha256sum. Arecordedchange proves it was written down, not that the step worked.Ask production’s pre-flight check to promote on that report and that check:
python deploy/preflight.py --settings deploy/tests/fixtures/production.json --other $S --test-smoke-report $E/smoke/smoke-report.json --test-evidence-check $E/evidence-check.jsonExpected:
PASS PF-09, thenFAIL PF-10: the hashes aren’t the ones inpromotion, the smoke report failed and ran in fixture mode, and the evidence check failed. The last lines arepreflight: 1 failed, 0 warning(s); dry run (nothing signed in, nothing called)andevidence 001-preflight.json: preflight fail.Run the deploy scripts’ tests.
python -m pytest deploy/tests -qExpected:
46 passed.Clean up.
rm -rf out
Check your work: for a stage, you can say which evidence rows are proved, why a fixture run can never promote, and what production’s pre-flight check needs before it passes.
Unit 5: Exercise: Lab 2 (the live test stage)
Not timed. Not run by anyone yet.
Load the test stage’s settings, sign in as the deploy identity, run the pre-flight check dry and live, then follow the twelve steps of Stage 1: live test: baseline evidence, the HR middleware, dynamic rules, Lifecycle Workflows created disabled, Group Policy as code, machine configuration in audit, Ansible in check mode, the drift pipeline, the smoke test, the evidence package, the rollback drill and the evidence check.
- You need: an Entra test tenant, an Azure test subscription with Arc-enabled test servers, and a test AD forest, never production; the three identities and the objects listed in made before the runbook starts.
- One check by hand: the smoke test reads an app-only run of the validation kit, and VK-DG-03 is delegated only, so the smoke test lets it end unverified. That is a documented exception: at the first live run, the Groups Administrator signs in and runs VK-DG-03 by hand, as step 9b shows, and its result is a
smokeevidence record. A failed hand check holds promotion. - Back it out: step 11 runs the rollback written from the record, newest first, and the validation kit afterwards should match the baseline.
Unit 6: Knowledge check
5 minutes
- The smoke test runs on fixtures with
--fixture. Can its report promote to production?- Yes, if it says GO.
- No: production’s pre-flight check needs a live, passing test run, and a fixture record proves nothing about the stage.
- Yes, with the CISO’s approval.
rollback.py addwrites an evidence record for a step. Why does the evidence check still mark that row missing?- The record is
recorded: it proves the change was written down, not that the step worked. - The record has no hash.
- The rollback hasn’t run yet.
- The record is
- Why does the runbook use three identities instead of one?
- Graph limits each app to a few permissions.
- One identity that can do everything is one credential that can break everything.
- Each stage needs its own.
- What does production’s pre-flight check compare the test smoke report against?
- Nothing: it reads the report’s result.
- The SHA-256 recorded in
promotion.test_smoke_report_sha256, and the report’s result, mode and stage. - The validation kit’s last run.
- b. See promotion go/no-go criteria and steps 4 and 6 of the lab.
- a. See try the scripts against fixtures first and step 5 of the lab.
- b. The chapter marks it as design guidance. See the identities and what each may do.
- b. See promotion go/no-go criteria, criteria 1 and 2.
Unit 7: Summary
2 minutes
You dry-ran the runbook’s own scripts: a pre-flight check, changes recorded with their rollback, a smoke test that says no-go on fixtures, an evidence check against the requirement rows, and a production pre-flight that refuses to promote without a live, passing test stage.
Evidence an ISSO keeps from this module: for each stage, the settings file and its hash, preflight.json, the validation kit runs before and after, each kit’s outputs, smoke-report.json, the evidence package and its verify result, deploy-record.json and the rollback scripts, and the evidence/ folder with evidence-check.json; for production, the promotion record and seven days of chained drift packages per wave. The full table is evidence the ISSO keeps. Before you run a stage, work through the Chapter 10 checklist.