Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Repository Layout and Authority

This page is the authoritative current-state reference for Prikk’s on-disk repository layout and storage authority boundaries. It describes the current implementation and is grounded in the code, released RFCs, and implementation status records listed in the anchor table at the foot of the page.

For logical object concepts, see the data model. For repository path and worktree write-safety rules, see path and worktree safety. For local persistence and recovery behavior, see durability and crash recovery. For trust and signature scope, see the trust and threat model. For lock and ref compare-and-swap behavior, see concurrency and locking. For format stability, migration limits, and release identity, see release, versioning, and compatibility.

Core Caveats

  • Prikk is early implementation software and is not a production Git replacement.
  • .prikk/ is Prikk’s native repository format and is not Git-compatible storage.
  • .prikk/FORMAT is a current format-version gate, not a stable-format or migration guarantee.
  • Ref files are mutable pointers for convenience and recovery, not roots of trust.
  • Cache-like directories are initialized today, but are not current roots of trust; the quarantine directory is retired and no longer initialized.
  • Durability and recovery claims are supported by current unit and integration tests, not by a completed crash-matrix or fuzzing campaign.
  • Repository mutation is exercised by project gates on Linux, macOS, and Windows (DC-87 Stage 2). Windows’ anchoring guarantee is weaker than Linux/macOS in one stated way — see platform support for the exact gap and which of the nine durability guarantees are held, weaker, or documented no-ops there. Read-only commands are CI-gated on macOS and Windows too — see platform support.
  • Stable repository-format migration, garbage collection, quarantine enforcement, cache rebuilding, hosted forge trust, and remote-tracking remain deferred. prikk sync (RFC 116, RFC 117) and prikk merge (DC-74) have since shipped — see the sync and merge guides.

Initialized Layout

A fresh prikk init creates the repository directory, the initialized directories below, and the format marker file. It does not create runtime leaf files such as the active WAL or active ref metadata. Ref, received-ref, and trust storage are the exception: the ref-pointer index, ref-log, received-ref index, and trust containers are all fixed, named files allocated by init itself, empty until first use — there is no per-ref, per-received-ref, or per-key-id file or directory created later, since none of those names exist at init time and a per-name file would have to be.

.prikk/
  FORMAT
  worktree.marker
  containers/
    patch/{a,b}.container
    block/{a,b}.container
    ref-state/{a,b}.container
    tag/{a,b}.container
    attestation/{a,b}.container
    blob/{a,b}.container
    index.container
    generations.log
  active/
    default/
      queue.wal
      ref-name
  refs/
    containers/
      log-{a,b}.container
      pointer-index-{a,b}.container
      pointer-index-generation.log
      received-index-{a,b}.container
      received-index-generation.log
    locks/
  trust/
    keys.container
    policy-{a,b}.container
    policy-generation.log
  cache/

Every file above is created by init and is empty until first use. No name under .prikk/ is created after init — that is a design invariant, not an implementation detail, and it is what makes the repository durable on filesystems that cannot make a new directory entry durable.

init also creates refs/tmp/, which is allocated but never written to. It is a remnant of a retired candidate-publication mechanism, not authority for anything, but its absence is an error: verify scans it on every run, so repository open validates it is present.

Ten further directories that were previously allocated at init for the same reason — objects/ and its six object-type subdirectories, quarantine/, refs/by-id/, refs/logs/ — are no longer created. Nothing has ever written into any of them (object and ref storage moved into containers years earlier), and nothing validates their presence at open, so a repository initialized before this change keeps them harmlessly and a newly initialized one simply lacks them. One dormant diagnostic still reads the objects/ tree if present — see Object Store below.

New repositories contain 6 in FORMAT. Formats 1 through 5 are rejected at open, with an error naming the format found and directing migration through prikk bundle export on a version that still supports it. There is no dual-layout bridge and no in-place migration: the format marker is a gate, and a repository whose on-disk shape does not match what the code expects is refused rather than opened and left to fail later.

cache/ holds rebuildable, non-authoritative state — a corrupt or absent cache file is never an error, and any operation’s result is identical whether the cache is warm, cold, or missing.

There is no initialized gc/ directory, and prikk performs no garbage collection: no object is ever deleted or superseded once written.

Object Store

Objects are not stored one-file-per-object. Every persistent object envelope is a checksum-framed record appended into a shared, per-type container allocated at init:

containers/<object-type>/a.container

with a paired b.container allocated at init and permanently unused — object containers have no data model to compact against (see Compaction below) — and one containers/index.container mapping each object id to the container, slot, offset and length where its record lives.

Six object types are persisted this way — patch, block, ref-state, tag, attestation, blob. RefUpdate is stored inline in ref logs, not as an object record.

Why containers rather than files. An object’s identity is its content hash, so every one-file-per-object write created a new directory entry. Making a new name durable requires an fsync on the parent directory, which POSIX provides and Windows does not — there is no documented or undocumented Windows primitive that makes a new directory entry durable. Appending to a file that already has a name needs only content durability, which every supported platform provides. Moving objects into fixed, init-allocated containers is what makes prikk’s durability guarantee statable as a property rather than as a list of platforms that happen to pass.

Reading is isolate-and-continue. A damaged record is named at its byte offset and the scan continues to the next intact one, so corruption is confined to the records it actually damaged rather than failing the whole container. A record is authority only when its frame checksum verifies, its envelope decodes and validates, it has the expected type, and its computed content-addressed id matches the requested id.

The index is rebuildable and off the durability path. An object record is appended and made durable before its index entry is appended, so a crash between the two leaves an object present but unindexed — recoverable by rebuilding the index from a container scan. The reverse ordering would let a reader see a valid index entry pointing at bytes that are not there, so the ordering is load-bearing.

Nothing supersedes or deletes an object. Writing an id that already exists with identical bytes is a no-op; writing one with different bytes is an error. Containers therefore only ever grow.

objects/ and its six type subdirectories are a retired one-file-per-object layout, replaced by the containers above; nothing in a format-3 repository writes into it, and init no longer creates it. A repository initialized before this change may still have it on disk — its presence or absence is not validated at open — and if so, one dormant diagnostic (scan_loose_file_temp_debris, kept for .pobj.tmp. debris a format-3 repository can no longer produce) still reads it during verify.

Refs and Ref Logs

Ref pointer and ref-log storage is shared, not per-ref: every ref’s own pointer entry and log records live in containers allocated once at init, not in a file named for that ref. A ref name does not exist until branch create/tag create mints it, well after init — a per-ref file could never be one of init’s own fixed names, so pointers and logs for every ref instead interleave inside:

refs/containers/pointer-index-{a,b}.container
refs/containers/log-a.container
refs/containers/log-b.container
refs/locks/<ref-name-storage-key>.lock

Locks remain per-ref files, one per ref name actually in use, unaffected by this: the storage key is the hex SHA-256 digest of the ref name bytes, the same digest used internally to attribute each container entry to its own ref.

pointer-index-{a,b}.container is an append-only, checksum-framed sequence of pointer entries; each entry records one ref’s human-readable name, its published RefState object id, and the SHA-256 key derived from that name. A ref’s current pointer is its last matching entry in the live slot — republishing a ref appends a new entry rather than rewriting the old one, and which slot is live is named by this container’s own generation log, not always a (see Compaction below). It is useful for lookup and recovery, but is not trusted alone: verification checks pointer content against RefState objects and ref-log evidence, the same as before.

received-index-{a,b}.container holds the same shape of entry for received refs — pointers imported by prikk bundle import under the separate remotes/<name> namespace (DC-78 §D4), never refs/by-id/: an imported RefState object keeps the origin repository’s own embedded ref name, which could never agree with a locally renamed pointer, so received refs get their own container and key space rather than a special case in the ordinary one. Last-entry-wins, the same as the ref-pointer index, and compacted the same way. This index is never consulted by verify_repository directly — every object a received pointer leads to is checked by the ordinary object-store scan regardless of how it was discovered.

log-a.container/log-b.container hold every ref’s RefUpdate log records, interleaved by append order; a reader filters to one ref’s own subsequence by that same per-record key. Each record carries a signed RefUpdate envelope inline with frame magic, versioning, length, and checksum. Ref logs are publication evidence when their record chain, referenced RefState objects, target Blocks, signatures, and trust policy checks all hold — unchanged in meaning, only in storage shape. Slot b is allocated alongside slot a at init and permanently unused: the ref log is DC-38/DC-69’s audit trail and must never be compacted, so writes always target slot a (see Compaction below).

refs/locks/*.lock files are local synchronization files for ref-specific publication and repair, not a root of trust. There is no longer a temporary-candidate mechanism: an append-only pointer entry has no candidate value to stage before becoming durable, so a publish that used to write, sync, and promote a candidate now durably appends the pointer entry directly, in one step.

refs/by-id/ and refs/logs/ are no longer initialized directories — nothing has read or written either since ref publication moved into containers, so init no longer creates them, and a repository missing them behaves identically to one that still has them from before this change (their presence or absence is not validated at open).

refs/tmp/ is different and is genuinely required. init still allocates it, and verify lists it on every run, so a repository missing it fails verification with directory is absent: refs/tmp. Nothing has written into it since ref publication moved into containers, so the scan can only ever find nothing — but the directory must exist for the scan to succeed.

Active Session

The default active-session paths are:

active/default/queue.wal
active/default/active.lock
active/default/ref-name

These files are runtime-written, not guaranteed members of a bare initialized repository.

queue.wal stores exact signed Patch envelopes before sealing. WAL records are load-bearing local session state: they are the pending changes that seal replays and publishes, but they are not sealed history until publication succeeds.

ref-name records which local branch ref owns a non-empty active WAL. Missing or malformed active ref metadata on a non-empty WAL is an integrity issue because seal must not guess the publication target.

active.lock is a local synchronization file. It prevents concurrent active-session writers, but it is not evidence of repository history or trust. Stale lock cleanup after a crash is manual today; see concurrency and locking for the current operator boundary.

Trust Store

The repository-local MAINTAINER trust containers are:

trust/keys.container
trust/policy-{a,b}.container

Both are allocated empty at init, then appended to by prikk trust maintainer add/remove — no name is created after init.

keys.container holds one append-only entry per adopted key id: the key id and its Ed25519 public key. It has no slot pair and is never compacted — TOFU history must persist across key removal (see Compaction below).

policy-{a,b}.container holds a sequence of complete policy snapshots in its live slot (named by this container’s own generation log, not always a) — each add or remove appends the entire current adopted-key-id list, not an incremental change; readers take the last complete snapshot, with required = 1 meaning any one adopted key’s signature suffices (never stored — it is a constant, not configurable). Seal checks the configured MAINTAINER signer against this repository-local policy before publication, and verify checks publication envelopes against the same local trust boundary.

This trust store is authority for current publication-trust checks. It is not remote trust, global identity, key rotation, hosted forge policy, or a multi-maintainer threshold system. Key revocation is supported (prikk trust maintainer remove): a removed key’s material is retained internally (so a different key presented later under the same id is still refused), but it no longer counts toward the adopted set or reserves its case-folded id.

Compaction

Three containers accumulate entries that are superseded the moment a newer one lands: pointer-index-{a,b}.container, received-index-{a,b}.container, and policy-{a,b}.container. A ref’s or received ref’s pointer is only ever its last matching entry — everything earlier for the same key is dead weight from the moment it is superseded — and a trust policy snapshot supersedes every earlier snapshot outright, since each one already carries the complete adopted-key-id list.

Each of these three containers is paired with its own generation log (pointer-index-generation.log, received-index-generation.log, policy-generation.log, all under the same directory as the container they belong to) recording which slot — a or b — is currently live. An empty generation log means no compaction has ever run for that container, and the live slot is a. A reader resolves the live slot by reading the last complete record in the generation log; it never assumes a.

prikk compact --pointer-index|--received-index|--trust-policy|--all reclaims the dead entries for one or all three: it reads the currently-live slot, keeps only what is still current (the last entry per key for the two indexes, the last snapshot for the trust policy), writes that reduced set to the other slot, makes it durable, and only then appends a generation record naming the new slot live — so a crash at any point before that append leaves the previous generation fully authoritative, and retrying the compaction from scratch is always safe. prikk compact ... --plan-only reports what a real run would reclaim without writing anything. Compaction never runs automatically; nothing else in Prikk invokes it.

Compacting one of these containers takes the same per-container lock its own writers take (see concurrency and locking), so a compaction run and an ordinary write to the same container cannot interleave, and a real run and a --plan-only preview never report stale numbers against each other.

Two container families never compact, deliberately — not because rotation is merely pending. Object containers (containers/<type>/{a,b}.container) have no data model to compact against: nothing is ever superseded or deleted (see Object Store above), so there is nothing for a second slot to reclaim. The ref log (refs/containers/log-{a,b}.container) is DC-38/DC-69’s audit trail, which must never be compacted. Both keep their b slot allocated at init and permanently unused. The trust key container (trust/keys.container) has no slot pair at all — TOFU history must persist across key removal, so it stays a single append-only file, never compacted.

Authority Model

Path or dataClassificationCurrent meaning
.prikk/FORMATFormat gateRequired by repository open; 6 is the current format. Formats 1-5 are rejected at open, with no bridge and no in-place migration.
containers/<type>/a.containerContent-addressed object authorityOne checksum-framed record per object. Authority when the frame checksum verifies, the envelope validates, and its computed id/type match the requested object. objects/ is a retired remnant no longer created by init; authority for nothing.
refs/containers/log-a.container (log-b.container reserved)Publication evidenceShared, append-only signed RefUpdate records for every ref, interleaved; authoritative only with valid chain, object, signature, and trust checks.
trust/policy-{a,b}.container and trust/keys.containerRepository-local trust authorityCurrent local MAINTAINER trust input for seal and verify publication-trust checks. policy-{a,b} is compacted by prikk compact --trust-policy (the live slot is named by its own generation log); keys.container has no slot pair and is never compacted — TOFU history must persist across key removal.
active/default/queue.walLocal active-session statePending signed Patch envelopes before seal; load-bearing for the active session, not sealed history.
active/default/ref-nameLocal active-session metadataIdentifies which ref owns a non-empty active WAL; not sealed history.
refs/containers/pointer-index-{a,b}.containerMutable convenience pointerShared, append-only, last-entry-wins RefState pointer for every ref; fast current-state lookup and recovery target; not trusted without RefState/ref-log checks. Compacted by prikk compact --pointer-index; the live slot is named by its own generation log, not always a — see Compaction.
refs/containers/received-index-{a,b}.containerMutable convenience pointerSame shape as the ref-pointer index, for imported remotes/<name> refs (prikk bundle import); not consulted by verify_repository directly. Compacted by prikk compact --received-index.
*-generation.log (pointer-index-, received-index-, policy-)Compaction stateRecords which slot (a/b) is currently live for its own container; empty means a. Not history or trust evidence — see Compaction.
active/default/active.lock, refs/locks/*.lock, and the four container locks (pointer-index.lock, log.lock, received-index.lock, policy.lock, all under refs/containers/ or trust/)Local synchronizationPrevent concurrent writers, and (for the container locks) a concurrent prikk compact run; not history or trust evidence. Recoverable after a crash via prikk unlock — see concurrency and locking.
cache/Initialized, rebuildable, non-rootNever authority; a corrupt or absent cache file is not an error and does not change any result.
refs/tmp/Initialized, unwritten, requiredinit still allocates it; nothing writes into it since ref publication moved into containers, but verify lists it on every run, so its absence fails verification. Not authority for anything.
objects/ (and its six type subdirectories), quarantine/, refs/by-id/, refs/logs/Retired, no longer initializedinit no longer creates these; nothing has written into any of them since object and ref publication state moved into containers. Not validated at open, so a repository initialized before this change keeps them harmlessly. Not authority for anything. objects/ alone still has one dormant reader — see Object Store.
gc/Deferred/not presentNo current initialized directory or released behavior.

Deferred and Not Stable

Prikk does not provide in-place or history-preserving migration between any two formats. The documented writable path is a newly initialized format-2 repository followed by deliberate worktree re-authoring, which creates new NodeIds, objects, signatures, and history. Copying .prikk/ data or editing FORMAT is not migration. This explicit transition does not promise general format stability.

prikk sync (RFC 116, RFC 117) and prikk merge (DC-74) have since shipped — see the sync and merge guides. Still deferred: garbage collection, cache rebuild semantics, quarantine enforcement, stable repository-format migration, backup/restore workflows, remote trust, hosted forge semantics, complete branch management, remote-tracking, and full cross-platform filesystem validation.

Claim-to-Source Anchors

ClaimSource anchors
Repository initialization creates the listed directories and writes .prikk/FORMAT.layout.rs, DC-31
.prikk/FORMAT selects current format 6; formats 1-5 are rejected at open.layout.rs, DC-40
Persistent objects are checksum-framed records appended into per-type containers allocated at init.layout.rs, object_store.rs
Six object types currently have initialized persistent object directories; RefUpdate is inline-only in ref logs.layout.rs, object_store.rs, refs/container.rs
Ref storage keys are SHA-256 hex digests of human-readable ref names, shared by the pointer index, the log container, and per-ref lock files.layout.rs, refs.rs
The ref-pointer index is a shared, append-only, last-entry-wins container holding every ref’s own current-pointer entry (name, RefState id, storage key).refs/pointer_index.rs, refs.rs, data model
The ref-log container is a shared, append-only sequence holding every ref’s own signed RefUpdate envelopes, interleaved, with frame magic, checksums, and per-ref replay semantics.refs/container.rs, refs.rs, durability and crash recovery
Active WAL and active ref metadata are runtime active-session state, not fresh-init files.active.rs, wal.rs, durability and crash recovery
Trust policy and maintainer public-key files are written by the trust command and define current repository-local MAINTAINER trust.trust.rs, layout.rs, security and signing setup
Verification checks object placement, ref pointer/log consistency, active WAL state, and publication trust within current limits.verify.rs, integrity and recovery diagnostics, trust and threat model
cache/ is initialized but not a root of trust; quarantine/ is retired and no longer initialized, and gc/ is not an initialized directory.layout.rs, DC-31
The received-ref index is a shared, append-only, last-entry-wins container for imported remotes/<name> pointers, kept separate from refs/by-id/ because an imported RefState’s own embedded ref name can never agree with a locally renamed pointer.received.rs, received_index.rs
Three containers (ref-pointer index, received-ref index, trust policy) each have a generation log naming which slot is live, defaulting to a when empty; prikk compact reads the live slot, writes the reduced set to the other slot durably, then appends a generation record naming it live; --plan-only performs the same read with no write. Object containers and the ref log allocate an unused b slot and never compact; the trust key container has no slot pair at all.generation.rs, compact.rs, lock.rs

Provenance

This reference implements DC-31 as a documentation-only extension of the DC-24 current-state reference series. It adds no code, schema, CLI behavior, repository behavior, trust behavior, verification behavior, repair behavior, or repository-format stability guarantee.