Skip to content

feat(serve): emit the bounded-autonomy seal on the live preflight path#7

Closed
gesh75 wants to merge 1 commit into
feat/llm-sealfrom
feat/llm-seal-serve
Closed

feat(serve): emit the bounded-autonomy seal on the live preflight path#7
gesh75 wants to merge 1 commit into
feat/llm-sealfrom
feat/llm-seal-serve

Conversation

@gesh75

@gesh75 gesh75 commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Wire CROSS-3 into the live preflight path (stacked on the seal PR)

A preflight run now emits a verifiable receipt end-to-end, and the examiner PDF shows it.

Base = feat/llm-seal (CROSS-3). Merge the stack in order.

What's here

  • POST /api/preflight/run embeds bundle["seal"] — the detached CROSS-3 receipt (or null when the change exceeds the autonomy ceiling; see change.authority).
  • GET /api/seal/pubkey — the pinned public key (alg/key_id/public_key_b64) a client uses to check seals offline, no secret.
  • POST /api/seal/verify{bundle, seal} → the G0–G6 verdict against the server's pinned key.
  • Signing keyAEGIS_SEAL_KEY (64-hex Ed25519 seed) pins a stable key; otherwise an ephemeral demo key (logged). Production swaps a YubiKey-PIV signer (same Signer protocol).
  • compute_sha256 excludes a top-level seal — the receipt rides in the response but is not part of the bundle's own integrity hash (it signs that hash). A safe no-op for every existing sealless bundle.
  • Examiner PDF gains a "Bounded-autonomy seal" section (model + authority + signature key).

Tests

api_test +3 (run emits seal · /api/seal/pubkey · /api/seal/verify) → PASS, 23 checks / 7 routes. seal_test +3 (seal_response bounded/unbounded · embedded-seal hash exclusion) → 15/15. pdf_test PASS (sealed section renders). Regression: stress/promote/ceiling(8)/authority(14)/twin/contract PASS; wedge 10/10; PR-1 29/29. (Verified across the base env + a throwaway flask/reportlab venv.)

Try it: python -m aegis.servePOST /api/preflight/run {"intent":"add vlan 10 to leaf-1"} returns a bundle with a seal; GET /api/seal/pubkey + POST /api/seal/verify confirm it offline.

Summary by Sourcery

Wire bounded-autonomy seal receipts into the live preflight API and surface their verification and metadata across the service.

New Features:

  • Emit a detached bounded-autonomy seal receipt from the preflight run API response when the change is within the autonomy ceiling.
  • Expose an API endpoint to retrieve the pinned public key used to verify seal receipts offline.
  • Expose an API endpoint to verify a provided {bundle, seal} pair against the server’s pinned key.
  • Render a bounded-autonomy seal section in the examiner PDF, summarizing model, authority, and signature key details.

Enhancements:

  • Introduce a configurable Ed25519 signing key for seals with support for persistent keys via environment variable and fallback to an ephemeral demo key.
  • Add a helper to seal live responses that returns either a receipt or a reason when sealing is not possible.
  • Exclude embedded seals from the bundle integrity hash computation so that embedding a detached seal does not affect verification.

Tests:

  • Extend API endpoint tests to cover seal emission behavior and the new seal-related endpoints.
  • Add seal tests for live-path sealing behavior and validation that embedded seals are excluded from the bundle integrity hash.
  • Keep PDF tests validating that the new bounded-autonomy seal section renders correctly.

Wires CROSS-3 into the HTTP surface so a preflight run produces a verifiable receipt
end-to-end, and the examiner PDF shows it.

- serve.py: load/generate the seal signing key at startup (AEGIS_SEAL_KEY = 64 hex Ed25519
  seed, else an ephemeral demo key; production swaps a YubiKey-PIV signer). POST
  /api/preflight/run now embeds bundle["seal"] (the detached receipt, None when the change
  exceeds the ceiling). New GET /api/seal/pubkey (the pinned key, offline check) + POST
  /api/seal/verify ({bundle, seal} -> G0..G6 verdict).
- evidence/bundler.py: compute_sha256 excludes a top-level "seal" key — the detached seal
  rides in the response but is NOT part of the bundle's own hash (it signs that hash). Safe
  no-op for every sealless bundle.
- evidence/pdf.py: the examiner PDF gains a "Bounded-autonomy seal" section (model +
  authority + signature key) when a seal is present.
- core/seal: seal_response() helper (returns (receipt, None) or (None, reason)).
- tests: seal_test +3 (seal_response bounded/unbounded, embedded-seal hash exclusion);
  api_test +3 (run emits seal, /api/seal/pubkey, /api/seal/verify).

Green (across base + a flask/reportlab venv): api 23 checks / 7 routes; pdf PASS; seal
15/15; stress/promote/ceiling/authority/twin/contract PASS; wedge 10/10; PR-1 29/29.
@sourcery-ai

sourcery-ai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Reviewer's Guide

Wires the CROSS-3 bounded-autonomy seal into the live preflight path so preflight responses include an optional detached seal, exposes endpoints to publish and verify the seal’s public key, ensures embedded seals don’t affect bundle integrity hashing, and surfaces seal metadata in the examiner PDF, with supporting tests.

Sequence diagram for bounded-autonomy seal in live preflight and offline verification

sequenceDiagram
    actor Client
    participant FlaskApp as FlaskApp
    participant Preflight as run_preflight
    participant Signer as Ed25519Signer
    participant Verifier as Ed25519Verifier

    title Bounded-autonomy seal in preflight run and offline verification

    Client->>FlaskApp: POST /api/preflight/run
    FlaskApp->>Preflight: run_preflight(intent, backend)
    Preflight-->>FlaskApp: bundle
    FlaskApp->>Signer: seal_response(bundle, _SIGNER, sealed_at_utc)
    Signer-->>FlaskApp: seal, _skipped
    FlaskApp-->>Client: bundle (includes optional seal)

    Client->>FlaskApp: GET /api/seal/pubkey
    FlaskApp-->>Client: {alg, key_id, public_key_b64}

    Client->>FlaskApp: POST /api/seal/verify {bundle, seal}
    FlaskApp->>Verifier: verify_seal(bundle, seal, _VERIFIER)
    Verifier-->>FlaskApp: SealVerdict(valid, gate, reason)
    FlaskApp-->>Client: {valid, gate, reason} (200 or 422)
Loading

File-Level Changes

Change Details Files
Emit a bounded-autonomy seal as part of the live preflight response using a process-wide Ed25519 signer.
  • Introduce a _load_signer helper that loads an Ed25519Signer from the AEGIS_SEAL_KEY environment variable or falls back to an ephemeral key with logging on failure.
  • Create module-level _SIGNER and _VERIFIER instances to provide a stable signing/verification context.
  • In the preflight_run handler, call seal_response with the computed bundle and current UTC timestamp and attach the returned seal (or None) under bundle['seal'] before returning the JSON response.
serve.py
Expose public-key publishing and offline seal verification HTTP endpoints.
  • Add GET /api/seal/pubkey to return the verifier’s algorithm, key ID, and base64-encoded public key bytes.
  • Add POST /api/seal/verify to accept a JSON body containing {bundle, seal}, validate types, verify the seal using verify_seal, and return validity, gate, and reason with a 200 or 422 status code.
  • Extend api_test checks to assert presence/absence of seals on preflight responses based on authority, that the pubkey endpoint responds correctly, and that the verify endpoint accepts a genuine bundle/seal pair, updating the route-count summary accordingly.
serve.py
tests/api_test.py
Ensure detached seals embedded in bundles do not alter the bundle integrity hash and provide a convenience API for live-path sealing.
  • Add seal_response to wrap seal_bundle, returning (receipt, None) on success or (None, reason) on SealError, simplifying live-path usage.
  • Update compute_sha256 to remove any top-level 'seal' field from the cloned bundle before hashing so the integrity hash remains stable when a detached seal is present.
  • Add seal tests to cover bounded/unbounded seal_response behavior and to assert that embedding a seal under bundle['seal'] does not break bundle verification while the seal itself remains verifiable.
core/seal/seal.py
evidence/bundler.py
tests/seal_test.py
core/seal/__init__.py
Render bounded-autonomy seal information in the examiner PDF when a seal is present.
  • Extend render_pdf to read bundle['seal'] if present and extract model, authority, and signature key metadata from the seal structure.
  • Add a "Bounded-autonomy seal" section to the PDF using a key-value table summarizing model provider/model/hash kind, authority bounds and whether the change is within the ceiling, and the signature algorithm/key ID.
  • Ensure existing PDFs remain unchanged when no seal is present by guarding the new section behind a truthy seal check.
evidence/pdf.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 found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="serve.py" line_range="48-57" />
<code_context>
 app.config["MAX_CONTENT_LENGTH"] = 2 * 1024 * 1024  # 2 MB


+def _load_signer() -> Ed25519Signer:
+    """Seal signing key. AEGIS_SEAL_KEY = 64 hex chars (an Ed25519 private seed) pins a
+    stable key whose receipts verify across restarts; otherwise an EPHEMERAL demo key is
+    used. Production swaps this for a YubiKey-PIV signer (same Signer protocol)."""
+    raw = (os.environ.get("AEGIS_SEAL_KEY") or "").strip()
+    if raw:
+        try:
+            return Ed25519Signer.from_private_bytes(bytes.fromhex(raw))
+        except Exception as exc:  # noqa: BLE001
+            print(f"[aegis.seal] AEGIS_SEAL_KEY invalid ({exc}); using an ephemeral key")
+    print("[aegis.seal] no AEGIS_SEAL_KEY set - EPHEMERAL demo signing key "
+          "(production uses a YubiKey-PIV key; receipts will not persist across restarts)")
+    return Ed25519Signer.generate()
+
+
</code_context>
<issue_to_address>
**suggestion:** Consider using structured logging instead of bare prints for key-loading diagnostics.

Using `print` for these messages makes them harder to manage in production (e.g., routing, filtering, log levels). Consider emitting them via the service’s logger (such as `logging.getLogger(__name__)`) so operators can configure levels and destinations consistently.

Suggested implementation:

```python
import logging

from aegis.core.orchestrator.pipeline import run_preflight, PreflightError
from aegis.core.backends.simulator import SimulatorBackend
from aegis.evidence.pdf import render_pdf
from aegis.core.seal import Ed25519Signer, Ed25519Verifier, seal_response, verify_seal

logger = logging.getLogger(__name__)

_UI = os.path.join(os.path.dirname(__file__), "ui", "preflight_screen.html")

app.config["MAX_CONTENT_LENGTH"] = 2 * 1024 * 1024  # 2 MB

```

```python
def _load_signer() -> Ed25519Signer:
    """Seal signing key. AEGIS_SEAL_KEY = 64 hex chars (an Ed25519 private seed) pins a
    stable key whose receipts verify across restarts; otherwise an EPHEMERAL demo key is
    used. Production swaps this for a YubiKey-PIV signer (same Signer protocol)."""
    raw = (os.environ.get("AEGIS_SEAL_KEY") or "").strip()
    if raw:
        try:
            return Ed25519Signer.from_private_bytes(bytes.fromhex(raw))
        except Exception as exc:  # noqa: BLE001
            logger.warning("AEGIS_SEAL_KEY invalid (%s); using an ephemeral key", exc)
    logger.warning(
        "No AEGIS_SEAL_KEY set - using EPHEMERAL demo signing key "
        "(production uses a YubiKey-PIV key; receipts will not persist across restarts)"
    )
    return Ed25519Signer.generate()

```

If your application has a centralized logging configuration (e.g., in the Flask app factory or a separate config module), ensure that it sets appropriate log levels and handlers so these `logger.warning(...)` messages are emitted to your desired sinks (stdout, files, structured log collectors, etc.).
</issue_to_address>

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.

Comment thread serve.py
Comment on lines +48 to +57
def _load_signer() -> Ed25519Signer:
"""Seal signing key. AEGIS_SEAL_KEY = 64 hex chars (an Ed25519 private seed) pins a
stable key whose receipts verify across restarts; otherwise an EPHEMERAL demo key is
used. Production swaps this for a YubiKey-PIV signer (same Signer protocol)."""
raw = (os.environ.get("AEGIS_SEAL_KEY") or "").strip()
if raw:
try:
return Ed25519Signer.from_private_bytes(bytes.fromhex(raw))
except Exception as exc: # noqa: BLE001
print(f"[aegis.seal] AEGIS_SEAL_KEY invalid ({exc}); using an ephemeral key")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Consider using structured logging instead of bare prints for key-loading diagnostics.

Using print for these messages makes them harder to manage in production (e.g., routing, filtering, log levels). Consider emitting them via the service’s logger (such as logging.getLogger(__name__)) so operators can configure levels and destinations consistently.

Suggested implementation:

import logging

from aegis.core.orchestrator.pipeline import run_preflight, PreflightError
from aegis.core.backends.simulator import SimulatorBackend
from aegis.evidence.pdf import render_pdf
from aegis.core.seal import Ed25519Signer, Ed25519Verifier, seal_response, verify_seal

logger = logging.getLogger(__name__)

_UI = os.path.join(os.path.dirname(__file__), "ui", "preflight_screen.html")

app.config["MAX_CONTENT_LENGTH"] = 2 * 1024 * 1024  # 2 MB
def _load_signer() -> Ed25519Signer:
    """Seal signing key. AEGIS_SEAL_KEY = 64 hex chars (an Ed25519 private seed) pins a
    stable key whose receipts verify across restarts; otherwise an EPHEMERAL demo key is
    used. Production swaps this for a YubiKey-PIV signer (same Signer protocol)."""
    raw = (os.environ.get("AEGIS_SEAL_KEY") or "").strip()
    if raw:
        try:
            return Ed25519Signer.from_private_bytes(bytes.fromhex(raw))
        except Exception as exc:  # noqa: BLE001
            logger.warning("AEGIS_SEAL_KEY invalid (%s); using an ephemeral key", exc)
    logger.warning(
        "No AEGIS_SEAL_KEY set - using EPHEMERAL demo signing key "
        "(production uses a YubiKey-PIV key; receipts will not persist across restarts)"
    )
    return Ed25519Signer.generate()

If your application has a centralized logging configuration (e.g., in the Flask app factory or a separate config module), ensure that it sets appropriate log levels and handlers so these logger.warning(...) messages are emitted to your desired sinks (stdout, files, structured log collectors, etc.).

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-seal-serve 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