Skip to content

status: fix FSMonitor history and clean-proof gaps - #74

Closed
ttaylorr-oai wants to merge 465 commits into
codex-unstablefrom
tb/codex/fsmonitor-hardlink-inodes-unstable
Closed

status: fix FSMonitor history and clean-proof gaps#74
ttaylorr-oai wants to merge 465 commits into
codex-unstablefrom
tb/codex/fsmonitor-hardlink-inodes-unstable

Conversation

@ttaylorr-oai

@ttaylorr-oai ttaylorr-oai commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Current candidate

Head: 5f1339f46d. This pull request remains review-only; do not merge or
enable auto-merge
. Fresh approval is required for this head before
controller admission.

Two additional patches address command patterns that prevented proof reuse:

  • Recognize empty filter.<driver>.clean and process with
    required=false as complete read-side disabling, without requiring a
    smudge override. Scoped FSMonitor reuse still requires a filter-free scope.
  • Certify a newly created worktree index under its mandatory writer lock,
    including when GIT_OPTIONAL_LOCKS=0. Later read-only status calls reuse
    the paired proof without writing the index or scanning tracked files.

Regression controls cover active filters, provider errors and resets,
post-checkout edits, partial overrides, and both hash algorithms.

Focused native validation passed:

  • t7519-status-fsmonitor.sh: 123 tests
  • t7527-builtin-fsmonitor.sh: 200 tests
  • t7530-status-clean-sidecar.sh: 67 tests
  • worktree add/prune/list/move/config and reset suites
  • unit tests, including the 406-case Clar suite; test lint and diff checks

Hosted push and pull-request CI each completed with 45 successful jobs and
two expected skips. Style and whitespace checks passed.

A fresh clean developer-mode full suite passed: 33,507 successes, zero
unexpected failures, and 378 known breakages. Full status and FSMonitor
suites also passed at both commit boundaries, as did a separate incremental
build through both patches.

Two executable Git workflows passed on each of macOS and Linux, covering
staging, stash, conflicting rebase, linked worktrees, merge, and worktree
removal. Optimized results matched conservative oracles, and the required
read-only checks left the index unchanged. The macOS workflows used the
same sealed artifact as the latency campaign.

Qualification remains open. An earlier incremental full-suite run failed
the linked-worktree stash-history test in t7519. Its failing assertion
was not captured. Subsequent clean, incremental, and paired stress runs
pass; branch traces do not support the proposed cause in these patches.
Three further full t7519 repeats per commit also passed. The simulated
review identified no supported source fix; the original failure remains
recorded and unexplained.

Same-base macOS latency qualification is still open. The corrected
four-repository run passed its oracle, provider-fence, identity, and
read-only-index checks, but did not meet the mean-latency thresholds for
clean writable and dirty read-only status. Median and p95 limits passed.
No samples were discarded and no threshold was relaxed.

A bounded diagnostic captured multi-second stalls in both builds before
Git initialized its process clock; the exact startup cause is unresolved.
These observations are not a latency pass. No release will be admitted
before the remaining latency qualification and fresh approval.

Earlier implementation and validation notes

Summary

  • reject missing clean-status snapshot paths before calling open(2)
  • serialize asynchronous simple-IPC start and stop transitions, make the
    shutdown wake reliable across EINTR, and contain SIGPIPE during gentle
    client writes
  • flush the Darwin FSEvents stream and drain its serial callback queue before
    accepting a query boundary, with bounded timeout, daemon retirement, token
    generation checks, and request coalescing

Controller scope

This review-only pull request presents one prerequisite and two topic patches
on top of 1293167e46. Approve it for controller admission, but do not merge
it into codex-unstable.

Performance

Overlapping Darwin queries whose cookies are already registered share one
provider fence. A timeout or shutdown intersection falls back conservatively
and retires the daemon. Paired same-base latency qualification is being run on
this exact source and binary; this description does not make a latency claim
before that evidence completes.

Validation

At commit 7bed8a334f:

  • clean build
  • t0052-simple-ipc.sh (11/11)
  • immediate-close client stress (200/200 on one socket path and 200/200 on
    unique paths)
  • forced-EINTR shutdown wake stress (50/50, with 50 verified injections)
  • test and diff checks

The preceding candidate, 08f907411e, completed unit tests (406/406),
t7519-status-fsmonitor.sh (110/110),
t7527-builtin-fsmonitor.sh (194/194),
t7530-status-clean-sidecar.sh (59/59), and the
ASan+UBSan clean_status_manifest unit tests (4/4). The only tree change from
that candidate is replacing an open-coded allocation in
t/helper/test-simple-ipc.c with CALLOC_ARRAY() as required by static
analysis. Production sources, FSMonitor tests, and the FSMonitor topic
patch-id are unchanged.

Exact-head hosted CI, same-base latency qualification, and executable workflow
qualification are in progress.

ttaylorr-oai and others added 30 commits August 25, 2026 18:07
Untracked status used one helper both to traverse the worktree and
to copy untracked and ignored entries into status output. Checking
whether a traversal actually validated the repository's UNTR cache
requires that directory walk without copying results or recording
user-facing timing.

Factor the walk into wt_status_collect_untracked_1() with an explicit
collection flag. Return whether the traversal used the index's own
untracked cache, and populate the result lists and advice timing
only when collection is requested.

Retain wt_status_collect_untracked() as the collecting wrapper.
Every existing production caller still requests collection, so
status output and ordinary traversal behavior remain unchanged.
Token adoption and validation-only production use are not added
by this preparatory patch.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
An external attributes file can be missing, reached through a symbolic
link, or redirected when an ancestor is replaced. Hashing its contents
alone cannot distinguish stable absence from a changed containing
namespace, or identical bytes reached through a different path.

Extend the filesystem-identity primitives from S06/P03 to
capture the lstat identity or absence of every component of an absolute
path. Compare snapshots component by component and hash their explicit
states and canonical identity fields with length-delimited framing.
Reject capture with EAGAIN when the platform cannot report reliable
object identity instead of hashing fabricated identity fields.

Expose whether the final component exists and release all snapshot
storage explicitly. Tests verify equal snapshots and hashes, a missing
target that subsequently appears, and an ancestor replacement that
changes the namespace even when the replacement has identical content.

The snapshot is independently testable. It does not read an external
attribute source or establish a status speedup.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
S11/P01 retains scan results for later processing, but treats
every changed stat observation as a pending content check. A nonzero
cached size that differs from the observed size already proves that a
tracked file changed. Racy timestamps, zero cached sizes, type changes,
and the Windows symlink-size sentinel cannot establish that conclusion.

Classify an entry as definitively modified only when its mode and type
remain comparable, its cached size is nonzero, and match_stat_data()
reports an actual data-size difference. Pass that terminal state
through the existing preload result, skip its speculative restat, and
emit a separate definitive-modification Trace2 count. Preserve
ordinary content verification for every ambiguous observation.

Update the APFS dirty-file test to require the new terminal
classification, no speculative lstat, and the still-required
authoritative refresh. This verifies the new state at its first
consumer without claiming that status already consumes it directly.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
read_one() allocates a subtree array even for leaf cache-tree nodes.
It also inserts each serialized child through cache_tree_sub(), which
searches children that the writer already emits in increasing order.

Allocate a child array only for non-leaf nodes and append increasing
child names directly. Retain subtree_nr + 2 pointer slots for each
non-leaf, but allocate them without zeroing because only populated
slots are inspected. Keep cache_tree_sub() as the compatibility
fallback for older, unsorted input.

Existing t/t0090-cache-tree.sh tests exercise ordinary cache-tree
decoding. This change adds no dedicated unsorted-input regression or
isolated benchmark.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
Once refresh_cache_ent() verifies that an entry's content still
matches, it allocates and copies a replacement, fills its stat data,
and preserves a caller-cleared CE_VALID bit under assume_unchanged.
Keeping that sequence in one caller would require another verified
refresh path to duplicate the allocation and validity handling.

Extract make_refreshed_cache_entry() as a private helper and keep
refresh_cache_ent() as its first consumer. Pass !ignore_valid through
the existing condition so the entry name, observed stat data, and
CE_VALID behavior remain unchanged.

This is a behavior-preserving refactor. It introduces no new index
write, configuration, test claim, or independent performance claim.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
A provider token obtained before a tracked or untracked scan does not
cover worktree changes racing with that scan. Publishing it as an FSMN
or FSUC proof can make a later status trust an index or untracked-cache
snapshot that was never valid at that boundary.

Keep bootstrap tokens pending while tracked entries are refreshed and
any rooted untracked cache is traversed. For builtin providers, query
again after the scans, apply intervening paths, and repeat the affected
scans until a clean boundary is found or three closing queries are
exhausted.

Treat a trivial closing reply as complete invalidation followed by
another scan; accept its replacement token only after a later clean
reply. Reject provider errors, incomplete cache proofs, and exhausted
retries with strong invalidation and complete fallback scans. Hook
providers cannot perform a closing IPC query, so accept their token
only after a complete tracked and applicable untracked collection;
reject failed or trivial hook replies.

A matching on-disk FSUC token can now authorize replay of recursive UNTR
validity established by S01. Reconstruct that validity only after the
entire extension has decoded, and only for directories without a cached
per-directory exclude digest. This lets a warm status prune known-empty
subtrees while still rechecking a changed .gitignore, including changes
made through an unwatched hardlink alias. Trust an indexed exclude's
metadata alone only when its identity is reliable and it has exactly
one link; otherwise retain the complete content-hash check.

Route both status collection and commit index refresh through the shared
closure. Preserve ordinary behavior for existing paired state,
path-limited requests, and ignored-mode collection. Cover clean and
changed closures, trivial replies, retry exhaustion, provider errors,
on-disk FSMN/FSUC publication, warm empty-subtree pruning, descendant
events, and cached exclude changes.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
An external attributes file can change conversion without changing a
worktree attribute manifest. Content alone is also insufficient: an
ancestor or linked target can be replaced, and a missing source is safe
to reuse only while its containing namespace remains stable.

Capture the normalized absolute-path namespace with S07/P09
before and after observing each enabled source. For a present source,
require nonblocking-open support, a regular singly linked file below
the attribute-file limit, and matching descriptor, pathname, and target
identities. Read the entire file into one allocation.

Record source configuration and contents in one framed digest, and
component and target identities in a separate namespace digest.
Recheck the complete namespace for stable missing sources. Enabled
sources inherit the namespace capture's fail-closed identity check;
disabled sources remain unobserved and safely digestible. Reject
instability rather than publishing an incomplete fingerprint.

Register the fingerprint library and Clar suite in both Make and Meson.
Tests separate content from metadata changes, detect an altered
ancestor of a missing source, preserve disabled-source digests, and
exercise both object formats. This does not select repository attribute
sources or integrate fingerprints into status.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
add_patterns() rejects pattern files larger than 100 MiB only after
allocating and reading their complete contents. An oversized filesystem
input can therefore exhaust the memory the limit is meant to protect,
or terminate Git when GIT_ALLOC_LIMIT rejects the allocation.

Check the size obtained from fstat() before allocating a filesystem
pattern buffer. Preserve the existing warning, close the descriptor,
and return the existing failure result. Keep the later size check for
index-backed fallback data, whose size is unavailable before it is
read.

Strengthen the existing EXPENSIVE regression by reading its 101 MiB
.gitignore under GIT_ALLOC_LIMIT=1m. The old ordering dies in
xmallocz(); the early rejection preserves the expected warning without
attempting the oversized allocation.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
A complete APFS preload can already prove that tracked entries are
deleted or have definitive size changes. Ordinary status nevertheless
refreshes those entries and later asks worktree diff to rediscover them.
Skipping refresh without preserving ambiguous content checks would
either duplicate work or misreport metadata-only changes.

Request terminal-result deferral explicitly from porcelain status and
retain a complete per-entry result on its index. Let refresh defer
proven modifications and deletions while marking ambiguous entries for
the ordinary content check. Insert terminal changes into the normal
status change list before running worktree diff, temporarily mark only
those entries up to date, and restore their flags afterward. Clear
retained results before another preload and release them with the
index. Other refresh callers keep their existing behavior.

Extend the APFS tests to assert direct modified and deleted results and
no redundant refresh stats. Add a metadata-only mismatch that must
still reach worktree diff and produce clean porcelain output. Retained
terminal state trades additional temporary memory for removing the
second classification of proven changes.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
The index extension worker decodes TREE and UNTR serially even though
their parsers read the same immutable mapping and publish to different
index_state fields. An unconditional additional worker would consume
cache-entry workers and interfere with split-index assembly.

Use the bounded framing from S02/P01 to select exactly one
TREE and one UNTR extension. Require extension-offset metadata and at
least four index workers; start an additional TREE worker only when
both payloads reach 1 MiB. Leave at least two cache-entry workers
available and join the TREE worker before unmapping the index.

Keep LINK, duplicate or missing extensions, insufficient workers,
small payloads, and auxiliary-worker creation failures on the existing
serial path. Malformed framing still reports index file corruption.

Allow GIT_TEST_PARALLEL_INDEX_EXTENSIONS to bypass only the payload
threshold. Add a PTHREADS, UNTRACKED_CACHE, and SHA1 regression that
compares parallel and serial status, cache-tree, and untracked-cache
results and checks the extension/parallel/tree-untracked Trace2 marker.
The regression unsets GIT_TEST_SPLIT_INDEX because split indexes
intentionally remain on the serial path.

The eligible path adds one auxiliary worker and its stack. The
benchmark covers the complete series, not this patch in isolation.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
Bulk preload defers metadata-mismatched entries to run_diff_files()
for a content check. When writable status confirms that such an entry
is clean, it still leaves old stat data in the index. The next status
must therefore repeat a content check already known to match.

Request DIFF_UPDATE_INDEX_STAT only when status holds the index lock
and bulk preload covers every indexed entry. After a real stat and a
successful content and mode check, refresh only entries marked
CE_CONTENT_CHECK_REQUIRED. Build the replacement with the helper from
S15/P01 and install it with replace_index_entry(), preserving
existing CE_VALID and index-change handling.

Read-only status, incomplete bulk scans, dirty entries, and ordinary
diff callers keep their existing behavior. The APFS regression compares
both status output and the written index with ordinary status, and
requires one bulk content check with no refresh-time lstat.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
S08 depends on S07 and S05. S05 now contains the former S01/P06
prerequisite, so join only the two independent histories before
applying S08.

Keep every fsmonitor test registration in sorted order in Makefile and
t/meson.build. In builtin/commit.c, wt-status.c, and wt-status.h, start
the attribute snapshot and UNTR preload before the token-closing
refresh, and preserve each state through cleanup.
A clean-status configuration digest cannot establish which index it
describes while it remains detached from the repository and index that
will consume it. External attribute content and namespace must also be
recorded before an index can reuse conversion-dependent history.

Attach a finalized, repository-bound digest at the beginning of
do_read_index(), fingerprint the system, global, and info attribute
sources, and store the resulting state on the index. Ignore an
unfinalized digest, another repository's digest, and a second
attachment. Release the state with release_index().

Extend the existing clean-status configuration unit suite to exercise
repository binding, one-shot attachment, semantic and attribute hashes,
unsafe-filter state, and index-lifetime cleanup. Register the new
production object with both Make and Meson.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
Index attachment cannot recover the configuration seen by git status or
git commit if their callbacks finish without recording it. A separate
configuration pass could also bind a different stream from the one
that established the commands' existing behavior.

Wrap each existing status or commit callback so the original callback
and clean-status digest consume the same key, value, and context.
Finalize and stage the digest after the existing configuration pass
and before either command reads its index.

Preserve determine_whence(), advice_enabled(), the original callback,
configuration order, and option handling. The index-owned attachment
and its existing configuration unit coverage are supplied by
S08/P01; this patch adds no command-specific regression.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
An index with a null trailing checksum cannot be bound to the file that
was actually read unless the platform supplies a durable file identity.
Treating a directory, multiply linked file, or unsupported platform as
equivalent would turn identity comparison into an unwarranted
correctness guarantee.

Add clean_status_identity_from_stat() for single-link regular files and
make clean_status_identity_is_durable() return true only on Apple
platforms. Keep unsupported platforms explicitly ineligible instead
of inferring durability from stat fields alone.

Register the identity object and its unit suite with Make and Meson.
The tests reject directories and multiply linked files, accept a
single-link regular file, and check the appropriate platform result.
Actual null-checksum index verification remains a separate change.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
With index.skipHash enabled, a null trailing checksum cannot prove that
verify_index_from() reopened the index that do_read_index() parsed.
Replacing the pathname between those operations can otherwise make an
unread index appear valid.

Record the identity from the index reader's existing fstat() result.
When verifying a null-checksum index on an Apple platform, compare it
with the identity from the verifier's existing file observation.
Reject an absent, nonregular, multiply linked, or replaced identity.

Leave checksummed indexes and platforms without durable identities on
their existing paths. Reuse the identity classification from
S08/P03 without adding an index-read system call. Register
the new object and unit suite with Make and Meson; the unit test
replaces the index pathname and checks the unsupported fallback.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
A filesystem-monitor token does not establish that saved configuration,
conversion rules, attribute inputs, or their complete manifest still
describe the current index. Accepting duplicate, stale, or partially
bound history could let status trust cached worktree state under
different semantics.

Recognize the FSCF index extension and delegate malformed-record
rejection to the bounded clean-proof parser from S07/P07.
Publish its token, configuration and semantic hashes, attribute hash,
and manifest only after the complete record validates. Reject
duplicate records, and adopt a manifest only when the current token,
hashes, complete proof flags, and filter policy all agree. Record
stronger semantic mismatches and withhold incoherent history.

Integrate validation into post_read_index_from(), release all owned
record and manifest storage with the index, and document the extension
layout. Register the history object and unit suite with Make and Meson.
A SHA-1 fixture rejects duplicate records; a SHA-256 fixture accepts
coherent history and detects a changed semantic hash.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
Reading a validated FSCF record is not enough to preserve it during a
generic index rewrite. Writing fresh token or stat bindings before the
current provider token is revalidated would claim a semantic proof that
the index has not established.

Write a newly bound FSCF extension only when configuration, attributes,
the complete manifest, the valid provider token, and its revalidated
token all agree. Otherwise preserve an existing validated record with
its token and stat bindings cleared; never serialize malformed or
missing history.

Add the extension to the existing index writer. Extend the history
unit tests to distinguish closed proofs from preserved unbound
manifests. Add a test-tool round trip and t7519 coverage that read,
write, and reread a coherent FSCF record through a real index.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
move_index_extensions() transfers extensions to a replacement index,
but index-owned FSCF history would otherwise remain on the old state.
A generic rewrite could silently discard a validated manifest, while
sharing its storage would create a lifetime hazard.

Copy only a parsed, valid serialized record into independently owned
destination storage. Reload the saved manifest through its validated
parser, copy the existing token and hashes, and leave an absent or
invalid source untouched. Invoke the transfer from
move_index_extensions() so ordinary index release owns each copy.

Extend the existing history unit suite with a real extension transfer.
Verify the copied record and manifest, invalidate the source, reject a
second transfer from that source, and confirm that the independent
first destination remains valid.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
Fingerprinting an external attribute file while reading the index does
not prevent the attribute parser from reopening a replaced file during
preload or status collection. Cached stat data could then be evaluated
with conversion rules that the original fingerprint did not cover.

Capture the system, global, and info attribute bytes and namespace once
and keep the immutable snapshot active from untracked-cache preload
through collection. Parse snapshot lines with the ordinary attribute
rules, including byte-order marks, embedded NULs, and line endings.
End the snapshot and release its bounded source buffers with status.

Make a failed capture or changed attribute content sticky and
invalidate fsmonitor validity and the untracked cache before ordinary
refresh. Preserve hook-provider behavior when semantic history is
absent or only the namespace changes: hooks have no closing query and
retain their reported-path contract. An observed content change still
invalidates hook-derived state.

Add t7531 integration coverage for file-parser parity, missing
attribute history, an observed hook-time attribute change, and the
hook missing-history exception. Update the existing history unit test
to exercise the public strong-mismatch predicate. The namespace-only
hook branch has no dedicated regression in this patch.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
An fsmonitor token can mark an entry valid even when the index has no
coherent history for the configuration and attributes that determine its
content. With minimal stat checks, a same-size rewrite can then be
reported as clean.

Rebuild the attribute manifest for expanded indexes during IPC
bootstrap. Compare it with the current in-process or retained on-disk
manifest, invalidate only the tracked and untracked scopes whose
attribute sources changed, and preserve the last complete manifest when
a rebuild fails.

A legacy index with no FSCF extension is different from a mismatched
proof: it contains no claim about semantic history to disprove. When it
also has a valid nontrivial FSMN token with core.trustctime enabled and
full core.checkStat, clear FSMN validity and seed a forward baseline
through ordinary configured stat checks. This avoids hashing every
tracked file solely because the index predates FSCF.

The baseline still needs to finish in the bootstrap command. Preserve
the freshly-proven FSMN-valid bit on entries replaced by that refresh,
so that the accepted token does not defer the same migration work into
the next status.

Keep strong global invalidation for semantic or attribute mismatches,
weak stat settings, a present FSCF without complete manifest history,
provider reset or failure, manifest rebuild failure, and fresh indexes
without a prior nontrivial FSMN token. Retain ordinary provider handling
when reliable file identity is unavailable. The migration exception has
ordinary Git stat semantics rather than a content-proof guarantee;
same-size changes hidden by the platform's configured stat identity can
remain hidden at that boundary.

Add coverage for the forward-baseline lane, the weak-stat same-size
rewrite, and the refreshed baseline FSMN bits, along with unit coverage
for coherent, manifest-only, missing, and present-without-manifest
history.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
An exact clean-result sidecar must remain bound to one physical index,
but resumable fsmonitor history needs to survive a format-only
rewrite by another Git implementation. It cannot use the index checksum
or file identity as its cross-implementation key.

Promote the checksummed path snapshot operations needed by an
external store. They open the named index without following its final
symlink and retain the descriptor, then require the descriptor and
current pathname to identify the same valid index. Null checksums remain
ineligible for durable snapshot pins. When fstat identity is reliable,
retain the validated reader descriptor for process-local proof epochs
only; generic certification and persisted CSHS still require a non-null
checksum.

Define a canonical digest of the ordered logical entries.  Include the
entry count and each path, stage, object ID, mode, CE_VALID,
skip-worktree, and intent-to-add state, while excluding index encoding,
cached stat data, and acceleration-only flags.  Unsupported transient
state rejects the digest rather than disappearing with the process.

Add the checksummed CSHS codec and a local-APFS-only, nofollow,
atomically-replaced store bounded to eight 16-MiB namespace slots.  This
commit has no status caller; the following history patch restores and
saves complete checkpoints through this persistence layer.

Cover both object formats, malformed and null-checksum snapshots,
pathname replacement, logical-entry bindings, malformed and independent
checkpoint namespaces, bounded retention, and idempotent writes.

A checkpoint may contain only the required FSMN and FSCF payloads.
Skip absent optional payloads rather than handing a NULL source and
zero length to memcpy(). Extend the malformed-checkpoint unit test to
round-trip that minimal valid form before its rejection cases.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
A collapsed sparse index cannot enumerate every tracked path needed
for a complete attribute manifest. Expanding the live index would
discard the sparse representation that status is supposed to preserve.

Pin the named index with S09/P02, reread the verified index
into a scratch index, and expand only that scratch copy. Build the
complete manifest from the expanded scratch index. Check that both
the parsed scratch state and original live state still match the held
descriptor and stored trailer checksum; discard the manifest if
either check fails.

Add a sparse-checkout regression that checks the collapsed outside
entry before and after status while detecting a same-size tracked
rewrite. Extend the existing index unit case with a parsed
A-to-B-to-A mismatch. Failed snapshot validation retains ordinary
full-invalidation fallback.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
A clean provider response closes only the filesystem interval after
its starting token. It cannot certify a refresh that started before
the named index, configuration, attributes, and manifest were
captured, or one whose semantic inputs subsequently changed.

Capture the proof epoch before each refresh whose provider token may
be accepted. Pin the named index, starting token, repository
configuration, external attribute fingerprint, and complete
full-index manifest. Recheck those inputs after the closing query.
Record semantic history only for the accepted token; reject missing
or changed inputs and fall back to a complete refresh.

For a null-checksum index, let only the proof-epoch pin use the
process-local reader descriptor retained by the preceding patch. The
proof-only exception rechecks both the retained source descriptor's
original stat identity and the current named path when pinning and
closing the epoch. Generic certification and persisted CSHS continue
to reject the null trailer.

Always rebuild the manifest when physical history is unavailable, even
if the stored semantic configuration already matches. Without that
manifest, a trivial response invalidates the old binding and leaves
the closing query with no complete epoch to bind, so each later status
repeats the fallback.

Teach this lifecycle to restore and save complete external history
checkpoints through the preceding CSHS store. A restore validates the
logical index and all FSMN, UNTR/FSUC, and FSCF sections in scratch
state, then rechecks the pinned index before installing them together.
A save requires the same logical entries before and after status and a
closed, writable proof. Keep both paths dormant until a later patch
enables them only for a normal top-level status.

A retry inside a captured epoch can also lose a freshly acquired
CE_FSMONITOR_VALID bit when replace_index_entry() applies its generic
conservative invalidation. Mark proof-epoch refreshes explicitly and
restore only a validity bit acquired by the replacement itself. Changed
or rejected closures still invalidate those provisional bits before
falling back.

Register clean-status-epoch.c in Make and Meson alongside its first
production consumer in wt-status.c. Add scripted regressions for
capture-before-refresh ordering and recovery from unbound physical
history. Add unit coverage for the complete full-index manifest, the
restricted post-status logical-digest exception, retained-descriptor
lifetime, a stat-visible same-inode size change, and atomic path
replacement. Later activation patches cover external checkpoint
recovery and the immediate warm run.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
A provider event can change a tracked .gitattributes file after
status captures its conversion inputs. Accepting the resulting token
against the previous manifest can incorrectly reuse tracked validity
when a complete status would report a content change.

Record when provider invalidation expires the current manifest and
semantic proof. Before retrying token closure, rebuild that manifest
and recapture external attribute sources when their content changes.
Keep the response token pending until the new scan and current
attribute epoch are both closed. Preserve ordinary provider handling
when file identity is unreliable.

Preserve reusable manifest history across ordinary index rewrites
without retaining expired bindings. Extend the history and manifest
unit cases and the index round-trip helper. Add a scripted regression
for tracked attribute changes. Manifest or snapshot failure still
forces a complete scan.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
An IPC provider cannot safely adopt missing semantic history merely
because tracked entries are marked fsmonitor-valid. Minimal stat
checks can conceal a content rewrite, and a clean token cannot
retroactively certify workers started under different attributes.

Capture the complete proof epoch before preparing semantic workers.
Prime each worker's attribute frames and verify the starting token
and complete epoch before hashing. After a clean closing query, apply
the proof only if the pinned index, configuration, attribute
content, manifest, worktree identity, and token remain consistent.
Permit attribute-namespace bookkeeping to change only after its
source bytes and initial namespace were verified.

Accept tracked validity independently of untracked validity. Keep a
query pending when the untracked scan has not run. Leave collapsed
sparse indexes, pathspecs, ignored-file requests, unreliable file
identity, non-IPC providers, and failed proofs on ordinary closure
or complete refresh.

Add scripted regressions for adopting missing tracked history
without hiding a same-size rewrite and for preserving a collapsed
sparse index on the ordinary closure path.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
An untracked-cache preload can inspect cached excludes and directory
state before tracked semantic adoption restores verified stat data.
One provider response also cannot certify an untracked traversal
performed after the tracked scan that response closes.

Defer provider-backed untracked validation until the tracked proof
has been applied and its first query has closed. Prime the untracked
cache afterward, issue a second closing query, and recheck the full
tracked proof before accepting either result. If the later query
reports a change, invalidate both results, reprime during ordinary
closure, and retry within the existing query bound.

Factor the existing proof-current checks into the predicate used by
proof application and deferred closure. Preserve automatic untracked
preload when no provider is enabled or file identity is unreliable.
Fall back to a complete scan if untracked validation or token closure
fails.

Add prerequisite-guarded scripted cases for successful deferred
scans, failed untracked closure, and a change reported by the second
closing query.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
An as-is commit refreshes its index before running the pre-commit
hook. If the hook rewrites a tracked path without changing its size
or mtime, the later in-process status must not certify the earlier
refresh as though it covered the hook.

For a nonsplit index using an IPC provider, perform the initial
refresh through status token closure. After an invoked hook, release
the saved attribute snapshot and reopen the last accepted provider
token before status runs again. Reject unavailable token state and
invalidate the manifest, tracked semantics, and untracked cache
before falling back to a complete refresh.

Pin the post-hook named index before persisting strong invalidation.
Write refreshed state only while its held descriptor, pathname,
stored trailer checksum, and in-memory index still match. Preserve a
hook-replaced index and the existing reread. Split indexes, platforms
without reliable file identity, and non-IPC providers retain their
original initial refresh.

Add prerequisite-guarded scripted cases for successful post-hook
closure without an untracked cache, failed closure with complete
worktree refresh, and a hook that updates the index itself.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
S13 depends on S09, S11, and S12. Join those three histories once before applying S13.

S09 and S11 add adjacent cleanup and test-list entries. Release both clean-status and bulk-preload state in read-cache.c, and keep t7529 before t7531 in t/meson.build. S12 requires no further resolution.
A bulk untracked scan cannot reuse its result merely because an ignore
file has familiar stat data. A file or its parent may be replaced while
the scan runs, an absent source may appear, and repeated reads of the
same source may observe different patterns.

Record each source beneath its nearest available anchored parent, along
with its path, symlink policy, presence, size, and blob identity. Check
descriptor and parent identities while capturing an observation, then
resolve the current parent again and compare the actual source bytes at
validation. Coalesce equivalent observations and invalidate the proof
immediately when observations conflict.

Validation uses nonblocking opens, so replacing a source with a FIFO
cannot hang. Equal contents remain acceptable even if the source or its
parent has a different identity. This also preserves an empty
/dev/null and an equivalent empty FIFO; changed or missing contents,
unavailable anchored primitives, and failed parent callbacks invalidate
the proof.

Register the implementation and focused unit suite in both Make and
Meson. The tests cover source and parent replacement, stable absence,
repeated and conflicting observations, missing buffers, no-follow
policy, /dev/null, FIFO replacement, and parent-opener failure.

Signed-off-by: Taylor Blau <ttaylorr@openai.com>
The Darwin daemon treats delivery of its cookie-file event as proof
that all earlier worktree changes have been published. That assumes the
callback containing the cookie cannot overtake logically older work.

A retained FSEvents trace disproves that assumption. The cookie
callback completed before a later callback published removals that had
happened before the cookie was created. A status query could therefore
answer from incomplete event history and report a dirty worktree as
clean.

After the ordinary cookie wait, ask a long-lived worker to flush the
FSEvents stream and then drain its serial callback queue. The flush
schedules provider events; the queue drain waits for those callbacks
to finish publishing. Accept the boundary only when the cookie was
seen in the same token generation, and coalesce overlapping requests
onto a single fence.

If the bounded fence times out or intersects shutdown, return a
conservative result and retire the daemon before unsafe stream
teardown. Advertise the stronger boundary as a capability and token
suffix so new clients replace unfenced daemons while older clients
retain prefix compatibility.

Exercise split and blocked callbacks, timeout replacement, generation
reset, listener shutdown, concurrent coalescing, second-wave requests,
rename and cache scopes, and protocol compatibility. Keep status proof
tests outside the split-index matrix where that proof is deliberately
disabled, and materialize externally restored tokens before raw-index
helpers consume them. The provider fence adds work to each Darwin
query, while overlapping queries share a fence when their cookies are
already registered.
@ttaylorr-oai
ttaylorr-oai force-pushed the tb/codex/fsmonitor-hardlink-inodes-unstable branch from 08f9074 to 7bed8a3 Compare August 27, 2026 22:40
ae161e8 (fsmonitor: validate builtin daemon responses before
applying them, 2026-07-10) validates each worktree path with
verify_path(). That helper enforces index-entry rules and rejects a
.git component anywhere in a path. Filesystem providers can
legitimately report such a component for an untracked nested
repository.

The client therefore rejects the entire response after an event such
as scratch/.git/file, forcing a full worktree scan. Commands that need
a current provider boundary cannot persist a clean proof from that
query.

Validate the narrower daemon-response contract instead: require a
relative path with nonempty, non-dot components and at most one
trailing separator. Keep rejecting absolute and traversal paths, but
allow .git components that already exist in the worktree.

Cover the parser directly and exercise a real Linux daemon event
against an optional-lock-free status oracle.
@ttaylorr-oai
ttaylorr-oai force-pushed the tb/codex/fsmonitor-hardlink-inodes-unstable branch 2 times, most recently from ad39dc8 to 2ebd9f8 Compare August 28, 2026 05:07
fbd7a23 (rebase: introduce and use pseudo-ref REBASE_HEAD,
2018-02-11) records the commit currently being replayed. The
sequencer normally deletes that ref before executing each todo item.

When the final item stops for a conflict, rebase --continue commits
the resolved result. pick_commits() then reaches the end of the list
without entering another iteration and removes the rebase state
directly. The merge backend skips finish_rebase() because the
sequencer owns cleanup, so REBASE_HEAD survives a successful rebase.

The same path also strands the fsmonitor proof when index.skipHash is
enabled. After committing the resolution, the sequencer reloads the
canonical index and repairs its proof through a close-only index.lock
witness. A skip-hash witness has a null trailer and a fresh file
identity, so proof-epoch validation cannot bind it to the in-memory
index. Rebase succeeds without FSUC, and read-only status cannot repair
it.

Delete REBASE_HEAD whenever interactive-rebase state is removed. This
matches finish_rebase() cleanup and also avoids retaining a ref for an
explicitly quit operation. Propagate a failed deletion so rebase does
not report success after leaving the stale ref behind.

Give only PROVISIONAL_LOCK witnesses a real checksum. The final index
rewrite continues to honor index.skipHash, preserving the normal index
write fast path while giving proof repair an authenticated epoch.

Extend the final-conflict test to require REBASE_HEAD during
resolution, its removal after completion, and a reported failure when
the ref cannot be deleted. Exercise skip-hash proof repair after a
clean-prefix, conflicted replay in primary and linked worktrees, with
plain and configured-filter repositories.
@ttaylorr-oai
ttaylorr-oai force-pushed the tb/codex/fsmonitor-hardlink-inodes-unstable branch from 2ebd9f8 to 16d98d8 Compare August 28, 2026 05:34
A proof repair can close one provider token, collect untracked results,
reopen the token, and close it again with the same struct wt_status. If
the first closure published untracked output, the second closure tries
to publish another snapshot over it and hits:

    BUG: publishing untracked results over collected status

This is reachable from stash pop when an index writer repairs a complete
FSMonitor proof while an untracked path is present.

Before closing a required new token, discard output explicitly marked as
coming from an earlier authenticated token closure or bulk preload.
Keep the BUG for ordinary caller-collected results, which must not be
silently overwritten. Extend refresh invalidation to discard both
authenticated forms as well.

Allow the scripted provider to opt into proof repair, and add a
regression covering the two-token stash path with visible untracked
output.
An fsmonitor provider can reset while merge is reading an index with an
authenticated clean-status proof. The reset leaves that proof available
for revalidation, but merge updates the worktree before repairing it.
The resulting index can lose FSUC after a clean merge. Read-only status
cannot persist the missing proof, so every later status falls back.

A multi-strategy merge can lose the same history after preparation. An
external strategy may replace the index before it declines or reports a
conflict. restore_state() then reloads that index while rewinding the
worktree. A later built-in strategy sees the original repair decision,
but no longer has the paired proof from which to repair.

Resolved conflicts expose a separate instance of the same failure.
merge clears the resolve-undo extension before updating the worktree.
That removal sets RESOLVE_UNDO_CHANGED, which prevented checkout from
transferring an otherwise current proof. The result had neither a live
provider token nor a pending token from which the writer could repair.

Revalidate an authenticated proof before a non-fast-forward merge
updates the worktree. Repair it before built-in results are published,
after successful external strategies, and after each restore_state()
rewind. Permit transfer after the resolve-undo map has been cleared,
since removing that optional extension changes neither tracked entries
nor worktree contents. Continue rejecting a live resolve-undo map.

A repaired writer stats only entries that lack provider validation or
stat data before certifying the new index. Fast-forward merges retain
their existing path, while conflicts continue to fail closed.

Cover built-in ort with and without retained resolve-undo history,
trivial and content-level resolve merges, an external strategy that
declines, and one that leaves a three-stage conflict before a clean ort
retry. Verify that clean results remove resolve-undo data, keep a paired
proof, and leave repeated read-only status unable to rewrite the index.
378744b (status: reuse closed proofs for scoped queries,
2026-08-11) taught the untracked cache to reconcile a provider-reported
direct child without reopening its directory.  A valid cached directory
can still have a null exclude_oid when its existing contents are all
tracked, since traversal never needed to load its tracked .gitignore.
prep_exclude() interprets that null OID as proof that no per-directory
exclude exists.  The targeted refresh can therefore report a newly
created ignored file as untracked.

Before refreshing a provider-dirty cached directory with a null exclude
OID, use its tracked exclude as the expected identity and load the
worktree source.  Prefer the exact stage-zero entry, then look for a
case-folded alias on case-insensitive worktrees.  add_patterns() still
opens and hashes the actual source when only an alias exists, so a case
collision can only force invalidation.  Use the empty-blob ID for an
unmerged, non-regular, removed, or intent-to-add match so that it forces
a source read and conservative invalidation unless the source is truly
empty.

Cover both exact and case-folded tracked excludes with read-only status
calls.  They must match a cold oracle without opening the directory or
writing the index, and a changed source must invalidate the cache.  Also
pin the sparse-index boundaries: an in-cone event retains targeted
refresh without expansion, while a vivified outside-cone source takes
the existing conservative expansion path.
@ttaylorr-oai
ttaylorr-oai force-pushed the tb/codex/fsmonitor-hardlink-inodes-unstable branch from fd910c1 to b4d251b Compare August 30, 2026 00:24
The provider fence added in 7bed8a3 (fsmonitor: fence Darwin
callbacks before answering queries, 2026-08-27) calls
FSEventStreamFlushSync() from a long-lived worker. The client gives
that worker one second before it retires the daemon.

Under sustained status traffic, the synchronous provider call can
cross that deadline and return immediately afterward. The timeout
still forces a daemon restart, and the next status conservatively
scans the worktree. A retained trace showed this turning a clean
status into a 17-second outlier.

On local APFS and HFS volumes, register sticky vnode watches on each
watched root and its canonical ancestors before starting the FSEvents
stream. Use FSEventStreamFlushAsync() and wait until the callback has
published through its returned event ID and a serial queue barrier.
The worker waits on its existing condition variable, so the bounded
timeout can interrupt it without racing an uncancellable provider
call.

WatchRoot notifications have event ID zero and cannot be represented
by that monotonic token. Treat the kqueue poll as the fence's
linearization point, and reject the fence if any watched namespace
edge occurred or a watched root changed identity. This also covers a
root or ancestor moving away and back before the fence completes.

Fall back to the synchronous provider fence when the vnode proof
cannot be installed, preserving the existing conservative behavior on
unsupported filesystems and resource failures.

Exercise the positive event-ID wait, the zero-ID rename ABA, the
synchronous fallback, and 512 consecutive read-only status calls.
Require every stress-test request to complete without restarting the
daemon.
@ttaylorr-oai
ttaylorr-oai force-pushed the tb/codex/fsmonitor-hardlink-inodes-unstable branch from b4d251b to 837f89d Compare August 30, 2026 00:48
An authenticated clean-status sidecar is bound to the identity of the
index file it certifies. A stash push can restore a complete FSMonitor
and untracked-cache proof after its child processes rewrite the index,
but the existing sidecar still names the old inode. The next read-only
status rejects it and takes the slower history path even though stash
left the worktree clean.

Remember whether stash started with a regular, singly linked sidecar.
When optional locks are available and no post-index-change hook is
configured, retain the status data gathered by proof repair, commit and
reread the final index, then issue a replacement sidecar from that same
certifying scan. This avoids a second worktree traversal while binding
the proof to the final index identity. Other stash paths keep the
existing repair behavior.

Cover a scoped push with a subsequent read-only status that must take
the clean-proof fast path. Let the existing writer-proof test accept
both authenticated sidecar hits and coherent-history reuse, since both
are valid read-only fast paths.
An authenticated clean-status sidecar is bound to the identity of the
index file it certifies. During an interactive rebase, the child commit
run by "rebase --continue" can replace that index. The worktree and
index are clean when the replay finishes, but the remaining sidecar
still names the old inode. The next read-only status rejects it with a
fast-index-mismatch and falls back to the slower history path.

Remember whether the rebase started with a regular, singly linked
sidecar and persistent FSMonitor proof history. After the replay
finishes successfully, reread the final index and use the existing
writer-proof repair to publish a replacement sidecar. Only do so when
optional locks are available and no post-index-change hook is
configured, matching the existing stash guardrails.

Move the sidecar-presence check and sidecar-capable repair helper into
wt-status so stash and sequencer can share them. Cover a conflicted
interactive rebase whose continuation must publish a replacement
sidecar and whose next read-only status must hit it.
The clean-status sidecar path accepts only the main worktree. A newly
created linked worktree therefore cannot publish a proof after a full
clean scan. Later read-only status commands rescan the worktree even
though each linked worktree has its own index and sidecar path.

Accept a linked worktree only when its per-worktree gitdir remains
registered in the common directory and the registered path names the
current worktree. Reject an alternate worktree paired with a linked-
worktree gitdir; the repository fingerprint continues to bind the
resolved worktree, gitdir, common directory, index, and filesystem
identities.

New worktrees are commonly probed with "git status --short". Let the
exact top-level --short and -s forms certify empty output. A fresh
worktree index can still be racy, so write and re-read it before saving
resumable history and issuing the sidecar. This binds both proofs to the
new on-disk index epoch.

Cover issuance and optional-lock-free reuse in a registered linked
worktree, and verify that an impostor worktree sharing its gitdir falls
back.
Stash and rebase may repair and reissue a clean-status sidecar after
their primary operation has completed.  A lock, read, or proof-repair
failure in that optional work currently replaces the successful command
result.  The command then reports failure even though it has already
updated the repository and worktree.

The repair path also commits its updated index before rereading it and
issuing the sidecar.  Another writer can replace the index in that gap.
The old clean scan could then be bound to the replacement index.
A later status could hide a newly staged change.

Treat sidecar repair as best-effort after stash and rebase complete.
Retain a descriptor-backed snapshot of the index produced by each
postwrite clean scan.  Require it to match both the reread index state
and the canonical index path before installing the sidecar.  A failed
repair or intervening write therefore omits the cache and falls back to
ordinary status without changing the primary command result.  The normal
sidecar-hit path remains unchanged.

Cover lock contention after successful stash and rebase operations, and
replace the index at deterministic postwrite barriers in both repair and
status issuance paths.
@ttaylorr-oai
ttaylorr-oai force-pushed the tb/codex/fsmonitor-hardlink-inodes-unstable branch from 23f8d90 to d5c2374 Compare August 30, 2026 11:11
Clean-status sidecars require a durable index identity on local APFS.
The history behavior is still valid on other filesystems, but two tests
required index.csts after their substantive assertions passed and
failed during cleanup on Linux.

Hardlink metadata events can also arrive before status refreshes the
index. If that refresh creates a racy index, status may conservatively
withhold a sidecar until a later scan restores its process-local proof.
Requiring immediate reissuance made the test depend on provider timing.

Gate sidecar removal on local APFS. For the racy-index case, accept
either immediate reissuance or the conservative fallback, but require
clean output and recovery to a new proof within three status calls.
@ttaylorr-oai
ttaylorr-oai force-pushed the tb/codex/fsmonitor-hardlink-inodes-unstable branch 5 times, most recently from a43fd03 to 02e1d29 Compare August 31, 2026 07:30
b65fc91 (status: retain the identity of an index it rewrites,
2026-08-17) lets status keep a race-proof receipt for an index it
rewrites. Receipt preparation duplicates the writer descriptor so it can
hash the final bytes, and requires the in-memory checksum to match the
configured null trailer.

Three owned write paths can leave an otherwise valid index without a
usable clean proof. A worktree-update repair first writes a checksummed
provisional index, then reopens the lockfile write-only before the final
skipHash write while the index still records the provisional checksum.
Receipt preparation rejects both states, so scoped stash cannot publish
a sidecar for the index it installs.

Worktree add creates its linked index before the new worktree has a
closed FSMonitor provider epoch. The index has FSMonitor and
untracked-cache extensions, but lacks the authenticated clean-config
proof. Later read-only status processes remain correct, but cannot
persist that proof and repeat the full fallback on every invocation.

A post-checkout hook can also change worktree-specific configuration.
Checking the invoking worktree's settings after the hook can therefore
skip priming when the hook enables FSMonitor and the untracked cache
only in the new worktree.

A clean non-fast-forward merge repairs its authenticated index proof
before committing, but leaves the existing sidecar bound to the old
index and HEAD tree. The next read-only status rejects it with a
fast-index-mismatch and scans the semantic manifest. Only a later
writable status can replace the stale sidecar.

On Apple, add a read-write reopen operation only for the provisional
index lock so receipt preparation can read the final index. Fall back to
the original write-only reopen when read access is unavailable, allowing
the write to succeed without a receipt. Other platforms retain the
write-only reopen. Finish provisional writes through a cold helper that
restores the null object ID before the receipt-aware write.

After worktree add successfully runs the post-checkout hook, read the
linked worktree's effective FSMonitor and untracked-cache settings. If
both features are enabled, run one silent status to establish the
provider epoch and persist the complete proof. Do so only when the
caller permits optional locks; never override an explicit
--no-optional-locks request.

Factor the best-effort sidecar reissue used by rebase into wt-status.
After a successful merge commit has installed its final HEAD and index,
use that helper to authenticate the settled state. Sidecar failure still
falls back to ordinary status and never changes the merge result.

Cover receipt publication and adoption after scoped stash, preserve the
generic write-only tempfile contract, and require worktree add to honor
post-checkout index writes, linked-worktree configuration, and disabled
optional locks. Also require a clean non-fast-forward merge to publish a
sidecar that its next read-only status can consume.
@ttaylorr-oai
ttaylorr-oai force-pushed the tb/codex/fsmonitor-hardlink-inodes-unstable branch from 02e1d29 to 8055b6c Compare August 31, 2026 08:59
A command can disable worktree-to-Git filters with empty clean and
process commands and required=false. It does not need to disable smudge,
which converts in the other direction. The status fingerprint only
normalizes the four-setting form, so an otherwise equivalent three-part
override discards scoped FSMonitor history and forces a tracked-file
scan.

Recognize the complete read-side override as well. Continue to
fingerprint partial or mixed-driver overrides, and retain the
normalized-filter bit so that temporarily disabling filters cannot
publish a clean sidecar. The scoped proof must still establish that no
tracked path uses a filter.

Exercise all subsets with both hash algorithms, the three-setting diff
invocation, and active-filter write and priming attempts with either
form.
The reset used by worktree add has no index proof to repair, and its
explicit GIT_WORK_TREE prevents proof authentication. A later optional
status can establish history, but GIT_OPTIONAL_LOCKS=0 suppresses that
priming step. Subsequent read-only status calls cannot persist the
missing proof and keep repeating the tracked-file scan.

When FSMonitor and writable untracked caching are enabled, let the
checkout discover the already registered worktree. Preserve the explicit
repository environment for unsupported contexts, including relative
configuration-file overrides whose meaning would change with cwd.

For a hard reset that creates the index, certify the checkout under the
mandatory index lock before committing it. Attach the current config to
the new index state, query the provider after checkout, and reuse the
writer repair machinery to bind the tracked and untracked proofs. Allow
certification under this owned lock even when optional locks are
disabled; ordinary read-only status still cannot write or repair its
index.

Require a complete untracked scan and a closing provider query. Active
filters, provider errors, and provider resets must leave the checkout
usable without certifying it. Cover those failures, post-checkout edits,
and repeated read-only status calls that reuse the proof without writing
the index or scanning tracked files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants