@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,90 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# scripts/gds/deploy-main-audit-gate.sh — the SEC #863 / ADR 0043 deploy-side gate.
|
|
3
|
+
#
|
|
4
|
+
# Refuses a deploy when a builder BELOW Metic modified a permission-sensitive path in the
|
|
5
|
+
# delta this deploy introduces. Run from the instance checkout root, AFTER the working tree
|
|
6
|
+
# has been moved to the new commit and BEFORE anything is migrated or restarted.
|
|
7
|
+
#
|
|
8
|
+
# OTB_DEPLOY_PREV_SHA="$(git rev-parse HEAD)"; export OTB_DEPLOY_PREV_SHA # BEFORE the pull
|
|
9
|
+
# git reset --hard origin/main
|
|
10
|
+
# sh node_modules/@bongos/core/scripts/gds/deploy-main-audit-gate.sh || exit 1
|
|
11
|
+
# ...migrate, restart...
|
|
12
|
+
#
|
|
13
|
+
# WHY THIS LIVES IN THE CORE (task 1001426). It used to live in the control-plane repo's
|
|
14
|
+
# infra/, where a standalone instance checkout cannot reach it — and on cloudbongos.com it
|
|
15
|
+
# was never wired into ~/deploy.sh at all, so the "poison never reaches prod" check had
|
|
16
|
+
# never once run. Shipping it beside main-audit.js means every instance inherits the gate
|
|
17
|
+
# with its pinned core, and there is no per-instance copy to drift.
|
|
18
|
+
#
|
|
19
|
+
# THE MARKER IS THE CHECKED-OUT HEAD, AND IT ADVANCES ONLY ON SUCCESS.
|
|
20
|
+
# That is the substance of this task and the reason the restore below is in the GATE rather
|
|
21
|
+
# than in each caller's deploy script. The gate can only audit what was pulled, so it runs
|
|
22
|
+
# after the tree has already moved. Exiting non-zero and leaving it moved would mean the
|
|
23
|
+
# next deploy computes OTB_DEPLOY_PREV_SHA from the POISONED commit, never re-audits that
|
|
24
|
+
# delta, and finds the poison already checked out — the marker-bypass this gate exists to
|
|
25
|
+
# close, reintroduced one layer up. So a refusal puts the tree back where it started.
|
|
26
|
+
# Set OTB_GATE_NO_RESTORE=1 only when the caller restores it itself.
|
|
27
|
+
#
|
|
28
|
+
# STRICT IS OFF BY DEFAULT, AND THAT IS NOT LAZINESS. Rank resolution keys on github_login
|
|
29
|
+
# only (#863-followup finding #6: display_name is builder-controlled, so keying it let one
|
|
30
|
+
# builder alias another's rank). A deploy lane whose commits are authored under a human
|
|
31
|
+
# display name ("Firstname Lastname") rather than the builder's github_login therefore
|
|
32
|
+
# resolves to nothing, and OTB_AUDIT_STRICT=1 would block every deploy forever.
|
|
33
|
+
# Non-strict still hard-blocks the incident class this was built for: a RESOLVED below-Metic
|
|
34
|
+
# author touching a protected path. Turn strict on once the lane commits under logins.
|
|
35
|
+
#
|
|
36
|
+
# KNOWN LIMIT, stated because a gate nobody distrusts is worse than no gate: if the roster
|
|
37
|
+
# cannot be read at all, every author is unresolved and non-strict passes. main-audit.js
|
|
38
|
+
# announces that loudly on stderr (task 1001426 gave it a fallback and a voice — it used to
|
|
39
|
+
# fail silently and return an empty map, so the gate reported a clean audit that never ran).
|
|
40
|
+
# Availability is chosen over a hard stop deliberately: a database blip must not freeze
|
|
41
|
+
# deploys. Read the stderr.
|
|
42
|
+
|
|
43
|
+
set -eu
|
|
44
|
+
|
|
45
|
+
if [ -n "${OTB_DEPLOY_PREV_SHA:-}" ]; then
|
|
46
|
+
OTB_AUDIT_RANGE="${OTB_DEPLOY_PREV_SHA}..HEAD"; export OTB_AUDIT_RANGE
|
|
47
|
+
echo "main audit: range ${OTB_AUDIT_RANGE} (exact deploy delta)"
|
|
48
|
+
else
|
|
49
|
+
echo "main audit: OTB_DEPLOY_PREV_SHA not set — falling back to the wide window."
|
|
50
|
+
echo " (Recommended: capture OTB_DEPLOY_PREV_SHA=\$(git rev-parse HEAD) BEFORE the pull;"
|
|
51
|
+
echo " without it a poisoned commit buried under the window can escape, and a refusal"
|
|
52
|
+
echo " cannot put the tree back.)"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
STRICT=""
|
|
56
|
+
[ "${OTB_AUDIT_STRICT:-0}" = "1" ] && STRICT="--strict"
|
|
57
|
+
|
|
58
|
+
# Prefer the installed core; fall back to an in-tree checkout (the core repo itself).
|
|
59
|
+
MAIN_AUDIT="node_modules/@bongos/core/scripts/gds/main-audit.js"
|
|
60
|
+
[ -f "$MAIN_AUDIT" ] || MAIN_AUDIT="scripts/gds/main-audit.js"
|
|
61
|
+
if [ ! -f "$MAIN_AUDIT" ]; then
|
|
62
|
+
echo "X DEPLOY ABORTED: main-audit.js not found (looked in node_modules/@bongos/core and in-tree)."
|
|
63
|
+
echo " A gate that cannot find its auditor must fail closed, not wave the deploy through."
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
if node "$MAIN_AUDIT" --verify-head --db ${STRICT}; then
|
|
68
|
+
echo "OK main audit clean${STRICT:+ (strict)} — proceeding with deploy."
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
echo ""
|
|
73
|
+
echo "X DEPLOY ABORTED (SEC #863): a below-Metic${STRICT:+ or unresolved} author modified a"
|
|
74
|
+
echo " permission-sensitive path in this deploy. Investigate and revert before deploying:"
|
|
75
|
+
echo " node $MAIN_AUDIT --range \"\${OTB_AUDIT_RANGE:-HEAD~200..HEAD}\" --revert"
|
|
76
|
+
|
|
77
|
+
if [ -n "${OTB_DEPLOY_PREV_SHA:-}" ] && [ "${OTB_GATE_NO_RESTORE:-0}" != "1" ]; then
|
|
78
|
+
if git rev-parse --verify --quiet "${OTB_DEPLOY_PREV_SHA}^{commit}" >/dev/null; then
|
|
79
|
+
echo ""
|
|
80
|
+
echo " restoring the checkout to ${OTB_DEPLOY_PREV_SHA} — the deployed marker advances only on success,"
|
|
81
|
+
echo " so the NEXT deploy re-audits this same delta instead of treating it as already-reviewed."
|
|
82
|
+
git reset --hard "${OTB_DEPLOY_PREV_SHA}" >/dev/null
|
|
83
|
+
else
|
|
84
|
+
echo ""
|
|
85
|
+
echo " ::WARNING:: cannot restore — ${OTB_DEPLOY_PREV_SHA} is not a commit in this checkout."
|
|
86
|
+
echo " The tree is left at the REFUSED commit; the next deploy will not re-audit this delta."
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
exit 1
|
|
@@ -251,6 +251,13 @@ function rowsToMap(rows) {
|
|
|
251
251
|
return map;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// Where getBuildersRoster lives. It was carved out of src/bongos/db.js into the
|
|
255
|
+
// lifecycle module (goal 1000010's per-domain db.js split) and this file was not
|
|
256
|
+
// updated with it, so `--db` threw `db.getBuildersRoster is not a function` on every
|
|
257
|
+
// run — for however long that carve has been landed. Kept as a named constant so the
|
|
258
|
+
// next carve breaks a test (tests/main_audit_db_roster.mjs) rather than a deploy gate.
|
|
259
|
+
const ROSTER_DB_MODULE = '../../modules/lifecycle/db.js';
|
|
260
|
+
|
|
254
261
|
// API mode (default): for an Archon session / operator laptop. DB mode (--db):
|
|
255
262
|
// for the droplet deploy gate, which has DB access but no GDS session — it reads
|
|
256
263
|
// the roster straight from Postgres. db.js is lazy-required so importing this
|
|
@@ -258,13 +265,24 @@ function rowsToMap(rows) {
|
|
|
258
265
|
async function fetchRankMap({ useDb = false } = {}) {
|
|
259
266
|
if (useDb) {
|
|
260
267
|
try {
|
|
261
|
-
const db = require(
|
|
268
|
+
const db = require(ROSTER_DB_MODULE);
|
|
269
|
+
if (typeof db.getBuildersRoster !== 'function') {
|
|
270
|
+
// The exact failure this file already shipped once, named rather than caught as a
|
|
271
|
+
// generic TypeError: a moved export reads identically to "no database" otherwise.
|
|
272
|
+
throw new Error(`${ROSTER_DB_MODULE} exports no getBuildersRoster — it has moved again`);
|
|
273
|
+
}
|
|
262
274
|
const roster = await db.getBuildersRoster({ staleHours: 24 });
|
|
263
|
-
|
|
275
|
+
const map = rowsToMap(roster.builders || roster);
|
|
276
|
+
if (Object.keys(map).length) return map;
|
|
277
|
+
console.error('main-audit: --db roster read returned NO builders — falling through to the API/public roster.');
|
|
264
278
|
} catch (e) {
|
|
265
|
-
console.error(`main-audit: --db roster read failed (${e.message})
|
|
266
|
-
return {};
|
|
279
|
+
console.error(`main-audit: --db roster read failed (${e.message}) — falling through to the API/public roster.`);
|
|
267
280
|
}
|
|
281
|
+
// FALL THROUGH, do not return {}. Returning an empty map here is the vacuous green
|
|
282
|
+
// this file's own comment warns about twelve lines down: with no ranks, every author
|
|
283
|
+
// resolves to null, no blocker can ever fire, and the gate reports success for the
|
|
284
|
+
// reason it was built to prevent. A `--db` gate that cannot reach Postgres is better
|
|
285
|
+
// served by the public leaderboard than by silence.
|
|
268
286
|
}
|
|
269
287
|
try {
|
|
270
288
|
const api = await cliClient({ allowUnauthenticated: true });
|
|
@@ -394,4 +412,8 @@ if (require.main === module) {
|
|
|
394
412
|
module.exports = {
|
|
395
413
|
analyzeCommits, resolveRank, parseCommits, rowsToMap,
|
|
396
414
|
clampWindow, defaultRange, firstParentDepth, DEFAULT_WINDOW,
|
|
415
|
+
// Exported so a test can assert the address is LIVE without a database. The --db
|
|
416
|
+
// path was dead for as long as the db.js carve has been landed precisely because
|
|
417
|
+
// nothing checked that this require target still exports what it is called for.
|
|
418
|
+
ROSTER_DB_MODULE, fetchRankMap,
|
|
397
419
|
};
|
|
@@ -213,12 +213,27 @@ const KNOWN_FAILING = new Set([
|
|
|
213
213
|
// suites set exitCode rather than hard-exiting, so they do not hit this.
|
|
214
214
|
const LIBUV_TEARDOWN_ABORT = /Assertion failed: !\(handle->flags & UV_HANDLE_CLOSING\)/;
|
|
215
215
|
|
|
216
|
+
// A suite that reports honest skips ends `N passed, 0 failed, M skipped` (task 1003993), so
|
|
217
|
+
// the optional suffix is part of a clean verdict. Without it, the first suite to start telling
|
|
218
|
+
// the truth about unrun coverage would have its clean verdict read as unrecognised and its
|
|
219
|
+
// Windows teardown abort counted as a real failure — a fix for one honesty bug creating another.
|
|
220
|
+
const CLEAN_VERDICT_RE = /\b[1-9]\d* passed, 0 failed(?:, \d+ skipped)?\s*$/;
|
|
221
|
+
|
|
222
|
+
// ` SKIP <name>` from a suite's own stdout. Counted here rather than parsed out of the summary
|
|
223
|
+
// line so a suite that prints skips without a summary suffix is still surfaced.
|
|
224
|
+
const SKIP_LINE_RE = /^\s*SKIP\b/;
|
|
225
|
+
|
|
226
|
+
function countSkips(stdout) {
|
|
227
|
+
if (!stdout) return 0;
|
|
228
|
+
return stdout.split('\n').filter((l) => SKIP_LINE_RE.test(l)).length;
|
|
229
|
+
}
|
|
230
|
+
|
|
216
231
|
function isCleanTeardownAbort(stdout, stderr) {
|
|
217
232
|
if (!LIBUV_TEARDOWN_ABORT.test(stderr)) return false;
|
|
218
233
|
// Line-leading only: a passing case titled "... is FAIL-CLOSED" is not a failure.
|
|
219
234
|
if (/^\s*FAIL\b/m.test(stdout)) return false;
|
|
220
235
|
const lastLine = stdout.trim().split('\n').pop();
|
|
221
|
-
return
|
|
236
|
+
return CLEAN_VERDICT_RE.test(lastLine);
|
|
222
237
|
}
|
|
223
238
|
|
|
224
239
|
// The failing tests as ONE line, for the `unit-report` commit status the workflow
|
|
@@ -427,9 +442,13 @@ function main() {
|
|
|
427
442
|
|
|
428
443
|
const failures = [];
|
|
429
444
|
const teardownAborts = [];
|
|
445
|
+
// A GREEN suite that skipped cases is the shape this roll-up used to hide: the suite said so
|
|
446
|
+
// on its own stdout, which was discarded on success, so the only visible line was `ok` and a
|
|
447
|
+
// session reading the roll-up could not tell exercised coverage from absent coverage.
|
|
448
|
+
const skipped = [];
|
|
430
449
|
const runOne = (displayName, filePath) => {
|
|
431
450
|
try {
|
|
432
|
-
execFileSync(process.execPath, [filePath], {
|
|
451
|
+
const out = execFileSync(process.execPath, [filePath], {
|
|
433
452
|
// task 1002644: stdout used to be IGNORED, so the failing assertion (test
|
|
434
453
|
// files print their FAIL lines to stdout) was discarded and the only
|
|
435
454
|
// context shown was the last 3 STDERR lines — often stale diagnostic
|
|
@@ -441,9 +460,16 @@ function main() {
|
|
|
441
460
|
timeout: perTestTimeout,
|
|
442
461
|
env: { ...process.env, NODE_ENV: 'test' },
|
|
443
462
|
});
|
|
444
|
-
|
|
463
|
+
const n = countSkips(out ? out.toString() : '');
|
|
464
|
+
if (n) skipped.push({ name: displayName, n });
|
|
465
|
+
console.log(` ok ${displayName}${n ? ` (${n} SKIPPED — not run on this platform)` : ''}`);
|
|
445
466
|
} catch (err) {
|
|
446
|
-
|
|
467
|
+
// `exit null` on its own is unreadable, and it is what a HANG looks like here: the
|
|
468
|
+
// signal that ended the process is the whole diagnosis, so say it (task 1004052 —
|
|
469
|
+
// a test that hung for the full budget reported `exit null` and nothing else).
|
|
470
|
+
const why = err.killed
|
|
471
|
+
? `TIMEOUT after ${perTestTimeout / 1000}s${err.signal ? `, ${err.signal}` : ''}`
|
|
472
|
+
: `exit ${err.status}${err.signal ? ` (${err.signal})` : ''}`;
|
|
447
473
|
const stdoutText = (err.stdout ? err.stdout.toString() : '').trim();
|
|
448
474
|
if (!err.killed && isCleanTeardownAbort(stdoutText, err.stderr ? err.stderr.toString() : '')) {
|
|
449
475
|
console.log(` ok ${displayName} (clean verdict, then libuv teardown abort, ${why})`);
|
|
@@ -453,6 +479,16 @@ function main() {
|
|
|
453
479
|
console.log(` FAIL ${displayName} (${why})`);
|
|
454
480
|
// The actual failures first: the file's own FAIL lines, then its stdout
|
|
455
481
|
// tail (the last failure's assertion detail + summary), both bounded.
|
|
482
|
+
// EMPTY STDOUT IS ITSELF THE CLUE, and it used to print as a blank gap. node:test
|
|
483
|
+
// buffers a file's whole report until the file finishes, so a suite that hangs
|
|
484
|
+
// mid-run emits NOTHING — which reads as "no detail available" when it actually
|
|
485
|
+
// means "it never got to the end". Name that, and name how to find the case.
|
|
486
|
+
if (!stdoutText) {
|
|
487
|
+
console.log(' (no output at all — node:test buffers a file\'s report until it finishes,');
|
|
488
|
+
console.log(' so this suite did not reach the end. To find which case: run it on the same');
|
|
489
|
+
console.log(' platform with each case writing its name to STDERR before it runs; stderr is');
|
|
490
|
+
console.log(' unbuffered, so the last name with no completion is the one that hung.)');
|
|
491
|
+
}
|
|
456
492
|
const failLines = stdoutText ? stdoutText.split('\n').filter((l) => /\bFAIL\b/.test(l)).slice(0, 5) : [];
|
|
457
493
|
for (const l of failLines) console.log(` ${l.trim()}`);
|
|
458
494
|
if (stdoutText) {
|
|
@@ -492,6 +528,11 @@ function main() {
|
|
|
492
528
|
if (teardownAborts.length) {
|
|
493
529
|
console.log(`${label}: ${teardownAborts.length} passed but aborted in libuv teardown (not failures; task 1003845) — ${teardownAborts.join(', ')}`);
|
|
494
530
|
}
|
|
531
|
+
if (skipped.length) {
|
|
532
|
+
const n = skipped.reduce((sum, s) => sum + s.n, 0);
|
|
533
|
+
console.log(`${label}: ${n} case(s) NOT RUN on this platform — ${skipped.map((s) => `${s.name} (${s.n})`).join(', ')}`);
|
|
534
|
+
console.log(`${label}: a skipped case is not a passing one. Green here does not mean that coverage was exercised.`);
|
|
535
|
+
}
|
|
495
536
|
reportToWorkflow(failures);
|
|
496
537
|
if (failures.length === 0) {
|
|
497
538
|
console.log(`${label}: all ${total} passed.`);
|
|
@@ -503,4 +544,4 @@ function main() {
|
|
|
503
544
|
|
|
504
545
|
if (require.main === module) main();
|
|
505
546
|
|
|
506
|
-
module.exports = { discoverModuleTests, isCleanTeardownAbort, describeFailures, STATUS_DESCRIPTION_MAX, INTEGRATION, INTEGRATION_PARKED, KNOWN_FAILING };
|
|
547
|
+
module.exports = { discoverModuleTests, isCleanTeardownAbort, countSkips, describeFailures, STATUS_DESCRIPTION_MAX, INTEGRATION, INTEGRATION_PARKED, KNOWN_FAILING };
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.877'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|