@bongos/core 1.19.161 → 1.19.162
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 +15 -10
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +3 -1
- package/tests/idea_quick_guard.mjs +142 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.162",
|
|
6
|
+
"core_contract": "1.19.162",
|
|
7
|
+
"source_commit": "1db6240e60bde4c45db842d783f1a109da3684e5",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-16T16:
|
|
9
|
+
"built_at": "2026-08-16T16:44:59.964Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 305,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1260,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1586,
|
|
20
|
+
"tree_sha256": "b21ff15e6dc8e250c96a99b03f62b79b69a99a870c2aca7d2db4bbc8a1e1f0f9",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -4197,12 +4197,12 @@
|
|
|
4197
4197
|
{
|
|
4198
4198
|
"path": "package-lock.json",
|
|
4199
4199
|
"mode": "0000644",
|
|
4200
|
-
"sha256": "
|
|
4200
|
+
"sha256": "0fa4ce8b1bb49791c487d1b90ac7f0ddea7577cf4c14db1b6366ccdccaed06ad"
|
|
4201
4201
|
},
|
|
4202
4202
|
{
|
|
4203
4203
|
"path": "package.json",
|
|
4204
4204
|
"mode": "0000644",
|
|
4205
|
-
"sha256": "
|
|
4205
|
+
"sha256": "33e29cccdddaea282c452b7296e2be6ec3fd06d61b6a8d35c60a98aa86d61bf2"
|
|
4206
4206
|
},
|
|
4207
4207
|
{
|
|
4208
4208
|
"path": "public-docs/index.html",
|
|
@@ -5797,7 +5797,7 @@
|
|
|
5797
5797
|
{
|
|
5798
5798
|
"path": "src/module-api.js",
|
|
5799
5799
|
"mode": "0000644",
|
|
5800
|
-
"sha256": "
|
|
5800
|
+
"sha256": "6e7fcf60af8a8abcc9e1a193c5ecf49fed08c9b410c9e3a1dd031f406bf1dea3"
|
|
5801
5801
|
},
|
|
5802
5802
|
{
|
|
5803
5803
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6909,6 +6909,11 @@
|
|
|
6909
6909
|
"mode": "0000644",
|
|
6910
6910
|
"sha256": "07cacc97990f0e775342f8b30171c87128e0acb1abd38d64c683c3f257927ebd"
|
|
6911
6911
|
},
|
|
6912
|
+
{
|
|
6913
|
+
"path": "tests/idea_quick_guard.mjs",
|
|
6914
|
+
"mode": "0000644",
|
|
6915
|
+
"sha256": "9be4414bbbbb92513a7737d4494265b24c5f5db71a67e840016cc275442df1fb"
|
|
6916
|
+
},
|
|
6912
6917
|
{
|
|
6913
6918
|
"path": "tests/idea_templates.mjs",
|
|
6914
6919
|
"mode": "0000644",
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.162",
|
|
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.162",
|
|
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.162",
|
|
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/src/module-api.js
CHANGED
|
@@ -749,8 +749,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
749
749
|
// landed since 1.19.159 with no explicit bump. run 31959145128. (task 1002620)
|
|
750
750
|
// 1.19.161 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
751
751
|
// landed since 1.19.160 with no explicit bump. run 31959299652. (task 1002620)
|
|
752
|
+
// 1.19.162 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
753
|
+
// landed since 1.19.161 with no explicit bump. run 31959496815. (task 1002620)
|
|
752
754
|
// ---------------------------------------------------------------------------
|
|
753
|
-
const CORE_VERSION = '1.19.
|
|
755
|
+
const CORE_VERSION = '1.19.162'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
754
756
|
|
|
755
757
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
756
758
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// tests/idea_quick_guard.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1002926 (BV1.R150, goal 1000062) — the regression guard: THE QUICK IDEA
|
|
4
|
+
// PATH IS UNTOUCHED by the grade era.
|
|
5
|
+
//
|
|
6
|
+
// Quick Idea exists to turn a thought into a task fast (the owner's brief), and
|
|
7
|
+
// goal 1000062's C1 says it "is never silently treated as an unfinished Full
|
|
8
|
+
// Idea". This file is that promise as executable assertions, pinned from FOUR
|
|
9
|
+
// angles so any future grade/template work that taxes the quick path fails CI
|
|
10
|
+
// here, by name, instead of quietly adding friction:
|
|
11
|
+
//
|
|
12
|
+
// 1. THE CAPTURE CONTRACT — captureIdea takes the same fields it always took,
|
|
13
|
+
// title is the only requirement, and its INSERT touches idea_inbox alone:
|
|
14
|
+
// ZERO statements against ideas_grades. A quick idea is quick because no
|
|
15
|
+
// grade row exists (the R140 design), not because one was written.
|
|
16
|
+
// 2. THE ROUTE SCHEMA — POST /inbox's validator declares exactly the pre-grade
|
|
17
|
+
// field set. No grade / template_key / field_values key exists in it, so no
|
|
18
|
+
// client is asked for (or may smuggle) grade-era input on the quick path.
|
|
19
|
+
// 3. THE READ — an idea with no grade row reads back grade='quick' with null
|
|
20
|
+
// template/score, via the LEFT JOIN + COALESCE (idea_grades.mjs owns the
|
|
21
|
+
// deep version of this; here it is asserted once as part of the contract).
|
|
22
|
+
// 4. THE PROMOTE — a triage promote writes idea_inbox verdict columns only;
|
|
23
|
+
// it neither reads nor writes ideas_grades.
|
|
24
|
+
//
|
|
25
|
+
// DB-free, the tests/idea_grades.mjs way: inbox.js destructures `pool` off the
|
|
26
|
+
// doorway at require time, so patching pool.query BEFORE requiring it
|
|
27
|
+
// intercepts every statement.
|
|
28
|
+
//
|
|
29
|
+
// Run: node tests/idea_quick_guard.mjs
|
|
30
|
+
|
|
31
|
+
import { strict as assert } from 'node:assert';
|
|
32
|
+
import { createRequire } from 'node:module';
|
|
33
|
+
import fs from 'node:fs';
|
|
34
|
+
import path from 'node:path';
|
|
35
|
+
import { fileURLToPath } from 'node:url';
|
|
36
|
+
|
|
37
|
+
const require = createRequire(import.meta.url);
|
|
38
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
|
|
40
|
+
const api = require('../src/module-api.js');
|
|
41
|
+
|
|
42
|
+
let queries = [];
|
|
43
|
+
let nextRows = [];
|
|
44
|
+
api.pool.query = async (sql, params = []) => {
|
|
45
|
+
queries.push({ sql, params });
|
|
46
|
+
return { rows: nextRows };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const inbox = require('../modules/ideas/inbox.js');
|
|
50
|
+
|
|
51
|
+
let passed = 0;
|
|
52
|
+
let failed = 0;
|
|
53
|
+
async function test(name, fn) {
|
|
54
|
+
queries = [];
|
|
55
|
+
nextRows = [{ id: '41', title: 'an idea', status: 'open', grade: 'quick' }];
|
|
56
|
+
try { await fn(); passed++; console.log(` ok ${name}`); }
|
|
57
|
+
catch (err) { failed++; console.error(` FAIL ${name}\n ${err.message}`); }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const grades = (q) => q.filter((x) => /ideas_grades/i.test(x.sql));
|
|
61
|
+
|
|
62
|
+
// ── 1. the capture contract ─────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
await test('capture: title alone is enough — the same minimal call as before the grade era', async () => {
|
|
65
|
+
await inbox.captureIdea({ title: 'just a thought', capturedBy: '7' });
|
|
66
|
+
assert.equal(queries.length, 1, 'one statement, the idea INSERT');
|
|
67
|
+
assert.match(queries[0].sql, /INSERT INTO idea_inbox/);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await test('capture: writes ZERO ideas_grades statements — quick is the absence of a row', async () => {
|
|
71
|
+
await inbox.captureIdea({
|
|
72
|
+
title: 'a fully-optioned quick idea',
|
|
73
|
+
bodyMd: 'with a body',
|
|
74
|
+
kind: 'feature',
|
|
75
|
+
suggestedVersion: 'BONGOS-V1',
|
|
76
|
+
suggestedPriority: 3,
|
|
77
|
+
capturedBy: '7',
|
|
78
|
+
});
|
|
79
|
+
assert.equal(grades(queries).length, 0, 'no grade row is written or read at capture');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await test('capture: accepts no grade-era argument — an unknown field is simply not a parameter', async () => {
|
|
83
|
+
// captureIdea's signature is destructured; passing grade-era keys must change
|
|
84
|
+
// nothing about what runs (they are not smuggled into SQL params).
|
|
85
|
+
await inbox.captureIdea({ title: 't', capturedBy: '7', grade: 'full', field_values: { purpose: 'x' } });
|
|
86
|
+
assert.equal(queries.length, 1);
|
|
87
|
+
const flat = JSON.stringify(queries[0].params);
|
|
88
|
+
assert.doesNotMatch(flat, /purpose/, 'grade-era keys never reach the INSERT params');
|
|
89
|
+
assert.equal(grades(queries).length, 0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await test('capture: the title requirement is the SAME refusal it always was', async () => {
|
|
93
|
+
try { await inbox.captureIdea({ capturedBy: '7' }); assert.fail('should refuse'); }
|
|
94
|
+
catch (err) { assert.equal(err.code, 'TITLE_REQUIRED'); }
|
|
95
|
+
assert.equal(queries.length, 0, 'refused before any query');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ── 2. the route schema ─────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
await test('POST /inbox declares the pre-grade field set — nothing grade-era, nothing newly required', () => {
|
|
101
|
+
const src = fs.readFileSync(path.join(__dirname, '..', 'modules', 'ideas', 'routes', 'inbox.js'), 'utf8');
|
|
102
|
+
const post = src.slice(src.indexOf("router.post('/inbox'"), src.indexOf('captureIdea('));
|
|
103
|
+
const schema = post.slice(post.indexOf('{', post.indexOf('validateOrRespond')), post.indexOf('})') + 1);
|
|
104
|
+
for (const key of ['title', 'body_md', 'kind', 'suggested_version', 'suggested_priority']) {
|
|
105
|
+
assert.ok(schema.includes(key), `the quick path still accepts ${key}`);
|
|
106
|
+
}
|
|
107
|
+
assert.doesNotMatch(schema, /grade|template|field_values|completeness/,
|
|
108
|
+
'no grade-era key in the quick capture schema');
|
|
109
|
+
const required = [...schema.matchAll(/(\w+):\s*\{[^}]*required:\s*true/g)].map((m) => m[1]);
|
|
110
|
+
assert.deepEqual(required, ['title'], 'title is the ONLY required field, as it always was');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ── 3. the read ──────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
await test('read: an ungraded idea comes back grade=quick with null template/score', async () => {
|
|
116
|
+
nextRows = [{ id: '41', title: 'an idea', status: 'open', grade: 'quick', template_key: null, field_values: null, completeness_score: null }];
|
|
117
|
+
const idea = await inbox.getIdea('41');
|
|
118
|
+
assert.equal(idea.grade, 'quick');
|
|
119
|
+
assert.equal(idea.template_key, null);
|
|
120
|
+
assert.equal(idea.completeness_score, null);
|
|
121
|
+
assert.match(queries[0].sql, /LEFT JOIN ideas_grades/, 'the read is a LEFT JOIN — absence reads as quick');
|
|
122
|
+
assert.match(queries[0].sql, /COALESCE\(g\.grade/, 'the COALESCE is what defaults it');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// ── 4. the promote ───────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
await test('promote: mutates idea_inbox only — the grade table is read (one CTE), never written', async () => {
|
|
128
|
+
nextRows = [{ id: '41', title: 'an idea', status: 'promoted', promoted_to_task_id: '900' }];
|
|
129
|
+
await inbox.updateIdea('41', { status: 'promoted', promoted_to_task_id: '900' });
|
|
130
|
+
assert.ok(queries.length >= 1);
|
|
131
|
+
for (const q of queries) {
|
|
132
|
+
// Reading the grade is CORRECT here (the CTE joins it so an already-sparked
|
|
133
|
+
// idea is not misreported as quick); what the guard forbids is any MUTATION
|
|
134
|
+
// of the grade table riding along with a triage verdict.
|
|
135
|
+
assert.doesNotMatch(q.sql, /UPDATE\s+ideas_grades|INSERT\s+INTO\s+ideas_grades|DELETE\s+FROM\s+ideas_grades/i,
|
|
136
|
+
'promote must not mutate the grade table');
|
|
137
|
+
}
|
|
138
|
+
assert.ok(queries.some((q) => /UPDATE idea_inbox/i.test(q.sql)), 'the promote is the same idea_inbox UPDATE');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
console.log(`\nidea_quick_guard: ${passed} passed, ${failed} failed`);
|
|
142
|
+
if (failed > 0) process.exit(1);
|