Skip to main content

aitp_session_bundle/
lib.rs

1//! Session Trust Bundle (RFC-AITP-0010).
2//!
3//! In a multi-agent session of N participants, requiring O(N²) bilateral
4//! Mutual Handshakes is unscalable. The Session Trust Bundle is a signed
5//! artifact a coordinator constructs from N coordinator↔participant
6//! Mutual Handshakes (each producing a peer-issued TCT) and distributes
7//! to all participants, so that every agent-to-agent pair within the
8//! session has a verifiable trust artifact without a full mesh of
9//! handshakes.
10//!
11//! # Trust model
12//!
13//! A bundle provides **coordinator-attested membership**, not
14//! peer-to-peer identity binding. If A and B both appear in the same
15//! bundle, they know:
16//! 1. The coordinator authenticated each of them directly via a
17//!    bilateral handshake (the coordinator holds a peer-issued TCT for
18//!    each participant).
19//! 2. The coordinator signed both of those TCTs into the same bundle.
20//!
21//! Pairs that need direct peer-to-peer identity binding (rather than
22//! coordinator-attested membership) MUST run a separate bilateral
23//! Mutual Handshake.
24
25#![forbid(unsafe_code)]
26#![warn(missing_docs)]
27
28pub mod builder;
29pub mod error;
30pub mod types;
31pub mod verifier;
32pub mod wire;
33
34pub use builder::{SessionBundleBuilder, DEFAULT_BUNDLE_VERSION};
35pub use error::SessionBundleError;
36pub use types::{
37    ParticipantEntry, SessionBundleEnvelope, SessionTrustBundle, SESSION_BUNDLE_MEMBERS,
38};
39pub use verifier::{
40    verify_session_bundle, BundleOutcome, CoordinatorKeySource, VerifySessionBundleContext,
41};
42pub use wire::{parse_session_bundle_envelope, parse_session_bundle_wire};
43
44/// Manifest `extensions` key under which a coordinator advertises the
45/// concrete HTTPS URL of its session-bundle endpoint (RFC-AITP-0010
46/// §4.3.1; registered in the spec's `registries/extension-keys.md`).
47///
48/// The §4.3.1 paths (`/aitp/session/bundle` and
49/// `/aitp/session/bundle/{session_id}`) are RECOMMENDED, not reserved:
50/// participants MUST discover the actual endpoint via this extension
51/// key and MUST NOT probe the default paths when the key is absent.
52pub const RFC_AITP_0010_BUNDLE_URI: &str = "rfc-aitp-0010.bundle_uri";
53
54/// Advertise a coordinator's session-bundle endpoint in the Manifest it
55/// is about to sign (RFC-AITP-0010 §4.3.1): sets
56/// `extensions["rfc-aitp-0010.bundle_uri"]` to `bundle_uri`, the
57/// **concrete** URL the coordinator exposes (e.g.
58/// `https://coord.example/aitp/session/bundle`). Because the key lives in
59/// the Manifest's `extensions`, the Manifest signature covers it.
60///
61/// `bundle_uri` MUST be HTTPS outside localhost development
62/// (`registries/extension-keys.md`); [`bundle_uri_from_manifest`] ignores
63/// any other value.
64pub fn advertise_bundle_uri<'a>(
65    builder: aitp_manifest::ManifestBuilder<'a>,
66    bundle_uri: &url::Url,
67) -> aitp_manifest::ManifestBuilder<'a> {
68    builder.extension(
69        RFC_AITP_0010_BUNDLE_URI,
70        serde_json::Value::String(bundle_uri.to_string()),
71    )
72}
73
74/// Read the advertised session-bundle endpoint from a coordinator's
75/// (verified) Manifest. `None` when the key is absent, not a string, not
76/// a URL, or neither HTTPS nor `http://localhost` / `http://127.0.0.1`.
77///
78/// Per RFC-AITP-0010 §4.3.1, `None` means HTTPS is unavailable for that
79/// coordinator: callers MUST use a non-HTTPS transport and MUST NOT probe
80/// the default `/aitp/session/bundle` path.
81pub fn bundle_uri_from_manifest(manifest: &aitp_manifest::Manifest) -> Option<url::Url> {
82    manifest
83        .extensions
84        .as_ref()?
85        .get(RFC_AITP_0010_BUNDLE_URI)
86        .and_then(|v| v.as_str())
87        .and_then(|s| url::Url::parse(s).ok())
88        .filter(|u| {
89            u.scheme() == "https"
90                || (u.scheme() == "http"
91                    && matches!(u.host_str(), Some("localhost") | Some("127.0.0.1")))
92        })
93}