Skip to content

feat(v1.100 Amendment 3): code-A — G1/AmbiguityConflictExternal orphan-CSF split#523

Merged
itcmsgr merged 1 commit intomainfrom
amendment-3-code-A-ambiguity-conflict-external-csf-orphan
Apr 29, 2026
Merged

feat(v1.100 Amendment 3): code-A — G1/AmbiguityConflictExternal orphan-CSF split#523
itcmsgr merged 1 commit intomainfrom
amendment-3-code-A-ambiguity-conflict-external-csf-orphan

Conversation

@itcmsgr
Copy link
Copy Markdown
Owner

@itcmsgr itcmsgr commented Apr 29, 2026

Summary

Implements the Amendment 3 contract (PR #522, merged 2026-04-29) §63 lattice extension + §64 evidence predicate. Decision-layer only — no execute path touched, no classifier touched, no mutation surface added.

The Amendment 3 lattice splits the existing G1/AmbiguityConflictExternal row (locked-REFUSE) into:

Row Result
G1/AmbiguityConflictExternal/default REFUSE (preserves pre-Amendment-3 hard-stop for every flag pattern outside the candidate quintuple)
G1/AmbiguityConflictExternal/orphan-intent-candidate-csf delegate to §64 predicate
G1/AmbiguityConflictExternal/OrphanProceed PROCEED Kind=PanelNative, FirewallType=csf (when §64 all-true)
G1/AmbiguityConflictExternal/EvidenceMismatch REFUSE (when §64 any-false)

Entry condition (the candidate quintuple): external=="csf" + Prior=NoRecord + Panel=DirectAdmin + --panel-auto-takeover + --accept-orphan-nftban.

Mirrors Amendment 2's G1/AuthorityNFTBan split exactly. Same evidence-gating discipline. Same operator-intent override pattern.

Files changed (5)

File Change Lines
internal/installer/restore/engine.go 3 new rule constants + decideAmbiguityConflictExternal() + Decide() dispatch +118/−1
internal/installer/restore/types.go ExternalIndicator field + AllTrueAmendment3() + FailedRowIDAmendment3() +98/0
internal/installer/restore/engine_amendment3_test.go 15-row §67 matrix + 3 helper tests (NEW) +449/0
internal/installer/restore/engine_test.go 2 sentinel fixtures + declaredRules() extension + 1 fixture rule-name shift +78/−4
internal/installer/restore/engine_amendment2_test.go row17 fixture rule-name shift +14/−4

NOT changed (per operator scope)

  • internal/installer/uninstall/* — classifier unchanged
  • internal/installer/restore/execute.go — mutation surface unchanged
  • internal/installer/state/* — state machine unchanged
  • cmd/nftban-installer/main.go — history gate unchanged
  • cmd/nftban-installer/flags.go — flag surface unchanged
  • .github/workflows/* — CI unchanged
  • §32 11-step ordering — unchanged
  • Amendment 2's §54 predicate — untouched; AllTrue() preserved

Test results (lab4 build host)

  • go test ./internal/installer/restore/...ok
  • go test ./cmd/nftban-installer/...ok
  • go test ./...64 packages ok, 0 FAIL

All 15 §67 rows pass:

# Scenario Expected Result
AMD3-1 full quintuple + all rows true PROCEED OrphanProceed
AMD3-2 E.7 false REFUSE EvidenceMismatch
AMD3-3 missing --accept-orphan-nftban REFUSE default
AMD3-4 missing --panel-auto-takeover REFUSE default
AMD3-5 Panel=cpanel REFUSE default
AMD3-6 no panel REFUSE G2/PanelAutoWithoutPanel
AMD3-7 external=ufw REFUSE default
AMD3-8 StrongPrior REFUSE default
AMD3-9 E.6 false REFUSE EvidenceMismatch
AMD3-10 OrphanNFTBan ambiguity REFUSE G4.3 (locked)
AMD3-11 AuthorityExternal REFUSE G1/AuthorityExternal (locked)
AMD3-12 Amendment 2 path regression PROCEED OrphanProceed (Amendment 2)
AMD3-13 external=="" defensive guard REFUSE default
AMD3-14 rule-label assertion (PROCEED) reason contains "amendment-3 orphan-intent"
AMD3-15 external="csf,ufw" multi-external REFUSE default

Test plan

  • Auditor on-PR audit verifies: no §54 modification, no classifier change, no §32 ordering change, no new state terminals, no new exit codes, no new mutation surface
  • Auditor verifies all 15 §67 rows + Amendment 2 regression rows pass
  • Operator merges
  • Fresh Tier 1 binary built on lab4 from post-merge HEAD (supersedes 153f7abe…)
  • Distribute to dns2, byte-pin source==host
  • Fresh operator H2/H3 + reachability monitor activation (HARD-GATE-WITH-NO-EXCEPTIONS)
  • Pre-execution Gate B retry audit returns GO
  • Single Gate B retry invocation on dns2 (--mode=restore --panel-auto-takeover --accept-orphan-nftban)
  • Post-B audit returns GO
  • PR-26 final mergeable

🤖 Generated with Claude Code

…n-CSF split

Implements the §63 lattice extension + §64 evidence predicate from the
Amendment 3 doc seed (#522). Decision-layer only.

Lattice changes (engine.go):
  - 3 new rule constants:
    G1/AmbiguityConflictExternal/default          → REFUSE (preserves pre-Amendment-3 hard-stop)
    G1/AmbiguityConflictExternal/OrphanProceed    → PROCEED PanelNative/csf
    G1/AmbiguityConflictExternal/EvidenceMismatch → REFUSE (predicate any-false)
  - decideAmbiguityConflictExternal() function mirrors decideAuthorityNFTBan()
    in shape: Group 2 precedence preserved → quintuple-check (csf + DA + NoRecord +
    --panel-auto-takeover + --accept-orphan-nftban) → §64 predicate delegation.
  - Decide() now calls decideAmbiguityConflictExternal(in) for the
    AuthorityAmbiguous + AmbiguityConflictExternal branch (replaces the
    inline REFUSE).

Evidence predicate (types.go):
  - New ExternalIndicator string field on DecisionInput (carries the
    classifier's external-authority string; required for §62 entry condition).
  - New AllTrueAmendment3() helper on OrphanEvidence: identical to AllTrue()
    EXCEPT row E.12 (NoConflictExternal) is omitted — the §62 entry IS
    AmbiguityConflictExternal so requiring "no conflict external" is
    incompatible by construction.
  - New FailedRowIDAmendment3() helper returns AMD3-E.{N} stable IDs so
    structured logs and Code-D evidence-records distinguish which
    predicate fired.

Test matrix (engine_amendment3_test.go — new file):
  - 15-row §67 matrix (AMD3-1 through AMD3-15) including the 3 auditor-
    required defensive-guard rows: AMD3-13 empty external, AMD3-14 rule-
    label assertion ("amendment-3 orphan-intent" reason substring),
    AMD3-15 multi-external "csf,ufw".
  - TestAmd3_RuleConstants pins canonical rule strings.
  - TestAmd3_AllTrueAmendment3_OmitsE12 verifies E.12 is excluded.
  - TestAmd3_FailedRowIDAmendment3_SkipsE12 verifies row-walk skips E.12.
  - TestAmd3_NilEvidence_AMD3E0 verifies nil-receiver sentinel.

Regression updates (engine_test.go, engine_amendment2_test.go):
  - 2 pre-existing fixtures asserting the old "G1/AmbiguityConflictExternal"
    rule string updated to RuleG1AmbConflictExtDefault. Behavior unchanged
    (still REFUSE); only the rule sub-classifier name shifts. Same pattern
    as Amendment 2's AuthorityNFTBan/default rename.
  - declaredRules() in engine_test.go updated to include the 3 new sub-rule
    constants (RuleG1AmbiguityConflictExt umbrella retained for grep parity).
  - 2 sentinel fixtures added to allFixtures pinning the new
    OrphanProceed and EvidenceMismatch rules for coverage assertion
    (full §67 matrix lives in engine_amendment3_test.go).

NOT touched (in scope per operator):
  - internal/installer/uninstall/* (classifier — semantic unchanged)
  - internal/installer/restore/execute.go (mutation surface unchanged)
  - internal/installer/state/* (state machine unchanged)
  - cmd/nftban-installer/main.go (history gate unchanged)
  - cmd/nftban-installer/flags.go (flag surface unchanged)
  - .github/workflows/* (CI unchanged)
  - §32 11-step ordering (unchanged)
  - Amendment 2's §54 predicate (untouched; AllTrue() preserved for the
    AuthorityNFTBan path; AllTrueAmendment3() is a new sibling)

Test results (lab4):
  - go test ./internal/installer/restore/...    → ok
  - go test ./cmd/nftban-installer/...          → ok
  - go test ./...                                → 64 packages ok, 0 FAIL

No host action. No binary rebuild. No nftban-installer invocation.
dns2 stays in canonical post-Gate-A state.

Closes part of PR-26 final closure path: Gate B retry on dns2 unblocks
once this PR merges + fresh Tier 1 binary is built + reachability monitor
activates + pre-execution Gate B retry audit returns GO.

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

Dependency Review

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

Scanned Files

None

@itcmsgr itcmsgr merged commit b81fe3c into main Apr 29, 2026
57 checks passed
@itcmsgr itcmsgr deleted the amendment-3-code-A-ambiguity-conflict-external-csf-orphan branch April 29, 2026 13:30
itcmsgr added a commit that referenced this pull request Apr 29, 2026
…anEvidenceAmendment3 helper per auditor recommendation

Per auditor pre-draft review: implement Sub-gap 2a as a separate
gatherOrphanEvidenceAmendment3() helper rather than a mode-conditional
inside the existing gatherOrphanEvidence(). Mirrors the AllTrue() vs
AllTrueAmendment3() split that PR #523 introduced in types.go and the
decideAuthorityNFTBan() vs decideAmbiguityConflictExternal() split in
engine.go. "Two helpers, shared rows, different entry-condition
filling" — symmetric with the engine pattern.

Refactor changes (no behavior delta vs the prior code-B commit on
this branch):

  cmd/nftban-installer/restore_decide_evidence.go
    - populateSharedOrphanEvidenceRows(): private helper populates all
      rows EXCEPT E.2 (the entry-condition row).
    - gatherOrphanEvidence(): Amendment 2 — calls shared helper +
      sets E.2 = (Authority == AuthorityNFTBan). Restored to
      Amendment-2-byte-clean E.2 evaluation.
    - gatherOrphanEvidenceAmendment3(): Amendment 3 — calls shared
      helper + sets E.2 = (AuthorityAmbiguous + ConflictExternal +
      external == "csf"). New sibling helper.
    - E.13 retained at the contract-wording-exact form (Ambiguity !=
      AmbiguityOrphanNFTBan); shared by both helpers via the populate
      function.

  cmd/nftban-installer/restore_decide.go
    - Dispatcher's evidence-gathering now switches on the candidate
      type: amd2Candidate calls gatherOrphanEvidence (unchanged from
      pre-Amendment-3 main); amd3Candidate calls
      gatherOrphanEvidenceAmendment3.
    - Diagnostic log line is now amendment-specific (no dual-predicate
      noise) — clearer audit trail per amendment.

  cmd/nftban-installer/restore_decide_amendment3_test.go
    - Renamed and reorganized test cases to test the two helpers
      independently:
      * TestGatherOrphanEvidenceAmendment3_* tests the Amendment 3
        helper specifically (rejects Amendment 2 entry, accepts §62
        entry, AMD3-E.2 attribution on external=ufw, omits E.12).
      * TestGatherOrphanEvidence_Amendment2Unchanged regression-tests
        the Amendment 2 helper: still strict on AuthorityNFTBan,
        rejects Amendment 3 entry.

Why two helpers (auditor reasoning):
  - Audit-chain clarity: one helper per amendment, mirroring the
    engine.go and types.go split structure.
  - Each helper's CI grep gates and tests can reason about it
    independently.
  - No runtime classifier-state coupling between the dispatcher's
    inspection and the evidence-gathering.
  - Symmetric with AllTrue()/AllTrueAmendment3() in types.go.

Test results (lab4):
  - go test ./cmd/nftban-installer/...           → ok
  - go test ./internal/installer/restore/...     → ok
  - go test ./...                                 → 64 packages ok, 0 FAIL

No host action. No engine.go change. No contract.md change. No new
mutation surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
itcmsgr added a commit that referenced this pull request Apr 29, 2026
… + Amendment-3 OrphanEvidence gathering (#527)

* feat(v1.100 Amendment 3): code-B — dispatcher wires ExternalIndicator + Amendment-3 OrphanEvidence gathering

Closes the dispatcher-side wiring gap surfaced by the post-merge audit
of Amendment-3-code-A (PR #523). Without this PR, the engine's
G1/AmbiguityConflictExternal split (added in PR #523) cannot be reached
because the dispatcher does not (a) plumb auth.External into
DecisionInput.ExternalIndicator, and (b) trigger gatherOrphanEvidence
on the Amendment 3 quintuple shape.

cmd/-side wiring only. Decision-layer engine.go untouched. Contract.md
untouched. Mutation surfaces unchanged. State machine, exit codes,
classifier, and CI all unchanged.

Changes:

  cmd/nftban-installer/restore_decide.go (+50/-13)
    - DecisionInput initializer now sets ExternalIndicator: auth.External
      so the engine's §62 entry condition (external == "csf") can be
      evaluated.
    - OrphanEvidence-gathering condition extended to recognize EITHER
      candidate quintuple:
        * Amendment 2 (§54.3): AuthorityNFTBan + NoRecord + DA + flags
        * Amendment 3 (§62):   AuthorityAmbiguous + ConflictExternal +
                                external=="csf" + NoRecord + DA + flags
    - Diagnostic log line now reports both predicates (amd2_all_true,
      amd2_failed_row, amd3_all_true, amd3_failed_row) for observability;
      the engine consumes whichever is appropriate per the entry path.

  cmd/nftban-installer/restore_decide_evidence.go (+47/-3)
    - E.2 reframed per Amendment 3 §64.1: bool is true under EITHER
      Amendment 2's AuthorityNFTBan entry OR Amendment 3's
      AuthorityAmbiguous + AmbiguityConflictExternal + external=="csf"
      entry. Both candidate quintuples produce E.2=true; all other
      classifier states produce E.2=false (defensive — empty external,
      non-csf external, multi-external, OrphanNFTBan ambiguity, and
      AuthorityExternal all fail E.2).
    - E.13 evaluation tightened to match §54.1 / §64.1 wording exactly
      ("no AmbiguityOrphanNFTBan" — not the looser "no AuthorityAmbiguous"
      that conflated the two Amendment 3 ambiguity sub-kinds). Amendment 2
      behavior preserved (its entry implies Ambiguity==None which is
      != OrphanNFTBan).

  cmd/nftban-installer/restore_decide_amendment3_test.go (NEW, +200)
    - TestAmd3Dispatcher_E2Reframed_AllTrueAmendment3_True confirms the
      Amendment 3 entry condition produces E.2=true and AllTrueAmendment3
      passes (and Amendment 2's AllTrue() correctly fails because E.12
      cannot be true under the §62 entry by construction).
    - TestAmd3Dispatcher_E2_FalseWhenMisclassified pins 7 classifier
      shapes: amd2-path-true, amd3-path-true, empty-external-false,
      ufw-external-false, multi-external-false, OrphanNFTBan-false,
      AuthorityExternal-false.
    - TestAmd3Dispatcher_FailedRow_Amendment3 verifies
      FailedRowIDAmendment3() returns "" on happy path and AMD3-E.7
      when E.7 is mutated false.

NOT touched:

  - internal/installer/restore/engine.go (lattice unchanged)
  - internal/installer/restore/types.go (struct unchanged)
  - internal/installer/restore/contract.md (no amendment)
  - internal/installer/uninstall/* (classifier unchanged)
  - internal/installer/state/* (state machine unchanged)
  - cmd/nftban-installer/main.go (history gate unchanged)
  - cmd/nftban-installer/flags.go (flag surface unchanged)
  - .github/workflows/* (CI unchanged)

Test results (lab4):
  - go test ./cmd/nftban-installer/...           → ok
  - go test ./internal/installer/restore/...     → ok
  - go test ./...                                 → 64 packages ok, 0 FAIL

No host action. No binary rebuild. dns2 stays in canonical post-Gate-A
state.

Unblocks Gate B retry once a fresh Tier 1 binary is built from
post-merge HEAD, distributed to dns2, signoff captured, reachability
monitor activated, and pre-execution Gate B audit returns GO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(v1.100 Amendment 3): code-B — split into separate gatherOrphanEvidenceAmendment3 helper per auditor recommendation

Per auditor pre-draft review: implement Sub-gap 2a as a separate
gatherOrphanEvidenceAmendment3() helper rather than a mode-conditional
inside the existing gatherOrphanEvidence(). Mirrors the AllTrue() vs
AllTrueAmendment3() split that PR #523 introduced in types.go and the
decideAuthorityNFTBan() vs decideAmbiguityConflictExternal() split in
engine.go. "Two helpers, shared rows, different entry-condition
filling" — symmetric with the engine pattern.

Refactor changes (no behavior delta vs the prior code-B commit on
this branch):

  cmd/nftban-installer/restore_decide_evidence.go
    - populateSharedOrphanEvidenceRows(): private helper populates all
      rows EXCEPT E.2 (the entry-condition row).
    - gatherOrphanEvidence(): Amendment 2 — calls shared helper +
      sets E.2 = (Authority == AuthorityNFTBan). Restored to
      Amendment-2-byte-clean E.2 evaluation.
    - gatherOrphanEvidenceAmendment3(): Amendment 3 — calls shared
      helper + sets E.2 = (AuthorityAmbiguous + ConflictExternal +
      external == "csf"). New sibling helper.
    - E.13 retained at the contract-wording-exact form (Ambiguity !=
      AmbiguityOrphanNFTBan); shared by both helpers via the populate
      function.

  cmd/nftban-installer/restore_decide.go
    - Dispatcher's evidence-gathering now switches on the candidate
      type: amd2Candidate calls gatherOrphanEvidence (unchanged from
      pre-Amendment-3 main); amd3Candidate calls
      gatherOrphanEvidenceAmendment3.
    - Diagnostic log line is now amendment-specific (no dual-predicate
      noise) — clearer audit trail per amendment.

  cmd/nftban-installer/restore_decide_amendment3_test.go
    - Renamed and reorganized test cases to test the two helpers
      independently:
      * TestGatherOrphanEvidenceAmendment3_* tests the Amendment 3
        helper specifically (rejects Amendment 2 entry, accepts §62
        entry, AMD3-E.2 attribution on external=ufw, omits E.12).
      * TestGatherOrphanEvidence_Amendment2Unchanged regression-tests
        the Amendment 2 helper: still strict on AuthorityNFTBan,
        rejects Amendment 3 entry.

Why two helpers (auditor reasoning):
  - Audit-chain clarity: one helper per amendment, mirroring the
    engine.go and types.go split structure.
  - Each helper's CI grep gates and tests can reason about it
    independently.
  - No runtime classifier-state coupling between the dispatcher's
    inspection and the evidence-gathering.
  - Symmetric with AllTrue()/AllTrueAmendment3() in types.go.

Test results (lab4):
  - go test ./cmd/nftban-installer/...           → ok
  - go test ./internal/installer/restore/...     → ok
  - go test ./...                                 → 64 packages ok, 0 FAIL

No host action. No engine.go change. No contract.md change. No new
mutation surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant