@bongos/core 1.21.5 → 1.21.7

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.
Files changed (82) hide show
  1. package/.bongos-core.json +152 -72
  2. package/clients/bongos-client/index.d.ts +1 -1
  3. package/docs/adr/0361-merges-publish-as-candidates-release-decides-what-others-are-offered.md +1 -0
  4. package/docs/api/openapi.json +2 -1
  5. package/docs/api-reference.md +1 -1
  6. package/docs/copy-inventory.md +156 -144
  7. package/docs/copy-registry.json +286 -158
  8. package/docs/module-api-changelog.md +4 -0
  9. package/docs/onboarding/diagrams/03-drachmae-karma.mmd +1 -1
  10. package/docs/onboarding/diagrams/assertions.json +1 -1
  11. package/docs/page-inventory.json +35 -6
  12. package/docs/page-readings.json +1056 -1013
  13. package/modules/autonomy/config-idle.js +192 -0
  14. package/modules/autonomy/routes/autonomy.js +22 -0
  15. package/modules/autonomy/runner-health.js +22 -1
  16. package/modules/hall-ui/public/city-draw.js +438 -0
  17. package/modules/hall-ui/public/city.css +110 -0
  18. package/modules/hall-ui/public/city.html +71 -0
  19. package/modules/hall-ui/public/city.js +177 -0
  20. package/modules/hall-ui/public/city.states.json +30 -0
  21. package/modules/hall-ui/public/gate.js +11 -4
  22. package/modules/hall-ui/public/profile.css +4 -0
  23. package/modules/hall-ui/public/profile.js +25 -1
  24. package/modules/hall-ui/public/settings-autobongos.js +19 -1
  25. package/modules/hall-ui/public/settings.css +26 -0
  26. package/modules/hall-ui/public/settings.html +8 -0
  27. package/modules/hall-ui/public/settings.js +43 -3
  28. package/modules/platform-identity/craft-rollup.js +72 -0
  29. package/modules/platform-identity/migrations/platform_identity_029_activity_crafts.sql +23 -0
  30. package/modules/platform-identity/platform-identity.js +12 -6
  31. package/modules/platform-identity/routes/sso.js +4 -0
  32. package/modules/platform-identity/tests/platform-identity.mjs +1 -1
  33. package/modules/provisioning/core-upgrade.js +34 -7
  34. package/modules/provisioning/module.json +2 -1
  35. package/modules/provisioning/seams.js +2 -0
  36. package/modules/public-landing/public/projects-hall-live.states.json +216 -0
  37. package/modules/public-landing/public/projects.html +43 -13
  38. package/modules/public-landing/public/projects.states.json +2 -1
  39. package/modules/ui-design/kit/fixtures/provisioning-instances-hall-live.json +106 -0
  40. package/package-lock.json +2 -2
  41. package/package.json +1 -1
  42. package/release-notes.json +64 -0
  43. package/scripts/gds/autobongos-grade-cap.js +187 -0
  44. package/scripts/gds/autobongos-loop.js +4 -0
  45. package/scripts/gds/autobongos-run.js +154 -13
  46. package/scripts/gds/autobongos-verify.js +211 -14
  47. package/scripts/gds/provision-core-upgrade.js +14 -2
  48. package/scripts/gds/provision-repo.js +77 -13
  49. package/scripts/gds/ship-flow.js +5 -0
  50. package/scripts/gds/ship-preflight-steps.js +2 -1
  51. package/scripts/gds/ship.js +10 -0
  52. package/scripts/gds/update-sweep.js +24 -8
  53. package/scripts/gds/upgrade-outcome.js +1 -0
  54. package/src/bongos/auth-admission.js +95 -13
  55. package/src/bongos/db-kernel.js +1 -1
  56. package/src/bongos/routes/core-update.js +27 -2
  57. package/src/bongos/serve-internal.js +14 -0
  58. package/src/bongos/software-update.js +3 -2
  59. package/src/module-api.js +1 -1
  60. package/tests/activity_rollup_order_db.mjs +3 -1
  61. package/tests/activity_snapshots_db.mjs +2 -0
  62. package/tests/autobongos_grade_cap.mjs +125 -0
  63. package/tests/autobongos_loop.mjs +230 -1
  64. package/tests/autobongos_verify.mjs +239 -3
  65. package/tests/autonomy_config_idle.mjs +272 -0
  66. package/tests/core_update_banner.mjs +29 -0
  67. package/tests/core_upgrade_door.mjs +31 -1
  68. package/tests/core_upgrade_runner.mjs +20 -0
  69. package/tests/hall_audit.mjs +10 -1
  70. package/tests/hall_city.mjs +369 -0
  71. package/tests/hall_page_gate_map.mjs +5 -0
  72. package/tests/hub_craft_rollup.mjs +350 -0
  73. package/tests/profile_rollup_consent.mjs +2 -0
  74. package/tests/profile_route.mjs +16 -2
  75. package/tests/project_invite_ui.mjs +65 -7
  76. package/tests/projects_hub_dns_ready.mjs +7 -1
  77. package/tests/provision.mjs +78 -0
  78. package/tests/settings_main_role.mjs +190 -0
  79. package/tests/software_update.mjs +56 -1
  80. package/tests/ui_design_kit.mjs +1 -1
  81. package/tests/update_subscription_engine.mjs +55 -3
  82. package/tests/wizard_physics_more.mjs +19 -0
@@ -580,6 +580,9 @@ module.exports = function ssoRoutes() {
580
580
  // here, like disciplines: the shape is checked and hard-capped in
581
581
  // upsertActivity, next to the data, so no route can forget it.
582
582
  recent_ships: {},
583
+ // Each craft's own counts (task 1004440): untyped here for the same reason,
584
+ // shaped and capped in upsertActivity (craft-rollup.js sanitizeCrafts).
585
+ crafts: {},
583
586
  })) return;
584
587
  const b = req.body || {};
585
588
  try {
@@ -617,6 +620,7 @@ module.exports = function ssoRoutes() {
617
620
  rank: b.rank || null,
618
621
  disciplines: Array.isArray(b.disciplines) ? b.disciplines : [],
619
622
  recentShips: b.recent_ships,
623
+ crafts: b.crafts,
620
624
  // The AUTHENTICATED client's registered origin — the bound every pushed
621
625
  // ship link must fall inside (task 1002803). Taken from the client row,
622
626
  // never from the body, for the same reason client_id is.
@@ -602,7 +602,7 @@ await ta('upsertActivity upserts per (client_id, github_id) with the talent snap
602
602
  // an empty list when the sender reported none, never NULL. Its own cap,
603
603
  // shape and link bound are covered by tests/hub_recent_ships.mjs. The last param
604
604
  // is the report's as-of stamp (task 1004268): null when the sender gave none.
605
- assert.deepEqual(c.params, ['c1', 42, 100, 5, 3, 'metic', ['engineer'], '[]', null]);
605
+ assert.deepEqual(c.params, ['c1', 42, 100, 5, 3, 'metic', ['engineer'], '[]', null, '{}']);
606
606
  });
607
607
 
608
608
  await ta('upsertActivity sanitizes third-party self-report (ADR 0171 D7): off-ladder rank → null, disciplines validated + capped', async () => {
@@ -250,14 +250,22 @@ function makeCoreUpgradeHandler({ provisioning, pool, parseId, validateOrRespond
250
250
  * and not asking it is what keeps the one comparison in the system (the runner's channel
251
251
  * resolution) the only one that can be wrong.
252
252
  *
253
- * An unknown `recommended` — the registry could not be read — leaves a failure STANDING.
254
- * Not knowing what is on offer is not evidence that the box recovered, and the direction
255
- * to fail in is showing an old failure, never hiding a live one.
253
+ * An UNREAD registry — `registryRead` false, the snapshot's `available` empty — leaves a
254
+ * failure STANDING. Not knowing what is on offer is not evidence that the box recovered,
255
+ * and the direction to fail in is showing an old failure, never hiding a live one. A
256
+ * registry that answered with nothing on offer is different: the box is current, and a
257
+ * failure about some other version is history (task 1004534).
256
258
  */
257
- function lastMoveSuperseded(target, served, recommended) {
259
+ function lastMoveSuperseded(target, served, recommended, registryRead = false) {
258
260
  if (!target) return false;
259
261
  if (served && String(served) === String(target)) return true;
260
- return !!(recommended && String(recommended) !== String(target));
262
+ if (recommended) return String(recommended) !== String(target);
263
+ // NOTHING ON OFFER FROM A REGISTRY THAT ANSWERED is an up-to-date box, not an unknown
264
+ // (task 1004534). The snapshot stores an empty `available` when the registry could not
265
+ // be read, so only that case leaves the failure standing. On 2026-10-02 cloudbongos
266
+ // served 1.21.5 with nothing newer in its rule, and a day-old failure about 1.20.73
267
+ // still led its /deploy page because a null recommendation was read as "unknown".
268
+ return !!(registryRead && served);
261
269
  }
262
270
 
263
271
  /**
@@ -371,7 +379,7 @@ function previewFromSnapshot(inst, snap, openIntent, lastMove) {
371
379
  // already moved past (task 1004172 — lastMoveSuperseded says why at length). The
372
380
  // CLIENT must not work this out: the page's standing rule is that it compares no
373
381
  // versions, and this is a version question wearing a different hat.
374
- superseded: lastMoveSuperseded(lastMove.target_version, served, recommended),
382
+ superseded: lastMoveSuperseded(lastMove.target_version, served, recommended, available.length > 0),
375
383
  // Truncated because it reaches a DOM and upgrade.js's refusals can carry a whole
376
384
  // `git status` (the dirty-tree one lists every file). Never set on a DONE move:
377
385
  // a done row's last_error is the note below, and a move that worked is not an error.
@@ -468,6 +476,25 @@ function updateChannelOf(inst) {
468
476
  return UPDATE_CHANNELS.includes(c) ? c : DEFAULT_UPDATE_CHANNEL;
469
477
  }
470
478
 
479
+ /**
480
+ * THIS hall's own update rule — the control-plane row's — for the offers the core makes
481
+ * outside /deploy: Settings → Software update and the hall's update banner (task 1004535).
482
+ * Those live in the core, which cannot read a module's table, so this is handed to them
483
+ * through the `provisioning.selfUpdateChannel` seam. Before it they asked with the platform
484
+ * default (patch) while the runner and the sweep followed the stored rule (minor on
485
+ * cloudbongos), so a step-up release would have read "up to date" there as it installed.
486
+ * null — no row, an unknown value, a failed read — means "use the default"; never throws.
487
+ */
488
+ async function selfUpdateChannel(db) {
489
+ try {
490
+ const { rows } = await db.query("SELECT update_channel FROM provisioning_instances WHERE hosting_shape = 'control-plane' LIMIT 1");
491
+ const c = rows[0] && rows[0].update_channel;
492
+ return UPDATE_CHANNELS.includes(c) ? c : null;
493
+ } catch {
494
+ return null;
495
+ }
496
+ }
497
+
471
498
  /**
472
499
  * POST /provisioning/instances/:id/update-channel — set how far this project's core
473
500
  * may move (task 1004445). Body: { channel: 'pinned' | 'patch' | 'minor' }.
@@ -518,5 +545,5 @@ function makeUpdateChannelHandler({ provisioning, pool, parseId, validateOrRespo
518
545
  };
519
546
  }
520
547
 
521
- module.exports = { UPDATE_CHANNELS, DEFAULT_UPDATE_CHANNEL, updateChannelOf, makeUpdateChannelHandler, CORE_UPGRADE_FROM, lastCoreMove, lastMoveSuperseded, parseRepoNote, preflightForOffer, UPGRADE_SHAPES, VERSION_RE, isCoreVersion, coreUpgradeFault, enqueueCoreUpgrade, makeCoreUpgradeHandler, previewFromSnapshot, makeCoreUpgradePreviewHandler, makeControlPlaneHandler };
548
+ module.exports = { UPDATE_CHANNELS, DEFAULT_UPDATE_CHANNEL, updateChannelOf, selfUpdateChannel, makeUpdateChannelHandler, CORE_UPGRADE_FROM, lastCoreMove, lastMoveSuperseded, parseRepoNote, preflightForOffer, UPGRADE_SHAPES, VERSION_RE, isCoreVersion, coreUpgradeFault, enqueueCoreUpgrade, makeCoreUpgradeHandler, previewFromSnapshot, makeCoreUpgradePreviewHandler, makeControlPlaneHandler };
522
549
 
@@ -35,7 +35,8 @@
35
35
  "provisioning.instanceState",
36
36
  "provisioning.projectPlanets",
37
37
  "provisioning.projectOwnedBy",
38
- "provisioning.projectPublishIdentity"
38
+ "provisioning.projectPublishIdentity",
39
+ "provisioning.selfUpdateChannel"
39
40
  ],
40
41
  "consumes": []
41
42
  }
@@ -6,6 +6,7 @@
6
6
  const api = require('../../src/module-api');
7
7
  const { pool } = api;
8
8
  const provisioning = require('./provisioning');
9
+ const { selfUpdateChannel } = require('./core-upgrade');
9
10
 
10
11
  // The `provisioning.projectPublishIdentity` port (task 1003578, ADR 0247 D4): the name and
11
12
  // description the publish gate judges, for the control-plane project a hub catalog row was
@@ -49,6 +50,7 @@ function registerProvisioningSeams() {
49
50
  api.registerProvider('provisioning.projectPlanets', (hosts) => provisioning.planetChoicesForHosts(pool, hosts));
50
51
  api.registerProvider('provisioning.projectOwnedBy', (target) => provisioning.projectOwnedBy(pool, target));
51
52
  api.registerProvider('provisioning.projectPublishIdentity', (target) => projectPublishIdentity(pool, target));
53
+ api.registerProvider('provisioning.selfUpdateChannel', () => selfUpdateChannel(pool));
52
54
  }
53
55
 
54
56
  module.exports = { registerProvisioningSeams, projectPublishIdentity };
@@ -0,0 +1,216 @@
1
+ {
2
+ "_": "The create wizard done panel once the hall answers (task 1004503): the same walk as projects.states.json in-new-done, with the instance list aliased so the project just filed (id 102) is live and verified up. The invite box opens only now; in-new-done in the main file pictures it shut while the project is set up. Rendered by name: node modules/ui-design/kit/render.js --states modules/public-landing/public/projects-hall-live.states.json",
3
+ "page": "/projects",
4
+ "surface": "public-landing",
5
+ "modes": [
6
+ "dark"
7
+ ],
8
+ "modeQuery": false,
9
+ "stub": {
10
+ "fixtures": {
11
+ "provisioning-instances": "provisioning-instances-hall-live"
12
+ }
13
+ },
14
+ "ignoreRequests": [
15
+ "/api/(gds|bongos)/me$",
16
+ "/my-projects",
17
+ "/provisioning/onboard-plan",
18
+ "/env-manifest$"
19
+ ],
20
+ "ignoreConsole": [
21
+ "/api/(gds|bongos)/me",
22
+ "\\b401\\b",
23
+ "\\b404\\b",
24
+ "Unauthorized"
25
+ ],
26
+ "states": {
27
+ "in-new-done-live": {
28
+ "auth": true,
29
+ "url": "/projects?view=new",
30
+ "actions": [
31
+ [
32
+ "wait",
33
+ 600
34
+ ],
35
+ [
36
+ "click",
37
+ "#termsAck"
38
+ ],
39
+ [
40
+ "wait",
41
+ 100
42
+ ],
43
+ [
44
+ "click",
45
+ "#wizStart"
46
+ ],
47
+ [
48
+ "wait",
49
+ 400
50
+ ],
51
+ [
52
+ "fill",
53
+ "#newName",
54
+ "Mercury"
55
+ ],
56
+ [
57
+ "click",
58
+ "#typeCards .tpill[data-type=\"game\"]"
59
+ ],
60
+ [
61
+ "fill",
62
+ "#f-desc",
63
+ "A co-op farming game where the seasons never repeat."
64
+ ],
65
+ [
66
+ "click",
67
+ "#panel-what button[type=submit]"
68
+ ],
69
+ [
70
+ "wait",
71
+ 400
72
+ ],
73
+ [
74
+ "select",
75
+ "#physCountry",
76
+ "US"
77
+ ],
78
+ [
79
+ "wait",
80
+ 100
81
+ ],
82
+ [
83
+ "click",
84
+ "[data-phys=\"builder_geography\"][data-val=\"global\"]"
85
+ ],
86
+ [
87
+ "click",
88
+ "[data-phys=\"minors_on_board\"][data-val=\"false\"]"
89
+ ],
90
+ [
91
+ "click",
92
+ "[data-phys=\"entity\"][data-val=\"intent-solo\"]"
93
+ ],
94
+ [
95
+ "click",
96
+ "#physNext"
97
+ ],
98
+ [
99
+ "wait",
100
+ 700
101
+ ],
102
+ [
103
+ "click",
104
+ "#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"
105
+ ],
106
+ [
107
+ "wait",
108
+ 200
109
+ ],
110
+ [
111
+ "click",
112
+ "#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"
113
+ ],
114
+ [
115
+ "wait",
116
+ 200
117
+ ],
118
+ [
119
+ "fill",
120
+ "#f-slug",
121
+ "mercury"
122
+ ],
123
+ [
124
+ "wait",
125
+ 900
126
+ ],
127
+ [
128
+ "fill",
129
+ "#f-domain",
130
+ "mercury.example.com"
131
+ ],
132
+ [
133
+ "wait",
134
+ 200
135
+ ],
136
+ [
137
+ "click",
138
+ "#reviewRows .setRow[data-row=\"address\"] [data-row-use]"
139
+ ],
140
+ [
141
+ "wait",
142
+ 200
143
+ ],
144
+ [
145
+ "click",
146
+ "#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"
147
+ ],
148
+ [
149
+ "wait",
150
+ 150
151
+ ],
152
+ [
153
+ "click",
154
+ "#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"
155
+ ],
156
+ [
157
+ "wait",
158
+ 150
159
+ ],
160
+ [
161
+ "click",
162
+ "#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"
163
+ ],
164
+ [
165
+ "wait",
166
+ 150
167
+ ],
168
+ [
169
+ "click",
170
+ "#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"
171
+ ],
172
+ [
173
+ "wait",
174
+ 200
175
+ ],
176
+ [
177
+ "click",
178
+ "#reviewGo"
179
+ ],
180
+ [
181
+ "wait",
182
+ 500
183
+ ],
184
+ [
185
+ "click",
186
+ "#doCreate"
187
+ ],
188
+ [
189
+ "wait",
190
+ 1500
191
+ ],
192
+ [
193
+ "wait",
194
+ 6000
195
+ ]
196
+ ],
197
+ "expect": {
198
+ "visible": [
199
+ "#panel-done",
200
+ "#wizInvite",
201
+ "#wizInviteWho"
202
+ ],
203
+ "text": [
204
+ [
205
+ "#done-h",
206
+ "Mercury is alive."
207
+ ]
208
+ ]
209
+ }
210
+ }
211
+ },
212
+ "ignoreControls": [
213
+ ".skip",
214
+ ".vh"
215
+ ]
216
+ }
@@ -4873,10 +4873,45 @@ summary{min-height:24px;padding:3px 0;}
4873
4873
  show(2, true);
4874
4874
  });
4875
4875
  /* the done panel's invite step names the demo's teammates (task 1004420): the hub
4876
- holds no invite of its own, so the founder sends each from the project's own box */
4876
+ holds no invite of its own, so the founder sends each from the project's own box.
4877
+ The names ride the created record too, so a reload before the hall is up keeps them
4878
+ (task 1004503). */
4879
+ function demoTeam() {
4880
+ if (state.demo && state.demo.invites && state.demo.invites.length) return state.demo.invites;
4881
+ var created = createdRecord();
4882
+ return created && Array.isArray(created.demo_invites) ? created.demo_invites : [];
4883
+ }
4884
+ /* The invite step (tasks 1002346 and 1004503). A second account cannot join until the
4885
+ project's hall is up, so the box opens only on the done panel's own verified Live
4886
+ (last_verified_up), never while the project is being set up. Before that, one plain
4887
+ line says when inviting opens, naming a demo's teammates so they are not lost. The
4888
+ CREATED RECORD is the proof the widget needs: no signed-in identity, no filed instance
4889
+ or no slug means nothing to invite to. Opening loads the suggestions once; a later
4890
+ tick does not ask again. */
4891
+ var invitesOpen = false;
4892
+ function paintInviteGate(live) {
4893
+ var created = createdRecord();
4894
+ var canInvite = !!(me && created && created.id && created.slug);
4895
+ if (live && canInvite) {
4896
+ if (invitesOpen) return;
4897
+ invitesOpen = true;
4898
+ setInviteSlug('wizInvite', created.slug);
4899
+ paintDemoTeam();
4900
+ loadInviteSuggestions('wizInvite', created.id);
4901
+ $('wizInviteLater').innerHTML = 'You can invite builders any time from ' + projectPageLink('the project’s own page') + '.';
4902
+ return;
4903
+ }
4904
+ invitesOpen = false;
4905
+ setInviteSlug('wizInvite', '');
4906
+ $('wizDemoTeam').hidden = true;
4907
+ var team = demoTeam();
4908
+ $('wizInviteLater').textContent = team.length
4909
+ ? 'You can invite ' + team.map(function (h) { return '@' + h; }).join(', ') + ' once its hall is up.'
4910
+ : 'You can invite builders once its hall is up.';
4911
+ }
4877
4912
  function paintDemoTeam() {
4878
4913
  var line = $('wizDemoTeam');
4879
- var who = state.demo && state.demo.invites && state.demo.invites.length ? state.demo.invites : [];
4914
+ var who = demoTeam();
4880
4915
  line.hidden = !who.length;
4881
4916
  line.textContent = who.length ? 'Your demo teammates: ' + who.map(function (h) { return '@' + h; }).join(', ') + '. Invite each one here.' : '';
4882
4917
  if (who.length && !$('wizInviteWho').value) $('wizInviteWho').value = who[0];
@@ -6399,7 +6434,9 @@ summary{min-height:24px;padding:3px 0;}
6399
6434
  sessionStorage.setItem(CREATED_KEY, JSON.stringify({
6400
6435
  id: data.instance && data.instance.id, slug: state.slug, domain: madeDomain,
6401
6436
  noaddr: !madeDomain,
6402
- mode: state.mode, name: state.name, at: Date.now()
6437
+ mode: state.mode, name: state.name, at: Date.now(),
6438
+ /* a demo's teammates, offered once the hall is up (task 1004503) */
6439
+ demo_invites: state.demo && state.demo.invites && state.demo.invites.length ? state.demo.invites.slice() : undefined
6403
6440
  }));
6404
6441
  sessionStorage.removeItem(DRAFT_KEY);
6405
6442
  } catch (e) {}
@@ -6650,16 +6687,8 @@ summary{min-height:24px;padding:3px 0;}
6650
6687
  skippable, so this panel names where it lives afterwards rather than
6651
6688
  leaving an owner to go looking for it. */
6652
6689
  $('doneModules').innerHTML = 'Add or remove modules later in ' + projectPageLink('the project’s settings') + '.';
6653
- /* the invite step (task 1002346, BV1.R27). The CREATED RECORD is the proof
6654
- the widget needs and the only one this panel has: no signed-in identity,
6655
- no filed instance or no slug ⇒ nothing to invite to, and the step stays
6656
- hidden. It is deliberately NOT keyed on instanceId, which startProgress
6657
- sets only AFTER this runs. */
6658
- var created = createdRecord();
6659
- setInviteSlug('wizInvite', (me && created && created.id && created.slug) ? created.slug : '');
6660
- paintDemoTeam();
6661
- loadInviteSuggestions('wizInvite', (me && created && created.slug) ? created.id : '');
6662
- $('wizInviteLater').innerHTML = 'You can invite builders any time from ' + projectPageLink('the project’s own page') + '.';
6690
+ /* the invite step waits for the hall (task 1004503): until it answers, one line */
6691
+ paintInviteGate(false);
6663
6692
  renderSteps();
6664
6693
  }
6665
6694
 
@@ -6912,6 +6941,7 @@ summary{min-height:24px;padding:3px 0;}
6912
6941
  if (inst.last_verified_up) {
6913
6942
  /* the alive moment (task 1004504): said only once the project has answered */
6914
6943
  $('done-h').textContent = (state.name || state.slug || 'Your project') + ' is alive.';
6944
+ paintInviteGate(true); /* the hall answers, so others can join now */
6915
6945
  setProgress(progressMarkup(-1, 3,
6916
6946
  'Your project is answering at ' + link + '. Finish the steps below and the hall is yours.', 'isLive', flow));
6917
6947
  return true;
@@ -41,6 +41,7 @@
41
41
  "in-new-terms-refused": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["click","#termsAck"],["wait",200]],"expect":{"visible":["#panel-fork"],"hidden":["#panel-what"],"attr":[["#wizStart","disabled",""],["#wizDemo","disabled",""]]}},
42
42
  "in-new-fork-demo": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizDemo"],["wait",300]],"expect":{"visible":["#panel-fork","#demoSetup","#demoFitVerdict","#demoInvites","#wizBack"],"hidden":["#forkIntro"],"attr":[["#wizDemo","aria-expanded","true"]]}},
43
43
  "in-new-demo-what": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizDemo"],["wait",300],["fill","#demoIdea","A voting app for movie night"],["click","#demoStart"],["wait",400]],"expect":{"visible":["#panel-what","#demoTag"],"hidden":["#panel-fork"],"text":[["#wizEyebrow","New demo"]]}},
44
+ "in-new-demo-review-address": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizDemo"],["wait",300],["fill","#demoIdea","A voting app for movie night"],["click","#demoStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["click","#panel-what button[type=submit]"],["wait",700],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",300],["fill","#f-slug","mercury"],["wait",900]],"expect":{"visible":["#panel-review","#reviewRows .setRow[data-row=\"address\"] .opts","#f-slug","#addrConfirm","#f-noaddr"],"hidden":["#panel-checks"],"attr":[["#reviewGo","disabled",""]],"text":[["#wizEyebrow","New demo"]]}},
44
45
  "in-new-what": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400]],"expect":{"visible":["#panel-what","#newName","#typeCards","#f-desc","#wizBack"],"hidden":["#panel-fork","#demoTag"],"attr":[["#wizRoom","data-screen","what"]]}},
45
46
  "in-new-name-prefilled": {"auth":true,"url":"/projects?view=new&name=Mercury","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400]],"expect":{"visible":["#panel-what","#newName"]}},
46
47
  "in-new-checks": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400]],"expect":{"visible":["#panel-checks","#physCountry","#physIntent"],"hidden":["#physAges","#physForms","#checksStop"],"attr":[["#wizRoom","data-screen","checks"]]}},
@@ -55,7 +56,7 @@
55
56
  "in-new-review-run": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-chg]"],["wait",300],["click","[data-phys=\"framing\"][data-val=\"hybrid\"]"],["wait",600]],"expect":{"visible":["#physSuite"]}},
56
57
  "in-new-review-confirmed": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200]],"expect":{"visible":["#reviewGo","#reviewRows .setRow.done"],"text":[["#reviewGo","Continue"]]}},
57
58
  "in-new-look": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200],["click","#reviewGo"],["wait",500]],"expect":{"visible":["#panel-look","#wizLook","#wizRecap","#doCreate"],"hidden":["#panel-review"],"attr":[["#wizRoom","data-screen","look"]]}},
58
- "in-new-done": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200],["click","#reviewGo"],["wait",500],["click","#doCreate"],["wait",1500]],"expect":{"visible":["#panel-done","#liveProgress .prog","#humanSteps li"],"hidden":["#panel-look","#qWrap","#wizBack"]}},
59
+ "in-new-done": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200],["click","#reviewGo"],["wait",500],["click","#doCreate"],["wait",1500]],"expect":{"visible":["#panel-done","#liveProgress .prog","#humanSteps li","#wizInviteLater"],"hidden":["#panel-look","#qWrap","#wizBack","#wizInvite"]}},
59
60
  "in-new-resume-offer": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200],["click","#reviewGo"],["wait",500],["click","#doCreate"],["wait",1500],["click","#projTrig"],["wait",350],["click","#menuProjects [data-nav=\"new\"]"],["wait",500]],"expect":{"visible":["#panel-fork","#wizResume","#wizResumeGo"],"hidden":["#panel-done"],"text":[["#wizResumeName","Mercury"]]}},
60
61
  "in-new-look-custom": {"auth":true,"url":"/projects?view=new","actions":[["wait",600],["click","#termsAck"],["wait",100],["click","#wizStart"],["wait",400],["fill","#newName","Mercury"],["click","#typeCards .tpill[data-type=\"game\"]"],["fill","#f-desc","A co-op farming game where the seasons never repeat."],["click","#panel-what button[type=submit]"],["wait",400],["select","#physCountry","US"],["wait",100],["click","[data-phys=\"builder_geography\"][data-val=\"global\"]"],["click","[data-phys=\"minors_on_board\"][data-val=\"false\"]"],["click","[data-phys=\"entity\"][data-val=\"intent-solo\"]"],["click","#physNext"],["wait",700],["click","#reviewRows .setRow[data-row=\"code\"] [data-row-ok]"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-chg]"],["wait",200],["fill","#f-slug","mercury"],["wait",900],["fill","#f-domain","mercury.example.com"],["wait",200],["click","#reviewRows .setRow[data-row=\"address\"] [data-row-use]"],["wait",200],["click","#reviewRows .setRow[data-row=\"app\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"extras\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"team\"] [data-row-ok]"],["wait",150],["click","#reviewRows .setRow[data-row=\"run\"] [data-row-ok]"],["wait",200],["click","#reviewGo"],["wait",500],["click","#wizLook-custom"],["wait",200]],"expect":{"visible":["#panel-look","#wizAccent","#wizAccentNote"],"hidden":["#panel-review"]}},
61
62
  "out-new": {"auth":false,"url":"/projects?view=new","expect":{"visible":["#panel-gate","#wizSignin"],"hidden":["#qWrap","#panel-fork"]}},
@@ -0,0 +1,106 @@
1
+ {
2
+ "_": "The owner's projects for the kit (task 1003345): four states the mine list and the manage page render — live, standing up, snagged, torn down. Timestamps written \"$now-3m\" / \"$now-1d\" are resolved by the stub at request time so \"checked 3m ago\" stays true. Mooring (task 1003345, the grade's finding): live but unreachable since its last probe, so the restart door renders and the restart probe has a row to drive.",
3
+ "instances": [
4
+ {
5
+ "id": 101,
6
+ "slug": "demo",
7
+ "status": "active",
8
+ "domain": "demo.cloudbongos.com",
9
+ "target_ref": "builder/demo",
10
+ "hosting_shape": "co-tenant",
11
+ "onboard_mode": "scaffold",
12
+ "created_at": "2026-08-01T10:00:00.000Z",
13
+ "provisioned_at": "2026-08-01T10:20:00.000Z",
14
+ "updated_at": "$now-3m",
15
+ "last_verified_at": "$now-3m",
16
+ "last_verified_up": true,
17
+ "error_note": null,
18
+ "dns_note": null,
19
+ "dns_resolves": true,
20
+ "torn_down_at": null,
21
+ "open_intent": null
22
+ },
23
+ {
24
+ "id": 102,
25
+ "slug": "mercury",
26
+ "status": "active",
27
+ "planet_template": "blob-frost-tint",
28
+ "planet_accent": "sun-lit",
29
+ "domain": "mercury.cloudbongos.com",
30
+ "target_ref": "builder/mercury",
31
+ "hosting_shape": "cloud-host",
32
+ "owner_monthly_usd": null,
33
+ "onboard_mode": "scaffold",
34
+ "created_at": "$now-6m",
35
+ "provisioned_at": "$now-1m",
36
+ "updated_at": "$now-2m",
37
+ "last_verified_at": "$now-1m",
38
+ "last_verified_up": true,
39
+ "error_note": null,
40
+ "dns_note": null,
41
+ "torn_down_at": null,
42
+ "open_intent": {
43
+ "action": "standup",
44
+ "state": "running",
45
+ "created_at": "$now-6m"
46
+ },
47
+ "dns_resolves": true
48
+ },
49
+ {
50
+ "id": 103,
51
+ "slug": "tidepool",
52
+ "status": "error",
53
+ "domain": "tidepool.cloudbongos.com",
54
+ "target_ref": "builder/tidepool",
55
+ "hosting_shape": "co-tenant",
56
+ "onboard_mode": "adopt",
57
+ "created_at": "2026-08-20T09:00:00.000Z",
58
+ "provisioned_at": null,
59
+ "updated_at": "$now-1d",
60
+ "last_verified_at": null,
61
+ "last_verified_up": null,
62
+ "error_note": "The deploy could not read the repository: it is private, and the deploy key only reads public ones.",
63
+ "dns_note": null,
64
+ "torn_down_at": null,
65
+ "open_intent": null
66
+ },
67
+ {
68
+ "id": 104,
69
+ "slug": "old-lantern",
70
+ "status": "torn_down",
71
+ "domain": "old-lantern.cloudbongos.com",
72
+ "target_ref": "builder/old-lantern",
73
+ "hosting_shape": "co-tenant",
74
+ "onboard_mode": "scaffold",
75
+ "created_at": "2026-07-02T09:00:00.000Z",
76
+ "provisioned_at": "2026-07-02T09:30:00.000Z",
77
+ "updated_at": "2026-08-15T12:00:00.000Z",
78
+ "last_verified_at": "2026-08-15T11:50:00.000Z",
79
+ "last_verified_up": true,
80
+ "error_note": null,
81
+ "dns_note": null,
82
+ "dns_resolves": true,
83
+ "torn_down_at": "2026-08-15T12:00:00.000Z",
84
+ "open_intent": null
85
+ },
86
+ {
87
+ "id": 105,
88
+ "slug": "mooring",
89
+ "status": "active",
90
+ "domain": "mooring.cloudbongos.com",
91
+ "target_ref": "builder/mooring",
92
+ "hosting_shape": "co-tenant",
93
+ "onboard_mode": "scaffold",
94
+ "created_at": "2026-08-10T09:00:00.000Z",
95
+ "provisioned_at": "2026-08-10T09:20:00.000Z",
96
+ "updated_at": "$now-20m",
97
+ "last_verified_at": "$now-20m",
98
+ "last_verified_up": false,
99
+ "error_note": null,
100
+ "dns_note": null,
101
+ "dns_resolves": true,
102
+ "torn_down_at": null,
103
+ "open_intent": null
104
+ }
105
+ ]
106
+ }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.5",
3
+ "version": "1.21.7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.5",
9
+ "version": "1.21.7",
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.21.5",
3
+ "version": "1.21.7",
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",