You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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>
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 (mirrors the PR-24 PR #493 → PR #494 pattern). The implementation PR opens in a separate branch after this one merges.
17
+
18
+
### Origin
19
+
20
+
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.
21
+
22
+
### Locked rule applied
23
+
24
+
> *"Normalize, do not expand."*
25
+
26
+
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. Items intentionally absent (final state-terminal names, final exit-code integers, test fixture matrix, real-host evidence plan, CI gate expansion plan, concrete `IsRestoreExecuted` signature, the PanelType→firewall mapping body) are documented in §27 as code-phase work.
27
+
28
+
### What changed
29
+
30
+
-`internal/installer/restore/contract.md` — appended §§16–29 (PR-25 execution contract) after §15 and before "Amendment history"; §1–§15 (PR-24 decision contract) untouched. New "PART II" header marks the boundary. Amendment history gains a 2026-04-27 v2 entry documenting the append + verified code anchors.
31
+
32
+
### Verified code anchors (2026-04-27)
33
+
34
+
| Lock reference | Verified at |
35
+
|---|---|
36
+
|`knownFirewallType` set `{ufw, firewalld, iptables, csf}`|`internal/installer/uninstall/prior.go:278-284`|
All three live in surfaces that were fenced under the PR-24 freeze and remained untouched throughout the GOTH removal + SF-1 + repo hygiene stabilization train.
41
+
42
+
### Out of scope (locked)
43
+
44
+
-**No code in this PR.** PR-25 implementation (Go types, execution engine, inline safety interlock, state terminals, tests, CI gate update) is the *next* PR (`feat/v1.100-pr25-restore-execution`) and opens only after this contract PR merges.
45
+
-**No expansion** of Q1–Q5 lock content.
46
+
- PR-26 contract content stays out of scope (defined by PR-26's own seed work).
47
+
48
+
Lifecycle completion lane (PR-25..PR-30) remains explicitly **OPEN** but is now mid-re-entry: contract is the first deliberate step.
49
+
50
+
---
51
+
14
52
## [Unreleased] - v1.100.3e Repo hygiene Phase A slice 1e (H-07 + H-08)
15
53
16
54
Closes audit findings **H-07** (STATUS.md version drift) and **H-08** (README.md badge version drift). Both displayed visible-version strings that no longer matched `/VERSION` (1.98.2). STATUS.md claimed v1.89.0 (-9 minor versions); README badge pinned 1.95.0 (-3 patches).
PR-25 is **restore execution only**. Bundled purge / remove / artifact-cleanup semantics are EXPLICITLY excluded.
354
+
355
+
### 17.1 Permitted (PR-25 may do)
356
+
357
+
- Kernel `nft` mutations within the authorized target's surface
358
+
- Service **run-state** changes: `start` / `stop` of `nftband.service` and the target-native firewall service (`ufw` / `firewalld` / `iptables` / `csf` per §20)
359
+
- Emergency-SSH safety net: insertion before mutation, removal after inline verification (§21)
360
+
- New execution-outcome state terminals + exit codes (§22)
-**INV-PR25-AUTHORITY-IMMUTABILITY**: `TargetAuthority` resolved by PR-24 PROCEED is unchanged across the entire PR-25 execution window. No mid-flight re-resolution.
381
+
-**INV-PR25-STATIC-SERVICE-LIFECYCLE**: PR-25 makes no change to systemd unit policy, enable/disable state beyond run-state, or unit files.
| `None` | `TargetNone()` OR zero value `TargetAuthority{}` (equivalent and intentional) | n/a |
420
+
| `RecordedPrior` | ONLY `TargetRecordedPrior(firewallType)` | `firewallType ∈ knownFirewallType` set defined at `internal/installer/uninstall/prior.go:278-284`: `{ufw, firewalld, iptables, csf}` |
421
+
| `PanelNative` | ONLY `TargetPanelNative(panel)` | `panel ≠ detect.PanelNone` |
422
+
423
+
### 18.3 Payload invariants (per Kind)
424
+
425
+
| Kind | Required fields shape |
426
+
|---|---|
427
+
| `None` | `firewallType=""`, `panel=PanelNone` |
428
+
| `RecordedPrior` | `firewallType ∈ known set`, `panel=PanelNone` |
- `Kind` enum is closed at the type level. Adding a variant requires §12-style review.
434
+
- Default branch in any `Kind` switch MUST `panic` at runtime.
435
+
- No exported mutators. Post-construction immutability is by type design — not enforced by CI/linter (acknowledged limit).
436
+
437
+
## 19. StateRestoreDecided downstream meaning (locked, Q3)
438
+
439
+
### 19.1 Semantic rule
440
+
441
+
> *"Exit code 0 from `--mode=restore` with `StateRestoreDecided` is a policy-handoff outcome only. It is NOT evidence that any restoration mutation has been executed."*
442
+
443
+
### 19.2 Four enforcement layers
444
+
445
+
| Layer | What | Where |
446
+
|---|---|---|
447
+
| 1 — Type-level distinctness (narrow) | `StateRestoreDecided` is a distinct constant; PR-25 execution-terminals are **separate** constants. Prevents equality-based confusion only. | `internal/installer/state/machine.go` |
448
+
| 2 — API-level disambiguation (PR-25, optional) | `IsRestoreExecuted(s State) bool` helper. Returns true ONLY for execution-terminals. Never true for `StateRestoreDecided`. | new helper alongside `IsApplyTerminal` |
449
+
| 3 — Contract-level documentation | Consumer-facing rule added to `state/machine.go` comment + this document. Covers the exit-code-misread class. | (this section) |
450
+
| 4 — Defensive gate (already landed) | `cfg.mode == "restore"` excluded from history-write at `cmd/nftban-installer/main.go:132`. | (existing) |
451
+
452
+
### 19.3 Consumer rule
453
+
454
+
Consumers MUST NOT use `sf.State == StateRestoreDecided` to infer that restoration execution has occurred.
455
+
456
+
### 19.4 Exit code discipline
457
+
458
+
PR-25 execution terminals MUST carry distinct exit codes, **separate from** the existing constants at `internal/installer/state/machine.go:149-155`:
459
+
460
+
- `ExitCommitted = 0`
461
+
- `ExitFatal = 4`
462
+
- `ExitRefused = 5`
463
+
- `ExitIntentRequired = 6`
464
+
465
+
New PR-25 exit codes are allocated from the next available integers.
466
+
467
+
## 20. Panel-auto target consistency (locked, Q4)
468
+
469
+
When `TargetAuthority.Kind == PanelNative`, PR-25 execution resolves the firewall via a **static explicit mapping**:
-**Static, compile-time.** Shipped with PR-25 code as a const map or exhaustive switch.
478
+
-**Key type:**`detect.PanelType`.
479
+
-**Output validation rule:** mapped value MUST be a member of `knownFirewallType` (the same set used in §18.2).
480
+
-**Not required to be exhaustive** over all `detect.PanelType` values.
481
+
482
+
### 20.2 Ambiguity resolution
483
+
484
+
| Case | PR-25 behavior |
485
+
|---|---|
486
+
| Panel ∈ mapping | Execute the mapped firewall |
487
+
| Panel ∉ mapping |**REFUSE before any mutation.** Transition to PR-25 execution-failure terminal (see §22). |
488
+
489
+
### 20.3 Forbidden fallbacks
490
+
491
+
-**No fallback** to a default firewall on unmapped panel.
492
+
-**No fallback** to recorded-prior `FirewallType` (structurally empty by §18.3 invariant when Kind=PanelNative; AND contractually forbidden to consult).
### 21.1 PR-25 — INLINE VERIFICATION = safety interlock, NOT a gate
502
+
503
+
-**Purpose:** Prove enough about the mutation outcome to *truthfully set the execution-terminal state* AND *safely remove the safety net*.
504
+
-**Scope rule:** Minimum-sufficient checks for terminal-state + safety-net removal decisions.
505
+
-**Coverage:** seed skeleton §11's first three assertions only:
506
+
- Target firewall is active
507
+
- nftban authority class is correct (post-mutation)
508
+
- Safety-net removal is safe to proceed with
509
+
-**Timing:** Runs WHILE the safety net is still present.
510
+
-**Classification:****safety interlock**, not a verification gate.
511
+
512
+
### 21.2 PR-26 — POST-RESTORE VERIFICATION GATE (scope-expanded, NOT in this contract)
513
+
514
+
PR-26 contract content is defined by PR-26's own contract seed work, NOT by §21.
515
+
516
+
What §21 records about PR-26:
517
+
- PR-26 is **scope-expanded** from V1100 contract §8's "post-uninstall verification" / G3-UN-VERIFY to also cover post-restore outcomes.
518
+
- PR-26 has its own verification-outcome terminals and exit codes.
519
+
- PR-26 has its own operator-invokable CLI surface (PR-26 scope, NOT §21).
520
+
- PR-26 is **scope-expanded, not replaced, renumbered, or split**.
521
+
522
+
### 21.3 Hard invariant
523
+
524
+
PR-25 MUST NOT remove the safety net until inline verification (§21.1) passes. (V1100 contract §8 step 5.)
525
+
526
+
### 21.4 PR-25 must NOT implement the gate
527
+
528
+
PR-25 implements only §21.1. The full gate is PR-26's contract scope.
529
+
530
+
## 22. State terminals + exit codes (PR-25 introduces)
531
+
532
+
Concrete names are open in the lock — confirmed candidate set, finalized in code phase:
533
+
534
+
| Candidate name | Meaning | Exit code |
535
+
|---|---|---|
536
+
|`StateRestoreExecuted`| Full mutation completed AND inline verification passed AND safety net removed | next available |
537
+
|`StateRestoreFailedExecution`| Mutation failed mid-flight; safety net still present; system rolled to known-safe state | next |
538
+
|`StateRestoreDegraded`| Mutation completed but inline verification flagged a soft-fail condition; safety net removed under explicit policy | next |
539
+
|`StateRestoreFailedVerification`| Mutation completed but inline verification hard-failed; safety net retained; explicit operator action required | next |
540
+
541
+
Constraints:
542
+
- All four MUST be NEW constants distinct from `StateRestoreDecided` (§19.2 layer 1).
543
+
- Exit codes MUST be distinct from `ExitCommitted=0` / `ExitFatal=4` / `ExitRefused=5` / `ExitIntentRequired=6`.
544
+
-`IsRestoreExecuted(s)` (§19.2 layer 2) returns true for `StateRestoreExecuted` and `StateRestoreDegraded`; false for the other two AND for `StateRestoreDecided`.
545
+
546
+
Final names + final integers chosen during the code phase.
547
+
548
+
## 23. Execution shape (V1100 §8, ordered)
549
+
550
+
PR-25 execution proceeds in this exact ordered sequence:
551
+
552
+
1.**Preflight target validation** — confirm authority resolution still satisfies invariants (§18.3 payload invariants); confirm `knownFirewallType` membership (RecordedPrior); confirm panel mapping resolves (PanelNative). Refuse here is non-mutating.
553
+
2.**Safety net insertion** — emergency-SSH allow rule before any other mutation.
554
+
3.**Minimal target-specific execution** — kernel nft mutations + service run-state changes for the authorized target only. No cross-target traffic.
555
+
4.**Verification while safety net still present** — inline checks per §21.1.
556
+
5.**Safety net removal only after verification passes** — hard gate, no exceptions.
557
+
6.**Terminal state set truthfully** — pick the execution-success or execution-failure terminal that matches step-4 outcome; emit the corresponding exit code.
558
+
559
+
## 24. Inputs PR-25 may consume
560
+
561
+
-**PR-24 output** — decision MUST be `PROCEED`. `REFUSE` / `REQUIRE_EXPLICIT_INTENT` → PR-25 does not run.
562
+
-**Resolved target identity** — `TargetAuthority` of `Kind=RecordedPrior` (with `firewallType`) OR `Kind=PanelNative` (with `panel`). `Kind=None` → PR-25 does not run.
563
+
-**Current classifier state** — must still be compatible at runtime (preflight at §23 step 1).
564
+
-**Prior record** — only as already reduced/approved by the PR-24 path. PR-25 does not re-reduce.
565
+
566
+
PR-25 may **NOT** consume:
567
+
- Any signal that re-derives `TargetAuthority` (locked by INV-PR25-AUTHORITY-IMMUTABILITY).
568
+
- Operator config beyond what was already authorized by PR-24.
569
+
570
+
## 25. Forbidden behaviors (consolidated)
571
+
572
+
- No execution if PR-24 ≠ `PROCEED`.
573
+
- No target inference beyond what PR-24 authorized.
574
+
- No reinterpretation of `StateRestoreDecided` as success.
575
+
- No silent fallback between targets.
576
+
- No "try restore, then rebuild nftban if fails" pattern.
577
+
- No hidden purge/remove unless explicitly in scope (it isn't).
578
+
- No history-schema redesign unless separately approved.
579
+
- No mutation outside the declared target surface.
580
+
- No mid-flight `TargetAuthority` re-resolution.
581
+
- No verification beyond §21.1 inline checks (gate is PR-26).
582
+
- No safety-net removal before inline verification passes.
-**§18 type safety × §20 panel resolution** → `FirewallType` structurally empty for `PanelNative` (§18.3 invariant) AND forbidden to consult (§20.3 contract) → no accidental cross-variant authority leak.
588
+
-**§17 authority-resolution immutability × §19 decision-vs-execution boundary** → `TargetAuthority` set once by PR-24, read-only by PR-25 → no reinterpretation across the boundary.
589
+
-**§19 rollback coupling × §21 inline safety interlock** → inline verification gates safety-net removal → prevents "remove before prove" rollback failure that would corrupt §19's decision-execution distinction.
590
+
591
+
## 27. What this contract does NOT contain (intentional, not omission)
592
+
593
+
- Final state-terminal names + final integer exit codes (§22 candidates only)
594
+
- Test fixture matrix (parallels PR-24's exhaustive matrix; produced during code phase)
595
+
- Real-host evidence plan (§28 below names hosts; the matrix itself is code-phase work)
596
+
- CI gate expansion plan (minimal beyond what's already in `ci-restore-canonization.yml`; specifics emerge in code phase)
597
+
- Concrete `IsRestoreExecuted` signature (helper is locked as §19.2 layer 2 optional API)
598
+
- The PanelType → firewall mapping itself (locked as static; concrete map content is code-phase work)
599
+
600
+
These are intentionally absent. **Expansion would violate the locked rule "no expansion beyond Q1–Q5".**
-[ ] §19.4 — new exit codes distinct from existing 0/4/5/6.
621
+
-[ ] §20.1 mapping is static (not config-driven, not runtime-discovered).
622
+
-[ ] §20.3 — no fallback paths exist anywhere in execution code.
623
+
-[ ] §21.1 inline verification covers exactly the three assertions; nothing more.
624
+
-[ ] §21.3 hard invariant — no code path removes safety net before §21.1 returns success.
625
+
-[ ] §23 execution sequence respected exactly; no reordering.
626
+
-[ ] §25 forbidden behaviors all absent from code.
627
+
-[ ] §28 real-host evidence captured for at least lab2 + lab4.
628
+
-[ ] CI gate updated minimally (no scope expansion beyond restore-execution surface).
629
+
630
+
---
631
+
335
632
## Amendment history
336
633
337
634
-**2026-04-20 v1 (seed)** — first committed seed. Lattice v2 + three locked corrections:
@@ -343,3 +640,5 @@ Four new gates in the `G4-RESTORE-*` namespace:
343
640
2.`§6` Group 4 precedence clarifier added: 4.1 / 4.2 match on prior state for flags {`none`, `--restore`}; 4.3 matches `--panel-auto-takeover` regardless of prior.
-**2026-04-27 v2 (PR-25 contract append)** — appends Part II (§§16–29: PR-25 execution contract). Faithful normalization of the locked Q1–Q5 design decisions (recorded 2026-04-20 during PR-24 freeze Day 0) following the locked "no expansion beyond Q1–Q5" rule. Sections §1–§15 are untouched. Verified live-code anchors at `internal/installer/uninstall/prior.go:278-284` (knownFirewallType set), `cmd/nftban-installer/main.go:132` (writeHistory gate), `internal/installer/state/machine.go:149-155` (existing exit-code constants). Doc-only commit; no code changes in this PR. Code phase opens in a separate PR after this one merges.
0 commit comments