Skip to main content
An admission reads a realm’s configuration and authorization files and decides from them. A realm admission fence keeps those files still while it does: a fence is a lock file, 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 with ADMISSION_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 whose flock 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.