@bongos/core 1.21.105 → 1.21.107

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 (55) hide show
  1. package/.bongos-core.json +88 -58
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +4 -0
  4. package/clients/bongos-client/index.cjs +4 -0
  5. package/clients/bongos-client/index.d.ts +6 -1
  6. package/clients/bongos-client/index.mjs +4 -0
  7. package/docs/api/openapi.json +91 -2
  8. package/docs/api-reference.md +5 -3
  9. package/docs/copy-inventory.md +368 -366
  10. package/docs/copy-registry.json +475 -457
  11. package/docs/design/project-startup-direction.md +14 -0
  12. package/docs/module-api-changelog.md +4 -0
  13. package/docs/page-inventory.json +10 -3
  14. package/docs/page-readings.json +1023 -977
  15. package/migrations/core_275_claims_auto_pickup.sql +24 -0
  16. package/modules/government/decision-rules.js +1 -0
  17. package/modules/lifecycle/autopickup.js +94 -0
  18. package/modules/lifecycle/db-claims.js +18 -4
  19. package/modules/lifecycle/routes/claims.js +12 -1
  20. package/modules/lifecycle/routes/project-settings.js +59 -0
  21. package/modules/public-landing/public/projects-hall-live.states.json +3 -2
  22. package/modules/public-landing/public/projects-setup.states.json +91 -0
  23. package/modules/public-landing/public/projects.html +535 -804
  24. package/modules/public-landing/public/projects.probes.json +3 -3
  25. package/modules/public-landing/public/projects.states.json +1 -1
  26. package/modules/ui-design/kit/fixtures/provisioning-instances-hall-live.json +22 -2
  27. package/modules/ui-design/kit/fixtures/provisioning-instances-setup.json +195 -0
  28. package/modules/ui-design/kit/serve.js +22 -4
  29. package/package-lock.json +2 -2
  30. package/package.json +1 -1
  31. package/release-notes.json +12 -0
  32. package/scripts/gds/government-walk.js +1 -0
  33. package/scripts/gds/run-unit-tests.js +3 -0
  34. package/src/bongos/project-settings.js +79 -0
  35. package/src/module-api.js +8 -1
  36. package/tests/autopickup_cap.mjs +236 -0
  37. package/tests/autopickup_cap_db.mjs +115 -0
  38. package/tests/cosmos_token_contrast.mjs +10 -18
  39. package/tests/github_handback_any_device.mjs +4 -3
  40. package/tests/github_one_trip.mjs +45 -47
  41. package/tests/github_org_account.mjs +2 -2
  42. package/tests/github_pick_up.mjs +40 -29
  43. package/tests/government_act_alone_buildings.mjs +1 -0
  44. package/tests/onboard.mjs +13 -7
  45. package/tests/project_invite_ui.mjs +6 -3
  46. package/tests/project_modules_ui.mjs +3 -3
  47. package/tests/projects_hub.mjs +8 -9
  48. package/tests/projects_hub_cloud_hop.mjs +77 -125
  49. package/tests/projects_hub_dns_ready.mjs +42 -51
  50. package/tests/projects_hub_pre_uat.mjs +68 -69
  51. package/tests/public_landing_projects.mjs +45 -102
  52. package/tests/setup_status_facts.mjs +400 -0
  53. package/tests/wizard_github_feedback.mjs +90 -87
  54. package/tests/wizard_one_page.mjs +11 -9
  55. package/tests/wizard_auto_open.mjs +0 -94
@@ -0,0 +1,24 @@
1
+ -- core_275 a claim can carry the auto_pickup mark
2
+ -- Applied under task 1004677 (BV2.TW19a, ADR 0371 D5/D6).
3
+ --
4
+ -- WHY. The Autobongos tweak lane picks up a submitted page round with nobody at a
5
+ -- terminal, and each pickup costs the project a grade nobody chose to spend. So the
6
+ -- lane claims with a mark, and the SERVER counts marked claims at claim time:
7
+ -- tweak.autopickup_monthly_cap per calendar month, and at most two per round. The
8
+ -- count is read from these rows (the ledger), never from a stored counter.
9
+ --
10
+ -- SHAPE. One nullable boolean. NULL and false both mean "an ordinary claim"; only
11
+ -- true is counted. Nullable with no default, so the column is additive and an
12
+ -- older release that never names it keeps inserting exactly as before.
13
+ --
14
+ -- The partial index serves the two counts (this month's marked claims, and one
15
+ -- round's marked claims), and stays tiny: only marked rows are in it.
16
+
17
+ ALTER TABLE claims ADD COLUMN IF NOT EXISTS auto_pickup boolean;
18
+
19
+ CREATE INDEX IF NOT EXISTS idx_claims_auto_pickup
20
+ ON claims (claimed_at, task_id)
21
+ WHERE auto_pickup IS TRUE;
22
+
23
+ INSERT INTO schema_migrations (version) VALUES ('core_275_claims_auto_pickup')
24
+ ON CONFLICT (version) DO NOTHING;
@@ -66,6 +66,7 @@ const AREAS = Object.freeze([
66
66
  today: 'whoever may change the project settings', acts: Object.freeze([
67
67
  'PATCH /project-settings/rot-days',
68
68
  'PATCH /project-settings/ship-mode',
69
+ 'PATCH /project-settings/tweak-autopickup',
69
70
  'PATCH /work-categories/:id',
70
71
  ]) }),
71
72
  Object.freeze({ key: 'disputes', name: 'Disputes', gate: 'security.report.adjudicate',
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ // modules/lifecycle/autopickup.js — the server's half of tweak auto-pickup
4
+ // (task 1004677 / BV2.TW19a, ADR 0371 D3, D5, D6).
5
+ //
6
+ // The Autobongos tweak lane claims a submitted page round with an `auto_pickup`
7
+ // mark (claims.auto_pickup, migration core_275). The runner is a builder's own
8
+ // process, so the limits on it are NOT left to the runner: claimTask calls
9
+ // autoPickupRefusal inside its transaction and the claim is refused when
10
+ //
11
+ // - the claim is marked but the task is not a page-tweak round (AUTO_PICKUP_NOT_A_TWEAK)
12
+ // - the project has tweak.autopickup off (AUTO_PICKUP_OFF)
13
+ // - this calendar month already holds `cap` marked claims (AUTO_PICKUP_CAP_REACHED)
14
+ // - this round already has two marked claims (AUTO_PICKUP_ROUND_LIMIT)
15
+ //
16
+ // THE LOCK. The count and the claim insert must not interleave with another
17
+ // runner's, or two runners racing for the last slot both read cap-1 and both
18
+ // insert. So the count runs after pg_advisory_xact_lock('page-tweak/autopickup'),
19
+ // one project-wide key, held to the end of claimTask's transaction (the insert
20
+ // commits under it). Only marked claims take the lock; an ordinary claim never
21
+ // waits on it.
22
+ //
23
+ // COUNTED FROM THE LEDGER, never a counter: a stored number drifts the first time
24
+ // a write fails between the claim and the bump. A released claim still counts —
25
+ // the pickup happened and the grade may already have been spent.
26
+ //
27
+ // The mark is a cost brake on a runner the owner chose to run, not a security
28
+ // boundary: a claim without it is an ordinary claim its builder could make by hand.
29
+
30
+ const api = require('../../src/module-api');
31
+
32
+ const PAGE_TWEAK_SOURCE = 'page-tweak';
33
+ const LOCK_KEY = 'page-tweak/autopickup';
34
+ // D6.2: one apply plus one try at the artist's note; a round sent back twice is a
35
+ // disagreement a person should read.
36
+ const MAX_AUTO_APPLIES_PER_ROUND = 2;
37
+
38
+ // The two counts, on any executor (the claim transaction's client, or the pool).
39
+ // Month boundaries are the database's, so every runner and every reader agree.
40
+ const MONTH_COUNT_SQL = `
41
+ SELECT count(*)::int AS n
42
+ FROM claims c JOIN tasks t ON t.id = c.task_id
43
+ WHERE c.auto_pickup IS TRUE
44
+ AND t.source = '${PAGE_TWEAK_SOURCE}'
45
+ AND c.claimed_at >= date_trunc('month', now())`;
46
+ const ROUND_COUNT_SQL = `
47
+ SELECT count(*)::int AS n FROM claims WHERE task_id = $1 AND auto_pickup IS TRUE`;
48
+
49
+ async function countThisMonth(exec = api.pool) {
50
+ const { rows } = await exec.query(MONTH_COUNT_SQL);
51
+ return Number(rows[0] && rows[0].n) || 0;
52
+ }
53
+
54
+ async function countForRound(exec, taskId) {
55
+ const { rows } = await exec.query(ROUND_COUNT_SQL, [taskId]);
56
+ return Number(rows[0] && rows[0].n) || 0;
57
+ }
58
+
59
+ // autoPickupRefusal(client, task, settings) — null when the marked claim may
60
+ // proceed, else the refusal object claimTask throws. `settings` is the resolved
61
+ // { mode, cap }, read BEFORE the transaction opens (the claimPermissionsFor
62
+ // precedent: a settings read is not part of the claim's consistency).
63
+ async function autoPickupRefusal(client, task, settings) {
64
+ if (!task || task.source !== PAGE_TWEAK_SOURCE) {
65
+ return { code: 'AUTO_PICKUP_NOT_A_TWEAK', message: 'Only a page tweak round can be claimed with the auto_pickup mark.' };
66
+ }
67
+ const s = settings || {};
68
+ if (s.mode !== 'on') {
69
+ return { code: 'AUTO_PICKUP_OFF', message: 'Tweak auto-pickup is off for this project, so a submitted page waits for a person.' };
70
+ }
71
+ await client.query('SELECT pg_advisory_xact_lock(hashtext($1))', [LOCK_KEY]);
72
+ const forRound = await countForRound(client, task.id);
73
+ if (forRound >= MAX_AUTO_APPLIES_PER_ROUND) {
74
+ return {
75
+ code: 'AUTO_PICKUP_ROUND_LIMIT', applied: forRound, limit: MAX_AUTO_APPLIES_PER_ROUND,
76
+ message: `This page round was already applied automatically ${forRound} times, so it waits for a person.`,
77
+ };
78
+ }
79
+ const used = await countThisMonth(client);
80
+ if (used >= s.cap) {
81
+ return {
82
+ code: 'AUTO_PICKUP_CAP_REACHED', used, cap: s.cap,
83
+ message: `Auto-pickup has used this month's ${s.cap} rounds, so submitted pages wait for a person until next month.`,
84
+ };
85
+ }
86
+ return null;
87
+ }
88
+
89
+ module.exports = {
90
+ LOCK_KEY,
91
+ MAX_AUTO_APPLIES_PER_ROUND,
92
+ countThisMonth,
93
+ autoPickupRefusal,
94
+ };
@@ -37,6 +37,7 @@ const { claimGate, claimPermissionsFor, disambiguateWorktreeName, foldWorktreeNa
37
37
  const { sealSessionLog } = require('./db-shared.js');
38
38
  const { gatingRebaseTasksForBuilder, shouldRefuseClaimResolve } = require('./db-ship.js');
39
39
  const { REWARD_SUGGESTION_CAP, rewardGateAssignment } = require('./db-tasks.js');
40
+ const { autoPickupRefusal } = require('./autopickup.js');
40
41
 
41
42
  // -------------------------------------------------------------------------
42
43
  // Claims (atomic)
@@ -99,8 +100,11 @@ function isRebaseGateExempt(task) {
99
100
  return Boolean(task) && task.kind === 'infra' && Number(task.priority) === 1;
100
101
  }
101
102
 
102
- async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSessionId, enforceOverlap = false }) {
103
+ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSessionId, enforceOverlap = false, autoPickup = false }) {
103
104
  const heldPermissions = await claimPermissionsFor(builderId); // BV1.R106 — before the tx opens
105
+ // ADR 0371 D5: a marked claim needs the project's switch and cap, read before the
106
+ // tx like the permissions above; the COUNT runs inside it, under the lock.
107
+ const autoPickupSettings = autoPickup ? await api.projectSettings.resolveTweakAutopickup() : null;
104
108
  const client = await pool.connect();
105
109
  try {
106
110
  await client.query('BEGIN');
@@ -142,7 +146,7 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
142
146
  }
143
147
 
144
148
  const { rows: taskRows } = await client.query(
145
- `SELECT id, status, touches, newcomer_friendly, requires_rank, needs_migration, module_key, credits_reward, reward_gate_exempt, est_minutes, kind, goal_id FROM tasks WHERE id = $1 FOR UPDATE`,
149
+ `SELECT id, status, touches, newcomer_friendly, requires_rank, needs_migration, module_key, credits_reward, reward_gate_exempt, est_minutes, kind, goal_id, source FROM tasks WHERE id = $1 FOR UPDATE`,
146
150
  [taskId]
147
151
  );
148
152
  if (taskRows.length === 0) {
@@ -350,6 +354,14 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
350
354
  }
351
355
  }
352
356
 
357
+ // ADR 0371 D5/D6 (task 1004677): the auto_pickup mark. The cap and the per-round
358
+ // limit are counted under a project-wide advisory lock that this transaction
359
+ // holds through the insert below, so two runners cannot both take the last slot.
360
+ if (autoPickup) {
361
+ const refusal = await autoPickupRefusal(client, task, autoPickupSettings);
362
+ if (refusal) throw refusal;
363
+ }
364
+
353
365
  // Pre-allocate a migration number if the task signals it'll add one.
354
366
  // Trigger: the tasks.needs_migration boolean (migration 081, touches[]
355
367
  // cleanup [1/8] / task 876). Replaced the old touches[]-contains-'migrations/'
@@ -396,9 +408,11 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
396
408
  finalWorktreeName = disambiguateWorktreeName(finalWorktreeName, taken);
397
409
  }
398
410
 
411
+ // The column is named only on a marked claim, so an ordinary claim's insert is
412
+ // byte-identical to before core_275.
399
413
  const insertClaim = await client.query(
400
- `INSERT INTO claims (task_id, builder_id, worktree_name, requested_worktree_name, head_sha, reserved_migration_number, creator_session_id)
401
- VALUES ($1, $2, $3, $4, $5, $6, $7)
414
+ `INSERT INTO claims (task_id, builder_id, worktree_name, requested_worktree_name, head_sha, reserved_migration_number, creator_session_id${autoPickup ? ', auto_pickup' : ''})
415
+ VALUES ($1, $2, $3, $4, $5, $6, $7${autoPickup ? ', TRUE' : ''})
402
416
  RETURNING id, task_id, builder_id, worktree_name, requested_worktree_name, claimed_at, head_sha, reserved_migration_number`,
403
417
  [taskId, builderId, finalWorktreeName, requestedWorktreeName, headSha ?? null, reservedMigrationNumber, creatorSessionId ?? null]
404
418
  );
@@ -276,8 +276,12 @@ module.exports = function buildClaimsRouter() {
276
276
  // an interactive single-human claim only WARNS (git merge is the real
277
277
  // collision detector — ADR 0017).
278
278
  enforce_touches: { type: 'boolean' },
279
+ // ADR 0371 D5 (task 1004677): the Autobongos tweak lane's mark. The server
280
+ // counts marked claims against the project's monthly cap and the per-round
281
+ // limit; see modules/lifecycle/autopickup.js.
282
+ auto_pickup: { type: 'boolean' },
279
283
  })) return;
280
- const { task_id: taskId, worktree_name: worktreeName, head_sha: headSha, bind_session: bindSession, enforce_touches: enforceTouches } = req.body || {};
284
+ const { task_id: taskId, worktree_name: worktreeName, head_sha: headSha, bind_session: bindSession, enforce_touches: enforceTouches, auto_pickup: autoPickup } = req.body || {};
281
285
  // Belt-and-braces — task_id must coerce to a finite number. Older calls
282
286
  // sent string ints (Number.isFinite passes both); reject NaN/garbage.
283
287
  if (!Number.isFinite(Number(taskId))) {
@@ -291,6 +295,7 @@ module.exports = function buildClaimsRouter() {
291
295
  headSha: headSha ?? null,
292
296
  creatorSessionId: resolveCreatorSessionId(bindSession, req.gdsSession?.session_id),
293
297
  enforceOverlap: enforceTouches === true,
298
+ autoPickup: autoPickup === true,
294
299
  });
295
300
  const { just_unlocked: justUnlocked, overlap_warning: overlapWarning, sibling_overlap_warning: siblingOverlapWarning, ...claim } = claimResult;
296
301
  const task = await db.getTask(Number(taskId));
@@ -375,6 +380,12 @@ module.exports = function buildClaimsRouter() {
375
380
  // block, not an authorization refusal). The refusal object already
376
381
  // carries `message` + `hint`, so no extra hint-building step needed.
377
382
  err.code === 'RELEVANCE_REVIEW_PENDING' ? 409 :
383
+ // ADR 0371 D5/D6: a marked auto-pickup claim the project's switch, monthly
384
+ // cap or per-round limit refuses. The round waits for a person.
385
+ err.code === 'AUTO_PICKUP_NOT_A_TWEAK' ? 409 :
386
+ err.code === 'AUTO_PICKUP_OFF' ? 409 :
387
+ err.code === 'AUTO_PICKUP_CAP_REACHED' ? 409 :
388
+ err.code === 'AUTO_PICKUP_ROUND_LIMIT' ? 409 :
378
389
  500;
379
390
  // V4.R14 (#952): a TASK_NOT_READY 409 names the way forward instead of
380
391
  // leaving the caller to discover the promote route by spelunking (#506).
@@ -31,6 +31,7 @@
31
31
  const express = require('express');
32
32
  const api = require('../../../src/module-api');
33
33
  const goalCategory = require('../goal-category');
34
+ const autopickup = require('../autopickup');
34
35
 
35
36
  const log = api.logger('lifecycle');
36
37
 
@@ -380,6 +381,64 @@ module.exports = function buildProjectSettingsRouter() {
380
381
  }
381
382
  });
382
383
 
384
+ // --- tweak auto-pickup (task 1004677, ADR 0371 D3/D5) ----------------------
385
+ //
386
+ // GET /project-settings/tweak-autopickup — the switch, the monthly cap, and how
387
+ // many rounds the lane has picked up this calendar month (counted from marked
388
+ // claims, never a stored counter).
389
+ //
390
+ // rank: any signed-in builder. The runner reads it before it tries a round, and
391
+ // the studio shows the count; it carries no reward internals or file paths.
392
+ async function autopickupBody() {
393
+ const resolved = await api.projectSettings.resolveTweakAutopickup();
394
+ const used = await autopickup.countThisMonth();
395
+ return {
396
+ mode: resolved.mode,
397
+ cap: resolved.cap,
398
+ used_this_month: used,
399
+ remaining_this_month: Math.max(0, resolved.cap - used),
400
+ per_round_limit: autopickup.MAX_AUTO_APPLIES_PER_ROUND,
401
+ source: resolved.source,
402
+ invalid: resolved.invalid,
403
+ sentence: api.projectSettings.describeTweakAutopickup(resolved),
404
+ };
405
+ }
406
+
407
+ router.get('/project-settings/tweak-autopickup', api.requireBuilder, async (req, res) => {
408
+ try {
409
+ res.set('Cache-Control', 'no-store');
410
+ res.json(await autopickupBody());
411
+ } catch (err) {
412
+ log.error(`GET /project-settings/tweak-autopickup: ${err && err.message}`);
413
+ res.fail('project_settings_read_failed', { status: 500, message: 'internal error' });
414
+ }
415
+ });
416
+
417
+ // PATCH /project-settings/tweak-autopickup — body { mode?: 'off' | 'on', cap?: n }.
418
+ //
419
+ // rank: project.settings.manage (metic+, government_010), the same atom as the
420
+ // other writes on this page. Each field is validated by the resolver's own parser,
421
+ // and both are checked before either is written, so a half-valid body writes nothing.
422
+ router.patch('/project-settings/tweak-autopickup', api.requireBuilder, api.requirePermission('project.settings.manage'), api.requireActAlone('rules'), async (req, res) => {
423
+ if (api.validateOrRespond(req, res, { mode: { type: 'string' }, cap: { type: 'number' } })) return;
424
+ const body = req.body || {};
425
+ if (body.mode == null && body.cap == null) {
426
+ return res.fail('bad_tweak_autopickup', { status: 400, message: 'send mode, cap, or both' });
427
+ }
428
+ const mode = body.mode == null ? null : api.projectSettings.parseAutopickup(body.mode);
429
+ if (mode && !mode.mode) return res.fail('bad_tweak_autopickup', { status: 400, message: `mode: ${mode.reason}` });
430
+ const cap = body.cap == null ? null : api.projectSettings.parseAutopickupCap(body.cap);
431
+ if (cap && cap.cap == null) return res.fail('bad_tweak_autopickup', { status: 400, message: `cap: ${cap.reason}` });
432
+ try {
433
+ if (mode) await api.projectSettings.set(api.projectSettings.KEYS.TWEAK_AUTOPICKUP, mode.mode, req.builder.id);
434
+ if (cap) await api.projectSettings.set(api.projectSettings.KEYS.TWEAK_AUTOPICKUP_CAP, cap.cap, req.builder.id);
435
+ res.json({ ok: true, ...(await autopickupBody()) });
436
+ } catch (err) {
437
+ log.error(`PATCH /project-settings/tweak-autopickup: ${err && err.message}`);
438
+ res.fail('project_settings_write_failed', { status: 500, message: 'internal error' });
439
+ }
440
+ });
441
+
383
442
  return router;
384
443
  };
385
444
 
@@ -1,5 +1,5 @@
1
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",
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, with its GitHub app approved, installed and signed in (since task 1004683 part 03 says it is alive only once every required fact is done). The invite box and the Enter Genesis door open 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
3
  "page": "/projects",
4
4
  "surface": "public-landing",
5
5
  "modes": [
@@ -131,7 +131,8 @@
131
131
  "#panel-done",
132
132
  "#wizInvite",
133
133
  "#wizInviteWho",
134
- "#humanSteps"
134
+ "#doneAlive",
135
+ "#enterGenesis"
135
136
  ],
136
137
  "hidden": [
137
138
  "#hop",
@@ -0,0 +1,91 @@
1
+ {
2
+ "_": "Part 03 after Create, the five scenarios of the design of record docs/design/mocks/day-night-rooms/setup-status.html (task 1004683): the same page and stub, the instance list aliased to one project per scenario, each opened by URL the way the GitHub return route lands a founder (?view=new&id=<project>). The GitHub halves of each owner read come from the REAL projections (see the fixture's note). The room follows the viewer's clock, so these renders picture whichever room the machine's clock gives, and the stub serves a local address with no apex base, so the Approve fact pictures its local-platform branch (GitHub cannot reach a local platform) rather than Connect GitHub. The day and night shots attached to the task were taken with the browser's timezone moved and the apex stamped. Rendered by name: node modules/ui-design/kit/render.js --states modules/public-landing/public/projects-setup.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-setup"
12
+ }
13
+ },
14
+ "ignoreRequests": [
15
+ "/api/(gds|bongos)/me$",
16
+ "/my-projects",
17
+ "/github/orgs",
18
+ "/env-manifest$"
19
+ ],
20
+ "ignoreConsole": [
21
+ "/api/(gds|bongos)/me",
22
+ "\\b401\\b",
23
+ "\\b404\\b",
24
+ "Unauthorized"
25
+ ],
26
+ "ignoreControls": [
27
+ ".skip",
28
+ ".vh"
29
+ ],
30
+ "states": {
31
+ "setup-just-created": {
32
+ "auth": true,
33
+ "url": "/projects?view=new&id=201",
34
+ "actions": [["wait", 1500]],
35
+ "expect": {
36
+ "visible": ["#panel-done", "#setupNow", "#homeFacts", "#ghFacts", "#hopLink", "#homeFacts [data-fact=\"server\"].now"],
37
+ "hidden": ["#wizParts", "#hop", "#doneAlive", "#aaPick"],
38
+ "text": [["#donePartT", "Getting kestrel ready"], ["#ghState", "1 of 6 done"]]
39
+ }
40
+ },
41
+ "setup-waiting-on-github": {
42
+ "auth": true,
43
+ "url": "/projects?view=new&id=202",
44
+ "actions": [["wait", 1500]],
45
+ "expect": {
46
+ "visible": ["#panel-done", "#homeFacts [data-fact=\"answers\"].now"],
47
+ "hidden": ["#hop", "#doneAlive", "#aaPick"],
48
+ "text": [["#ghState", "2 of 6 done"]]
49
+ }
50
+ },
51
+ "setup-installing": {
52
+ "auth": true,
53
+ "url": "/projects?view=new&id=203",
54
+ "actions": [["wait", 1500]],
55
+ "expect": {
56
+ "visible": ["#panel-done", "#ghFacts [data-fact=\"install\"].you"],
57
+ "hidden": ["#hop", "#doneAlive"],
58
+ "text": [["#ghState", "4 of 6 done"]]
59
+ }
60
+ },
61
+ "setup-stopped": {
62
+ "auth": true,
63
+ "url": "/projects?view=new&id=204",
64
+ "actions": [["wait", 1500]],
65
+ "expect": {
66
+ "visible": ["#panel-done", "#ghFacts [data-fact=\"install\"].bad [data-action=\"pick-up\"]"],
67
+ "hidden": ["#hop", "#doneAlive"],
68
+ "text": [["#ghState", "5 of 6 done"]]
69
+ }
70
+ },
71
+ "setup-all-done": {
72
+ "auth": true,
73
+ "url": "/projects?view=new&id=205",
74
+ "actions": [["wait", 1500]],
75
+ "expect": {
76
+ "visible": ["#panel-done", "#doneAlive", "#enterGenesis"],
77
+ "hidden": ["#hop", "#hopLink"],
78
+ "text": [["#ghState", "ready ✓"], ["#done-h", "wren is alive."], ["#enterGenesis", "Enter Genesis"]]
79
+ }
80
+ },
81
+ "setup-cloud-hop-open": {
82
+ "auth": true,
83
+ "url": "/projects?view=new&id=201",
84
+ "actions": [["wait", 1200], ["click", "#hopLink"], ["wait", 600]],
85
+ "expect": {
86
+ "visible": ["#hop", "#hopCanvas"],
87
+ "attr": [["#hopLink", "aria-expanded", "true"]]
88
+ }
89
+ }
90
+ }
91
+ }