@bongos/core 1.19.1015 → 1.19.1016
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 +98 -73
- package/.claude/skills/planning-session/SKILL.md +1 -1
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/adr/0331-the-sky-is-the-ideators-home.md +12 -0
- package/docs/adr/0335-an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md +113 -0
- package/docs/adr/0336-a-guilds-record-is-the-sum-of-what-its-members-already-show.md +201 -0
- package/docs/adr/README.md +2 -0
- package/docs/api/openapi.json +4 -0
- package/docs/api-reference.md +1 -1
- package/docs/architecture.md +4 -1
- package/docs/file-map.md +0 -1
- package/docs/module-api-changelog.md +3 -1
- package/migrations/core_253_idea_filed_via.sql +59 -0
- package/modules/agents/pollers/event-router.js +2 -0
- package/modules/discord/discord-bugs.js +2 -0
- package/modules/discord/discord-inbound.js +3 -0
- package/modules/hall-ui/public/sky-data.js +22 -8
- package/modules/hall-ui/public/sky-draw.js +3 -2
- package/modules/hall-ui/public/sky-panel.js +1 -1
- package/modules/ideas/born-of.js +55 -0
- package/modules/ideas/inbox.js +23 -8
- package/modules/ideas/projection.js +48 -0
- package/modules/ideas/routes/inbox.js +20 -0
- package/modules/lifecycle/sky.js +25 -4
- package/modules/platform-identity/activity-snapshots.js +47 -0
- package/modules/platform-identity/migrations/platform_identity_022_activity_snapshots.sql +46 -0
- package/modules/platform-identity/platform-identity.js +13 -0
- package/modules/platform-identity/tests/activity-snapshots.mjs +68 -0
- package/modules/platform-identity/tests/platform-identity.mjs +21 -0
- package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/capture.js +11 -0
- package/scripts/gds/run-unit-tests.js +4 -0
- package/src/module-api.js +1 -1
- package/tests/activity_snapshots_db.mjs +97 -0
- package/tests/discord_bugs.mjs +1 -0
- package/tests/discord_inbound.mjs +1 -0
- package/tests/goal_map_page.mjs +14 -0
- package/tests/guild_namespace_reserved.mjs +76 -0
- package/tests/hall_sky.mjs +33 -16
- package/tests/idea_filed_via.mjs +185 -0
- package/tests/landing_page.mjs +8 -0
- package/tests/profile_rollup_consent.mjs +5 -1
- package/tests/project_admin_console_model.mjs +105 -0
- package/tests/sky_read.mjs +19 -2
- package/tests/ui_design_skills.mjs +9 -0
- package/modules/public-landing/public/world/assets/clouds/cloud-island-large.png +0 -0
- package/modules/public-landing/public/world/assets/clouds/cloud-puff.png +0 -0
- package/modules/public-landing/public/world/assets/creatures/creature-whale-cargo.png +0 -0
- package/modules/public-landing/public/world/assets/creatures/creature-whale-glide.png +0 -0
- package/modules/public-landing/public/world/assets/plates/plate-clouds-near.png +0 -0
- package/modules/public-landing/public/world/assets/plates/scene-establishing.png +0 -0
|
@@ -62,6 +62,7 @@ const ALL_ORIGINS = [PUB_ORIGIN, PRIV_ORIGIN, STEALTH_ORIGIN, UNREAD_ORIGIN];
|
|
|
62
62
|
const q = (sql, params) => pool.query(sql, params);
|
|
63
63
|
|
|
64
64
|
async function cleanup() {
|
|
65
|
+
await q(`DELETE FROM platform_identity_activity_snapshots WHERE github_id = ANY($1::bigint[])`, [ALL_IDS]).catch(() => {});
|
|
65
66
|
await q(`DELETE FROM platform_identity_builder_project_activity WHERE github_id = ANY($1::bigint[])`, [ALL_IDS]).catch(() => {});
|
|
66
67
|
await q(`DELETE FROM platform_identity_project_memberships WHERE github_id = ANY($1::bigint[])`, [ALL_IDS]).catch(() => {});
|
|
67
68
|
await q(`DELETE FROM platform_identity_accounts WHERE github_id = ANY($1::bigint[])`, [ALL_IDS]).catch(() => {});
|
|
@@ -81,7 +82,9 @@ try {
|
|
|
81
82
|
// error dressed up as a boundary failure — instead of a clean assertion.
|
|
82
83
|
// 011 and 020 carry the columns the naming rule reads (description, visibility,
|
|
83
84
|
// the door-known pair); 012_recent_ships the column the rollup SELECTs; 008 and
|
|
84
|
-
// 009 the handle and display_name_source columns upsertAccount writes
|
|
85
|
+
// 009 the handle and display_name_source columns upsertAccount writes; 022 is
|
|
86
|
+
// the table upsertActivity now also writes on every call (task 1002977) — step
|
|
87
|
+
// 5 below issues that INSERT, and a fresh database without it fails 42P01.
|
|
85
88
|
await q(migrationSql('platform_identity_001_tables.sql'));
|
|
86
89
|
await q(migrationSql('platform_identity_004_projects.sql'));
|
|
87
90
|
await q(migrationSql('platform_identity_005_builder_activity.sql'));
|
|
@@ -93,6 +96,7 @@ try {
|
|
|
93
96
|
await q(migrationSql('platform_identity_012_recent_ships.sql'));
|
|
94
97
|
await q(migrationSql('platform_identity_019_terms_acceptance.sql'));
|
|
95
98
|
await q(migrationSql('platform_identity_020_visibility_pull.sql'));
|
|
99
|
+
await q(migrationSql('platform_identity_022_activity_snapshots.sql'));
|
|
96
100
|
await cleanup();
|
|
97
101
|
|
|
98
102
|
// CONSENT IS TWO COLUMNS (ADR 0213, task 1002913): profile_state='active' AND
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// tests/project_admin_console_model.mjs — the project admin console model holds
|
|
2
|
+
// (task 1002813, R1 of the console wave; the ADR whose filename ends in ADR_SLUG).
|
|
3
|
+
//
|
|
4
|
+
// The ADR decides three things and then tells three build tasks what to build on
|
|
5
|
+
// them. Its decisions rest on facts about live code, and a decision whose premises
|
|
6
|
+
// drift quietly is worse than none, so the load-bearing ones are pinned here:
|
|
7
|
+
//
|
|
8
|
+
// D1 the hub relays and notifies; it never writes a project's admission row
|
|
9
|
+
// D2 an invite is a row the existing gate already reads — `kind` and
|
|
10
|
+
// `created_by` are descriptive and never enter the admission read
|
|
11
|
+
// D3 the admission verdict stays `access_request.review` at the archon floor,
|
|
12
|
+
// and `project.recruit`, once minted, takes exactly the shape the ADR fixes
|
|
13
|
+
//
|
|
14
|
+
// The ADR is found by its slug, not its number, because a parallel ADR can take
|
|
15
|
+
// the number first and force a renumber (tests/adr_renumber_integrity.mjs).
|
|
16
|
+
//
|
|
17
|
+
// Run: node --test --test-reporter=tap tests/project_admin_console_model.mjs
|
|
18
|
+
import assert from 'node:assert/strict';
|
|
19
|
+
import { test } from 'node:test';
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { createRequire } from 'node:module';
|
|
24
|
+
|
|
25
|
+
const require = createRequire(import.meta.url);
|
|
26
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
27
|
+
const ADR_DIR = path.join(ROOT, 'docs', 'adr');
|
|
28
|
+
const ADR_SLUG = 'an-invite-is-a-pre-approved-row-on-the-projects-own-instance.md';
|
|
29
|
+
const catalog = require(path.join(ROOT, 'modules', 'government', 'catalog.js'));
|
|
30
|
+
const { stripComments } = require(path.join(ROOT, 'scripts', 'gds', 'category-advisory-guard.js'));
|
|
31
|
+
|
|
32
|
+
const read = (...p) => fs.readFileSync(path.join(ROOT, ...p), 'utf8');
|
|
33
|
+
|
|
34
|
+
function findAdr() {
|
|
35
|
+
const hits = fs.readdirSync(ADR_DIR).filter((f) => /^\d{4}-/.test(f) && f.endsWith(ADR_SLUG));
|
|
36
|
+
assert.equal(hits.length, 1, `exactly one ADR ends in ${ADR_SLUG}; found ${hits.length}`);
|
|
37
|
+
return hits[0];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function jsFilesUnder(dir) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
43
|
+
if (entry.name === 'node_modules' || entry.name === 'tests') continue;
|
|
44
|
+
const full = path.join(dir, entry.name);
|
|
45
|
+
if (entry.isDirectory()) out.push(...jsFilesUnder(full));
|
|
46
|
+
else if (entry.name.endsWith('.js')) out.push(full);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test('the ADR exists, is indexed, and ADR 0141 names it as superseding its §5 invitations note', () => {
|
|
52
|
+
const file = findAdr();
|
|
53
|
+
const index = read('docs', 'adr', 'README.md');
|
|
54
|
+
assert.ok(index.includes(`](${file})`), 'docs/adr/README.md carries an index row linking the ADR');
|
|
55
|
+
|
|
56
|
+
const status = read('docs', 'adr', '0141-cross-site-identity-federated-hub-idp.md')
|
|
57
|
+
.split(/\r?\n/)
|
|
58
|
+
.find((l) => l.startsWith('- **Status:**'));
|
|
59
|
+
assert.ok(status, 'ADR 0141 keeps a Status line');
|
|
60
|
+
assert.ok(status.includes(file), 'ADR 0141 Status line links the superseding ADR');
|
|
61
|
+
assert.match(status, /§5/, 'the supersession is partial — it names §5, not the whole of ADR 0141');
|
|
62
|
+
|
|
63
|
+
const specHead = read('docs', 'specs', 'bongos-v1-project-admin-console.md').split(/\r?\n/)[0];
|
|
64
|
+
assert.ok(specHead.includes(file), 'the spike spec sends its reader to the ADR that narrowed it');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Both admission reads: the gate's own (hasInvitedAccessRequest) and the state the
|
|
68
|
+
// "confirming access" waiting page polls (accessStateByLogin), which must agree with it.
|
|
69
|
+
test('D2: both admission reads look at status alone — an invite and an approved application admit identically', () => {
|
|
70
|
+
const src = read('src', 'bongos', 'db-kernel.js');
|
|
71
|
+
for (const fn of ['hasInvitedAccessRequest', 'accessStateByLogin']) {
|
|
72
|
+
const m = new RegExp(`async function ${fn}\\([^)]*\\)\\s*\\{([\\s\\S]*?)\\n\\}`).exec(src);
|
|
73
|
+
assert.ok(m, `${fn} is still an admission read in db-kernel.js`);
|
|
74
|
+
const body = m[1];
|
|
75
|
+
assert.match(body, /status\s*=\s*'invited'/, `${fn}: admission is status = invited`);
|
|
76
|
+
assert.doesNotMatch(body, /\bkind\b/, `${fn}: kind is descriptive; it must never decide admission`);
|
|
77
|
+
assert.doesNotMatch(body, /\bcreated_by\b/, `${fn}: created_by is audit data; it must never decide admission`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('D1: no hub (platform-identity) source writes access_requests — the hub relays, it never admits', () => {
|
|
82
|
+
const writers = [];
|
|
83
|
+
for (const f of jsFilesUnder(path.join(ROOT, 'modules', 'platform-identity'))) {
|
|
84
|
+
const code = stripComments(fs.readFileSync(f, 'utf8'));
|
|
85
|
+
if (/\b(?:INSERT\s+INTO|UPDATE|DELETE\s+FROM)\s+access_requests\b/i.test(code)) {
|
|
86
|
+
writers.push(path.relative(ROOT, f));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
assert.deepEqual(writers, [], 'a project admits on its own instance; the hub reaches it only through a relay');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('D3: the verdict stays access_request.review at archon, and project.recruit takes the fixed shape', () => {
|
|
93
|
+
const review = catalog.byKey('access_request.review');
|
|
94
|
+
assert.ok(review, 'access_request.review exists');
|
|
95
|
+
assert.equal(review.floor, 'archon', 'the admission verdict stays at the archon floor');
|
|
96
|
+
|
|
97
|
+
// R3 (task 1002815) mints the atom. Until then there is nothing to check; once
|
|
98
|
+
// it lands, this is the shape the ADR fixed, so a drifted mint reds here.
|
|
99
|
+
const recruit = catalog.byKey('project.recruit');
|
|
100
|
+
if (!recruit) return;
|
|
101
|
+
assert.equal(recruit.floor, 'archon', 'the per-project invite floor defaults to archon (ADR 0217 D1)');
|
|
102
|
+
assert.equal(recruit.system, false, 'grantable, and composable into a custom rank');
|
|
103
|
+
assert.ok(!recruit.substrate, 'enforced in the app, so a grant is never inert');
|
|
104
|
+
assert.ok(recruit.principal == null, 'held by ranks, never by a machine principal');
|
|
105
|
+
});
|
package/tests/sky_read.mjs
CHANGED
|
@@ -47,7 +47,7 @@ await test('the shape: wells carry their criteria and their compact tasks; edges
|
|
|
47
47
|
criteria: [cr(10, 1, 100, 'shipped'), cr(10, 1, 101, 'ready'), cr(11, 2, null)],
|
|
48
48
|
tasks: [t(100, 1, { status: 'shipped', idea_id: '555' }), t(101, 1), t(200, 2)],
|
|
49
49
|
});
|
|
50
|
-
assert.deepEqual(Object.keys(out), ['goals', 'edges', 'counts']);
|
|
50
|
+
assert.deepEqual(Object.keys(out), ['goals', 'edges', 'born', 'counts']);
|
|
51
51
|
assert.deepEqual(out.edges, [{ from: '1', to: '2', count: 3 }]);
|
|
52
52
|
assert.deepEqual(out.counts, { goals: 2, edges: 1, criteria: 2, tasks: 3 });
|
|
53
53
|
const w1 = out.goals[0];
|
|
@@ -108,7 +108,7 @@ await test('a PRIVATE goal the viewer cannot see stays a well with its counts, r
|
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
await test('an empty corpus is a valid, empty sky', () => {
|
|
111
|
-
assert.deepEqual(shapeSky({}), { goals: [], edges: [], counts: { goals: 0, edges: 0, criteria: 0, tasks: 0 } });
|
|
111
|
+
assert.deepEqual(shapeSky({}), { goals: [], edges: [], born: { goals: {}, criteria: {} }, counts: { goals: 0, edges: 0, criteria: 0, tasks: 0 } });
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
// ---- the criterion→task link agrees with the progress rollup ------------------------
|
|
@@ -198,6 +198,23 @@ await test('the payload is the shaped sky, categories included, from one fake re
|
|
|
198
198
|
assert.equal(out.goals[0].tasks[0].title, 'Task 100');
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
+
// ---- born of a thought (task 1004233, sky part 5) ---------------------------------------
|
|
202
|
+
|
|
203
|
+
await test('born rides on the sky from the ideas port, normalised to string ids; an absent or failing port is an EMPTY map — never a guess', async () => {
|
|
204
|
+
const pool = fakePool({ goalRows: [{ goal_id: '1', title: 'A', status: 'open', version_id: 'V1', category_id: null, visible: true, task_total: 1, task_done: 0 }], extraRows: [x(1)] });
|
|
205
|
+
const out = await sky({ viewerId: 1 }, { pool, categoryIndex: async () => CATS, readBornOf: async () => ({ goals: { 1: 55 }, criteria: { 10: '56', 11: null } }) });
|
|
206
|
+
assert.deepEqual(out.born, { goals: { 1: '55' }, criteria: { 10: '56' } });
|
|
207
|
+
assert.equal(pool.calls.length, 5, 'the born read is the ideas module\'s own — not a sixth query here');
|
|
208
|
+
const none = await sky({ viewerId: 1 }, { pool: fakePool(), categoryIndex: async () => CATS, readBornOf: null });
|
|
209
|
+
assert.deepEqual(none.born, { goals: {}, criteria: {} });
|
|
210
|
+
const broken = await sky({ viewerId: 1 }, { pool: fakePool(), categoryIndex: async () => CATS, readBornOf: async () => { throw new Error('port down'); } });
|
|
211
|
+
assert.deepEqual(broken.born, { goals: {}, criteria: {} }, 'a failing port empties the map and the sky still draws');
|
|
212
|
+
assert.deepEqual(skyLib.normalizeBorn({ goals: 'nope' }), { goals: {}, criteria: {} });
|
|
213
|
+
const SKY_SRC = fs.readFileSync(path.join(ROOT, 'modules/lifecycle/sky.js'), 'utf8');
|
|
214
|
+
assert.match(SKY_SRC, /resolveOptional\('ideas\.bornOf'\)/, 'production resolves the port');
|
|
215
|
+
assert.equal(/ideas_goal_ratifications|criterion_proposal_audit/.test(SKY_SRC), false, 'lifecycle reads no ideas table itself');
|
|
216
|
+
});
|
|
217
|
+
|
|
201
218
|
// ---- the cache and the handler -------------------------------------------------------
|
|
202
219
|
|
|
203
220
|
function run(handler, req) {
|
|
@@ -145,6 +145,15 @@ test('impeccable is one in-house skill whose sixteen sub-commands each own exact
|
|
|
145
145
|
}
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
+
test('impeccable menu names the kit-report temp dir OS-generically, not Windows-only %TMP% (task 1003636)', () => {
|
|
149
|
+
const skill = read(path.join(IMPECCABLE, 'SKILL.md'));
|
|
150
|
+
const signalLine = skill.split('\n').find((l) => l.includes('The last kit report'));
|
|
151
|
+
assert.ok(signalLine, 'the kit-report menu signal line exists');
|
|
152
|
+
assert.doesNotMatch(signalLine, /`%TMP%\/ui-design-kit/, 'the signal does not lead with a bare Windows %TMP% path');
|
|
153
|
+
assert.match(signalLine, /os\.tmpdir\(\)/, 'points at os.tmpdir(), which resolves per-OS');
|
|
154
|
+
assert.match(signalLine, /\$TMPDIR/, 'names the mac/linux env var too, not just %TMP%');
|
|
155
|
+
});
|
|
156
|
+
|
|
148
157
|
test('impeccable carries none of the behaviour that made its origin dangerous: no hooks, no bypass agent, no telemetry, no remote bundle, no live-server token', () => {
|
|
149
158
|
const files = [path.join(IMPECCABLE, 'SKILL.md'), ...fs.readdirSync(path.join(IMPECCABLE, 'reference')).map((f) => path.join(IMPECCABLE, 'reference', f))];
|
|
150
159
|
for (const f of files) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|