@bongos/core 1.19.592 → 1.19.594

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 (39) hide show
  1. package/.bongos-core.json +61 -41
  2. package/.claude/skills/planning-session/SKILL.md +2 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +3 -1
  5. package/clients/bongos-client/index.cjs +3 -1
  6. package/clients/bongos-client/index.d.ts +6 -2
  7. package/clients/bongos-client/index.mjs +3 -1
  8. package/docs/adr/0157-archon-is-rank-and-identity-only.md +2 -0
  9. package/docs/api/openapi.json +103 -7
  10. package/docs/api-reference.md +4 -3
  11. package/docs/copy-inventory.md +135 -116
  12. package/docs/copy-registry.json +319 -138
  13. package/docs/module-api-changelog.md +4 -0
  14. package/modules/dev-box/app/src/vendor/bongos-client.cjs +3 -1
  15. package/modules/government/catalog.js +20 -11
  16. package/modules/government/migrations/government_012_version_create_archon.sql +45 -0
  17. package/modules/hall-ui/public/goals-render.js +182 -6
  18. package/modules/hall-ui/public/goals.css +51 -0
  19. package/modules/hall-ui/public/roadmap.css +5 -0
  20. package/modules/hall-ui/public/roadmap.js +25 -0
  21. package/modules/hall-ui/public/task-detail.js +22 -11
  22. package/modules/lifecycle/db-goals.js +34 -2
  23. package/modules/lifecycle/db-versions.js +195 -3
  24. package/modules/lifecycle/db.js +4 -1
  25. package/modules/lifecycle/routes/goals.js +15 -1
  26. package/modules/lifecycle/routes/version-route-authz.js +131 -1
  27. package/modules/lifecycle/routes/versions.js +79 -1
  28. package/package-lock.json +2 -2
  29. package/package.json +1 -1
  30. package/scripts/gds/triage.js +60 -26
  31. package/src/module-api.js +1 -1
  32. package/tests/goal_archive_hall.mjs +198 -0
  33. package/tests/government_require_permission.mjs +7 -3
  34. package/tests/government_seed.mjs +101 -9
  35. package/tests/promote_goal_id.mjs +85 -30
  36. package/tests/publish_reconciler.mjs +35 -5
  37. package/tests/task_detail_ui.mjs +36 -9
  38. package/tests/version_close_route.mjs +261 -0
  39. package/tests/version_override_visibility.mjs +176 -0
@@ -0,0 +1,176 @@
1
+ // tests/version_override_visibility.mjs — an override nobody can count is
2
+ // indistinguishable from no gate at all (BV1.R15, task 1003602, goal 1000086,
3
+ // ADR 0250 §3).
4
+ //
5
+ // THE RULE R03 CREATED, AND THE HOLE R15 CLOSES. A goal may only be added to a
6
+ // version still in `planning`; an Archon may override that and admit one into a
7
+ // version already building, recording a reason. R07 (task 1003594) built the
8
+ // record and put the count on `GET /versions/:id/progress`. That route is not a
9
+ // surface anyone opens — the roadmap reads the ROLLUP, the board reads
10
+ // `listGoals`, and neither carried a single bit about admissions. So the
11
+ // override existed, was recorded, and was invisible everywhere a person looks,
12
+ // which is the same accountability as not recording it.
13
+ //
14
+ // WHAT THIS FILE PINS: the count rides the rollup the hall actually reads; the
15
+ // board can tell an admitted goal from a normal one; the detail names WHO and
16
+ // WHY; and the zero case renders nothing, so the line is a signal rather than
17
+ // chrome.
18
+ //
19
+ // Run: node --test tests/version_override_visibility.mjs
20
+
21
+ import assert from 'node:assert/strict';
22
+ import { test } from 'node:test';
23
+ import { readFileSync } from 'node:fs';
24
+ import { createRequire } from 'node:module';
25
+ import { makeSqlAwareClient } from './helpers.mjs';
26
+
27
+ process.env.NODE_ENV = 'test';
28
+ const require = createRequire(import.meta.url);
29
+ const { listGoals, listVersionAdmissions, versionProgress } = require('../modules/lifecycle/db.js');
30
+
31
+ const src = (rel) => readFileSync(new URL('../' + rel, import.meta.url), 'utf8');
32
+ const GOALS_RENDER = src('modules/hall-ui/public/goals-render.js');
33
+ const ROADMAP = src('modules/hall-ui/public/roadmap.js');
34
+
35
+ function fakePool(handler) {
36
+ const c = makeSqlAwareClient(handler);
37
+ return { query: (sql, params) => c._client.query(sql, params), connect: c.connect, queries: c._client.queries };
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // The rollup — the surface the roadmap reads
42
+ // ---------------------------------------------------------------------------
43
+
44
+ test('versionProgress asks for the admission count', async () => {
45
+ let seen = null;
46
+ const pool = fakePool((s) => { seen = s; return { rows: [] }; });
47
+ await versionProgress({ pool });
48
+ assert.match(seen, /lifecycle_goal_version_admissions/,
49
+ 'the rollup must carry the override count — R07 put it only on /versions/:id/progress, which the roadmap never calls');
50
+ assert.match(seen, /COALESCE\(adm\.admission_count, 0\)/,
51
+ 'a version nobody widened has no row in the aggregate, so the zero comes from COALESCE, not from a manufactured join row');
52
+ });
53
+
54
+ test('versionProgress returns admission_count as a number, defaulting to 0', async () => {
55
+ const pool = fakePool(() => ({
56
+ rows: [{
57
+ version_id: 'BONGOS-V1', name: 'platform', status: 'building',
58
+ shipped_count: 1, total_count: 2, shipped_weight: 1, total_weight: 2,
59
+ done_when: null, criteria_count: 3,
60
+ lifecycle_completed_count: 0, lifecycle_confirmed_count: 0, lifecycle_shipped_count: 1,
61
+ admission_count: 2,
62
+ }],
63
+ }));
64
+ const [row] = await versionProgress({ pool });
65
+ assert.equal(row.admission_count, 2);
66
+ assert.equal(typeof row.admission_count, 'number',
67
+ 'the client compares it to 0; a string "2" from the pg driver would render but never equal 0');
68
+ });
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // The board — which goals were admitted
72
+ // ---------------------------------------------------------------------------
73
+
74
+ test('listGoals carries an `admitted` flag', async () => {
75
+ let seen = null;
76
+ const pool = fakePool((s) => { seen = s; return { rows: [] }; });
77
+ await listGoals({ versionId: 'BONGOS-V1' }, { pool });
78
+ assert.match(seen, /EXISTS \(SELECT 1 FROM lifecycle_goal_version_admissions/);
79
+ assert.match(seen, /AS admitted/);
80
+ });
81
+
82
+ test('the board flag is EXISTS, not a join that could duplicate a goal', async () => {
83
+ // A LEFT JOIN would return one row per admission. There is one today, but the
84
+ // table has no unique constraint on goal_id, so a second admission row would
85
+ // silently make the goal appear twice in the board's list.
86
+ let seen = null;
87
+ const pool = fakePool((s) => { seen = s; return { rows: [] }; });
88
+ await listGoals({}, { pool });
89
+ assert.equal(/LEFT JOIN lifecycle_goal_version_admissions/.test(seen), false);
90
+ });
91
+
92
+ test('listGoals still filters and orders as before', async () => {
93
+ let seen = null;
94
+ const pool = fakePool((s) => { seen = s; return { rows: [] }; });
95
+ await listGoals({ versionId: 'X', status: 'open', limit: 5 }, { pool });
96
+ assert.match(seen, /version_id = \$1/);
97
+ assert.match(seen, /status = \$2/);
98
+ // Unqualified, exactly as before: the admissions EXISTS names `goals.id` in
99
+ // full so no other line of this query had to change. tests/goal_tier.mjs pins
100
+ // this literal ordering text.
101
+ assert.match(seen, /ORDER BY version_id, sort_order, created_at, id/);
102
+ });
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // The detail — by whom, and why
106
+ // ---------------------------------------------------------------------------
107
+
108
+ test('listVersionAdmissions resolves the admitter to a NAME', async () => {
109
+ let seen = null;
110
+ const pool = fakePool((s) => { seen = s; return { rows: [] }; });
111
+ await listVersionAdmissions('BONGOS-V1', { pool });
112
+ // ADR 0250 §3 asks "by whom". A numeric builder id does not answer it, and
113
+ // making every caller issue a second lookup per row is how a counter stops
114
+ // being read.
115
+ assert.match(seen, /admitted_by_login/);
116
+ assert.match(seen, /admitted_by_name/);
117
+ });
118
+
119
+ test('an admission whose admitter was offboarded still appears', async () => {
120
+ // The one failure mode that matters: an inner JOIN would DROP the row, hiding
121
+ // exactly the history someone with an interest in hiding it would target.
122
+ let seen = null;
123
+ const pool = fakePool((s) => { seen = s; return { rows: [] }; });
124
+ await listVersionAdmissions('BONGOS-V1', { pool });
125
+ assert.match(seen, /LEFT JOIN builders b ON b\.id = a\.admitted_by/);
126
+ });
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // The hall
130
+ // ---------------------------------------------------------------------------
131
+
132
+ test('the goal detail renders WHO admitted it', () => {
133
+ assert.match(GOALS_RENDER, /goal-badge--admitted/);
134
+ assert.match(GOALS_RENDER, /admitted_by_name \|\| adm\.admitted_by_login \|\| \('builder ' \+ adm\.admitted_by\)/,
135
+ 'falls back through name → login → id so an offboarded admitter still renders as someone');
136
+ });
137
+
138
+ test('the REASON is rendered, not hidden in a title attribute', () => {
139
+ // A tooltip is invisible on touch and to anyone not already suspicious. An
140
+ // override you have to hover to justify is the same unaccountable override.
141
+ assert.match(GOALS_RENDER, /why\.className = 'goal-admission';/);
142
+ assert.match(GOALS_RENDER, /adm\.reason \? ` — \$\{escapeHtml\(String\(adm\.reason\)\)\}`/);
143
+ });
144
+
145
+ test('an admission with no recorded reason says so rather than rendering blank', () => {
146
+ assert.match(GOALS_RENDER, /no reason recorded/);
147
+ });
148
+
149
+ test('a normally-created goal renders nothing at all', () => {
150
+ // `admitted` is null for every goal that went through the gate — the positive
151
+ // fact "this did not skip it", not missing data.
152
+ assert.match(GOALS_RENDER, /const adm = d\.admitted;/);
153
+ assert.match(GOALS_RENDER, /const admittedSpan = adm\s*\n?\s*\?/);
154
+ assert.match(GOALS_RENDER, /if \(adm\) \{/);
155
+ });
156
+
157
+ test('the board renders one chip, and does not leak the reason onto every row', () => {
158
+ assert.match(GOALS_RENDER, /fact-chip--admitted/);
159
+ assert.match(GOALS_RENDER, /g\.admitted\s*\n?\s*\?/);
160
+ // The chip's own markup must not interpolate a reason: listGoals deliberately
161
+ // returns a boolean, and an Archon's free text does not belong on a list row.
162
+ const chip = GOALS_RENDER.slice(GOALS_RENDER.indexOf('const admittedChip'), GOALS_RENDER.indexOf('const factsHtml'));
163
+ assert.equal(/reason/.test(chip), false);
164
+ });
165
+
166
+ test('the roadmap renders the count only when it is above zero', () => {
167
+ assert.match(ROADMAP, /function admissionNote\(v\)/);
168
+ assert.match(ROADMAP, /if \(n === 0\) return '';/,
169
+ 'a permanent "0 overrides" line is chrome a reader learns to skip — and would go on skipping when it became 2');
170
+ assert.match(ROADMAP, /admissions: Number\(p\.admission_count\) \|\| 0/);
171
+ assert.match(ROADMAP, /\$\{admissionNote\(v\)\}/, 'the helper must actually be called from the goals block');
172
+ });
173
+
174
+ test('the roadmap note pluralises, so "1 goals" never ships', () => {
175
+ assert.match(ROADMAP, /n === 1 \? '' : 's'/);
176
+ });