fix(validator): B80-1 shim — validate_structure() → Go validator authority#369
Merged
fix(validator): B80-1 shim — validate_structure() → Go validator authority#369
Conversation
…ority
Option B (shim) per discussion 2026-04-11, with Amend-1/2/3 from
2026-04-13 review.
The function validate_structure() in cli/lib/nftban/core/nftban_validator.sh
is now a thin shim over ${NFTBAN_LIB_DIR}/bin/nftban-validate (the Go
validator). The body:
1. calls the Go binary via NFTBAN_LIB_DIR/bin/nftban-validate --json
2. translates the Go schema {status:protected|degraded|down, findings[]}
into the legacy shell schema {status:OK|WARNING|ERROR, errors[],
warnings[], info[]}
3. preserves the legacy text output format byte-for-byte so existing
callers in cmd_validate.sh and cmd_firewall.sh are unaffected
(cmd_validate.sh:103 prints validate_structure stdout directly to
the user — losing the output format would regress `nftban validate`)
4. fails CLOSED with distinct exit codes:
return 2 — Go binary not found/not executable (Amend-1)
return 1 — Go binary empty/unparseable output OR validation errors
return 0 — OK or warnings-only
5. has a jq-missing graceful fallback (Amend-2): when jq is not
installed the shim falls back to grep-based protected-detection,
emits a minimal text banner, and preserves the exit-code contract
6. has NO independent fallback validation logic — that would re-create
the exact drift risk B80-1 is closing
Amend-1: missing Go binary → return 2 (distinct from 1=validation-failed)
— matches Unix convention for "prerequisite missing" vs "check failed"
— Go binary missing is a broken-installation case; exit 2 lets callers
distinguish it from a real validation failure
Amend-2: jq-missing fallback
— if command -v jq fails, shim bypasses the jq schema-translation path
and uses grep for `"status":"protected"` on the Go output
— preserves the exit-code contract even on jq-broken environments
— fail-closed paths for missing-binary and empty-output use hand-built
JSON via printf (not jq -n) so they work without jq
Amend-3: INV-CONS-001 smoke now reads nftban status --json
— extraction hierarchy: .status // .protection_state // .health // .state
— the first three are the "canonical" keys from the truth hierarchy
— .state is the ACTUAL key currently emitted by cmd_status.sh:1562
output_json() (verified 2026-04-13 via source read)
— the fall-through order means if the shell key is later renamed to
the canonical .status, this test automatically picks up the rename
— comparison is uppercase-normalized
Scope fence (NOT touched):
- check_ip_or_port() — v1.81 migration target
- get_firewall_stats() — v1.81 migration target
- load_spec(), get_live_ruleset() — still defined but now unused
(removal = v1.81 cleanup)
- cmd_check.sh, cmd_firewall.sh — no caller changes
- internal/nftbanconf/commands.go ValidatorScript() — unchanged
- no file rename, no file deletion
Tests (three layers of proof):
1. Static regression guard (hermetic) — NEW
cli/lib/nftban/tests/test_validate_structure_is_shim.sh
17 assertions across:
positive invariants (shim MUST call Go binary, preserve legacy
schema, contain B80-1 NOTE comment)
negative invariants (body MUST NOT call load_spec/get_live_ruleset,
MUST NOT use --slurpfile/nft list ruleset/spec field names, body
length ≤260 lines, jq invocations ≤12, has jq-missing fallback,
uses return 2 on missing binary)
scope-fence guardrails (check_ip_or_port and get_firewall_stats
preserved; file still exists — deletion is v1.81 scope)
2. Runtime INV-CONS-001 smoke check — added to smoke_test.sh
run_runtime_tests. Compares `nftban status --json` state against
`nftban-validate --json` status with uppercase normalization.
Extraction order .status // .protection_state // .health // .state
preserves the intended truth hierarchy while working on current main.
This is the first positive enforcement of INV-CONS-001 at the
top-level CLI truth surface.
3. Behavioural equivalence (offline, verified locally)
5 scenarios (protected / degraded_critical / warn_only / text /
missing-binary) verified via fake Go binary against the shim:
protected → OK + exit 0
degraded_critical → ERROR with CRITICAL: prefix + exit 1
warn_only → WARNING with WARNING: prefix + exit 0
text rendering → legacy header + ❌ ERRORS (N): + message lines
missing binary → ERROR fail-closed + exit 2 (Amend-1)
jq-missing fallback path verified at the static-guard layer (N8);
behavioural test would require brittle environment manipulation.
B80-2 is separately a no-op: cmd_status.sh on main already calls the
Go binary directly via _NFTBAN_VALIDATOR_BIN at line 131 and has no
shell validator fallback references. B80-2 marked CLOSED as
already-satisfied (roadmap doc update in a follow-up commit).
Refs: V1.80_ROADMAP/MASTER_TODO.md B80-1 (truth consolidation)
Refs: V1.80_ROADMAP/10_EXECUTION_PLAN.md Day 2 (legacy validator removal)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
3 tasks
itcmsgr
added a commit
that referenced
this pull request
Apr 13, 2026
v1.80 closes the structural truth-surface hardening line. Protection state now fails correctly for broken kernel structure, empty required chains, dead required runtime, schema drift, and duplicate schema authority. v1.80.0 does not change the effective detection/scoring model; effectiveness tuning remains future work (BUG-1 / v1.81+). Blockers closed: B80-1 through B80-8 + BUG-6 (NOT-A-BUG). PRs: #368, #369, #370, #371, #372, #373. Bumps VERSION 1.79.3 -> 1.80.0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
itcmsgr
added a commit
that referenced
this pull request
Apr 13, 2026
v1.80 closes the structural truth-surface hardening line. Protection state now fails correctly for broken kernel structure, empty required chains, dead required runtime, schema drift, and duplicate schema authority. v1.80.0 does not change the effective detection/scoring model; effectiveness tuning remains future work (BUG-1 / v1.81+). Blockers closed: B80-1 through B80-8 + BUG-6 (NOT-A-BUG). PRs: #368, #369, #370, #371, #372, #373. Bumps VERSION 1.79.3 -> 1.80.0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
itcmsgr
added a commit
that referenced
this pull request
Apr 13, 2026
v1.80 closes the structural truth-surface hardening line. Protection state now fails correctly for broken kernel structure, empty required chains, dead required runtime, schema drift, and duplicate schema authority. v1.80.0 does not change the effective detection/scoring model; effectiveness tuning remains future work (BUG-1 / v1.81+). Blockers closed: B80-1 through B80-8 + BUG-6 (NOT-A-BUG). PRs: #368, #369, #370, #371, #372, #373. Bumps VERSION 1.79.3 -> 1.80.0. Co-authored-by: Claude Opus 4.6 (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.
Summary
B80-1 is closed behaviorally, not structurally. Full shell-file deletion and responsibility split (
check_ip_or_port/get_firewall_stats/load_spec/get_live_ruleset) are deferred to v1.81.This patch does not change rebuild ordering, CLI output format, or caller interfaces. It replaces the body of
validate_structure()incli/lib/nftban/core/nftban_validator.shwith a thin compatibility shim over the Go validator binary (nftban-validate), making validation authority Go-only at runtime while preserving backward compatibility for all existing callers.What this patch does
validate_structure()now calls${NFTBAN_LIB_DIR}/bin/nftban-validate --jsonand translates the Go schema into the legacy shell schema{status:OK|WARNING|ERROR, errors[], warnings[], info[]}cmd_validate.sh:103prints this directly to the user$1preserved asoutput_jsonboolean flag (true/false) controlling text vs JSON mode0(OK / warnings-only),1(validation errors / parse failure),2(Go binary missing — Amend-1, distinguishes "tool absent" from "tool ran but validation failed")printf(notjq -n) so they work without jqload_spec, noget_live_ruleset, nonft list ruleset, no spec-field referencesJSON field mismatch — intentional, documented
nftban status --jsoncurrently emits.state(seecmd_status.sh:1562). The new INV-CONS-001 smoke assertion prefers canonical keys first (.status // .protection_state // .health) and falls through to.statefor current-main compatibility. If the shell key is later renamed to a canonical form, the test picks it up automatically without code change.Scope fence
Patch-size framing
The raw diff reads 477 / -167, but the runtime behavioral change is narrow:
~60 linesactual functional shim body~50 linesAmend-2 jq-missing fallback path (compatibility scaffolding)~30 linesNOTE comment block inside the shim~40 linesnew INV-CONS-001 smoke assertion insmoke_test.sh~260 linesnewtest_validate_structure_is_shim.sh(17-assertion regression guardrail)~130 linesremoved from the old batch-jqvalidate_structure()bodyRisk surface = the 60-line shim body. Everything else is guardrail, comment, or test.
Tests (three layers of proof)
1. Static regression guard —
tests/test_validate_structure_is_shim.sh(17 assertions)Positive: body calls Go binary, builds path via
$NFTBAN_LIB_DIR, contains B80-1 NOTE, preserves legacy JSON schema, maps severity levelsNegative: body does NOT call
load_spec/get_live_ruleset, does NOT use--slurpfile/nft list ruleset/spec field names, body ≤260 lines, jq ≤12 invocations, has jq-missing fallback (Amend-2 contract), usesreturn 2on missing binary (Amend-1 contract)Scope fence: file still exists,
check_ip_or_port()andget_firewall_stats()preserved2. Runtime INV-CONS-001 smoke — added to
smoke_test.sh run_runtime_testsCompares
nftban status --jsonstate (extraction:.status // .protection_state // .health // .state // empty) againstnftban-validate --json.status, uppercase-normalized. First positive enforcement of INV-CONS-001 at the top-level CLI truth surface.3. Behavioural equivalence — verified locally via fake Go binary
protected, findings=[]{status:OK, errors:[], warnings:[]}degraded, findings=[critical, error]{status:ERROR, errors:[CRITICAL:…]}protected, findings=[warn]{status:WARNING, warnings:[WARNING:…]}{status:ERROR, errors:[CRITICAL: Go validator not found…]}Text rendering preserved (header +
❌ ERRORS (N):+ message lines).Test plan
Refs
V1.80_ROADMAP/MASTER_TODO.mdB80-1 (truth consolidation)V1.80_ROADMAP/10_EXECUTION_PLAN.mdDay 2 (legacy validator removal)cmd_status.shalready calls Go binary only (line 131). Marked CLOSED as already-satisfied.🤖 Generated with Claude Code