45 KiB
Lazaret schemas
This document is a mutable coordination schema for Lazaret records and protocol data shapes. The specification defines their authoritative physical use, while the TLA+ models define authoritative lifecycle and ordering above the implementation abstraction. The diagrams coordinate logical fields, variants, relationships, and serialization boundaries across implementation work; they do not select a programming-language representation or override the specification or models.
Every class carries exactly one serialization-category stereotype:
<<serialized>> identifies a record that may cross a configuration,
durable-storage, or protocol codec boundary, while <<runtime>> identifies an
in-memory authority or plan that must not implement Serde serialization or
deserialization. Shape stereotypes such as <<enumeration>> and <<union>> are
orthogonal and may coexist.
Trusted host configuration
The trusted-host configuration dispatch identifier is exactly
lazaret-trusted-host-config-v1. The v1 TOML document has the following closed
shape; every table recursively rejects unknown fields, duplicate keys, duplicate
tables, field aliases, and unknown enumeration values.
| Table | Field | Presence and value |
|---|---|---|
| root | schema |
required; exact dispatch identifier |
| root | storage |
optional; defaults to an empty table |
| root | network |
optional; defaults to an empty table |
| root | profiles |
required; at least one dynamically named profile |
storage |
data_root |
optional host path; defaults beneath the XDG data home |
network |
default_allow |
optional list of IPv4 prefixes; defaults empty |
network |
default_deny |
optional list of IPv4 prefixes; defaults empty |
| profile | image |
required nonempty OCI image reference |
| profile | isolate_gitignored |
optional Boolean; defaults true |
| profile | persistent_dirs |
optional list of sandbox paths; defaults empty |
| profile | allow_egress |
optional list of IPv4 prefixes; defaults empty |
| profile | deny_egress |
optional list of IPv4 prefixes; defaults empty |
| profile | readiness |
required at load for the selected profile; closed denied, permitted, and DNS target table |
| profile | env |
optional map from sandbox environment name to env: reference; defaults empty |
| profile | volumes |
optional list of volume tables; defaults empty |
| profile | escalation |
optional table; defaults to disabled with no rules |
readiness |
denied |
one to 32 unique { address = IPv4, port = nonzero } targets denied by effective policy |
readiness |
permitted |
one to 32 unique { address = IPv4, port = nonzero } targets allowed by effective policy |
readiness |
dns |
one to 32 unique { resolver = IPv4, query = strict DNS name } targets |
| volume | host |
required host path |
| volume | container |
required sandbox path |
| volume | kind |
required: file or directory |
| volume | access |
optional: read-only or read-write; defaults read-write |
| volume | create |
optional Boolean; defaults false |
escalation |
enabled |
optional Boolean; defaults false |
escalation |
auto_approve |
optional list of auto-approval rules; defaults empty |
| auto-approval rule | executable |
required host executable path |
| auto-approval rule | args |
optional ordered list of arguments; defaults empty |
| auto-approval rule | allow_extra_args |
optional Boolean; defaults false |
Profile names are ASCII and match
[A-Za-z0-9][A-Za-z0-9._-]{0,63}, excluding . and ... The caller selects
one profile by its exact case-sensitive name; the document has no default
profile and declaration order never selects one. All configuration strings are
UTF-8 and NUL-free. Environment map keys and the names following env: match
[A-Za-z_][A-Za-z0-9_]*. An environment value is a reference only; literal
values and other reference schemes are invalid.
Host path declarations are absolute or begin with exactly ~/; only a volume
host may instead begin with exactly {profile_dir}/. A declaration rejects an
empty suffix, an empty component, repeated or trailing separators, . or ..,
and every other expansion, variable, glob, or shell form. In particular,
~//etc and {profile_dir}//etc are invalid. Sandbox paths are normalized
absolute UTF-8 paths with the same component restrictions; the root path is
invalid.
Persistent paths are pairwise distinct and neither equal nor ancestral in
either direction. Volume container paths are pairwise distinct and
non-ancestral. Each volume container path is a strict descendant of exactly one
persistent path; equality with that path is invalid. Resolution requires an
internal SandboxLayoutV1, whose closed protected-destination set contains
/workspace and every control, credential, mailbox, proc, sys, dev, runtime,
and other product-owned sandbox mount destination selected by the production
layout. Persistent and volume paths must not intersect any protected
destination in either ancestry direction. Configuration semantics do not assign
destinations other than the specification-fixed /workspace; the production
sandbox-layout constructor owns that complete set and cannot construct a layout
that omits /workspace. A volume source has the declared kind. create=true is
valid only for a directory source anchored strictly beneath {profile_dir}; a
file source is never created. Symlinks, magic links, mount crossings, sockets,
devices, FIFOs, and other special files cannot satisfy a source.
The v1 network language is IPv4-only. Each prefix is parsed, has host bits cleared, and is deduplicated. Effective allow prefixes are the union of global and selected-profile allows. Effective deny prefixes are the union of global, selected-profile, and the three RFC 1918 prefixes. An exact normalized prefix present in both sets is invalid; overlaps at different prefix lengths are valid and use longest-prefix routing. The Agent Sandbox disables IPv6 for v1.
An auto-approval rule is valid only while its profile escalation table is
enabled. Its executable resolves to a canonical regular executable outside the
Agent Workspace, every configured volume source, the profile directory, and
every Lazaret data, runtime, Domain, and control tree. At load and before every
execution, the object must have exactly one hard link, be owned by root or the
invoking user, grant no group or other write permission, and be executable by
the invoking user. Rule arguments are UTF-8, NUL-free strings.
allow_extra_args=false matches only the exact ordered argument list.
allow_extra_args=true requires at least one configured argument and matches
that nonempty ordered prefix followed by zero or more arguments. Exact duplicate
normalized rules are invalid.
MailboxRequest.command_argv is nonempty and every element is UTF-8 and
NUL-free. Element zero is an absolute path and must be byte-for-byte equal to
the rule's canonical executable path; elements one onward are the arguments
matched against args. Lazaret performs no PATH lookup. The structured v1
request language has no command-string or shell-expansion form; such an
unsupported input is CommandRequiresShell and cannot auto-approve. The host
launcher never inserts or interprets a shell. An explicitly configured exact
vector such as /bin/sh, -c, and a fixed command string remains a direct
vector and can match only that exact rule. A configured script remains eligible:
executing the same opened script object uses ordinary kernel shebang handling
without turning its arguments into a Lazaret-interpreted shell command.
classDiagram
class TrustedHostConfigurationV1 {
<<serialized>>
schema_id
profiles
}
class ResolvedDomainConfiguration {
<<runtime>>
selected_profile
domain_id
configuration_fingerprint
effective_network_policy
ephemeral_environment
confined_authorities
}
class SandboxLayoutV1 {
<<runtime>>
protected_destinations
}
class ConfigurationFingerprintRecordV1 {
<<serialized>>
selected_profile
storage_root
runtime_root
image
isolate_gitignored
persistent_dirs
environment_references
volumes
effective_allow_prefixes
effective_deny_prefixes
network_readiness
escalation_enabled
auto_approval_rules
}
class EnvironmentReference {
<<serialized>>
sandbox_name
host_name
}
class ResolvedVolume {
<<serialized>>
canonical_host_source
container_path
kind
access
create
}
class ResolvedAutoApprovalRule {
<<serialized>>
canonical_executable
args
allow_extra_args
}
class ResolvedNetworkReadinessV1 {
<<serialized>>
denied
permitted
dns
}
class ConfinedPathPlan {
<<runtime>>
retained_ancestor
ancestor_device
ancestor_inode
ancestor_mount_id
relative_suffix
canonical_label
}
TrustedHostConfigurationV1 --> ResolvedDomainConfiguration : resolves selected profile
SandboxLayoutV1 --> ResolvedDomainConfiguration : constrains sandbox destinations
ResolvedDomainConfiguration --> ConfigurationFingerprintRecordV1
ConfigurationFingerprintRecordV1 --> EnvironmentReference
ConfigurationFingerprintRecordV1 --> ResolvedVolume
ConfigurationFingerprintRecordV1 --> ResolvedAutoApprovalRule
ConfigurationFingerprintRecordV1 --> ResolvedNetworkReadinessV1
ResolvedDomainConfiguration --> ConfinedPathPlan
ResolvedDomainConfiguration is one move-owned, non-serializable value. It is
the only successful output of loading a selected profile and owns all retained
path descriptors, normalized selected-profile data, confined use plans, and
resolved environment values. Resolved environment values have no field in
ConfigurationFingerprintRecordV1; they are never serialized, hashed, logged,
or persisted. The parsed source and nonselected profiles do not become runtime
configuration authority.
SandboxLayoutV1 is an internal, non-serializable product value with no public
unchecked constructor. Configuration resolution cannot succeed without it. Its
production constructor is owned by sandbox-layout construction and supplies the
complete normalized protected-destination set; configuration loading neither
guesses unnamed destinations nor permits a caller to remove /workspace. Every
member is a normalized, absolute, non-root sandbox path using the sandbox-path
grammar above; the finite set is nonempty and contains no duplicate member.
ConfinedPathPlan owns a retained ancestor directory descriptor and exposes to
consumers only its typed device, inode, and mount identity plus a normalized
relative suffix. canonical_label is diagnostic or fingerprint input, not open
authority. Consumers reopen relative to the retained descriptor and revalidate
the intended type and identity constraints. They do not receive an ambient
absolute path with which to repeat authorization.
Domain and configuration digests
DomainIdV1 is the 32-byte SHA-256 digest of the following preimage:
"lazaret-domain-id-v1\0"
|| bytes(selected_profile_utf8)
|| bytes(canonical_workspace_path)
ConfigurationFingerprintV1 is the 32-byte SHA-256 digest of the following
canonical record:
"lazaret-configuration-fingerprint-v1\0"
|| bytes(selected_profile_utf8)
|| bytes(canonical_storage_root)
|| bytes(canonical_runtime_root)
|| bytes(image_utf8)
|| boolean(isolate_gitignored)
|| list(persistent_dirs)
|| list(environment_reference_records)
|| list(volume_records)
|| list(effective_allow_prefixes)
|| list(effective_deny_prefixes)
|| boolean(escalation_enabled)
|| list(auto_approval_rule_records)
The Domain-ID separator is the exact 20 ASCII bytes
lazaret-domain-id-v1 followed by one NUL byte. Both variable fields use
bytes; concatenating their unframed bytes is not a valid v1 preimage.
bytes(x) is u32be(length(x)) || x; lengths and list counts must fit in
u32. A Boolean is one byte, 0x00 or 0x01. Each list is
u32be(item_count) followed by bytes(item_encoding) for every item. Every
listed collection is order-independent and sorts complete item encodings by
unsigned bytewise order before framing, except that arguments inside one
auto-approval record retain their declared order. Strings use UTF-8 bytes and
canonical host paths use raw Linux pathname bytes.
An environment-reference record is bytes(sandbox_name) || bytes(host_name).
A persistent-path item is bytes(path), and an argument item is bytes(arg).
A volume record is bytes(canonical_host_source) || bytes(container_path) || kind || access || boolean(create), where file/directory are 0x00/0x01 and
read-only/read-write are 0x00/0x01. An IPv4 prefix is its four network-order
address bytes followed by its one-byte prefix length. An auto-approval record is
bytes(canonical_executable) || list_in_declared_order(args) || boolean(allow_extra_args). A declared-order argument list uses the same list
framing without sorting.
Source comments, TOML declaration order, defaults versus their explicit equal values, nonselected profiles, configuration source pathname, host environment values, retained descriptor numbers, filesystem inode metadata, and executable contents are excluded. Every resolved selected-profile field above is included. Neither digest is an authentication credential.
Domain Store
classDiagram
class DomainStore {
<<serialized>>
}
class StoreMetadata {
<<serialized>>
schema_id
schema_version
}
class Domain {
<<serialized>>
domain_id
profile_name
canonical_workspace
}
class Generation {
<<serialized>>
domain_id
generation_id
configuration_fingerprint
coordinator_id
network_sidecar_id
sandbox_id
durable_decision
}
class Participant {
<<serialized>>
generation_id
role
state
ownership
}
class ParticipantState {
<<serialized>>
<<enumeration>>
Intent
LeaseHeld
Prepared
Committed
ReleaseIntent
Clean
}
class ParticipantOwnership {
<<serialized>>
<<union>>
PreparedLower
StateAnchor
GitRouting
Network
Helper
Sandbox
Publication
}
class Session {
<<serialized>>
generation_id
session_id
cli_id
session_anchor_id
session_phase
}
class SessionTerminal {
<<serialized>>
generation_id
session_id
state
container_id
}
class SessionTerminalState {
<<serialized>>
<<enumeration>>
Intent
Running
ReleaseIntent
Clean
}
class Mailbox {
<<serialized>>
generation_id
identity_path
inbox_path
outbox_path
state
}
DomainStore "1" *-- "1" StoreMetadata
DomainStore "1" *-- "1" Domain
Domain "1" --> "0..*" Generation
Generation "1" --> "0..*" Participant
Participant --> ParticipantState
Participant --> "0..1" ParticipantOwnership
Generation "1" --> "0..*" Session
Session "1" --> "0..1" SessionTerminal
SessionTerminal --> SessionTerminalState
Generation "1" --> "0..1" Mailbox
Mailbox --> ParticipantState
durable_decision and session_phase contain values defined by the application
protocol model. This schema records them without redefining either state machine.
Each store carries its own schema identity and version. The current Domain Store
identity is lazaret-domain-store-v1 with version 1. Unknown, unversioned,
older, and newer metadata refuses before any row is interpreted. The
Storage Catalog remains lazaret-storage-catalog-v1 with version 1.
Host process identity and staged ownership
classDiagram
class PidfsProcessAuthorityV1 {
<<serialized>>
boot_id
pidfs_st_dev
pidfs_st_ino
}
class PidfsProcessIdentityV1 {
<<serialized>>
host_pid
start_time_ticks
}
class HostProcessIdentity {
<<serialized>>
identity
}
class PersistedHostProcessOwnership {
<<serialized>>
<<union>>
Unassigned
Staged
Committed
}
class HostProcessOwnershipSlot {
<<serialized>>
<<enumeration>>
SandboxHostCoordinator
SandboxRuntimeSupervisor
SandboxAttachedRunsc
StateAnchorDaemon
GitRoutingDaemon
NetworkRuntimeSupervisor
HelperConnectivityHelper
HelperRuntimeSupervisor
SessionTerminalHostCoordinator
SessionTerminalExecClient
EscalationHostProcess
}
class ProcessOwnershipMessageVersion {
<<serialized>>
<<enumeration>>
V1
}
class StagedProcessOwnership {
<<serialized>>
version
domain_id
generation_id
coordinator_id
slot
identity
}
class ProcessOwnershipCommitAck {
<<serialized>>
version
committed
}
PidfsProcessIdentityV1 --> PidfsProcessAuthorityV1 : authority
HostProcessIdentity --> PidfsProcessIdentityV1 : current authority
PersistedHostProcessOwnership --> PidfsProcessIdentityV1 : Staged
PersistedHostProcessOwnership --> HostProcessIdentity : Committed
StagedProcessOwnership --> ProcessOwnershipMessageVersion
StagedProcessOwnership --> HostProcessOwnershipSlot
StagedProcessOwnership --> PidfsProcessIdentityV1
ProcessOwnershipCommitAck --> ProcessOwnershipMessageVersion
ProcessOwnershipCommitAck --> StagedProcessOwnership : committed
boot_id is the canonical 16-byte UUID value from the current Linux boot ID;
pidfs_st_dev and pidfs_st_ino are distinct unsigned 64-bit pidfs types. Only
their typed PidfsProcessAuthorityV1 tuple is positive durable authority.
host_pid is an unsigned 32-bit host-PID-namespace locator and
start_time_ticks is unsigned 64-bit negative evidence. Equality of the latter
two fields cannot produce or upgrade authority.
Every persisted host-process edge uses PersistedHostProcessOwnership.
Unassigned records that no process ownership has been acquired. Staged
contains only a current pidfs identity. It is positive authority for exact
same-handle cleanup and PidfsRecoveryMatch, including bootstrap recovery, but
cannot satisfy readiness, publication, dependent-resource creation, or
adoption. Committed contains the current pidfs identity and is the only
serviceable process-ownership phase.
Participant Intent and ReleaseIntent may retain any process-slot phase.
Every active participant state and Session Terminal Running requires current
committed ownership in every role-required process slot. Clean requires every
process slot to be Unassigned.
StagedProcessOwnership binds one acquisition to its Domain, generation,
generation-scoped Session Coordinator ID, exact ownership slot, and complete
pidfs identity. ProcessOwnershipCommitAck.committed echoes that exact staged
value. The receiver accepts one matching v1 acknowledgment only while that
exact launch barrier is pending on the authenticated generation channel. A
mismatched, duplicated after release, unknown-version, lost, or unauthenticated
acknowledgment cannot release a barrier. These shapes define correlation and
the durable boundary; they do not select the authentication or transport codec.
Session terminal ownership
classDiagram
class SessionTerminal {
<<serialized>>
generation_id
session_id
state
container_id
}
class PtyIdentity {
<<serialized>>
master_fd
device
inode
}
class GuestProcessIdentity {
<<serialized>>
leader_pid
leader_start_time
process_group_id
}
SessionTerminal --> PersistedHostProcessOwnership : host_coordinator
SessionTerminal --> PtyIdentity : pty_master
SessionTerminal --> PersistedHostProcessOwnership : exec_client
SessionTerminal --> GuestProcessIdentity : guest_process
Host process ownership uses the phase-aware pidfs dispatch above. Guest
process identity uses the leader PID and start time observed inside the exact
generation-bound runsc container; its process-group ID is relative to that
container's root PID namespace and never converts to host process authority.
The PTY identity is exact only in combination with its Host Coordinator process
identity. SessionTerminalState is a physical recovery state below the
application protocol model; it does not redefine session_phase.
SessionTerminal=Intent permits partial acquisition: container_id,
pty_master, and guest_process are optional, while both process slots use the
three-phase ownership union. Running requires all three non-process identities
and current committed HC and exec-client ownership. ReleaseIntent retains all
recorded evidence, including staged current pidfs authority. Clean requires
all three optional identities absent and both process slots unassigned. A bare
Intent may become this explicit cleared snapshot only after runtime absence is
proven.
Session terminal protocol
classDiagram
class SessionTerminalEvent {
<<serialized>>
<<union>>
}
class TerminalInput {
<<serialized>>
session_id
bytes
}
class TerminalResize {
<<serialized>>
session_id
rows
columns
pixel_width
pixel_height
}
class TerminalSignal {
<<serialized>>
session_id
signal
}
class TerminalSignalKind {
<<serialized>>
<<enumeration>>
Interrupt
Quit
Suspend
}
class SessionClose {
<<serialized>>
session_id
}
class SessionTerminalMessage {
<<serialized>>
<<union>>
}
class TerminalOutput {
<<serialized>>
session_id
bytes
}
class SessionExit {
<<serialized>>
session_id
outcome
exit_code
}
class SessionExitOutcome {
<<serialized>>
<<enumeration>>
Exited
Terminated
RuntimeLost
}
class NonInteractiveSessionRequest {
<<serialized>>
protocol_version
session_id
argv
}
class NonInteractiveOutput {
<<serialized>>
session_id
bytes
}
class NonInteractiveSessionMessage {
<<serialized>>
<<union>>
Stdout
Stderr
Exit
}
SessionTerminalEvent --> TerminalInput
SessionTerminalEvent --> TerminalResize
SessionTerminalEvent --> TerminalSignal
SessionTerminalEvent --> SessionClose
TerminalSignal --> TerminalSignalKind
SessionTerminalMessage --> TerminalOutput
SessionTerminalMessage --> SessionExit
SessionExit --> SessionExitOutcome
NonInteractiveSessionMessage --> NonInteractiveOutput : Stdout / Stderr
NonInteractiveSessionMessage --> SessionExit : Exit
exit_code is present only for Exited and is the status collected from the
non-detached runsc exec client. Interactive PTY output is one merged byte stream;
it does not preserve stdout and stderr as separate channels.
Protocol cryptography
classDiagram
class GenerationIdentity {
<<serialized>>
agent_profile
domain_id
generation_id
configuration_fingerprint
coordinator_id
sandbox_id
}
class TrustManifest {
<<serialized>>
protocol_version
generation_identity
tls_ca_sha256
protocol_issuer_public_key
issuer_signature
}
class TlsCredential {
<<serialized>>
generation_identity
role
subject_id
certificate_der
certificate_sha256
}
class ProtocolPublicationIdentity {
<<serialized>>
protocol_version
generation_identity
tls_ca_certificate_der
trust_manifest_sha256
coordinator_tls_certificate_sha256
}
class ClientEnrollmentRequest {
<<serialized>>
protocol_version
generation_identity
cli_id
certificate_signing_request_der
}
class ClientEnrollmentResponse {
<<serialized>>
protocol_version
credential
}
class SessionRegistrationRequest {
<<serialized>>
protocol_version
session_binding
}
class SessionRegistrationResponse {
<<serialized>>
protocol_version
session_capability
}
class ControlRequest {
<<serialized>>
<<union>>
Enroll
Register
Execute
}
class ControlResponse {
<<serialized>>
<<union>>
Enrolled
Registered
}
class SandboxCredentialBundle {
<<serialized>>
protocol_version
generation_identity
trust_manifest
coordinator_protocol_credential
sandbox_protocol_credential
sandbox_private_key
}
class TlsRole {
<<serialized>>
<<enumeration>>
SessionCoordinator
ClientCLI
}
class ProtocolCredential {
<<serialized>>
protocol_version
credential_id
generation_identity
role
subject_id
public_key
issuer_signature
}
class ProtocolCredentialRole {
<<serialized>>
<<enumeration>>
SessionCoordinator
AgentSandbox
}
class SessionBinding {
<<serialized>>
generation_identity
cli_id
session_id
session_anchor_public_key
}
class SessionRequestCapability {
<<serialized>>
protocol_version
capability_id
session_binding
coordinator_credential_id
sandbox_credential_id
coordinator_signature
}
TrustManifest --> GenerationIdentity
TlsCredential --> TlsRole
TlsCredential --> GenerationIdentity
ProtocolPublicationIdentity --> GenerationIdentity
ProtocolPublicationIdentity --> TrustManifest
ProtocolPublicationIdentity --> TlsCredential : coordinator TLS leaf
ClientEnrollmentRequest --> GenerationIdentity
ClientEnrollmentResponse --> TlsCredential
SessionRegistrationRequest --> SessionBinding
SessionRegistrationResponse --> SessionRequestCapability
ControlRequest --> ClientEnrollmentRequest : Enroll
ControlRequest --> SessionRegistrationRequest : Register
ControlRequest --> NonInteractiveSessionRequest : Execute
ControlResponse --> ClientEnrollmentResponse : Enrolled
ControlResponse --> SessionRegistrationResponse : Registered
SandboxCredentialBundle --> GenerationIdentity
SandboxCredentialBundle --> TrustManifest
SandboxCredentialBundle --> ProtocolCredential : coordinator verifier
SandboxCredentialBundle --> ProtocolCredential : sandbox signer
ProtocolCredential --> GenerationIdentity
ProtocolCredential --> ProtocolCredentialRole
SessionBinding --> GenerationIdentity
SessionRequestCapability --> SessionBinding
SessionRequestCapability --> ProtocolCredential : coordinator signer
SessionRequestCapability --> ProtocolCredential : sandbox subject
Trust Manifests, credentials, capabilities, and private keys are ephemeral protocol material, not Domain Store records.
classDiagram
class SignedProtocolRecord {
<<serialized>>
<<union>>
payload_bytes
signature
}
class MailboxRequest {
<<serialized>>
protocol_version
session_binding
request_id
session_capability
sandbox_credential_id
command_argv
justification
}
class MailboxCancellation {
<<serialized>>
protocol_version
session_binding
request_id
session_capability
sandbox_credential_id
}
class MailboxResultAck {
<<serialized>>
protocol_version
session_binding
request_id
request_sha256
session_capability
sandbox_credential_id
highest_contiguous_sequence
}
class DeveloperDecision {
<<serialized>>
protocol_version
session_binding
request_id
request_sha256
decision
approved_command_argv
}
class DeveloperDecisionKind {
<<serialized>>
<<enumeration>>
Approve
Deny
}
class MailboxResult {
<<serialized>>
protocol_version
session_binding
request_id
request_sha256
coordinator_credential_id
outcome
exit_code
chunk_count
stdout_sha256
stderr_sha256
}
class MailboxResultChunk {
<<serialized>>
protocol_version
session_binding
request_id
request_sha256
coordinator_credential_id
sequence
stream
bytes
}
class ResultStream {
<<serialized>>
<<enumeration>>
Stdout
Stderr
}
class EscalationOutcome {
<<serialized>>
<<enumeration>>
Completed
Denied
TimedOut
Cancelled
Failed
}
SignedProtocolRecord --> MailboxRequest
SignedProtocolRecord --> MailboxCancellation
SignedProtocolRecord --> MailboxResultAck
SignedProtocolRecord --> DeveloperDecision
SignedProtocolRecord --> MailboxResult
SignedProtocolRecord --> MailboxResultChunk
MailboxRequest --> SessionBinding
MailboxRequest --> SessionRequestCapability
MailboxCancellation --> SessionBinding
MailboxCancellation --> SessionRequestCapability
MailboxResultAck --> SessionBinding
MailboxResultAck --> SessionRequestCapability
DeveloperDecision --> SessionBinding
DeveloperDecision --> DeveloperDecisionKind
MailboxResult --> SessionBinding
MailboxResult --> EscalationOutcome
MailboxResultChunk --> SessionBinding
MailboxResultChunk --> ResultStream
SessionCoordinator is the logical protocol identity served physically by the
Host Coordinator. It is not a distinct Host Coordinator principal. Physical
encoding, signing, size, and validation rules are defined by the specification.
For embedded signed claims, the field order shown above is the Postcard field
order. A protocol credential ID is SHA-256 over the minimally encoded claim
fields excluding credential_id and issuer_signature. A Session Request
Capability ID is SHA-256 over the minimally encoded claim fields excluding
capability_id and coordinator_signature. A Trust Manifest digest is SHA-256
over the complete minimally encoded manifest, including its issuer signature.
The manifest issuer signs its minimally encoded fields excluding
issuer_signature.
Every signature input is the ASCII lazaret/v1/ prefix plus the lower-kebab
record name, followed by the unsigned 64-bit big-endian payload length and the
exact payload bytes. Mailbox signatures use the bytes carried in
SignedProtocolRecord; verification occurs before decoding those payload bytes.
Result stream digests are SHA-256 over the ordered concatenation of bytes for
that stream.
Participant ownership
classDiagram
class ParticipantOwnership {
<<serialized>>
<<union>>
}
class NamespaceIdentity {
<<serialized>>
device
inode
}
class MountIdentity {
<<serialized>>
mount_id
device
inode
}
class PreparedLowerOwnership {
<<serialized>>
optional prepared_artifact_id
optional source_identity
optional lease_state
optional path
optional device
optional inode
optional released_state_anchor
}
class StateAnchorReleaseEvidence {
<<serialized>>
daemon
user_namespace
mount_namespace
mount
}
class StateAnchorOwnership {
<<serialized>>
optional lower_artifact_id
optional upper_path
optional work_path
optional merged_path
daemon
optional mount
}
class GitRoutingOwnership {
<<serialized>>
optional mountpoint
optional host_root
optional isolated_root
daemon
optional mount
}
class NetworkOwnership {
<<serialized>>
optional derived_policy_digest
optional readiness_fingerprint
optional readiness_evidence
optional namespace
runtime_supervisor
}
class HelperOwnership {
<<serialized>>
optional network_sidecar_id
connectivity_helper
runtime_supervisor
}
class SandboxOwnership {
<<serialized>>
optional container_id
optional runtime_root
optional bundle_digest
host_coordinator
runtime_supervisor
attached_runsc
}
class PublicationOwnership {
<<serialized>>
optional socket_path
optional identity_path
}
ParticipantOwnership --> PreparedLowerOwnership
PreparedLowerOwnership --> StateAnchorReleaseEvidence : released_state_anchor
StateAnchorReleaseEvidence --> PersistedHostProcessOwnership : daemon
StateAnchorReleaseEvidence --> NamespaceIdentity : user_namespace
StateAnchorReleaseEvidence --> NamespaceIdentity : mount_namespace
StateAnchorReleaseEvidence --> MountIdentity : mount
ParticipantOwnership --> StateAnchorOwnership
ParticipantOwnership --> GitRoutingOwnership
ParticipantOwnership --> NetworkOwnership
ParticipantOwnership --> HelperOwnership
ParticipantOwnership --> SandboxOwnership
ParticipantOwnership --> PublicationOwnership
StateAnchorOwnership --> PersistedHostProcessOwnership : daemon
StateAnchorOwnership --> MountIdentity : mount
GitRoutingOwnership --> PersistedHostProcessOwnership : daemon
GitRoutingOwnership --> MountIdentity : mount
NetworkOwnership --> NamespaceIdentity : namespace
NetworkOwnership --> PersistedHostProcessOwnership : runtime_supervisor
NetworkOwnership --> NetworkReadinessEvidence : readiness_evidence
HelperOwnership --> PersistedHostProcessOwnership : connectivity_helper
HelperOwnership --> PersistedHostProcessOwnership : runtime_supervisor
SandboxOwnership --> PersistedHostProcessOwnership : host_coordinator
SandboxOwnership --> PersistedHostProcessOwnership : runtime_supervisor
SandboxOwnership --> PersistedHostProcessOwnership : attached_runsc
Participant.ownership=None is the single encoding of a bare role-bound
Intent. Once a role snapshot exists it never returns to None. Intent admits
a non-empty partial matching snapshot. ReleaseIntent admits a non-empty
partial or complete matching snapshot and retains every exact acquired value.
Clean requires a matching explicit empty snapshot: all optional physical
fields are absent and all process slots are unassigned. This distinguishes
"nothing acquired yet" from "absence was proved and cleanup committed."
The only serviceable role/state pairs are:
| Role | Active state | Required physical evidence |
|---|---|---|
PreparedLower |
LeaseHeld |
artifact ID, source, lease_state=LeaseHeld, path, device, inode |
StateAnchor |
Prepared |
lower artifact, three paths, mount, current committed daemon |
GitRouting |
Prepared |
three paths, mount, current committed daemon |
Network |
Committed |
policy digest, fingerprint, readiness evidence, namespace, current committed RS |
Helper |
Committed |
sidecar ID, current committed helper and RS |
Sandbox |
Committed |
container, runtime root, bundle digest, current committed HC, RS, and runsc |
Publication |
Committed |
socket and identity paths |
Network readiness evidence must name the enclosing Participant generation and match its policy digest, fingerprint, namespace, and committed RS identity.
Intent acquisition is monotonic: optional fields move only absent-to-present,
and newly acquired child slots move only Unassigned to Staged to exact
matching current Committed. A current committed owner or supervisor may
enter a newly created dependent-role snapshot directly and is then immutable.
Recorded evidence cannot be replaced or cleared. A transition to ReleaseIntent
preserves all evidence. PreparedLower changes its lease phase to
ReleaseIntent and may atomically add the exact released State Anchor daemon,
user namespace, mount namespace, and mount evidence needed to resume physical
release. That release evidence is immutable within ReleaseIntent. ReleaseIntent
becomes Clean only after independent absence proof and atomically clears every
physical field. A bare Intent may become Clean only after the same sealed
runtime-absence check.
The direct existing-process edges are Network RS, Helper RS, Sandbox HC, and
Session Terminal HC. State Anchor daemon, Git-routing daemon, Helper process,
Sandbox RS, attached runsc, and Session Terminal exec client are acquired child
edges and require staging.
No bare Intent moves directly to an active state. PreparedLower must also move
from recorded lease_state=Intent to LeaseHeld; it cannot acquire the catalog
lease from an absent lease phase.
Storage Catalog
classDiagram
class StorageCatalog {
<<serialized>>
}
class StoreMetadata {
<<serialized>>
schema_id
schema_version
}
class PreparedArtifact {
<<serialized>>
prepared_artifact_id
source_identity
selected_manifest_digest
policy_id
id_map_digest
path
device
inode
state
}
class ArtifactState {
<<serialized>>
<<enumeration>>
Available
Reclaiming
}
class ArtifactLease {
<<serialized>>
prepared_artifact_id
domain_id
generation_id
}
class PrepareOperation {
<<serialized>>
operation_id
expected_artifact_id
source_identity
selected_manifest_digest
policy_id
id_map_digest
stage_path
stage_state
device
inode
}
class PrepareStageState {
<<serialized>>
<<enumeration>>
Intent
Owned
}
StorageCatalog "1" *-- "1" StoreMetadata
StorageCatalog "1" *-- "0..*" PreparedArtifact
StorageCatalog "1" *-- "0..*" ArtifactLease
StorageCatalog "1" *-- "0..*" PrepareOperation
PreparedArtifact --> ArtifactState
PreparedArtifact "1" --> "0..*" ArtifactLease
PrepareOperation "0..*" --> "0..1" PreparedArtifact : publishes
PrepareOperation --> PrepareStageState
A prepare operation is durably recorded with stage_state=Intent and absent
device/inode evidence before its stage is created. The exact same operation then
moves to Owned with both values present. Only an owned operation can publish or
authorize exact abandoned-stage recovery.
An artifact lease identifies one prepared artifact, Domain, and generation. Its Domain and generation fields correspond to a Domain Store generation; the stores do not form one transactional database.
ReleaseIntent is a physical recovery state. It does not redefine the modeled
lease lifecycle: the specification defines how Domain Store release intent,
physical unmount, and Storage Catalog lease removal refine model unmount and
release actions. After Host Coordinator loss, the exact Runtime Supervisor may
unmount under the retained Prepared ownership record, but the Artifact Lease
remains until bootstrap Host Coordinator recovery records release intent.
Derived network policy
classDiagram
class DerivedNetworkPolicy {
<<serialized>>
schema_id
address_families
default_actions
prefix_rules
resolvers
}
class AddressFamilyDisposition {
<<serialized>>
family
disposition
}
class DefaultAction {
<<serialized>>
family
action
}
class PrefixRule {
<<serialized>>
family
network
action
}
class Resolver {
<<serialized>>
family
address
}
class AddressFamily {
<<serialized>>
<<enumeration>>
IPv4
IPv6
}
class PolicyAction {
<<serialized>>
<<enumeration>>
Allow
Deny
}
DerivedNetworkPolicy "1" *-- "2" AddressFamilyDisposition
DerivedNetworkPolicy "1" *-- "1..2" DefaultAction
DerivedNetworkPolicy "1" *-- "0..*" PrefixRule
DerivedNetworkPolicy "1" *-- "1..*" Resolver
AddressFamilyDisposition --> AddressFamily
DefaultAction --> AddressFamily
DefaultAction --> PolicyAction
PrefixRule --> AddressFamily
PrefixRule --> PolicyAction
Resolver --> AddressFamily
The policy has exactly one disposition for each address family. Each enabled family has exactly one default action. Prefixes and addresses are canonical, and equal-prefix action conflicts are invalid. Policy identity is the SHA-256 digest of its canonical form with a schema-specific domain separator.
DerivedNetworkPolicy.schema_id is exactly
lazaret-derived-network-policy-v1. Its canonical form is the minimal Postcard
encoding of the complete document after address-family dispositions, default
actions, prefix rules, and resolvers are sorted respectively by family; family;
(family, address bytes, prefix length, action); and (family, address bytes).
Duplicate prefixes and resolvers are invalid rather than silently removed. The
policy-digest domain separator is the UTF-8 schema ID followed by exactly one
zero byte.
Network readiness
classDiagram
class NetworkReadinessEvidence {
<<serialized>>
domain_id
generation_id
network_sidecar_id
readiness_fingerprint
}
class NetworkReadinessAttestation {
<<serialized>>
schema_id
domain_id
generation_id
configuration_fingerprint
network_sidecar_id
derived_policy_digest
enforcement_namespace
runtime_supervisor
connectivity_helper
observation
}
class NetworkObservation {
<<serialized>>
policy_delivery
helper_ready
observed_routes
routes_attested
}
class PolicyDelivery {
<<serialized>>
<<enumeration>>
SealedMemfd
}
class NetworkReadinessIdentity {
<<serialized>>
schema_id
domain_id
generation_id
configuration_fingerprint
network_sidecar_id
derived_policy_digest
enforcement_namespace
}
class NamespaceIdentity {
<<serialized>>
device
inode
}
class NetworkReachabilityProbe {
<<serialized>>
domain_id
generation_id
enforcement_namespace
}
class DeniedDestinationObservation {
<<serialized>>
destination
unreachable
}
class PermittedDestinationObservation {
<<serialized>>
destination
reachable
}
class DnsObservation {
<<serialized>>
resolver
query_name
resolved_addresses
}
NetworkReadinessEvidence --> NetworkReadinessAttestation : accepts
NetworkReadinessEvidence --> NetworkReadinessIdentity : fingerprints
NetworkReadinessEvidence --> NetworkReachabilityProbe : includes
NetworkReadinessAttestation --> NamespaceIdentity : enforcement_namespace
NetworkReadinessAttestation --> PidfsProcessIdentityV1 : runtime_supervisor
NetworkReadinessAttestation --> PidfsProcessIdentityV1 : connectivity_helper
NetworkReadinessAttestation --> NetworkObservation
NetworkObservation --> PolicyDelivery
NetworkReadinessIdentity --> NamespaceIdentity : enforcement_namespace
NetworkReadinessAttestation ..> NetworkReadinessIdentity : projects
NetworkReachabilityProbe --> NamespaceIdentity : enforcement_namespace
NetworkReachabilityProbe "1" *-- "1..*" DeniedDestinationObservation
NetworkReachabilityProbe "1" *-- "1..*" PermittedDestinationObservation
NetworkReachabilityProbe "1" *-- "1..*" DnsObservation
The readiness fingerprint is the SHA-256 digest of the canonical readiness identity with a schema-specific domain separator. Process identities and network observations are attestation evidence but are not part of that fingerprint. The Host Coordinator binds the accepted attestation and generation-bound reachability probe into Network Readiness Evidence before publication.
NetworkReadinessAttestation.schema_id is exactly
lazaret-network-attestation-v1.
NetworkReadinessIdentity.schema_id is exactly
lazaret-network-readiness-identity-v1. They are independent version-dispatch
domains: the v1 attestation projects only to the v1 readiness identity, and any
other identifier or version is refused without fallback or cross-domain
interpretation.
The readiness-fingerprint domain separator is the UTF-8 bytes of
lazaret-network-readiness-identity-v1 followed by exactly one zero byte. This
prefixes the minimal Postcard encoding of the complete
NetworkReadinessIdentity document. The schema ID is therefore present in both
the domain-separation context and the encoded version-dispatched document.