@bongos/core 1.19.876 → 1.19.878
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 +110 -40
- 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 +163 -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/digest.js +344 -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/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/autobongos-digest.js +123 -0
- package/scripts/gds/autobongos-run.js +375 -11
- package/scripts/gds/autobongos-service.cmd +38 -0
- package/scripts/gds/autobongos-service.ps1 +135 -0
- package/scripts/gds/deploy-main-audit-gate.sh +90 -0
- package/scripts/gds/main-audit.js +26 -4
- package/scripts/gds/provision-core-upgrade.js +23 -3
- package/scripts/gds/run-unit-tests.js +16 -1
- package/src/module-api.js +1 -1
- package/tests/autobongos_cadence.mjs +602 -0
- package/tests/autonomy_digest.mjs +302 -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/core_upgrade_runner.mjs +43 -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
|
@@ -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
|
};
|
|
@@ -187,6 +187,25 @@ async function coreUpgradeInstance(inst, deps, intent) {
|
|
|
187
187
|
const healthUrl = `http://127.0.0.1:${inst.port}/healthz`;
|
|
188
188
|
const versionUrl = `http://127.0.0.1:${inst.port}/version`;
|
|
189
189
|
const sudoP = privileged ? 'sudo ' : '';
|
|
190
|
+
// WHERE THE CLI LIVES AND WHERE THE TARGET LIVES ARE DIFFERENT PLACES (task 1004053).
|
|
191
|
+
//
|
|
192
|
+
// `scripts/gds/upgrade.js` ships in the CORE checkout. A provisioned instance root holds
|
|
193
|
+
// no scripts/ directory at all — its core sits under node_modules/@bongos/core — so
|
|
194
|
+
// running the CLI with cwd = the instance root resolved nothing, and EVERY standalone
|
|
195
|
+
// upgrade died before it began:
|
|
196
|
+
//
|
|
197
|
+
// Error: Cannot find module '/srv/<base>/<slug>/scripts/gds/upgrade.js'
|
|
198
|
+
//
|
|
199
|
+
// It worked only where instanceDir HAPPENED to be this checkout — the control-plane self
|
|
200
|
+
// row and a co-tenant — which is why it survived to production: the shapes that are
|
|
201
|
+
// upgraded by hand are the shapes the relative path fits. Every ACTIVE tenant is
|
|
202
|
+
// standalone, so the deploy door could not move a single one of them.
|
|
203
|
+
//
|
|
204
|
+
// upgrade.js's own header states the contract this restores: "Runs from the consumer
|
|
205
|
+
// instance root (default) or `--instance <dir>`. This script ships IN the core … against
|
|
206
|
+
// `--instance <consumer>`." So: run it where it exists, and NAME the target.
|
|
207
|
+
const runDir = REPO_ROOT;
|
|
208
|
+
const targetArg = instanceDir === REPO_ROOT ? '' : `--instance ${instanceDir}`;
|
|
190
209
|
const cli = path.posix.join('scripts', 'gds', 'upgrade.js');
|
|
191
210
|
const requestedBy = intent.requested_by && /^api:archon:(\d+)$/.test(intent.requested_by)
|
|
192
211
|
? intent.requested_by.replace(/^api:archon:/, '')
|
|
@@ -195,6 +214,7 @@ async function coreUpgradeInstance(inst, deps, intent) {
|
|
|
195
214
|
`${sudoP}node ${cli}`,
|
|
196
215
|
'--registry',
|
|
197
216
|
`--to ${to}`,
|
|
217
|
+
targetArg,
|
|
198
218
|
`--service ${inst.slug}`,
|
|
199
219
|
`--health-url ${healthUrl}`,
|
|
200
220
|
`--version-url ${versionUrl}`,
|
|
@@ -204,13 +224,13 @@ async function coreUpgradeInstance(inst, deps, intent) {
|
|
|
204
224
|
].filter(Boolean).join(' ');
|
|
205
225
|
|
|
206
226
|
if (!apply) {
|
|
207
|
-
log(` ✓ would run: ${cmd} (dry-run — nothing ran)`);
|
|
227
|
+
log(` ✓ would run (in ${runDir}): ${cmd} (dry-run — nothing ran)`);
|
|
208
228
|
log(` ✓ would then read ${versionUrl} on the box and require it to say ${to}`);
|
|
209
229
|
return { ok: true, served: null };
|
|
210
230
|
}
|
|
211
231
|
try {
|
|
212
|
-
log(` [upgrade] ${cmd}`);
|
|
213
|
-
boxExec(cmd, { cwd:
|
|
232
|
+
log(` [upgrade] (in ${runDir}) ${cmd}`);
|
|
233
|
+
boxExec(cmd, { cwd: runDir });
|
|
214
234
|
} catch (e) {
|
|
215
235
|
// upgrade.js rolls itself back on a failed health check or an unchanged served
|
|
216
236
|
// version, so by the time this throws the instance is back on the core it had.
|
|
@@ -464,7 +464,12 @@ function main() {
|
|
|
464
464
|
if (n) skipped.push({ name: displayName, n });
|
|
465
465
|
console.log(` ok ${displayName}${n ? ` (${n} SKIPPED — not run on this platform)` : ''}`);
|
|
466
466
|
} catch (err) {
|
|
467
|
-
|
|
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})` : ''}`;
|
|
468
473
|
const stdoutText = (err.stdout ? err.stdout.toString() : '').trim();
|
|
469
474
|
if (!err.killed && isCleanTeardownAbort(stdoutText, err.stderr ? err.stderr.toString() : '')) {
|
|
470
475
|
console.log(` ok ${displayName} (clean verdict, then libuv teardown abort, ${why})`);
|
|
@@ -474,6 +479,16 @@ function main() {
|
|
|
474
479
|
console.log(` FAIL ${displayName} (${why})`);
|
|
475
480
|
// The actual failures first: the file's own FAIL lines, then its stdout
|
|
476
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
|
+
}
|
|
477
492
|
const failLines = stdoutText ? stdoutText.split('\n').filter((l) => /\bFAIL\b/.test(l)).slice(0, 5) : [];
|
|
478
493
|
for (const l of failLines) console.log(` ${l.trim()}`);
|
|
479
494
|
if (stdoutText) {
|
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.878'; // 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');
|