@bongos/core 1.19.551 → 1.19.553

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.551",
6
- "core_contract": "1.19.551",
7
- "source_commit": "de0b6c99753fd502f2356bcfb6bf338227445cc7",
5
+ "core_version": "1.19.553",
6
+ "core_contract": "1.19.553",
7
+ "source_commit": "0037c66da0cfa4bf64edadc01329b5b053aa50ad",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-04T20:39:18.276Z",
9
+ "built_at": "2026-09-06T16:03:40.475Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 442,
13
13
  "agent_docs_stubbed": 24,
14
- "functional_verbatim": 2018,
14
+ "functional_verbatim": 2019,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2484,
20
- "tree_sha256": "38fea0008aa898d10d6e7388c30a7d422be81dcde8d201172848e551d72d361b",
19
+ "file_count": 2485,
20
+ "tree_sha256": "5394d408102a1217b87c8481579b5f9ee59eec97a9adac6f841254af6495e52a",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -2652,7 +2652,7 @@
2652
2652
  {
2653
2653
  "path": "docs/module-api-changelog.md",
2654
2654
  "mode": "0000644",
2655
- "sha256": "1389ff20919a19aef2fbff28102f596f0ca168c34bc4c4114cf557bbf52627a5"
2655
+ "sha256": "ccbe2f9174064241f3e14dc6d1e816d6ebaae4f982497ff4efe3493e6800846c"
2656
2656
  },
2657
2657
  {
2658
2658
  "path": "docs/modules-contract.md",
@@ -4607,7 +4607,7 @@
4607
4607
  {
4608
4608
  "path": "modules/hall-ui/public/builders.js",
4609
4609
  "mode": "0000644",
4610
- "sha256": "b9cf0c5dc9e0df09eb16a8c14b3d50d9d8ebcf899da5c7b10318f81fe5615d45"
4610
+ "sha256": "876c4f21b3aaa77fad298915afe7cc481a39b5235e846f61bcc7657a270dd227"
4611
4611
  },
4612
4612
  {
4613
4613
  "path": "modules/hall-ui/public/chime.wav",
@@ -4822,7 +4822,7 @@
4822
4822
  {
4823
4823
  "path": "modules/hall-ui/public/hall-render.js",
4824
4824
  "mode": "0000644",
4825
- "sha256": "f8025b961adedc8a02d09626a96e0db3d41a13656fcb862ab9e23f654c1b217e"
4825
+ "sha256": "df47881aa19503356bc54c04ffca1167effb68f43b5c442e909074744f9af63c"
4826
4826
  },
4827
4827
  {
4828
4828
  "path": "modules/hall-ui/public/hall-widgets.js",
@@ -4862,7 +4862,7 @@
4862
4862
  {
4863
4863
  "path": "modules/hall-ui/public/idea.js",
4864
4864
  "mode": "0000644",
4865
- "sha256": "73f2d9a4f826f8680cbcf7ba8b5443ef0716a743c4cca0a2b0213057d161c2d9"
4865
+ "sha256": "e2b0491898652f569a569617c6f3ce969c9011072e04e376355b95d969a09119"
4866
4866
  },
4867
4867
  {
4868
4868
  "path": "modules/hall-ui/public/idea.states.json",
@@ -5217,12 +5217,12 @@
5217
5217
  {
5218
5218
  "path": "modules/hall-ui/public/task-lib.js",
5219
5219
  "mode": "0000644",
5220
- "sha256": "3efb1199b1fc3c979b1c3aaa23d817241b48104dd1dfd40cbc20ed80b1a971cc"
5220
+ "sha256": "64cfbc3a3815fd12249a4f8694668b4503272ec7c795ed3eaafbc81ed77183f3"
5221
5221
  },
5222
5222
  {
5223
5223
  "path": "modules/hall-ui/public/task.css",
5224
5224
  "mode": "0000644",
5225
- "sha256": "c811a3aafeb8e792315b5ac77c637f31d00169e9ba9e4007f897c276f418f935"
5225
+ "sha256": "1f2d0ed8f8c8497468e1102dd2d90b0e63f15bd89158e5935f50de1016c355e4"
5226
5226
  },
5227
5227
  {
5228
5228
  "path": "modules/hall-ui/public/task.html",
@@ -5232,7 +5232,7 @@
5232
5232
  {
5233
5233
  "path": "modules/hall-ui/public/task.js",
5234
5234
  "mode": "0000644",
5235
- "sha256": "c11bacc90cfc86e0cf809a70e42ead7c31a5925c22f037170817db8783e3e10b"
5235
+ "sha256": "4e4ae2a620f30bc5d8863e0acf75003f97f9071405fceb51fda60de4deb7e3ad"
5236
5236
  },
5237
5237
  {
5238
5238
  "path": "modules/hall-ui/public/task.states.json",
@@ -5277,7 +5277,7 @@
5277
5277
  {
5278
5278
  "path": "modules/hall-ui/public/work.js",
5279
5279
  "mode": "0000644",
5280
- "sha256": "7283ebe918bb1ab2eedf4f88e7ccfc8fa7ab79b790460c1886c89cabc2632f9c"
5280
+ "sha256": "1fed935dc521c45972e49435308cb791f2c509ea544ff55663221c9723590632"
5281
5281
  },
5282
5282
  {
5283
5283
  "path": "modules/hall-ui/public/work.states.json",
@@ -5427,7 +5427,7 @@
5427
5427
  {
5428
5428
  "path": "modules/lifecycle/db-goals.js",
5429
5429
  "mode": "0000644",
5430
- "sha256": "3ad7f78cbad987307d2203facbc91cec879565f398f9893a0218bbf29fc061bb"
5430
+ "sha256": "713b7165b1dbbfe43a77db5678ea98ebb03e17209d7b7bee5dbd1fc416be5cc2"
5431
5431
  },
5432
5432
  {
5433
5433
  "path": "modules/lifecycle/db-grade.js",
@@ -5477,7 +5477,7 @@
5477
5477
  {
5478
5478
  "path": "modules/lifecycle/done-when.js",
5479
5479
  "mode": "0000644",
5480
- "sha256": "fab91acf54bea4fca254bfd420a9a934e6eae434101ff57ae2a1bed48d2342d5"
5480
+ "sha256": "4e276151342f0eab3d4c0cea1ed55db8716a1d3d4b40568966be495f4892c1f4"
5481
5481
  },
5482
5482
  {
5483
5483
  "path": "modules/lifecycle/est-advisory.js",
@@ -5517,7 +5517,7 @@
5517
5517
  {
5518
5518
  "path": "modules/lifecycle/goal-rollup.js",
5519
5519
  "mode": "0000644",
5520
- "sha256": "cd3c3c28e9e1361b1f02a8de7a973d0d4d87b916477af98b9032de87dfbc588d"
5520
+ "sha256": "1debae561ef745ba22440ea2b32f27dd567e9e7ed56f8a5509e90fc1ddecd10e"
5521
5521
  },
5522
5522
  {
5523
5523
  "path": "modules/lifecycle/goal-suggest.js",
@@ -5734,6 +5734,11 @@
5734
5734
  "mode": "0000644",
5735
5735
  "sha256": "7ed98bbd3d4954808dc64228acb6899a96083081ecc241e81f2b5db9eb568f1f"
5736
5736
  },
5737
+ {
5738
+ "path": "modules/lifecycle/task-status.js",
5739
+ "mode": "0000644",
5740
+ "sha256": "28db8b28430bbb1bc901b300322ce42ed5e9254e0b934941d3647b83c92b2083"
5741
+ },
5737
5742
  {
5738
5743
  "path": "modules/lifecycle/task-visual-db.js",
5739
5744
  "mode": "0000644",
@@ -5752,7 +5757,7 @@
5752
5757
  {
5753
5758
  "path": "modules/lifecycle/version-close.js",
5754
5759
  "mode": "0000644",
5755
- "sha256": "d71d0c0583cfa873001c0e359d6ee6e0e0a26d846121064c25ed36a1d27054ae"
5760
+ "sha256": "a977d807adb1e73544d1c02f6231005c2dbbb1a491316a8160080fd279e1b7a0"
5756
5761
  },
5757
5762
  {
5758
5763
  "path": "modules/memory/CLAUDE.md",
@@ -6692,7 +6697,7 @@
6692
6697
  {
6693
6698
  "path": "modules/public-landing/public/index.html",
6694
6699
  "mode": "0000644",
6695
- "sha256": "9d1e904bdb89b929f29871c6c50cbdf9a33d73943b689d167ad26cb1201a01e2"
6700
+ "sha256": "c6ad936d8f4340a16f7faea30ec0b97b7a1c526a9a991d1e1c6ff1833ddb57ce"
6696
6701
  },
6697
6702
  {
6698
6703
  "path": "modules/public-landing/public/index.probes.json",
@@ -7467,17 +7472,17 @@
7467
7472
  {
7468
7473
  "path": "package-lock.json",
7469
7474
  "mode": "0000644",
7470
- "sha256": "c71d59c13b87d9ffbd6c792eb56b26eb434704e855aed3afdfc58914dba02e29"
7475
+ "sha256": "cfbffbcea38df68b7a0bffb2c6f99c76be20b0efcbb1d86489e029272e7d410b"
7471
7476
  },
7472
7477
  {
7473
7478
  "path": "package.json",
7474
7479
  "mode": "0000644",
7475
- "sha256": "6a2c55e98a36d257177254a100569a14bec9b3720931caf9ef265971b81ba319"
7480
+ "sha256": "445928943329f12df7a6aa27a544b0f1a7c40bd4634c9c8460518777eb69981f"
7476
7481
  },
7477
7482
  {
7478
7483
  "path": "public-docs/index.html",
7479
7484
  "mode": "0000644",
7480
- "sha256": "09891860e91b4c25e4314de24e3189c3dbb36c29e1fbc01eb5d3125fb2ac6985"
7485
+ "sha256": "5ca4ae4f141fb9af378b8bdc0f3a013a6cb3f5289e0b9ba2f5ae5069c4c34844"
7481
7486
  },
7482
7487
  {
7483
7488
  "path": "public-docs/redoc.standalone.js",
@@ -8497,7 +8502,7 @@
8497
8502
  {
8498
8503
  "path": "scripts/gds/sequence.js",
8499
8504
  "mode": "0000644",
8500
- "sha256": "52c43371c2e6e8f7ffeac588143167ffc43e0010bceb7137ebce4ed61eaa64e6"
8505
+ "sha256": "08b2165b899aa71983826789613d7eadd1bdded7f4ef12b05d7c1a60bb79337e"
8501
8506
  },
8502
8507
  {
8503
8508
  "path": "scripts/gds/session-cost.js",
@@ -9167,7 +9172,7 @@
9167
9172
  {
9168
9173
  "path": "src/module-api.js",
9169
9174
  "mode": "0000644",
9170
- "sha256": "95e7b476152291d84e955bfd585e62c48c48d85dcab0ca8d73c4519af2997e8b"
9175
+ "sha256": "87189caf049da61dd52f02a1a8f884caac9bde741d3dd29543a7815e6994bca0"
9171
9176
  },
9172
9177
  {
9173
9178
  "path": "src/module-loader/catalog.js",
@@ -9427,7 +9432,7 @@
9427
9432
  {
9428
9433
  "path": "tests/auto_satisfy_criteria.mjs",
9429
9434
  "mode": "0000644",
9430
- "sha256": "135d0af6b1535e50d68ed62baa36b616ba28888e899940cabf2f2940f3cd6f9d"
9435
+ "sha256": "548e6a477660bb5e5cb260a0d5821bc3e11201225e64cfa45611c2fae07c80b2"
9431
9436
  },
9432
9437
  {
9433
9438
  "path": "tests/autonomous_fit_protected_paths.mjs",
@@ -10302,7 +10307,7 @@
10302
10307
  {
10303
10308
  "path": "tests/goal_sort_staleness.mjs",
10304
10309
  "mode": "0000644",
10305
- "sha256": "1c34a429a1e96c409e8f6e2199d79a0f75b95f25a2cd5f1a86b6f2e254d6ac40"
10310
+ "sha256": "cf399c4c8ea36f96bbdb1f68e36a71eb3d0f4965c3e88278c9387d92896e7db4"
10306
10311
  },
10307
10312
  {
10308
10313
  "path": "tests/goal_subtitle.mjs",
@@ -1551,5 +1551,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
1551
1551
  landed since 1.19.549 with no explicit bump. run 33907331050. (task 1002620)
1552
1552
  1.19.551 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1553
1553
  landed since 1.19.550 with no explicit bump. run 33917261720. (task 1002620)
1554
+ 1.19.552 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1555
+ landed since 1.19.551 with no explicit bump. run 34043771286. (task 1002620)
1556
+ 1.19.553 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1557
+ landed since 1.19.552 with no explicit bump. run 34044208907. (task 1002620)
1554
1558
  ---------------------------------------------------------------------------
1555
1559
  ```
@@ -335,7 +335,7 @@
335
335
 
336
336
 
337
337
  // Wires the public landing's access-request form. Submits to the one public
338
- // GDS write (POST /access-requests). On success the form collapses into a
338
+ // Bongos write (POST /access-requests). On success the form collapses into a
339
339
  // confirmation so the stranger knows the Archon has been pinged.
340
340
 
341
341
  // Reads the module-level lastKarmaState / lastCostState directly — every caller
@@ -427,7 +427,7 @@
427
427
  // * pick_disciplines — the craft picker (PATCH /me/disciplines).
428
428
  // * join_discord — a Connect-Discord button (the existing OAuth flow,
429
429
  // auto-joins + assigns rank role); clears when linked.
430
- // cli_setup clears once the GDS CLI is authenticated (db.hasCliSession) and
430
+ // cli_setup clears once the Bongos CLI is authenticated (db.hasCliSession) and
431
431
  // ship_three on the 3rd ship — both passive. The terminal ship_three step
432
432
  // shows the live "N of 3" counter (its `detail`). `discord` is the /me discord
433
433
  // block ({ linked, configured }) — when Discord isn't configured server-side
@@ -972,7 +972,7 @@
972
972
  // local action or page reload while the hall sits open:
973
973
  // * box_connect — stamped by the box's heartbeat the first time a `claude`
974
974
  // process runs on the dev box (builder_boxes.first_connected_at);
975
- // * cli_setup — stamped when the GDS CLI is first authenticated on the box;
975
+ // * cli_setup — stamped when the Bongos CLI is first authenticated on the box;
976
976
  // * join_discord — flips on the OAuth link (usually a full redirect, but can
977
977
  // also land from another tab).
978
978
  // Without a poll, a newcomer who connects their box AFTER opening the hall sees
@@ -1268,7 +1268,7 @@
1268
1268
  // The hall used to fetch every panel once at load and then freeze until a
1269
1269
  // manual refresh — so an onboarding step that ticked server-side (e.g. after
1270
1270
  // /builder-ship) never showed. We now open a Server-Sent Events stream
1271
- // (GET /api/bongos/live) that emits a tiny `nudge` whenever GDS state changes via
1271
+ // (GET /api/bongos/live) that emits a tiny `nudge` whenever Bongos state changes via
1272
1272
  // ANY write path — API routes, background routines, even direct psql writes
1273
1273
  // (the trigger lives in the DB; see migration 066 + src/bongos/routes/live.js).
1274
1274
  // On a nudge we re-run loadAll(), debounced so a burst coalesces into one
@@ -916,7 +916,7 @@
916
916
  // time. (The Discord step already did this; the craft picker and the
917
917
  // See-the-works link used to render on locked/cleared steps too.) Every
918
918
  // step auto-detects its own completion server-side (#777): join_discord
919
- // clears on the Discord link, cli_setup once the GDS CLI is authenticated,
919
+ // clears on the Discord link, cli_setup once the Bongos CLI is authenticated,
920
920
  // ship_three at the third ship — no manual ticking.
921
921
  let action = '';
922
922
  if (s.is_current && !s.cleared) {
@@ -114,7 +114,7 @@
114
114
  }
115
115
 
116
116
  // The body is markdown and renders through the hall's one parser, the way the
117
- // task record's description does (task 1003465); a bare #NNN is a GDS id and
117
+ // task record's description does (task 1003465); a bare #NNN is a Bongos id and
118
118
  // drops its hash so it cannot read as a GitHub issue. Without the reader the
119
119
  // text still shows, escaped.
120
120
  function prose(text) {
@@ -65,7 +65,7 @@
65
65
  return `<span class="task__tag task__tag--rank" title="Minimum rank required to claim this task">${esc(label)}</span>`;
66
66
  }
67
67
 
68
- // Strip bare "#NNN" GDS refs from prose we are about to paste elsewhere: on
68
+ // Strip bare "#NNN" Bongos refs from prose we are about to paste elsewhere: on
69
69
  // GitHub a bare #NNN autolinks to an ISSUE, and our ids are not GitHub's, so
70
70
  // the link 404s. (Same rule the repo enforces in committed markdown.)
71
71
  // VERBATIM from the two originals — do not "tidy" either detail:
@@ -95,7 +95,7 @@
95
95
  const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
96
96
  const hallUrl = `${hallBase}#/task/${t.id}`;
97
97
  return [
98
- `I am working on GDS task ${t.id} (${t.title}) — ${hallUrl}`,
98
+ `I am working on Bongos task ${t.id} (${t.title}) — ${hallUrl}`,
99
99
  `Read the task description, follow its acceptance criteria, stay strictly within touches[], and ship via /builder-ship when done.`,
100
100
  ``,
101
101
  `Task description:`,
@@ -15,7 +15,7 @@
15
15
  beats with exactly ONE lit, which is the same lit-step grammar as the open tab
16
16
  and the current nav item. It is a READOUT, not a control — nothing here is
17
17
  pressable, so the accent lights only the beat the task is actually on.
18
- The six GDS statuses fold into five beats because backlog / blocked / ready
18
+ The six Bongos statuses fold into five beats because backlog / blocked / ready
19
19
  are all "no builder has it yet"; task.js writes the caption that says which. */
20
20
  .tk-rail {
21
21
  display: grid;
@@ -244,7 +244,7 @@
244
244
  return `<section><p class="rec-sec__h">${escapeHtml(kindWord)}</p><ul class="rec-deplist">${rows}</ul></section>`;
245
245
  }
246
246
 
247
- // The lifecycle rail: five beats, exactly one lit. Six GDS statuses fold into
247
+ // The lifecycle rail: five beats, exactly one lit. Six Bongos statuses fold into
248
248
  // five because backlog / blocked / ready all mean "no builder holds it yet";
249
249
  // the first beat's caption is what says WHICH of the three it is, so the rail
250
250
  // never implies a claim that has not happened.
@@ -321,7 +321,7 @@
321
321
  }
322
322
  }
323
323
 
324
- // A bare #NNN in a description is a GDS id, and anywhere it is pasted or
324
+ // A bare #NNN in a description is a Bongos id, and anywhere it is pasted or
325
325
  // rendered it would autolink to a GitHub issue that does not exist (CLAUDE.md's
326
326
  // no-bare-#NNN rule) — so both the copy and the rendered paths drop the hash.
327
327
  const delinkRefs = (s) => String(s || '').replace(/(^|\s)#(\d+)\b/g, '$1$2');
@@ -343,7 +343,7 @@
343
343
  function buildPrompt(t) {
344
344
  const url = `${location.origin}${hallBase()}/task/${t.id}`;
345
345
  const desc = delinkRefs(t.description);
346
- return `I am working on GDS task ${t.id} (${t.title || ''}) — ${url}\n\n` +
346
+ return `I am working on Bongos task ${t.id} (${t.title || ''}) — ${url}\n\n` +
347
347
  `Claim it with \`/builder-claim ${t.id}\`, build it, then \`/builder-ship ${t.id}\`.\n\n` +
348
348
  `Task description:\n${desc}`;
349
349
  }
@@ -817,7 +817,7 @@
817
817
  function buildBulkParallelPrompt(tasks) {
818
818
  const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
819
819
  const lines = [
820
- `I want to work on ${tasks.length} GDS tasks IN PARALLEL — open ${tasks.length} separate Claude Code sessions, each in its own worktree, one per task below, so they can run at the same time without touching each other's files.`,
820
+ `I want to work on ${tasks.length} Bongos tasks IN PARALLEL — open ${tasks.length} separate Claude Code sessions, each in its own worktree, one per task below, so they can run at the same time without touching each other's files.`,
821
821
  '',
822
822
  ];
823
823
  tasks.forEach((t, i) => {
@@ -831,7 +831,7 @@
831
831
  function buildBulkSequentialPrompt(tasks) {
832
832
  const hallBase = location.hostname.startsWith('builders.') ? location.origin : `${location.origin}/builders`;
833
833
  const lines = [
834
- `I want to work on ${tasks.length} GDS tasks IN ORDER, one after another in this same session — claim, build, and ship each before moving to the next.`,
834
+ `I want to work on ${tasks.length} Bongos tasks IN ORDER, one after another in this same session — claim, build, and ship each before moving to the next.`,
835
835
  '',
836
836
  ];
837
837
  tasks.forEach((t, i) => {
@@ -14,6 +14,7 @@ const api = require('../../src/module-api');
14
14
  const { pool } = api;
15
15
  const doneWhen = require('./done-when.js');
16
16
  const { deriveRequiredRank, highestRank } = require('./db-rank-authz.js');
17
+ const { nonTerminalSql } = require('./task-status');
17
18
 
18
19
  // -------------------------------------------------------------------------
19
20
  // Goals (ADR 0086 §2 / BV1.R56 — task 1510)
@@ -328,7 +329,7 @@ async function listCriteriaForGoal(goalId, deps = {}) {
328
329
  -- task 1002651 (migration 017's family): abandoned is a terminal
329
330
  -- state (deliberately cut), not pending work — it must not hold a
330
331
  -- criterion's "met — pending review" flip hostage forever.
331
- count(*) FILTER (WHERE t.status NOT IN ('shipped', 'abandoned'))::int AS unshipped_tasks
332
+ count(*) FILTER (WHERE ${nonTerminalSql()})::int AS unshipped_tasks
332
333
  FROM task_criteria tc
333
334
  JOIN tasks t ON t.id = tc.task_id
334
335
  WHERE tc.criterion_id IN (SELECT id FROM crit)
@@ -24,6 +24,7 @@
24
24
  // POST /api/gds/done-when/:criterionId/unsatisfy — auth
25
25
 
26
26
  const { pool } = require('../../src/module-api');
27
+ const { nonTerminalSql } = require('./task-status');
27
28
 
28
29
  // isPendingReview — the ONE derivation of the "met — pending review" auto-flag
29
30
  // (ADR 0086 §6 / BV1.R63): a criterion with >=1 linked task, every one shipped,
@@ -134,7 +135,7 @@ async function listPendingReviewCriteria({ versionId = null } = {}, deps = {}) {
134
135
  JOIN tasks t ON t.id = tc.task_id
135
136
  WHERE c.satisfied = false
136
137
  GROUP BY c.id
137
- HAVING count(*) FILTER (WHERE t.status NOT IN ('shipped', 'abandoned')) = 0
138
+ HAVING count(*) FILTER (WHERE ${nonTerminalSql()}) = 0
138
139
  ),
139
140
  goal_unsat AS (
140
141
  SELECT goal_id, count(*)::int AS unsatisfied
@@ -426,7 +427,7 @@ async function autoSatisfyShippedCriteria(exec, { taskId = null } = {}) {
426
427
  AND ($1::bigint IS NULL OR c.id IN (
427
428
  SELECT criterion_id FROM task_criteria WHERE task_id = $1::bigint))
428
429
  GROUP BY c.id
429
- HAVING count(*) FILTER (WHERE t.status NOT IN ('shipped', 'abandoned')) = 0
430
+ HAVING count(*) FILTER (WHERE ${nonTerminalSql()}) = 0
430
431
  AND count(*) FILTER (WHERE t.status = 'shipped') >= 1
431
432
  )
432
433
  UPDATE done_when_criteria c
@@ -32,10 +32,12 @@
32
32
  const api = require('../../src/module-api');
33
33
  const { pool } = api;
34
34
 
35
- // The status vocabularies, kept beside the SQL that uses them so a reader can
36
- // check them against goals-lib.js in one glance.
37
- const DONE_STATUSES = ['shipped', 'confirmed', 'completed'];
38
- const CUT_STATUSES = ['abandoned', 'dropped'];
35
+ // The status vocabularies live in task-status.js (task 1003588) so this module
36
+ // has ONE spelling of each. These two are the PRESENTATIONAL buckets — they
37
+ // mirror goals-lib.js `readinessOf`, and they are NOT the terminal set: `done`
38
+ // here includes the mid-ship states on purpose, which is right for a progress
39
+ // ring and wrong for any question about whether a goal may close.
40
+ const { DONE_STATUSES, CUT_STATUSES } = require('./task-status');
39
41
 
40
42
  // goalRollups — one row per goal that has at least one task in `versionId`.
41
43
  // Returns [{ goal_id, done, active, ready, blocked, backlog, cut, total,
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ // modules/lifecycle/task-status.js — the task-status vocabulary, declared once
4
+ // (task 1003588, goal 1000086, criterion sv-goal-outlives-nothing).
5
+ //
6
+ // WHY THIS FILE EXISTS. "Finished" had two spellings inside one module.
7
+ // `goal-rollup.js` counted shipped|confirmed|completed as done; `done-when.js`
8
+ // counted only shipped and treated abandoned as accounted-for; `db-goals.js`,
9
+ // `version-close.js` and the `sequence` CLI each re-typed the SQL literal
10
+ // `NOT IN ('shipped', 'abandoned')` by hand. Two definitions of finished in one
11
+ // module is precisely what lets a goal read closed on one surface and open on
12
+ // another — the failure goal 1000086 exists to make impossible. So the
13
+ // vocabulary is declared here, once, and every consumer reads it.
14
+ //
15
+ // THE TWO SETS ARE NOT THE SAME QUESTION, and conflating them is the bug in the
16
+ // other direction:
17
+ //
18
+ // TERMINAL_STATUSES — the LEDGER truth. A task is finished with the project
19
+ // when it shipped or was deliberately abandoned. Nothing else is terminal:
20
+ // `completed` and `confirmed` are mid-ship states that can still fail a
21
+ // grade or strand, so work sitting in them is unfinished work and must hold
22
+ // its goal open. This is the set the closure invariants read.
23
+ //
24
+ // DONE_STATUSES / CUT_STATUSES — a PRESENTATIONAL bucketing for the goals
25
+ // board, mirroring the client's `goals-lib.js readinessOf` verbatim (a
26
+ // divergence shows a person different numbers per surface, which
27
+ // tests/goal_sort_staleness.mjs pins). "Done" there means "no longer in the
28
+ // queue", which deliberately includes the mid-ship states. It is a progress
29
+ // ring, not a ledger, and must never be used to decide whether a goal may
30
+ // close.
31
+ //
32
+ // NO DEPENDENCIES, deliberately. This file requires nothing — not even
33
+ // `module-api` — so a CLI (`scripts/gds/sequence.js`) can read the vocabulary
34
+ // without dragging the server's `db.js` and its connection pool into a process
35
+ // that only needs a Set of two strings.
36
+
37
+ // TERMINAL_STATUSES — the only spelling of "this task is finished with the
38
+ // project". shipped = delivered; abandoned = deliberately cut (task 1002651,
39
+ // migration 017's family). Everything else holds work open.
40
+ const TERMINAL_STATUSES = ['shipped', 'abandoned'];
41
+
42
+ // TERMINAL_STATUS_SET — the same set for JS membership tests.
43
+ const TERMINAL_STATUS_SET = new Set(TERMINAL_STATUSES);
44
+
45
+ // isTerminalStatus — true when a task in this status is finished with the
46
+ // project and can no longer hold a criterion, a goal, or a version open.
47
+ function isTerminalStatus(status) {
48
+ return TERMINAL_STATUS_SET.has(status);
49
+ }
50
+
51
+ // nonTerminalSql — the SQL predicate for "this task is still unfinished",
52
+ // rendered from TERMINAL_STATUSES so the literal exists in exactly one place.
53
+ // `column` is the status expression in the caller's query (`t.status` under a
54
+ // join, bare `status` in a single-table read). Output is the byte-identical
55
+ // `<column> NOT IN ('shipped', 'abandoned')` every call site used to hardcode.
56
+ function nonTerminalSql(column = 't.status') {
57
+ return `${column} NOT IN (${TERMINAL_STATUSES.map((s) => `'${s}'`).join(', ')})`;
58
+ }
59
+
60
+ // DONE_STATUSES / CUT_STATUSES — the goals-board buckets. Presentational only;
61
+ // see the header. Mirrors goals-lib.js `readinessOf`.
62
+ const DONE_STATUSES = ['shipped', 'confirmed', 'completed'];
63
+ const CUT_STATUSES = ['abandoned', 'dropped'];
64
+
65
+ module.exports = {
66
+ TERMINAL_STATUSES,
67
+ TERMINAL_STATUS_SET,
68
+ isTerminalStatus,
69
+ nonTerminalSql,
70
+ DONE_STATUSES,
71
+ CUT_STATUSES,
72
+ };
@@ -11,6 +11,7 @@
11
11
  // (separate task #43).
12
12
 
13
13
  const { pool } = require('../../src/module-api');
14
+ const { nonTerminalSql } = require('./task-status');
14
15
 
15
16
  const PERCENT_GATE = 80;
16
17
 
@@ -54,7 +55,7 @@ async function getCarryOverCount(versionId) {
54
55
  `SELECT count(*) AS n
55
56
  FROM tasks
56
57
  WHERE version_id = $1
57
- AND status NOT IN ('shipped', 'abandoned')
58
+ AND ${nonTerminalSql('status')}
58
59
  AND title NOT LIKE '\\_\\_smoke\\_\\_%' ESCAPE '\\'`,
59
60
  [versionId]
60
61
  );
@@ -910,7 +910,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
910
910
  <script>
911
911
  (function () {
912
912
  var API = '/api/bongos';
913
- var GDS = '/api/gds';
913
+ var API_ROOT = '/api/gds';
914
914
 
915
915
  /* ── the tagline. It is ONE server-stamped string, but the composition wants it
916
916
  on two lines, so the two halves are baked as separate spans. A host whose
@@ -1109,7 +1109,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
1109
1109
  return d.getDate() + ' ' + MONTHS[d.getMonth()];
1110
1110
  }
1111
1111
 
1112
- json(GDS + '/public/progress').then(function (data) {
1112
+ json(API_ROOT + '/public/progress').then(function (data) {
1113
1113
  var rows = (data && data.progress) || [];
1114
1114
  if (!rows.length) return;
1115
1115
  var live = null;
@@ -1129,7 +1129,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
1129
1129
  }
1130
1130
  });
1131
1131
 
1132
- json(GDS + '/public/recent-shipped?limit=6').then(function (data) {
1132
+ json(API_ROOT + '/public/recent-shipped?limit=6').then(function (data) {
1133
1133
  var tasks = ((data && data.tasks) || []).slice(0, 6);
1134
1134
  var list = document.querySelector('[data-rows]');
1135
1135
  if (!tasks.length || !list) return;
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.551",
3
+ "version": "1.19.553",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.551",
9
+ "version": "1.19.553",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.551",
3
+ "version": "1.19.553",
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",
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>{{productName}} — API docs</title>
7
- <meta name="description" content="{{productName}} GDS API reference (OpenAPI 3.1)." />
7
+ <meta name="description" content="{{productName}} API reference (OpenAPI 3.1)." />
8
8
  <style>
9
9
  /* Self-contained: no external fonts/CSS. Redoc paints the rest. */
10
10
  html, body { margin: 0; padding: 0; }
@@ -33,8 +33,10 @@ const RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
33
33
 
34
34
  // A task in one of these statuses is done as far as the chain is concerned —
35
35
  // 'completed'/'confirmed' are mid-ship states, so the chain must not re-claim
36
- // them, but they are also not yet 'shipped' so successors stay gated.
37
- const TERMINAL = new Set(['shipped', 'abandoned']);
36
+ // them, but they are also not yet 'shipped' so successors stay gated. The set
37
+ // itself comes from the lifecycle's one declaration (task 1003588); that file
38
+ // requires nothing, so reading it costs this CLI no server pool.
39
+ const { TERMINAL_STATUS_SET: TERMINAL } = require('../../modules/lifecycle/task-status');
38
40
  const IN_FLIGHT = new Set(['active', 'completed', 'confirmed']);
39
41
 
40
42
  const DEFAULT_LIMIT = 50;
package/src/module-api.js CHANGED
@@ -49,7 +49,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
49
49
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
50
50
  // the entry to that file. Look for a version's history there, not here.
51
51
  // ---------------------------------------------------------------------------
52
- const CORE_VERSION = '1.19.551'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
52
+ const CORE_VERSION = '1.19.553'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
53
53
 
54
54
  // A namespaced logger so a module's log lines are attributable + consistent.
55
55
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -61,7 +61,10 @@ const CRIT = { id: 10, version_id: 'BONGOS-V1', criterion_id: 'c-one', criterion
61
61
  await test('GUARD (sql): the update requires BOTH nothing-pending AND >=1 shipped', () => {
62
62
  const src = readFileSync(new URL('../modules/lifecycle/done-when.js', import.meta.url), 'utf8');
63
63
  const body = src.slice(src.indexOf('async function autoSatisfyShippedCriteria'));
64
- assert.match(body, /HAVING count\(\*\) FILTER \(WHERE t\.status NOT IN \('shipped', 'abandoned'\)\) = 0/,
64
+ // task 1003588: the literal moved to task-status.js nonTerminalSql(), which
65
+ // renders this exact predicate - so the guard is asserted here on the CALL,
66
+ // and below on what that one declaration actually produces.
67
+ assert.match(body, /HAVING count\(\*\) FILTER \(WHERE \$\{nonTerminalSql\(\)\}\) = 0/,
65
68
  'nothing may still be pending');
66
69
  assert.match(body, /AND count\(\*\) FILTER \(WHERE t\.status = 'shipped'\) >= 1/,
67
70
  'THE GUARD: an all-abandoned gating set delivered nothing and must not close a criterion');
@@ -72,7 +75,7 @@ await test('ASYMMETRY (sql): the review queue deliberately does NOT carry the de
72
75
  const start = src.indexOf('async function listPendingReviewCriteria');
73
76
  const body = src.slice(start, src.indexOf('async function criterionProgress', start));
74
77
  assert.ok(body.length > 200, 'the slice must actually contain the function');
75
- assert.match(body, /HAVING count\(\*\) FILTER \(WHERE t\.status NOT IN \('shipped', 'abandoned'\)\) = 0/);
78
+ assert.match(body, /HAVING count\(\*\) FILTER \(WHERE \$\{nonTerminalSql\(\)\}\) = 0/);
76
79
  assert.ok(!/status = 'shipped'\) >= 1/.test(body),
77
80
  'task 1002651 intends an all-abandoned criterion to SURFACE for review; only auto-close is gated');
78
81
  });
@@ -211,3 +214,22 @@ await test('closeCompletedWorkForShip: silent on the common no-op, and logs what
211
214
  });
212
215
 
213
216
  summary();
217
+
218
+ // ---- 4. the terminal vocabulary has exactly one spelling (task 1003588) ----
219
+
220
+ await test('VOCABULARY: nonTerminalSql renders the predicate every call site used to hardcode', async () => {
221
+ const mod = await import('../modules/lifecycle/task-status.js');
222
+ const { nonTerminalSql, TERMINAL_STATUSES } = mod.default || mod;
223
+ assert.deepEqual(TERMINAL_STATUSES, ['shipped', 'abandoned'],
224
+ 'shipped or deliberately abandoned - the mid-ship states are NOT terminal');
225
+ assert.equal(nonTerminalSql(), "t.status NOT IN ('shipped', 'abandoned')");
226
+ assert.equal(nonTerminalSql('status'), "status NOT IN ('shipped', 'abandoned')");
227
+ });
228
+
229
+ await test('VOCABULARY: no consumer re-types the terminal set behind the constant', () => {
230
+ for (const f of ['done-when.js', 'db-goals.js', 'version-close.js', 'goal-rollup.js']) {
231
+ const src = readFileSync(new URL('../modules/lifecycle/' + f, import.meta.url), 'utf8');
232
+ assert.ok(!/NOT IN \('shipped', 'abandoned'\)/.test(src),
233
+ f + ' must read the terminal set from task-status.js, not spell it again');
234
+ }
235
+ });
@@ -156,10 +156,16 @@ await test('the SQL rollup buckets MIRROR goals-lib readinessOf exactly', () =>
156
156
  // surface they opened, which is the opposite of what this goal is for.
157
157
  const rollup = read('modules/lifecycle/goal-rollup.js');
158
158
  const lib = GOALS_LIB_SRC;
159
- // done + cut vocabularies
160
- assert.match(rollup, /const DONE_STATUSES = \['shipped', 'confirmed', 'completed'\]/);
159
+ // done + cut vocabularies. task 1003588 moved the server side's declaration
160
+ // into task-status.js so the module has one spelling of each; the mirror this
161
+ // test defends is unchanged, it is just read from where the constant now
162
+ // lives, plus the assertion that goal-rollup.js consumes it rather than
163
+ // re-declaring a second copy that could drift from the client.
164
+ const vocab = read('modules/lifecycle/task-status.js');
165
+ assert.match(rollup, /require\('\.\/task-status'\)/);
166
+ assert.match(vocab, /const DONE_STATUSES = \['shipped', 'confirmed', 'completed'\]/);
161
167
  assert.match(lib, /DONE_STATUSES = new Set\(\['completed', 'confirmed', 'shipped'\]\)/);
162
- assert.match(rollup, /const CUT_STATUSES = \['abandoned', 'dropped'\]/);
168
+ assert.match(vocab, /const CUT_STATUSES = \['abandoned', 'dropped'\]/);
163
169
  assert.match(lib, /CUT_STATUSES = new Set\(\['abandoned', 'dropped'\]\)/);
164
170
  // blocked = backlog with >=1 unshipped dependency, both sides
165
171
  assert.match(rollup, /WHEN s\.status = 'backlog' AND s\.has_unshipped_dep THEN 'blocked'/);