fix(airlock): Round 8 polish and 1.4.0 release prep — exception hygie… - #160
fix(airlock): Round 8 polish and 1.4.0 release prep — exception hygie…#160kenwalger wants to merge 1 commit into
Conversation
…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 SummaryThis PR delivers three independent fixes for
Confidence Score: 4/5Safe 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
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)
%%{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)
|
…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.