@bongos/core 1.19.619 → 1.19.621

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.619",
6
- "core_contract": "1.19.619",
7
- "source_commit": "b3a494e84958a264492cf55b3525d70680f7bbda",
5
+ "core_version": "1.19.621",
6
+ "core_contract": "1.19.621",
7
+ "source_commit": "09c7ba3955b0789f34ce6265e17cfcf52de9f146",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-09T06:39:30.103Z",
9
+ "built_at": "2026-09-09T06:59:03.131Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 463,
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": 2573,
20
- "tree_sha256": "f0ad9c8d1e70405ab669163d2d2153cd7f6ae97a699036bb8e4278f1e3a435d5",
19
+ "file_count": 2574,
20
+ "tree_sha256": "76d591f3c619056f89124f5d2abb9e8d1fc2745277375693f575438d155faf7e",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/backlog-review/SKILL.md",
@@ -377,7 +377,7 @@
377
377
  {
378
378
  "path": "clients/bongos-client/README.md",
379
379
  "mode": "0000644",
380
- "sha256": "7486f9978e473b0adf09f8bfcbe2a029eb2833636ae3bd55fc307ccb3e773963"
380
+ "sha256": "65fb7649709e966a90f431d57b8855bd39951684a754320c4a797da9e3ccac13"
381
381
  },
382
382
  {
383
383
  "path": "clients/bongos-client/bongos-client.global.js",
@@ -397,7 +397,7 @@
397
397
  {
398
398
  "path": "clients/bongos-client/index.d.ts",
399
399
  "mode": "0000644",
400
- "sha256": "877ca14cd017c1392fcc5206c2f3603fb24becdc32670aa1e2ad0de9d930dfca"
400
+ "sha256": "07caf5c1db2cfd16c92713891055b477982aef4dd847af67042b742293e6a9ce"
401
401
  },
402
402
  {
403
403
  "path": "clients/bongos-client/index.mjs",
@@ -407,7 +407,7 @@
407
407
  {
408
408
  "path": "clients/bongos-client/package.json",
409
409
  "mode": "0000644",
410
- "sha256": "ca32245dc6322f0399058b91f07fe12709fe7b97485fd5d8f5f37cb42f730aae"
410
+ "sha256": "d299252221daca415cf982b61fe5e3d9c3707c672a7d47adc052a9ee8166223f"
411
411
  },
412
412
  {
413
413
  "path": "config/branding.neutral.json",
@@ -1172,7 +1172,7 @@
1172
1172
  {
1173
1173
  "path": "docs/adr/0134-private-first-npm-distribution.md",
1174
1174
  "mode": "0000644",
1175
- "sha256": "ce1345a7de47416745964de31ec0d72cdf37f29fc1d41337b24d0e705710b0d7"
1175
+ "sha256": "87b5f5038221e8244c3f98e6f9bc6bf64b23cf8cc758327dd5a58c9b31196fb2"
1176
1176
  },
1177
1177
  {
1178
1178
  "path": "docs/adr/0135-module-upstream-submission-interim-queue.md",
@@ -1812,7 +1812,7 @@
1812
1812
  {
1813
1813
  "path": "docs/adr/0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md",
1814
1814
  "mode": "0000644",
1815
- "sha256": "43bf2232da3353ab49bfd956a73d09c5045e5fa155e0004d0855a50587c299cd"
1815
+ "sha256": "e701aa98eddf2a0bde803fecfaf681502db33e67765f6a355e883cd3c714cd08"
1816
1816
  },
1817
1817
  {
1818
1818
  "path": "docs/adr/0259-a-projects-departure-from-the-public-list-is-public.md",
@@ -1872,17 +1872,17 @@
1872
1872
  {
1873
1873
  "path": "docs/adr/README.md",
1874
1874
  "mode": "0000644",
1875
- "sha256": "d660b4bc0bc3306f5fcf583118ca9149ba3deafe82dfe3894fe0b1c11b60cc2f"
1875
+ "sha256": "6b90114abfa5e891a8f2ea16c547c960c982030d6228d43c2a833840123a3f69"
1876
1876
  },
1877
1877
  {
1878
1878
  "path": "docs/api-reference.md",
1879
1879
  "mode": "0000644",
1880
- "sha256": "b430ecad01c7458dc893c11d976d3ae35f2dd5b8310e451a38af387908f64659"
1880
+ "sha256": "8adebe11d51d4bbce7e5acacc003ce923e820f0d8532541d3f5a74ed07570ce8"
1881
1881
  },
1882
1882
  {
1883
1883
  "path": "docs/api/openapi.json",
1884
1884
  "mode": "0000644",
1885
- "sha256": "81485c0440de636511dc9ee723e18af89f313b175ea626723d0beeeed31bba2f"
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": "1fe02fc79e2952efb61450bbd45e6b09e41e8cf73453c6e0a2fbbf2944c1cdab"
2760
+ "sha256": "37afa2a8a4f3e0d4b2689de8b45dfafa6c17569ff07575ff4b3d6b1b1393d683"
2761
2761
  },
2762
2762
  {
2763
2763
  "path": "docs/modules-contract.md",
@@ -2947,13 +2947,18 @@
2947
2947
  {
2948
2948
  "path": "docs/recipes/private-npm-distribution.md",
2949
2949
  "mode": "0000644",
2950
- "sha256": "3c6613920881220ec4a076b4608ed57557d645c43dee3fbb4086ad4adb81f593"
2950
+ "sha256": "87851cddc742fb4c62de1cde8b0443a6b6fd800e753e715ed8d2ab838557d4b8"
2951
2951
  },
2952
2952
  {
2953
2953
  "path": "docs/recipes/redteam-patrol.md",
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": "e978cadb879a57d8d7e3bb84657a787ac7e83125666fa2d31013ac64b37b0d7a"
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": "b4b2d6d7890ec67e52fc00718a814bf01a64f4432ed7380a175dca8bc794b0e4"
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": "87843c62d8ce7af3d7d6aa5772791bd52823afe5f2c4b9b7427ca9f00dd14c11"
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": "41aca5e97b1cb72e389656c14ddd1bb35a3d5fdce8145f05e294c3fa3f52dc15"
7665
+ "sha256": "cc6fce6807181651ec0ec230ad34473092623805b54a1ac9fbaa84e3092b3f34"
7661
7666
  },
7662
7667
  {
7663
7668
  "path": "package.json",
7664
7669
  "mode": "0000644",
7665
- "sha256": "2084b4c59fd9b3857bb83b5a4198d43c308f0180edc0e21a698e4285809323fd"
7670
+ "sha256": "aa932c27317fc8f47d1939478cd55134c96eb1265357cc37de7765e98fe146fc"
7666
7671
  },
7667
7672
  {
7668
7673
  "path": "public-docs/index.html",
@@ -7867,7 +7872,7 @@
7867
7872
  {
7868
7873
  "path": "scripts/gds/build-cli-package.js",
7869
7874
  "mode": "0000644",
7870
- "sha256": "de1cdede17761775722509b1e1edb4bb43fbbf336450556f22119b18b93e43d3"
7875
+ "sha256": "e24710d5546428beba9400831d69eb3101e76bfdb1083916bf12411839e16735"
7871
7876
  },
7872
7877
  {
7873
7878
  "path": "scripts/gds/bump-version.js",
@@ -8152,7 +8157,7 @@
8152
8157
  {
8153
8158
  "path": "scripts/gds/gen-api-client.js",
8154
8159
  "mode": "0000644",
8155
- "sha256": "e9a6c6498f41b51dbcc17a3538d997b220f671beb433ae65ebc5365727a444ec"
8160
+ "sha256": "d363c76caf379a051faea40a2497db8bbba18effd68e9cd9922e9445638c57c5"
8156
8161
  },
8157
8162
  {
8158
8163
  "path": "scripts/gds/gen-api-docs.js",
@@ -9392,7 +9397,7 @@
9392
9397
  {
9393
9398
  "path": "src/module-api.js",
9394
9399
  "mode": "0000644",
9395
- "sha256": "983d456cff4042137990a5baacb002919c3d213a33f663d6e8fcc7e9f5945ae6"
9400
+ "sha256": "155f6fd854a43d20569e168af8238b3a082a03d74360d9e6248650ec453568a8"
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": "e2f03570b76b4d5af7af3c87a2a4989827eb71d1b50c92ab546a456e85c4024e"
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": "b0690cf28c30bfa4ef1ecc4ceffa7d4be1666dbb09a6605ceb02cc057d2a9cc4"
11680
+ "sha256": "27a3b5023b6857f3f53734966bc7685757383f2b8dadfa3c0bf1aca2e7db5517"
11676
11681
  },
11677
11682
  {
11678
11683
  "path": "tests/path_match.mjs",
@@ -7,14 +7,26 @@ by hand; it regenerates when the spec changes, so it can never drift from the ro
7
7
  - API version: **v1** (served at `/api/bongos/v1`)
8
8
  - 360 operations across 55 resource groups
9
9
 
10
- ## Install
10
+ ## Use it from your project
11
+
12
+ **This is not an npm package** — it is not published, and nothing depends on it as one
13
+ (task 1003742). Consume it by VENDORING the build you need, which is what every consumer
14
+ in this repo already does:
11
15
 
12
16
  ```bash
13
- npm install @bongos/client # internal registry / workspace path
17
+ # ESM / bundler: cp clients/bongos-client/index.mjs <your project>/vendor/
18
+ # CommonJS: cp clients/bongos-client/index.cjs <your project>/vendor/
19
+ # Browser global: cp clients/bongos-client/bongos-client.global.js <your project>/public/
14
20
  ```
15
21
 
22
+ Copy `index.d.ts` alongside it for types. Re-copy after `gen-api-client.js` runs, so the
23
+ client cannot drift from the routes.
24
+
16
25
  ## Use
17
26
 
27
+ The examples import by package name, which resolves in a workspace. A VENDORED copy is
28
+ imported by its path instead — `from './vendor/index.mjs'` — everything below is identical.
29
+
18
30
  ```js
19
31
  import { createClient, ApiError } from '@bongos/client';
20
32
 
@@ -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 }
@@ -25,8 +25,5 @@
25
25
  "README.md"
26
26
  ],
27
27
  "license": "AGPL-3.0-or-later",
28
- "publishConfig": {
29
- "access": "restricted"
30
- },
31
28
  "sideEffects": false
32
29
  }
@@ -1,6 +1,6 @@
1
1
  # 0134 — Private-first npm distribution for the core + client (paid scoped, then public at launch)
2
2
 
3
- - **Status:** Accepted — publish path + private-by-default config built in task 2089; the real publish is a gated owner step.
3
+ - **Status:** Accepted for the CORE; the **client half is superseded** by [task 1003742](https://cloudbongos.com/builders#/task/1003742) (2026-09-09) see the note under Decision. Publish path + private-by-default config built in task 2089; the real publish is a gated owner step.
4
4
  - **Date:** 2026-07-07
5
5
  - **Deciders:** example-owner (owner/Archon), Claude. Owner asked to have the npm/CLI onboarding commands ready but kept private pre-launch, choosing paid private npm over the free GitHub Packages option.
6
6
  - **Task:** [#2089](https://example.com/builders#/task/2089) (publish path + private config) · [#2092](https://example.com/builders#/task/2092) (the `@cloudbongos` → `@bongos` scope rename + first real publish) · **Goal:** 35 (project-creation flow), criterion 165.
@@ -18,6 +18,25 @@ One npm rule forces the shape: **unscoped packages are always public — only *s
18
18
 
19
19
  Publish **`@bongos/core`** and **`@bongos/client`** as **scoped, private** npm packages during pre-launch, on the owner's **paid** `bongos` npm org — the **canonical** brand org.
20
20
 
21
+ > **SUPERSEDED for the client (2026-09-09, [task 1003742](https://cloudbongos.com/builders#/task/1003742)).** `@bongos/client`
22
+ > is no longer an npm package at all, and the reason is worth keeping so it does not come back.
23
+ >
24
+ > It was published **once** — 2026-07-07, version 0.0.2 — and never updated, while the API it is
25
+ > *generated from* moved 500+ core versions. In that time it had **zero consumers**: a sweep of every
26
+ > `package.json` across the core and every instance repo (hermeslines-marketing, cloudbongos-instance,
27
+ > mercury, charter, demo) found no dependency on it; the only matches were its own manifest. Everyone
28
+ > who needs the generated client **vendors a copy** — `modules/dev-box/app/src/vendor/bongos-client.cjs`,
29
+ > and `build-cli-package.js` into `@cloudbongos/cli`, which as a *public* package could not have
30
+ > depended on a private one anyway ([ADR 0258](<redacted>.md)).
31
+ > A stale generated client that nobody installs is worse than none: the only thing it could do is
32
+ > mislead someone into pinning a client 500 versions behind the routes.
33
+ >
34
+ > The owner removed it from the registry. The **generated artifact stays** and still regenerates
35
+ > ([ADR 0118](<redacted>.md)) — `clients/bongos-client/` and
36
+ > `gen-api-client.js` earn their place. What is retired is only the claim that it is a package: the
37
+ > generator no longer emits a `publishConfig`, and its README now documents vendoring instead of
38
+ > `npm install`. **This ADR's core half is unaffected.**
39
+
21
40
  > **Scope correction (task 2092).** The publish path first shipped (task 2089) under the `@cloudbongos` scope, matching the name the repo already used. But when publishing, the paid org turned out to be **`bongos`** (the canonical brand the owner put on Teams), while `cloudbongos` — one of the ~10 defensive orgs — was left on the free tier. Since a private scoped publish requires the scope to match a *paid* org, and `@bongos` is the intended canonical brand anyway, we **renamed the core + client package identity `@cloudbongos/*` → `@bongos/*`** (a consistent rename across the build + vendored-install flow, verified by the test suite) rather than pay for a second org. Existing vendored consumers keep their pinned `@cloudbongos/core` tarball; only new scaffolds use `@bongos`.
22
41
 
23
42
  - **Private by default in code, not just by flag.** The synthesized core `package.json` (`buildPackageJson`) and the generated client `package.json` (`gen-api-client.js`) both carry `publishConfig: { access: "restricted" }`. `package-core.js --publish` additionally passes `--access restricted` on the CLI — belt-and-suspenders so a stray config edit can't silently make the pre-launch artifact public.
@@ -120,9 +120,11 @@ benefit. It gains one when ADR 0099's mirror lands.
120
120
  - **Publishing is the owner's action, not a builder's.** It is the owner's npm account, and a
121
121
  public package name cannot be un-taken. The build stops at a packed tarball and hands over one
122
122
  `npm publish --access public`.
123
- - `@bongos/client` stays private and is **vendored** as a single file rather than declared as a
124
- dependency — a public package that depended on a private one would be uninstallable for
125
- everyone.
123
+ - `@bongos/client` is **vendored** as a single file rather than declared as a dependency — a public
124
+ package that depended on a private one would be uninstallable for everyone. (It was private then;
125
+ since [task 1003742](https://cloudbongos.com/builders#/task/1003742) it is not published at all,
126
+ which makes the vendoring not merely preferable but the only option. Vendoring was the right call
127
+ either way, and this package is one of the two reasons the client had no consumers to lose.)
126
128
  - Task 1002025 is superseded and should be closed against this ADR rather than done.
127
129
  - **The journey above is instance-dependent, and 0.1.1 shipped assuming it was not**
128
130
  ([task 1003730](https://cloudbongos.com/builders#/task/1003730)). `bongos shell` is the answer
@@ -208,7 +208,7 @@ This keeps the decision history honest and traceable.
208
208
  | 0131 | [Rank-scoped skill visibility — feasibility spike (task [#1650](https://example.com/builders#/task/1650)). Wanted: a session lists only the skills the builder's rank can use, via a SessionStart hook filtering the skill menu — pure UX declutter (the server rank gate + each skill's early self-check still enforce). Finding: Claude Code hooks CANNOT filter/hide/rewrite the skill listing — SessionStart offers only `additionalContext` (inject text) + `reloadSkills` (add-only), no hook subtracts a skill, and hooks can't mutate settings at runtime. A HARD menu hide needs a pre-launch step (the launcher that already fetches rank) writing per-builder `skillOverrides:"off"` into `.claude/settings.local.json` before `claude` starts (misses plugin skills; workspace-trust-gated). Recommendation: don't build the heavy launcher change for a cosmetic gain; if some declutter is wanted now, use the existing hook's `additionalContext` as a soft nudge; keep the server gate + self-checks. Owner to decide.](<redacted>.md) | dx / skills |
209
209
  | 0132 | [Migrate the co-hosting fleet to the control plane over an SSH DB tunnel (task [#2071](https://example.com/builders#/task/2071), BONGOS-V1, goal 26; extends ADR 0130, completes 2065 / blocker 64). Stripping the co-hosting box's tokens proved NOT a switch-flip: it runs FIVE token-consuming timers (the 4 `box.js` dev-box lifecycle sweeps + `provision-intent-runner`), each reading its queue from the box's LOCAL `example` Postgres (firewalled to localhost) — so a control-plane runner can't reach the queues, and ADR 0130 moved only box-step execution, not queue reading. Also `box.js` was never migrated. Decision: a persistent SSH tunnel (control-plane `127.0.0.1:5433` → co-hosting `127.0.0.1:5432`, `infra/cohost-db-tunnel.service`) + all five runners re-homed as `cohost-*` control-plane variants reading the example queues via `DATABASE_URL` through the tunnel (no public Postgres exposure), calling DO/CF with the control-plane tokens; provisioning remote-execs box steps per ADR 0130. DB auth: a scram password on the EXISTING `lars` role (identical privileges the runners already use; reachable only via `127.0.0.1`), stored only in chmod-600 `/etc/cloudbongos/cohost-fleet.env`. `box.js` unchanged (pure DO/CF API + Discord/DNS-hook execs, all on the control plane). `cohost-provision-intent-runner.*` supersedes 2065's `provision-intent-runner-cloudbongos.*`. Rejected: exposing Postgres over the VPC (opens a TCP surface + `pg_hba`/cred work; droplets not guaranteed same-VPC); a token-signing broker on the co-hosting box (leaves tokens' blast radius there — opposite of the goal); moving the queue tables to the cloudbongos DB (the OTB web tier can't reach it either — firewall symmetry). Units ship additive + not-enabled, self-healed install-only. Activation (SSH trust · DB password · tunnel · enable `cohost-*` + disable co-hosting timers same window · verify via drift-reconcile · bake · owner token rotate+strip · drop rollback DB) is a sequenced operator-op.](<redacted>.md) | infra / Cloud Bongos |
210
210
  | 0133 | [One-click GitHub sign-in for onboarding via the GitHub App Manifest flow (task [#2080](https://example.com/builders#/task/2080), BONGOS-V1, goal 35 / criterion 165). The last onboarding friction was wiring GitHub sign-in — the owner had to hand-create an OAuth App + paste a client secret, unscriptable because GitHub has NO create-OAuth-App API. Decision: use GitHub's App **Manifest** flow. The signed-in owner clicks one pre-filled link; GitHub creates the App and returns a temporary `code`; the control-plane runner (`provision.js`, sole token-holder) exchanges it for `client_id`/`client_secret`, writes them to `/etc/<slug>/web.env` (the `oauth-secret.js` path), restarts, and verifies `/healthz` auth_configured — the secret never touches the browser, chat, or web tier. Spans the trust boundary: web tier mints a one-time `state` + stores a `provisioning_oauth_manifests` row + a PUBLIC callback stores GitHub's code (`state` is the auth); the control-plane runner drains `code_received` rows on its existing timer tick (gated on instance=active). Sign-in code unchanged — a GitHub App's user token hits the same `GET /user`; GitHub ignores the extra `scope` for Apps. Back-compat: existing OAuth-app instances untouched; manifest is the new default, manual path kept as fallback. All three onboarding surfaces (hall wizard one-click button, `bongos onboard`, `/new-project`) updated in lockstep.](<redacted>.md) | onboarding / auth |
211
- | 0134 | [Private-first npm distribution for the core + client (task [#2089](https://example.com/builders#/task/2089), BONGOS-V1, goal 35 / criterion 165; refines [#2025](https://example.com/builders#/task/2025), builds on ADR 0108). Owner wants the npm/CLI onboarding commands ready but private pre-launch, choosing paid private npm over free GitHub Packages. npm rule forces the shape: unscoped names are always public, only SCOPED packages can be private (paid plan) — so the private form stays scoped `@bongos/core` + `@bongos/client` (zero rename; org already owned). Decision: publish both as scoped PRIVATE packages now via `publishConfig:{access:"restricted"}` baked into the synthesized core `package.json` (`package-core.js buildPackageJson`) + the generated client `package.json` (`gen-api-client.js`); `package-core.js --publish` dry-runs by default, real publish needs `--publish --live` + owner `npm login` (tooling holds no token). Consumers auth via env-fed `.npmrc` `_authToken=<redacted> Vendored-tarball install (task 2053) stays the working default; provision/init registry auto-pull is a follow-up. Going public at launch is one flag (`npm access public`) or task 2025's unscoped vanity `cloudbongos` — 2025 becomes the go-public step, not superseded. The daily `bongos` command is name-independent throughout. Runbook: docs/recipes/private-npm-distribution.md.](<redacted>.md) | distribution / npm |
211
+ | 0134 | [Private-first npm distribution for the core + client (task [#2089](https://example.com/builders#/task/2089), BONGOS-V1, goal 35 / criterion 165; refines [#2025](https://example.com/builders#/task/2025), builds on ADR 0108). Owner wants the npm/CLI onboarding commands ready but private pre-launch, choosing paid private npm over free GitHub Packages. npm rule forces the shape: unscoped names are always public, only SCOPED packages can be private (paid plan) — so the private form stays scoped `@bongos/core` + `@bongos/client` (zero rename; org already owned). Decision: publish both as scoped PRIVATE packages now via `publishConfig:{access:"restricted"}` baked into the synthesized core `package.json` (`package-core.js buildPackageJson`) + the generated client `package.json` (`gen-api-client.js`); `package-core.js --publish` dry-runs by default, real publish needs `--publish --live` + owner `npm login` (tooling holds no token). Consumers auth via env-fed `.npmrc` `_authToken=<redacted> Vendored-tarball install (task 2053) stays the working default; provision/init registry auto-pull is a follow-up. Going public at launch is one flag (`npm access public`) or task 2025's unscoped vanity `cloudbongos` — 2025 becomes the go-public step, not superseded. The daily `bongos` command is name-independent throughout. Runbook: docs/recipes/private-npm-distribution.md. **CLIENT HALF SUPERSEDED** ([task 1003742](https://cloudbongos.com/builders#/task/1003742), 2026-09-09): `@bongos/client` is not an npm package at all — published once at 0.0.2 on 2026-07-07, never updated while the API moved 500+ core versions, and ZERO consumers the whole time (no package.json in the core or any instance repo ever declared it; every consumer vendors a copy, and `@cloudbongos/cli` could not have depended on a private package anyway). Removed from the registry; the generator no longer emits a `publishConfig` and its README documents vendoring. The generated artifact stays (ADR 0118). Core half unaffected.](<redacted>.md) | distribution / npm |
212
212
  | 0135 | [Module upstream submission: the review queue lives in the submitting instance's own DB, interim (task [#1770](https://example.com/builders#/task/1770), BONGOS-V1, builds on ADR 0107). ADR 0107 §1 says `bongos module submit` files a packaged module into a rank-gated review queue "on the core" — but the extracted core (`github.com/example-owner/cloud-bongos`) is a private GIT REPO, not a running service (no HTTP API), and per ADR 0100 §3 each project runs its OWN GDS DB, so there is no shared cross-repo queue to file into; R85 (the forcing-function second consumer) was also abandoned. Decision: the queue is a table (`module_submissions`, migration `<redacted>.sql`) in THIS instance's own Bongos DB. `bongos module submit <key>` and the hall's submit button both call the SAME route, `POST /modules/:key/submit` (metic+archon), which re-runs the ADR 0107 §4 pre-check against its OWN on-disk copy (authoritative — a local CLI pass is a courtesy) and refuses without a sign-off; `GET /modules/submissions` lists the queue for task 1771 (core-side review & accept, still backlog) to read from. Rejected: a live call to a not-yet-built core endpoint (guesses at 1771's contract); a GitHub Issue/PR against the private core repo (loses the structured queue, no standing credential); a dry-run-only local-file client mirroring do-api.js (a worse queue than a DB table this instance already operates). Forward-compatible: when a real core endpoint exists, filing becomes a network hop with no change to the CLI/UI contract.](<redacted>.md) | modules / upstreaming |
213
213
  | 0136 | [Update-channel subscription policy (task [#2150](https://example.com/builders#/task/2150), BONGOS-V1, goal 26; completes the update-consumption workstream on ADR 0100 §2 + ADR 0134 + task 2149). Answers "how do projects subscribe to regular @bongos/core updates?" with the owner-chosen policy: **patch-only by default, health-gated with auto-rollback, opt-in, autonomy-gated.** Per-instance `update_channel` = `pinned` (never) / `patch` (higher z within x.y — DEFAULT) / `minor` (higher y within the major); a MAJOR is never automatic, prereleases never auto-targeted — the pure math is `scripts/gds/update-channel.js` (`resolveChannelTarget`), unit-tested in `tests/update_channel.mjs`. The registry (npm view, ADR 0134) is the "what's newest" source. A new `mode:deterministic` instance-wide routine `core-update-subscription` reads the opt-in roster `config/update-subscriptions.json` (ships EMPTY) and runs `bongos upgrade --to <v> --registry` per subscribed instance with auto-rollback ON (task 2149). Triple-gated: autonomy flag OFF by default (ADR 0115) + empty roster + health-gated. Upstream auto-publish (whether main merges auto-publish a patch) is deliberately SPLIT to its own follow-up task, so goal 26 closes on the consumption half; the subscription just consumes whatever the owner publishes. Scope is local same-host instances; multi-host fleet orchestration is the deferred fleet-control-plane epic (task 1948). Rejected: a `provisioning_instances` DB column (presumes the still-unbuilt fleet-registry schema; a config manifest is migration-free + ADR-0115-shaped), minor-by-default (less conservative), deciding auto-publish here (orthogonal, would block consumption).](<redacted>.md) | distribution / updates |
214
214
  | 0137 | [Upstream publish policy: `@bongos/core` patches are published MANUALLY (owner-gated), NOT auto-published on merge (task [#2158](https://example.com/builders#/task/2158), BONGOS-V1, goal 26; the SUPPLY half split out of ADR 0136 §5 / task 2150, builds on ADR 0134). Owner chose manual over auto. Decision: the owner runs `package-core.js --publish --live` to cut a release (optional `core-v<version>` git tag as the auditable marker — triggers no CI); `CORE_VERSION` (src/module-api.js) is bumped by hand in-task with a changelog line (provenance stays in git, not a release-tool side effect); NO npm write-token at rest (preserves ADR 0134 tokenless tooling — owner auths at publish time; only the read token exists); the no-leak/mirror-redact gate is already fail-closed inside `package-core.js`, so `--publish` cannot upload a leaking artifact. The ADR 0136 subscription consumes whatever is published; freshness is owner-paced by design. Rejected: auto-publish-on-merge (needs a standing npm write-token as a CI secret — reverses the tokenless posture for little gain at low, deliberate release volume). Revisit as a tag-triggered CI publish when the managed-fleet epic (task 1948) makes the manual step a real bottleneck. Ritual: docs/recipes/private-npm-distribution.md "Cutting a core release".](<redacted>.md) | distribution / npm |
@@ -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
@@ -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,9 @@ 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)
1692
+ 1.19.621 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1693
+ landed since 1.19.620 with no explicit bump. run 34321546662. (task 1002620)
1690
1694
  ---------------------------------------------------------------------------
1691
1695
  ```
@@ -1,8 +1,10 @@
1
- # Recipe — private-first npm distribution (core + client)
1
+ # Recipe — private-first npm distribution (the core)
2
2
 
3
3
  > **This is one leg of three.** For the whole path from a change on core `main` to a live instance — and for when publishing to npm is *not* what moves an instance — start at [the core release pipeline](core-release-pipeline.md).
4
4
  >
5
- > Implements [ADR 0134](../adr/<redacted>.md). Pre-launch, `@bongos/core` and `@bongos/client` publish as **private, scoped** npm packages on the owner's paid `bongos` npm org (the canonical brand); at launch they flip to public. The daily command is `bongos` regardless of package name.
5
+ > Implements [ADR 0134](../adr/<redacted>.md), **core half only**. Pre-launch, `@bongos/core` publishes as a **private, scoped** npm package on the owner's paid `bongos` npm org (the canonical brand); at launch it flips to public. The daily command is `bongos` regardless of package name.
6
+ >
7
+ > **`@bongos/client` is no longer part of this.** It was published once (2026-07-07, 0.0.2), never updated while the API moved 500+ core versions, and had **zero** consumers the whole time — every consumer vendors a copy instead. The owner removed it from npm on 2026-09-09; ADR 0134's client half is superseded ([task 1003742](https://cloudbongos.com/builders#/task/1003742)). The generated client still exists and still regenerates — it is simply not a package.
6
8
  >
7
9
  > **Why private must be scoped:** on npm, *unscoped* names are always public — only *scoped* names (`@bongos/…`) can be private, and only on a paid plan. So the packages publish under the `@bongos` scope, which maps to the paid `bongos` org.
8
10
 
@@ -11,9 +13,10 @@
11
13
  | Package | What it is | Built by |
12
14
  |---|---|---|
13
15
  | `@bongos/core` | the platform an instance installs + the `bongos` CLI (`bin`) | `bongos package-core` (`scripts/gds/package-core.js`) — synthesizes the package.json + a pruned lockfile, redacts, and packs a `.tgz` |
14
- | `@bongos/client` | the generated typed API client | `node scripts/gds/gen-api-client.js` (regenerates `clients/bongos-client/`) |
15
16
 
16
- Both carry `publishConfig: { access: "restricted" }`, so **publishing them is private by default** — no public leak if a flag is forgotten.
17
+ `@bongos/core` carries `publishConfig: { access: "restricted" }`, so **publishing it is private by default** — no public leak if a flag is forgotten.
18
+
19
+ The generated typed client (`node scripts/gds/gen-api-client.js` → `clients/bongos-client/`) is deliberately **not** in this table: it is not published, carries no `publishConfig`, and is consumed by vendoring. See its own README.
17
20
 
18
21
  ## One-time owner setup (only the owner can do these)
19
22
 
@@ -80,9 +83,10 @@ Then, with `NPM_TOKEN` set in the environment:
80
83
 
81
84
  ```sh
82
85
  npm install -g @bongos/core # global → the `bongos` command, then `bongos init`
83
- npm install @bongos/client # in a project that calls the API
84
86
  ```
85
87
 
88
+ (A project that calls the API vendors the generated client instead — it is not installable.)
89
+
86
90
  Without a valid token + org membership the install 404s — that is the private gate working.
87
91
 
88
92
  ## Provisioning: registry pull vs vendored tarball (task 2090)
@@ -104,7 +108,6 @@ Pick either — both keep the daily `bongos` command working:
104
108
  - **Flip the same scoped packages public** (simplest):
105
109
  ```sh
106
110
  npm access public @bongos/core
107
- npm access public @bongos/client
108
111
  ```
109
112
  (Or change `publishConfig.access` to `"public"` and republish.) Public installs need no token; the paid seats can be dropped.
110
113
  - **Also publish the unscoped vanity name** for `npm install -g cloudbongos` — that is [task 2025](https://example.com/builders#/task/2025) (rename in `package-core.js`/`upgrade.js` + docs), done at launch.
@@ -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,
@@ -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.619",
3
+ "version": "1.19.621",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.619",
9
+ "version": "1.19.621",
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.619",
3
+ "version": "1.19.621",
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",
@@ -87,11 +87,12 @@ const FILES = [
87
87
  'src/bongos/api-prefix.js',
88
88
 
89
89
  // The generated typed API client (ADR 0118), which cli-lib.js reaches by a dynamic
90
- // `import()` of this exact path. Vendored as a single file rather than depended on: it is
91
- // published as `@bongos/client`, which is PRIVATE, so a public package that declared it as a
92
- // dependency would be uninstallable for everyone. index.mjs is self-contained and imports
93
- // nothing; the sibling package.json is deliberately NOT shipped (it carries
94
- // publishConfig.access=restricted and a nested manifest only confuses packing).
90
+ // `import()` of this exact path. Vendored as a single file rather than depended on: it is not
91
+ // published to npm at all (task 1003742 it was private, had zero consumers, and was removed),
92
+ // so there is nothing to declare a dependency ON. Vendoring was already the right call while it
93
+ // was merely private, since a public package cannot depend on a private one. index.mjs is
94
+ // self-contained and imports nothing; the sibling package.json is deliberately NOT shipped —
95
+ // a nested manifest only confuses packing.
95
96
  'clients/bongos-client/index.mjs',
96
97
 
97
98
  // Neutral branding defaults, so the CLI has sane strings with no instance checkout present.
@@ -10,8 +10,9 @@
10
10
  //
11
11
  // Design (mirrors gen-api-docs.js / ADR 0063): deterministic (stable sort, no
12
12
  // clock), zero runtime deps, --check gate. The client itself has ZERO deps — it
13
- // uses the platform `fetch` (Node 18+ / browsers), so `npm i @bongos/client`
14
- // pulls nothing transitive.
13
+ // uses the platform `fetch` (Node 18+ / browsers), so vendoring it pulls nothing
14
+ // transitive. It is NOT published to npm (task 1003742) — consumers copy the build
15
+ // they need; see the generated README.
15
16
  //
16
17
  // Usage:
17
18
  // node scripts/gds/gen-api-client.js # (re)generate the client package
@@ -372,10 +373,17 @@ function renderPackageJson(spec) {
372
373
  },
373
374
  files: ['index.mjs', 'index.cjs', 'index.d.ts', 'bongos-client.global.js', 'README.md'],
374
375
  license: 'AGPL-3.0-or-later',
375
- // Pre-launch: publish PRIVATE (task 2089 / ADR 0134). Scoped + access:"restricted"
376
- // = only paid-org members can install. Flip to "public" (or `npm access public
377
- // @bongos/client`) at launch, alongside the core.
378
- publishConfig: { access: 'restricted' },
376
+ // NO publishConfig this client is NOT an npm package (task 1003742).
377
+ //
378
+ // It was published once, 2026-07-07 at 0.0.2, under ADR 0134's private-first plan, and never
379
+ // updated while the API it is generated FROM moved 500+ core versions. It had zero consumers
380
+ // the whole time: nothing across the core or any instance repo ever declared it as a
381
+ // dependency — every consumer VENDORS a copy instead (modules/dev-box/app/src/vendor/, and
382
+ // build-cli-package.js into @cloudbongos/cli, which could not depend on it anyway). The owner
383
+ // removed it from the registry on 2026-09-09.
384
+ //
385
+ // The manifest itself stays: `main`/`types`/`exports` are what make the vendored copy and a
386
+ // workspace link resolve. Declaring a publish posture is the part that was fiction.
379
387
  sideEffects: false,
380
388
  }, null, 2) + '\n';
381
389
  }
@@ -427,14 +435,26 @@ function renderReadme(spec, ops) {
427
435
  `- API version: **${spec['x-api-version'] || 'v1'}** (served at \`${base}\`)`,
428
436
  `- ${ops.length} operations across ${new Set(ops.map((o) => o.tag)).size} resource groups`,
429
437
  ``,
430
- `## Install`,
438
+ `## Use it from your project`,
439
+ ``,
440
+ `**This is not an npm package** — it is not published, and nothing depends on it as one`,
441
+ `(task 1003742). Consume it by VENDORING the build you need, which is what every consumer`,
442
+ `in this repo already does:`,
431
443
  ``,
432
444
  '```bash',
433
- `npm install @bongos/client # internal registry / workspace path`,
445
+ `# ESM / bundler: cp clients/bongos-client/index.mjs <your project>/vendor/`,
446
+ `# CommonJS: cp clients/bongos-client/index.cjs <your project>/vendor/`,
447
+ `# Browser global: cp clients/bongos-client/bongos-client.global.js <your project>/public/`,
434
448
  '```',
435
449
  ``,
450
+ `Copy \`index.d.ts\` alongside it for types. Re-copy after \`gen-api-client.js\` runs, so the`,
451
+ `client cannot drift from the routes.`,
452
+ ``,
436
453
  `## Use`,
437
454
  ``,
455
+ `The examples import by package name, which resolves in a workspace. A VENDORED copy is`,
456
+ `imported by its path instead — \`from './vendor/index.mjs'\` — everything below is identical.`,
457
+ ``,
438
458
  '```js',
439
459
  `import { createClient, ApiError } from '@bongos/client';`,
440
460
  ``,
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.619'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
58
+ const CORE_VERSION = '1.19.621'; // 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
- assert.ok(/&& !hasTitle\) \{/.test(routesSrc), 'a title-only PATCH is a supported request, not no_supported_fields');
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
  ];