This plan defines the exact migration from the current extension implementation to a correctness-safe replay trust model that prevents false unchanged after compaction/tree navigation.
It is intentionally exhaustive and implementation-directed.
A post-compaction read can return:
[readcache: unchanged, ...]
when the model no longer has a proven base in active context.
Current src/replay.ts applies read metadata unconditionally:
applyReadMeta(...)always writesknowledge[path][scope] = servedHash- this includes modes:
unchanged,unchanged_range,diff
Current knowledge model stores only hash strings (no proof freshness/state), so replay cannot distinguish:
- anchor evidence (
full/baseline_fallback) from - derived evidence (
unchanged/diff) that requires prior trust.
Result: replay can incorrectly bootstrap trust from non-anchor entries inside post-compaction replay window.
unchanged/diffmust never create trust without validated base.- Trust must be branch-local and replay-derived only.
- Compaction replay boundary logic must remain deterministic.
readtool contract and baseline fallback behavior must not regress.- Persistent invalidation semantics must continue to work.
Trust is per (pathKey, scopeKey) and includes freshness sequence.
interface ScopeTrust {
hash: string;
seq: number; // replay sequence index (monotonic within replay window)
}
type ScopeTrustMap = Map<ScopeKey, ScopeTrust>;
type KnowledgeMap = Map<string, ScopeTrustMap>; // pathKey -> scope trusts- Full scope key:
full - Range scope key:
r:<start>:<end>
Range reads may derive base from either:
- exact range scope trust
- full scope trust
When both exist, choose the fresher trust (max(seq)).
For each replayed ReadCacheMetaV1 event in order (seq++):
Let:
S = scopeKeyM = modeH = servedHashB = baseHashTfull = trust(path, full)Trange = trust(path, S)if S is range
If M in {"full", "baseline_fallback"}:
- set
trust(path, S) = { hash: H, seq }
If M == "unchanged" and S == "full":
- apply only if:
BexistsTfullexistsTfull.hash == BH == B(sanity)
- then set
trust(path, full) = { hash: H, seq } - else ignore event
If M == "diff" and S == "full":
- apply only if:
BexistsTfullexistsTfull.hash == B
- then set
trust(path, full) = { hash: H, seq } - else ignore event
If M == "unchanged_range" and S is a range:
- apply only if
Bexists and one of:Trangeexists andTrange.hash == BTfullexists andTfull.hash == B
- then set
trust(path, S) = { hash: H, seq } - else ignore event
Ignore (no trust mutation).
For replayed invalidation entry { kind: "invalidate", pathKey, scopeKey }:
- If
scopeKey == full:- delete full trust for path
- delete all range trusts for path
- Else:
- delete only that specific range trust
- if no scopes remain, remove path from map
This remains branch-aware and durable through history replay.
Maintain current boundary algorithm:
- Build active branch with
sessionManager.getBranch(). - Find latest
compactionon active path. - Start replay from:
- index of
firstKeptEntryIdif present on path - else
compaction index + 1 - else
0
- index of
Important migration check:
- boundary logic stays same
- trust derivation changes to guarded transitions
In tool.ts, for request scope Sreq:
- If
Sreq == full:- candidate =
trust(path, full)
- candidate =
- If
Sreqis range:- candidateExact =
trust(path, Sreq) - candidateFull =
trust(path, full) - choose candidate with greater
seq
- candidateExact =
Then:
baseHash = candidate?.hash
Do not use unsequenced map preference; freshness tie-break is required.
- Add
ScopeTrusttype{ hash: string; seq: number }. - Change
KnowledgeMapfromMap<string, Map<ScopeKey, string>>toMap<string, Map<ScopeKey, ScopeTrust>>. - Keep
ReadCacheMetaV1schema stable (no breaking change required).
- Downstream compile failures where string hash was expected.
- replay, tool, telemetry, tests.
- Replace unconditional
applyReadMetawith guarded transition function:applyReadMetaTransition(knowledge, meta, seq)
- Add trust helpers:
getTrust(path, scope)setTrust(path, scope, hash, seq)
- Replay loop must pass monotonic
seq. - Keep invalidation processing; adjust to trust-map values.
mergeKnowledgemust mergeScopeTrustvalues.
- Guard logic must exactly match Section 4.
- Accidentally allowing
unchangedto set trust whenbaseHashmissing.
- Update base selection logic for range scope to choose freshest trust by
seq. - Replace direct hash map reads with trust-object reads.
- Preserve all fallback behavior unchanged.
- Keep metadata writing unchanged (still write
readcacheblock).
- Reintroducing exact-scope-preferred static precedence without
seqcomparison.
- Strengthen validation:
- modes
unchanged,unchanged_range,diffrequire non-emptybaseHash. - modes
full,baseline_fallbackmay omitbaseHash.
- modes
- Keep parser fail-open (invalid entries ignored by replay).
- Older malformed entries are ignored; no throws.
- Adjust
summarizeKnowledgeiteration for trust objects. - Ensure mode counting remains replay-meta based (unchanged logic).
- No semantic changes required.
- Ensure status still works with new
KnowledgeMapstructure.
- No semantic change required.
- Keep event-based runtime cache invalidation only.
Add/adjust to cover transitions:
applies_full_anchor_without_prior_trustignores_unchanged_without_full_anchorapplies_unchanged_with_matching_full_anchorignores_diff_without_matching_full_anchorapplies_diff_with_matching_full_anchorapplies_unchanged_range_with_matching_range_anchorapplies_unchanged_range_with_matching_full_anchorfull_invalidation_clears_all_scopesrange_invalidation_clears_only_range_scope
Add exact scenario:
- read file =>
mode=full - read same file =>
mode=unchanged - append compaction with
firstKeptEntryIdpointing to unchanged entry - read again
- assert next mode is
full(orbaseline_fallbackif baseline path required), notunchanged
This proves non-anchor entries cannot bootstrap trust post-boundary.
Scenario:
- establish full trust at hash H1
- establish exact range trust at older hash H0
- request same range
- assert chosen base is H1 (fresher seq)
tree-navigationselective-rangerefresh-invalidationrestart-resumecompaction-boundary
- Add failing tests for compaction/non-anchor bootstrap.
- Refactor types and replay trust transitions.
- Update tool base selection by freshness.
- Tighten metadata validation.
- Update telemetry/status compile points.
- Get unit tests green.
- Get integration tests green.
- Run full test suite + typecheck.
- Only then update
IMPLEMENTATION_SPEC.mdandIMPLEMENTATION_PLAN.md.
From repo root:
npm run typechecknpm test -- test/unit/replay.test.tsnpm test -- test/integration/compaction-boundary.test.tsnpm test -- test/integration/tree-navigation.test.tsnpm test -- test/integration/selective-range.test.tsnpm test -- test/integration/refresh-invalidation.test.tsnpm test -- test/integration/restart-resume.test.tsnpm test
All must pass before docs are updated.
Update both files to exactly match implemented behavior:
-
IMPLEMENTATION_SPEC.md- Add explicit guarded transition rules and freshness selection
- Clarify anchor vs derived modes
-
IMPLEMENTATION_PLAN.md- Add migration tasks and test IDs
- Add regression gate for compaction/non-anchor bootstrap
No doc drift allowed.
Mitigation:
- full anchor always establishes trust
- unchanged/diff only advance from proven base
- tests for expected unchanged behavior where full anchor exists
Mitigation:
- explicit seq-based candidate selection test
Mitigation:
- invalid entry ignored, never fatal
- fallback to baseline remains intact
Risk D: Hidden regressions in UI/tool rendering
Mitigation:
- preserve result shape and details.truncation behavior
- rerun compatibility tests and manual smoke reads
Complete only when all are true:
- Transition state machine implemented with guarded mode rules.
- Non-anchor post-compaction bootstrap regression test passes.
- Freshness (
seq) base selection implemented and tested. - Full suite + typecheck green.
- Spec and plan docs updated to exact behavior.
- No fallback/contract regressions detected.
If any item is unchecked, migration is not complete.