round 2: pin checkpoint recognition at compile time

Move COMPACT_CHECKPOINT_SOURCE and isCompactCheckpointSource into a
cordis-free src/checkpoint.ts leaf, re-exported from the root so every
host-side consumer keeps its import. The client can then type-import the
leaf without reaching dsh-session's root, whose Context merge declares the
host sessions service and collides with the client's -- the dsh-commands/brand
shape. Renaming the plugin id now fails the client typecheck.

Also: keep recoverable summary text when a compact/summary mixes text with
other block types, and capture the seeded-history provenance seqs from the
pushes that produce them instead of deriving them by arithmetic.
This commit is contained in:
Hypatia May
2026-07-30 14:07:08 +08:00
parent 8e5c792b50
commit 91ee264e16
21 changed files with 197 additions and 108 deletions

View File

@@ -1,17 +1,15 @@
/**
* Drift trap for the compaction-checkpoint recognition rule.
* Behavioral half of the compaction-checkpoint drift trap.
*
* `TranscriptAdapter` restates the compaction seam's checkpoint source as a
* local literal because it cannot import `dsh-compact` in any form: a VALUE
* import fails the client purity gate, and a TYPE-ONLY import fails typecheck —
* `dsh-compact`'s root reaches `dsh-session`'s root, whose cordis `Context`
* merge declares the HOST `sessions: SessionStore` against the client program's
* `sessions: ISessions` (`TS2717`). This spec runs in the client TEST program,
* which does not carry that collision, and it is the only thing keeping the two
* implementations from drifting: it drives the adapter with a checkpoint built
* from the canonical `COMPACT_CHECKPOINT_SOURCE` itself, so renaming the seam's
* plugin fails HERE instead of silently deleting every compaction marker from
* the web transcript.
* `TranscriptAdapter` pins its plugin literal to the seam's own declaration at
* compile time through a type-only import of `dsh-compact/checkpoint`, so
* renaming the seam's plugin already fails `tsc`. This spec covers the same
* drift from the other side — end to end through the adapter, driving it with a
* checkpoint built from the canonical `COMPACT_CHECKPOINT_SOURCE` **value** and
* checking the seam's own predicate agrees. It runs in the client TEST program,
* which can value-import the package root; a `packages/client/*` package
* program cannot, because that root reaches `dsh-session`'s root and collides
* the host `Context.sessions` merge (`TS2717`).
*/
import { COMPACT_CHECKPOINT_SOURCE, isCompactCheckpointSource } from '@deepseek-ai/dsh-compact'

View File

@@ -225,7 +225,7 @@ describe('TranscriptAdapter', () => {
it.each([
['absent provenance', undefined],
['malformed summary blocks', compactSummary(1, [{ type: 'image', data: 'nope' }])],
['text-less summary blocks', compactSummary(1, [{ type: 'image', data: 'nope' }])],
['a whitespace-only summary', compactSummary(1, [{ type: 'text', text: ' ' }])],
['an empty summary array', compactSummary(1, [])],
['a non-array summary', compactSummary(1, 'plain string')],
@@ -240,6 +240,19 @@ describe('TranscriptAdapter', () => {
])
})
it('keeps the text of a mixed-block summary, skipping the blocks it cannot render', () => {
// ContentBlock is merge-extensible and the payload type is ContentBlock[],
// so a non-text block must not discard recoverable text beside it.
const adapter = new TranscriptAdapter()
adapter.reset([
compactSummary(1, [{ type: 'text', text: '可用摘要' }, { type: 'image', data: 'nope' }]),
checkpoint(2, 1, { start: 0, end: 0, sourceEventSeqs: [1, 0] }),
])
expect(adapter.nodes()).toEqual([
{ kind: 'compaction', seq: 2, time: 1_700_000_000_002, summary: '可用摘要' },
])
})
it('leaves the summary null when the checkpoint records no provenance at all', () => {
const adapter = new TranscriptAdapter()
adapter.reset([at(2, {