Skip to content

feat(risk): deterministic authority gate + no-self-escalation — #5-core#4

Closed
gesh75 wants to merge 1 commit into
feat/llm-wedge2from
feat/llm-tier-authority
Closed

feat(risk): deterministic authority gate + no-self-escalation — #5-core#4
gesh75 wants to merge 1 commit into
feat/llm-wedge2from
feat/llm-tier-authority

Conversation

@gesh75

@gesh75 gesh75 commented Jun 1, 2026

Copy link
Copy Markdown
Owner

#5-core — per-risk-tier authority gate (stacked on the wedge PR)

The deterministic autonomy model: two orthogonal axes — Severity (NONE..CRITICAL) and Authority (AUTO<HITL<HOTL<BLOCK) — with hard-force overrides and the no-self-escalation ceiling. Additive core/risk/ package; no pipeline/schema change (keeps the sealed bundle untouched).

Base = feat/llm-wedge2 (#4-wedge). Merge the stack in order.

What's here

  • unify_severity — one canonical 5-bucket scorer reconciling AEGIS's two existing engines (guards.risk_tier 3-bucket + bundler._severity 5-bucket). The CROSS-2 differential (richer engine wins) is encoded as a test.
  • classify_change — regex-reliable AS/RD/RT detection + a documented, fail-closed spine/underlay heuristic (to be backed by containerlab labels / NetBox role at integration).
  • required_authority — severity base tier, then hard-force: AS/RD/RT → BLOCK, spine/underlay → ≥ HOTL, most-restrictive wins. The product insight: a twin-clean LOW-severity AS edit still BLOCKsseverity ≠ authority.
  • authorize — the no-self-escalation ceiling (required ≤ max_authorized, else BLOCK). max_authorized is a plain input here; binding it to a PIV-signed, compiled-in value is the feat(promote): enforce the no-self-escalation ceiling at the gate — #5-ceiling #5-ceiling / CROSS-3 follow-up — no crypto is claimed (avoids the false-green the completeness critic flagged).

Tests — tests/authority_test.py, 14/14

severity table · differential vs both legacy scorers · AS/RD/RT + spine/underlay classification · severity→authority mapping · two hard-force PROPERTY tests (fabric-identity always BLOCK; spine/underlay never AUTO over every severity) · no-self-escalation 0-violations property · end-to-end clean-LOW-AS-edit→BLOCK.

Regression: stress PASS, wedge 10/10, promote/contract PASS, PR-1 egress 29/29.

Next: #5-ceiling wires this into gate.py (new rule G5) + binds max_authorized to a hardware-PIV-signed ceiling → CROSS-3 SEAL (fuses the model-identity wedge + the signed authority ceiling).

Summary by Sourcery

Introduce a deterministic risk authority model that separates severity from authority tiers, with hard-force overrides for critical fabric changes and a no-self-escalation ceiling.

New Features:

  • Add a canonical 5-level severity scorer that reconciles existing risk scoring engines into a single scale.
  • Add change classification for AS/RD/RT and spine/underlay touches to drive authority overrides.
  • Add an authority tier model that derives required autonomy from severity and change classification, plus a no-self-escalation authorization check.

Tests:

  • Add comprehensive tests covering severity scoring, change classification, authority mapping, no-self-escalation invariants, and end-to-end blocking of low-severity but fabric-identity changes.

The per-risk-tier authority model — two orthogonal axes (severity vs authority), hard-force
overrides, and the no-self-escalation ceiling. Additive new package (core/risk/); no
pipeline or schema change.

- Severity (NONE..CRITICAL) and Tier (AUTO<HITL<HOTL<BLOCK) as ordered IntEnums.
- unify_severity(): one canonical 5-bucket scorer reconciling guards.risk_tier (3-bucket)
  and bundler._severity (5-bucket) — the CROSS-2 differential, richer engine wins.
- classify_change(): regex-reliable AS/RD/RT detection + a documented, fail-closed
  spine/underlay heuristic (to be backed by clab/NetBox role at integration).
- required_authority(): severity base tier, then hard-force — AS/RD/RT -> BLOCK,
  spine/underlay -> >= HOTL — most-restrictive wins. A twin-clean LOW-severity AS edit
  still BLOCKs: severity != authority is the real product change.
- authorize(): the no-self-escalation ceiling (required <= max_authorized, else BLOCK).
  max_authorized is a plain input here; binding it to a PIV-signed, compiled-in value is
  the #5-ceiling / CROSS-3 follow-up — no crypto is claimed.

tests/authority_test.py: 14 tests incl. the differential vs both legacy scorers, two
hard-force PROPERTY tests (fabric-identity always BLOCK; spine/underlay never AUTO across
all severities), and the no-self-escalation 0-violations property. 14/14 green; stress/
wedge(10)/promote/contract pass; PR-1 egress 29/29.
@sourcery-ai

sourcery-ai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces a deterministic, test-covered authority model under core/risk that unifies severity scoring, classifies fabric changes, maps severity plus change class to required authority tiers, and enforces a no-self-escalation ceiling, without altering existing pipelines or schemas.

Sequence diagram for risk authority evaluation and no-self-escalation

sequenceDiagram
    participant Planner
    participant RiskModel as core_risk

    Planner->>RiskModel: classify_change(configs: list[GeneratedConfig])
    RiskModel-->>Planner: ChangeClass

    Planner->>RiskModel: unify_severity(batfish_errors, devices_affected, sessions_dropped, converged)
    RiskModel-->>Planner: Severity

    Planner->>RiskModel: required_authority(severity: Severity, change_class: ChangeClass)
    RiskModel-->>Planner: Tier required

    Planner->>RiskModel: authorize(required: Tier, max_authorized: Tier)
    RiskModel-->>Planner: AuthorityDecision (allowed, effective, reason)
Loading

File-Level Changes

Change Details Files
Add deterministic authority model with severity/authority axes and no-self-escalation ceiling.
  • Define Severity and Tier enums with ordered semantics for comparison and mapping.
  • Implement unify_severity to provide a canonical 5-bucket severity scorer that mirrors the richer legacy engine and supersedes the 3-bucket scorer.
  • Implement classify_change to derive ChangeClass flags from configs using regex-based AS/RD/RT detection and heuristic spine/underlay detection.
  • Implement required_authority to compute the most restrictive authority tier from severity plus change classification, with hard-force rules for fabric-identity and spine/underlay.
  • Implement authorize and AuthorityDecision to enforce a ceiling on effective authority such that required authority cannot exceed max_authorized.
core/risk/authority.py
core/risk/__init__.py
Add test suite validating severity unification, change classification, authority mapping, and no-self-escalation invariants.
  • Add table-driven tests for unify_severity and differential checks against both legacy severity scorers.
  • Test regex-based AS/RD/RT and spine/underlay classification heuristics on representative configs and device names.
  • Test severity-to-authority base mapping plus hard-force behaviors for fabric-identity and spine/underlay changes, including property-style loops over severities and flags.
  • Test authorize behavior for both allowed and blocked cases and assert zero violations of the no-self-escalation invariant across all tier pairs.
  • Provide a lightweight main runner to execute all tests without pytest for CI or ad-hoc runs.
tests/authority_test.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In classify_change, the GeneratedConfig input is treated as a mapping (c.get(...)) despite the type hint suggesting a concrete class; consider either switching to attribute access or defining a small protocol/TypedDict interface so the expected shape is explicit and mismatches are caught earlier.
  • The underlay/spine heuristics (_UNDERLAY_CONFIG_RE, _SPINE_NAME_RE, _UNDERLAY_NAME_RE) are quite broad (e.g., any OSPF/ISIS reference marks a change as underlay-touching); if this is intended as a long-term behavior, it might be worth tightening the patterns (or scoping them by context) to avoid over-classifying benign changes as underlay.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `classify_change`, the `GeneratedConfig` input is treated as a mapping (`c.get(...)`) despite the type hint suggesting a concrete class; consider either switching to attribute access or defining a small protocol/TypedDict interface so the expected shape is explicit and mismatches are caught earlier.
- The underlay/spine heuristics (`_UNDERLAY_CONFIG_RE`, `_SPINE_NAME_RE`, `_UNDERLAY_NAME_RE`) are quite broad (e.g., any OSPF/ISIS reference marks a change as underlay-touching); if this is intended as a long-term behavior, it might be worth tightening the patterns (or scoping them by context) to avoid over-classifying benign changes as underlay.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

gesh75 added a commit that referenced this pull request Jun 1, 2026
…authority gate, ceiling, CROSS-3 seal (#9)

Squashed release of the 8-PR stack (#2..#8): the offline, hardware-rootable bounded-autonomy
receipt and everything under it. AUTO disabled by default; the only un-built piece is the
hardware-PIV signer (docs/PIV_HARDWARE_SIGNER_PLAN.md) — software Ed25519 runs the whole demo.

- core/llm: single shared LLM egress + adapter (local-first fallback chain, retry/cooldown,
  4-event hook bus; raw httpx, no anthropic SDK; air-gap fail-closed). [#2]
- evidence: model-identity wedge — change.model_identity sealed into the bundle hash; honest
  UNKNOWN / unattested states, never a faked attribution. [#3]
- core/risk: deterministic AUTO/HITL/HOTL/BLOCK authority model (severity != authority;
  AS/RD/RT -> BLOCK; spine/underlay -> >= HOTL) + the no-self-escalation ceiling. [#4]
- core/promote: gate rule G5 enforces the ceiling from the sealed required tier;
  change.authority sealed (bundle_version 1.1->1.2). [#5]
- core/seal: CROSS-3 detached, offline-verifiable bounded-autonomy receipt (Ed25519; gates
  G0..G6; refuses to seal an unbounded change). [#6]
- serve: /api/preflight/run emits the seal; GET /api/seal/pubkey + POST /api/seal/verify;
  examiner PDF shows the seal. [#7]
- docs: PIV hardware-signer plan — the final, hardware-gated CROSS-3 pass. [#8]

Property-tested safety invariants: no-self-escalation 0-violations, every-seal-is-bounded,
tamper detection, offline-keyless verify, air-gap fail-closed.
@gesh75

gesh75 commented Jun 1, 2026

Copy link
Copy Markdown
Owner Author

Superseded by the squashed release #9, now merged to main. This PR's changes are in main; closing for tidiness (branch kept for history).

@gesh75 gesh75 closed this Jun 1, 2026
@gesh75
gesh75 deleted the feat/llm-tier-authority branch June 1, 2026 18:10
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