Add phase-aware pidfs process ownership records
This commit is contained in:
parent
f840a48376
commit
de57e77dc8
1 changed files with 3513 additions and 246 deletions
|
|
@ -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. |
|
||||
|
|
|
|||
Loading…
Reference in a new issue