Expand description
Defensive Filing Wizard, sub-project-2 Phase P-A, Task 1 — the PROMOTE chokepoint: a reusable
plan/confirm/apply pipeline extracted VERBATIM from the shipped CLI verb
(cmd::promote::promote_tranche, Approach-B Task 10, cmd/promote.rs:364-488) so a future TUI can
drive the EXACT SAME gated pipeline as the CLI. cmd::promote::promote_tranche is now a thin driver
over this module: Session::open → build args → plan_promote (map Refusal → CliError) →
println!("{}", render_consent(&plan)) → prompt/collect ack → apply_promote.
Gate ordering (DFW-D2) MUST match cmd/promote.rs:378-485 exactly: resolve-live → BG-D5
provenance → BG-D7 Part II → BG-D3 floor/coverage → BG-D6 consent_terms → synthetic-promote advisory
→ gift-only relabel → consent render (incl. wide_window_note) → ack inside apply_promote,
fail-closed → would_conflict → append.
★ I-1 (byte-parity): the shipped verb prints, IN ORDER (promote.rs:443-455), the
synthetic-promote ADVISORY (pre-consent) → render_consent(&terms, &gift_only_years) → the
wide_window_note (post-consent). PromotePlan therefore carries THREE ordered pieces
(advisory_lines, gift_only_years, post_consent_note) so this module’s render_consent(&plan)
reproduces advisory → consent → note byte-for-byte when printed via a single println! — a single
flat Vec cannot place terms BETWEEN the pre-advisory and the note; do NOT collapse the three. The
shipped render_consent(terms, gift_only_years) stays in cmd::promote (still pub — external KATs
in tests/promote_cli.rs call it directly — and is invoked from here); gift_only_flagged_years/
wide_window_note move HERE.
★ DFW-D6 (the ONE intended behavior change — the sub-1 pseudo-off fix): plan_promote forces
cfg.pseudo_reconcile = false on its own COPY (ProjectionConfig is Copy) before consent_terms /
promote_prior_year_advisory / gift_only_flagged_years — mirroring would_conflict
(project/mod.rs:118). Without this, a pseudo-active vault’s consent screen — and the RECORDED
Acknowledgment.shown_terms, the §6664(c) good-faith artifact — could fold in a synthetic default that
was never persisted, misstating what the filer actually acknowledged.
★ arch-m-6/tax-N-1: Refusal::Target covers the resolve-live gate — unknown/voided/wrong-type
target only (resolve_live_tranche). Already-promoted (a DOUBLE promote) is NOT caught here; it
surfaces as would_conflict at APPLY time (a CliError, never a plan Refusal) — mirroring
promote.rs:475-483.
★ arch-m-new-3: plan_promote takes no Session/state — the shipped pipeline rebuilds
everything from events (promote.rs:364-488) — so a caller (CLI or future TUI) supplies its own
already-loaded events/prices/cfg.
Task 2 — the DECLARE chokepoint: plan_declare/apply_declare, extracted from the shipped
cmd::tranche::declare_tranche (tranche.rs:120-175). plan_declare gates on the shipped set
(sat>0, ws<=we, guard_tranche_vs_allocation — the LAST one stays defined in cmd::tranche, not
duplicated here) ALWAYS; iff target_shortfall = Some(id) it ALSO runs the DFW-D5.2 target-scoped
clearance shadow: append the candidate DeclareTranche → re-project (pseudo FORCED off, mirroring
would_conflict) → assert no BlockerKind::UncoveredDisposal remains on id; else Refusal::Coverage.
target_shortfall = None (the CLI free-form path) is the shipped declare_tranche gate set,
BYTE-FOR-BYTE (DFW-D8/SPEC §5) — no clearance shadow runs at all. apply_declare is a plain
append+save (declaring is $0/revocable/no-Form-8275 — DFW-D8 — so unlike promote there is no
acknowledgment gate and no would_conflict pre-check; the shipped verb never ran one either).
★ Refusal-variant note: the shared Refusal enum stays CLOSED at the four Task-1 variants (the
plan review explicitly rejected adding a new Conflict variant for this task). The shipped-set gates
(sat/window/allocation) have no promote-shaped variant of their own, so — like the new clearance
failure the brief names explicitly — they map to Refusal::Coverage. Every variant collapses to the
identical CliError::Usage(msg) via From<Refusal> (below), so this is a pure internal-taxonomy
choice: the filer-facing message text is unchanged from the shipped verb either way.
Structs§
- Declare
Plan - Everything needed to append a
DeclareTranchedecision. UnlikePromotePlan, declaring has no acknowledgment gate or consent screen (DFW-D8 — a plain$0, revocable confirmation) — so there is nothing else to carry beside the payload itself. - Promote
Plan - Everything computed BEFORE the filer types the acknowledgment phrase (the
PromoteTranchedecision id,target, is not yet known — it is assigned atapply_promote’sappend_decision). ★ I-1: the three ordered fields (advisory_lines,gift_only_years,post_consent_note) letrender_consentreproduce the shipped verb’sadvisory → consent → notebyte order — do NOT collapse them into oneVecor pre-rendergift_only_yearsinto a string.
Enums§
- Refusal
- A
plan_promoterefusal — fail-closed, BEFORE any computation past the failing gate. Each variant carries the exact filer-facing message (byte-identical to the shipped verb’sCliError::Usagetext), so mapping toCliError(the thin CLI driver) or a distinct TUI error surface is trivial either way.
Functions§
- apply_
declare - Apply a planned declare: append + save. No acknowledgment gate and no
would_conflictpre-check (DFW-D8 — declaring is a plain$0, revocable confirmation, unlike promote’s typed-phrase tier; the shipped verb never ran awould_conflictcheck either —cmd/tranche.rs:166-174appends immediately onceguard_tranche_vs_allocationpasses). - apply_
promote - Apply a planned promote: the acknowledgment gate (BG-D6, fail-closed, INSIDE apply) →
would_conflictpre-check (BG-D9 — a second live promote on this target, or any other resolver-level conflict; refuses BEFORE appending, NOT last-wins) → append + save. Reloadsevents/cfgfresh fromsession(arch-m-new-3:plan_promotetook noSession) — a single synchronous CLI/TUI invocation cannot append anything betweenplan_promoteandapply_promote, so this is behavior-preserving. - plan_
declare - Plan a
DeclareTranchedecision. Gates on the shipped set ALWAYS —sat>0,ws<=we,guard_tranche_vs_allocation(cmd::tranche, the single source of that guard for all four allocation append sites — NOT duplicated here) — replicatingcmd/tranche.rs:134-154exactly (arch-m-new-3: noSession— the caller supplies its own already-loadedevents/prices/cfg, mirroringplan_promote). - plan_
promote - Plan a
PromoteTranchedecision — the DFW-D2 gate order, everything computable BEFORE the filer types the acknowledgment phrase: resolve-live → BG-D5 provenance → BG-D7 Part II → BG-D3 floor/coverage → BG-D6consent_terms→ synthetic-promote advisory → gift-only relabel.events/prices/cfgare the caller’s own already-loaded state (arch-m-new-3: noSessionhere — the CLI’s thin driver and a future TUI each supply their own). - render_
consent - Re-emit the shipped verb’s ordered filer-visible text:
advisory_lines→ the shippedrender_consent(&plan.terms, &plan.gift_only_years)(cmd::promote) →post_consent_note— ★ I-1: byte-identical topromote.rs:443-455when the RESULT is printed via a singleprintln!("{}", render_consent(&plan))(the shipped verb instead used three separateprintln!calls; a single combined string reproduces the exact same stdout bytes becauseprintln!always adds exactly one trailing\n). Do NOT collapse this intoplan.terms/plan.gift_only_yearsalone — the pre-advisory must land BEFORE the consent screen and the note AFTER it.