task-loop-store-data-integrity.test.mjs
143 lines 4.7 KB
Raw
sha256:baa800aedf841dbf32081aa7b2befa288ac33dfc7175ac55014c55c4d8c742f9 docs: move durable-auth freeze/evidence to local developmen… Human 11 days ago
1 /**
2 * Tier 5 — DATA-INTEGRITY: loop_ref/occurrence_key uniqueness and SD-2 instance linkage.
3 *
4 * @see docs/TASK-LOOP-STORE-CONTRACT-2G-c.md §6 — I-T-LOOP-01
5 */
6 import { describe, it, beforeEach, afterEach } from 'node:test';
7 import assert from 'node:assert/strict';
8 import fs from 'node:fs';
9 import path from 'node:path';
10 import { fileURLToPath } from 'node:url';
11 import { getRepoRoot } from '../lib/repo-root.mjs';
12 import { saveFlowStore } from '../lib/flow/flow-store.mjs';
13 import {
14 listTaskLoops,
15 assertLoopOccurrenceUniqueness,
16 } from '../lib/task/task-loop-store.mjs';
17 import { getTask } from '../lib/task/task-store.mjs';
18
19 const __dirname = path.dirname(fileURLToPath(import.meta.url));
20 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-task-loop-integrity');
21 const loopStarterDir = path.join(getRepoRoot(), 'task-loops/starter');
22 const graphStarterDir = path.join(getRepoRoot(), 'orchestrator-graphs/starter');
23 const instancesDir = path.join(getRepoRoot(), 'task-loops/starter/instances');
24 const vaultId = 'vault-loop-integrity';
25
26 describe('Task loop store — data integrity', () => {
27 beforeEach(() => {
28 fs.rmSync(tmpRoot, { recursive: true, force: true });
29 fs.mkdirSync(tmpRoot, { recursive: true });
30 });
31
32 afterEach(() => {
33 fs.rmSync(tmpRoot, { recursive: true, force: true });
34 });
35
36 it('I-T-LOOP-01: loop_school_trip instance has reciprocal loop_ref and null run_ref until agent pass', () => {
37 const dataDir = path.join(tmpRoot, 'data');
38 fs.mkdirSync(dataDir, { recursive: true });
39
40 listTaskLoops(dataDir, vaultId, {
41 visibleScopes: new Set(['personal']),
42 filterScopes: new Set(['personal']),
43 effectiveScope: 'personal',
44 starterDir: loopStarterDir,
45 graphsDir: graphStarterDir,
46 instancesDir,
47 });
48
49 const task = getTask(dataDir, vaultId, 'task_school_trip_2026_w25', {
50 visibleScopes: new Set(['personal']),
51 starterDir: instancesDir,
52 });
53
54 assert.ok(task);
55 assert.equal(task.loop_ref, 'loop_school_trip');
56 assert.equal(task.occurrence_key, '2026-W25');
57 assert.equal(task.run_ref, null);
58 });
59
60 it('assertLoopOccurrenceUniqueness detects duplicate (loop_ref, occurrence_key)', () => {
61 const dataDir = path.join(tmpRoot, 'dup');
62 fs.mkdirSync(dataDir, { recursive: true });
63
64 saveFlowStore(dataDir, {
65 vaults: {
66 [vaultId]: {
67 flows: [],
68 steps: [],
69 runs: [],
70 candidates: [],
71 projections: [],
72 tasks: [
73 {
74 schema: 'knowtation.task/v0',
75 task_id: 'task_dup_a',
76 kind: 'personal',
77 scope: 'personal',
78 status: 'pending',
79 title: 'Dup A',
80 workspace_id: 'ws-personal',
81 due_at: null,
82 run_ref: null,
83 loop_ref: 'loop_school_trip',
84 occurrence_key: '2026-W25',
85 artifact_links: [],
86 created: '2026-06-01T00:00:00Z',
87 updated: '2026-06-01T00:00:00Z',
88 truncated: false,
89 },
90 {
91 schema: 'knowtation.task/v0',
92 task_id: 'task_dup_b',
93 kind: 'personal',
94 scope: 'personal',
95 status: 'pending',
96 title: 'Dup B',
97 workspace_id: 'ws-personal',
98 due_at: null,
99 run_ref: null,
100 loop_ref: 'loop_school_trip',
101 occurrence_key: '2026-W25',
102 artifact_links: [],
103 created: '2026-06-01T00:00:00Z',
104 updated: '2026-06-01T00:00:00Z',
105 truncated: false,
106 },
107 ],
108 task_loops: [],
109 orchestrator_graphs: [],
110 },
111 },
112 });
113
114 const check = assertLoopOccurrenceUniqueness(dataDir, vaultId);
115 assert.equal(check.ok, false);
116 if (!check.ok) {
117 assert.equal(check.duplicate, 'loop_school_trip::2026-W25');
118 }
119 });
120
121 it('memory_links on loop records remain pointer-only', () => {
122 const dataDir = path.join(tmpRoot, 'pointers');
123 fs.mkdirSync(dataDir, { recursive: true });
124
125 listTaskLoops(dataDir, vaultId, {
126 visibleScopes: new Set(['personal']),
127 filterScopes: new Set(['personal']),
128 effectiveScope: 'personal',
129 starterDir: loopStarterDir,
130 graphsDir: graphStarterDir,
131 instancesDir,
132 });
133
134 const store = JSON.parse(
135 fs.readFileSync(path.join(dataDir, 'hub_flow_store.json'), 'utf8'),
136 );
137 const loop = store.vaults[vaultId].task_loops.find((l) => l.loop_id === 'loop_school_trip');
138 assert.ok(loop);
139 for (const link of loop.memory_links) {
140 assert.deepEqual(Object.keys(link).sort(), ['kind', 'ref']);
141 }
142 });
143 });
File History 1 commit
sha256:baa800aedf841dbf32081aa7b2befa288ac33dfc7175ac55014c55c4d8c742f9 docs: move durable-auth freeze/evidence to local developmen… Human 11 days ago