verify-capture-flywheel-live-smoke.mjs
135 lines 5.6 KB
Raw
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 10 days ago
1 #!/usr/bin/env node
2 /**
3 * Live capture flywheel smoke (9-apply / 9-kn-c re-run driver).
4 *
5 * Exchanges the durable kt_agent_ credential for an access JWT, then drives the
6 * hosted capture path: POST observe (fresh candidate) → GET candidates (blob
7 * persistence check) → POST propose (canister proposal for the operator to
8 * approve in the Hub tray). Never prints secrets. Exit 0 only on full PASS.
9 *
10 * Content-minimized session meta only (ids / hashes / counts — no raw content),
11 * matching the FLOW-CAPTURE-FLYWHEEL contract.
12 *
13 * Usage:
14 * KNOWTATION_HUB_AGENT_CREDENTIAL_FILE=~/.config/knowtation/agent_cred \
15 * node scripts/verify-capture-flywheel-live-smoke.mjs
16 *
17 * Optional env:
18 * KNOWTATION_HUB_URL (default https://api.knowtation.store)
19 * KNOWTATION_HUB_VAULT_ID (default: first vault_id from the token exchange)
20 * SMOKE_FORCE_NEW_FLOW=1 (bypass structural-overlap dedup with force_new_flow
21 * when a prior smoke Flow already exists)
22 */
23
24 import fs from 'node:fs';
25 import path from 'node:path';
26 import crypto from 'node:crypto';
27
28 const apiBase = (process.env.KNOWTATION_HUB_URL || 'https://api.knowtation.store').replace(/\/$/, '');
29
30 function resolveCredential() {
31 let c = (process.env.KNOWTATION_HUB_AGENT_CREDENTIAL || '').trim();
32 const fp = (process.env.KNOWTATION_HUB_AGENT_CREDENTIAL_FILE || '').trim();
33 if (!c && fp) {
34 const expanded = fp.startsWith('~') ? path.join(process.env.HOME || '', fp.slice(1)) : fp;
35 c = fs.readFileSync(expanded, 'utf8').trim();
36 }
37 return c;
38 }
39
40 function fail(step, detail) {
41 console.log(`FAIL ${step}: ${detail}`);
42 process.exit(1);
43 }
44
45 async function jfetch(step, url, opts = {}) {
46 const res = await fetch(url, opts);
47 const text = await res.text();
48 let json = {};
49 try {
50 json = text ? JSON.parse(text) : {};
51 } catch {
52 json = { raw: text.slice(0, 200) };
53 }
54 return { step, res, json, text };
55 }
56
57 const credential = resolveCredential();
58 if (!credential) fail('setup', 'set KNOWTATION_HUB_AGENT_CREDENTIAL or _FILE');
59 if (!credential.startsWith('kt_agent_')) fail('setup', 'credential must start with kt_agent_');
60
61 console.log(`api=${apiBase}`);
62
63 // 1. Exchange credential → access JWT.
64 const exch = await jfetch('exchange', `${apiBase}/api/v1/auth/agent/token`, {
65 method: 'POST',
66 headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
67 body: JSON.stringify({ credential }),
68 });
69 if (!exch.res.ok) fail('exchange', `HTTP ${exch.res.status} ${exch.json.code || ''} ${exch.json.error || ''}`);
70 const access = exch.json.access_token;
71 if (!access) fail('exchange', 'missing access_token');
72 const vaultId =
73 process.env.KNOWTATION_HUB_VAULT_ID ||
74 (Array.isArray(exch.json.vault_ids) ? exch.json.vault_ids[0] : '') ||
75 'default';
76 console.log(`exchange OK scopes=${JSON.stringify(exch.json.scopes)} vault=${vaultId}`);
77
78 const headers = {
79 'Content-Type': 'application/json',
80 Accept: 'application/json',
81 Authorization: `Bearer ${access}`,
82 'X-Vault-Id': vaultId,
83 };
84
85 // 2. Fresh observe — content-minimized meta with enough repetition to detect.
86 const sessionId = crypto.randomBytes(32).toString('hex');
87 const observe = await jfetch('observe', `${apiBase}/api/v1/flows/capture/observe`, {
88 method: 'POST',
89 headers,
90 body: JSON.stringify({
91 session_id: sessionId,
92 step_sequence_refs: ['flow_weekly_review#1', 'flow_weekly_review#2'],
93 skill_ref_ids: ['mcp_prompt:daily-brief'],
94 observed_counts: { repetition: 4, repeated_correction: 1 },
95 signal_hints: ['repetition'],
96 harness: 'overseer-live-smoke',
97 }),
98 });
99 if (!observe.res.ok) fail('observe', `HTTP ${observe.res.status} ${observe.json.code || ''} ${observe.json.error || ''}`);
100 if (observe.json.detection_authorized !== true) {
101 fail('observe', `detection_authorized=${observe.json.detection_authorized} (FLOW_CAPTURE_DETECTION_ENABLED off hosted-side?)`);
102 }
103 const candidate = (observe.json.candidates || [])[0];
104 if (!candidate) fail('observe', 'no candidate returned');
105 console.log(`observe OK candidate_id=${candidate.candidate_id} confidence=${candidate.confidence}`);
106
107 // 3. Candidates list — with the blob-persistence fix the candidate must be
108 // visible regardless of which lambda instance serves this read.
109 const list = await jfetch('candidates', `${apiBase}/api/v1/flows/candidates?limit=50`, { headers });
110 if (!list.res.ok) fail('candidates', `HTTP ${list.res.status} ${list.json.code || ''}`);
111 const seen = (list.json.candidates || []).some((c) => c.candidate_id === candidate.candidate_id);
112 if (!seen) fail('candidates', `candidate ${candidate.candidate_id} not in list — blob persistence NOT live`);
113 console.log(`candidates OK (${(list.json.candidates || []).length} listed, new candidate present)`);
114
115 // 4. Propose — creates the canister proposal for the operator's Hub-tray approve.
116 const propose = await jfetch(
117 'propose',
118 `${apiBase}/api/v1/flows/candidates/${encodeURIComponent(candidate.candidate_id)}/propose`,
119 {
120 method: 'POST',
121 headers,
122 body: JSON.stringify({
123 confirmed_scope: 'personal',
124 intent: 'Promote captured weekly-review procedure to a saved Flow (9-apply re-run)',
125 ...(process.env.SMOKE_FORCE_NEW_FLOW === '1' ? { force_new_flow: true } : {}),
126 }),
127 },
128 );
129 if (!propose.res.ok) fail('propose', `HTTP ${propose.res.status} ${propose.json.code || ''} ${propose.json.error || ''}`);
130 console.log(`propose OK proposal_id=${propose.json.proposal_id}`);
131
132 console.log('');
133 console.log('PASS capture flywheel live smoke (exchange + observe + candidates + propose)');
134 console.log(`NEXT: operator approves ${propose.json.proposal_id} in the Hub tray (https://knowtation.store/hub)`);
135 process.exit(0);
File History 1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 10 days ago