@bongos/core 1.19.706 → 1.19.708

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,111 @@
1
+ // tests/agent_invoke_live.mjs — the end-to-end proof, reproducible (task 1002491).
2
+ //
3
+ // tests/agent_invoke.mjs proves the DECISIONS with a fake pool, a fake runner and
4
+ // a fake tree. It cannot prove the one thing criterion `agents-historian-exemplar`
5
+ // actually claims: that the real historian, given the real corpus, comes back with
6
+ // an answer whose citations are real. Only a real model call can say that, and a
7
+ // real model call costs real money (~$0.90 a question at the time of writing) and
8
+ // needs an authenticated `claude` CLI — so it must never run in CI or in the unit
9
+ // gate, and it is OPT-IN.
10
+ //
11
+ // AGENT_INVOKE_LIVE=1 node tests/agent_invoke_live.mjs ["<question>"]
12
+ //
13
+ // Skipped and exit 0 otherwise, so `run-unit-tests.js` sweeps it up harmlessly.
14
+ //
15
+ // The POOL is still faked — a builder box has no database, which is also why the
16
+ // script's every edge is injectable. What is real here is the definition (parsed
17
+ // from the committed .claude/agents/historian.md exactly as agents-sync would land
18
+ // it), the corpus (this checkout), the model, and the citation check.
19
+
20
+ import { strict as assert } from 'node:assert';
21
+ import fs from 'node:fs';
22
+ import path from 'node:path';
23
+ import { fileURLToPath } from 'node:url';
24
+ import { createRequire } from 'node:module';
25
+
26
+ const require = createRequire(import.meta.url);
27
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
28
+
29
+ if (process.env.AGENT_INVOKE_LIVE !== '1') {
30
+ console.log(' skip agent_invoke_live — set AGENT_INVOKE_LIVE=1 to spend ~$1 proving the chain for real');
31
+ process.exit(0);
32
+ }
33
+
34
+ const invoke = require('../scripts/gds/agent-invoke.js');
35
+ const agentsSync = require('../scripts/gds/agents-sync.js');
36
+ const grader = require('../modules/grading/grader.js');
37
+
38
+ // The definition exactly as the deploy-time reconcile would land it, so what runs
39
+ // is the committed file and not a fixture that drifted from it.
40
+ const candidate = agentsSync.parseAgentFile(
41
+ fs.readFileSync(path.join(ROOT, '.claude/agents/historian.md'), 'utf8'),
42
+ { name: 'historian' },
43
+ );
44
+ const definition = {
45
+ id: 1,
46
+ ...candidate,
47
+ trigger_spec: {},
48
+ scope_modules: [],
49
+ scope_paths: [],
50
+ scope_violation: null,
51
+ source: 'file',
52
+ provenance: 'built-in',
53
+ // Armed for the duration of this check. Note that a real deploy does NOT land
54
+ // it armed — see the header of scripts/gds/agent-invoke.js.
55
+ author_rank: 'archon',
56
+ enabled: true,
57
+ };
58
+
59
+ const ledger = [];
60
+ const pool = {
61
+ async query(sql, params) {
62
+ if (/FROM agents_definitions/.test(sql)) return { rows: [definition] };
63
+ // No database on a builder box; the degrade path is exercised for free.
64
+ if (/FROM learnings/.test(sql)) throw new Error('no database on this box');
65
+ if (/^INSERT INTO agents_runs/.test(sql)) { ledger.push(['insert', params]); return { rows: [{ id: 1 }] }; }
66
+ ledger.push(['update', params]);
67
+ return { rows: [] };
68
+ },
69
+ };
70
+
71
+ const question = process.argv.slice(2).join(' ')
72
+ || 'Why does this project enforce permissions server-side rather than in the client, and what is the rule about editing local files to grant yourself authority?';
73
+
74
+ console.log(` Q: ${question}`);
75
+ const started = Date.now();
76
+ const result = await invoke.invokeAgent(
77
+ { name: 'historian', question, requestedByBuilderId: 1, requestedByLogin: 'live-check' },
78
+ {
79
+ pool,
80
+ runSubagentCached: (a) => grader.runSubagentCached({ ...a, opts: { ...a.opts, timeoutMs: 600000 } }),
81
+ spawnDeps: { brandingModels: { subagentDefault: 'claude-opus-5', subagentRoutine: 'claude-haiku-4-5-20251001' } },
82
+ warn: (m) => console.log(` warn: ${m}`),
83
+ },
84
+ );
85
+ const secs = Math.round((Date.now() - started) / 1000);
86
+
87
+ if (!result.ok) {
88
+ console.error(` FAIL ${result.code} — ${result.reason || ''} (${secs}s)`);
89
+ process.exit(1);
90
+ }
91
+
92
+ console.log(`\n${result.answer.answer}\n`);
93
+ console.log(' Citations, every one resolved against this checkout:');
94
+ for (const c of result.answer.citations) console.log(` - ${c.ref}`);
95
+ console.log(`\n ${secs}s · ${result.model} · $${result.cost_usd} · confidence ${result.answer.confidence}`);
96
+
97
+ // The claim the criterion makes, asserted rather than eyeballed.
98
+ assert.ok(result.answer.citations.length > 0, 'a cited result');
99
+ for (const c of result.answer.citations) {
100
+ const p = invoke.citationPath(c.ref);
101
+ if (/^learning:/.test(c.ref)) continue;
102
+ assert.ok(fs.existsSync(path.join(ROOT, p)), `${p} exists in the tree`);
103
+ }
104
+ // ...and the ledger said so: one go row, finished ok, with a real cost on it.
105
+ const [insert] = ledger.filter(([k]) => k === 'insert');
106
+ const [update] = ledger.filter(([k]) => k === 'update');
107
+ assert.ok(insert && insert[1].includes('go'), 'the gate said go');
108
+ assert.ok(update && update[1].includes('ok'), 'the run finished ok');
109
+ assert.ok(Number(result.cost_usd) > 0, 'a real fire costs real money and the ledger carries it');
110
+
111
+ console.log('\n ok the historian returned a cited result end-to-end, and the ledger agrees');
@@ -17,8 +17,12 @@ import { createRequire } from 'node:module';
17
17
  import fs from 'node:fs';
18
18
  import os from 'node:os';
19
19
  import path from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
20
21
  import { makeRunner } from './helpers.mjs';
21
22
 
23
+ // This repo's root — the boundary tests at the bottom read the REAL tree.
24
+ const REPO_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
25
+
22
26
  const require = createRequire(import.meta.url);
23
27
  const box = require('../scripts/gds/box.js');
24
28
 
@@ -153,5 +157,90 @@ await test('readCloudInit renders a non-empty cloud-init with no leftover __PLAC
153
157
  assert.ok(!/__[A-Z0-9_]+__/.test(out), 'no unsubstituted __PLACEHOLDER__ tokens remain');
154
158
  });
155
159
 
160
+ // ===========================================================================
161
+ // An UNKNOWN placeholder half-wires the box (task 1003635, ADR 0150 prong B)
162
+ //
163
+ // missingInfra only catches files this renderer knows to ask for. The other
164
+ // direction — an instance template carrying a token box-infra.js never
165
+ // substitutes — used to render straight through, leaving the literal token in
166
+ // the cloud-init for cloud-init to write out as a file. ADR 0150 named the
167
+ // assertion as an escalation and it was never implemented.
168
+ // ===========================================================================
169
+
170
+ await test('an UNKNOWN __TOKEN__ in the instance template is refused, not rendered through', () => {
171
+ const { infra } = makeInfra();
172
+ fs.appendFileSync(path.join(infra, TEMPLATE), '\n# __SOME_FUTURE_THING_B64__\n');
173
+ assert.throws(
174
+ () => box.readCloudInit({ infraDir: infra }),
175
+ (err) => /__SOME_FUTURE_THING_B64__/.test(err.message) && /placeholder|half-wired|ADR 0150/i.test(err.message),
176
+ 'an unsubstituted placeholder must fail loud — rendering it through writes garbage onto the box',
177
+ );
178
+ });
179
+
180
+ await test('a misspelled AUTHORIZED_KEYS token cannot ship a permanently SSH-unreachable box', () => {
181
+ // The stakes case. A template that spells the key block even slightly wrong
182
+ // gets no substitution, so the box boots with a garbage authorized_keys and
183
+ // nobody can SSH in — the outcome task 1003635 reported, by the mechanism it
184
+ // did not find. It must be refused before DigitalOcean is touched.
185
+ const { infra } = makeInfra();
186
+ fs.appendFileSync(path.join(infra, TEMPLATE), '\n# __BUILDER_AUTHORIZED_KEY_B64__\n');
187
+ assert.throws(
188
+ () => box.readCloudInit({ infraDir: infra, authorizedKeysBlock: 'ssh-ed25519 AAAA' }),
189
+ /__BUILDER_AUTHORIZED_KEY_B64__/,
190
+ );
191
+ });
192
+
193
+ await test('the happy path still renders — the assertion does not fire on a correct template', () => {
194
+ const { infra } = makeInfra();
195
+ const out = box.readCloudInit({
196
+ infraDir: infra, authorizedKeysBlock: 'ssh-ed25519 AAAA', boxEnv: 'X=1', gdsSession: '{"t":1}',
197
+ });
198
+ assert.ok(out && out.length > 0, 'a correct template must still render');
199
+ });
200
+
201
+ // ===========================================================================
202
+ // The core / instance boundary (task 1003635)
203
+ //
204
+ // The absence of the cloud-init set from the core's own infra/ was filed once as
205
+ // a P1 outage ("a dev box becomes permanently SSH-unreachable"). It is not one —
206
+ // ADR 0150 says the neutral core ships no box cloud-init, and the two throws
207
+ // above are what make a missing one legible instead of dangerous. These pin that
208
+ // boundary against the REAL tree so the next reader gets a test telling them so.
209
+ // ===========================================================================
210
+
211
+ await test('the INLINED list is exactly what box-infra.js actually asks fileGzB64 for', () => {
212
+ // The list above says "kept in sync with box.js's fileGzB64 calls" — by hand.
213
+ // Derive it instead, so adding a 10th inlined file cannot leave these tests
214
+ // silently proving less than they claim.
215
+ const SRC = fs.readFileSync(path.join(REPO_ROOT, 'scripts/gds/box-infra.js'), 'utf8');
216
+ const called = [...SRC.matchAll(/fileGzB64\('([^']+)'\)/g)].map((m) => m[1]);
217
+ assert.ok(called.length > 0, 'found no fileGzB64 calls — did box-infra.js move or get renamed?');
218
+ assert.deepEqual([...new Set(called)].sort(), [...INLINED].sort(),
219
+ 'the INLINED fixture list has drifted from box-infra.js — the happy-path test would stop covering every inlined file');
220
+ });
221
+
222
+ await test('the neutral core ships NO box cloud-init — that is ADR 0150, not a missing file', () => {
223
+ const coreInfra = path.join(REPO_ROOT, 'infra');
224
+ assert.ok(!fs.existsSync(path.join(coreInfra, TEMPLATE)),
225
+ `the core now ships infra/${TEMPLATE}. ADR 0150 says it must not ("The neutral core still ships no infra/") — the cloud-init is instance-owned. If that decision changed, amend ADR 0150 and this test together.`);
226
+ for (const name of INLINED) {
227
+ assert.ok(!fs.existsSync(path.join(coreInfra, name)),
228
+ `infra/${name} is a cloud-init asset and belongs to the host instance, not the core (ADR 0150)`);
229
+ }
230
+ });
231
+
232
+ await test("core infra/ carries only the CLONE-RIDE scripts, which is why it looks half-empty", () => {
233
+ // The other population: these run on the box out of /workspace/infra/, arriving
234
+ // with the cloned source rather than through cloud-init. Their presence beside
235
+ // nine absences is the thing that reads as "files are missing" — infra/README.md
236
+ // exists to answer that, so it is required to stay.
237
+ const coreInfra = path.join(REPO_ROOT, 'infra');
238
+ for (const name of ['box-heartbeat.sh', 'box-report-host-keys.sh', 'box-report-terminal.sh', 'box-report-version.sh']) {
239
+ assert.ok(fs.existsSync(path.join(coreInfra, name)), `infra/${name} rides the clone and must stay in the core`);
240
+ }
241
+ assert.ok(fs.existsSync(path.join(coreInfra, 'README.md')),
242
+ 'infra/README.md explains the two populations — without it the nine absences read as breakage again (task 1003635)');
243
+ });
244
+
156
245
  for (const d of tmps) { try { fs.rmSync(d, { recursive: true, force: true }); } catch { /* best-effort */ } }
157
246
  summary();