Classify serialized and runtime schema records

This commit is contained in:
ms2k 2026-07-14 23:34:56 +09:00
commit f106a9f636
3 changed files with 697 additions and 110 deletions

View file

@ -1,90 +1,21 @@
# Lazaret Configuration Template (config.toml)
# Copy this file to ~/.config/lazaret/config.toml.
# Lazaret configuration is host-only and must not be placed in a mounted workspace.
# Lazaret trusted-host configuration v1
schema = "lazaret-trusted-host-config-v1"
[storage]
# Mount path on the host where persistent profile directories
# (e.g., usr, var, etc, opt, root) will be initialized and stored.
data_root = "~/.local/share/lazaret"
[network]
# Default global whitelist applied to the Netlink router gateway.
# Nameservers from the host's /etc/resolv.conf are added during Domain startup.
default_allow = [
"198.51.100.53/32", # Documentation address
"192.0.2.53/32" # Documentation address
]
default_allow = ["192.0.2.53/32", "198.51.100.53/32"]
# Default global blacklist applied to the Netlink router gateway.
# Private RFC 1918 ranges are blackholed by default to prevent scanning/accessing the local network.
default_deny = [
"10.0.0.0/8", # Class A Private Network Range
"172.16.0.0/12", # Class B Private Network Range
"192.168.0.0/16" # Class C Private Network Range
]
[escalation]
# Set to true to enable agent-to-host escalation through Lazaret's host-private control plane.
enabled = true
# runtime_dir is optional. Lazaret defaults to a private directory beneath
# $XDG_RUNTIME_DIR.
# Host commands that the agent is allowed to execute on the host automatically
# without triggering an interactive Human-In-The-Loop (HITL) prompt.
[[escalation.auto_approve]]
argv = ["aws", "sts", "get-caller-identity"]
allow_extra_args = false
# AI AGENT PROFILES
[profiles.gemini]
[profiles.default]
image = "docker.io/library/debian:trixie"
isolate_gitignored = true
persistent_dirs = ["/root", "/usr", "/var", "/etc", "/opt"]
allow_egress = ["10.222.0.0/16"]
env = { GEMINI_API_KEY = "env:GEMINI_API_KEY" }
persistent_dirs = ["/root"]
allow_egress = ["203.0.113.10/32"]
volumes = [
{ host = "{profile_dir}/.gemini", container = "/root/.gemini", mode = "z" }
{ host = "{profile_dir}/agent-state", container = "/root/.local/state/agent", kind = "directory", access = "read-write", create = true },
]
[profiles.claude]
image = "docker.io/library/debian:trixie"
isolate_gitignored = true
persistent_dirs = ["/root", "/usr", "/var", "/etc", "/opt"]
allow_egress = ["10.222.0.0/16"]
env = { ANTHROPIC_API_KEY = "env:ANTHROPIC_API_KEY" }
volumes = [
{ host = "{profile_dir}/.claude", container = "/root/.claude", mode = "z" }
]
[profiles.codex]
image = "docker.io/library/debian:trixie"
isolate_gitignored = true
persistent_dirs = ["/root", "/usr", "/var", "/etc", "/opt"]
allow_egress = ["10.222.0.0/16"]
env = { OPENAI_API_KEY = "env:OPENAI_API_KEY" }
volumes = [
{ host = "{profile_dir}/.codex", container = "/root/.codex", mode = "z" }
]
[profiles.grok]
image = "docker.io/library/debian:trixie"
isolate_gitignored = true
persistent_dirs = ["/root", "/usr", "/var", "/etc", "/opt"]
allow_egress = ["10.222.0.0/16"]
env = { XAI_API_KEY = "env:XAI_API_KEY" }
volumes = [
{ host = "{profile_dir}/.grok", container = "/root/.grok", mode = "z" }
]
[profiles.opencode]
image = "docker.io/library/debian:trixie"
isolate_gitignored = true
persistent_dirs = ["/root", "/usr", "/var", "/etc", "/opt"]
allow_egress = ["10.222.0.0/16"]
env = { ANTHROPIC_API_KEY = "env:ANTHROPIC_API_KEY", OPENAI_API_KEY = "env:OPENAI_API_KEY", GEMINI_API_KEY = "env:GEMINI_API_KEY" }
volumes = [
{ host = "{profile_dir}/opencode-config", container = "/root/.config/opencode", mode = "z" },
{ host = "{profile_dir}/opencode-share", container = "/root/.local/share/opencode", mode = "z" }
]
[profiles.default.escalation]
enabled = false

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

View file

@ -63,9 +63,10 @@ workspace paths remain writable, while Git-ignored paths are isolated into
Domain-scoped sandbox state by default. The developer may explicitly disable that
isolation in trusted host configuration.
The physical refinement of model configuration confinement canonicalizes the
configuration file and every bind-mount source, compares their resolved ancestry,
and omits configuration bytes from the sandbox mount specification.
The trusted-host configuration refinement below owns strict parsing,
canonicalization, path authority, and configuration fingerprinting. Successful
loading is the physical meaning of the model's `ConfigSafe`; configuration bytes
never enter a sandbox mount specification.
## 2. Glossary
@ -178,7 +179,154 @@ Runtime paths and durable rows carry Domain ID, generation, configuration
fingerprint, role identity, and physical ownership fields. Bootstrap recovery is
initiated by a host Lazaret CLI invocation, not by a resident scheduler.
### 3.2 Domain and Session Lifecycle Refinement
### 3.2 Trusted Host Configuration Refinement
The trusted-host configuration has the closed v1 shape in the
[design schemas](schema.md). Lazaret chooses its configuration file from an
explicit host CLI option or, when absent, from
`$XDG_CONFIG_HOME/lazaret/config.toml`, with
`$HOME/.config/lazaret/config.toml` as the XDG fallback. It derives the Storage
Root from `storage.data_root` when present, otherwise from
`$XDG_DATA_HOME/lazaret`, with `$HOME/.local/share/lazaret` as the fallback. The
Runtime Root is `$XDG_RUNTIME_DIR/lazaret`; v1 has no configuration field that
can redirect runtime control state elsewhere. Required XDG and home inputs must
be NUL-free host paths. An unset, empty, or relative XDG config or data value is
treated as unset and uses its home fallback; the home path and XDG runtime path
must be present and absolute. An invalid fallback or runtime value refuses
loading.
The caller supplies one profile name explicitly. Lazaret never selects the
first profile, infers a profile from an image or executable, or falls back to a
different profile. The canonical Agent Workspace is the existing directory
selected by the caller, normally the current working directory. Lazaret opens
it as a directory, obtains its canonical raw pathname and filesystem identity,
and retains the descriptor. The selected profile name and canonical workspace
bytes produce `DomainIdV1` exactly as defined by the schema.
The explicit configuration path follows the schema's strict host-path grammar.
The loader resolves any declared symlink once to a canonical target and builds
a no-symlink confined plan to that target. The source is limited to 1,048,576
bytes, must be a regular file owned by root or the invoking user with exactly
one hard link, and must not grant group or other write permission. Lazaret first
opens it
with an O_PATH-style descriptor so validating the file type cannot activate a
special file, reopens that exact descriptor for nonblocking read, and compares
their identities. It records size and nanosecond change time, reads exactly the
recorded size into owned bytes, refuses trailing growth or a short read, reads
the same range a second time, and requires equal bytes and unchanged identity,
size, and change time before and after both reads. The loader never retries a
changed source. It then performs recursive strict TOML decoding and v1 dispatch.
No parser default or permissive flattening may make an unknown, duplicate,
aliased, or unknown-version field meaningful. Every profile is structurally
decoded, but filesystem and environment resolution applies only to the
explicitly selected profile.
Path resolution produces a `ConfinedPathPlan`, not an ambient absolute path.
Each plan retains a validated ancestor directory descriptor and a normalized
relative suffix, records the ancestor's device, inode, and mount identity, and
keeps canonical raw pathname bytes only as a diagnostic label or digest input.
Every consumer uses descriptor-relative opens with beneath, no-magic-link,
no-symlink, and no-mount-crossing resolution and revalidates the expected type.
The consumer refuses a replaced ancestor, mount transition, symlink, special
file, type mismatch, or any source that cannot be revalidated; it does not
recover by opening the canonical label as an absolute pathname.
Lazaret creates a missing Storage, Runtime, profile, or Domain root one path
component at a time beneath its retained existing ancestor. Every created
component has mode `0700`. Creation refuses symlinks, magic links, and child
mounts, and reopens each new component before proceeding. The selected root, if
it already exists, and every component Lazaret creates beneath the closest
existing ambient ancestor are Lazaret-owned: each must be a directory owned by
the invoking user with mode exactly `0700`. Ambient ancestors above that
ownership boundary use the same descriptor-relative confinement but need not
use Lazaret's ownership or mode; an ordinary `0755` home-directory ancestor
therefore does not by itself refuse loading. A creation or validation failure is
final for that load and never authorizes a partially resolved root.
The profile directory is the `profiles/<profile-name>` child of the Storage
Root. Persistent and runtime Domain roots use the lowercase hexadecimal Domain
ID beneath separate `domains` children of the Storage and Runtime Roots. Those
fixed layouts are constructed only from validated root handles; a path string
or matching digest alone is not authority over a root.
Configuration confinement compares canonical component ancestry and retained
filesystem identities after every source has been opened. The configuration
file, Agent Workspace, Storage Root, Runtime Root, and their Domain/control
trees may not overlap in either ancestor direction. Volume sources may be
strict descendants of the profile directory, but they may not overlap the
configuration source, Agent Workspace, runtime or Domain control trees, or one
another. Other volume sources must be disjoint from all Lazaret data and control
trees as well. All paths are reopened through their plans after the comparisons;
any identity or mount change refuses the complete load. Volume creation is
limited to a declared directory strictly beneath the profile directory and uses
the same component-wise `0700` creation rules.
Configuration resolution also requires an internal `SandboxLayoutV1`. Its
closed protected-destination set contains `/workspace` and every product-owned
sandbox mount destination selected by production layout construction. Each
persistent or volume destination is compared with every member in both ancestry
directions. The configuration loader does not guess paths for control,
credential, mailbox, proc, sys, dev, runtime, or later product mounts, and a
layout cannot be constructed without `/workspace`. Production layout
construction, rather than trusted-host configuration syntax, owns the remaining
exact destination choices.
The loader resolves environment references only after structural, path, and
network validation. A missing, non-UTF-8, or NUL-containing host value refuses
the selected profile. Resolved values live only in the move-owned
`ResolvedDomainConfiguration`; its representation is not serializable and its
diagnostics redact values. Only reference names enter the configuration
fingerprint. Configuration bytes, nonselected profiles, and unreferenced host
environment values are not retained as runtime authority or copied into the
Agent Sandbox.
The Host Coordinator derives the selected profile's effective IPv4 policy and
`ConfigurationFingerprintV1` using the schema's exact normalization and
canonical framing. IPv6 is disabled for a v1 sandbox. Joining a running Domain
requires the same Domain ID and configuration fingerprint; the modeled
configuration-mismatch path refuses every other value.
For auto-approval, the configured executable resolves to one canonical path
outside every sandbox-writable or Lazaret control tree. That path, rather than a
`PATH` lookup or basename, is fingerprinted and matched. On every approved
execution the Host Coordinator reopens the current object through its confined
plan, requires a regular file with exactly one hard link, root or invoking-user
ownership, no group or other write permission, and an executable mode for the
invoking user, and passes that open descriptor into the causal launch wrapper.
The same checks apply when configuration is loaded. The wrapper
executes `/proc/self/fd/<inherited-descriptor>` while retaining that descriptor,
so pathname replacement after validation cannot select another object. There is
no shell, numeric descriptor supplied by the requester, ambient-path reopen, or
fallback execution path. Scripts are subject to the kernel's ordinary shebang
handling; the rule authorizes the exact opened script object, not an arbitrary
requester-selected interpreter.
`MailboxRequest.command_argv` is nonempty. Every element is UTF-8 and NUL-free;
element zero is an absolute path byte-equal to the rule's canonical executable,
and elements one onward are matched against the rule arguments. Lazaret performs
no `PATH` lookup. The v1 structured request language accepts no command-string
or shell-expansion form; an unsupported form has the physical
`CommandRequiresShell` classification and cannot auto-approve. The launcher
never inserts or interprets a shell. An explicitly configured `/bin/sh -c ...`
vector can match only its exact configured rule. A directly configured script
continues to use ordinary kernel shebang handling for the same opened script
object.
Any shell-requiring request form, invalid executable, post-load path conflict,
or argument mismatch follows the modeled developer approval path or is refused;
it is never widened into automatic approval. Requiring one hard link rejects
hardlinked package layouts, including affected Nix-style binaries, in v1. This
is a deliberate conservative compatibility boundary. Relaxing it requires a
new executable-alias authority policy in a later version; v1 has no exception or
fallback.
`ResolvedDomainConfiguration` is created before a generation, durable intent,
or external runtime resource. Every production configuration consumer receives
that same owned resolved value or a capability-bounded borrow from it; consumers
do not reparse the TOML document or independently repeat default, path, network,
environment, fingerprint, or approval decisions.
### 3.3 Domain and Session Lifecycle Refinement
The Host Coordinator implements Domain ownership with a non-blocking host
`flock`, Domain-scoped runtime paths, an exclusive Domain Store handle, and
@ -375,7 +523,7 @@ withdraws publication before dropping keys and credential artifacts. HC loss
destroys the in-memory package; bootstrap never restores it into another
generation.
### 3.3 Session Terminals
### 3.4 Session Terminals
The Host Coordinator owns one host PTY master per interactive Client Session. The
PTY slave is attached to non-detached `runsc exec` standard streams with a guest
@ -491,7 +639,7 @@ after that runtime-absence check. A process, container, PTY, or generation
mismatch fails closed and retains the record; it is never adopted or signaled as
the recorded session.
### 3.4 Filesystem and Git Model
### 3.5 Filesystem and Git Model
The Agent Workspace is mounted read-write. Tracked paths and untracked paths that
are not ignored propagate in both directions. The Git index, `HEAD`, references,
@ -523,7 +671,7 @@ These protections do not make the workspace immutable. Their purpose is to stop
the sandbox from writing the protected host Git configuration and default hook
directory while preserving normal Git operations and bidirectional source edits.
### 3.5 Persistent Sandbox State
### 3.6 Persistent Sandbox State
The State Anchor preserves developer tooling and selected sandbox system paths for
one Domain. It combines a Lazaret-owned prepared lower from the selected base
@ -654,7 +802,7 @@ allowing unmodified base-image content to follow the selected image.
Persistent state is sandbox state, not host configuration. It must not contain or
override Lazaret's host control plane.
### 3.6 Network Isolation
### 3.7 Network Isolation
The Network Sidecar role is realized on the Runtime Supervisor-owned install
path. The Runtime Supervisor holds network-administration capability while it
@ -712,7 +860,7 @@ The OCI configuration joins only the Domain enforcement network namespace, and
`slirp4netns` attached to that namespace is the sole external egress mechanism.
Dynamic policy changes are outside the current physical refinement.
### 3.7 Host Escalation
### 3.8 Host Escalation
The Escalation Client exchanges request and result records through the
Escalation Mailbox: regular files written by atomic rename beneath host-private,
@ -748,7 +896,7 @@ Escalation authentication, correlation, approval, execution, cancellation, resul
delivery, and terminal routing are defined by the
[TLA+ application protocol model](protocol/Lazaret.tla).
### 3.8 State Inspection and Reset
### 3.9 State Inspection and Reset
The host CLI provides read-only discovery, inspection, structural verification,
and explicit reset operations for Domain State Anchors. These operations resolve