@bongos/core 1.19.150 → 1.19.152

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,11 +2,11 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/gds/package-core.js",
5
- "core_version": "1.19.150",
6
- "core_contract": "1.19.150",
7
- "source_commit": "ba28eaba03c65c990128c56929d0e30a18a2cb14",
5
+ "core_version": "1.19.152",
6
+ "core_contract": "1.19.152",
7
+ "source_commit": "57dc9f163cb5625cf780805c8beca257e40771a9",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-16T14:43:24.321Z",
9
+ "built_at": "2026-08-16T15:32:33.424Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 304,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 1579,
20
- "tree_sha256": "5429ba0237ab985ff79be4a1b9a03dd24cacd6260fdc604247b7eef8a36583dc",
20
+ "tree_sha256": "08344ee3c5fade79cafd7d56d0de9e5a08d542c27b67e1c7362213fb51fe6eb1",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -1177,7 +1177,7 @@
1177
1177
  {
1178
1178
  "path": "docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md",
1179
1179
  "mode": "0000644",
1180
- "sha256": "e4ddc6e640919691a6f6143493158ee080bb6e9edb9e6bf0b8cc24832fb9c3b2"
1180
+ "sha256": "6f3312ff0cdfdb824626375ca4e3e517e21f5f0f7087f579feee40e836bb3cc1"
1181
1181
  },
1182
1182
  {
1183
1183
  "path": "docs/adr/0144-federated-single-logout-backchannel.md",
@@ -1352,7 +1352,7 @@
1352
1352
  {
1353
1353
  "path": "docs/architecture.md",
1354
1354
  "mode": "0000644",
1355
- "sha256": "3967a6a48afbbd5e71d486f4f518f768fc3e3000ccd75ee310406a93977a3489"
1355
+ "sha256": "49747f42b1229ee8a2ecc54c317fc7173a91a4340ddfe2033e1a78b0f6ed1482"
1356
1356
  },
1357
1357
  {
1358
1358
  "path": "docs/branding-contract.md",
@@ -4187,12 +4187,12 @@
4187
4187
  {
4188
4188
  "path": "package-lock.json",
4189
4189
  "mode": "0000644",
4190
- "sha256": "86e259a725603b58d5659d630124568731893e6497d933327cbc29ab733955b8"
4190
+ "sha256": "bd55eac572b5c23a50a7e53328e42984a7b5f14a62bbe038648792c6ddf6b92f"
4191
4191
  },
4192
4192
  {
4193
4193
  "path": "package.json",
4194
4194
  "mode": "0000644",
4195
- "sha256": "cf358f722d2710aa11787645b256c890c6508e5c34ff735044ef8a9b28951d7e"
4195
+ "sha256": "e04229a40fcabca2604e6eec41b7b3c6a1c393701f2191f85b12bce1ac19faa1"
4196
4196
  },
4197
4197
  {
4198
4198
  "path": "public-docs/index.html",
@@ -4622,7 +4622,7 @@
4622
4622
  {
4623
4623
  "path": "scripts/gds/box.js",
4624
4624
  "mode": "0000644",
4625
- "sha256": "117b0ed6730e35559356403b50240d0ef5b6ab46752a8cf76de4e0a8ad244d42"
4625
+ "sha256": "c8a79e6541a2e26e590bd8ee1bbf31cb0f7300c53dbd30c241f4c1f04a2af7bf"
4626
4626
  },
4627
4627
  {
4628
4628
  "path": "scripts/gds/build-bongos.js",
@@ -5032,7 +5032,7 @@
5032
5032
  {
5033
5033
  "path": "scripts/gds/newcomer-floor.templates.json",
5034
5034
  "mode": "0000644",
5035
- "sha256": "5e680edbe89cab7876dd2c52ec506229044ea4b1483f00cdefeebc33cd53aae1"
5035
+ "sha256": "84abea5b31457611ed8fa27d33e492d513535ba373f47ba1144c06a56923d1d0"
5036
5036
  },
5037
5037
  {
5038
5038
  "path": "scripts/gds/newcomer-floor.test.js",
@@ -5782,7 +5782,7 @@
5782
5782
  {
5783
5783
  "path": "src/module-api.js",
5784
5784
  "mode": "0000644",
5785
- "sha256": "6cdc6cf28bcb58d18a41a575a253e9066e4fc6d1f86b8d1f7be60e12b99784db"
5785
+ "sha256": "31253b17c39aa10433fe72daffaf8e1591082cb5d8bbac470957be0b2aea125f"
5786
5786
  },
5787
5787
  {
5788
5788
  "path": "src/module-loader/catalog.js",
@@ -39,3 +39,6 @@ Stand up a **cloudbongos-native box plane** alongside (not replacing) the legacy
39
39
  2. **Cloudflare token scope.** Provision warned the ADR 0053 cookie-strip Transform Rule was **not** applied — `CLOUDFLARE_API_TOKEN` lacks "Transform Rules" edit scope on the cloudbongos.com zone. Add the scope (or apply the rule once in the dashboard) **before** other builders get boxes on `*.cloudbongos.com`, so platform cookies (`cookieDomain=<redacted> aren't sent to box subdomains.
40
40
  3. **Cosmetic `otb-` prefix.** Tunnel names (`otb-term-<login>`) and the droplet name (`otb-box-<login>`) are still hardcoded in `box-onboard.js`/`box.js`. Harmless (fleet separation is by tag, and DNS is per-zone), but a branding leak worth parameterizing.
41
41
  4. **Phase 4 — retire the legacy example plane** (owner decision) — **part (a) DONE in task 1002248 (2026-07-15).** Pre-checked read-only that the legacy fleet was empty (0 `otb-builder-box` droplets on DigitalOcean; cohost intent queue `ran=0`), then `systemctl disable --now` on the four `cohost-box-*` timers (intent-runner / idle-suspend / drift-reconcile / dormant-reclaim) on the control-plane host — now `disabled`/`inactive`; the native `cloudbongos-box-*` timers were left `enabled`/`active`. **Part (b) — disabling the dev-box module on example.com itself — NOT reachable from the control plane, flagged to owner:** example's web app + instance checkout are not co-located on the cloudbongos droplet (no local checkout, no ssh alias) and the local example GDS token is expired, so a durable module-disable needs owner access to example's own config/host. Harmless meanwhile: with the runners stopped there is no cost/security impact — worst case is a box request on the legacy instance that now hangs unfulfilled.
42
+ 5. **The control plane silently froze on core 1.19.2 — FIXED (task 1002738, 2026-08-16).** The four `cloudbongos-box-*` units were installed with `WorkingDirectory=/home/lars/cloudbongos` (§2 above), and `infra/core-node.sh` resolves the core from *that* root's `node_modules/@bongos/core` — pinned to the vendored `bongos-core-1.19.2.tgz`. The web app meanwhile moved to the npm registry pin and rode the nightly patch sweep to **1.19.146**. The two never converged, because `config/update-subscriptions.json` lists exactly ONE instance (`/home/lars/cloudbongos-instance`) and the box root was never added. Net effect: **every park/destroy/provision decision on this fleet was made by a core ~145 patches old**, so the whole point of tasks 1002726/1002727 — an idle box *parks* instead of being destroyed — was landed in `main`, released, deployed to the web app, and still **never executed**: `cmdSweepIdle` in 1.19.2 calls `deprovisionOne`. Nothing surfaced it; a shipped, released, deployed fix was simply inert.
43
+ **Fix.** A systemd drop-in on each of the four units re-points `WorkingDirectory` at `/home/lars/cloudbongos-instance` — the subscribed root the nightly sweep keeps current — and runs `node node_modules/@bongos/core/scripts/gds/box.js <cmd> --apply` directly. Same shape as the drop-ins already applied to `{selfhost,cohost}-provision-intent-runner`. The box lifecycle now inherits every core release with no second upgrade path to remember. The stale root is NOT retired: it still vendors `infra/` (the cloud-init + bring-up scripts the core package does not ship — follow-up 1 / ADR 0150), supplied to the units via `CLOUDBONGOS_BOX_INFRA_DIR=/home/lars/cloudbongos/infra`, the documented escape hatch that is candidate [#1](http://localhost:3000/builders#/task/1) in `boxInfraCandidates()`. Verified: all four commands dry-ran **identically** on old and new core before cutover (0 idle, 0 intents, DO⇄DB consistent, 0 dormant), then ran live under the drop-ins; the dormant sweep's new `warn >11d` window in the journal is the 1002727 code proving itself present.
44
+ **Why it stayed invisible, and the guard.** The sweep banner printed the *threshold* but never the *code*, so a stale plane looked identical to a current one in `journalctl`; diagnosing it needed a filesystem dig across two instance roots. `box.js` now prints one provenance line on every invocation — `box.js · running from @bongos/core@<version> · instance <root>` — naming the package it actually resolved. A frozen plane now announces itself in its own logs.
@@ -11,7 +11,7 @@
11
11
 
12
12
  - **Domain:** `example.com` — registered at Cloudflare Registrar. DNS A records (apex + `www`) point to the droplet at `REDACTED_IP`. Cloudflare proxy is **ON (orange cloud)**. CF terminates TLS at the edge; the **Cloudflare Origin Certificate** handles the CF↔origin leg (valid through 2041-05-04). A `status` subdomain is added at PMS-V1 deploy time (see [`docs/pms-v1-deploy.md`](pms-v1-deploy.md)) with the same proxy/cert posture; Origin Cert needs `*.example.com` SAN. Real client IPs come in via `CF-Connecting-IP` / `X-Forwarded-For`, wired into Caddy's `trusted_proxies static` (the Cloudflare IP ranges) in the `infra/Caddyfile` global options block.
13
13
  - **Production droplet (co-hosting box):** DigitalOcean, hostname `example`, IPv4 `REDACTED_IP`, IPv6 `REDACTED_IP`. NYC3, Premium Intel 2 GB / 1 vCPU / 60 GB NVMe. Ubuntu 24.04 LTS. ~$14/mo. Hosts the OTB game + co-hosted project instances (`emersonian-circles.`, `staging.`).
14
- - **Cloud Bongos control-plane droplet (NEW 2026-07-05, [ADR 0126](adr/<redacted>.md), task [#2063](https://example.com/builders#/task/2063)):** DigitalOcean, hostname `cloudbongos`, IPv4 `REDACTED_IP`, NYC3, `s-1vcpu-2gb` (2 GB / 1 vCPU), Ubuntu 24.04, ~$12/mo, tag `cloudbongos-control`. Runs **cloudbongos.com + `builders.` + `status.`** (`src/platform-server.js`, Cloud Bongos brand, port 3002, own `cloudbongos` DB, checkout `/home/lars/cloudbongos`) and is the **sole token-holding control plane** — `/etc/cloudbongos/box.env` holds the account-wide Cloudflare token (IP-locked to this box) + the DigitalOcean token; this is where `provision.js` provisions instance subdomains. **Structurally separate SSH** (own `cloudbongos_ed25519` key). IPv6 egress disabled so the IPv4-locked CF token can't be spuriously rejected. cloudbongos.com was previously a co-tenant on the co-hosting box; that unit was decommissioned at cutover. **Follow-ups:** deploy automation (task [#2064](https://example.com/builders#/task/2064)) + fully stripping cloud tokens off the co-hosting box (task [#2065](https://example.com/builders#/task/2065)).
14
+ - **Cloud Bongos control-plane droplet (NEW 2026-07-05, [ADR 0126](adr/<redacted>.md), task [#2063](https://example.com/builders#/task/2063)):** DigitalOcean, hostname `cloudbongos`, IPv4 `REDACTED_IP`, NYC3, `s-1vcpu-2gb` (2 GB / 1 vCPU), Ubuntu 24.04, ~$12/mo, tag `cloudbongos-control`. Runs **cloudbongos.com + `builders.` + `status.`** (`src/platform-server.js`, Cloud Bongos brand, port 3002, own `cloudbongos` DB, checkout `/home/lars/cloudbongos-instance`, the root subscribed to the nightly core-patch sweep) and is the **sole token-holding control plane** — a SECOND root, `/home/lars/cloudbongos`, survives only to vendor `infra/` (the box cloud-init the core package does not ship, ADR 0150); it is not subscribed, so nothing may resolve core code from it (task 1002738, [ADR 0144](adr/<redacted>.md) follow-up 5) — `/etc/cloudbongos/box.env` holds the account-wide Cloudflare token (IP-locked to this box) + the DigitalOcean token; this is where `provision.js` provisions instance subdomains. **Structurally separate SSH** (own `cloudbongos_ed25519` key). IPv6 egress disabled so the IPv4-locked CF token can't be spuriously rejected. cloudbongos.com was previously a co-tenant on the co-hosting box; that unit was decommissioned at cutover. **Follow-ups:** deploy automation (task [#2064](https://example.com/builders#/task/2064)) + fully stripping cloud tokens off the co-hosting box (task [#2065](https://example.com/builders#/task/2065)).
15
15
  - **Server stack:** Node.js 22 LTS · PostgreSQL 16 · Caddy 2.11.2. `example.service` (systemd) runs `node server.js` as user `lars` on `127.0.0.1:3000`; restart-on-failure.
16
16
  - **Hardening:** `lars` user with passwordless sudo; SSH key-only (root login disabled, password auth disabled); ufw allows 22/80/443; fail2ban active; unattended-upgrades enabled; timezone `America/New_York`.
17
17
  - **TLS:** Origin cert at `/etc/caddy/origin.crt` + `/etc/caddy/origin.key` for the example apexes on the co-hosting box. **cloudbongos.com's apexes moved to the dedicated control-plane droplet** ([ADR 0126](adr/<redacted>.md)) with their own copy of the Cloudflare Origin Cert (SAN `*.cloudbongos.com, cloudbongos.com`) at `/etc/caddy/cloudbongos-origin.{crt,key}` on `REDACTED_IP`. Cloudflare SSL/TLS mode = Full (strict). Renewal is a non-issue for ~15 years. **Provisioned instance hosts use a different, coexisting posture** ([ADR 0111](adr/<redacted>.md) §4 / task P4): Caddy **on-demand ACME** (Let's Encrypt) via the global `on_demand_tls { ask … }` block, gated to hostnames the `provisioning` module actually stood up (`/api/bongos/provisioning/tls-check`). Their DNS records are **proxied=false (grey cloud)** so the ACME challenge reaches Caddy — the opposite of the origin-cert apexes; do not enable the proxy on them or renewal breaks silently ([ADR 0003](adr/<redacted>.md)). Per-instance site snippets live in `/etc/caddy/sites/*.caddy` (`import`ed by `infra/Caddyfile`). **Tradeoff (accepted, SEC review of task 1945):** `proxied=false` publishes the box's real origin IP in public DNS for every co-tenant provisioned hostname — defeating, for those names, the Cloudflare-proxy IP-hiding the apexes enjoy (a co-tenant host resolves to the same `REDACTED_IP` that serves the main game). This is inherent to on-demand ACME (the challenge must hit the origin) and acceptable under the budget/trust model; prefer a **dedicated droplet** (its own IP) for any instance expected to draw hostile attention.
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.150",
3
+ "version": "1.19.152",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.150",
9
+ "version": "1.19.152",
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.150",
3
+ "version": "1.19.152",
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",
@@ -1447,11 +1447,38 @@ Named browser-terminal tunnel (#771, ADR 0038 — OPT-IN, default OFF):
1447
1447
  // never imports it, it is always spawned) it exposes the orchestration fns so the
1448
1448
  // dry-run/live contract can be exercised with injected fakes (#1034). The guard
1449
1449
  // keeps a require() from running the CLI + calling process.exit.
1450
+ // provenance() — WHICH core is making this fleet decision, and from which root.
1451
+ //
1452
+ // Task 1002738: the four cloudbongos-box-* units ran with WorkingDirectory pointed at
1453
+ // a root whose vendored @bongos/core was 1.19.2 while the web app had long since moved
1454
+ // to 1.19.146 — so every park/destroy/provision decision was made by a core ~145
1455
+ // patches behind, including a cmdSweepIdle that still DESTROYED an idle box. Nothing
1456
+ // in the journal said so: the sweep banner reports the THRESHOLD but never the code.
1457
+ // Diagnosing it took a filesystem dig across two instance roots.
1458
+ //
1459
+ // One line, printed by every invocation, makes that failure self-announcing: a stale
1460
+ // plane now says its own version in `journalctl -u cloudbongos-box-idle-suspend`, next
1461
+ // to the instance root that supplied it. Cheap, unconditional, and it can only be
1462
+ // wrong if the package it reads is the package it ran.
1463
+ // Names the package REPO_ROOT resolved to, not just its version: installed, that is
1464
+ // `@bongos/core@1.19.148`; in a monolith checkout it is the instance's own package, and
1465
+ // saying so is the point — the line must never let a monolith masquerade as a core pin.
1466
+ function provenance() {
1467
+ let pkg = 'unknown';
1468
+ try {
1469
+ const p = require(path.join(REPO_ROOT, 'package.json'));
1470
+ pkg = `${p.name || 'unknown'}@${p.version || 'unknown'}`;
1471
+ } catch { /* a core with no readable package.json still runs — just say unknown */ }
1472
+ const inst = instanceConfig.resolveInstanceRootExplicit() || process.cwd();
1473
+ return `box.js · running from ${pkg} · instance ${inst}`;
1474
+ }
1475
+
1450
1476
  async function main() {
1451
1477
  const args = process.argv.slice(2);
1452
1478
  const cmd = args[0];
1453
1479
  APPLY = hasFlag(args, '--apply');
1454
1480
  if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') { usage(); process.exit(0); }
1481
+ console.log(provenance());
1455
1482
 
1456
1483
  const known = ['provision', 'onboard', 'park', 'wake', 'deprovision', 'run-intents', 'sweep-idle', 'sweep-dormant', 'reconcile', 'reconcile-drift', 'status'];
1457
1484
  if (!known.includes(cmd)) { console.error(`unknown command: ${cmd}\n`); usage(); process.exit(2); }
@@ -1,5 +1,5 @@
1
1
  {
2
- "_doc": "The evergreen newcomer chores, grouped BY DISCIPLINE. Each discipline maps to a LIST of chore templates (was a single object before task 1488 — a discipline can now offer several first-builder chores so newcomers are not funnelled into one). The deterministic replenisher (scripts/gds/newcomer-floor.js) and the on-claim restock (src/bongos/newcomer-restock.js) stamp disposable claimable COPIES from each template; the TEMPLATE is never claimed and never ships. Each copy's source_ref is tmpl:<discipline>:<template_id>:<seq> (monotonic), so identical-title copies never collide with the dedup. Per-template `floor` sets that chore’s standing pool depth; if a template omits it, the discipline default in newcomer-floor.config.json applies. credits_reward, est_minutes and floor all live here (per chore). version_id is per-template so each chore lands on the correct track (engineer=internal GDS-V4, artist+ideator=product V2); it MUST point at an open (non-shipped) version — update it at version close. touches[] is intentionally empty so multiple same-discipline newcomers can claim copies concurrently; git-merge is the real collision detector. To ADD a chore: append an entry to the discipline's array. To CHANGE one: edit it here — no code change needed. Engineer offers a SAFE coding chore (add a unit test — touches only tests/, which auto-ships) and the docs-hygiene sweep (constrained to non-protected docs so a sub-Metic ship does not stall on the authority core).",
2
+ "_doc": "The evergreen newcomer chores, grouped BY DISCIPLINE. Each discipline maps to a LIST of chore templates (was a single object before task 1488 — a discipline can now offer several first-builder chores so newcomers are not funnelled into one). The deterministic replenisher (scripts/gds/newcomer-floor.js) and the on-claim restock (modules/onboarding/newcomer-restock.js) stamp disposable claimable COPIES from each template; the TEMPLATE is never claimed and never ships. Each copy's source_ref is tmpl:<discipline>:<template_id>:<seq> (monotonic), so identical-title copies never collide with the dedup. Per-template `floor` sets that chore’s standing pool depth; if a template omits it, the discipline default in newcomer-floor.config.json applies. credits_reward, est_minutes and floor all live here (per chore). version_id is per-template so each chore lands on the correct track (engineer=internal GDS-V4, artist+ideator=product V2); it MUST point at an open (non-shipped) version — update it at version close. touches[] is intentionally empty so multiple same-discipline newcomers can claim copies concurrently; git-merge is the real collision detector. To ADD a chore: append an entry to the discipline's array. To CHANGE one: edit it here — no code change needed. Engineer offers a SAFE coding chore (add a unit test — touches only tests/, which auto-ships) and the docs-hygiene sweep (constrained to non-protected docs so a sub-Metic ship does not stall on the authority core).",
3
3
  "engineer": [
4
4
  {
5
5
  "template_id": "unit-test-pass",
@@ -10,7 +10,7 @@
10
10
  "credits_reward": 15,
11
11
  "floor": 2,
12
12
  "title": "Add a unit test for an untested pure exported helper (assert its real behaviour)",
13
- "description": "## What you're doing\n\nThe repo's safety net is its **unit suite** — every `tests/*.mjs` file, run by `scripts/gds/run-unit-tests.js` in CI on every PR. New pure tests are **auto-discovered** (there is no allowlist to edit), so the moment you add one it joins the gate. Your first ship as an engineer: pick ONE exported helper that has thin or no test coverage and add a focused, DB-free unit test for it.\n\nIt is identical for every newcomer and **always valuable** — coverage is never \"done\", the codebase has hundreds of small pure helpers, and a good test both guards behaviour and teaches you how a corner of the system works. It is also **safe to auto-ship**: you add ONE file under `tests/`, which is not a protected surface, so a clean test merges on green with no owner approval.\n\n## How to pick a target (a PURE helper)\n\nPick a **pure** function — deterministic, with no database, network, filesystem, or live-server dependency — so the test runs standalone. Good hunting grounds (look for an exported function with little or no matching `tests/` coverage):\n- `src/bongos/path-match.js` (`touchesOverlap`, `matchOne`), `src/bongos/llm-pricing.js` (`familyFor`, `canonicalizeModelId`), `src/bongos/task-classifier.js`, `src/bongos/render-prefs.js`, `src/bongos/wandering-prefs.js`.\n- pure parsing/format helpers exported from `scripts/gds/*.js`.\n\nCheck `tests/` first: if a helper already has a thorough test file, add a missing case to a DIFFERENT helper, or pick another module — don't duplicate existing coverage.\n\n## How to write it (mirror an existing test — there is no framework)\n\nThese tests are plain Node scripts using `node:assert`; there is no test runner. Open a small existing one as your model — for example `tests/criterion_ref_parse.mjs` or `tests/task_classifier_vocab.mjs` — and copy the shape:\n1. `import` / `require` the helper from its module.\n2. Assert **several** cases, including at least one **edge / boundary** case (empty input, null, a tricky value).\n3. Print a one-line success message at the end (an assert that throws exits non-zero, which is how the gate sees a failure).\n\nName the file `tests/<helper-or-module>.mjs`.\n\n## Verify + ship\n\nRun BOTH and confirm both are green:\n- `node tests/<your-file>.mjs` — your test alone, exits 0.\n- `node scripts/gds/run-unit-tests.js` — the whole gate, with your test auto-included.\n\nThen `/builder-ship`. The ship notes must name the helper and file you tested.\n\n## Stay safe (off-limits)\n\nAdd exactly ONE new file under `tests/`. Do NOT edit `scripts/gds/run-unit-tests.js` (it auto-discovers your test, and it is a protected file you may not touch), and do NOT change the helper you are testing. Your test must be **DB-free**: anything needing Postgres, the network, or a live server belongs in the integration gate and will fail standalone here. Touch no permission, pipeline, migration, or infra file.",
13
+ "description": "## What you're doing\n\nThe repo's safety net is its **unit suite** — every `tests/*.mjs` file, run by `scripts/gds/run-unit-tests.js` in CI on every PR. New pure tests are **auto-discovered** (there is no allowlist to edit), so the moment you add one it joins the gate. Your first ship as an engineer: pick ONE exported helper that has thin or no test coverage and add a focused, DB-free unit test for it.\n\nIt is identical for every newcomer and **always valuable** — coverage is never \"done\", the codebase has hundreds of small pure helpers, and a good test both guards behaviour and teaches you how a corner of the system works. It is also **safe to auto-ship**: you add ONE file under `tests/`, which is not a protected surface, so a clean test merges on green with no owner approval.\n\n## How to pick a target (a PURE helper)\n\nPick a **pure** function — deterministic, with no database, network, filesystem, or live-server dependency — so the test runs standalone. Good hunting grounds (look for an exported function with little or no matching `tests/` coverage):\n- `src/bongos/path-match.js` (`touchesOverlap`, `matchOne`), `src/bongos/llm-pricing.js` (`familyFor`, `canonicalizeModelId`), `modules/lifecycle/task-classifier.js`, `modules/builder-settings/render-prefs.js`, `modules/builder-settings/wandering-prefs.js`.\n- pure parsing/format helpers exported from `scripts/gds/*.js`.\n\nCheck `tests/` first: if a helper already has a thorough test file, add a missing case to a DIFFERENT helper, or pick another module — don't duplicate existing coverage.\n\n## How to write it (mirror an existing test — there is no framework)\n\nThese tests are plain Node scripts using `node:assert`; there is no test runner. Open a small existing one as your model — for example `tests/criterion_ref_parse.mjs` or `tests/task_classifier_vocab.mjs` — and copy the shape:\n1. `import` / `require` the helper from its module.\n2. Assert **several** cases, including at least one **edge / boundary** case (empty input, null, a tricky value).\n3. Print a one-line success message at the end (an assert that throws exits non-zero, which is how the gate sees a failure).\n\nName the file `tests/<helper-or-module>.mjs`.\n\n## Verify + ship\n\nRun BOTH and confirm both are green:\n- `node tests/<your-file>.mjs` — your test alone, exits 0.\n- `node scripts/gds/run-unit-tests.js` — the whole gate, with your test auto-included.\n\nThen `/builder-ship`. The ship notes must name the helper and file you tested.\n\n## Stay safe (off-limits)\n\nAdd exactly ONE new file under `tests/`. Do NOT edit `scripts/gds/run-unit-tests.js` (it auto-discovers your test, and it is a protected file you may not touch), and do NOT change the helper you are testing. Your test must be **DB-free**: anything needing Postgres, the network, or a live server belongs in the integration gate and will fail standalone here. Touch no permission, pipeline, migration, or infra file.",
14
14
  "done_when": [
15
15
  "A NEW tests/<name>.mjs file was added (it did not exist before) that unit-tests a PURE exported helper — no Postgres, network, filesystem, or live server.",
16
16
  "`node tests/<name>.mjs` exits 0 and the test makes real assertions about the helper's behaviour — at least two cases including one edge/boundary case — not a trivial always-true assertion.",
package/src/module-api.js CHANGED
@@ -727,8 +727,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
727
727
  // landed since 1.19.148 with no explicit bump. run 31953207683. (task 1002620)
728
728
  // 1.19.150 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
729
729
  // landed since 1.19.149 with no explicit bump. run 31953504697. (task 1002620)
730
+ // 1.19.151 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
731
+ // landed since 1.19.150 with no explicit bump. run 31953827282. (task 1002620)
732
+ // 1.19.152 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
733
+ // landed since 1.19.151 with no explicit bump. run 31955923444. (task 1002620)
730
734
  // ---------------------------------------------------------------------------
731
- const CORE_VERSION = '1.19.150'; // CI auto-patch carrier (ADR 0161); see changelog above
735
+ const CORE_VERSION = '1.19.152'; // CI auto-patch carrier (ADR 0161); see changelog above
732
736
 
733
737
  // A namespaced logger so a module's log lines are attributable + consistent.
734
738
  // Usage: const log = api.logger('dev-box'); log.info('mounted');