@bongos/core 1.21.18 → 1.21.20
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 +60 -35
- package/docs/copy-inventory.md +5 -5
- package/docs/copy-registry.json +6 -6
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +1 -0
- package/docs/page-readings.json +196 -197
- package/modules/autonomy/governor-docket.js +118 -4
- package/modules/economy/cost.js +36 -0
- package/modules/economy/governor-docket.js +81 -0
- package/modules/economy/routes/cost.js +2 -0
- package/modules/government/docket-floors.js +222 -0
- package/modules/government/docket-vocab.js +20 -0
- package/modules/government/docket.js +186 -5
- package/modules/hall-ui/public/street-amend.js +240 -0
- package/modules/hall-ui/public/street-card.js +4 -1
- package/modules/hall-ui/public/street-inside.js +99 -43
- package/modules/hall-ui/public/street.css +8 -0
- package/modules/hall-ui/public/street.html +5 -3
- package/modules/hall-ui/public/street.js +164 -11
- package/modules/hall-ui/public/street.states.json +12 -5
- package/modules/ideas/governor-docket.js +42 -1
- package/modules/lifecycle/governor-docket.js +197 -2
- package/modules/security/routes/reports.js +16 -0
- package/modules/security/security.js +5 -2
- package/modules/ui-design/kit/fixtures/government-constitution-rules.json +184 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/module-api.js +1 -1
- package/tests/governor_docket.mjs +186 -2
- package/tests/hall_street.mjs +1 -1
- package/tests/hall_street_amend.mjs +336 -0
- package/tests/hall_street_inside.mjs +101 -5
- package/tests/rank_tier_single_source.mjs +5 -0
|
@@ -68,6 +68,8 @@ function docket(matters = [], over = {}) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
const goal = (id, status = 'open', title = `Goal ${id}`) => ({ id: String(id), title, status });
|
|
71
|
+
// a docket floor row in the route's shape (docket.js buildDocket `floors`)
|
|
72
|
+
const floorRow = (key, over = {}) => ({ key, building: key.split('.')[0], status: 'live', count: '3', line: 'three things', lines: [['One thing.', 'today']], hot: false, reason: null, ...over });
|
|
71
73
|
|
|
72
74
|
// ── the drawing is the prototype's ───────────────────────────────────────────
|
|
73
75
|
|
|
@@ -176,10 +178,12 @@ test('each building keeps the prototype\'s capacity, words and floor order', ()
|
|
|
176
178
|
|
|
177
179
|
// ── what a floor says comes from the docket, and only from it ───────────────
|
|
178
180
|
|
|
179
|
-
test('a floor
|
|
180
|
-
const
|
|
181
|
+
test('a floor whose engine is coming stands as not recorded yet: faint, no number, and says so', () => {
|
|
182
|
+
const coming = docket([], { floors: I.PLAN.charter.floors.map((f) => floorRow(f[2].floor, { status: 'coming', count: '', lines: [], reason: 'Not yet.' })) });
|
|
183
|
+
const b = I.civicInfo('charter', { docket: coming, goals: [], now: NOW });
|
|
181
184
|
assert.ok(b.floors.every((f) => f.status === 'coming' && f.count === ''), 'nothing on the charter is read yet');
|
|
182
|
-
|
|
185
|
+
assert.deepEqual(b.floors[1].lines, [['Not yet.', 'coming']], 'it says why, in the docket\'s words');
|
|
186
|
+
const bv = I.buildingModel({ key: 'charter', docket: coming, mode: 'dark', now: NOW });
|
|
183
187
|
const quiet = bv.texts.filter((t) => t.v === 'not recorded yet');
|
|
184
188
|
assert.equal(quiet.length, 8, 'every used floor says it on the wall');
|
|
185
189
|
assert.ok(bv.texts.filter((t) => t.s === 14.5).every((t) => t.c === '#8e8e93'), 'its name lettered faint, like a proposed floor');
|
|
@@ -265,6 +269,96 @@ test('who holds it: the docket\'s holders when it names them, else an empty seat
|
|
|
265
269
|
assert.deepEqual([held.seats, held.who, held.how], [['M'], 'Mira, treasurer', 'more than half must say yes']);
|
|
266
270
|
});
|
|
267
271
|
|
|
272
|
+
// ── real data on every floor (task 1004527) ─────────────────────────────────
|
|
273
|
+
|
|
274
|
+
test('every floor of every building is bound to a docket feed: none is left unwired', () => {
|
|
275
|
+
for (const key of S.CIVIC_KEYS) {
|
|
276
|
+
for (const [name, , bind] of I.PLAN[key].floors) {
|
|
277
|
+
assert.ok(bind && (bind.floor || bind.source || bind.reading || bind.goals), `${key} · ${name} reads something`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('a live floor shows exactly what the docket says: its count, line and lines; hot is being decided', () => {
|
|
283
|
+
const d = docket([], { floors: [
|
|
284
|
+
floorRow('treasury.rewards', { count: '$2,400', line: '$2,400 this month · 6 builders', lines: [['Paid to builders for shipped work.', 'each ship']] }),
|
|
285
|
+
floorRow('automation.allowed_goals', { count: '2', line: '2 goals it may build', lines: [['goal 7', 'first in line'], ['goal 99', 'since sep 2']] }),
|
|
286
|
+
floorRow('charter.founding', { count: '', line: '1 of 2 founding steps passed', lines: [['The charter.', 'passed']], hot: true }),
|
|
287
|
+
] });
|
|
288
|
+
const t = I.civicInfo('treasury', { docket: d, now: NOW }).floors.find((f) => f.name === 'Builder rewards');
|
|
289
|
+
assert.deepEqual([t.count, t.line, t.status, t.lines], ['$2,400', '$2,400 this month · 6 builders', 'ok', [['Paid to builders for shipped work.', 'each ship']]]);
|
|
290
|
+
const a = I.civicInfo('automation', { docket: d, goals: [goal(7, 'open', 'Onboarding')], now: NOW }).floors.find((f) => f.name === 'Allowed goals');
|
|
291
|
+
assert.deepEqual(a.lines, [['Onboarding', 'first in line'], ['goal 99', 'since sep 2']], 'a goal it knows is lettered by title; one it does not keeps its id');
|
|
292
|
+
const c = I.buildingModel({ key: 'charter', docket: d, mode: 'dark', now: NOW });
|
|
293
|
+
assert.equal(c.selFloor, 0, 'a founding step being decided is the floor chosen first');
|
|
294
|
+
assert.equal(c.openKicker, 'floor 1 · being decided');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('Rules in force: each rule and what it settled, from its floor; the reading when the floor is absent', () => {
|
|
298
|
+
const d = docket([], { floors: [floorRow('rules.in_force', { count: '1', line: '1 rule · settled 18 this month', lines: [['Every filing lands in its goal without a vote.', 'settled 18']] })] });
|
|
299
|
+
const f = I.civicInfo('rules', { docket: d, now: NOW }).floors[0];
|
|
300
|
+
assert.deepEqual([f.count, f.line, f.lines[0]], ['1', '1 rule · settled 18 this month', ['Every filing lands in its goal without a vote.', 'settled 18']]);
|
|
301
|
+
const old = docket();
|
|
302
|
+
Object.assign(old.readings.find((r) => r.key === 'rules'), { status: 'live', figure: '1 rule', line: 'the landing rule settled 4 filings this month' });
|
|
303
|
+
assert.deepEqual(I.civicInfo('rules', { docket: old, now: NOW }).floors[0].count, '1', 'an older docket still reads the reading');
|
|
304
|
+
const h = I.holderOf('people', { decision: { who: 'metic', rule: 'consent' }, holders: [], holder_count: 3 }, []);
|
|
305
|
+
assert.equal(h.who, 'metic', 'a count with no names never letters undefined');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('a floor the docket cannot answer never shows a number; Goals falls back to the open goals', () => {
|
|
309
|
+
const d = docket([], { floors: [floorRow('people.ranks', { status: 'not_yours', count: '9' }), floorRow('people.council', { status: 'off', count: '5', reason: 'Off here.' }), floorRow('people.newcomers', { status: 'unavailable', count: '2' })] });
|
|
310
|
+
const by = Object.fromEntries(I.civicInfo('people', { docket: d, now: NOW }).floors.map((f) => [f.name, f]));
|
|
311
|
+
for (const [n, st] of [['Ranks', 'not_yours'], ['The council', 'off'], ['Newcomers', 'unavailable'], ['Testers', 'unavailable']]) {
|
|
312
|
+
assert.deepEqual([by[n].status, by[n].count], [st, ''], `${n} says ${st} and no number`);
|
|
313
|
+
}
|
|
314
|
+
const g = I.civicInfo('planning', { docket: docket(), goals: [goal(1), goal(2)], now: NOW }).floors[0];
|
|
315
|
+
assert.deepEqual([g.count, g.line], ['2', '2 goals open'], 'an older docket without floors still counts the open goals');
|
|
316
|
+
const live = I.civicInfo('planning', { docket: docket([], { floors: [floorRow('planning.goals', { count: '1', line: '1 goal open', lines: [['Hall', '5 of 5 kept']] })] }), goals: [goal(1), goal(2)], now: NOW }).floors[0];
|
|
317
|
+
assert.deepEqual([live.count, live.lines], ['1', [['Hall', '5 of 5 kept']]], 'with the floor, its promises kept of total');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('no made-up number: every count on every wall is one the docket sent', () => {
|
|
321
|
+
const ms = [matter({ id: 'access_requests:1', source: 'access_requests', building: 'people' })];
|
|
322
|
+
const floors = [floorRow('people.ranks', { count: '4' }), floorRow('treasury.hosting', { count: '$120' }), floorRow('release.live_month', { count: '7' })];
|
|
323
|
+
const d = docket(ms, { floors });
|
|
324
|
+
Object.assign(d.readings.find((r) => r.key === 'money'), { status: 'live', figure: '$1,100 left', line: 'left' });
|
|
325
|
+
const sent = new Set(['', ...floors.map((f) => f.count), '1', '0', '$1,100']);
|
|
326
|
+
for (const key of S.CIVIC_KEYS) {
|
|
327
|
+
for (const f of I.civicInfo(key, { docket: d, goals: [], now: NOW }).floors) assert.ok(sent.has(f.count), `${key} · ${f.name} shows ${f.count}`);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test('who holds it reads the decision table: the people it seats, its rule, you, and a vacant seat', () => {
|
|
332
|
+
const info = { key: 'release', decision: { who: 'whoever may put a release live', rule: 'decide', rule_words: 'decides alone', as_today: true }, holders: [{ id: '95', name: 'Ana' }, { id: '7', login: 'theo' }], holder_count: 2 };
|
|
333
|
+
const h = I.holderOf('release', info, [], '95');
|
|
334
|
+
assert.deepEqual([h.seats, h.who, h.rule, h.how], [['you', 'T'], 'Ana and theo · whoever may put a release live', 'decide', 'one person decides']);
|
|
335
|
+
const many = I.holderOf('people', { decision: { who: 'metic and above', rule: 'consent' }, holders: [{ name: 'A' }, { name: 'B' }, { name: 'C' }], holder_count: 7 }, []);
|
|
336
|
+
assert.equal(many.who, 'A, B, C and 4 more · metic and above');
|
|
337
|
+
const vacant = I.holderOf('review', { decision: { who: 'no one yet', rule: 'vacant' }, holders: [] }, []);
|
|
338
|
+
assert.deepEqual([vacant.seats, vacant.who, vacant.how], [['—'], 'no one holds it yet', 'its matters wait until a seat is filled']);
|
|
339
|
+
const d = docket();
|
|
340
|
+
d.buildings.find((b) => b.key === 'review').decision = { who: 'no one yet', rule: 'vacant' };
|
|
341
|
+
assert.equal(I.civicInfo('review', { docket: d, now: NOW }).vacant, true, 'a vacant row is an unheld Review');
|
|
342
|
+
const held = I.civicInfo('review', { docket: docket(), now: NOW });
|
|
343
|
+
assert.deepEqual(held.floors.map((f) => f.name), ['Testers', 'Being tested', 'Signed off'], 'held, its queue is the prototype\'s Being tested');
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('the record drawers read the real history by month, file a sitting once, and cap a full drawer', () => {
|
|
347
|
+
const history = [
|
|
348
|
+
{ id: 'government:amendment:12', source: 'government', building: 'charter', title: '§6 Review: testers hold the seat', at: '2026-10-01T09:00:00.000Z', outcome: 'passed' },
|
|
349
|
+
{ id: 'government:founding:3', source: 'government', building: 'charter', title: 'Founding: the charter', at: '2026-07-04T09:00:00.000Z', outcome: 'passed' },
|
|
350
|
+
{ id: 'releases:9', source: 'releases', building: 'release', title: 'Invite flow', at: '2026-09-26T09:00:00.000Z', outcome: 'live' },
|
|
351
|
+
];
|
|
352
|
+
const ms = [matter({ id: 'board:12', source: 'board', building: 'charter', state: 'settled', title: '§6 Review: testers hold the seat', ref: { type: 'board_item', id: '12' }, opened_at: '2026-09-28T00:00:00.000Z' })];
|
|
353
|
+
const m = I.buildingModel({ key: 'charter', docket: docket(ms, { history }), mode: 'dark', now: NOW });
|
|
354
|
+
assert.deepEqual(m.drawers.map((x) => [x.label, x.count]), [['oct', 1], ['jul', 1]], 'the settled sitting is filed once, by when it closed');
|
|
355
|
+
assert.deepEqual(m.drawerItems.map((x) => [x.text, x.meta]), [['§6 Review: testers hold the seat', 'passed']]);
|
|
356
|
+
assert.equal(m.onFile, '1 matter on the docket · 2 on the record');
|
|
357
|
+
const lots = Array.from({ length: 15 }, (_, i) => ({ id: `releases:${i}`, building: 'release', title: `R${i}`, at: '2026-09-02T00:00:00.000Z', outcome: 'live' }));
|
|
358
|
+
const r = I.recordOf([], NOW, lots);
|
|
359
|
+
assert.deepEqual([r[0].count, r[0].items.length, r[0].items[12]], [15, 13, ['and 3 more', '']]);
|
|
360
|
+
});
|
|
361
|
+
|
|
268
362
|
test('the annex lot stands only when the building is full; an unheld Review is the dashed ghost', () => {
|
|
269
363
|
assert.equal(I.buildingModel({ key: 'charter', docket: docket(), mode: 'dark', now: NOW }).hasAnnex, false, '8 of 9');
|
|
270
364
|
const save = I.PLAN.rules.cap;
|
|
@@ -475,8 +569,10 @@ test('picking a floor moves the SAME lift node and re-enters the chosen block; d
|
|
|
475
569
|
assert.equal(p.byId.get('insideDrawers').children[1].attrs['aria-pressed'], 'true');
|
|
476
570
|
assert.equal(p.byId.get('insideAmendNote').hidden, true);
|
|
477
571
|
p.byId.get('insideAmend').fire('click');
|
|
478
|
-
|
|
479
|
-
|
|
572
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
573
|
+
// the draft itself is tests/hall_street_amend.mjs (task 1004530); with no constitution to read it says so, never nothing
|
|
574
|
+
assert.equal(p.byId.get('insideAmendNote').hidden, false, 'change how this works answers, rather than nothing');
|
|
575
|
+
assert.match(p.byId.get('insideAmendNote').textContent, /could not be read/);
|
|
480
576
|
});
|
|
481
577
|
|
|
482
578
|
test('a table row opens the short card; Escape gives focus back to the row; back to the street returns to the door', async () => {
|
|
@@ -124,6 +124,11 @@ const STATIC_LADDER_FILES = [
|
|
|
124
124
|
// match the canonical order or the example teaches a ladder that does not
|
|
125
125
|
// exist.
|
|
126
126
|
'modules/hall-ui/public/government.js',
|
|
127
|
+
// task 1004530: the street's drafted amendment offers the rank ladder as the
|
|
128
|
+
// seats a building can be handed to (rank:<key>+). Browser code, so a mirror;
|
|
129
|
+
// tests/hall_street_amend.mjs pins GovAmend.RANKS equal to the server's
|
|
130
|
+
// membership-predicate RANK_ORDER.
|
|
131
|
+
'modules/hall-ui/public/street-amend.js',
|
|
127
132
|
// task 1002487: the pure agent-definition validator applies the ADR 0043
|
|
128
133
|
// sub-Metic floor to an agent's declared scope, so it needs the ladder to
|
|
129
134
|
// compare against. It is one of the "dependency-free" cases this array exists
|