@bongos/core 1.20.32 → 1.20.34

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.20.32",
6
- "core_contract": "1.20.32",
7
- "source_commit": "22743b01c499a1cc3bd285e9b25d6efe937aa8a7",
5
+ "core_version": "1.20.34",
6
+ "core_contract": "1.20.34",
7
+ "source_commit": "b6e2dfd60ddc3de92e31e4ca330985ae253bedab",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-30T21:45:12.402Z",
9
+ "built_at": "2026-09-30T22:16:46.560Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 562,
13
13
  "agent_docs_stubbed": 25,
14
- "functional_verbatim": 2669,
14
+ "functional_verbatim": 2671,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 3257,
20
- "tree_sha256": "7de0ec4831b06324127256eef3791a481ef07b33654cd0ca8b2cc058ec0e14ef",
19
+ "file_count": 3259,
20
+ "tree_sha256": "91cd2d81b5a990188e0fc5b9230e3f322cf350277f64f07a5d3db549627845bf",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -617,7 +617,7 @@
617
617
  {
618
618
  "path": "docs/adr/0042-builder-self-deploy-ci-auto-merge.md",
619
619
  "mode": "0000644",
620
- "sha256": "9d51518e14f6a7818f2f6504519f4f3159296f78d7b89409361d459c0974f5db"
620
+ "sha256": "1a2c7e3b5ebeb50991f65dfe4032dab10de8be0848193c3daf9baae9aed4e314"
621
621
  },
622
622
  {
623
623
  "path": "docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md",
@@ -2257,7 +2257,7 @@
2257
2257
  {
2258
2258
  "path": "docs/adr/README.md",
2259
2259
  "mode": "0000644",
2260
- "sha256": "d21d0eb8389864b4f7394ba0f4b0d0d809182f0bef82d9fc6824edd00e174b39"
2260
+ "sha256": "17dbc3a335653ba36f2e415c44eced76d94011585477a079765dd1f5fad36d86"
2261
2261
  },
2262
2262
  {
2263
2263
  "path": "docs/api-reference.md",
@@ -2802,7 +2802,7 @@
2802
2802
  {
2803
2803
  "path": "docs/module-api-changelog.md",
2804
2804
  "mode": "0000644",
2805
- "sha256": "bd97221b57256af19ec7b09b897cce082e54fca07e69500ccf485feba106317f"
2805
+ "sha256": "974525f923ba04ab5b3fe9a0746a4ec6c859a30e4aebbb55a223e75e9c60689c"
2806
2806
  },
2807
2807
  {
2808
2808
  "path": "docs/modules-contract.md",
@@ -2912,7 +2912,7 @@
2912
2912
  {
2913
2913
  "path": "docs/recipes/autobongos-windows-host.md",
2914
2914
  "mode": "0000644",
2915
- "sha256": "e70faa969725827307388ccb0620bde203500333ed299d860ca7a001cc8577a1"
2915
+ "sha256": "5e4821f62223ffc49cef22475f7c31d5bb798bde167ed03823931694f15060d7"
2916
2916
  },
2917
2917
  {
2918
2918
  "path": "docs/recipes/bongos-cli-release.md",
@@ -3057,7 +3057,7 @@
3057
3057
  {
3058
3058
  "path": "docs/recipes/upgrading-the-core.md",
3059
3059
  "mode": "0000644",
3060
- "sha256": "6d44cfdcd7fa92d3b6c6514af69356d6e986d5443219251cd091685918e4e1f2"
3060
+ "sha256": "81df5ffa3f55b075b284c51790fd1fea78ae7cab6af46460fcbf648987f7eb11"
3061
3061
  },
3062
3062
  {
3063
3063
  "path": "docs/recipes/windows-builders.md",
@@ -4077,7 +4077,7 @@
4077
4077
  {
4078
4078
  "path": "modules/autonomy/cadence.js",
4079
4079
  "mode": "0000644",
4080
- "sha256": "8c482afe460f9a9a8315b549c8e48c35e42dc05ce76e708baa24133046917af0"
4080
+ "sha256": "e2ab1fd3381fa94af9966a036467ddcffe004cec6e4c07cc25da29b9f7aa4446"
4081
4081
  },
4082
4082
  {
4083
4083
  "path": "modules/autonomy/db.js",
@@ -4087,7 +4087,7 @@
4087
4087
  {
4088
4088
  "path": "modules/autonomy/digest.js",
4089
4089
  "mode": "0000644",
4090
- "sha256": "cfe22a887e5a69cf314a318f7b410c81f91cfba65f7643605e9f3ac1ebef8022"
4090
+ "sha256": "f40114fde91ae3453ed58cd4c37d0ce4b292ae1a84b3faa6af62c8f2e500a33c"
4091
4091
  },
4092
4092
  {
4093
4093
  "path": "modules/autonomy/fence.js",
@@ -4097,7 +4097,7 @@
4097
4097
  {
4098
4098
  "path": "modules/autonomy/gauge.js",
4099
4099
  "mode": "0000644",
4100
- "sha256": "8a126f3c93b41c18971c8bc07d424446db5c6179ad9fb1aaaad03c10d5b5f9c9"
4100
+ "sha256": "5cbbe803f8c977f49d715cb803fa7dc20bd1800e599659669c5fe28f4f9b9b20"
4101
4101
  },
4102
4102
  {
4103
4103
  "path": "modules/autonomy/migrations/autonomy_001_autobongos_fence.sql",
@@ -8992,12 +8992,12 @@
8992
8992
  {
8993
8993
  "path": "package-lock.json",
8994
8994
  "mode": "0000644",
8995
- "sha256": "cd508994cbd9d44ef721787e0eb7c803bfeda98459587d53ba726a36cff2581e"
8995
+ "sha256": "59289cb04f2457826ed40890631dffdbf24e079c5af32938989be59732703cd6"
8996
8996
  },
8997
8997
  {
8998
8998
  "path": "package.json",
8999
8999
  "mode": "0000644",
9000
- "sha256": "5c53762b7665282aeca491b037cb2c02ee8c6def6c00018262596235d02bbb64"
9000
+ "sha256": "95aa8bfbee50b46e7eb01de527366c0ed88c2d262b00f1550a7eb177cf90967d"
9001
9001
  },
9002
9002
  {
9003
9003
  "path": "public-docs/index.html",
@@ -9017,7 +9017,7 @@
9017
9017
  {
9018
9018
  "path": "release-notes.json",
9019
9019
  "mode": "0000644",
9020
- "sha256": "1f7d261d95f2fbaf1d0e438e9c3ad7f13773c5b56d349263815d5be03ebab401"
9020
+ "sha256": "f93660db46a480e8ed3fd8302937423a3b69843192b2e052ddd4c3e17e0614b0"
9021
9021
  },
9022
9022
  {
9023
9023
  "path": "scripts/bongos-mcp.js",
@@ -9172,12 +9172,12 @@
9172
9172
  {
9173
9173
  "path": "scripts/gds/autobongos-loop.js",
9174
9174
  "mode": "0000644",
9175
- "sha256": "3415d46e27c2a198180744425f70084c72bfe0b11253f79fd8667604f2e91c8c"
9175
+ "sha256": "6aba5f9b059680ea802993d1d79dc24caa9e16de35fdc334344dbec27d82fad0"
9176
9176
  },
9177
9177
  {
9178
9178
  "path": "scripts/gds/autobongos-run.js",
9179
9179
  "mode": "0000644",
9180
- "sha256": "c8e6967fe75be8573e65c1fd73caec6912aeb75648dfdb3c82a04cd7bc7f6a6d"
9180
+ "sha256": "ac0d77b54920e50205546db4269957fd6a0fc2f1ba1aca9c097abc920fa04f2a"
9181
9181
  },
9182
9182
  {
9183
9183
  "path": "scripts/gds/autobongos-service.cmd",
@@ -9312,7 +9312,7 @@
9312
9312
  {
9313
9313
  "path": "scripts/gds/claim.js",
9314
9314
  "mode": "0000644",
9315
- "sha256": "78a7e407218051892c1aa0934474084deec94768fdd28fe2c3fa447fe48905fd"
9315
+ "sha256": "77c5d63df2ba881c11e184f11e7972527bac310d856a82d0d7cd234de514665c"
9316
9316
  },
9317
9317
  {
9318
9318
  "path": "scripts/gds/classify-discipline.js",
@@ -9864,6 +9864,11 @@
9864
9864
  "mode": "0000644",
9865
9865
  "sha256": "2bb8d87422e2b24db1ea9fa0c51556b09a7bea99da5af7ad3798c3b386809f4b"
9866
9866
  },
9867
+ {
9868
+ "path": "scripts/gds/move-escalation.js",
9869
+ "mode": "0000644",
9870
+ "sha256": "de4251cf593b77a9dee3590de55319127b0e84d3bb9560ce7f07ee3f4628a23e"
9871
+ },
9867
9872
  {
9868
9873
  "path": "scripts/gds/newcomer-floor.config.json",
9869
9874
  "mode": "0000644",
@@ -10037,7 +10042,7 @@
10037
10042
  {
10038
10043
  "path": "scripts/gds/provision.js",
10039
10044
  "mode": "0000644",
10040
- "sha256": "dd972cc62ed8282af5f1239a3b728761fb06a414120cac8a39b38838e8301f7d"
10045
+ "sha256": "a014e09f61a19cf16380ae35d5ab93661b65e3fd037d7fddc0fe5ab046a6e699"
10041
10046
  },
10042
10047
  {
10043
10048
  "path": "scripts/gds/publish-credential-check.js",
@@ -11152,7 +11157,7 @@
11152
11157
  {
11153
11158
  "path": "src/module-api.js",
11154
11159
  "mode": "0000644",
11155
- "sha256": "a1f03380a933a2c9fdcd63f55f0152bf7d36101135d171c729f7cd79c9ef0ec4"
11160
+ "sha256": "df86f50ae0baa2f659cc454dfd08b57841fe02ae2704c4a40c425f36046d794c"
11156
11161
  },
11157
11162
  {
11158
11163
  "path": "src/module-loader/catalog.js",
@@ -11597,7 +11602,7 @@
11597
11602
  {
11598
11603
  "path": "tests/autobongos_cadence.mjs",
11599
11604
  "mode": "0000644",
11600
- "sha256": "98f86c0f7cb84ecfe955f74a066326d82e8af1008c17d0a28c0ad40042f876b8"
11605
+ "sha256": "67b8d8868952172945c1db8f6fa2fe2e3a28212c49afc2b2378e0801df000441"
11601
11606
  },
11602
11607
  {
11603
11608
  "path": "tests/autobongos_fence.mjs",
@@ -11612,7 +11617,7 @@
11612
11617
  {
11613
11618
  "path": "tests/autobongos_loop.mjs",
11614
11619
  "mode": "0000644",
11615
- "sha256": "e14d2c211c28ebada8e915d0d336989e74410afb68924d9b89dc8efd666457c8"
11620
+ "sha256": "91e92c46fe27a4382a762029418437642b038a77286de0977ac694397759a0b6"
11616
11621
  },
11617
11622
  {
11618
11623
  "path": "tests/autobongos_service_ps1.mjs",
@@ -11632,7 +11637,7 @@
11632
11637
  {
11633
11638
  "path": "tests/autonomy_digest.mjs",
11634
11639
  "mode": "0000644",
11635
- "sha256": "633ede3096916c1d5571def211fabac9ae934f48723d182c54f2fe27c2ead105"
11640
+ "sha256": "928a8a0461adab72037a03d184966e7c1e20838e72421b0ed93c6ce5b8c84520"
11636
11641
  },
11637
11642
  {
11638
11643
  "path": "tests/autonomy_gate.mjs",
@@ -11837,7 +11842,7 @@
11837
11842
  {
11838
11843
  "path": "tests/claim_error_surface.mjs",
11839
11844
  "mode": "0000644",
11840
- "sha256": "ad5b5b24942ff31930bb1c718e9a4ac7f8934dfe5d016225b6f7a3911ae50d36"
11845
+ "sha256": "7678a98254284a541684292b3615bd02729003dfddbdd9d7b24bd895f63fb908"
11841
11846
  },
11842
11847
  {
11843
11848
  "path": "tests/claim_from_task_and_home.mjs",
@@ -11912,7 +11917,7 @@
11912
11917
  {
11913
11918
  "path": "tests/cli_exit_no_abort.mjs",
11914
11919
  "mode": "0000644",
11915
- "sha256": "39b63a76b153426f486240e4c57d4fafb2b5b130fb9853c800d20d9f4e5acdde"
11920
+ "sha256": "bce64eee77c2fb5af0fdab2a1c4b7d59cd1465817724ca7065572ba466a16fe5"
11916
11921
  },
11917
11922
  {
11918
11923
  "path": "tests/cli_lib_formatters.mjs",
@@ -14224,6 +14229,11 @@
14224
14229
  "mode": "0000644",
14225
14230
  "sha256": "ee165bd5e817b909be4299014646d3c8b5e34bf00854491ea42cc5afc663fdc3"
14226
14231
  },
14232
+ {
14233
+ "path": "tests/move_escalation.mjs",
14234
+ "mode": "0000644",
14235
+ "sha256": "0406212bd27e4b878b27414625071b646e4e023af1694f8fc2449fdc61be243c"
14236
+ },
14227
14237
  {
14228
14238
  "path": "tests/my_projects_surface.mjs",
14229
14239
  "mode": "0000644",
@@ -14467,7 +14477,7 @@
14467
14477
  {
14468
14478
  "path": "tests/plain_cards.mjs",
14469
14479
  "mode": "0000644",
14470
- "sha256": "ccf7558194db8894811399aba8ec548cbb4a74b474ff6fd534d496bf98cf516f"
14480
+ "sha256": "731956f3836cd73878668d0d2707f18eaec64ed70495ce1beaf5410d3fb9af8a"
14471
14481
  },
14472
14482
  {
14473
14483
  "path": "tests/planning_session_skill.mjs",
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Date:** 2026-06-07
4
4
  **Context:** GDS-V3, task [#679](https://example.com/builders#/task/679) (cut prod deploy over to CI). Completes the [#597](https://example.com/builders#/task/597) / [ADR 0031](0031-cloud-dev-environments-for-builders.md) §6 goal — *no builder box holds the production droplet SSH key*. Extends [ADR 0031](0031-cloud-dev-environments-for-builders.md) §6; re-applies [ADR 0016](0016-trust-boundary-server-enforced-permissions.md) to CI.
5
- **Status:** Accepted — **armed in production 2026-06-13** (interim, grader-off variant — task [#859](https://example.com/builders#/task/859); `config/deploy.json` `mode: ci`; see the *Arming record* below + session log [`<redacted>.md`](../session-logs/<redacted>.md)).
5
+ **Status:** Accepted — **armed in production 2026-06-13** (interim, grader-off variant — task [#859](https://example.com/builders#/task/859); `config/deploy.json` `mode: ci`; see the *Arming record* below + session log [`<redacted>.md`](../session-logs/<redacted>.md)).
6
6
  **Amended 2026-09-30:** the CI grade-gate (precondition 1) is permanently retired, not deferred — see the *Amendment* at the end (task 1004456, blocker 1000103).
7
7
 
8
8
  ## Problem
9
9
 
@@ -35,7 +35,7 @@ With no human in the merge loop, **the CI checks *are* the authority**. The thre
35
35
 
36
36
  1. **[`CODEOWNERS`](../../CODEOWNERS)** — a PR touching any gate surface (`.github/`, `config/deploy.json`, `ship.js`, the grader, `auth.js`/`route-rank-check.js`) **or any code that executes on the droplet at deploy** (`migrations/`, `scripts/migrate.sh`, `package.json`/`package-lock.json`, `scripts/deploy/` — `~/deploy.sh` runs migrations + `npm ci` from the merged tree, so a malicious migration or poisoned lockfile would otherwise auto-merge and run on prod) requires Archon review *even under auto-merge*, when branch protection has "Require review from Code Owners" on. Ordinary PRs (game, art, docs) carry no code owner and auto-merge freely — that's the point.
37
37
 
38
- 2. **The grader must RE-RUN in CI** — CODEOWNERS only catches *committed* changes to `ship.js`. A builder could run a **locally-edited, uncommitted** `ship.js` that skips the grade and opens an auto-merge PR anyway. An earlier draft of this ADR proposed a CI check that *reads the server-recorded grade* — but that is **theater**: `POST /tasks/:id/grade` records the grader scores *from the request body* (the server does not recompute them), so a tampered local ship.js can POST a fake passing grade. The only tamper-proof gate runs the grader **in CI, against the PR diff**, where the builder cannot touch it — [`.github/workflows/grade-gate.yml`](../../.github/workflows/grade-gate.yml) + [`scripts/gds/ci-grade.js`](../../scripts/gds/ci-grade.js) ([#855](https://example.com/builders#/task/855)), which reuse the exact production grading path (`ship.js runSubagentGrader` → the adversarial worker panel) and **fail closed**. It is **skip-safe** (green-but-not-gating) until the operator wires a grader auth secret AND marks it a required check — the same inert-until-armed pattern as `deploy-prod.yml`. This is a **hard precondition** of arming `ci` mode.
38
+ 2. **The grader must RE-RUN in CI** — CODEOWNERS only catches *committed* changes to `ship.js`. A builder could run a **locally-edited, uncommitted** `ship.js` that skips the grade and opens an auto-merge PR anyway. An earlier draft of this ADR proposed a CI check that *reads the server-recorded grade* — but that is **theater**: `POST /tasks/:id/grade` records the grader scores *from the request body* (the server does not recompute them), so a tampered local ship.js can POST a fake passing grade. The only tamper-proof gate runs the grader **in CI, against the PR diff**, where the builder cannot touch it — [`.github/workflows/grade-gate.yml`](../../.github/workflows/grade-gate.yml) + [`scripts/gds/ci-grade.js`](../../scripts/gds/ci-grade.js) ([#855](https://example.com/builders#/task/855)), which reuse the exact production grading path (`ship.js runSubagentGrader` → the adversarial worker panel) and **fail closed**. It is **skip-safe** (green-but-not-gating) until the operator wires a grader auth secret AND marks it a required check — the same inert-until-armed pattern as `deploy-prod.yml`. This was written as a **hard precondition** of arming `ci` mode. *(Superseded 2026-09-30: the CI grade-gate is permanently retired — see the Amendment below.)*
39
39
 
40
40
  ### Why not just hand builders the droplet key
41
41
 
@@ -43,7 +43,7 @@ Rejected. It puts a full shell on the production box (prod DB, every secret) on
43
43
 
44
44
  ## Cutover preconditions (do NOT arm `ci` until ALL hold)
45
45
 
46
- 1. **CI grade-gate armed + verified** — the grader RE-RUN-in-CI check (`grade-gate.yml` + `ci-grade.js`, [#855](https://example.com/builders#/task/855)) is built and skip-safe but NOT yet a real gate. The operator must (a) add a grader auth secret (`ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` — the grader spawns the `claude` CLI, ~4 model calls/PR), (b) verify a real grader-in-CI run passes a known-good PR and fails a known-bad one, and (c) mark `grade-gate` a required status check on `main`. *Without this, `ci` mode lets a tampered local ship.js bypass the quality grader.* (Built in [#855](https://example.com/builders#/task/855); arming/verification is part of the cutover.)
46
+ 1. ~~**CI grade-gate armed + verified**~~ *(retired 2026-09-30 — see the Amendment below; never armed)* — the grader RE-RUN-in-CI check (`grade-gate.yml` + `ci-grade.js`, [#855](https://example.com/builders#/task/855)) is built and skip-safe but NOT yet a real gate. The operator must (a) add a grader auth secret (`ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` — the grader spawns the `claude` CLI, ~4 model calls/PR), (b) verify a real grader-in-CI run passes a known-good PR and fails a known-bad one, and (c) mark `grade-gate` a required status check on `main`. *Without this, `ci` mode lets a tampered local ship.js bypass the quality grader.* (Built in [#855](https://example.com/builders#/task/855); arming/verification is part of the cutover.)
47
47
  2. **Functional checks required** — the DB-free **unit suite** runs as a PR check ([`.github/workflows/pr-unit-tests.yml`](../../.github/workflows/pr-unit-tests.yml) + [`scripts/gds/run-unit-tests.js`](../../scripts/gds/run-unit-tests.js), 56 tests, [#856](https://example.com/builders#/task/856)); the operator marks it required at cutover. The **integration smoke** (full server + Postgres service container) is a follow-up ([#862](https://example.com/builders#/task/862)). The existing `secrets-scan` / `dep-audit` already run on PRs.
48
48
  3. **Branch protection on `main`** — require a PR (block direct pushes), require the checks in (1)+(2) + Code-Owner review, enable auto-merge.
49
49
  4. **`deploy-prod.yml` armed** — `PROD_SSH_KEY` + `DROPLET_KNOWN_HOSTS` secrets + `PROD_DEPLOY_VIA_CI=true` (a dedicated deploy keypair, not a personal key).
@@ -60,7 +60,7 @@ Until (1)–(5), the switch stays `laptop` and the operator lands work as today
60
60
 
61
61
  ## Follow-ups (filed as GDS tasks)
62
62
 
63
- - CI **grade-gate** reading the server-recorded grade — **blocks cutover** (precondition 1).
63
+ - ~~CI **grade-gate** reading the server-recorded grade — **blocks cutover** (precondition 1).~~ Retired 2026-09-30 (Amendment below).
64
64
  - PR-check workflows: unit suite + integration smoke (Postgres service) as required checks (precondition 2).
65
65
  - Drop the per-builder `gh` dependency (server-mediated PR creation) + `doctor.js gh` check.
66
66
  - Commit/version endpoint for a commit-pinned edge deploy confirmation.
@@ -79,4 +79,18 @@ during the provider outage. With the grader off everywhere, gating auto-merge on
79
79
 
80
80
  What was set: `PROD_SSH_KEY` (a dedicated ed25519 deploy key, **forced-command `~/deploy.sh`** in the droplet's `authorized_keys`) + `DROPLET_KNOWN_HOSTS` secrets; repo var `PROD_DEPLOY_VIA_CI=true`; branch protection on `main` (require PR, required checks `unit`+`trufflehog`, require Code-Owner review, 0 ordinary approvals, block direct push for non-admins); repo auto-merge enabled; `config/deploy.json` → `ci`. `deploy-prod.yml` verified end-to-end (Actions → SSH → `~/deploy.sh` → public healthz) before the flip.
81
81
 
82
- **Tighten-later (re-arm when the grader returns):** turn `GRADER_BYPASS_ENABLED` off, re-introduce the `grade-gate` required check (precondition 1, needs an `ANTHROPIC_API_KEY` Actions secret), and land the integration-smoke gate (task 862, precondition 2). **Rollback:** set `config/deploy.json` → `laptop` **and** unset the `PROD_DEPLOY_VIA_CI` repo var (admins retain direct main-push for this).
82
+ **Tighten-later (re-arm when the grader returns):** turn `GRADER_BYPASS_ENABLED` off ~~, re-introduce the `grade-gate` required check (precondition 1, needs an `ANTHROPIC_API_KEY` Actions secret)~~ *(the grade-gate leg is retired — Amendment below)*, and land the integration-smoke gate (task 862, precondition 2). **Rollback:** set `config/deploy.json` → `laptop` **and** unset the `PROD_DEPLOY_VIA_CI` repo var (admins retain direct main-push for this).
83
+
84
+ ## Amendment — 2026-09-30: the CI grade-gate is permanently retired
85
+
86
+ **Owner ruling** (blocker 1000103, recorded under task 1004456): precondition 1 — the grader re-run in CI (`grade-gate.yml` + `scripts/gds/ci-grade.js`) — is **retired, not deferred**. It was never armed: `grade-gate.yml` does not exist and `ci-grade.js` has no caller. This ADR no longer promises it.
87
+
88
+ **What holds instead** is the layered model [ADR 0164](0164-implausible-pass-guard-substance-and-evidence.md) documents:
89
+
90
+ - **Grade time — honest-mistake hygiene.** The panel verdict stays *client-asserted* (`POST /tasks/:id/grade` records scores from the request body). The implausible-pass guard catches empty or evidence-free passes; it is not a tamper-proof gate and does not claim to be.
91
+ - **Land time — the binding authorities.** The required CI `unit` check, gate-review, Code-Owner review on gate surfaces, and the deploy-time main audit ([ADR 0043](0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md)) decide what reaches `main`, whatever the grade said.
92
+ - **Metic+ grade-time honesty is accepted honor-system.** The forgery classes this leaves uncaught are listed in ADR 0164 §3.
93
+
94
+ **Why B over landing the gate.** Across the 106-grade evaluation window (goal 1000056) there were zero known forged passes. A CI re-grade would roughly double grading spend per ship and add a new required check that strands every ship whenever the grader is down — the outage class later work spent two waves defusing. The honor-system residual is judged cheaper than that.
95
+
96
+ **Revisit if** a forged or tampered grade is ever found on `main`: the gate design in §*The auto-merge gate must be tamper-proof* (item 2) remains the reference for re-introducing it.
@@ -134,7 +134,7 @@ These 20 numbers are each shared by exactly two files. They are **accepted histo
134
134
  | 0039 | [Setup-first onboarding UX: two-config selector + action-only Path checklist](0039-setup-first-onboarding-ux.md) | GDS / builder onboarding |
135
135
  | 0040 | [Remote Control is the default browser-only on-ramp (reverses 0038's "RC is dead" verdict)](0040-remote-control-default-browser-onramp.md) | GDS / builder onboarding |
136
136
  | 0041 | [Temporary global grader-bypass kill-switch (`GRADER_BYPASS_ENABLED`)](0041-temporary-grader-bypass-killswitch.md) | GDS / shipping pipeline |
137
- | 0042 | [Builder self-deploy: CI auto-deploy on main + PR auto-merge (no builder box holds the prod key)](0042-builder-self-deploy-ci-auto-merge.md) | GDS / shipping pipeline |
137
+ | 0042 | [Builder self-deploy: CI auto-deploy on main + PR auto-merge (no builder box holds the prod key). **Amended 2026-09-30:** the CI grade-gate (precondition 1) is permanently retired in favour of ADR 0164's layered model](0042-builder-self-deploy-ci-auto-merge.md) | GDS / shipping pipeline |
138
138
  | 0043 | [Git/SSH trust-boundary gap + a rank floor on permission-sensitive paths](0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md) | GDS / trust boundary |
139
139
  | 0044-a | [Full Mediterranean palette replacement (not a split)](0044-mediterranean-palette-replacement.md) | Art pipeline / palette |
140
140
  | 0044-b | [Per-box live game preview (the builder sandbox)](0044-per-box-live-game-preview.md) | GDS / builder experience |
@@ -2721,5 +2721,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
2721
2721
  landed since 1.20.30 with no explicit bump. run 36779142919. (task 1002620)
2722
2722
  1.20.32 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2723
2723
  landed since 1.20.31 with no explicit bump. run 36781343271. (task 1002620)
2724
+ 1.20.33 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2725
+ landed since 1.20.32 with no explicit bump. run 36783765458. (task 1002620)
2726
+ 1.20.34 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2727
+ landed since 1.20.33 with no explicit bump. run 36784565937. (task 1002620)
2724
2728
  ---------------------------------------------------------------------------
2725
2729
  ```
@@ -178,7 +178,7 @@ It is the machine's working copy, not a place to edit.
178
178
  | Runner line says **Not checking in** | is the PC on? `...\autobongos-service.ps1 status` on the box |
179
179
  | **Checking in** but nothing ever ships | first the Gate page — is a goal allowlisted, and is the switch on? If both are on, the **gauge**: `autobongos-service.ps1 digest`, or `node scripts/gds/autonomy-gauge.js`. A `hold` with `unknown: true` means it cannot read your remaining capacity, and it will wait forever rather than guess |
180
180
  | The log repeats `hold ... unknown: true` | the gauge has no reading it can vouch for. Open a **terminal** `claude` session on the box once — that writes it. A desktop-app session does not: the writer is a statusLine command, and only a terminal renders one |
181
- | `hold` naming a **derived** gauge and a cap | working as designed. After the last real reading's window resets, the runner may do `AUTOBONGOS_DERIVED_TASK_CAP` tasks (default 2) before it wants a fresh reading. A terminal session lifts it |
181
+ | `hold` naming the **usage limit** | working as designed. A worker was cut off by the subscription limit, so the runner sleeps until the reset time and then resumes by itself. There is no fixed task cap (task 1004454) |
182
182
  | Shipping stopped after a while | the log: the runner backs off after three failures and probes; the reason is in the log |
183
183
  | It keeps restarting | `status` shows the last result; the `====` banners in the log mark each start |
184
184
 
@@ -180,6 +180,7 @@ running a go-live.
180
180
  ## Gotchas
181
181
 
182
182
  - **The Deploy door fixes three known snags by itself, once** (task 1004448, `scripts/gds/wedge-remedy.js`). When a core move through `/deploy` fails on a **dirty tree** (it commits the tracked changes inside the project), a **full disk** (it prunes that project's own nightly dumps down to the newest 7) or a **database behind its code** (it migrates that database with its own `PGDATABASE` before the code moves), the runner applies the fix, records a `core-upgrade-remedy` event on the project, and tries the move once more on the next tick. If the retry fails for the same reason, or the fix itself fails, the move is retired as `unknown` (`remedy_did_not_hold` / `remedy_failed`) for a person — never a second fix. The manual fixes below still apply to the unattended nightly sweep, which does not go through the door.
183
+ - **A Deploy-door move nobody can fix files a blocker and posts to Discord — once** (task 1004449, `scripts/gds/move-escalation.js`). A move retired as `unknown` (including `remedy_did_not_hold`, `remedy_failed` and a failed rollback) files one blocker (`source=provisioning`, `source_ref=core-move:<instance id>:<intent id>`) with the reason and what the move reported, and posts one notice to `#ship-news` that names the project only if it is public. The next core move on that project that lands closes the blocker by itself and posts an all-clear. Refusals the owner can act on (`needs_decision`) are shown on the deploy page instead, not filed.
183
184
  - **`--registry` needs no credential at all** (task 1003878). `@bongos/core` is **public** on npm (task 1003872), so `bongos upgrade --registry` resolves it from the default registry like any other dependency — no token, no login, and no instance `.npmrc`. An install failure on this path is therefore a *real* failure (network, registry outage, or a version that was never published), never a missing credential.
184
185
  *History, so the symptom is recognisable on an old instance:* the core used to publish private, and the instance `.npmrc` read `_authToken=<redacted> npm gives that per-project key precedence, so with the env var unset it expanded EMPTY and **masked a perfectly valid literal token in `~/.npmrc`** — npm went anonymous, the private core 404'd, and the error blamed a credential the box actually held. Task 1002754 worked around it by resolving the token in-process; task 1003878 deleted the whole mechanism along with the `.npmrc` that caused it. An instance still carrying a committed `.npmrc` from a pre-1003878 scaffold can safely delete it.
185
186
  - **A green `publish` run is not evidence of a publish.** The publish-on-merge lane succeeds on its no-op paths too (cascade terminator, armed-gate). Read the run's **job summary**, which now states the verdict explicitly: `PUBLISHED @bongos/core@<v>` vs `NO-OP — nothing was published this run`.
@@ -110,6 +110,14 @@ function classifyEvent(row = {}) {
110
110
  // counting it as such would hold a broken runner at full speed indefinitely.
111
111
  if (row.verified === true) return { class: 'progress', detail: 'a task shipped and the ledger confirms it' };
112
112
  if (row.ledger_shape === 'undetermined') return { class: 'transient', detail: 'the ledger could not be read back' };
113
+ // CUT OFF BY THE USAGE LIMIT (task 1004454). Checked before workerNeverRan,
114
+ // whose shape it shares (seconds long, nothing spent): the machine is fine,
115
+ // the window is spent, and the owner has said hitting it is acceptable. So it
116
+ // is a wait to the reset, never a step toward probing.
117
+ if (row.usage_limit) {
118
+ const resetAt = row.usage_limit.reset_at ?? null;
119
+ return { class: 'quiet', detail: `the usage limit was hit — sleeping to its reset${resetAt ? '' : ' (time not stated; a bounded wait)'}`, sleepUntil: resetAt };
120
+ }
113
121
  // DID THE WORKER ACTUALLY RUN? (task 1004179, found by the 1003907 proof run.)
114
122
  //
115
123
  // Everything below this line assumes a worker that ran and met a real
@@ -178,6 +186,15 @@ function classifyEvent(row = {}) {
178
186
 
179
187
  if (event === 'verify_halt') return { class: 'transient', detail: 'the ledger could not be read back after a worker' };
180
188
 
189
+ if (event === 'queue_gated') {
190
+ // A stranded ship refuses EVERY claim (task 1004396). That is somebody's
191
+ // unlanded work, not this machine breaking, so it is quiet: escalating would
192
+ // slow the runner for a fault it cannot fix. The wait already wakes early when
193
+ // main moves, and a strand landing is a merge to main.
194
+ const on = Array.isArray(row.waiting_on) && row.waiting_on.length ? row.waiting_on.map((id) => `task ${id}`).join(', ') : 'a stranded task';
195
+ return { class: 'quiet', detail: `waiting for ${on} to land — every claim is refused until then` };
196
+ }
197
+
181
198
  if (event === 'pick_failed' || event === 'worktree_failed' || event === 'claim_failed') {
182
199
  return {
183
200
  class: looksSystemic(reason) ? 'systemic' : 'transient',
@@ -173,7 +173,9 @@ function buildDigest(rows = [], { gauge = null, nowEpochS = null, sinceMs = null
173
173
  }
174
174
  }
175
175
 
176
- const held = all.filter((r) => r.event === 'hold').map((r) => ({ reason: r.reason || null, sleepUntil: r.sleep_until ?? null }));
176
+ // A queue gate (task 1004396) is a hold too: the runner chose not to claim, and
177
+ // the reason names the stranded task it is waiting on.
178
+ const held = all.filter((r) => r.event === 'hold' || r.event === 'queue_gated').map((r) => ({ reason: r.reason || null, sleepUntil: r.sleep_until ?? null }));
177
179
 
178
180
  // 3. WHAT EACH WORKER DECIDED that its spec did not determine — grouped by task,
179
181
  // because the SHAPE of this list is the signal. One task with six decisions is a
@@ -211,7 +213,8 @@ function buildDigest(rows = [], { gauge = null, nowEpochS = null, sinceMs = null
211
213
  // How the runner spent the night, so "nothing shipped" can be told apart from
212
214
  // "nothing ran".
213
215
  const KNOWN = new Set(['worked', 'fenced', 'hold', 'nothing_claimable', 'dry_run',
214
- 'pick_failed', 'worktree_failed', 'claim_failed', 'verify_halt', 'cadence', 'boot_reconcile']);
216
+ 'pick_failed', 'worktree_failed', 'claim_failed', 'verify_halt', 'cadence', 'boot_reconcile',
217
+ 'queue_gated', 'queue_gate_cleared']);
215
218
  const byEvent = {};
216
219
  let unrecognised = 0;
217
220
  for (const r of all) {
@@ -283,7 +283,11 @@ function decideCapacity({ gauge, now, config = {} } = {}) {
283
283
  // actually binds a heavy week — does not run at all. A stale weekly figure
284
284
  // cannot stand in for it either: usage only grows, so an under-reported value
285
285
  // makes the check MORE permissive, which is the wrong direction to be wrong in.
286
- // Bounding the work done since `derivedSince` is what replaces that pacing.
286
+ // Bounding the work done since `derivedSince` is what replaced that pacing,
287
+ // until the owner ruled against a fixed cap (task 1004454, 2026-09-30): the
288
+ // runner now works on a derived reading and stops when a worker is actually cut
289
+ // off by the limit, sleeping to its reset. The field is kept on the decision so
290
+ // the log still says which boundary a derived reading stood on.
287
291
  //
288
292
  // The MOST RECENT boundary, not the oldest (task 1004376). A derived seven-day
289
293
  // window starts up to a week back; counting from there would make the cap a
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.20.32",
3
+ "version": "1.20.34",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.20.32",
9
+ "version": "1.20.34",
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.20.32",
3
+ "version": "1.20.34",
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",
@@ -8264,5 +8264,25 @@
8264
8264
  "id": "1004448",
8265
8265
  "text": "When an update to a project gets stuck on one of three known problems (leftover file changes, a full disk, or a database that is behind), the system now fixes it by itself and tries again once. If that doesn't work, it stops a"
8266
8266
  }
8267
+ ],
8268
+ "1.20.33": [
8269
+ {
8270
+ "id": "1004456",
8271
+ "text": "The self-deploy decision record no longer promises a CI re-grade check that was never built; it now records the owner's choice to retire it."
8272
+ },
8273
+ {
8274
+ "id": "1004396",
8275
+ "text": "The unattended runner no longer gets stuck retrying a task it isn't allowed to take: it moves on to the next one, and only waits when a stuck ship is blocking everyone, resuming by itself once that lands."
8276
+ },
8277
+ {
8278
+ "id": "1004449",
8279
+ "text": "When a project update fails in a way the system can't fix by itself, it now opens one blocker for a person and posts one message on Discord, instead of failing quietly. When a later update succeeds, the blocker closes by itsel"
8280
+ }
8281
+ ],
8282
+ "1.20.34": [
8283
+ {
8284
+ "id": "1004454",
8285
+ "text": "The unattended runner no longer stops after two tasks: it keeps working while there's usage left, and if it hits the usage limit it sleeps until the limit resets and then carries on by itself."
8286
+ }
8267
8287
  ]
8268
8288
  }
@@ -164,7 +164,43 @@ function classifyRun({ envelope = null, exitCode = 0, timedOut = false, spawnErr
164
164
  return { outcome: 'claims_shipped', reason: parsed.verdict.what_happened, releaseClaim: false, verdict: parsed.verdict, costUsd: parsed.costUsd };
165
165
  }
166
166
 
167
+ // usageLimitHit — was this worker cut off by the subscription's usage limit?
168
+ // (task 1004454)
169
+ //
170
+ // Owner ruling, 2026-09-30: no fixed cap on work; hitting the limit is fine, and
171
+ // the runner restarts once it resets. That makes the limit message the stop
172
+ // signal, so it has to be recognised — otherwise a limit-hit worker (seconds
173
+ // long, nothing spent) reads as workerNeverRan() and escalates the runner into
174
+ // probing for a condition the owner accepts.
175
+ //
176
+ // Only an ERROR envelope, or a run with no parseable envelope, can be a limit
177
+ // hit. A worker that FINISHED may well write about limits in its verdict (it
178
+ // might have fixed a rate-limit bug), and sleeping on that would stop the runner
179
+ // for doing its job.
180
+ //
181
+ // The CLI's machine form is `Claude AI usage limit reached|<epoch seconds>`; the
182
+ // human forms ("You've hit your limit · resets 3am", "5-hour limit reached ∙
183
+ // resets 3pm") carry a clock time in a zone we would have to guess, so they
184
+ // return resetAt: null and the caller picks a bounded wait instead of parsing one.
185
+ const LIMIT_EPOCH_RE = /usage limit reached\|(\d{9,11})/i;
186
+ const LIMIT_TEXT_RE = /usage limit reached|hit your (usage )?limit|\b(5|five)-hour limit reached|weekly limit reached|limit reached\s*[∙·•|-]\s*resets/i;
187
+ function usageLimitHit({ envelope = null, stderr = '' } = {}) {
188
+ let text = '';
189
+ let parsedEnvelope = null;
190
+ try { parsedEnvelope = typeof envelope === 'string' ? (envelope.trim() ? JSON.parse(envelope) : null) : envelope; } catch (_) { parsedEnvelope = null; }
191
+ if (parsedEnvelope && typeof parsedEnvelope === 'object') {
192
+ if (parsedEnvelope.is_error !== true) return null;
193
+ text = String(parsedEnvelope.result || '');
194
+ } else if (typeof envelope === 'string') {
195
+ text = envelope;
196
+ }
197
+ text = `${text}\n${stderr || ''}`;
198
+ const m = LIMIT_EPOCH_RE.exec(text);
199
+ if (m) return { resetAt: Number(m[1]) };
200
+ return LIMIT_TEXT_RE.test(text) ? { resetAt: null } : null;
201
+ }
202
+
167
203
  module.exports = {
168
204
  WORKER_VERDICT_SCHEMA, DEFAULT_MODEL, DEFAULT_WORKER_TIMEOUT_MS,
169
- buildWorkerArgs, buildWorkerPrompt, parseWorkerEnvelope, classifyRun,
205
+ buildWorkerArgs, buildWorkerPrompt, parseWorkerEnvelope, classifyRun, usageLimitHit,
170
206
  };