Add phase-aware pidfs process ownership records

This commit is contained in:
ms2k 2026-07-14 01:46:58 +09:00
commit de57e77dc8
2 changed files with 3513 additions and 246 deletions

View file

@ -98,7 +98,7 @@ classDiagram
Domain "1" --> "0..*" Generation
Generation "1" --> "0..*" Participant
Participant --> ParticipantState
Participant --> ParticipantOwnership
Participant --> "0..1" ParticipantOwnership
Generation "1" --> "0..*" Session
Session "1" --> "0..1" SessionTerminal
SessionTerminal --> SessionTerminalState
@ -108,7 +108,121 @@ classDiagram
`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.
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 {
boot_id
pidfs_st_dev
pidfs_st_ino
}
class PidfsProcessIdentityV1 {
host_pid
start_time_ticks
}
class LegacyUnverifiedProcessIdentityV1 {
host_pid
start_time_ticks
}
class HostProcessIdentity {
<<union>>
PidfsV1
LegacyUnverifiedV1
}
class PersistedHostProcessOwnership {
<<union>>
Unassigned
Staged
Committed
}
class HostProcessOwnershipSlot {
<<enumeration>>
SandboxHostCoordinator
SandboxRuntimeSupervisor
SandboxAttachedRunsc
StateAnchorDaemon
GitRoutingDaemon
NetworkRuntimeSupervisor
HelperConnectivityHelper
HelperRuntimeSupervisor
SessionTerminalHostCoordinator
SessionTerminalExecClient
EscalationHostProcess
}
class ProcessOwnershipMessageVersion {
<<enumeration>>
V1
}
class StagedProcessOwnership {
version
domain_id
generation_id
coordinator_id
slot
identity
}
class ProcessOwnershipCommitAck {
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
@ -127,31 +241,35 @@ classDiagram
inode
}
class ProcessIdentity {
pid
start_time
}
class GuestProcessIdentity {
leader_pid
leader_start_time
process_group_id
}
SessionTerminal --> ProcessIdentity : host_coordinator
SessionTerminal --> PersistedHostProcessOwnership : host_coordinator
SessionTerminal --> PtyIdentity : pty_master
SessionTerminal --> ProcessIdentity : exec_client
SessionTerminal --> PersistedHostProcessOwnership : exec_client
SessionTerminal --> GuestProcessIdentity : guest_process
```
Host process identities use host pid and start time. 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.
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
@ -452,11 +570,6 @@ classDiagram
<<union>>
}
class ProcessIdentity {
pid
start_time
}
class NamespaceIdentity {
device
inode
@ -469,57 +582,57 @@ classDiagram
}
class PreparedLowerOwnership {
prepared_artifact_id
source_identity
lease_state
path
device
inode
optional prepared_artifact_id
optional source_identity
optional lease_state
optional path
optional device
optional inode
}
class StateAnchorOwnership {
lower_artifact_id
upper_path
work_path
merged_path
optional lower_artifact_id
optional upper_path
optional work_path
optional merged_path
daemon
mount
optional mount
}
class GitRoutingOwnership {
mountpoint
host_root
isolated_root
optional mountpoint
optional host_root
optional isolated_root
daemon
mount
optional mount
}
class NetworkOwnership {
derived_policy_digest
readiness_fingerprint
readiness_evidence
namespace
optional derived_policy_digest
optional readiness_fingerprint
optional readiness_evidence
optional namespace
runtime_supervisor
}
class HelperOwnership {
network_sidecar_id
optional network_sidecar_id
connectivity_helper
runtime_supervisor
}
class SandboxOwnership {
container_id
runtime_root
bundle_digest
optional container_id
optional runtime_root
optional bundle_digest
host_coordinator
runtime_supervisor
attached_runsc
}
class PublicationOwnership {
socket_path
identity_path
optional socket_path
optional identity_path
}
ParticipantOwnership --> PreparedLowerOwnership
@ -529,20 +642,62 @@ classDiagram
ParticipantOwnership --> HelperOwnership
ParticipantOwnership --> SandboxOwnership
ParticipantOwnership --> PublicationOwnership
StateAnchorOwnership --> ProcessIdentity : daemon
StateAnchorOwnership --> PersistedHostProcessOwnership : daemon
StateAnchorOwnership --> MountIdentity : mount
GitRoutingOwnership --> ProcessIdentity : daemon
GitRoutingOwnership --> PersistedHostProcessOwnership : daemon
GitRoutingOwnership --> MountIdentity : mount
NetworkOwnership --> NamespaceIdentity : namespace
NetworkOwnership --> ProcessIdentity : runtime_supervisor
NetworkOwnership --> PersistedHostProcessOwnership : runtime_supervisor
NetworkOwnership --> NetworkReadinessEvidence : readiness_evidence
HelperOwnership --> ProcessIdentity : connectivity_helper
HelperOwnership --> ProcessIdentity : runtime_supervisor
SandboxOwnership --> ProcessIdentity : host_coordinator
SandboxOwnership --> ProcessIdentity : runtime_supervisor
SandboxOwnership --> ProcessIdentity : attached_runsc
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
@ -720,11 +875,6 @@ classDiagram
inode
}
class ProcessIdentity {
pid
start_time
}
class NetworkReachabilityProbe {
domain_id
generation_id
@ -751,8 +901,8 @@ classDiagram
NetworkReadinessEvidence --> NetworkReadinessIdentity : fingerprints
NetworkReadinessEvidence --> NetworkReachabilityProbe : includes
NetworkReadinessAttestation --> NamespaceIdentity : enforcement_namespace
NetworkReadinessAttestation --> ProcessIdentity : runtime_supervisor
NetworkReadinessAttestation --> ProcessIdentity : connectivity_helper
NetworkReadinessAttestation --> PidfsProcessIdentityV1 : runtime_supervisor
NetworkReadinessAttestation --> PidfsProcessIdentityV1 : connectivity_helper
NetworkReadinessAttestation --> NetworkObservation
NetworkObservation --> PolicyDelivery
NetworkReadinessIdentity --> NamespaceIdentity : enforcement_namespace

View file

@ -194,6 +194,158 @@ mounts, `slirp4netns`, PTYs, runsc clients, durable store encoding, and exact
ownership records). Physical recovery enumerates and inspects those resources
independently of process liveness.
#### Host process identity and causal acquisition refinement
Lazaret supports host-process authority only on an exactly source/build-qualified
Linux x86-64 kernel at or above 6.9. Each supported upstream or vendor build
preserves the boot-monotonic 64-bit pidfs inode allocation used by the qualified
build and passes its qualification suite. A release string, version floor,
partial backport, or successful runtime smoke cannot substitute for exact build
qualification. Linux 32-bit, other architectures, kernels below 6.9, and
unqualified vendor builds refuse before a generation or external runtime
resource is created.
On every boot Lazaret runs the finite `PidfsHostSupportProbe` before the first
generation and caches success only under the current boot ID. The probe has the
repository-registered operation sequence and bounds. It checks pidfd operations
and pidfs magic, same-live-process concurrent and last-close/reopen identity,
distinct simultaneous processes, stale-handle non-action, and
exit/reap/drop-all/later-process non-reuse. Any missing operation, failed
observation, cleanup uncertainty, timeout, or unexpected equality refuses
support for that boot. This bounded smoke checks the running path; it does not
prove global inode non-reuse or qualify the kernel source/build.
`PidfsProcessIdentityV1` separates three kinds of data:
- `PidfsProcessAuthorityV1 = (boot_id, pidfs_st_dev, pidfs_st_ino)` is the only
positive durable host-process authority. The boot ID is stored as the 16 UUID
bytes in canonical UUID field order; both pidfs values are unsigned 64-bit
types distinct from general filesystem device and inode identities.
- `host_pid` is an unsigned 32-bit lookup locator interpreted only in the host
PID namespace where Host Coordinator and bootstrap recovery run.
- `start_time_ticks` is unsigned 64-bit `/proc/<pid>/stat` negative evidence.
Equality of PID and start time adds no authority; inequality or an unreadable
required observation refuses.
A retained pidfd is the primary live authority. Polling, waiting, signaling,
and termination act through that exact retained descriptor. Production process
control has no numeric-PID signal or reopen-and-signal fallback.
`CausalChildAcquisitionV1` applies to every Lazaret-owned child that can become
durable process ownership. Each Host Coordinator and Runtime Supervisor has one
exclusive in-process broker for all spawn, `SIGCHLD`, and reap operations. The
process has default non-auto-reaping `SIGCHLD`; no same-parent spawn or reap and
no dependency process API bypasses the broker while acquisition is active. The
broker performs this sequence:
1. Persist the owning participant or terminal intent without positive process
ownership.
2. Hold process-wide spawn/reap exclusion and create an inherited fail-closed
launch barrier.
3. Spawn a direct repository-owned wrapper that blocks before target work;
barrier EOF or malformed release exits without executing the target.
4. Capture the host-visible child PID, call `pidfd_open` exactly once, and call
`waitid(P_PIDFD, WEXITED | WNOHANG | WNOWAIT)` on that descriptor. `ECHILD`
or any other error refuses; `WNOWAIT` never reaps the child.
5. `fstat` the same pidfd, read the current boot ID and diagnostic start time,
and form `PidfsProcessIdentityV1`.
6. Persist `Staged` ownership. An RS-launched child is sent as the exact
`StagedProcessOwnership` tuple to HC, and RS waits for an authenticated
`ProcessOwnershipCommitAck` that echoes the same Domain, generation,
generation-scoped coordinator ID, ownership slot, and complete process
identity. Lost, mismatched, unknown-version, unauthenticated, or duplicate
after release acknowledgment is never success.
7. Release the wrapper barrier only after the exact staged ownership commit is
acknowledged. The receiver consumes the acknowledgment once while that
exact barrier is pending.
8. The wrapper arms its parent-death action, rechecks the expected parent pidfs
authority to close the arming race, and execs the foreground,
non-daemonizing target in place.
9. Promote ownership to `Committed` only after target-specific readiness.
Staged ownership cannot satisfy readiness, publication, dependent-resource
creation, or adoption. It remains positive authority for exact same-handle
cleanup and `PidfsRecoveryMatch`, including bootstrap recovery.
Every durable host-process slot is exactly `Unassigned`,
`Staged(PidfsProcessIdentityV1)`, or `Committed(HostProcessIdentity)`. Only a
current pidfs identity may be staged. A committed legacy-unverified identity is
retained only for migration and refusal. Current staged and committed identity
both authorize exact matched cleanup; only committed current ownership may
satisfy an active role, readiness, publication, or adoption.
A role-bound Participant is first committed as bare `Intent` with no ownership
snapshot. Once physical acquisition begins, its role snapshot is present and
never returns to absent. Every non-process physical field is optional while
acquisition is partial; every process field uses the three-phase slot above.
Within Intent, optional fields move only absent-to-present. A newly acquired
child slot moves only `Unassigned` to `Staged` to the exact same current
identity in `Committed`. An already committed current owner or supervising
process may enter a newly created dependent-role snapshot directly, but that
recorded identity then remains immutable. Legacy data cannot use this
existing-process rule. Recorded evidence cannot be replaced or cleared.
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: a non-empty acquisition
snapshot is committed first. PreparedLower additionally records
`lease_state=Intent` before the catalog insertion changes both participant and
lease phase to `LeaseHeld`.
The active Participant pairs are `prepared_lower=LeaseHeld`,
`state_anchor=Prepared`, `git_routing=Prepared`, and
`network|helper|sandbox|publication=Committed`. An active snapshot contains all
role-required physical evidence and current committed pidfs ownership in every
required process slot. `SessionTerminal=Running` likewise requires its
container, PTY, guest identity, and current committed HC and exec-client slots.
Staged, unassigned, or legacy process ownership cannot satisfy either boundary.
Network readiness evidence must also name the enclosing generation and match
its policy, fingerprint, namespace, and committed RS identity internally.
Any partial or active snapshot moves to `ReleaseIntent` while preserving every
recorded identity. A staged current identity remains exact cleanup and recovery
authority in ReleaseIntent. `ReleaseIntent` becomes `Clean` only after exact or
independent absence proof; that transaction clears every process slot to
Unassigned and every optional physical field to absent. Clean retains the
logical generation key, role, and state as an explicit empty role snapshot. A
bare Intent may become that explicit Clean snapshot only after the sealed
runtime-absence check. No ordinary lifecycle transition may clear evidence or
produce Clean.
An exited, unreaped child remains pinned while acquisition completes. The
descriptor-based child check rejects a foreign replacement, while the exclusive
same-parent broker prevents a replacement child from passing the same-parent
control case. Implementations preserve these as safety conditions rather than
timing assumptions.
`PidfsRecoveryMatch` is the only positive recovery reacquisition path:
1. Read and validate the current boot ID.
2. On boot mismatch, do not look up the stored PID. Reconcile only resources
whose independent exact ownership survives reboot.
3. Verify that the stored locator is a host-PID-namespace PID; nested or
ambiguous mappings refuse before lookup.
4. Call `pidfd_open` exactly once for that locator. `ESRCH` or any other error
cannot authorize process action.
5. `fstat` the resulting pidfd and compare its device and inode exactly with the
durable authority. Either mismatch refuses and closes it without action.
6. Apply start-time and expected namespace observations only as negative checks.
7. Use that same matched pidfd for every action. An action-time `ESRCH` or
observation failure refuses without numeric fallback.
Domain Store v1 PID/start-only values dispatch to
`LegacyUnverifiedProcessIdentityV1`. They cannot be observed, converted, or
infallibly migrated into positive authority. They refuse live process action
until reboot, independently proven absence, or explicit operator resolution
allows the ordinary recovery state machine to reconcile the surrounding record.
Boot mismatch fences all old pidfs authority before PID lookup.
The contract accepts two residual assumptions: Linux boot-ID collision is
operationally negligible but not mathematically impossible, and the qualified
boot-monotonic 64-bit pidfs inode counter does not wrap during one boot. Codecs
and comparisons support `u64::MAX` and refuse every unequal authority tuple;
they do not claim to recognize a hypothetical equal tuple after wrap.
#### Protocol cryptography refinement
Before starting the Agent Sandbox, the Host Coordinator creates one ephemeral
@ -291,15 +443,17 @@ connection is already lost, the outcome is not retained for reattachment.
Physical Session Terminal acquisition is ordered as follows:
1. The Host Coordinator records `SessionTerminal=Intent` in the Domain Store.
2. It allocates the PTY and forks a wrapper with the expected HC pid/start
identity. The wrapper arms its HC parent-death edge, re-reads its parent pid
and start identity, and aborts before invoking runsc if either differs. It may
create the guest process only after that recheck succeeds.
2. It allocates the PTY and starts the exec wrapper through
`CausalChildAcquisitionV1` with the expected committed HC pidfs authority.
The wrapper arms its HC parent-death edge, rechecks that authority, and aborts
before invoking runsc if it differs. It may create the guest process only
after staged exec-client ownership is durably committed and the barrier is
released.
3. It establishes the guest process group and obtains the guest leader PID,
start time, and group ID.
4. One Domain Store transaction records the exact Host Coordinator, PTY, exec
client, container, guest leader, and guest process-group identities and sets
`SessionTerminal=Running`.
4. One Domain Store transaction promotes the exact Host Coordinator and exec
client ownership to committed, records the PTY, container, guest leader, and
guest process-group identities, and sets `SessionTerminal=Running`.
5. Only then may the Host Coordinator accept terminal events for the session.
Failure before `Running` never admits the terminal. A live Host Coordinator uses
@ -418,11 +572,12 @@ defined by the protocol model. The product store encodes that generation documen
in typed tables under a schema/db version (unknown newer versions fail closed
until an explicit Host Coordinator migration), including generation identity,
minted runtime component IDs, and participant ownership material (for example
prepared-artifact ID, pid/start, mount identity, network readiness fingerprint).
prepared-artifact ID, staged or committed pidfs process ownership, mount
identity, and network readiness fingerprint).
Participant roles are `prepared_lower`, `state_anchor`, `git_routing`, `network`,
`helper`, `sandbox`, and `publication`. The `helper` participant independently
records durable intent and exact connectivity-helper pid/start identity before
the `network` participant records readiness. Sessions and the generation mailbox
records durable intent and exact committed connectivity-helper pidfs ownership
before the `network` participant records readiness. Sessions and the generation mailbox
are separate generation records. Deterministic paths are lookup hints for
physical recovery, not ownership proofs.
@ -492,9 +647,11 @@ bootstrap recovery may advance it to release intent. These rules make every
interrupted release conservative and prevent a mounted State Anchor from
becoming unleased.
Store version acceptance occurs before physical resource inspection. The first
product version has no product predecessor. Future migration steps create and
validate destination rows in one redb transaction and update the version last.
Store version acceptance occurs before physical resource inspection. The
Domain Store v1-to-v2 migration preserves every PID/start-only value as
legacy-unverified and never observes it into pidfs authority. Migration steps
create and validate destination rows in one redb transaction and update the
version last. Future versions follow the same fail-closed ordering.
Files not represented in the upper layer follow the selected base image. Files,
directories, and whiteouts recorded in the upper layer remain authoritative when
@ -530,9 +687,10 @@ inherited memfd. The Runtime Supervisor verifies the seals and digest, installs
the policy, and returns observed route, helper, process, and namespace evidence
over an inherited Host Coordinator-created private `SOCK_SEQPACKET` channel.
Before helper launch, the Host Coordinator commits the independent `helper`
participant as `Intent`. After launch it verifies and commits the exact helper
and supervising Runtime Supervisor pid/start identities before accepting network
readiness evidence and advancing the `network` participant.
participant as `Intent`. It acquires the helper through
`CausalChildAcquisitionV1`; after the exact helper and supervising Runtime
Supervisor pidfs ownership is committed, it may accept network readiness
evidence and advance the `network` participant.
The Host Coordinator independently reads the Runtime Supervisor's namespace
identity and persists the attestation, fingerprint, and generation-bound
deny/permit/DNS probe result. Persisted readiness files are evidence after this
@ -648,13 +806,13 @@ not duplicated here.
| **FR-1** | Lazaret shall derive a stable Domain ID from the selected Agent Profile and canonical Agent Workspace. |
| **FR-3** | Tracked and non-ignored workspace edits and supported Git operations shall propagate bidirectionally between host and sandbox. |
| **FR-4** | Selected sandbox paths and Ignored Workspace State shall persist across Domain restarts without copying Lazaret host configuration into the sandbox. |
| **FR-7** | Product recovery shall inspect external runtime resources independently of owner-process liveness and compare typed pid/start, namespace, mount, artifact, path, and inode evidence before physical cleanup. |
| **FR-7** | Product recovery shall inspect external runtime resources independently of owner-process liveness. Host-process action shall require the retained pidfd or `PidfsRecoveryMatch` against staged or committed current `PidfsProcessIdentityV1`; legacy PID/start-only ownership, unequal pidfs authority, boot or namespace ambiguity, and unreadable negative evidence shall refuse without numeric-PID signaling. Staged ownership authorizes only exact cleanup/recovery, never service or adoption. Recovery shall also compare typed namespace, mount, artifact, path, and inode evidence before physical cleanup. |
| **FR-8** | Lazaret shall support interactive terminal sessions without degrading supported host terminal capabilities. |
| **FR-9** | Trusted host configuration shall allow the developer to disable Git-ignored path isolation for an Agent Profile. |
| **FR-11** | The host CLI shall list, inspect, structurally verify, and reset all or selected State Anchors without requiring direct manipulation of Lazaret's storage layout. |
| **FR-14** | The Host Coordinator shall own one host PTY master per interactive Client Session; the PTY slave shall attach through non-detached `runsc exec` standard streams. The root attached `runsc run` client shall remain terminal-neutral. Production Session PTYs shall not depend on `runsc exec -console-socket`. |
| **FR-15** | For each interactive Client Session, Lazaret shall preserve the Session Terminal record and protocol in the normative design schema; serialize bounded terminal events and merged output by Session ID; map resize and the `Interrupt`, `Quit`, and `Suspend` events to the specified exact process-group operations; collect exit status from the non-detached `runsc exec` client; and follow the specified release and hard-loss recovery ordering without reattachment or mismatched cleanup. |
| **FR-16** | Domain network connectivity shall use `slirp4netns` as the production connectivity helper. The Domain Store shall record independent `helper` intent before launch and exact helper and supervising Runtime Supervisor pid/start identities before network readiness. |
| **FR-16** | Domain network connectivity shall use `slirp4netns` as the production connectivity helper. The Domain Store shall record independent `helper` intent before launch and exact committed helper and supervising Runtime Supervisor pidfs ownership before network readiness. |
| **FR-17** | The Domain Store shall be opened only by the Host Coordinator process holding the Domain `flock`; mutations shall use whole redb transactions. Storage Catalog opens shall be bounded by `storage-catalog.lock` and one transaction. Cross-store lease release shall follow the durable release-intent, exact-unmount, catalog-delete, Domain-clean ordering defined by the durable generation refinement. |
| **FR-18** | Product network-readiness evidence shall conform to the normative design schema and include the readiness fingerprint plus the generation-bound deny/permit/DNS probe result. |
| **FR-19** | The Host Coordinator shall derive generation-bound network policy from trusted host configuration; the Runtime Supervisor shall install only that derived policy and attest readiness by observed namespace state. Durable network participant state shall record the network readiness fingerprint. |