Expand description
Source-cursor driver — assemble a SourceCursor from live workspace
state, so the brief’s changed-slice preface can steer a pass at what moved.
Engine-side port of the plugin’s computeSourceCursor (inject.mjs). For
each of a binding’s source facets it resolves the change-detection
strategy, reads the durable baseline from the destination mem’s
sync_state (keyed "<binding-id>/<facet-or-refmem>#synced", D4), computes
the changed slice against the source’s current state, and unions the
per-facet slices.
Strategies:
- git — diff the stored commit id against the source tree’s current
HEAD(subprocessgit rev-parse/git diff --name-status), with the facet scope + ingestdeny_pathspushed down as:(glob)/:(glob,exclude)pathspecs. - graph — diff the source mem’s snapshot token via the engine’s own
Engine::changes_since; reference mems are graph-detected too. - mtime — enumerate the facet’s files (minus the facet scope’s own
denies and the ingest
deny_paths, applied identically to the git strategy’s exclude pathspecs — seeenumerate_facet_files), compute a stat-map digest, memoise it under.memstead.cache/ingest/source-cursor/, and diff the current digest against the memoised baseline via the puresuper::slice::mtime_slice_outcomecore (precise, incl. deletions).
Deny invariance. Ingest deny_paths are enforced identically by every
strategy that reads a file tree — git, mtime, and refinement’s enumeration,
plus both token computations (current_primary_token / source_moved).
A file matching a deny_paths entry appears in no changed slice, no
refinement batch, and never influences the mtime digest or the
source_moved token. The graph strategy is exempt by definition:
deny_paths entries are file-path globs, but a graph source’s artifacts are
entities (entity-granular), so a file-path glob can never select one. This
exemption is designed, not an omission.
One deny dialect. A deny_paths entry is a workspace-relative glob
— the exact grammar and resolution root as a facet-scope entry, resolved by
the same [build_glob_set] / :(glob,exclude) machinery. The plugin’s
PreToolUse deny hook enforces the identical dialect against the ingest
agent’s Read/Glob/Grep, reading the active list from an engine-written cache
file. write_active_deny_file publishes that file during brief rendering
(remove-then-write, overwrite-always), so hook enforcement tracks the last
rendered ingest and is never stale. A deny entry that selects no file in
the project tree is surfaced as a rendered brief warning
(SourceCursor::dead_denies) rather than silently no-op’ing — catching
typos and un-migrated legacy bare names, never a hard error.
One empty-scope semantic. A facet with no allow patterns is
unscoped — and that is a typed refusal, identical on every file-tree
strategy: git, mtime, and refinement all decline to diff or enumerate the
whole medium (a facet_unscoped check gates it). No strategy silently emits an
empty slice, enumeration, or batch for an unscoped facet; instead the source
contributes NoSignalReason::Unscoped, which renders in the brief. A
facet that genuinely wants the whole medium writes **/*. This is a
different field from the ingest’s deny_paths: an empty deny_paths
list is valid and means “no denies” — it never trips the unscoped refusal.
Visible no-signal. Every source contributes a per-source outcome. A
genuinely-unchanged source (baseline present, nothing moved) stays silent —
the only documented silence, preserving the “brief is byte-identical to a
plain roam when nothing moved” property. Every other no-signal condition —
unscoped facet, signal:none, git failure / unknown baseline, missing graph
snapshot — is collected as a NoSignalNote and rendered distinguishably.
Load-bearing invariant: the new baseline token is only collected here
(into write_commands / reseed); it is recorded by the engine’s
set_mem_sync_state writer when projection advance completes a full pass
(D7). The driver never writes it.
Functions§
- compute_
source_ cursor - Assemble the combined
SourceCursorfor an ingest from live state: the destination mem’ssync_statebaselines and each source’s current state. - enumerate_
facet_ files - Enumerate the workspace-relative file paths a primary source’s facet scope
selects — the
mtimestrategy’s input set. Mirrors the plugin’senumerateFacetFiles: onlycodebase/filesystemmediums; the facet’s allow globs minus its deny globs, evaluated over the medium’s directory tree. Returns a sorted, de-duplicated list. An unscoped facet (no allows) yields an empty list here — but callers must not treat that as signal: the strategy layer (compute_mtime_slice/current_primary_token) refuses an unscoped facet viafacet_unscopedbefore enumerating, so the empty list is only ever reached for a genuinely-empty scoped enumeration. - medium_
base - The medium pointer resolved to an absolute base directory. Public
so init-time surfaces (CLI
projection init) can resolve a medium base exactly as the strategies do — e.g. to warn when it falls outside the workspace root. - source_
moved - Whether any of an ingest’s sources moved since its last synced pass — the
cheap, slice-free predicate the backoff uses as its additive second
trigger. Compares each source’s current token to the baseline stored in the
destination mem’s
sync_state; a source with no baseline is not “moved” (a first sync does not by itself defeat backoff). Mirrors the plugin’ssourceChangedSince. - source_
moved_ since - The generalized form of
source_moved: compare each source’s current change-detection token against the baseline stored under"<binding>/<facet>#<state>"in the destination mem’ssync_state. Thestatesuffix selects the baseline family —"synced"(the build/sync baselinesource_movedreads) or"verified"(the verify baseline). - write_
active_ deny_ file - Write the active ingest’s deny list for the plugin hook, stale-safe.