You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CIRISAgent 2.9.6 (and any future federation peer) needs the deployed lens's full hybrid federation identity to address it: Ed25519 + X25519 (transport KEX classical) + ML-DSA-65 + ML-KEM-768 (transport KEX PQ). Today there's no published endpoint — consumers would have to compose engine.local_public_key_b64() + engine.local_pqc_public_key_b64() via the internal API + derive X25519 from Ed25519 themselves + have no path to the ml-kem-768 half at all (it lives in the Edge runtime).
This issue covers the lens-side endpoint that publishes the aggregate. It's blocked on two prereqs that have to land first; see "chain" below.
What
GET /api/v1/identity — public, no auth, rate-limited + cached, returns the lens's federation hybrid identity bundle.
Implementation is a one-liner once the prereqs ship:
# api/identity.py (new module) or a section of accord_api.py@router.get("/identity")asyncdefget_lens_identity() ->dict[str, Any]:
engine=persist_engine.get_engine()
ifengineisNone:
raiseHTTPException(503, "persist engine unavailable")
returnjson.loads(engine.local_identity_aggregate())
Cache TTL = the lifetime of the worker process (the identity is stable; only changes on Engine re-init with a rotated steward key). Rate limit at the existing scoring-API tier (60/min/IP).
Caddy routing: /api/v1/identity is on the public list alongside /api/v1/accord/* and /api/v1/scoring/*. CLAUDE.md's Caddy section gets the line.
Chain — prerequisites
CIRISPersist#198 — Engine.local_identity_aggregate() primitive lands the JSON bundle. Until this exists, the lens has no way to express the four keys in one call without composing two existing accessors + a derivation + a None.
CIRISLensCore#43.1 — the host-Engine PyCapsule handshake that makes LensClient (and by extension install_relay) see the wheel-built host Engine. Until this lands, attempting install_relay(edge) in the cohabitation/pip setup throws RuntimeError: no process Engine.
CIRISLens#18 §2 — wire init_edge_runtime(engine, ...) + install_relay(edge) into the lens-API startup hook. Until this lands, persist#198's ml_kem_768_pubkey_b64 stays None on the deployed lens (X25519 + Ed25519 + ML-DSA-65 are available unconditionally; ML-KEM-768 needs Edge).
When all three are in, this issue is a ~20-line lens-side patch:
Add api/identity.py (router) or extend accord_api.py
Wire into main.py's include_router
Update Caddyfile + CLAUDE.md "Public accord API" line to list /api/v1/identity
Test plan
Smoke against a docker-postgres harness with Edge wired: returns the 4-key bundle, all fields populated, identity_hash stable across requests.
Verify X25519 derivation against the live Ed25519: kogV3xbe0Lh4JiKhiAlCproQrAYJVBZ77DRklrQs9Bw= matches NaCl's crypto_sign_ed25519_pk_to_curve25519 over kdtTafS49EAuPD3z8/zhwsoMp5d4LYs/CCZPEowiFUs=.
Bridge probe round-trips: curl https://lens.ciris-services-1.ai/api/v1/identity returns the bundle agent 2.9.6 can address against.
CIRISVerify#47 (FederationHandshake X25519 + ML-KEM-768 hybrid KEX with HKDF-SHA256 binding — the wire shape the published identity has to be addressable against)
CIRISEdge#54 (closed; Edge consumed FederationHandshake on the transport side)
Why
CIRISAgent 2.9.6 (and any future federation peer) needs the deployed lens's full hybrid federation identity to address it: Ed25519 + X25519 (transport KEX classical) + ML-DSA-65 + ML-KEM-768 (transport KEX PQ). Today there's no published endpoint — consumers would have to compose
engine.local_public_key_b64()+engine.local_pqc_public_key_b64()via the internal API + derive X25519 from Ed25519 themselves + have no path to the ml-kem-768 half at all (it lives in the Edge runtime).This issue covers the lens-side endpoint that publishes the aggregate. It's blocked on two prereqs that have to land first; see "chain" below.
What
GET /api/v1/identity— public, no auth, rate-limited + cached, returns the lens's federation hybrid identity bundle.{ "steward_key_id": "lens-steward", "steward_pqc_key_id": "lens-steward-mldsa", "ed25519_pubkey_b64": "kdtTafS49EAuPD3z8/zhwsoMp5d4LYs/CCZPEowiFUs=", "x25519_pubkey_b64": "kogV3xbe0Lh4JiKhiAlCproQrAYJVBZ77DRklrQs9Bw=", "ml_dsa_65_pubkey_b64": "GF2uj2PO2/LmUwM...(1952 b64)", "ml_kem_768_pubkey_b64": "...(1184 b64)" , "did_key": "did:key:z6Mk...", "identity_hash": "sha256:...", "evaluated_at_unix_ms": 1750000000000 }Implementation is a one-liner once the prereqs ship:
Cache TTL = the lifetime of the worker process (the identity is stable; only changes on Engine re-init with a rotated steward key). Rate limit at the existing scoring-API tier (60/min/IP).
Caddy routing:
/api/v1/identityis on the public list alongside/api/v1/accord/*and/api/v1/scoring/*. CLAUDE.md's Caddy section gets the line.Chain — prerequisites
Engine.local_identity_aggregate()primitive lands the JSON bundle. Until this exists, the lens has no way to express the four keys in one call without composing two existing accessors + a derivation + aNone.LensClient(and by extensioninstall_relay) see the wheel-built host Engine. Until this lands, attemptinginstall_relay(edge)in the cohabitation/pip setup throwsRuntimeError: no process Engine.init_edge_runtime(engine, ...)+install_relay(edge)into the lens-API startup hook. Until this lands, persist#198'sml_kem_768_pubkey_b64staysNoneon the deployed lens (X25519 + Ed25519 + ML-DSA-65 are available unconditionally; ML-KEM-768 needs Edge).When all three are in, this issue is a ~20-line lens-side patch:
api/identity.py(router) or extendaccord_api.pymain.py'sinclude_router/api/v1/identityTest plan
ml_kem_768_pubkey_b64: null.kogV3xbe0Lh4JiKhiAlCproQrAYJVBZ77DRklrQs9Bw=matches NaCl'scrypto_sign_ed25519_pk_to_curve25519overkdtTafS49EAuPD3z8/zhwsoMp5d4LYs/CCZPEowiFUs=.curl https://lens.ciris-services-1.ai/api/v1/identityreturns the bundle agent 2.9.6 can address against.References