Skip to content

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).

AreaStateEvidenceNotes
Bounded convergence and fail-closed stop conditionsimplementedservices/core-control-plane/src/fdai/core/deploy_preflight/reassemble.py and focused reassembly testsManual blockers, repeated toggles, regressions, iteration caps, and raised reanalysis all stop without applying a partial result.
One proposal per applied toggleimplementedservices/core-control-plane/src/fdai/core/deploy_preflight/reassembly_proposals.py and test_reassembly_proposals.pyCleared outcomes produce deterministic, idempotent proposal envelopes; escalated outcomes submit none.
ActionType, data-only toggle modules, and reference consumerimplementedrule-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 primitiveimplementedservices/core-control-plane/src/fdai/agents/_framework/norns_deployment_learning.py and services/core-control-plane/tests/agents/test_norns_preflight.pyNorns emits an inert candidate from caller-supplied observations; it does not create or promote a toggle.
Live trigger, plan renderer, pipeline binding, PR, and auditnot-startedThe composition boundary in this documentNo production composition invokes the loop and binds its ProposalSink to Huginn and the PR/audit path.
DateStateChangeEvidenceRemaining
2026-08-14in-progressAdopted 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 tableCompose a live observation mode path and retain PR plus audit evidence.
  • Bind live policy detected issues to a caller-owned plan renderer and prove the same analyzer re-verifies every generated override.
  • Bind ProposalSink through 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_revert rollback evidence.

The rails already exist; active reassembly connects them end to end:

  1. Detection - a FeasibilityProbe emits a grounded ProbeFinding (feasibility_probe.py).
  2. 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.
  3. 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 autofix toggle 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.

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 autofix toggle. A single manual-resolution blocker routes the whole pass to hil - 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.

The loop MUST terminate. Its stop-conditions are safety invariants, not optimizations:

Stop-conditionEffect
max_reassembly_iterations (default 3) exceededroute to hil, attach the last report
same toggle proposed twice for the same detected issue idnon-convergence -> hil (prevents flip-flop / infinite loop)
a reassembly pass produces more blocking detected issues than the prior passregression -> hil
any probe raisesfail-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: remediation
  • trigger_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, so irreversible: false.
  • default_mode: shadow - the first ship judges and renders the PR as a draft with the shadow label; 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) and no_conflicting_open_action_on_resource.
  • stop_conditions - the convergence caps above, plus the standard time_box_exceeded_seconds and provider_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 to hil.

An autofix PR is proposed automatically only when all of these hold; otherwise the detected issue degrades to guidance + hil:

  1. the resolution kind is TERRAFORM_TOGGLE with autofix: true;
  2. the toggle is a deterministic data-only module (no LLM in the path);
  3. the reassembled plan re-passes preflight (verifier re-check);
  4. 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.

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.

Honesty about the boundary is a safety property, not a caveat:

  • Reassemblable - blockers with a registered alternate rendering: inline disk deny -> attach_existing; blocked docker.io egress -> acr_mirror; NSG create deny -> byo; PyPI egress deny -> internal python_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 a MANUAL resolution 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).

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.

PieceLocationStatus
Toggle resolution on a detected issuefeasibility_probe.pyshipped
Capability-mode toggle modulesinfra/modules/preflight-toggles/shipped (data-only)
Readiness report + decisioncore/deploy_preflight/report.pyshipped
Report -> PR check publishcore/deploy_preflight/check_publish.pyshipped (report only)
Convergence loop + stop-conditionscore/deploy_preflight/reassemble.pyshipped
remediate.apply-preflight-toggle ActionTyperule-catalog/action-types/shipped
Overrides -> Action proposals (one per toggle)core/deploy_preflight/reassembly_proposals.pyshipped
Recurring manual blocker -> inert candidateagents/norns.pyshipped (caller-supplied observation)
Reference consumer wiring (one toggle)infra/modules/preflight-toggles/reference-disk-consumer/shipped (fork copies it)
Composition wiring: ProposalSink + live triggercomposition 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.

Each is separately reviewable:

  1. Docs-first (this document) - the loop, ActionType, and limits. (shipped)
  2. The remediate.apply-preflight-toggle ActionType YAML + schema validation. (shipped)
  3. 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)
  4. One reference consumer wiring (the disk_provisioning toggle) under infra/ so a fork has a copy-paste starting point. (shipped)
  5. The overrides-to-executor step: render each applied toggle into a remediate.apply-preflight-toggle proposal (one Action per toggle, granularity A) and submit through the typed pipeline seam. (shipped)
  6. Composition wiring (bind the ProposalSink to 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)