feat(v1.100 PR-24): authority restoration policy decision engine#494
Merged
feat(v1.100 PR-24): authority restoration policy decision engine#494
Conversation
Implements the pure decision engine defined by the merged contract seed (PR #493). PR-24 is POLICY ONLY — no kernel, service, or filesystem mutation, no history write, no restore execution code. Scope (locked per seed §2): - internal/installer/restore/ decision engine - --mode=restore CLI dispatcher - state-machine entries: StateRestoreRefused, StateRestoreIntentRequired (terminal, non-failed, non-apply-terminal); StateRestoreDecided (non-terminal handoff marker) - exit codes: ExitRefused=5, ExitIntentRequired=6 (distinct from generic failure to enable scriptability) - 4 CI gates in G4-RESTORE-* namespace Lattice (seed §6 v2 + locked amendments): - Top-down precedence: classifier hard-stops → input validity → prior-record integrity → panel gates → proceed decisions - AuthorityNFTBan / AuthorityExternal / AmbiguityConflictExternal → REFUSE absolutely (no flag may override) - NoRecord + --restore → REQUIRE_EXPLICIT_INTENT (locked amendment: no implicit target) - NoRecord + --panel-auto-takeover + panel → PROCEED (panel-auto carries its own target) - Complete + ActiveAtInstall=false → REQUIRE_EXPLICIT_INTENT (any flag; restore semantics ambiguous between preserve-inactive and activate) - Stale / Incomplete → REQUIRE_EXPLICIT_INTENT - Orphan + --panel-auto-takeover → REFUSE (panel-auto must not fire over nftban residue) - Staleness window fixed at 365 days (seed §3.B; configurability is a deferred follow-up) Output is a closed enum of three values: PROCEED, REFUSE, REQUIRE_EXPLICIT_INTENT. No fourth output; no default branch; no fallthrough. Unreached code paths panic as contract-regression guards. Tests (internal/installer/restore/engine_test.go): - Rule-path coverage matrix — every Rule* constant must have a fixture - Determinism — reflect.DeepEqual on two back-to-back evaluations - Closed-enum output invariant - Locked-amendment guards (NoRecord + --restore in both AuthorityNone and AmbiguityOrphanNFTBan paths) - Hard-stop dominance — Group 1 refuses under every flag / prior / panel combination (4 * 5 * 2 * 3 = 120 cells per hard-stop) - Orphan + panel-auto refusal under every prior state CI gates (.github/workflows/ci-restore-canonization.yml): - G4-RESTORE-NO-IMPLICIT-EXEC — static scan for forbidden symbols (exec.*, nft/iptables/systemctl literal calls, service helpers, os.Create/WriteFile/Rename/Remove/Mkdir, rebuild/switchop/services mutation helpers) - G4-RESTORE-DECISION-CORRECTNESS — runs the full fixture matrix including rule-path coverage assertion - G4-RESTORE-REFUSAL-INTEGRITY — structural check that restore package has no executor dependency AND dispatcher does not import mutation- capable packages - G4-RESTORE-DETERMINISM — two independent test runs; normalized diff must be empty Dispatcher (cmd/nftban-installer/restore_decide.go): - Classifies authority via uninstall.Classify (single source of truth) - Probes prior-record via uninstall.Probe (PR-P2-1 hardened schema) - Reduces uninstall.PriorRecordState + 365-day window → restore.PriorState - Detects panel via detect.DetectPanel - Assembles DecisionInput; calls restore.Decide (pure) - Preflight errors (malformed record, Ambiguous invariant violation) short-circuit to ExitFatal WITHOUT emitting a lattice output — keeps the three-output space closed (seed §9) - Transitions state file to terminal (Refused / IntentRequired) or non-terminal (Decided) - history.json write skipped: IsApplyTerminal=false for all three states + main.go gate now also excludes cfg.mode=="restore" as belt-and-braces defense No real-host mutation. Decision-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.OpenSSF Scorecard
Scanned Files
|
5 tasks
itcmsgr
added a commit
that referenced
this pull request
Apr 27, 2026
#510) Appends the PR-25 execution contract to internal/installer/restore/ contract.md as a new "PART II — PR-25 execution contract" section (§§16-29). This is the doc-only first PR of the PR-25 two-PR split, mirroring the PR-24 PR #493 → PR #494 pattern. The implementation PR opens in a separate branch after this one merges. Origin: The contract is a faithful normalization of the locked Q1-Q5 design decisions (recorded 2026-04-20 during PR-24 freeze Day 0 via the §12 protocol: Stage 1 scope classification + Stage 2 five-field answer + LOCK/REVISE/REJECT review). The v0 staging sheet (memory/project_pr25_contract_sheet_v0.md) was reviewed and locked 2026-04-27 prior to opening this PR. Locked rule applied: "Normalize, do not expand." Every clause in §§16-29 traces back to a Q1-Q5 lock or to V1100 contract §8. No design decisions were made in this PR. Section map: - §16 Purpose - §17 Scope (Option A) + 2 named invariants - §18 TargetAuthority concretization (Q2) - §19 StateRestoreDecided downstream meaning (Q3) - §20 Panel-auto target consistency (Q4) - §21 Post-restore verification split (Q5) - §22 State terminals + exit codes (candidates) - §23 Execution shape (V1100 §8 ordered) - §24 Inputs PR-25 may consume - §25 Forbidden behaviors (consolidated) - §26 Cross-lock consistency - §27 What this contract does NOT contain (intentional) - §28 Merge-blocking real-host matrix (code phase) - §29 Reviewer checklist (code phase) Verified live code anchors (2026-04-27): - knownFirewallType set {ufw, firewalld, iptables, csf} at internal/installer/uninstall/prior.go:278-284 - writeHistory gate excluding cfg.mode == "restore" at cmd/nftban-installer/main.go:132 - Exit-code constants ExitCommitted=0/ExitFatal=4/ExitRefused=5/ ExitIntentRequired=6 at internal/installer/state/machine.go:149-155 §1-§15 (PR-24 decision contract) are untouched. Out of scope (locked): - No code in this PR. PR-25 implementation is the next PR (feat/v1.100-pr25-restore-execution). - No expansion of Q1-Q5 lock content. - PR-26 contract stays out of scope. Lifecycle completion lane (PR-25..PR-30) remains explicitly OPEN but is now mid-re-entry: contract is the first deliberate step. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the pure decision engine locked by PR #493 (contract seed). PR-24 is POLICY ONLY — zero kernel, service, or filesystem mutation. Refusal and intent-required are valid and expected outcomes.
Scope (locked per seed §2)
internal/installer/restore/decision engine (pure function over 4 axes)--mode=restoreCLI dispatcherStateRestoreRefused,StateRestoreIntentRequired(terminal, non-failed, non-apply-terminal) +StateRestoreDecided(non-terminal handoff marker, per seed §7 locked constraints)ExitRefused=5,ExitIntentRequired=6G4-RESTORE-*namespaceIsApplyTerminal=false+cfg.mode != "restore"gate)Lattice (seed §6 v2 + locked amendments)
Top-down precedence (§5, load-bearing):
Locked amendments implemented:
NoRecord + --restore → REQUIRE_EXPLICIT_INTENT(not PROCEED —--restorerequires a recorded target)ActiveAtInstall→ classified asIncompletebyuninstall.Probe(PR-P2-1) → flows to REQUIRE_EXPLICIT_INTENTStateRestoreDecidednon-terminal, non-apply-terminal, excluded from history, not evidence of restorationOutput discipline
Closed Go enum of exactly three values:
No fourth value. No default branch. Unreached code paths
panic()as contract-regression guards (CI catches them).Tests
Fixture matrix in
internal/installer/restore/engine_test.go:TestDecide_FixtureMatrix— 24 rule-path fixtures (every declared rule exercised)TestRuleCoverage_EveryRuleExercised— bidirectional coverage assertion (declared ↔ fixtures)TestDecide_Determinism— reflect.DeepEqual on back-to-back evaluationsTestDecide_OutputClosedEnum— no path emits outside the 3-value enumTestDecide_LockedAmendment_NoRecordRestoreRequiresIntent— explicit guard for the locked amendment in both AuthorityNone + AmbiguityOrphanNFTBan pathsTestDecide_HardStopsDominateAnyFlag— exhaustive matrix: 3 hard-stops × 4 flag combos × 5 priors × 2 panel states = 120 cells per hard-stop, all must REFUSETestDecide_OrphanPanelAutoRefused— orphan +--panel-auto-takeoverREFUSES under every priorCI gates
.github/workflows/ci-restore-canonization.yml(matrix: ubuntu-24.04 + almalinux-9):G4-RESTORE-NO-IMPLICIT-EXECexec.*,os.Create/WriteFile/Rename/Remove/Mkdir,nft/iptables/systemctlliteral calls, service helpers, rebuild/switchop mutation helpers)G4-RESTORE-DECISION-CORRECTNESSG4-RESTORE-REFUSAL-INTEGRITYexecutor; dispatcher does not importswitchop/servicesG4-RESTORE-DETERMINISMNo G3 gate weakened. Carry-forward uninstall gates (
G3-UN-SHIM-LOCK,G3-UN-NO-MUTATION,G3-EXEC-TRACE,G3-KS-SNAPSHOT) continue to apply.Dispatcher flow (
cmd/nftban-installer/restore_decide.go)uninstall.Classify→ authority + ambiguity (single source of truth)uninstall.Probe→ prior-record (PR-P2-1 hardened schema)restore.PriorStatenormalized enumdetect.DetectPanel→ panel type → booleanDecisionInputrestore.Decide(input)— pure, no side effectssf.Transitionto terminal / non-terminal statePreflight errors (malformed prior record, classifier
Ambiguouswithout sub-kind) →ExitFatal=4— does NOT emit a lattice output, keeping the three-output space closed per seed §9.Reviewer checklist (merge-blocking, per seed §13 + code-phase §6)
Policy correctness:
--panel-auto-takeoverNoRecord + --restorereturnsREQUIRE_EXPLICIT_INTENTActiveAtInstall→Incomplete→REQUIRE_EXPLICIT_INTENTSafety:
AuthorityExternalnever overriddenAmbiguityConflictExternalnever overridden--panel-auto-takeover→REFUSEREQUIRE_EXPLICIT_INTENTtoPROCEEDPurity:
update-history.jsonschema unchangedOutput discipline:
StateRestoreDecidedexcluded fromIsApplyTerminaland from historyExitRefused=5,ExitIntentRequired=6Evidence:
Real-host evidence (2026-04-20)
Required decision-only host matrix satisfied. Supplementary
AuthorityNFTBan → REFUSEbranch also proven on real host. Remaining dangerous branches (AuthorityExternal,AmbiguityConflictExternal,AmbiguityOrphanNFTBan, weak-record, panel-driven proceed) are fixture-only per seed §11 — simulating them at kernel level would violate the no-mutation gate via the test harness itself.Binary provenance
Frozen CI artifact from commit
7330f8cb:Identical binary used on both hosts.
lab4 — AlmaLinux 9.7 / RPM (required matrix)
Host state:
AuthorityNone + NoRecord(released post-PR-23, never reinstalled).--mode=restore(bare)G3.3/NoRecord+NoFlagREQUIRE_EXPLICIT_INTENT--mode=restore --restore-prior-authorityG3.3/NoRecord+RestoreREQUIRE_EXPLICIT_INTENTTerminal state on both:
RESTORE_INTENT_REQUIRED. Panel:cpanel(informational; Group 3.3 bare/restore paths do not depend on panel context).lab2 — Ubuntu 24.04 / DEB (supplementary)
Host state:
AuthorityNFTBan(from PR-23 reinstall round-trip; intentionally not forced into AuthorityNone).--mode=restore(bare)G1/AuthorityNFTBanREFUSETerminal state:
RESTORE_REFUSED. Panel:plesk(informational; Group 1 dominates regardless of panel).Purity proof (all three invocations)
Process spawns observed via
strace -f -e trace=execve:Zero spawns of:
nftwithadd/flush/delete/create/insert/replacesystemctlwithstart/stop/enable/disable/mask/unmask/reload/restart/daemon-reloadiptables/ip6tables/ufw/firewall-cmd(any args)Host-state invariance
nft list tablesnftband.servicenft list tablesinet filter+ip nftban+ip6 nftbannftband.serviceZero host-state change across all three invocations.
Test plan
Build & Testgreen — fixture matrix + coverage + determinism + closed-enum + locked-amendment + hard-stop dominance + orphan+panel-auto refusalRestore Canonization Gategreen — G4-RESTORE-NO-IMPLICIT-EXEC + G4-RESTORE-DECISION-CORRECTNESS + G4-RESTORE-REFUSAL-INTEGRITY + G4-RESTORE-DETERMINISM across DEB + RPM matrixAuthorityNFTBan → REFUSE)Follow-up items (seed §15, carried forward, not blocking PR-24)
ActiveAtInstallcapture in new prior-record writes🤖 Generated with Claude Code