# Lazaret schemas This document is a mutable coordination schema for Lazaret records and protocol data shapes. The [specification](specification.md) 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: `<>` identifies a record that may cross a configuration, durable-storage, or protocol codec boundary, while `<>` identifies an in-memory authority or plan that must not implement Serde serialization or deserialization. Shape stereotypes such as `<>` and `<>` 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 | `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 | | 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. ```mermaid classDiagram class TrustedHostConfigurationV1 { <> schema_id profiles } class ResolvedDomainConfiguration { <> selected_profile domain_id configuration_fingerprint effective_network_policy ephemeral_environment confined_authorities } class SandboxLayoutV1 { <> protected_destinations } class ConfigurationFingerprintRecordV1 { <> selected_profile storage_root runtime_root image isolate_gitignored persistent_dirs environment_references volumes effective_allow_prefixes effective_deny_prefixes escalation_enabled auto_approval_rules } class EnvironmentReference { <> sandbox_name host_name } class ResolvedVolume { <> canonical_host_source container_path kind access create } class ResolvedAutoApprovalRule { <> canonical_executable args allow_extra_args } class ConfinedPathPlan { <> 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 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: ```text "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: ```text "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 ```mermaid classDiagram class DomainStore { <> } class StoreMetadata { <> schema_id schema_version } class Domain { <> domain_id profile_name canonical_workspace } class Generation { <> domain_id generation_id configuration_fingerprint coordinator_id network_sidecar_id sandbox_id durable_decision } class Participant { <> generation_id role state ownership } class ParticipantState { <> <> Intent LeaseHeld Prepared Committed ReleaseIntent Clean } class ParticipantOwnership { <> <> PreparedLower StateAnchor GitRouting Network Helper Sandbox Publication } class Session { <> generation_id session_id cli_id session_anchor_id session_phase } class SessionTerminal { <> generation_id session_id state container_id } class SessionTerminalState { <> <> Intent Running ReleaseIntent Clean } class Mailbox { <> 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 ```mermaid classDiagram class PidfsProcessAuthorityV1 { <> boot_id pidfs_st_dev pidfs_st_ino } class PidfsProcessIdentityV1 { <> host_pid start_time_ticks } class HostProcessIdentity { <> identity } class PersistedHostProcessOwnership { <> <> Unassigned Staged Committed } class HostProcessOwnershipSlot { <> <> SandboxHostCoordinator SandboxRuntimeSupervisor SandboxAttachedRunsc StateAnchorDaemon GitRoutingDaemon NetworkRuntimeSupervisor HelperConnectivityHelper HelperRuntimeSupervisor SessionTerminalHostCoordinator SessionTerminalExecClient EscalationHostProcess } class ProcessOwnershipMessageVersion { <> <> V1 } class StagedProcessOwnership { <> version domain_id generation_id coordinator_id slot identity } class ProcessOwnershipCommitAck { <> 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 ```mermaid classDiagram class SessionTerminal { <> generation_id session_id state container_id } class PtyIdentity { <> master_fd device inode } class GuestProcessIdentity { <> 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 ```mermaid classDiagram class SessionTerminalEvent { <> <> } class TerminalInput { <> session_id bytes } class TerminalResize { <> session_id rows columns pixel_width pixel_height } class TerminalSignal { <> session_id signal } class TerminalSignalKind { <> <> Interrupt Quit Suspend } class SessionClose { <> session_id } class SessionTerminalMessage { <> <> } class TerminalOutput { <> session_id bytes } class SessionExit { <> session_id outcome exit_code } class SessionExitOutcome { <> <> Exited Terminated RuntimeLost } SessionTerminalEvent --> TerminalInput SessionTerminalEvent --> TerminalResize SessionTerminalEvent --> TerminalSignal SessionTerminalEvent --> SessionClose TerminalSignal --> TerminalSignalKind SessionTerminalMessage --> TerminalOutput SessionTerminalMessage --> SessionExit SessionExit --> SessionExitOutcome ``` `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 ```mermaid classDiagram class GenerationIdentity { <> agent_profile domain_id generation_id configuration_fingerprint coordinator_id sandbox_id } class TrustManifest { <> protocol_version generation_identity tls_ca_sha256 protocol_issuer_public_key issuer_signature } class TlsCredential { <> generation_identity role subject_id certificate_der certificate_sha256 } class ProtocolPublicationIdentity { <> protocol_version generation_identity tls_ca_certificate_der trust_manifest_sha256 coordinator_tls_certificate_sha256 } class ClientEnrollmentRequest { <> protocol_version generation_identity cli_id certificate_signing_request_der } class ClientEnrollmentResponse { <> protocol_version credential } class SessionRegistrationRequest { <> protocol_version session_binding } class SessionRegistrationResponse { <> protocol_version session_capability } class ControlRequest { <> <> Enroll Register } class ControlResponse { <> <> Enrolled Registered } class SandboxCredentialBundle { <> protocol_version generation_identity trust_manifest coordinator_protocol_credential sandbox_protocol_credential sandbox_private_key } class TlsRole { <> <> SessionCoordinator ClientCLI } class ProtocolCredential { <> protocol_version credential_id generation_identity role subject_id public_key issuer_signature } class ProtocolCredentialRole { <> <> SessionCoordinator AgentSandbox } class SessionBinding { <> generation_identity cli_id session_id session_anchor_public_key } class SessionRequestCapability { <> 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 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. ```mermaid classDiagram class SignedProtocolRecord { <> <> payload_bytes signature } class MailboxRequest { <> protocol_version session_binding request_id session_capability sandbox_credential_id command_argv justification } class MailboxCancellation { <> protocol_version session_binding request_id session_capability sandbox_credential_id } class MailboxResultAck { <> protocol_version session_binding request_id request_sha256 session_capability sandbox_credential_id highest_contiguous_sequence } class DeveloperDecision { <> protocol_version session_binding request_id request_sha256 decision approved_command_argv } class DeveloperDecisionKind { <> <> Approve Deny } class MailboxResult { <> protocol_version session_binding request_id request_sha256 coordinator_credential_id outcome exit_code chunk_count stdout_sha256 stderr_sha256 } class MailboxResultChunk { <> protocol_version session_binding request_id request_sha256 coordinator_credential_id sequence stream bytes } class ResultStream { <> <> Stdout Stderr } class EscalationOutcome { <> <> 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 ```mermaid classDiagram class ParticipantOwnership { <> <> } class NamespaceIdentity { <> device inode } class MountIdentity { <> mount_id device inode } class PreparedLowerOwnership { <> optional prepared_artifact_id optional source_identity optional lease_state optional path optional device optional inode } class StateAnchorOwnership { <> optional lower_artifact_id optional upper_path optional work_path optional merged_path daemon optional mount } class GitRoutingOwnership { <> optional mountpoint optional host_root optional isolated_root daemon optional mount } class NetworkOwnership { <> optional derived_policy_digest optional readiness_fingerprint optional readiness_evidence optional namespace runtime_supervisor } class HelperOwnership { <> optional network_sidecar_id connectivity_helper runtime_supervisor } class SandboxOwnership { <> optional container_id optional runtime_root optional bundle_digest host_coordinator runtime_supervisor attached_runsc } class PublicationOwnership { <> optional socket_path optional identity_path } ParticipantOwnership --> PreparedLowerOwnership 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 only its lease phase to `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 ```mermaid classDiagram class StorageCatalog { <> } class StoreMetadata { <> schema_id schema_version } class PreparedArtifact { <> prepared_artifact_id source_identity selected_manifest_digest policy_id id_map_digest path device inode state } class ArtifactState { <> <> Available Reclaiming } class ArtifactLease { <> prepared_artifact_id domain_id generation_id } class PrepareOperation { <> operation_id expected_artifact_id source_identity selected_manifest_digest policy_id id_map_digest stage_path stage_state device inode } class PrepareStageState { <> <> 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 ```mermaid classDiagram class DerivedNetworkPolicy { <> schema_id address_families default_actions prefix_rules resolvers } class AddressFamilyDisposition { <> family disposition } class DefaultAction { <> family action } class PrefixRule { <> family network action } class Resolver { <> family address } class AddressFamily { <> <> IPv4 IPv6 } class PolicyAction { <> <> 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 ```mermaid classDiagram class NetworkReadinessEvidence { <> domain_id generation_id network_sidecar_id readiness_fingerprint } class NetworkReadinessAttestation { <> schema_id domain_id generation_id configuration_fingerprint network_sidecar_id derived_policy_digest enforcement_namespace runtime_supervisor connectivity_helper observation } class NetworkObservation { <> policy_delivery helper_ready observed_routes routes_attested } class PolicyDelivery { <> <> SealedMemfd } class NetworkReadinessIdentity { <> schema_id domain_id generation_id configuration_fingerprint network_sidecar_id derived_policy_digest enforcement_namespace } class NamespaceIdentity { <> device inode } class NetworkReachabilityProbe { <> domain_id generation_id enforcement_namespace } class DeniedDestinationObservation { <> destination unreachable } class PermittedDestinationObservation { <> destination reachable } class DnsObservation { <> 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.