@bongos/core 1.19.876 → 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 +93 -38
- 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 +2 -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/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 +16 -1
- 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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# scripts/gds/autobongos-service.ps1 -- install the Autobongos runner so it
|
|
2
|
+
# outlives the reboot (task 1003905, ADR 0307 section 5).
|
|
3
|
+
#
|
|
4
|
+
# powershell -ExecutionPolicy Bypass -File scripts\gds\autobongos-service.ps1 install
|
|
5
|
+
# powershell -ExecutionPolicy Bypass -File scripts\gds\autobongos-service.ps1 status
|
|
6
|
+
# powershell -ExecutionPolicy Bypass -File scripts\gds\autobongos-service.ps1 start|stop|uninstall
|
|
7
|
+
#
|
|
8
|
+
# WHY TASK SCHEDULER AND NOT A REAL SERVICE. Windows services speak a protocol
|
|
9
|
+
# (SCM handshakes, stop signals) that node does not, so every "node as a service"
|
|
10
|
+
# route needs a third-party wrapper: nssm, winsw, node-windows. Each is a binary
|
|
11
|
+
# to download, verify, update and explain -- a permanent dependency bought for one
|
|
12
|
+
# machine. Task Scheduler is already on the box, runs at boot with no session,
|
|
13
|
+
# survives sign-out, and restarts a crashed task natively. The one thing it does
|
|
14
|
+
# not give is the Services panel, which nobody here needs.
|
|
15
|
+
#
|
|
16
|
+
# WHAT ACTUALLY KEEPS IT ALIVE IS THE INNER LOOP, and that is the important part
|
|
17
|
+
# to understand before reading the RestartCount settings below. `--forever` never
|
|
18
|
+
# returns: it backs off and probes instead of exiting (task 1003904), so the
|
|
19
|
+
# scheduler's restart is the OUTER ring, for the cases the loop cannot survive at
|
|
20
|
+
# all -- a reboot, a power cut, a node crash, an OOM kill. If this ring is doing
|
|
21
|
+
# work regularly, something is wrong that the backoff should have absorbed, and
|
|
22
|
+
# the run log will say so.
|
|
23
|
+
#
|
|
24
|
+
# UNPRIVILEGED ON PURPOSE. It runs as the logged-in user, not SYSTEM: the runner
|
|
25
|
+
# needs that user's git credential, npm config and Claude session, and SYSTEM has
|
|
26
|
+
# none of them. It is also the smaller blast radius for a process that spawns
|
|
27
|
+
# workers with blanket bypassPermissions.
|
|
28
|
+
|
|
29
|
+
[CmdletBinding()]
|
|
30
|
+
param(
|
|
31
|
+
[Parameter(Position = 0)]
|
|
32
|
+
[ValidateSet('install', 'uninstall', 'start', 'stop', 'status')]
|
|
33
|
+
[string]$Action = 'status',
|
|
34
|
+
|
|
35
|
+
# The checkout to run from. Defaults to the repo this script lives in, so the
|
|
36
|
+
# common case needs no argument and cannot point at the wrong tree by typo.
|
|
37
|
+
[string]$RepoRoot,
|
|
38
|
+
|
|
39
|
+
[string]$TaskName = 'Autobongos'
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
$ErrorActionPreference = 'Stop'
|
|
43
|
+
|
|
44
|
+
if (-not $RepoRoot) {
|
|
45
|
+
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
|
46
|
+
}
|
|
47
|
+
$LogDir = Join-Path $env:LOCALAPPDATA 'cloudbongos'
|
|
48
|
+
$LogFile = Join-Path $LogDir 'autobongos.log'
|
|
49
|
+
$Wrapper = Join-Path $RepoRoot 'scripts\gds\autobongos-service.cmd'
|
|
50
|
+
|
|
51
|
+
function Assert-Prereqs {
|
|
52
|
+
if (-not (Test-Path (Join-Path $RepoRoot 'scripts\gds\autobongos-run.js'))) {
|
|
53
|
+
throw "No autobongos-run.js under $RepoRoot -- pass -RepoRoot <path to the checkout>."
|
|
54
|
+
}
|
|
55
|
+
foreach ($exe in @('node', 'git', 'claude')) {
|
|
56
|
+
if (-not (Get-Command $exe -ErrorAction SilentlyContinue)) {
|
|
57
|
+
# Named, not guessed at: a runner that starts without `claude` on PATH will
|
|
58
|
+
# spawn-fail every worker and look like a mysterious outage in the log.
|
|
59
|
+
throw "'$exe' is not on PATH for this user. The runner needs node, git and claude."
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function Show-Status {
|
|
65
|
+
$t = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
|
66
|
+
if (-not $t) { Write-Host "not installed (task '$TaskName' does not exist)"; return }
|
|
67
|
+
$info = Get-ScheduledTaskInfo -TaskName $TaskName
|
|
68
|
+
Write-Host "task : $TaskName"
|
|
69
|
+
Write-Host "state : $($t.State)"
|
|
70
|
+
Write-Host "last run : $($info.LastRunTime) (result $($info.LastTaskResult))"
|
|
71
|
+
Write-Host "next run : $($info.NextRunTime)"
|
|
72
|
+
Write-Host "log : $LogFile"
|
|
73
|
+
if (Test-Path $LogFile) {
|
|
74
|
+
Write-Host "log size : $([math]::Round((Get-Item $LogFile).Length / 1KB)) KB"
|
|
75
|
+
Write-Host ''
|
|
76
|
+
Write-Host '--- last 15 lines ---'
|
|
77
|
+
Get-Content $LogFile -Tail 15
|
|
78
|
+
} else {
|
|
79
|
+
Write-Host 'log : (not written yet)'
|
|
80
|
+
}
|
|
81
|
+
Write-Host ''
|
|
82
|
+
Write-Host 'The scheduler only says whether the PROCESS is up. Whether the runner'
|
|
83
|
+
Write-Host 'is actually working is the hall: it reads the heartbeat this posts.'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
switch ($Action) {
|
|
87
|
+
'install' {
|
|
88
|
+
Assert-Prereqs
|
|
89
|
+
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
|
|
90
|
+
|
|
91
|
+
$action = New-ScheduledTaskAction -Execute $Wrapper -WorkingDirectory $RepoRoot
|
|
92
|
+
# AtStartup covers the cold reboot the done-when actually names. AtLogOn is
|
|
93
|
+
# NOT used: it would wait for someone to sign in, which is the one thing that
|
|
94
|
+
# must not be required.
|
|
95
|
+
$trigger = New-ScheduledTaskTrigger -AtStartup
|
|
96
|
+
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType S4U -RunLevel Limited
|
|
97
|
+
$settings = New-ScheduledTaskSettingsSet `
|
|
98
|
+
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
|
99
|
+
-StartWhenAvailable `
|
|
100
|
+
-RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) `
|
|
101
|
+
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
|
102
|
+
-MultipleInstances IgnoreNew
|
|
103
|
+
# ExecutionTimeLimit ZERO = never kill it. The default is three days, and a
|
|
104
|
+
# daemon silently executed after three days is exactly the invisible death
|
|
105
|
+
# this whole task exists to prevent.
|
|
106
|
+
# MultipleInstances IgnoreNew = the scheduler will not start a second copy
|
|
107
|
+
# over a live one; the heartbeat file is the second guard, inside the runner.
|
|
108
|
+
|
|
109
|
+
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger `
|
|
110
|
+
-Principal $principal -Settings $settings -Force | Out-Null
|
|
111
|
+
|
|
112
|
+
Write-Host "installed '$TaskName' -- starts at boot, restarts on crash, logs to $LogFile"
|
|
113
|
+
Write-Host ''
|
|
114
|
+
Write-Host 'Start it now with: .\scripts\gds\autobongos-service.ps1 start'
|
|
115
|
+
Write-Host 'Then confirm from the hall (/builders/gate) that it is checking in.'
|
|
116
|
+
}
|
|
117
|
+
'uninstall' {
|
|
118
|
+
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
|
|
119
|
+
Write-Host "removed '$TaskName'. The log at $LogFile is left in place."
|
|
120
|
+
}
|
|
121
|
+
'start' { Start-ScheduledTask -TaskName $TaskName; Write-Host "started '$TaskName'" }
|
|
122
|
+
'stop' {
|
|
123
|
+
Stop-ScheduledTask -TaskName $TaskName
|
|
124
|
+
Write-Host "stopped '$TaskName' -- but note this only stops the PROCESS on this machine."
|
|
125
|
+
Write-Host 'The durable way to stop the runner is the kill switch in the hall, which'
|
|
126
|
+
Write-Host 'survives a reboot and cannot be undone by anything running here.'
|
|
127
|
+
}
|
|
128
|
+
default { Show-Status }
|
|
129
|
+
}
|
|
@@ -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
|
};
|
|
@@ -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.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');
|