feat(serve): emit the bounded-autonomy seal on the live preflight path#7
feat(serve): emit the bounded-autonomy seal on the live preflight path#7gesh75 wants to merge 1 commit into
Conversation
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.
Reviewer's GuideWires 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 verificationsequenceDiagram
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)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| 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") |
There was a problem hiding this comment.
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 MBdef _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.).
…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.
|
Superseded by the squashed release #9, now merged to main. This PR's changes are in main; closing for tidiness (branch kept for history). |
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.
What's here
POST /api/preflight/runembedsbundle["seal"]— the detached CROSS-3 receipt (ornullwhen the change exceeds the autonomy ceiling; seechange.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.AEGIS_SEAL_KEY(64-hex Ed25519 seed) pins a stable key; otherwise an ephemeral demo key (logged). Production swaps a YubiKey-PIV signer (sameSignerprotocol).compute_sha256excludes a top-levelseal— 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.Tests
api_test+3 (run emits seal ·/api/seal/pubkey·/api/seal/verify) → PASS, 23 checks / 7 routes.seal_test+3 (seal_responsebounded/unbounded · embedded-seal hash exclusion) → 15/15.pdf_testPASS (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.serve→POST /api/preflight/run {"intent":"add vlan 10 to leaf-1"}returns a bundle with aseal;GET /api/seal/pubkey+POST /api/seal/verifyconfirm 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:
Enhancements:
Tests: