@bongos/core 1.19.656 → 1.19.658
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 +43 -38
- package/docs/adr/0182-project-visibility-two-axes.md +9 -1
- package/docs/copy-inventory.md +56 -56
- package/docs/copy-registry.json +57 -57
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/board-lib.js +165 -0
- package/modules/hall-ui/public/board-room.css +5 -16
- package/modules/hall-ui/public/board-room.html +3 -0
- package/modules/hall-ui/public/board-room.js +26 -86
- package/modules/hall-ui/public/government.css +10 -8
- package/modules/hall-ui/public/government.html +3 -0
- package/modules/hall-ui/public/government.js +31 -85
- package/modules/hall-ui/public/oversight.css +31 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/fitness-checks-vocabulary.js +75 -0
- package/scripts/gds/fitness.js +3 -3
- package/src/bongos/platform-visibility-gate.js +7 -4
- package/src/bongos/{stealth-gate.js → prelaunch-gate.js} +71 -12
- package/src/bongos/serve-internal.js +4 -4
- package/src/module-api.js +1 -1
- package/tests/government_board_room_copy.mjs +30 -14
- package/tests/government_constitution_view.mjs +7 -2
- package/tests/{stealth_gate.mjs → prelaunch_gate.mjs} +139 -10
- package/tests/rank_tier_single_source.mjs +28 -16
|
@@ -9,25 +9,14 @@
|
|
|
9
9
|
rows are the oversight family's (oversight.css); the ?v= cache-bust is
|
|
10
10
|
stamped from this file's content hash at boot (src/asset-version.js).
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
stylesheet named for a different room in order to inherit them, which is what
|
|
18
|
-
the first cut of this carve did and what the review called out. The NAMES stay
|
|
12
|
+
`.gov-grid-note` AND `.gov-held-note` LIVE IN oversight.css (task 1003761).
|
|
13
|
+
They were duplicated here under a note calling the copy the cheaper of two bad
|
|
14
|
+
options, the alternative being this page loading a stylesheet named for a
|
|
15
|
+
different room. oversight.css is named for neither room and is already loaded
|
|
16
|
+
by both pages, which is the option that note did not weigh. The NAMES stay
|
|
19
17
|
`gov-*` because board-room.js emits them and
|
|
20
18
|
tests/government_board_room_copy.mjs reads them as literal markup. */
|
|
21
19
|
|
|
22
|
-
/* The framing line under a heading — the family voice, duplicated per the header
|
|
23
|
-
note above rather than inherited across pages. */
|
|
24
|
-
.gov-grid-note { margin: 0 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); max-width: var(--measure); }
|
|
25
|
-
.gov-grid-note strong { color: var(--ink); font-weight: 500; }
|
|
26
|
-
.gov-grid-note code { font-family: var(--font-mono); font-size: 12.5px; color: var(--ink); overflow-wrap: anywhere; }
|
|
27
|
-
|
|
28
|
-
/* "your vote" beside the reader's own row in the open ballot. */
|
|
29
|
-
.gov-held-note { font-size: 12.5px; color: var(--ok); white-space: nowrap; }
|
|
30
|
-
|
|
31
20
|
/* ---- the Board Room: one sitting, one panel -------------------------------------
|
|
32
21
|
The card order is task 1003270's (title, ballot, the idea, the apparatus); the
|
|
33
22
|
surface is the shell's .scroll (task 1003469). The subject of the vote is the
|
|
@@ -65,6 +65,9 @@
|
|
|
65
65
|
<script src="/builders/palette.js" defer></script>
|
|
66
66
|
<script src="/builders/dom-utils.js?v=2026-06-16-board"></script>
|
|
67
67
|
<script src="/builders/hall-kit.js"></script>
|
|
68
|
+
<!-- task 1003761: the vocabulary this page and /government both speak. Must load
|
|
69
|
+
BEFORE board-room.js, which destructures window.OTBBoardLib at IIFE entry. -->
|
|
70
|
+
<script src="/builders/board-lib.js?v=2026-09-10-boardlib1"></script>
|
|
68
71
|
<script src="/builders/board-room.js?v=2026-09-09-board-carve"></script>
|
|
69
72
|
</body>
|
|
70
73
|
</html>
|
|
@@ -35,14 +35,23 @@
|
|
|
35
35
|
// reason and names one section. That requirement is why voting lives in the
|
|
36
36
|
// hall and not on a chat reaction.
|
|
37
37
|
//
|
|
38
|
-
// ON THE
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// government
|
|
45
|
-
//
|
|
38
|
+
// ON THE SHARED HELPERS (task 1003761). The vocabulary this page and the
|
|
39
|
+
// Constitution panel on /government BOTH speak now lives in board-lib.js, loaded
|
|
40
|
+
// before this script as window.OTBBoardLib: the ladder and `membershipRankKeys`
|
|
41
|
+
// (which mirror the SERVER's predicate grammar), `rankNames`, `fmtWindow`, the
|
|
42
|
+
// two pass-rule maps, and the `pill` / `groupHead` / `when` row pieces. The carve
|
|
43
|
+
// that split the two pages copied all of it, which is the two-copies state
|
|
44
|
+
// modules/government/CLAUDE.md forbids — "the grammar is written ONCE".
|
|
45
|
+
// tests/government_constitution_view.mjs holds the lib's parser equal to the
|
|
46
|
+
// server's. (This header used to name tests/government_board_vote_ui.mjs for
|
|
47
|
+
// that; it never asserted the parser, so the copy that lived here was pinned by
|
|
48
|
+
// nothing.)
|
|
49
|
+
//
|
|
50
|
+
// `fetchJson` / `sendJson` stay per-page: a hall page script is a self-contained
|
|
51
|
+
// IIFE over the DOM, and those two carry this page's own error copy.
|
|
52
|
+
//
|
|
53
|
+
// SECTION_LABELS and fmtRemaining stay HERE, not in the lib — this page is their
|
|
54
|
+
// only reader, and the lib is for what the two pages share.
|
|
46
55
|
//
|
|
47
56
|
// The function ORDER here is deliberate and matches the pre-carve source:
|
|
48
57
|
// tests/government_board_room_copy.mjs slices this file by function-name
|
|
@@ -57,8 +66,13 @@
|
|
|
57
66
|
// /lib/bongos-client.js. baseUrl:'' passes the full `${API}${path}` URLs below
|
|
58
67
|
// through unchanged; result-mode ({ status, ok, data }) preserves control flow 1:1.
|
|
59
68
|
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
60
|
-
const { escapeHtml
|
|
69
|
+
const { escapeHtml } = window.OTB;
|
|
61
70
|
const { emptyStateHtml } = window.OTBKit;
|
|
71
|
+
// The vocabulary /government and this page share (task 1003761). board-lib.js
|
|
72
|
+
// is loaded before this script; see the header note. The ladder and fmtInt went
|
|
73
|
+
// with it — groupHead was this page's only fmtInt reader.
|
|
74
|
+
const { membershipRankKeys, rankNames, fmtWindow,
|
|
75
|
+
PASS_RULE_PLAIN, pill, groupHead, when } = window.OTBBoardLib;
|
|
62
76
|
const toast = (msg) => window.OTB.toast(msg);
|
|
63
77
|
|
|
64
78
|
// ---- fetch helpers (mirror gate.js) --------------------------------------
|
|
@@ -98,52 +112,6 @@
|
|
|
98
112
|
};
|
|
99
113
|
const friendlyError = (code) => ERROR_COPY[code] || null;
|
|
100
114
|
|
|
101
|
-
// ---- shared row pieces ---------------------------------------------------
|
|
102
|
-
|
|
103
|
-
// A state pill in the shell's vocabulary. `mod` is one of the .fact-pill
|
|
104
|
-
// states (ok / warn / danger / info / quiet); the label is what the reader sees.
|
|
105
|
-
const pill = (mod, label) => `<span class="fact-pill fact-pill--${mod}">${escapeHtml(label)}</span>`;
|
|
106
|
-
const groupHead = (name, n, note) =>
|
|
107
|
-
`<div class="ov-group__head"><span class="ov-group__name">${escapeHtml(name)}</span>` +
|
|
108
|
-
(n === undefined ? '' : `<span class="ov-group__n">${fmtInt(n)}</span>`) +
|
|
109
|
-
(note ? `<span class="ov-group__note">${escapeHtml(note)}</span>` : '') +
|
|
110
|
-
'</div>';
|
|
111
|
-
// A timestamp the way the rows have always shown one: "2026-08-30 14:05".
|
|
112
|
-
const when = (iso) => escapeHtml(String(iso || '').slice(0, 16).replace('T', ' '));
|
|
113
|
-
|
|
114
|
-
// The predicate grammar, in plain language (task 1003094). This MIRRORS
|
|
115
|
-
// modules/government/membership-predicate.js — browser code cannot import the
|
|
116
|
-
// server module, the established pattern here (the five section labels do the
|
|
117
|
-
// same) — and tests/government_board_vote_ui.mjs holds the two equal.
|
|
118
|
-
//
|
|
119
|
-
// Rendering the raw predicate was fine while the only form was `rank:<key>`.
|
|
120
|
-
// It is not fine now: `rank:metic+` would read as "builders holding the metic+
|
|
121
|
-
// rank", which names a rank that does not exist and hides the very thing the
|
|
122
|
-
// suffix says.
|
|
123
|
-
const STANDARD_LADDER = ['xenos', 'thetes', 'metic', 'archon'];
|
|
124
|
-
|
|
125
|
-
function membershipRankKeys(predicate) {
|
|
126
|
-
const m = String(predicate || '');
|
|
127
|
-
if (!m.startsWith('rank:')) return null;
|
|
128
|
-
const body = m.slice(5);
|
|
129
|
-
if (!body) return null;
|
|
130
|
-
const keys = [];
|
|
131
|
-
for (const element of body.split(',')) {
|
|
132
|
-
const match = /^([a-z][a-z0-9_-]*)(\+?)$/.exec(element);
|
|
133
|
-
if (!match) return null;
|
|
134
|
-
let expanded;
|
|
135
|
-
if (match[2] === '+') {
|
|
136
|
-
const i = STANDARD_LADDER.indexOf(match[1]);
|
|
137
|
-
if (i === -1) return null; // "and above" is undefined off the ladder
|
|
138
|
-
expanded = STANDARD_LADDER.slice(i);
|
|
139
|
-
} else {
|
|
140
|
-
expanded = [match[1]];
|
|
141
|
-
}
|
|
142
|
-
for (const k of expanded) if (keys.indexOf(k) === -1) keys.push(k);
|
|
143
|
-
}
|
|
144
|
-
return keys.length ? keys : null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
115
|
// ---- the Board Room panel (R16) --------------------------------------------
|
|
148
116
|
|
|
149
117
|
// The room renders what the server assembled: open items with the database's
|
|
@@ -257,37 +225,9 @@
|
|
|
257
225
|
//
|
|
258
226
|
// Read from the item's SNAPSHOT constitution (the rules in force when the
|
|
259
227
|
// window opened), never today's config — the same rules the server tallies
|
|
260
|
-
// under.
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
// actionable fact and hiding it would help nobody.
|
|
264
|
-
function rankNames(keys) {
|
|
265
|
-
const labels = keys.map((k) => escapeHtml(window.OTB.rankLabel(k)));
|
|
266
|
-
if (labels.length === 1) return labels[0];
|
|
267
|
-
return `${labels.slice(0, -1).join(', ')} or ${labels[labels.length - 1]}`;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// A sitting length in the unit a person would say it in. The config stores
|
|
271
|
-
// minutes; "2880 minutes" is a number nobody converts in their head.
|
|
272
|
-
//
|
|
273
|
-
// DUPLICATED, KNOWINGLY: government.js keeps its own copy for the Constitution
|
|
274
|
-
// panel's windowSentence. Two readers, two files, one wording — if you change
|
|
275
|
-
// the phrasing here, change it there.
|
|
276
|
-
function fmtWindow(minutes) {
|
|
277
|
-
const m = Number(minutes);
|
|
278
|
-
if (!Number.isFinite(m) || m <= 0) return `${escapeHtml(String(minutes))} minutes`;
|
|
279
|
-
const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
280
|
-
if (m % 1440 === 0) return plural(m / 1440, 'day');
|
|
281
|
-
if (m % 60 === 0) return plural(m / 60, 'hour');
|
|
282
|
-
return plural(m, 'minute');
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const PASS_RULE_PLAIN = {
|
|
286
|
-
first_ratifier: 'The first vote decides it: a yes ratifies, an objection returns it to its author.',
|
|
287
|
-
consent: 'It passes unless someone objects — silence is agreement, and one reasoned objection returns it.',
|
|
288
|
-
majority: 'It passes only when more than half the board votes yes. Silence is not agreement here.',
|
|
289
|
-
unanimous: 'Everyone who votes must vote yes, and at least one member must. One objection sends it back for revision.',
|
|
290
|
-
};
|
|
228
|
+
// under. `rankNames`, `fmtWindow` and `PASS_RULE_PLAIN` come from board-lib.js
|
|
229
|
+
// (task 1003761) — the Constitution panel on /government says the same three
|
|
230
|
+
// things and used to say them from its own copies.
|
|
291
231
|
|
|
292
232
|
function decidedByHtml(item) {
|
|
293
233
|
const c = item.constitution || {};
|
|
@@ -23,11 +23,9 @@
|
|
|
23
23
|
color: var(--ink);
|
|
24
24
|
text-shadow: var(--type-halo);
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
font-family: var(--font-mono); font-size: 12.5px; color: var(--ink); overflow-wrap: anywhere;
|
|
30
|
-
}
|
|
26
|
+
/* `.gov-grid-note` (and the `.ov-*` code rule it shared a declaration with) moved
|
|
27
|
+
to oversight.css in task 1003761 — /board-room renders it too and had a copy.
|
|
28
|
+
`.gov-h` stays: this page is its only renderer. */
|
|
31
29
|
|
|
32
30
|
/* Holds the ladder-rank reset button. Empty for every other rank, so it collapses
|
|
33
31
|
rather than reserving a gap — hence no margin on the empty box itself. */
|
|
@@ -57,9 +55,13 @@
|
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
/* ---- the Board Room: MOVED OUT to board-room.css (ADR 0266) ----------------
|
|
60
|
-
The sitting card
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
The sitting card and the ballot controls went with the room when it became its
|
|
59
|
+
own page, and nothing here renders them.
|
|
60
|
+
CORRECTION (task 1003761): `.gov-held-note` went with them under the same
|
|
61
|
+
"nothing on THIS page renders them" reasoning, and that was wrong —
|
|
62
|
+
renderRankList marks a held Archon rank with one, so it rendered unstyled here
|
|
63
|
+
from the carve until this task. It lives in oversight.css now, which both
|
|
64
|
+
pages load. */
|
|
63
65
|
|
|
64
66
|
/* ---- the charter library ------------------------------------------------------- */
|
|
65
67
|
/* The configuration line under a charter: what an owner would type, in the
|
|
@@ -139,6 +139,9 @@
|
|
|
139
139
|
<script src="/builders/palette.js" defer></script>
|
|
140
140
|
<script src="/builders/dom-utils.js?v=2026-06-16-gate"></script>
|
|
141
141
|
<script src="/builders/hall-kit.js"></script>
|
|
142
|
+
<!-- task 1003761: the vocabulary this page and /board-room both speak. Must load
|
|
143
|
+
BEFORE government.js, which destructures window.OTBBoardLib at IIFE entry. -->
|
|
144
|
+
<script src="/builders/board-lib.js?v=2026-09-10-boardlib1"></script>
|
|
142
145
|
<script src="/builders/government.js?v=2026-09-02-gov-rows"></script>
|
|
143
146
|
</body>
|
|
144
147
|
</html>
|
|
@@ -79,6 +79,10 @@
|
|
|
79
79
|
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
80
80
|
const { escapeHtml, fmtInt } = window.OTB;
|
|
81
81
|
const { emptyStateHtml } = window.OTBKit;
|
|
82
|
+
// The vocabulary this page and the Board Room share (task 1003761).
|
|
83
|
+
// board-lib.js is loaded before this script.
|
|
84
|
+
const { STANDARD_LADDER, membershipRankKeys, rankNames, fmtWindow,
|
|
85
|
+
PASS_RULE_NAME, pill, groupHead, when } = window.OTBBoardLib;
|
|
82
86
|
const toast = (msg) => window.OTB.toast(msg);
|
|
83
87
|
|
|
84
88
|
// ---- fetch helpers (mirror gate.js) --------------------------------------
|
|
@@ -137,19 +141,6 @@
|
|
|
137
141
|
|
|
138
142
|
const selectedRank = () => ranks.find((r) => r.rank_key === selectedRankKey) || null;
|
|
139
143
|
|
|
140
|
-
// ---- shared row pieces ---------------------------------------------------
|
|
141
|
-
|
|
142
|
-
// A state pill in the shell's vocabulary. `mod` is one of the .fact-pill
|
|
143
|
-
// states (ok / warn / danger / info / quiet); the label is what the reader sees.
|
|
144
|
-
const pill = (mod, label) => `<span class="fact-pill fact-pill--${mod}">${escapeHtml(label)}</span>`;
|
|
145
|
-
const groupHead = (name, n, note) =>
|
|
146
|
-
`<div class="ov-group__head"><span class="ov-group__name">${escapeHtml(name)}</span>` +
|
|
147
|
-
(n === undefined ? '' : `<span class="ov-group__n">${fmtInt(n)}</span>`) +
|
|
148
|
-
(note ? `<span class="ov-group__note">${escapeHtml(note)}</span>` : '') +
|
|
149
|
-
'</div>';
|
|
150
|
-
// A timestamp the way the rows have always shown one: "2026-08-30 14:05".
|
|
151
|
-
const when = (iso) => escapeHtml(String(iso || '').slice(0, 16).replace('T', ' '));
|
|
152
|
-
|
|
153
144
|
// ---- render: rank list ---------------------------------------------------
|
|
154
145
|
|
|
155
146
|
// One rank, one selectable oversight row: the label, the DESCRIPTION the
|
|
@@ -539,39 +530,12 @@
|
|
|
539
530
|
// switch can never be flipped quietly, and every change is auditable against
|
|
540
531
|
// the constitution of its day.
|
|
541
532
|
|
|
542
|
-
// The predicate grammar
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
//
|
|
548
|
-
// It is not fine now: `rank:metic+` would read as "builders holding the metic+
|
|
549
|
-
// rank", which names a rank that does not exist and hides the very thing the
|
|
550
|
-
// suffix says. A constitution panel that misdescribes who governs is the
|
|
551
|
-
// failure this whole task exists to fix, one layer up.
|
|
552
|
-
const STANDARD_LADDER = ['xenos', 'thetes', 'metic', 'archon'];
|
|
553
|
-
|
|
554
|
-
function membershipRankKeys(predicate) {
|
|
555
|
-
const m = String(predicate || '');
|
|
556
|
-
if (!m.startsWith('rank:')) return null;
|
|
557
|
-
const body = m.slice(5);
|
|
558
|
-
if (!body) return null;
|
|
559
|
-
const keys = [];
|
|
560
|
-
for (const element of body.split(',')) {
|
|
561
|
-
const match = /^([a-z][a-z0-9_-]*)(\+?)$/.exec(element);
|
|
562
|
-
if (!match) return null;
|
|
563
|
-
let expanded;
|
|
564
|
-
if (match[2] === '+') {
|
|
565
|
-
const i = STANDARD_LADDER.indexOf(match[1]);
|
|
566
|
-
if (i === -1) return null; // "and above" is undefined off the ladder
|
|
567
|
-
expanded = STANDARD_LADDER.slice(i);
|
|
568
|
-
} else {
|
|
569
|
-
expanded = [match[1]];
|
|
570
|
-
}
|
|
571
|
-
for (const k of expanded) if (keys.indexOf(k) === -1) keys.push(k);
|
|
572
|
-
}
|
|
573
|
-
return keys.length ? keys : null;
|
|
574
|
-
}
|
|
533
|
+
// The predicate grammar and the ladder it expands against come from
|
|
534
|
+
// board-lib.js (task 1003761). They MIRROR modules/government/
|
|
535
|
+
// membership-predicate.js — browser code cannot import the server module —
|
|
536
|
+
// and the carve that took the Board Room onto its own page had left a second
|
|
537
|
+
// copy here. A constitution panel that misdescribes who governs is the failure
|
|
538
|
+
// this whole area exists to fix, and two copies is how it gets there.
|
|
575
539
|
|
|
576
540
|
function membershipSentence(board) {
|
|
577
541
|
const raw = String(board.membership || '');
|
|
@@ -682,18 +646,10 @@
|
|
|
682
646
|
close_early_on_full_turnout: 'Closing early on full turnout',
|
|
683
647
|
};
|
|
684
648
|
|
|
685
|
-
//
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
//
|
|
689
|
-
// landed a new reader on the Constitution side while the room was moving out.
|
|
690
|
-
// Its only caller is fieldValueHtml; membershipSentence above spells its own
|
|
691
|
-
// labels inline, so do not route that through here expecting one wording.
|
|
692
|
-
function rankNames(keys) {
|
|
693
|
-
const labels = keys.map((k) => escapeHtml(window.OTB.rankLabel(k)));
|
|
694
|
-
if (labels.length === 1) return labels[0];
|
|
695
|
-
return `${labels.slice(0, -1).join(', ')} or ${labels[labels.length - 1]}`;
|
|
696
|
-
}
|
|
649
|
+
// `rankNames` comes from board-lib.js (task 1003761). Its only caller here is
|
|
650
|
+
// fieldValueHtml; membershipSentence above spells its own labels inline because
|
|
651
|
+
// it wraps each in <strong>, so do not route that through it expecting one
|
|
652
|
+
// wording.
|
|
697
653
|
|
|
698
654
|
// One constitution VALUE, in the words a person would use — the same rule the
|
|
699
655
|
// dials keep, applied to the two halves of a divergence. Without this the
|
|
@@ -716,32 +672,11 @@
|
|
|
716
672
|
|
|
717
673
|
// One dial, one row: the dial's name as the title, the live sentence as the
|
|
718
674
|
// sub line. The sentence is trusted HTML from the helpers above.
|
|
719
|
-
// A sitting length in the unit a person would say it in. The config stores
|
|
720
|
-
// minutes; "2880 minutes" is a number nobody converts in their head.
|
|
721
675
|
//
|
|
722
|
-
//
|
|
723
|
-
//
|
|
724
|
-
//
|
|
725
|
-
//
|
|
726
|
-
function fmtWindow(minutes) {
|
|
727
|
-
const m = Number(minutes);
|
|
728
|
-
if (!Number.isFinite(m) || m <= 0) return `${escapeHtml(String(minutes))} minutes`;
|
|
729
|
-
const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
730
|
-
if (m % 1440 === 0) return plural(m / 1440, 'day');
|
|
731
|
-
if (m % 60 === 0) return plural(m / 60, 'hour');
|
|
732
|
-
return plural(m, 'minute');
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// The short name of a pass rule, for a place a whole sentence will not fit —
|
|
736
|
-
// the change-history line below. Also carved-region residue (ADR 0266): the
|
|
737
|
-
// sentence-length twin PASS_RULE_PLAIN went to board-room.js with the card,
|
|
738
|
-
// this one stayed with its only reader, historyRow.
|
|
739
|
-
const PASS_RULE_NAME = {
|
|
740
|
-
first_ratifier: 'by first ratifier',
|
|
741
|
-
consent: 'by consent',
|
|
742
|
-
majority: 'by majority',
|
|
743
|
-
unanimous: 'by unanimous agreement',
|
|
744
|
-
};
|
|
676
|
+
// `fmtWindow` and `PASS_RULE_NAME` come from board-lib.js (task 1003761).
|
|
677
|
+
// fmtWindow had a twin in board-room.js under a "keep the phrasing in step by
|
|
678
|
+
// hand" note; PASS_RULE_NAME sits beside its sentence-length twin
|
|
679
|
+
// PASS_RULE_PLAIN there, so the two renderings of one pass rule cannot drift.
|
|
745
680
|
|
|
746
681
|
function dialRow(name, sentenceHtml) {
|
|
747
682
|
return `<li class="ov-row"><div class="ov-row__main"><span class="ov-row__title">${escapeHtml(name)}</span><p class="ov-row__sub">${sentenceHtml}</p></div></li>`;
|
|
@@ -794,6 +729,14 @@
|
|
|
794
729
|
// `predicate: null` means the predicate is the owner's to choose (Foundation
|
|
795
730
|
// names their own custom rank), which is honest rather than unchecked — the
|
|
796
731
|
// test asserts such a charter says so in its `how`.
|
|
732
|
+
//
|
|
733
|
+
// A SECOND test pins these too, for a different reason. The `rank:xenos+` row
|
|
734
|
+
// spells the full ladder out in `seatsRanks`, and that literal is why this file
|
|
735
|
+
// stays in tests/rank_tier_single_source.mjs's STATIC_LADDER_FILES even though
|
|
736
|
+
// task 1003761 moved the page's real ladder to board-lib.js. It is documentation
|
|
737
|
+
// of the grammar rather than a second implementation of it — but a worked
|
|
738
|
+
// example that drifts teaches a ladder nobody has, so it is held to the
|
|
739
|
+
// canonical order.
|
|
797
740
|
const CHARTERS = [
|
|
798
741
|
{ key: 'monarchy', name: 'Monarchy / BDFL', selectable: true,
|
|
799
742
|
predicate: 'rank:archon', seatsRanks: ['archon'],
|
|
@@ -919,8 +862,11 @@
|
|
|
919
862
|
// What stayed behind, and why, so neither reads as a leftover:
|
|
920
863
|
// • fmtWindow — windowSentence (the Constitution's clock dial) reads it.
|
|
921
864
|
// • PASS_RULE_NAME — historyRow (the amendment history line) reads it.
|
|
922
|
-
// Both are
|
|
923
|
-
//
|
|
865
|
+
// Both READERS are still here; the two helpers themselves are not. Task 1003761
|
|
866
|
+
// hoisted them into board-lib.js, which this page destructures at IIFE entry.
|
|
867
|
+
// This block used to end "both are duplicated in board-room.js rather than
|
|
868
|
+
// shared; a hall page script is a self-contained IIFE, which is the convention
|
|
869
|
+
// here" — that was the justification for the copies, and it is retired.
|
|
924
870
|
//
|
|
925
871
|
// The old deep link is redirected below in init() — a fragment never reaches
|
|
926
872
|
// the server, so #board-room could not be caught by a route.
|
|
@@ -178,6 +178,37 @@ a.fact-chip:hover { color: var(--accent-ink); border-color: var(--accent-ink); t
|
|
|
178
178
|
.ov-danger { color: var(--danger); }
|
|
179
179
|
.ov-ok { color: var(--ok); }
|
|
180
180
|
|
|
181
|
+
/* ---- the government family's two shared lines (task 1003761) -----------------
|
|
182
|
+
Both are rendered by /government AND /board-room, so they belong to the family
|
|
183
|
+
sheet both pages load rather than to either page's own. ADR 0266's carve had
|
|
184
|
+
put them elsewhere, and each half of that was wrong in its own way:
|
|
185
|
+
|
|
186
|
+
`.gov-grid-note` was COPIED into board-room.css under a header note calling
|
|
187
|
+
the duplication the cheaper of two bad options — the alternative it weighed
|
|
188
|
+
was one page loading a sheet named for the other page's room. This sheet is
|
|
189
|
+
named for neither room, which is the option that was missing.
|
|
190
|
+
|
|
191
|
+
`.gov-held-note` was MOVED to board-room.css under a note in government.css
|
|
192
|
+
saying "nothing on THIS page renders them". government.js does: it marks a
|
|
193
|
+
held Archon rank with one (renderRankList). So that line has rendered unstyled
|
|
194
|
+
on /government since the carve — a live defect, not a tidiness point.
|
|
195
|
+
|
|
196
|
+
The NAMES stay `gov-*`: board-room.js and government.js emit them, and
|
|
197
|
+
tests/government_board_room_copy.mjs reads `.gov-grid-note` as literal markup.
|
|
198
|
+
The `.ov-*` selectors on the code rule came with it from government.css, where
|
|
199
|
+
they were already sharing the declaration. */
|
|
200
|
+
|
|
201
|
+
/* The framing line under a heading — the family's voice. */
|
|
202
|
+
.gov-grid-note { margin: 0 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--ink-soft); max-width: var(--measure); }
|
|
203
|
+
.gov-grid-note strong { color: var(--ink); font-weight: 500; }
|
|
204
|
+
.gov-grid-note code, .ov-row__sub code, .ov-row__title code, .ov-foot code {
|
|
205
|
+
font-family: var(--font-mono); font-size: 12.5px; color: var(--ink); overflow-wrap: anywhere;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* "your vote" beside the reader's own row in the open ballot (/board-room), and
|
|
209
|
+
"held" beside an Archon-held rank (/government). */
|
|
210
|
+
.gov-held-note { font-size: 12.5px; color: var(--ok); white-space: nowrap; }
|
|
211
|
+
|
|
181
212
|
/* Detail lines under a row: a reason per line (the gate's holds), or a key and
|
|
182
213
|
a value (the harbor's make and cost). Hairlines between, none on the first. */
|
|
183
214
|
.ov-lines { list-style: none; margin: 10px 0 0; padding: 0; max-width: 72ch; }
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.658",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.658",
|
|
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.19.
|
|
3
|
+
"version": "1.19.658",
|
|
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",
|
|
@@ -298,11 +298,85 @@ function checkGovernmentRenameIdentity() {
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
|
|
301
|
+
// ── Check 19d — "stealth" is the PRODUCT's word, not the gate's (task 1003152) ─
|
|
302
|
+
//
|
|
303
|
+
// The third vocabulary wall, and the same defect as ADR 0174's "role": one word,
|
|
304
|
+
// two live meanings, close to OPPOSITE. `platform_identity_projects.visibility =
|
|
305
|
+
// 'stealth'` is a project that IS on the public orbs map — anonymous, rendered as
|
|
306
|
+
// a black hole, refusing applications, but published and visible; only its
|
|
307
|
+
// identity is withheld. The pre-launch gate is the reverse: the whole instance is
|
|
308
|
+
// UNREADABLE without a password. ADR 0182 locks the product sense to the orb
|
|
309
|
+
// metaphor via goal 1000046's own criterion, so the PRODUCT keeps the word and
|
|
310
|
+
// the INFRA half renamed (owner decision, 2026-09-09).
|
|
311
|
+
//
|
|
312
|
+
// SCOPED TO THE INFRA SURFACE, for the same reason check 19 is scoped to the
|
|
313
|
+
// government surface: ~16 files carry the product sense legitimately, and a
|
|
314
|
+
// repo-wide ban would have to allowlist every one of them — an allowlist longer
|
|
315
|
+
// than the rule. The honest unit is a SURFACE.
|
|
316
|
+
//
|
|
317
|
+
// NO ALLOWLIST. Two exemptions, both properties of a line rather than a file:
|
|
318
|
+
// a line naming one of the four documented COMPATIBILITY identifiers (the live
|
|
319
|
+
// cookie, the HMAC label, the legacy submit path, the deprecated env var — see
|
|
320
|
+
// prelaunch-gate.js's header for why each stayed), and a line CITING the rename
|
|
321
|
+
// or the other meaning. Everything else is drift.
|
|
322
|
+
// tests/prelaunch_gate.mjs is deliberately NOT here, the same way check 19's
|
|
323
|
+
// mutation test sits outside the government surface: that file's job includes
|
|
324
|
+
// PLANTING the banned word to prove the rule can fail, and a wall that forbids
|
|
325
|
+
// its own negative test can only be satisfied by deleting the evidence.
|
|
326
|
+
const PRELAUNCH_SURFACE = [
|
|
327
|
+
'src/bongos/prelaunch-gate.js',
|
|
328
|
+
'src/bongos/platform-visibility-gate.js',
|
|
329
|
+
];
|
|
330
|
+
// serve-internal.js is 1,500+ lines and mounts every surface on the instance;
|
|
331
|
+
// scanning it whole would drag in prose about the product's visibility. The
|
|
332
|
+
// lines that name the GATE are the only ones that can regress here.
|
|
333
|
+
const PRELAUNCH_LINE_SCOPED = [
|
|
334
|
+
{ file: 'src/bongos/serve-internal.js', mustMention: /prelaunch|PRE-LAUNCH/i },
|
|
335
|
+
];
|
|
336
|
+
const PRELAUNCH_COMPAT = /cb_stealth|stealth-v1|__stealth|STEALTH_PASSWORD/;
|
|
337
|
+
const PRELAUNCH_CITATION = /task 1003152|ADR 0182|stealth-gate\.js|platform_identity_projects/;
|
|
338
|
+
|
|
339
|
+
function checkPrelaunchVocabulary() {
|
|
340
|
+
const violations = [];
|
|
341
|
+
let scanned = 0;
|
|
342
|
+
const scanFile = (rel, lineFilter = null) => {
|
|
343
|
+
const abs = path.join(CORE_ROOT, rel);
|
|
344
|
+
let src;
|
|
345
|
+
try { src = fs.readFileSync(abs, 'utf8'); } catch { return; }
|
|
346
|
+
scanned += 1;
|
|
347
|
+
src.split('\n').forEach((line, i) => {
|
|
348
|
+
if (lineFilter && !lineFilter.test(line)) return;
|
|
349
|
+
if (!/stealth/i.test(line)) return;
|
|
350
|
+
if (PRELAUNCH_COMPAT.test(line)) return;
|
|
351
|
+
if (PRELAUNCH_CITATION.test(line)) return;
|
|
352
|
+
violations.push(`${rel}:${i + 1}: "stealth" — on the pre-launch surface the word is PRELAUNCH. `
|
|
353
|
+
+ '"stealth" belongs to the PRODUCT\'s visibility axis (ADR 0182), which means almost the opposite.'
|
|
354
|
+
+ ` → ${line.trim().slice(0, 90)}`);
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
for (const rel of PRELAUNCH_SURFACE) scanFile(rel);
|
|
358
|
+
for (const { file, mustMention } of PRELAUNCH_LINE_SCOPED) scanFile(file, mustMention);
|
|
359
|
+
return {
|
|
360
|
+
name: 'the pre-launch gate is PRELAUNCH; "stealth" is the product\'s word (task 1003152)',
|
|
361
|
+
ok: violations.length === 0,
|
|
362
|
+
hardFail: violations.length > 0,
|
|
363
|
+
violations,
|
|
364
|
+
warnings: [],
|
|
365
|
+
note: `${scanned} pre-launch-surface file(s) scanned (${PRELAUNCH_SURFACE.length} whole plus `
|
|
366
|
+
+ `${PRELAUNCH_LINE_SCOPED.length} line-scoped to the lines naming the gate); no allowlist — `
|
|
367
|
+
+ 'a documented compatibility identifier or a line CITING the rename are the only exemptions.',
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
301
371
|
module.exports = {
|
|
302
372
|
GOAL_VOCAB_CITATION,
|
|
303
373
|
GOAL_VOCAB_LINE_SCOPED,
|
|
304
374
|
GOAL_VOCAB_SURFACE,
|
|
305
375
|
LIFECYCLE_DB_FILES,
|
|
376
|
+
PRELAUNCH_CITATION,
|
|
377
|
+
PRELAUNCH_COMPAT,
|
|
378
|
+
PRELAUNCH_LINE_SCOPED,
|
|
379
|
+
PRELAUNCH_SURFACE,
|
|
306
380
|
RENAME_IDENTITY_RE,
|
|
307
381
|
VOCAB_ALLOW,
|
|
308
382
|
VOCAB_BANNED,
|
|
@@ -312,6 +386,7 @@ module.exports = {
|
|
|
312
386
|
VOCAB_SURFACE,
|
|
313
387
|
checkGoalMembershipVocabulary,
|
|
314
388
|
checkGovernmentRenameIdentity,
|
|
389
|
+
checkPrelaunchVocabulary,
|
|
315
390
|
checkVocabularyWall,
|
|
316
391
|
vocabFilesToScan,
|
|
317
392
|
};
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -36,7 +36,7 @@ const { resolveCoreRoot, resolveInstanceRoot } = require('../../src/instance-con
|
|
|
36
36
|
const { CORE_ROOT, INSTANCE_ROOT, MODULE_ROOTS, REPO_ROOT, rel, walkJs, walkJsAllRoots } = require('./fitness-lib.js');
|
|
37
37
|
const { IDENTITY_SCAN_ALLOWED, IDENTITY_SCAN_EXCLUDE, IDENTITY_SCAN_EXTRA_FILES, IDENTITY_SCAN_EXTS, IDENTITY_SCAN_ROOTS, IDENTITY_SCAN_SKIP_DIRS, PROMPT_IDENTITY_LITERALS, checkDesignContract, checkKernelPromptsNoHardcodedIdentity, checkNeutralConfigsNoHostIdentity, collectIdentityFiles, scanPromptIdentity, stripCommentsForIdentity } = require('./fitness-checks-identity.js');
|
|
38
38
|
const { WRITE_VALIDATION_BASELINE, WRITE_VALIDATION_EXEMPT, checkWriteRoutesValidated, scanUnvalidatedWriteRoutes } = require('./fitness-checks-write-validation.js');
|
|
39
|
-
const { GOAL_VOCAB_CITATION, GOAL_VOCAB_LINE_SCOPED, GOAL_VOCAB_SURFACE, LIFECYCLE_DB_FILES, RENAME_IDENTITY_RE, VOCAB_ALLOW, VOCAB_BANNED, VOCAB_CITATION, VOCAB_EXTS, VOCAB_HISTORICAL_REF, VOCAB_SURFACE, checkGoalMembershipVocabulary, checkGovernmentRenameIdentity, checkVocabularyWall, vocabFilesToScan } = require('./fitness-checks-vocabulary.js');
|
|
39
|
+
const { GOAL_VOCAB_CITATION, GOAL_VOCAB_LINE_SCOPED, GOAL_VOCAB_SURFACE, LIFECYCLE_DB_FILES, PRELAUNCH_SURFACE, RENAME_IDENTITY_RE, VOCAB_ALLOW, VOCAB_BANNED, VOCAB_CITATION, VOCAB_EXTS, VOCAB_HISTORICAL_REF, VOCAB_SURFACE, checkGoalMembershipVocabulary, checkGovernmentRenameIdentity, checkPrelaunchVocabulary, checkVocabularyWall, vocabFilesToScan } = require('./fitness-checks-vocabulary.js');
|
|
40
40
|
// ============================================================================
|
|
41
41
|
// Check 1 — core ↔ host/game import boundary (ADR 0062 §1 cut-line)
|
|
42
42
|
// ============================================================================
|
|
@@ -1374,7 +1374,7 @@ const CHECKS = [
|
|
|
1374
1374
|
checkErrorEnvelope, // Check 17 — BV1 R03 / task 1990: no route hand-rolls a flat error (ADR 0116)
|
|
1375
1375
|
checkVocabularyWall, // Check 19 — ADR 0174 / goal 1000068 R07: "role" is a craft, never a rank
|
|
1376
1376
|
checkGoalMembershipVocabulary, // Check 19b — ADR 0177 / task 1003013: the FOURTH "role" (goal membership)
|
|
1377
|
-
checkGovernmentRenameIdentity, //
|
|
1377
|
+
checkGovernmentRenameIdentity, checkPrelaunchVocabulary, // 19c task 1003120 (`X`→`X` rename arrow) + 19d task 1003152 ("stealth" is the product's word) — sharing a line: this file is AT the 1,500 budget (Check 23/24 precedent below; task 1003825 buys it back)
|
|
1378
1378
|
checkWriteRoutesValidated, // Check 18 — BV1 R12 / task 1999: every body-reading write route validates (ADR 0118)
|
|
1379
1379
|
require('./fitness-ratchets.js').checkQualityRatchets, // Check 20 — task 1003129: quality budgets only tighten (mechanics + knip CI step live in fitness-ratchets.js)
|
|
1380
1380
|
require('./doc-cli-guard.js').checkDocNamesRealCli, // Check 22 — task 1003165 / G01: a doc that names a CLI invocation must match the real CLI
|
|
@@ -1436,7 +1436,7 @@ if (require.main === module) main();
|
|
|
1436
1436
|
|
|
1437
1437
|
module.exports = {
|
|
1438
1438
|
checkVocabularyWall, VOCAB_ALLOW, VOCAB_CITATION, VOCAB_EXTS, vocabFilesToScan,
|
|
1439
|
-
checkGoalMembershipVocabulary, GOAL_VOCAB_SURFACE, checkGovernmentRenameIdentity, RENAME_IDENTITY_RE, // 19c
|
|
1439
|
+
checkGoalMembershipVocabulary, GOAL_VOCAB_SURFACE, checkGovernmentRenameIdentity, RENAME_IDENTITY_RE, checkPrelaunchVocabulary, PRELAUNCH_SURFACE, // 19c task 1003120, 19d task 1003152
|
|
1440
1440
|
runAll,
|
|
1441
1441
|
resolvesToHostGame,
|
|
1442
1442
|
checkCoreHostBoundary,
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
// - What a member is shown must not be cached for a stranger: a passed
|
|
25
25
|
// response leaves marked private, whatever the route behind it said.
|
|
26
26
|
//
|
|
27
|
-
// It is NOT the pre-launch gate (
|
|
28
|
-
// password for an unannounced instance, inert unless
|
|
27
|
+
// It is NOT the pre-launch gate (prelaunch-gate.js): that one is a shared
|
|
28
|
+
// password for an unannounced instance, inert unless PRELAUNCH_PASSWORD is set,
|
|
29
29
|
// and it mounts first. This one is a per-project policy with accounts behind it.
|
|
30
30
|
// Both can be on at once; each refuses on its own terms.
|
|
31
31
|
|
|
@@ -67,7 +67,9 @@ const API = `(?:${ALL_API_PREFIXES.map(escRe).join('|')})`;
|
|
|
67
67
|
// /downloads/* the installer script and the Dev Box updater — a
|
|
68
68
|
// cookie-less `curl … | sh`, and release redirects carry
|
|
69
69
|
// no project data.
|
|
70
|
-
// /
|
|
70
|
+
// /__prelaunch the pre-launch gate's own submit, when both gates are on.
|
|
71
|
+
// /__stealth is its pre-rename alias (task 1003152) and is
|
|
72
|
+
// exempt for as long as the gate still accepts it.
|
|
71
73
|
const EXEMPT = [
|
|
72
74
|
new RegExp(`^${API}/auth(/|$)`),
|
|
73
75
|
new RegExp(`^${API}/instance$`),
|
|
@@ -76,6 +78,7 @@ const EXEMPT = [
|
|
|
76
78
|
/^\/healthz$/,
|
|
77
79
|
/^\/version$/,
|
|
78
80
|
/^\/metrics$/,
|
|
81
|
+
/^\/__prelaunch$/,
|
|
79
82
|
/^\/__stealth$/,
|
|
80
83
|
/^\/downloads\//,
|
|
81
84
|
];
|
|
@@ -127,7 +130,7 @@ function signInHref(req) {
|
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
// One leading slash, no '//' (protocol-relative), no backslash, no control
|
|
130
|
-
// bytes — the
|
|
133
|
+
// bytes — the pre-launch gate's rule; anything else returns to '/'.
|
|
131
134
|
function relativeReturn(raw) {
|
|
132
135
|
if (typeof raw !== 'string' || !raw.length || raw.length > 512) return null;
|
|
133
136
|
if (!raw.startsWith('/') || raw.startsWith('//') || raw.includes('\\')) return null;
|