@bongos/core 1.20.57 → 1.20.59

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 (73) hide show
  1. package/.bongos-core.json +188 -53
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +10 -0
  4. package/clients/bongos-client/index.cjs +10 -0
  5. package/clients/bongos-client/index.d.ts +14 -4
  6. package/clients/bongos-client/index.mjs +10 -0
  7. package/docs/api/openapi.json +220 -20
  8. package/docs/api-reference.md +8 -3
  9. package/docs/architecture.md +1 -0
  10. package/docs/copy-inventory.md +21 -5
  11. package/docs/copy-registry.json +169 -15
  12. package/docs/module-api-changelog.md +4 -0
  13. package/docs/page-inventory.json +8 -3
  14. package/docs/page-readings.json +72 -72
  15. package/migrations/core_268_builders_main_discipline.sql +35 -0
  16. package/modules/copy-desk/artist-stats.js +56 -0
  17. package/modules/copy-desk/module.json +1 -1
  18. package/modules/copy-desk/page-status.js +39 -0
  19. package/modules/copy-desk/routes/copy-desk.js +7 -16
  20. package/modules/copy-desk/tests/copy_no_cms.mjs +10 -1
  21. package/modules/economy/credits-by-craft.js +208 -0
  22. package/modules/economy/reward.js +8 -0
  23. package/modules/hall-ui/public/genesis/r5-life-1-primordial-sea-cut.webp +0 -0
  24. package/modules/hall-ui/public/genesis/r5-life-1-primordial-sea.jpg +0 -0
  25. package/modules/hall-ui/public/genesis/r5-life-2-first-cells-cut.webp +0 -0
  26. package/modules/hall-ui/public/genesis/r5-life-2-first-cells.jpg +0 -0
  27. package/modules/hall-ui/public/genesis/r5-life-3-genome-cut.webp +0 -0
  28. package/modules/hall-ui/public/genesis/r5-life-3-genome.jpg +0 -0
  29. package/modules/hall-ui/public/genesis/r5-life-4-membrane-cut.webp +0 -0
  30. package/modules/hall-ui/public/genesis/r5-life-4-membrane.jpg +0 -0
  31. package/modules/hall-ui/public/genesis/r5-life-5-nervous-system-cut.webp +0 -0
  32. package/modules/hall-ui/public/genesis/r5-life-5-nervous-system.jpg +0 -0
  33. package/modules/hall-ui/public/genesis-home.js +349 -0
  34. package/modules/hall-ui/public/hall-render.js +2 -2
  35. package/modules/hall-ui/public/index-genesis-decide.states.json +13 -0
  36. package/modules/hall-ui/public/index-genesis.states.json +13 -0
  37. package/modules/hall-ui/public/index.html +2 -0
  38. package/modules/hall-ui/public/profile.js +1 -1
  39. package/modules/hall-ui/public/style.css +65 -0
  40. package/modules/hall-ui/records/genesis-home.md +18 -0
  41. package/modules/ideas/ideator-stats.js +149 -0
  42. package/modules/ideas/module.json +2 -1
  43. package/modules/ideas/routes/inbox.js +7 -0
  44. package/modules/lifecycle/activity.js +5 -2
  45. package/modules/lifecycle/db-analytics.js +4 -2
  46. package/modules/lifecycle/db-tasks.js +49 -1
  47. package/modules/lifecycle/db.js +3 -2
  48. package/modules/lifecycle/kickoff-checklist.js +54 -1
  49. package/modules/lifecycle/kickoff-seed.js +61 -2
  50. package/modules/lifecycle/page-tweak-reads.js +23 -5
  51. package/modules/lifecycle/routes/kickoff-seed.js +7 -1
  52. package/modules/onboarding/genesis-home.js +274 -0
  53. package/modules/onboarding/routes/onboarding.js +56 -0
  54. package/modules/ui-design/kit/fixtures/founding-genesis-decide.json +108 -0
  55. package/modules/ui-design/kit/fixtures/founding-genesis-stage.json +162 -0
  56. package/modules/ui-design/kit/fixtures/government-constitution-sample.json +205 -0
  57. package/modules/ui-design/kit/serve.js +7 -1
  58. package/package-lock.json +2 -2
  59. package/package.json +1 -1
  60. package/release-notes.json +12 -0
  61. package/scripts/gds/run-unit-tests.js +5 -0
  62. package/src/bongos/db.js +44 -6
  63. package/src/bongos/role-stats.js +155 -0
  64. package/src/bongos/routes/builders.js +15 -2
  65. package/src/bongos/routes/me.js +11 -3
  66. package/src/module-api.js +1 -1
  67. package/tests/copy_desk_page_reads.mjs +1 -1
  68. package/tests/fixtures/page-ledger.mjs +1 -1
  69. package/tests/genesis_home.mjs +288 -0
  70. package/tests/main_discipline.mjs +81 -0
  71. package/tests/profile_role_stats.mjs +416 -0
  72. package/tests/role_stats_db.mjs +237 -0
  73. package/tests/tweak_replaces_artist_review.mjs +1 -1
@@ -0,0 +1,274 @@
1
+ 'use strict';
2
+
3
+ // modules/onboarding/genesis-home.js — THE GENESIS HOME: what a founding project does,
4
+ // stage by stage, until it comes alive (task 1004423 / BV2.PS08, goal 1000121; spec
5
+ // docs/specs/bongos-v2-project-startup.md D4 + D7; design of record
6
+ // docs/design/mocks/project-startup/GenesisDecide|GenesisHall|Alive.dc.html).
7
+ //
8
+ // The flow, in founding-mode.js's stages:
9
+ // decide the founder settles a plan: each stage's length and what counts as
10
+ // done, the starting government, how many passes, optional advisors.
11
+ // Saving it files the five stages as REAL tasks (the kickoff seeder, on
12
+ // the lifecycle port) and moves the project to scope.
13
+ // scope .. each stage is worked as tasks. When they are all finished (shipped or
14
+ // legislation dropped) the stage is put to the Board Room under the constitution in
15
+ // force (the government port's genesis-stage subject, spec D7). Under
16
+ // Monarchy / BDFL the founder's one yes closes it, which is the solo
17
+ // fast-track's "one confirm per stage" (D4). A pass moves the project to
18
+ // the next stage, and the rooms that stage woke stop sleeping
19
+ // (founding-mode.js WAKES, which the shell already reads).
20
+ // alive once legislation has passed, the founder brings the project to life:
21
+ // founding mode switches off and the full hall opens.
22
+ //
23
+ // WHERE THE STATE LIVES. The stage is founding-mode's (project_settings). The plan is
24
+ // one more project_settings row, `founding.plan`, JSON. The tasks are the board's. The
25
+ // decision is the Board Room's: a stage moves on only because a sitting PASSED, read
26
+ // back from the government port, never because a client said so. A pass is announced
27
+ // as `board.genesis_stage.passed` (the route factory listens), and every read
28
+ // reconciles again, so a missed nudge cannot strand a stage.
29
+ //
30
+ // THE BOUNDARY. Core through ../../src/module-api.js; the lifecycle and government
31
+ // modules through their ports only (resolveOptional), never a require.
32
+
33
+ const fm = require('./founding-mode');
34
+
35
+ const PLAN_KEY = 'founding.plan';
36
+ const PROPER = Object.freeze(fm.STAGES.slice(1)); // the five stages after 'decide'
37
+
38
+ // Each stage's life-strip plate (the world pair under the hall's genesis/ folder, task
39
+ // 1004425), what the design calls that moment of life, and its plain done-when line.
40
+ const STAGE_INFO = Object.freeze({
41
+ scope: Object.freeze({ label: 'Scope', life: 'primordial sea', plate: 'r5-life-1-primordial-sea', days: 7,
42
+ done_when: 'The scope is written: what you are making, for whom, and what is left out.' }),
43
+ strategize: Object.freeze({ label: 'Strategize', life: 'first cells', plate: 'r5-life-2-first-cells', days: 7,
44
+ done_when: 'The strategy is written out, or the one you have is linked.' }),
45
+ charter: Object.freeze({ label: 'Charter', life: 'the genome', plate: 'r5-life-3-genome', days: 4,
46
+ done_when: 'The starting government is chosen and the bylaws are written.' }),
47
+ legalize: Object.freeze({ label: 'Legalize', life: 'the membrane', plate: 'r5-life-4-membrane', days: 7,
48
+ done_when: 'Rewards are decided, and who can be paid.' }),
49
+ legislation: Object.freeze({ label: 'Legislation', life: 'the nervous system', plate: 'r5-life-5-nervous-system', days: 3,
50
+ done_when: 'The voting rules are set: who votes, and how many must agree.' }),
51
+ });
52
+
53
+ const PASSES = Object.freeze([1, 2]);
54
+ const ADVISORS = Object.freeze(['none', 'cbaas']);
55
+ const MAX_DAYS = 90;
56
+
57
+ const intIn = (v, lo, hi) => Number.isInteger(v) && v >= lo && v <= hi;
58
+
59
+ // parsePlan(body) — PURE. The founder's plan, defaults filled in, or why it is refused.
60
+ // The government is recorded, not applied: the charter stage (task 1004427) files it as
61
+ // an amendment, so here it is only a well-formed form key.
62
+ function parsePlan(body = {}) {
63
+ const b = body && typeof body === 'object' ? body : {};
64
+ const days = {};
65
+ for (const s of PROPER) {
66
+ const raw = b.days && b.days[s] != null ? Number(b.days[s]) : STAGE_INFO[s].days;
67
+ if (!intIn(raw, 1, MAX_DAYS)) return { ok: false, reason: 'bad_days', stage: s };
68
+ days[s] = raw;
69
+ }
70
+ const passes = b.passes == null ? 1 : Number(b.passes);
71
+ if (!PASSES.includes(passes)) return { ok: false, reason: 'bad_passes' };
72
+ const advisors = b.advisors == null ? 'none' : String(b.advisors);
73
+ if (!ADVISORS.includes(advisors)) return { ok: false, reason: 'bad_advisors' };
74
+ const government = b.government == null || b.government === '' ? null : String(b.government);
75
+ if (government !== null && !/^[a-z][a-z-]{1,39}$/.test(government)) return { ok: false, reason: 'bad_government' };
76
+ const expected = b.expected_builders == null || b.expected_builders === '' ? null : Number(b.expected_builders);
77
+ if (expected !== null && !intIn(expected, 1, 100000)) return { ok: false, reason: 'bad_expected_builders' };
78
+ return { ok: true, plan: { days, passes, advisors, government, expected_builders: expected, existing: b.existing === true } };
79
+ }
80
+
81
+ function readPlanValue(raw) {
82
+ if (!raw) return null;
83
+ try { const p = JSON.parse(raw); return p && typeof p === 'object' ? p : null; } catch (_) { return null; }
84
+ }
85
+
86
+ const FINISHED = Object.freeze(['shipped', 'abandoned']); // the Board Room's own gate, mirrored for display
87
+
88
+ // shapeGenesis({ mode, plan, tasks, board, stageAt }) — PURE. The genesis home's view.
89
+ // mode foundingModeFor's shape (non-null)
90
+ // plan the saved plan or null (deciding)
91
+ // tasks { stage: [{ id, title, status }] }
92
+ // board { stage: genesisStageStatus row } or {}
93
+ // stageAt { stage: ISO } — when each stage began, for its due date
94
+ function shapeGenesis({ mode, plan, tasks = {}, board = {}, stageAt = {} }) {
95
+ const at = fm.STAGES.indexOf(mode.stage);
96
+ const stages = PROPER.map((key) => {
97
+ const info = STAGE_INFO[key];
98
+ const idx = fm.STAGES.indexOf(key);
99
+ const list = (tasks[key] || []).map((t) => ({ ...t, finished: FINISHED.includes(t.status) }));
100
+ const b = board[key] || null;
101
+ const days = plan && plan.days && plan.days[key] ? plan.days[key] : info.days;
102
+ const began = stageAt[key] ? new Date(stageAt[key]) : null;
103
+ const due = began && Number.isFinite(began.getTime()) ? new Date(began.getTime() + days * 86400000).toISOString() : null;
104
+ return {
105
+ key,
106
+ number: idx,
107
+ label: info.label,
108
+ life: info.life,
109
+ plate: info.plate,
110
+ done_when: info.done_when,
111
+ wakes: fm.WAKES[key] || [],
112
+ days,
113
+ due,
114
+ state: idx < at ? 'done' : idx === at ? 'now' : 'later',
115
+ tasks: list,
116
+ finished: list.filter((t) => t.finished).length,
117
+ total: list.length,
118
+ board: b ? {
119
+ state: b.state, item_id: b.item_id || null, needs: b.needs || null,
120
+ one_vote_decides: !!b.one_vote_decides, waiting_on: b.waiting_on || [],
121
+ } : null,
122
+ };
123
+ });
124
+ const now = stages.find((s) => s.state === 'now') || null;
125
+ const sitting = now && now.board && now.board.state === 'sitting';
126
+ const passed = now && now.board && now.board.state === 'passed';
127
+ return {
128
+ stage: mode.stage,
129
+ stage_number: mode.stage_number,
130
+ stage_count: mode.stage_count,
131
+ day: mode.day,
132
+ deciding: mode.stage === 'decide',
133
+ plan: plan || null,
134
+ stages,
135
+ // The close door: every task of the current stage finished, and not already sitting.
136
+ can_close: !!(now && now.total > 0 && now.finished === now.total && !sitting && !passed),
137
+ // Alive: the last stage has passed its sitting.
138
+ can_come_alive: !!(now && now.key === 'legislation' && passed),
139
+ };
140
+ }
141
+
142
+ // ── the reads and writes, over injectable seams ──────────────────────────────
143
+
144
+ function seams(deps = {}) {
145
+ const api = deps.api || require('../../src/module-api');
146
+ return {
147
+ get: deps.getSetting || api.projectSettings.get,
148
+ set: deps.setSetting || api.projectSettings.set,
149
+ lifecycle: deps.lifecycle !== undefined ? deps.lifecycle : api.resolveOptional('lifecycle.kickoffSeed'),
150
+ government: deps.government !== undefined ? deps.government : api.resolveOptional('government'),
151
+ now: deps.now,
152
+ };
153
+ }
154
+ const modeDeps = (s) => ({ getSetting: s.get, setSetting: s.set, government: s.government, now: s.now });
155
+
156
+ async function boardByStage(gov) {
157
+ if (!gov || typeof gov.genesisStageStatus !== 'function') return {};
158
+ const status = await gov.genesisStageStatus();
159
+ const out = {};
160
+ for (const row of (status && status.stages) || []) out[row.key] = row;
161
+ return out;
162
+ }
163
+
164
+ // reconcile — move on from every stage whose sitting has passed. The ONLY thing that
165
+ // advances a stage, called on the pass event and on every read and write. Legislation
166
+ // does not advance: its pass opens the alive door instead.
167
+ async function reconcile(s) {
168
+ let mode = await fm.foundingModeFor(modeDeps(s));
169
+ if (!mode || !PROPER.includes(mode.stage) || mode.stage === 'legislation') return mode;
170
+ const board = await boardByStage(s.government);
171
+ while (mode && PROPER.includes(mode.stage) && mode.stage !== 'legislation'
172
+ && board[mode.stage] && board[mode.stage].state === 'passed') {
173
+ const next = fm.STAGES[fm.STAGES.indexOf(mode.stage) + 1];
174
+ mode = await fm.setFoundingMode({ stage: next }, null, modeDeps(s));
175
+ }
176
+ return mode;
177
+ }
178
+
179
+ // readGenesis(deps) → the view, or null when the project is not founding.
180
+ async function readGenesis(deps = {}) {
181
+ const s = seams(deps);
182
+ const mode = await reconcile(s);
183
+ if (!mode) return null;
184
+ const planRow = await s.get(PLAN_KEY);
185
+ const plan = readPlanValue(planRow && planRow.value);
186
+ const tasks = s.lifecycle && typeof s.lifecycle.genesisTasks === 'function' ? await s.lifecycle.genesisTasks() : {};
187
+ const board = await boardByStage(s.government);
188
+ const stageAt = {};
189
+ for (const key of PROPER) {
190
+ const row = await s.get(fm.stageAtKey(key));
191
+ if (row && row.value) stageAt[key] = row.value;
192
+ }
193
+ return shapeGenesis({ mode, plan, tasks, board, stageAt });
194
+ }
195
+
196
+ // startFounding — a brand-new project enters genesis (the create flow's birth calls
197
+ // this). Refused once the project has come alive: genesis happens once (spec D8).
198
+ async function startFounding(builderId, deps = {}) {
199
+ const s = seams(deps);
200
+ const ended = await s.get(fm.KEYS.ENDED_AT);
201
+ if (ended && ended.value) return { ok: false, reason: 'already_alive' };
202
+ const mode = await fm.setFoundingMode({ active: true }, builderId, modeDeps(s));
203
+ return { ok: true, mode };
204
+ }
205
+
206
+ // savePlan — the decide stage's door: record the plan, file the five stages as tasks,
207
+ // and move to scope. Only while deciding.
208
+ async function savePlan(body, builderId, deps = {}) {
209
+ const s = seams(deps);
210
+ const mode = await fm.foundingModeFor(modeDeps(s));
211
+ if (!mode) return { ok: false, reason: 'not_founding' };
212
+ if (mode.stage !== 'decide') return { ok: false, reason: 'not_deciding' };
213
+ const parsed = parsePlan(body);
214
+ if (!parsed.ok) return parsed;
215
+ if (!s.lifecycle || typeof s.lifecycle.seedGenesis !== 'function') return { ok: false, reason: 'no_board' };
216
+ await s.set(PLAN_KEY, JSON.stringify(parsed.plan), builderId);
217
+ const seeded = await s.lifecycle.seedGenesis({ builderId, existing: parsed.plan.existing });
218
+ await fm.setFoundingMode({ stage: 'scope' }, builderId, modeDeps(s));
219
+ return { ok: true, plan: parsed.plan, seeded: seeded && seeded.seeded };
220
+ }
221
+
222
+ // closeStage — put the current stage to the Board Room. The finished-tasks gate is the
223
+ // government module's (it checks the ids against the board); this only names the
224
+ // stage's tasks. With no government module there is no Board Room to put it to, and
225
+ // the founder's confirm closes the stage itself once its tasks are finished.
226
+ async function closeStage(stageKey, builderId, deps = {}) {
227
+ const s = seams(deps);
228
+ const mode = await reconcile(s);
229
+ if (!mode) return { ok: false, reason: 'not_founding' };
230
+ if (mode.stage !== stageKey) return { ok: false, reason: 'not_current_stage', stage: mode.stage };
231
+ const all = s.lifecycle && typeof s.lifecycle.genesisTasks === 'function' ? await s.lifecycle.genesisTasks() : {};
232
+ const list = all[stageKey] || [];
233
+ const gov = s.government;
234
+ if (gov && typeof gov.openGenesisStageClose === 'function') {
235
+ const r = await gov.openGenesisStageClose({ stageKey, taskIds: list.map((t) => t.id), openedBy: builderId });
236
+ if (!r.opened && r.reason !== 'already_open') return { ok: false, reason: r.reason, unfinished: r.unfinished || [] };
237
+ return { ok: true, item_id: r.item ? String(r.item.id) : null, already_open: !r.opened };
238
+ }
239
+ if (!list.length || !list.every((t) => FINISHED.includes(t.status))) return { ok: false, reason: 'stage_tasks_unfinished' };
240
+ if (stageKey !== 'legislation') {
241
+ await fm.setFoundingMode({ stage: fm.STAGES[fm.STAGES.indexOf(stageKey) + 1] }, builderId, modeDeps(s));
242
+ }
243
+ return { ok: true, item_id: null, closed: true };
244
+ }
245
+
246
+ // comeAlive — the last door. Only once legislation has passed (or, with no Board Room,
247
+ // once its tasks are finished): founding mode switches off and every room opens.
248
+ async function comeAlive(builderId, deps = {}) {
249
+ const s = seams(deps);
250
+ const mode = await reconcile(s);
251
+ if (!mode) return { ok: false, reason: 'not_founding' };
252
+ if (mode.stage !== 'legislation') return { ok: false, reason: 'not_at_legislation' };
253
+ if (s.government && typeof s.government.genesisStageStatus === 'function') {
254
+ const board = await boardByStage(s.government);
255
+ if (!board.legislation || board.legislation.state !== 'passed') return { ok: false, reason: 'legislation_not_passed' };
256
+ } else {
257
+ const all = s.lifecycle && typeof s.lifecycle.genesisTasks === 'function' ? await s.lifecycle.genesisTasks() : {};
258
+ const list = all.legislation || [];
259
+ if (!list.length || !list.every((t) => FINISHED.includes(t.status))) return { ok: false, reason: 'legislation_not_passed' };
260
+ }
261
+ await fm.setFoundingMode({ active: false }, builderId, modeDeps(s));
262
+ return { ok: true };
263
+ }
264
+
265
+ // onStagePassed — the `board.genesis_stage.passed` listener. Best-effort: the next read
266
+ // reconciles anyway.
267
+ async function onStagePassed(deps = {}) {
268
+ try { await reconcile(seams(deps)); } catch (_) { /* recovered on the next read */ }
269
+ }
270
+
271
+ module.exports = {
272
+ PLAN_KEY, STAGE_INFO, PROPER, parsePlan, shapeGenesis,
273
+ readGenesis, startFounding, savePlan, closeStage, comeAlive, onStagePassed,
274
+ };
@@ -35,6 +35,7 @@ const api = require('../../../src/module-api');
35
35
  const onboardingPort = require('../port');
36
36
  const buildAccessRequestsRouter = require('./access-requests');
37
37
  const foundingBuilders = require('../founding-builders');
38
+ const genesis = require('../genesis-home');
38
39
 
39
40
  // Register the `onboarding` kernel PORT. See the header + port.js for the full
40
41
  // consumer list and the graceful-degradation contract.
@@ -61,5 +62,60 @@ module.exports = function buildOnboardingRouter() {
61
62
  res.set('Cache-Control', 'private, max-age=60');
62
63
  res.json({ builders });
63
64
  });
65
+ mountGenesisRoutes(router);
64
66
  return router;
65
67
  };
68
+
69
+ // ── the genesis home (task 1004423) ──────────────────────────────────────────
70
+ // Read by any signed-in builder of a founding project (everyone sees where genesis
71
+ // is); every write is the project owner's, behind the same atom that guards the
72
+ // project's other settings. The decision that moves a stage on is NOT here: it is the
73
+ // Board Room's vote, and these routes only put a finished stage to it.
74
+ const GENESIS_REFUSALS = {
75
+ not_founding: 409, not_deciding: 409, not_current_stage: 409, already_alive: 409,
76
+ not_at_legislation: 409, legislation_not_passed: 409, stage_tasks_unfinished: 409,
77
+ no_stage_tasks: 409, unknown_stage: 400, no_board: 503,
78
+ bad_days: 400, bad_passes: 400, bad_advisors: 400, bad_government: 400, bad_expected_builders: 400,
79
+ };
80
+ let genesisListening = false;
81
+
82
+ function mountGenesisRoutes(router) {
83
+ // A stage's sitting passed: move the project on now rather than at the next read.
84
+ // Registered once per process; the route factory runs again on a test rebuild.
85
+ if (!genesisListening && typeof api.on === 'function') {
86
+ genesisListening = true;
87
+ api.on('board.genesis_stage.passed', () => genesis.onStagePassed());
88
+ }
89
+ const owns = api.requirePermission('project.settings.manage');
90
+ const refuse = (res, r) => res.status(GENESIS_REFUSALS[r.reason] || 409).json({
91
+ error: { code: r.reason, message: r.reason }, ...(r.unfinished ? { unfinished: r.unfinished } : {}),
92
+ });
93
+ const handle = (fn) => async (req, res) => {
94
+ try {
95
+ const r = await fn(req);
96
+ if (r && r.ok === false) return refuse(res, r);
97
+ res.set('Cache-Control', 'no-store');
98
+ res.json(r);
99
+ } catch (err) {
100
+ api.logger('onboarding').error('[onboarding] genesis', err);
101
+ res.status(500).json({ error: { code: 'genesis_failed', message: 'internal error' } });
102
+ }
103
+ };
104
+ router.get('/founding/genesis', api.requireBuilder, handle(async (req) => ({ genesis: await genesis.readGenesis(), viewer_id: String(req.builder.id) })));
105
+ router.post('/founding/start', api.requireBuilder, owns, handle((req) => genesis.startFounding(req.builder.id)));
106
+ // The plan's ranges and vocabularies are genesis-home.js parsePlan's (one source);
107
+ // this schema only names the fields so an unknown one is refused.
108
+ router.post('/founding/plan', api.requireBuilder, owns, (req, res, next) => {
109
+ if (api.validateOrRespond(req, res, {
110
+ days: { type: 'object' },
111
+ passes: { type: 'integer', min: 1, max: 2 },
112
+ advisors: { type: 'string', maxLength: 16 },
113
+ government: { type: 'string', maxLength: 40 },
114
+ expected_builders: { type: 'integer', min: 1, max: 100000 },
115
+ existing: { type: 'boolean' },
116
+ })) return;
117
+ next();
118
+ }, handle((req) => genesis.savePlan(req.body || {}, req.builder.id)));
119
+ router.post('/founding/stages/:stage/close', api.requireBuilder, owns, handle((req) => genesis.closeStage(String(req.params.stage), req.builder.id)));
120
+ router.post('/founding/alive', api.requireBuilder, owns, handle((req) => genesis.comeAlive(req.builder.id)));
121
+ }
@@ -0,0 +1,108 @@
1
+ {
2
+ "_": "The genesis home while deciding (task 1004423): the plan step, every default pre-picked.",
3
+ "genesis": {
4
+ "stage": "decide",
5
+ "stage_number": 0,
6
+ "stage_count": 5,
7
+ "day": 1,
8
+ "deciding": true,
9
+ "plan": null,
10
+ "stages": [
11
+ {
12
+ "key": "scope",
13
+ "number": 1,
14
+ "label": "Scope",
15
+ "life": "primordial sea",
16
+ "plate": "r5-life-1-primordial-sea",
17
+ "done_when": "The scope is written: what you are making, for whom, and what is left out.",
18
+ "wakes": [
19
+ "roadmap",
20
+ "goals",
21
+ "goal-map"
22
+ ],
23
+ "days": 7,
24
+ "due": null,
25
+ "state": "later",
26
+ "tasks": [],
27
+ "finished": 0,
28
+ "total": 0,
29
+ "board": null
30
+ },
31
+ {
32
+ "key": "strategize",
33
+ "number": 2,
34
+ "label": "Strategize",
35
+ "life": "first cells",
36
+ "plate": "r5-life-2-first-cells",
37
+ "done_when": "The strategy is written out, or the one you have is linked.",
38
+ "wakes": [
39
+ "work"
40
+ ],
41
+ "days": 7,
42
+ "due": null,
43
+ "state": "later",
44
+ "tasks": [],
45
+ "finished": 0,
46
+ "total": 0,
47
+ "board": null
48
+ },
49
+ {
50
+ "key": "charter",
51
+ "number": 3,
52
+ "label": "Charter",
53
+ "life": "the genome",
54
+ "plate": "r5-life-3-genome",
55
+ "done_when": "The starting government is chosen and the bylaws are written.",
56
+ "wakes": [
57
+ "board-room"
58
+ ],
59
+ "days": 4,
60
+ "due": null,
61
+ "state": "later",
62
+ "tasks": [],
63
+ "finished": 0,
64
+ "total": 0,
65
+ "board": null
66
+ },
67
+ {
68
+ "key": "legalize",
69
+ "number": 4,
70
+ "label": "Legalize",
71
+ "life": "the membrane",
72
+ "plate": "r5-life-4-membrane",
73
+ "done_when": "Rewards are decided, and who can be paid.",
74
+ "wakes": [
75
+ "economy"
76
+ ],
77
+ "days": 7,
78
+ "due": null,
79
+ "state": "later",
80
+ "tasks": [],
81
+ "finished": 0,
82
+ "total": 0,
83
+ "board": null
84
+ },
85
+ {
86
+ "key": "legislation",
87
+ "number": 5,
88
+ "label": "Legislation",
89
+ "life": "the nervous system",
90
+ "plate": "r5-life-5-nervous-system",
91
+ "done_when": "The voting rules are set: who votes, and how many must agree.",
92
+ "wakes": [
93
+ "ranks"
94
+ ],
95
+ "days": 3,
96
+ "due": null,
97
+ "state": "later",
98
+ "tasks": [],
99
+ "finished": 0,
100
+ "total": 0,
101
+ "board": null
102
+ }
103
+ ],
104
+ "can_close": false,
105
+ "can_come_alive": false
106
+ },
107
+ "viewer_id": "1"
108
+ }
@@ -0,0 +1,162 @@
1
+ {
2
+ "_": "The genesis home mid-way (task 1004423): scope passed, strategize in progress with its task active.",
3
+ "genesis": {
4
+ "stage": "strategize",
5
+ "stage_number": 2,
6
+ "stage_count": 5,
7
+ "day": 9,
8
+ "deciding": false,
9
+ "plan": {
10
+ "days": {
11
+ "scope": 7,
12
+ "strategize": 7,
13
+ "charter": 4,
14
+ "legalize": 7,
15
+ "legislation": 3
16
+ },
17
+ "passes": 2,
18
+ "advisors": "none",
19
+ "government": "monarchy",
20
+ "expected_builders": 3,
21
+ "existing": false
22
+ },
23
+ "stages": [
24
+ {
25
+ "key": "scope",
26
+ "number": 1,
27
+ "label": "Scope",
28
+ "life": "primordial sea",
29
+ "plate": "r5-life-1-primordial-sea",
30
+ "done_when": "The scope is written: what you are making, for whom, and what is left out.",
31
+ "wakes": [
32
+ "roadmap",
33
+ "goals",
34
+ "goal-map"
35
+ ],
36
+ "days": 7,
37
+ "due": "2026-10-08T00:00:00.000Z",
38
+ "state": "done",
39
+ "tasks": [
40
+ {
41
+ "id": "101",
42
+ "title": "Write the project scope: what we are making, for whom, and what is out of scope",
43
+ "status": "shipped",
44
+ "finished": true
45
+ }
46
+ ],
47
+ "finished": 1,
48
+ "total": 1,
49
+ "board": {
50
+ "state": "passed",
51
+ "item_id": "7",
52
+ "needs": null,
53
+ "one_vote_decides": true,
54
+ "waiting_on": []
55
+ }
56
+ },
57
+ {
58
+ "key": "strategize",
59
+ "number": 2,
60
+ "label": "Strategize",
61
+ "life": "first cells",
62
+ "plate": "r5-life-2-first-cells",
63
+ "done_when": "The strategy is written out, or the one you have is linked.",
64
+ "wakes": [
65
+ "work"
66
+ ],
67
+ "days": 7,
68
+ "due": "2026-10-15T00:00:00.000Z",
69
+ "state": "now",
70
+ "tasks": [
71
+ {
72
+ "id": "102",
73
+ "title": "Write out the strategy, or link the one you already have",
74
+ "status": "active",
75
+ "finished": false
76
+ }
77
+ ],
78
+ "finished": 0,
79
+ "total": 1,
80
+ "board": null
81
+ },
82
+ {
83
+ "key": "charter",
84
+ "number": 3,
85
+ "label": "Charter",
86
+ "life": "the genome",
87
+ "plate": "r5-life-3-genome",
88
+ "done_when": "The starting government is chosen and the bylaws are written.",
89
+ "wakes": [
90
+ "board-room"
91
+ ],
92
+ "days": 4,
93
+ "due": null,
94
+ "state": "later",
95
+ "tasks": [
96
+ {
97
+ "id": "103",
98
+ "title": "Choose the starting government and write the bylaws",
99
+ "status": "ready",
100
+ "finished": false
101
+ }
102
+ ],
103
+ "finished": 0,
104
+ "total": 1,
105
+ "board": null
106
+ },
107
+ {
108
+ "key": "legalize",
109
+ "number": 4,
110
+ "label": "Legalize",
111
+ "life": "the membrane",
112
+ "plate": "r5-life-4-membrane",
113
+ "done_when": "Rewards are decided, and who can be paid.",
114
+ "wakes": [
115
+ "economy"
116
+ ],
117
+ "days": 7,
118
+ "due": null,
119
+ "state": "later",
120
+ "tasks": [
121
+ {
122
+ "id": "104",
123
+ "title": "Decide how contributors are rewarded and who can be paid",
124
+ "status": "ready",
125
+ "finished": false
126
+ }
127
+ ],
128
+ "finished": 0,
129
+ "total": 1,
130
+ "board": null
131
+ },
132
+ {
133
+ "key": "legislation",
134
+ "number": 5,
135
+ "label": "Legislation",
136
+ "life": "the nervous system",
137
+ "plate": "r5-life-5-nervous-system",
138
+ "done_when": "The voting rules are set: who votes, and how many must agree.",
139
+ "wakes": [
140
+ "ranks"
141
+ ],
142
+ "days": 3,
143
+ "due": null,
144
+ "state": "later",
145
+ "tasks": [
146
+ {
147
+ "id": "105",
148
+ "title": "Set the voting rules: who votes and how many must agree",
149
+ "status": "ready",
150
+ "finished": false
151
+ }
152
+ ],
153
+ "finished": 0,
154
+ "total": 1,
155
+ "board": null
156
+ }
157
+ ],
158
+ "can_close": false,
159
+ "can_come_alive": false
160
+ },
161
+ "viewer_id": "1"
162
+ }