@bongos/core 1.22.2 → 1.23.2
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 +104 -64
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +8 -0
- package/clients/bongos-client/index.cjs +8 -0
- package/clients/bongos-client/index.d.ts +11 -1
- package/clients/bongos-client/index.mjs +8 -0
- package/docs/adr/0342-module-categories-are-eight-parents-with-approved-sub-categories.md +4 -2
- package/docs/api/openapi.json +210 -4
- package/docs/api-reference.md +6 -2
- package/docs/architecture.md +11 -0
- package/docs/copy-inventory.md +1 -1
- package/docs/copy-registry.json +1 -1
- package/docs/module-api-changelog.md +8 -0
- package/docs/modules-contract.md +1 -0
- package/docs/page-readings.json +40 -40
- package/migrations/core_280_store_subcategories.sql +58 -0
- package/modules/government/catalog.js +3 -0
- package/modules/government/migrations/government_034_module_subcategory_review.sql +35 -0
- package/modules/hall-ui/public/deploy.js +3 -3
- package/modules/hall-ui/public/dom-utils.js +5 -1
- package/modules/hall-ui/public/genesis-home.js +1 -1
- package/modules/lifecycle/github-push.js +1 -2
- package/modules/npm-release/preview/supervisor.js +2 -1
- package/modules/platform-identity/project-stats.js +2 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +13 -0
- package/scripts/bongos/autobongos-run.js +2 -1
- package/scripts/bongos/autonomy-gauge.js +2 -1
- package/scripts/bongos/backfill-disciplines.js +1 -1
- package/scripts/bongos/backfill-sessions.js +1 -1
- package/scripts/bongos/backup.js +2 -1
- package/scripts/bongos/dev.js +1 -1
- package/scripts/bongos/do-api.js +2 -1
- package/scripts/bongos/fitness.js +5 -6
- package/scripts/bongos/local-preview.js +2 -1
- package/scripts/bongos/login.js +1 -1
- package/scripts/bongos/page-reader.js +2 -1
- package/scripts/bongos/probe-cache.js +2 -1
- package/scripts/bongos/provision-core-upgrade.js +2 -1
- package/scripts/bongos/provision.js +5 -4
- package/scripts/bongos/publish-integration-gate.js +2 -1
- package/scripts/bongos/render-api.js +2 -1
- package/scripts/bongos/sandbox-stage.js +2 -1
- package/scripts/bongos/seed-bongos-v1-idea-routing-tasks.js +1 -1
- package/scripts/bongos/seed-bv1-profiles-tasks.js +1 -1
- package/scripts/bongos/seed-bv2-ideator-surface-tasks.js +1 -1
- package/scripts/bongos/session-eval.js +2 -1
- package/scripts/bongos/setup.js +2 -1
- package/scripts/bongos/sleep-helper-guard.js +88 -0
- package/scripts/bongos/upgrade.js +2 -2
- package/scripts/gds/sleep-helper-guard.js +6 -0
- package/src/bongos/module-scope-map.js +1 -0
- package/src/bongos/module-store.js +8 -3
- package/src/bongos/module-subcategories.js +316 -0
- package/src/bongos/route-rank-check.js +5 -0
- package/src/bongos/routes/modules.js +55 -1
- package/src/module-api.js +6 -1
- package/src/module-loader/manifest-schema.js +4 -3
- package/src/sleep.js +24 -0
- package/tests/deploy_divergence_line.mjs +4 -1
- package/tests/deploy_page_projects.mjs +2 -1
- package/tests/module_api.mjs +1 -0
- package/tests/module_store_instance_root.mjs +2 -1
- package/tests/module_store_subcategories.mjs +364 -0
- package/tests/sleep_helper.mjs +99 -0
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
const fs = require('node:fs');
|
|
20
20
|
const { decideCapacity } = require('../../modules/autonomy/gauge.js');
|
|
21
|
+
const { sleep: sharedSleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
21
22
|
|
|
22
23
|
function gaugePath() {
|
|
23
24
|
if (process.env.AUTOBONGOS_GAUGE_FILE) return process.env.AUTOBONGOS_GAUGE_FILE;
|
|
@@ -70,7 +71,7 @@ async function main(argv) {
|
|
|
70
71
|
const now = Math.floor(Date.now() / 1000);
|
|
71
72
|
const waitS = d.sleepUntil ? Math.max(1, d.sleepUntil - now) : 60;
|
|
72
73
|
if (!json) console.log(` sleeping ${waitS}s…`);
|
|
73
|
-
await
|
|
74
|
+
await sharedSleep(waitS * 1000);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -47,7 +47,7 @@ function classify(task) {
|
|
|
47
47
|
return { discipline: FALLBACK, reasoned: false, confidence: null };
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const sleep = (
|
|
50
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
51
51
|
|
|
52
52
|
// PATCH one row, WAITING OUT the per-session rate limit rather than counting it as a
|
|
53
53
|
// failure. The first run of this backfill treated a 429 as a failed row and stopped
|
|
@@ -241,7 +241,7 @@ function loadExisting() {
|
|
|
241
241
|
return set;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
const sleep = (
|
|
244
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
245
245
|
|
|
246
246
|
async function main() {
|
|
247
247
|
const existing = loadExisting();
|
package/scripts/bongos/backup.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// Rank gate: metic+archon.
|
|
9
9
|
|
|
10
10
|
const { cliClient, requireSession } = require('./cli-lib');
|
|
11
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
11
12
|
|
|
12
13
|
function fmtBytes(n) {
|
|
13
14
|
if (n == null) return '(unknown size)';
|
|
@@ -84,7 +85,7 @@ async function trigger() {
|
|
|
84
85
|
const deadline = Date.now() + 60_000;
|
|
85
86
|
let landed = false;
|
|
86
87
|
while (Date.now() < deadline) {
|
|
87
|
-
await
|
|
88
|
+
await sleep(3_000);
|
|
88
89
|
const status = await fetchStatus();
|
|
89
90
|
const nowFile = status.latest_dump && status.latest_dump.filename;
|
|
90
91
|
if (nowFile && nowFile !== beforeFile) {
|
package/scripts/bongos/dev.js
CHANGED
|
@@ -57,7 +57,7 @@ function tail(s, n = 6) {
|
|
|
57
57
|
}
|
|
58
58
|
// Portable synchronous pause (no subprocess, no foreground-sleep dependency).
|
|
59
59
|
function sleepSync(ms) { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
|
|
60
|
-
const sleep = (
|
|
60
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
61
61
|
|
|
62
62
|
function usage() {
|
|
63
63
|
console.log(`bongos dev — run THIS instance, in one command.
|
package/scripts/bongos/do-api.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
// createDroplet · getDroplet · deleteDroplet · settleDroplet (poll to active)
|
|
25
25
|
|
|
26
26
|
const { JsonHttpApi, makeApiError } = require('./http-api-client');
|
|
27
|
+
const { sleep: sharedSleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
27
28
|
|
|
28
29
|
const DO_API_BASE = 'https://api.digitalocean.com/v2';
|
|
29
30
|
|
|
@@ -79,7 +80,7 @@ class DoApi extends JsonHttpApi {
|
|
|
79
80
|
// Returns the stopping value, or { timedOut:true } after `tries` rounds.
|
|
80
81
|
// `sleepFn` lets tests drive the loop with no real clock.
|
|
81
82
|
async _poll({ tries = 60, sleepMs = 5000, sleepFn } = {}, step) {
|
|
82
|
-
const sleep = sleepFn ||
|
|
83
|
+
const sleep = sleepFn || sharedSleep;
|
|
83
84
|
for (let i = 0; i < tries; i++) {
|
|
84
85
|
const r = await step(i);
|
|
85
86
|
if (r && r.done) return r.value;
|
|
@@ -523,7 +523,7 @@ const KERNEL_FILES = [
|
|
|
523
523
|
'src/module-api.js', // the published doorway
|
|
524
524
|
'src/modules.js', // the registry/resolver facade
|
|
525
525
|
'src/module-seams.js', // the seam registry (ports/events/contributions)
|
|
526
|
-
'src/role-responsibilities.js', 'src/html-escape.js', // owner's role statements (task 1003732) + THE server HTML escaper (task 1004876) —
|
|
526
|
+
'src/role-responsibilities.js', 'src/html-escape.js', 'src/sleep.js', // owner's role statements (task 1003732) + THE server HTML escaper (task 1004876) + THE server sleep (task 1004893) — all import nothing
|
|
527
527
|
'src/module-loader/loader.js', // the disk loader (discover → validate → mount)
|
|
528
528
|
'src/module-loader/manifest-schema.js', // the manifest validator
|
|
529
529
|
'src/module-loader/semver.js', // coreVersion compatibility
|
|
@@ -1365,13 +1365,12 @@ const CHECKS = [
|
|
|
1365
1365
|
require('./doc-control-claims.js').checkDocControlClaims, // Check 37 — task 1003418 / audit A14: a doc cannot describe an enforcement control that does not exist (derived from the docs, not a hand list)
|
|
1366
1366
|
require('./doc-cli-guard.js').checkDocNamesRealCli, // Check 22 — task 1003165 / G01: a doc that names a CLI invocation must match the real CLI
|
|
1367
1367
|
require('./skill-preflight.js').checkOpsSkillsDeclareMachine, // Check 23 — task 1003167 / G03: an ops skill declares the machine it needs
|
|
1368
|
-
// Check 24 — task 1003166 / G02: no ops script hardcodes a version id. It
|
|
1369
|
-
//
|
|
1370
|
-
//
|
|
1371
|
-
// Task 1003206 carved this file from 2,004 lines to ~1,350, so that toll is paid
|
|
1372
|
-
// and the check gets its own line back.
|
|
1368
|
+
// Check 24 — task 1003166 / G02: no ops script hardcodes a version id. It once shared
|
|
1369
|
+
// Check 23's line because fitness.js was over the excess-lines ratchet (idea 1000810);
|
|
1370
|
+
// task 1003206 carved this file from 2,004 lines to ~1,350, so it has its own line back.
|
|
1373
1371
|
require('./version-literal-guard.js').checkNoVersionLiterals,
|
|
1374
1372
|
require('./escape-helper-guard.js').checkOneHtmlEscaper, // task 1004876: ONE HTML escaper per side (dom-utils.js / src/html-escape.js), and hall pages load dom-utils first
|
|
1373
|
+
require('./sleep-helper-guard.js').checkOneSleepHelper, // task 1004893: ONE sleep per side (dom-utils.js OTB.sleep / src/sleep.js)
|
|
1375
1374
|
require('./dev-box-guard.js').checkNoDevBox, // task 1003899 / ADR 0346: the retired dev box stays out (its exemption list lives in that file)
|
|
1376
1375
|
require('./control-manifest.js').checkControlsExist, // Check 21 — task 1003131: goal-1000076 controls assert their own existence (the ADR 0022 assurance-of-assurance)
|
|
1377
1376
|
require('./routine-schedule.js').checkRoutineCadencesScheduled, // Check 25 — task 1003209: a routine that declares a cadence must have a scheduler firing it AT that period (ADR 0188)
|
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
// shipped. Set OTB_PREVIEW_PORT to override the default 3100.
|
|
16
16
|
|
|
17
17
|
const lib = require('./local-preview-lib');
|
|
18
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
18
19
|
|
|
19
20
|
async function pollHealthy(ms = 20_000) {
|
|
20
21
|
const end = Date.now() + ms;
|
|
21
22
|
while (Date.now() < end) {
|
|
22
23
|
if (await lib.health()) return true;
|
|
23
|
-
await
|
|
24
|
+
await sleep(500);
|
|
24
25
|
}
|
|
25
26
|
return false;
|
|
26
27
|
}
|
package/scripts/bongos/login.js
CHANGED
|
@@ -86,7 +86,7 @@ function prompt(q) {
|
|
|
86
86
|
return new Promise((resolve) => rl.question(q, (a) => { rl.close(); resolve(String(a || '').trim()); }));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
const sleep = (
|
|
89
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
90
90
|
|
|
91
91
|
// Run the device flow and return one of:
|
|
92
92
|
// { state: 'ok', token, builder } | { state: 'not_approved' } | { state: 'expired' }
|
|
@@ -57,6 +57,7 @@ const fs = require('node:fs');
|
|
|
57
57
|
const path = require('node:path');
|
|
58
58
|
const crypto = require('node:crypto');
|
|
59
59
|
const { spawn, spawnSync } = require('node:child_process');
|
|
60
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
60
61
|
|
|
61
62
|
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
62
63
|
const INVENTORY_REL = 'docs/page-inventory.json';
|
|
@@ -511,7 +512,7 @@ async function renderOne(browser, kit, plan, base) {
|
|
|
511
512
|
try { await kit.runActions(page, plan.actions); } catch (e) { notes.push(`${plan.state}: an action failed (${String(e.message).split('\n')[0].slice(0, 120)}); read as it stood`); }
|
|
512
513
|
// A body read on a response from a page that has since navigated away never
|
|
513
514
|
// settles, so the wait for the data is bounded.
|
|
514
|
-
await Promise.race([Promise.all(pending),
|
|
515
|
+
await Promise.race([Promise.all(pending), sleep(2000)]);
|
|
515
516
|
const got = await page.evaluate(captureInPage);
|
|
516
517
|
if (got.branding) collectStrings(got.branding, data);
|
|
517
518
|
return { state: plan.state, strings: got.strings, notes, data };
|
|
@@ -38,6 +38,7 @@ const fs = require('node:fs');
|
|
|
38
38
|
const os = require('node:os');
|
|
39
39
|
const path = require('node:path');
|
|
40
40
|
const crypto = require('node:crypto');
|
|
41
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
41
42
|
|
|
42
43
|
// Default freshness. The probes this serves answer questions that change on a
|
|
43
44
|
// deploy or a ship — minutes, not seconds — so 60s is generous for a session brief
|
|
@@ -156,7 +157,7 @@ const POLL_MS = 100;
|
|
|
156
157
|
async function waitForRecord(file, ttlMs, now) {
|
|
157
158
|
const deadline = Date.now() + WAIT_MS;
|
|
158
159
|
while (Date.now() < deadline) {
|
|
159
|
-
await
|
|
160
|
+
await sleep(POLL_MS);
|
|
160
161
|
const rec = readRecord(file);
|
|
161
162
|
if (isFresh(rec, ttlMs, now())) return rec;
|
|
162
163
|
// The holder is gone and left nothing: stop waiting and do the work ourselves.
|
|
@@ -39,6 +39,7 @@ const { ensurePinWriteKey, hasOwnAccount, pinKeyPath, pinSshCommand } = require(
|
|
|
39
39
|
const { replayPinCommit } = require('./provision-pin-replay.js'); // GitHub ahead → pin on top (task 1004733)
|
|
40
40
|
const { isValidSlug } = require('../../modules/provisioning/provisioning.js');
|
|
41
41
|
const { RELEASE_NOTES_FILE, RELEASE_NOTES_MAX_BYTES, parseChangeRecords, notesBetween } = require('./release-notes.js');
|
|
42
|
+
const { sleep: sharedSleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
42
43
|
const outcome = require('./upgrade-outcome.js'); // how far a failed move got, as a class (task 1004446)
|
|
43
44
|
|
|
44
45
|
// The shapes whose box this runner can actually reach. Mirrors UPGRADE_SHAPES in the
|
|
@@ -359,7 +360,7 @@ async function coreUpgradeInstance(inst, deps, intent) {
|
|
|
359
360
|
// single immediate read races it binding its port. A false "did not take" here is
|
|
360
361
|
// never cosmetic — it is what the owner is shown after the one act they were told they
|
|
361
362
|
// could finally perform.
|
|
362
|
-
const sleep = deps.sleep ||
|
|
363
|
+
const sleep = deps.sleep || sharedSleep;
|
|
363
364
|
let served = null;
|
|
364
365
|
for (let i = 0; i < MANIFEST_VERIFY_TRIES; i++) {
|
|
365
366
|
const res = boxExec(versionCmd(inst), { allowFail: true });
|
|
@@ -57,6 +57,7 @@ const { capacityPreflight, statusCapacityLine } = require('./provision-capacity.
|
|
|
57
57
|
const { installedCoreVersion, runnerBanner } = require('./runner-drift.js');
|
|
58
58
|
const { teardownInstance } = require('./provision-teardown.js');
|
|
59
59
|
const { disconnectInstance } = require('./provision-disconnect.js');
|
|
60
|
+
const { sleep: sharedSleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
60
61
|
const { coreUpgradeInstance, preflightCoreUpgrade, snapshotAllCoreVersions } = require('./provision-core-upgrade.js'); // the owner's deploy door, ADR 0293
|
|
61
62
|
const { isByoHost, recordByoHost } = require('./provision-byo-host.js'); // stand up NOTHING for a byo-host row, ADR 0323
|
|
62
63
|
|
|
@@ -515,7 +516,7 @@ async function provisionInstance(inst, deps) {
|
|
|
515
516
|
// budget on an instance that may simply be slow); the liveness overlay is
|
|
516
517
|
// what tells the owner the truth.
|
|
517
518
|
log(' [healthz] verify the instance answers');
|
|
518
|
-
const sleep = deps.sleep ||
|
|
519
|
+
const sleep = deps.sleep || sharedSleep;
|
|
519
520
|
let healthy = null; // null = not observed (dry-run)
|
|
520
521
|
const tries = apply ? 10 : 1;
|
|
521
522
|
for (let i = 0; i < tries; i++) {
|
|
@@ -869,7 +870,7 @@ async function settingsApplyInstance(inst, deps) {
|
|
|
869
870
|
// 7's rule); a loopback PASS proves the process, not the address — the
|
|
870
871
|
// domain-scoped sweep earns that claim.
|
|
871
872
|
log(' [healthz] verify the instance answers after the restart');
|
|
872
|
-
const sleep = deps.sleep ||
|
|
873
|
+
const sleep = deps.sleep || sharedSleep;
|
|
873
874
|
let healthy = false;
|
|
874
875
|
for (let i = 0; i < MANIFEST_VERIFY_TRIES; i++) {
|
|
875
876
|
const res = boxExec(healthzCmd(inst), { allowFail: true });
|
|
@@ -959,7 +960,7 @@ async function restartInstance(inst, deps) {
|
|
|
959
960
|
// false "down" is never cosmetic here: this instance is ALREADY flagged
|
|
960
961
|
// unreachable, and a hasty verdict would tell the owner their restart failed.
|
|
961
962
|
log(' [healthz] verify the instance answers after the restart');
|
|
962
|
-
const sleep = deps.sleep ||
|
|
963
|
+
const sleep = deps.sleep || sharedSleep;
|
|
963
964
|
let healthy = false;
|
|
964
965
|
for (let i = 0; i < MANIFEST_VERIFY_TRIES; i++) {
|
|
965
966
|
const res = boxExec(healthzCmd(inst), { allowFail: true });
|
|
@@ -1113,7 +1114,7 @@ async function placeManifestCreds(inst, mrow, deps) {
|
|
|
1113
1114
|
// used to record a failure for a setup that had succeeded. The JSON answer lives
|
|
1114
1115
|
// at /api/bongos/healthz — the root /healthz says a plain "ok" (task 1004577).
|
|
1115
1116
|
if (apply) {
|
|
1116
|
-
const sleep = deps.sleep ||
|
|
1117
|
+
const sleep = deps.sleep || sharedSleep;
|
|
1117
1118
|
let authConfigured = false;
|
|
1118
1119
|
for (let i = 0; i < MANIFEST_VERIFY_TRIES; i++) {
|
|
1119
1120
|
const out = boxExec(authHealthzCmd(inst), { allowFail: true });
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
'use strict';
|
|
39
39
|
|
|
40
40
|
const fs = require('node:fs');
|
|
41
|
+
const { sleep: sharedSleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
41
42
|
|
|
42
43
|
const WORKFLOW_FILE = 'integration.yml';
|
|
43
44
|
const DEFAULT_TIMEOUT_MIN = 12; // inside publish.yml's 20-minute job, leaving room to pack + publish
|
|
@@ -70,7 +71,7 @@ function runsUrl(repo, sha) {
|
|
|
70
71
|
// touches the world is injectable for tests/publish_integration_gate.mjs.
|
|
71
72
|
async function waitForIntegration({
|
|
72
73
|
sha, repo, token, timeoutMs, intervalMs,
|
|
73
|
-
fetchImpl = globalThis.fetch, sleep =
|
|
74
|
+
fetchImpl = globalThis.fetch, sleep = sharedSleep, now = Date.now,
|
|
74
75
|
}) {
|
|
75
76
|
const deadline = now() + timeoutMs;
|
|
76
77
|
let last = { verdict: 'wait', run: null, why: 'no read yet' };
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
'use strict';
|
|
32
32
|
|
|
33
33
|
const { JsonHttpApi, makeApiError } = require('./http-api-client');
|
|
34
|
+
const { sleep: sharedSleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
34
35
|
|
|
35
36
|
const RENDER_API_BASE = 'https://api.render.com/v1';
|
|
36
37
|
|
|
@@ -184,7 +185,7 @@ class RenderApi extends JsonHttpApi {
|
|
|
184
185
|
|
|
185
186
|
// Poll until a new database is 'available'. `sleepFn` lets tests drive it with no clock.
|
|
186
187
|
async settlePostgres(postgresId, { tries = 60, sleepMs = 5000, sleepFn } = {}) {
|
|
187
|
-
const sleep = sleepFn ||
|
|
188
|
+
const sleep = sleepFn || sharedSleep;
|
|
188
189
|
for (let i = 0; i < tries; i++) {
|
|
189
190
|
const pg = await this.getPostgres(postgresId);
|
|
190
191
|
if (pg.status === 'available') return pg;
|
|
@@ -26,6 +26,7 @@ const os = require('node:os');
|
|
|
26
26
|
const path = require('node:path');
|
|
27
27
|
const readline = require('node:readline');
|
|
28
28
|
const localPreview = require('./local-preview-lib');
|
|
29
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
29
30
|
|
|
30
31
|
// The default health URL waitHealthy probes when a caller supplies none; the
|
|
31
32
|
// local context always passes its own (localPreview.healthUrl).
|
|
@@ -198,7 +199,7 @@ async function waitHealthy(deps = {}) {
|
|
|
198
199
|
} catch (e) {
|
|
199
200
|
lastErr = e.message;
|
|
200
201
|
}
|
|
201
|
-
await
|
|
202
|
+
await sleep(deps.pollMs || HEALTH_POLL_MS);
|
|
202
203
|
}
|
|
203
204
|
return { ok: false, reason: lastErr };
|
|
204
205
|
}
|
|
@@ -25,7 +25,7 @@ const META_TASK = 1003059; // this planning session's meta task — moved i
|
|
|
25
25
|
const DISCORD_BUGS_IDEA = 1000771; // subsumed by R11; marked promoted against it
|
|
26
26
|
const H = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
|
|
27
27
|
|
|
28
|
-
const sleep = (
|
|
28
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
29
29
|
async function api(method, path, body, _tries = 0) {
|
|
30
30
|
await sleep(400); // base throttle to stay under the per-IP rate limit
|
|
31
31
|
const res = await fetch(`${BASE}/api/bongos${path}`, {
|
|
@@ -21,7 +21,7 @@ const GOAL = 1000045;
|
|
|
21
21
|
const SRC = 'planning-session';
|
|
22
22
|
const H = { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' };
|
|
23
23
|
|
|
24
|
-
const sleep = (
|
|
24
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
25
25
|
async function api(method, path, body, _tries = 0) {
|
|
26
26
|
await sleep(400); // base throttle to stay under the per-IP rate limit
|
|
27
27
|
const res = await fetch(`${BASE}/api/bongos${path}`, {
|
|
@@ -32,7 +32,7 @@ const CRIT = {
|
|
|
32
32
|
|
|
33
33
|
const SPEC = 'Spec: `docs/specs/bongos-v2-ideator-surface.md` (final). Design of record: idea 1001072 + `docs/design/ideator-surface-direction.md`.';
|
|
34
34
|
|
|
35
|
-
const sleep = (
|
|
35
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
36
36
|
async function api(method, path, body, _tries = 0) {
|
|
37
37
|
await sleep(400); // base throttle to stay under the per-IP rate limit
|
|
38
38
|
const res = await fetch(`${BASE}/api/bongos${path}`, {
|
|
@@ -27,6 +27,7 @@ const wanderingPrefs = require('../../modules/builder-settings/wandering-prefs')
|
|
|
27
27
|
// fitness scans (modules/sessions/CLAUDE.md, "NOT in this module").
|
|
28
28
|
const { scoreAccessibility } = require('../../modules/sessions/accessibility');
|
|
29
29
|
const { resolveDefaultVersion } = require('./version-select.js');
|
|
30
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
30
31
|
|
|
31
32
|
// ---- classification vocabularies -----------------------------------------
|
|
32
33
|
|
|
@@ -489,7 +490,7 @@ if (require.main === module) {
|
|
|
489
490
|
candidates = (search.data && search.data.sessions) || [];
|
|
490
491
|
if (candidates.length > 0 || attempt === maxAttempts) break;
|
|
491
492
|
console.error(`search returned 0 candidates (attempt ${attempt}/${maxAttempts}) — retrying in 1500ms (possible stale read, #608)...`);
|
|
492
|
-
await
|
|
493
|
+
await sleep(1500);
|
|
493
494
|
}
|
|
494
495
|
// An empty corpus is an ANOMALY by default (stale read / upload gap), NOT a
|
|
495
496
|
// clean "nothing to do" — refuse to write a misleadingly-empty report so the
|
package/scripts/bongos/setup.js
CHANGED
|
@@ -32,6 +32,7 @@ function statusOrigin() {
|
|
|
32
32
|
return API_BASE;
|
|
33
33
|
}
|
|
34
34
|
const onboardingConfig = require('./onboarding-config');
|
|
35
|
+
const { sleep } = require('../../src/sleep'); // the one shared sleep (task 1004893)
|
|
35
36
|
|
|
36
37
|
const DRY_RUN = process.argv.includes('--dry-run');
|
|
37
38
|
// --skip-keys mirrors the same flag on scripts/setup-builder.sh — when set,
|
|
@@ -586,7 +587,7 @@ async function main() {
|
|
|
586
587
|
const deadline = Date.now() + expires_in * 1000;
|
|
587
588
|
let pollInterval = interval * 1000;
|
|
588
589
|
while (Date.now() < deadline) {
|
|
589
|
-
await
|
|
590
|
+
await sleep(pollInterval);
|
|
590
591
|
const poll = await (await cliClient()).auth.postAuthDevicePoll({ body: { device_code } });
|
|
591
592
|
// Admission gate (SR-19 / task #1000): a first-time GitHub account that an
|
|
592
593
|
// Archon hasn't approved is refused (HTTP 403, state:'not_approved'). Print a
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// scripts/bongos/sleep-helper-guard.js — fitness check: ONE "wait N milliseconds"
|
|
2
|
+
// helper per side (task 1004893, goal 1000135).
|
|
3
|
+
//
|
|
4
|
+
// WHY. ~27 files each hand-rolled a Promise around setTimeout to wait. That is
|
|
5
|
+
// harmless on its own, but it is the copy-paste that task 1004876 found ~45 of for
|
|
6
|
+
// the HTML escaper, where some copies had quietly drifted. They were collapsed onto
|
|
7
|
+
// exactly two definitions:
|
|
8
|
+
//
|
|
9
|
+
// browser modules/hall-ui/public/dom-utils.js → window.OTB.sleep
|
|
10
|
+
// server src/sleep.js → sleep (modules reach it
|
|
11
|
+
// through src/module-api.js)
|
|
12
|
+
//
|
|
13
|
+
// This check keeps it that way. It looks for the SHAPE, not a name: a Promise whose
|
|
14
|
+
// resolver is handed straight to setTimeout is a sleep whatever the variable is
|
|
15
|
+
// called, and inline copies with no name at all were half of what the audit found. A BLOCKING sleep (Atomics.wait —
|
|
16
|
+
// merge-lock.js, rename-ledger-prose.js) has a different shape and is left alone:
|
|
17
|
+
// it freezes the thread on purpose, and the shared helper cannot do that.
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const fs = require('node:fs');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
|
|
24
|
+
const { REPO_ROOT } = require('./fitness-lib.js');
|
|
25
|
+
|
|
26
|
+
const ALLOWED = new Map([
|
|
27
|
+
['modules/hall-ui/public/dom-utils.js', 'the shared BROWSER sleep (window.OTB.sleep)'],
|
|
28
|
+
['src/sleep.js', 'the shared SERVER sleep (module-api sleep)'],
|
|
29
|
+
['modules/ui-design/kit/render.js', 'its wait runs INSIDE the browser page via page.evaluate — the function is serialized, so it cannot reach either shared helper'],
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
// Where code lives. Tests are out of scope: a test injects its own fake sleep.
|
|
33
|
+
const SCAN_ROOTS = ['src', 'scripts', 'modules', 'server.js', 'bin'];
|
|
34
|
+
const SCAN_EXT = /\.(c|m)?js$|\.html$/;
|
|
35
|
+
const SKIP_DIR = new Set(['node_modules', '.git', 'tests', 'fixtures']);
|
|
36
|
+
|
|
37
|
+
// new Promise(<resolver> => [{] setTimeout(<same resolver>, … — arrow or function form.
|
|
38
|
+
const SLEEP_RE = /new\s+Promise\(\s*(?:function\s*)?\(?\s*(\w+)\s*\)?\s*(?:=>\s*)?\{?\s*setTimeout\(\s*(\w+)\s*,/g;
|
|
39
|
+
|
|
40
|
+
function walk(abs, out) {
|
|
41
|
+
let st;
|
|
42
|
+
try { st = fs.statSync(abs); } catch { return out; }
|
|
43
|
+
if (st.isFile()) { if (SCAN_EXT.test(abs)) out.push(abs); return out; }
|
|
44
|
+
for (const ent of fs.readdirSync(abs, { withFileTypes: true })) {
|
|
45
|
+
if (SKIP_DIR.has(ent.name)) continue;
|
|
46
|
+
walk(path.join(abs, ent.name), out);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// findHandRolledSleeps(text) → 1-based line numbers holding a hand-rolled sleep.
|
|
52
|
+
function findHandRolledSleeps(text) {
|
|
53
|
+
const lines = String(text).split('\n');
|
|
54
|
+
const hits = [];
|
|
55
|
+
for (let i = 0; i < lines.length; i++) {
|
|
56
|
+
for (const m of lines[i].matchAll(SLEEP_RE)) {
|
|
57
|
+
if (m[1] === m[2]) { hits.push(i + 1); break; }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return hits;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function checkOneSleepHelper({ root = REPO_ROOT } = {}) {
|
|
64
|
+
const violations = [];
|
|
65
|
+
let scanned = 0;
|
|
66
|
+
for (const r of SCAN_ROOTS) {
|
|
67
|
+
for (const abs of walk(path.join(root, r), [])) {
|
|
68
|
+
scanned++;
|
|
69
|
+
const relPath = path.relative(root, abs).split(path.sep).join('/');
|
|
70
|
+
if (ALLOWED.has(relPath)) continue;
|
|
71
|
+
const hits = findHandRolledSleeps(fs.readFileSync(abs, 'utf8'));
|
|
72
|
+
if (hits.length) {
|
|
73
|
+
violations.push(`${relPath}:${hits[0]} — a hand-rolled sleep (new Promise → setTimeout). Use the shared one: `
|
|
74
|
+
+ 'window.OTB.sleep in the browser, sleep from src/sleep.js (or api.sleep in a module) on the server.');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
name: 'one sleep helper per side (task 1004893)',
|
|
80
|
+
ok: violations.length === 0,
|
|
81
|
+
hardFail: violations.length > 0,
|
|
82
|
+
violations,
|
|
83
|
+
warnings: [],
|
|
84
|
+
note: `${scanned} code files hold no hand-rolled sleep (allowed: ${[...ALLOWED.keys()].join(', ')}).`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { checkOneSleepHelper, findHandRolledSleeps };
|
|
@@ -832,7 +832,7 @@ async function recordLedger({ fromVersion, toVersion, sourceCommit, note, reques
|
|
|
832
832
|
// Connection-refused fails fast, so the early retries are cheap; the backoff caps at 2s.
|
|
833
833
|
async function pollHealth(healthUrl, deps = {}) {
|
|
834
834
|
const fetchImpl = deps.fetch || fetch;
|
|
835
|
-
const sleep = deps.sleep || (
|
|
835
|
+
const sleep = deps.sleep || require('../../src/sleep').sleep; // the one shared sleep (task 1004893)
|
|
836
836
|
const now = deps.now || Date.now;
|
|
837
837
|
const budgetMs = deps.healthBudgetMs != null ? deps.healthBudgetMs : 30_000;
|
|
838
838
|
const deadline = now() + budgetMs;
|
|
@@ -882,7 +882,7 @@ function deriveVersionUrl(healthUrl) {
|
|
|
882
882
|
// (a real failure) from "it wouldn't tell me" (unconfirmed), because only the first is proof.
|
|
883
883
|
async function pollServedVersion(versionUrl, deps = {}) {
|
|
884
884
|
const fetchImpl = deps.fetch || fetch;
|
|
885
|
-
const sleep = deps.sleep || (
|
|
885
|
+
const sleep = deps.sleep || require('../../src/sleep').sleep; // the one shared sleep (task 1004893)
|
|
886
886
|
const now = deps.now || Date.now;
|
|
887
887
|
// Default to the health budget when a caller (or a test) has already narrowed it, else 15s.
|
|
888
888
|
const budgetMs = deps.versionBudgetMs != null
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// scripts/gds/sleep-helper-guard.js — @bongos-forwarder (task 1004848). Generated by
|
|
2
|
+
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
+
// The real script is scripts/bongos/sleep-helper-guard.js. This forwarder keeps callers that still name
|
|
4
|
+
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
+
'use strict';
|
|
6
|
+
require('../bongos/forwarder.js').forward(module, 'sleep-helper-guard.js');
|
|
@@ -465,6 +465,7 @@ const MODULE_GLOBS = {
|
|
|
465
465
|
'scripts/bongos/seam-wiring-guard.js', 'scripts/bongos/skill-preflight.js',
|
|
466
466
|
'scripts/bongos/test-path-guard.js', 'scripts/bongos/version-literal-guard.js', 'scripts/bongos/dev-box-guard.js',
|
|
467
467
|
'scripts/bongos/escape-helper-guard.js', // task 1004876
|
|
468
|
+
'scripts/bongos/sleep-helper-guard.js', // task 1004893
|
|
468
469
|
|
|
469
470
|
// ADOPTED (task 1004111): the enforcement surfaces this module already IS.
|
|
470
471
|
// install-git-hooks.js + scripts/bongos/hooks/ are the pre-push hook beside the
|
|
@@ -25,6 +25,7 @@ const path = require('node:path');
|
|
|
25
25
|
const crypto = require('node:crypto');
|
|
26
26
|
const { parseVersion, cmp } = require('../module-loader/semver');
|
|
27
27
|
const { KEY_RE } = require('../module-loader/manifest-schema');
|
|
28
|
+
const { recordPublishCategory } = require('./module-subcategories');
|
|
28
29
|
|
|
29
30
|
// Where the store lives: <INSTANCE ROOT>/var/module-store (task 1004399).
|
|
30
31
|
//
|
|
@@ -172,11 +173,11 @@ function relativeArtifactPath(file, { dir = storeDir() } = {}) {
|
|
|
172
173
|
const PUBLISH_CHANNEL = 'tester';
|
|
173
174
|
|
|
174
175
|
// One transaction. `db` is a pg Pool (anything with connect()). Returns
|
|
175
|
-
// { ok: true, version: row, created: bool, channel } or { ok: false, code, message, status }.
|
|
176
|
+
// { ok: true, version: row, created: bool, channel, category } or { ok: false, code, message, status }.
|
|
176
177
|
async function publishVersion(input, { db, beforeCommit } = {}) {
|
|
177
178
|
const {
|
|
178
179
|
key, title, description, version, coreVersion, manifest,
|
|
179
|
-
treeSha256, tarballSha256, bytes, artifactPath: relPath, builderId,
|
|
180
|
+
treeSha256, tarballSha256, bytes, artifactPath: relPath, builderId, category,
|
|
180
181
|
} = input;
|
|
181
182
|
const pool = db || require('./pool').pool;
|
|
182
183
|
const client = await pool.connect();
|
|
@@ -229,11 +230,15 @@ async function publishVersion(input, { db, beforeCommit } = {}) {
|
|
|
229
230
|
`INSERT INTO store_module_version_channels (version_id, channel, reason, set_by)
|
|
230
231
|
VALUES ($1, $2, $3, $4)`,
|
|
231
232
|
[row.id, PUBLISH_CHANNEL, 'new_version', builderId]);
|
|
233
|
+
// Where the module sits, on the same transaction (task 1004355, ADR 0342 D3): a
|
|
234
|
+
// sub-category the store has not seen is proposed here and is not live until a
|
|
235
|
+
// Metic+ person approves it. A version naming no category leaves it where it was.
|
|
236
|
+
const placed = category ? await recordPublishCategory(client, { key, category, builderId }) : null;
|
|
232
237
|
// Put the tarball in place while the row is still uncommitted: if this throws,
|
|
233
238
|
// the catch below rolls the row back, so the two can never disagree.
|
|
234
239
|
if (beforeCommit) await beforeCommit();
|
|
235
240
|
await client.query('COMMIT');
|
|
236
|
-
return { ok: true, created, version: row, channel: PUBLISH_CHANNEL };
|
|
241
|
+
return { ok: true, created, version: row, channel: PUBLISH_CHANNEL, category: placed };
|
|
237
242
|
} catch (e) {
|
|
238
243
|
try { await client.query('ROLLBACK'); } catch { /* the connection may be gone */ }
|
|
239
244
|
// Two publishes of the same version racing past the check: the UNIQUE wins.
|