Preflight Active Plan Reassembly (policy blocker to re-rendered terraform)
Use this design to understand how the shipped pure loop responds when
deployment-preflight reports a policy_guardrail
or supply_chain_egress blocker that has a registered capability-mode toggle. The loop can
calculate and re-verify overrides that actively re-render the terraform plan into a supported
alternate shape. That shape never emits the denied operation.
Delivery as a fix PR through the existing
executor begins after live composition wiring lands.
This document is authoritative for the active-reassembly loop, its convergence and stop-conditions, the ActionType that carries it, and the honest limits of what can be reassembled. The blocker taxonomy, the toggle mapping table, and the report shape stay in deployment-preflight.md; the toggle modules themselves live in infra/modules/preflight-toggles/.
Customer-agnostic: no denylist value, mirror endpoint, or toggle default is baked in upstream. The upstream ships the reassembly machinery and the generic toggle catalog; a fork supplies the specific guardrail values and consumer wiring (generic-scope.instructions.md).
Why This Is Possible (and Not Magic)
Section titled “Why This Is Possible (and Not Magic)”The rails already exist; active reassembly connects them end to end:
- Detection - a
FeasibilityProbeemits a groundedProbeFinding(feasibility_probe.py). - Mapping - the detected issue carries a
ProbeResolution(kind=TERRAFORM_TOGGLE, autofix, module, set_vars)naming the exact infra sub-module and the variable override that makes the deploy comply. - Alternate rendering - the
preflight-toggles modules
encode the compliant shape (
disk_provisioning=attach_existing,registry_source=acr_mirror, …) as data-only Terraform.
The two pieces this design added now have these states:
- Toggle proposal builder (shipped): renders every
autofixtoggle in a cleared outcome as one typed proposal and submits it only behind the pre-publication gate. The live publisher binding is still absent, so it does not open a PR (check_publish.py). - Convergence loop (shipped): uses caller-provided plan-render and reanalysis callbacks to ensure a fix for one blocker cannot silently introduce another.
The Reassembly Loop
Section titled “The Reassembly Loop”Reassembly is a bounded, deterministic loop, never a single shot - a re-rendered plan must be re-checked because a toggle can move a blocker rather than remove it.
terraform plan (JSON) -> preflight.analyze -> CLEAR -> deliver plan / merge -> BLOCKED + autofix toggle for every blocking finding -> render tfvars override (reassemble) -> re-plan -> back to preflight.analyze (bounded) -> BLOCKED + a blocking finding has no autofix toggle -> hil (partial autofix is never applied)- All-or-nothing per pass: reassembly proceeds only when every blocking
detected issue has an
autofixtoggle. A single manual-resolution blocker routes the whole pass tohil- the loop never applies a partial fix that would still fail apply. - Verifier is authority: the reassembled plan is re-checked by the same deterministic preflight (OPA re-check + what-if), never trusted because a toggle was applied. This mirrors the quality-gate rule: execution eligibility is granted by verification, not by the fix generator.
Convergence and Stop-Conditions
Section titled “Convergence and Stop-Conditions”The loop MUST terminate. Its stop-conditions are safety invariants, not optimizations:
| Stop-condition | Effect |
|---|---|
max_reassembly_iterations (default 3) exceeded | route to hil, attach the last report |
| same toggle proposed twice for the same detected issue id | non-convergence -> hil (prevents flip-flop / infinite loop) |
| a reassembly pass produces more blocking detected issues than the prior pass | regression -> hil |
| any probe raises | fail-closed -> hil (never reassemble on a partial pass) |
The iteration counter, the per-detected issue toggle history, and the caps are
configuration, not hardcoded literals, so a fork can tune them without editing
core/.
ActionType: remediate.apply-preflight-toggle
Section titled “ActionType: remediate.apply-preflight-toggle”Active reassembly is not a new privileged path. It reuses the existing
executor by registering a first-class
ontology ActionType, so the seven safeguards, observation-first gating, and
the append-only audit entry come for free (the same reason the console vocabulary
routes every action through the typed pipeline, see
architecture.instructions.md).
The declaration (authored under rule-catalog/action-types/):
category: remediationtrigger_kind: both- the preflight loop initiates it automatically on a blocking detected issue, and an operator MAY request a specific toggle; it is parametric (argument_schema:scope,finding_id,toggle_module,set_vars,reason), so it is not a static resource-posture rule.execution_path: pr_native- the change is a tfvars-override PR against the infra repo, never a direct substrate mutation.rollback_contract: pr_revert- reverting the PR restores the prior tfvars; the reassembly is fully reversible, soirreversible: false.default_mode: shadow- the first ship judges and renders the PR as a draft with theshadowlabel; it never auto-merges.promotion_gate- measured on the frozen scenario set (false-positive rate of the toggle mapping) before any per-category promotion to enforcement mode.preconditions-graph_fresh_within_seconds(the plan and the environment profile must be current) andno_conflicting_open_action_on_resource.stop_conditions- the convergence caps above, plus the standardtime_box_exceeded_secondsandprovider_api_error_streak.blast_radius- the set of infra variables the override touches; a reassembly that would flip more toggles than the cap holds for review tohil.
Autofix Eligibility Gate
Section titled “Autofix Eligibility Gate”An autofix PR is proposed automatically only when all of these hold;
otherwise the detected issue degrades to guidance + hil:
- the resolution
kindisTERRAFORM_TOGGLEwithautofix: true; - the toggle is a deterministic data-only module (no LLM in the path);
- the reassembled plan re-passes preflight (verifier re-check);
- the override stays within the declared
blast_radius.
autofix: false toggles submit no proposal or diff. They remain manual guidance in the report,
the whole pass escalates, and the operator reviews the variable change.
Publication Is Granted by Re-Verification
Section titled “Publication Is Granted by Re-Verification”A cleared loop is a decision, not an authorization. The pre-publication gate
(pre_publication_gate.py)
re-runs the same analyzer on the accumulated overrides immediately before the
proposals are submitted, and withholds the whole pass when the fresh report is
blocked, stale, bound to another scope, or when the loop escalated. Every hold is
decided before the first submission, so a withheld pass submits nothing - never a
partial proposal set - and the caller routes it to hil
(publication holds).
Submission crosses the ingress boundary as preflight_toggle_blocker, a
control-plane signal rather than an operator request. Huginn honors a
payload-supplied action_type only for an explicit operator request, so this
signal carries none: Forseti binds remediate.apply-preflight-toggle from its own
event-type table and issues the default hil decision. A blocker therefore cannot
reach a provider commit without a distinct human approval, and no ingress producer
can spoof the ActionType. The same trust gate drops params for this signal, so the composed
live path still owes a governed way to carry the per-toggle arguments to the executor.
Action Granularity: One Action per Toggle
Section titled “Action Granularity: One Action per Toggle”A reassembly can apply several toggles (across detected issues and iterations). Each
applied toggle becomes its own remediate.apply-preflight-toggle Action -
not one bundled Action per pass. This keeps the ActionType’s argument_schema
single-toggle (finding_id + toggle_module + set_vars), so audit, rollback
(pr_revert), and impact scope stay at toggle granularity and map 1:1 to the
detected issue each toggle resolves. The loop retains the per-toggle provenance
(AppliedToggle: finding_id, module, set_vars, scope); the proposal
builder (reassembly_proposals.py)
renders one proposal per toggle and submits each through the same typed pipeline
seam an operator command re-enters (ProposalSink -> Huginn -> Forseti -> Thor),
observation-first. An escalated outcome yields no proposals - the caller routes it to
hil.
What Can and Cannot Be Reassembled
Section titled “What Can and Cannot Be Reassembled”Honesty about the boundary is a safety property, not a caveat:
- Reassemblable - blockers with a registered alternate rendering: inline disk
deny ->
attach_existing; blockeddocker.ioegress ->acr_mirror; NSG create deny ->byo; PyPI egress deny -> internalpython_index_url; ordering violation ->dependency_ordering=strict. - Not reassemblable (routes to
hil) - policies with no supported alternate: a region banned outright, a mandatory-tag policy, a denied SKU with no substitute SKU, or any guardrail whose only resolution is a scoped exemption or a governance decision. These emit aMANUALresolution and never auto-reassemble.
The preflight composition can report a recurring MANUAL blocker to Norns with a bounded
detected issue id, category, evidence source, and scope. Norns deduplicates scope digests and, after
three distinct scopes by default, proposes one inert preflight-toggle-gap candidate. The
candidate suggests a new reviewed alternate rendering and enters the standard quality gate;
it never creates a toggle or raises deployment authority
(architecture.instructions.md § Rule Catalog).
Safety Invariants
Section titled “Safety Invariants”Every reassembly action satisfies all seven safeguards, enforced by the executor it reuses:
- Stop-condition - the convergence caps above, declared on the ActionType.
- Rollback path -
pr_revert; the override PR is a single-commit revert away from the prior plan, and the rollback reference is embedded in the PR body. - Impact scope limit - the reassembly touches only the declared infra
variables; exceeding the cap holds for review to
hil. - Audit-log entry - the pure loop returns an audit-grade terminal reason and toggle provenance. Live composition writes the hash-chained audit record when it submits the result; the currently unwired core primitive does not call the audit store.
Reassembly ships observation-first: the PR is a draft, judged and rendered but not merged, until the toggle mapping’s false-positive rate is measured and the category is explicitly promoted to enforcement mode.
Subsystem Layout
Section titled “Subsystem Layout”| Piece | Location | Status |
|---|---|---|
| Toggle resolution on a detected issue | feasibility_probe.py | shipped |
| Capability-mode toggle modules | infra/modules/preflight-toggles/ | shipped (data-only) |
| Readiness report + decision | core/deploy_preflight/report.py | shipped |
| Report -> PR check publish | core/deploy_preflight/check_publish.py | shipped (report only) |
| Convergence loop + stop-conditions | core/deploy_preflight/reassemble.py | shipped |
remediate.apply-preflight-toggle ActionType | rule-catalog/action-types/ | shipped |
| Overrides -> Action proposals (one per toggle) | core/deploy_preflight/reassembly_proposals.py | shipped |
| Re-verification before publication | core/deploy_preflight/pre_publication_gate.py | shipped |
| Recurring manual blocker -> inert candidate | agents/norns.py | shipped (caller-supplied observation) |
| Reference consumer wiring (one toggle) | infra/modules/preflight-toggles/reference-disk-consumer/ | shipped (fork copies it) |
Composition wiring: ProposalSink + live trigger | composition root + delivery/azure/preflight/ | remaining |
core/ sees only the FeasibilityProbe Protocol and a caller-supplied ProposalSink callable;
the reassembly loop constructs no cloud SDK
and opens no PR itself - it decides the overrides and hands them to the executor
(via the ActionType), which owns the publish and the invariants.
Delivery Increments
Section titled “Delivery Increments”Each is separately reviewable:
- Docs-first (this document) - the loop, ActionType, and limits. (shipped)
- The
remediate.apply-preflight-toggleActionType YAML + schema validation. (shipped) - The bounded convergence loop, observation mode, with property tests: “same toggle never applied twice”, “partial blocker -> human approval”, “reassembled plan is re-verified”, “regression -> human approval”, “fail-closed on a raising reanalyze”. (shipped)
- One reference consumer wiring (the
disk_provisioningtoggle) underinfra/so a fork has a copy-paste starting point. (shipped) - The overrides-to-executor step: render each applied toggle into a
remediate.apply-preflight-toggleproposal (one Action per toggle, granularity A) and submit through the typed pipeline seam. (shipped) - Composition wiring (bind the gated
ProposalSinkto Huginn ingest at the runtime composition root) plus live Azure adapters that feed real policy detected issues into the loop and open the tfvars-override PR (after the preflight live adapters land, observation-first). The gated seam and its Huginn/Forseti integration test exist; the composition root and live trigger are (remaining).
References
Section titled “References”- deployment-preflight.md - probe taxonomy, toggle mapping table, report shape
- infra/modules/preflight-toggles/README.md - the capability-mode toggle modules
- architecture.instructions.md - control loop, quality gate, safety invariants, action ontology
- project-structure.md - executor, module boundaries, infra sub-module pattern
- risk-classification.md - how a blocking detected issue routes to
hil - coding-conventions.instructions.md - the seven safeguards, observation-first, ActionType contract
Related docs
Section titled “Related docs”| To learn about | Read |
|---|---|
| Delivery status and remaining work | Implementation ledger |