2026-07-08 16:35:10 +08:00
// Tier 2 contract golden tests (see docs/adr/0002-two-tier-runtime-contract.md).
//
// These lock the *composite return shapes* that agents parse and that
// references/api-reference.md documents explicitly, so the upcoming TypeScript /
// runtime-core refactor cannot silently reshape a helper's output. Two guards
// work together:
//
// 1. Live-shape assertions: build a representative in-memory DB, call each
// helper, and assert the returned object's key set matches the contract.
// 2. Doc-sync guard: assert every contracted key name also appears in
// references/api-reference.md. If an implementation shape changes, guard (1)
// fails; if the test contract changes without updating the doc, guard (2)
// fails. Either way, changing a helper forces a doc change.
//
// Out of scope here (covered elsewhere):
// - Bare `Array<session_row>` helpers (sessions/recent/workflows/trace/thread)
// return `SELECT *` rows; their columns are pinned by db-schema.test.mjs.
import { test } from 'node:test' ;
import assert from 'node:assert/strict' ;
import { createRequire } from 'node:module' ;
import { readFileSync , writeFileSync , mkdtempSync } from 'node:fs' ;
import { tmpdir } from 'node:os' ;
import { join } from 'node:path' ;
2026-07-12 00:37:32 +08:00
import { createQueryApi , createAttuneApi } from '../packages/core/src/query.ts' ;
2026-07-08 16:35:10 +08:00
const require = createRequire ( import . meta . url );
const { DatabaseSync } = require ( 'node:sqlite' );
2026-07-12 00:28:17 +08:00
const SCHEMA = readFileSync ( new URL ( '../packages/core/src/schema.sql' , import . meta . url ), 'utf8' );
2026-07-16 17:26:23 +08:00
const API_REFERENCE = readFileSync ( new URL ( '../skill-doc/references/api-reference.md' , import . meta . url ), 'utf8' );
2026-07-08 16:35:10 +08:00
// Every key asserted below is recorded here so the doc-sync guard can confirm
// references/api-reference.md still documents it.
const assertedKeys = new Set ();
function exactKeys ( obj , expected , label ) {
expected . forEach ( k => assertedKeys . add ( k ));
assert . deepEqual (
Object . keys ( obj ). sort (),
[... expected ]. sort (),
` ${ label } : key set drifted from api-reference.md` ,
);
}
function hasKeys ( obj , expected , label ) {
const keys = new Set ( Object . keys ( obj ));
for ( const k of expected ) {
assertedKeys . add ( k );
assert . ok ( keys . has ( k ), ` ${ label } : missing documented key " ${ k } "` );
}
}
function fixture () {
const db = new DatabaseSync ( ':memory:' );
db . exec ( SCHEMA );
db . prepare ( `INSERT INTO sessions (id, title, project, project_path, started_at, ended_at, git_branch, message_count, source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)` ). run (
'sid-1' , 'Contract session' , 'quiet-zero' , process . cwd (),
'2026-06-10T10:00:00Z' , '2026-06-10T11:00:00Z' , 'main' , 5 , 'claude' ,
);
const insertMsg = db . prepare ( `INSERT INTO messages
(uuid, session_id, type, parent_uuid, timestamp, role, text, content_type, is_meta, model, agent_id, cwd, source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` );
insertMsg . run ( 'm-root' , 'sid-1' , 'user' , null , '2026-06-10T10:00:00Z' , 'user' , 'contract needle root' , 'text' , 0 , null , null , process . cwd (), 'claude' );
insertMsg . run ( 'm-child' , 'sid-1' , 'assistant' , 'm-root' , '2026-06-10T10:00:10Z' , 'assistant' , 'contract needle child' , 'text' , 0 , 'claude-opus' , 'sub-A' , process . cwd (), 'claude' );
insertMsg . run ( 'm-fail' , 'sid-1' , 'assistant' , 'm-child' , '2026-06-10T10:00:20Z' , 'assistant' , 'ran a command' , 'tool_use' , 0 , 'claude-opus' , null , process . cwd (), 'claude' );
insertMsg . run ( 'm-after' , 'sid-1' , 'assistant' , 'm-fail' , '2026-06-10T10:00:30Z' , 'assistant' , 'recovered after failure' , 'text' , 0 , 'claude-opus' , null , process . cwd (), 'claude' );
db . prepare ( `INSERT INTO tool_calls (id, message_uuid, session_id, name, input_json, file_path)
VALUES (?, ?, ?, ?, ?, ?)` ). run ( 'tc-read' , 'm-child' , 'sid-1' , 'Read' , '{"file_path":"/x/file.ts"}' , '/x/file.ts' );
db . prepare ( `INSERT INTO tool_calls (id, message_uuid, session_id, name, input_json, file_path)
VALUES (?, ?, ?, ?, ?, ?)` ). run ( 'tc-bash' , 'm-fail' , 'sid-1' , 'Bash' , '{"command":"boom"}' , null );
db . prepare ( `INSERT INTO tool_results (tool_use_id, message_uuid, session_id, content, file_path, is_error)
VALUES (?, ?, ?, ?, ?, ?)` ). run ( 'tc-bash' , 'm-fail' , 'sid-1' , 'command failed' , null , 1 );
db . prepare ( `INSERT INTO subagents (agent_id, session_id, parent_tool_use_id, agent_type, description, duration_ms, total_tokens)
VALUES (?, ?, ?, ?, ?, ?, ?)` ). run ( 'sub-A' , 'sid-1' , 'tc-read' , 'general-purpose' , 'a subagent' , 100 , 200 );
db . prepare ( `INSERT INTO workflows (run_id, session_id, task_id, script, result_json, timestamp, agent_count, status, workflow_name)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)` ). run ( 'wf-1' , 'sid-1' , 'task-1' , 'export const meta={}' , '{"ok":true}' , '2026-06-10T10:05:00Z' , 1 , 'done' , 'demo' );
db . prepare ( `INSERT INTO workflow_agents (agent_id, run_id, session_id, agent_type, description, phase, label, model, state, duration_ms, tokens, tool_calls)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` ). run ( 'wa-1' , 'wf-1' , 'sid-1' , 'worker' , 'agent' , 'Find' , 'find:x' , 'claude-opus' , 'done' , 50 , 10 , 2 );
db . prepare ( `INSERT INTO summaries (id, session_id, timestamp, source, content)
VALUES (?, ?, ?, ?, ?)` ). run ( 'su-1' , 'sid-1' , '2026-06-10T10:06:00Z' , 'away_summary' , 'a summary' );
db . prepare ( `INSERT INTO memories (id, session_id, project, path, summary, created_at)
VALUES (?, ?, ?, ?, ?, ?)` ). run ( 'mem-1' , 'sid-1' , 'quiet-zero' , '.obelisk/memories/x.md' , 'Decision: contract fixture memory.' , '2026-06-10T10:07:00Z' );
return db ;
}
test ( 'search() hit shape matches api-reference.md' , () => {
const db = fixture ();
const hit = createQueryApi ( db ). search ( 'needle' , { limit : 1 })[ 0 ];
exactKeys ( hit , [ 'message' , 'session' , 'rank' , 'context' ], 'search() hit' );
exactKeys ( hit . message , [ 'uuid' , 'text' , 'content_type' , 'is_meta' , 'role' , 'timestamp' , 'model' , 'cwd' , 'source' ], 'search() hit.message' );
exactKeys ( hit . session , [ 'id' , 'title' , 'project' , 'started_at' , 'source' ], 'search() hit.session' );
assert . ok ( Array . isArray ( hit . context ), 'search() hit.context is an array' );
db . close ();
});
test ( 'context() shape matches api-reference.md' , () => {
const db = fixture ();
const ctx = createQueryApi ( db ). context ( 'm-child' );
exactKeys ( ctx , [ 'message' , 'parentChain' , 'session' , 'subagent' , 'workflow' ], 'context()' );
assert . ok ( Array . isArray ( ctx . parentChain ), 'context() parentChain is an array' );
assert . equal ( ctx . parentChain [ 0 ]. uuid , 'm-root' );
db . close ();
});
test ( 'fileHistory() row shape matches api-reference.md' , () => {
const db = fixture ();
const row = createQueryApi ( db ). fileHistory ( '/x/file.ts' )[ 0 ];
exactKeys ( row , [ 'toolCall' , 'session' , 'timestamp' ], 'fileHistory() row' );
exactKeys ( row . toolCall , [ 'id' , 'message_uuid' , 'name' , 'input_json' ], 'fileHistory() row.toolCall' );
exactKeys ( row . session , [ 'id' , 'title' , 'project' ], 'fileHistory() row.session' );
db . close ();
});
test ( 'failures() row shape matches api-reference.md' , () => {
const db = fixture ();
const row = createQueryApi ( db ). failures ()[ 0 ];
exactKeys ( row , [ 'toolCall' , 'result' , 'session' , 'nextMessages' ], 'failures() row' );
assert . ok ( Array . isArray ( row . nextMessages ), 'failures() row.nextMessages is an array' );
assert . equal ( row . nextMessages [ 0 ]. uuid , 'm-after' );
db . close ();
});
test ( 'subagents() row carries messageCount' , () => {
const db = fixture ();
const row = createQueryApi ( db ). subagents ()[ 0 ];
hasKeys ( row , [ 'messageCount' ], 'subagents() row' );
assert . equal ( row . messageCount , 1 );
db . close ();
});
test ( 'workflowTree() shape carries result and agents with messageCount' , () => {
const db = fixture ();
const tree = createQueryApi ( db ). workflowTree ( 'wf-1' );
hasKeys ( tree , [ 'result' , 'agents' ], 'workflowTree()' );
assert . deepEqual ( tree . result , { ok : true });
assert . ok ( Array . isArray ( tree . agents ));
hasKeys ( tree . agents [ 0 ], [ 'messageCount' ], 'workflowTree() agent' );
db . close ();
});
test ( 'summaries() row carries session_title and project' , () => {
const db = fixture ();
const row = createQueryApi ( db ). summaries ()[ 0 ];
hasKeys ( row , [ 'session_title' , 'project' ], 'summaries() row' );
db . close ();
});
test ( 'overview() shape matches api-reference.md' , () => {
const db = fixture ();
const view = createQueryApi ( db ). overview ({ limit : 5 });
exactKeys ( view , [ 'current' , 'current_project' , 'projects' , 'totals' ], 'overview()' );
exactKeys ( view . current , [ 'cwd' , 'project' ], 'overview() current' );
exactKeys ( view . totals , [ 'projects' , 'sessions' , 'memories' , 'sources' ], 'overview() totals' );
db . close ();
});
test ( 'forget() result shape matches api-reference.md' , () => {
const db = fixture ();
const api = createAttuneApi ( db );
const forgotten = api . forget ({ id : 'mem-1' , reason : 'contract test' });
exactKeys ( forgotten , [ 'id' , 'deleted_at' , 'deleted_reason' ], 'forget()' );
db . close ();
});
test ( 'raw() shape matches api-reference.md' , () => {
const dir = mkdtempSync ( join ( tmpdir (), 'obelisk-raw-' ));
const jsonlPath = join ( dir , 'session.jsonl' );
const line = JSON . stringify ({ uuid : 'm-raw' , type : 'user' , message : { role : 'user' , content : 'raw line body' } });
writeFileSync ( jsonlPath , line + '\n' );
const db = new DatabaseSync ( ':memory:' );
db . exec ( SCHEMA );
db . prepare ( `INSERT INTO sessions (id, title, project, project_path, jsonl_path, source)
VALUES (?, ?, ?, ?, ?, ?)` ). run ( 'sid-raw' , 'Raw session' , 'quiet-zero' , dir , jsonlPath , 'claude' );
db . prepare ( `INSERT INTO messages (uuid, session_id, type, role, text, content_type, source)
VALUES (?, ?, ?, ?, ?, ?, ?)` ). run ( 'm-raw' , 'sid-raw' , 'user' , 'user' , 'raw line body' , 'text' , 'claude' );
const result = createQueryApi ( db ). raw ( 'm-raw' );
exactKeys ( result , [ 'text' , 'totalLength' , 'offset' , 'limit' , 'hasMore' ], 'raw()' );
assert . equal ( result . text , line );
assert . equal ( result . totalLength , line . length );
db . close ();
});
test ( 'doc-sync guard: every asserted contract key appears in api-reference.md' , () => {
// Runs after the shape tests have populated assertedKeys. Guarantees the test
// contract and the authoritative doc cannot drift apart silently.
assert . ok ( assertedKeys . size > 0 , 'expected contract keys to have been asserted' );
const missing = [... assertedKeys ]. filter ( k => ! API_REFERENCE . includes ( k ));
assert . deepEqual ( missing , [], `keys asserted in tests but absent from api-reference.md: ${ missing . join ( ', ' ) } ` );
});