Multi-Branch Tracker Merge Safety
Tracked by pm-wc1r, with the integrity and concurrency fixes pm-9q2t, pm-cxyv, pm-gpo7, pm-m3nl, pm-wwfd, and pm-xdn6. Fresh-init fence ownership is tracked by pm-1w3ljt; runtime-cache index governance by pm-hous; local allocation safety by pm-khdq; fence-coverage completeness and drift detection by pm-i4fx; package-defined item coverage by pm-5rexki; non-item JSON coverage by pm-gjicmx; portable driver identity by pm-w91mvg; pending receipt validation by pm-ysqb6n; receipt classification by pm-jtwsct; direction-independent item conflict selection by pm-dlx7v7; corrected reconciliation guidance by pm-lwmstb; cross-branch id collision safety by pm-pibw; auditable merge history by pm-9j2r3b; durable conflict decisions by pm-rh98vo; continuous conformance by pm-76dnfg; workspace-wide CI enforcement by pm-pdr8t1; post-merge reconciliation by pm-mfkv92; linked-command execution trust by pm-ed28wi; this repository's own adoption by pm-iwsj.
Lossless receipt health gating is tracked by pm-baksix, and executable remediation that performs settlement is tracked by pm-r0p3at. Immutable record sealing and retained re-anchor evidence are tracked by pm-javbsq and pm-aka8m7; preferred-era receipt-summary compatibility is tracked by pm-wn3ee5. Lossless concurrent acceptance-criteria composition is tracked by pm-inn5y5. The Bun launcher and strict-health repair are tracked by pm-vks66s, with the Bun 1.3.5 package-runner recurrence tracked by pm-aaxq4n. Recency-aware scalar convergence, bounded fresh-clone decision evidence, and audited pre-durable receipt dispositions are tracked by pm-7wzb6d, pm-mg13iz, and pm-cf4t42.
pm stores project context as reviewable repository files. Concurrent agents can therefore use ordinary branches and worktrees, but tracker artifacts need semantic merge behavior: raw line merging cannot preserve TOON collection counts, JSON object structure, or append-only history hash chains.
Install the repository merge contract
Fresh pm init runs this automatically when the tracker is inside a Git
worktree. It writes the shared fence and configures the current clone before
returning success. Use pm init --no-merge-fence only when another system
deliberately owns Git merge configuration.
Existing trackers and fresh clones can install or repair the contract directly:
pm merge install
git add .gitattributes
git commit -m "chore(pm): install tracker merge drivers"
pm merge install writes an idempotent, fenced .gitattributes block and repository-local git config entries. The attributes are committed; the driver commands are clone-local, so every collaborator and fresh CI clone that performs merges must run the install command.
The clone-local driver values record the absolute runtime launcher and bundled
dist/cli.js path resolved by the installing SDK. With Node, the installer
preserves an absolute PATH symlink when it resolves to the executing runtime, so
a runtime manager can retarget that launcher during upgrades without breaking
every clone. With Bun, the installer checks the absolute executable published
by the package runner, absolute PATH entries, and the directly invoked binary
for a matching Bun version, then records its stable target. Known temporary
bun-node-* launcher copies are rejected even when their version matches.
Relative paths, inaccessible launchers, and
different runtimes are ignored. If no durable matching Bun launcher is available,
installation fails with recovery guidance before it writes Git configuration or
the shared fence; a temporary bunx runtime is never recorded as a successful
driver installation.
Git therefore does not depend
on a bare pm command or the caller's later PATH when it merges tracker data.
The item-path placeholder is stored as bare %P: Git performs the required
shell quoting when it expands the placeholder. Receipt ingestion also removes
one legacy matching quote pair so receipts written by older clone-local drivers
remain reconcilable.
pm validate --check-storage-integrity and pm health also compare every
effective driver definition with the installed SDK. A command installed by
another valid @unbrained/pm-cli package is
also accepted when its Node or Bun launcher and dist/cli.js paths exist, its manifest owns the
pm bin, the driver arguments are semantically identical, and its version
matches any exact project runtime pins. A driver inside a sibling worktree of
the same repository is rejected, even when that package is otherwise valid.
Independent global installations remain supported. A missing, malformed, or semantically stale definition is
reported even when the committed attribute fence is correct.
Health and validation name pm merge install as the repair command for missing
or drifted drivers. This upgrade behavior is tracked by
pm-rcjyft.
The installer publishes the shared .gitattributes fence only after the
clone-local driver commands are configured. Matching definitions are read
without rewriting Git configuration. Concurrent pm installers coordinate
through a clone-local lock shared by its worktrees. Each required Git write
waits up to five seconds for Git's native config-file lock, with capped backoff;
it never removes another writer's lock. Persistent contention, malformed
configuration, and permission failures return the stable
merge_git_config_unwritable error with recovery guidance and leave an absent
fence absent. This behavior is tracked by
pm-gh1424.
Use pm merge install --dry-run --json to inspect the contract in
intentionally read-only workspaces.
Independent worktree runtimes
Tracked by pm-c1dwbt.
Run pm merge install from each worktree with that worktree's intended pm
installation. When installing in a linked worktree, the SDK enables Git's
extensions.worktreeConfig and writes drivers with git config --worktree.
Existing main-worktree drivers are preserved in its config.worktree; later
installs cannot replace another worktree's runtime. Main-only core.worktree
and core.bare=true settings migrate with their original scope. Ordinary
clones retain local configuration until worktree isolation is needed.
Git subprocesses discard inherited repository-location variables so an SDK
caller's GIT_DIR cannot redirect installation or diagnosis to another clone.
Configuration migration uses multiple Git writes: a failed write is reported,
and rerunning pm merge install after resolving the config lock or permissions
completes the migration while preserving values already copied for the main tree.
Health reads Git's effective configuration, including worktree overrides.
Removing a sibling worktree does not remove a separately installed main driver.
Use pm merge install in the affected worktree to repair stale, missing, or
incompatible commands. A dry run changes neither configuration nor attributes.
Fence coverage contract
Extension assets are tracked by pm-gh1460.
Files under extensions/** use Git's built-in merge=text driver, so separate
text edits combine and overlapping edits retain normal conflict markers.
Git still detects binary content and preserves its conflicted stages. The later
.managed-extensions.json rule keeps the structured pm-json driver.
The v2 fence grammar remains stable; pm merge install refreshes stale patterns
without discarding sibling fences or restoring legacy unversioned markers.
The fenced block is generated by one shared coverage contract (buildMergeAttributePatterns) covering every mergeable artifact class: tracker-wide **/*.toon and **/*.md item patterns, explicit per-type diagnostic patterns, all tracker **/*.jsonl paths under the relationship-event driver with the more-specific history/*.jsonl rule overriding history streams, root settings.json, and every nested **/*.json document. The tracker-wide item rules protect extension-defined type folders even when a receiving branch has not installed the package that registered them; explicit type rows still make active-registry drift diagnosable. The broad JSON rules automatically cover schema, managed-extension, evaluation-corpus, and future package-owned authoritative JSON without another hand-maintained allowlist. The broad JSONL rule is required because RelationshipEventStore supports package-owned custom relative paths anywhere below the tracker root. Two mechanisms keep the committed fence from drifting out of that contract:
pm schema add-type/pm schema remove-typerefresh the fence automatically when it is installed (warningmerge_fence_refreshed); repos inside git that never installed it get the actionablemerge_fence_not_installedhint instead.pm validate(thestorage_integritycheck) audits the committed fence against the active schema's type folders and reportsvalidate_merge_fence_driftwith the exact missing/stale attribute lines when they diverge — for example after editing type folders out-of-band. Rerunpm merge installand commit.gitattributesto clear it.
Preview without mutation:
pm merge install --dry-run --json
Artifact semantics
History scalar rebasing and ignored settlement artifacts are tracked by pm-gh1553 and pm-gh1556.
| Artifact | Driver | Merge behavior |
|---|---|---|
Item .toon / .md |
pm-item-toon / pm-item-markdown |
Three-way field merge; append-like collections use set union, updated_at uses the latest timestamp, scalar conflicts select the value from the later document update with stable value order as an equal-timestamp tie-break, and canonical serialization recomputes TOON counts. |
history/*.jsonl |
pm-history |
Preserves the common prefix and both divergent suffixes, orders deterministically, then re-anchors the resulting hash chain. |
tracker **/*.jsonl except the later history/*.jsonl override |
pm-relationship |
Covers default and package-owned custom relationship event paths, unions divergent suffixes by eventId (timestamp-ordered, ours-first on ties), and renumbers sequence consecutively so the strict-sequence store loader accepts the merged stream. |
root settings.json and nested **/*.json |
pm-json |
Recursively merges objects per key. Arrays compose when both branches preserve the base and add distinct entries, so independent extension installs and evaluation additions merge without weakening edit/removal conflict detection. |
History union validates each event in its original branch before rebasing its
effects. A whole metadata-field replacement still assigns its exact value when
another branch removed that field; a repeated whole-field removal keeps its
event even when absence already satisfies its effect. This covers a branch
claim following another branch's release without losing closure or comments.
Nested paths and conditional patches remain strict. An unsafe scalar patch
refuses with history_union_rebase_conflict and SDK history_merge coordinates
(branch, one-based event line, operation, scalar-patch index and JSON pointer).
CLI diagnostics name those coordinates in the error message. Preserve all three
inputs and both item snapshots for deliberate recovery;
verify the resolved history and preview reconciliation before committing.
The tests collection has an additional execution-safety rule. Its semantic
identity excludes provenance so the same command/context does not duplicate
when branches record different authorship metadata. A test definition newly
contributed by the other merge side is persisted with
provenance.source_kind=merge_union while retaining its author, creation time,
and source ref. pm test --run and pm test-all refuse that command before
spawning a process until the receiving clone acknowledges its fingerprint or
uses the two-part project-policy plus per-run override documented in
Testing. pm validate --check-command-references reports outstanding entries. This is the threat
boundary for append-like data that becomes executable after a merge; ordinary
notes, files, and other non-executable collections retain normal set-union
behavior.
Although acceptance_criteria is serialized as a semicolon-delimited scalar
for backward compatibility, the item driver treats its parsed criteria as a
collection. Independent additions compose in branch order, removals made by
either side remain removed, the field is reported in union_fields, and a
lossless merge does not create a scalar conflict decision. This gives repeated
pm update --add-ac mutations the same multi-branch preservation guarantee as
native append-like metadata without a storage migration.
When both sides change the same item scalar differently, the driver retains the
value from the document with the later metadata.updated_at timestamp. Equal
timestamps fall back to stable value ordering, so reversing Git's ours/theirs
labels still converges to the same result. Item results, driver results, and
receipts expose conflict_resolution: latest_document_update and
requested_preference_applied: false; each conflict decision adds
retained_side and resolution_basis so review automation can distinguish a
recency decision from the stable tie-break. The caller's
requested_preference remains observable but does not override item scalar
convergence. Readers continue to accept legacy preferred_side and
stable_value_order receipts and normalize the legacy preferred key. JSON
leaf conflicts retain their explicit preferred-side policy. Git keeps the item
path conflicted so a human or coordinating agent must review the discarded
value and explicitly git add the resolution.
The exact object { "pm_item_scalar_missing": true } is reserved for
JSON-stable missing-value evidence. Item merge inputs reject that object as a
present metadata value, while the hashing primitive independently separates
present and missing domains. This prevents extension data from impersonating a
deletion without wrapping every ordinary scalar in receipt output.
The driver result's guidance always points unresolved conflicts to pm merge report. When a clone-local receipt exists, guidance includes its privacy-safe receipt and item ids for exact correlation; discarded values remain confined to the local receipt and never appear in generic logs or tracker history. Tracked by pm-fbrz7p.
For item conflicts, the driver writes a clone-local receipt below the Git
directory and a durable privacy-safe sidecar below merge-receipts/ in the
tracker. The local receipt contains retained and discarded values so recovery
does not depend on a reflog. The tracked sidecar applies the versioned
bounded_non_sensitive_scalars_v1 policy. Built-in lifecycle statuses,
priority integers from 0 through 4, bounded risk/confidence/severity ordinals,
and null are stored with both their value and matching hash. All other values
remain hash-only. value_availability distinguishes bounded_inline, mixed,
and hash_only, while the clone-local receipt remains clone_local. This lets
a fresh-clone reviewer recover ordinary control-plane decisions without
publishing titles, descriptions, custom statuses, or other potentially private
content. When both copies exist the SDK deduplicates them and prefers the
locally recoverable values. After immutable provenance matches, durable
reconciled state takes precedence over a stale clone-local pending copy only
when verified item history contains a sealed merge_reconcile event with the
same complete receipt summary and disposition. An absent, unreadable, invalid,
unsealed, or mismatched audit keeps the receipt pending in memory; inspection
rewrites neither receipt nor history. History is verified once per affected
item in each receipt scan.
Historical receipt recovery is tracked by pm-tknnzp.
Historical item-state hashes do not authenticate audit context. An older
unsealed merge_reconcile summary therefore cannot retain settled state,
even when its receipt identity, fields, and value hashes match. Editing that
context leaves the legacy item chain valid. Keep these receipts pending for
explicit reviewed migration to a modern sealed audit; never retroactively
trust the old summary or synthesize an old signature during inspection.
If compaction removed the audit, the receipt also stays pending. Preview with
pm merge reconcile --dry-run; review its summaries and repair failures and
re-apply any intended discarded values. Only with explicit review authority,
use pm merge reconcile --force --message "<reviewed receipt disposition and rationale>".
That command appends acceptance evidence; it cannot recover missing evidence
or restore discarded values. This migration guidance also applies to a
lossless receipt whose snapshot proof is unavailable.
Copy provenance includes normalized requested_preference_applied, so copies
that disagree on whether preference participated cannot share lifecycle state
even if history matches one copy. Inspection reports copy_provenance_mismatch,
and settlement refuses before either receipt is written. Legacy omission uses
the declared conflict policy's default participation value.
A local reconciled copy paired with a pending durable copy remains pending,
so an interrupted settlement can finish its durable write:
pm merge report
pm merge report --include-reconciled
The underlying public SDK exports are mergeItemDocuments, mergeHistoryStreams, mergeRelationshipEventStreams, mergeJsonDocuments, runMergeDriver, runMergeInstall, installMergeFence, findGitWorkspaceRoot, runMergeReconcile, runMergeReceiptReport, runMergeReceiptEvidenceReport, inspectMergeReceiptEvidence, listMergeReceipts, auditMergeDriverConfiguration, refreshMergeAttributeFenceIfInstalled, buildMergeAttributePatterns, and auditMergeAttributeFence from @unbrained/pm-cli/sdk. installMergeFence accepts explicit tracker and workspace roots, so custom init hosts do not depend on process cwd or CLI globals.
Collection projection is tracked by pm-pftgcz.
History union reconstructs each event in its original branch before merging
collection values with the item driver's shared policy. Positional comment,
doc and test patches from an earlier reconciliation therefore cannot overwrite
an unrelated entry at the same array index on another branch. Original event
attribution and patches remain in sealed rewrite evidence; replayed collections
use the canonical item order and receiving-side linked-test provenance.
The SDK reanchor primitive accepts projectPatch for this explicit projection
boundary. It retains the replaced patch rather than erasing its source.
Exact receipt settlement still requires an authoritative matching snapshot,
and newly merged executable tests still require clone-local trust acknowledgement.
listMergeReceipts is the compatibility projection for callers that only need
validated receipts. It cannot distinguish an empty evidence store from a store
whose candidates were all rejected. Gates and diagnostic integrations should
use inspectMergeReceiptEvidence, whose invalid_evidence_count preserves
that distinction without returning malformed contents.
runMergeReceiptEvidenceReport and pm merge report --json expose the same
loss-aware contract through complete, invalid_evidence_count, bounded
invalid_evidence[], invalid_evidence_truncated, and
clone_local_evidence_resolved. Each rejected candidate reports a stable
reason plus its clone_local, durable, or copy-consistency source. Schema and
identity failures additionally expose a bounded validation_error such as
required_fields, item_path, filename, or durable_decisions. A safe
receipt filename is returned as receipt_id; unsafe candidate names are
represented only by candidate_name_hash, and malformed contents are never
returned. The detail list is capped at 100 rows while the count remains exact,
so automated gates stay token-bounded. Directory traversal distinguishes a
truly absent store from a non-directory or unreadable ancestor on Windows and
POSIX instead of treating platform-specific ENOENT/ENOTDIR spellings as
equivalent. Full health also cross-checks the privacy-safe receipt summaries in
append-only history against valid pending and reconciled evidence. Missing
evidence emits merge_receipt_history_reference_missing:<n> with bounded item,
history-line, and receipt-id-or-hash coordinates. Recover an available receipt
from an authoritative clone or backup first. For an exact reference whose
history-event timestamp predates tracked durable receipts, pm merge reconcile --force can append a merge_reconcile audit event containing the original
item, line, receipt id, timestamp, and the closed
legacy_clone_local_only reason. Health accepts only that exact coordinate and
reports it through accepted_missing_merge_receipt_dispositions; it never
rewrites or deletes the original history. The audit event must also occur after
the referenced history line; an earlier event cannot pre-authorize a later
missing receipt. Missing post-durable evidence remains blocking and cannot be
dispositioned through this compatibility path. The CLI
exits nonzero when evidence is incomplete, even when the valid-receipt count is
zero. Current SDK implementations always emit the new field, while its optional
type preserves structural compatibility for existing typed adapters and test
fixtures. A structurally valid preferred-era history summary that contains its
complete privacy-safe receipt evidence is accepted in place: health counts it
as accepted_legacy_merge_receipt_references and does not demand an external
receipt file that the older writer never created. Incomplete, contradictory, or
modern summaries still fail closed. runMergeReceiptReport remains the
compatible valid-only report.
Portable receipt proof
Reconciliation refuses merge_receipt_not_portable before any audit write when
an active clone-local receipt lacks a validated tracked counterpart. --force
does not bypass this boundary, including for direct SDK history-repair calls.
An earlier aborted operation can leave an unreferenced local receipt whose field
hashes happen to match a later merge; those hashes do not make its evidence
portable. Review and preserve unrelated stale receipts outside the active store,
or restore the original valid durable sidecar. Never copy private local receipt
values into tracked evidence as a substitute.
inspectMergeReceiptEvidence().durable_receipt_ids identifies validated durable
counterparts whose immutable provenance matches the stored blob referenced by the Git index;
clone-local replacement refs cannot substitute different evidence.
Untracked files and mismatched staged versions cannot authorize an audit write.
unstaged_durable_receipt_ids reports valid disk candidates for a read-only
preview; clone-local-only evidence remains refused even in preview. Current readers
return both arrays; their optional interface properties preserve structural compatibility,
and a missing property provides no evidence. Health resolves committed history
references against these durable identities, so a local copy cannot hide evidence
that a fresh clone will lack. Receipt summaries remain privacy-safe.
Historical receipt reader compatibility
Tracked by pm-2xtv84 and
pm-v66zir. Version-1 preferred-era and
identified requested_preference/clone_local receipts may omit base, ours,
or theirs when the original value was undefined. Readers normalize those slots to the explicit missing-value marker
in memory. They still require retained/discarded evidence and valid collections;
reading never rewrites the original file. Collection failures expose a bounded
validation_path, such as union_fields or decisions[0]. The diagnostic
category collections is not a property that should be added to a receipt.
The writer now encodes all five scalar slots itself, including undefined
retained/discarded values supplied directly through the SDK, before JSON storage.
Paired clone-local and historical hash-only copies accept the original scalar hash encoding throughout inspection and settlement. Compatibility does not apply to durable copies declaring the modern value policy. Changed values or immutable coordinates remain copy-provenance failures. Hash-only evidence does not recover the original private value.
Receipts from an aborted rebase
Tracked by pm-466m0j and
pm-gh1405. New receipts created during a
merge or rebase capture only its operation kind, original commit, and item blob
in the typed MergeReceiptOperation contract. A plain merge captures these
before Git publishes MERGE_HEAD: Git must supply its GITHEAD_<incoming object ID> driver environment signal, HEAD must equal ORIG_HEAD, and the
driver's ours input must hash to the item blob at that commit. Missing or
ambiguous coordinates are omitted. Matching origin references and bytes alone
cannot identify a plain merge: cherry-pick and revert also invoke content
drivers. Without Git's incoming-head signal, those receipts remain unresolved
and cannot receive a plain-merge abandonment audit. Branch labels from the
driver environment are never persisted. After aborting, run:
pm merge reconcile --dry-run --json
pm merge reconcile --message "Record restored original Git state" --json
SDK consumers that exhaustively handle MergeReceiptOperation.kind must
accept both rebase and merge. Existing rebase coordinates and receipt
version remain valid; no stored receipt migration is required.
Settlement requires Git to have left every active merge/rebase/cherry-pick/revert
operation, HEAD to match the original commit, and the item in HEAD, index, and
working tree to match the original blob. The item history must already be clean.
Requiring the original HEAD also rules out a new commit incorporating the
abandoned merge's other branch.
The history transaction rechecks the proof against its exact protected item
snapshot. Apply appends an explicit merge_reconcile event with
abandoned_receipts and the reason original_git_state_restored; it then marks
both copies reconciled with that settlement reason. The result counts these in
receipts.abandoned, separately from applied merge decisions. Preview performs
the same eligibility checks and leaves both history and receipts untouched.
If receipt persistence fails after the audit commits, retry reuses the identical
verified disposition and finishes the receipt writes without appending another
audit event. An unrelated earlier audit does not satisfy this check.
The driver-created merge-receipts/<receipt-id>.json sidecar can remain
untracked after Git aborts. After reviewed settlement, commit that privacy-safe
sidecar together with the item's new history audit. Reconciliation preserves
the durable evidence for other worktrees instead of deleting the sidecar.
Applied-merge settlement has the same retry contract, tracked by
pm-gh1390. The current item must still
satisfy authoritative receipt proof; only an identical receipt context in a
sealed event from reanchored, verified history suppresses a repeated provenance
event. Matching unsealed legacy context receives a new sealed audit while its
original entry remains intact. This also works with
durable-only evidence in another worktree. Once that worktree commits the
settled sidecar and history, a receiving worktree's stale local pending receipt
cannot reopen it. A subsequent pm merge reconcile leaves all three files
unchanged.
Old receipts without operation coordinates remain pending for ordinary review.
Tracked merge-receipts/ sidecars are durable provenance and must remain tracked.
Deleting or ignoring them would lose the evidence needed by other clones.
Cross-branch id collision safety
Tracked by pm-qx95lz.
Item ids are <prefix> plus random base36 characters. Local allocation reserves
both live documents and retained history, but cannot see independent creations
on another branch. The four-character default is a small, synchronized-workspace
setting; it is not a fleet uniqueness guarantee. ids.token_length accepts 4–12.
The allocator increases width after 32 local collisions to escape local density;
this does not measure concurrent branch activity.
For a shared base containing n reserved ids and m unsynchronized creations,
choose width L so m*(m-1)/(2*(36^L-n)) stays below the desired per-sync
collision probability. This birthday union bound assumes independent uniform
allocation and all branches know the same reserved ids. At one million reserved
ids, eight characters keep 1,000 concurrent creations below one in a million;
nine characters do so for 10,000 creations. Configure the width before branching:
pm config project set ids_token_length 8
The SDK item merge refuses an empty common ancestor with
item_identity_conflict, even for equal item content: document equality alone
cannot prove a common creation. The pure merge primitive leaves inputs unchanged.
The Git driver preserves both original snapshots in visible conflict markers
and names the item in its refusal, tracked by
pm-gh1491. Staging these files does not
clear the conflict-marker health and storage failures. A copied creation or
history prefix alone never authorizes fusion of two independently introduced
identities. The history merge also
refuses different create events across its inputs, multiple creates in either
input, or creations on both sides when the ancestor lacks a create event even
when their bytes match, before fast-forwarding or reanchoring. A common ancestor
containing the shared create event remains valid.
storage_integrity detects multiple physical documents and a second create event
within one history stream, reporting its line in history_unparseable_streams
as an invalid lifecycle. This check also protects merges Git resolves without
invoking a content driver. pm get and SDK item lookup refuse multiple physical
matches with item_identity_ambiguous; the message lists tracker-relative paths
and SDK error context exposes a typed paths array. Format preference cannot
select a winner. Only candidate paths are probed, so lookup
does not enumerate every item in the workspace.
Preserve both branches when a collision is reported. Recover each item from its unambiguous source branch, create the second under a fresh id, and update its references before retrying the merge. Do not repair a fused stream merely to make its hashes pass: a new hash chain does not recover a lost identity.
Within one working tree, create and copy serialize on the candidate id and
recheck every built-in and extension-defined type folder before the
authoritative write. A raced collision fails with item_id_collision instead
of replacing the existing document.
Executable convergence evidence
pm-1tns7o replaces the tag-only property
with generated complete-document comparisons. Fixed branch snapshots are folded
in original, reversed, and rotated order, with both ours/theirs directions.
Cases cover conflicting scalar writes and bodies, collection additions, distinct
and equal timestamps, disjoint edits, and optional-field deletion. Independent
expected documents are constructed directly for every N-branch case to check
retained values and preserved context. A policy table
also asserts that the compatibility preferred_side option is directional.
The field-class inventories are pinned so changes require an explicit test update.
These properties exercise the declared three-way policies; they do not turn the document-level timestamp into per-field causal provenance. In particular, the N-branch conflict fixture compares competing writes to the same scalar set. Arbitrary histories, ordered executable test definitions with receiving-clone trust, and relationship-event ordering retain their separate conformance contracts.
Settle a lossless merge in one commit
Tracked by pm-gh1475. Pause Git before it creates the merge commit, then stage the audited settlement:
git merge --no-commit --no-ff <branch>
pm merge reconcile --stage
git diff --cached
git commit
--stage first previews reconciliation. It refuses discarded-value decisions,
untrusted or unrelated repairs, invalid storage, missing durable evidence,
unresolved Git paths, alternate Git indices, and unstaged edits to the reconciliation paths. It never
accepts --force. Only the verified history streams and receipt sidecars are
added to the index; unrelated staged changes retain their existing state.
Validated new sidecars enter the index before any audit history is written.
If apply fails, their new index entries are reverted while their files and
unrelated staged work remain available for review. Staged receipt deletions
remain operator decisions and are refused.
--stage --dry-run checks this boundary without applying or staging anything.
The result's staged_paths lists the repository-relative artifacts added.
Tracker aliases resolve before preview, so proof verification, repair and staging
use the same physical tracker root.
Both preview and apply check Git's effective ignore rules before any staging or
audit write. An ignored artifact refuses with merge_settlement_review_required,
reason: ignored_merge_artifacts, its relative path, and the owning
file:line: pattern. Remove or narrow that rule, then retry the preview. Older
workspaces may still ignore merge-receipts/ from the former clone-local receipt
policy; current receipt sidecars must travel with the merge commit. pm health
checks a representative receipt path even before a merge exists and reports
merge_artifacts_ignored with the rule coordinates. A file-specific rule is
also checked against each actual settlement artifact. Git negations and tracked
files retain their native meaning; pm neither force-adds artifacts nor rewrites
user ignore rules.
Repositories can explicitly opt into a merge-aware pre-commit hook:
pm merge install --pre-commit-hook --dry-run
pm merge install --pre-commit-hook
git merge --no-commit --no-ff <branch>
git commit
The hook runs guarded settlement only when MERGE_HEAD exists; ordinary commits
are unchanged. Installation honors Git's hooks directory, including
core.hooksPath, and preserves any hook without the pm ownership header. For
an existing hook, integrate pm merge reconcile --stage into the reviewed merge
path yourself. The installation result reports settlement_hook.path; removing
that owned hook restores the manual workflow. Hooks are clone-local and must be
installed explicitly in each clone. No hook is installed by default.
The explicit --no-commit --no-ff step is required. Git's automatic merge commit
can capture its tree before a pre-merge-commit hook stages new audit artifacts;
that hook cannot provide this single-commit guarantee. Rebase and already
committed merges continue to use the explicit reconciliation gate below.
Required post-merge gate
Reconciliation resolves receipt discovery and settlement from the requested tracker. An explicit tracker outside Git does not consume clone-local receipts from an unrelated invocation directory; see pm-q5grv5.
After every branch merge that touches .agents/pm, run:
pm merge reconcile --dry-run --json
# When the preview lists unstaged sidecars, review and stage those files first.
pm merge reconcile --message "Reconcile merged tracker histories" --json
# Required only to accept a receipt that lacks qualifying exact hash proof:
pm merge reconcile --force --message "Accept reviewed merge decisions" --json
The preview reports every drifted stream and pending receipt without mutation.
unstaged_durable_receipt_ids identifies valid filesystem sidecars awaiting
matching immutable evidence in the Git index. The preview can inspect those
sidecars, but ordinary apply refuses to cite them until they are staged. For the
default tracker their paths are .agents/pm/merge-receipts/<receipt-id>.json;
use git add -- <reviewed sidecar paths> followed by git diff --cached. Use
--stage for the guarded lossless workflow above. After ordinary apply, stage
the updated history and receipt lifecycle files together before committing.
It applies the same exact-snapshot proof requirement as apply: stale or
unprovable receipts appear as failed streams in both modes, tracked by
pm-ao0bu9.
Lossless receipts do not become discarded-value decisions and reconcile without
--force, but pm health reports merge_receipts_pending:<n> and remains
non-green until the apply pass settles them. pm history-repair cannot clear
that receipt finding. When a drifted item also has a pending receipt, the
history_drift remediation map prioritizes pm merge reconcile only
when canonical item path, changed-field, and merged-value hash evidence all
attribute that finding to one or more receipts loaded from the clone-local Git
evidence store or the authoritative durable receipt store. Every field declared by each receipt must match its current merged-value
hash, even when only a subset appears in the history reconciliation diff.
Disjoint valid receipts may collectively cover a multi-field reconciliation;
the audit and settlement then retain every individually proven receipt id.
Serialized source claims are ignored. Receipt readers validate the complete
bounded schema, safe identifiers, filename and item-path identity, timestamps,
and bounded decision structure before a sidecar enters health or
reconciliation. Reads use size-preflighted, no-follow regular-file descriptors;
durable decision values must conform to the declared bounded-inline policy or
remain hash-only. Qualifying durable-only receipts
are reloaded from the authoritative store and accepted only after exact canonical
item path, declared-field, and merged-value hash verification. Legacy receipts,
receipts whose declared fields disagree with their hashes, same-item tampering,
and drift on unrelated items fail closed to the normal pm history-repair
guidance. Health indexes authoritative evidence once by item and reconciliation
uses the same per-item groups with a fixed receipt-only worker pool, so committed
sidecars cannot amplify drift scans into unbounded parallel repair work.
The machine-executable remediation for a missing receipt reference names
pm merge reconcile --dry-run. It never publishes --force as an executable
hint: the human-readable summary explains that exact eligible pre-durable
coordinates may be dispositioned by a separately reviewed force pass. Ordinary
pending lossless receipts retain their unforced apply remediation.
Apply-mode reconciliation repeats the same proof against the exact
item snapshot used by the audited history rewrite. The audit event and
settlement include only the individually proven receipt id, so one valid receipt
cannot authorize an untrusted same-item sibling. Failed or unproven receipts
remain pending unless the coordinator explicitly reviews and supplies --force.
Snapshot verification accepts both the current presence-domain digest and the
legacy version-1 scalar digest, so already-written receipts remain repairable;
new receipts always use the collision-free presence-domain scheme.
Receipts with discarded scalar values retain the distinct
merge_decisions_unreviewed:<n> finding. Matching authoritative hash proof
allows those receipts to settle without --force; without qualifying proof,
the apply pass refuses them until the coordinator explicitly supplies --force
after review. The history_drift_merge_receipt remediation may therefore emit
an unforced apply command that clears merge_decisions_unreviewed when the
receipt proves the exact current merged values. This prevents routine repair
from hiding unfinished reconciliation while avoiding redundant force for an
already-proven canonical snapshot.
The same --force boundary also handles an unrecoverable pre-durable
clone-local-only history reference. Preview and apply results expose
missing_history_references_before, legacy_disposition_eligible,
legacy_disposition_recorded, and missing_history_references_after. When
coordinates exceed the bounded health response, explicit result guidance
requires repeated dry-run and reviewed force passes until health no longer
reports truncated coordinate details and the remaining count is zero. An apply
result cannot return ok: true while any missing reference or truncated
evidence remains.
It exits nonzero while either merge-critical validation check is non-green, so
CI and explicit post-merge hooks cannot approve unresolved receipts or drift.
The apply pass uses the audited history rewrite boundary to append a
merge_reconcile event whose patch reproduces the merged item exactly. The
event includes privacy-safe receipt provenance; a clean receipt-bearing stream
gets a no-op merge event so the merge remains addressable even when replay
already matches. The command immediately validates history drift plus storage
integrity and exits nonzero when an invariant remains red. Default Git hooks remain unchanged. The explicit merge-aware pre-commit option
above includes settlement before committing; a repository-owned post-merge hook
can still enforce reconciliation after an already-created merge commit.
The default validation surface includes storage_integrity. It fails on unreadable item documents, history conflict markers, malformed history tails, live items whose latest history operation is delete (a delete/modify resurrection candidate), and unparseable settings/schema files. This prevents the ordinary tolerant read path from turning corruption into a green gate.
A history union can be structurally valid while replayed history and the chosen item document describe different effective state. In that case inspect both authors' events before reconciliation, then run the audited repair:
pm history <item-id> --verify --strict-exit
pm history-repair <item-id> --dry-run
pm history-repair <item-id>
pm merge reconcile --dry-run --json
history-repair records the reconciliation patch and classifies its changed fields against the final item. Append-only collection unions and deterministic reordering are reported as preserved context without a data-loss warning. For native comments, docs and tests, duplicate copies of the same identified record and changes between the known local_mutation/merge_union display markers are also reported as preserved context. Author, creation time, origin, payload and unknown-field changes remain significant. This diagnosis does not authorize linked commands or relax exact receipt hashes. Fields whose replayed values are actually removed or replaced remain loud with discarded event authors/operations and recovery guidance. Re-apply any intended losing mutation as a normal pm update so it remains explicit and auditable.
History events now declare an item-hash epoch. Epochs are immutable writer
contracts, not aliases for the current item serializer: epoch 1 sorts linked
tests; epoch 2 preserves their insertion order and has both an earlier
field-frozen form and a later expanded form because the writer surface grew
before the marker advanced; epoch 3 is the current form. Verification accepts
both recorded epoch-2 forms without allowing one entry to mix them, and repair
preserves the form evidenced by each event. Unversioned streams are verified
against the supported legacy canonicalizations. Current-document comparison
selects the resolved epoch candidate that matches the chain head. A union merge
may consume suffixes written by both epoch-2 forms, but its synthesized output
uses one form for the complete re-anchored stream so every stored after_hash
is exactly the next stored before_hash. An unknown explicit epoch is
reported as unsupported_item_hash_version and repair refuses to guess. This
keeps version incompatibility distinct from item corruption and is tracked by
pm-2htk4p and
pm-2qahia.
New history events also carry record_hash_version and record_hash. Unlike
the item anchors, the record hash covers the complete immutable event: author
and agent provenance, timestamp, operation, message, context, patch, item-hash
epoch, and before/after anchors. A record that carries only one envelope field,
uses an unsupported record epoch, or changes after sealing fails verification.
Entries created before this envelope remain readable as explicit
item_state_only coverage; they are not presented as record-authenticated.
Every maintenance rewrite verifies a present record hash before changing the
stream and reseals the output. When anchors or patch representation change, the
entry's append-only reanchor_evidence retains the prior anchors, item epoch,
patch digest, and prior record envelope. Lenient repair additionally retains
the complete replaced patch operations. verifyHistoryRewriteEvidence()
reconstructs every available prior envelope and rejects a record that was
modified and merely resealed after maintenance. Redaction never copies sensitive patch
values into new evidence: it keeps the digest and drops a retained patch if the
patch itself matched a redaction rule. Compaction baselines retain the pruned
entry count and ordered stream digest plus an explicit
individual_pruned_entries_require_pre_compaction_stream limitation. A holder
of the pre-compaction stream can therefore validate the checkpoint, while the
post-compaction stream does not falsely claim that it can reconstruct content
that was intentionally pruned.
Delete versus modify policy
A delete on one branch and an edit on another is not safely resolvable by a generic file driver. The merged workspace must not silently resurrect the item. storage_integrity reports the live document plus delete-terminated history as a hard finding. The coordinator chooses one policy explicitly:
- Honor deletion: remove the live item document, keep its append-only history, and commit the resolution.
- Honor modification: restore/recreate through pm so the history contains an explicit post-delete operation and rationale.
Never remove conflict markers manually while leaving the authoritative item and history semantically inconsistent.
Runtime receipts and retention
transactions/ and checkpoints/ are per-branch crash-recovery state, not shared project context. pm init places both below the resolved tracker root in the managed .gitignore block, including custom --pm-path roots.
Each tracker sharing a repository retains its own cache exclusions and merge patterns in either initialization order. Tracker names containing Git wildcard characters are escaped as literal paths, while the generated item/history globs remain active. Unrelated user rules and curated evaluation files are preserved. These contracts are tracked by pm-nl60tbxzkjwp and pm-47wuv8awpqvj.
Ignore rules do not remove files that were already committed. pm health
(integrity) and pm validate (storage_integrity) therefore scan Git's
index for tracked files below runtime/, search/, locks/,
transactions/, and checkpoints/. A finding includes repository-relative
paths plus an exact git rm --cached -r -- ... command. That command removes
only the indexed copies; local caches and recovery receipts remain on disk
under the managed ignore fence. The intentional
search/eval-queries.json relevance-evaluation corpus remains tracked and is
excluded from this cache diagnostic.
Terminal SDK transaction journals use the same checkpoints.retention_days policy as rollback checkpoints. The default GC sweep includes both receipt classes:
pm gc --dry-run
pm gc --scope transactions --dry-run
pm gc --scope transactions
Only aged committed or compensated journals are removed. applying, compensating, unreadable, and unparseable journals are always retained so cleanup cannot destroy live recovery state.
Temporary-clone acceptance test
Before releasing merge-contract changes, validate the packed package in a temporary Git repository: initialize pm, install the merge contract, create one base item, branch twice, append disjoint metadata/history on both branches, merge, and run the strict validation commands above. Include a same-key conflict case and prove Git leaves it unresolved while the output remains parseable.
The packed first-run matrix exercises the installed README workflow, a real comment/history merge, and MCP on supported platforms.
From docs/MERGE_SAFETY.md in pm 2026.11.13. View on GitHub