flow-store-data-integrity.test.mjs
89 lines 3.4 KB
Raw
sha256:8915fe406161f95c1681f9469375e7bae5b28c884f00bedbdef65e4b0cd0738d docs(flow): commit FLOW-V0-SPEC.md hygiene for 7A-INT merge Human 21 hours ago
1 /**
2 * Tier 5 — DATA INTEGRITY: starter round-trip and index source-of-truth.
3 *
4 * @see docs/FLOW-STORE-CONTRACT-7A-10.md §9
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 {
12 seedStarterFlows,
13 getFlow,
14 loadFlowStore,
15 saveFlowStore,
16 } from '../lib/flow/flow-store.mjs';
17 import { getRepoRoot } from '../lib/repo-root.mjs';
18
19 const __dirname = path.dirname(fileURLToPath(import.meta.url));
20 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-flow-integrity');
21 const starterDir = path.join(getRepoRoot(), 'flows/starter');
22
23 describe('Flow store — data integrity', () => {
24 const dataDir = path.join(tmpRoot, 'data');
25 const vaultId = 'default';
26 const visible = new Set(['personal', 'project']);
27
28 beforeEach(() => {
29 fs.rmSync(tmpRoot, { recursive: true, force: true });
30 fs.mkdirSync(dataDir, { recursive: true });
31 seedStarterFlows(dataDir, vaultId, { starterDir });
32 });
33
34 afterEach(() => {
35 fs.rmSync(tmpRoot, { recursive: true, force: true });
36 });
37
38 it('seed → getFlow preserves starter bundle fields byte-for-byte on key shapes', () => {
39 const bundle = JSON.parse(
40 fs.readFileSync(path.join(starterDir, 'flow_overseer_handover.json'), 'utf8'),
41 );
42 const got = getFlow(dataDir, vaultId, 'flow_overseer_handover', { filterScopes: visible });
43 assert.ok(got);
44 assert.equal(got.flow.flow_id, bundle.flow.flow_id);
45 assert.equal(got.flow.version, bundle.flow.version);
46 assert.equal(got.flow.scope, bundle.flow.scope);
47 assert.equal(got.steps.length, bundle.steps.length);
48 for (let i = 0; i < bundle.steps.length; i += 1) {
49 assert.equal(got.steps[i].ordinal, bundle.steps[i].ordinal);
50 assert.equal(got.steps[i].verification.kind, bundle.steps[i].verification.kind);
51 assert.equal(got.steps[i].requires?.length ?? 0, bundle.steps[i].requires?.length ?? 0);
52 }
53 });
54
55 it('done + evidence_required run step states keep verified:true invariant on read-only store', () => {
56 const store = loadFlowStore(dataDir);
57 store.vaults[vaultId].runs = [{
58 schema: 'knowtation.flow_run/v0',
59 run_id: 'run_integrity',
60 flow_id: 'flow_overseer_handover',
61 flow_version: '0.1.0',
62 scope: 'project',
63 status: 'in_progress',
64 step_states: [{
65 step_id: 'flow_overseer_handover#1',
66 status: 'done',
67 verified: true,
68 evidence_ref: 'prop_abc',
69 }],
70 started: '2026-06-20T00:00:00Z',
71 provenance: { actor: 'deadbeef', harness: 'test' },
72 }];
73 saveFlowStore(dataDir, store);
74 const got = getFlow(dataDir, vaultId, 'flow_overseer_handover', { filterScopes: visible });
75 assert.ok(got);
76 const doneState = store.vaults[vaultId].runs[0].step_states[0];
77 assert.equal(doneState.status, 'done');
78 assert.equal(doneState.verified, true);
79 });
80
81 it('vault mirror path in index is returned but index remains authoritative', () => {
82 const got = getFlow(dataDir, vaultId, 'flow_overseer_handover', { filterScopes: visible });
83 assert.ok(got);
84 assert.equal(got.flow.vault_mirror_path, 'meta/flows/overseer-handover.md');
85 const store = loadFlowStore(dataDir);
86 assert.equal(store.vaults[vaultId].flows.find((f) => f.flow_id === 'flow_overseer_handover')?.vault_mirror_path,
87 'meta/flows/overseer-handover.md');
88 });
89 });
File History 1 commit
sha256:8915fe406161f95c1681f9469375e7bae5b28c884f00bedbdef65e4b0cd0738d docs(flow): commit FLOW-V0-SPEC.md hygiene for 7A-INT merge Human 21 hours ago