@bongos/core 1.21.166 → 1.21.168
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 +153 -73
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +8 -0
- package/clients/bongos-client/index.cjs +8 -0
- package/clients/bongos-client/index.d.ts +13 -0
- package/clients/bongos-client/index.mjs +8 -0
- package/docs/api/openapi.json +306 -3
- package/docs/api-reference.md +7 -3
- package/docs/copy-inventory.md +21 -20
- package/docs/copy-registry.json +34 -25
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +1 -0
- package/docs/page-inventory.json +3 -0
- package/docs/page-readings.json +445 -445
- package/modules/autonomy/governor-docket.js +10 -5
- package/modules/builder-settings/builder-needs.js +29 -3
- package/modules/copy-desk/guarded-lines.js +89 -0
- package/modules/copy-desk/guarded-lines.json +14 -0
- package/modules/copy-desk/module.json +2 -2
- package/modules/copy-desk/page-status.js +77 -7
- package/modules/copy-desk/pages.js +4 -0
- package/modules/copy-desk/pollers/stuck-page-digest.js +34 -0
- package/modules/copy-desk/routes/copy-desk.js +8 -0
- package/modules/copy-desk/stuck-tweaks.js +79 -0
- package/modules/copy-desk/tests/copy_no_cms.mjs +6 -1
- package/modules/copy-desk/tests/copy_page_status.mjs +2 -2
- package/modules/government/asks-db.js +38 -2
- package/modules/government/catalog.js +1 -1
- package/modules/government/docket.js +20 -2
- package/modules/government/migrations/government_032_ask_flags.sql +35 -0
- package/modules/government/routes/government.js +43 -1
- package/modules/hall-ui/public/approval-queue-lib.js +4 -0
- package/modules/hall-ui/public/approval-queue.js +1 -0
- package/modules/hall-ui/public/brand-apply.js +3 -0
- package/modules/hall-ui/public/city-decide.js +642 -0
- package/modules/hall-ui/public/city-draw.js +14 -3
- package/modules/hall-ui/public/city-switchboard.js +141 -0
- package/modules/hall-ui/public/city.css +176 -10
- package/modules/hall-ui/public/city.html +8 -3
- package/modules/hall-ui/public/city.js +185 -38
- package/modules/hall-ui/public/sky-data.js +4 -1
- package/modules/hall-ui/public/tweak-editor-lib.js +21 -2
- package/modules/hall-ui/public/tweak-editor.css +2 -0
- package/modules/hall-ui/public/tweak-editor.js +3 -1
- package/modules/lifecycle/db-claims.js +20 -5
- package/modules/lifecycle/rebase-gate.js +78 -0
- package/modules/lifecycle/routes/claims.js +10 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +28 -0
- package/scripts/gds/ask.js +68 -1
- package/scripts/gds/claim.js +6 -1
- package/scripts/gds/copy-apply.js +2 -0
- package/src/bongos/module-promotion.js +219 -0
- package/src/bongos/route-rank-check.js +7 -0
- package/src/bongos/routes/me.js +11 -0
- package/src/bongos/routes/modules.js +46 -0
- package/src/module-api.js +1 -1
- package/src/platform-server.js +5 -0
- package/tests/claim_gate_ci_unblock.mjs +4 -0
- package/tests/claim_gate_rebase.mjs +56 -3
- package/tests/copy_desk_page_editor.mjs +1 -1
- package/tests/copy_desk_page_submit.mjs +42 -0
- package/tests/copy_guarded_lines.mjs +54 -0
- package/tests/copy_stuck_page_tweaks.mjs +118 -0
- package/tests/government_ask_flags.mjs +80 -0
- package/tests/government_seed.mjs +1 -0
- package/tests/government_treasury.mjs +1 -0
- package/tests/governor_docket.mjs +1 -1
- package/tests/hall_approval_queue.mjs +15 -0
- package/tests/hall_city.mjs +5 -4
- package/tests/hall_city_living.mjs +118 -12
- package/tests/hall_city_rewording.mjs +51 -0
- package/tests/hall_city_switchboard.mjs +63 -0
- package/tests/hall_gov_real_site.mjs +2 -2
- package/tests/hall_sky.mjs +18 -0
- package/tests/hall_street_deck.mjs +6 -4
- package/tests/hall_street_depth.mjs +1 -1
- package/tests/hall_street_desk.mjs +5 -3
- package/tests/hall_tweak_editor.mjs +26 -0
- package/tests/module_promotion.mjs +290 -0
- package/tests/rebase_gate_set_aside.mjs +73 -0
- package/tests/reward_mode_surfacing.mjs +12 -0
|
@@ -532,6 +532,23 @@ function registeredContributions() {
|
|
|
532
532
|
// the matters this viewer already sees, so an ask is never more visible than its
|
|
533
533
|
// matter. A read that fails or is slow attaches none: the city still stands, and
|
|
534
534
|
// each matter keeps its own words.
|
|
535
|
+
//
|
|
536
|
+
// The reword flags (government_032, owner 2026-10-23) ride the same way: a
|
|
537
|
+
// matter someone pressed "?" on carries `unclear` { flagged_at, note, times }.
|
|
538
|
+
async function attachFlags(matters, readFlags, timeoutMs) {
|
|
539
|
+
if (typeof readFlags !== 'function' || !matters.length) return;
|
|
540
|
+
try {
|
|
541
|
+
const found = await withTimeout(Promise.resolve(readFlags(matters.map((m) => m.id))), timeoutMs);
|
|
542
|
+
if (!found || typeof found.get !== 'function') return;
|
|
543
|
+
for (const m of matters) {
|
|
544
|
+
const flag = found.get(m.id);
|
|
545
|
+
if (flag) m.unclear = flag;
|
|
546
|
+
}
|
|
547
|
+
} catch (err) {
|
|
548
|
+
log.warn({ err: err && err.message }, 'governor docket: reword flags unreadable');
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
535
552
|
async function attachAsks(matters, readAsks, timeoutMs) {
|
|
536
553
|
if (typeof readAsks !== 'function' || !matters.length) return;
|
|
537
554
|
try {
|
|
@@ -561,7 +578,7 @@ async function attachAsks(matters, readAsks, timeoutMs) {
|
|
|
561
578
|
// `seats` is who sits in each seat (loadSeats below: Leads in office, the owner
|
|
562
579
|
// seat, each building's holders); given, every matter carries `route` (routing.js,
|
|
563
580
|
// task 1004705) and the docket says `routed: true`. null routes nothing.
|
|
564
|
-
async function buildDocket({ viewer, permissions, contributions = registeredContributions(), now = Date.now(), timeoutMs = SOURCE_TIMEOUT_MS, decisions = null, government = null, readAsks = null, seats = null } = {}) {
|
|
581
|
+
async function buildDocket({ viewer, permissions, contributions = registeredContributions(), now = Date.now(), timeoutMs = SOURCE_TIMEOUT_MS, decisions = null, government = null, readAsks = null, readFlags = null, seats = null } = {}) {
|
|
565
582
|
const held = new Set(permissions || []);
|
|
566
583
|
const ctx = Object.freeze({
|
|
567
584
|
builderId: String(viewer.id),
|
|
@@ -614,6 +631,7 @@ async function buildDocket({ viewer, permissions, contributions = registeredCont
|
|
|
614
631
|
sourceStatus.sort((a, b) => order.indexOf(a.source) - order.indexOf(b.source));
|
|
615
632
|
matters.sort(compareMatters);
|
|
616
633
|
await attachAsks(matters, readAsks, timeoutMs);
|
|
634
|
+
await attachFlags(matters, readFlags, timeoutMs);
|
|
617
635
|
// THE DIRECTION DESK (task 1004705): who each matter waits on, from real seats.
|
|
618
636
|
const yours = seats ? routing.routeAll(matters, ctx.builderId, seats, now) : null;
|
|
619
637
|
|
|
@@ -813,7 +831,7 @@ async function docketFor(builder) {
|
|
|
813
831
|
});
|
|
814
832
|
const government = permissions.includes(GOVERNMENT_GATE.permission) ? await loadGovernment({ decisions }) : null;
|
|
815
833
|
const seats = government ? await loadSeats(government) : null;
|
|
816
|
-
return buildDocket({ viewer: builder, permissions, decisions, government, readAsks: asksDb.asksFor, seats });
|
|
834
|
+
return buildDocket({ viewer: builder, permissions, decisions, government, readAsks: asksDb.asksFor, readFlags: asksDb.unclearFor, seats });
|
|
817
835
|
}
|
|
818
836
|
|
|
819
837
|
// Who sits in each seat now (task 1004705), read per request, uncached (ADR
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- government_032_ask_flags.sql — THE REWORD LIST: a reader says a card does not
|
|
2
|
+
-- make sense (owner, 2026-10-23: the "?" on every city card).
|
|
3
|
+
--
|
|
4
|
+
-- WHAT THIS ADDS
|
|
5
|
+
--
|
|
6
|
+
-- government_ask_flags — ONE ROW PER DOCKET MATTER someone flagged as unclear,
|
|
7
|
+
-- keyed by the matter's docket id (`blockers:12`), the same key as
|
|
8
|
+
-- government_asks. An AI session works the open rows (GET
|
|
9
|
+
-- /government/asks/unclear), writes the matter a proper ask (PUT
|
|
10
|
+
-- /government/asks/:matter, the standard short card), and that write closes
|
|
11
|
+
-- the flag (resolved_at). Flagging again after a rewrite reopens it.
|
|
12
|
+
--
|
|
13
|
+
-- A flag is a request, not a verdict: it grants nothing and changes nothing a
|
|
14
|
+
-- person decides. `note` is the reader's optional words on what confused them.
|
|
15
|
+
--
|
|
16
|
+
-- Additive and idempotent: a new table (IF NOT EXISTS) and one index. No row is
|
|
17
|
+
-- seeded.
|
|
18
|
+
|
|
19
|
+
BEGIN;
|
|
20
|
+
|
|
21
|
+
CREATE TABLE IF NOT EXISTS government_ask_flags (
|
|
22
|
+
matter_id text PRIMARY KEY
|
|
23
|
+
CHECK (matter_id ~ '^[a-z][a-z_]{1,39}:[A-Za-z0-9._-]{1,64}$'),
|
|
24
|
+
flagged_by bigint NULL,
|
|
25
|
+
flagged_at timestamptz NOT NULL DEFAULT now(),
|
|
26
|
+
note text NULL CHECK (note IS NULL OR char_length(note) <= 500),
|
|
27
|
+
times integer NOT NULL DEFAULT 1 CHECK (times >= 1),
|
|
28
|
+
resolved_at timestamptz NULL
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
-- the open reword list, oldest first
|
|
32
|
+
CREATE INDEX IF NOT EXISTS government_ask_flags_open_idx
|
|
33
|
+
ON government_ask_flags (flagged_at) WHERE resolved_at IS NULL;
|
|
34
|
+
|
|
35
|
+
COMMIT;
|
|
@@ -826,6 +826,48 @@ module.exports = function buildGovernmentRouter() {
|
|
|
826
826
|
}
|
|
827
827
|
});
|
|
828
828
|
|
|
829
|
+
// rank: perm board.vote.cast — "I DON'T UNDERSTAND THIS" (the "?" on a city
|
|
830
|
+
// card, owner 2026-10-23): puts a matter the flagger can see on the reword
|
|
831
|
+
// list (government_032). An AI session works that list and writes the matter
|
|
832
|
+
// a standard ask (PUT above), which closes the flag. A flag changes nothing a
|
|
833
|
+
// person decides; it only asks for better words.
|
|
834
|
+
//
|
|
835
|
+
// Body: { note? } (≤ 500 chars, what confused them) → 200 { ok, matter_id, flag }
|
|
836
|
+
router.post('/government/asks/:matterId/unclear', api.requireBuilder, api.requirePermission('board.vote.cast'), async (req, res) => {
|
|
837
|
+
const matterId = String(req.params.matterId || '');
|
|
838
|
+
if (!asks.isMatterId(matterId)) return res.fail('bad_matter_id', { status: 400, message: 'Name the matter by its docket id, like blockers:12.' });
|
|
839
|
+
if (validateOrRespond(req, res, { note: { type: 'string', maxLength: 500 } })) return;
|
|
840
|
+
try {
|
|
841
|
+
const found = await docket.findMatterFor(req.builder, matterId);
|
|
842
|
+
if (!found.matter) {
|
|
843
|
+
if (found.reason === 'unavailable') return res.fail('matter_unavailable', { status: 503, message: 'That matter could not be read just now, so nothing was flagged. Try again.' });
|
|
844
|
+
return res.fail('matter_not_found', { status: 404, message: 'No open matter by that id that you can see.' });
|
|
845
|
+
}
|
|
846
|
+
const flag = await asksDb.flagUnclear({ matterId, builderId: req.builder.id, note: req.body && req.body.note });
|
|
847
|
+
res.json({ ok: true, matter_id: matterId, flag });
|
|
848
|
+
} catch (err) {
|
|
849
|
+
log.error('[gds] POST /government/asks/:matterId/unclear', err);
|
|
850
|
+
res.fail('flag_save_failed', { status: 500, message: 'internal error' });
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// rank: perm board.vote.cast — THE REWORD LIST: the open matters this viewer
|
|
855
|
+
// can see that someone flagged as unclear, oldest flag first, each with its
|
|
856
|
+
// words as they stand (title, detail, stored ask) so a session can rewrite it.
|
|
857
|
+
// → 200 { ok, matters: [{ id, building, source, title, detail, ask, flag }] }
|
|
858
|
+
router.get('/government/asks/unclear', api.requireBuilder, api.requirePermission('board.vote.cast'), async (req, res) => {
|
|
859
|
+
try {
|
|
860
|
+
const d = await docket.docketFor(req.builder);
|
|
861
|
+
const list = (d.matters || []).filter((m) => m.unclear)
|
|
862
|
+
.sort((a, b) => String(a.unclear.flagged_at).localeCompare(String(b.unclear.flagged_at)))
|
|
863
|
+
.map((m) => ({ id: m.id, building: m.building, source: m.source, title: m.title, detail: m.detail || null, ask: m.ask || null, flag: m.unclear }));
|
|
864
|
+
res.json({ ok: true, matters: list });
|
|
865
|
+
} catch (err) {
|
|
866
|
+
log.error('[gds] GET /government/asks/unclear', err);
|
|
867
|
+
res.fail('flags_read_failed', { status: 500, message: 'internal error' });
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
|
|
829
871
|
// rank: perm board.vote.cast — PASS AN ASK ON to another seat (task 1004705,
|
|
830
872
|
// the direction desk). Only whoever the ask waits on today may hand it on
|
|
831
873
|
// (routing.passRefusal, from real seats: its Lead, the owner seat, a board's
|
|
@@ -847,7 +889,7 @@ module.exports = function buildGovernmentRouter() {
|
|
|
847
889
|
}
|
|
848
890
|
const refused = routing.passRefusal(found.matter, req.builder.id, seat, found.seats);
|
|
849
891
|
if (refused) return res.fail(refused.code, { status: refused.status, message: refused.message });
|
|
850
|
-
const ask = await asksDb.saveAsk({ matterId, ask: { ...found.matter.ask, waits_on: seat }, builderId: req.builder.id });
|
|
892
|
+
const ask = await asksDb.saveAsk({ matterId, ask: { ...found.matter.ask, waits_on: seat }, builderId: req.builder.id, resolveFlag: false });
|
|
851
893
|
const route = routing.routeMatter({ ...found.matter, ask }, req.builder.id, found.seats);
|
|
852
894
|
res.json({ ok: true, matter_id: matterId, ask, route });
|
|
853
895
|
} catch (err) {
|
|
@@ -120,6 +120,10 @@
|
|
|
120
120
|
aside: line.refused ? refusedWords(line.refused) : (after ? `was: ${line.before}` : `becomes: ${line.after}`),
|
|
121
121
|
refused: !!line.refused,
|
|
122
122
|
// task 1004775: a test still asserts the old words, so it fails once this lands.
|
|
123
|
+
// task 1004776: the rewrite dropped what this line must still say.
|
|
124
|
+
...(!line.refused && line.must_still_say
|
|
125
|
+
? { guarded: `This line no longer says what it must: ${line.must_still_say}` }
|
|
126
|
+
: {}),
|
|
123
127
|
...(!line.refused && Array.isArray(line.pinned_by) && line.pinned_by.length
|
|
124
128
|
? { pinned: `A test still checks the old words: ${line.pinned_by.join(', ')}. It needs updating when this ships.` }
|
|
125
129
|
: {}),
|
|
@@ -152,6 +152,7 @@
|
|
|
152
152
|
${r.section ? `<span class="aq-line__section">${esc(r.section)}</span>` : ''}
|
|
153
153
|
<span class="aq-line__text">${r.marked ? `<mark class="aq-mark">${words(r.text)}</mark>` : words(r.text)}</span>
|
|
154
154
|
<span class="aq-line__aside">${words(r.aside)}</span>
|
|
155
|
+
${r.guarded ? `<span class="aq-line__pinned">${esc(r.guarded)}</span>` : ''}
|
|
155
156
|
${r.pinned ? `<span class="aq-line__pinned">${esc(r.pinned)}</span>` : ''}
|
|
156
157
|
</li>`).join('')}</ol>`;
|
|
157
158
|
}
|
|
@@ -100,8 +100,11 @@
|
|
|
100
100
|
var s = task && task.status;
|
|
101
101
|
return s === 'confirmed' || s === 'shipped';
|
|
102
102
|
}
|
|
103
|
+
// task 1004782 — an abandoned (or merged-away) task earns nothing and is
|
|
104
|
+
// owed nothing, so it is worth 0 wherever a total adds it up.
|
|
103
105
|
function taskCreditValue(task) {
|
|
104
106
|
if (!task) return null;
|
|
107
|
+
if (task.status === 'abandoned' || task.merged_into_task_id != null) return 0;
|
|
105
108
|
if (taskSettled(task)) {
|
|
106
109
|
var awarded = Number(task.credits_awarded);
|
|
107
110
|
// Settled with a real ledger payout → show it. If a settled task has no
|