lazaret/docs/schema.md

1286 lines
41 KiB
Markdown
Raw Normal View History

# 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:
`<<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 | `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 {
<<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
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 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
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 {
<<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-v2` with version `2`. Domain Store v1 metadata
dispatches only to legacy migration handling; it is never decoded as v2. The
Storage Catalog remains `lazaret-storage-catalog-v1` with version `1`.
### Host process identity and staged ownership
```mermaid
classDiagram
class PidfsProcessAuthorityV1 {
<<serialized>>
boot_id
pidfs_st_dev
pidfs_st_ino
}
class PidfsProcessIdentityV1 {
<<serialized>>
host_pid
start_time_ticks
}
class LegacyUnverifiedProcessIdentityV1 {
<<serialized>>
host_pid
start_time_ticks
}
class HostProcessIdentity {
<<serialized>>
<<union>>
PidfsV1
LegacyUnverifiedV1
}
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 : PidfsV1
HostProcessIdentity --> LegacyUnverifiedProcessIdentityV1 : LegacyUnverifiedV1
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` may contain a current pidfs identity or a
legacy-unverified v1 PID/start-only value. Only committed current pidfs
ownership is serviceable; the legacy variant always refuses positive process
action. There is no observation-based conversion from legacy data to
`PidfsProcessIdentityV1`.
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 {
<<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
```mermaid
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
}
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;
2026-07-12 03:08:09 +09:00
it does not preserve stdout and stderr as separate channels.
## Protocol cryptography
```mermaid
classDiagram
class GenerationIdentity {
<<serialized>>
2026-07-12 03:08:09 +09:00
agent_profile
domain_id
generation_id
configuration_fingerprint
coordinator_id
sandbox_id
}
class TrustManifest {
<<serialized>>
2026-07-12 03:08:09 +09:00
protocol_version
generation_identity
tls_ca_sha256
protocol_issuer_public_key
issuer_signature
}
class TlsCredential {
<<serialized>>
2026-07-12 03:08:09 +09:00
generation_identity
role
subject_id
certificate_der
certificate_sha256
}
class ProtocolPublicationIdentity {
<<serialized>>
2026-07-12 03:08:09 +09:00
protocol_version
generation_identity
tls_ca_certificate_der
trust_manifest_sha256
coordinator_tls_certificate_sha256
}
class SandboxCredentialBundle {
<<serialized>>
2026-07-12 03:08:09 +09:00
protocol_version
generation_identity
trust_manifest
coordinator_protocol_credential
sandbox_protocol_credential
sandbox_private_key
}
class TlsRole {
<<serialized>>
2026-07-12 03:08:09 +09:00
<<enumeration>>
SessionCoordinator
ClientCLI
}
class ProtocolCredential {
<<serialized>>
2026-07-12 03:08:09 +09:00
protocol_version
credential_id
generation_identity
role
subject_id
public_key
issuer_signature
}
class ProtocolCredentialRole {
<<serialized>>
2026-07-12 03:08:09 +09:00
<<enumeration>>
SessionCoordinator
AgentSandbox
}
class SessionBinding {
<<serialized>>
2026-07-12 03:08:09 +09:00
generation_identity
cli_id
session_id
session_anchor_public_key
}
class SessionRequestCapability {
<<serialized>>
2026-07-12 03:08:09 +09:00
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
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 {
<<serialized>>
2026-07-12 03:08:09 +09:00
<<union>>
payload_bytes
signature
}
class MailboxRequest {
<<serialized>>
2026-07-12 03:08:09 +09:00
protocol_version
session_binding
request_id
session_capability
sandbox_credential_id
command_argv
justification
}
class MailboxCancellation {
<<serialized>>
2026-07-12 03:08:09 +09:00
protocol_version
session_binding
request_id
session_capability
sandbox_credential_id
}
class MailboxResultAck {
<<serialized>>
2026-07-12 03:08:09 +09:00
protocol_version
session_binding
request_id
request_sha256
session_capability
sandbox_credential_id
highest_contiguous_sequence
}
class DeveloperDecision {
<<serialized>>
2026-07-12 03:08:09 +09:00
protocol_version
session_binding
request_id
request_sha256
decision
approved_command_argv
}
class DeveloperDecisionKind {
<<serialized>>
2026-07-12 03:08:09 +09:00
<<enumeration>>
Approve
Deny
}
class MailboxResult {
<<serialized>>
2026-07-12 03:08:09 +09:00
protocol_version
session_binding
request_id
request_sha256
coordinator_credential_id
outcome
exit_code
chunk_count
stdout_sha256
stderr_sha256
}
class MailboxResultChunk {
<<serialized>>
2026-07-12 03:08:09 +09:00
protocol_version
session_binding
request_id
request_sha256
coordinator_credential_id
sequence
stream
bytes
}
class ResultStream {
<<serialized>>
2026-07-12 03:08:09 +09:00
<<enumeration>>
Stdout
Stderr
}
class EscalationOutcome {
<<serialized>>
2026-07-12 03:08:09 +09:00
<<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.
### Participant ownership
```mermaid
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
}
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
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;
legacy ownership cannot use this rule. 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 {
<<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
stage_path
device
inode
}
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
```
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 {
<<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.
## Network readiness
```mermaid
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
fixes the domain-separation context only; it does not select the byte encoding of
the canonical readiness-identity fields.