|
|
@@ -249,7 +249,7 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => {
|
|
|
|
|
|
it('path-traversal session ids are neutralized (no escape from root)', async () => {
|
|
|
const evil = SessionId('../../etc/pwn')
|
|
|
- const m = { version: 1, id: evil, createdAt: 1 }
|
|
|
+ const m = { version: 0, id: evil, createdAt: 1 }
|
|
|
await ctx.sessionPersistence.create(m)
|
|
|
await ctx.sessionPersistence.append(evil, oneTurnLog())
|
|
|
// The file lives UNDER root, not at ../../etc.
|
|
|
@@ -310,7 +310,7 @@ describe('SessionPersistenceJsonl: scanLog unit', () => {
|
|
|
|
|
|
it('a seq gap after the last turn/end bounds the preserved tail (torn fragment tolerated)', () => {
|
|
|
const log = [
|
|
|
- JSON.stringify({ type: 'session', version: 1, id: 'g', createdAt: 1 }),
|
|
|
+ JSON.stringify({ type: 'session', version: 0, id: 'g', createdAt: 1 }),
|
|
|
JSON.stringify({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } } }),
|
|
|
JSON.stringify({ type: 'step/start', seq: 2, time: 2, data: { turn: 1, step: 1 } }), // gap: missing seq 1
|
|
|
].join('\n') + '\n'
|
|
|
@@ -323,7 +323,7 @@ describe('SessionPersistenceJsonl: scanLog unit', () => {
|
|
|
|
|
|
it('rejects a seq gap BEFORE a later committed turn/end (committed data damaged)', () => {
|
|
|
const log = [
|
|
|
- JSON.stringify({ type: 'session', version: 1, id: 'g2', createdAt: 1 }),
|
|
|
+ JSON.stringify({ type: 'session', version: 0, id: 'g2', createdAt: 1 }),
|
|
|
JSON.stringify({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } } }),
|
|
|
JSON.stringify({ type: 'step/start', seq: 2, time: 2, data: { turn: 1, step: 1 } }), // gap: missing seq 1
|
|
|
JSON.stringify({ type: 'turn/end', seq: 3, time: 3, data: { turn: 1, reason: { kind: 'completed' } } }),
|
|
|
@@ -335,7 +335,7 @@ describe('SessionPersistenceJsonl: scanLog unit', () => {
|
|
|
|
|
|
it('rejects a corrupt line BEFORE a later committed turn/end (committed data damaged)', () => {
|
|
|
const log = [
|
|
|
- JSON.stringify({ type: 'session', version: 1, id: 'c', createdAt: 1 }),
|
|
|
+ JSON.stringify({ type: 'session', version: 0, id: 'c', createdAt: 1 }),
|
|
|
'{not json', // corrupt, sits in the committed region (a turn/end follows)
|
|
|
JSON.stringify({ type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'completed' } } }),
|
|
|
].join('\n') + '\n'
|
|
|
@@ -343,7 +343,7 @@ describe('SessionPersistenceJsonl: scanLog unit', () => {
|
|
|
})
|
|
|
|
|
|
it('a header-only log (no event lines at all) preserves nothing — committedBytes is the header', () => {
|
|
|
- const log = JSON.stringify({ type: 'session', version: 1, id: 'h0', createdAt: 1 }) + '\n'
|
|
|
+ const log = JSON.stringify({ type: 'session', version: 0, id: 'h0', createdAt: 1 }) + '\n'
|
|
|
const scanned = scanLog(Buffer.from(log))
|
|
|
expect(scanned.events).toEqual([])
|
|
|
// committedBytes falls back to the header line's end (no preserved events).
|
|
|
@@ -352,7 +352,7 @@ describe('SessionPersistenceJsonl: scanLog unit', () => {
|
|
|
|
|
|
it('a corrupt line after the last turn/end bounds the preserved tail', () => {
|
|
|
const log = [
|
|
|
- JSON.stringify({ type: 'session', version: 1, id: 'c2', createdAt: 1 }),
|
|
|
+ JSON.stringify({ type: 'session', version: 0, id: 'c2', createdAt: 1 }),
|
|
|
JSON.stringify({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } } }),
|
|
|
'{not json', // corrupt crash fragment, no turn/end committed
|
|
|
].join('\n') + '\n'
|
|
|
@@ -363,7 +363,7 @@ describe('SessionPersistenceJsonl: scanLog unit', () => {
|
|
|
|
|
|
it('tolerates a seq gap AFTER a turn/end (uncommitted tail)', () => {
|
|
|
const log = [
|
|
|
- JSON.stringify({ type: 'session', version: 1, id: 't', createdAt: 1 }),
|
|
|
+ JSON.stringify({ type: 'session', version: 0, id: 't', createdAt: 1 }),
|
|
|
JSON.stringify({ type: 'turn/start', seq: 0, time: 1, data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } } }),
|
|
|
JSON.stringify({ type: 'turn/end', seq: 1, time: 2, data: { turn: 1, reason: { kind: 'completed' } } }),
|
|
|
JSON.stringify({ type: 'step/start', seq: 9, time: 3, data: { turn: 2, step: 1 } }), // gap in uncommitted tail
|
|
|
@@ -442,7 +442,7 @@ describe('SessionPersistenceJsonl: edge cases', () => {
|
|
|
// field is tolerated by the header type guard) and confirm list() reads it.
|
|
|
const bucket = join(root, '_no-cwd')
|
|
|
await mkdir(bucket, { recursive: true })
|
|
|
- const bigHeader = JSON.stringify({ type: 'session', version: 1, id: 'big', createdAt: 1, pad: 'x'.repeat(9000) })
|
|
|
+ const bigHeader = JSON.stringify({ type: 'session', version: 0, id: 'big', createdAt: 1, pad: 'x'.repeat(9000) })
|
|
|
await writeFile(join(bucket, 'big.jsonl'), bigHeader + '\n')
|
|
|
const ids = (await ctx.sessionPersistence.list()).map(x => x.id)
|
|
|
expect(ids).toContain('big')
|