@bongos/core 1.19.835 → 1.19.836

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.835",
6
- "core_contract": "1.19.835",
7
- "source_commit": "31b17fb901dd65b7c6893f797e935c3b503a0de9",
5
+ "core_version": "1.19.836",
6
+ "core_contract": "1.19.836",
7
+ "source_commit": "faff4dc07439d637df2442a60de7b6ffe4942bef",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-17T16:17:34.798Z",
9
+ "built_at": "2026-09-17T16:35:52.288Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 493,
13
13
  "agent_docs_stubbed": 26,
14
- "functional_verbatim": 2248,
14
+ "functional_verbatim": 2251,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2767,
20
- "tree_sha256": "a2ec29793c06b863434f540138312423bddbe46023b7584468bbbdb7d5dfd2b2",
19
+ "file_count": 2770,
20
+ "tree_sha256": "b5a83bc0c677727ffcea2ac2708a0cecfbc67bf4ad36bb5fd810f89370f8a5a7",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -52,7 +52,7 @@
52
52
  {
53
53
  "path": ".claude/skills/builder-claim/SKILL.md",
54
54
  "mode": "0000644",
55
- "sha256": "2fb6020a75965376ec426eec1bfc7e850d2cd659ce9e2205cffa76956f3e1ea7"
55
+ "sha256": "6e5555dccf68419b690ec2f4ac1f31c98399f946f9c39f23503afbbfe808ee15"
56
56
  },
57
57
  {
58
58
  "path": ".claude/skills/builder-cost/SKILL.md",
@@ -87,7 +87,7 @@
87
87
  {
88
88
  "path": ".claude/skills/builder-release/SKILL.md",
89
89
  "mode": "0000644",
90
- "sha256": "8076ea3f6c74cfa18eb18f68a40a9c8d7d941f18437e64bae8e96d24d01ef3fe"
90
+ "sha256": "c9f9607939f1048370e0fd35626b5d8dc0d6c7285d0b6aa4bbb745c5cb4b16a5"
91
91
  },
92
92
  {
93
93
  "path": ".claude/skills/builder-sequence/SKILL.md",
@@ -102,7 +102,7 @@
102
102
  {
103
103
  "path": ".claude/skills/builder-ship/SKILL.md",
104
104
  "mode": "0000644",
105
- "sha256": "d7f935846a7f53ad66bc08de00747b5c1719bf6c6a5df47fd7f88743db63b49d"
105
+ "sha256": "dca7c8f6e31c21c08ab2c4810cb6345b35bb287d51be93ed46d793c2096b86b4"
106
106
  },
107
107
  {
108
108
  "path": ".claude/skills/builder-stage/SKILL.md",
@@ -112,7 +112,7 @@
112
112
  {
113
113
  "path": ".claude/skills/builder-start/SKILL.md",
114
114
  "mode": "0000644",
115
- "sha256": "919fd6a9a93baeab66181a47cb7cf7459de6cc0efae4afb51afe0b0413f56001"
115
+ "sha256": "db68cfce733b850121f3b3e6ec094bd2864a85b36c414626037200fa69f34529"
116
116
  },
117
117
  {
118
118
  "path": ".claude/skills/design-sync/SKILL.md",
@@ -2562,7 +2562,7 @@
2562
2562
  {
2563
2563
  "path": "docs/module-api-changelog.md",
2564
2564
  "mode": "0000644",
2565
- "sha256": "6cd1402c15f4a97fe2e7da159ad42f283992d01058781df987f5948c25ff9df4"
2565
+ "sha256": "5945b79e8b7f10f3a6fdd8d2e4df80e0219ee19101801cc3d8dd39a412f3c957"
2566
2566
  },
2567
2567
  {
2568
2568
  "path": "docs/modules-contract.md",
@@ -5587,7 +5587,7 @@
5587
5587
  {
5588
5588
  "path": "modules/lifecycle/db-claims.js",
5589
5589
  "mode": "0000644",
5590
- "sha256": "1c250de70282c19f66324f9203d0899eb874c8d59f14af940a004a235bf9d1b9"
5590
+ "sha256": "4b9beaede9eec500e1d25739f33f0903b0262f7ec8be7374279a4c6c3d38fa36"
5591
5591
  },
5592
5592
  {
5593
5593
  "path": "modules/lifecycle/db-deps-criteria.js",
@@ -5627,7 +5627,7 @@
5627
5627
  {
5628
5628
  "path": "modules/lifecycle/db-ship.js",
5629
5629
  "mode": "0000644",
5630
- "sha256": "19e70254936af3f51fa12d3b9aa9d25dbc0a84761aee84b5143ebfe6a8654b30"
5630
+ "sha256": "02b7d3ca2ec138b1ffd8509e48702a68f79faab3a16dfdc3f223ce807fdf9e90"
5631
5631
  },
5632
5632
  {
5633
5633
  "path": "modules/lifecycle/db-tasks.js",
@@ -5792,7 +5792,7 @@
5792
5792
  {
5793
5793
  "path": "modules/lifecycle/publish-reconciler.js",
5794
5794
  "mode": "0000644",
5795
- "sha256": "dce7913d2635c40ce9557e7cb554aa152dbbe4c40e88676faa3f5e5b34bc26ae"
5795
+ "sha256": "6176e109575f02c681240b809b85b272c438c7284a7fdd290cd87b46abdb9e7c"
5796
5796
  },
5797
5797
  {
5798
5798
  "path": "modules/lifecycle/rot.js",
@@ -7852,12 +7852,12 @@
7852
7852
  {
7853
7853
  "path": "package-lock.json",
7854
7854
  "mode": "0000644",
7855
- "sha256": "72ff1b680bbebe422781c8e96ff9da2627b98581a871e0f826907525a0eecf04"
7855
+ "sha256": "c03405cc4429940c79f245171cd6b474c802187584b108f842bfffcf1b904bdf"
7856
7856
  },
7857
7857
  {
7858
7858
  "path": "package.json",
7859
7859
  "mode": "0000644",
7860
- "sha256": "95c3f6f48a670b840fbf7b1f05f97b5fa93fa06ed08b6322d766b19e46beeef5"
7860
+ "sha256": "f6889cab0ca16f36a0902f132a23f0251d8e30ad20f4b11b861ab60f66d6fef6"
7861
7861
  },
7862
7862
  {
7863
7863
  "path": "public-docs/index.html",
@@ -7952,7 +7952,7 @@
7952
7952
  {
7953
7953
  "path": "scripts/gds/api.js",
7954
7954
  "mode": "0000644",
7955
- "sha256": "af8c10397ffeca0e74f520b277f0bb1044cef9a62318552ee5a2b911d33f560f"
7955
+ "sha256": "d36fa664267d64796faf071278437cb7cfa8c2b1c7ae2140bcb01d7c2ddf16e1"
7956
7956
  },
7957
7957
  {
7958
7958
  "path": "scripts/gds/api.ps1",
@@ -8117,7 +8117,7 @@
8117
8117
  {
8118
8118
  "path": "scripts/gds/build-cli-package.js",
8119
8119
  "mode": "0000644",
8120
- "sha256": "8286f8bf978cfb49b96e035eefbbc40878c73b8a14f16f6a9c11c308f74d4cd3"
8120
+ "sha256": "630bdedb252e6d77a3427d962fca94094c747c377679ec1157644d76f142b6e8"
8121
8121
  },
8122
8122
  {
8123
8123
  "path": "scripts/gds/bump-version.js",
@@ -8152,7 +8152,7 @@
8152
8152
  {
8153
8153
  "path": "scripts/gds/claim.js",
8154
8154
  "mode": "0000644",
8155
- "sha256": "2d225377a6ab3bdc8e8cb195d5e6588517275179fe49100c42362c88a3465324"
8155
+ "sha256": "a02e76a5f817501745c843c1df25fdd9584e91eb3d97fe76976fd508b70f72d9"
8156
8156
  },
8157
8157
  {
8158
8158
  "path": "scripts/gds/classify-discipline.js",
@@ -8177,7 +8177,7 @@
8177
8177
  {
8178
8178
  "path": "scripts/gds/cli-lib.js",
8179
8179
  "mode": "0000644",
8180
- "sha256": "9aaa511f1c889717a21636f53bd3cf588fed740490c11b29b48b1f90de1c0729"
8180
+ "sha256": "d320945b629ad20f4e6cc272f3b531d6f98ed2dae2f07286b459db5b15d2c07a"
8181
8181
  },
8182
8182
  {
8183
8183
  "path": "scripts/gds/client-baseurl-guard.js",
@@ -8714,6 +8714,11 @@
8714
8714
  "mode": "0000644",
8715
8715
  "sha256": "b61317006e5df92afb5744bd9fb9b3180bf8a674b7f691773114280660a42a2a"
8716
8716
  },
8717
+ {
8718
+ "path": "scripts/gds/plain-cards.js",
8719
+ "mode": "0000644",
8720
+ "sha256": "7f54da72ae01fa7db57098e3dfd523f9f65a65efa316e8962b9cf7d60faf9be0"
8721
+ },
8717
8722
  {
8718
8723
  "path": "scripts/gds/portability-smoke-art.sh",
8719
8724
  "mode": "0000644",
@@ -8817,7 +8822,7 @@
8817
8822
  {
8818
8823
  "path": "scripts/gds/push-path-brief.js",
8819
8824
  "mode": "0000644",
8820
- "sha256": "3e4d1f99b3edd2883ba63fecfd3718f26b6e53f2255f5669ab7f5e4afe3f9c34"
8825
+ "sha256": "4fbbb81668b9fa2d7713ba9585bfe8dfacba25c5bd87c9368d1078cec6d985a3"
8821
8826
  },
8822
8827
  {
8823
8828
  "path": "scripts/gds/read-session-export.js",
@@ -8847,7 +8852,7 @@
8847
8852
  {
8848
8853
  "path": "scripts/gds/release.js",
8849
8854
  "mode": "0000644",
8850
- "sha256": "e1eee039027e79ab3dfee9bcfe12af43c96e788c39c7861b4910a83eeddb90e7"
8855
+ "sha256": "997c4c4f45d360b765877e3583038be21b52267112b7b33e9c673ab245d682fc"
8851
8856
  },
8852
8857
  {
8853
8858
  "path": "scripts/gds/rename-goals.js",
@@ -9127,7 +9132,7 @@
9127
9132
  {
9128
9133
  "path": "scripts/gds/ship-flow.js",
9129
9134
  "mode": "0000644",
9130
- "sha256": "bb39ac6572410567ce0a2881f26b0512df817fd04cec9130792ca24c48e8d02a"
9135
+ "sha256": "c29075a13419ef2bc13f2ff1fd921c725c633ab1e3284dbe9531e1fb55219678"
9131
9136
  },
9132
9137
  {
9133
9138
  "path": "scripts/gds/ship-git.js",
@@ -9137,7 +9142,7 @@
9137
9142
  {
9138
9143
  "path": "scripts/gds/ship-grade-steps.js",
9139
9144
  "mode": "0000644",
9140
- "sha256": "98731f2d03f4465ebb018e1384829f0ed84a8a57ae88912bb4406d1d96f8bd0b"
9145
+ "sha256": "158a01214499cd229a51819cd2dfcfee2eae34475f87f23795f81ec83d54f337"
9141
9146
  },
9142
9147
  {
9143
9148
  "path": "scripts/gds/ship-grade.js",
@@ -9157,7 +9162,7 @@
9157
9162
  {
9158
9163
  "path": "scripts/gds/ship-land.js",
9159
9164
  "mode": "0000644",
9160
- "sha256": "0ae08db904a8777c7b6402ab2064d0ee092cf014a63a4c5421582d5aa998e7c3"
9165
+ "sha256": "fafa751359543787133e26a7aa18307fec163b0955eea38677a1b319d4dc6162"
9161
9166
  },
9162
9167
  {
9163
9168
  "path": "scripts/gds/ship-memory-sync.js",
@@ -9272,7 +9277,7 @@
9272
9277
  {
9273
9278
  "path": "scripts/gds/start.js",
9274
9279
  "mode": "0000644",
9275
- "sha256": "b5f2ae50f30ccd55f7ff7d1ea10ec971a9972ad7c2cd8bfdba33c205cf9f0799"
9280
+ "sha256": "70aa432e6142fa7a5e7ca5232aa0f362c1e2b891b8ac34ad7d34a4665abe043f"
9276
9281
  },
9277
9282
  {
9278
9283
  "path": "scripts/gds/status.js",
@@ -9777,7 +9782,7 @@
9777
9782
  {
9778
9783
  "path": "src/module-api.js",
9779
9784
  "mode": "0000644",
9780
- "sha256": "a5a74874643eb65e4e9727c492b8990e42dd5b28d13c40e5a523abcbc1aa320d"
9785
+ "sha256": "7faebdb6698a23244085b855c10b7f25d3363a5fd92f8f1190305f4a3b627da9"
9781
9786
  },
9782
9787
  {
9783
9788
  "path": "src/module-loader/catalog.js",
@@ -10457,7 +10462,7 @@
10457
10462
  {
10458
10463
  "path": "tests/claim_gate_rebase.mjs",
10459
10464
  "mode": "0000644",
10460
- "sha256": "64b24b9706f542b6cff381cfa4e9f59ceb91ce30aa92c5c34d41b43da76e0b46"
10465
+ "sha256": "944e025c19ed31efbe04bf27dccccd0705782a5c028271299c8982461d050eb7"
10461
10466
  },
10462
10467
  {
10463
10468
  "path": "tests/claim_recall.mjs",
@@ -12374,6 +12379,11 @@
12374
12379
  "mode": "0000644",
12375
12380
  "sha256": "c3ceef2141a0f5f19c6dacfa0f9c927e5904757c39925e852b34b08c4c2576a5"
12376
12381
  },
12382
+ {
12383
+ "path": "tests/plain_cards.mjs",
12384
+ "mode": "0000644",
12385
+ "sha256": "925f4ffa450b2eddff31712b8ea1cd3e7853110f156d8fd4574f1ba1d05d16a8"
12386
+ },
12377
12387
  {
12378
12388
  "path": "tests/planning_session_skill.mjs",
12379
12389
  "mode": "0000644",
@@ -12809,6 +12819,11 @@
12809
12819
  "mode": "0000644",
12810
12820
  "sha256": "f9e7b2e7ae906198ef0badd1bbbe8e28cd4e8d20770ddc351b85aa2c8ce85198"
12811
12821
  },
12822
+ {
12823
+ "path": "tests/rebase_gate_unprovable.mjs",
12824
+ "mode": "0000644",
12825
+ "sha256": "07a40476b9215049c2e61ae2323ade7443b23dab6ca7bac3cd9d6041103d250e"
12826
+ },
12812
12827
  {
12813
12828
  "path": "tests/recall_cli.mjs",
12814
12829
  "mode": "0000644",
@@ -6,6 +6,8 @@ description: >-
6
6
 
7
7
  **Script skill (authoritative).** The core action is `node scripts/gds/claim.js <task-id> [--worktree NAME]`. Once the args are determined, run it and relay its output verbatim — do not paraphrase error messages or reformat the claimed card.
8
8
 
9
+ **The one exception — a refusal, for a non-technical builder (task 1003989).** Every refusal now ends with an `[otb-plain]` card: what happened, what you will do next, whether a person is needed (and who), and whether this is a rule or a fault. Read the session's interaction profile (the Conductor injects it; with none injected, assume non-technical — CLAUDE.md §2). **Non-technical or mixed:** lead with the card in your own plain words and do what its "What I will do" line says; keep the raw text back unless they ask for it. **Engineer:** relay verbatim, as above. **Any profile:** `By design: Yes` means the refusal is a rule, not breakage — never look for a way round it and never offer one.
10
+
9
11
  You are claiming a task on behalf of the current builder. The claim is atomic — either it succeeds (and the task is locked to this session) or it fails with a clear reason and the user picks differently.
10
12
 
11
13
  ## What this skill does
@@ -4,7 +4,7 @@ description: >-
4
4
  Cancel an active claim without shipping: the task returns to 'ready' and no credits are awarded. Triggers: "/builder-release N", "cancel my claim", "I'm dropping this task", "give it back".
5
5
  ---
6
6
 
7
- **Script skill (authoritative).** The core action is `node scripts/gds/release.js <task-id> --reason "..."`. Once the task id and reason are determined, run it and print its output verbatim — do not editorialize the result.
7
+ **Script skill (authoritative).** The core action is `node scripts/gds/release.js <task-id> --reason "..."`. Once the task id and reason are determined, run it and print its output verbatim — do not editorialize the result. If the release is refused, the output closes with an `[otb-plain]` card; for a builder whose interaction profile is not `engineer`, tell them what that card says instead of pasting the refusal (the rule is spelled out in `/builder-claim`).
8
8
 
9
9
  You are releasing a task on behalf of the current builder. Releasing is the honest "I started this, but I'm not finishing it right now" exit.
10
10
 
@@ -25,6 +25,8 @@ The CLI drives a **three-state lifecycle** with a subagent grader between states
25
25
  - **`GRADER UNAVAILABLE`** (`panel_outcome='unavailable'`) — the grading panel could not run (worker infra outage or a pre-spawn bypass). This is **not a quality rejection**: no quality signal was produced, there are no findings to fix, and the task parks at `completed` ungraded (aggregates exclude these rounds as `n_unavailable` — an outage never counts as a zero). Recovery: when the grader is reachable again, re-run just the grade step — `node scripts/gds/ship.js <id> --regrade`.
26
26
  - **A genuine `FAIL`** — the panel ran and returned findings. Fix the real issues, commit on the claim branch (the grader reads committed history, not the working tree), then re-grade with the same command: `node scripts/gds/ship.js <id> --regrade`.
27
27
 
28
+ **Every way a ship can stall closes with an `[otb-plain]` card** (task 1003989) — both shapes above, an authorization refusal, a red pull request, a failed push, a land still pending, a land the server could not prove. When the builder's interaction profile is not `engineer`, that card is what you tell them: what happened, what you are doing about it, and whether they need to ask anyone. Its `By design` line decides your next move — `Yes` is a rule to respect (a pending land needs nothing; a protected path cannot be re-graded past), `No` is a fault to go and fix. Do not answer a stall by running the ship again unchanged.
29
+
28
30
  (Game-surface changes, `--approved`) Persists the builder's sandbox self-approval to `tasks.sandbox_review` — `{ by, at, stamp, url, method }`, with `by`/`at` stamped server-side ([#1048](https://example.com/builders#/task/1048), ADR 0046).
29
31
 
30
32
  ## How to use
@@ -40,7 +40,7 @@ Delivery is now deterministic (task 1288). The **card-delivery hook** (`.claude/
40
40
  Either way, don't hand-run internal scripts; the package isn't unpacked yet.
41
41
  - **The card says the instance isn't reachable / running.** Same branch. **Hosted:** the server is not this machine's to start — check the origin is up (`curl -sI <buildersOrigin>/healthz`) and re-run; `bongos dev` cannot reach it, and starting a local instance instead would hide the outage behind an empty board. **Local:** the server was never brought up — route: `bongos dev`, then re-run. (`start.js` prints a guided card itself on a connection failure — relay it verbatim.)
42
42
  - **Exit code 3 / "Wrong instance — refusing to act".** This machine's signed-in session targets a *different* instance than this project's `config/branding.json`. This is correct-by-design (it prevents writing to the wrong place). Route: run the exact `bongos login <origin>` line the message prints (the origin is right there), then re-run. **Do not** set an env override to force the mismatched instance.
43
- - **"no Bongos session"** — the auth case above (`/builder-setup` for a first-timer, `/builder-reauth` for a stale session).
43
+ - **"no Bongos session"** — the auth case above (`/builder-setup` for a first-timer, `/builder-reauth` for a stale session). The sign-in guidance blocks end with an `[otb-plain]` card (task 1003989): unless the builder's interaction profile is `engineer`, say what the card says and then walk the sign-in with them, rather than relaying the block.
44
44
 
45
45
  Emit one guided next step, not a wall of options. These are the same fixes `bongos doctor` and the environment preflight (`preflight.js`) describe — this just front-loads them onto the first command so a newcomer isn't diagnosing a crash.
46
46
 
@@ -2144,5 +2144,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2144
2144
  landed since 1.19.833 with no explicit bump. run 35244719662. (task 1002620)
2145
2145
  1.19.835 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2146
2146
  landed since 1.19.834 with no explicit bump. run 35245617627. (task 1002620)
2147
+ 1.19.836 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2148
+ landed since 1.19.835 with no explicit bump. run 35247495964. (task 1002620)
2147
2149
  ---------------------------------------------------------------------------
2148
2150
  ```
@@ -29,7 +29,7 @@ const { unsatisfiedDeps } = require('./db-deps-criteria.js');
29
29
  const { depRefusalRow } = require('./dead-deps.js');
30
30
  const { claimGate, claimPermissionsFor, disambiguateWorktreeName, foldWorktreeName } = require('./db-rank-authz.js');
31
31
  const { sealSessionLog } = require('./db-shared.js');
32
- const { needsRebaseTasksForBuilder, shouldRefuseClaimResolve } = require('./db-ship.js');
32
+ const { gatingRebaseTasksForBuilder, shouldRefuseClaimResolve } = require('./db-ship.js');
33
33
  const { REWARD_SUGGESTION_CAP, rewardGateAssignment } = require('./db-tasks.js');
34
34
 
35
35
  // -------------------------------------------------------------------------
@@ -108,7 +108,9 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
108
108
  // the builder is motivated to clear it; since it gates their queue, they
109
109
  // can't walk away from it. Runs in THIS txn so it can't race a concurrent
110
110
  // ship that clears the last flag.
111
- const owedRebases = await needsRebaseTasksForBuilder(builderId, client);
111
+ // The GATING subset, not the surfacing one: a strand the builder cannot clear
112
+ // stops blocking after a grace, while still being reported (task 1003294).
113
+ const owedRebases = await gatingRebaseTasksForBuilder(builderId, client);
112
114
  if (owedRebases.length > 0) {
113
115
  throw {
114
116
  code: 'REBASE_REQUIRED',
@@ -377,6 +377,67 @@ async function flagTaskNeedsRebase(taskId, reason = null) {
377
377
  // claims and by the hall/CLI surfacing (part 5). Accepts an optional client so
378
378
  // it can run inside claimTask's FOR UPDATE txn or standalone on the pool.
379
379
  // Ordered oldest-flagged first so the surfacing can show the longest-outstanding.
380
+ // UNPROVABLE_GATE_GRACE_MS — how long a strand the builder may be UNABLE to clear
381
+ // keeps gating their queue before the gate lets go of it (task 1003294).
382
+ //
383
+ // ADR 0120 part 3 blocks a builder's NEW claims while they owe a rebase, because
384
+ // the rebase is a chore only they can do and the gate is what makes them do it.
385
+ // That reasoning holds for every strand reason EXCEPT one.
386
+ //
387
+ // 'no_branch_no_tip' means: no branch in the CORE repo, and no published tip on the
388
+ // claim. Two very different situations produce it and the reconciler cannot tell
389
+ // them apart:
390
+ //
391
+ // (a) the builder never pushed -> THEY CAN FIX IT. The gate is working.
392
+ // (b) the task delivered to the INSTANCE repo, not the core -- e.g. task 1003253,
393
+ // whose commit is real, complete and deployed on the instance host. The core
394
+ // reconciler has structurally no way to see that repository, so the land can
395
+ // NEVER be proven here.
396
+ //
397
+ // In case (b) the gate blocks every future claim by that builder over a task that
398
+ // cannot be cleared by any action available to them, and the recorded remedy has
399
+ // been a hand-run SQL UPDATE on the droplet -- exactly the surgery this reconciler
400
+ // exists to remove. Two builders hit it in one session (1002459, then 1003253).
401
+ //
402
+ // So the gate keeps its teeth for a bounded window -- long enough to motivate the
403
+ // builder in case (a), who only has to push -- and then stops BLOCKING. It does not
404
+ // stop REPORTING: the flag stays set, the row keeps its reason, and every surfacing
405
+ // (me.js, the hall, the CLI) still shows it, because needsRebaseTasksForBuilder is
406
+ // deliberately left unfiltered. A strand that outlives the window needs a human
407
+ // disposition, and a frozen queue is not how to ask for one.
408
+ const UNPROVABLE_GATE_GRACE_MS = Number(process.env.REBASE_GATE_UNPROVABLE_GRACE_MS) || 24 * 60 * 60_000;
409
+
410
+ // The reason that carries this ambiguity. Stored by the reconciler as
411
+ // `strand:<reason>` (announceStrand), so it is matched here in that spelling.
412
+ const UNPROVABLE_STRAND_REASON = 'strand:no_branch_no_tip';
413
+
414
+ // gatingRebaseTasksForBuilder — the subset of owed rebases that may REFUSE a new
415
+ // claim. Split from needsRebaseTasksForBuilder on purpose: that one answers "what
416
+ // does this builder owe?" (surfacing, part 5) and must stay complete, while this one
417
+ // answers "what may stop them working?" (the gate, part 3). Collapsing the two is
418
+ // what would turn "stop blocking" into "stop telling them", which is the opposite of
419
+ // the fix.
420
+ //
421
+ // COALESCE, not a bare comparison: a NULL reason or NULL timestamp must fall through
422
+ // to "still gating". Written as NOT(...) over non-NULL operands so no row can be
423
+ // dropped by three-valued logic -- an unknown is a reason to keep the gate, never to
424
+ // release it.
425
+ async function gatingRebaseTasksForBuilder(builderId, client = pool, graceMs = UNPROVABLE_GATE_GRACE_MS) {
426
+ const { rows } = await client.query(
427
+ `SELECT id, title, needs_rebase_reason, needs_rebase_at
428
+ FROM tasks
429
+ WHERE needs_rebase = true AND status = 'confirmed' AND shipped_by = $1
430
+ AND NOT (
431
+ COALESCE(needs_rebase_reason, '') = $3
432
+ AND needs_rebase_at IS NOT NULL
433
+ AND needs_rebase_at < now() - ($2::bigint * interval '1 millisecond')
434
+ )
435
+ ORDER BY needs_rebase_at ASC NULLS LAST, id ASC`,
436
+ [builderId, Math.max(0, Number(graceMs) || 0), UNPROVABLE_STRAND_REASON]
437
+ );
438
+ return rows;
439
+ }
440
+
380
441
  async function needsRebaseTasksForBuilder(builderId, client = pool) {
381
442
  const { rows } = await client.query(
382
443
  `SELECT id, title, needs_rebase_reason, needs_rebase_at
@@ -1297,6 +1358,9 @@ module.exports = {
1297
1358
  getClaimPublishedSha,
1298
1359
  listConfirmedTasksForReconcile,
1299
1360
  needsRebaseTasksForBuilder,
1361
+ gatingRebaseTasksForBuilder,
1362
+ UNPROVABLE_GATE_GRACE_MS,
1363
+ UNPROVABLE_STRAND_REASON,
1300
1364
  releaseClaimOnBehalf,
1301
1365
  resolveClaim,
1302
1366
  restoreTask,
@@ -76,6 +76,10 @@
76
76
  const githubPush = require('./github-push');
77
77
  const seams = require('../../src/module-api');
78
78
  const conflictResolve = require('./conflict-resolve');
79
+ // The claim gate's grace for a strand its builder may be unable to clear — imported
80
+ // rather than restated so the sweep's escalation and the gate agree on one number
81
+ // (task 1003294).
82
+ const { UNPROVABLE_GATE_GRACE_MS, UNPROVABLE_STRAND_REASON } = require('./db-ship.js');
79
83
 
80
84
  // 5-min cadence: frequent enough that a strand heals within minutes of CI
81
85
  // finishing, cheap enough that the GitHub API cost is negligible: steady state
@@ -281,6 +285,7 @@ async function runSweep(deps = {}) {
281
285
  let errors = 0;
282
286
  let unprovable = 0; // confirmed tasks the sweep could not prove landed this pass
283
287
  let staleStrands = 0; // of those, the ones older than STALE_CONFIRMED_MS (WARN'd)
288
+ let unprovableUngated = 0; // strands past the grace that no longer gate (task 1003294)
284
289
  let strandsFlagged = 0; // task 1002606: needs_rebase flags NEWLY set this sweep (any site)
285
290
  // task 1002723: one entry per confirmed task per sweep ("<id>:<disposition>").
286
291
  // The 1002702/1002716 strands sat through 5+ sweeps with ZERO journal evidence
@@ -722,6 +727,23 @@ async function runSweep(deps = {}) {
722
727
  `strand:${reason}`,
723
728
  `confirmed ~${Math.round(ageMs / 3_600_000)}h ago, unprovable (${reason}) — no landed proof`
724
729
  );
730
+ // ESCALATE rather than keep blocking silently (task 1003294). Past the
731
+ // grace, a 'no_branch_no_tip' strand has stopped gating its builder's
732
+ // queue — see gatingRebaseTasksForBuilder. That is deliberate, because it
733
+ // may be a task delivered to the INSTANCE repo, which this reconciler has
734
+ // no way to see. It is NOT a resolution: the row is still flagged and still
735
+ // unlanded, and it now needs a human to dispose of it. Say so on every
736
+ // sweep, with the age, so it cannot rot unnoticed the way a frozen queue
737
+ // at least made loud.
738
+ if (`strand:${reason}` === UNPROVABLE_STRAND_REASON && ageMs > UNPROVABLE_GATE_GRACE_MS) {
739
+ unprovableUngated++;
740
+ errFn(
741
+ `[publish-reconciler] UNPROVABLE task ${r.task_id}: no core branch and no published tip after ` +
742
+ `~${Math.round(ageMs / 3_600_000)}h — no longer gating its builder's queue (grace ` +
743
+ `${Math.round(UNPROVABLE_GATE_GRACE_MS / 3_600_000)}h). It is still confirmed and still unlanded: ` +
744
+ `push the branch, or dispose of it deliberately if the work landed outside this repository.`
745
+ );
746
+ }
725
747
  } else if (prClosedUnmerged) {
726
748
  await announceStrand(
727
749
  r,
@@ -789,7 +811,7 @@ async function runSweep(deps = {}) {
789
811
  }
790
812
  if (staleStrands) {
791
813
  errFn(
792
- `[publish-reconciler] ${staleStrands} stale strand(s): confirmed >${Math.round(STALE_CONFIRMED_MS / 60_000)}min with no provable land — credited but unlanded; see STRAND lines above`
814
+ `[publish-reconciler] ${staleStrands} stale strand(s): confirmed >${Math.round(STALE_CONFIRMED_MS / 60_000)}min with no provable land — credited but unlanded; see STRAND lines above${unprovableUngated ? `; ${unprovableUngated} past the gate grace and no longer blocking (see UNPROVABLE lines)` : ''}`
793
815
  );
794
816
  }
795
817
  // task 1002723: the always-on per-sweep account. Every confirmed task the sweep
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.835",
3
+ "version": "1.19.836",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.835",
9
+ "version": "1.19.836",
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.835",
3
+ "version": "1.19.836",
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",
@@ -20,6 +20,7 @@
20
20
  const fs = require('node:fs');
21
21
  const { cliClient, arg, stripBom, resolveQuiet, isBoxScope, printBoxScopeGuidanceAndExit, cliExit, repairMsysMangledPath,
22
22
  instanceDevBoxSupport, devBoxUnavailableMessage } = require('./cli-lib');
23
+ const { plainCardLines } = require('./plain-cards');
23
24
 
24
25
 
25
26
  (async () => {
@@ -96,7 +97,10 @@ const { cliClient, arg, stripBom, resolveQuiet, isBoxScope, printBoxScopeGuidanc
96
97
  // that a /box/ 404 always means — and only for a reader who typed a box command.
97
98
  if (res.status === 404 && /^\/api\/(?:bongos\/v1|bongos|gds)\/box\b/.test(urlPath)) {
98
99
  const { available } = await instanceDevBoxSupport();
99
- if (!available) console.error(`\n✖ ${devBoxUnavailableMessage()}`);
100
+ if (!available) {
101
+ console.error(`\n✖ ${devBoxUnavailableMessage()}`);
102
+ for (const line of plainCardLines('dev_box_unavailable')) console.error(line);
103
+ }
100
104
  }
101
105
  // task 1002734: process.exit() here aborted node 5/5 on Windows —
102
106
  // "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c"
@@ -56,6 +56,7 @@ const FILES = [
56
56
  'scripts/gds/install-git-hooks.js', // lazy dep of setup.js
57
57
  'scripts/gds/preflight.js',
58
58
  'scripts/gds/surface.js', // who the output is for: agent vs terminal (task 1003680)
59
+ 'scripts/gds/plain-cards.js', // required by cli-lib + release; prints nothing on the terminal surface (task 1003989)
59
60
  // The partial-checkout preflight (task 1003098). start/claim/ship require it
60
61
  // ABOVE their first module require, so an absent file here breaks every verb in
61
62
  // the published package -- which is exactly how tests/cli_package.mjs caught it.
@@ -31,6 +31,7 @@ const { CORE_NS } = require('./migration-namespace');
31
31
  const { contextPack, formatContextPack, resolveWorktreeRoot } = require('./context-pack');
32
32
  const { printClaimRecall } = require('./claim-recall');
33
33
  const { decideWorktreeClaim, ESCAPE_FLAG } = require('./worktree-claim-guard');
34
+ const { plainCardLines } = require('./plain-cards');
34
35
  // task 1003153 — the DEAD dependency edge, one definition shared with the route
35
36
  // and the detector. A lifecycle CLI may require the lifecycle module directly
36
37
  // (the documented exception in modules/lifecycle/CLAUDE.md).
@@ -164,7 +165,10 @@ function enforceWorktreeClaim(taskId, { allowMainCheckout }) {
164
165
  });
165
166
  if (d.verdict === 'allow') return tree;
166
167
  for (const line of d.lines) console.error(line);
167
- if (d.verdict === 'refuse') process.exit(2);
168
+ if (d.verdict === 'refuse') {
169
+ for (const line of plainCardLines('main_checkout_claim')) console.error(line);
170
+ process.exit(2);
171
+ }
168
172
  console.error('');
169
173
  return tree;
170
174
  }
@@ -315,8 +319,19 @@ function clip(s, max = 160) {
315
319
  // conflict apart from a failed worktree detection. Optional and defaulted — the
316
320
  // function stays pure and every existing caller/test keeps working.
317
321
  function formatClaimFailure(r, taskId, ctx) {
322
+ const info = claimErrorInfo(r);
323
+ const { lines, exit } = claimFailureGuidance(info, r, taskId, ctx);
324
+ // task 1003989: the same refusal in plain words, AFTER the curated text so nothing
325
+ // an engineer relies on moves. The owed-task gate names a flagged reason per task;
326
+ // the first one is translated too, because that token is what a builder is stuck on.
327
+ const { code, d } = info;
328
+ const owed = code === 'REBASE_REQUIRED' ? (d.rebaseTasks || d.rebase_tasks || []) : [];
329
+ const flagged = owed.length ? plainCardLines(String(owed[0].reason || '')) : [];
330
+ return { lines: [...lines, ...plainCardLines(code), ...flagged], exit };
331
+ }
332
+
333
+ function claimFailureGuidance({ code, message, d }, r, taskId, ctx) {
318
334
  const lines = [];
319
- const { code, message, d } = claimErrorInfo(r);
320
335
 
321
336
  // #766: a generic onboarding stage gate (kept rendering cleanly, not as raw JSON).
322
337
  if (code === 'stage_required') {
@@ -528,6 +528,7 @@ function printUnauthGuidanceAndExit() {
528
528
  console.error(' Once re-authed, re-run whichever command you were trying.');
529
529
  // `doctor` is checkout-only — it is not one of the public CLI's verbs (ADR 0258).
530
530
  if (!isTerminalSurface()) console.error(' For a full diagnosis, run: node scripts/gds/doctor.js');
531
+ for (const line of plainCardLines('session_invalid')) console.error(line);
531
532
  console.error('');
532
533
  process.exit(2);
533
534
  }
@@ -572,6 +573,7 @@ function printBoxScopeGuidanceAndExit() {
572
573
  console.error(' YOUR machine — under "CLI Access" click "Re-issue CLI token", copy the one-liner, paste it back.');
573
574
  console.error(' No terminal Device Flow. Once re-authed, re-run whichever command you were');
574
575
  console.error(' trying — if it had already done work, it resumes from where it stopped (task 941).');
576
+ for (const line of plainCardLines('box_scope')) console.error(line);
575
577
  console.error('');
576
578
  process.exit(10); // 10 = "re-auth needed" (matches reauth.js's exit code)
577
579
  }
@@ -655,6 +657,7 @@ function printInstanceMismatchAndExit({ expected, actual }) {
655
657
  console.error('');
656
658
  console.error(' Intentionally targeting another instance? Set the base explicitly, e.g.');
657
659
  console.error(` ${pfx}_API_BASE=${actual} <your command>`);
660
+ for (const line of plainCardLines('instance_mismatch')) console.error(line);
658
661
  console.error('');
659
662
  process.exit(3); // 3 = instance mismatch (distinct from 2 = unauth, 10 = box-scope)
660
663
  }
@@ -879,6 +882,7 @@ async function requireSession() {
879
882
  console.error(` • First time on the project → run /builder-setup (one-time GitHub sign-in that also registers you)`);
880
883
  console.error(` • Signed in before / expired → run /builder-reauth (fast UI re-issue of your CLI token)`);
881
884
  }
885
+ for (const line of plainCardLines('no_session')) console.error(line);
882
886
  process.exit(2);
883
887
  }
884
888
  return s;
@@ -984,6 +988,13 @@ function isTerminalSurface() {
984
988
  catch (_) { return false; }
985
989
  }
986
990
 
991
+ // The same refusal in plain words, for an agent to relay (task 1003989). Same fail-open
992
+ // shape as the two helpers above: a guidance printer must never die on its own footnote.
993
+ function plainCardLines(code) {
994
+ try { return require('./plain-cards').plainCardLines(code); }
995
+ catch (_) { return []; }
996
+ }
997
+
987
998
  function nudgeActionForStage(stage) {
988
999
  switch (stage) {
989
1000
  case 'auth_complete':