Skip to content

fix(airlock): Round 8 polish and 1.4.0 release prep — exception hygie… - #160

Closed
kenwalger wants to merge 1 commit into
mainfrom
repo-polish-v1
Closed

fix(airlock): Round 8 polish and 1.4.0 release prep — exception hygie…#160
kenwalger wants to merge 1 commit into
mainfrom
repo-polish-v1

Conversation

@kenwalger

Copy link
Copy Markdown
Owner

…ne, warning preservation, SPDX migration

Round 8 — exception de-duplication (policy.py): AirlockConfigurationError extends ValueError, so clean boot-time validation errors raised inside _parse_rule were being caught by the broad except (KeyError, TypeError, ValueError) handler in PolicyEngine.init and silently re-wrapped with a redundant "Invalid rule definition" prefix. An explicit except AirlockConfigurationError: raise guard inserted before the broad handler lets those errors surface natively.

Round 8 — pre-sieve warning preservation (exception.py, boundary.py): AirlockPolicyViolation.init now accepts optional warnings: list[str] | None (stored as self.warnings). When AirlockBoundary.process() raises AirlockPolicyViolation on a post-sieve deny verdict, it passes verdict.warnings so callers can inspect accumulated pre-sieve warn-action diagnostic context via exc.warnings without losing it at the exception boundary. RuntimeError message contract is unchanged; all existing callers remain compatible.

Release prep — SPDX license metadata (pyproject.toml): project.license migrated from the deprecated TOML table form ({ text = "MIT" }) to the SPDX string literal ("MIT"). Deprecated License :: OSI Approved :: MIT License classifier removed. Eliminates two SetuptoolsDeprecationWarning emissions that become hard build errors after 2027-02-18.

PEP 517 build validated: uv build --package sovereign-sdk-airlock produces clean sdist and pure-Python wheel with no editable-path leakage and zero deprecation warnings. Wheel METADATA confirmed: Name, Version, Requires-Python, Description-Content-Type, all Requires-Dist entries, and full README.md long-description (3,401 bytes, UTF-8).

Documentation: CHANGELOG.md [Unreleased] promoted to [1.4.0] — 2026-07-05. ROADMAP.md Phase 9.6 delivered list updated. Package and workspace README.md updated with AirlockPolicyViolation.warnings inspection pattern. 84 tests pass, 0 regressions.

…ne, warning preservation, SPDX migration

Round 8 — exception de-duplication (policy.py): AirlockConfigurationError extends
ValueError, so clean boot-time validation errors raised inside _parse_rule were being
caught by the broad except (KeyError, TypeError, ValueError) handler in
PolicyEngine.__init__ and silently re-wrapped with a redundant "Invalid rule definition"
prefix. An explicit except AirlockConfigurationError: raise guard inserted before the
broad handler lets those errors surface natively.

Round 8 — pre-sieve warning preservation (exception.py, boundary.py):
AirlockPolicyViolation.__init__ now accepts optional warnings: list[str] | None (stored
as self.warnings). When AirlockBoundary.process() raises AirlockPolicyViolation on a
post-sieve deny verdict, it passes verdict.warnings so callers can inspect accumulated
pre-sieve warn-action diagnostic context via exc.warnings without losing it at the
exception boundary. RuntimeError message contract is unchanged; all existing callers
remain compatible.

Release prep — SPDX license metadata (pyproject.toml): project.license migrated from
the deprecated TOML table form ({ text = "MIT" }) to the SPDX string literal ("MIT").
Deprecated License :: OSI Approved :: MIT License classifier removed. Eliminates two
SetuptoolsDeprecationWarning emissions that become hard build errors after 2027-02-18.

PEP 517 build validated: uv build --package sovereign-sdk-airlock produces clean sdist
and pure-Python wheel with no editable-path leakage and zero deprecation warnings.
Wheel METADATA confirmed: Name, Version, Requires-Python, Description-Content-Type,
all Requires-Dist entries, and full README.md long-description (3,401 bytes, UTF-8).

Documentation: CHANGELOG.md [Unreleased] promoted to [1.4.0] — 2026-07-05. ROADMAP.md
Phase 9.6 delivered list updated. Package and workspace README.md updated with
AirlockPolicyViolation.warnings inspection pattern. 84 tests pass, 0 regressions.
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR delivers three independent fixes for sovereign-sdk-airlock 1.4.0: exception de-duplication in PolicyEngine.__init__ so AirlockConfigurationError is no longer swallowed and re-wrapped by its own parent ValueError handler; a warnings attribute on AirlockPolicyViolation that preserves pre-sieve warn-action messages when a post-sieve deny fires; and SPDX license metadata migration in pyproject.toml to eliminate impending SetuptoolsDeprecationWarning build errors.

  • policy.py: except AirlockConfigurationError: raise guard correctly breaks the inheritance-masking loop so configuration errors always surface with their original message.
  • exception.py + boundary.py: AirlockPolicyViolation gains a warnings: list[str] attribute; the post-sieve deny path passes verdict.warnings to it, but the pre-sieve deny path (line 134) still raises without forwarding the same verdict.warnings, making the attribute asymmetrically empty for pre-sieve denials even when warn rules also matched.
  • pyproject.toml: license = \"MIT\" (SPDX string) and removal of the deprecated OSI classifier are the correct PEP 639 migration steps.

Confidence Score: 4/5

Safe to merge with one fix: the pre-sieve deny path should forward verdict.warnings to the exception the same way the post-sieve deny path does.

The AirlockPolicyViolation.warnings feature is only half-wired: when a deny rule fires during the pre-sieve evaluation pass, verdict.warnings is never passed to the raised exception, so callers inspecting exc.warnings on a pre-sieve denial will always see an empty list even when warn rules matched in the same pass. The other two changes — the AirlockConfigurationError re-raise guard and the SPDX license migration — are straightforward and correct.

packages/sovereign-airlock/src/sovereign_airlock/boundary.py — the pre-sieve deny raise at line 134 needs warnings=verdict.warnings added to match the post-sieve deny path.

Important Files Changed

Filename Overview
packages/sovereign-airlock/src/sovereign_airlock/boundary.py Post-sieve deny correctly forwards pre-sieve warnings to the exception, but the pre-sieve deny path still raises without warnings, silently dropping any warn-rule messages that accumulated alongside a pre-sieve deny.
packages/sovereign-airlock/src/sovereign_airlock/exception.py Adds init with optional warnings parameter to AirlockPolicyViolation; defensively copies the input list and defaults to []. Implementation is correct and backward-compatible.
packages/sovereign-airlock/src/sovereign_airlock/policy.py Adds except AirlockConfigurationError: raise guard before the broad (KeyError, TypeError, ValueError) handler, correctly preventing double-wrapping of already-typed configuration errors.
packages/sovereign-airlock/pyproject.toml Migrates license from deprecated TOML-table form to SPDX string literal and drops the deprecated classifier; correct PEP 639 migration.
CHANGELOG.md Promotes [Unreleased] section to [1.4.0] — 2026-07-05 with accurate descriptions of all three changes.
packages/sovereign-airlock/README.md Adds Exception Inspection section documenting exc.warnings usage; documentation is accurate for post-sieve deny but note the asymmetry with pre-sieve deny.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant AirlockBoundary
    participant PolicyEngine
    participant Sieve
    participant ReceiptBuilder

    Caller->>AirlockBoundary: process(payload)
    AirlockBoundary->>PolicyEngine: evaluate(payload)
    PolicyEngine-->>AirlockBoundary: verdict (allowed, violations, warnings)

    alt pre-sieve deny
        AirlockBoundary-->>Caller: raise AirlockPolicyViolation(violations) [verdict.warnings NOT forwarded]
    end

    AirlockBoundary->>Sieve: sieve_with_metrics(raw_content)
    Sieve-->>AirlockBoundary: sieve_output + telemetry

    AirlockBoundary->>PolicyEngine: evaluate_post_sieve(telemetry)
    PolicyEngine-->>AirlockBoundary: post_verdict (allowed, violations, warnings)

    alt post-sieve deny
        AirlockBoundary-->>Caller: "raise AirlockPolicyViolation(violations, warnings=verdict.warnings)"
    end

    AirlockBoundary->>ReceiptBuilder: build_and_commit(...)
    ReceiptBuilder-->>AirlockBoundary: ForensicReceipt (non-fatal on failure)

    AirlockBoundary-->>Caller: AirlockResult(sieved_content, telemetry, receipt, policy_warnings)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant AirlockBoundary
    participant PolicyEngine
    participant Sieve
    participant ReceiptBuilder

    Caller->>AirlockBoundary: process(payload)
    AirlockBoundary->>PolicyEngine: evaluate(payload)
    PolicyEngine-->>AirlockBoundary: verdict (allowed, violations, warnings)

    alt pre-sieve deny
        AirlockBoundary-->>Caller: raise AirlockPolicyViolation(violations) [verdict.warnings NOT forwarded]
    end

    AirlockBoundary->>Sieve: sieve_with_metrics(raw_content)
    Sieve-->>AirlockBoundary: sieve_output + telemetry

    AirlockBoundary->>PolicyEngine: evaluate_post_sieve(telemetry)
    PolicyEngine-->>AirlockBoundary: post_verdict (allowed, violations, warnings)

    alt post-sieve deny
        AirlockBoundary-->>Caller: "raise AirlockPolicyViolation(violations, warnings=verdict.warnings)"
    end

    AirlockBoundary->>ReceiptBuilder: build_and_commit(...)
    ReceiptBuilder-->>AirlockBoundary: ForensicReceipt (non-fatal on failure)

    AirlockBoundary-->>Caller: AirlockResult(sieved_content, telemetry, receipt, policy_warnings)
Loading

Comments Outside Diff (1)

  1. packages/sovereign-airlock/src/sovereign_airlock/boundary.py, line 133-134 (link)

    P1 Pre-sieve deny drops accumulated warn messages

    The pre-sieve deny path raises AirlockPolicyViolation without passing verdict.warnings, so exc.warnings is always [] even when warn-action rules fired during the same pre-sieve evaluation pass. Since all rules are evaluated regardless of prior denials (the accumulation semantics that make this feature useful), it is entirely possible for both deny and warn rules to match during the pre-sieve phase, yet the resulting exception silently discards the warn messages. The post-sieve path correctly passes verdict.warnings; the pre-sieve path should too. The README comment # exc.warnings carries any pre-sieve warn-rule messages accumulated before the deny is accurate for post-sieve denies but misleading here.

Reviews (1): Last reviewed commit: "fix(airlock): Round 8 polish and 1.4.0 r..." | Re-trigger Greptile

@kenwalger kenwalger closed this Jul 6, 2026
@kenwalger
kenwalger deleted the repo-polish-v1 branch July 6, 2026 05:52
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