feat(session): project the inherited-history boundary into the log

A plugin owning a standalone open/close bracket cannot tell a dead marker
from a live one: an unmatched `compact/start` reads identically whether the
previous writer died mid-compaction or a compaction is running now.
`Session.firstLiveSeq` already holds that answer exactly, but only in memory.

Append the log-only `session/inherited` event at that seq from the seeded
constructor — the single waist all six seeded-start paths pass through
(resume, configured startup on a persisted id, `sessions.fork()`, a subagent
fork child, `adopt()`'s live prefix, and a bare seeded `create`). Read it
through the new `isInheritedSeq(events, seq)`.

The constructor placement means persistence needs no changes: the marker is
already in `events` when a backend captures the creation seed, so it rides
the ordinary seed path with no load-time write. It also covers fork, where
the inherited bracket's owner may still be running — the case a
persistence-layer boundary could not reach.

Activity ordering excludes the boundary through `lastActivityTime()`, since
lazy resume makes browsing a pickup and the three call sites would otherwise
float every opened session to the top of a picker or list.
This commit is contained in:
Hypatia May
2026-07-30 11:38:51 +08:00
parent 2a53806275
commit b341155652
41 changed files with 850 additions and 122 deletions
@@ -417,7 +417,8 @@ describe('replay anchors and surface folds', () => {
expect(after.surfaceDeltaTokens).toBeLessThan(0)
expectSurfaceTotal(after)
expect(before.nodes).toHaveLength(2)
expect(before.logRevision).toBe(original.events.length)
// The earlier snapshot still reports the log it measured: seed + boundary.
expect(before.logRevision).toBe(original.events.length + 1)
expect(before.surfaceDeltaTokens).toBeGreaterThan(0)
})
@@ -677,13 +678,15 @@ describe('malformed replay and listener lifecycle', () => {
content: [{ type: 'text', text: 'one' }],
source: { kind: 'user' },
}), { surfaceOp: 'append' })
expect(revisions).toEqual([2])
expect(activeMeter.measure(session).logRevision).toBe(2)
// Seed, constructor boundary, then the append above. Only the last
// published: the boundary predates store attachment, like the seed.
expect(revisions).toEqual([3])
expect(activeMeter.measure(session).logRevision).toBe(3)
await firstFiber.dispose()
const secondFiber = await ctx.plugin(TokenMeterService)
activeMeter = ctx.tokenMeter
expect(activeMeter.measure(session).logRevision).toBe(2)
expect(activeMeter.measure(session).logRevision).toBe(3)
await secondFiber.dispose()
})
})