@bongos/core 1.19.641 → 1.19.642
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 +11 -11
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/db.js +27 -5
- package/src/module-api.js +1 -1
- package/tests/government_rank_sync.mjs +127 -0
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.642",
|
|
6
|
+
"core_contract": "1.19.642",
|
|
7
|
+
"source_commit": "c6bea45e42c4f596aab75c09f8a0b5c1659a51bb",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-10T17:
|
|
9
|
+
"built_at": "2026-09-10T17:19:26.832Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 470,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2600,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "220035e0bd7f1d6881e5ff218c34a135e0b0822527a21e30e4c2aa5a67ee4625",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/backlog-review/SKILL.md",
|
|
@@ -2772,7 +2772,7 @@
|
|
|
2772
2772
|
{
|
|
2773
2773
|
"path": "docs/module-api-changelog.md",
|
|
2774
2774
|
"mode": "0000644",
|
|
2775
|
-
"sha256": "
|
|
2775
|
+
"sha256": "12455239558665503941d95dda844f8de0d1db80ad9df889684ca896d251b663"
|
|
2776
2776
|
},
|
|
2777
2777
|
{
|
|
2778
2778
|
"path": "docs/modules-contract.md",
|
|
@@ -7722,12 +7722,12 @@
|
|
|
7722
7722
|
{
|
|
7723
7723
|
"path": "package-lock.json",
|
|
7724
7724
|
"mode": "0000644",
|
|
7725
|
-
"sha256": "
|
|
7725
|
+
"sha256": "1a1e2e7386cf99e8554916563b80a12da0ead3338a232208a4adab57cedb23bd"
|
|
7726
7726
|
},
|
|
7727
7727
|
{
|
|
7728
7728
|
"path": "package.json",
|
|
7729
7729
|
"mode": "0000644",
|
|
7730
|
-
"sha256": "
|
|
7730
|
+
"sha256": "e831988b7dfec47e49c0e0c763fd9cb467cf51c296c08ea6a51d1f2747a19398"
|
|
7731
7731
|
},
|
|
7732
7732
|
{
|
|
7733
7733
|
"path": "public-docs/index.html",
|
|
@@ -9232,7 +9232,7 @@
|
|
|
9232
9232
|
{
|
|
9233
9233
|
"path": "src/bongos/db.js",
|
|
9234
9234
|
"mode": "0000644",
|
|
9235
|
-
"sha256": "
|
|
9235
|
+
"sha256": "b205304524629b0e867200e43a083bdf23b9121a6dbc2463c1e80e0e3fca8b8f"
|
|
9236
9236
|
},
|
|
9237
9237
|
{
|
|
9238
9238
|
"path": "src/bongos/github-repos.js",
|
|
@@ -9482,7 +9482,7 @@
|
|
|
9482
9482
|
{
|
|
9483
9483
|
"path": "src/module-api.js",
|
|
9484
9484
|
"mode": "0000644",
|
|
9485
|
-
"sha256": "
|
|
9485
|
+
"sha256": "0ab1529d0da82c501530a79cfc3b7f47b00421677059a7afa7bf8382a9098fe6"
|
|
9486
9486
|
},
|
|
9487
9487
|
{
|
|
9488
9488
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10897,7 +10897,7 @@
|
|
|
10897
10897
|
{
|
|
10898
10898
|
"path": "tests/government_rank_sync.mjs",
|
|
10899
10899
|
"mode": "0000644",
|
|
10900
|
-
"sha256": "
|
|
10900
|
+
"sha256": "d4bd68c98f6e4bbec59890dde225f27f1daf380397f39d329b447fb77fd51c14"
|
|
10901
10901
|
},
|
|
10902
10902
|
{
|
|
10903
10903
|
"path": "tests/government_require_permission.mjs",
|
|
@@ -1731,5 +1731,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1731
1731
|
landed since 1.19.639 with no explicit bump. run 34506041899. (task 1002620)
|
|
1732
1732
|
1.19.641 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1733
1733
|
landed since 1.19.640 with no explicit bump. run 34506718627. (task 1002620)
|
|
1734
|
+
1.19.642 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1735
|
+
landed since 1.19.641 with no explicit bump. run 34507442448. (task 1002620)
|
|
1734
1736
|
---------------------------------------------------------------------------
|
|
1735
1737
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.642",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.642",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.642",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/src/bongos/db.js
CHANGED
|
@@ -517,11 +517,12 @@ async function getLatestRankChangeForBuilder(builderId) {
|
|
|
517
517
|
// Graceful builder exit (V3.R88 #307). Deactivating a builder is reversible
|
|
518
518
|
// and lossless: drachmae (credits) + credit_log, shipped tasks, and historical
|
|
519
519
|
// session_logs all stay. What changes: status → inactive (blocks new claims via
|
|
520
|
-
// claimTask's guard), rank → 'xenos' (sandboxed tier)
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
//
|
|
520
|
+
// claimTask's guard), rank → 'xenos' (sandboxed tier) WITH its seeded rank-role
|
|
521
|
+
// rotated to match (task 1003193 — the direct SQL used to skip that), active claims
|
|
522
|
+
// released cleanly with a final 'abandoned' session_log sealing each, and the tasks
|
|
523
|
+
// returned to 'ready' for someone else. Reactivation flips status back and re-syncs
|
|
524
|
+
// the rank-role; rank itself is NOT auto-restored (an Archon re-promotes deliberately
|
|
525
|
+
// via PATCH /builders/:id/rank). Memory archival is local-first and handled by the
|
|
525
526
|
// /builder-exit skill — the server never holds another builder's local memory
|
|
526
527
|
// (the server-side memory store is criterion #54's job, not this task's).
|
|
527
528
|
|
|
@@ -571,6 +572,18 @@ async function deactivateBuilder({ builderId, reason, actorBuilderId }) {
|
|
|
571
572
|
deactivated_at, reactivated_at, deactivated_reason`,
|
|
572
573
|
[builderId, reason ?? null]
|
|
573
574
|
);
|
|
575
|
+
// BV1.R95b — rotate the seeded rank-role IN THIS TRANSACTION, exactly as
|
|
576
|
+
// _applyRankChangeTx does (task 1003193). The UPDATE above forces rank to
|
|
577
|
+
// 'xenos' by direct SQL rather than through setBuilderRank, so without this the
|
|
578
|
+
// builder's OLD seeded rank-role (archon, say) survives in
|
|
579
|
+
// government_builder_ranks — getRankKeysForBuilder has no builders.status filter.
|
|
580
|
+
// It is not a live grant while they are inactive (every session is revoked below
|
|
581
|
+
// and requireBuilder denies 'inactive' per request), but reactivateBuilder flips
|
|
582
|
+
// status back without touching rank, so the stale-high role would return live.
|
|
583
|
+
// The rank is read back from the row just written rather than re-spelled here.
|
|
584
|
+
// Deliberately uncaught, for the same reason as the twin: a committed
|
|
585
|
+
// deactivation whose rotation failed IS the privilege-retention bug.
|
|
586
|
+
await syncGovernanceRankRole(client, builderId, updated[0].rank);
|
|
574
587
|
// Interim audit (→ audit_log when R36 lands). delta=0 = no-op for credits.
|
|
575
588
|
await client.query(
|
|
576
589
|
`INSERT INTO credit_log (builder_id, task_id, delta, reason)
|
|
@@ -629,6 +642,15 @@ async function reactivateBuilder({ builderId, actorBuilderId }) {
|
|
|
629
642
|
);
|
|
630
643
|
// Rank is intentionally left at whatever it is (xenos after a deactivation):
|
|
631
644
|
// an Archon re-promotes deliberately via PATCH /builders/:id/rank.
|
|
645
|
+
//
|
|
646
|
+
// REACTIVATION IS THE EXPOSURE (task 1003193). Rank does not move here, but
|
|
647
|
+
// status flipping back to 'active' makes whatever seeded rank-role the builder
|
|
648
|
+
// still holds live again — getActiveBuildersWithRank, builderHoldsRankActive,
|
|
649
|
+
// the board roll and the R10 tally denominator all read it. So sync to the row's
|
|
650
|
+
// CURRENT rank: a stale-high role left by a deactivation predating this fix (or
|
|
651
|
+
// drifted in from anywhere else) is removed as PART of the reactivation rather
|
|
652
|
+
// than after it. Idempotent — a consistent builder costs two SELECTs, no write.
|
|
653
|
+
await syncGovernanceRankRole(client, builderId, updated[0].rank);
|
|
632
654
|
await client.query(
|
|
633
655
|
`INSERT INTO credit_log (builder_id, task_id, delta, reason)
|
|
634
656
|
VALUES ($1, NULL, 0, $2)`,
|
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.642'; // 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');
|
|
@@ -326,3 +326,130 @@ test('the writer EXECUTES its plan: a custom rank survives a rank change', async
|
|
|
326
326
|
const del = c.find(/DELETE FROM government_builder_ranks/);
|
|
327
327
|
assert.deepEqual(del.params, [11, ['archon']]);
|
|
328
328
|
});
|
|
329
|
+
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
// offboard + reactivation (task 1003193)
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
//
|
|
334
|
+
// deactivateBuilder forces rank='xenos' by direct SQL — not through
|
|
335
|
+
// setBuilderRank/_applyRankChangeTx, the only paths that rotated the role — so the
|
|
336
|
+
// offboarded builder's OLD seeded rank-role survived in government_builder_ranks,
|
|
337
|
+
// and getRankKeysForBuilder has no builders.status filter. The stale row is not a
|
|
338
|
+
// live grant while they are inactive; REACTIVATION is the exposure, because
|
|
339
|
+
// reactivateBuilder restores status='active' without touching rank, and a
|
|
340
|
+
// reactivated ex-Archon sitting at builders.rank='xenos' then matches
|
|
341
|
+
// getActiveBuildersWithRank / builderHoldsRankActive on the stale archon row.
|
|
342
|
+
//
|
|
343
|
+
// Asserted by EXECUTION, like the writer tests above: withTx is stubbed on
|
|
344
|
+
// db-kernel BEFORE src/bongos/db.js is required (it destructures at load), the
|
|
345
|
+
// government port is wired to the REAL syncRank, and both functions run against one
|
|
346
|
+
// recording client — so the rotation being on the same transaction, and after the
|
|
347
|
+
// rank write, is a fact about the code rather than a regex over it.
|
|
348
|
+
|
|
349
|
+
const kernel = require(path.join(ROOT, 'src/bongos/db-kernel.js'));
|
|
350
|
+
const seams = require(path.join(ROOT, 'src/module-seams.js'));
|
|
351
|
+
if (!seams.hasProvider('government.rankSync')) {
|
|
352
|
+
seams.registerProvider('government.rankSync', { syncRank: govDb.syncRank });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// One client for the whole transaction: the builders/claims/sessions statements the
|
|
356
|
+
// exit path issues AND the two government reads syncRank makes. Order the matchers
|
|
357
|
+
// specific-first — the assignments read also names government_ranks (it joins it).
|
|
358
|
+
function exitTxClient({ builderId, rank, status, held = [], newRank, newStatus }) {
|
|
359
|
+
const calls = [];
|
|
360
|
+
return {
|
|
361
|
+
calls,
|
|
362
|
+
find: (re) => calls.find((c) => re.test(c.sql)),
|
|
363
|
+
indexOf: (re) => calls.findIndex((c) => re.test(c.sql)),
|
|
364
|
+
async query(sql, params) {
|
|
365
|
+
calls.push({ sql, params });
|
|
366
|
+
if (/INSERT INTO|DELETE FROM/.test(sql)) return { rows: [] };
|
|
367
|
+
if (/FROM government_builder_ranks br/.test(sql)) return { rows: held };
|
|
368
|
+
if (/FROM government_ranks/.test(sql)) return { rows: SEEDED };
|
|
369
|
+
if (/FROM builders WHERE id = \$1 FOR UPDATE/.test(sql)) {
|
|
370
|
+
return { rows: [{ id: builderId, rank, status }] };
|
|
371
|
+
}
|
|
372
|
+
if (/FROM claims/.test(sql)) return { rows: [] };
|
|
373
|
+
if (/UPDATE builders/.test(sql)) {
|
|
374
|
+
return { rows: [{ id: builderId, rank: newRank, status: newStatus }] };
|
|
375
|
+
}
|
|
376
|
+
return { rows: [] };
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let txClient = null;
|
|
382
|
+
kernel.withTx = async (fn) => fn(txClient);
|
|
383
|
+
const coreDb = require(path.join(ROOT, 'src/bongos/db.js'));
|
|
384
|
+
|
|
385
|
+
test('offboarding an Archon removes the archon rank-role in the deactivation transaction', async () => {
|
|
386
|
+
txClient = exitTxClient({
|
|
387
|
+
builderId: 25, rank: 'archon', status: 'active',
|
|
388
|
+
held: [seededRank_('archon')], newRank: 'xenos', newStatus: 'inactive',
|
|
389
|
+
});
|
|
390
|
+
await coreDb.deactivateBuilder({ builderId: 25, reason: 'left the team', actorBuilderId: 3 });
|
|
391
|
+
|
|
392
|
+
const del = txClient.find(/DELETE FROM government_builder_ranks/);
|
|
393
|
+
assert.ok(del, 'deactivateBuilder must rotate the seeded rank-role, not only write rank=xenos');
|
|
394
|
+
assert.deepEqual(del.params, [25, ['archon']], 'the stale archon assignment must be the row deleted');
|
|
395
|
+
const ins = txClient.find(/INSERT INTO government_builder_ranks/);
|
|
396
|
+
assert.deepEqual(ins.params, [25, ['xenos']], 'and the sandboxed rank-role takes its place');
|
|
397
|
+
|
|
398
|
+
assert.ok(txClient.indexOf(/DELETE FROM government_builder_ranks/) > txClient.indexOf(/UPDATE builders/),
|
|
399
|
+
'the rotation must follow the rank write inside the same transaction');
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('reactivation re-syncs the rank-role, so a stale-high role cannot come back live', async () => {
|
|
403
|
+
// The pre-fix residue: rank was forced to xenos but the archon assignment stayed.
|
|
404
|
+
// Flipping status back to 'active' is the moment it becomes a live grant again.
|
|
405
|
+
txClient = exitTxClient({
|
|
406
|
+
builderId: 25, rank: 'xenos', status: 'inactive',
|
|
407
|
+
held: [seededRank_('archon')], newRank: 'xenos', newStatus: 'active',
|
|
408
|
+
});
|
|
409
|
+
await coreDb.reactivateBuilder({ builderId: 25, actorBuilderId: 3 });
|
|
410
|
+
|
|
411
|
+
const del = txClient.find(/DELETE FROM government_builder_ranks/);
|
|
412
|
+
assert.ok(del, 'reactivateBuilder must sync the rank-role to the row\'s current rank');
|
|
413
|
+
assert.deepEqual(del.params, [25, ['archon']],
|
|
414
|
+
'a reactivated ex-Archon must not resolve Archon permissions on the stale row');
|
|
415
|
+
assert.deepEqual(txClient.find(/INSERT INTO government_builder_ranks/).params, [25, ['xenos']]);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test('a consistent builder is reactivated with no government write at all', async () => {
|
|
419
|
+
// The sync is a repair, not a rewrite: it must not churn rows on every offboard
|
|
420
|
+
// round-trip once the assignment already agrees with builders.rank.
|
|
421
|
+
txClient = exitTxClient({
|
|
422
|
+
builderId: 26, rank: 'xenos', status: 'inactive',
|
|
423
|
+
held: [seededRank_('xenos'), customRank('release-manager')], newRank: 'xenos', newStatus: 'active',
|
|
424
|
+
});
|
|
425
|
+
await coreDb.reactivateBuilder({ builderId: 26, actorBuilderId: 3 });
|
|
426
|
+
|
|
427
|
+
assert.ok(!txClient.find(/(INSERT INTO|DELETE FROM) government_builder_ranks/),
|
|
428
|
+
'no rotation is due — and the custom rank must survive untouched');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test('a custom rank survives an offboard (only the seeded rank-role rotates)', async () => {
|
|
432
|
+
txClient = exitTxClient({
|
|
433
|
+
builderId: 27, rank: 'metic', status: 'active',
|
|
434
|
+
held: [seededRank_('metic'), customRank('release-manager')], newRank: 'xenos', newStatus: 'inactive',
|
|
435
|
+
});
|
|
436
|
+
await coreDb.deactivateBuilder({ builderId: 27, reason: null, actorBuilderId: 3 });
|
|
437
|
+
|
|
438
|
+
assert.deepEqual(txClient.find(/DELETE FROM government_builder_ranks/).params, [27, ['metic']],
|
|
439
|
+
'offboarding must never revoke an Archon-assigned custom rank');
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test('both exit paths rotate the role on the transaction client, uncaught', async () => {
|
|
443
|
+
// The posture half, which execution cannot show: no try/catch around the call, so
|
|
444
|
+
// a rotation that cannot be written rolls the status change back with it.
|
|
445
|
+
const src = fs.readFileSync(path.join(ROOT, 'src/bongos/db.js'), 'utf8');
|
|
446
|
+
for (const fn of ['deactivateBuilder', 'reactivateBuilder']) {
|
|
447
|
+
const body = src.slice(src.indexOf(`async function ${fn}(`));
|
|
448
|
+
const end = body.indexOf('\n}\n');
|
|
449
|
+
const scope = body.slice(0, end === -1 ? body.length : end);
|
|
450
|
+
const sync = scope.indexOf('await syncGovernanceRankRole(client');
|
|
451
|
+
assert.ok(sync !== -1, `${fn}: must rotate the seeded rank-role inside its transaction`);
|
|
452
|
+
assert.ok(!/try\s*{[\s\S]*syncGovernanceRankRole/.test(scope),
|
|
453
|
+
`${fn}: the rotation must not be swallowed — a failed rotation has to roll the write back`);
|
|
454
|
+
}
|
|
455
|
+
});
|