Classify serialized and runtime schema records

This commit is contained in:
ms2k 2026-07-14 23:34:56 +09:00
commit f106a9f636

View file

@ -1,30 +1,300 @@
# Lazaret schemas
This document is the normative design schema for Lazaret records and protocol
data shapes. The
[specification](specification.md) defines their physical use, while the TLA+
models define lifecycle and ordering above the implementation abstraction. The
diagrams define logical fields, variants, and relationships; they do not select a
programming-language representation.
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
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
@ -35,6 +305,7 @@ classDiagram
}
class Participant {
<<serialized>>
generation_id
role
state
@ -42,6 +313,7 @@ classDiagram
}
class ParticipantState {
<<serialized>>
<<enumeration>>
Intent
LeaseHeld
@ -52,6 +324,7 @@ classDiagram
}
class ParticipantOwnership {
<<serialized>>
<<union>>
PreparedLower
StateAnchor
@ -63,6 +336,7 @@ classDiagram
}
class Session {
<<serialized>>
generation_id
session_id
cli_id
@ -71,6 +345,7 @@ classDiagram
}
class SessionTerminal {
<<serialized>>
generation_id
session_id
state
@ -78,6 +353,7 @@ classDiagram
}
class SessionTerminalState {
<<serialized>>
<<enumeration>>
Intent
Running
@ -86,6 +362,7 @@ classDiagram
}
class Mailbox {
<<serialized>>
generation_id
identity_path
inbox_path
@ -118,28 +395,33 @@ Storage Catalog remains `lazaret-storage-catalog-v1` with version `1`.
```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
@ -147,6 +429,7 @@ classDiagram
}
class HostProcessOwnershipSlot {
<<serialized>>
<<enumeration>>
SandboxHostCoordinator
SandboxRuntimeSupervisor
@ -162,11 +445,13 @@ classDiagram
}
class ProcessOwnershipMessageVersion {
<<serialized>>
<<enumeration>>
V1
}
class StagedProcessOwnership {
<<serialized>>
version
domain_id
generation_id
@ -176,6 +461,7 @@ classDiagram
}
class ProcessOwnershipCommitAck {
<<serialized>>
version
committed
}
@ -229,6 +515,7 @@ the durable boundary; they do not select the authentication or transport codec.
```mermaid
classDiagram
class SessionTerminal {
<<serialized>>
generation_id
session_id
state
@ -236,12 +523,14 @@ classDiagram
}
class PtyIdentity {
<<serialized>>
master_fd
device
inode
}
class GuestProcessIdentity {
<<serialized>>
leader_pid
leader_start_time
process_group_id
@ -275,15 +564,18 @@ proven.
```mermaid
classDiagram
class SessionTerminalEvent {
<<serialized>>
<<union>>
}
class TerminalInput {
<<serialized>>
session_id
bytes
}
class TerminalResize {
<<serialized>>
session_id
rows
columns
@ -292,11 +584,13 @@ classDiagram
}
class TerminalSignal {
<<serialized>>
session_id
signal
}
class TerminalSignalKind {
<<serialized>>
<<enumeration>>
Interrupt
Quit
@ -304,25 +598,30 @@ classDiagram
}
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
@ -348,6 +647,7 @@ it does not preserve stdout and stderr as separate channels.
```mermaid
classDiagram
class GenerationIdentity {
<<serialized>>
agent_profile
domain_id
generation_id
@ -357,6 +657,7 @@ classDiagram
}
class TrustManifest {
<<serialized>>
protocol_version
generation_identity
tls_ca_sha256
@ -365,6 +666,7 @@ classDiagram
}
class TlsCredential {
<<serialized>>
generation_identity
role
subject_id
@ -373,6 +675,7 @@ classDiagram
}
class ProtocolPublicationIdentity {
<<serialized>>
protocol_version
generation_identity
tls_ca_certificate_der
@ -381,6 +684,7 @@ classDiagram
}
class SandboxCredentialBundle {
<<serialized>>
protocol_version
generation_identity
trust_manifest
@ -390,12 +694,14 @@ classDiagram
}
class TlsRole {
<<serialized>>
<<enumeration>>
SessionCoordinator
ClientCLI
}
class ProtocolCredential {
<<serialized>>
protocol_version
credential_id
generation_identity
@ -406,12 +712,14 @@ classDiagram
}
class ProtocolCredentialRole {
<<serialized>>
<<enumeration>>
SessionCoordinator
AgentSandbox
}
class SessionBinding {
<<serialized>>
generation_identity
cli_id
session_id
@ -419,6 +727,7 @@ classDiagram
}
class SessionRequestCapability {
<<serialized>>
protocol_version
capability_id
session_binding
@ -451,12 +760,14 @@ protocol material, not Domain Store records.
```mermaid
classDiagram
class SignedProtocolRecord {
<<serialized>>
<<union>>
payload_bytes
signature
}
class MailboxRequest {
<<serialized>>
protocol_version
session_binding
request_id
@ -467,6 +778,7 @@ classDiagram
}
class MailboxCancellation {
<<serialized>>
protocol_version
session_binding
request_id
@ -475,6 +787,7 @@ classDiagram
}
class MailboxResultAck {
<<serialized>>
protocol_version
session_binding
request_id
@ -485,6 +798,7 @@ classDiagram
}
class DeveloperDecision {
<<serialized>>
protocol_version
session_binding
request_id
@ -494,12 +808,14 @@ classDiagram
}
class DeveloperDecisionKind {
<<serialized>>
<<enumeration>>
Approve
Deny
}
class MailboxResult {
<<serialized>>
protocol_version
session_binding
request_id
@ -513,6 +829,7 @@ classDiagram
}
class MailboxResultChunk {
<<serialized>>
protocol_version
session_binding
request_id
@ -524,12 +841,14 @@ classDiagram
}
class ResultStream {
<<serialized>>
<<enumeration>>
Stdout
Stderr
}
class EscalationOutcome {
<<serialized>>
<<enumeration>>
Completed
Denied
@ -567,21 +886,25 @@ encoding, signing, size, and validation rules are defined by the specification.
```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
@ -591,6 +914,7 @@ classDiagram
}
class StateAnchorOwnership {
<<serialized>>
optional lower_artifact_id
optional upper_path
optional work_path
@ -600,6 +924,7 @@ classDiagram
}
class GitRoutingOwnership {
<<serialized>>
optional mountpoint
optional host_root
optional isolated_root
@ -608,6 +933,7 @@ classDiagram
}
class NetworkOwnership {
<<serialized>>
optional derived_policy_digest
optional readiness_fingerprint
optional readiness_evidence
@ -616,12 +942,14 @@ classDiagram
}
class HelperOwnership {
<<serialized>>
optional network_sidecar_id
connectivity_helper
runtime_supervisor
}
class SandboxOwnership {
<<serialized>>
optional container_id
optional runtime_root
optional bundle_digest
@ -631,6 +959,7 @@ classDiagram
}
class PublicationOwnership {
<<serialized>>
optional socket_path
optional identity_path
}
@ -702,14 +1031,18 @@ lease from an absent lease phase.
```mermaid
classDiagram
class StorageCatalog
class StorageCatalog {
<<serialized>>
}
class StoreMetadata {
<<serialized>>
schema_id
schema_version
}
class PreparedArtifact {
<<serialized>>
prepared_artifact_id
source_identity
selected_manifest_digest
@ -722,18 +1055,21 @@ classDiagram
}
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
@ -767,6 +1103,7 @@ remains until bootstrap Host Coordinator recovery records release intent.
```mermaid
classDiagram
class DerivedNetworkPolicy {
<<serialized>>
schema_id
address_families
default_actions
@ -775,33 +1112,39 @@ classDiagram
}
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
@ -829,6 +1172,7 @@ of its canonical form with a schema-specific domain separator.
```mermaid
classDiagram
class NetworkReadinessEvidence {
<<serialized>>
domain_id
generation_id
network_sidecar_id
@ -836,6 +1180,7 @@ classDiagram
}
class NetworkReadinessAttestation {
<<serialized>>
schema_id
domain_id
generation_id
@ -849,6 +1194,7 @@ classDiagram
}
class NetworkObservation {
<<serialized>>
policy_delivery
helper_ready
observed_routes
@ -856,11 +1202,13 @@ classDiagram
}
class PolicyDelivery {
<<serialized>>
<<enumeration>>
SealedMemfd
}
class NetworkReadinessIdentity {
<<serialized>>
schema_id
domain_id
generation_id
@ -871,27 +1219,32 @@ classDiagram
}
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