Skip to content

release: v1.97.0 — lifecycle Wave 1 (types + engine + authority + CLI dry-run)#457

Merged
itcmsgr merged 7 commits intomainfrom
feat/v1.97-lifecycle-wave1
Apr 17, 2026
Merged

release: v1.97.0 — lifecycle Wave 1 (types + engine + authority + CLI dry-run)#457
itcmsgr merged 7 commits intomainfrom
feat/v1.97-lifecycle-wave1

Conversation

@itcmsgr
Copy link
Copy Markdown
Owner

@itcmsgr itcmsgr commented Apr 17, 2026

Summary

Implements v1.97 Lifecycle Wave 1 — the lifecycle foundation for v1.98+ installer/update canonization.

Side-effect free by contract (INV-LC-001). No nftables mutation, no installer replacement, no truth drift.

Core additions (6 PRs)

  • PR-01: Lifecycle types — Stage, Outcome, Mode, AuthorityOwner, LifecycleHealth, Action enums + structs
  • PR-02: Pure state machine engine — Run(Snapshot) → RunResult, deterministic, no side effects
  • PR-03: Authority resolution — prior → desired → resulting with health dimension (INV-LC-004)
  • PR-04: JSON output schema v1.0 — stable, versioned, 25 required fields snapshot-tested
  • PR-05: CLI dry-run — nftban lifecycle run --mode=X [--json], refuses --no-dry-run
  • PR-06: Evidence logging — structured JSONL events (detect/plan/result)

Key architectural decisions

  • FAILED_RECOVERED + PROTECTED preserved correctly: Lifecycle output carries both truths — operation failed but system is healthy after rollback (INV-LC-008)
  • Degraded ownership is still ownership: NFTBAN + DEGRADED = owned but unsafe, NOT "no authority" (INV-LC-004)
  • v1.96 truths consumed, never redefined: last_operation.result, failure_class, recovery_pending, last_rebuild_failed all embedded in lifecycle output from rebuild.RecoveryMarker (INV-LC-003)
  • Real execution refused: --no-dry-run prints "not available in v1.97" and exits 1 (INV-LC-007)

Contract

V197_LIFECYCLE_CONTRACT.md (locked 2026-04-17)

8 invariants (INV-LC-001 through INV-LC-008):

  1. Engine is side-effect free
  2. Lifecycle is not a truth source
  3. v1.96 operation results are canonical
  4. Authority owner and health are separate dimensions
  5. Dry-run uses same logic as real execution planning
  6. Output schema is stable and versioned
  7. No lifecycle command in v1.97 may mutate the system
  8. Recovery truth (FAILED_RECOVERED + PROTECTED) is preserved

Lab4 Validation

Unit tests (50 PASS)

Suite Tests Status
types 13 PASS
engine 13 PASS
authority 11 PASS
output 8 PASS
logger 5 PASS

CLI tests (5 PASS on lab4)

Test Result
Text output (--mode=install) Authority/detection/plan/outcome rendered correctly
JSON output (--mode=update --json) Schema v1.0, all fields present
--no-dry-run rejected "real execution is not available in v1.97" exit 1
Invalid mode (--mode=deploy) "invalid mode" exit 1
Help (--help) Usage rendered

Build

  • Go binary builds successfully on lab4 (AlmaLinux 9)
  • All pre-commit hooks pass

Files Changed

New files (8):

  • internal/lifecycle/types.go — enums + structs
  • internal/lifecycle/types_test.go
  • internal/lifecycle/engine.go — pure state machine
  • internal/lifecycle/engine_test.go
  • internal/lifecycle/authority.go — authority resolution
  • internal/lifecycle/authority_test.go
  • internal/lifecycle/output.go — JSON + text rendering
  • internal/lifecycle/output_test.go
  • internal/lifecycle/logger.go — evidence logging
  • internal/lifecycle/logger_test.go
  • cmd/nftban-core/cmd_lifecycle.go — CLI command

Modified files (1):

  • cmd/nftban-core/main.go — added lifecycle command dispatch

Test plan

  • 50/50 Go unit tests PASS on lab4
  • 5/5 CLI tests PASS on lab4
  • Go binary builds on lab4
  • Pre-commit hooks pass (SPDX, inventory)
  • INV-LC-001: no side effects (pure function engine)
  • INV-LC-003: v1.96 truths preserved in output
  • INV-LC-004: DEGRADED ownership modeled correctly
  • INV-LC-007: --no-dry-run refused
  • INV-LC-008: FAILED_RECOVERED + PROTECTED coexist
  • JSON field stability: 25 fields snapshot-tested

🤖 Generated with Claude Code

itcmsgr and others added 6 commits April 17, 2026 10:48
Add internal/lifecycle package with orchestration types:

- Stage enum: DETECT, PLAN, APPLY, VERIFY, FINAL
- Outcome enum: SUCCESS, FAILED, FAILED_RECOVERED
  (high-level orchestration; detailed failure in v1.96 FailureClass)
- Mode enum: install, update, uninstall, maintenance
- AuthorityOwner enum: NFTBAN, EXTERNAL, NONE
- LifecycleHealth enum: PROTECTED, IDLE, DEGRADED, DOWN
- Action enum: NOOP, TAKE_AUTHORITY, PRESERVE_AUTHORITY, ABORT, NEEDS_RECOVERY
- AuthorityState struct with Owner+Health independence (INV-LC-004)
  - NormalizeNone(): Owner=NONE normalizes health to DOWN
- Detection struct: conflicting_firewall, kernel_valid, validator_consistent, ssh_safe
- LastOperation struct: embeds v1.96 result/failure_class/recovery_pending (INV-LC-003)
- Plan struct with HasAbort()/HasRecovery() helpers
- Snapshot (engine input) and RunResult (engine output)
- JSON schema version 1.0

15 unit tests covering: mode validation, authority ownership + health
independence, DEGRADED ownership preservation, NONE normalization,
plan helpers, two-truths model (FAILED_RECOVERED + PROTECTED), JSON
roundtrip for Snapshot and RunResult.

Contract: V197_LIFECYCLE_CONTRACT.md
No behavior change — types only. INV-LC-001 enforced.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add lifecycle engine: pure function, no side effects (INV-LC-001).

Run(Snapshot) → RunResult:
- DETECT stage: checks conflicting firewall, recovery pending, mode validity
- PLAN stage: resolves action per mode (install/update/uninstall/maintenance)
- FINAL stage: determines outcome and resulting authority

State machine rules:
- ConflictingFirewall + desired NFTBAN → ABORT
- RecoveryPending → NEEDS_RECOVERY (must resolve v1.96 recovery first)
- EXTERNAL authority → ABORT (no unauthorized takeover)
- NONE + DOWN → TAKE_AUTHORITY (safe for fresh install)
- NFTBAN + DOWN + update → NEEDS_RECOVERY
- NFTBAN + DEGRADED + update → PRESERVE with caution warning
- FAILED_RECOVERED + PROTECTED → SUCCESS with warning (INV-LC-008)

Per-mode resolvers:
- install: NONE→TAKE, EXTERNAL→ABORT, NFTBAN→PRESERVE
- update: requires NFTBAN owner, DOWN→NEEDS_RECOVERY
- uninstall: NONE→NOOP, otherwise release authority
- maintenance: requires NFTBAN owner, DOWN→NEEDS_RECOVERY

13 test cases covering all decision paths.
No side effects. No system mutation. Plan-only.

Contract: V197_LIFECYCLE_CONTRACT.md §6 PR-02

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add authority resolution: prior → desired → resulting with health dimension.

ResolveAuthority() rules:
- NFTBAN + PROTECTED/IDLE → PRESERVE_AUTHORITY
- NFTBAN + DEGRADED → PRESERVE_AUTHORITY (still owned, INV-LC-004)
- NFTBAN + DOWN → NEEDS_RECOVERY
- EXTERNAL + desired NFTBAN → ABORT (unless --force)
- EXTERNAL + force → TAKE_AUTHORITY (explicit takeover)
- NONE + desired NFTBAN → TAKE_AUTHORITY (fresh install)
- ConflictingFirewall → ABORT
- Uninstall (NFTBAN → NONE) → release authority

Helpers:
- CanProceed(action) — true for TAKE/PRESERVE/NOOP, false for ABORT/RECOVERY
- TransitionDescription() — human-readable transition logging
- NormalizeNone() applied on entry (NONE+PROTECTED → NONE+DOWN)

11 test cases covering all authority transitions.
No side effects. No system mutation.

Contract: V197_LIFECYCLE_CONTRACT.md §6 PR-03

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add stable lifecycle output rendering:

- RenderJSON(): versioned JSON output (schema_version always set)
- RenderText(): human-readable summary with authority, detection, plan
- ParseRunResult(): deserialize from JSON
- Field stability test: 25 required JSON field names snapshot-tested

v1.96 truth preservation (INV-LC-003, INV-LC-008):
- last_operation.result, failure_class, recovery_pending, last_rebuild_failed
  all present in JSON output
- FAILED_RECOVERED + PROTECTED renders correctly in both formats
- Schema version 1.0, additive-only (INV-LC-006)

8 test cases: JSON render, v1.96 truth preservation, text render,
text with recovery, parse roundtrip, invalid parse, schema version
auto-set, field stability snapshot.

Contract: V197_LIFECYCLE_CONTRACT.md §4, §6 PR-04

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add structured lifecycle event logging:

- Logger with detect/plan/result event emission
- JSONL output format (one JSON object per line)
- Events: detect (authority + detection observations),
  plan (actions + warnings + v1.96 last_operation),
  result (outcome + stage + resulting authority)
- Dry-run logs say "plan_completed", not "execution_completed"
  (no misleading success claims, INV-LC-001)
- Reusable by v1.98+ installer/update execution

5 tests: detect event, plan with v1.96 truth, dry-run result label,
execution result label, JSONL format validation (3 events = 3 lines).

Contract: V197_LIFECYCLE_CONTRACT.md §6 PR-06

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add nftban lifecycle run --mode=MODE [--dry-run] [--json]:

- Reads real system state: authority (validator), recovery marker (v1.96),
  detection (conflicting firewalls, kernel validity, SSH safety)
- Runs lifecycle engine (pure, no side effects)
- Renders output as text or JSON
- Logs evidence (detect/plan/result) to stderr
- REFUSES --no-dry-run (INV-LC-007: no execution in v1.97)
- Exit 0 for valid plan, exit 1 for failures/aborts

System detection:
- Authority: checks nftban-validate status for health
- Recovery: reads v1.96 rebuild.RecoveryMarker
- Conflicts: checks firewalld/ufw service state
- Kernel: checks nftban table existence via nft list table

No side effects. No nftables mutation. Plan-only.

Contract: V197_LIFECYCLE_CONTRACT.md §6 PR-05

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 17, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Comment thread cmd/nftban-core/cmd_lifecycle.go
Comment thread cmd/nftban-core/cmd_lifecycle.go
Comment thread internal/lifecycle/logger.go Fixed
Comment thread internal/lifecycle/logger.go Fixed
Policy gate requires //lint:ignore, not //nolint:. Simpler fix:
use _, _ = to discard write errors (best-effort log writes).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@itcmsgr itcmsgr merged commit 83ee4bd into main Apr 17, 2026
47 of 48 checks passed
@itcmsgr itcmsgr deleted the feat/v1.97-lifecycle-wave1 branch April 17, 2026 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants