@bongos/core 1.19.223 → 1.19.225
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 +43 -28
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +3 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +82 -2
- package/docs/api-reference.md +3 -2
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/government/board.js +211 -0
- package/modules/government/db.js +125 -0
- package/modules/government/routes/government.js +54 -0
- package/modules/hall-ui/public/government.html +28 -2
- package/modules/hall-ui/public/government.js +40 -4
- package/modules/hall-ui/public/palette.js +10 -4
- package/modules/ideas/module.json +2 -1
- package/modules/ideas/routes/inbox.js +27 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/route-rank-check.js +4 -0
- package/src/module-api.js +5 -1
- package/tests/government_board_vote.mjs +247 -0
- package/tests/government_board_window.mjs +144 -0
- package/tests/government_routes.mjs +26 -5
- package/tests/hall_palette.mjs +10 -0
package/modules/government/db.js
CHANGED
|
@@ -281,6 +281,123 @@ async function builderHoldsRankActive(builderId, rankKey) {
|
|
|
281
281
|
return rows.length > 0;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
// --- the board WRITES (BV1.R07 of goal 1000069) ------------------------------
|
|
285
|
+
|
|
286
|
+
// Open a window for a subject. The idempotency is STRUCTURAL: the INSERT
|
|
287
|
+
// targets government_003's partial unique index (one OPEN item per subject),
|
|
288
|
+
// so a concurrent double-open inserts exactly one row — the loser gets null and
|
|
289
|
+
// the caller reads the winner back with getOpenBoardItem. closes_at is computed
|
|
290
|
+
// by the DATABASE clock (now() + the window), absolute rather than a duration,
|
|
291
|
+
// so changing the project's window length never moves an already-open deadline;
|
|
292
|
+
// NULL windowMinutes means NO CLOCK (the monarchy configuration — the item
|
|
293
|
+
// closes on the first vote, not on time).
|
|
294
|
+
async function openBoardItem({ subjectType, subjectId, openedBy = null, constitution, windowMinutes = null }) {
|
|
295
|
+
const { rows } = await pool.query(
|
|
296
|
+
`INSERT INTO government_board_items (subject_type, subject_id, opened_by, closes_at, constitution)
|
|
297
|
+
VALUES ($1, $2, $3,
|
|
298
|
+
CASE WHEN $4::int IS NULL THEN NULL ELSE now() + make_interval(mins => $4::int) END,
|
|
299
|
+
$5::jsonb)
|
|
300
|
+
ON CONFLICT (subject_type, subject_id) WHERE closed_at IS NULL DO NOTHING
|
|
301
|
+
RETURNING *`,
|
|
302
|
+
[subjectType, subjectId, openedBy, windowMinutes, JSON.stringify(constitution || {})],
|
|
303
|
+
);
|
|
304
|
+
return rows[0] || null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function getOpenBoardItem(subjectType, subjectId) {
|
|
308
|
+
const { rows } = await pool.query(
|
|
309
|
+
`SELECT * FROM government_board_items
|
|
310
|
+
WHERE subject_type = $1 AND subject_id = $2 AND closed_at IS NULL
|
|
311
|
+
LIMIT 1`,
|
|
312
|
+
[subjectType, subjectId],
|
|
313
|
+
);
|
|
314
|
+
return rows[0] || null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// --- the vote READS + WRITES (BV1.R08 of goal 1000069) -----------------------
|
|
318
|
+
|
|
319
|
+
// One item, with the expiry computed on the DATABASE clock so the route never
|
|
320
|
+
// compares a Postgres timestamptz against a process clock. NULL closes_at is
|
|
321
|
+
// the no-clock configuration and never expires.
|
|
322
|
+
async function getBoardItemForVote(itemId) {
|
|
323
|
+
const { rows } = await pool.query(
|
|
324
|
+
`SELECT *, (closes_at IS NOT NULL AND closes_at < now()) AS expired
|
|
325
|
+
FROM government_board_items
|
|
326
|
+
WHERE id = $1`,
|
|
327
|
+
[itemId],
|
|
328
|
+
);
|
|
329
|
+
return rows[0] || null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// The anti-self rule's subject: who put this item up. A full_idea's author is
|
|
333
|
+
// the idea's captor (idea_inbox is a shared core table — reading it directly is
|
|
334
|
+
// the sanctioned pattern; the boundary is code imports, not table access). An
|
|
335
|
+
// amendment's author is its proposer, already on the row as opened_by. NULL —
|
|
336
|
+
// an auto-opened item whose idea row is somehow gone — means no self to refuse.
|
|
337
|
+
async function getBoardItemAuthor(item) {
|
|
338
|
+
if (!item) return null;
|
|
339
|
+
if (item.subject_type === 'full_idea') {
|
|
340
|
+
const { rows } = await pool.query(
|
|
341
|
+
`SELECT captured_by FROM idea_inbox WHERE id = $1`,
|
|
342
|
+
[item.subject_id],
|
|
343
|
+
);
|
|
344
|
+
return rows[0] ? rows[0].captured_by : null;
|
|
345
|
+
}
|
|
346
|
+
return item.opened_by ?? null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// The open ballot's one write. Idempotency is STRUCTURAL — UNIQUE(item_id,
|
|
350
|
+
// voter_id); the loser of a double-cast gets null and the caller reads the
|
|
351
|
+
// standing vote back.
|
|
352
|
+
async function insertBoardVote({ itemId, voterId, direction, reasonMd = null, sectionKey = null }) {
|
|
353
|
+
const { rows } = await pool.query(
|
|
354
|
+
`INSERT INTO government_board_votes (item_id, voter_id, direction, reason_md, section_key)
|
|
355
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
356
|
+
ON CONFLICT (item_id, voter_id) DO NOTHING
|
|
357
|
+
RETURNING *`,
|
|
358
|
+
[itemId, voterId, direction, reasonMd, sectionKey],
|
|
359
|
+
);
|
|
360
|
+
return rows[0] || null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function getBoardVote(itemId, voterId) {
|
|
364
|
+
const { rows } = await pool.query(
|
|
365
|
+
`SELECT * FROM government_board_votes WHERE item_id = $1 AND voter_id = $2`,
|
|
366
|
+
[itemId, voterId],
|
|
367
|
+
);
|
|
368
|
+
return rows[0] || null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Voter karma (+1 in-window, owner decision 8) — rides the FRESH insert only,
|
|
372
|
+
// so the idempotent re-cast can never double-pay. granted_by NULL: earned from
|
|
373
|
+
// the system by the act, not granted by a person (the governance_002 backfill
|
|
374
|
+
// convention). karma_log is a shared core table; same access rule as above.
|
|
375
|
+
// Author-ratification karma (+3, board.js AUTHOR_RATIFY_KARMA_DELTA) — fired by
|
|
376
|
+
// the R10 close on a passed item. IDEMPOTENT: the reason key is unique per item
|
|
377
|
+
// and the INSERT is one atomic WHERE-NOT-EXISTS statement (the economy
|
|
378
|
+
// credit-lane idiom — karma_log carries no unique constraint, so the predicate
|
|
379
|
+
// IS the guard). Returns whether a row was actually written.
|
|
380
|
+
async function insertAuthorRatifyKarma({ authorId, itemId, delta }) {
|
|
381
|
+
const reason = `board.item.ratified:${itemId}`;
|
|
382
|
+
const { rowCount } = await pool.query(
|
|
383
|
+
`INSERT INTO karma_log (builder_id, delta, reason, granted_by)
|
|
384
|
+
SELECT $1, $2, $3, NULL
|
|
385
|
+
WHERE NOT EXISTS (
|
|
386
|
+
SELECT 1 FROM karma_log WHERE builder_id = $1 AND reason = $3
|
|
387
|
+
)`,
|
|
388
|
+
[authorId, delta, reason],
|
|
389
|
+
);
|
|
390
|
+
return rowCount > 0;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function insertVoteKarma({ voterId, itemId, delta }) {
|
|
394
|
+
await pool.query(
|
|
395
|
+
`INSERT INTO karma_log (builder_id, delta, reason, granted_by)
|
|
396
|
+
VALUES ($1, $2, $3, NULL)`,
|
|
397
|
+
[voterId, delta, `board.vote.cast:${itemId}`],
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
284
401
|
module.exports = {
|
|
285
402
|
getPermissionKeysForBuilder,
|
|
286
403
|
getRankKeysForBuilder,
|
|
@@ -298,4 +415,12 @@ module.exports = {
|
|
|
298
415
|
unassignRank,
|
|
299
416
|
getActiveBuildersWithRank,
|
|
300
417
|
builderHoldsRankActive,
|
|
418
|
+
openBoardItem,
|
|
419
|
+
getOpenBoardItem,
|
|
420
|
+
getBoardItemForVote,
|
|
421
|
+
getBoardItemAuthor,
|
|
422
|
+
insertBoardVote,
|
|
423
|
+
getBoardVote,
|
|
424
|
+
insertVoteKarma,
|
|
425
|
+
insertAuthorRatifyKarma,
|
|
301
426
|
};
|
|
@@ -24,6 +24,7 @@ const catalog = require('../catalog');
|
|
|
24
24
|
const db = require('../db');
|
|
25
25
|
const sessionScopes = require('../session-scopes');
|
|
26
26
|
const boardMembership = require('../board-membership');
|
|
27
|
+
const board = require('../board');
|
|
27
28
|
|
|
28
29
|
// The port surface. Deliberately narrow: resolution + the catalog reads a gate or
|
|
29
30
|
// the Archon tab needs. It grows with R98-R101 (principals, session-scopes,
|
|
@@ -86,6 +87,10 @@ const governmentPort = {
|
|
|
86
87
|
parseMembershipPredicate: boardMembership.parseMembershipPredicate,
|
|
87
88
|
resolveBoardMembers: boardMembership.resolveBoardMembers,
|
|
88
89
|
isBoardMember: boardMembership.isBoardMember,
|
|
90
|
+
// R07: the ideas capture path fires this when a Full Idea's grade lands — the
|
|
91
|
+
// bar gate lives INSIDE board.js (a sub-bar idea never reaches the board, and
|
|
92
|
+
// no caller is trusted to pre-filter). Returns, never throws into capture.
|
|
93
|
+
openBoardWindowForIdea: board.openWindowForFullIdea,
|
|
89
94
|
};
|
|
90
95
|
|
|
91
96
|
function registerSeams() {
|
|
@@ -405,6 +410,55 @@ module.exports = function buildGovernmentRouter() {
|
|
|
405
410
|
}
|
|
406
411
|
});
|
|
407
412
|
|
|
413
|
+
// rank: perm board.vote.cast — cast a vote on an open board item (R08 of goal
|
|
414
|
+
// 1000069, ADR 0175). The atom FLOORS AT XENOS deliberately (the R04 carry-
|
|
415
|
+
// note): the permission is only the coarse gate, and the real wall is the
|
|
416
|
+
// in-handler membership re-check against the constitution SNAPSHOTTED on the
|
|
417
|
+
// item — do not "tighten" the floor to archon; that would fork authority
|
|
418
|
+
// truth and make widening the franchise a migration instead of a config
|
|
419
|
+
// change. Open ballot, anti-self, idempotent (UNIQUE(item_id, voter_id)),
|
|
420
|
+
// voter karma +1 on a fresh in-window cast. The policy lives in board.js
|
|
421
|
+
// castVote; this handler is the HTTP mapping.
|
|
422
|
+
router.post('/government/board/items/:itemId/votes', api.requireBuilder, api.requirePermission('board.vote.cast'), async (req, res) => {
|
|
423
|
+
const itemId = parseId(req, res, { param: 'itemId', code: 'bad_item_id', positiveInt: true });
|
|
424
|
+
if (itemId === null) return;
|
|
425
|
+
if (validateOrRespond(req, res, {
|
|
426
|
+
direction: { required: true, type: 'string', maxLength: 3 },
|
|
427
|
+
reason_md: { type: 'string', maxLength: 5000 },
|
|
428
|
+
section_key: { type: 'string', maxLength: 32 },
|
|
429
|
+
})) return;
|
|
430
|
+
try {
|
|
431
|
+
const out = await board.castVote({
|
|
432
|
+
itemId,
|
|
433
|
+
voterId: req.builder.id,
|
|
434
|
+
direction: req.body.direction,
|
|
435
|
+
reasonMd: req.body.reason_md ?? null,
|
|
436
|
+
sectionKey: req.body.section_key ?? null,
|
|
437
|
+
});
|
|
438
|
+
if (!out.ok) {
|
|
439
|
+
const status =
|
|
440
|
+
out.reason === 'item_not_found' ? 404 :
|
|
441
|
+
out.reason === 'not_board_member' ? 403 :
|
|
442
|
+
out.reason === 'anti_self_vote' ? 403 :
|
|
443
|
+
out.reason === 'item_closed' ? 409 :
|
|
444
|
+
out.reason === 'vote_window_expired' ? 409 :
|
|
445
|
+
400; // bad_direction / yes_carries_nothing / objection_needs_reason / objection_needs_section
|
|
446
|
+
return res.fail(out.reason, status, { item_id: itemId });
|
|
447
|
+
}
|
|
448
|
+
// Idempotent re-cast reads back the standing vote — 200, not 201, and no
|
|
449
|
+
// karma line: nothing new was recorded.
|
|
450
|
+
res.status(out.fresh ? 201 : 200).json({
|
|
451
|
+
ok: true,
|
|
452
|
+
vote: out.vote,
|
|
453
|
+
already_voted: !out.fresh,
|
|
454
|
+
...(out.fresh ? { karma_delta: out.karma_delta } : {}),
|
|
455
|
+
});
|
|
456
|
+
} catch (err) {
|
|
457
|
+
console.error('[gds] POST /government/board/items/:itemId/votes', err);
|
|
458
|
+
res.fail('vote_failed', { status: 500, message: 'internal error' });
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
408
462
|
return router;
|
|
409
463
|
};
|
|
410
464
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Government — {{worldName}}</title>
|
|
7
|
-
<meta name="description" content="
|
|
7
|
+
<meta name="description" content="How this project is governed — the constitution it runs, the Board Room where Full Ideas are ratified, and the ranks & permissions that administer it.">
|
|
8
8
|
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
@@ -26,6 +26,30 @@
|
|
|
26
26
|
<p class="gov-sealed" id="gov-sealed" hidden>Archons only.</p>
|
|
27
27
|
|
|
28
28
|
<section id="gov-body" hidden>
|
|
29
|
+
<!-- The three-section IA (R15 of goal 1000069, ADR 0175): the page that
|
|
30
|
+
answers "how is this project governed". Tabs are OTBKit.makeTabs
|
|
31
|
+
(the work.js idiom) with hash deep links — #constitution,
|
|
32
|
+
#board-room, #ranks. -->
|
|
33
|
+
<div id="gov-tabs"></div>
|
|
34
|
+
|
|
35
|
+
<!-- §1 Constitution — what this project runs. The live view (today's
|
|
36
|
+
values + the change history) and the charter library land with
|
|
37
|
+
R18/R19; until then the section frames the model honestly. -->
|
|
38
|
+
<section id="gov-panel-constitution" hidden>
|
|
39
|
+
<h2 class="gov-h">Constitution</h2>
|
|
40
|
+
<p class="gov-grid-note">Three dials decide how this project is governed: <strong>who sits on the board</strong> (a rank — everyone holding it is a member), <strong>what counts as passing</strong> (a first ratifier, or the consent of the sitting), and <strong>how long a vote stays open</strong> (a timed sitting, or no clock at all). They are configuration on the same machinery at every setting — a project starts with the founder ratifying alone and opens up by changing numbers, not code.</p>
|
|
41
|
+
<div id="gov-constitution"></div>
|
|
42
|
+
</section>
|
|
43
|
+
|
|
44
|
+
<!-- §2 Board Room — open items, the clock, your vote (R16/R17). -->
|
|
45
|
+
<section id="gov-panel-board" hidden>
|
|
46
|
+
<h2 class="gov-h">Board Room</h2>
|
|
47
|
+
<p class="gov-grid-note">Where a Full Idea that cleared the completeness grade comes to be ratified — and where the constitution itself is amended. Open items appear here with their clock and a live tally; an objection must carry a reason and name the section it faults, which is why votes are cast here and only mirrored to chat.</p>
|
|
48
|
+
<div id="gov-board"></div>
|
|
49
|
+
</section>
|
|
50
|
+
|
|
51
|
+
<!-- §3 Ranks & Permissions — the administration surface, unchanged. -->
|
|
52
|
+
<section id="gov-panel-ranks" hidden>
|
|
29
53
|
<div class="gov-layout">
|
|
30
54
|
<div class="gov-ranks-col">
|
|
31
55
|
<h2 class="gov-h">Ranks</h2>
|
|
@@ -59,6 +83,7 @@
|
|
|
59
83
|
<h2 class="gov-h">Machine principals</h2>
|
|
60
84
|
<p class="gov-grid-note">Capabilities held by a machine identity, never by a person or a rank. Shown for completeness — nothing here can be granted.</p>
|
|
61
85
|
<div id="gov-principals"></div>
|
|
86
|
+
</section>
|
|
62
87
|
</section>
|
|
63
88
|
</main>
|
|
64
89
|
|
|
@@ -73,6 +98,7 @@
|
|
|
73
98
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
74
99
|
<script src="/builders/palette.js" defer></script>
|
|
75
100
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
76
|
-
<script src="/builders/
|
|
101
|
+
<script src="/builders/hall-kit.js"></script>
|
|
102
|
+
<script src="/builders/government.js?v=2026-08-18-government-ia"></script>
|
|
77
103
|
</body>
|
|
78
104
|
</html>
|
|
@@ -25,9 +25,16 @@
|
|
|
25
25
|
// gated server-side on the `government.manage` atom (archon floor). On 401 →
|
|
26
26
|
// web sign-in.
|
|
27
27
|
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
28
|
+
// SINCE R15 (goal 1000069, ADR 0175) this page carries the three-section IA —
|
|
29
|
+
// Constitution · Board Room · Ranks & Permissions — because Government is the
|
|
30
|
+
// page that answers "how is this project governed", and administration is only
|
|
31
|
+
// the third part of that answer. Tabs are OTBKit.makeTabs with hash deep links
|
|
32
|
+
// (#constitution / #board-room / #ranks). The first two sections are FRAMES
|
|
33
|
+
// until their build-out lands: R18 fills Constitution with the live values +
|
|
34
|
+
// change history, R19 adds the charter library, R16/R17 fill the Board Room
|
|
35
|
+
// with open items and the vote. Until then each frame states what belongs there
|
|
36
|
+
// and shows an honest kit empty state — never a fake or a hardcoded value that
|
|
37
|
+
// could drift from the real config.
|
|
31
38
|
|
|
32
39
|
(() => {
|
|
33
40
|
'use strict';
|
|
@@ -392,6 +399,34 @@
|
|
|
392
399
|
});
|
|
393
400
|
}
|
|
394
401
|
|
|
402
|
+
// ---- the three-section IA (R15) -------------------------------------------
|
|
403
|
+
|
|
404
|
+
// Tab order is the spec's — Constitution · Board Room · Ranks & Permissions —
|
|
405
|
+
// but the INITIAL tab is Ranks until R16/R18 fill the other two: landing the
|
|
406
|
+
// Archon on a frame when the working surface is one tab over would be IA
|
|
407
|
+
// winning over usefulness. Flip `initial` to 'constitution' when R18 lands.
|
|
408
|
+
function wireTabs() {
|
|
409
|
+
window.OTBKit.makeTabs({
|
|
410
|
+
mount: document.getElementById('gov-tabs'),
|
|
411
|
+
hash: true,
|
|
412
|
+
initial: 'ranks',
|
|
413
|
+
tabs: [
|
|
414
|
+
{ id: 'constitution', label: 'Constitution', panel: 'gov-panel-constitution' },
|
|
415
|
+
{ id: 'board-room', label: 'Board Room', panel: 'gov-panel-board' },
|
|
416
|
+
{ id: 'ranks', label: 'Ranks & Permissions', panel: 'gov-panel-ranks' },
|
|
417
|
+
],
|
|
418
|
+
});
|
|
419
|
+
// The frames' empty states — kit-standard, one line, no fake data. What this
|
|
420
|
+
// project actually RUNS is config the server owns; rendering it here before
|
|
421
|
+
// the R18 read exists would mean hardcoding a value that drifts.
|
|
422
|
+
document.getElementById('gov-constitution').innerHTML = window.OTBKit.emptyStateHtml(
|
|
423
|
+
'The live constitution — what this project runs today, and how it has changed — appears here when the Board Room opens.',
|
|
424
|
+
);
|
|
425
|
+
document.getElementById('gov-board').innerHTML = window.OTBKit.emptyStateHtml(
|
|
426
|
+
'Nothing is before the board. When a Full Idea clears the completeness grade, its window opens here.',
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
395
430
|
// ---- init ----------------------------------------------------------------
|
|
396
431
|
|
|
397
432
|
function showSealed() {
|
|
@@ -416,8 +451,9 @@
|
|
|
416
451
|
const rank = (me && (me.builder || me).rank || '').toLowerCase();
|
|
417
452
|
if (rank !== 'archon') { showSealed(); return; }
|
|
418
453
|
|
|
419
|
-
document.getElementById('gov-sub').textContent = '
|
|
454
|
+
document.getElementById('gov-sub').textContent = 'How this project is governed — the constitution it runs, the Board Room, and the ranks & permissions that administer it.';
|
|
420
455
|
document.getElementById('gov-body').hidden = false;
|
|
456
|
+
wireTabs();
|
|
421
457
|
wireForms();
|
|
422
458
|
try {
|
|
423
459
|
await load();
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// 1. Pages — read live from the sidebar shell.js rendered, so rank + module
|
|
14
14
|
// gating come for free and can never drift from the nav. A hidden nav item
|
|
15
15
|
// is a hidden destination. (Cosmetic only; the server still gates each page.)
|
|
16
|
-
// 2. Sections — the sub-destinations of the
|
|
16
|
+
// 2. Sections — the sub-destinations of the four pages that have them. These
|
|
17
17
|
// cannot be read from another page's DOM, so they are declared below and
|
|
18
18
|
// held to the real ids by tests/hall_palette.mjs.
|
|
19
19
|
// 3. Records — id syntax: `1234`, `g 12`, `b 7`, `i 42`.
|
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
// element whose visibility decides whether the group is offered — the nav item
|
|
29
29
|
// itself for pages, the profile chip for Settings (which is not a nav item).
|
|
30
30
|
// Anchors must stay in step with their source of truth; the test asserts it:
|
|
31
|
-
// standing
|
|
32
|
-
// work
|
|
33
|
-
//
|
|
31
|
+
// standing → builders.js STANDING_GROUPS (+ shell.js VIEW_ALIASES)
|
|
32
|
+
// work → work.js makeTabs ids
|
|
33
|
+
// government → government.js makeTabs ids (the R15 three-section IA)
|
|
34
|
+
// settings → settings.js RAIL_GROUPS
|
|
34
35
|
var SECTIONS = [
|
|
35
36
|
{ parent: 'Standing', gate: 'nav-standing', items: [
|
|
36
37
|
{ label: 'Work', href: '/#standing-work' },
|
|
@@ -44,6 +45,11 @@
|
|
|
44
45
|
{ label: 'Backlog', href: '/work#backlog' },
|
|
45
46
|
{ label: 'Completed', href: '/work#completed' },
|
|
46
47
|
] },
|
|
48
|
+
{ parent: 'Government', gate: 'nav-government', items: [
|
|
49
|
+
{ label: 'Constitution', href: '/government#constitution' },
|
|
50
|
+
{ label: 'Board Room', href: '/government#board-room' },
|
|
51
|
+
{ label: 'Ranks & Permissions', href: '/government#ranks' },
|
|
52
|
+
] },
|
|
47
53
|
{ parent: 'Settings', gate: 'shell-user', items: [
|
|
48
54
|
{ label: 'Account', href: '/settings#account' },
|
|
49
55
|
{ label: 'Access', href: '/settings#access' },
|
|
@@ -146,6 +146,33 @@ module.exports = function buildInboxRouter() {
|
|
|
146
146
|
// no warning surfaced — the idea and its grade are intact; the credit
|
|
147
147
|
// is the only thing missed, and a reconcile/re-grade path can pay it.
|
|
148
148
|
}
|
|
149
|
+
|
|
150
|
+
// R07 (goal 1000069, ADR 0175 §5): a Full Idea crossing the
|
|
151
|
+
// completeness bar automatically opens its ratification window in the
|
|
152
|
+
// Board Room. Resolved through the `government` port (declared in
|
|
153
|
+
// consumes), never a direct import — and the BAR GATE lives on the
|
|
154
|
+
// government side (board.js refuses a sub-bar score), so this hook
|
|
155
|
+
// fires unconditionally with the stored score and the board decides.
|
|
156
|
+
// FAIL-OPEN like the credit above: the idea and its grade are already
|
|
157
|
+
// stored, and a board hiccup must never lose a capture. The open is
|
|
158
|
+
// idempotent (one open window per subject, structural in the schema),
|
|
159
|
+
// so a retry or re-capture can never open two.
|
|
160
|
+
try {
|
|
161
|
+
const government = api.resolveOptional && api.resolveOptional('government');
|
|
162
|
+
if (government && government.openBoardWindowForIdea) {
|
|
163
|
+
const w = await government.openBoardWindowForIdea({
|
|
164
|
+
ideaId: idea.id,
|
|
165
|
+
completenessScore: row.completeness_score,
|
|
166
|
+
});
|
|
167
|
+
if (w && w.opened && w.item) {
|
|
168
|
+
idea.board_window = { item_id: w.item.id, closes_at: w.item.closes_at };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch (boardErr) {
|
|
172
|
+
console.error('[gds] POST /inbox board-window open failed (idea captured + graded, window skipped):', boardErr);
|
|
173
|
+
// R11's expiry poller sweeps open items, not missed opens; a missed
|
|
174
|
+
// open is recoverable by resubmission (R13) — capture stays intact.
|
|
175
|
+
}
|
|
149
176
|
} catch (gradeErr) {
|
|
150
177
|
console.error('[gds] POST /inbox full-grade write failed (idea captured, landing as quick):', gradeErr);
|
|
151
178
|
grade_warning = 'captured_but_ungraded';
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.225",
|
|
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.225",
|
|
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.225",
|
|
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",
|
|
@@ -247,6 +247,10 @@ const GOVERNMENT_PINS = {
|
|
|
247
247
|
'POST /government/ranks/:rankKey/reset': 'perm:government.manage',
|
|
248
248
|
'POST /government/assignments': 'perm:government.manage',
|
|
249
249
|
'DELETE /government/assignments/:builderId/:rankKey': 'perm:government.manage',
|
|
250
|
+
// The Board Room's first route (R08 of goal 1000069 — the R04-carried pin).
|
|
251
|
+
// The XENOS-floored atom is the COARSE gate by design; the real wall is the
|
|
252
|
+
// in-handler membership re-check against the item's snapshotted constitution.
|
|
253
|
+
'POST /government/board/items/:itemId/votes': 'perm:board.vote.cast',
|
|
250
254
|
};
|
|
251
255
|
|
|
252
256
|
// Module-scoped privileged-rank pins: a privileged route that lives in a FEATURE
|
package/src/module-api.js
CHANGED
|
@@ -873,8 +873,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
873
873
|
// landed since 1.19.221 with no explicit bump. run 32096034408. (task 1002620)
|
|
874
874
|
// 1.19.223 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
875
875
|
// landed since 1.19.222 with no explicit bump. run 32097491676. (task 1002620)
|
|
876
|
+
// 1.19.224 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
877
|
+
// landed since 1.19.223 with no explicit bump. run 32098083321. (task 1002620)
|
|
878
|
+
// 1.19.225 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
879
|
+
// landed since 1.19.224 with no explicit bump. run 32099793591. (task 1002620)
|
|
876
880
|
// ---------------------------------------------------------------------------
|
|
877
|
-
const CORE_VERSION = '1.19.
|
|
881
|
+
const CORE_VERSION = '1.19.225'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
878
882
|
|
|
879
883
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
880
884
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|