admission.lock, in a directory whose files an
admission reads. An admission holds its set of fences SHARED; a writer of one
of those files takes its fence EXCLUSIVE first, so it waits for the admission
to end, and writers of one fence exclude each other the same way. Fences are
native only (meerkat_core::realm_admission); a browser runtime has none.
They are a different mechanism from a governed helper’s admission fence, the
authorization check of the
authorization integration guide.
The Fences
Every fence file is
<directory>/admission.lock. A realm fence is named by
the realm layout (the id sanitized), and an id that is ., .. or more than
one path component is refused.
The Order
An operation that takes more than one fence takes them in one global order: U, then C by realm id, then R from the chain root out to the head, then the fence beside a document. Every comparison is by bytes. Two realm fences go together only as a composed chain’s (AdmissionFence::realm_chain, the one
function that gives realm fences their chain positions), and a fence beside a
document is only ever the one fence of its operation: a set that breaks
either rule is refused with ADMISSION_FENCE_UNAVAILABLE before anything is
locked.
A fenced operation that waits for its fences takes them before any other lock
it takes (a document’s custody, a grant owner’s administrative custody, a
store’s maintenance fence or a realm’s leases), other than the config
runtime’s own in-process write mutex, which only that runtime’s own writes
take. An operation that already holds another lock takes its fences only
with the form that never waits: the work owner’s child issuance, beside its
reservation, and the staged grant, under the stage’s custody. A persisted
credential store’s coordinator is the exception to both rules: an entry for a
credential key takes the coordinator’s in-process gate for that key first (a
refresh first joins one already running for the key), then waits for C of the
key’s realm, unless its operation already holds it, and then for the key’s
lock file, both within the entry’s bound.
Writers And Holders
AdmissionGuard takes a set EXCLUSIVE, for a writer: acquire waits up to
its bound, try_acquire never waits. AdmissionHold takes a set SHARED, for
an admission, and never creates a fence. A guard proves its fences to every
write it reaches: a store’s write takes the operation’s guard and refuses one
that does not hold the file’s fence, with nothing written
(AdmissionGuard::covers, by fence file). Once its guard is held an operation
settles on a task of its own that owns the guard, so a caller that stops
waiting releases nothing before the last write returned.
Only a realm delete or prune and the migration archive remove or move a live
admission.lock, and only while they hold it.
Refusals
Neither is an unknown outcome. In Rust they are
AdmissionRefusal::InProgress and AdmissionRefusal::Unavailable, with a
typed FenceUnavailableCause, and each fenced API has its own variants:
AdmissionInProgress and AdmissionFenceUnavailable in ConfigError,
ConfigRuntimeError, StockGrantError, StockGovernanceError and
ReviewedConnectorRefusal, AdmissionInProgress in
HelperChildIssuanceRefusal, and AdmissionFence in the grant owner’s
GrantRefusal, GrantStoreError and GrantDurabilityError.
The credential writes have their own variants: AdmissionInProgress and
AdmissionFenceUnavailable in RefreshError, CredentialMutationError and
McpServiceSecretUpdateError (a coordinator with no credential root to fence
under refuses as unavailable), and AdmissionFence in TokenStoreError, a
store write whose guard does not hold the store’s fence. The other credential
surfaces report the two refusals as ones they already have, and no credential
refusal carries an ADMISSION_* code: connector login and MCP OAuth as
CredentialAccess pending or unavailable, the host-auth wire reason as
credential_access_pending or credential_store_unavailable, a provider’s
AuthError as CredentialAccessPending or CredentialStoreUnavailable, and
a connector client update as ConnectorClientUpdateError::Ownership, which
did not start.
Permissions
The first participant creates a fence exclusively, mode exactly 0640 whatever the umask, with its directory’s group: a setgid directory gives it, otherwise the creator sets it. A creator that cannot set the mode or the group refuses withADMISSION_FENCE_UNAVAILABLE, and so does every participant the file
denies, until its owner corrects it. A fence is a regular file with one link,
opened without following a symbolic link, and every descriptor is
close-on-exec. Only the process that took a guard or a hold unlocks it.
Filesystems And Installs
A fence lives only on a local filesystem whoseflock is native: on Linux
ext2/3/4, XFS, Btrfs, tmpfs, ramfs, F2FS, overlayfs, ZFS and bcachefs; on
macOS APFS and HFS+. Other platforms host no fence.
rkat storage hosts-fences DIR... answers that question for each directory,
with the same predicate the writers use.
On any other filesystem a standalone install skips the fence, since no
holder can exist there. A composed install, whose roots (the state root, the
user rkat root and the credential root) hold composed-install.json, never
skips: such a fence refuses with ADMISSION_FENCE_UNAVAILABLE.