@bongos/core 1.19.965 → 1.19.967

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 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.965",
6
- "core_contract": "1.19.965",
7
- "source_commit": "bbaec9cc0649177ca52efa5afa2cea91b9fead9b",
5
+ "core_version": "1.19.967",
6
+ "core_contract": "1.19.967",
7
+ "source_commit": "7f7f99034d971eec4bc9d9741d516decfb6c1f14",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-22T22:06:32.886Z",
9
+ "built_at": "2026-09-22T23:07:17.959Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 515,
13
13
  "agent_docs_stubbed": 26,
14
- "functional_verbatim": 2364,
14
+ "functional_verbatim": 2365,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2905,
20
- "tree_sha256": "686c7cbaf16a9143fa9e322b03a8c6dc0fe6d16fa76848b028507a7f528f295f",
19
+ "file_count": 2906,
20
+ "tree_sha256": "ddb385e48e6ccbb09169729f409babe82f980fd1d8ee8cee5855194cf955839a",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2697,7 +2697,7 @@
2697
2697
  {
2698
2698
  "path": "docs/module-api-changelog.md",
2699
2699
  "mode": "0000644",
2700
- "sha256": "f84a2fba932e6e634773c3245f5dbe12f370f42ed6285a2e74546227024b07b5"
2700
+ "sha256": "c998e897524293549e03bffbecdabeeecc2ba7f7cd327bdb96f24ce88a7469cc"
2701
2701
  },
2702
2702
  {
2703
2703
  "path": "docs/modules-contract.md",
@@ -8102,12 +8102,12 @@
8102
8102
  {
8103
8103
  "path": "package-lock.json",
8104
8104
  "mode": "0000644",
8105
- "sha256": "1eb1f4122d6de764110bb2d783bd0c79d3727dff6663d23917edb602a081774f"
8105
+ "sha256": "81ffeea318f60a6abfde034d31d70812372b1e7bc17fad2005f777a0eb759530"
8106
8106
  },
8107
8107
  {
8108
8108
  "path": "package.json",
8109
8109
  "mode": "0000644",
8110
- "sha256": "58625b2583bfd11c6972959c530409e6c63085cc2de47e9f6278bec7c767b9db"
8110
+ "sha256": "bbc2cda2140d830f4faf271399ac8c1c1df06f84781262f849598451861d1c30"
8111
8111
  },
8112
8112
  {
8113
8113
  "path": "public-docs/index.html",
@@ -10112,7 +10112,7 @@
10112
10112
  {
10113
10113
  "path": "src/module-api.js",
10114
10114
  "mode": "0000644",
10115
- "sha256": "a71508cbe155a0041db078c9a66c9e78cc578163d0056ae2cf12c9aa2bcc2df0"
10115
+ "sha256": "5ffee651905384e38b5eba0d85dd725dd3f54b7c3c57fe5de6971e2daa78ba7d"
10116
10116
  },
10117
10117
  {
10118
10118
  "path": "src/module-loader/catalog.js",
@@ -12529,6 +12529,11 @@
12529
12529
  "mode": "0000644",
12530
12530
  "sha256": "cf0d082fad9bd7fdb3071016fac252fd59e5d24cec8280aab72c760c416d1482"
12531
12531
  },
12532
+ {
12533
+ "path": "tests/liveness_notify.mjs",
12534
+ "mode": "0000644",
12535
+ "sha256": "8bd62820c232cae7346129c9d54aa9b56e3e50ed0d7488b238993c78c004c55e"
12536
+ },
12532
12537
  {
12533
12538
  "path": "tests/llm-cache-route.mjs",
12534
12539
  "mode": "0000644",
@@ -2404,5 +2404,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
2404
2404
  landed since 1.19.963 with no explicit bump. run 35787848112. (task 1002620)
2405
2405
  1.19.965 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2406
2406
  landed since 1.19.964 with no explicit bump. run 35790478196. (task 1002620)
2407
+ 1.19.966 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2408
+ landed since 1.19.965 with no explicit bump. run 35793400408. (task 1002620)
2409
+ 1.19.967 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2410
+ landed since 1.19.966 with no explicit bump. run 35795807836. (task 1002620)
2407
2411
  ---------------------------------------------------------------------------
2408
2412
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.965",
3
+ "version": "1.19.967",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.965",
9
+ "version": "1.19.967",
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.965",
3
+ "version": "1.19.967",
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
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.965'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.967'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -0,0 +1,204 @@
1
+ // tests/liveness_notify.mjs
2
+ //
3
+ // The three rules that keep the liveness heartbeat from becoming a siren
4
+ // (task 1004151; the rules themselves are task 1002894's, from idea 1000703).
5
+ //
6
+ // WHY THIS FILE EXISTS AT ALL. `modules/provisioning/pollers/liveness-notify.js` decides
7
+ // what reaches the owner's Discord when a project stops answering, and until now none of
8
+ // its rules had a test. Its CLAUDE.md row cited `tests/liveness-notify.mjs` as its proof
9
+ // and **that file had never existed** — `git log --all --diff-filter=A` for it is empty,
10
+ // and nothing under `tests/` named `DOWN_CONFIRM`, `FLEET_GUARD` or `noticeText`. Found
11
+ // while building the sibling poller under task 1004000.
12
+ //
13
+ // THE REASON IT MATTERS MORE THAN AN ORDINARY COVERAGE GAP: all three rules fail
14
+ // SILENTLY, and toward noise or silence rather than error. A broken edge check
15
+ // re-announces every outage on every control-plane restart. A broken confirm streak turns
16
+ // one refused connection into an owner-facing alarm. A broken collapse turns a
17
+ // prober-side blip into one message per project. Each of those reaches a human's Discord
18
+ // and none of them throws, so nothing but an assertion can catch them.
19
+ //
20
+ // EVERY CASE HERE WAS CHECKED BY DELETING THE RULE AND WATCHING IT GO RED — a guard is
21
+ // not finished until its test has been seen to fail. The mutation each case survives is
22
+ // named in its own comment, so a later reader can re-run the experiment instead of
23
+ // trusting this sentence.
24
+ //
25
+ // `planNotices` is pure over (observations, state) and mutates only the Map it is handed,
26
+ // so none of this needs a sweep, a database or a network.
27
+ //
28
+ // Run: node tests/liveness_notify.mjs
29
+
30
+ import { strict as assert } from 'node:assert';
31
+ import { createRequire } from 'node:module';
32
+
33
+ const require = createRequire(import.meta.url);
34
+ const { planNotices, noticeText, DOWN_CONFIRM, FLEET_GUARD } = require('../modules/provisioning/pollers/liveness-notify.js');
35
+
36
+ let passed = 0, failed = 0;
37
+ function t(name, fn) {
38
+ try { fn(); passed++; console.log(` PASS ${name}`); }
39
+ catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
40
+ }
41
+
42
+ // An observation as the sweep builds it: { id, slug, isPublic, up, prevUp }. `prevUp` is
43
+ // what recordLivenessProbe returned as the row's PRIOR verdict — the only place a fall is
44
+ // visible, which is what the edge rule is built on.
45
+ const obs = (over = {}) => ({ id: 1, slug: 'lantern', isPublic: true, up: false, prevUp: true, ...over });
46
+
47
+ // Drive N consecutive identical sweeps over one state Map and collect every notice.
48
+ function ticks(n, observations, state = new Map(), opts) {
49
+ const all = [];
50
+ for (let i = 0; i < n; i++) all.push(...planNotices(observations, state, opts).notices);
51
+ return { notices: all, state };
52
+ }
53
+
54
+ console.log('rule 1 — EDGE, not state:');
55
+
56
+ t('a project already down when the process started is NEVER announced, however long it stays down', () => {
57
+ // The rule: a fall is visible only in `prevUp`, so a row that was already down before
58
+ // this process began watching has no edge to see. Without it, every control-plane
59
+ // restart re-announces every ongoing outage — the exact siren this is built to avoid.
60
+ //
61
+ // MUTATION CHECKED: deleting the `else continue` arm (so an absent prev starts a streak
62
+ // of 1) makes this case fail on the second tick.
63
+ const { notices, state } = ticks(5, [obs({ prevUp: false })]);
64
+ assert.deepEqual(notices, [], 'no notice, no matter how many sweeps see it down');
65
+ assert.equal(state.size, 0, 'and it is not even tracked — there is nothing to announce later');
66
+ });
67
+
68
+ t('a null prevUp — a row never probed before — is also not an edge', () => {
69
+ // recordLivenessProbe returns null for a row with no prior verdict. That is "unknown",
70
+ // not "was up", and reading it as a fall would announce every brand-new project that
71
+ // happens to be mid-standup.
72
+ const { notices } = ticks(3, [obs({ prevUp: null })]);
73
+ assert.deepEqual(notices, []);
74
+ });
75
+
76
+ t('a fall OBSERVED in this process is announced — the edge rule is not just "stay quiet"', () => {
77
+ // The control for the two cases above: the same project, with prevUp true, does reach a
78
+ // notice. Without this, "announces nothing ever" would pass the edge tests too.
79
+ const { notices } = ticks(DOWN_CONFIRM, [obs({ prevUp: true })]);
80
+ assert.equal(notices.length, 1);
81
+ assert.equal(notices[0].kind, 'down');
82
+ });
83
+
84
+ console.log('\nrule 2 — CONFIRM before speaking:');
85
+
86
+ t('one down probe followed by a recovery says NOTHING — a blip costs nothing', () => {
87
+ // The rule: wait DOWN_CONFIRM consecutive sweeps. One refused connection is not an
88
+ // outage, and a project that heals in between was never announced, so nothing needs
89
+ // unsaying.
90
+ //
91
+ // MUTATION CHECKED: lowering the gate to `streak >= 1` makes this case fail — it posts
92
+ // a down, and then an all-clear for an outage nobody experienced.
93
+ const state = new Map();
94
+ const first = planNotices([obs({ up: false, prevUp: true })], state);
95
+ assert.deepEqual(first.notices, [], 'the first down is not yet news');
96
+ const healed = planNotices([obs({ up: true })], state);
97
+ assert.deepEqual(healed.notices, [], 'and the recovery is not news either, because the fall never was');
98
+ assert.equal(state.size, 0, 'the streak is dropped, so a later fall starts clean');
99
+ });
100
+
101
+ t('two consecutive downs post EXACTLY ONE message, and staying down adds none', () => {
102
+ // MUTATION CHECKED: dropping the `notified` latch makes the third tick post again.
103
+ const { notices, state } = ticks(6, [obs()]);
104
+ assert.equal(notices.length, 1, `one message for one outage, got ${notices.length}`);
105
+ assert.equal(notices[0].kind, 'down');
106
+ assert.equal(state.get(1).notified, true, 'the latch is what keeps it quiet afterwards');
107
+ });
108
+
109
+ t('DOWN_CONFIRM is honoured as configured, not hardcoded at two', () => {
110
+ // The threshold is an env knob; a test that pinned "2" would pass on a deployment that
111
+ // had raised it and was therefore silent for longer than anyone expected.
112
+ const state = new Map();
113
+ for (let i = 1; i < DOWN_CONFIRM; i++) {
114
+ assert.deepEqual(planNotices([obs()], state).notices, [], `silent at ${i} of ${DOWN_CONFIRM}`);
115
+ }
116
+ assert.equal(planNotices([obs()], state).notices.length, 1, `speaks at ${DOWN_CONFIRM}`);
117
+ });
118
+
119
+ console.log('\nrecovery is scoped to what was actually announced:');
120
+
121
+ t('an all-clear fires ONLY where a down notice really went out', () => {
122
+ // MUTATION CHECKED: pushing to `ups` without the `prev.notified` guard makes the blip
123
+ // case above emit an unpaired "is answering again" for an outage never reported.
124
+ const state = new Map();
125
+ ticks(DOWN_CONFIRM, [obs()], state); // announced
126
+ const back = planNotices([obs({ up: true })], state);
127
+ assert.equal(back.notices.length, 1);
128
+ assert.equal(back.notices[0].kind, 'up');
129
+ assert.equal(state.size, 0, 'and the row is forgotten, so a later fall is a fresh edge');
130
+ assert.deepEqual(planNotices([obs({ up: true })], state).notices, [], 'the all-clear is not repeated');
131
+ });
132
+
133
+ console.log('\nrule 3 — COLLAPSE a fan-out:');
134
+
135
+ t('FLEET_GUARD simultaneous falls become ONE message that names no project', () => {
136
+ // The rule: when several projects fall in the same sweep the cause is almost always on
137
+ // this side — a control-plane restart, a prober-side blip — so one message says that,
138
+ // rather than N messages that read like N independent faults.
139
+ //
140
+ // MUTATION CHECKED: returning `rows.map(...)` unconditionally from collapse() makes this
141
+ // case fail with FLEET_GUARD separate notices.
142
+ const many = Array.from({ length: FLEET_GUARD }, (_, i) => obs({ id: i + 1, slug: `p${i + 1}` }));
143
+ const state = new Map();
144
+ let out;
145
+ for (let i = 0; i < DOWN_CONFIRM; i++) out = planNotices(many, state);
146
+ assert.equal(out.notices.length, 1, `one collapsed message, got ${out.notices.length}`);
147
+ assert.equal(out.notices[0].ids.length, FLEET_GUARD, 'it carries every id');
148
+ assert.equal(out.suppressed, FLEET_GUARD, 'and reports how many it stood in for');
149
+ for (const o of many) {
150
+ assert.ok(!out.notices[0].text.includes(o.slug), `a collapsed message must not name ${o.slug}`);
151
+ }
152
+ assert.match(out.notices[0].text, /usually the platform's own check/, 'it says where to look first');
153
+ });
154
+
155
+ t('one BELOW the guard stays per-project, so the collapse is a threshold and not the default', () => {
156
+ const few = Array.from({ length: FLEET_GUARD - 1 }, (_, i) => obs({ id: i + 1, slug: `p${i + 1}` }));
157
+ const state = new Map();
158
+ let out;
159
+ for (let i = 0; i < DOWN_CONFIRM; i++) out = planNotices(few, state);
160
+ assert.equal(out.notices.length, FLEET_GUARD - 1, 'one message each');
161
+ assert.equal(out.suppressed, 0);
162
+ assert.ok(out.notices.every((n) => n.ids.length === 1));
163
+ });
164
+
165
+ console.log('\nthe privacy rule (the one with a consequence):');
166
+
167
+ t('a project that is not public on the map is DESCRIBED, never named', () => {
168
+ // A private project's slug must not ride a Discord channel. This is the rule in this
169
+ // file whose failure is a disclosure rather than noise.
170
+ //
171
+ // MUTATION CHECKED: passing `r.slug` instead of `r.isPublic ? r.slug : null` in
172
+ // collapse() makes this case fail with the slug in the text.
173
+ const state = new Map();
174
+ let out;
175
+ for (let i = 0; i < DOWN_CONFIRM; i++) out = planNotices([obs({ isPublic: false, slug: 'secret-thing' })], state);
176
+ assert.equal(out.notices.length, 1);
177
+ assert.ok(!out.notices[0].text.includes('secret-thing'), 'the slug must never appear');
178
+ assert.match(out.notices[0].text, /A private project/, 'it is described instead');
179
+ // The recovery half has the same obligation — it is a second message about the same row.
180
+ const back = planNotices([obs({ isPublic: false, slug: 'secret-thing', up: true })], state);
181
+ assert.ok(!back.notices[0].text.includes('secret-thing'), 'and the all-clear must not leak it either');
182
+ });
183
+
184
+ t('a public project IS named — the control for the rule above', () => {
185
+ const state = new Map();
186
+ let out;
187
+ for (let i = 0; i < DOWN_CONFIRM; i++) out = planNotices([obs({ isPublic: true, slug: 'lantern' })], state);
188
+ assert.match(out.notices[0].text, /lantern/);
189
+ });
190
+
191
+ console.log('\nthe copy:');
192
+
193
+ t('noticeText makes no wall-clock promise, and says what happens next', () => {
194
+ // The owner is being told something is wrong; a message that promises "back shortly"
195
+ // is a commitment nothing here can keep.
196
+ const down = noticeText({ kind: 'down', name: 'lantern' });
197
+ assert.doesNotMatch(down, /shortly|briefly|minute|second|soon\b/i);
198
+ assert.match(down, /say so here when the project answers again/, 'it says what the platform will do');
199
+ assert.match(noticeText({ kind: 'up', name: 'lantern' }), /answering again/);
200
+ assert.match(noticeText({ kind: 'up', count: 4 }), /All 4 projects/, 'the collapsed all-clear counts them');
201
+ });
202
+
203
+ console.log(`\nliveness_notify: ${passed} passed, ${failed} failed`);
204
+ if (failed) process.exit(1);