@bongos/core 1.21.7 → 1.21.9
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 +98 -38
- 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 +8 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/adr/0363-who-decides-what-is-a-table-changed-only-by-amendment.md +52 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +182 -3
- package/docs/api-reference.md +4 -2
- package/docs/copy-inventory.md +20 -5
- package/docs/copy-registry.json +163 -16
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +33 -4
- package/docs/page-readings.json +44 -2
- package/modules/discord/board-broadcast.js +2 -0
- package/modules/government/board.js +82 -23
- package/modules/government/db.js +81 -6
- package/modules/government/decision-board.js +300 -0
- package/modules/government/decision-rules.js +312 -0
- package/modules/government/docket.js +49 -9
- package/modules/government/migrations/government_023_decision_rules.sql +190 -0
- package/modules/government/routes/government.js +115 -0
- package/modules/hall-ui/public/city-draw.js +11 -2
- package/modules/hall-ui/public/street-draw.js +585 -0
- package/modules/hall-ui/public/street.css +188 -0
- package/modules/hall-ui/public/street.html +118 -0
- package/modules/hall-ui/public/street.js +364 -0
- package/modules/hall-ui/public/street.states.json +43 -0
- package/modules/hall-ui/records/governor-street.md +14 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +16 -0
- package/src/bongos/route-rank-check.js +6 -0
- package/src/bongos/serve-internal.js +12 -0
- package/src/module-api.js +1 -1
- package/tests/government_abuse_matrix.mjs +3 -0
- package/tests/government_decision_rules.mjs +701 -0
- package/tests/government_routes.mjs +4 -0
- package/tests/government_seed.mjs +1 -0
- package/tests/hall_audit.mjs +9 -1
- package/tests/hall_page_gate_map.mjs +3 -0
- package/tests/hall_street.mjs +521 -0
|
@@ -122,6 +122,10 @@ const governmentPort = {
|
|
|
122
122
|
// `charter` block. Opening a sitting decides nothing; the vote does.
|
|
123
123
|
proposeCharter: board.proposeCharter,
|
|
124
124
|
genesisStageStatus: board.genesisStageStatus,
|
|
125
|
+
// task 1004529 — who decides what. The table, resolved, and the check a
|
|
126
|
+
// building's own direct route runs before letting one builder act alone.
|
|
127
|
+
decisionRulesView: board.decisionRulesView,
|
|
128
|
+
checkActAlone: board.checkActAlone,
|
|
125
129
|
};
|
|
126
130
|
|
|
127
131
|
function registerSeams() {
|
|
@@ -225,6 +229,31 @@ module.exports = function buildGovernmentRouter() {
|
|
|
225
229
|
}
|
|
226
230
|
return rank;
|
|
227
231
|
}
|
|
232
|
+
// WHO DECIDES AT PEOPLE (task 1004529). Giving or taking a custom rank is
|
|
233
|
+
// People's own act, and the project's decision-rules table says who may do it
|
|
234
|
+
// alone. As today (the default row) the route's own permission is the whole
|
|
235
|
+
// gate, unchanged. Once a passed amendment hands People to a sitting, one
|
|
236
|
+
// builder may no longer do it alone — the route refuses and names the matter
|
|
237
|
+
// route instead — unless the row's rule lets a seated member decide alone and
|
|
238
|
+
// this builder is seated. Server-side, per request, uncached (ADR 0016).
|
|
239
|
+
// Fail-CLOSED: an unreadable row refuses rather than waving the act through.
|
|
240
|
+
async function peopleMayActAlone(req, res) {
|
|
241
|
+
let out;
|
|
242
|
+
try {
|
|
243
|
+
out = await board.checkActAlone('people', req.builder.id);
|
|
244
|
+
} catch (err) {
|
|
245
|
+
log.error('[gds] people decision rule unreadable', err);
|
|
246
|
+
res.fail('decision_rule_unreadable', { status: 503, message: 'who decides at People could not be read just now' });
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
if (out.ok) return true;
|
|
250
|
+
res.fail('decided_by_sitting', 409, {
|
|
251
|
+
area: out.area, rule: out.rule, who: out.who,
|
|
252
|
+
what_this_means: out.what_this_means, open_with: out.open_with,
|
|
253
|
+
});
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
228
257
|
const ARCHON = 'archon';
|
|
229
258
|
const ARCHON_REASON = 'the Archon rank holds every permission and is immutable — it is the rank that can never be locked out of this tab (ADR 0172)';
|
|
230
259
|
const ASSIGN_REASON = 'a seeded rank is assigned by rank (R95b sync); change the builder\'s rank instead';
|
|
@@ -447,6 +476,7 @@ module.exports = function buildGovernmentRouter() {
|
|
|
447
476
|
try {
|
|
448
477
|
const rank = await loadAssignableRank(res, rankKey);
|
|
449
478
|
if (rank === null) return;
|
|
479
|
+
if (!(await peopleMayActAlone(req, res))) return;
|
|
450
480
|
const assigned = await db.assignRank({
|
|
451
481
|
builderId: req.body.builder_id,
|
|
452
482
|
rankKey,
|
|
@@ -472,6 +502,7 @@ module.exports = function buildGovernmentRouter() {
|
|
|
472
502
|
try {
|
|
473
503
|
const rank = await loadAssignableRank(res, rankKey);
|
|
474
504
|
if (rank === null) return;
|
|
505
|
+
if (!(await peopleMayActAlone(req, res))) return;
|
|
475
506
|
const removed = await db.unassignRank({ builderId, rankKey });
|
|
476
507
|
res.json({ ok: true, builder_id: builderId, rank_key: rankKey, removed });
|
|
477
508
|
} catch (err) {
|
|
@@ -678,6 +709,90 @@ module.exports = function buildGovernmentRouter() {
|
|
|
678
709
|
}
|
|
679
710
|
});
|
|
680
711
|
|
|
712
|
+
// rank: perm board.item.open — propose a change to WHO DECIDES WHAT (task
|
|
713
|
+
// 1004529): one or more buildings' rows of the decision-rules table, filed as
|
|
714
|
+
// an amendment of kind 'decision_rules' and sat under the constitution IN
|
|
715
|
+
// FORCE, like every amendment. Nothing here changes the table — only the
|
|
716
|
+
// sitting passing does, inside its closing transaction. The same atom as
|
|
717
|
+
// proposing any amendment.
|
|
718
|
+
//
|
|
719
|
+
// Body: { rules: [{ area, decided_by, rule?, who? }], rationale_md? }
|
|
720
|
+
// area ∈ rules disputes people planning automation review release treasury
|
|
721
|
+
// (the charter is the board — change it with a constitution amendment)
|
|
722
|
+
// decided_by 'gate' (as today) | 'nobody' (vacant) | rank:<key> | rank:<key>+ | rank:a,b
|
|
723
|
+
// rule decide | first | consent | majority | unanimous | signoff (vacant with 'nobody';
|
|
724
|
+
// none with 'gate')
|
|
725
|
+
// who the seat in words, display only ("Mira under $50, the council above")
|
|
726
|
+
router.post('/government/board/decision-rules', api.requireBuilder, api.requirePermission('board.item.open'), async (req, res) => {
|
|
727
|
+
if (validateOrRespond(req, res, {
|
|
728
|
+
rules: { required: true, type: 'array' },
|
|
729
|
+
rationale_md: { type: 'string', maxLength: 5000 },
|
|
730
|
+
})) return;
|
|
731
|
+
try {
|
|
732
|
+
const out = await board.proposeDecisionRules({
|
|
733
|
+
rules: req.body.rules,
|
|
734
|
+
rationaleMd: req.body.rationale_md ?? null,
|
|
735
|
+
proposedBy: req.builder.id,
|
|
736
|
+
});
|
|
737
|
+
if (!out.ok) {
|
|
738
|
+
if (out.reason === 'no_change') return res.fail('no_change', 409, { reason: 'every building named is already decided this way' });
|
|
739
|
+
return res.fail('invalid_decision_rules', 400, {
|
|
740
|
+
reason: out.reason,
|
|
741
|
+
...(out.area ? { area: out.area } : {}),
|
|
742
|
+
...(out.field ? { field: out.field } : {}),
|
|
743
|
+
...(out.expected ? { expected: out.expected } : {}),
|
|
744
|
+
...(out.rank_key ? { rank_key: out.rank_key } : {}),
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
res.status(201).json({ ok: true, amendment: out.amendment, item: out.item, rules: out.rules });
|
|
748
|
+
} catch (err) {
|
|
749
|
+
log.error('[gds] POST /government/board/decision-rules', err);
|
|
750
|
+
res.fail('propose_failed', { status: 500, message: 'internal error' });
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// rank: perm board.item.open — put a MATTER to a building's sitting (task
|
|
755
|
+
// 1004529): the board's new subjects, one per building other than the
|
|
756
|
+
// charter. Refused unless the building's row names a sitting — under the
|
|
757
|
+
// default rows every building is decided as today and this route opens
|
|
758
|
+
// nothing. The sitting is snapshotted from the row (who and how).
|
|
759
|
+
//
|
|
760
|
+
// Body: { area, title, detail_md?, action? }
|
|
761
|
+
// action (People only): { type: 'assign_rank' | 'unassign_rank', builder_id, rank_key }
|
|
762
|
+
// — carried out when the sitting passes, under the same walls as the direct route.
|
|
763
|
+
router.post('/government/board/matters', api.requireBuilder, api.requirePermission('board.item.open'), async (req, res) => {
|
|
764
|
+
if (validateOrRespond(req, res, {
|
|
765
|
+
area: { required: true, type: 'string', maxLength: 32 },
|
|
766
|
+
title: { required: true, type: 'string', maxLength: 200 },
|
|
767
|
+
detail_md: { type: 'string', maxLength: 5000 },
|
|
768
|
+
action: { type: 'object' },
|
|
769
|
+
})) return;
|
|
770
|
+
try {
|
|
771
|
+
const out = await board.openMatter({
|
|
772
|
+
area: req.body.area,
|
|
773
|
+
title: req.body.title,
|
|
774
|
+
detailMd: req.body.detail_md ?? null,
|
|
775
|
+
action: req.body.action ?? null,
|
|
776
|
+
proposedBy: req.builder.id,
|
|
777
|
+
});
|
|
778
|
+
if (!out.ok) {
|
|
779
|
+
if (out.reason === 'decided_by_gate' || out.reason === 'no_one_holds_this') {
|
|
780
|
+
return res.fail(out.reason, 409, { area: out.area, ...(out.gate ? { gate: out.gate } : {}), what_this_means: out.what_this_means });
|
|
781
|
+
}
|
|
782
|
+
return res.fail('invalid_matter', 400, {
|
|
783
|
+
reason: out.reason,
|
|
784
|
+
...(out.field ? { field: out.field } : {}),
|
|
785
|
+
...(out.expected ? { expected: out.expected } : {}),
|
|
786
|
+
...(out.rank_key ? { rank_key: out.rank_key } : {}),
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
res.status(201).json({ ok: true, matter: out.matter, item: out.item, decided: out.decided });
|
|
790
|
+
} catch (err) {
|
|
791
|
+
log.error('[gds] POST /government/board/matters', err);
|
|
792
|
+
res.fail('matter_failed', { status: 500, message: 'internal error' });
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
|
|
681
796
|
// rank: perm board.vote.cast — cast a vote on an open board item (R08 of goal
|
|
682
797
|
// 1000069, ADR 0175). The atom FLOORS AT XENOS deliberately (the R04 carry-
|
|
683
798
|
// note): the permission is only the coarse gate, and the real wall is the
|
|
@@ -314,6 +314,15 @@
|
|
|
314
314
|
return out;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
// Rules lights its band (the prototype's st.fixes, set when the small-fixes
|
|
318
|
+
// rule passed) when a standing rule is at work: the docket's "rules at work"
|
|
319
|
+
// reading is live and counts at least one. Shared with street-draw.js.
|
|
320
|
+
function rulesAtWork(docket) {
|
|
321
|
+
const rs = docket && Array.isArray(docket.readings) ? docket.readings : [];
|
|
322
|
+
const r = rs.find((x) => x && x.key === 'rules');
|
|
323
|
+
return !!r && r.status === 'live' && Number(r.value) > 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
317
326
|
function pinState(state) {
|
|
318
327
|
return state === 'needs' || state === 'moving' || state === 'settled' ? state : 'moving';
|
|
319
328
|
}
|
|
@@ -364,7 +373,7 @@
|
|
|
364
373
|
const blds = [];
|
|
365
374
|
CIVIC_SLOTS.forEach((s) => {
|
|
366
375
|
const polys = [];
|
|
367
|
-
isoInstitution(polys, k, s[0], s[1], s[2], { fixes:
|
|
376
|
+
isoInstitution(polys, k, s[0], s[1], s[2], { fixes: rulesAtWork(docket) });
|
|
368
377
|
blds.push({ key: s[0], depth: s[1] + s[2] + 2, polys: polys });
|
|
369
378
|
});
|
|
370
379
|
districts.forEach((D) => {
|
|
@@ -429,7 +438,7 @@
|
|
|
429
438
|
const api = {
|
|
430
439
|
isoTheme: isoTheme, isoKit: isoKit, isoBox: isoBox, isoGable: isoGable, isoHip: isoHip, isoSaw: isoSaw,
|
|
431
440
|
isoTree: isoTree, isoLight: isoLight, isoInstitution: isoInstitution, isoWork: isoWork, glyph: glyph, rnd: rnd,
|
|
432
|
-
lotsFor: lotsFor, districtWork: districtWork, cityModel: cityModel,
|
|
441
|
+
lotsFor: lotsFor, districtWork: districtWork, cityModel: cityModel, rulesAtWork: rulesAtWork,
|
|
433
442
|
CIVIC_SLOTS: CIVIC_SLOTS, ROOF: ROOF, BASE_LOTS: BASE_LOTS, MAX_DISTRICTS: MAX_DISTRICTS, MAX_PINS_PER_BUILDING: MAX_PINS_PER_BUILDING,
|
|
434
443
|
FALLBACK_LINE: FALLBACK_LINE, STREET_HREF: STREET_HREF,
|
|
435
444
|
};
|