@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
|
@@ -57,6 +57,37 @@ function rulesReading(settledThisMonth) {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Rules · Rules in force (task 1004527): each standing rule and how many
|
|
61
|
+
// matters it settled this month. The project records one standing rule today,
|
|
62
|
+
// the landing rule; a rule put to the council has nowhere to live yet (the
|
|
63
|
+
// docket's standing_rules source says coming). Pure.
|
|
64
|
+
const LANDING_RULE = 'Every filing lands in its goal without a vote.';
|
|
65
|
+
function rulesFloor(settledThisMonth) {
|
|
66
|
+
const n = Number(settledThisMonth) || 0;
|
|
67
|
+
return {
|
|
68
|
+
count: '1',
|
|
69
|
+
line: `1 rule · settled ${n} this month`,
|
|
70
|
+
lines: [[LANDING_RULE, `settled ${n}`], ['A new rule put to the council is coming.', 'coming']],
|
|
71
|
+
hot: false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// The Rules record: what the landing rule settled, month by month. Pure.
|
|
75
|
+
function rulesHistory(months) {
|
|
76
|
+
return (months || []).filter((m) => Number(m.n) > 0).map((m) => ({
|
|
77
|
+
id: `landing:${m.month}`, title: `The landing rule settled ${m.n} ${Number(m.n) === 1 ? 'filing' : 'filings'}`,
|
|
78
|
+
at: `${m.month}-01T00:00:00.000Z`, outcome: 'without a vote',
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
async function routedByMonth(months = 6) {
|
|
82
|
+
const { rows } = await api.pool.query(
|
|
83
|
+
`SELECT to_char(created_at, 'YYYY-MM') AS month, count(*)::int AS n FROM ideas_task_lineage
|
|
84
|
+
WHERE origin = 'route' AND created_at >= date_trunc('month', now()) - (($1::int - 1) * interval '1 month')
|
|
85
|
+
GROUP BY month ORDER BY month DESC`,
|
|
86
|
+
[months],
|
|
87
|
+
);
|
|
88
|
+
return rows;
|
|
89
|
+
}
|
|
90
|
+
|
|
60
91
|
// Filings the routing rule landed this month (origin 'route').
|
|
61
92
|
async function countRoutedThisMonth() {
|
|
62
93
|
const { rows } = await api.pool.query(
|
|
@@ -66,6 +97,12 @@ async function countRoutedThisMonth() {
|
|
|
66
97
|
return rows[0] ? rows[0].n : 0;
|
|
67
98
|
}
|
|
68
99
|
|
|
100
|
+
// One read per docket request: the collector hands every read a per-request
|
|
101
|
+
// `once(key, fn)` (docket.js buildDocket), so two floors that need the same rows
|
|
102
|
+
// share one query. Without it (a direct call) the read simply runs.
|
|
103
|
+
const shared = (once, key, fn) => (typeof once === 'function' ? once(key, fn) : fn());
|
|
104
|
+
const routedFor = (once) => shared(once, 'ideas.routedThisMonth', () => countRoutedThisMonth());
|
|
105
|
+
|
|
69
106
|
function contributeIdeasToDocket() {
|
|
70
107
|
api.contribute(POINT, {
|
|
71
108
|
source: 'blockers',
|
|
@@ -81,13 +118,17 @@ function contributeIdeasToDocket() {
|
|
|
81
118
|
api.contribute(POINT, {
|
|
82
119
|
reading: 'rules',
|
|
83
120
|
gate: { anyBuilder: true },
|
|
84
|
-
read: async () => rulesReading(await
|
|
121
|
+
read: async (_ctx, { once } = {}) => rulesReading(await routedFor(once)),
|
|
85
122
|
});
|
|
123
|
+
api.contribute(POINT, { floor: 'rules.in_force', gate: { anyBuilder: true }, read: async (_ctx, { once } = {}) => rulesFloor(await routedFor(once)) });
|
|
124
|
+
api.contribute(POINT, { history: 'rules', building: 'rules', gate: { anyBuilder: true }, read: async () => rulesHistory(await routedByMonth()) });
|
|
86
125
|
}
|
|
87
126
|
|
|
88
127
|
module.exports = {
|
|
89
128
|
contributeIdeasToDocket,
|
|
90
129
|
blockerDocketMatter,
|
|
91
130
|
rulesReading,
|
|
131
|
+
rulesFloor,
|
|
132
|
+
rulesHistory,
|
|
92
133
|
countRoutedThisMonth,
|
|
93
134
|
};
|
|
@@ -123,7 +123,192 @@ function promisesReading(criteria) {
|
|
|
123
123
|
return { value: { kept, total }, figure: `${kept} of ${total} kept`, line: 'what the goals say they deliver', hot: false };
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// ── floors and the record (task 1004527) ─────────────────────────────────────
|
|
127
|
+
// Each is pure over rows the existing reads already return; the registration
|
|
128
|
+
// below gives each the atom of the route that serves those rows (GET /goals,
|
|
129
|
+
// GET /versions, GET /tasks and GET /public/recent-shipped are any builder's).
|
|
130
|
+
|
|
131
|
+
const MAX_LINES = 4;
|
|
132
|
+
const SHIPPED_LIMIT = 200;
|
|
133
|
+
const MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
|
134
|
+
function monthStart(now) { const d = new Date(now); return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1); }
|
|
135
|
+
function dayWord(at) { const d = new Date(at); return Number.isNaN(d.getTime()) ? '' : `${MONTHS[d.getUTCMonth()]} ${d.getUTCDate()}`; }
|
|
136
|
+
function plural(n, one, many) { return `${n} ${n === 1 ? one : many}`; }
|
|
137
|
+
function more(lines, total) { if (total > lines.length) lines.push([`and ${total - lines.length} more`, '']); return lines; }
|
|
138
|
+
|
|
139
|
+
// Planning · Goals: each open goal with its promises kept of total.
|
|
140
|
+
function goalsFloor(goals, criteria) {
|
|
141
|
+
const open = (goals || []).filter((g) => g.status === 'open');
|
|
142
|
+
const by = new Map();
|
|
143
|
+
for (const c of criteria || []) {
|
|
144
|
+
if (c.goal_id == null) continue;
|
|
145
|
+
const k = String(c.goal_id);
|
|
146
|
+
if (!by.has(k)) by.set(k, { kept: 0, total: 0 });
|
|
147
|
+
const e = by.get(k); e.total += 1; if (c.satisfied === true) e.kept += 1;
|
|
148
|
+
}
|
|
149
|
+
const lines = open.slice(0, MAX_LINES).map((g) => {
|
|
150
|
+
const e = by.get(String(g.id));
|
|
151
|
+
return [String(g.title), e ? `${e.kept} of ${e.total} kept` : 'no promises yet'];
|
|
152
|
+
});
|
|
153
|
+
return { count: String(open.length), line: open.length ? plural(open.length, 'goal open', 'goals open') : 'no goal open', lines: more(lines, open.length), hot: false };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Planning · Priorities: the building version's open goals in their worked order.
|
|
157
|
+
function prioritiesFloor(goals, versionId) {
|
|
158
|
+
const mine = (goals || []).filter((g) => g.status === 'open' && (!versionId || g.version_id === versionId));
|
|
159
|
+
if (!mine.length) return { count: '', line: 'no goal in the order yet', lines: [['No open goal is ordered in the version building now.', '']], hot: false };
|
|
160
|
+
const top = mine.slice(0, 3).map((g, i) => `${i + 1} ${g.title}`).join(' · ');
|
|
161
|
+
return { count: '', line: 'the order goals are worked in', lines: [[top, versionId ? `version ${versionId}` : ''], [`${plural(mine.length, 'goal is', 'goals are')} in the order.`, '']], hot: false };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Planning · Versions: what is building, and the last that shipped.
|
|
165
|
+
function versionsFloor(versions, goals) {
|
|
166
|
+
const all = versions || [];
|
|
167
|
+
const building = all.filter((v) => v.status === 'building');
|
|
168
|
+
const shipped = all.filter((v) => v.status === 'shipped' && v.shipped_at).sort((a, b) => Date.parse(b.shipped_at) - Date.parse(a.shipped_at));
|
|
169
|
+
const goalsIn = (id) => (goals || []).filter((g) => g.version_id === id && g.status !== 'archived').length;
|
|
170
|
+
const lines = building.slice(0, MAX_LINES).map((v) => [`${v.name || v.id} is building.`, plural(goalsIn(v.id), 'goal', 'goals')]);
|
|
171
|
+
if (shipped[0]) lines.push([`${shipped[0].name || shipped[0].id} shipped.`, dayWord(shipped[0].shipped_at)]);
|
|
172
|
+
const head = building[0];
|
|
173
|
+
return {
|
|
174
|
+
count: '',
|
|
175
|
+
line: head ? `${head.name || head.id} building · ${plural(goalsIn(head.id), 'goal', 'goals')} in it` : 'no version building',
|
|
176
|
+
lines: lines.length ? lines : [['No version is recorded yet.', '']],
|
|
177
|
+
hot: false,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Review · Signed off: promises a tester has marked kept, newest first.
|
|
182
|
+
function signedOffFloor(criteria, now) {
|
|
183
|
+
const done = (criteria || []).filter((c) => c.satisfied === true && c.checked_at).sort((a, b) => Date.parse(b.checked_at) - Date.parse(a.checked_at));
|
|
184
|
+
const month = done.filter((c) => Date.parse(c.checked_at) >= monthStart(now)).length;
|
|
185
|
+
return {
|
|
186
|
+
count: String(month),
|
|
187
|
+
line: done.length ? `${month} signed off this month` : 'nothing signed off yet',
|
|
188
|
+
lines: done.slice(0, MAX_LINES).map((c) => [String(c.criterion_md), dayWord(c.checked_at)]),
|
|
189
|
+
hot: false,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Release · Live this month: shipped work, newest first.
|
|
194
|
+
function liveMonthFloor(shipped, now) {
|
|
195
|
+
const month = (shipped || []).filter((t) => t.shipped_at && Date.parse(t.shipped_at) >= monthStart(now));
|
|
196
|
+
return {
|
|
197
|
+
count: String(month.length),
|
|
198
|
+
line: month.length ? `${plural(month.length, 'piece', 'pieces')} of work went live` : 'nothing went live yet this month',
|
|
199
|
+
lines: more(month.slice(0, MAX_LINES).map((t) => [String(t.title), dayWord(t.shipped_at)]), month.length),
|
|
200
|
+
hot: false,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Release · Held back: approved work that cannot go live until it catches up.
|
|
205
|
+
function heldBackFloor(confirmed) {
|
|
206
|
+
const held = (confirmed || []).filter((t) => t.needs_rebase === true);
|
|
207
|
+
return {
|
|
208
|
+
count: String(held.length),
|
|
209
|
+
line: held.length ? `${plural(held.length, 'piece', 'pieces')} held` : 'nothing held',
|
|
210
|
+
lines: more(held.slice(0, MAX_LINES).map((t) => [String(t.title), 'must catch up first']), held.length),
|
|
211
|
+
hot: false,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// People · Newcomers: newcomer tasks being built, and the ones waiting.
|
|
216
|
+
function newcomersFloor(active, ready) {
|
|
217
|
+
const building = (active || []).filter((t) => t.newcomer_friendly === true);
|
|
218
|
+
const waiting = (ready || []).filter((t) => t.newcomer_friendly === true);
|
|
219
|
+
return {
|
|
220
|
+
count: String(building.length),
|
|
221
|
+
line: `${plural(building.length, 'newcomer task', 'newcomer tasks')} being built`,
|
|
222
|
+
lines: [...building.slice(0, MAX_LINES - 1).map((t) => [String(t.title), 'being built']), [`${plural(waiting.length, 'newcomer task is', 'newcomer tasks are')} ready to claim.`, '']],
|
|
223
|
+
hot: false,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// The record: sign-offs, releases, goals achieved. Pure.
|
|
228
|
+
function signoffHistory(criteria) {
|
|
229
|
+
return (criteria || []).filter((c) => c.satisfied === true && c.checked_at)
|
|
230
|
+
.map((c) => ({ id: c.id, title: String(c.criterion_md), at: c.checked_at, outcome: 'signed off' }));
|
|
231
|
+
}
|
|
232
|
+
function releaseHistory(shipped) {
|
|
233
|
+
return (shipped || []).filter((t) => t.shipped_at).map((t) => ({ id: t.id, title: String(t.title), at: t.shipped_at, outcome: 'live' }));
|
|
234
|
+
}
|
|
235
|
+
function goalHistory(goals) {
|
|
236
|
+
return (goals || []).filter((g) => g.status === 'achieved' && g.updated_at)
|
|
237
|
+
.map((g) => ({ id: g.id, title: `${g.title} achieved`, at: g.updated_at, outcome: 'all promises kept' }));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// The criteria of the versions the open goals live in — one read per version,
|
|
241
|
+
// bounded, through the same listCriteria the version page serves.
|
|
242
|
+
async function criteriaFor(versionIds) {
|
|
243
|
+
const ids = [...new Set(versionIds.filter(Boolean))].slice(0, 6);
|
|
244
|
+
return (await Promise.all(ids.map((v) => doneWhen.listCriteria(v)))).flat();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// One read per docket request: the collector hands every read a per-request
|
|
248
|
+
// `once(key, fn)` (docket.js buildDocket), so two floors that need the same rows
|
|
249
|
+
// share one query. Without it (a direct call) the read simply runs.
|
|
250
|
+
const shared = (once, key, fn) => (typeof once === 'function' ? once(key, fn) : fn());
|
|
251
|
+
const openGoals = (once) => shared(once, 'lifecycle.openGoals', () => db.listGoals({ status: 'open' }));
|
|
252
|
+
const confirmedTasks = (once) => shared(once, 'lifecycle.confirmedTasks', () => db.listTasks({ status: 'confirmed', limit: STUCK_LIMIT }));
|
|
253
|
+
|
|
254
|
+
function contributeLifecycleFloors() {
|
|
255
|
+
const any = { anyBuilder: true };
|
|
256
|
+
api.contribute(POINT, {
|
|
257
|
+
floor: 'planning.goals', gate: any,
|
|
258
|
+
read: async (_ctx, { once } = {}) => {
|
|
259
|
+
const goals = await openGoals(once);
|
|
260
|
+
return goalsFloor(goals, await criteriaFor(goals.map((g) => g.version_id)));
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
api.contribute(POINT, {
|
|
264
|
+
floor: 'planning.priorities', gate: any,
|
|
265
|
+
read: async (_ctx, { once } = {}) => prioritiesFloor(await openGoals(once), await db.currentBuildingVersionId()),
|
|
266
|
+
});
|
|
267
|
+
api.contribute(POINT, {
|
|
268
|
+
floor: 'planning.versions', gate: any,
|
|
269
|
+
read: async () => versionsFloor(await db.listVersions(), await db.listGoals({})),
|
|
270
|
+
});
|
|
271
|
+
api.contribute(POINT, {
|
|
272
|
+
floor: 'review.signed_off', gate: any,
|
|
273
|
+
read: async (_ctx, { now } = {}) => {
|
|
274
|
+
const v = await db.currentBuildingVersionId();
|
|
275
|
+
return signedOffFloor(v ? await doneWhen.listCriteria(v) : [], now);
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
api.contribute(POINT, {
|
|
279
|
+
floor: 'release.live_month', gate: any,
|
|
280
|
+
read: async (_ctx, { now } = {}) => liveMonthFloor(await db.recentShippedTasks(SHIPPED_LIMIT, { days: 31 }), now),
|
|
281
|
+
});
|
|
282
|
+
api.contribute(POINT, {
|
|
283
|
+
floor: 'release.held_back', gate: any,
|
|
284
|
+
read: async (_ctx, { once } = {}) => heldBackFloor(await confirmedTasks(once)),
|
|
285
|
+
});
|
|
286
|
+
api.contribute(POINT, {
|
|
287
|
+
floor: 'people.newcomers', gate: any,
|
|
288
|
+
read: async () => {
|
|
289
|
+
const [active, ready] = await Promise.all([db.listTasks({ status: 'active', limit: STUCK_LIMIT }), db.listTasks({ status: 'ready', limit: 200 })]);
|
|
290
|
+
return newcomersFloor(active, ready);
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
api.contribute(POINT, {
|
|
294
|
+
history: 'signoffs', building: 'review', gate: any,
|
|
295
|
+
read: async () => {
|
|
296
|
+
const v = await db.currentBuildingVersionId();
|
|
297
|
+
return signoffHistory(v ? await doneWhen.listCriteria(v) : []);
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
api.contribute(POINT, {
|
|
301
|
+
history: 'releases', building: 'release', gate: any,
|
|
302
|
+
read: async () => releaseHistory(await db.recentShippedTasks(60, { days: 120 })),
|
|
303
|
+
});
|
|
304
|
+
api.contribute(POINT, {
|
|
305
|
+
history: 'goals', building: 'planning', gate: any,
|
|
306
|
+
read: async () => goalHistory(await db.listGoals({ status: 'achieved' })),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
126
310
|
function contributeLifecycleToDocket() {
|
|
311
|
+
contributeLifecycleFloors();
|
|
127
312
|
api.contribute(POINT, {
|
|
128
313
|
source: 'override_requests',
|
|
129
314
|
building: 'release',
|
|
@@ -162,10 +347,10 @@ function contributeLifecycleToDocket() {
|
|
|
162
347
|
api.contribute(POINT, {
|
|
163
348
|
reading: 'flow',
|
|
164
349
|
gate: { anyBuilder: true },
|
|
165
|
-
read: async (_ctx, { now } = {}) => {
|
|
350
|
+
read: async (_ctx, { now, once } = {}) => {
|
|
166
351
|
const [completed, confirmed] = await Promise.all([
|
|
167
352
|
db.listTasks({ status: 'completed', limit: STUCK_LIMIT }),
|
|
168
|
-
|
|
353
|
+
confirmedTasks(once),
|
|
169
354
|
]);
|
|
170
355
|
return flowReading({ completed, confirmed }, now);
|
|
171
356
|
},
|
|
@@ -189,4 +374,14 @@ module.exports = {
|
|
|
189
374
|
joinRequestMatter,
|
|
190
375
|
flowReading,
|
|
191
376
|
promisesReading,
|
|
377
|
+
goalsFloor,
|
|
378
|
+
prioritiesFloor,
|
|
379
|
+
versionsFloor,
|
|
380
|
+
signedOffFloor,
|
|
381
|
+
liveMonthFloor,
|
|
382
|
+
heldBackFloor,
|
|
383
|
+
newcomersFloor,
|
|
384
|
+
signoffHistory,
|
|
385
|
+
releaseHistory,
|
|
386
|
+
goalHistory,
|
|
192
387
|
};
|
|
@@ -69,6 +69,21 @@ function contributeReportsToDocket() {
|
|
|
69
69
|
return lists.flat().map(redteamDocketMatter);
|
|
70
70
|
},
|
|
71
71
|
});
|
|
72
|
+
// The Disputes record (task 1004527): every report that was ruled on, dated by
|
|
73
|
+
// its ruling (confirmed or fixed). Same atom as the list route it reads.
|
|
74
|
+
api.contribute('governor.docket', {
|
|
75
|
+
history: 'rulings',
|
|
76
|
+
building: 'disputes',
|
|
77
|
+
gate: { permission: 'security.report.adjudicate' },
|
|
78
|
+
read: async () => rulingHistory((await Promise.all(['confirmed', 'fixed'].map((status) => security.listVulnerabilityReports({ scope: 'all', status, limit: 60 })))).flat()),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const RULING_WORDS = Object.freeze({ confirmed: 'confirmed', fixed: 'fixed', disputed: 'disputed', wontfix: 'will not fix' });
|
|
83
|
+
function rulingHistory(reports) {
|
|
84
|
+
return (reports || [])
|
|
85
|
+
.filter((r) => RULING_WORDS[r.status] && (r.fixed_at || r.confirmed_at))
|
|
86
|
+
.map((r) => ({ id: r.id, title: `Security report: ${r.title}`, at: r.fixed_at || r.confirmed_at, outcome: RULING_WORDS[r.status] }));
|
|
72
87
|
}
|
|
73
88
|
|
|
74
89
|
module.exports = function buildSecurityReportsRouter() {
|
|
@@ -302,3 +317,4 @@ module.exports = function buildSecurityReportsRouter() {
|
|
|
302
317
|
};
|
|
303
318
|
|
|
304
319
|
module.exports.redteamDocketMatter = redteamDocketMatter;
|
|
320
|
+
module.exports.rulingHistory = rulingHistory;
|
|
@@ -155,7 +155,7 @@ async function createVulnerabilityReport({
|
|
|
155
155
|
//
|
|
156
156
|
// `scope: 'own'` REQUIRES builderId; omitting it throws rather than silently
|
|
157
157
|
// leaking every report. Optional `status` narrows either scope.
|
|
158
|
-
async function listVulnerabilityReports({ scope = 'own', builderId = null, status = null } = {}) {
|
|
158
|
+
async function listVulnerabilityReports({ scope = 'own', builderId = null, status = null, limit = null } = {}) {
|
|
159
159
|
if (scope !== 'own' && scope !== 'all') {
|
|
160
160
|
throw { code: 'INVALID_SCOPE', scope, allowed: ['own', 'all'] };
|
|
161
161
|
}
|
|
@@ -176,7 +176,10 @@ async function listVulnerabilityReports({ scope = 'own', builderId = null, statu
|
|
|
176
176
|
`SELECT ${VULN_COLUMNS} FROM vulnerability_reports` +
|
|
177
177
|
(where.length ? ` WHERE ${where.join(' AND ')}` : '') +
|
|
178
178
|
` ORDER BY reported_at DESC, id DESC`;
|
|
179
|
-
|
|
179
|
+
// Bounded when the caller asks (the governor docket's record, task 1004527).
|
|
180
|
+
let tail = '';
|
|
181
|
+
if (Number.isInteger(limit) && limit > 0) { params.push(limit); tail = ` LIMIT $${params.length}`; }
|
|
182
|
+
const { rows } = await pool.query(sql + tail, params);
|
|
180
183
|
return rows;
|
|
181
184
|
}
|
|
182
185
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "GET /government/constitution as the street reads it for a drafted amendment (task 1004530): the board block and the who-decides-what table, resolved by modules/government/decision-rules.js resolveAll (Release already amended to a sitting; every other building as today). Regenerate from the resolver, never by hand.",
|
|
3
|
+
"board": {
|
|
4
|
+
"membership": "rank:metic+",
|
|
5
|
+
"pass_rule": "majority",
|
|
6
|
+
"window_minutes": 4320,
|
|
7
|
+
"close_early_on_full_turnout": true
|
|
8
|
+
},
|
|
9
|
+
"history": [],
|
|
10
|
+
"headcount": 6,
|
|
11
|
+
"decision_rules": {
|
|
12
|
+
"areas": [
|
|
13
|
+
{
|
|
14
|
+
"area": "charter",
|
|
15
|
+
"name": "Charter",
|
|
16
|
+
"decided_by": "board",
|
|
17
|
+
"rule": "majority",
|
|
18
|
+
"rule_words": "majority vote",
|
|
19
|
+
"who": "the board (metic and above (metic, archon))",
|
|
20
|
+
"as_today": true,
|
|
21
|
+
"sitting": false,
|
|
22
|
+
"gate": null,
|
|
23
|
+
"today": "the board, under the constitution in force",
|
|
24
|
+
"acts": [],
|
|
25
|
+
"set_by_item_id": null,
|
|
26
|
+
"updated_at": null
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"area": "rules",
|
|
30
|
+
"name": "Rules",
|
|
31
|
+
"decided_by": "gate",
|
|
32
|
+
"rule": "decide",
|
|
33
|
+
"rule_words": "decides alone",
|
|
34
|
+
"who": "whoever may change the project settings",
|
|
35
|
+
"as_today": true,
|
|
36
|
+
"sitting": false,
|
|
37
|
+
"gate": "project.settings.manage",
|
|
38
|
+
"today": "whoever may change the project settings",
|
|
39
|
+
"acts": [],
|
|
40
|
+
"set_by_item_id": null,
|
|
41
|
+
"updated_at": null
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"area": "disputes",
|
|
45
|
+
"name": "Disputes",
|
|
46
|
+
"decided_by": "gate",
|
|
47
|
+
"rule": "decide",
|
|
48
|
+
"rule_words": "decides alone",
|
|
49
|
+
"who": "whoever may judge reports",
|
|
50
|
+
"as_today": true,
|
|
51
|
+
"sitting": false,
|
|
52
|
+
"gate": "security.report.adjudicate",
|
|
53
|
+
"today": "whoever may judge reports",
|
|
54
|
+
"acts": [],
|
|
55
|
+
"set_by_item_id": null,
|
|
56
|
+
"updated_at": null
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"area": "people",
|
|
60
|
+
"name": "People",
|
|
61
|
+
"decided_by": "gate",
|
|
62
|
+
"rule": "decide",
|
|
63
|
+
"rule_words": "decides alone",
|
|
64
|
+
"who": "whoever may manage ranks",
|
|
65
|
+
"as_today": true,
|
|
66
|
+
"sitting": false,
|
|
67
|
+
"gate": "government.manage",
|
|
68
|
+
"today": "whoever may manage ranks",
|
|
69
|
+
"acts": [
|
|
70
|
+
"POST /government/assignments",
|
|
71
|
+
"DELETE /government/assignments/:builderId/:rankKey"
|
|
72
|
+
],
|
|
73
|
+
"set_by_item_id": null,
|
|
74
|
+
"updated_at": null
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"area": "planning",
|
|
78
|
+
"name": "Planning",
|
|
79
|
+
"decided_by": "gate",
|
|
80
|
+
"rule": "decide",
|
|
81
|
+
"rule_words": "decides alone",
|
|
82
|
+
"who": "whoever may create goals",
|
|
83
|
+
"as_today": true,
|
|
84
|
+
"sitting": false,
|
|
85
|
+
"gate": "goal.create",
|
|
86
|
+
"today": "whoever may create goals",
|
|
87
|
+
"acts": [],
|
|
88
|
+
"set_by_item_id": null,
|
|
89
|
+
"updated_at": null
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"area": "automation",
|
|
93
|
+
"name": "Automation",
|
|
94
|
+
"decided_by": "gate",
|
|
95
|
+
"rule": "decide",
|
|
96
|
+
"rule_words": "decides alone",
|
|
97
|
+
"who": "whoever may switch unattended building on and off",
|
|
98
|
+
"as_today": true,
|
|
99
|
+
"sitting": false,
|
|
100
|
+
"gate": "autonomy.fence.manage",
|
|
101
|
+
"today": "whoever may switch unattended building on and off",
|
|
102
|
+
"acts": [],
|
|
103
|
+
"set_by_item_id": null,
|
|
104
|
+
"updated_at": null
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"area": "review",
|
|
108
|
+
"name": "Review",
|
|
109
|
+
"decided_by": "gate",
|
|
110
|
+
"rule": "decide",
|
|
111
|
+
"rule_words": "decides alone",
|
|
112
|
+
"who": "whoever may mark work as meeting its checks",
|
|
113
|
+
"as_today": true,
|
|
114
|
+
"sitting": false,
|
|
115
|
+
"gate": "criterion.satisfy",
|
|
116
|
+
"today": "whoever may mark work as meeting its checks",
|
|
117
|
+
"acts": [],
|
|
118
|
+
"set_by_item_id": null,
|
|
119
|
+
"updated_at": null
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"area": "release",
|
|
123
|
+
"name": "Release",
|
|
124
|
+
"decided_by": "rank:archon",
|
|
125
|
+
"rule": "unanimous",
|
|
126
|
+
"rule_words": "everyone agrees",
|
|
127
|
+
"who": "exactly archon",
|
|
128
|
+
"as_today": false,
|
|
129
|
+
"sitting": true,
|
|
130
|
+
"gate": "core.pin.move",
|
|
131
|
+
"today": "whoever may put a release live",
|
|
132
|
+
"acts": [],
|
|
133
|
+
"set_by_item_id": "3",
|
|
134
|
+
"updated_at": null
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"area": "treasury",
|
|
138
|
+
"name": "Treasury",
|
|
139
|
+
"decided_by": "gate",
|
|
140
|
+
"rule": "decide",
|
|
141
|
+
"rule_words": "decides alone",
|
|
142
|
+
"who": "whoever may set spending limits",
|
|
143
|
+
"as_today": true,
|
|
144
|
+
"sitting": false,
|
|
145
|
+
"gate": "builder.budget.set",
|
|
146
|
+
"today": "whoever may set spending limits",
|
|
147
|
+
"acts": [],
|
|
148
|
+
"set_by_item_id": null,
|
|
149
|
+
"updated_at": null
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"rules": [
|
|
153
|
+
{
|
|
154
|
+
"key": "decide",
|
|
155
|
+
"words": "decides alone"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"key": "first",
|
|
159
|
+
"words": "first yes wins"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"key": "consent",
|
|
163
|
+
"words": "passes unless someone objects"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"key": "majority",
|
|
167
|
+
"words": "majority vote"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"key": "unanimous",
|
|
171
|
+
"words": "everyone agrees"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"key": "signoff",
|
|
175
|
+
"words": "signed off by someone who did not build it"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"key": "vacant",
|
|
179
|
+
"words": "no one holds this yet"
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"changed_by": "a passed amendment only — POST /government/board/decision-rules files one"
|
|
183
|
+
}
|
|
184
|
+
}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.20",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.20",
|
|
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.21.
|
|
3
|
+
"version": "1.21.20",
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -8864,5 +8864,17 @@
|
|
|
8864
8864
|
"id": "1004555",
|
|
8865
8865
|
"text": "Project setup no longer shows an \"Allow repo access\" button — if the site needs GitHub access it fetches it in a quick reload and brings you straight back to where you were."
|
|
8866
8866
|
}
|
|
8867
|
+
],
|
|
8868
|
+
"1.21.19": [
|
|
8869
|
+
{
|
|
8870
|
+
"id": "1004530",
|
|
8871
|
+
"text": "Inside any building on the street, \"change how this works\" now drafts a real amendment. It shows how the building decides today crossed out, and the proposed change written in. You pick who decides and how from the project's o"
|
|
8872
|
+
}
|
|
8873
|
+
],
|
|
8874
|
+
"1.21.20": [
|
|
8875
|
+
{
|
|
8876
|
+
"id": "1004527",
|
|
8877
|
+
"text": "Inside every Governor City building, the floors now show real numbers instead of \"not recorded yet\". The Charter reads each section from the rules in force. People shows who sits on the council, the testers, the ranks and newc"
|
|
8878
|
+
}
|
|
8867
8879
|
]
|
|
8868
8880
|
}
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.21.
|
|
78
|
+
const CORE_VERSION = '1.21.20'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|