The orchestration boundary between private execution and shared state.
LayerStack owns durable history, namespace processes own execution boundaries, and OverlayFS owns the writable view. A workspace session holds their handles together and decides when lifecycle edges may cross.
- Lease
- Pins the immutable LayerStack view used as the session’s publication base.
- Holder + namespace FDs
- Keep the user, mount, PID, and optional network boundaries alive.
- Overlay handle
- Names the upperdir, workdir, and merged workspace owned by this session.
- Admission gate
- Serializes lifecycle edges without serializing admitted command runtime.
- Command ledger
- Tracks which namespace executions still belong to the session.
- Finalize policy
- Either retain the explicit session or publish-then-destroy an implicit one.
Build forward, compensate backward.
Session creation crosses storage, process, network, mount, persistence, and registry boundaries. A failure never leaves the later resources alive while silently dropping an earlier owner.
- 01
Acquire lease
Pin the active manifest before any runtime state exists.
on failure
release lease
- 02
Create scratch
Allocate upperdir, workdir, run directory, and session identity.
on failure
remove run tree
- 03
Spawn holder
Create namespaces, capture FDs, and prepare optional isolated networking.
on failure
kill holder + free network
- 04
Mount overlay
Project the leased lower chain with the private upperdir.
on failure
namespace teardown
- 05
Persist + register
Write manager state, prepare the cgroup leaf, then insert the live session.
on failure
destroy or surface rollback failure
Serialize decisions, not command runtime.
One mutex per session protects admission, completion, finalization, file operations, remounts, and guarded destroy. The mutex is released after a command is attached, so admitted commands still execute concurrently.
One command lifecycle
- 01
Lock gate
Resolve the live session and prevent a concurrent finalize, remount, file operation, or destroy.
- 02
Insert ledger entry
Attach the namespace execution ID before the runner can complete.
- 03
Launch + attach
Reserve execution capacity, start the runner, and connect its terminal outcome.
- 04
Release gate
The command runs concurrently after its lifecycle edge is committed.
- 05
Complete
Remove the ledger entry; an empty ledger may transition the session into finalization.
Active
Commands may be admitted; explicit sessions remain after the ledger drains.
Finalizing
No new work enters while capture, publish, and implicit teardown run under the gate.
FinalizeFailed
The session remains destroyable but cannot quietly return to normal admission.
The upperdir closes the loop back into LayerStack.
Capture reads the actual OverlayFS upperdir. Planning uses only the immutable session base; conflict resolution moves under the writer lock and compares against the current active head.
- 01
Capture
Walk the upperdir and derive writes, symlinks, whiteouts, and opaque directories from kernel OverlayFS metadata.
- 02
Plan
Route paths and record expected content fingerprints against the session’s immutable leased base.
- 03
Lock + resolve
Acquire the LayerStack writer lock and compare actual fingerprints against the current active head.
- 04
Merge or reject
Accept equal paths, three-way merge eligible text, or reject the complete publication on conflict.
- 05
Commit + attribute
Promote one staged immutable layer, prepend the manifest, then append ownership evidence.
Kernel metadata, not filename folklore
Capture recognizes OverlayFS whiteouts and opaque directories from device or xattr metadata. A user-created filename beginning with .wh. is not silently reinterpreted as a deletion marker.
Stale manifests are allowed. Conflicting paths are not.
Each changed source path carries an expected fingerprint from the leased base. Under the writer lock, LayerStack resolves the actual fingerprint from current head. This per-path comparison—not manifest freshness—is the real concurrency control.
Eligible text is merged on line regions. The runtime emits clean bytes and structural origin ranges—never conflict markers.
Unchanged at head
expected = actualaccept command change
Disjoint text edits
merge(base, head, command)publish merged bytes
Overlapping edits
different replacement regionsreject source_conflict
Non-text or oversized
merge ineligiblereject, no conflict markers
Ignore rules route attribution; they do not simply erase changes. Source-routed paths receive fingerprint validation and line origin, while ignored paths use wholesale attribution from the session base policy.
Durable payload first. Manifest visibility last.
A resolved changeset is written into a nonce staging directory. Files and directories are synced before rename; the manifest is rechecked immediately before the new immutable layer becomes active.
- 01
Write staging
Materialize every resolved change under a unique temporary layer directory.
- 02
Sync + rename
fsync payload and tree, then atomically rename staging to its final L* path.
- 03
Recheck head
If the manifest changed after resolve, remove the promoted layer and report conflict.
- 04
Prepend manifest
Write the next version with L* first; only then can new leases observe it.
Empty or byte-identical output is a no-op. A rejection creates no partial layer and is surfaced on the command that triggered finalization.
Coordination scales with changed state, not the base checkout.
The shared base remains pinned and immutable. Session setup scales with lower-chain metadata; finalization scales with captured paths and affected bytes.
| Operation | Dominant bound | Reason |
|---|---|---|
| Session create | Θ(L) | Build and pin an L-layer lower chain; no Θ(B) base copy. |
| Capture | O(C log C + bytes visited) | Walk and deterministically order C upperdir changes. |
| Resolve | O(C·L·P + H) | Find C paths through L layers and hash or merge H affected bytes. |
| Commit | O(changed bytes + manifest) | Write one staging tree, sync, rename, and prepend metadata. |
Attribution follows the resolved result
The merge records whether final line ranges came from active history or the command. After commit, those origins are mapped to runtime owners and appended outside the compactable layer store.
Related LayerStack model
File-operation blame
