@bongos/core 1.19.619 → 1.19.620
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 +24 -19
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/api/openapi.json +1 -0
- package/docs/api-reference.md +1 -1
- package/docs/module-api-changelog.md +2 -0
- package/docs/recipes/reranking-a-goals-board.md +93 -0
- package/modules/lifecycle/db-tasks.js +10 -1
- package/modules/lifecycle/db.js +2 -0
- package/modules/lifecycle/routes/task-write-routes.js +30 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/migration_allocation.mjs +27 -1
- package/tests/patch_gate_ordering.mjs +3 -0
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.620",
|
|
6
|
+
"core_contract": "1.19.620",
|
|
7
|
+
"source_commit": "7e8c8d38a15a24575258eda58829e9120d9ed55f",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-09T06:
|
|
9
|
+
"built_at": "2026-09-09T06:53:24.739Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 464,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
14
|
"functional_verbatim": 2086,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2574,
|
|
20
|
+
"tree_sha256": "8c5d0cef512909da1de5dd90510ca13a74737c8d3b80ec2d8e0a7fc5e1d9451d",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/backlog-review/SKILL.md",
|
|
@@ -397,7 +397,7 @@
|
|
|
397
397
|
{
|
|
398
398
|
"path": "clients/bongos-client/index.d.ts",
|
|
399
399
|
"mode": "0000644",
|
|
400
|
-
"sha256": "
|
|
400
|
+
"sha256": "07caf5c1db2cfd16c92713891055b477982aef4dd847af67042b742293e6a9ce"
|
|
401
401
|
},
|
|
402
402
|
{
|
|
403
403
|
"path": "clients/bongos-client/index.mjs",
|
|
@@ -1877,12 +1877,12 @@
|
|
|
1877
1877
|
{
|
|
1878
1878
|
"path": "docs/api-reference.md",
|
|
1879
1879
|
"mode": "0000644",
|
|
1880
|
-
"sha256": "
|
|
1880
|
+
"sha256": "8adebe11d51d4bbce7e5acacc003ce923e820f0d8532541d3f5a74ed07570ce8"
|
|
1881
1881
|
},
|
|
1882
1882
|
{
|
|
1883
1883
|
"path": "docs/api/openapi.json",
|
|
1884
1884
|
"mode": "0000644",
|
|
1885
|
-
"sha256": "
|
|
1885
|
+
"sha256": "2059cb9b02d6a20e4b1297c866db9f717bb3514ec49ab2454c776c806775d3da"
|
|
1886
1886
|
},
|
|
1887
1887
|
{
|
|
1888
1888
|
"path": "docs/architecture.md",
|
|
@@ -2757,7 +2757,7 @@
|
|
|
2757
2757
|
{
|
|
2758
2758
|
"path": "docs/module-api-changelog.md",
|
|
2759
2759
|
"mode": "0000644",
|
|
2760
|
-
"sha256": "
|
|
2760
|
+
"sha256": "6b3c53cfb31b4bf6963f740bb386c24059ef370af8927b0ececde09e062fff33"
|
|
2761
2761
|
},
|
|
2762
2762
|
{
|
|
2763
2763
|
"path": "docs/modules-contract.md",
|
|
@@ -2954,6 +2954,11 @@
|
|
|
2954
2954
|
"mode": "0000644",
|
|
2955
2955
|
"sha256": "f6d2bb1da53f03f54080b8e0aec9f63390d3343c32917b64225ac47ae4ba7b42"
|
|
2956
2956
|
},
|
|
2957
|
+
{
|
|
2958
|
+
"path": "docs/recipes/reranking-a-goals-board.md",
|
|
2959
|
+
"mode": "0000644",
|
|
2960
|
+
"sha256": "ecd8986c5e4831db53a803dae625e5f1b898b711911e8e6f1c4f1b79a98cbfe8"
|
|
2961
|
+
},
|
|
2957
2962
|
{
|
|
2958
2963
|
"path": "docs/recipes/search-index-ingestion.md",
|
|
2959
2964
|
"mode": "0000644",
|
|
@@ -5592,7 +5597,7 @@
|
|
|
5592
5597
|
{
|
|
5593
5598
|
"path": "modules/lifecycle/db-tasks.js",
|
|
5594
5599
|
"mode": "0000644",
|
|
5595
|
-
"sha256": "
|
|
5600
|
+
"sha256": "15c9efea1fb5a8187fe5bae756777ebeb1a214e47e0434d7a8adeb156bd1e093"
|
|
5596
5601
|
},
|
|
5597
5602
|
{
|
|
5598
5603
|
"path": "modules/lifecycle/db-versions.js",
|
|
@@ -5602,7 +5607,7 @@
|
|
|
5602
5607
|
{
|
|
5603
5608
|
"path": "modules/lifecycle/db.js",
|
|
5604
5609
|
"mode": "0000644",
|
|
5605
|
-
"sha256": "
|
|
5610
|
+
"sha256": "af6af95c05aa6cc76c0e49cb0a99bfb24685b3efb66df0f90883c3301baeede0"
|
|
5606
5611
|
},
|
|
5607
5612
|
{
|
|
5608
5613
|
"path": "modules/lifecycle/dead-deps.js",
|
|
@@ -5832,7 +5837,7 @@
|
|
|
5832
5837
|
{
|
|
5833
5838
|
"path": "modules/lifecycle/routes/task-write-routes.js",
|
|
5834
5839
|
"mode": "0000644",
|
|
5835
|
-
"sha256": "
|
|
5840
|
+
"sha256": "cc86b3c05885c733cc28ba6e41f1b08cb8f0fe652e3181c1c88be4caabf18d61"
|
|
5836
5841
|
},
|
|
5837
5842
|
{
|
|
5838
5843
|
"path": "modules/lifecycle/routes/tasks.js",
|
|
@@ -7657,12 +7662,12 @@
|
|
|
7657
7662
|
{
|
|
7658
7663
|
"path": "package-lock.json",
|
|
7659
7664
|
"mode": "0000644",
|
|
7660
|
-
"sha256": "
|
|
7665
|
+
"sha256": "401ae455c8e67d7bf4d622ef7e93fce203948868ae3b07de9770e6d90607249f"
|
|
7661
7666
|
},
|
|
7662
7667
|
{
|
|
7663
7668
|
"path": "package.json",
|
|
7664
7669
|
"mode": "0000644",
|
|
7665
|
-
"sha256": "
|
|
7670
|
+
"sha256": "f6c0e1047dcef4fe6e429e149606d521e0d196ff748bcdeb6de6e1c63d2a60ab"
|
|
7666
7671
|
},
|
|
7667
7672
|
{
|
|
7668
7673
|
"path": "public-docs/index.html",
|
|
@@ -9392,7 +9397,7 @@
|
|
|
9392
9397
|
{
|
|
9393
9398
|
"path": "src/module-api.js",
|
|
9394
9399
|
"mode": "0000644",
|
|
9395
|
-
"sha256": "
|
|
9400
|
+
"sha256": "d2b45162be47422810ab7a05c42c9926520df6c1c5ab7e7274386c5a25eb07a3"
|
|
9396
9401
|
},
|
|
9397
9402
|
{
|
|
9398
9403
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11432,7 +11437,7 @@
|
|
|
11432
11437
|
{
|
|
11433
11438
|
"path": "tests/migration_allocation.mjs",
|
|
11434
11439
|
"mode": "0000644",
|
|
11435
|
-
"sha256": "
|
|
11440
|
+
"sha256": "0d1bc299555d98aa2ad8571e0a5a185776ca727f95577c3308a7e4b36efbfd97"
|
|
11436
11441
|
},
|
|
11437
11442
|
{
|
|
11438
11443
|
"path": "tests/migration_namespace.mjs",
|
|
@@ -11672,7 +11677,7 @@
|
|
|
11672
11677
|
{
|
|
11673
11678
|
"path": "tests/patch_gate_ordering.mjs",
|
|
11674
11679
|
"mode": "0000644",
|
|
11675
|
-
"sha256": "
|
|
11680
|
+
"sha256": "27a3b5023b6857f3f53734966bc7685757383f2b8dadfa3c0bf1aca2e7db5517"
|
|
11676
11681
|
},
|
|
11677
11682
|
{
|
|
11678
11683
|
"path": "tests/path_match.mjs",
|
|
@@ -182,7 +182,7 @@ export interface PatchProvisioningInstancesIdDetailResponse { ok: boolean; descr
|
|
|
182
182
|
export interface PatchProvisioningInstancesIdSettingsResponse { ok: boolean; saved: unknown; planet: unknown; push: unknown }
|
|
183
183
|
export interface PatchTaskRecommendationsIdRequest { status: "declined" | "withdrawn" }
|
|
184
184
|
export interface PatchTaskRecommendationsIdResponse { ok: boolean; recommendation: unknown }
|
|
185
|
-
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 }
|
|
185
|
+
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; priority?: unknown; status?: unknown }
|
|
186
186
|
export interface PostAccessRequestsInviteRequest { github_login: string }
|
|
187
187
|
export interface PostAccessRequestsInviteResponse { ok: boolean; already_invited?: boolean; github_login?: unknown; request?: unknown }
|
|
188
188
|
export interface PostAccessRequestsRequest { github_login: string; display_name?: string; note?: string }
|
package/docs/api/openapi.json
CHANGED
|
@@ -18880,6 +18880,7 @@
|
|
|
18880
18880
|
"minLength": 1,
|
|
18881
18881
|
"maxLength": 200
|
|
18882
18882
|
},
|
|
18883
|
+
"priority": {},
|
|
18883
18884
|
"status": {}
|
|
18884
18885
|
},
|
|
18885
18886
|
"additionalProperties": false
|
package/docs/api-reference.md
CHANGED
|
@@ -591,7 +591,7 @@ Base path: `/api/bongos`. Ranks (enforced server-side, [ADR 0016](adr/<redacted>
|
|
|
591
591
|
| GET | `/api/bongos/tasks` | `any-builder` | — | |
|
|
592
592
|
| 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 … |
|
|
593
593
|
| GET | `/api/bongos/tasks/:id` | `any-builder` | — | single-task read; preserved per CRITICAL §4 (supervisor hot path). |
|
|
594
|
-
| 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 … |
|
|
594
|
+
| 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`, `priority`, `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 … |
|
|
595
595
|
| POST | `/api/bongos/tasks/:id/abandon` | `metic+archon` | `reason` | POST /tasks/:id/abandon — terminal "retire this task" transition (task 987). |
|
|
596
596
|
| POST | `/api/bongos/tasks/:id/attest-gate` | `any-builder` | `head_sha` | POST /tasks/:id/attest-gate — body: { head_sha }. |
|
|
597
597
|
| POST | `/api/bongos/tasks/:id/confirm` | `any-builder` | `verified` | |
|
|
@@ -1687,5 +1687,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1687
1687
|
landed since 1.19.617 with no explicit bump. run 34318552090. (task 1002620)
|
|
1688
1688
|
1.19.619 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1689
1689
|
landed since 1.19.618 with no explicit bump. run 34320024204. (task 1002620)
|
|
1690
|
+
1.19.620 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1691
|
+
landed since 1.19.619 with no explicit bump. run 34321089991. (task 1002620)
|
|
1690
1692
|
---------------------------------------------------------------------------
|
|
1691
1693
|
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Re-ranking a goal's board — the needed-vs-nice rubric
|
|
2
|
+
|
|
3
|
+
> Written at task 1003750 (2026-09-09), the first whole-goal re-rank the project ran. Read this before re-ranking any board: the rubric is here so it is applied consistently rather than re-invented per session, and the mechanics section records the two traps that made the first pass cost more than the analysis did.
|
|
4
|
+
|
|
5
|
+
`tasks.priority` is an integer **1–5, where 1 is most urgent**. Two surfaces read it and both take low-as-urgent:
|
|
6
|
+
|
|
7
|
+
- version progress is weighted `6 - COALESCE(t.priority, 5)` (`migrations/003_pms.sql`), so an unset priority weighs the same as a P5;
|
|
8
|
+
- the session optimizer orders claimable work `priority ASC NULLS LAST` (`src/bongos/optimizer.js`).
|
|
9
|
+
|
|
10
|
+
So priority is not a label. It steers what every builder is offered first, and an unset one is not neutral — it sits at the bottom.
|
|
11
|
+
|
|
12
|
+
## The rubric
|
|
13
|
+
|
|
14
|
+
The axis is **needed vs nice-to-have**, which is deliberately *not* "is this still true" (that is a relevance audit — see [the goal 1000090 one](../audits/<redacted>.md)) and not "how big is it". A task can be tiny and P1, or large and P5.
|
|
15
|
+
|
|
16
|
+
| Tier | Test | What earns it |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| **P1** | Bleeding now | A live path is broken, or **reports success falsely**, or finished paid work is being lost. Fix regardless of what else is queued. |
|
|
19
|
+
| **P2** | Needed, not bleeding | A defect with a workaround that taxes every session; a keystone freezing other tasks; a cost window closing on its own. |
|
|
20
|
+
| **P3** | Should do | Real reliability or legibility gain, no forcing function. |
|
|
21
|
+
| **P4** | Nice to have | Consistency, doc nits, cleanups, and work blocked behind a decision nobody has made yet. |
|
|
22
|
+
| **P5** | Nice to have / speculative | Large refactors and research with no forcing function. Also: tasks whose premise is contested and needs an owner call — park them here rather than guessing. |
|
|
23
|
+
|
|
24
|
+
Three rules that did the most work in practice:
|
|
25
|
+
|
|
26
|
+
1. **"Reports success falsely" outranks "is broken".** A broken thing gets noticed; a lying one does not. Three of the five P1s on goal 1000090 were only P1 because a tool printed a success it had not verified.
|
|
27
|
+
2. **Read the body, not the title.** [Task 1001479](https://cloudbongos.com/builders#/task/1001479) sat at P1 from June while its own description read *"Trivial, low priority."* Titles drift from bodies; bodies drift from reality.
|
|
28
|
+
3. **A blocked task is not urgent, but its keystone is.** Where five tasks waited on one unwritten ADR, the ADR moved up and the five moved down. Prioritizing the blocked five would have produced five claims that cannot start.
|
|
29
|
+
|
|
30
|
+
**Sanity check before writing:** a tier list that is mostly P1 has ranked nothing. Aim for a distribution that narrows sharply at the top — goal 1000090's 63 open rows came out 5 / 8 / 27 / 17 / 6.
|
|
31
|
+
|
|
32
|
+
## Mechanics
|
|
33
|
+
|
|
34
|
+
**Now:** `PATCH /api/bongos/tasks/:id` accepts `priority` (integer 1–5, or `null` to clear). It did not before task 1003750 — priority was write-once at create and immutable at every rank, which is why the first re-rank had to be SQL.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
echo '{"priority":2}' > /tmp/p.json
|
|
38
|
+
node scripts/gds/api.js PATCH /api/gds/tasks/1002884 --body-file /tmp/p.json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Two things to know before you batch this:
|
|
42
|
+
|
|
43
|
+
- **Bulk PATCH is rate-limited to roughly 30 per minute.** A 63-row pass needs pacing, not a tight loop.
|
|
44
|
+
- **`priority` is not settable at the same time as `status`.** Status moves only through its own transition routes (`/promote`, `/confirm`, …); a body carrying `status` is refused outright with `status_not_patchable`.
|
|
45
|
+
|
|
46
|
+
**The trap that outlives this recipe: a P1 in `backlog` is inert.** Priority orders the *claimable* feed, and `backlog` rows are not in it. On goal 1000090, four of the five P1s were in `backlog`, so the re-rank changed nothing about what builders saw until they were promoted. **Always re-check status after re-ranking** — `/promote` the ones that should be live, and use [`/backlog-review`](../../.claude/skills/backlog-review/SKILL.md) to walk what is waiting on a person.
|
|
47
|
+
|
|
48
|
+
And before promoting anything into the claimable pool, **check `requires_rank`.** New tasks floor at `xenos` and the auto-derivation has been inert since ADR 0049, so a task about the deploy or authority surface is newcomer-claimable unless someone raised it by hand. Promoting a P1 without checking that is how a deploy-authority design lands in the newcomer queue.
|
|
49
|
+
|
|
50
|
+
### When the API cannot reach the live board
|
|
51
|
+
|
|
52
|
+
Core reaches a live instance only through a release plus a pin move (gate 3, [`core-release-pipeline.md`](core-release-pipeline.md)) — so a *newly shipped* route is not callable against production for as long as the pin lags. In that window the board is only reachable by SQL on the control-plane box, and an agent cannot run it (the permission classifier blocks a mutating `sudo -u lars` over ssh, by design — see [task 1003159](https://cloudbongos.com/builders#/task/1003159), which exists to close exactly this gap).
|
|
53
|
+
|
|
54
|
+
Generate the statements from a checked map rather than typing ids, keep it in one transaction, and **write the revert first** — the prior values are only recoverable if you captured them before the write:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
node scripts/gds/api.js GET '/api/gds/tasks?goal_id=<goal>&limit=200' --quiet > before.json
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Then group the ids by target tier and emit one `UPDATE` per tier, guarded to the goal and to open statuses so a concurrent ship cannot be clobbered:
|
|
61
|
+
|
|
62
|
+
```sql
|
|
63
|
+
BEGIN;
|
|
64
|
+
UPDATE tasks SET priority=1, updated_at=now()
|
|
65
|
+
WHERE goal_id=<goal> AND status IN ('ready','backlog') AND id IN (<ids>);
|
|
66
|
+
-- one statement per tier
|
|
67
|
+
COMMIT;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Build the revert from `before.json` the same way, grouping by each row's *original* priority (including a `NULL` group — unset is a real prior value and restoring it as 5 is not the same thing). Verify every id in the map appears in the emitted SQL before running anything; a silently dropped id leaves a row at a priority nobody chose.
|
|
71
|
+
|
|
72
|
+
## The goal 1000090 pass, as a worked example
|
|
73
|
+
|
|
74
|
+
All 63 open rows of [goal 1000090](https://cloudbongos.com/builders#/goal/1000090) (working area 4 — Bongos Core distribution), 2026-09-09. 48 of 63 changed; 24 had no priority at all.
|
|
75
|
+
|
|
76
|
+
| Tier | Tasks |
|
|
77
|
+
|---|---|
|
|
78
|
+
| **P1** | 1002708, 1002714, 1002884, 1003159, 1003200 |
|
|
79
|
+
| **P2** | 1001431, 1002204, 1002205, 1002863, 1002869, 1003526, 1003537, 1003742 |
|
|
80
|
+
| **P3** | 1000380, 1000861, 1000862, 1001219, 1001429, 1001433, 1001436, 1001930, 1002206, 1002531, 1002532, 1002583, 1002617, 1002622, 1002669, 1002707, 1002854, 1002880, 1002900, 1002903, 1003499, 1003502, 1003564, 1003625, 1003647, 1003648, 1003658 |
|
|
81
|
+
| **P4** | 1001311, 1001441, 1001479, 1001641, 1001927, 1001929, 1002049, 1002208, 1002209, 1002842, 1002844, 1002879, 1002882, 1003563, 1003573, 1003651, 1003657 |
|
|
82
|
+
| **P5** | 1002406, 1002413, 1002839, 1002840, 1002841, 1002843 |
|
|
83
|
+
|
|
84
|
+
The five P1s all said the same thing, which is the finding the exercise produced: **shipped work does not reliably reach users, and the tooling misreports it.** Task 1002884 health-checked the old still-running process and printed "✓ upgrade complete" while live served the previous version; 1002708 and 1002714 leave the pin uncommitted so the next commit silently downgrades live; 1003159 is the owner having no deploy control at all; 1003200 is that nothing checks whether a claimed artifact ever reached a remote, found after three paid tasks turned out to exist only on laptops.
|
|
85
|
+
|
|
86
|
+
1002406 and 1002413 sit at P5 as *contested*, not as unimportant — ADR 0062 §7 and task 1003740 now point against their premise, so they need an owner's kill-or-re-argue rather than a builder's guess.
|
|
87
|
+
|
|
88
|
+
## Related
|
|
89
|
+
|
|
90
|
+
- [`docs/audits/<redacted>.md`](../audits/<redacted>.md) — the relevance pass on the same board. Run relevance first: there is no point ranking a task whose premise is gone.
|
|
91
|
+
- `/backlog-review` — walks `status='backlog'`, which is where a re-rank's P1s often turn out to be hiding.
|
|
92
|
+
- `/priority-session` — reweights the **idea inbox**, not tasks, and writes no priorities. It ends in a recommendation; this recipe is the write.
|
|
93
|
+
- [`core-release-pipeline.md`](core-release-pipeline.md) — why a shipped route is not a live route.
|
|
@@ -272,7 +272,7 @@ async function findTaskBySourceRef({ source, sourceRef } = {}, deps = {}) {
|
|
|
272
272
|
|
|
273
273
|
const TASK_SETTER_COLUMNS = new Set([
|
|
274
274
|
'kind', 'discipline', 'newcomer_friendly', 'security_sensitive', 'parallel_safe', 'automation_tag',
|
|
275
|
-
'needs_migration', 'module_key', 'description', 'title',
|
|
275
|
+
'needs_migration', 'module_key', 'description', 'title', 'priority',
|
|
276
276
|
]);
|
|
277
277
|
|
|
278
278
|
async function updateTaskColumn(id, column, value) {
|
|
@@ -339,6 +339,14 @@ async function updateTaskDescription(id, description) { return updateTaskColumn(
|
|
|
339
339
|
// 1002791) was permanent. Same permission, same audit trail as the body.
|
|
340
340
|
async function updateTaskTitle(id, title) { return updateTaskColumn(id, 'title', title); }
|
|
341
341
|
|
|
342
|
+
// task 1003750: the third write-once planning field, and the one that steers
|
|
343
|
+
// what gets claimed — version progress is weighted `6 - COALESCE(priority, 5)`
|
|
344
|
+
// and the session optimizer orders claimable work by `priority ASC NULLS LAST`.
|
|
345
|
+
// Set at create and then immutable at every rank, so re-ranking a goal's board
|
|
346
|
+
// (needed vs nice-to-have) had no route at all and had to be done as raw SQL.
|
|
347
|
+
// 1 = most urgent, 5 = least; null clears it back to unset (weighted as 5).
|
|
348
|
+
async function updateTaskPriority(id, priority) { return updateTaskColumn(id, 'priority', priority); }
|
|
349
|
+
|
|
342
350
|
|
|
343
351
|
async function updateTaskDiscipline(id, discipline) { return updateTaskColumn(id, 'discipline', discipline); }
|
|
344
352
|
|
|
@@ -936,6 +944,7 @@ module.exports = {
|
|
|
936
944
|
updateTaskNeedsMigration,
|
|
937
945
|
updateTaskNewcomerFriendly,
|
|
938
946
|
updateTaskParallelSafe,
|
|
947
|
+
updateTaskPriority,
|
|
939
948
|
updateTaskRequiresRank,
|
|
940
949
|
updateTaskSecuritySensitive,
|
|
941
950
|
updateTaskStatus,
|
package/modules/lifecycle/db.js
CHANGED
|
@@ -127,6 +127,7 @@ const {
|
|
|
127
127
|
updateTaskNeedsMigration,
|
|
128
128
|
updateTaskNewcomerFriendly,
|
|
129
129
|
updateTaskParallelSafe,
|
|
130
|
+
updateTaskPriority,
|
|
130
131
|
updateTaskRequiresRank,
|
|
131
132
|
updateTaskSecuritySensitive,
|
|
132
133
|
updateTaskStatus,
|
|
@@ -301,6 +302,7 @@ module.exports = {
|
|
|
301
302
|
disambiguateWorktreeName,
|
|
302
303
|
foldWorktreeName,
|
|
303
304
|
updateTaskParallelSafe,
|
|
305
|
+
updateTaskPriority,
|
|
304
306
|
updateTaskSecuritySensitive,
|
|
305
307
|
updateTaskAutomationTag,
|
|
306
308
|
updateTaskNeedsMigration,
|
|
@@ -318,6 +318,14 @@ module.exports = function registerTaskWriteRoutes(router) {
|
|
|
318
318
|
// briefs naming tables that hard-fail CI). Same shape as create, same
|
|
319
319
|
// task.edit permission, same audit_log trail as a description edit.
|
|
320
320
|
title: { type: 'string', minLength: 1, maxLength: LIMITS.TITLE },
|
|
321
|
+
// task 1003750: priority was accepted at create and then immutable at every
|
|
322
|
+
// rank — the same write-once defect title/description carried above. It is
|
|
323
|
+
// not cosmetic: the DB weights version progress as `6 - COALESCE(priority, 5)`
|
|
324
|
+
// and the session optimizer orders claimable work by `priority ASC NULLS LAST`,
|
|
325
|
+
// so a wrong value at create time steers what every builder claims, forever.
|
|
326
|
+
// A whole-goal re-rank (needed-vs-nice) had no route and had to run as SQL.
|
|
327
|
+
// null clears it back to unset; the 1..5 bounds mirror tasks_priority_check.
|
|
328
|
+
priority: {}, // 1..5 or null — checked inline
|
|
321
329
|
// task 1002614 (map D-18): DECLARED so strict-mode admits the request far
|
|
322
330
|
// enough to reach the deliberate status_not_patchable hint below (#514) —
|
|
323
331
|
// the hint had been unreachable dead code behind unknown_field since the
|
|
@@ -345,6 +353,7 @@ module.exports = function registerTaskWriteRoutes(router) {
|
|
|
345
353
|
const hasGoalId = Object.prototype.hasOwnProperty.call(body, 'goal_id');
|
|
346
354
|
const hasDescription = Object.prototype.hasOwnProperty.call(body, 'description');
|
|
347
355
|
const hasTitle = Object.prototype.hasOwnProperty.call(body, 'title');
|
|
356
|
+
const hasPriority = Object.prototype.hasOwnProperty.call(body, 'priority');
|
|
348
357
|
// #514: status is a lifecycle field, not a PATCH-able column — it moves only
|
|
349
358
|
// through the dedicated transition routes. The #506 session tried
|
|
350
359
|
// `PATCH status=ready`, got the generic no_supported_fields error, and had
|
|
@@ -356,8 +365,8 @@ module.exports = function registerTaskWriteRoutes(router) {
|
|
|
356
365
|
if (Object.prototype.hasOwnProperty.call(body, 'status')) {
|
|
357
366
|
return res.fail('status_not_patchable', { status: 400, message: STATUS_HINT });
|
|
358
367
|
}
|
|
359
|
-
if (!hasParent && !hasKind && !hasDiscipline && !hasNewcomerOrXenos && !hasParallelSafe && !hasSecuritySensitive && !hasAutomationTag && !hasNeedsMigration && !hasRequiresRank && !hasModuleKey && !hasGoalId && !hasDescription && !hasTitle) {
|
|
360
|
-
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 });
|
|
368
|
+
if (!hasParent && !hasKind && !hasDiscipline && !hasNewcomerOrXenos && !hasParallelSafe && !hasSecuritySensitive && !hasAutomationTag && !hasNeedsMigration && !hasRequiresRank && !hasModuleKey && !hasGoalId && !hasDescription && !hasTitle && !hasPriority) {
|
|
369
|
+
return res.fail('no_supported_fields', { status: 400, message: 'PATCH /tasks/:id supports parent_task_id, kind, discipline, title, description, priority, parallel_safe, security_sensitive, automation_tag, needs_migration, requires_rank, module_key, goal_id, and newcomer_friendly (alias: xenos_claimable). ' + STATUS_HINT });
|
|
361
370
|
}
|
|
362
371
|
// ---- task 1002609 (G-22): settle EVERY gate and validation before the ----
|
|
363
372
|
// ---- FIRST write. The previous shape validated-and-wrote per field in ----
|
|
@@ -374,6 +383,16 @@ module.exports = function registerTaskWriteRoutes(router) {
|
|
|
374
383
|
if (hasParallelSafe && body.parallel_safe !== null && typeof body.parallel_safe !== 'boolean') {
|
|
375
384
|
return res.fail('bad_parallel_safe', { status: 400, message: 'parallel_safe must be true, false, or null' });
|
|
376
385
|
}
|
|
386
|
+
// task 1003750: bounds mirror the tasks_priority_check DB constraint via the
|
|
387
|
+
// shared TASK_FIELD_BOUNDS the create schema uses, so the route and the
|
|
388
|
+
// column can't drift. Integer or null only — a float or "3" would pass a
|
|
389
|
+
// bare range test and then fail at the column.
|
|
390
|
+
if (hasPriority && body.priority !== null) {
|
|
391
|
+
const p = body.priority;
|
|
392
|
+
if (!Number.isInteger(p) || p < TASK_FIELD_BOUNDS.priority.min || p > TASK_FIELD_BOUNDS.priority.max) {
|
|
393
|
+
return res.fail('bad_priority', { status: 400, message: `priority must be an integer ${TASK_FIELD_BOUNDS.priority.min}-${TASK_FIELD_BOUNDS.priority.max} (1 = most urgent), or null to clear` });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
377
396
|
if (hasSecuritySensitive) {
|
|
378
397
|
// task 990: explicit Archon-only-broadcast flag. Boolean only (no null) —
|
|
379
398
|
// it gates whether ship.js routes the broadcast to the Archon-only
|
|
@@ -528,6 +547,15 @@ module.exports = function registerTaskWriteRoutes(router) {
|
|
|
528
547
|
return res.fail('patch_failed', { status: 500, message: 'internal error' });
|
|
529
548
|
}
|
|
530
549
|
}
|
|
550
|
+
if (hasPriority) {
|
|
551
|
+
try {
|
|
552
|
+
updated = await db.updateTaskPriority(id, body.priority ?? null);
|
|
553
|
+
if (!updated) return res.fail('task_not_found', 404);
|
|
554
|
+
} catch (err) {
|
|
555
|
+
log.error('[gds] PATCH /tasks/:id priority', err);
|
|
556
|
+
return res.fail('patch_failed', { status: 500, message: 'internal error' });
|
|
557
|
+
}
|
|
558
|
+
}
|
|
531
559
|
if (hasGoalId) {
|
|
532
560
|
try {
|
|
533
561
|
updated = await db.updateTaskGoal(id, body.goal_id);
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.620",
|
|
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.620",
|
|
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.620",
|
|
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/src/module-api.js
CHANGED
|
@@ -55,7 +55,7 @@ const { buildInfo } = require('./build-info');
|
|
|
55
55
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
56
56
|
// the entry to that file. Look for a version's history there, not here.
|
|
57
57
|
// ---------------------------------------------------------------------------
|
|
58
|
-
const CORE_VERSION = '1.19.
|
|
58
|
+
const CORE_VERSION = '1.19.620'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
59
59
|
|
|
60
60
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
61
61
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -204,6 +204,7 @@ function shouldReserveMigrationNumber(task) {
|
|
|
204
204
|
[() => db.updateTaskModuleKey(1, null), 'module_key'],
|
|
205
205
|
[() => db.updateTaskDescription(1, 'edited body'), 'description'], // task 1002647
|
|
206
206
|
[() => db.updateTaskTitle(1, 'corrected title'), 'title'], // task 1002792
|
|
207
|
+
[() => db.updateTaskPriority(1, 2), 'priority'], // task 1003750
|
|
207
208
|
];
|
|
208
209
|
for (const [call, column] of setters) {
|
|
209
210
|
const before = seen.length;
|
|
@@ -225,10 +226,35 @@ function shouldReserveMigrationNumber(task) {
|
|
|
225
226
|
'the PATCH schema must declare title with the create-time limits — a title can be rewritten, never blanked'
|
|
226
227
|
);
|
|
227
228
|
assert.ok(/const hasTitle = Object\.prototype\.hasOwnProperty\.call\(body, 'title'\)/.test(routesSrc), 'title is gated like every other field');
|
|
228
|
-
|
|
229
|
+
// Assert !hasTitle is a TERM of the no_supported_fields guard, not that it is
|
|
230
|
+
// the LAST term: pinning the final position made this red the moment task
|
|
231
|
+
// 1003750 appended !hasPriority, for a change that kept the stated intent
|
|
232
|
+
// (a title-only PATCH is supported) perfectly intact.
|
|
233
|
+
assert.ok(/if \(!hasParent &&[^)]*!hasTitle\b/.test(routesSrc), 'a title-only PATCH is a supported request, not no_supported_fields');
|
|
229
234
|
assert.ok(/db\.updateTaskTitle\(id, body\.title\.trim\(\)\)/.test(routesSrc), 'the write goes through the allowlisted setter');
|
|
230
235
|
});
|
|
231
236
|
|
|
237
|
+
// task 1003750: priority was the third write-once planning field (after title
|
|
238
|
+
// and description above) — accepted at create, then immutable at every rank.
|
|
239
|
+
// It is the one that steers what gets claimed: version progress is weighted
|
|
240
|
+
// `6 - COALESCE(priority, 5)` and the optimizer orders by `priority ASC NULLS
|
|
241
|
+
// LAST`, so re-ranking a goal's board had to be done as raw SQL against prod.
|
|
242
|
+
await test('PATCH /tasks/:id accepts priority so a board can be re-ranked (task 1003750)', () => {
|
|
243
|
+
assert.ok(/priority: \{\},\s*\/\/ 1\.\.5 or null/.test(routesSrc), 'the PATCH schema must declare priority so strict-mode admits it');
|
|
244
|
+
assert.ok(/const hasPriority = Object\.prototype\.hasOwnProperty\.call\(body, 'priority'\)/.test(routesSrc), 'priority is gated like every other field');
|
|
245
|
+
assert.ok(/if \(!hasParent &&[^)]*!hasPriority\b/.test(routesSrc), 'a priority-only PATCH is a supported request');
|
|
246
|
+
assert.ok(/db\.updateTaskPriority\(id, body\.priority \?\? null\)/.test(routesSrc), 'the write goes through the allowlisted setter, and null clears');
|
|
247
|
+
// The bounds must come from the shared TASK_FIELD_BOUNDS the create schema
|
|
248
|
+
// reads, never a literal 1..5 here — that is how a route and its column drift.
|
|
249
|
+
assert.ok(/TASK_FIELD_BOUNDS\.priority\.min.*TASK_FIELD_BOUNDS\.priority\.max/s.test(routesSrc), 'priority bounds are shared with the create schema, not re-typed');
|
|
250
|
+
// Validation must sit ABOVE the first write (the task 1002609 rule this
|
|
251
|
+
// handler documents): a body mixing a valid field with a bad priority must
|
|
252
|
+
// commit nothing.
|
|
253
|
+
const guardAt = routesSrc.indexOf("res.fail('bad_priority'");
|
|
254
|
+
const firstWriteAt = routesSrc.indexOf('await db.updateTaskKind(');
|
|
255
|
+
assert.ok(guardAt > 0 && firstWriteAt > 0 && guardAt < firstWriteAt, 'the priority bounds check must run before the first write, not after');
|
|
256
|
+
});
|
|
257
|
+
|
|
232
258
|
await test('PATCH /tasks/:id accepts needs_migration so a wrong flag is correctable', () => {
|
|
233
259
|
assert.ok(
|
|
234
260
|
/needs_migration: \{ type: 'boolean' \}/.test(routesSrc),
|
|
@@ -46,6 +46,9 @@ const REFUSALS = [
|
|
|
46
46
|
// rendered from goalNotOpenRefusal, so the pin looks for the call.
|
|
47
47
|
'goalNotOpenRefusal(goal)',
|
|
48
48
|
"'bad_requires_rank'",
|
|
49
|
+
// task 1003750: priority joined the PATCH surface; its bounds check is a
|
|
50
|
+
// validation refusal like the rest, so it must settle before the first write.
|
|
51
|
+
"'bad_priority'",
|
|
49
52
|
'badKind(res)',
|
|
50
53
|
"'bad_discipline'",
|
|
51
54
|
];
|