@bongos/core 1.19.825 → 1.19.826

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,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.825",
6
- "core_contract": "1.19.825",
7
- "source_commit": "cb729764661db914505acde16e83bf6db16e47fb",
5
+ "core_version": "1.19.826",
6
+ "core_contract": "1.19.826",
7
+ "source_commit": "9871f72043eb86daa58427159fe170b869c6f8b9",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-17T13:27:19.562Z",
9
+ "built_at": "2026-09-17T13:41:33.888Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 491,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 2751,
20
- "tree_sha256": "d080ea1d5501cd02f6af8cde89d25b58442a1abf7d282379bbf683df5b00ee33",
20
+ "tree_sha256": "32197a053d73c55f5800a78d0d66c04463154f6439996ac03cd6079f1e5ca82c",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2552,7 +2552,7 @@
2552
2552
  {
2553
2553
  "path": "docs/module-api-changelog.md",
2554
2554
  "mode": "0000644",
2555
- "sha256": "bc4fb1f646f00a5ac5870be4468a5bcb94c1bc19d6c69eea48c54d63f5d0eb70"
2555
+ "sha256": "819da3ee2c3e79b97d42df736f3bd9af57baffa1d21234532b611acf42a878ac"
2556
2556
  },
2557
2557
  {
2558
2558
  "path": "docs/modules-contract.md",
@@ -4867,7 +4867,7 @@
4867
4867
  {
4868
4868
  "path": "modules/hall-ui/public/goal-inbox.js",
4869
4869
  "mode": "0000644",
4870
- "sha256": "0e6aacc6e720d1865c45f71b2458f23c5bac8f1643082256f4d7e71d97078994"
4870
+ "sha256": "86aefc97c267f860c931ec000ace7a0a4a4e7e56796891eb5c3fed893446dae5"
4871
4871
  },
4872
4872
  {
4873
4873
  "path": "modules/hall-ui/public/goals-lib.js",
@@ -7817,12 +7817,12 @@
7817
7817
  {
7818
7818
  "path": "package-lock.json",
7819
7819
  "mode": "0000644",
7820
- "sha256": "2a7a81e9c97e32cc83b22d74eef060a6829d4e6847dfabaa9a851ebff576b151"
7820
+ "sha256": "639ff75dd31684341558f62220e1a7d358484ab8f994ebe9cc036335f2c7e5bb"
7821
7821
  },
7822
7822
  {
7823
7823
  "path": "package.json",
7824
7824
  "mode": "0000644",
7825
- "sha256": "67e42f1c6c167a0857846b43f877395adc67ce48ae72c31cc4709240906e6432"
7825
+ "sha256": "3f63f0df3e2a39105a2d73c03f418e48b72b303d0aa3d2acd3f34ea8a81c738c"
7826
7826
  },
7827
7827
  {
7828
7828
  "path": "public-docs/index.html",
@@ -8752,7 +8752,7 @@
8752
8752
  {
8753
8753
  "path": "scripts/gds/provision.js",
8754
8754
  "mode": "0000644",
8755
- "sha256": "b81947c37dccac487771865a699ab0a581b75b1991e0e76b07ce2a1877db414c"
8755
+ "sha256": "d5a8fdc3b79685826c52c9092a6b512c556f0f992c4fc799f5a8e60012f296ab"
8756
8756
  },
8757
8757
  {
8758
8758
  "path": "scripts/gds/publish-credential-check.js",
@@ -9742,7 +9742,7 @@
9742
9742
  {
9743
9743
  "path": "src/module-api.js",
9744
9744
  "mode": "0000644",
9745
- "sha256": "8f04e8b51652109c0e95424fbc9801ed170b2bc899f91793ac827152e653c07a"
9745
+ "sha256": "204763d74db8bd35283f77922256bafe1885e1d173b36a730053190f6110501d"
9746
9746
  },
9747
9747
  {
9748
9748
  "path": "src/module-loader/catalog.js",
@@ -12527,7 +12527,7 @@
12527
12527
  {
12528
12528
  "path": "tests/provision.mjs",
12529
12529
  "mode": "0000644",
12530
- "sha256": "42fe144a388efe2f5da6068082138f96a18fb8bd209d9babb5b074b0311e8459"
12530
+ "sha256": "1c580b0f1ebd7eca67e7a603c5e10fe2a987e75f60de514dfaf9ac8aff826a12"
12531
12531
  },
12532
12532
  {
12533
12533
  "path": "tests/provision_disconnect.mjs",
@@ -12837,7 +12837,7 @@
12837
12837
  {
12838
12838
  "path": "tests/rot_inbox.mjs",
12839
12839
  "mode": "0000644",
12840
- "sha256": "48f53c806042de1886e9aed96b41ff7070bb91b93790cdd3c0c7f0a08be4cf1c"
12840
+ "sha256": "382ddf61536d7fb738d99127cee03fa06df8ca79e53f876f702676189d900a67"
12841
12841
  },
12842
12842
  {
12843
12843
  "path": "tests/route_permission_check.mjs",
@@ -2123,6 +2123,8 @@ is load-bearing: the script throws rather than guess if it is missing, and
2123
2123
  1.19.824 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2124
2124
  landed since 1.19.823 with no explicit bump. run 35220051893. (task 1002620)
2125
2125
  1.19.825 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2126
- landed since 1.19.824 with no explicit bump. run 35227106979. (task 1002620)
2126
+ landed since 1.19.824 with no explicit bump. run 35227529838. (task 1002620)
2127
+ 1.19.826 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2128
+ landed since 1.19.825 with no explicit bump. run 35228618989. (task 1002620)
2127
2129
  ---------------------------------------------------------------------------
2128
2130
  ```
@@ -242,12 +242,12 @@
242
242
  const who = r.filed_by_login ? ` · filed by ${escapeHtml(r.filed_by_login)}` : '';
243
243
  const goal = r.goal_title ? escapeHtml(r.goal_title) : `goal ${escapeHtml(String(r.goal_id))}`;
244
244
  return `<li class="ginbox__item" data-goal="${escapeHtml(String(r.goal_id))}">
245
- <div class="ginbox__main">
245
+ <div class="ginbox__body">
246
246
  <a class="ginbox__goal" href="/task/${encodeURIComponent(r.task_id)}">${title}</a>
247
247
  <span class="ginbox__sub">${goal}${who}</span>
248
248
  </div>
249
249
  ${kind}
250
- <div class="ginbox__acts">
250
+ <div class="ginbox__actions">
251
251
  <button class="btn-ghost" type="button" data-nod-task="${escapeHtml(String(r.task_id))}">Give the nod</button>
252
252
  </div>
253
253
  </li>`;
@@ -322,11 +322,11 @@
322
322
  + ` title="${escapeHtml(String(v.hint || ''))}">${escapeHtml(String(v.label || v.verb || '?'))}</button>`;
323
323
  }).join('');
324
324
  return `<li class="ginbox__item" data-rot-tier="${escapeHtml(String(r.tier))}">
325
- <div class="ginbox__main">
325
+ <div class="ginbox__body">
326
326
  <a class="ginbox__goal" href="${href}">${escapeHtml(r.title || `${r.tier} #${r.id}`)}</a>
327
327
  <span class="ginbox__sub">${escapeHtml(String(r.status || ''))} · ${where} · ${escapeHtml(untouchedFor(r.last_touched_at))}${again}</span>
328
328
  </div>
329
- <div class="ginbox__acts">${buttons}</div>
329
+ <div class="ginbox__actions">${buttons}</div>
330
330
  </li>`;
331
331
  }
332
332
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.825",
3
+ "version": "1.19.826",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.825",
9
+ "version": "1.19.826",
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.825",
3
+ "version": "1.19.826",
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",
@@ -659,6 +659,20 @@ async function releaseAbandonedDomains(inst, { controlExec, log, apply, db, prov
659
659
  }
660
660
  let firstFailed = null;
661
661
  for (const host of hosts) {
662
+ // task 1003954: the route frees a name once this row moves off it, so another
663
+ // project may have attached it since — parking it now would blackhole THAT
664
+ // project. A name we cannot check is kept for the next run, never parked on a guess.
665
+ let holder = null;
666
+ try { holder = provisioning.getInstanceByDomain ? await provisioning.getInstanceByDomain(db, host) : null; }
667
+ catch (e) {
668
+ log(` ⚠ could not check who holds ${host}: ${(e && e.message) || e} — left for the next run`);
669
+ if (!firstFailed) firstFailed = host;
670
+ continue;
671
+ }
672
+ if (holder && String(holder.id) !== String(inst.id) && holder.status !== 'torn_down') {
673
+ log(` [dns] leave ${host} alone — ${holder.slug} holds it now`);
674
+ continue;
675
+ }
662
676
  const relEnv = dnsReleaseEnv(host);
663
677
  log(` [dns] release ${host} -> ${relEnv.BOX_IP} (blackhole, zone ${relEnv.CLOUDFLARE_ZONE}) via ${CONFIG.dnsHook}`);
664
678
  if (!apply) continue;
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.825'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.826'; // 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');
@@ -2010,6 +2010,48 @@ await ta('domainAttachInstance(--apply): a release the hook REFUSES keeps the qu
2010
2010
  } finally { P.CONFIG.publicIp = savedPub; P.CONFIG.dnsHook = savedHook; }
2011
2011
  });
2012
2012
 
2013
+ // task 1003954: the route frees a BYO name the moment its row moves off it, so
2014
+ // another project can attach it before this row's release drains.
2015
+ await ta('domainAttachInstance(--apply): an address ANOTHER live project now holds is never parked — and the queue still clears', async () => {
2016
+ const savedPub = P.CONFIG.publicIp, savedHook = P.CONFIG.dnsHook;
2017
+ P.CONFIG.publicIp = '203.0.113.5';
2018
+ P.CONFIG.dnsHook = '/opt/infra/box-dns-cloudflare.sh';
2019
+ try {
2020
+ const { deps, cmds, envs, cleared } = attachRecorder({ apply: true });
2021
+ const holders = {
2022
+ 'typo.example.com': { id: 42, slug: 'other', status: 'active' },
2023
+ 'gone.example.com': { id: 43, slug: 'retired', status: 'torn_down' },
2024
+ };
2025
+ deps.provisioning.getInstanceByDomain = async (_db, host) => holders[host] || null;
2026
+ const r = await P.domainAttachInstance(
2027
+ { ...activeStandalone, released_domains: ['TYPO.example.com', 'gone.example.com', 'free.example.com'] }, deps);
2028
+ assert.equal(r.ok, true);
2029
+ assert.equal(r.releaseFailed, undefined, 'a name someone else took is not a failed release');
2030
+ const parked = cmds.map((c, i) => i)
2031
+ .filter((i) => cmds[i] === '/opt/infra/box-dns-cloudflare.sh' && envs[i].BOX_IP === '192.0.2.1')
2032
+ .map((i) => envs[i].BOX_HOSTNAME);
2033
+ assert.deepEqual(parked, ['gone.example.com', 'free.example.com'],
2034
+ "the live holder's address is left alone; a torn-down holder's and a free one are parked");
2035
+ assert.deepEqual(cleared, [18], 'the queue clears — the skipped name is no longer this row\'s to release');
2036
+ } finally { P.CONFIG.publicIp = savedPub; P.CONFIG.dnsHook = savedHook; }
2037
+ });
2038
+
2039
+ await ta('domainAttachInstance(--apply): a holder lookup that FAILS parks nothing for that name and keeps the queue', async () => {
2040
+ const savedPub = P.CONFIG.publicIp, savedHook = P.CONFIG.dnsHook;
2041
+ P.CONFIG.publicIp = '203.0.113.5';
2042
+ P.CONFIG.dnsHook = '/opt/infra/box-dns-cloudflare.sh';
2043
+ try {
2044
+ const { deps, cmds, envs, cleared } = attachRecorder({ apply: true });
2045
+ deps.provisioning.getInstanceByDomain = async () => { throw new Error('connection reset'); };
2046
+ const r = await P.domainAttachInstance({ ...activeStandalone, released_domains: ['typo.example.com'] }, deps);
2047
+ assert.equal(r.ok, true, 'a failed release must never fail a re-point that already landed');
2048
+ assert.equal(r.releaseFailed, 'typo.example.com');
2049
+ assert.ok(!envs.some((e, i) => cmds[i] === '/opt/infra/box-dns-cloudflare.sh' && e.BOX_IP === '192.0.2.1'),
2050
+ 'an unknown holder is never blackholed on a guess');
2051
+ assert.deepEqual(cleared, [], 'the queue survives, so the next run asks again');
2052
+ } finally { P.CONFIG.publicIp = savedPub; P.CONFIG.dnsHook = savedHook; }
2053
+ });
2054
+
2013
2055
  await ta('domainAttachInstance(--apply): no DNS hook → the release is named for the operator, and the queue is kept', async () => {
2014
2056
  const savedHook = P.CONFIG.dnsHook; P.CONFIG.dnsHook = '';
2015
2057
  try {
@@ -48,6 +48,7 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
48
48
  const MIGRATION_SRC = readFileSync(join(ROOT, 'migrations/core_231_project_settings_and_attention_water.sql'), 'utf8');
49
49
  const TASK_LIB_SRC = readFileSync(join(ROOT, 'modules/hall-ui/public/task-lib.js'), 'utf8');
50
50
  const GOAL_INBOX_SRC = readFileSync(join(ROOT, 'modules/hall-ui/public/goal-inbox.js'), 'utf8');
51
+ const STYLE_SRC = readFileSync(join(ROOT, 'modules/hall-ui/public/style.css'), 'utf8');
51
52
 
52
53
  // ---- the recording fake pool ----------------------------------------------
53
54
  let queued = [];
@@ -602,6 +603,19 @@ test('the client renders SERVER-declared verbs rather than a hardcoded button li
602
603
  assert.match(GOAL_INBOX_SRC, /data-rot-path=/, 'each button carries the server-declared path');
603
604
  });
604
605
 
606
+ test('row builders emit the CSS classes style.css actually defines, not ginbox__main/ginbox__acts (task 1003284)', () => {
607
+ assert.match(STYLE_SRC, /\.ginbox__body\s*\{/, 'style.css must define .ginbox__body');
608
+ assert.match(STYLE_SRC, /\.ginbox__actions\s*\{/, 'style.css must define .ginbox__actions');
609
+ assert.doesNotMatch(GOAL_INBOX_SRC, /ginbox__main\b/,
610
+ '.ginbox__main is undefined in style.css — with it the row title and sub-line run together inline instead of stacking');
611
+ assert.doesNotMatch(GOAL_INBOX_SRC, /ginbox__acts\b/,
612
+ '.ginbox__acts is undefined in style.css — with it the action button loses its flex sizing');
613
+ assert.match(GOAL_INBOX_SRC, /function nodRowHtml[\s\S]*?class="ginbox__body"[\s\S]*?class="ginbox__actions"/,
614
+ 'nodRowHtml must emit the defined ginbox__body/ginbox__actions classes');
615
+ assert.match(GOAL_INBOX_SRC, /function rotRowHtml[\s\S]*?class="ginbox__body"[\s\S]*?class="ginbox__actions"/,
616
+ 'rotRowHtml must emit the defined ginbox__body/ginbox__actions classes');
617
+ });
618
+
605
619
  test('acting on a rot line refreshes the ROT slice, not the goals list', () => {
606
620
  assert.match(GOAL_INBOX_SRC, /ROT_TOASTS\[verb\] \|\| 'Done\.', loadRot\)/,
607
621
  'refreshing the wrong slice leaves the acted-on row sitting there — the task-1003066 lesson');