@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.
Files changed (34) hide show
  1. package/.bongos-core.json +60 -35
  2. package/docs/copy-inventory.md +5 -5
  3. package/docs/copy-registry.json +6 -6
  4. package/docs/module-api-changelog.md +4 -0
  5. package/docs/page-inventory.json +1 -0
  6. package/docs/page-readings.json +196 -197
  7. package/modules/autonomy/governor-docket.js +118 -4
  8. package/modules/economy/cost.js +36 -0
  9. package/modules/economy/governor-docket.js +81 -0
  10. package/modules/economy/routes/cost.js +2 -0
  11. package/modules/government/docket-floors.js +222 -0
  12. package/modules/government/docket-vocab.js +20 -0
  13. package/modules/government/docket.js +186 -5
  14. package/modules/hall-ui/public/street-amend.js +240 -0
  15. package/modules/hall-ui/public/street-card.js +4 -1
  16. package/modules/hall-ui/public/street-inside.js +99 -43
  17. package/modules/hall-ui/public/street.css +8 -0
  18. package/modules/hall-ui/public/street.html +5 -3
  19. package/modules/hall-ui/public/street.js +164 -11
  20. package/modules/hall-ui/public/street.states.json +12 -5
  21. package/modules/ideas/governor-docket.js +42 -1
  22. package/modules/lifecycle/governor-docket.js +197 -2
  23. package/modules/security/routes/reports.js +16 -0
  24. package/modules/security/security.js +5 -2
  25. package/modules/ui-design/kit/fixtures/government-constitution-rules.json +184 -0
  26. package/package-lock.json +2 -2
  27. package/package.json +1 -1
  28. package/release-notes.json +12 -0
  29. package/src/module-api.js +1 -1
  30. package/tests/governor_docket.mjs +186 -2
  31. package/tests/hall_street.mjs +1 -1
  32. package/tests/hall_street_amend.mjs +336 -0
  33. package/tests/hall_street_inside.mjs +101 -5
  34. 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 no feed reads stands as not recorded yet: faint, no number, and says so', () => {
180
- const b = I.civicInfo('charter', { docket: docket(), goals: [], now: NOW });
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
- const bv = I.buildingModel({ key: 'charter', docket: docket(), mode: 'dark', now: NOW });
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
- assert.equal(p.byId.get('insideAmendNote').hidden, false, 'change how this works says it is not built yet, rather than nothing');
479
- assert.match(p.byId.get('insideAmendNote').textContent, /not built yet/);
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