refactor(session-projection): compact checkpoint row fields to ver/seq/val

The persisted row (sessionId, key, stateVersion, observedSeq, state)
becomes (sessionId, key, ver, seq, val) — the cache medium repeats these
three names for every unit of every session, so the long forms dominated
the JSON payload. ProjectionCheckpointRow and the checkpointRow zod spec
rename together; the domain spec bumps to v3 (cache semantics: the old
medium is discarded, not migrated). The unit-facing declaration keeps
stateVersion — only the persisted/checkpoint row shape changes.
This commit is contained in:
imccyu
2026-07-28 22:45:35 +08:00
parent 931dd934e3
commit b4bc4f382e
17 changed files with 121 additions and 121 deletions
@@ -3,10 +3,10 @@
* checkpoints of every registered projection unit's state, one record per
* session on the domain data form (`session_projcache` domain — the shipped
* json backend lands it beside `workspace.json`). The cache is a fold
* shortcut, never an authority: a row is possibly stale (its `observedSeq`
* shortcut, never an authority: a row is possibly stale (its `seq`
* says how stale) but never wrong, so every write path is fail-soft (a lost
* write costs a longer tail replay on the next cold read) and a
* `stateVersion` mismatch discards the row instead of migrating it. Design
* `ver` mismatch discards the row instead of migrating it. Design
* authority: the session-projection RFC
* (.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md).
* @module @deepseek-ai/dsh-session-projection-cache
@@ -125,7 +125,7 @@ export class SessionProjectionCache extends Service {
// The block carries ONE cut: the lowest served watermark is the seq every
// value is at least current as of (under-claiming is safe under
// higher-seq-wins; over-claiming would let a stale value outrank pushes).
const asOfSeq = Math.min(...keys.map(key => (record.rows[key] as { observedSeq: number }).observedSeq))
const asOfSeq = Math.min(...keys.map(key => (record.rows[key] as { seq: number }).seq))
return { asOfSeq, values }
}
@@ -16,7 +16,7 @@ export const inject = ['invariants']
/**
* No runtime invariant: the cache's correctness relation (a stored row equals
* the registry fold at its `observedSeq`) is only checkable by re-running the
* the registry fold at its `seq` watermark) is only checkable by re-running the
* fold over the persisted log — duplicating the implementation rather than
* detecting drift — and its staleness is by design (fail-soft writes). The
* durable boundary is already schema-validated by the storage-domain layer
@@ -1,7 +1,7 @@
/**
* The session-projcache domain declaration: one `sessions` table keyed by
* {@link SessionId}, each record the full projection checkpoint for one
* session (`key → {stateVersion, observedSeq, state}` rows). The spec object
* session (`key → {ver, seq, val}` rows). The spec object
* is the single source of the domain's identity, version, and record schema;
* the storage-domain routing decides the medium (the shipped composition's
* json backend lands it at `<root>/session_projcache.json`, beside
@@ -14,17 +14,17 @@ import { SessionId } from '@deepseek-ai/dsh-session'
import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'
/**
* One persisted checkpoint row (the RFC's `(sessionId, key, stateVersion,
* observedSeq, state)` minus the two record keys). `state` is the unit's
* internal state — plain JSON by the unit contract; `z.json()` enforces that
* at the durable boundary. A row is never wrong, only possibly stale:
* `observedSeq` says exactly how stale, and a `stateVersion` mismatch
* One persisted checkpoint row (the RFC's `(sessionId, key, ver, seq, val)`
* minus the two record keys). `val` is the unit's internal state — plain
* JSON by the unit contract; `z.json()` enforces that at the durable
* boundary. A row is never wrong, only possibly stale: `seq` says exactly
* how stale, and a `ver` mismatch against the live unit's `stateVersion`
* discards it at read time (never a migration).
*/
export const checkpointRow = z.object({
stateVersion: z.number().int().nonnegative(),
observedSeq: z.number().int().gte(-1),
state: z.json(),
ver: z.number().int().nonnegative(),
seq: z.number().int().gte(-1),
val: z.json(),
})
/**
@@ -61,10 +61,11 @@ export type CheckpointRecord = z.infer<typeof checkpointRecord>
/**
* The session-projcache domain spec. Version bumps discard the whole medium
* (cache semantics: a stale or unreadable cache costs a longer tail replay,
* never a wrong value). v2 added the record's log-identity binding.
* never a wrong value). v2 added the record's log-identity binding; v3
* renamed the row fields to `ver`/`seq`/`val`.
*/
export const projectionCacheDomainSpec = defineDomain({
name: 'session_projcache',
version: 2,
version: 3,
tables: { sessions: domainTable<SessionId, CheckpointRecord>(checkpointRecord) },
})