Preflight Active Plan Reassembly (policy blocker to re-rendered terraform)
When deployment-preflight reports a policy_guardrail
or supply_chain_egress blocker that has a registered capability-mode toggle,
the shipped pure 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).
Implementation status
Section titled “Implementation status”Implementation scope
Section titled “Implementation scope”| Area | State | Evidence | Notes |
|---|---|---|---|
| Bounded convergence and fail-closed stop conditions | implemented | services/core-control-plane/src/fdai/core/deploy_preflight/reassemble.py and focused reassembly tests | Manual blockers, repeated toggles, regressions, iteration caps, and raised reanalysis all stop without applying a partial result. |
| One proposal per applied toggle | implemented | services/core-control-plane/src/fdai/core/deploy_preflight/reassembly_proposals.py and test_reassembly_proposals.py | Cleared outcomes produce deterministic, idempotent proposal envelopes; escalated outcomes submit none. |
| ActionType, data-only toggle modules, and reference consumer | implemented | rule-catalog/action-types/remediate.apply-preflight-toggle.yaml and infra/modules/preflight-toggles/ | These artifacts define the governed action and one reference Terraform consumption pattern. |
| Recurring manual-blocker learning primitive | implemented | services/core-control-plane/src/fdai/agents/_framework/norns_deployment_learning.py and services/core-control-plane/tests/agents/test_norns_preflight.py | Norns emits an inert candidate from caller-supplied observations; it does not create or promote a toggle. |
| Live trigger, plan renderer, pipeline binding, PR, and audit | not-started | The composition boundary in this document | No production composition invokes the loop and binds its ProposalSink to Huginn and the PR/audit path. |
Implementation history
Section titled “Implementation history”| Date | State | Change | Evidence | Remaining |
|---|---|---|---|---|
| 2026-08-14 | in-progress | Adopted the implementation ledger; earlier provenance was not reconstructed. Kept pure reassembly mechanics separate from the uncomposed delivery path. | current change; focused reassembly, proposal, and Norns tests listed in the scope table | Compose a live observation mode path and retain PR plus audit evidence. |
Remaining work
Section titled “Remaining work”- Bind live policy detected issues to a caller-owned plan renderer and prove the same analyzer re-verifies every generated override.
- Bind
ProposalSinkthrough Huginn to the governed pipeline and pass an integration test proving blocked or escalated outcomes open no PR. - Publish one observation mode tfvars-override PR per toggle and retain its append-only audit intent, terminal outcome, and tested
pr_revertrollback evidence.
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. The live sink/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.
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 |
| 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
ProposalSinkto Huginn ingest) 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). (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