@bongos/core 1.19.376 → 1.19.378

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.376",
6
- "core_contract": "1.19.376",
7
- "source_commit": "a431d7ef6e3018eeb8b16fbf2616b7b99ec4d5d5",
5
+ "core_version": "1.19.378",
6
+ "core_contract": "1.19.378",
7
+ "source_commit": "71bcea42aeb104d9931a45ea2fdc5b1816bead92",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-25T23:21:03.279Z",
9
+ "built_at": "2026-08-27T00:03:57.043Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 335,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1557,
14
+ "functional_verbatim": 1565,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1914,
20
- "tree_sha256": "6c7b5981caac40dce57dfecbee01587f18c3b4e5b4cb8fa65ebde70c9869ef53",
19
+ "file_count": 1922,
20
+ "tree_sha256": "0a04e3d05efdc0ba120a60c0d163afc0c7ac5075cf143d082d047fe6c56fcd16",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -367,7 +367,7 @@
367
367
  {
368
368
  "path": "clients/bongos-client/index.d.ts",
369
369
  "mode": "0000644",
370
- "sha256": "28f456a1b259b898b4bfc0d353c9877b5f10828beae478c2ee6aa1a66e64c33a"
370
+ "sha256": "e9a6dd8d60741347814d184016bb3eaae1b319ba299121e7a39a55367bc12e56"
371
371
  },
372
372
  {
373
373
  "path": "clients/bongos-client/index.mjs",
@@ -1477,12 +1477,12 @@
1477
1477
  {
1478
1478
  "path": "docs/api-reference.md",
1479
1479
  "mode": "0000644",
1480
- "sha256": "96e9749fd1a6061c3ae96edf1da81a42fc6f89144bca6844870e5abcc7aeb7fe"
1480
+ "sha256": "1d40e3666f05b4b28d7b9433e84c0ae4bfccc907660053006694814799365bf0"
1481
1481
  },
1482
1482
  {
1483
1483
  "path": "docs/api/openapi.json",
1484
1484
  "mode": "0000644",
1485
- "sha256": "a743ea7c9fbe29419a6efc5978472c8fa27da2178eef34edbc988574990733f8"
1485
+ "sha256": "57b66bbfe0c1d7aa8274a43d813c77c83986a3e4cf43046e9e0a637ac4fe308d"
1486
1486
  },
1487
1487
  {
1488
1488
  "path": "docs/architecture.md",
@@ -4077,7 +4077,7 @@
4077
4077
  {
4078
4078
  "path": "modules/lifecycle/db-tasks.js",
4079
4079
  "mode": "0000644",
4080
- "sha256": "94b785f970615056c9c3e896009868ef762fe78ab9f640889b9286ccef46fcf0"
4080
+ "sha256": "745663b0a264ae742702919fc9486f49723010dd797e1fe7151de5d65b98bf21"
4081
4081
  },
4082
4082
  {
4083
4083
  "path": "modules/lifecycle/db-versions.js",
@@ -4087,7 +4087,7 @@
4087
4087
  {
4088
4088
  "path": "modules/lifecycle/db.js",
4089
4089
  "mode": "0000644",
4090
- "sha256": "70448ebd079d5167bcf0c9c2b4473d46e996a54202f09f5a8ebc30980aba4166"
4090
+ "sha256": "b7f0d36dc13ebad0206b94879262e661e6c2524539732f31e7cec25f61fd9fd6"
4091
4091
  },
4092
4092
  {
4093
4093
  "path": "modules/lifecycle/dead-deps.js",
@@ -4154,6 +4154,16 @@
4154
4154
  "mode": "0000644",
4155
4155
  "sha256": "0027e5e8a3b718a16dcae5ad4387b4773525d429944e712228fbd38a5335e23a"
4156
4156
  },
4157
+ {
4158
+ "path": "modules/lifecycle/kickoff-checklist.js",
4159
+ "mode": "0000644",
4160
+ "sha256": "829dd13fb87033dba1db8d3003a0359e1be8bb97198df419edb2617558bfd984"
4161
+ },
4162
+ {
4163
+ "path": "modules/lifecycle/kickoff-seed.js",
4164
+ "mode": "0000644",
4165
+ "sha256": "f26a507ea55169df9f4841edb2347610f3dea9b7eac729c034c038a0edd1c7af"
4166
+ },
4157
4167
  {
4158
4168
  "path": "modules/lifecycle/lifecycle.js",
4159
4169
  "mode": "0000644",
@@ -4187,7 +4197,7 @@
4187
4197
  {
4188
4198
  "path": "modules/lifecycle/module.json",
4189
4199
  "mode": "0000644",
4190
- "sha256": "30653b4064be8933894679ac4e272711edcc629e151d87017e2b2e45be96d78f"
4200
+ "sha256": "401e4121a3f335ae0ae532b6b7ccff63e668b3a53520a0ff3f0d1a4223b02020"
4191
4201
  },
4192
4202
  {
4193
4203
  "path": "modules/lifecycle/pollers/publish-reconciler.js",
@@ -4244,6 +4254,11 @@
4244
4254
  "mode": "0000644",
4245
4255
  "sha256": "0e9430101ce99d0a84f1893d13bcf1434af13325c2ce531e693b6bce42cb9385"
4246
4256
  },
4257
+ {
4258
+ "path": "modules/lifecycle/routes/kickoff-seed.js",
4259
+ "mode": "0000644",
4260
+ "sha256": "f5090caaf0d0f32feff5bf4263e65ecf831ad6fd10ae5b053788c987e1a86399"
4261
+ },
4247
4262
  {
4248
4263
  "path": "modules/lifecycle/routes/lifecycle.js",
4249
4264
  "mode": "0000644",
@@ -4262,7 +4277,7 @@
4262
4277
  {
4263
4278
  "path": "modules/lifecycle/routes/task-write-routes.js",
4264
4279
  "mode": "0000644",
4265
- "sha256": "4ef343018b69528ab9518683ba0c6878bfcf5b9398ee7a2ade21780c20218538"
4280
+ "sha256": "10313e3a6bd619fe2cf5cd691927032914fcb25d52ba743e8ae16d51d8d97d5c"
4266
4281
  },
4267
4282
  {
4268
4283
  "path": "modules/lifecycle/routes/tasks.js",
@@ -4927,12 +4942,12 @@
4927
4942
  {
4928
4943
  "path": "package-lock.json",
4929
4944
  "mode": "0000644",
4930
- "sha256": "0cdd9747593cc2fb83327041cf2d869234271cf7e3dcc7ecfb626c43ed262155"
4945
+ "sha256": "f96269625487590e9dc70bff805db37a0f00592178fc6614f6c82b4610ec3c0d"
4931
4946
  },
4932
4947
  {
4933
4948
  "path": "package.json",
4934
4949
  "mode": "0000644",
4935
- "sha256": "00ff5e9289bc25cc0e6d1a7226259b52b906eaf887c9dd417067d41193877d7c"
4950
+ "sha256": "40e93caa20eab8a0602a3fe4a1073e63aca106888f5afcbe28627200e17d84f6"
4936
4951
  },
4937
4952
  {
4938
4953
  "path": "public-docs/index.html",
@@ -5627,7 +5642,7 @@
5627
5642
  {
5628
5643
  "path": "scripts/gds/fitness.js",
5629
5644
  "mode": "0000644",
5630
- "sha256": "95f116e70ba6d6e890705ff862a5c5011002c42a58ba60553c50ec2d656dcf3b"
5645
+ "sha256": "4dec7f6bee219ca3c11dbf5b9c3380de286264a66830fd8e2d37018a989eb83e"
5631
5646
  },
5632
5647
  {
5633
5648
  "path": "scripts/gds/gate-review.js",
@@ -5747,7 +5762,7 @@
5747
5762
  {
5748
5763
  "path": "scripts/gds/init.js",
5749
5764
  "mode": "0000644",
5750
- "sha256": "bde86f3f65cdd4a1ad0e143c58f6517328dd7cdee7c1194c0fa614aaf9df56f6"
5765
+ "sha256": "280197b62032374c11824c4f97ec713725e28e2b57cfd6c8aa42d493b02c4ed7"
5751
5766
  },
5752
5767
  {
5753
5768
  "path": "scripts/gds/install-git-hooks.js",
@@ -6399,6 +6414,11 @@
6399
6414
  "mode": "0000644",
6400
6415
  "sha256": "88b9b721b3911957b13aa5e8023e6d41534e34afd4bbc70f40435efc52e2017b"
6401
6416
  },
6417
+ {
6418
+ "path": "scripts/gds/test-path-guard.js",
6419
+ "mode": "0000644",
6420
+ "sha256": "d3e542cf977f9aadad8f856defa6d809f3604e184cd261fbee7738aa1da736ea"
6421
+ },
6402
6422
  {
6403
6423
  "path": "scripts/gds/triage.js",
6404
6424
  "mode": "0000644",
@@ -6542,7 +6562,7 @@
6542
6562
  {
6543
6563
  "path": "src/bongos/auth-admission.js",
6544
6564
  "mode": "0000644",
6545
- "sha256": "0d2e4aaf4b99ac4b1122c182f279ae622f53d8896baa31d796c5bb0f4ef6532c"
6565
+ "sha256": "9978b10e2e675b6f7c1211dd74c8cf302395b2f47062eba5df376203ee77d341"
6546
6566
  },
6547
6567
  {
6548
6568
  "path": "src/bongos/auth-config.js",
@@ -6817,7 +6837,7 @@
6817
6837
  {
6818
6838
  "path": "src/module-api.js",
6819
6839
  "mode": "0000644",
6820
- "sha256": "94c29ffd7acffe072f72060e6bf7deb322031a3521e935f77f4069cf7da1a699"
6840
+ "sha256": "f291431298b7e0b6369f71f944d87c5fcf67a651da063a6a7af0f4935d99e33a"
6821
6841
  },
6822
6842
  {
6823
6843
  "path": "src/module-loader/catalog.js",
@@ -7694,6 +7714,11 @@
7694
7714
  "mode": "0000644",
7695
7715
  "sha256": "5466ddf0b7e0298206d46da7b84cfd3826e0f549395088bf5ed91397d15f1746"
7696
7716
  },
7717
+ {
7718
+ "path": "tests/first_admin_kickoff_seed.mjs",
7719
+ "mode": "0000644",
7720
+ "sha256": "8006f4696e6bae21b84c9e8ec25cb7a7d34c5899f187d2ac59b2a6dcbb6f92d1"
7721
+ },
7697
7722
  {
7698
7723
  "path": "tests/first_run_engine_check.mjs",
7699
7724
  "mode": "0000644",
@@ -8374,6 +8399,16 @@
8374
8399
  "mode": "0000644",
8375
8400
  "sha256": "db9ad86df4c9593db89f91ef2306c30eb33ae56bebe5b210a4b3a1912f67d42c"
8376
8401
  },
8402
+ {
8403
+ "path": "tests/kickoff_checklist.mjs",
8404
+ "mode": "0000644",
8405
+ "sha256": "d0849053675cb4705fe6774b1a8ac6b3edb5b43a758775446fad257be6cb9ba3"
8406
+ },
8407
+ {
8408
+ "path": "tests/kickoff_seed.mjs",
8409
+ "mode": "0000644",
8410
+ "sha256": "416d413757a13fbddb5841ea8a2c503077d7e6067bc1c58ce1f4d416b8d8487f"
8411
+ },
8377
8412
  {
8378
8413
  "path": "tests/known_failing_ratchet.mjs",
8379
8414
  "mode": "0000644",
@@ -8497,7 +8532,7 @@
8497
8532
  {
8498
8533
  "path": "tests/migration_allocation.mjs",
8499
8534
  "mode": "0000644",
8500
- "sha256": "7b0e40f4b3e348a45ccdc543dd1f3dfd78113ae86348823001e4eabb78f126de"
8535
+ "sha256": "e2f03570b76b4d5af7af3c87a2a4989827eb71d1b50c92ab546a456e85c4024e"
8501
8536
  },
8502
8537
  {
8503
8538
  "path": "tests/migration_namespace.mjs",
@@ -8832,7 +8867,7 @@
8832
8867
  {
8833
8868
  "path": "tests/provisioning_onboard_mode.mjs",
8834
8869
  "mode": "0000644",
8835
- "sha256": "7749df454ac3cca7c7e5ea1acc94ead15d1c9f87b38fe2028a1c93c8adf31bcf"
8870
+ "sha256": "97d04ee39cc06b720d4a5b42f3e3cc614f64d9462b27471a1af7598b5e2929be"
8836
8871
  },
8837
8872
  {
8838
8873
  "path": "tests/provisioning_settings.mjs",
@@ -9057,7 +9092,7 @@
9057
9092
  {
9058
9093
  "path": "tests/search_ingest.mjs",
9059
9094
  "mode": "0000644",
9060
- "sha256": "504a855803d88468667663f663656dfd8e25726b90a412e34bcf55da0ad57d5e"
9095
+ "sha256": "8d685a46ab9f4cf9aaa71b9ee78774d95c16daa27c15a229b8628a97688b9a55"
9061
9096
  },
9062
9097
  {
9063
9098
  "path": "tests/search_ingest_db.mjs",
@@ -9192,7 +9227,7 @@
9192
9227
  {
9193
9228
  "path": "tests/setup_smoke.mjs",
9194
9229
  "mode": "0000644",
9195
- "sha256": "919541c9444b7dc60f33a1e56b2f7defaf07ea1da5fb4b0c9760ca1bb9e7f1e0"
9230
+ "sha256": "c55f6c506758d973b7cbe750a873e8c38d02006e1495f027fedb0ebfc84c7186"
9196
9231
  },
9197
9232
  {
9198
9233
  "path": "tests/ship_api_client_pathspec.mjs",
@@ -9459,6 +9494,11 @@
9459
9494
  "mode": "0000644",
9460
9495
  "sha256": "3ff18b286a68b244a272b3f2606ae33b298622347aae97305152f235ca57b330"
9461
9496
  },
9497
+ {
9498
+ "path": "tests/test_path_guard.mjs",
9499
+ "mode": "0000644",
9500
+ "sha256": "bbb0b063bffc6e394114bc4895da5006d8797a0317ed38f1c7f9b989841d5eab"
9501
+ },
9462
9502
  {
9463
9503
  "path": "tests/tool-scan-fixtures/README.md",
9464
9504
  "mode": "0000644",
@@ -163,7 +163,7 @@ export interface PatchProvisioningInstancesIdSettingsRequest { platform_visibili
163
163
  export interface PatchProvisioningInstancesIdSettingsResponse { ok: boolean; saved: unknown; push: unknown }
164
164
  export interface PatchTaskRecommendationsIdRequest { status: "declined" | "withdrawn" }
165
165
  export interface PatchTaskRecommendationsIdResponse { ok: boolean; recommendation: unknown }
166
- export interface PatchTasksIdRequest { kind?: string; discipline?: string; requires_rank?: string; module_key?: string; goal_id?: number; parent_task_id?: StringifiableId; parallel_safe?: unknown; newcomer_friendly?: boolean; xenos_claimable?: boolean; security_sensitive?: boolean; automation_tag?: string; needs_migration?: boolean; description?: string; status?: unknown }
166
+ export interface PatchTasksIdRequest { kind?: string; discipline?: string; requires_rank?: string; module_key?: string; goal_id?: number; parent_task_id?: StringifiableId; parallel_safe?: unknown; newcomer_friendly?: boolean; xenos_claimable?: boolean; security_sensitive?: boolean; automation_tag?: string; needs_migration?: boolean; description?: string; title?: string; status?: unknown }
167
167
  export interface PostAccessRequestsInviteRequest { github_login: string }
168
168
  export interface PostAccessRequestsInviteResponse { ok: boolean; already_invited?: boolean; github_login?: unknown; request?: unknown }
169
169
  export interface PostAccessRequestsRequest { github_login: string; display_name?: string; note?: string }
@@ -16737,6 +16737,11 @@
16737
16737
  "minLength": 1,
16738
16738
  "maxLength": 50000
16739
16739
  },
16740
+ "title": {
16741
+ "type": "string",
16742
+ "minLength": 1,
16743
+ "maxLength": 200
16744
+ },
16740
16745
  "status": {}
16741
16746
  },
16742
16747
  "additionalProperties": false
@@ -542,7 +542,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
542
542
  | GET | `/api/bongos/tasks` | `any-builder` | — | |
543
543
  | POST | `/api/bongos/tasks` | `metic+archon` | `version_id`, `title`, `description`, `status`, `touches`, `est_minutes`, `est_cost_usd`, `manual_degree`, `priority`, `automation_tag`, `credits_reward`, `source`, `source_ref`, `value_summary`, `kind`, `discipline`, `parallel_safe`, `newcomer_friendly`, `security_sensitive`, `requires_rank`, `auto_detect_touches`, `criterion_ids`, `module_key`, `goal_id` | Task create/promote/edit (plan/scope mutation) is Metic+ as of ADR 0090 (owner decision 2026-06-26): a Metic who can run idea-triage may … |
544
544
  | GET | `/api/bongos/tasks/:id` | `any-builder` | — | single-task read; preserved per CRITICAL §4 (supervisor hot path). |
545
- | PATCH | `/api/bongos/tasks/:id` | `metic+archon` | `kind`, `discipline`, `requires_rank`, `module_key`, `goal_id`, `parent_task_id`, `parallel_safe`, `newcomer_friendly`, `xenos_claimable`, `security_sensitive`, `automation_tag`, `needs_migration`, `description`, `status` | PATCH /tasks/:id — supports body.parent_task_id (V2 C2), body.kind (V2 B1), body.discipline (mig 014), body.parallel_safe (mig 028), and … |
545
+ | PATCH | `/api/bongos/tasks/:id` | `metic+archon` | `kind`, `discipline`, `requires_rank`, `module_key`, `goal_id`, `parent_task_id`, `parallel_safe`, `newcomer_friendly`, `xenos_claimable`, `security_sensitive`, `automation_tag`, `needs_migration`, `description`, `title`, `status` | PATCH /tasks/:id — supports body.parent_task_id (V2 C2), body.kind (V2 B1), body.discipline (mig 014), body.parallel_safe (mig 028), and … |
546
546
  | POST | `/api/bongos/tasks/:id/abandon` | `metic+archon` | `reason` | POST /tasks/:id/abandon — terminal "retire this task" transition (task 987). |
547
547
  | POST | `/api/bongos/tasks/:id/attest-gate` | `any-builder` | `head_sha` | POST /tasks/:id/attest-gate — body: { head_sha }. |
548
548
  | POST | `/api/bongos/tasks/:id/confirm` | `any-builder` | `verified` | |
@@ -203,7 +203,7 @@ async function findTaskBySourceRef({ source, sourceRef } = {}, deps = {}) {
203
203
 
204
204
  const TASK_SETTER_COLUMNS = new Set([
205
205
  'kind', 'discipline', 'newcomer_friendly', 'security_sensitive', 'parallel_safe', 'automation_tag',
206
- 'needs_migration', 'module_key', 'description',
206
+ 'needs_migration', 'module_key', 'description', 'title',
207
207
  ]);
208
208
 
209
209
  async function updateTaskColumn(id, column, value) {
@@ -264,6 +264,12 @@ async function updateTaskKind(id, kind) { return updateTaskColumn(id, 'kind', ki
264
264
  // write-audit middleware captures each edit's new body in audit_log.
265
265
  async function updateTaskDescription(id, description) { return updateTaskColumn(id, 'description', description); }
266
266
 
267
+ // task 1002792: the title is the MORE visible half of that same prompt — what
268
+ // /builder-start lists and the hall shows — and was write-once at every rank,
269
+ // so a wrong title (two seeded briefs naming tables that hard-fail CI, task
270
+ // 1002791) was permanent. Same permission, same audit trail as the body.
271
+ async function updateTaskTitle(id, title) { return updateTaskColumn(id, 'title', title); }
272
+
267
273
 
268
274
  async function updateTaskDiscipline(id, discipline) { return updateTaskColumn(id, 'discipline', discipline); }
269
275
 
@@ -853,6 +859,7 @@ module.exports = {
853
859
  taskTouchesProtectedPath,
854
860
  updateTaskAutomationTag,
855
861
  updateTaskDescription,
862
+ updateTaskTitle,
856
863
  updateTaskDiscipline,
857
864
  updateTaskGoal,
858
865
  updateTaskKind,
@@ -112,6 +112,7 @@ const {
112
112
  taskTouchesProtectedPath,
113
113
  updateTaskAutomationTag,
114
114
  updateTaskDescription,
115
+ updateTaskTitle,
115
116
  updateTaskDiscipline,
116
117
  updateTaskGoal,
117
118
  updateTaskKind,
@@ -244,6 +245,7 @@ module.exports = {
244
245
  createCriterion,
245
246
  updateTaskKind,
246
247
  updateTaskDescription,
248
+ updateTaskTitle,
247
249
  updateTaskDiscipline,
248
250
  updateTaskNewcomerFriendly,
249
251
  updateTaskRequiresRank,
@@ -0,0 +1,138 @@
1
+ // modules/lifecycle/kickoff-checklist.js — the kickoff checklist: the launch steps a fresh
2
+ // instance needs that the platform CANNOT do for its owner, as task specs (task 1003239).
3
+ //
4
+ // ONE list, two filers. `bongos init` files it at scaffold time on the self-host path
5
+ // (scripts/gds/init.js kickoffTasks); the lifecycle module files it at the founder's
6
+ // FIRST SIGN-IN on the control-plane path (modules/lifecycle/kickoff-seed.js), where
7
+ // the scaffold ran `init --no-seed` and no Archon existed to file anything — walk
8
+ // finding W4: the founder reached an empty board. Moved here from init.js so both
9
+ // filers read the same steps and the copy cannot drift. It lives with the lifecycle
10
+ // module because tasks are its domain and the kernel doorway must not grow a domain
11
+ // import (ADR 0091 §1); init is a core CLI and, like abandon.js or audit-deps.js,
12
+ // may require module code directly. Pure — imports nothing.
13
+ //
14
+ // Each step becomes a real, claimable task so it is tracked, never silently skipped
15
+ // (grounded in the onboarding/deploy surfaces; the task 1204 mapping pass). Per-module
16
+ // prerequisites are appended only for the modules the owner enabled.
17
+
18
+ 'use strict';
19
+
20
+ // The one kickoff step the provisioning module AUTOMATES (ADR 0111 §3): standing up
21
+ // production hosting/TLS/DNS/managed-DB. Kept as a named builder so init can either
22
+ // FILE it (no provisioning requested, or the request couldn't be enqueued) or OMIT
23
+ // it (a request went in — the control-plane runner covers this leg).
24
+ function productionTopologyKickoff() {
25
+ return {
26
+ title: 'Production topology: TLS, real origins, managed Postgres',
27
+ kind: 'infra',
28
+ description: 'For a real (non-localhost) launch: front the instance with TLS (Caddy/Cloudflare/nginx), point ' +
29
+ 'publicOrigin/buildersOrigin/statusOrigin at the real domain (the OAuth callback depends on this), and move off the bundled container DB volume to managed/backed-up Postgres.',
30
+ };
31
+ }
32
+
33
+ // The admission step as the SELF-HOST owner meets it: an env flag on their own box.
34
+ const ADMISSION_SELF_HOST = {
35
+ title: 'Set the admission posture and invite the first builders',
36
+ kind: 'infra',
37
+ description: 'Enrollment is invite-only by default. Beyond you, no one can join until you approve an access request ' +
38
+ 'or set <ENVPREFIX>_OPEN_XENOS_ENROLLMENT=1. Decide who can join and invite/approve the first builders.',
39
+ };
40
+
41
+ // The same decision as a HUB-PROVISIONED founder meets it: the door is the project's
42
+ // "How people join" setting on the hub (task 1003044), never an env var they cannot
43
+ // reach; invites are the Watch page's "Invite by GitHub username". Names only surfaces
44
+ // that exist — a blank board is better than untrue copy (task 1003239).
45
+ const ADMISSION_AT_FIRST_SIGN_IN = {
46
+ title: 'Decide how people join, and invite the first builders',
47
+ kind: 'infra',
48
+ description: 'Unless you opened the door when you created the project, nobody beyond you can join until you invite them. ' +
49
+ 'Set "How people join" (open / apply to join / invite-only) from your project\'s manage page on the hub, ' +
50
+ 'and invite the first builders by GitHub username from your project\'s Watch page.',
51
+ };
52
+
53
+ const MERGE_GATE = {
54
+ title: 'Arm the merge gate before a second builder joins (ci deploy mode)',
55
+ kind: 'infra',
56
+ description: 'For a shared instance on ci deploy mode, enable branch protection on main + required checks ' +
57
+ '(unit / secret-scan / gate-review), so untrusted builders cannot push straight to production. No review requirement — gates are CI checks, not human approvals (ADR 0162). Skippable for a solo/local instance.',
58
+ };
59
+
60
+ const INSTANCE_LOOK = {
61
+ title: 'Fill the instance look with your own LLM',
62
+ // 'ideator' is a DISCIPLINE, not a kind — tasks_kind_check rejects it as a
63
+ // kind and the task silently vanished from the checklist (the one non-infra
64
+ // kickoff step). Deciding the instance look is a decision by an ideator.
65
+ kind: 'decision',
66
+ discipline: 'ideator',
67
+ description: 'init wrote a lean config/branding.json and left the LOOK to you (a locked project rule: brand with your ' +
68
+ 'own LLM, not a generator). Hand docs/branding-fill-prompt.md to your AI to design a palette/fonts/wordmark, or keep the vanilla look.',
69
+ };
70
+
71
+ // Steps that are already TRUE the moment a hub-provisioned founder signs in: auth
72
+ // works (they just used it), the sign-in is this one, and the scaffold bound the repo
73
+ // and chose the deploy mode. Filed on the self-host path only.
74
+ function selfHostOnlySteps(spec, origin) {
75
+ return [
76
+ {
77
+ title: 'Register a GitHub OAuth app and set its credentials',
78
+ kind: 'infra',
79
+ description: 'Auth is the hard prerequisite for every sign-in — without it the server returns 503 ' +
80
+ 'auth_not_configured and nobody (including you) can log in. Create a GitHub OAuth app, set ' +
81
+ `<ENVPREFIX>_GITHUB_CLIENT_ID/SECRET, tick "Enable Device Flow" (a separate save), and set the callback URL to ${origin}/api/gds/auth/web/callback.`,
82
+ },
83
+ {
84
+ title: `Sign in once as ${spec.firstAdmin} to seat the founding owner`,
85
+ kind: 'infra',
86
+ description: `A fresh database has no owner. Signing in as the configured firstAdmin (${spec.firstAdmin}) ` +
87
+ 'the first time auto-seats that account as the founding Archon (owner). Config alone does nothing — you must actually sign in.',
88
+ },
89
+ {
90
+ title: 'Confirm the GitHub repo binding and deploy mode',
91
+ kind: 'infra',
92
+ description: `This instance ships work to ${spec.repo.owner}/${spec.repo.name}. Confirm the git remote / repo binding ` +
93
+ 'and pick deploy mode in config/deploy.json (ci = server-mediated PR auto-merge, or laptop). A wrong/absent repo binding lets a task grade but never merge.',
94
+ },
95
+ ];
96
+ }
97
+
98
+ // Per-module prerequisites — a key the module cannot run without.
99
+ function moduleKeySteps(spec) {
100
+ const enabled = new Set(Array.isArray(spec.modules) ? spec.modules : []);
101
+ const tasks = [];
102
+ if (enabled.has('art-pipeline')) {
103
+ tasks.push({ title: 'Provide a Google Gemini API key for the art pipeline', kind: 'infra',
104
+ description: 'The art-pipeline module needs a Google AI Studio (Gemini) key (GOOGLE_AI_STUDIO_KEY, a shared key, or per-builder keys) or it cannot generate art.' });
105
+ }
106
+ if (enabled.has('discord')) {
107
+ tasks.push({ title: 'Provide Discord bot token + webhooks', kind: 'infra',
108
+ description: 'The discord module needs a bot token + webhook URLs + DISCORD_BOT_ENABLED=true. Note: outbound/link surfaces fire on credentials even if the module flag is later turned off.' });
109
+ }
110
+ if (enabled.has('dev-box')) {
111
+ tasks.push({ title: 'Provide a DigitalOcean API token for dev boxes', kind: 'infra',
112
+ description: 'The dev-box module needs a DigitalOcean API token (and optionally Cloudflare) on the operator control plane before any builder box can be provisioned.' });
113
+ }
114
+ return tasks;
115
+ }
116
+
117
+ // kickoffChecklist(spec, opts) → task specs ({ title, kind, description[, discipline] }).
118
+ // provisioned — the owner opted into provisioning at init: the control-plane runner
119
+ // covers production topology, so that step is omitted (ADR 0111 §3).
120
+ // firstSignIn — filed the moment the founder of a hub-provisioned project is seated:
121
+ // only the steps still true after the standup, with the admission door
122
+ // described as the hub setting it now is.
123
+ // Pure — no I/O; the caller files them.
124
+ function kickoffChecklist(spec = {}, { provisioned = false, firstSignIn = false } = {}) {
125
+ const origin = (spec.domains && spec.domains.publicOrigin) || 'http://localhost:3000';
126
+ const tasks = firstSignIn ? [] : selfHostOnlySteps(spec, origin);
127
+ tasks.push(firstSignIn ? ADMISSION_AT_FIRST_SIGN_IN : ADMISSION_SELF_HOST);
128
+ tasks.push(MERGE_GATE);
129
+ // Production topology is either AUTOMATED (the owner opted into provisioning — the
130
+ // control-plane runner stands up TLS/DNS/managed-DB) or a tracked manual step.
131
+ // Keep it in its infra slot, before the look step, when it's still manual.
132
+ if (!provisioned && !firstSignIn) tasks.push(productionTopologyKickoff());
133
+ tasks.push(INSTANCE_LOOK);
134
+ tasks.push(...moduleKeySteps(spec));
135
+ return tasks.map((t) => ({ ...t }));
136
+ }
137
+
138
+ module.exports = { kickoffChecklist, productionTopologyKickoff };
@@ -0,0 +1,69 @@
1
+ // modules/lifecycle/kickoff-seed.js — file the kickoff checklist at the founder's first
2
+ // sign-in (task 1003239; behind the `lifecycle.kickoffSeed` port).
3
+ //
4
+ // WHY HERE, WHY THEN. `bongos init` files the kickoff checklist as tasks — but the
5
+ // control-plane scaffold runs `init --no-seed` (scripts/gds/provision-repo.js), and a
6
+ // hub-provisioned project has no Archon until its founder signs in, so nothing could
7
+ // be filed earlier: the founder reached an EMPTY board (walk finding W4). The kernel's
8
+ // first-admin seating (src/bongos/auth-admission.js) resolves this port the moment the
9
+ // owner is seated — the first moment the checklist CAN be filed.
10
+ //
11
+ // IDEMPOTENT BY EMPTINESS, not by path. It seeds only when the tasks table is empty:
12
+ // the self-host path already filed its checklist before anyone signed in, so it is
13
+ // skipped there with no "was I provisioned" marker to keep true. The caller treats a
14
+ // failure as best-effort — a seed hiccup must never fail the sign-in that just seated
15
+ // the owner.
16
+ //
17
+ // THE BOUNDARY (ADR 0093 §1): core is reached only through ../../src/module-api.js.
18
+
19
+ 'use strict';
20
+
21
+ const api = require('../../src/module-api');
22
+ const db = require('./db');
23
+ const { kickoffChecklist } = require('./kickoff-checklist');
24
+
25
+ // The spec the checklist reads, from the instance's own branding pack. `modules` is
26
+ // empty on purpose: a hub-provisioned project is scaffolded with every optional module
27
+ // off, and its founder cannot have enabled one before their first sign-in.
28
+ function specFromBranding(b = api.branding()) {
29
+ return {
30
+ firstAdmin: b.firstAdmin,
31
+ repo: b.repo || {},
32
+ domains: { publicOrigin: b.domains && b.domains.publicOrigin },
33
+ modules: [],
34
+ };
35
+ }
36
+
37
+ // The version the checklist lands under: the building one, else the first by id. The
38
+ // scaffold seeds exactly one (provision-repo.js seedFirstVersionCmd).
39
+ const FIRST_VERSION_SQL = `SELECT id FROM versions ORDER BY (status = 'building') DESC, id ASC LIMIT 1`;
40
+
41
+ // seedKickoffIfEmpty({ builderId }, deps) → { seeded, versionId } | { seeded: 0, skipped }.
42
+ // deps.pool / deps.createTask / deps.spec are test seams; production reads the doorway.
43
+ async function seedKickoffIfEmpty({ builderId = null } = {}, deps = {}) {
44
+ const pool = deps.pool || api.pool;
45
+ const insert = deps.createTask || ((row) => db.createTask(row));
46
+ const { rows: [{ n }] } = await pool.query('SELECT count(*)::int AS n FROM tasks');
47
+ if (n > 0) return { seeded: 0, skipped: 'tasks_exist' };
48
+ const { rows: versions } = await pool.query(FIRST_VERSION_SQL);
49
+ if (!versions.length) return { seeded: 0, skipped: 'no_version' };
50
+ const versionId = versions[0].id;
51
+ const steps = kickoffChecklist(deps.spec || specFromBranding(), { firstSignIn: true });
52
+ let seeded = 0;
53
+ for (const t of steps) {
54
+ await insert({
55
+ versionId,
56
+ title: t.title,
57
+ description: t.description || '',
58
+ status: 'ready',
59
+ kind: t.kind || 'infra',
60
+ discipline: t.discipline || 'engineer',
61
+ source: 'first-sign-in',
62
+ createdBy: builderId,
63
+ });
64
+ seeded += 1;
65
+ }
66
+ return { seeded, versionId };
67
+ }
68
+
69
+ module.exports = { seedKickoffIfEmpty, specFromBranding, FIRST_VERSION_SQL };
@@ -21,14 +21,16 @@
21
21
  "help-requests",
22
22
  "recommendations",
23
23
  "closeness",
24
- "visuals"
24
+ "visuals",
25
+ "kickoff-seed"
25
26
  ],
26
27
  "pollers": [
27
28
  "publish-reconciler"
28
29
  ]
29
30
  },
30
31
  "provides": [
31
- "lifecycle"
32
+ "lifecycle",
33
+ "lifecycle.kickoffSeed"
32
34
  ],
33
35
  "consumes": []
34
36
  }
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ // modules/lifecycle/routes/kickoff-seed.js — registers the `lifecycle.kickoffSeed` port
4
+ // (task 1003239): the founder's first board, filed the moment the kernel seats them.
5
+ //
6
+ // Resolved by ONE caller — the first-admin bootstrap in src/bongos/auth-admission.js —
7
+ // through resolveOptional, so an instance without this module signs in exactly as
8
+ // before. A route factory for a routeless capability, for the reason routes/lifecycle.js
9
+ // and routes/rank-sync.js give: mountModuleRoutes is the boot hook that runs in the live
10
+ // server AND whenever a test rebuilds the router. The router stays EMPTY — seeding has
11
+ // no HTTP surface, and an endpoint here would let anyone re-file a founder's checklist.
12
+
13
+ const express = require('express');
14
+ const api = require('../../../src/module-api');
15
+ const seed = require('../kickoff-seed');
16
+
17
+ // Delegates at call time (never spreads) so a stubbed seed still bites.
18
+ const kickoffSeedPort = {
19
+ seedIfEmpty: (...a) => seed.seedKickoffIfEmpty(...a),
20
+ };
21
+
22
+ module.exports = function buildLifecycleKickoffSeedRouter() {
23
+ if (!api.hasProvider('lifecycle.kickoffSeed')) {
24
+ api.registerProvider('lifecycle.kickoffSeed', kickoffSeedPort);
25
+ }
26
+ return express.Router();
27
+ };
28
+
29
+ module.exports.kickoffSeedPort = kickoffSeedPort;
@@ -285,6 +285,11 @@ module.exports = function registerTaskWriteRoutes(router) {
285
285
  // can be rewritten, never blanked (and null is refused by the type check).
286
286
  // Each edit's new body lands in audit_log via the write-audit middleware.
287
287
  description: { type: 'string', minLength: 1, maxLength: LIMITS.DESCRIPTION },
288
+ // task 1002792: the title — the more visible half of the same prompt — was
289
+ // write-once at every rank, so a wrong one was permanent (task 1002791's two
290
+ // briefs naming tables that hard-fail CI). Same shape as create, same
291
+ // task.edit permission, same audit_log trail as a description edit.
292
+ title: { type: 'string', minLength: 1, maxLength: LIMITS.TITLE },
288
293
  // task 1002614 (map D-18): DECLARED so strict-mode admits the request far
289
294
  // enough to reach the deliberate status_not_patchable hint below (#514) —
290
295
  // the hint had been unreachable dead code behind unknown_field since the
@@ -311,6 +316,7 @@ module.exports = function registerTaskWriteRoutes(router) {
311
316
  const hasModuleKey = Object.prototype.hasOwnProperty.call(body, 'module_key');
312
317
  const hasGoalId = Object.prototype.hasOwnProperty.call(body, 'goal_id');
313
318
  const hasDescription = Object.prototype.hasOwnProperty.call(body, 'description');
319
+ const hasTitle = Object.prototype.hasOwnProperty.call(body, 'title');
314
320
  // #514: status is a lifecycle field, not a PATCH-able column — it moves only
315
321
  // through the dedicated transition routes. The #506 session tried
316
322
  // `PATCH status=ready`, got the generic no_supported_fields error, and had
@@ -322,8 +328,8 @@ module.exports = function registerTaskWriteRoutes(router) {
322
328
  if (Object.prototype.hasOwnProperty.call(body, 'status')) {
323
329
  return res.fail('status_not_patchable', { status: 400, message: STATUS_HINT });
324
330
  }
325
- if (!hasParent && !hasKind && !hasDiscipline && !hasNewcomerOrXenos && !hasParallelSafe && !hasSecuritySensitive && !hasAutomationTag && !hasNeedsMigration && !hasRequiresRank && !hasModuleKey && !hasGoalId && !hasDescription) {
326
- return res.fail('no_supported_fields', { status: 400, message: 'PATCH /tasks/:id supports parent_task_id, kind, discipline, description, parallel_safe, security_sensitive, automation_tag, needs_migration, requires_rank, module_key, goal_id, and newcomer_friendly (alias: xenos_claimable). ' + STATUS_HINT });
331
+ if (!hasParent && !hasKind && !hasDiscipline && !hasNewcomerOrXenos && !hasParallelSafe && !hasSecuritySensitive && !hasAutomationTag && !hasNeedsMigration && !hasRequiresRank && !hasModuleKey && !hasGoalId && !hasDescription && !hasTitle) {
332
+ return res.fail('no_supported_fields', { status: 400, message: 'PATCH /tasks/:id supports parent_task_id, kind, discipline, title, description, parallel_safe, security_sensitive, automation_tag, needs_migration, requires_rank, module_key, goal_id, and newcomer_friendly (alias: xenos_claimable). ' + STATUS_HINT });
327
333
  }
328
334
  // ---- task 1002609 (G-22): settle EVERY gate and validation before the ----
329
335
  // ---- FIRST write. The previous shape validated-and-wrote per field in ----
@@ -478,6 +484,15 @@ module.exports = function registerTaskWriteRoutes(router) {
478
484
  return res.fail('patch_failed', { status: 500, message: 'internal error' });
479
485
  }
480
486
  }
487
+ if (hasTitle) {
488
+ try {
489
+ updated = await db.updateTaskTitle(id, body.title.trim());
490
+ if (!updated) return res.fail('task_not_found', 404);
491
+ } catch (err) {
492
+ log.error('[gds] PATCH /tasks/:id title', err);
493
+ return res.fail('patch_failed', { status: 500, message: 'internal error' });
494
+ }
495
+ }
481
496
  if (hasGoalId) {
482
497
  try {
483
498
  updated = await db.updateTaskGoal(id, body.goal_id);