@bongos/core 1.19.308 → 1.19.310

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.308",
6
- "core_contract": "1.19.308",
7
- "source_commit": "73400ffdb3672b797a1dda57f589d9cc43da2ed1",
5
+ "core_version": "1.19.310",
6
+ "core_contract": "1.19.310",
7
+ "source_commit": "5b818fdd0299678fb8ba3d222dd9f916281057ea",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-23T20:30:08.216Z",
9
+ "built_at": "2026-08-23T20:40:41.952Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 322,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 1764,
20
- "tree_sha256": "bfea309c25b9ba974096b8f0572e268ea80238aaf00e23d700adeb9e58458d49",
20
+ "tree_sha256": "a15e680234e3ff733027b28fadc46849add8160eec5afcf7b7c3d5dc2509a9fb",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -1417,12 +1417,12 @@
1417
1417
  {
1418
1418
  "path": "docs/api-reference.md",
1419
1419
  "mode": "0000644",
1420
- "sha256": "d545aada2bc0127be904c5df12c393c3ab8756f84ef86cc7bcee1348074769f9"
1420
+ "sha256": "a654d9def4780d3210c66031193198f58fd0d7d84f5088c4ce7e6d0a58682193"
1421
1421
  },
1422
1422
  {
1423
1423
  "path": "docs/api/openapi.json",
1424
1424
  "mode": "0000644",
1425
- "sha256": "828558b3bd6cc8d7c9c720eef962e098e4f886ab33360833b2c6d3946acbd1d1"
1425
+ "sha256": "e0b531f19187a3c00a54917bff4e2827a75b250f1aaa761229660a974a4f6217"
1426
1426
  },
1427
1427
  {
1428
1428
  "path": "docs/architecture.md",
@@ -3777,7 +3777,7 @@
3777
3777
  {
3778
3778
  "path": "modules/ideas/routes/blockers.js",
3779
3779
  "mode": "0000644",
3780
- "sha256": "7c61821b9ddf9a9255f6a9916659140881a6436dbd50f3d53b4cf818640a39a4"
3780
+ "sha256": "2247bb4499e6296fca8ccde3614b6726a50435786fc3cf634eaacd35982ef3f4"
3781
3781
  },
3782
3782
  {
3783
3783
  "path": "modules/ideas/routes/inbox.js",
@@ -3927,7 +3927,7 @@
3927
3927
  {
3928
3928
  "path": "modules/lifecycle/routes/done-when.js",
3929
3929
  "mode": "0000644",
3930
- "sha256": "7fb4a74237c1f84a5c20591663e634a7b190861e6a3f1c5804e22145ef58f6be"
3930
+ "sha256": "73dee3ef1546bf873a27bac8a160c8de64a6dbb5d0da6f3b68648d3a90e07d21"
3931
3931
  },
3932
3932
  {
3933
3933
  "path": "modules/lifecycle/routes/gate-approvals.js",
@@ -4577,12 +4577,12 @@
4577
4577
  {
4578
4578
  "path": "package-lock.json",
4579
4579
  "mode": "0000644",
4580
- "sha256": "4293f77940579b7c64fa5bc09f5a132c27efb5e4d21ee37a29ee3cf08d60ad88"
4580
+ "sha256": "0d120722614fdddf98aa543303d1c1fada639e93da07d75b87fdf257e59ae098"
4581
4581
  },
4582
4582
  {
4583
4583
  "path": "package.json",
4584
4584
  "mode": "0000644",
4585
- "sha256": "254eba7acbbc7ba707abfe41dd50f8e83880cf8f93cbe5daff2c88cf02dd46f1"
4585
+ "sha256": "8c05cbebb968c9d858ee8ca77cb58282f6a64c876096a11cc772aa08f330d2a6"
4586
4586
  },
4587
4587
  {
4588
4588
  "path": "public-docs/index.html",
@@ -5067,7 +5067,7 @@
5067
5067
  {
5068
5068
  "path": "scripts/gds/claude-materialize.js",
5069
5069
  "mode": "0000644",
5070
- "sha256": "e2d6f177e1ee5eda21b5d8711145b7de79b41a9f8dc1a1b9e2836dde2f1c0143"
5070
+ "sha256": "a044b7ea705a87e569f903eb4fb19c941a1fbfd11b069563a2bc767be339d184"
5071
5071
  },
5072
5072
  {
5073
5073
  "path": "scripts/gds/claude-md-template.js",
@@ -6162,7 +6162,7 @@
6162
6162
  {
6163
6163
  "path": "src/bongos/routes/healthz.js",
6164
6164
  "mode": "0000644",
6165
- "sha256": "da50a75349d36f7fee03c00b5f89697cf97fba91d4778fe304257d29470fedb5"
6165
+ "sha256": "26f4992c484d2fc5882131c695c4173db950e4c815424fad68385bfa64fa7ab8"
6166
6166
  },
6167
6167
  {
6168
6168
  "path": "src/bongos/routes/instance.js",
@@ -6197,7 +6197,7 @@
6197
6197
  {
6198
6198
  "path": "src/bongos/routes/public.js",
6199
6199
  "mode": "0000644",
6200
- "sha256": "fbfc3422f111e6d3f1429cefec78741d4a919c89985bc9d69f0a7a8e070c52b5"
6200
+ "sha256": "295b4bafc8a7bf832ed33fb0d35f4487a788caaff994383cda451cc24de2c6c7"
6201
6201
  },
6202
6202
  {
6203
6203
  "path": "src/bongos/routes/security.js",
@@ -6252,7 +6252,7 @@
6252
6252
  {
6253
6253
  "path": "src/module-api.js",
6254
6254
  "mode": "0000644",
6255
- "sha256": "87cec9eba20525297a298914c20d89882db138f0cdbc5c1ae4a5ce3d285147db"
6255
+ "sha256": "6d3b31b887cd58a7f28d9050d4f31da50bc0b82146921a175665397a6f36cc7f"
6256
6256
  },
6257
6257
  {
6258
6258
  "path": "src/module-loader/catalog.js",
@@ -6727,7 +6727,7 @@
6727
6727
  {
6728
6728
  "path": "tests/claude_materialize.mjs",
6729
6729
  "mode": "0000644",
6730
- "sha256": "ad3daf4c6cc3a0f56d8d205077c835397b1d112a0219dabb05a7e8b3a8da9492"
6730
+ "sha256": "5ca8997f238d8395663992c9ce9ccef08ea2b09fbee6735c44f119050ef9455b"
6731
6731
  },
6732
6732
  {
6733
6733
  "path": "tests/clawback_confirmed_backlog.mjs",
@@ -1269,7 +1269,7 @@
1269
1269
  "blockers"
1270
1270
  ],
1271
1271
  "summary": "GET /blockers",
1272
- "description": "List currently-open blockers + the tasks each is blocking. Public: anyone landing on /builders or status.amazonprimea.com can see what's stuck on Lars. rank: public — open blocker list; documented \"Public: anyone landing on /builders or status.amazonprimea.com can see what's stuck on Lars\" in the handler comment above.\n\n**Rank:** `public` — No authentication — any caller.",
1272
+ "description": "List currently-open blockers + the tasks each is blocking. Public: anyone landing on /builders or the status dashboard (status.<apex>) can see what's stuck on the owner. rank: public — open blocker list; documented \"Public: anyone landing on /builders or the status dashboard can see what's stuck on the owner\" in the handler comment above.\n\n**Rank:** `public` — No authentication — any caller.",
1273
1273
  "x-rank": "public",
1274
1274
  "x-source": "modules/ideas/routes/blockers.js",
1275
1275
  "responses": {
@@ -6167,7 +6167,7 @@
6167
6167
  "healthz"
6168
6168
  ],
6169
6169
  "summary": "GET /healthz",
6170
- "description": "liveness probe; status.amazonprimea.com hits this anonymously.\n\n**Rank:** `public` — No authentication — any caller.",
6170
+ "description": "liveness probe; the status dashboard (status.<apex>) hits this anonymously.\n\n**Rank:** `public` — No authentication — any caller.",
6171
6171
  "x-rank": "public",
6172
6172
  "x-source": "src/bongos/routes/healthz.js",
6173
6173
  "responses": {
@@ -10623,7 +10623,7 @@
10623
10623
  "public"
10624
10624
  ],
10625
10625
  "summary": "GET /public/tasks/:id",
10626
- "description": "Single-task resolver for \"#NNN\" deep links. Our docs reference GDS task IDs as bare \"#NNN\"; read on github.com those collide with GitHub's own PR/issue autolinker (a separate number space) and mis-link, so they are rewritten to status-dashboard permalinks (status.amazonprimea.com/#/task/N) that resolve here. Privacy gate: a full title/summary is exposed ONLY for already-public work (status shipped|confirmed — same surface as /public/recent-shipped). In-flight tasks return status + version with the title WITHHELD, so unshipped or security-sensitive task names never leak to the open web. `kind`/`priority` are non-sensitive and always returned. rank: public — single-task resolver for doc deep links (title gated to shipped/confirmed).\n\n**Rank:** `public` — No authentication — any caller.",
10626
+ "description": "Single-task resolver for \"#NNN\" deep links. Our docs reference GDS task IDs as bare \"#NNN\"; read on github.com those collide with GitHub's own PR/issue autolinker (a separate number space) and mis-link, so they are rewritten to status-dashboard permalinks (status.<apex>/#/task/N) that resolve here. Privacy gate: a full title/summary is exposed ONLY for already-public work (status shipped|confirmed — same surface as /public/recent-shipped). In-flight tasks return status + version with the title WITHHELD, so unshipped or security-sensitive task names never leak to the open web. `kind`/`priority` are non-sensitive and always returned. rank: public — single-task resolver for doc deep links (title gated to shipped/confirmed).\n\n**Rank:** `public` — No authentication — any caller.",
10627
10627
  "x-rank": "public",
10628
10628
  "x-source": "src/bongos/routes/public.js",
10629
10629
  "parameters": [
@@ -10693,7 +10693,7 @@
10693
10693
  "public"
10694
10694
  ],
10695
10695
  "summary": "GET /public/versions",
10696
- "description": "read-only surface for status.amazonprimea.com.\n\n**Rank:** `public` — No authentication — any caller.",
10696
+ "description": "read-only surface for the status dashboard (status.<apex>).\n\n**Rank:** `public` — No authentication — any caller.",
10697
10697
  "x-rank": "public",
10698
10698
  "x-source": "src/bongos/routes/public.js",
10699
10699
  "responses": {
@@ -13471,7 +13471,7 @@
13471
13471
  "versions"
13472
13472
  ],
13473
13473
  "summary": "GET /versions/:id/done-when",
13474
- "description": "Public: status.amazonprimea.com fetches this every 60s alongside other public/* surfaces. No auth, same Cache-Control posture as the other status feeds. rank: public — status.amazonprimea.com fetches every 60s anonymously.\n\n**Rank:** `public` — No authentication — any caller.",
13474
+ "description": "Public: the status dashboard (status.<apex>) fetches this every 60s alongside other public/* surfaces. No auth, same Cache-Control posture as the other status feeds. rank: public — the status dashboard (status.<apex>) fetches every 60s anonymously.\n\n**Rank:** `public` — No authentication — any caller.",
13475
13475
  "x-rank": "public",
13476
13476
  "x-source": "modules/lifecycle/routes/done-when.js",
13477
13477
  "parameters": [
@@ -256,7 +256,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
256
256
 
257
257
  | Method | Path | Rank | Body | Description |
258
258
  |---|---|---|---|---|
259
- | GET | `/api/bongos/healthz` | `public` | — | liveness probe; status.example.com hits this anonymously. |
259
+ | GET | `/api/bongos/healthz` | `public` | — | liveness probe; the status dashboard (status.<apex>) hits this anonymously. |
260
260
 
261
261
  ## `inbox` (14)
262
262
 
@@ -439,7 +439,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
439
439
  | GET | `/api/bongos/public/repo-info` | `public` | — | GitHub URL bases for the deployed checkout (V3.R13-followup-2 task 411). |
440
440
  | GET | `/api/bongos/public/tasks/:id` | `public` | — | Single-task resolver for "#NNN" deep links. |
441
441
  | GET | `/api/bongos/public/uptime` | `public` | — | Uptime: current status + 90-day history bucketed by hour. |
442
- | GET | `/api/bongos/public/versions` | `public` | — | read-only surface for status.example.com. |
442
+ | GET | `/api/bongos/public/versions` | `public` | — | read-only surface for the status dashboard (status.<apex>). |
443
443
 
444
444
  ## `scouting` (1)
445
445
 
@@ -536,7 +536,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
536
536
  |---|---|---|---|---|
537
537
  | GET | `/api/bongos/versions` | `public` | — | read-only version list; mirrors /public/versions. |
538
538
  | POST | `/api/bongos/versions` | `metic+archon` | `id`, `name`, `status`, `track`, `done_when`, `scope_doc_path`, `criteria` | POST /versions — create a version + its done-when criteria in one call. |
539
- | GET | `/api/bongos/versions/:id/done-when` | `public` | — | Public: status.example.com fetches this every 60s alongside other public/* surfaces. |
539
+ | GET | `/api/bongos/versions/:id/done-when` | `public` | — | Public: the status dashboard (status.<apex>) fetches this every 60s alongside other public/* surfaces. |
540
540
  | POST | `/api/bongos/versions/:id/done-when` | `metic+archon` | `criterion_id`, `criterion_md`, `goal_id`, `sort_order` | Auth (Metic+, then the goal's own authority): create a done-when criterion at RUNTIME. |
541
541
  | GET | `/api/bongos/versions/:id/progress` | `public` | — | Per-criterion progress rollup: each done-when criterion → the tasks that gate it (task_criteria, migration 055) → live statuses → what re… |
542
542
  | GET | `/api/bongos/versions/close-suggestions` | `public` | — | Close-flow gate suggestions for every version still 'building'. |
@@ -10,10 +10,10 @@ module.exports = function buildBlockersRouter() {
10
10
  const router = express.Router();
11
11
 
12
12
  // List currently-open blockers + the tasks each is blocking.
13
- // Public: anyone landing on /builders or status.amazonprimea.com can see
14
- // what's stuck on Lars.
13
+ // Public: anyone landing on /builders or the status dashboard (status.<apex>)
14
+ // can see what's stuck on the owner.
15
15
  // rank: public — open blocker list; documented "Public: anyone landing on /builders
16
- // or status.amazonprimea.com can see what's stuck on Lars" in the handler comment above.
16
+ // or the status dashboard can see what's stuck on the owner" in the handler comment above.
17
17
  router.get('/blockers', async (req, res) => {
18
18
  try {
19
19
  // R14 (#2001 / ADR 0119): the open-blocker list is public + grows with the
@@ -80,10 +80,10 @@ function authorizeCriterionCreate({ goal, version, actorId, actorRank, actorMemb
80
80
  module.exports = function buildDoneWhenRouter() {
81
81
  const router = express.Router();
82
82
 
83
- // Public: status.amazonprimea.com fetches this every 60s alongside other
84
- // public/* surfaces. No auth, same Cache-Control posture as the other
83
+ // Public: the status dashboard (status.<apex>) fetches this every 60s alongside
84
+ // other public/* surfaces. No auth, same Cache-Control posture as the other
85
85
  // status feeds.
86
- // rank: public — status.amazonprimea.com fetches every 60s anonymously.
86
+ // rank: public — the status dashboard (status.<apex>) fetches every 60s anonymously.
87
87
  router.get('/versions/:id/done-when', asyncHandler('GET /versions/:id/done-when', async (req, res) => {
88
88
  const versionId = String(req.params.id || '').trim();
89
89
  if (!versionId) return res.fail('version_id_required', 400);
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.308",
3
+ "version": "1.19.310",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.308",
9
+ "version": "1.19.310",
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.308",
3
+ "version": "1.19.310",
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",
@@ -82,6 +82,13 @@ function hostOf(origin) { try { return new URL(String(origin)).host || null; } c
82
82
  // instance-config uses, so nothing here is ever a founder literal (R76-safe).
83
83
  function effConfigDir(b) { return (b && b.configDir) || ic.FALLBACK_DIR; }
84
84
  function effEnvPrefix(b) { return (b && b.envPrefix) || ic.FALLBACK_ENV_PREFIX; }
85
+ // The brand token outbound User-Agents are built from, mirroring src/branding.js
86
+ // userAgent(): repo name, else env prefix, lowercased. Kept in sync by the
87
+ // tests/claude_materialize.mjs UA-rule case + tests/hook_user_agents.mjs.
88
+ function effUaBase(b) {
89
+ const base = (b && b.repo && b.repo.name) || (b && b.envPrefix) || '';
90
+ return String(base).toLowerCase();
91
+ }
85
92
 
86
93
  // Ordered source→target rebrand rules from two branding objects. PURE + exported.
87
94
  function buildRebrandRules(from = {}, to = {}) {
@@ -109,6 +116,15 @@ function buildRebrandRules(from = {}, to = {}) {
109
116
  add(`[${fc}]`, `[${tc}]`);
110
117
  // The instance env-var prefix (e.g. OTB_ → CLOUDBONGOS_), bounded by its underscore.
111
118
  add(`${effEnvPrefix(from)}_`, `${effEnvPrefix(to)}_`);
119
+ // task 1002672: the bare User-Agent brand token (e.g. `amazonprimea-gds-cli/1.0`
120
+ // in a hook's outbound fetch). It carries no TLD, so none of the HOST rules above
121
+ // ever matched it and a founder-branded UA leaked into every materialized
122
+ // instance verbatim. Bounded by the `-gds` suffix (the userAgent() convention)
123
+ // so a bare founder word in prose is NOT rewritten — only the UA token shape.
124
+ // Both bases must resolve: an empty side would degenerate into a bare `-gds`
125
+ // rule that rewrites every occurrence of the suffix itself.
126
+ const fromUa = effUaBase(from), toUa = effUaBase(to);
127
+ if (fromUa && toUa) add(`${fromUa}-gds`, `${toUa}-gds`);
112
128
  // Longest literal first so a full host is replaced before any bare token inside it.
113
129
  rules.sort((x, y) => y.pattern.source.length - x.pattern.source.length);
114
130
  return rules;
@@ -4,7 +4,7 @@ const auth = require('../auth');
4
4
  module.exports = function buildHealthzRouter() {
5
5
  const router = express.Router();
6
6
 
7
- // rank: public — liveness probe; status.amazonprimea.com hits this anonymously.
7
+ // rank: public — liveness probe; the status dashboard (status.<apex>) hits this anonymously.
8
8
  router.get('/healthz', async (_req, res) => {
9
9
  res.json({
10
10
  ok: true,
@@ -140,7 +140,7 @@ module.exports = function buildPublicRouter() {
140
140
  res.json(clientModules());
141
141
  });
142
142
 
143
- // rank: public — read-only surface for status.amazonprimea.com.
143
+ // rank: public — read-only surface for the status dashboard (status.<apex>).
144
144
  router.get('/public/versions', async (req, res) => {
145
145
  // R14 (#2001 / ADR 0119): adopt the shared pagination contract (was unbounded).
146
146
  // Small list → fetch + slice; `page` is additive (readers of .versions unchanged).
@@ -378,7 +378,7 @@ module.exports = function buildPublicRouter() {
378
378
  // Single-task resolver for "#NNN" deep links. Our docs reference GDS task IDs
379
379
  // as bare "#NNN"; read on github.com those collide with GitHub's own PR/issue
380
380
  // autolinker (a separate number space) and mis-link, so they are rewritten to
381
- // status-dashboard permalinks (status.amazonprimea.com/#/task/N) that resolve
381
+ // status-dashboard permalinks (status.<apex>/#/task/N) that resolve
382
382
  // here. Privacy gate: a full title/summary is exposed ONLY for
383
383
  // already-public work (status shipped|confirmed — same surface as
384
384
  // /public/recent-shipped). In-flight tasks return status + version with the
package/src/module-api.js CHANGED
@@ -1043,8 +1043,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
1043
1043
  // landed since 1.19.306 with no explicit bump. run 32663876767. (task 1002620)
1044
1044
  // 1.19.308 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1045
1045
  // landed since 1.19.307 with no explicit bump. run 32664559221. (task 1002620)
1046
+ // 1.19.309 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1047
+ // landed since 1.19.308 with no explicit bump. run 32664868080. (task 1002620)
1048
+ // 1.19.310 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1049
+ // landed since 1.19.309 with no explicit bump. run 32665109518. (task 1002620)
1046
1050
  // ---------------------------------------------------------------------------
1047
- const CORE_VERSION = '1.19.308'; // CI auto-patch carrier (ADR 0161); see changelog above
1051
+ const CORE_VERSION = '1.19.310'; // CI auto-patch carrier (ADR 0161); see changelog above
1048
1052
 
1049
1053
  // A namespaced logger so a module's log lines are attributable + consistent.
1050
1054
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -200,6 +200,25 @@ t('buildRebrandRules: partial target (only publicOrigin) → subdomains fall bac
200
200
  assert.equal(out, 'acme.example.org', 'no builders.acme.example.org mangling');
201
201
  });
202
202
 
203
+ // task 1002672: the bare UA brand token (no TLD — none of the host rules match it).
204
+ // A founder-branded hook UA like `amazonprimea-gds-cli/1.0` must neutralize on
205
+ // materialize; a bare founder word in prose (no `-gds` suffix) must NOT be touched.
206
+ t('buildRebrandRules: User-Agent brand token rewritten; bare brand word in prose untouched', () => {
207
+ const from = { ...OTB, repo: { name: 'amazonprimea' } };
208
+ const to = { domains: { publicOrigin: 'https://acme.example.org' }, envPrefix: 'ACME', repo: { name: 'acme-city' } };
209
+ const rules = m.buildRebrandRules(from, to);
210
+ assert.equal(m.rebrandText(`'User-Agent': 'amazonprimea-gds-cli/1.0'`, rules), `'User-Agent': 'acme-city-gds-cli/1.0'`);
211
+ assert.equal(m.rebrandText('amazonprimea-gds/1.0', rules), 'acme-city-gds/1.0');
212
+ assert.equal(m.rebrandText('the amazonprimea style guide', rules), 'the amazonprimea style guide', 'prose token without -gds suffix stays');
213
+ // envPrefix fallback when repo.name is absent on either side.
214
+ const rules2 = m.buildRebrandRules(OTB, { domains: { publicOrigin: 'https://acme.example.org' }, envPrefix: 'ACME' });
215
+ assert.equal(m.rebrandText('otb-gds-cli/1.0', rules2), 'acme-gds-cli/1.0');
216
+ // No degenerate bare `-gds` rule when a side resolves to nothing.
217
+ const rules3 = m.buildRebrandRules({ domains: {} }, { domains: { publicOrigin: 'https://acme.example.org' }, envPrefix: 'ACME' });
218
+ assert.ok(!rules3.some((r) => r.pattern.source === '-gds'), 'no bare -gds rule');
219
+ assert.equal(m.rebrandText('plain-gds/1.0 text', m.buildRebrandRules({}, {})), 'plain-gds/1.0 text');
220
+ });
221
+
203
222
  t('buildRebrandRules: source === target is an empty ruleset → rebrandText is a no-op', () => {
204
223
  assert.equal(m.buildRebrandRules(OTB, OTB).length, 0);
205
224
  const s = 'https://amazonprimea.com ~/.config/otb';