Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`sovereign-sdk-runtime`, and `sovereign-sdk-edge` internal dependency declarations
updated to reference the new `sovereign-sdk-*` distribution names at `>=1.3.0`.

## [Unreleased]
## [1.4.0] — 2026-07-05

### Added

Expand Down Expand Up @@ -135,6 +135,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`from_sieve_output()`. Content expansion (sieved > raw) clamps to `0.0`; impossible
inversion (negative `optimized_token_count`) clamps to `100.0`.

- **`AirlockPolicyViolation.warnings` diagnostic attribute** (`exception.py`,
`boundary.py`): `AirlockPolicyViolation.__init__` now accepts an optional
`warnings: list[str] | None` parameter; accumulated pre-sieve `warn`-action messages
are stored as `self.warnings: list[str]`. When `AirlockBoundary.process()` raises
`AirlockPolicyViolation` on a post-sieve deny verdict, it passes `verdict.warnings`
so callers can programmatically inspect pre-sieve diagnostic context via `exc.warnings`
without losing it at the exception boundary.

- **`AirlockConfigurationError` de-duplication** (`policy.py`): Since
`AirlockConfigurationError` extends `ValueError`, clean validation errors raised inside
`_parse_rule` were being caught by the broad `except (KeyError, TypeError, ValueError)`
handler in `PolicyEngine.__init__` and re-wrapped with a redundant "Invalid rule
definition" prefix. An explicit `except AirlockConfigurationError: raise` guard now
lets those errors surface natively with their original message intact.

- **SPDX license metadata** (`pyproject.toml`): `project.license` migrated from
deprecated TOML table form (`{ text = "MIT" }`) to 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 validation**: `uv build --package sovereign-sdk-airlock` clean-room
pass produces `sovereign_sdk_airlock-1.4.0.tar.gz` and
`sovereign_sdk_airlock-1.4.0-py3-none-any.whl` with no editable-path or direct-url
leakage. Wheel METADATA confirmed: all `Requires-Dist` entries, `Description-Content-Type:
text/markdown`, and full `README.md` long-description body (3,401 bytes, UTF-8).

- **Phase 9.5 — `sovereign-edge` sensor ingestion bridge** (new workspace member
`packages/sovereign-edge/`): Introduces the middleware pipeline that intercepts
sealed sensor wire frames from `sovereign-sensor`, applies the `sovereign-sieve`
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ try:
# result.policy_warnings — non-fatal warn-rule messages
except AirlockPolicyViolation as exc:
# Payload blocked by a deny rule — do not transmit
# exc.warnings carries any pre-sieve warn-rule messages accumulated before the deny
raise
```

Expand Down
17 changes: 14 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -1300,11 +1300,22 @@ result = await boundary.process(normalize_openai(request))
`sieved_content`, `telemetry`, `receipt`, and `policy_warnings`
* [x] `AirlockPolicyViolation(RuntimeError)` and `AirlockConfigurationError(ValueError)`
(`exception.py`) — domain exceptions for deny-action enforcement and configuration
invariant violations respectively
invariant violations respectively; `AirlockPolicyViolation.__init__` accepts optional
`warnings: list[str] | None` (default `[]`) stored as `self.warnings`, preserving
pre-sieve `warn`-action diagnostic context when a post-sieve deny fires
* [x] `AirlockConfigurationError` de-duplication (`policy.py`) — explicit
`except AirlockConfigurationError: raise` guard in `PolicyEngine.__init__` prevents
clean boot-time validation errors raised by `_parse_rule` from being caught and
re-wrapped by the broad `except (KeyError, TypeError, ValueError)` handler (since
`AirlockConfigurationError` extends `ValueError`)
* [x] `packages/sovereign-airlock/pyproject.toml` — workspace member at version
`1.4.0`; runtime dependencies: `sovereign-sdk-core>=1.3.0`,
`sovereign-sdk-ledger>=1.3.0`, `sovereign-sdk-sieve>=1.3.0`, `pyyaml>=6.0`
* [x] 83-case test suite across four files (`TestAirlockTelemetry`: 12;
`sovereign-sdk-ledger>=1.3.0`, `sovereign-sdk-sieve>=1.3.0`, `pyyaml>=6.0`;
`license` migrated from deprecated TOML table form to SPDX string literal (`"MIT"`);
deprecated `License :: OSI Approved :: MIT License` classifier removed; PEP 517 build
validated via `uv build --package sovereign-sdk-airlock` — clean sdist and
pure-Python wheel produced with no editable-path or direct-url leakage
* [x] 84-case test suite across four files (`TestAirlockTelemetry`: 12;
`TestPolicyLoading` + `TestRawScopeEvaluation` + `TestFieldsScopeEvaluation` +
`TestTelemetryScopeEvaluation` + `TestGlobalCeiling`: 35; `TestReceiptBuilder`: 11;
`TestAirlockBoundaryHappyPath` + `TestAirlockBoundaryPolicyDenial` +
Expand Down
14 changes: 14 additions & 0 deletions packages/sovereign-airlock/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ rules:
| `warn` | Appends to `AirlockResult.policy_warnings`; transmission continues |
| `deny` | Raises `AirlockPolicyViolation`; payload blocked |

### Exception Inspection

When a post-sieve `deny` rule fires, any `warn`-action messages accumulated during the
pre-sieve pass are preserved on the exception for diagnostic inspection:

```python
try:
result = await boundary.process(payload)
except AirlockPolicyViolation as exc:
print(exc) # deny violation message
print(exc.warnings) # pre-sieve warn messages (list[str], may be empty)
raise
```

---

## Transport Normalisation
Expand Down
3 changes: 1 addition & 2 deletions packages/sovereign-airlock/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ version = "1.4.0"
description = "Model-neutral outbound governance boundary for sovereign perimeter inspection, policy enforcement, and evidence generation"
readme = "README.md"
requires-python = ">=3.12"
license = { text = "MIT" }
license = "MIT"
dependencies = [
"sovereign-sdk-core>=1.3.0",
"sovereign-sdk-ledger>=1.3.0",
"sovereign-sdk-sieve>=1.3.0",
"pyyaml>=6.0",
]
classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.12",
"Intended Audience :: Developers",
"Topic :: Security",
Expand Down
5 changes: 4 additions & 1 deletion packages/sovereign-airlock/src/sovereign_airlock/boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,10 @@ async def process(self, payload: NormalizedPayload) -> AirlockResult:
# Post-sieve: evaluate telemetry rules requiring actual sieve output
post_verdict = self._policy.evaluate_post_sieve(telemetry)
if not post_verdict.allowed:
raise AirlockPolicyViolation("; ".join(post_verdict.violations))
raise AirlockPolicyViolation(
"; ".join(post_verdict.violations),
warnings=verdict.warnings,
)
verdict.warnings.extend(post_verdict.warnings)

# Component D: Evidence generation — non-fatal on any failure
Expand Down
17 changes: 17 additions & 0 deletions packages/sovereign-airlock/src/sovereign_airlock/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,25 @@ class AirlockPolicyViolation(RuntimeError):

:param message: Human-readable description of the policy rules that were violated.
:type message: str
:param warnings: Non-fatal ``warn``-action messages accumulated prior to the deny,
preserved here for caller diagnostics. Empty list when none were collected.
:type warnings: list[str] | None
"""

warnings: list[str]

def __init__(self, message: str, warnings: list[str] | None = None) -> None:
"""
Initialise the violation exception with violation message and optional prior warnings.

:param message: Human-readable description of the policy rules that were violated.
:type message: str
:param warnings: Non-fatal ``warn``-action messages accumulated before the deny verdict.
:type warnings: list[str] | None
"""
super().__init__(message)
self.warnings = list(warnings) if warnings is not None else []


class AirlockConfigurationError(ValueError):
"""Raised when a policy YAML configuration file is missing, malformed, or structurally invalid.
Expand Down
2 changes: 2 additions & 0 deletions packages/sovereign-airlock/src/sovereign_airlock/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ def __init__(self, config_path: str | Path) -> None:
for rule_def in raw.get("rules") or []:
try:
self._rules.append(self._parse_rule(rule_def))
except AirlockConfigurationError:
raise
except (KeyError, TypeError, ValueError) as exc:
raise AirlockConfigurationError(
f"Invalid rule definition {rule_def!r}: {exc}"
Expand Down
Loading