@bongos/core 1.19.551 → 1.19.552
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 +23 -18
- package/docs/module-api-changelog.md +2 -0
- package/modules/lifecycle/db-goals.js +2 -1
- package/modules/lifecycle/done-when.js +3 -2
- package/modules/lifecycle/goal-rollup.js +6 -4
- package/modules/lifecycle/task-status.js +72 -0
- package/modules/lifecycle/version-close.js +2 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/sequence.js +4 -2
- package/src/module-api.js +1 -1
- package/tests/auto_satisfy_criteria.mjs +24 -2
- package/tests/goal_sort_staleness.mjs +9 -3
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.552",
|
|
6
|
+
"core_contract": "1.19.552",
|
|
7
|
+
"source_commit": "c33c4fe6dd1cf9319051a7bee1e9c940472549a5",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-06T15:55:11.203Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 442,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2019,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2485,
|
|
20
|
+
"tree_sha256": "c628eebd2a60fa4e7346604f1f5507a913cfac851f45fc632f3eff4aedf36487",
|
|
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": "
|
|
2655
|
+
"sha256": "2fb63f0c4427c7f7bff711fcf41f5637cca7b31a8009a21305530748b130e310"
|
|
2656
2656
|
},
|
|
2657
2657
|
{
|
|
2658
2658
|
"path": "docs/modules-contract.md",
|
|
@@ -5427,7 +5427,7 @@
|
|
|
5427
5427
|
{
|
|
5428
5428
|
"path": "modules/lifecycle/db-goals.js",
|
|
5429
5429
|
"mode": "0000644",
|
|
5430
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
5760
|
+
"sha256": "a977d807adb1e73544d1c02f6231005c2dbbb1a491316a8160080fd279e1b7a0"
|
|
5756
5761
|
},
|
|
5757
5762
|
{
|
|
5758
5763
|
"path": "modules/memory/CLAUDE.md",
|
|
@@ -7467,12 +7472,12 @@
|
|
|
7467
7472
|
{
|
|
7468
7473
|
"path": "package-lock.json",
|
|
7469
7474
|
"mode": "0000644",
|
|
7470
|
-
"sha256": "
|
|
7475
|
+
"sha256": "cc08dead6d4714851b7db549d9b6ad9829bbd0e22722fab29cef54f9b28f7693"
|
|
7471
7476
|
},
|
|
7472
7477
|
{
|
|
7473
7478
|
"path": "package.json",
|
|
7474
7479
|
"mode": "0000644",
|
|
7475
|
-
"sha256": "
|
|
7480
|
+
"sha256": "36e67dc71f4fced7aa31d42102c8ff9c424ded06dea1a6f2f67d45888e95e406"
|
|
7476
7481
|
},
|
|
7477
7482
|
{
|
|
7478
7483
|
"path": "public-docs/index.html",
|
|
@@ -8497,7 +8502,7 @@
|
|
|
8497
8502
|
{
|
|
8498
8503
|
"path": "scripts/gds/sequence.js",
|
|
8499
8504
|
"mode": "0000644",
|
|
8500
|
-
"sha256": "
|
|
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": "
|
|
9175
|
+
"sha256": "bd96dabd37302c58d3519dde872d4a77f20302b5da263dc829db0fda37654edb"
|
|
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": "
|
|
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": "
|
|
10310
|
+
"sha256": "cf399c4c8ea36f96bbdb1f68e36a71eb3d0f4965c3e88278c9387d92896e7db4"
|
|
10306
10311
|
},
|
|
10307
10312
|
{
|
|
10308
10313
|
"path": "tests/goal_subtitle.mjs",
|
|
@@ -1551,5 +1551,7 @@ 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)
|
|
1554
1556
|
---------------------------------------------------------------------------
|
|
1555
1557
|
```
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
58
|
+
AND ${nonTerminalSql('status')}
|
|
58
59
|
AND title NOT LIKE '\\_\\_smoke\\_\\_%' ESCAPE '\\'`,
|
|
59
60
|
[versionId]
|
|
60
61
|
);
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.552",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.552",
|
|
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.
|
|
3
|
+
"version": "1.19.552",
|
|
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",
|
package/scripts/gds/sequence.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
52
|
+
const CORE_VERSION = '1.19.552'; // 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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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'/);
|