Skip to content

refactor(airlock): align physical directory name to workspace flat-si… - #153

Closed
kenwalger wants to merge 1 commit into
mainfrom
airlock-v1
Closed

refactor(airlock): align physical directory name to workspace flat-si…#153
kenwalger wants to merge 1 commit into
mainfrom
airlock-v1

Conversation

@kenwalger

Copy link
Copy Markdown
Owner

…bling convention

Renames packages/sovereign-sdk-airlock/ to packages/sovereign-airlock/ to match the established directory layout of sibling packages (sovereign-core, sovereign-sieve, sovereign-ledger, sovereign-edge, sovereign-sensor).

Distribution name (sovereign-sdk-airlock), import namespace (sovereign_airlock), and all runtime dependency declarations are unchanged.

Changes:

  • packages/sovereign-sdk-airlock/ → packages/sovereign-airlock/ (directory rename)
  • Updated file-path header comments in all 7 source modules under src/sovereign_airlock/
  • uv.lock: editable path references updated from packages/sovereign-sdk-airlock to packages/sovereign-airlock; re-resolved cleanly (uv lock)
  • README.md: workspace topography tree entry corrected
  • CHANGELOG.md: workspace member path reference corrected
  • ROADMAP.md: Phase 9.6 pyproject.toml path reference corrected

Verification: 469 passed, 1 skipped. Zero regressions.

…bling convention

Renames `packages/sovereign-sdk-airlock/` to `packages/sovereign-airlock/` to match
the established directory layout of sibling packages (`sovereign-core`, `sovereign-sieve`,
`sovereign-ledger`, `sovereign-edge`, `sovereign-sensor`).

Distribution name (`sovereign-sdk-airlock`), import namespace (`sovereign_airlock`), and
all runtime dependency declarations are unchanged.

Changes:
- packages/sovereign-sdk-airlock/ → packages/sovereign-airlock/ (directory rename)
- Updated file-path header comments in all 7 source modules under src/sovereign_airlock/
- uv.lock: editable path references updated from packages/sovereign-sdk-airlock to
  packages/sovereign-airlock; re-resolved cleanly (uv lock)
- README.md: workspace topography tree entry corrected
- CHANGELOG.md: workspace member path reference corrected
- ROADMAP.md: Phase 9.6 pyproject.toml path reference corrected

Verification: 469 passed, 1 skipped. Zero regressions.
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds the sovereign-sdk-airlock package under packages/sovereign-airlock/, implementing the four-component outbound governance boundary (policy evaluation → sieve convergence → telemetry assembly → evidence generation). The distribution name, import namespace, and all dependency declarations match the established workspace pattern.

  • PolicyEngine (policy.py): YAML rule evaluator supporting raw, fields, and telemetry scopes with allow/warn/deny actions. Two issues found: prose_tax_warning_threshold is parsed and stored but never evaluated anywhere in evaluate(), and regex patterns are stored as raw strings rather than pre-compiled — so a malformed regex raises re.error at evaluation time instead of during PolicyEngine.__init__().
  • AirlockBoundary (boundary.py): Async orchestrator wired correctly — deny blocks before sieve/ledger, ledger write failure is non-fatal, signing failures propagate as documented.
  • NormalizedPayload (payload.py): Frozen dataclass with dict and list fields; frozen=True prevents attribute reassignment but not mutation of the collections themselves.

Confidence Score: 3/5

The core boundary orchestration and evidence pipeline are solid, but the PolicyEngine has two functional gaps that would silently misbehave in production.

The prose_tax_warning_threshold global config knob is documented, exposed in the README policy example, and parsed at startup — but is never consulted during evaluation. Any operator who configures it will get no warnings, with no indication the config is inert. Separately, invalid regex patterns in policy rules are not detected at PolicyEngine construction time; they raise an uncaught re.error during a live governance call, bypassing the AirlockConfigurationError contract the class advertises. Both issues are in the security-critical policy evaluation path and should be resolved before this ships.

packages/sovereign-airlock/src/sovereign_airlock/policy.py — the _prose_tax_warning_threshold dead code and the missing regex pre-compilation at parse time both need attention.

Important Files Changed

Filename Overview
packages/sovereign-airlock/src/sovereign_airlock/policy.py New PolicyEngine with two defects: prose_tax_warning_threshold is stored but never evaluated, and regex patterns are not validated at parse time (re.error surfaces at runtime instead of init).
packages/sovereign-airlock/src/sovereign_airlock/payload.py New NormalizedPayload frozen dataclass and normalizer functions; mutable dict/list fields mean deep immutability is not enforced despite frozen=True.
packages/sovereign-airlock/src/sovereign_airlock/boundary.py New AirlockBoundary async orchestrator; four-component lifecycle is correctly sequenced, deny verdict raises before sieve/ledger ops, evidence recording is non-fatal.
packages/sovereign-airlock/src/sovereign_airlock/telemetry.py New AirlockTelemetry frozen dataclass; ZeroDivisionError guard for raw_tokens==0 is present, SHA-256 hash derivation is correct.
packages/sovereign-airlock/src/sovereign_airlock/receipt.py New ReceiptBuilder; ledger write failure is correctly non-fatal, signing failures propagate as documented.
packages/sovereign-airlock/pyproject.toml New package manifest; distribution name sovereign-sdk-airlock, directory sovereign-airlock — consistent with stated workspace convention.
pyproject.toml Root workspace manifest updated to add sovereign-sdk-airlock to dev-dependencies and uv.sources; correctly references packages/sovereign-airlock.
uv.lock Lock file re-resolved cleanly; editable path points to packages/sovereign-airlock, pyyaml 6.0.3 added as new transitive dependency.
packages/sovereign-airlock/tests/test_policy.py 21-case policy test suite covering all three scopes, all three actions, global ceiling, and error conditions; no missing coverage gaps spotted.
packages/sovereign-airlock/tests/test_boundary.py 18-case boundary test suite; happy path, policy denial, warnings, transport neutrality, and resiliency (ledger failure) are all exercised.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant AirlockBoundary
    participant PolicyEngine
    participant sieve_with_metrics
    participant AirlockTelemetry
    participant ReceiptBuilder
    participant SovereignLedger

    Caller->>AirlockBoundary: process(NormalizedPayload)
    AirlockBoundary->>PolicyEngine: evaluate(payload)
    PolicyEngine-->>AirlockBoundary: PolicyVerdict
    alt "verdict.allowed == False"
        AirlockBoundary-->>Caller: raise AirlockPolicyViolation
    end
    AirlockBoundary->>sieve_with_metrics: sieve_with_metrics(raw_content)
    sieve_with_metrics-->>AirlockBoundary: SieveOutput
    AirlockBoundary->>AirlockTelemetry: from_sieve_output(sieve_output, raw_content)
    AirlockTelemetry-->>AirlockBoundary: AirlockTelemetry
    AirlockBoundary->>ReceiptBuilder: build_and_commit(sieved_content, telemetry, warnings, source)
    ReceiptBuilder->>SovereignLedger: append_receipt(receipt, sieved_content)
    alt ledger write fails
        SovereignLedger-->>ReceiptBuilder: Exception (non-fatal, logged)
    end
    ReceiptBuilder-->>AirlockBoundary: ForensicReceipt
    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_with_metrics
    participant AirlockTelemetry
    participant ReceiptBuilder
    participant SovereignLedger

    Caller->>AirlockBoundary: process(NormalizedPayload)
    AirlockBoundary->>PolicyEngine: evaluate(payload)
    PolicyEngine-->>AirlockBoundary: PolicyVerdict
    alt "verdict.allowed == False"
        AirlockBoundary-->>Caller: raise AirlockPolicyViolation
    end
    AirlockBoundary->>sieve_with_metrics: sieve_with_metrics(raw_content)
    sieve_with_metrics-->>AirlockBoundary: SieveOutput
    AirlockBoundary->>AirlockTelemetry: from_sieve_output(sieve_output, raw_content)
    AirlockTelemetry-->>AirlockBoundary: AirlockTelemetry
    AirlockBoundary->>ReceiptBuilder: build_and_commit(sieved_content, telemetry, warnings, source)
    ReceiptBuilder->>SovereignLedger: append_receipt(receipt, sieved_content)
    alt ledger write fails
        SovereignLedger-->>ReceiptBuilder: Exception (non-fatal, logged)
    end
    ReceiptBuilder-->>AirlockBoundary: ForensicReceipt
    AirlockBoundary-->>Caller: AirlockResult(sieved_content, telemetry, receipt, policy_warnings)
Loading

Comments Outside Diff (1)

  1. packages/sovereign-airlock/src/sovereign_airlock/policy.py, line 919-922 (link)

    P1 Invalid regex patterns not caught at load time

    pattern is stored as a raw string in PolicyRule and only handed to re.search() inside _evaluate_raw / _evaluate_fields at evaluation time. A malformed pattern (e.g. "[") will raise re.error during a live governance call rather than during PolicyEngine.__init__(), bypassing the AirlockConfigurationError contract the class documents. Pre-compiling patterns with re.compile() inside _parse_rule (storing the compiled object or raising AirlockConfigurationError on re.error) would surface the problem at startup and align with the documented "raises … on structurally invalid rule definitions" guarantee.

Reviews (1): Last reviewed commit: "refactor(airlock): align physical direct..." | Re-trigger Greptile

Comment on lines +109 to +113
global_cfg: dict[str, Any] = raw.get("global") or {}
self._max_token_ceiling = int(global_cfg.get("max_token_ceiling", 0))
self._prose_tax_warning_threshold = float(
global_cfg.get("prose_tax_warning_threshold", 0.0)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 prose_tax_warning_threshold is loaded but never evaluated

_prose_tax_warning_threshold is parsed and stored from the YAML global block (and documented as a configurable knob in the README and ROADMAP), but it is never consulted anywhere in evaluate(). An operator who sets prose_tax_warning_threshold: 0.35 will never see a warning emitted — the value is silently ignored. The evaluate() method only checks _max_token_ceiling; a matching branch in _evaluate_telemetry (or directly in evaluate() after the sieve pass) is needed to honour this configuration intent.

Comment on lines +33 to +36
content: list[str]
metadata: dict[str, Any] = field(default_factory=dict)
tools: list[dict[str, Any]] = field(default_factory=list)
token_estimate: int = 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Frozen dataclass with mutable collection fields

frozen=True prevents attribute reassignment, but callers can still mutate payload.metadata["k"] = "v" or payload.tools.append(...) after construction, silently invalidating the token_estimate and any downstream governance decision that was computed against the original content. Consider typing content as tuple, using types.MappingProxyType for metadata, or adding a __post_init__ that converts them to immutable equivalents if deep immutability is a required invariant for the governance surface.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@kenwalger kenwalger closed this Jul 6, 2026
@kenwalger
kenwalger deleted the airlock-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