@bongos/core 1.19.835 → 1.19.837

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.
@@ -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':
@@ -375,12 +375,38 @@ function checkDeployTooling() {
375
375
  }
376
376
  }
377
377
 
378
+ // Intent-runner drift (task 1002722): a runner on a stale core once turned a domain-attach
379
+ // into a teardown (task 1002716). Only a host with *intent-runner* units shows this section,
380
+ // so a builder laptop or dev box sees nothing.
381
+ function runnerDriftRows(results) {
382
+ return (results || []).map((r) => {
383
+ const msg = `${r.name}: ${r.reason}`;
384
+ if (r.status === 'ok') return { status: 'ok', msg };
385
+ if (r.status === 'drift') return { status: 'fail', msg: `${msg} — repoint the unit's WorkingDirectory + ExecStart at the installed core (a systemd drop-in, as task 1002716 did).` };
386
+ return { status: 'warn', msg };
387
+ });
388
+ }
389
+
390
+ function checkRunnerDrift() {
391
+ let rows = [];
392
+ try {
393
+ const { collectRunnerDrift } = require('./runner-drift.js');
394
+ rows = runnerDriftRows(collectRunnerDrift({ instanceRoot: process.env.BONGOS_INSTANCE_ROOT || process.cwd() }));
395
+ } catch (_) {
396
+ return;
397
+ }
398
+ if (!rows.length) return;
399
+ section('Intent runners');
400
+ const emit = { ok, warn, fail };
401
+ for (const row of rows) emit[row.status]('runners', row.msg);
402
+ }
403
+
378
404
  // --------------------------------------------------------------------------
379
405
  // main
380
406
  // --------------------------------------------------------------------------
381
407
  // Guarded so requiring this file for its pure helpers (tests/doctor_hooks.mjs)
382
408
  // does not run the checks, which shell out to git and hit the network.
383
- module.exports = { classifyHooksState };
409
+ module.exports = { classifyHooksState, runnerDriftRows };
384
410
 
385
411
  if (require.main === module) (async () => {
386
412
  console.log(`${C.bold}GDS Doctor${C.reset} — local-machine health check`);
@@ -390,6 +416,7 @@ if (require.main === module) (async () => {
390
416
  checkGitPlumbing();
391
417
  await checkRepoState();
392
418
  checkEnvironment();
419
+ checkRunnerDrift();
393
420
 
394
421
  const fails = findings.filter((f) => f.status === 'fail').length;
395
422
  const warns = findings.filter((f) => f.status === 'warn').length;
@@ -0,0 +1,348 @@
1
+ // scripts/gds/plain-cards.js — the plain-language card for every failure a builder can
2
+ // be parked behind (task 1003989).
3
+ //
4
+ // WHY. The CLIs explain a refusal in engineer prose, and the lifecycle skills told the
5
+ // agent to relay it verbatim — so a non-technical builder met `REBASE_REQUIRED` or
6
+ // `strand:pr_not_landed` raw, at the moment they were most stuck, whatever their
7
+ // interaction profile said. A card says the same thing four ways a person can use:
8
+ // what happened, what the agent will do next, whether a PERSON is needed (and who), and
9
+ // whether this is a rule rather than a fault. That last one is the expensive mistake:
10
+ // an agent that reads a deliberate restriction as breakage goes looking for a way round
11
+ // it (the box session that asked the owner for a personal access token).
12
+ //
13
+ // STATIC TEXT ONLY. A card interpolates nothing — no task title, no server message — so
14
+ // printing it adds no prompt-injection surface (the strand-watch / push-path-brief rule).
15
+ // The engineer text above it keeps the ids. tests/plain_cards.mjs fails when a CLI gains
16
+ // a failure code with no card, and when a card drifts back into the vocabulary it exists
17
+ // to translate (it reuses the accessibility scorer's jargon list, ADR 0291).
18
+
19
+ 'use strict';
20
+
21
+ const { isTerminal } = require('./surface');
22
+
23
+ // The line a skill looks for. Agent-surface marker, same family as [otb-confirm].
24
+ const PLAIN_MARKER = '[otb-plain]';
25
+
26
+ const RULE = true; // deliberate: never look for a way round it
27
+ const FAULT = false; // something is actually off; diagnosing it is fine
28
+
29
+ const card = (happened, next, person, byDesign) => Object.freeze({ happened, next, person, by_design: byDesign });
30
+
31
+ const ONE_TASK_PER_SESSION = card(
32
+ 'This chat session is already working on another task. Each session handles one task at a time, so finished work never gets mixed together.',
33
+ 'I will finish or hand back the task we already have — or tell you how to open a second session for this one.',
34
+ null, RULE);
35
+
36
+ const CARDS = Object.freeze({
37
+ // ── starting a task ─────────────────────────────────────────────────────────
38
+ stage_required: card(
39
+ 'There is a getting-started step to finish before this task can be started.',
40
+ 'I will show you the step the server named and help you complete it.',
41
+ null, RULE),
42
+ session_already_has_active_claim: ONE_TASK_PER_SESSION,
43
+ session_claim_check_failed: ONE_TASK_PER_SESSION,
44
+ session_already_has_bound_claim: ONE_TASK_PER_SESSION,
45
+ newcomer_must_ship_first: card(
46
+ 'New builders finish their first task before starting a second one.',
47
+ 'I will help you finish the task you already have.',
48
+ null, RULE),
49
+ REBASE_REQUIRED: card(
50
+ 'An earlier task of yours was approved but has not made it into the project yet, so starting new tasks is paused until it does.',
51
+ 'I will run /strand-fix on the stuck task. That is the supported way to land it and it needs nothing technical from you.',
52
+ 'Only if /strand-fix says it needs the owner.', RULE),
53
+ ALREADY_CLAIMED: card(
54
+ 'Someone else started this task a moment before us.',
55
+ 'I will pull a fresh list and suggest another task. Trying again would not help.',
56
+ null, RULE),
57
+ MODULE_CONFLICT: card(
58
+ 'Another task in progress is changing the same part of the project.',
59
+ 'I will suggest a task in a different area, or we wait for the other one to finish.',
60
+ null, RULE),
61
+ TOUCHES_CONFLICT: card(
62
+ 'Another task in progress is changing some of the same files.',
63
+ 'I will suggest a task that does not overlap, or we wait for the other one to finish.',
64
+ null, RULE),
65
+ TASK_NOT_READY: card(
66
+ 'This task is not open for work yet — its status says so.',
67
+ 'I will tell you its status and find a task that is ready.',
68
+ null, RULE),
69
+ TASK_NOT_FOUND: card(
70
+ 'No task has that number.',
71
+ 'I will list the tasks that exist so we can pick the right one.',
72
+ null, FAULT),
73
+ DEPS_NOT_SHIPPED: card(
74
+ 'This task builds on other tasks that are not finished yet.',
75
+ 'I will show which ones. If one of them was cancelled, the link between them needs a person to re-decide it.',
76
+ 'Sometimes — whoever owns the goal, when a task it depends on was cancelled.', RULE),
77
+ OPEN_BLOCKER: card(
78
+ 'This task is waiting on a decision or some input that was written down as a blocker.',
79
+ 'I will show you the blocker. /blocker-solve can drive it to done.',
80
+ 'Usually the owner, for the decision the blocker names.', RULE),
81
+ GOAL_MEMBERSHIP_REQUIRED: card(
82
+ 'This task belongs to a goal you have not joined.',
83
+ 'I will offer to join it for you — or apply, if it is private. I will not do either without your yes.',
84
+ 'For a private goal, its owner approves the request.', RULE),
85
+ archon_approval_required: card(
86
+ 'This goal covers a protected part of the project, so joining it needs an Archon to say yes.',
87
+ 'I will file the request for you and we pick other work while it waits.',
88
+ 'An Archon approves the request.', RULE),
89
+ goal_private: card(
90
+ 'This goal is private, so joining it is by request.',
91
+ 'I will file the request if you want, and we pick other work while it waits.',
92
+ 'The goal owner approves the request.', RULE),
93
+ request_pending: card(
94
+ 'Your request to join this goal is already waiting for an answer.',
95
+ 'Nothing more to send. I will suggest other work in the meantime.',
96
+ 'The goal owner, who has the request.', RULE),
97
+ BUILDER_INACTIVE: card(
98
+ 'Your builder account is switched off, so it cannot start tasks.',
99
+ 'I cannot change that from here. Ask an Archon to switch the account back on.',
100
+ 'An Archon.', RULE),
101
+ INSUFFICIENT_RANK: card(
102
+ 'This task needs a higher rank than yours.',
103
+ 'I will find tasks open to your rank. I will not look for a way round the rank rule.',
104
+ 'An Archon grants ranks.', RULE),
105
+ rank_too_low_for_task: card(
106
+ 'Your rank cannot start this kind of task yet.',
107
+ 'I will find tasks open to your rank. I will not look for a way round the rank rule.',
108
+ 'An Archon grants ranks.', RULE),
109
+ BATCH_DUPLICATE_IDS: card(
110
+ 'The same task was listed twice in one request.',
111
+ 'I will send the list again with each task once.',
112
+ null, FAULT),
113
+ BATCH_EMPTY: card(
114
+ 'The request to start several tasks had no tasks in it.',
115
+ 'I will ask which tasks you meant.',
116
+ null, FAULT),
117
+ BATCH_FAILED: card(
118
+ 'One of the tasks in the group could not be started, so none of them were.',
119
+ 'I will show which one and why, then retry with the rest if you want.',
120
+ null, RULE),
121
+ main_checkout_claim: card(
122
+ 'Tasks are worked on in their own separate copy of the project, and this session is standing in the main copy.',
123
+ 'I will make the separate copy with one command and start the task from there.',
124
+ null, RULE),
125
+
126
+ board_unavailable: card(
127
+ 'The list of tasks could not be loaded — the project answered with an error.',
128
+ 'I will check that the project is reachable and that you are signed in, then try once more.',
129
+ null, FAULT),
130
+
131
+ // ── handing a task back ─────────────────────────────────────────────────────
132
+ CLAIM_SESSION_MISMATCH: card(
133
+ 'This task was started from a different chat session, so this one is not allowed to hand it back by accident.',
134
+ 'If you are sure it is yours to give up, I will repeat the command with the override it names.',
135
+ null, RULE),
136
+ no_active_claim: card(
137
+ 'You are not working on that task right now, so there is nothing to hand back.',
138
+ 'I will show what you do have in progress.',
139
+ null, FAULT),
140
+
141
+ // ── why an approved task has not landed ─────────────────────────────────────
142
+ 'strand:pr_not_landed': card(
143
+ 'Your work was approved and sent for the automatic checks, but it never made it into the project.',
144
+ 'I will look up what stopped it and fix that, then land it.',
145
+ null, FAULT),
146
+ 'strand:pr_closed_unmerged': card(
147
+ 'The request to add your work was closed without being accepted, so it will never land by waiting.',
148
+ 'I will send the work again. If a check failed, I fix that first.',
149
+ null, FAULT),
150
+ 'strand:no_branch_no_tip': card(
151
+ 'The task was approved, but its work never left the computer it was written on.',
152
+ 'I will send the work from this computer. If it lives on another machine, it has to be sent from there.',
153
+ 'Only if the work is on a machine I cannot reach.', FAULT),
154
+ 'strand:land_not_proven': card(
155
+ 'The project could not prove that your approved work actually arrived.',
156
+ 'I will check what arrived and send the work again if it did not.',
157
+ null, FAULT),
158
+ 'strand:unresolvable_conflict': card(
159
+ 'Your work and newer work changed the same lines, and the automatic fix could not choose between them.',
160
+ 'I will bring your work up to date by hand, keeping both changes, and send it again.',
161
+ null, FAULT),
162
+ 'strand:branch_modifies_executed_code': card(
163
+ 'Your work changes the tools that would be used to update it, so the automatic update was refused on purpose.',
164
+ 'I will bring your work up to date on this computer instead, and send it again.',
165
+ null, RULE),
166
+
167
+ // ── why a request to add work has not been accepted ─────────────────────────
168
+ failing_check: card(
169
+ 'An automatic test failed on your work, so it has not been added to the project.',
170
+ 'I will run that test here, fix what it found, and send the work again. Sending the same work again would fail the same way.',
171
+ null, FAULT),
172
+ non_required_check_failing: card(
173
+ 'An optional automatic test failed on your work.',
174
+ 'I will look at what it found. It does not stop the work being added, but it is usually worth fixing.',
175
+ null, FAULT),
176
+ non_required_check_pending: card(
177
+ 'An optional automatic test is still running on your work.',
178
+ 'Nothing to do yet — I will check again shortly.',
179
+ null, RULE),
180
+ failing_check_unreadable: card(
181
+ 'An automatic test failed on your work, but this project is not allowed to see which one.',
182
+ 'I will run all the tests here to find it, fix it, and send the work again. Sending the same work again cannot help.',
183
+ 'The owner can switch on the permission that shows which test failed.', FAULT),
184
+ blocked_required_review_or_pending_check: card(
185
+ 'Your work is waiting on a test that has not finished, or on a review.',
186
+ 'Nothing to do yet — I will check again shortly.',
187
+ null, RULE),
188
+ review_required: card(
189
+ 'Your work needs a person to review it before it can be added.',
190
+ 'I will tell you who can review it. I cannot approve my own work.',
191
+ 'A reviewer with the right to approve.', RULE),
192
+ changes_requested: card(
193
+ 'A reviewer asked for changes before your work can be added.',
194
+ 'I will read what they asked for and make the changes.',
195
+ null, RULE),
196
+ merge_conflict: card(
197
+ 'Your work and newer work changed the same lines, so they cannot be combined automatically.',
198
+ 'I will bring your work up to date, keeping both changes, and send it again.',
199
+ null, FAULT),
200
+ branch_behind_base: card(
201
+ 'The project moved on while your work was being checked, so your work needs refreshing first.',
202
+ 'The server usually does this by itself. If it has not, I will refresh it and send it again.',
203
+ null, RULE),
204
+ draft_pr: card(
205
+ 'Your work was sent as a draft, and drafts are not added to the project.',
206
+ 'I will mark it ready.',
207
+ null, FAULT),
208
+ mergeability_computing: card(
209
+ 'The project is still working out whether your work can be added.',
210
+ 'Nothing to do yet — I will check again in a moment.',
211
+ null, RULE),
212
+ no_open_pr: card(
213
+ 'There is no open request to add your work to the project.',
214
+ 'I will check whether it was already added, and send it if it was not.',
215
+ null, FAULT),
216
+ unknown: card(
217
+ 'The project did not say why your work has not been added.',
218
+ 'I will check again, and look at it directly if the answer stays empty.',
219
+ null, FAULT),
220
+
221
+ // ── sending work from this computer ─────────────────────────────────────────
222
+ 'ship:workflow_scope': card(
223
+ 'Your work changes the project\'s automatic test setup, and the sign-in this computer uses is not allowed to send that kind of change.',
224
+ 'Sending it again will fail the same way. I will show the one-time command that sends it another way.',
225
+ 'You, once, to run that command or refresh the sign-in.', RULE),
226
+ 'ship:protected_branch': card(
227
+ 'Nobody is allowed to put work straight into the project\'s main copy — everything goes through the automatic checks first.',
228
+ 'I will send your work through the checked route instead. I will not try to get round the protection.',
229
+ null, RULE),
230
+ 'ship:auth': card(
231
+ 'This computer\'s sign-in to the code host was refused.',
232
+ 'I will show you how to sign in again. I will not ask anyone for a password or a key.',
233
+ 'You, to sign in again.', RULE),
234
+ 'ship:stale_branch': card(
235
+ 'The project moved on since your work started, so it has to be refreshed before it can be sent.',
236
+ 'I will refresh it and send it again.',
237
+ null, FAULT),
238
+ 'ship:unknown': card(
239
+ 'Sending your work failed for a reason I do not recognise.',
240
+ 'I will read the message above, fix what it names, and try again once.',
241
+ null, FAULT),
242
+ 'ship:main-checkout-on-branch': card(
243
+ 'This task was worked on in the project\'s main copy instead of its own separate copy, so it cannot be added the usual way.',
244
+ 'I will send it through the checked route instead. Next time I will start the task in its own copy.',
245
+ null, FAULT),
246
+ 'ship:no-task-branch': card(
247
+ 'I could not find the saved work for this task on this computer.',
248
+ 'I will look for where it was saved. If it is on another machine, it has to be sent from there.',
249
+ 'Only if the work is on a machine I cannot reach.', FAULT),
250
+ land_pending: card(
251
+ 'Your work passed review and is waiting for the automatic tests. The server adds it to the project by itself when they pass.',
252
+ 'Nothing to do. This is normal, not a failure.',
253
+ null, RULE),
254
+ reconcile_blocked: card(
255
+ 'Your work was added to the project, but the server could not prove it was this task\'s, so the task is not marked finished yet.',
256
+ 'The server re-checks every few minutes. If it stays stuck I will run the ship command again.',
257
+ null, FAULT),
258
+ smoke_failed: card(
259
+ 'A quick health check of the project failed before your work was handed in.',
260
+ 'I will find what the check tripped on and fix it before handing the work in.',
261
+ null, FAULT),
262
+ nothing_to_ship: card(
263
+ 'There is no saved work for this task, so there is nothing to hand in.',
264
+ 'I will check whether the work was saved. If the task really needed no changes, I will say so explicitly.',
265
+ null, RULE),
266
+ uncommitted_work: card(
267
+ 'Some of your work has not been saved into the project\'s history yet, so handing it in now would leave it behind.',
268
+ 'I will save it, then hand it in.',
269
+ null, RULE),
270
+
271
+ // ── the review of your work ─────────────────────────────────────────────────
272
+ grade_unavailable: card(
273
+ 'The automatic reviewers could not run. Your work was not judged — this is an outage, not a rejection.',
274
+ 'I will ask for the review again later. There is nothing in your work to fix.',
275
+ 'Only if the outage lasts — an Archon can accept the work by hand.', FAULT),
276
+ grade_lens_unavailable: card(
277
+ 'One of the automatic reviewers did not run; the others found nothing wrong. Your work was not rejected.',
278
+ 'I will ask for the review again when the reviewers are healthy.',
279
+ 'Only if it keeps happening — an Archon can accept the work by hand.', FAULT),
280
+ grade_refused_authorization: card(
281
+ 'Your work touches a protected part of the project that your rank may not change, so the server would not accept it.',
282
+ 'Asking again will be refused again. I will take the protected files out and hand in the rest, or we pass the task to someone with the rank.',
283
+ 'A builder with a high enough rank, for the protected part.', RULE),
284
+ grade_failed: card(
285
+ 'The automatic reviewers found problems in your work, so it is not accepted yet.',
286
+ 'I will fix what they listed, save it, and ask for the review again. That costs nothing extra.',
287
+ null, FAULT),
288
+
289
+ // ── signing in ──────────────────────────────────────────────────────────────
290
+ no_session: card(
291
+ 'This computer is not signed in to the project yet.',
292
+ 'I will walk you through signing in — it happens in your browser.',
293
+ 'You, to sign in.', RULE),
294
+ session_invalid: card(
295
+ 'Your sign-in to the project has ended.',
296
+ 'I will walk you through signing in again — Settings, then one copy and paste.',
297
+ 'You, to sign in again.', RULE),
298
+ box_scope: card(
299
+ 'This sign-in is limited on purpose: it can do the work, but not hand it in.',
300
+ 'I will walk you through the full sign-in, then hand the work in.',
301
+ 'You, to sign in again.', RULE),
302
+ instance_mismatch: card(
303
+ 'This computer is signed in to a different project than the one this folder belongs to.',
304
+ 'I will show which project each one is, and help you sign in to the right one.',
305
+ 'You, to choose the project.', FAULT),
306
+ dev_box_unavailable: card(
307
+ 'This project does not offer hosted computers to work from.',
308
+ 'We can work from this computer instead.',
309
+ null, RULE),
310
+ no_push_credential: card(
311
+ 'This computer is not allowed to send work to the code host itself. That is deliberate: the server sends it for you.',
312
+ 'I will hand the work in with /builder-ship and read its progress from the server. I will never ask anyone for a password, key or sign-in to work round this.',
313
+ null, RULE),
314
+ });
315
+
316
+ // cardFor(code) → the card, or null. Reasons that carry a name or a file list after a
317
+ // colon (`failing_check:unit`, `strand:unresolvable_conflict:a.md,b.md`) resolve on
318
+ // their stem. `XENOS_RESTRICTED` is the legacy spelling of rank_too_low_for_task.
319
+ function cardFor(code) {
320
+ if (typeof code !== 'string' || !code) return null;
321
+ if (code === 'XENOS_RESTRICTED') return CARDS.rank_too_low_for_task;
322
+ if (Object.prototype.hasOwnProperty.call(CARDS, code)) return CARDS[code];
323
+ const parts = code.split(':');
324
+ for (let n = parts.length - 1; n >= 1; n--) {
325
+ const stem = parts.slice(0, n).join(':');
326
+ if (Object.prototype.hasOwnProperty.call(CARDS, stem)) return CARDS[stem];
327
+ }
328
+ return null;
329
+ }
330
+
331
+ // plainCardLines(code) → the printable card, or [] for a code with none. Never throws:
332
+ // a missing card must not be able to break the refusal it was meant to explain.
333
+ // AGENT SURFACE ONLY: a card is written in the agent's voice ("I will…") for a
334
+ // session to relay. A person running the `bongos` CLI reads the text above it directly,
335
+ // and several cards name slash commands that do not exist there (tests/cli_surface.mjs).
336
+ function plainCardLines(code, env = process.env) {
337
+ const c = isTerminal(env) ? null : cardFor(code);
338
+ if (!c) return [];
339
+ return [
340
+ ` ${PLAIN_MARKER} in plain words`,
341
+ ` What happened ${c.happened}`,
342
+ ` What I will do ${c.next}`,
343
+ ` Needs a person ${c.person || 'No.'}`,
344
+ ` By design ${c.by_design ? 'Yes — this is a rule, not a fault. Do not look for a way round it.' : 'No — something is actually off, so looking into it is right.'}`,
345
+ ];
346
+ }
347
+
348
+ module.exports = { CARDS, cardFor, plainCardLines, PLAIN_MARKER };
@@ -54,6 +54,7 @@ const { APP_USER, alreadyScaffolded, buildCorePinRefreshCommit, dbCreateCmd, dbN
54
54
  const { backupScriptPath, backupService, backupServicePath, backupTimer, backupTimerPath, backupUnitName, instanceManifestCmd, originEnvVarsFor, serviceUnit, serviceUnitPath, settingsConsumed, settingsEnvVarsFor, upsertEnvVars, webEnvBody, webEnvPath } = require('./provision-units.js');
55
55
  const { caddyBlock, caddySnippetPath, checkDnsTokenScope, classifyZoneScope, dnsReleaseEnv, dnsUpsertEnv, federateInstance, federationHubOrigin, healthzCmd, identityCmd, identityVerdict, shouldFederate, zoneForDomain } = require('./provision-net.js');
56
56
  const { capacityPreflight, statusCapacityLine } = require('./provision-capacity.js');
57
+ const { installedCoreVersion, runnerBanner } = require('./runner-drift.js');
57
58
  const { teardownInstance } = require('./provision-teardown.js');
58
59
  const { disconnectInstance } = require('./provision-disconnect.js');
59
60
  const { coreUpgradeInstance, snapshotAllCoreVersions } = require('./provision-core-upgrade.js'); // the owner's deploy door, ADR 0293
@@ -1221,6 +1222,8 @@ async function drainOAuthManifests(deps) {
1221
1222
  // ---------------------------------------------------------------------------
1222
1223
  async function cmdRunIntents(deps) {
1223
1224
  const { db, provisioning, log, apply } = deps;
1225
+ const instanceRoot = deps.instanceRoot || process.env.BONGOS_INSTANCE_ROOT || process.cwd();
1226
+ log(runnerBanner({ runnerVersion: coreVersionSafe(), runnerRoot: REPO_ROOT, instanceVersion: installedCoreVersion(instanceRoot), instanceRoot }));
1224
1227
  if (!apply) {
1225
1228
  const { rows } = await db.query(
1226
1229
  `SELECT pi.*, i.slug FROM provisioning_intents pi
@@ -1254,7 +1257,7 @@ async function cmdRunIntents(deps) {
1254
1257
  else if (intent.action === 'restart') result = await restartInstance(inst, deps);
1255
1258
  else if (intent.action === 'disconnect') result = await disconnectInstance(inst, deps);
1256
1259
  else if (intent.action === 'core-upgrade') result = await coreUpgradeInstance(inst, deps, intent); // also reads the INTENT: the version the owner previewed (ADR 0293 D4)
1257
- else result = { ok: false, error: `unknown intent action '${intent.action}' — refusing to guess` };
1260
+ else result = { ok: false, error: `unknown_action '${intent.action}' — runner core ${coreVersionSafe() || '?'} does not know it (runner too old?); refusing to guess` };
1258
1261
  if (result && result.ok === false) {
1259
1262
  if (intent.attempts >= MAX_INTENT_ATTEMPTS) {
1260
1263
  await provisioning.resolveIntent(db, intent.id, 'error', result.error || 'op failed');
@@ -33,6 +33,7 @@
33
33
  // Detection override (tests / forcing): OTB_PUSH_VIA_SERVER=1 (server) / =0 (local).
34
34
 
35
35
  const { hasFlag } = require('./cli-lib');
36
+ const { plainCardLines } = require('./plain-cards');
36
37
 
37
38
  // ---- pure core ---------------------------------------------------------------
38
39
 
@@ -70,6 +71,7 @@ function report(via) {
70
71
  if (via === 'server') {
71
72
  console.log(' server-mediated (no authenticated gh — a dev box or credential-less checkout).');
72
73
  console.log(` ${BRIEF}`);
74
+ for (const line of plainCardLines('no_push_credential')) console.log(line);
73
75
  } else if (via === 'local') {
74
76
  console.log(' local — this machine pushes with its own git + gh credentials.');
75
77
  } else {
@@ -10,6 +10,7 @@ const { execFileSync } = require('node:child_process');
10
10
  const path = require('node:path');
11
11
  const { cliClient, requireSession, arg } = require('./cli-lib');
12
12
  const { parkedMainCheckoutWarning } = require('./worktree-claim-guard');
13
+ const { plainCardLines } = require('./plain-cards');
13
14
 
14
15
  // gitAt(args, cwd) — run git with an ARGV ARRAY and no shell.
15
16
  //
@@ -73,6 +74,7 @@ function mainCheckoutRoot() {
73
74
  console.error(`Your active claims:`);
74
75
  for (const c of claims) console.error(` #${c.task_id}: ${c.title}`);
75
76
  }
77
+ for (const line of plainCardLines('no_active_claim')) console.error(line);
76
78
  process.exit(1);
77
79
  }
78
80
 
@@ -97,6 +99,7 @@ function mainCheckoutRoot() {
97
99
  console.error(`This claim was created in a different session${wt ? ` (worktree: ${wt})` : ''}.`);
98
100
  console.error('Only the original session can release it.');
99
101
  console.error('If that session is gone, re-run with --force to override.');
102
+ for (const line of plainCardLines('CLAIM_SESSION_MISMATCH')) console.error(line);
100
103
  } else {
101
104
  console.error(`release failed (${r.status}):`, r.data);
102
105
  }