chore: establish YCode replication baseline and TODO
This commit is contained in:
commit
6dc59f89ba
1118 files changed
+196002
No files matched your search
@@ -0,0 +1,110 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import path from 'node:path'
|
||||
import { DirectoryArtifactReader } from './DirectoryArtifactReader.js'
|
||||
import { createEvidence } from '../domain/evidence.js'
|
||||
|
||||
const MAX_BYTES = 32 * 1024 * 1024
|
||||
const MAX_ENTRIES = 20000
|
||||
export async function exportSelectedConversationText(projectsRoot, sessionId, includeImages = false) {
|
||||
if (!path.isAbsolute(projectsRoot) || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(sessionId)) {
|
||||
throw new TypeError('An absolute projects root and exact UUID session ID are required')
|
||||
}
|
||||
const reader = new DirectoryArtifactReader(projectsRoot)
|
||||
const matches = []
|
||||
let examined = 0
|
||||
try {
|
||||
for await (const entry of reader.entries()) {
|
||||
if (++examined > MAX_ENTRIES) throw new RangeError('Conversation inventory entry budget exceeded')
|
||||
if (entry.path.split('/').at(-1) === `${sessionId}.jsonl`) matches.push(entry)
|
||||
}
|
||||
if (matches.length !== 1) throw new Error(`Expected one exact session transcript, found ${matches.length}`)
|
||||
const entry = matches[0]
|
||||
if (entry.kind !== 'file' || !Number.isSafeInteger(entry.declaredSize) || entry.declaredSize > MAX_BYTES) {
|
||||
throw new RangeError('Selected transcript is not a bounded regular file')
|
||||
}
|
||||
const chunks = []
|
||||
let bytes = 0
|
||||
for await (const chunk of await reader.open(entry)) {
|
||||
const data = Buffer.from(chunk)
|
||||
bytes += data.length
|
||||
if (bytes > MAX_BYTES) throw new RangeError('Transcript grew beyond the byte budget')
|
||||
chunks.push(data)
|
||||
}
|
||||
const data = Buffer.concat(chunks)
|
||||
if (data.length !== entry.declaredSize) throw new Error('Transcript size changed during read')
|
||||
const text = new TextDecoder('utf-8', { fatal: true }).decode(data)
|
||||
const sha256 = createHash('sha256').update(data).digest('hex')
|
||||
let records = 0
|
||||
const imageRefs = new Map()
|
||||
for (const line of text.split(/\r?\n/)) {
|
||||
if (!line.trim()) continue
|
||||
const record = JSON.parse(line)
|
||||
if (!record || typeof record !== 'object' || Array.isArray(record)) throw new Error('JSONL record is not an object')
|
||||
if (record.sessionId && record.sessionId !== sessionId) throw new Error('Transcript contains another session ID')
|
||||
if (includeImages && record.type === 'message' && Array.isArray(record.content)) {
|
||||
for (const block of record.content) {
|
||||
const image = block?.type === 'input_image' ? block.image : undefined
|
||||
if (image?.type !== 'image_blob_ref') continue
|
||||
if (!/^[a-f0-9]{64}$/.test(image.blob_id) || !['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(image.mime)) {
|
||||
throw new Error('Unsupported or invalid transcript image reference')
|
||||
}
|
||||
imageRefs.set(image.blob_id, image)
|
||||
}
|
||||
}
|
||||
records++
|
||||
}
|
||||
const attachments = includeImages ? await readImages(projectsRoot, [...imageRefs.values()]) : []
|
||||
return createEvidence({ path: path.join(projectsRoot, entry.path), sha256, format: 'file' },
|
||||
{ id: 'rea-selected-conversation-text', name: 'REA selected local conversation transcript', version: '1+local.1' }, {
|
||||
predicateType: 'rea.conversation.selected-text', operation: 'export_selected_conversation_text',
|
||||
parameters: { projects_root: projectsRoot, session_id: sessionId, include_images: includeImages, max_bytes: MAX_BYTES, max_entries: MAX_ENTRIES },
|
||||
result: { session_id: sessionId, file: { path: entry.path, sha256, bytes, records, text }, attachments,
|
||||
scope: 'One exact session JSONL and optionally its referenced image blobs; other file contents are not read' },
|
||||
confidence: 'observed', authority: 'historical-reference',
|
||||
limitations: ['Stable directory-reader identity checks do not establish an atomic snapshot of an actively written conversation.',
|
||||
'Exports stored history only; non-image external files and live kernel state are not copied.'],
|
||||
locations: [{ kind: 'artifact-path', path: entry.path }]
|
||||
})
|
||||
} finally { await reader.close() }
|
||||
}
|
||||
|
||||
async function readImages(projectsRoot, references) {
|
||||
if (!references.length) return []
|
||||
if (references.length > 32) throw new RangeError('Too many image references')
|
||||
const root = path.resolve(projectsRoot, '../blobs')
|
||||
const selected = new Map()
|
||||
for (const ref of references) {
|
||||
const target = path.resolve(ref.blob_path)
|
||||
const relative = path.relative(root, target).split(path.sep).join('/')
|
||||
if (!new RegExp(`^${ref.blob_id.slice(0, 2)}/${ref.blob_id}\\.(png|jpg|jpeg|webp|gif)$`).test(relative)) {
|
||||
throw new Error('Image reference is outside the selected content-addressed blob root')
|
||||
}
|
||||
selected.set(relative, ref)
|
||||
}
|
||||
const reader = new DirectoryArtifactReader(root)
|
||||
const files = []
|
||||
let total = 0
|
||||
try {
|
||||
for await (const entry of reader.entries(undefined, { descendInto: directory => [...selected.keys()].some(file => file.startsWith(directory + '/')) })) {
|
||||
const ref = selected.get(entry.path)
|
||||
if (!ref) continue
|
||||
if (entry.kind !== 'file' || !Number.isSafeInteger(entry.declaredSize) || entry.declaredSize > 8 * 1024 * 1024) {
|
||||
throw new Error('Referenced image is not a bounded regular file')
|
||||
}
|
||||
const chunks = []
|
||||
for await (const chunk of await reader.open(entry)) {
|
||||
total += chunk.length
|
||||
if (total > 16 * 1024 * 1024) throw new RangeError('Image export byte budget exceeded')
|
||||
chunks.push(Buffer.from(chunk))
|
||||
}
|
||||
const data = Buffer.concat(chunks)
|
||||
const sha256 = createHash('sha256').update(data).digest('hex')
|
||||
if (sha256 !== ref.blob_id || data.length !== ref.size || data.length !== entry.declaredSize) {
|
||||
throw new Error('Image blob digest/size does not match the transcript reference')
|
||||
}
|
||||
files.push({ blob_id: ref.blob_id, mime: ref.mime, bytes: data.length, sha256, data_base64: data.toString('base64') })
|
||||
}
|
||||
if (files.length !== references.length) throw new Error('A referenced image blob is absent')
|
||||
return files
|
||||
} finally { await reader.close() }
|
||||
}
|
||||
Reference in new issue
Block a user