@bongos/core 1.19.347 → 1.19.348
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 +29 -19
- package/docs/copy-inventory.md +22 -22
- package/docs/copy-registry.json +25 -25
- package/modules/hall-ui/public/idea.js +9 -1
- package/modules/hall-ui/public/ideas.js +17 -5
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/client-baseurl-guard.js +116 -0
- package/scripts/gds/deploy-divergence.js +34 -3
- package/scripts/gds/fitness.js +1 -0
- package/scripts/gds/update-channel.js +121 -5
- package/src/module-api.js +3 -1
- package/tests/client_baseurl_guard.mjs +122 -0
- package/tests/idea_hall_composer.mjs +11 -0
- package/tests/update_channel.mjs +125 -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.348",
|
|
6
|
+
"core_contract": "1.19.348",
|
|
7
|
+
"source_commit": "d42db56154d01be276f13e04e364e7ced8d8c400",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-25T02:
|
|
9
|
+
"built_at": "2026-08-25T02:48:07.802Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 328,
|
|
13
13
|
"agent_docs_stubbed": 22,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1499,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1849,
|
|
20
|
+
"tree_sha256": "3004531bfb70cd33dbca752e2d945c4687d6ea1148f54548b6fcf921f953681f",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -1472,12 +1472,12 @@
|
|
|
1472
1472
|
{
|
|
1473
1473
|
"path": "docs/copy-inventory.md",
|
|
1474
1474
|
"mode": "0000644",
|
|
1475
|
-
"sha256": "
|
|
1475
|
+
"sha256": "0caaef6980475bc4abbb51df79ebe4b14b2a3ad7b97f3e2da13f08d8e1ae1b11"
|
|
1476
1476
|
},
|
|
1477
1477
|
{
|
|
1478
1478
|
"path": "docs/copy-registry.json",
|
|
1479
1479
|
"mode": "0000644",
|
|
1480
|
-
"sha256": "
|
|
1480
|
+
"sha256": "d5410ce76bb98ee335363f00e483fd66539b9ac35ffb4ec8a7bd3e0af928faf0"
|
|
1481
1481
|
},
|
|
1482
1482
|
{
|
|
1483
1483
|
"path": "docs/design-contract.md",
|
|
@@ -3577,7 +3577,7 @@
|
|
|
3577
3577
|
{
|
|
3578
3578
|
"path": "modules/hall-ui/public/idea.js",
|
|
3579
3579
|
"mode": "0000644",
|
|
3580
|
-
"sha256": "
|
|
3580
|
+
"sha256": "b742657979f37993db85efbf05d0983e8ef295dcde7b0058a826909bfa352074"
|
|
3581
3581
|
},
|
|
3582
3582
|
{
|
|
3583
3583
|
"path": "modules/hall-ui/public/ideas.css",
|
|
@@ -3592,7 +3592,7 @@
|
|
|
3592
3592
|
{
|
|
3593
3593
|
"path": "modules/hall-ui/public/ideas.js",
|
|
3594
3594
|
"mode": "0000644",
|
|
3595
|
-
"sha256": "
|
|
3595
|
+
"sha256": "96796c1d7192ed2f6c4ac2ef185ade5e6001ecb828feb1b99a123a457bfdc8bf"
|
|
3596
3596
|
},
|
|
3597
3597
|
{
|
|
3598
3598
|
"path": "modules/hall-ui/public/index.html",
|
|
@@ -4762,12 +4762,12 @@
|
|
|
4762
4762
|
{
|
|
4763
4763
|
"path": "package-lock.json",
|
|
4764
4764
|
"mode": "0000644",
|
|
4765
|
-
"sha256": "
|
|
4765
|
+
"sha256": "6745e4e181b20731968d02f8e4dac7cb864acbf3fbbd6ec6054c799a41487618"
|
|
4766
4766
|
},
|
|
4767
4767
|
{
|
|
4768
4768
|
"path": "package.json",
|
|
4769
4769
|
"mode": "0000644",
|
|
4770
|
-
"sha256": "
|
|
4770
|
+
"sha256": "65ce6bcba41342833cd48800e028fd1e3420a4d9d7fb82c1250cfca724baa0ad"
|
|
4771
4771
|
},
|
|
4772
4772
|
{
|
|
4773
4773
|
"path": "public-docs/index.html",
|
|
@@ -5279,6 +5279,11 @@
|
|
|
5279
5279
|
"mode": "0000644",
|
|
5280
5280
|
"sha256": "5aa658b099b2acda06cdb3aecfe905da7de79f2b12c844faca091ff061454017"
|
|
5281
5281
|
},
|
|
5282
|
+
{
|
|
5283
|
+
"path": "scripts/gds/client-baseurl-guard.js",
|
|
5284
|
+
"mode": "0000644",
|
|
5285
|
+
"sha256": "8ac4c8909b75aa5e2022a967ba9ac0cbcdac60da9d75757603827b86964c825b"
|
|
5286
|
+
},
|
|
5282
5287
|
{
|
|
5283
5288
|
"path": "scripts/gds/code.js",
|
|
5284
5289
|
"mode": "0000644",
|
|
@@ -5342,7 +5347,7 @@
|
|
|
5342
5347
|
{
|
|
5343
5348
|
"path": "scripts/gds/deploy-divergence.js",
|
|
5344
5349
|
"mode": "0000644",
|
|
5345
|
-
"sha256": "
|
|
5350
|
+
"sha256": "f9ade0941ad0e49cb6412936ad5d657561da0ab591508a2844a3645e25debdcb"
|
|
5346
5351
|
},
|
|
5347
5352
|
{
|
|
5348
5353
|
"path": "scripts/gds/design-sync.js",
|
|
@@ -5447,7 +5452,7 @@
|
|
|
5447
5452
|
{
|
|
5448
5453
|
"path": "scripts/gds/fitness.js",
|
|
5449
5454
|
"mode": "0000644",
|
|
5450
|
-
"sha256": "
|
|
5455
|
+
"sha256": "91b125de588406e6b93f8cc29d4d53891932a52744a6e92824459c734f747f16"
|
|
5451
5456
|
},
|
|
5452
5457
|
{
|
|
5453
5458
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -6217,7 +6222,7 @@
|
|
|
6217
6222
|
{
|
|
6218
6223
|
"path": "scripts/gds/update-channel.js",
|
|
6219
6224
|
"mode": "0000644",
|
|
6220
|
-
"sha256": "
|
|
6225
|
+
"sha256": "db54fba6065c8b662f574aa9443e686df4e5a152865342999b1f20d50ece01ed"
|
|
6221
6226
|
},
|
|
6222
6227
|
{
|
|
6223
6228
|
"path": "scripts/gds/upgrade.js",
|
|
@@ -6617,7 +6622,7 @@
|
|
|
6617
6622
|
{
|
|
6618
6623
|
"path": "src/module-api.js",
|
|
6619
6624
|
"mode": "0000644",
|
|
6620
|
-
"sha256": "
|
|
6625
|
+
"sha256": "7eca632fc6880711d60fad718a743301e62e99ca990a8a4cf07767726f86f0cf"
|
|
6621
6626
|
},
|
|
6622
6627
|
{
|
|
6623
6628
|
"path": "src/module-loader/catalog.js",
|
|
@@ -7124,6 +7129,11 @@
|
|
|
7124
7129
|
"mode": "0000644",
|
|
7125
7130
|
"sha256": "fae365f4c0cbc41a9077cfa19387a2f016f39d14fad37cf2867230eaf9196b11"
|
|
7126
7131
|
},
|
|
7132
|
+
{
|
|
7133
|
+
"path": "tests/client_baseurl_guard.mjs",
|
|
7134
|
+
"mode": "0000644",
|
|
7135
|
+
"sha256": "541407601a480408b725768a1a1b0ba8b71e0969f82d6f4747f862abcb62a5d0"
|
|
7136
|
+
},
|
|
7127
7137
|
{
|
|
7128
7138
|
"path": "tests/client_bundle_route.mjs",
|
|
7129
7139
|
"mode": "0000644",
|
|
@@ -7972,7 +7982,7 @@
|
|
|
7972
7982
|
{
|
|
7973
7983
|
"path": "tests/idea_hall_composer.mjs",
|
|
7974
7984
|
"mode": "0000644",
|
|
7975
|
-
"sha256": "
|
|
7985
|
+
"sha256": "dbe8f91e4f3acc566a36547ce42d30328ba9e651a622fe1f25092d49d4e3a955"
|
|
7976
7986
|
},
|
|
7977
7987
|
{
|
|
7978
7988
|
"path": "tests/idea_ideate_full.mjs",
|
|
@@ -9187,7 +9197,7 @@
|
|
|
9187
9197
|
{
|
|
9188
9198
|
"path": "tests/update_channel.mjs",
|
|
9189
9199
|
"mode": "0000644",
|
|
9190
|
-
"sha256": "
|
|
9200
|
+
"sha256": "9debf9ecdb02b986c882f3d29bdf4b5fe2154cb4e7639db0fd0a0336cd4bdc3b"
|
|
9191
9201
|
},
|
|
9192
9202
|
{
|
|
9193
9203
|
"path": "tests/upgrade.mjs",
|
package/docs/copy-inventory.md
CHANGED
|
@@ -125,7 +125,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
125
125
|
| `634738746b81` | certain | A fresh box has a | `modules/hall-ui/public/harbor.js:122` |
|
|
126
126
|
| `df5eb70b5a05` | certain | A higher rank. Its details are hidden until you reach the rank directly below it. | `modules/hall-ui/public/ranks.js:125` |
|
|
127
127
|
| `a07c7b335a66` | certain | A protected-scope goal requires a Metic+ member. | `modules/hall-ui/public/goals-render.js:903` |
|
|
128
|
-
| `5723baf26b2d` | certain | A quick idea is a thought filed fast. It is not an unfinished Full Idea — only its author can offer it up for | `modules/hall-ui/public/idea.js:
|
|
128
|
+
| `5723baf26b2d` | certain | A quick idea is a thought filed fast. It is not an unfinished Full Idea — only its author can offer it up for | `modules/hall-ui/public/idea.js:245` |
|
|
129
129
|
| `6359b5afbc6b` | certain | A short path to finding your feet. Tick the steps you’ve done; the rest are tracked for you. | `modules/hall-ui/public/hall-widgets.js:60` |
|
|
130
130
|
| `58f7f6f0d8bd` | certain | A small app for your menu bar (Mac) or system tray (Windows): one switch to start and stop your dev box, with | `modules/hall-ui/public/settings.html:170` |
|
|
131
131
|
| `a3185a43c230` | certain | A sound plays at each turn of your work — a gentle chime when Claude finishes, a flourish when a task ships or | `modules/hall-ui/public/settings.html:265` |
|
|
@@ -147,7 +147,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
147
147
|
| `200a83529fa6` | certain | Already acknowledged — you're all set. | `modules/hall-ui/public/primer.html:68` |
|
|
148
148
|
| `d11477f287b7` | certain | An objection returns the idea to its author with your reason and the section it faults \u2014 the feedback the | `modules/hall-ui/public/government.js:741` |
|
|
149
149
|
| `69f0ca4a4d84` | certain | Analytics scope | `modules/hall-ui/public/hall-render.js:995` |
|
|
150
|
-
| `14bdceaac078` | certain | Answer the five questions and this spark becomes a Full Idea. | `modules/hall-ui/public/ideas.js:
|
|
150
|
+
| `14bdceaac078` | certain | Answer the five questions and this spark becomes a Full Idea. | `modules/hall-ui/public/ideas.js:423` |
|
|
151
151
|
| `52ed0e2e3470` | certain | Any rank | `modules/hall-ui/public/task-lib.js:62` |
|
|
152
152
|
| `c89833aa985a` | certain | Approval failed — try again. | `modules/hall-ui/public/pair.js:92` |
|
|
153
153
|
| `0832ba017c90` | certain | Approve & re-run | `modules/hall-ui/public/gate.js:100` |
|
|
@@ -165,7 +165,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
165
165
|
| `4195d0d114e4` | certain | Archons only. | `modules/hall-ui/public/sessions.js:97` |
|
|
166
166
|
| `4195d0d114e4` | certain | Archons only. | `modules/hall-ui/public/watch.js:838` |
|
|
167
167
|
| `5fcfdf8975cf` | certain | As configuration: | `modules/hall-ui/public/government.js:633` |
|
|
168
|
-
| `01a0f29ff406` | certain | As first filed | `modules/hall-ui/public/idea.js:
|
|
168
|
+
| `01a0f29ff406` | certain | As first filed | `modules/hall-ui/public/idea.js:237` |
|
|
169
169
|
| `688cc6dd38ee` | certain | Asked of you | `modules/hall-ui/public/collab.html:32` |
|
|
170
170
|
| `b2656d0b2402` | certain | Asked {…} | `modules/hall-ui/public/collab.js:36` |
|
|
171
171
|
| `b2656d0b2402` | certain | Asked {…} | `modules/hall-ui/public/watch.js:205` |
|
|
@@ -280,7 +280,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
280
280
|
| `dec4b11f316d` | certain | Could not load dev boxes just now. | `modules/hall-ui/public/harbor.js:288` |
|
|
281
281
|
| `0f7c244312b1` | certain | Could not load government data just now. | `modules/hall-ui/public/government.js:883` |
|
|
282
282
|
| `cd00bf198661` | certain | Could not load held pull requests just now. | `modules/hall-ui/public/gate.js:180` |
|
|
283
|
-
| `589a4cfc5442` | certain | Could not load idea {…}. | `modules/hall-ui/public/ideas.js:
|
|
283
|
+
| `589a4cfc5442` | certain | Could not load idea {…}. | `modules/hall-ui/public/ideas.js:456` |
|
|
284
284
|
| `192c893fc64e` | certain | Could not load modules. | `modules/hall-ui/public/modules.js:226` |
|
|
285
285
|
| `4e09b612c6b7` | certain | Could not load modules. Return to the hall and try again. | `modules/hall-ui/public/modules.js:225` |
|
|
286
286
|
| `8a4b590fa1a0` | certain | Could not load requests. | `modules/hall-ui/public/collab.js:137` |
|
|
@@ -289,11 +289,11 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
289
289
|
| `5420bb0b8475` | certain | Could not load the Board Room just now. | `modules/hall-ui/public/government.js:804` |
|
|
290
290
|
| `3f82d4f29e5b` | certain | Could not load the constitution just now. | `modules/hall-ui/public/government.js:628` |
|
|
291
291
|
| `8f597eabf4ac` | certain | Could not load the drachmae guide. | `modules/hall-ui/public/drachmae.js:162` |
|
|
292
|
-
| `b2147cc90e36` | certain | Could not load the idea template. Reload, or file from the terminal with capture.js. | `modules/hall-ui/public/ideas.js:
|
|
292
|
+
| `b2147cc90e36` | certain | Could not load the idea template. Reload, or file from the terminal with capture.js. | `modules/hall-ui/public/ideas.js:438` |
|
|
293
293
|
| `6264ebc9ede0` | certain | Could not load the primer. | `modules/hall-ui/public/primer.js:620` |
|
|
294
294
|
| `7604af13c40e` | certain | Could not load the profile. | `modules/hall-ui/public/profile.js:330` |
|
|
295
|
-
| `8b69e445592a` | certain | Could not load the question labels; showing the answers as stored. | `modules/hall-ui/public/idea.js:
|
|
296
|
-
| `edd5bafbe075` | certain | Could not load the queue just now. | `modules/hall-ui/public/ideas.js:
|
|
295
|
+
| `8b69e445592a` | certain | Could not load the question labels; showing the answers as stored. | `modules/hall-ui/public/idea.js:229` |
|
|
296
|
+
| `edd5bafbe075` | certain | Could not load the queue just now. | `modules/hall-ui/public/ideas.js:387` |
|
|
297
297
|
| `02bb0570996a` | certain | Could not load the ranks. | `modules/hall-ui/public/ranks.js:197` |
|
|
298
298
|
| `2878778cf9f3` | certain | Could not load this page. | `modules/hall-ui/public/watch.js:884` |
|
|
299
299
|
| `4b0c8d94541c` | certain | Could not load your account. Reload the page to try again. | `modules/hall-ui/public/settings.js:1270` |
|
|
@@ -353,7 +353,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
353
353
|
| `cc681021a140` | certain | Description (optional) | `modules/hall-ui/public/government.html:61` |
|
|
354
354
|
| `8cac6be8afdf` | certain | Dev box app | `modules/hall-ui/public/settings.html:169` |
|
|
355
355
|
| `9c37356c3a04` | certain | Dev-box costs | `modules/hall-ui/public/watch.html:86` |
|
|
356
|
-
| `0a4fe7150971` | certain | Develop this idea | `modules/hall-ui/public/ideas.js:
|
|
356
|
+
| `0a4fe7150971` | certain | Develop this idea | `modules/hall-ui/public/ideas.js:422` |
|
|
357
357
|
| `11b8266d6aab` | certain | Developing a Big Idea Spark | `modules/hall-ui/public/ideas.html:56` |
|
|
358
358
|
| `457f4967541b` | certain | Device pairing | `modules/hall-ui/public/pair.html:15` |
|
|
359
359
|
| `457f4967541b` | certain | Device pairing | `modules/hall-ui/public/pair.html:15` |
|
|
@@ -374,7 +374,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
374
374
|
| `9c728558d6e7` | certain | Drachmae & karma | `modules/hall-ui/public/drachmae.html:15` |
|
|
375
375
|
| `9d9cc4ef8557` | certain | Drachmae & karma — {{worldName}} | `modules/hall-ui/public/drachmae.html:6` |
|
|
376
376
|
| `9d9cc4ef8557` | certain | Drachmae & karma — {{worldName}} | `modules/hall-ui/public/drachmae.html:6` |
|
|
377
|
-
| `2f8d0404039e` | certain | Draft discarded. | `modules/hall-ui/public/ideas.js:
|
|
377
|
+
| `2f8d0404039e` | certain | Draft discarded. | `modules/hall-ui/public/ideas.js:347` |
|
|
378
378
|
| `d59beed97d58` | certain | Drag to pan, scroll to zoom | `modules/hall-ui/public/diagrams.html:42` |
|
|
379
379
|
| `67f09099c2d3` | certain | Duplicate — should be said once, in one place | `modules/hall-ui/public/copy-desk.html:95` |
|
|
380
380
|
| `db7f370c0d38` | certain | Each builder’s dev box at a glance. | `modules/hall-ui/public/harbor.js:335` |
|
|
@@ -404,7 +404,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
404
404
|
| `86ef86c5c647` | certain | Fit to view | `modules/hall-ui/public/diagram-viewer.js:97` |
|
|
405
405
|
| `86ef86c5c647` | certain | Fit to view | `modules/hall-ui/public/diagram-viewer.js:100` |
|
|
406
406
|
| `78107a76a755` | certain | Five diagrams that show, at a glance, how work flows through {{worldName}}'s development system. | `modules/hall-ui/public/diagrams.html:42` |
|
|
407
|
-
| `18ab1be9d3ad` | certain | Five questions, and a score that counts how much of each you actually answered. | `modules/hall-ui/public/ideas.js:
|
|
407
|
+
| `18ab1be9d3ad` | certain | Five questions, and a score that counts how much of each you actually answered. | `modules/hall-ui/public/ideas.js:448` |
|
|
408
408
|
| `b9267033b1f1` | certain | Flag this copy | `modules/hall-ui/public/copy-desk.html:87` |
|
|
409
409
|
| `448d00771dc8` | certain | Flagged. It is in the queue. | `modules/hall-ui/public/copy-desk.js:281` |
|
|
410
410
|
| `617f13fa3fdb` | certain | Flip the switch. | `modules/hall-ui/public/hall-render.js:609` |
|
|
@@ -457,7 +457,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
457
457
|
| `298ff22576ee` | certain | How your output has changed over time — work shipped, cost and time per work, and how reviews held up. | `modules/hall-ui/public/hall-widgets.js:182` |
|
|
458
458
|
| `08d3bf741a2c` | certain | I have read it | `modules/hall-ui/public/primer.html:67` |
|
|
459
459
|
| `08d3bf741a2c` | certain | I have read it | `modules/hall-ui/public/primer.js:496` |
|
|
460
|
-
| `a4ff1e9c34e3` | certain | Idea {…} | `modules/hall-ui/public/idea.js:
|
|
460
|
+
| `a4ff1e9c34e3` | certain | Idea {…} | `modules/hall-ui/public/idea.js:252` |
|
|
461
461
|
| `ba82ec56f1e3` | certain | Idea — {{worldName}} | `modules/hall-ui/public/idea.html:6` |
|
|
462
462
|
| `ba82ec56f1e3` | certain | Idea — {{worldName}} | `modules/hall-ui/public/idea.html:6` |
|
|
463
463
|
| `e181637c5a5e` | certain | Ideas awaiting triage | `modules/hall-ui/public/builders.js:843` |
|
|
@@ -473,7 +473,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
473
473
|
| `d176de7857f3` | certain | Invited. | `modules/hall-ui/public/goals-render.js:895` |
|
|
474
474
|
| `2170b74c6526` | certain | Isometric map of the repository | `modules/hall-ui/public/atlas.html:39` |
|
|
475
475
|
| `79791be35018` | certain | It opens this hall in your browser — click one | `modules/hall-ui/public/hall-render.js:609` |
|
|
476
|
-
| `b0bcb3f844ec` | certain | Its author marked this as needing development — anyone can turn it into a Full Idea, and they keep the credit | `modules/hall-ui/public/idea.js:
|
|
476
|
+
| `b0bcb3f844ec` | certain | Its author marked this as needing development — anyone can turn it into a Full Idea, and they keep the credit | `modules/hall-ui/public/idea.js:243` |
|
|
477
477
|
| `34dc2af8664f` | certain | Join a project | `modules/hall-ui/public/my-projects.html:44` |
|
|
478
478
|
| `b7719e855009` | certain | Join goal | `modules/hall-ui/public/goals-render.js:758` |
|
|
479
479
|
| `9f9424a5100b` | certain | Join our Discord | `modules/hall-ui/public/hall-render.js:607` |
|
|
@@ -565,7 +565,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
565
565
|
| `6d6fd2314248` | certain | No cross-project activity reported yet. | `modules/hall-ui/public/profile.js:233` |
|
|
566
566
|
| `6d6fd2314248` | certain | No cross-project activity reported yet. | `modules/hall-ui/public/scouting.js:29` |
|
|
567
567
|
| `b1ada579483c` | certain | No custom ranks yet | `modules/hall-ui/public/government.js:303` |
|
|
568
|
-
| `6e2d2e11f84a` | certain | No description. | `modules/hall-ui/public/idea.js:
|
|
568
|
+
| `6e2d2e11f84a` | certain | No description. | `modules/hall-ui/public/idea.js:114` |
|
|
569
569
|
| `cb1a2f7e92bf` | certain | No dev boxes yet — no builder has opened one. | `modules/hall-ui/public/harbor.js:304` |
|
|
570
570
|
| `36fc20a2a55e` | certain | No duplicated strings in the registry. | `modules/hall-ui/public/copy-desk.js:181` |
|
|
571
571
|
| `93c00cb1423a` | certain | No goal | `modules/hall-ui/public/work.js:421` |
|
|
@@ -591,7 +591,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
591
591
|
| `c72e0bdb834a` | certain | Not declared in module.json — this is who ships the module. | `modules/hall-ui/public/modules.js:49` |
|
|
592
592
|
| `361b353ded5e` | certain | Not enough shipped work yet to chart. | `modules/hall-ui/public/hall-widgets.js:184` |
|
|
593
593
|
| `90230572d017` | certain | Not found | `modules/hall-ui/public/task.js:304` |
|
|
594
|
-
| `61250b49a90b` | certain | Not scored. | `modules/hall-ui/public/idea.js:
|
|
594
|
+
| `61250b49a90b` | certain | Not scored. | `modules/hall-ui/public/idea.js:78` |
|
|
595
595
|
| `8d6afe6def47` | certain | Not signed in | `modules/hall-ui/public/settings.html:33` |
|
|
596
596
|
| `19adac0efed1` | certain | Not yet available | `modules/hall-ui/public/not-ready.html:24` |
|
|
597
597
|
| `2c19f41704b9` | certain | Not yet available — {{worldName}} | `modules/hall-ui/public/not-ready.html:6` |
|
|
@@ -602,7 +602,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
602
602
|
| `fec5d78497e8` | certain | Nothing in the backlog right now. | `modules/hall-ui/public/work.js:953` |
|
|
603
603
|
| `2c6e0dfbd781` | certain | Nothing is waiting on you. Requests reach you by your craft — set yours in | `modules/hall-ui/public/collab.html:38` |
|
|
604
604
|
| `18ce0723be0d` | certain | Nothing is waiting — no held changes need your approval. | `modules/hall-ui/public/gate.js:188` |
|
|
605
|
-
| `2ec0fbb20a42` | certain | Nothing is waiting. A spark appears here when its author offers a quick idea up for development. | `modules/hall-ui/public/ideas.js:
|
|
605
|
+
| `2ec0fbb20a42` | certain | Nothing is waiting. A spark appears here when its author offers a quick idea up for development. | `modules/hall-ui/public/ideas.js:365` |
|
|
606
606
|
| `ad9a17a8a5eb` | certain | Nothing shipped yet. | `modules/hall-ui/public/profile.js:144` |
|
|
607
607
|
| `ad9a17a8a5eb` | certain | Nothing shipped yet. | `modules/hall-ui/public/work.js:914` |
|
|
608
608
|
| `42909e1460c4` | certain | Nothing to submit — every module here already ships with the core. | `modules/hall-ui/public/modules.js:166` |
|
|
@@ -769,7 +769,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
769
769
|
| `6374d6172678` | certain | Shipped {…}{…}. | `modules/hall-ui/public/task.js:234` |
|
|
770
770
|
| `14d32a35c15f` | certain | Show my cross-project record | `modules/hall-ui/public/settings.js:579` |
|
|
771
771
|
| `dc0f9fe50a65` | certain | Show the setup guide again | `modules/hall-ui/public/index.html:55` |
|
|
772
|
-
| `2af4c0f2b4b9` | certain | Showing {…} of {…}. | `modules/hall-ui/public/ideas.js:
|
|
772
|
+
| `2af4c0f2b4b9` | certain | Showing {…} of {…}. | `modules/hall-ui/public/ideas.js:369` |
|
|
773
773
|
| `2f12d46f96ca` | certain | Sign in from the app. | `modules/hall-ui/public/hall-render.js:609` |
|
|
774
774
|
| `19ab56cc3932` | certain | Sign in to filter to your goals | `modules/hall-ui/public/goals-page.js:793` |
|
|
775
775
|
| `b057dfbf49c5` | certain | Sign in when Claude prompts you. | `modules/hall-ui/public/hall-render.js:609` |
|
|
@@ -926,7 +926,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
926
926
|
| `c5fd76a9e03d` | certain | Won {…} | `modules/hall-ui/public/profile.js:115` |
|
|
927
927
|
| `520d139df9eb` | certain | Work is complete but the version is held — no tasks are being claimed here right now. | `modules/hall-ui/public/roadmap.js:223` |
|
|
928
928
|
| `52e5fe3c6075` | certain | Write a Full Idea | `modules/hall-ui/public/ideas.html:43` |
|
|
929
|
-
| `7664f4373d1f` | certain | Write one | `modules/hall-ui/public/idea.js:
|
|
929
|
+
| `7664f4373d1f` | certain | Write one | `modules/hall-ui/public/idea.js:243` |
|
|
930
930
|
| `c368b56ecf40` | certain | Xenos builders start with a focused set of pages — ship your first | `modules/hall-ui/public/not-ready.html:26` |
|
|
931
931
|
| `b6b0fcb2bcee` | certain | You already have a pending request or invitation for this goal. | `modules/hall-ui/public/goals-render.js:947` |
|
|
932
932
|
| `a674836fec16` | certain | You already have access ✓ | `modules/hall-ui/public/hall-render.js:355` |
|
|
@@ -1020,8 +1020,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1020
1020
|
| `17ab8db79e1f` | certain | holds the worker, the code, and your local memory; the | `modules/hall-ui/public/diagrams.html:83` |
|
|
1021
1021
|
| `7d5cbe3d81e4` | certain | how long a vote stays open | `modules/hall-ui/public/government.html:40` |
|
|
1022
1022
|
| `dacb901c4f10` | certain | idea number | `modules/hall-ui/public/task-detail.js:458` |
|
|
1023
|
-
| `7df22e74c87a` | certain | idea {…} | `modules/hall-ui/public/idea.js:
|
|
1024
|
-
| `7df22e74c87a` | certain | idea {…} | `modules/hall-ui/public/ideas.js:
|
|
1023
|
+
| `7df22e74c87a` | certain | idea {…} | `modules/hall-ui/public/idea.js:198` |
|
|
1024
|
+
| `7df22e74c87a` | certain | idea {…} | `modules/hall-ui/public/ideas.js:334` |
|
|
1025
1025
|
| `2123a62d69e3` | certain | if this stays empty. | `modules/hall-ui/public/collab.html:40` |
|
|
1026
1026
|
| `b2b399fdd6a4` | certain | in progress | `modules/hall-ui/public/goals-render.js:188` |
|
|
1027
1027
|
| `b2b399fdd6a4` | certain | in progress | `modules/hall-ui/public/roadmap.js:108` |
|
|
@@ -1040,7 +1040,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1040
1040
|
| `754bdec368ed` | certain | lowest credits | `modules/hall-ui/public/hall-widgets.js:136` |
|
|
1041
1041
|
| `cffba8409070` | certain | manage its visibility in Settings → Cross-project record | `modules/hall-ui/public/profile.js:200` |
|
|
1042
1042
|
| `5c3fdd34cd02` | certain | means expressible through the real configuration — a rank-based membership plus a pass rule — with no new code | `modules/hall-ui/public/government.js:610` |
|
|
1043
|
-
| `0eefd625d954` | certain | merged into | `modules/hall-ui/public/idea.js:
|
|
1043
|
+
| `0eefd625d954` | certain | merged into | `modules/hall-ui/public/idea.js:198` |
|
|
1044
1044
|
| `70c234985d42` | certain | most recent first | `modules/hall-ui/public/hall-widgets.js:134` |
|
|
1045
1045
|
| `7421250dc155` | certain | name, author, origin project, or licence… | `modules/hall-ui/public/modules.html:30` |
|
|
1046
1046
|
| `f61d8fc1516d` | certain | new SSH identity | `modules/hall-ui/public/harbor.js:122` |
|
|
@@ -1084,7 +1084,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1084
1084
|
| `cfc61d8cabe6` | certain | tally: {…} yes · {…} no — sitting under | `modules/hall-ui/public/government.js:786` |
|
|
1085
1085
|
| `129ed842096b` | certain | target gone | `modules/hall-ui/public/copy-desk.js:63` |
|
|
1086
1086
|
| `4d8ee7d68974` | certain | task {…} | `modules/hall-ui/public/gate.js:123` |
|
|
1087
|
-
| `4d8ee7d68974` | certain | task {…} | `modules/hall-ui/public/idea.js:
|
|
1087
|
+
| `4d8ee7d68974` | certain | task {…} | `modules/hall-ui/public/idea.js:195` |
|
|
1088
1088
|
| `a41659dc2bee` | certain | tasks remaining | `modules/hall-ui/public/roadmap.js:273` |
|
|
1089
1089
|
| `bd3402efa59e` | certain | task{…} shipped | `modules/hall-ui/public/goals-render.js:656` |
|
|
1090
1090
|
| `ee431f143549` | certain | three tasks | `modules/hall-ui/public/not-ready.html:28` |
|
|
@@ -1147,7 +1147,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
|
|
|
1147
1147
|
| `085f93f3f775` | certain | {…} — see all standings | `modules/hall-ui/public/hall-render.js:80` |
|
|
1148
1148
|
| `f75ee96a7511` | certain | {…} — total payout from shipped work. | `modules/hall-ui/public/profile.js:58` |
|
|
1149
1149
|
| `2a2728a4ec4f` | certain | {…}% of tasks complete | `modules/hall-ui/public/roadmap.js:70` |
|
|
1150
|
-
| `c43050b62297` | certain | {…}/100 complete | `modules/hall-ui/public/idea.js:
|
|
1150
|
+
| `c43050b62297` | certain | {…}/100 complete | `modules/hall-ui/public/idea.js:81` |
|
|
1151
1151
|
| `499916045b19` | certain | {…}{…} asks to join | `modules/hall-ui/public/goal-inbox.js:69` |
|
|
1152
1152
|
| `99d107c17070` | certain | · by {…} · | `modules/hall-ui/public/gate.js:138` |
|
|
1153
1153
|
| `4188b6f8997e` | certain | · default (on) | `modules/hall-ui/public/settings.js:500` |
|
package/docs/copy-registry.json
CHANGED
|
@@ -1224,8 +1224,8 @@
|
|
|
1224
1224
|
"text": "idea {…}",
|
|
1225
1225
|
"occurrences": 2,
|
|
1226
1226
|
"at": [
|
|
1227
|
-
"modules/hall-ui/public/idea.js:
|
|
1228
|
-
"modules/hall-ui/public/ideas.js:
|
|
1227
|
+
"modules/hall-ui/public/idea.js:198",
|
|
1228
|
+
"modules/hall-ui/public/ideas.js:334"
|
|
1229
1229
|
]
|
|
1230
1230
|
},
|
|
1231
1231
|
{
|
|
@@ -1265,7 +1265,7 @@
|
|
|
1265
1265
|
"occurrences": 2,
|
|
1266
1266
|
"at": [
|
|
1267
1267
|
"modules/hall-ui/public/gate.js:123",
|
|
1268
|
-
"modules/hall-ui/public/idea.js:
|
|
1268
|
+
"modules/hall-ui/public/idea.js:195"
|
|
1269
1269
|
]
|
|
1270
1270
|
},
|
|
1271
1271
|
{
|
|
@@ -1752,7 +1752,7 @@
|
|
|
1752
1752
|
"surface": "builders-hall",
|
|
1753
1753
|
"text": "A quick idea is a thought filed fast. It is not an unfinished Full Idea — only its author can offer it up for development.",
|
|
1754
1754
|
"file": "modules/hall-ui/public/idea.js",
|
|
1755
|
-
"line":
|
|
1755
|
+
"line": 245,
|
|
1756
1756
|
"origin": "js-markup",
|
|
1757
1757
|
"confidence": "certain"
|
|
1758
1758
|
},
|
|
@@ -1950,7 +1950,7 @@
|
|
|
1950
1950
|
"surface": "builders-hall",
|
|
1951
1951
|
"text": "Answer the five questions and this spark becomes a Full Idea.",
|
|
1952
1952
|
"file": "modules/hall-ui/public/ideas.js",
|
|
1953
|
-
"line":
|
|
1953
|
+
"line": 423,
|
|
1954
1954
|
"origin": "js-text-assign",
|
|
1955
1955
|
"confidence": "certain"
|
|
1956
1956
|
},
|
|
@@ -2112,7 +2112,7 @@
|
|
|
2112
2112
|
"surface": "builders-hall",
|
|
2113
2113
|
"text": "As first filed",
|
|
2114
2114
|
"file": "modules/hall-ui/public/idea.js",
|
|
2115
|
-
"line":
|
|
2115
|
+
"line": 237,
|
|
2116
2116
|
"origin": "js-markup",
|
|
2117
2117
|
"confidence": "certain"
|
|
2118
2118
|
},
|
|
@@ -3147,7 +3147,7 @@
|
|
|
3147
3147
|
"surface": "builders-hall",
|
|
3148
3148
|
"text": "Could not load idea {…}.",
|
|
3149
3149
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3150
|
-
"line":
|
|
3150
|
+
"line": 456,
|
|
3151
3151
|
"origin": "js-text-assign",
|
|
3152
3152
|
"confidence": "certain"
|
|
3153
3153
|
},
|
|
@@ -3228,7 +3228,7 @@
|
|
|
3228
3228
|
"surface": "builders-hall",
|
|
3229
3229
|
"text": "Could not load the idea template. Reload, or file from the terminal with capture.js.",
|
|
3230
3230
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3231
|
-
"line":
|
|
3231
|
+
"line": 438,
|
|
3232
3232
|
"origin": "js-text-assign",
|
|
3233
3233
|
"confidence": "certain"
|
|
3234
3234
|
},
|
|
@@ -3255,7 +3255,7 @@
|
|
|
3255
3255
|
"surface": "builders-hall",
|
|
3256
3256
|
"text": "Could not load the question labels; showing the answers as stored.",
|
|
3257
3257
|
"file": "modules/hall-ui/public/idea.js",
|
|
3258
|
-
"line":
|
|
3258
|
+
"line": 229,
|
|
3259
3259
|
"origin": "js-markup",
|
|
3260
3260
|
"confidence": "certain"
|
|
3261
3261
|
},
|
|
@@ -3264,7 +3264,7 @@
|
|
|
3264
3264
|
"surface": "builders-hall",
|
|
3265
3265
|
"text": "Could not load the queue just now.",
|
|
3266
3266
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3267
|
-
"line":
|
|
3267
|
+
"line": 387,
|
|
3268
3268
|
"origin": "js-markup",
|
|
3269
3269
|
"confidence": "certain"
|
|
3270
3270
|
},
|
|
@@ -3804,7 +3804,7 @@
|
|
|
3804
3804
|
"surface": "builders-hall",
|
|
3805
3805
|
"text": "Develop this idea",
|
|
3806
3806
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3807
|
-
"line":
|
|
3807
|
+
"line": 422,
|
|
3808
3808
|
"origin": "js-text-assign",
|
|
3809
3809
|
"confidence": "certain"
|
|
3810
3810
|
},
|
|
@@ -3993,7 +3993,7 @@
|
|
|
3993
3993
|
"surface": "builders-hall",
|
|
3994
3994
|
"text": "Draft discarded.",
|
|
3995
3995
|
"file": "modules/hall-ui/public/ideas.js",
|
|
3996
|
-
"line":
|
|
3996
|
+
"line": 347,
|
|
3997
3997
|
"origin": "js-notice",
|
|
3998
3998
|
"confidence": "certain"
|
|
3999
3999
|
},
|
|
@@ -4263,7 +4263,7 @@
|
|
|
4263
4263
|
"surface": "builders-hall",
|
|
4264
4264
|
"text": "Five questions, and a score that counts how much of each you actually answered.",
|
|
4265
4265
|
"file": "modules/hall-ui/public/ideas.js",
|
|
4266
|
-
"line":
|
|
4266
|
+
"line": 448,
|
|
4267
4267
|
"origin": "js-text-assign",
|
|
4268
4268
|
"confidence": "certain"
|
|
4269
4269
|
},
|
|
@@ -4740,7 +4740,7 @@
|
|
|
4740
4740
|
"surface": "builders-hall",
|
|
4741
4741
|
"text": "Idea {…}",
|
|
4742
4742
|
"file": "modules/hall-ui/public/idea.js",
|
|
4743
|
-
"line":
|
|
4743
|
+
"line": 252,
|
|
4744
4744
|
"origin": "js-text-assign",
|
|
4745
4745
|
"confidence": "certain"
|
|
4746
4746
|
},
|
|
@@ -4884,7 +4884,7 @@
|
|
|
4884
4884
|
"surface": "builders-hall",
|
|
4885
4885
|
"text": "Its author marked this as needing development — anyone can turn it into a Full Idea, and they keep the credit for having had it.",
|
|
4886
4886
|
"file": "modules/hall-ui/public/idea.js",
|
|
4887
|
-
"line":
|
|
4887
|
+
"line": 243,
|
|
4888
4888
|
"origin": "js-markup",
|
|
4889
4889
|
"confidence": "certain"
|
|
4890
4890
|
},
|
|
@@ -5712,7 +5712,7 @@
|
|
|
5712
5712
|
"surface": "builders-hall",
|
|
5713
5713
|
"text": "No description.",
|
|
5714
5714
|
"file": "modules/hall-ui/public/idea.js",
|
|
5715
|
-
"line":
|
|
5715
|
+
"line": 114,
|
|
5716
5716
|
"origin": "js-markup",
|
|
5717
5717
|
"confidence": "certain"
|
|
5718
5718
|
},
|
|
@@ -5946,7 +5946,7 @@
|
|
|
5946
5946
|
"surface": "builders-hall",
|
|
5947
5947
|
"text": "Not scored.",
|
|
5948
5948
|
"file": "modules/hall-ui/public/idea.js",
|
|
5949
|
-
"line":
|
|
5949
|
+
"line": 78,
|
|
5950
5950
|
"origin": "js-markup",
|
|
5951
5951
|
"confidence": "certain"
|
|
5952
5952
|
},
|
|
@@ -6045,7 +6045,7 @@
|
|
|
6045
6045
|
"surface": "builders-hall",
|
|
6046
6046
|
"text": "Nothing is waiting. A spark appears here when its author offers a quick idea up for development.",
|
|
6047
6047
|
"file": "modules/hall-ui/public/ideas.js",
|
|
6048
|
-
"line":
|
|
6048
|
+
"line": 365,
|
|
6049
6049
|
"origin": "js-markup",
|
|
6050
6050
|
"confidence": "certain"
|
|
6051
6051
|
},
|
|
@@ -7548,7 +7548,7 @@
|
|
|
7548
7548
|
"surface": "builders-hall",
|
|
7549
7549
|
"text": "Showing {…} of {…}.",
|
|
7550
7550
|
"file": "modules/hall-ui/public/ideas.js",
|
|
7551
|
-
"line":
|
|
7551
|
+
"line": 369,
|
|
7552
7552
|
"origin": "js-markup",
|
|
7553
7553
|
"confidence": "certain"
|
|
7554
7554
|
},
|
|
@@ -8961,7 +8961,7 @@
|
|
|
8961
8961
|
"surface": "builders-hall",
|
|
8962
8962
|
"text": "Write one",
|
|
8963
8963
|
"file": "modules/hall-ui/public/idea.js",
|
|
8964
|
-
"line":
|
|
8964
|
+
"line": 243,
|
|
8965
8965
|
"origin": "js-markup",
|
|
8966
8966
|
"confidence": "certain"
|
|
8967
8967
|
},
|
|
@@ -9807,7 +9807,7 @@
|
|
|
9807
9807
|
"surface": "builders-hall",
|
|
9808
9808
|
"text": "idea {…}",
|
|
9809
9809
|
"file": "modules/hall-ui/public/idea.js",
|
|
9810
|
-
"line":
|
|
9810
|
+
"line": 198,
|
|
9811
9811
|
"origin": "js-markup",
|
|
9812
9812
|
"confidence": "certain"
|
|
9813
9813
|
},
|
|
@@ -9816,7 +9816,7 @@
|
|
|
9816
9816
|
"surface": "builders-hall",
|
|
9817
9817
|
"text": "idea {…}",
|
|
9818
9818
|
"file": "modules/hall-ui/public/ideas.js",
|
|
9819
|
-
"line":
|
|
9819
|
+
"line": 334,
|
|
9820
9820
|
"origin": "js-markup",
|
|
9821
9821
|
"confidence": "certain"
|
|
9822
9822
|
},
|
|
@@ -9987,7 +9987,7 @@
|
|
|
9987
9987
|
"surface": "builders-hall",
|
|
9988
9988
|
"text": "merged into",
|
|
9989
9989
|
"file": "modules/hall-ui/public/idea.js",
|
|
9990
|
-
"line":
|
|
9990
|
+
"line": 198,
|
|
9991
9991
|
"origin": "js-markup",
|
|
9992
9992
|
"confidence": "certain"
|
|
9993
9993
|
},
|
|
@@ -10383,7 +10383,7 @@
|
|
|
10383
10383
|
"surface": "builders-hall",
|
|
10384
10384
|
"text": "task {…}",
|
|
10385
10385
|
"file": "modules/hall-ui/public/idea.js",
|
|
10386
|
-
"line":
|
|
10386
|
+
"line": 195,
|
|
10387
10387
|
"origin": "js-markup",
|
|
10388
10388
|
"confidence": "certain"
|
|
10389
10389
|
},
|
|
@@ -10950,7 +10950,7 @@
|
|
|
10950
10950
|
"surface": "builders-hall",
|
|
10951
10951
|
"text": "{…}/100 complete",
|
|
10952
10952
|
"file": "modules/hall-ui/public/idea.js",
|
|
10953
|
-
"line":
|
|
10953
|
+
"line": 81,
|
|
10954
10954
|
"origin": "js-markup",
|
|
10955
10955
|
"confidence": "certain"
|
|
10956
10956
|
},
|
|
@@ -39,7 +39,15 @@
|
|
|
39
39
|
'use strict';
|
|
40
40
|
|
|
41
41
|
const API = '/api/gds';
|
|
42
|
-
|
|
42
|
+
// NO baseUrl OVERRIDE — this page calls the GENERATED namespaced methods
|
|
43
|
+
// (api.inbox.*, api.me.*), whose path templates are API-RELATIVE ('/inbox/template').
|
|
44
|
+
// baseUrl:'' means "these paths are already absolute" and passes them through
|
|
45
|
+
// unchanged, which sent every request to the SITE ROOT and 404'd — this page was
|
|
46
|
+
// dead in production from the day it shipped. Letting it default resolves them
|
|
47
|
+
// against '/api/bongos/v1' (api-prefix.js VERSIONED_API_PREFIX), where the router
|
|
48
|
+
// actually mounts. A page that DOES pass baseUrl:'' must build full paths itself
|
|
49
|
+
// via api.request('GET', `${API}/...`) as work.js/copy-desk.js do — never mix the two.
|
|
50
|
+
const api = window.BongosClient.createClient({ credentials: 'same-origin', throwOnError: false });
|
|
43
51
|
const { escapeHtml, $, fmtDate } = window.OTB;
|
|
44
52
|
|
|
45
53
|
const ideaId = () => {
|
|
@@ -44,7 +44,15 @@
|
|
|
44
44
|
(() => {
|
|
45
45
|
'use strict';
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
// NO baseUrl OVERRIDE — this page calls the GENERATED namespaced methods
|
|
48
|
+
// (api.inbox.*, api.me.*), whose path templates are API-RELATIVE ('/inbox/template').
|
|
49
|
+
// baseUrl:'' means "these paths are already absolute" and passes them through
|
|
50
|
+
// unchanged, which sent every request to the SITE ROOT and 404'd — this page was
|
|
51
|
+
// dead in production from the day it shipped. Letting it default resolves them
|
|
52
|
+
// against '/api/bongos/v1' (api-prefix.js VERSIONED_API_PREFIX), where the router
|
|
53
|
+
// actually mounts. A page that DOES pass baseUrl:'' must build full paths itself
|
|
54
|
+
// via api.request('GET', `${API}/...`) as work.js/copy-desk.js do — never mix the two.
|
|
55
|
+
const api = window.BongosClient.createClient({ credentials: 'same-origin', throwOnError: false });
|
|
48
56
|
const { escapeHtml, $, toast } = window.OTB;
|
|
49
57
|
|
|
50
58
|
// One key per THING BEING WRITTEN. A new idea and a spark being developed are
|
|
@@ -417,6 +425,14 @@
|
|
|
417
425
|
}
|
|
418
426
|
|
|
419
427
|
async function main() {
|
|
428
|
+
// THE QUEUE IS LOADED FIRST, AND UNCONDITIONALLY. The composer and the spark
|
|
429
|
+
// queue are independent surfaces that happen to share a page: the queue needs
|
|
430
|
+
// no template. Loading it after the template check meant one failed fetch took
|
|
431
|
+
// BOTH down — the reported symptom was "Could not load the idea template" AND a
|
|
432
|
+
// queue stuck on "Loading…" forever, which read as two bugs and was one.
|
|
433
|
+
// A surface that does not depend on the failure must not be hostage to it.
|
|
434
|
+
loadQueue();
|
|
435
|
+
|
|
420
436
|
const r = await api.inbox.getInboxTemplate({});
|
|
421
437
|
if (!r.ok || !r.data || !r.data.template) {
|
|
422
438
|
$('#fi-sub').textContent = 'Could not load the idea template. Reload, or file from the terminal with capture.js.';
|
|
@@ -434,10 +450,6 @@
|
|
|
434
450
|
$('#fi-form').addEventListener('submit', file);
|
|
435
451
|
$('#fi-clear').addEventListener('click', discard);
|
|
436
452
|
|
|
437
|
-
// The queue loads on every visit, develop mode or not: having just picked one
|
|
438
|
-
// up, seeing what else is waiting is the natural next thing.
|
|
439
|
-
loadQueue();
|
|
440
|
-
|
|
441
453
|
if (DEVELOP_ID) {
|
|
442
454
|
const d = await api.inbox.getInboxId({ id: DEVELOP_ID });
|
|
443
455
|
if (!d.ok || !d.data || !d.data.idea) {
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.348",
|
|
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.348",
|
|
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.348",
|
|
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",
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/client-baseurl-guard.js — a web page must not send its API calls to
|
|
3
|
+
// the site root (task 1003263).
|
|
4
|
+
//
|
|
5
|
+
// THE BUG THIS EXISTS TO STOP. The generated client (clients/bongos-client) exposes
|
|
6
|
+
// two call styles, and mixing them is silent and fatal:
|
|
7
|
+
//
|
|
8
|
+
// NAMESPACED api.inbox.getInboxTemplate({}) → path template '/inbox/template',
|
|
9
|
+
// API-RELATIVE, needs the API base
|
|
10
|
+
// ESCAPE HATCH api.request('GET', `${API}/me`) → caller supplies the FULL path
|
|
11
|
+
//
|
|
12
|
+
// createClient documents `baseUrl: ''` as "paths are already absolute" and passes
|
|
13
|
+
// them through unchanged. So a page that sets baseUrl:'' AND calls a namespaced
|
|
14
|
+
// method requests '/inbox/template' — the SITE ROOT, where nothing is mounted — and
|
|
15
|
+
// 404s on every call. The router mounts at '/api/bongos/v1' (api-prefix.js
|
|
16
|
+
// VERSIONED_API_PREFIX), which is exactly the client's own default.
|
|
17
|
+
//
|
|
18
|
+
// WHY A FITNESS CHECK AND NOT A TEST. Three pages shipped this defect — ideas.js and
|
|
19
|
+
// idea.js were dead in production from the day they shipped, and collab.js was caught
|
|
20
|
+
// in review one commit before it would have been. Every page test stubs the client
|
|
21
|
+
// method directly, so URL construction is never exercised; tests/idea_hall_composer.mjs
|
|
22
|
+
// even asserted with a regex against the source text, which passes happily on a broken
|
|
23
|
+
// URL. 54 green tests over pages that 404 on every call. The defect is invisible to the
|
|
24
|
+
// test style this repo uses for hall pages, so it needs a guard at a different altitude.
|
|
25
|
+
//
|
|
26
|
+
// STATIC AND CONSERVATIVE. It flags only the unambiguous combination — an explicit
|
|
27
|
+
// empty baseUrl in a createClient call, in a file that also makes a namespaced call.
|
|
28
|
+
// A page using the escape hatch with baseUrl:'' is CORRECT and stays silent (work.js,
|
|
29
|
+
// copy-desk.js, goal-inbox.js and most of the hall do exactly that).
|
|
30
|
+
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
const fs = require('node:fs');
|
|
34
|
+
const path = require('node:path');
|
|
35
|
+
const { execFileSync } = require('node:child_process');
|
|
36
|
+
|
|
37
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
38
|
+
const NAME = 'a web page sends its API calls to the API, not the site root (task 1003263)';
|
|
39
|
+
|
|
40
|
+
// createClient({ ... baseUrl: '' ... }) — an EXPLICIT empty base. Omitting baseUrl is
|
|
41
|
+
// the correct fix and must not match; only the literal empty string is the defect.
|
|
42
|
+
const EMPTY_BASEURL = /createClient\(\s*\{[^}]*baseUrl\s*:\s*(''|""|`\s*`)/;
|
|
43
|
+
|
|
44
|
+
// api.<namespace>.<method>( — the generated namespaced style. `api.request(` is the
|
|
45
|
+
// escape hatch and is deliberately NOT matched: with it, baseUrl:'' is correct.
|
|
46
|
+
const NAMESPACED_CALL = /\bapi\s*\.\s*(?!request\b)[A-Za-z_$][\w$]*\s*\.\s*[A-Za-z_$][\w$]*\s*\(/;
|
|
47
|
+
|
|
48
|
+
// Every tracked browser-served script. Server code never calls createClient this way.
|
|
49
|
+
function trackedPageScripts() {
|
|
50
|
+
try {
|
|
51
|
+
const out = execFileSync('git', ['ls-files', 'modules/*/public/*.js', 'public/*.js'],
|
|
52
|
+
{ cwd: REPO_ROOT, encoding: 'utf8' });
|
|
53
|
+
return out.split('\n').map((s) => s.trim()).filter(Boolean);
|
|
54
|
+
} catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function scanPages(files, read) {
|
|
60
|
+
const violations = [];
|
|
61
|
+
let scanned = 0;
|
|
62
|
+
let usingClient = 0;
|
|
63
|
+
for (const rel of files) {
|
|
64
|
+
const src = read(rel);
|
|
65
|
+
if (src == null) continue;
|
|
66
|
+
scanned += 1;
|
|
67
|
+
if (!/createClient\(/.test(src)) continue;
|
|
68
|
+
usingClient += 1;
|
|
69
|
+
if (!EMPTY_BASEURL.test(src)) continue;
|
|
70
|
+
if (!NAMESPACED_CALL.test(src)) continue; // escape-hatch page — baseUrl:'' is right
|
|
71
|
+
const m = NAMESPACED_CALL.exec(src);
|
|
72
|
+
violations.push(
|
|
73
|
+
`${rel} sets baseUrl:'' AND calls the generated client (${m[0].replace(/\s+/g, '')}…). `
|
|
74
|
+
+ "Those paths are API-relative, so baseUrl:'' sends them to the site root and every "
|
|
75
|
+
+ 'call 404s. Drop the baseUrl override, or switch every call to '
|
|
76
|
+
+ "api.request('METHOD', `${API}/…`) with a full path.",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return { violations, scanned, usingClient };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function checkClientBaseUrl({ files, read } = {}) {
|
|
83
|
+
const list = files || trackedPageScripts();
|
|
84
|
+
if (!list.length) {
|
|
85
|
+
return {
|
|
86
|
+
name: NAME, ok: false, hardFail: true,
|
|
87
|
+
violations: ['scan defect — enumerated 0 tracked page scripts; a broken enumeration, not a clean result.'],
|
|
88
|
+
warnings: [], note: 'static scan of browser-served scripts for a root-bound API client.',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const reader = read || ((rel) => {
|
|
92
|
+
try { return fs.readFileSync(path.join(REPO_ROOT, rel), 'utf8'); } catch { return null; }
|
|
93
|
+
});
|
|
94
|
+
const { violations, scanned, usingClient } = scanPages(list, reader);
|
|
95
|
+
return {
|
|
96
|
+
name: NAME,
|
|
97
|
+
ok: violations.length === 0,
|
|
98
|
+
hardFail: violations.length > 0,
|
|
99
|
+
violations,
|
|
100
|
+
warnings: [],
|
|
101
|
+
note: `${scanned} browser-served script(s) scanned, ${usingClient} building a client: none may combine `
|
|
102
|
+
+ "baseUrl:'' with a generated namespaced call (api.<ns>.<method>()) — that pairing 404s every request. "
|
|
103
|
+
+ "baseUrl:'' with api.request() and a full path is the correct escape-hatch style and is not flagged.",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function main() {
|
|
108
|
+
const r = checkClientBaseUrl();
|
|
109
|
+
for (const v of r.violations) console.error(`✗ ${v}`);
|
|
110
|
+
console.log(`${r.hardFail ? 'FAIL' : 'PASS'} ${r.note}`);
|
|
111
|
+
process.exit(r.hardFail ? 1 : 0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (require.main === module) main();
|
|
115
|
+
|
|
116
|
+
module.exports = { checkClientBaseUrl, scanPages, trackedPageScripts, EMPTY_BASEURL, NAMESPACED_CALL };
|
|
@@ -160,6 +160,28 @@ async function fetchLive(base, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
// Is the automation that should have PREVENTED this divergence itself alive? (task 1003158)
|
|
164
|
+
//
|
|
165
|
+
// This script detects the SYMPTOM — live is behind main — and only ever at a moment a human is
|
|
166
|
+
// already looking. What went unwatched is the auto-upgrade subscription itself: when it stopped,
|
|
167
|
+
// nothing said so, and cloudbongos.com sat 36 releases behind for days. Reading the roster here means
|
|
168
|
+
// a dead upgrader is reported in the same breath as the staleness it was supposed to prevent.
|
|
169
|
+
//
|
|
170
|
+
// Speaks ONLY when a subscription manifest is actually present. An ordinary core checkout has none by
|
|
171
|
+
// design (ADR 0136 — it ships empty), and alarming about that every session is exactly the noise that
|
|
172
|
+
// gets a real warning scrolled past. Present-but-broken, or present-and-subscribing-nothing, is the
|
|
173
|
+
// signal worth having: someone meant to enroll instances here and none are being upgraded.
|
|
174
|
+
function subscriptionHealth(dir = REPO_ROOT) {
|
|
175
|
+
try {
|
|
176
|
+
const uc = require('./update-channel');
|
|
177
|
+
const roster = uc.loadSubscriptions({ instanceDir: dir });
|
|
178
|
+
if (!roster.present) return null;
|
|
179
|
+
return uc.describeRoster(roster, { controlPlane: true });
|
|
180
|
+
} catch {
|
|
181
|
+
return null; // never let the health probe break the divergence check it rides along with
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
163
185
|
async function check({ base, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
164
186
|
const target = base || arg('--url') || API_BASE;
|
|
165
187
|
const local = coreVersionAt(null);
|
|
@@ -173,7 +195,9 @@ async function check({ base, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
|
173
195
|
const unreleased = unreleasedSince(mainRef);
|
|
174
196
|
const live = await fetchLive(target, timeoutMs);
|
|
175
197
|
|
|
176
|
-
|
|
198
|
+
const subscriptions = subscriptionHealth();
|
|
199
|
+
|
|
200
|
+
if (live.error) return { ok: false, undetermined: true, reason: `could not read ${live.url}: ${live.error}`, target, local, onMain, unreleased, subscriptions };
|
|
177
201
|
|
|
178
202
|
const behind = cmpSemver(live.coreVersion, onMain) < 0;
|
|
179
203
|
const ahead = cmpSemver(live.coreVersion, onMain) > 0;
|
|
@@ -181,7 +205,7 @@ async function check({ base, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
|
181
205
|
|
|
182
206
|
return {
|
|
183
207
|
ok: !behind && !hasUnreleased,
|
|
184
|
-
target, local, onMain, mainRef, unreleased, live, behind, ahead, hasUnreleased,
|
|
208
|
+
target, local, onMain, mainRef, unreleased, live, behind, ahead, hasUnreleased, subscriptions,
|
|
185
209
|
};
|
|
186
210
|
}
|
|
187
211
|
|
|
@@ -232,6 +256,13 @@ function report(r, { quiet = false } = {}) {
|
|
|
232
256
|
if (r.ahead) {
|
|
233
257
|
say(` note: live (${r.live.coreVersion}) is AHEAD of ${r.mainRef} (${r.onMain}) — this checkout is stale; fetch.`);
|
|
234
258
|
}
|
|
259
|
+
// The watcher's watcher: if this checkout carries a subscription roster and that roster is not
|
|
260
|
+
// healthy, the unattended upgrade is not going to close the gap above on its own.
|
|
261
|
+
if (r.subscriptions && r.subscriptions.level !== 'ok') {
|
|
262
|
+
say('');
|
|
263
|
+
say(` ${r.subscriptions.level === 'alarm' ? '⚠ AUTO-UPGRADE' : 'note: auto-upgrade'} — ${r.subscriptions.message}`);
|
|
264
|
+
if (r.behind) say(' So the gap above will NOT close by itself.');
|
|
265
|
+
}
|
|
235
266
|
say('');
|
|
236
267
|
say(' Merging to main deploys nothing by design. "shipped" means "merged" —');
|
|
237
268
|
say(' it does not mean users can see it.');
|
|
@@ -317,4 +348,4 @@ if (require.main === module) {
|
|
|
317
348
|
});
|
|
318
349
|
}
|
|
319
350
|
|
|
320
|
-
module.exports = { check, report, line, coreVersionAt, unreleasedSince, cmpSemver };
|
|
351
|
+
module.exports = { check, report, line, coreVersionAt, unreleasedSince, cmpSemver, subscriptionHealth };
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -1307,6 +1307,7 @@ const CHECKS = [
|
|
|
1307
1307
|
require('./control-manifest.js').checkControlsExist, // Check 21 — task 1003131: goal-1000076 controls assert their own existence (the ADR 0022 assurance-of-assurance)
|
|
1308
1308
|
require('./routine-schedule.js').checkRoutineCadencesScheduled, // Check 25 — task 1003209: a routine that declares a cadence must have a scheduler firing it AT that period (ADR 0188)
|
|
1309
1309
|
require('./baseline-staleness.js').checkBaselineFloors, // Check 26 — task 1003210: a shrink-only baseline that never shrinks is unmanaged debt; a floor exempting one must say why
|
|
1310
|
+
require('./client-baseurl-guard.js').checkClientBaseUrl, // Check 27 — task 1003263: a page must not combine baseUrl:'' with a generated namespaced call (that pairing 404s every request; three pages shipped it, two of them dead in prod)
|
|
1310
1311
|
];
|
|
1311
1312
|
|
|
1312
1313
|
function runAll() {
|
|
@@ -32,6 +32,23 @@ const DEFAULT_CHANNEL = 'patch';
|
|
|
32
32
|
// core template — a fresh instance subscribes nothing until the operator lists a target here.
|
|
33
33
|
const SUBSCRIPTIONS_REL = path.join('config', 'update-subscriptions.json');
|
|
34
34
|
|
|
35
|
+
// Why a roster came back the way it did. The routine reports this EVERY run: an upgrader whose only
|
|
36
|
+
// job is keeping instances current must never be indistinguishable from one that has stopped. Before
|
|
37
|
+
// task 1003158 an absent manifest, a malformed manifest and a deliberately empty roster all produced
|
|
38
|
+
// the same silent `instances: []`, which is exactly how cloudbongos.com sat 36 releases behind for
|
|
39
|
+
// days with nothing complaining (the same failure SHAPE as blocker 1000097 / task 1002521).
|
|
40
|
+
const ROSTER_OK = 'ok'; // at least one usable entry
|
|
41
|
+
const ROSTER_ABSENT = 'manifest-absent'; // no file at all
|
|
42
|
+
const ROSTER_MALFORMED = 'manifest-malformed';// file present, JSON unparseable
|
|
43
|
+
const ROSTER_EMPTY = 'roster-empty'; // file parsed, nothing subscribable in it
|
|
44
|
+
const ROSTER_REASONS = [ROSTER_OK, ROSTER_ABSENT, ROSTER_MALFORMED, ROSTER_EMPTY];
|
|
45
|
+
|
|
46
|
+
// How many releases a subscribed instance may fall behind before "the upgrader stopped" is the only
|
|
47
|
+
// remaining explanation. A successful sweep takes the HIGHEST permitted version in one jump, so a
|
|
48
|
+
// healthy roster returns to 0 behind after every run — a backlog that keeps growing means no run is
|
|
49
|
+
// landing. 5 leaves room for a busy publish-on-merge day between two daily sweeps without crying wolf.
|
|
50
|
+
const DEFAULT_STALENESS_THRESHOLD = 5;
|
|
51
|
+
|
|
35
52
|
// ---- semver (stable x.y.z, optional prerelease tag) -------------------------
|
|
36
53
|
// A deliberately small parser — the repo avoids an npm-semver dependency in the CLI path (see
|
|
37
54
|
// upgrade.js versionFromDep). We only need compare + the major/minor/patch fields.
|
|
@@ -143,16 +160,23 @@ function parseNpmViewVersions(stdout) {
|
|
|
143
160
|
// this control plane keeps up to date. Never throws: a missing/malformed file ⇒ an empty roster
|
|
144
161
|
// (subscribe nothing), because an unreadable manifest must not upgrade anything. Each entry is
|
|
145
162
|
// normalized to { slug, dir, service, healthUrl, channel } with the channel validated + defaulted.
|
|
163
|
+
//
|
|
164
|
+
// Still fails CLOSED, no longer fails QUIET (task 1003158): every return carries a `reason` from
|
|
165
|
+
// ROSTER_REASONS and a `dropped` count of entries that were unusable, so a caller can say WHY it is
|
|
166
|
+
// about to do nothing. Pair it with describeRoster() for the line to print.
|
|
146
167
|
function loadSubscriptions({ instanceDir = process.cwd(), fsImpl = fs, file = SUBSCRIPTIONS_REL } = {}) {
|
|
147
168
|
const abs = path.isAbsolute(file) ? file : path.join(instanceDir, file);
|
|
148
169
|
let raw;
|
|
149
|
-
try { raw = fsImpl.readFileSync(abs, 'utf8'); } catch (_) { return { defaultChannel: DEFAULT_CHANNEL, instances: [], source: abs, present: false }; }
|
|
170
|
+
try { raw = fsImpl.readFileSync(abs, 'utf8'); } catch (_) { return { defaultChannel: DEFAULT_CHANNEL, instances: [], source: abs, present: false, dropped: 0, reason: ROSTER_ABSENT }; }
|
|
150
171
|
let json;
|
|
151
|
-
try { json = JSON.parse(raw); } catch (e) { return { defaultChannel: DEFAULT_CHANNEL, instances: [], source: abs, present: true, error: `malformed JSON: ${e.message}
|
|
172
|
+
try { json = JSON.parse(raw); } catch (e) { return { defaultChannel: DEFAULT_CHANNEL, instances: [], source: abs, present: true, dropped: 0, error: `malformed JSON: ${e.message}`, reason: ROSTER_MALFORMED }; }
|
|
152
173
|
const defaultChannel = normalizeChannel(json.defaultChannel);
|
|
153
174
|
const rows = Array.isArray(json.instances) ? json.instances : [];
|
|
154
|
-
|
|
155
|
-
|
|
175
|
+
// An entry without a dir can't be upgraded — drop it, but COUNT the drops. Silently discarding an
|
|
176
|
+
// operator's malformed row is how an instance believes it is enrolled while nothing ever touches it.
|
|
177
|
+
const usable = rows.filter((r) => r && typeof r === 'object' && r.dir);
|
|
178
|
+
const dropped = rows.length - usable.length;
|
|
179
|
+
const instances = usable
|
|
156
180
|
.map((r) => ({
|
|
157
181
|
slug: String(r.slug || r.dir),
|
|
158
182
|
dir: String(r.dir),
|
|
@@ -178,7 +202,90 @@ function loadSubscriptions({ instanceDir = process.cwd(), fsImpl = fs, file = SU
|
|
|
178
202
|
? Object.fromEntries(Object.entries(r.env).filter(([, v]) => typeof v === 'string'))
|
|
179
203
|
: {},
|
|
180
204
|
}));
|
|
181
|
-
return {
|
|
205
|
+
return {
|
|
206
|
+
defaultChannel, instances, source: abs, present: true, dropped,
|
|
207
|
+
reason: instances.length ? ROSTER_OK : ROSTER_EMPTY,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---- reporting: make "nothing happened" audible -----------------------------
|
|
212
|
+
|
|
213
|
+
// Turn a loadSubscriptions() result into the line a routine prints EVERY run, plus a level a caller
|
|
214
|
+
// can act on ('ok' | 'warn' | 'alarm'). The judgement that matters is `controlPlane`: a fresh
|
|
215
|
+
// instance subscribing nothing is the shipped default (ADR 0136 — the manifest ships empty), but a
|
|
216
|
+
// CONTROL PLANE that subscribes nothing has silently stopped doing its only job, and that is an alarm.
|
|
217
|
+
// A malformed manifest is an alarm everywhere — an operator wrote something and it does not parse.
|
|
218
|
+
function describeRoster(roster, { controlPlane = false } = {}) {
|
|
219
|
+
const r = roster || {};
|
|
220
|
+
const reason = ROSTER_REASONS.includes(r.reason) ? r.reason : ROSTER_EMPTY;
|
|
221
|
+
const where = r.source || SUBSCRIPTIONS_REL;
|
|
222
|
+
const dropped = Number(r.dropped) || 0;
|
|
223
|
+
const n = Array.isArray(r.instances) ? r.instances.length : 0;
|
|
224
|
+
|
|
225
|
+
if (reason === ROSTER_MALFORMED) {
|
|
226
|
+
return { level: 'alarm', reason, count: n, dropped,
|
|
227
|
+
message: `update-subscriptions: MALFORMED manifest at ${where} (${r.error || 'unparseable'}) — subscribing NOTHING. No instance will be upgraded until this parses.` };
|
|
228
|
+
}
|
|
229
|
+
if (reason === ROSTER_ABSENT) {
|
|
230
|
+
return { level: controlPlane ? 'alarm' : 'warn', reason, count: n, dropped,
|
|
231
|
+
message: `update-subscriptions: no manifest at ${where} — subscribing NOTHING.${controlPlane ? ' This is a CONTROL PLANE: it is supposed to keep instances current and currently keeps none.' : ' That is the shipped default; add an instance to enroll it.'}` };
|
|
232
|
+
}
|
|
233
|
+
if (reason === ROSTER_EMPTY) {
|
|
234
|
+
return { level: controlPlane ? 'alarm' : 'warn', reason, count: n, dropped,
|
|
235
|
+
message: `update-subscriptions: manifest at ${where} lists no upgradable instance${dropped ? ` (${dropped} entr${dropped === 1 ? 'y' : 'ies'} dropped for a missing \`dir\`)` : ''} — subscribing NOTHING.${controlPlane ? ' This is a CONTROL PLANE: it is supposed to keep instances current and currently keeps none.' : ''}` };
|
|
236
|
+
}
|
|
237
|
+
// Usable entries — still surface partial loss, which is the quiet half of an enrolled-but-untouched
|
|
238
|
+
// instance: the sweep looks healthy while the operator's broken row is never upgraded.
|
|
239
|
+
if (dropped > 0) {
|
|
240
|
+
return { level: 'warn', reason, count: n, dropped,
|
|
241
|
+
message: `update-subscriptions: ${n} instance${n === 1 ? '' : 's'} subscribed, but ${dropped} entr${dropped === 1 ? 'y was' : 'ies were'} DROPPED for a missing \`dir\` in ${where} — those instances are NOT being upgraded.` };
|
|
242
|
+
}
|
|
243
|
+
return { level: 'ok', reason, count: n, dropped,
|
|
244
|
+
message: `update-subscriptions: ${n} instance${n === 1 ? '' : 's'} subscribed from ${where}.` };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---- watching the watcher ---------------------------------------------------
|
|
248
|
+
|
|
249
|
+
// How far behind is this instance in releases its OWN channel is entitled to take? Not raw version
|
|
250
|
+
// distance: majors, prereleases and (on the patch channel) minors are releases the subscription is
|
|
251
|
+
// correctly declining, and counting them would alarm on instances that are behaving exactly as
|
|
252
|
+
// configured. `behind` is therefore "stable releases this channel WOULD have taken and has not".
|
|
253
|
+
//
|
|
254
|
+
// This is the check that was missing (task 1003158). deploy-divergence.js detects the SYMPTOM — live
|
|
255
|
+
// is behind main — at ship time, when a human is already looking. Nothing watched whether the
|
|
256
|
+
// upgrader itself was still alive, so its death was only ever discovered by someone noticing stale UI
|
|
257
|
+
// days later. A healthy sweep drives `behind` to 0 every run, so a persistent backlog IS the alarm.
|
|
258
|
+
//
|
|
259
|
+
// A `pinned` instance is never stale: channelAllows declines everything, so behind === 0 by
|
|
260
|
+
// construction. That is correct — a pinned instance is not neglected, it is pinned.
|
|
261
|
+
function subscriptionStaleness({ installed, channel, available, threshold = DEFAULT_STALENESS_THRESHOLD } = {}) {
|
|
262
|
+
const ch = normalizeChannel(channel);
|
|
263
|
+
const list = Array.isArray(available) ? available : [];
|
|
264
|
+
// `installed` is parsed and the channel re-normalized ONCE, not per candidate:
|
|
265
|
+
// channelAllows re-does both on every element, and this filter is the only place
|
|
266
|
+
// that calls it in a loop. Cheap either way at registry scale — hoisted because
|
|
267
|
+
// the invariant genuinely is invariant, not because the arithmetic hurts.
|
|
268
|
+
const from = parseSemver(installed);
|
|
269
|
+
const behind = (ch === 'pinned' || !from) ? 0
|
|
270
|
+
: list.filter((v) => channelAllows(installed, v, ch)).length;
|
|
271
|
+
const t = Number(threshold);
|
|
272
|
+
const limit = Number.isFinite(t) && t >= 0 ? t : DEFAULT_STALENESS_THRESHOLD;
|
|
273
|
+
return {
|
|
274
|
+
channel: ch,
|
|
275
|
+
installed: installed == null ? null : String(installed),
|
|
276
|
+
target: resolveChannelTarget({ installed, channel: ch, available: list }),
|
|
277
|
+
behind,
|
|
278
|
+
threshold: limit,
|
|
279
|
+
stale: behind > limit,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// The alarm line for one subscribed instance, or null when it is healthy (nothing to say).
|
|
284
|
+
function describeStaleness(st, { slug = 'instance' } = {}) {
|
|
285
|
+
if (!st || !st.stale) return null;
|
|
286
|
+
return `update-subscriptions: ${slug} is ${st.behind} ${st.channel} release${st.behind === 1 ? '' : 's'} behind `
|
|
287
|
+
+ `(installed ${st.installed || 'unknown'}, entitled to ${st.target}) — over the ${st.threshold} threshold. `
|
|
288
|
+
+ 'A healthy sweep clears this to zero every run, so the upgrader has most likely stopped.';
|
|
182
289
|
}
|
|
183
290
|
|
|
184
291
|
module.exports = {
|
|
@@ -195,4 +302,13 @@ module.exports = {
|
|
|
195
302
|
listAvailableVersions,
|
|
196
303
|
parseNpmViewVersions,
|
|
197
304
|
loadSubscriptions,
|
|
305
|
+
ROSTER_OK,
|
|
306
|
+
ROSTER_ABSENT,
|
|
307
|
+
ROSTER_MALFORMED,
|
|
308
|
+
ROSTER_EMPTY,
|
|
309
|
+
ROSTER_REASONS,
|
|
310
|
+
DEFAULT_STALENESS_THRESHOLD,
|
|
311
|
+
describeRoster,
|
|
312
|
+
subscriptionStaleness,
|
|
313
|
+
describeStaleness,
|
|
198
314
|
};
|
package/src/module-api.js
CHANGED
|
@@ -1124,8 +1124,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
1124
1124
|
// landed since 1.19.345 with no explicit bump. run 32801147433. (task 1002620)
|
|
1125
1125
|
// 1.19.347 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1126
1126
|
// landed since 1.19.346 with no explicit bump. run 32802265337. (task 1002620)
|
|
1127
|
+
// 1.19.348 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1128
|
+
// landed since 1.19.347 with no explicit bump. run 32802746363. (task 1002620)
|
|
1127
1129
|
// ---------------------------------------------------------------------------
|
|
1128
|
-
const CORE_VERSION = '1.19.
|
|
1130
|
+
const CORE_VERSION = '1.19.348'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
1129
1131
|
|
|
1130
1132
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
1131
1133
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// tests/client_baseurl_guard.mjs — the guard that stops a page 404ing every call
|
|
2
|
+
// (task 1003263).
|
|
3
|
+
//
|
|
4
|
+
// Three pages shipped the defect this guard exists to catch: ideas.js and idea.js
|
|
5
|
+
// were dead in production from the day they shipped, and collab.js was caught one
|
|
6
|
+
// commit before it would have joined them. So the guard itself is worth testing on
|
|
7
|
+
// synthetic sources rather than only on the live tree — a scan that passes because
|
|
8
|
+
// the tree happens to be clean today proves nothing about what it would REFUSE.
|
|
9
|
+
//
|
|
10
|
+
// Every case below is an in-memory file: no fixtures on disk, no dependence on the
|
|
11
|
+
// current state of modules/hall-ui/public/.
|
|
12
|
+
//
|
|
13
|
+
// Run: node tests/client_baseurl_guard.mjs
|
|
14
|
+
|
|
15
|
+
import { strict as assert } from 'node:assert';
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const { checkClientBaseUrl, scanPages } = require('../scripts/gds/client-baseurl-guard.js');
|
|
20
|
+
|
|
21
|
+
let passed = 0;
|
|
22
|
+
let failed = 0;
|
|
23
|
+
function test(name, fn) {
|
|
24
|
+
try { fn(); passed++; console.log(` ok ${name}`); }
|
|
25
|
+
catch (err) { failed++; console.error(` FAIL ${name}\n ${err.message}`); }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const from = (map) => ({
|
|
29
|
+
files: Object.keys(map),
|
|
30
|
+
read: (rel) => (rel in map ? map[rel] : null),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// --- THE DEFECT ------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
test('the exact shipped defect is refused: baseUrl:\'\' plus a namespaced call', () => {
|
|
36
|
+
const { violations } = scanPages(...Object.values(from({
|
|
37
|
+
'modules/hall-ui/public/bad.js':
|
|
38
|
+
"const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin' });\n"
|
|
39
|
+
+ 'const r = await api.inbox.getInboxTemplate({});\n',
|
|
40
|
+
})));
|
|
41
|
+
assert.equal(violations.length, 1);
|
|
42
|
+
assert.match(violations[0], /bad\.js/);
|
|
43
|
+
assert.match(violations[0], /site root/, 'the message says WHY it is wrong, not just that it is');
|
|
44
|
+
assert.match(violations[0], /Drop the baseUrl override/, 'and what to do about it');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('double-quoted and backtick empty bases are the same defect', () => {
|
|
48
|
+
for (const empty of ['""', '``']) {
|
|
49
|
+
const { violations } = scanPages(...Object.values(from({
|
|
50
|
+
'public/p.js': `const api = createClient({ baseUrl: ${empty} });\napi.me.getMe({});\n`,
|
|
51
|
+
})));
|
|
52
|
+
assert.equal(violations.length, 1, `baseUrl: ${empty} must be caught`);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// --- WHAT IT MUST NOT FLAG -------------------------------------------------
|
|
57
|
+
// A guard that fires on correct code gets suppressed, and then it protects nothing.
|
|
58
|
+
|
|
59
|
+
test("the escape-hatch style is CORRECT with baseUrl:'' — never flagged", () => {
|
|
60
|
+
// work.js, copy-desk.js and most of the hall do exactly this: empty base, but
|
|
61
|
+
// every path written out in full. Flagging it would be a false positive on the
|
|
62
|
+
// majority of the hall.
|
|
63
|
+
const { violations } = scanPages(...Object.values(from({
|
|
64
|
+
'modules/hall-ui/public/work.js':
|
|
65
|
+
"const API = '/api/bongos';\n"
|
|
66
|
+
+ "const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin' });\n"
|
|
67
|
+
+ "const res = await api.request('GET', `${API}/tasks`);\n",
|
|
68
|
+
})));
|
|
69
|
+
assert.deepEqual(violations, []);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('the fix itself passes — omitting baseUrl with namespaced calls is correct', () => {
|
|
73
|
+
const { violations } = scanPages(...Object.values(from({
|
|
74
|
+
'modules/hall-ui/public/ideas.js':
|
|
75
|
+
"const api = window.BongosClient.createClient({ credentials: 'same-origin', throwOnError: false });\n"
|
|
76
|
+
+ 'await api.inbox.getInboxTemplate({});\n',
|
|
77
|
+
})));
|
|
78
|
+
assert.deepEqual(violations, [], 'the shipped fix must not trip the guard that motivated it');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('a non-empty baseUrl is a deliberate choice, not the defect', () => {
|
|
82
|
+
const { violations } = scanPages(...Object.values(from({
|
|
83
|
+
'public/ext.js':
|
|
84
|
+
"const api = createClient({ baseUrl: 'https://other.example/api/bongos/v1' });\n"
|
|
85
|
+
+ 'api.me.getMe({});\n',
|
|
86
|
+
})));
|
|
87
|
+
assert.deepEqual(violations, []);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('a page that builds no client at all is ignored', () => {
|
|
91
|
+
const { violations, usingClient } = scanPages(...Object.values(from({
|
|
92
|
+
'public/static.js': "document.querySelector('#x').textContent = 'hi';\n",
|
|
93
|
+
})));
|
|
94
|
+
assert.deepEqual(violations, []);
|
|
95
|
+
assert.equal(usingClient, 0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// --- THE SCAN MUST NOT SILENTLY SUCCEED ------------------------------------
|
|
99
|
+
|
|
100
|
+
test('an empty enumeration is a hard FAIL, not a clean pass', () => {
|
|
101
|
+
// The failure mode that makes a guard worthless: git ls-files returns nothing,
|
|
102
|
+
// zero files are scanned, and the check reports green forever.
|
|
103
|
+
const r = checkClientBaseUrl({ files: [], read: () => null });
|
|
104
|
+
assert.equal(r.hardFail, true);
|
|
105
|
+
assert.match(r.violations[0], /scan defect/);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('an unreadable file is skipped without being counted as scanned', () => {
|
|
109
|
+
const { scanned } = scanPages(['gone.js'], () => null);
|
|
110
|
+
assert.equal(scanned, 0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('the check reports hardFail so CI actually stops', () => {
|
|
114
|
+
const r = checkClientBaseUrl(from({
|
|
115
|
+
'public/bad.js': "createClient({ baseUrl: '' });\napi.inbox.postInbox({});\n",
|
|
116
|
+
}));
|
|
117
|
+
assert.equal(r.ok, false);
|
|
118
|
+
assert.equal(r.hardFail, true, 'a page dead in prod is not a warning');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
console.log(`\nclient_baseurl_guard: ${passed} passed, ${failed} failed`);
|
|
122
|
+
process.exit(failed ? 1 : 0);
|
|
@@ -112,6 +112,17 @@ await test('the page gets both the FORM and the METER from the server', () => {
|
|
|
112
112
|
assert.match(IDEAS_JS, /getInboxTemplate/, 'the form is rendered from GET /inbox/template');
|
|
113
113
|
assert.match(IDEAS_JS, /postInboxScore/, 'the meter is filled from POST /inbox/score');
|
|
114
114
|
assert.match(IDEAS_JS, /postInbox\b/, 'and it files through POST /inbox');
|
|
115
|
+
|
|
116
|
+
// ...AND THOSE CALLS MUST REACH THE API. The three assertions above are regexes
|
|
117
|
+
// over the SOURCE TEXT, so they passed for months while the page 404'd on every
|
|
118
|
+
// one of them: baseUrl:'' told the client the API-relative templates were already
|
|
119
|
+
// absolute, and each request went to the site root. Naming a method proves the
|
|
120
|
+
// call is written, never that it is addressed — task 1003263.
|
|
121
|
+
assert.doesNotMatch(
|
|
122
|
+
IDEAS_JS,
|
|
123
|
+
/createClient\(\s*\{[^}]*baseUrl\s*:\s*(''|"")/,
|
|
124
|
+
"baseUrl:'' sends the generated client's API-relative paths to the site root",
|
|
125
|
+
);
|
|
115
126
|
});
|
|
116
127
|
|
|
117
128
|
await test('the page does not print a pass bar — that rule lives in economy', () => {
|
package/tests/update_channel.mjs
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// 4. parseNpmViewVersions — the array / bare-string / empty shapes npm view emits.
|
|
9
9
|
// 5. listAvailableVersions — ok + fail-soft with an injected runner.
|
|
10
10
|
// 6. loadSubscriptions — missing/malformed → empty; channel default + validate; env kept; dropless.
|
|
11
|
+
// 7. describeRoster — an empty roster REPORTS WHY, and is an alarm on a control plane (task 1003158).
|
|
12
|
+
// 8. subscriptionStaleness — releases the channel was entitled to and did not take = "the upgrader stopped".
|
|
11
13
|
//
|
|
12
14
|
// DB-free + network-free (every external is injected). Run: node tests/update_channel.mjs
|
|
13
15
|
|
|
@@ -182,6 +184,129 @@ t('loadSubscriptions: an entry without a dir is dropped (cannot be upgraded)', (
|
|
|
182
184
|
assert.equal(r.instances[0].slug, 'real');
|
|
183
185
|
});
|
|
184
186
|
|
|
187
|
+
// ---- describeRoster: fails closed, no longer fails quiet (task 1003158) ----
|
|
188
|
+
//
|
|
189
|
+
// The regression these pin: before this, a missing manifest, a malformed manifest and a deliberately
|
|
190
|
+
// empty roster were INDISTINGUISHABLE — all three returned a bare `instances: []` and the routine
|
|
191
|
+
// shrugged. cloudbongos.com sat 36 releases behind for days because "nothing happened" looked
|
|
192
|
+
// identical to "nothing needed to happen". Every empty roster must now say WHY, every run.
|
|
193
|
+
|
|
194
|
+
t('loadSubscriptions: missing file reports reason manifest-absent (not just an empty list)', () => {
|
|
195
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-subs-none2-'));
|
|
196
|
+
const r = uc.loadSubscriptions({ instanceDir: dir });
|
|
197
|
+
assert.deepEqual(r.instances, []);
|
|
198
|
+
assert.equal(r.reason, uc.ROSTER_ABSENT);
|
|
199
|
+
assert.equal(r.dropped, 0);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
t('loadSubscriptions: malformed JSON reports reason manifest-malformed + keeps the error', () => {
|
|
203
|
+
const dir = scratchWith('{ not valid');
|
|
204
|
+
const r = uc.loadSubscriptions({ instanceDir: dir });
|
|
205
|
+
assert.equal(r.reason, uc.ROSTER_MALFORMED);
|
|
206
|
+
assert.match(r.error, /malformed JSON/);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
t('loadSubscriptions: parsed-but-unsubscribable reports roster-empty and COUNTS the drops', () => {
|
|
210
|
+
const dir = scratchWith({ instances: [{ slug: 'ghost' }, { slug: 'alsoghost' }] });
|
|
211
|
+
const r = uc.loadSubscriptions({ instanceDir: dir });
|
|
212
|
+
assert.deepEqual(r.instances, []);
|
|
213
|
+
assert.equal(r.reason, uc.ROSTER_EMPTY);
|
|
214
|
+
assert.equal(r.dropped, 2);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
t('loadSubscriptions: a usable roster reports ok', () => {
|
|
218
|
+
const dir = scratchWith({ instances: [{ slug: 'cb', dir: '/srv/cb' }] });
|
|
219
|
+
const r = uc.loadSubscriptions({ instanceDir: dir });
|
|
220
|
+
assert.equal(r.reason, uc.ROSTER_OK);
|
|
221
|
+
assert.equal(r.dropped, 0);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
t('describeRoster: an empty roster on a CONTROL PLANE is an alarm, not a shrug', () => {
|
|
225
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-subs-cp-'));
|
|
226
|
+
const d = uc.describeRoster(uc.loadSubscriptions({ instanceDir: dir }), { controlPlane: true });
|
|
227
|
+
assert.equal(d.level, 'alarm');
|
|
228
|
+
assert.match(d.message, /CONTROL PLANE/);
|
|
229
|
+
assert.match(d.message, /subscribing NOTHING/);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
t('describeRoster: the same empty roster on a plain instance is only a warn (shipped default)', () => {
|
|
233
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-subs-plain-'));
|
|
234
|
+
const d = uc.describeRoster(uc.loadSubscriptions({ instanceDir: dir }));
|
|
235
|
+
assert.equal(d.level, 'warn');
|
|
236
|
+
assert.match(d.message, /shipped default/);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
t('describeRoster: a malformed manifest is an alarm everywhere', () => {
|
|
240
|
+
const d = uc.describeRoster(uc.loadSubscriptions({ instanceDir: scratchWith('{ nope') }));
|
|
241
|
+
assert.equal(d.level, 'alarm');
|
|
242
|
+
assert.match(d.message, /MALFORMED/);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
t('describeRoster: partial loss is surfaced — a healthy-looking sweep that silently skips a row', () => {
|
|
246
|
+
const dir = scratchWith({ instances: [{ slug: 'cb', dir: '/srv/cb' }, { slug: 'ghost' }] });
|
|
247
|
+
const d = uc.describeRoster(uc.loadSubscriptions({ instanceDir: dir }));
|
|
248
|
+
assert.equal(d.level, 'warn');
|
|
249
|
+
assert.equal(d.dropped, 1);
|
|
250
|
+
assert.match(d.message, /DROPPED/);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
t('describeRoster: a clean roster is ok and says how many it holds', () => {
|
|
254
|
+
const dir = scratchWith({ instances: [{ slug: 'cb', dir: '/srv/cb' }] });
|
|
255
|
+
const d = uc.describeRoster(uc.loadSubscriptions({ instanceDir: dir }), { controlPlane: true });
|
|
256
|
+
assert.equal(d.level, 'ok');
|
|
257
|
+
assert.equal(d.count, 1);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ---- subscriptionStaleness: watching the watcher ---------------------------
|
|
261
|
+
|
|
262
|
+
const RELEASES = Array.from({ length: 41 }, (_, i) => `1.19.${250 + i}`); // 1.19.250 … 1.19.290
|
|
263
|
+
|
|
264
|
+
t('subscriptionStaleness: reproduces the incident — 1.19.254 on patch is 36 releases behind, stale', () => {
|
|
265
|
+
const st = uc.subscriptionStaleness({ installed: '1.19.254', channel: 'patch', available: RELEASES });
|
|
266
|
+
assert.equal(st.behind, 36);
|
|
267
|
+
assert.equal(st.target, '1.19.290');
|
|
268
|
+
assert.equal(st.stale, true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
t('subscriptionStaleness: a swept instance is 0 behind and not stale', () => {
|
|
272
|
+
const st = uc.subscriptionStaleness({ installed: '1.19.290', channel: 'patch', available: RELEASES });
|
|
273
|
+
assert.equal(st.behind, 0);
|
|
274
|
+
assert.equal(st.target, null);
|
|
275
|
+
assert.equal(st.stale, false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
t('subscriptionStaleness: a busy publish-on-merge day under the threshold does NOT cry wolf', () => {
|
|
279
|
+
const st = uc.subscriptionStaleness({ installed: '1.19.287', channel: 'patch', available: RELEASES });
|
|
280
|
+
assert.equal(st.behind, 3);
|
|
281
|
+
assert.equal(st.stale, false);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
t('subscriptionStaleness: a pinned instance is never stale (declining is not neglect)', () => {
|
|
285
|
+
const st = uc.subscriptionStaleness({ installed: '1.19.254', channel: 'pinned', available: RELEASES });
|
|
286
|
+
assert.equal(st.behind, 0);
|
|
287
|
+
assert.equal(st.stale, false);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
t('subscriptionStaleness: counts only what the CHANNEL may take — minors/majors are not a backlog', () => {
|
|
291
|
+
const st = uc.subscriptionStaleness({ installed: '1.19.254', channel: 'patch', available: ['1.20.0', '1.20.7', '2.0.0'] });
|
|
292
|
+
assert.equal(st.behind, 0);
|
|
293
|
+
assert.equal(st.stale, false);
|
|
294
|
+
// the same list on the minor channel IS a backlog
|
|
295
|
+
assert.equal(uc.subscriptionStaleness({ installed: '1.19.254', channel: 'minor', available: ['1.20.0', '1.20.7', '2.0.0'] }).behind, 2);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
t('subscriptionStaleness: threshold is overridable; a bad threshold falls back to the default', () => {
|
|
299
|
+
assert.equal(uc.subscriptionStaleness({ installed: '1.19.287', channel: 'patch', available: RELEASES, threshold: 1 }).stale, true);
|
|
300
|
+
assert.equal(uc.subscriptionStaleness({ installed: '1.19.287', channel: 'patch', available: RELEASES, threshold: 'nonsense' }).threshold, uc.DEFAULT_STALENESS_THRESHOLD);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
t('describeStaleness: silent when healthy, explicit about "the upgrader stopped" when not', () => {
|
|
304
|
+
assert.equal(uc.describeStaleness(uc.subscriptionStaleness({ installed: '1.19.290', channel: 'patch', available: RELEASES })), null);
|
|
305
|
+
const msg = uc.describeStaleness(uc.subscriptionStaleness({ installed: '1.19.254', channel: 'patch', available: RELEASES }), { slug: 'cloudbongos.com' });
|
|
306
|
+
assert.match(msg, /cloudbongos\.com is 36 patch releases behind/);
|
|
307
|
+
assert.match(msg, /upgrader has most likely stopped/);
|
|
308
|
+
});
|
|
309
|
+
|
|
185
310
|
console.log(`\nupdate_channel: ${passed} passed, ${failed} failed`);
|
|
186
311
|
process.exit(failed ? 1 : 0);
|
|
187
312
|
|