Skip to content

feat(sidecar): add http listen mode for direct attestation dial - #2

Merged
echobt merged 1 commit into
BaseIntelligence:mainfrom
alpha1122x:feat/sidecar-listen-mode
Jul 27, 2026
Merged

feat(sidecar): add http listen mode for direct attestation dial#2
echobt merged 1 commit into
BaseIntelligence:mainfrom
alpha1122x:feat/sidecar-listen-mode

Conversation

@alpha1122x

@alpha1122x alpha1122x commented Jul 27, 2026

Copy link
Copy Markdown

Summary

  • The attestation sidecar was pull-only (run / answer-once) and never bound a socket, so BASE had no way to reach it.
  • Adds an HTTP listen mode so BASE can dial a running Lium instance directly and poll it for signed attestations.
  • Standard library only — the image stays hermetic.

Changes

  • New src/prism_recipe/sidecar/listen.pyThreadingHTTPServer from http.server.
    • POST /v1/sidecar/attest — body {"nonce": "<str>", "phase": "start"|"interval"|"end"} (phase defaults to start). Returns the same signed wire payload as answer-once.
    • GET /healthz — cheap liveness, no measurement, no signing.
    • build_server(config, host, port) factory; host is explicit (no implicit 0.0.0.0 inside the library) so tests can bind port 0.
  • src/prism_recipe/sidecar/__main__.py — new serve subcommand (--host, default 0.0.0.0; --port, default 8787), identity resolution shared with run.
  • Dockerfile / Dockerfile.cudaEXPOSE 8787 so the port can be published as a Lium internal_ports entry.

Measurement and signing are reused from service.py / wire.py, not reimplemented.

Failure behaviour (fail-closed)

Status When
400 Malformed JSON, missing/blank nonce, invalid phase
404 Unknown path
405 Wrong method on a known path
413 Body larger than 64 KiB
500 Internal error — no stack trace, no secret material in the body

Test plan

  • uv run pytest tests/test_sidecar_listen.py -q — 12 cases (healthz, happy-path attest, malformed JSON, unknown path, wrong method, oversized body); server runs on a background thread bound to port 0 and is torn down by fixture.
  • uv run pytest -q — 145 passed, 5 skipped. The single failure test_gpu_train.py::test_run_gpu_short_train_cpu_fingerprint is torch_not_installed, environmental and pre-existing: that test imports only prism_recipe.gpu_train and has zero references to the sidecar.
  • uv run ruff check . — clean.
  • uv run ruff format --check on the three touched files — clean. (Repo-wide format --check reports 18 pre-existing unformatted files that this PR deliberately does not touch.)
  • Manual QA against a real server on an ephemeral port: curl /healthz -> 200 {"status":"ok"}; curl -X POST /v1/sidecar/attest -> 200 signed payload; malformed body -> 400 {"error":"malformed JSON"}. Process killed and port confirmed released.

CI

  • Waiting for the required Repro image check on this head SHA. The Dockerfile change (EXPOSE) is deterministic, so the dual no-cache build must still yield identical digests.

Notes

  • No dependency added; pyproject.toml and lockfiles untouched.
  • No change to the signing or measurement path, so the attestation wire format is unchanged and backward compatible.
  • Consumer side (BASE dialling this endpoint, digest triangle, continuous re-check) ships separately in the base repo.

Summary by CodeRabbit

  • New Features

    • Added sidecar listen mode for attestation requests.
    • Added health-check and attestation HTTP endpoints.
    • Added configurable host and port options for the sidecar service.
    • Exposed port 8787 in container images for sidecar communication.
  • Bug Fixes

    • Added validation and safe error responses for malformed, oversized, or invalid requests.
    • Prevented sensitive secret material from appearing in error responses.
  • Tests

    • Added coverage for health checks, attestation responses, validation errors, routing, signatures, and security safeguards.

The sidecar was pull-only (run / answer-once) and never bound a socket, so
BASE could not reach it. Add a stdlib ThreadingHTTPServer exposing
POST /v1/sidecar/attest and GET /healthz, reusing the existing measurement
and signing path rather than duplicating it.

Uses only the standard library so the image stays hermetic: no fastapi,
uvicorn or any new dependency. Requests are size-capped and malformed
input fails closed (400/404/405/413/500) without leaking stack traces or
secret material.

Ports are published for Lium via EXPOSE 8787 on both Dockerfiles.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an HTTP listen mode for sidecar attestation, a serve CLI command, port 8787 exposure in both Docker images, and tests covering successful requests, validation, routing, wire compatibility, size limits, and secret handling.

Changes

Sidecar listen mode

Layer / File(s) Summary
HTTP attestation listener
src/prism_recipe/sidecar/listen.py
Adds threaded HTTP serving for health and attestation endpoints, challenge validation, bounded request handling, JSON wire serialization, and error mapping.
CLI and configuration wiring
src/prism_recipe/sidecar/__main__.py
Adds the serve command with host and port options, connects configuration to serve_forever, and updates transport/configuration handling.
Container exposure and validation
Dockerfile, Dockerfile.cuda, tests/test_sidecar_listen.py
Exposes port 8787 and adds integration tests for listen-mode behavior, response compatibility, invalid requests, size limits, and secret non-disclosure.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HTTPServer
  participant AttestationSidecar
  Client->>HTTPServer: POST /v1/sidecar/attest with nonce and phase
  HTTPServer->>AttestationSidecar: answer_challenge(Challenge)
  AttestationSidecar-->>HTTPServer: ChallengeAnswer
  HTTPServer-->>Client: JSON attestation response
Loading

Possibly related PRs

Suggested reviewers: echobt

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding an HTTP listen mode for the sidecar to serve direct attestation requests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@echobt
echobt merged commit 4029e76 into BaseIntelligence:main Jul 27, 2026
1 of 2 checks passed

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/prism_recipe/sidecar/__main__.py (1)

17-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate wire-construction logic; reuse answer_to_wire.

_cmd_answer_once manually rebuilds the exact same dict shape (signed_attestation_to_wire + phase/baked_manifest_match/mismatched_paths) that listen.answer_to_wire now encapsulates. Since a test (test_wire_shape_matches_answer_once) explicitly guards that these two paths stay identical, keeping one source of truth avoids future drift.

♻️ Proposed refactor
-from prism_recipe.sidecar.listen import serve_forever
+from prism_recipe.sidecar.listen import answer_to_wire, serve_forever
-        wire = signed_attestation_to_wire(answer.signed)
-        wire["phase"] = answer.phase.value
-        wire["baked_manifest_match"] = answer.baked_manifest_match
-        wire["mismatched_paths"] = list(answer.mismatched_paths)
+        wire = answer_to_wire(answer)

Also applies to: 110-114

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/prism_recipe/sidecar/__main__.py` at line 17, Update _cmd_answer_once to
construct its response through listen.answer_to_wire instead of manually
combining signed_attestation_to_wire with phase, baked_manifest_match, and
mismatched_paths. Reuse this existing helper as the single source of truth while
preserving the current wire response shape and values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/prism_recipe/sidecar/__main__.py`:
- Line 176: Preserve an explicit zero retry budget in the retry configuration
branches by replacing the retry_budget fallback expressions with an explicit
None check, matching the neighboring interval_count, interval_min_s, and
interval_max_s handling. Update both retry_budget assignments around the
affected configuration logic so None still selects the default while 0 remains
fail-fast.

---

Nitpick comments:
In `@src/prism_recipe/sidecar/__main__.py`:
- Line 17: Update _cmd_answer_once to construct its response through
listen.answer_to_wire instead of manually combining signed_attestation_to_wire
with phase, baked_manifest_match, and mismatched_paths. Reuse this existing
helper as the single source of truth while preserving the current wire response
shape and values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e0e75a3c-aeda-4458-9c70-45d35b5a916e

📥 Commits

Reviewing files that changed from the base of the PR and between ae33e08 and 42e9bb5.

📒 Files selected for processing (5)
  • Dockerfile
  • Dockerfile.cuda
  • src/prism_recipe/sidecar/__main__.py
  • src/prism_recipe/sidecar/listen.py
  • tests/test_sidecar_listen.py

interval_min_s=args.interval_min_s if args.interval_min_s is not None else 0.0,
interval_max_s=args.interval_max_s if args.interval_max_s is not None else 0.0,
base_url=args.base_url,
retry_budget=retry_budget or 3,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

retry_budget=0 is silently overridden to the default.

Both branches use retry_budget or <default>, but interval_count/interval_min_s/interval_max_s on the same lines correctly use an explicit is not None check to preserve 0. Since 0 is a falsy-but-valid value for retry_budget (fail-fast, no retries), 0 or 3 (and 0 or env_cfg.retry_budget) silently discards an explicit --retry-budget 0.

🐛 Proposed fix
-            retry_budget=retry_budget or 3,
+            retry_budget=retry_budget if retry_budget is not None else 3,
-            retry_budget=retry_budget or env_cfg.retry_budget,
+            retry_budget=retry_budget if retry_budget is not None else env_cfg.retry_budget,

Also applies to: 191-191

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/prism_recipe/sidecar/__main__.py` at line 176, Preserve an explicit zero
retry budget in the retry configuration branches by replacing the retry_budget
fallback expressions with an explicit None check, matching the neighboring
interval_count, interval_min_s, and interval_max_s handling. Update both
retry_budget assignments around the affected configuration logic so None still
selects the default while 0 remains fail-fast.

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.

2 participants