feat(session-persistence): expose readRaw for per-session artifacts
The persistence contract gains a concrete readRaw default (undefined for backends without a per-session artifact) and the JSONL backend overrides it with the decode of its physical zstd frames, so a consumer can read the stored artifact text verbatim — the session-log export depends on it.
This commit is contained in:
@@ -217,6 +217,26 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => {
|
||||
expect((await ctx.sessionPersistence.list()).map(h => h.id)).toContain(m.id)
|
||||
})
|
||||
|
||||
it('readRaw returns the stored artifact text verbatim with its original filename', async () => {
|
||||
const m = meta('raw-read', '/work')
|
||||
await ctx.sessionPersistence.create(m)
|
||||
await ctx.sessionPersistence.append(m.id, oneTurnLog())
|
||||
const raw = await ctx.sessionPersistence.readRaw(m.id)
|
||||
expect(raw).toBeDefined()
|
||||
expect(raw!.filename).toBe('session.jsonl')
|
||||
expect(raw!.meta.id).toBe(m.id)
|
||||
// Byte-identical to the physical file — never a reconstruction.
|
||||
expect(raw!.content).toBe(await readFile(rawLogPath(root, '/work', m.id), 'utf8'))
|
||||
expect(raw!.content.split('\n')[0]).toBe(JSON.stringify(toHeaderLine(m)))
|
||||
const scanned = scanLog(Buffer.from(raw!.content))
|
||||
expect(scanned.events.map(event => event.type)).toEqual(oneTurnLog().map(event => event.type))
|
||||
})
|
||||
|
||||
it('readRaw is undefined for an absent session', async () => {
|
||||
const m = meta('raw-missing', '/work')
|
||||
expect(await ctx.sessionPersistence.readRaw(m.id)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('keeps the same location on resume and gives a fork its own location', async () => {
|
||||
const parent = meta('location-parent', '/work')
|
||||
const parentLocation = ctx.sessionPersistence.locate(parent)
|
||||
|
||||
@@ -356,6 +356,27 @@ describe('SessionPersistenceJsonl: default Zstandard encoding', () => {
|
||||
expect((await ctx.sessionPersistence.load(header.id)).events).toEqual(oneTurnLog())
|
||||
})
|
||||
|
||||
it('readRaw decodes the compressed artifact back to the original JSONL text', async () => {
|
||||
const root = await freshRoot()
|
||||
const ctx = await mount(root)
|
||||
const header = meta('raw-read-zstd', '/work')
|
||||
await ctx.sessionPersistence.create(header)
|
||||
await ctx.sessionPersistence.append(header.id, oneTurnLog())
|
||||
|
||||
const raw = await ctx.sessionPersistence.readRaw(header.id)
|
||||
expect(raw).toBeDefined()
|
||||
// The logical name drops the physical encoding suffix.
|
||||
expect(raw!.filename).toBe('session.jsonl')
|
||||
expect(raw!.meta.id).toBe(header.id)
|
||||
expect(raw!.content).toBe([
|
||||
JSON.stringify(toHeaderLine(header)),
|
||||
...oneTurnLog().map(e => JSON.stringify(e)),
|
||||
'',
|
||||
].join('\n'))
|
||||
const scanned = scanLog(Buffer.from(raw!.content))
|
||||
expect(scanned.events.map(event => event.type)).toEqual(oneTurnLog().map(event => event.type))
|
||||
})
|
||||
|
||||
it('resolves the default when a programmatic wrapper bypasses Loader schema normalization', async () => {
|
||||
const root = await freshRoot()
|
||||
const ctx = new Context()
|
||||
|
||||
Reference in New Issue
Block a user