Split record modules without changing wire behavior

This commit is contained in:
ms2k 2026-07-15 02:09:18 +09:00
commit 6301eeba3e
8 changed files with 5674 additions and 5591 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,201 @@
use super::{BTreeMap, Deserialize, HostPath, Serialize};
pub const DOMAIN_STORE_SCHEMA_ID: &str = "lazaret-domain-store-v2";
pub const LEGACY_DOMAIN_STORE_SCHEMA_ID: &str = "lazaret-domain-store-v1";
pub const STORAGE_CATALOG_SCHEMA_ID: &str = "lazaret-storage-catalog-v1";
pub const DOMAIN_STORE_SCHEMA_VERSION: u32 = 2;
pub const LEGACY_DOMAIN_STORE_SCHEMA_VERSION: u32 = 1;
pub const STORAGE_CATALOG_SCHEMA_VERSION: u32 = 1;
pub const TRUSTED_HOST_CONFIGURATION_SCHEMA_ID: &str = "lazaret-trusted-host-config-v1";
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct StorageConfigurationV1 {
#[serde(default)]
pub data_root: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NetworkConfigurationV1 {
#[serde(default)]
pub default_allow: Vec<String>,
#[serde(default)]
pub default_deny: Vec<String>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum VolumeKind {
File,
Directory,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum VolumeAccess {
ReadOnly,
#[default]
ReadWrite,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct VolumeConfigurationV1 {
pub host: String,
pub container: String,
pub kind: VolumeKind,
#[serde(default)]
pub access: VolumeAccess,
#[serde(default)]
pub create: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AutoApprovalRuleConfigurationV1 {
pub executable: String,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub allow_extra_args: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct EscalationConfigurationV1 {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub auto_approve: Vec<AutoApprovalRuleConfigurationV1>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProfileConfigurationV1 {
pub image: String,
#[serde(default = "default_isolate_gitignored")]
pub isolate_gitignored: bool,
#[serde(default)]
pub persistent_dirs: Vec<String>,
#[serde(default)]
pub allow_egress: Vec<String>,
#[serde(default)]
pub deny_egress: Vec<String>,
#[serde(default)]
pub env: BTreeMap<String, String>,
#[serde(default)]
pub volumes: Vec<VolumeConfigurationV1>,
#[serde(default)]
pub escalation: EscalationConfigurationV1,
}
const fn default_isolate_gitignored() -> bool {
true
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct TrustedHostConfigurationV1 {
#[serde(rename = "schema")]
schema_id: String,
storage: StorageConfigurationV1,
network: NetworkConfigurationV1,
profiles: BTreeMap<String, ProfileConfigurationV1>,
}
impl TrustedHostConfigurationV1 {
#[must_use]
pub fn schema_id(&self) -> &str {
&self.schema_id
}
#[must_use]
pub const fn storage(&self) -> &StorageConfigurationV1 {
&self.storage
}
#[must_use]
pub const fn network(&self) -> &NetworkConfigurationV1 {
&self.network
}
#[must_use]
pub const fn profiles(&self) -> &BTreeMap<String, ProfileConfigurationV1> {
&self.profiles
}
}
impl<'de> Deserialize<'de> for TrustedHostConfigurationV1 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Fields {
#[serde(rename = "schema")]
schema_id: String,
#[serde(default)]
storage: StorageConfigurationV1,
#[serde(default)]
network: NetworkConfigurationV1,
profiles: BTreeMap<String, ProfileConfigurationV1>,
}
let fields = Fields::deserialize(deserializer)?;
if fields.schema_id != TRUSTED_HOST_CONFIGURATION_SCHEMA_ID {
return Err(serde::de::Error::custom(
"unknown trusted-host configuration schema",
));
}
if fields.profiles.is_empty() {
return Err(serde::de::Error::custom(
"trusted-host configuration has no profiles",
));
}
Ok(Self {
schema_id: fields.schema_id,
storage: fields.storage,
network: fields.network,
profiles: fields.profiles,
})
}
}
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct EnvironmentReference {
pub sandbox_name: String,
pub host_name: String,
}
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct ResolvedVolume {
pub canonical_host_source: HostPath,
pub container_path: String,
pub kind: VolumeKind,
pub access: VolumeAccess,
pub create: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct ResolvedAutoApprovalRule {
pub canonical_executable: HostPath,
pub args: Vec<String>,
pub allow_extra_args: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ConfigurationFingerprintRecordV1 {
pub selected_profile: String,
pub storage_root: HostPath,
pub runtime_root: HostPath,
pub image: String,
pub isolate_gitignored: bool,
pub persistent_dirs: Vec<String>,
pub environment_references: Vec<EnvironmentReference>,
pub volumes: Vec<ResolvedVolume>,
pub effective_allow_prefixes: Vec<([u8; 4], u8)>,
pub effective_deny_prefixes: Vec<([u8; 4], u8)>,
pub escalation_enabled: bool,
pub auto_approval_rules: Vec<ResolvedAutoApprovalRule>,
}

View file

@ -0,0 +1,632 @@
use super::{
CapabilityId, CertificateDigest, ClientId, ConfigurationFingerprint, CoordinatorId,
Deserialize, DomainId, GenerationId, ProtocolCredentialId, RequestDigest, RequestId, SandboxId,
Serialize, SessionId, StreamDigest, TrustManifestDigest, fmt,
};
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ProtocolVersion {
V1,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RecordInvariantError {
CredentialBundleRoles,
ParticipantSnapshotState,
SessionTerminalSnapshotState,
}
impl fmt::Display for RecordInvariantError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CredentialBundleRoles => {
formatter.write_str("credential bundle roles are invalid")
}
Self::ParticipantSnapshotState => {
formatter.write_str("participant ownership snapshot is invalid for its role/state")
}
Self::SessionTerminalSnapshotState => {
formatter.write_str("Session Terminal evidence is invalid for its state")
}
}
}
}
impl std::error::Error for RecordInvariantError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RecordTransitionError {
RecordIdentityChanged,
StateRegression,
EvidenceChanged,
EvidenceLost,
ProcessStageSkipped,
PriorAcquisitionSnapshotRequired,
AbsenceProofRequired,
InvalidCleanTarget,
}
impl fmt::Display for RecordTransitionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RecordIdentityChanged => formatter.write_str("record identity changed"),
Self::StateRegression => formatter.write_str("lifecycle transition is invalid"),
Self::EvidenceChanged => formatter.write_str("recorded evidence changed"),
Self::EvidenceLost => formatter.write_str("recorded evidence was cleared"),
Self::ProcessStageSkipped => {
formatter.write_str("process ownership did not follow the staged barrier")
}
Self::PriorAcquisitionSnapshotRequired => {
formatter.write_str("active state requires a prior acquisition snapshot")
}
Self::AbsenceProofRequired => formatter.write_str("cleanup requires absence proof"),
Self::InvalidCleanTarget => formatter.write_str("Clean target is not fully cleared"),
}
}
}
impl std::error::Error for RecordTransitionError {}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct GenerationIdentity {
pub agent_profile: String,
pub domain_id: DomainId,
pub generation_id: GenerationId,
pub configuration_fingerprint: ConfigurationFingerprint,
pub coordinator_id: CoordinatorId,
pub sandbox_id: SandboxId,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TrustManifest {
pub protocol_version: ProtocolVersion,
pub generation_identity: GenerationIdentity,
pub tls_ca_sha256: CertificateDigest,
pub protocol_issuer_public_key: Vec<u8>,
pub issuer_signature: Vec<u8>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum TlsRole {
SessionCoordinator,
ClientCli,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum TlsSubjectId {
SessionCoordinator(CoordinatorId),
ClientCli(ClientId),
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct TlsCredential {
pub generation_identity: GenerationIdentity,
role: TlsRole,
subject_id: TlsSubjectId,
pub certificate_der: Vec<u8>,
pub certificate_sha256: CertificateDigest,
}
impl TlsCredential {
#[must_use]
pub fn session_coordinator(
generation_identity: GenerationIdentity,
subject_id: CoordinatorId,
certificate_der: Vec<u8>,
certificate_sha256: CertificateDigest,
) -> Self {
Self {
generation_identity,
role: TlsRole::SessionCoordinator,
subject_id: TlsSubjectId::SessionCoordinator(subject_id),
certificate_der,
certificate_sha256,
}
}
#[must_use]
pub fn client_cli(
generation_identity: GenerationIdentity,
subject_id: ClientId,
certificate_der: Vec<u8>,
certificate_sha256: CertificateDigest,
) -> Self {
Self {
generation_identity,
role: TlsRole::ClientCli,
subject_id: TlsSubjectId::ClientCli(subject_id),
certificate_der,
certificate_sha256,
}
}
#[must_use]
pub const fn role(&self) -> TlsRole {
self.role
}
#[must_use]
pub const fn subject_id(&self) -> TlsSubjectId {
self.subject_id
}
}
impl<'de> Deserialize<'de> for TlsCredential {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Fields {
generation_identity: GenerationIdentity,
role: TlsRole,
subject_id: TlsSubjectId,
certificate_der: Vec<u8>,
certificate_sha256: CertificateDigest,
}
let fields = Fields::deserialize(deserializer)?;
match (fields.role, fields.subject_id) {
(TlsRole::SessionCoordinator, TlsSubjectId::SessionCoordinator(subject_id)) => {
Ok(Self::session_coordinator(
fields.generation_identity,
subject_id,
fields.certificate_der,
fields.certificate_sha256,
))
}
(TlsRole::ClientCli, TlsSubjectId::ClientCli(subject_id)) => Ok(Self::client_cli(
fields.generation_identity,
subject_id,
fields.certificate_der,
fields.certificate_sha256,
)),
_ => Err(serde::de::Error::custom(
"TLS role does not match its subject identity",
)),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ProtocolPublicationIdentity {
pub protocol_version: ProtocolVersion,
pub generation_identity: GenerationIdentity,
pub tls_ca_certificate_der: Vec<u8>,
pub trust_manifest_sha256: TrustManifestDigest,
pub coordinator_tls_certificate_sha256: CertificateDigest,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ProtocolCredentialRole {
SessionCoordinator,
AgentSandbox,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ProtocolSubjectId {
SessionCoordinator(CoordinatorId),
AgentSandbox(SandboxId),
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ProtocolCredential {
pub protocol_version: ProtocolVersion,
pub credential_id: ProtocolCredentialId,
pub generation_identity: GenerationIdentity,
role: ProtocolCredentialRole,
subject_id: ProtocolSubjectId,
pub public_key: Vec<u8>,
pub issuer_signature: Vec<u8>,
}
impl ProtocolCredential {
#[must_use]
pub fn session_coordinator(
credential_id: ProtocolCredentialId,
generation_identity: GenerationIdentity,
subject_id: CoordinatorId,
public_key: Vec<u8>,
issuer_signature: Vec<u8>,
) -> Self {
Self {
protocol_version: ProtocolVersion::V1,
credential_id,
generation_identity,
role: ProtocolCredentialRole::SessionCoordinator,
subject_id: ProtocolSubjectId::SessionCoordinator(subject_id),
public_key,
issuer_signature,
}
}
#[must_use]
pub fn agent_sandbox(
credential_id: ProtocolCredentialId,
generation_identity: GenerationIdentity,
subject_id: SandboxId,
public_key: Vec<u8>,
issuer_signature: Vec<u8>,
) -> Self {
Self {
protocol_version: ProtocolVersion::V1,
credential_id,
generation_identity,
role: ProtocolCredentialRole::AgentSandbox,
subject_id: ProtocolSubjectId::AgentSandbox(subject_id),
public_key,
issuer_signature,
}
}
#[must_use]
pub const fn role(&self) -> ProtocolCredentialRole {
self.role
}
#[must_use]
pub const fn subject_id(&self) -> ProtocolSubjectId {
self.subject_id
}
}
impl<'de> Deserialize<'de> for ProtocolCredential {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Fields {
protocol_version: ProtocolVersion,
credential_id: ProtocolCredentialId,
generation_identity: GenerationIdentity,
role: ProtocolCredentialRole,
subject_id: ProtocolSubjectId,
public_key: Vec<u8>,
issuer_signature: Vec<u8>,
}
let fields = Fields::deserialize(deserializer)?;
match fields.protocol_version {
ProtocolVersion::V1 => {}
}
match (fields.role, fields.subject_id) {
(
ProtocolCredentialRole::SessionCoordinator,
ProtocolSubjectId::SessionCoordinator(subject_id),
) => Ok(Self::session_coordinator(
fields.credential_id,
fields.generation_identity,
subject_id,
fields.public_key,
fields.issuer_signature,
)),
(ProtocolCredentialRole::AgentSandbox, ProtocolSubjectId::AgentSandbox(subject_id)) => {
Ok(Self::agent_sandbox(
fields.credential_id,
fields.generation_identity,
subject_id,
fields.public_key,
fields.issuer_signature,
))
}
_ => Err(serde::de::Error::custom(
"protocol credential role does not match its subject identity",
)),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct SandboxCredentialBundle {
pub protocol_version: ProtocolVersion,
pub generation_identity: GenerationIdentity,
pub trust_manifest: TrustManifest,
coordinator_protocol_credential: ProtocolCredential,
sandbox_protocol_credential: ProtocolCredential,
pub sandbox_private_key: Vec<u8>,
}
impl SandboxCredentialBundle {
/// Constructs a bundle only when its two credentials have their required roles.
///
/// # Errors
///
/// Returns an error when either credential has the wrong role.
pub fn new(
generation_identity: GenerationIdentity,
trust_manifest: TrustManifest,
coordinator_protocol_credential: ProtocolCredential,
sandbox_protocol_credential: ProtocolCredential,
sandbox_private_key: Vec<u8>,
) -> Result<Self, RecordInvariantError> {
if coordinator_protocol_credential.role() != ProtocolCredentialRole::SessionCoordinator
|| sandbox_protocol_credential.role() != ProtocolCredentialRole::AgentSandbox
{
return Err(RecordInvariantError::CredentialBundleRoles);
}
Ok(Self {
protocol_version: ProtocolVersion::V1,
generation_identity,
trust_manifest,
coordinator_protocol_credential,
sandbox_protocol_credential,
sandbox_private_key,
})
}
#[must_use]
pub const fn coordinator_protocol_credential(&self) -> &ProtocolCredential {
&self.coordinator_protocol_credential
}
#[must_use]
pub const fn sandbox_protocol_credential(&self) -> &ProtocolCredential {
&self.sandbox_protocol_credential
}
}
impl<'de> Deserialize<'de> for SandboxCredentialBundle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Fields {
protocol_version: ProtocolVersion,
generation_identity: GenerationIdentity,
trust_manifest: TrustManifest,
coordinator_protocol_credential: ProtocolCredential,
sandbox_protocol_credential: ProtocolCredential,
sandbox_private_key: Vec<u8>,
}
let fields = Fields::deserialize(deserializer)?;
match fields.protocol_version {
ProtocolVersion::V1 => {}
}
Self::new(
fields.generation_identity,
fields.trust_manifest,
fields.coordinator_protocol_credential,
fields.sandbox_protocol_credential,
fields.sandbox_private_key,
)
.map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct SessionBinding {
pub generation_identity: GenerationIdentity,
pub cli_id: ClientId,
pub session_id: SessionId,
pub session_anchor_public_key: Vec<u8>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct SessionRequestCapability {
pub protocol_version: ProtocolVersion,
pub capability_id: CapabilityId,
pub session_binding: SessionBinding,
pub coordinator_credential_id: ProtocolCredentialId,
pub sandbox_credential_id: ProtocolCredentialId,
pub coordinator_signature: Vec<u8>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct SignedProtocolRecord {
pub payload_bytes: Vec<u8>,
pub signature: Vec<u8>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct MailboxRequest {
protocol_version: ProtocolVersion,
session_binding: SessionBinding,
request_id: RequestId,
session_capability: SessionRequestCapability,
sandbox_credential_id: ProtocolCredentialId,
command_argv: Vec<Vec<u8>>,
justification: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MailboxRequestError {
EmptyCommand,
InvalidArgument,
NonAbsoluteExecutable,
}
impl fmt::Display for MailboxRequestError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyCommand => formatter.write_str("command argument vector is empty"),
Self::InvalidArgument => formatter.write_str("command argument is not NUL-free UTF-8"),
Self::NonAbsoluteExecutable => {
formatter.write_str("command executable is not an absolute path")
}
}
}
}
impl std::error::Error for MailboxRequestError {}
impl MailboxRequest {
/// # Errors
/// The command must contain an absolute executable followed by NUL-free UTF-8 arguments.
pub fn new(
session_binding: SessionBinding,
request_id: RequestId,
session_capability: SessionRequestCapability,
sandbox_credential_id: ProtocolCredentialId,
command_argv: Vec<Vec<u8>>,
justification: String,
) -> Result<Self, MailboxRequestError> {
let Some(executable) = command_argv.first() else {
return Err(MailboxRequestError::EmptyCommand);
};
if command_argv
.iter()
.any(|argument| argument.contains(&0) || std::str::from_utf8(argument).is_err())
{
return Err(MailboxRequestError::InvalidArgument);
}
if !executable.starts_with(b"/") {
return Err(MailboxRequestError::NonAbsoluteExecutable);
}
Ok(Self {
protocol_version: ProtocolVersion::V1,
session_binding,
request_id,
session_capability,
sandbox_credential_id,
command_argv,
justification,
})
}
#[must_use]
pub const fn protocol_version(&self) -> ProtocolVersion {
self.protocol_version
}
#[must_use]
pub const fn session_binding(&self) -> &SessionBinding {
&self.session_binding
}
#[must_use]
pub const fn request_id(&self) -> RequestId {
self.request_id
}
#[must_use]
pub const fn session_capability(&self) -> &SessionRequestCapability {
&self.session_capability
}
#[must_use]
pub const fn sandbox_credential_id(&self) -> ProtocolCredentialId {
self.sandbox_credential_id
}
#[must_use]
pub fn command_argv(&self) -> &[Vec<u8>] {
&self.command_argv
}
#[must_use]
pub fn justification(&self) -> &str {
&self.justification
}
}
impl<'de> Deserialize<'de> for MailboxRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Fields {
protocol_version: ProtocolVersion,
session_binding: SessionBinding,
request_id: RequestId,
session_capability: SessionRequestCapability,
sandbox_credential_id: ProtocolCredentialId,
command_argv: Vec<Vec<u8>>,
justification: String,
}
let fields = Fields::deserialize(deserializer)?;
if fields.protocol_version != ProtocolVersion::V1 {
return Err(serde::de::Error::custom("unknown mailbox request version"));
}
Self::new(
fields.session_binding,
fields.request_id,
fields.session_capability,
fields.sandbox_credential_id,
fields.command_argv,
fields.justification,
)
.map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MailboxCancellation {
pub protocol_version: ProtocolVersion,
pub session_binding: SessionBinding,
pub request_id: RequestId,
pub session_capability: SessionRequestCapability,
pub sandbox_credential_id: ProtocolCredentialId,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MailboxResultAck {
pub protocol_version: ProtocolVersion,
pub session_binding: SessionBinding,
pub request_id: RequestId,
pub request_sha256: RequestDigest,
pub session_capability: SessionRequestCapability,
pub sandbox_credential_id: ProtocolCredentialId,
pub highest_contiguous_sequence: u64,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum DeveloperDecisionKind {
Approve,
Deny,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DeveloperDecision {
pub protocol_version: ProtocolVersion,
pub session_binding: SessionBinding,
pub request_id: RequestId,
pub request_sha256: RequestDigest,
pub decision: DeveloperDecisionKind,
pub approved_command_argv: Vec<Vec<u8>>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum EscalationOutcome {
Completed,
Denied,
TimedOut,
Cancelled,
Failed,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MailboxResult {
pub protocol_version: ProtocolVersion,
pub session_binding: SessionBinding,
pub request_id: RequestId,
pub request_sha256: RequestDigest,
pub coordinator_credential_id: ProtocolCredentialId,
pub outcome: EscalationOutcome,
pub exit_code: Option<i32>,
pub chunk_count: u64,
pub stdout_sha256: StreamDigest,
pub stderr_sha256: StreamDigest,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ResultStream {
Stdout,
Stderr,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct MailboxResultChunk {
pub protocol_version: ProtocolVersion,
pub session_binding: SessionBinding,
pub request_id: RequestId,
pub request_sha256: RequestDigest,
pub coordinator_credential_id: ProtocolCredentialId,
pub sequence: u64,
pub stream: ResultStream,
pub bytes: Vec<u8>,
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,373 @@
use super::{
ConfigurationFingerprint, DerivedPolicyDigest, Deserialize, DomainId, GenerationId,
NamespaceIdentity, NetworkSidecarId, PidfsProcessIdentityV1, ReadinessFingerprint, Serialize,
};
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum AddressFamily {
Ipv4,
Ipv6,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum AddressFamilyDispositionValue {
Enabled,
Disabled,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AddressFamilyDisposition {
pub family: AddressFamily,
pub disposition: AddressFamilyDispositionValue,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum IpAddress {
V4([u8; 4]),
V6([u8; 16]),
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct IpNetwork {
pub address: IpAddress,
pub prefix_length: u8,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum PolicyAction {
Allow,
Deny,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DefaultAction {
pub family: AddressFamily,
pub action: PolicyAction,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PrefixRule {
pub family: AddressFamily,
pub network: IpNetwork,
pub action: PolicyAction,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Resolver {
pub family: AddressFamily,
pub address: IpAddress,
}
pub const DERIVED_NETWORK_POLICY_SCHEMA_ID: &str = "lazaret-derived-network-policy-v1";
pub const NETWORK_READINESS_ATTESTATION_SCHEMA_ID: &str = "lazaret-network-attestation-v1";
pub const NETWORK_READINESS_IDENTITY_SCHEMA_ID: &str = "lazaret-network-readiness-identity-v1";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DerivedNetworkPolicyFields {
pub address_families: Vec<AddressFamilyDisposition>,
pub default_actions: Vec<DefaultAction>,
pub prefix_rules: Vec<PrefixRule>,
pub resolvers: Vec<Resolver>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct DerivedNetworkPolicy {
schema_id: String,
pub address_families: Vec<AddressFamilyDisposition>,
pub default_actions: Vec<DefaultAction>,
pub prefix_rules: Vec<PrefixRule>,
pub resolvers: Vec<Resolver>,
}
impl DerivedNetworkPolicy {
#[must_use]
pub fn v1(fields: DerivedNetworkPolicyFields) -> Self {
Self {
schema_id: DERIVED_NETWORK_POLICY_SCHEMA_ID.into(),
address_families: fields.address_families,
default_actions: fields.default_actions,
prefix_rules: fields.prefix_rules,
resolvers: fields.resolvers,
}
}
#[must_use]
pub fn schema_id(&self) -> &str {
&self.schema_id
}
}
impl<'de> Deserialize<'de> for DerivedNetworkPolicy {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Fields {
schema_id: String,
address_families: Vec<AddressFamilyDisposition>,
default_actions: Vec<DefaultAction>,
prefix_rules: Vec<PrefixRule>,
resolvers: Vec<Resolver>,
}
let fields = Fields::deserialize(deserializer)?;
if fields.schema_id != DERIVED_NETWORK_POLICY_SCHEMA_ID {
return Err(serde::de::Error::custom(
"unknown derived-network-policy schema identity",
));
}
Ok(Self::v1(DerivedNetworkPolicyFields {
address_families: fields.address_families,
default_actions: fields.default_actions,
prefix_rules: fields.prefix_rules,
resolvers: fields.resolvers,
}))
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum PolicyDelivery {
SealedMemfd,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct NetworkObservation {
pub policy_delivery: PolicyDelivery,
pub helper_ready: bool,
pub observed_routes: Vec<IpNetwork>,
pub routes_attested: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NetworkReadinessAttestationFields {
pub domain_id: DomainId,
pub generation_id: GenerationId,
pub configuration_fingerprint: ConfigurationFingerprint,
pub network_sidecar_id: NetworkSidecarId,
pub derived_policy_digest: DerivedPolicyDigest,
pub enforcement_namespace: NamespaceIdentity,
pub runtime_supervisor: PidfsProcessIdentityV1,
pub connectivity_helper: PidfsProcessIdentityV1,
pub observation: NetworkObservation,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct NetworkReadinessAttestation {
schema_id: String,
pub domain_id: DomainId,
pub generation_id: GenerationId,
pub configuration_fingerprint: ConfigurationFingerprint,
pub network_sidecar_id: NetworkSidecarId,
pub derived_policy_digest: DerivedPolicyDigest,
pub enforcement_namespace: NamespaceIdentity,
pub runtime_supervisor: PidfsProcessIdentityV1,
pub connectivity_helper: PidfsProcessIdentityV1,
pub observation: NetworkObservation,
}
impl NetworkReadinessAttestation {
#[must_use]
pub fn v1(fields: NetworkReadinessAttestationFields) -> Self {
Self {
schema_id: NETWORK_READINESS_ATTESTATION_SCHEMA_ID.into(),
domain_id: fields.domain_id,
generation_id: fields.generation_id,
configuration_fingerprint: fields.configuration_fingerprint,
network_sidecar_id: fields.network_sidecar_id,
derived_policy_digest: fields.derived_policy_digest,
enforcement_namespace: fields.enforcement_namespace,
runtime_supervisor: fields.runtime_supervisor,
connectivity_helper: fields.connectivity_helper,
observation: fields.observation,
}
}
#[must_use]
pub fn schema_id(&self) -> &str {
&self.schema_id
}
}
impl<'de> Deserialize<'de> for NetworkReadinessAttestation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Fields {
schema_id: String,
domain_id: DomainId,
generation_id: GenerationId,
configuration_fingerprint: ConfigurationFingerprint,
network_sidecar_id: NetworkSidecarId,
derived_policy_digest: DerivedPolicyDigest,
enforcement_namespace: NamespaceIdentity,
runtime_supervisor: PidfsProcessIdentityV1,
connectivity_helper: PidfsProcessIdentityV1,
observation: NetworkObservation,
}
let fields = Fields::deserialize(deserializer)?;
if fields.schema_id != NETWORK_READINESS_ATTESTATION_SCHEMA_ID {
return Err(serde::de::Error::custom(
"unknown network-readiness-attestation schema identity",
));
}
Ok(Self::v1(NetworkReadinessAttestationFields {
domain_id: fields.domain_id,
generation_id: fields.generation_id,
configuration_fingerprint: fields.configuration_fingerprint,
network_sidecar_id: fields.network_sidecar_id,
derived_policy_digest: fields.derived_policy_digest,
enforcement_namespace: fields.enforcement_namespace,
runtime_supervisor: fields.runtime_supervisor,
connectivity_helper: fields.connectivity_helper,
observation: fields.observation,
}))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NetworkReadinessIdentityFields {
pub domain_id: DomainId,
pub generation_id: GenerationId,
pub configuration_fingerprint: ConfigurationFingerprint,
pub network_sidecar_id: NetworkSidecarId,
pub derived_policy_digest: DerivedPolicyDigest,
pub enforcement_namespace: NamespaceIdentity,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct NetworkReadinessIdentity {
schema_id: String,
pub domain_id: DomainId,
pub generation_id: GenerationId,
pub configuration_fingerprint: ConfigurationFingerprint,
pub network_sidecar_id: NetworkSidecarId,
pub derived_policy_digest: DerivedPolicyDigest,
pub enforcement_namespace: NamespaceIdentity,
}
impl NetworkReadinessIdentity {
#[must_use]
pub fn v1(fields: NetworkReadinessIdentityFields) -> Self {
Self {
schema_id: NETWORK_READINESS_IDENTITY_SCHEMA_ID.into(),
domain_id: fields.domain_id,
generation_id: fields.generation_id,
configuration_fingerprint: fields.configuration_fingerprint,
network_sidecar_id: fields.network_sidecar_id,
derived_policy_digest: fields.derived_policy_digest,
enforcement_namespace: fields.enforcement_namespace,
}
}
#[must_use]
pub fn schema_id(&self) -> &str {
&self.schema_id
}
}
impl<'de> Deserialize<'de> for NetworkReadinessIdentity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Fields {
schema_id: String,
domain_id: DomainId,
generation_id: GenerationId,
configuration_fingerprint: ConfigurationFingerprint,
network_sidecar_id: NetworkSidecarId,
derived_policy_digest: DerivedPolicyDigest,
enforcement_namespace: NamespaceIdentity,
}
let fields = Fields::deserialize(deserializer)?;
if fields.schema_id != NETWORK_READINESS_IDENTITY_SCHEMA_ID {
return Err(serde::de::Error::custom(
"unknown network-readiness-identity schema identity",
));
}
Ok(Self::v1(NetworkReadinessIdentityFields {
domain_id: fields.domain_id,
generation_id: fields.generation_id,
configuration_fingerprint: fields.configuration_fingerprint,
network_sidecar_id: fields.network_sidecar_id,
derived_policy_digest: fields.derived_policy_digest,
enforcement_namespace: fields.enforcement_namespace,
}))
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DeniedDestinationObservation {
pub destination: IpAddress,
pub unreachable: bool,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PermittedDestinationObservation {
pub destination: IpAddress,
pub reachable: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DnsObservation {
pub resolver: IpAddress,
pub query_name: String,
pub resolved_addresses: Vec<IpAddress>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct NetworkReachabilityProbe {
pub domain_id: DomainId,
pub generation_id: GenerationId,
pub enforcement_namespace: NamespaceIdentity,
pub denied_destinations: Vec<DeniedDestinationObservation>,
pub permitted_destinations: Vec<PermittedDestinationObservation>,
pub dns_observations: Vec<DnsObservation>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct NetworkReadinessEvidence {
pub domain_id: DomainId,
pub generation_id: GenerationId,
pub network_sidecar_id: NetworkSidecarId,
pub readiness_fingerprint: ReadinessFingerprint,
pub attestation: NetworkReadinessAttestation,
pub readiness_identity: NetworkReadinessIdentity,
pub reachability_probe: NetworkReachabilityProbe,
}
impl NetworkReadinessEvidence {
pub(super) fn matches(
&self,
generation_id: GenerationId,
derived_policy_digest: DerivedPolicyDigest,
readiness_fingerprint: ReadinessFingerprint,
namespace: NamespaceIdentity,
runtime_supervisor: &PidfsProcessIdentityV1,
) -> bool {
self.generation_id == generation_id
&& self.readiness_fingerprint == readiness_fingerprint
&& self.domain_id == self.attestation.domain_id
&& self.domain_id == self.readiness_identity.domain_id
&& self.domain_id == self.reachability_probe.domain_id
&& self.generation_id == self.attestation.generation_id
&& self.generation_id == self.readiness_identity.generation_id
&& self.generation_id == self.reachability_probe.generation_id
&& self.network_sidecar_id == self.attestation.network_sidecar_id
&& self.network_sidecar_id == self.readiness_identity.network_sidecar_id
&& self.attestation.configuration_fingerprint
== self.readiness_identity.configuration_fingerprint
&& self.attestation.derived_policy_digest == derived_policy_digest
&& self.readiness_identity.derived_policy_digest == derived_policy_digest
&& self.attestation.enforcement_namespace == namespace
&& self.readiness_identity.enforcement_namespace == namespace
&& self.reachability_probe.enforcement_namespace == namespace
&& self.attestation.runtime_supervisor == *runtime_supervisor
}
}

View file

@ -0,0 +1,138 @@
use super::{
AddressFamily, AddressFamilyDisposition, ArtifactLease, ArtifactState,
ConfigurationFingerprintRecordV1, DefaultAction, DeniedDestinationObservation,
DerivedNetworkPolicy, DeveloperDecision, DeveloperDecisionKind, DnsObservation, Domain,
DomainStore, EnvironmentReference, EscalationOutcome, Generation, GenerationIdentity,
GitRoutingOwnership, GuestProcessIdentity, HelperOwnership, HostProcessIdentity,
HostProcessOwnershipSlot, LegacyUnverifiedProcessIdentityV1, Mailbox, MailboxCancellation,
MailboxRequest, MailboxResult, MailboxResultAck, MailboxResultChunk, MountIdentity,
NamespaceIdentity, NetworkObservation, NetworkOwnership, NetworkReachabilityProbe,
NetworkReadinessAttestation, NetworkReadinessEvidence, NetworkReadinessIdentity, Participant,
ParticipantOwnership, ParticipantState, PermittedDestinationObservation,
PersistedHostProcessOwnership, PidfsProcessAuthorityV1, PidfsProcessIdentityV1, PolicyAction,
PolicyDelivery, PrefixRule, PrepareOperation, PreparedArtifact, PreparedLowerOwnership,
ProcessOwnershipCommitAck, ProcessOwnershipMessageVersion, ProtocolCredential,
ProtocolCredentialRole, ProtocolPublicationIdentity, PtyIdentity, PublicationOwnership,
ResolvedAutoApprovalRule, ResolvedVolume, Resolver, ResultStream, SandboxCredentialBundle,
SandboxOwnership, Session, SessionBinding, SessionClose, SessionExit, SessionExitOutcome,
SessionRequestCapability, SessionTerminal, SessionTerminalEvent, SessionTerminalMessage,
SessionTerminalState, SignedProtocolRecord, StagedProcessOwnership, StateAnchorOwnership,
StorageCatalog, StoreMetadata, TerminalInput, TerminalOutput, TerminalResize, TerminalSignal,
TerminalSignalKind, TlsCredential, TlsRole, TrustManifest, TrustedHostConfigurationV1,
};
pub trait SchemaType {
const NAME: &'static str;
}
macro_rules! schema_types {
($($name:literal => $record:ty),+; $($declared:literal),* $(,)?) => {
$(
impl SchemaType for $record {
const NAME: &'static str = $name;
}
)+
pub const IMPLEMENTED_SERIALIZED_SCHEMA_TYPE_NAMES: &[&str] = &[$($name),+];
pub const DECLARED_SERIALIZED_SCHEMA_TYPE_NAMES: &[&str] = &[
$($name),+,
$($declared),*
];
};
}
schema_types!(
"StoreMetadata" => StoreMetadata,
"Domain" => Domain,
"Generation" => Generation,
"Participant" => Participant,
"ParticipantState" => ParticipantState,
"ParticipantOwnership" => ParticipantOwnership,
"Session" => Session,
"SessionTerminal" => SessionTerminal,
"SessionTerminalState" => SessionTerminalState,
"Mailbox" => Mailbox,
"PtyIdentity" => PtyIdentity,
"PidfsProcessAuthorityV1" => PidfsProcessAuthorityV1,
"PidfsProcessIdentityV1" => PidfsProcessIdentityV1,
"LegacyUnverifiedProcessIdentityV1" => LegacyUnverifiedProcessIdentityV1,
"HostProcessIdentity" => HostProcessIdentity,
"PersistedHostProcessOwnership" => PersistedHostProcessOwnership,
"HostProcessOwnershipSlot" => HostProcessOwnershipSlot,
"ProcessOwnershipMessageVersion" => ProcessOwnershipMessageVersion,
"StagedProcessOwnership" => StagedProcessOwnership,
"ProcessOwnershipCommitAck" => ProcessOwnershipCommitAck,
"GuestProcessIdentity" => GuestProcessIdentity,
"SessionTerminalEvent" => SessionTerminalEvent,
"TerminalInput" => TerminalInput,
"TerminalResize" => TerminalResize,
"TerminalSignal" => TerminalSignal,
"TerminalSignalKind" => TerminalSignalKind,
"SessionClose" => SessionClose,
"SessionTerminalMessage" => SessionTerminalMessage,
"TerminalOutput" => TerminalOutput,
"SessionExit" => SessionExit,
"SessionExitOutcome" => SessionExitOutcome,
"GenerationIdentity" => GenerationIdentity,
"TrustManifest" => TrustManifest,
"TlsCredential" => TlsCredential,
"ProtocolPublicationIdentity" => ProtocolPublicationIdentity,
"SandboxCredentialBundle" => SandboxCredentialBundle,
"TlsRole" => TlsRole,
"ProtocolCredential" => ProtocolCredential,
"ProtocolCredentialRole" => ProtocolCredentialRole,
"SessionBinding" => SessionBinding,
"SessionRequestCapability" => SessionRequestCapability,
"SignedProtocolRecord" => SignedProtocolRecord,
"MailboxRequest" => MailboxRequest,
"MailboxCancellation" => MailboxCancellation,
"MailboxResultAck" => MailboxResultAck,
"DeveloperDecision" => DeveloperDecision,
"DeveloperDecisionKind" => DeveloperDecisionKind,
"MailboxResult" => MailboxResult,
"MailboxResultChunk" => MailboxResultChunk,
"ResultStream" => ResultStream,
"EscalationOutcome" => EscalationOutcome,
"NamespaceIdentity" => NamespaceIdentity,
"MountIdentity" => MountIdentity,
"PreparedLowerOwnership" => PreparedLowerOwnership,
"StateAnchorOwnership" => StateAnchorOwnership,
"GitRoutingOwnership" => GitRoutingOwnership,
"NetworkOwnership" => NetworkOwnership,
"HelperOwnership" => HelperOwnership,
"SandboxOwnership" => SandboxOwnership,
"PublicationOwnership" => PublicationOwnership,
"PreparedArtifact" => PreparedArtifact,
"ArtifactState" => ArtifactState,
"ArtifactLease" => ArtifactLease,
"PrepareOperation" => PrepareOperation,
"AddressFamilyDisposition" => AddressFamilyDisposition,
"DefaultAction" => DefaultAction,
"PrefixRule" => PrefixRule,
"Resolver" => Resolver,
"AddressFamily" => AddressFamily,
"PolicyAction" => PolicyAction,
"DerivedNetworkPolicy" => DerivedNetworkPolicy,
"NetworkReadinessEvidence" => NetworkReadinessEvidence,
"NetworkReadinessAttestation" => NetworkReadinessAttestation,
"NetworkObservation" => NetworkObservation,
"PolicyDelivery" => PolicyDelivery,
"NetworkReadinessIdentity" => NetworkReadinessIdentity,
"NetworkReachabilityProbe" => NetworkReachabilityProbe,
"DeniedDestinationObservation" => DeniedDestinationObservation,
"PermittedDestinationObservation" => PermittedDestinationObservation,
"DnsObservation" => DnsObservation,
"TrustedHostConfigurationV1" => TrustedHostConfigurationV1,
"ConfigurationFingerprintRecordV1" => ConfigurationFingerprintRecordV1,
"EnvironmentReference" => EnvironmentReference,
"ResolvedVolume" => ResolvedVolume,
"ResolvedAutoApprovalRule" => ResolvedAutoApprovalRule,
"DomainStore" => DomainStore,
"StorageCatalog" => StorageCatalog;
);
pub const DECLARED_RUNTIME_SCHEMA_TYPE_NAMES: &[&str] = &[
"ResolvedDomainConfiguration",
"SandboxLayoutV1",
"ConfinedPathPlan",
];

View file

@ -0,0 +1,49 @@
use super::{
Deserialize, DeviceId, DomainId, GenerationId, HostPath, IdMapDigest, InodeId, PolicyId,
PrepareOperationId, PreparedArtifactId, SelectedManifestDigest, Serialize, SourceIdentity,
StorageCatalogMetadata,
};
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum ArtifactState {
Available,
Reclaiming,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PreparedArtifact {
pub prepared_artifact_id: PreparedArtifactId,
pub source_identity: SourceIdentity,
pub selected_manifest_digest: SelectedManifestDigest,
pub policy_id: PolicyId,
pub id_map_digest: IdMapDigest,
pub path: HostPath,
pub device: DeviceId,
pub inode: InodeId,
pub state: ArtifactState,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ArtifactLease {
pub prepared_artifact_id: PreparedArtifactId,
pub domain_id: DomainId,
pub generation_id: GenerationId,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PrepareOperation {
pub operation_id: PrepareOperationId,
pub expected_artifact_id: PreparedArtifactId,
pub source_identity: SourceIdentity,
pub stage_path: HostPath,
pub device: DeviceId,
pub inode: InodeId,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct StorageCatalog {
pub metadata: StorageCatalogMetadata,
pub prepared_artifacts: Vec<PreparedArtifact>,
pub artifact_leases: Vec<ArtifactLease>,
pub prepare_operations: Vec<PrepareOperation>,
}

View file

@ -0,0 +1,138 @@
use super::{Deserialize, NonZeroU16, Serialize, SessionId};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TerminalInput {
pub session_id: SessionId,
pub bytes: Vec<u8>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TerminalResize {
pub session_id: SessionId,
pub rows: NonZeroU16,
pub columns: NonZeroU16,
pub pixel_width: u16,
pub pixel_height: u16,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum TerminalSignalKind {
Interrupt,
Quit,
Suspend,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TerminalSignal {
pub session_id: SessionId,
pub signal: TerminalSignalKind,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct SessionClose {
pub session_id: SessionId,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum SessionTerminalEvent {
Input(TerminalInput),
Resize(TerminalResize),
Signal(TerminalSignal),
Close(SessionClose),
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TerminalOutput {
pub session_id: SessionId,
pub bytes: Vec<u8>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum SessionExitOutcome {
Exited,
Terminated,
RuntimeLost,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
pub struct SessionExit {
session_id: SessionId,
outcome: SessionExitOutcome,
exit_code: Option<i32>,
}
impl SessionExit {
#[must_use]
pub const fn exited(session_id: SessionId, exit_code: i32) -> Self {
Self {
session_id,
outcome: SessionExitOutcome::Exited,
exit_code: Some(exit_code),
}
}
#[must_use]
pub const fn terminated(session_id: SessionId) -> Self {
Self {
session_id,
outcome: SessionExitOutcome::Terminated,
exit_code: None,
}
}
#[must_use]
pub const fn runtime_lost(session_id: SessionId) -> Self {
Self {
session_id,
outcome: SessionExitOutcome::RuntimeLost,
exit_code: None,
}
}
#[must_use]
pub const fn session_id(self) -> SessionId {
self.session_id
}
#[must_use]
pub const fn outcome(self) -> SessionExitOutcome {
self.outcome
}
#[must_use]
pub const fn exit_code(self) -> Option<i32> {
self.exit_code
}
}
impl<'de> Deserialize<'de> for SessionExit {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct Fields {
session_id: SessionId,
outcome: SessionExitOutcome,
exit_code: Option<i32>,
}
let fields = Fields::deserialize(deserializer)?;
match (fields.outcome, fields.exit_code) {
(SessionExitOutcome::Exited, Some(exit_code)) => {
Ok(Self::exited(fields.session_id, exit_code))
}
(SessionExitOutcome::Terminated, None) => Ok(Self::terminated(fields.session_id)),
(SessionExitOutcome::RuntimeLost, None) => Ok(Self::runtime_lost(fields.session_id)),
_ => Err(serde::de::Error::custom(
"exit_code must be present exactly for Exited",
)),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum SessionTerminalMessage {
Output(TerminalOutput),
Exit(SessionExit),
}