diff --git a/package-lock.json b/package-lock.json index 9f0a1a7..bf435bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "primer-brain", - "version": "0.1.3", + "version": "0.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "primer-brain", - "version": "0.1.3", + "version": "0.1.4", "license": "Apache-2.0", "dependencies": { "@agentclientprotocol/sdk": "^1.3.0", diff --git a/package.json b/package.json index e8d912d..48fd1f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "primer-brain", - "version": "0.1.3", + "version": "0.1.4", "description": "An open learner record + MCP server. The memory layer for AI tutoring. No fixed interface \u2014 the tutor builds one per child, per moment.", "license": "Apache-2.0", "type": "module", diff --git a/src/db/migrations/003_artifacts.sql b/src/db/migrations/003_artifacts.sql index 33187eb..5681d6c 100644 --- a/src/db/migrations/003_artifacts.sql +++ b/src/db/migrations/003_artifacts.sql @@ -8,6 +8,22 @@ -- reviewing an artifact writes evidence back the same way any other observation does -- (`observation.kind = 'artifact_review'`). +-- Some early records predate the artifact table entirely. The baseline schema +-- normally creates it before migrations run, but this keeps the upgrade safe +-- when a record was made by an older standalone binary. +CREATE TABLE IF NOT EXISTS artifact ( + id TEXT PRIMARY KEY, + learner_id TEXT NOT NULL REFERENCES learner(id) ON DELETE CASCADE, + session_id TEXT, + ts TEXT NOT NULL, + kind TEXT NOT NULL, + path TEXT NOT NULL, + mime TEXT, + caption TEXT, + skill_ids TEXT +); +CREATE INDEX IF NOT EXISTS idx_artifact_learner ON artifact(learner_id, ts DESC); + ALTER TABLE artifact ADD COLUMN duration_ms INTEGER; ALTER TABLE artifact ADD COLUMN mime_type TEXT; ALTER TABLE artifact ADD COLUMN prompt TEXT; -- what they were asked to read or do diff --git a/test/migrations.test.ts b/test/migrations.test.ts new file mode 100644 index 0000000..208fa21 --- /dev/null +++ b/test/migrations.test.ts @@ -0,0 +1,30 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { DatabaseSync } from 'node:sqlite'; +import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +const ROOT = join(import.meta.dirname, '..'); + +test('the artifact migration upgrades a record from before artifacts existed', () => { + const dir = mkdtempSync(join(tmpdir(), 'primer-pre-artifact-')); + const database = new DatabaseSync(join(dir, 'record.db')); + try { + // This is the only dependency 003 has after 002. The record is otherwise an + // old baseline where audio artifacts had never been introduced. + database.exec('CREATE TABLE setting (key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL, updated_by TEXT)'); + database.exec(readFileSync(join(ROOT, 'src/db/migrations/003_artifacts.sql'), 'utf8')); + + const columns = database + .prepare('SELECT name FROM pragma_table_info(\'artifact\') ORDER BY name') + .all() as { name: string }[]; + assert.deepEqual( + columns.map((column) => column.name), + ['bytes', 'caption', 'duration_ms', 'id', 'kind', 'learner_id', 'mime', 'mime_type', 'path', 'prompt', 'reviewed_at', 'reviewed_by', 'session_id', 'skill_ids', 'transcript', 'ts'], + ); + } finally { + database.close(); + rmSync(dir, { recursive: true, force: true }); + } +});