flow-authoring-performance.test.mjs
117 lines 4.3 KB
Raw
sha256:8915fe406161f95c1681f9469375e7bae5b28c884f00bedbdef65e4b0cd0738d docs(flow): commit FLOW-V0-SPEC.md hygiene for 7A-INT merge Human 20 hours ago
1 /**
2 * Tier 6 — PERFORMANCE: propose validation + flowStateId within a p95 budget on
3 * a large fixture; approve→apply bounded; no quadratic version resolution.
4 *
5 * @see lib/flow/flow-authoring.mjs
6 * @see docs/FLOW-AUTHORING-WRITEBACK-CONTRACT-7A-L1.md §7
7 */
8 import { describe, it, beforeEach, afterEach } from 'node:test';
9 import assert from 'node:assert/strict';
10 import fs from 'node:fs';
11 import path from 'node:path';
12 import { fileURLToPath } from 'node:url';
13 import {
14 handleFlowProposeRequest,
15 precheckApprovedFlowProposal,
16 applyFlowProposalToIndex,
17 flowStateId,
18 } from '../lib/flow/flow-authoring.mjs';
19 import { flowDefinitionForClient, MAX_STEPS_PER_FLOW, buildFlowStepId } from '../lib/flow/flow-store.mjs';
20 import { createProposal, getProposal } from '../hub/proposals-store.mjs';
21
22 const __dirname = path.dirname(fileURLToPath(import.meta.url));
23 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-flow-authoring-perf');
24 const visible = new Set(['personal', 'project', 'org']);
25
26 function bigFlow(flowId, version, stepCount) {
27 const steps = [];
28 const refs = [];
29 for (let i = 1; i <= stepCount; i += 1) {
30 const stepId = buildFlowStepId(flowId, i);
31 refs.push(stepId);
32 steps.push({
33 schema: 'knowtation.flow_step/v0',
34 step_id: stepId,
35 flow_id: flowId,
36 ordinal: i,
37 owned_job: `Job ${i}`,
38 instruction: `Do step ${i}.`,
39 trigger: `Run ${i}.`,
40 when_not_to_run: `Skip ${i}.`,
41 boundaries: ['Read only'],
42 output_shape: `Out ${i}.`,
43 verification: { kind: 'artifact_exists', evidence_required: true, description: `Artifact ${i}.` },
44 automatable: 'manual',
45 });
46 }
47 return {
48 flow: {
49 schema: 'knowtation.flow/v0', flow_id: flowId, title: 'Big', version,
50 scope: 'personal', summary: 'big', tags: [], steps: refs, inputs: [],
51 updated: '2026-06-20T00:00:00Z', truncated: false,
52 },
53 steps,
54 };
55 }
56
57 function p95(samples) {
58 const sorted = [...samples].sort((a, b) => a - b);
59 return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95))];
60 }
61
62 describe('Flow authoring — performance', () => {
63 const dataDir = path.join(tmpRoot, 'data');
64 const vaultId = 'default';
65
66 beforeEach(() => {
67 fs.rmSync(tmpRoot, { recursive: true, force: true });
68 fs.mkdirSync(dataDir, { recursive: true });
69 process.env.FLOW_AUTHORING_WRITES = '1';
70 });
71 afterEach(() => {
72 fs.rmSync(tmpRoot, { recursive: true, force: true });
73 delete process.env.FLOW_AUTHORING_WRITES;
74 });
75
76 it('flowStateId on a MAX_STEPS_PER_FLOW flow stays under a p95 budget', () => {
77 const big = bigFlow('flow_perf', '1.0.0', MAX_STEPS_PER_FLOW);
78 const def = flowDefinitionForClient(big.flow, big.steps);
79 const samples = [];
80 for (let i = 0; i < 200; i += 1) {
81 const t = process.hrtime.bigint();
82 flowStateId(def.flow, def.steps);
83 samples.push(Number(process.hrtime.bigint() - t) / 1e6);
84 }
85 assert.ok(p95(samples) < 25, `flowStateId p95 ${p95(samples).toFixed(2)}ms`);
86 });
87
88 it('propose validation on the large fixture stays under a p95 budget', () => {
89 const samples = [];
90 for (let i = 0; i < 50; i += 1) {
91 const big = bigFlow(`flow_perf_${i}`, '1.0.0', MAX_STEPS_PER_FLOW);
92 const t = process.hrtime.bigint();
93 const r = handleFlowProposeRequest({
94 dataDir, vaultId, visibleScopes: visible, kind: 'new',
95 flow: big.flow, steps: big.steps, intent: 'perf', createProposal,
96 });
97 samples.push(Number(process.hrtime.bigint() - t) / 1e6);
98 assert.equal(r.ok, true);
99 }
100 assert.ok(p95(samples) < 150, `propose p95 ${p95(samples).toFixed(2)}ms`);
101 });
102
103 it('approve→apply reconcile is bounded for a large flow', () => {
104 const big = bigFlow('flow_perf_apply', '1.0.0', MAX_STEPS_PER_FLOW);
105 const proposed = handleFlowProposeRequest({
106 dataDir, vaultId, visibleScopes: visible, kind: 'new',
107 flow: big.flow, steps: big.steps, intent: 'perf', createProposal,
108 });
109 const proposal = getProposal(dataDir, proposed.payload.proposal_id);
110 const t = process.hrtime.bigint();
111 const pre = precheckApprovedFlowProposal(dataDir, proposal);
112 applyFlowProposalToIndex(dataDir, pre.vaultId, pre.flow, pre.steps);
113 const ms = Number(process.hrtime.bigint() - t) / 1e6;
114 assert.ok(pre.ok);
115 assert.ok(ms < 200, `reconcile ${ms.toFixed(2)}ms`);
116 });
117 });
File History 1 commit
sha256:8915fe406161f95c1681f9469375e7bae5b28c884f00bedbdef65e4b0cd0738d docs(flow): commit FLOW-V0-SPEC.md hygiene for 7A-INT merge Human 20 hours ago