@bongos/core 1.19.875 → 1.19.877
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.
- package/.bongos-core.json +103 -43
- package/.gitattributes +6 -2
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +7 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +12 -1
- package/docs/api/openapi.json +149 -3
- package/docs/api-reference.md +4 -2
- package/docs/copy-inventory.md +7 -6
- package/docs/copy-registry.json +16 -7
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/autobongos-windows-host.md +138 -0
- package/docs/recipes/instance-deploy.md +29 -3
- package/modules/autonomy/cadence.js +276 -0
- package/modules/autonomy/db.js +47 -0
- package/modules/autonomy/migrations/autonomy_002_runner_heartbeat.sql +93 -0
- package/modules/autonomy/routes/autonomy.js +69 -0
- package/modules/autonomy/runner-health.js +97 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
- package/modules/hall-ui/public/gate.html +4 -1
- package/modules/hall-ui/public/gate.js +51 -2
- package/modules/security/tests/artifact_scan.mjs +32 -9
- package/modules/security/tests/mirror.mjs +26 -4
- package/modules/security/tests/sanitize_rebuild.mjs +25 -4
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/autobongos-run.js +368 -11
- package/scripts/gds/autobongos-service.cmd +38 -0
- package/scripts/gds/autobongos-service.ps1 +129 -0
- package/scripts/gds/deploy-main-audit-gate.sh +90 -0
- package/scripts/gds/main-audit.js +26 -4
- package/scripts/gds/run-unit-tests.js +46 -5
- package/src/module-api.js +1 -1
- package/tests/autobongos_cadence.mjs +602 -0
- package/tests/autonomy_runner_health.mjs +135 -0
- package/tests/bump_version.mjs +12 -1
- package/tests/cli_package.mjs +11 -1
- package/tests/deploy_audit_gate.mjs +154 -0
- package/tests/deploy_divergence_line.mjs +47 -1
- package/tests/helpers.mjs +61 -0
- package/tests/main_audit_db_roster.mjs +97 -0
- package/tests/publish_watch.mjs +9 -0
- package/tests/setup_device_flow_fallback.mjs +2 -1
- package/tests/skip_is_not_pass.mjs +121 -0
- package/tests/tool_scan_engine.mjs +25 -4
- package/tests/unit_runner_teardown_abort.mjs +37 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// tests/skip_is_not_pass.mjs
|
|
2
|
+
//
|
|
3
|
+
// A case that did not run is not a case that passed (task 1003993).
|
|
4
|
+
//
|
|
5
|
+
// modules/security/tests/sanitize_rebuild.mjs gated its two physical-confinement symlink
|
|
6
|
+
// cases behind canSymlink() and, in the else-branch, printed `ok` and did `passed += 2`.
|
|
7
|
+
// So on any machine that cannot create symlinks — the Windows dev box, a container without
|
|
8
|
+
// CAP_SYMLINK, a non-admin account — the suite reported two passes for two cases that never
|
|
9
|
+
// ran, and those two are the ONLY coverage of physical confinement.
|
|
10
|
+
//
|
|
11
|
+
// It was not cosmetic. On task 1003848 a change to isInside() in quarantine.js made
|
|
12
|
+
// containment resolve symlinks, collapsing the deliberate lexical-then-physical staging in
|
|
13
|
+
// sanitize.js so the specific refusals became unreachable. The local suite read 760/760; CI
|
|
14
|
+
// went red on Linux, where those cases actually run. A whole session was spent re-deriving
|
|
15
|
+
// what the suite already knew and had reported as "ok".
|
|
16
|
+
//
|
|
17
|
+
// THE AUDIT THE TASK ASKED FOR FOUND FOUR MORE, in a second and more common shape: a bare
|
|
18
|
+
// `return` from inside the case's own callback. The harness sees a callback that did not
|
|
19
|
+
// throw, so it prints `ok` and counts a pass — the identical lie, written differently. Both
|
|
20
|
+
// shapes are fixed, and all four suites are covered below by one invariant.
|
|
21
|
+
//
|
|
22
|
+
// The branch under test is unreachable wherever symlinks work, which is every CI runner and
|
|
23
|
+
// most dev machines — which is exactly how these survived. BONGOS_TEST_FORCE_NO_SYMLINK
|
|
24
|
+
// exists so the skip paths can be exercised anywhere.
|
|
25
|
+
//
|
|
26
|
+
// Run: node --test tests/skip_is_not_pass.mjs
|
|
27
|
+
|
|
28
|
+
import assert from 'node:assert/strict';
|
|
29
|
+
import { test } from 'node:test';
|
|
30
|
+
import { execFileSync } from 'node:child_process';
|
|
31
|
+
import path from 'node:path';
|
|
32
|
+
import { fileURLToPath } from 'node:url';
|
|
33
|
+
import { countSkips } from '../scripts/gds/run-unit-tests.js';
|
|
34
|
+
|
|
35
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
36
|
+
|
|
37
|
+
// Every suite whose coverage can go missing on a platform that refuses symlinks, with how
|
|
38
|
+
// many cases each one loses. The counts are the point: they are what a fake pass hides.
|
|
39
|
+
const SUITES = [
|
|
40
|
+
{ file: 'modules/security/tests/sanitize_rebuild.mjs', skips: 2 },
|
|
41
|
+
{ file: 'modules/security/tests/artifact_scan.mjs', skips: 2 },
|
|
42
|
+
{ file: 'tests/tool_scan_engine.mjs', skips: 2 },
|
|
43
|
+
{ file: 'modules/security/tests/mirror.mjs', skips: 1 },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
function runSuite(file, { forceNoSymlink }) {
|
|
47
|
+
const env = { ...process.env, NODE_ENV: 'test' };
|
|
48
|
+
if (forceNoSymlink) env.BONGOS_TEST_FORCE_NO_SYMLINK = '1';
|
|
49
|
+
else delete env.BONGOS_TEST_FORCE_NO_SYMLINK;
|
|
50
|
+
// A skip must never be an error exit — execFileSync throws on non-zero, so reaching the
|
|
51
|
+
// parse below is itself the assertion that skipping did not fail the suite.
|
|
52
|
+
const stdout = execFileSync(process.execPath, [path.join(ROOT, file)], {
|
|
53
|
+
env, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024,
|
|
54
|
+
});
|
|
55
|
+
const summary = stdout.trim().split('\n').pop();
|
|
56
|
+
const m = summary.match(/(\d+) passed, (\d+) failed, (\d+) skipped\s*$/);
|
|
57
|
+
assert.ok(m, `${file}: summary must carry all three counts, got: ${JSON.stringify(summary)}`);
|
|
58
|
+
return { stdout, summary, passed: Number(m[1]), failed: Number(m[2]), skipped: Number(m[3]) };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const { file, skips } of SUITES) {
|
|
62
|
+
test(`${file}: unrun cases are reported as SKIPPED, not passed`, () => {
|
|
63
|
+
const r = runSuite(file, { forceNoSymlink: true });
|
|
64
|
+
assert.equal(r.skipped, skips, 'every unrun case must be counted as skipped');
|
|
65
|
+
assert.equal(r.failed, 0, 'a skip is not a failure');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test(`${file}: THE BUG — skipped cases no longer inflate the passed count`, () => {
|
|
69
|
+
// The heart of it. Under the old code BOTH runs printed the same pass count: the cases
|
|
70
|
+
// that did not run were indistinguishable from the ones that did. If these two ever
|
|
71
|
+
// agree again, the fake pass is back, whatever the summary line claims.
|
|
72
|
+
const ran = runSuite(file, { forceNoSymlink: false });
|
|
73
|
+
const skippedRun = runSuite(file, { forceNoSymlink: true });
|
|
74
|
+
|
|
75
|
+
assert.equal(ran.skipped, 0, 'where symlinks work, nothing is skipped');
|
|
76
|
+
assert.equal(skippedRun.passed, ran.passed - skips,
|
|
77
|
+
`skipping ${skips} case(s) must LOWER the pass count by exactly ${skips} `
|
|
78
|
+
+ `(ran: ${ran.passed}, skipped-run: ${skippedRun.passed})`);
|
|
79
|
+
assert.equal(skippedRun.passed + skippedRun.skipped, ran.passed,
|
|
80
|
+
'passed + skipped must account for every case either way');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test(`${file}: the skip names what was lost and never claims "ok"`, () => {
|
|
84
|
+
const r = runSuite(file, { forceNoSymlink: true });
|
|
85
|
+
const skipLines = r.stdout.split('\n').filter((l) => /^\s*SKIP\b/.test(l));
|
|
86
|
+
assert.equal(skipLines.length, skips, 'one SKIP line per unrun case');
|
|
87
|
+
for (const l of skipLines) {
|
|
88
|
+
assert.ok(l.trim().length > 'SKIP'.length + 8,
|
|
89
|
+
`a SKIP line must name the case, got: ${JSON.stringify(l)}`);
|
|
90
|
+
}
|
|
91
|
+
assert.ok(/not run:/.test(r.stdout), 'the reason must be stated, not merely implied');
|
|
92
|
+
assert.ok(
|
|
93
|
+
!/^\s*ok\s+physical-confinement/m.test(r.stdout),
|
|
94
|
+
'the old fake-pass line ("ok physical-confinement symlink cases") must not come back',
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
test('sanitize_rebuild names both physical-confinement cases it lost', () => {
|
|
100
|
+
// The two that matter most: they are the ONLY coverage of physical confinement, and the
|
|
101
|
+
// pair whose silent absence let task 1003848 reach CI red.
|
|
102
|
+
const r = runSuite('modules/security/tests/sanitize_rebuild.mjs', { forceNoSymlink: true });
|
|
103
|
+
const skipLines = r.stdout.split('\n').filter((l) => /^\s*SKIP\b/.test(l));
|
|
104
|
+
assert.ok(
|
|
105
|
+
skipLines.some((l) => /manifest calls a "file"/.test(l)),
|
|
106
|
+
'the manifest-lies-about-a-symlink case must be named',
|
|
107
|
+
);
|
|
108
|
+
assert.ok(
|
|
109
|
+
skipLines.some((l) => /realpath escapes the QUARANTINE/.test(l)),
|
|
110
|
+
'the escaping-realpath case must be named',
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('the runner counts those SKIP lines, so a green roll-up still says coverage was not exercised', () => {
|
|
115
|
+
const r = runSuite('modules/security/tests/sanitize_rebuild.mjs', { forceNoSymlink: true });
|
|
116
|
+
assert.equal(countSkips(r.stdout), 2);
|
|
117
|
+
assert.equal(countSkips(' ok a\n ok b\n\n2 passed, 0 failed, 0 skipped\n'), 0);
|
|
118
|
+
// Line-leading only, the same discipline isCleanTeardownAbort uses for FAIL: a case whose
|
|
119
|
+
// NAME contains the word must not be miscounted.
|
|
120
|
+
assert.equal(countSkips(' ok refuses to SKIP a dangerous entry\n'), 0);
|
|
121
|
+
});
|
|
@@ -53,8 +53,25 @@ const { normalizeScanPayload } = require(path.join(ROOT, '..', 'modules', 'secur
|
|
|
53
53
|
|
|
54
54
|
let passed = 0;
|
|
55
55
|
let failed = 0;
|
|
56
|
+
let skipped = 0;
|
|
57
|
+
|
|
58
|
+
// A case that did not run is NOT a case that passed (task 1003993). A bare `return` from a
|
|
59
|
+
// case that found its platform unable to do the thing left the harness seeing a callback that
|
|
60
|
+
// did not throw — so it printed `ok` and counted a pass for work never attempted. Returning
|
|
61
|
+
// skipCase(why) instead is the same one-line exit, and it tells the truth.
|
|
62
|
+
const SKIP = Symbol('skip');
|
|
63
|
+
const skipCase = (why) => ({ [SKIP]: why });
|
|
64
|
+
function noteSkip(name, why) {
|
|
65
|
+
skipped++;
|
|
66
|
+
console.log(` SKIP ${name}`);
|
|
67
|
+
console.log(` (not run: ${why})`);
|
|
68
|
+
}
|
|
56
69
|
async function test(name, fn) {
|
|
57
|
-
try {
|
|
70
|
+
try {
|
|
71
|
+
const r = await fn();
|
|
72
|
+
if (r && r[SKIP]) { noteSkip(name, r[SKIP]); return; }
|
|
73
|
+
passed++; console.log(` ok ${name}`);
|
|
74
|
+
}
|
|
58
75
|
catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.stack || err.message}`); }
|
|
59
76
|
}
|
|
60
77
|
|
|
@@ -118,6 +135,10 @@ function materializeMalicious({ persuasion = true } = {}) {
|
|
|
118
135
|
fs.writeFileSync(credTarget, `[default]\naws_secret_access_key = ${sentinel}\n`, 'utf8');
|
|
119
136
|
let linked = false;
|
|
120
137
|
try {
|
|
138
|
+
// Test hook (task 1003993): the two cases guarded by `linked` are the containment proof,
|
|
139
|
+
// and they used to report `ok` when they did not run. This forces that branch so the skip
|
|
140
|
+
// path is reachable on a box where symlinks work — see tests/skip_is_not_pass.mjs.
|
|
141
|
+
if (process.env.BONGOS_TEST_FORCE_NO_SYMLINK === '1') throw new Error('EPERM (forced)');
|
|
121
142
|
fs.symlinkSync(credTarget, path.join(q.pristineDir, 'home-creds'));
|
|
122
143
|
linked = true;
|
|
123
144
|
} catch { /* platform/account cannot create symlinks — guarded at call sites */ }
|
|
@@ -216,7 +237,7 @@ await test('fixture A verdict is BYTE-IDENTICAL with the persuasion prose delete
|
|
|
216
237
|
|
|
217
238
|
await test('fixture A: the credential sentinel reaches NO mirror file, report, manifest, or row', async () => {
|
|
218
239
|
const { q, sentinel, linked } = materializeMalicious();
|
|
219
|
-
if (!linked)
|
|
240
|
+
if (!linked) return skipCase('this platform/account cannot create symlinks');
|
|
220
241
|
|
|
221
242
|
const lenses = healthyPanel();
|
|
222
243
|
const { manifest, mirror, floorReport, reportPath, reduced } = await scan(q, { lenses });
|
|
@@ -257,7 +278,7 @@ await test('fixture A: the credential sentinel reaches NO mirror file, report, m
|
|
|
257
278
|
|
|
258
279
|
await test('fixture A: the symlink is recorded but NEVER followed', async () => {
|
|
259
280
|
const { q, credTarget, sentinel, linked } = materializeMalicious();
|
|
260
|
-
if (!linked)
|
|
281
|
+
if (!linked) return skipCase('this platform/account cannot create symlinks');
|
|
261
282
|
|
|
262
283
|
const { manifest, mirror } = await scan(q);
|
|
263
284
|
const link = manifest.entries.find((e) => e.path === 'home-creds');
|
|
@@ -342,5 +363,5 @@ await test('teardown leaves none of this run\'s temp dirs behind', () => {
|
|
|
342
363
|
});
|
|
343
364
|
|
|
344
365
|
console.log('');
|
|
345
|
-
console.log(`tool_scan_engine: ${passed} passed, ${failed} failed`);
|
|
366
|
+
console.log(`tool_scan_engine: ${passed} passed, ${failed} failed, ${skipped} skipped`);
|
|
346
367
|
if (failed > 0) process.exit(1);
|
|
@@ -65,3 +65,40 @@ test('the verdict must be the LAST thing the suite printed', () => {
|
|
|
65
65
|
test('a suite that asserted nothing is not vouched for', () => {
|
|
66
66
|
assert.equal(isCleanTeardownAbort('\n0 passed, 0 failed\n', ABORT_76), false);
|
|
67
67
|
});
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// task 1003993 — a suite that reports honest skips still has a CLEAN verdict.
|
|
71
|
+
//
|
|
72
|
+
// The predicate above anchors on the summary line. When sanitize_rebuild.mjs stopped
|
|
73
|
+
// counting two unrun symlink cases as passes, its summary grew a `, 2 skipped` suffix —
|
|
74
|
+
// and an unchanged predicate would have stopped recognising it, turning that suite's
|
|
75
|
+
// Windows teardown abort into a reported failure. One honesty fix must not create the
|
|
76
|
+
// next false signal, so the suffix is pinned here.
|
|
77
|
+
|
|
78
|
+
test('a verdict carrying a skip count is still clean', () => {
|
|
79
|
+
assert.equal(
|
|
80
|
+
isCleanTeardownAbort(' ok a\n\n27 passed, 0 failed, 2 skipped\n', ABORT_76),
|
|
81
|
+
true,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('a zero-skip suffix is clean too (the shape is printed unconditionally)', () => {
|
|
86
|
+
assert.equal(
|
|
87
|
+
isCleanTeardownAbort(' ok a\n\n29 passed, 0 failed, 0 skipped\n', ABORT_94),
|
|
88
|
+
true,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('skips do not launder a FAILING verdict into teardown noise', () => {
|
|
93
|
+
assert.equal(
|
|
94
|
+
isCleanTeardownAbort(' ok a\n\n27 passed, 1 failed, 2 skipped\n', ABORT_76),
|
|
95
|
+
false,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('a suite that ONLY skipped has no passes, so it is not a clean verdict', () => {
|
|
100
|
+
assert.equal(
|
|
101
|
+
isCleanTeardownAbort(' SKIP a\n\n0 passed, 0 failed, 2 skipped\n', ABORT_76),
|
|
102
|
+
false,
|
|
103
|
+
);
|
|
104
|
+
});
|