wicked-crew 0.9.2 → 0.9.3

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.
Files changed (75) hide show
  1. package/dist/api/delivery-assurance.d.ts +22 -1
  2. package/dist/api/delivery-assurance.d.ts.map +1 -1
  3. package/dist/api/delivery-assurance.js +66 -2
  4. package/dist/api/delivery-assurance.js.map +1 -1
  5. package/dist/api/routes.d.ts +148 -8
  6. package/dist/api/routes.d.ts.map +1 -1
  7. package/dist/api/routes.js +54 -16
  8. package/dist/api/routes.js.map +1 -1
  9. package/dist/api/server.d.ts.map +1 -1
  10. package/dist/api/server.js +31 -31
  11. package/dist/api/server.js.map +1 -1
  12. package/dist/cli/index.d.ts.map +1 -1
  13. package/dist/cli/index.js +32 -1
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/service.js +1 -1
  16. package/dist/cli/service.js.map +1 -1
  17. package/dist/core/adapter.d.ts +6 -3
  18. package/dist/core/adapter.d.ts.map +1 -1
  19. package/dist/core/adapter.js +36 -17
  20. package/dist/core/adapter.js.map +1 -1
  21. package/dist/core/ado-deliver-helper.d.ts +5 -0
  22. package/dist/core/ado-deliver-helper.d.ts.map +1 -1
  23. package/dist/core/ado-deliver-helper.js +50 -2
  24. package/dist/core/ado-deliver-helper.js.map +1 -1
  25. package/dist/core/deliver-credentials.d.ts +8 -4
  26. package/dist/core/deliver-credentials.d.ts.map +1 -1
  27. package/dist/core/deliver-credentials.js +25 -7
  28. package/dist/core/deliver-credentials.js.map +1 -1
  29. package/dist/core/deliver-text.d.ts +8 -0
  30. package/dist/core/deliver-text.d.ts.map +1 -1
  31. package/dist/core/deliver-text.js +16 -0
  32. package/dist/core/deliver-text.js.map +1 -1
  33. package/dist/core/deliver.d.ts +22 -18
  34. package/dist/core/deliver.d.ts.map +1 -1
  35. package/dist/core/deliver.js +168 -79
  36. package/dist/core/deliver.js.map +1 -1
  37. package/dist/core/types.d.ts +7 -0
  38. package/dist/core/types.d.ts.map +1 -1
  39. package/dist/core/types.js.map +1 -1
  40. package/dist/interactive/chat-events.d.ts +11 -20
  41. package/dist/interactive/chat-events.d.ts.map +1 -1
  42. package/dist/interactive/chat-events.js +69 -108
  43. package/dist/interactive/chat-events.js.map +1 -1
  44. package/dist/interactive/draft-events.d.ts +13 -46
  45. package/dist/interactive/draft-events.d.ts.map +1 -1
  46. package/dist/interactive/draft-events.js +81 -130
  47. package/dist/interactive/draft-events.js.map +1 -1
  48. package/dist/interactive/draft-skill.d.ts +11 -19
  49. package/dist/interactive/draft-skill.d.ts.map +1 -1
  50. package/dist/interactive/draft-skill.js +16 -31
  51. package/dist/interactive/draft-skill.js.map +1 -1
  52. package/dist/interactive/edit-events.d.ts +11 -21
  53. package/dist/interactive/edit-events.d.ts.map +1 -1
  54. package/dist/interactive/edit-events.js +70 -83
  55. package/dist/interactive/edit-events.js.map +1 -1
  56. package/dist/interactive/run-units.d.ts +91 -0
  57. package/dist/interactive/run-units.d.ts.map +1 -0
  58. package/dist/interactive/run-units.js +149 -0
  59. package/dist/interactive/run-units.js.map +1 -0
  60. package/dist/skills/core-closure.d.ts +26 -0
  61. package/dist/skills/core-closure.d.ts.map +1 -1
  62. package/dist/skills/core-closure.js +45 -0
  63. package/dist/skills/core-closure.js.map +1 -1
  64. package/dist/skills/phase-skill-gaps.d.ts +16 -71
  65. package/dist/skills/phase-skill-gaps.d.ts.map +1 -1
  66. package/dist/skills/phase-skill-gaps.js +16 -145
  67. package/dist/skills/phase-skill-gaps.js.map +1 -1
  68. package/dist/studio/assets/{index-BLYKfepE.css → index-BGfFmWw4.css} +1 -1
  69. package/dist/studio/assets/{index-BbXtfDnB.js → index-BMRUMU4X.js} +112 -112
  70. package/dist/studio/editors/wicked-page/editor.json +1 -1
  71. package/dist/studio/editors/wicked-page/index.html +1 -1
  72. package/dist/studio/index.html +2 -2
  73. package/dist/studio/testid-inventory.json +83 -5
  74. package/endpoint-manifest.json +1 -1
  75. package/package.json +3 -3
@@ -40,11 +40,13 @@
40
40
  * (f) the PR URL is the last line of the phase output.
41
41
  *
42
42
  * One deliberate change from the field version: NO gh account is baked into crew code (the
43
- * overlay guarded a personal account). Instead, when the `GH_ACCOUNT` env var is set the script
44
- * compares it against `gh api user -q .login` and REFUSES when they differ (DES-L9 D-18, crew#549):
45
- * the daemon's push identity is what its gh — or an exported `GH_TOKEN` — holds, disclosed on the
46
- * deliver gate card, never switched at push time (the switch this replaced pushed under whatever
47
- * account it could flip to, and a 403 hard-failed the run). Unset ⇒ whatever gh holds, said aloud.
43
+ * overlay guarded a personal account). Instead the push identity is CONFIGURED (the deliver
44
+ * identity setting, the repository's pin, or the `GH_ACCOUNT` env var) and chosen PER COMMAND
45
+ * (crew#940): on a github.com origin the script reads that account's own token from gh's keyring
46
+ * and hands it to its gh calls and its git fetch/push alone, so gh's machine-wide active account
47
+ * never decides who pushes; an exported `GH_TOKEN` wins, and one that authenticates as another
48
+ * login is refused. It is disclosed on the deliver gate card and never switched at push time.
49
+ * Unset ⇒ whatever gh holds, said aloud.
48
50
  *
49
51
  * REVISION mode (DES-L9 / crew#550, `POST /runs {revisesPr}`): the run was based on an OPEN pull
50
52
  * request's head branch; the script pushes `wicked/<run>` onto `refs/heads/<that branch>` (the PR
@@ -89,7 +91,7 @@ import { childEnvWithBootEstateDb } from './governance-store.js';
89
91
  import { ADO_HELPER_JS } from './ado-deliver-helper.js';
90
92
  import { ADO_PAT_ENV, ADO_SP_ENV, DELIVER_CREDENTIALS_MISSING_MARKER, adoRepoOf } from './deliver-credentials.js';
91
93
  import { DELIVER_NOT_SHIPPED_HEADING, DELIVER_STRANDED_SENTINEL, ENV_TEMPLATE_SAFE_LINE_ERE, SCRATCH_DIRS, TOOL_ARTIFACT_DIRS } from './deliver-exclusions.js';
92
- import { composeEmbeddedDeliverText, factsFromWorkflow, framedDeliverText, runUrlFor, configuredPublicOrigin, commitSubject, urlPathSegment, } from './deliver-text.js';
94
+ import { adoWorkItemIds, composeEmbeddedDeliverText, factsFromWorkflow, framedDeliverText, runUrlFor, configuredPublicOrigin, commitSubject, urlPathSegment, } from './deliver-text.js';
93
95
  /**
94
96
  * The content-address of wicked-core's built-in evidence floor (`builtin_floors::EVIDENCE_FLOOR_PIN`,
95
97
  * criterion "the run left a change in its worktree (done is re-derived from the diff, never
@@ -190,21 +192,14 @@ export function deliverIdentityFor(settings, repoRef) {
190
192
  return { login: pinned.trim(), source: 'repo' };
191
193
  return { login: (settings.deliverIdentityLogin ?? '').trim(), source: 'setting' };
192
194
  }
193
- /**
194
- * (crew#737) The logins gh holds for github.com on this machine (`gh auth status`), bounded to 4 s.
195
- * `null` when gh is absent, times out or answers nothing parseable — unknown, never "none". Reads
196
- * the account NAMES only; no token is read here.
197
- */
198
- export async function ghSignedInLogins(host = 'github.com') {
199
- const res = await new Promise((resolve) => {
200
- execFile('gh', ['auth', 'status', '--hostname', host], { timeout: 4_000, encoding: 'utf8', env: childEnvWithBootEstateDb(process.env) }, (err, stdout, stderr) => {
201
- const spawnFailed = err !== null && typeof err.code !== 'number';
202
- resolve({ out: `${String(stdout ?? '')}\n${String(stderr ?? '')}`, ok: !spawnFailed });
203
- });
204
- });
205
- if (!res.ok)
206
- return null;
207
- return parseGhAuthStatusLogins(res.out);
195
+ /** (crew#940) The push identity a delivery pins: the configured login (the repository's pin or the
196
+ * setting), else `GH_ACCOUNT`; `null` = none. Pure over `env`. */
197
+ export function pushIdentityOf(configured, env = process.env) {
198
+ const c = (configured ?? '').trim();
199
+ if (c !== '')
200
+ return c;
201
+ const g = (env['GH_ACCOUNT'] ?? '').trim();
202
+ return g === '' ? null : g;
208
203
  }
209
204
  /** The logins in `gh auth status` text: "Logged in to <host> account <login>" (gh ≥ 2.40) or
210
205
  * "Logged in to <host> as <login>" (older). Pure, for the tests. */
@@ -230,13 +225,34 @@ export function isSafeRefName(name) {
230
225
  }
231
226
  /** The one shape a PR URL may take before it is baked into the script and printed as the phase's
232
227
  * last line (crew re-derives "delivered" from that line — `prUrlFrom`). */
233
- const SAFE_PR_URL = /^https:\/\/[A-Za-z0-9.-]+\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+\/pull\/[0-9]+$/;
228
+ const SAFE_PR_URL = /^https:\/\/[A-Za-z0-9.-]+\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+\/pull\/[0-9]+$|^https:\/\/dev\.azure\.com\/[A-Za-z0-9._~%()-]+\/[A-Za-z0-9._~%()-]+\/_git\/[A-Za-z0-9._~%()-]+\/pullrequest\/[0-9]+$/;
234
229
  /** The `bug` def's `fix` phase instructions — the SAME literal wicked-core's `bug_def()` carries
235
230
  * (`BUG_FIX_SWEEP_INSTRUCTIONS`, DES-L9 BC-60 / core#432): both carriers are live (`deliver:pr`
236
231
  * plans from this mirror, `deliver:none` from core's def), so one string, pinned by a test. Short
237
232
  * (≤ 90 ASCII bytes) on purpose — the PTY carrier's whole prompt is 1000 B and core's budget test
238
233
  * keeps ≥ 300 B of intent headroom. */
239
234
  export const BUG_FIX_SWEEP_INSTRUCTIONS = 'Update every consumer of behaviour this fix retires or changes: tests, docs, comments.';
235
+ /** The helper written to a private temp dir and run with the node that runs crew; the credential
236
+ * stays in the daemon's environment and is read by the helper alone. */
237
+ export const defaultAdoHelperExec = async (args, opts) => {
238
+ const { mkdtemp, writeFile, rm } = await import('node:fs/promises');
239
+ const { tmpdir } = await import('node:os');
240
+ const { join } = await import('node:path');
241
+ const dir = await mkdtemp(join(tmpdir(), 'crew-ado-'));
242
+ try {
243
+ const file = join(dir, 'ado.mjs');
244
+ await writeFile(file, ADO_HELPER_JS, { mode: 0o600 });
245
+ return await new Promise((resolve) => {
246
+ execFile(process.execPath, [file, ...args], { timeout: opts.timeoutMs, encoding: 'utf8', env: childEnvWithBootEstateDb(process.env) }, (err, stdout, stderr) => {
247
+ const code = err === null ? 0 : typeof err.code === 'number' ? err.code : null;
248
+ resolve({ stdout: String(stdout ?? ''), stderr: String(stderr ?? ''), code });
249
+ });
250
+ });
251
+ }
252
+ finally {
253
+ await rm(dir, { recursive: true, force: true });
254
+ }
255
+ };
240
256
  export const defaultGhExec = (args, opts) => new Promise((resolve) => {
241
257
  // The daemon's governance-store variables never ride into a child (crew#495): the boot value is
242
258
  // restored and the exported store URL is stripped, the same helper every other spawn site uses.
@@ -287,33 +303,40 @@ export async function readDeliverOriginUrl(rootPath) {
287
303
  * - a head branch name the script cannot splice safely;
288
304
  * - `gh` unavailable, unauthenticated, timed out or answering anything but the JSON asked for.
289
305
  */
290
- export async function resolvePullRequest(repoRoot, number, exec = defaultGhExec) {
306
+ export async function resolvePullRequest(repoRoot, number, exec = defaultGhExec, adoExec = defaultAdoHelperExec) {
291
307
  if (!Number.isInteger(number) || number <= 0)
292
308
  return { ok: false, error: `revisesPr must be a positive pull request number (got ${number})` };
309
+ // (crew#933) An Azure DevOps origin is asked through its REST API with the daemon's own
310
+ // credential (the deliver helper's `view`); the answer has gh's shape, so one parse serves both.
311
+ const ado = adoRepoOf(await readDeliverOriginUrl(repoRoot).catch(() => null));
312
+ const who = ado === null ? 'gh' : 'Azure DevOps';
293
313
  let out;
294
314
  try {
295
- out = await exec(['pr', 'view', String(number), '--json', 'headRefName,state,isCrossRepository,url'], { cwd: repoRoot, timeoutMs: 5000 });
315
+ out =
316
+ ado === null
317
+ ? await exec(['pr', 'view', String(number), '--json', 'headRefName,state,isCrossRepository,url'], { cwd: repoRoot, timeoutMs: 5000 })
318
+ : await adoExec(['view', ado.loginBase, ado.apiBase, ado.webUrl, String(number)], { timeoutMs: 30_000 });
296
319
  }
297
320
  catch (err) {
298
- return { ok: false, error: `gh could not read PR #${number}: ${err instanceof Error ? err.message : String(err)}` };
321
+ return { ok: false, error: `${who} could not read PR #${number}: ${err instanceof Error ? err.message : String(err)}` };
299
322
  }
300
323
  if (out.code !== 0) {
301
324
  const why = (out.stderr || out.stdout).trim().split('\n').slice(-2).join(' ').slice(0, 300);
302
- return { ok: false, error: `gh could not read PR #${number}: ${why || (out.code === null ? 'gh timed out or could not be spawned' : `exit ${out.code}`)}` };
325
+ return { ok: false, error: `${who} could not read PR #${number}: ${why || (out.code === null ? `${who} timed out or could not be spawned` : `exit ${out.code}`)}` };
303
326
  }
304
327
  let parsed;
305
328
  try {
306
329
  parsed = JSON.parse(out.stdout);
307
330
  }
308
331
  catch {
309
- return { ok: false, error: `gh could not read PR #${number}: its answer was not the JSON asked for` };
332
+ return { ok: false, error: `${who} could not read PR #${number}: its answer was not the JSON asked for` };
310
333
  }
311
334
  const r = (parsed ?? {});
312
335
  const headRef = r['headRefName'];
313
336
  const state = r['state'];
314
337
  const url = r['url'];
315
338
  if (typeof headRef !== 'string' || typeof state !== 'string' || typeof url !== 'string') {
316
- return { ok: false, error: `gh could not read PR #${number}: headRefName / state / url missing from its answer` };
339
+ return { ok: false, error: `${who} could not read PR #${number}: headRefName / state / url missing from its answer` };
317
340
  }
318
341
  if (state !== 'OPEN')
319
342
  return { ok: false, error: `revisesPr #${number} is ${state} — only an open pull request can be revised` };
@@ -323,7 +346,7 @@ export async function resolvePullRequest(repoRoot, number, exec = defaultGhExec)
323
346
  if (!isSafeRefName(headRef))
324
347
  return { ok: false, error: `revisesPr #${number}'s head branch name cannot be used as a push target: ${JSON.stringify(headRef)}` };
325
348
  if (!SAFE_PR_URL.test(url))
326
- return { ok: false, error: `revisesPr #${number}: gh answered a URL that is not a pull request URL (${url.slice(0, 120)})` };
349
+ return { ok: false, error: `revisesPr #${number}: ${who} answered a URL that is not a pull request URL (${url.slice(0, 120)})` };
327
350
  return { ok: true, pr: { number, headRef, url, state } };
328
351
  }
329
352
  /**
@@ -507,11 +530,19 @@ export function deliverPrScript(intent, opts = {}) {
507
530
  if (!/^[0-9a-fA-F]{16,64}$/.test(nonce))
508
531
  throw new Error(`deliver nonce: not 16-64 hex digits: ${JSON.stringify(nonce)}`);
509
532
  // (crew#720) AN AZURE DEVOPS ORIGIN: the push and the pull request go through the REST API with
510
- // the daemon's own credential. Revising an existing ADO pull request is not supported yet —
511
- // refused at compose time rather than silently opening a second PR.
533
+ // the daemon's own credential. (crew#933) A revision pushes onto the PR's head branch and comments
534
+ // the run record as a thread, as on GitHub; the intent's Azure Boards work items (`AB#<id>`) are
535
+ // linked on the PR it opens. Digits only, so the literal is safe.
512
536
  const ado = opts.adoTarget !== undefined ? opts.adoTarget : adoRepoOf(opts.originUrl);
513
- if (ado !== null && revises !== null) {
514
- throw new Error(`revisesPr #${revises.number}: revising an Azure DevOps pull request is not supported; launch a new delivery`);
537
+ const adoWorkItems = ado === null ? '' : adoWorkItemIds(intent ?? '').join(',');
538
+ // The revised PR must be THIS delivery's pull request: on Azure DevOps its URL names the target
539
+ // repository and the number (an origin re-pointed between resolution and composition must not
540
+ // push and comment on another organisation's PR); on GitHub it is never an Azure DevOps URL.
541
+ if (revises !== null) {
542
+ const isAdoUrl = revises.url.startsWith('https://dev.azure.com/');
543
+ if (ado !== null ? revises.url.toLowerCase() !== `${ado.webUrl}/pullrequest/${revises.number}`.toLowerCase() : isAdoUrl) {
544
+ throw new Error(`revisesPr #${revises.number}: ${revises.url} is not pull request #${revises.number} of the repository this delivery pushes to`);
545
+ }
515
546
  }
516
547
  return [
517
548
  'set -euo pipefail',
@@ -561,12 +592,28 @@ export function deliverPrScript(intent, opts = {}) {
561
592
  // can never hand the header to another program (codex r1 + r2).
562
593
  `ADOPROTO=${shellSingleQuote(ado === null ? '' : new URL(ado.gitUrl).protocol.replace(':', ''))}`,
563
594
  "ADOHDR=''",
595
+ `ADOWI='${adoWorkItems}'`,
596
+ // (crew#940) THE PINNED GITHUB TOKEN lives in an UNEXPORTED shell variable and reaches each gh
597
+ // and git network call one command at a time (`GH_TOKEN=… gh`, a one-command credential
598
+ // helper for git) — never argv, disk or the log, and no child that does not talk to GitHub
599
+ // inherits it. GHGIT is the canonical https URL a pinned github.com delivery fetches from and
600
+ // pushes to; NETGIT is that URL or the Azure DevOps one, '' for a plain `origin` delivery.
601
+ // An ambient `http.extraHeader` (actions/checkout writes one per host) would authenticate
602
+ // before any helper is asked, and a `url.*.insteadOf` could rewrite the canonical URL to ssh
603
+ // (whose key is another identity), so the GitHub arm of `_gnet` resets the header, allows only
604
+ // https and refuses redirects, as the ADO arm does.
605
+ "GHT=''",
606
+ "GHPIN=''",
607
+ "GHGIT=''",
608
+ "NETGIT=''",
609
+ // Pinned, gh talks to github.com whatever GH_HOST / GH_REPO the daemon inherited (codex r2).
610
+ '_gh() { if [ -n "$GHT" ]; then GH_TOKEN="$GHT" GH_HOST=github.com GH_REPO="github.com/$GHREPO" gh "$@"; else gh "$@"; fi; }',
564
611
  // Plain `git` outside Azure DevOps mode; in it, the network calls carry the auth header ONE-SHOT
565
612
  // in the child's environment, scoped to the exact repository URL (`http.<url>.extraHeader`),
566
613
  // after an empty entry resets any ambient extra header; redirects are refused, hooks and
567
614
  // credential helpers are off. The header lives in an UNEXPORTED shell variable: no file, no
568
615
  // argv, no log, and no other child of this script inherits it.
569
- `_gnet() { if [ -n "$ADOHDR" ]; then GIT_CONFIG_COUNT=4 GIT_CONFIG_KEY_0=http.extraHeader GIT_CONFIG_VALUE_0= GIT_CONFIG_KEY_1="http.$ADOGIT.extraHeader" GIT_CONFIG_VALUE_1= GIT_CONFIG_KEY_2="http.$ADOGIT.extraHeader" GIT_CONFIG_VALUE_2="Authorization: $ADOHDR" GIT_CONFIG_KEY_3=http.followRedirects GIT_CONFIG_VALUE_3=false GIT_TERMINAL_PROMPT=0 GIT_ALLOW_PROTOCOL="$ADOPROTO" git -c core.hooksPath=/dev/null -c credential.helper= "$@"; else git "$@"; fi; }`,
616
+ `_gnet() { if [ -n "$ADOHDR" ]; then GIT_CONFIG_COUNT=4 GIT_CONFIG_KEY_0=http.extraHeader GIT_CONFIG_VALUE_0= GIT_CONFIG_KEY_1="http.$ADOGIT.extraHeader" GIT_CONFIG_VALUE_1= GIT_CONFIG_KEY_2="http.$ADOGIT.extraHeader" GIT_CONFIG_VALUE_2="Authorization: $ADOHDR" GIT_CONFIG_KEY_3=http.followRedirects GIT_CONFIG_VALUE_3=false GIT_TERMINAL_PROMPT=0 GIT_ALLOW_PROTOCOL="$ADOPROTO" git -c core.hooksPath=/dev/null -c credential.helper= "$@"; elif [ -n "$GHGIT" ]; then GIT_CONFIG_COUNT=4 GIT_CONFIG_KEY_0=http.extraHeader GIT_CONFIG_VALUE_0= GIT_CONFIG_KEY_1="http.$GHGIT.extraHeader" GIT_CONFIG_VALUE_1= GIT_CONFIG_KEY_2=http.followRedirects GIT_CONFIG_VALUE_2=false GIT_CONFIG_KEY_3="http.$GHGIT.followRedirects" GIT_CONFIG_VALUE_3=false GIT_ALLOW_PROTOCOL=https GH_TOKEN="$GHT" GIT_TERMINAL_PROMPT=0 git -c credential.helper= -c 'credential.https://github.com.helper=!f() { test "$1" = get && echo username=x-access-token && echo "password=$GH_TOKEN"; }; f' "$@"; else git "$@"; fi; }`,
570
617
  'if [ -n "$ADOGIT" ]; then',
571
618
  ' echo "deliver: Azure DevOps origin — $ADOWEB (the push and the pull request use the daemon\'s Azure DevOps credential; gh is not used)"',
572
619
  ` if ! { [ -n "\${${ADO_PAT_ENV}:-}" ] || { ${ADO_SP_ENV.map((n) => `[ -n "\${${n}:-}" ]`).join(' && ')}; }; }; then _credmissing azure_devops "Azure DevOps credentials not configured: set ${ADO_SP_ENV.join(', ')} (a service principal) or ${ADO_PAT_ENV} in the daemon's environment and restart it."; fi`,
@@ -585,35 +632,67 @@ export function deliverPrScript(intent, opts = {}) {
585
632
  // The raw credential leaves the environment now: nothing this script runs later (the crew#426
586
633
  // npm preflight, git's commit machinery) can read it.
587
634
  ` unset ${[ADO_PAT_ENV, ...ADO_SP_ENV].join(' ')}`,
635
+ ' NETGIT="$ADOGIT"',
588
636
  'else',
589
637
  `CFG='${identity}'`,
590
638
  'if [ -n "$CFG" ]; then GH_ACCOUNT="$CFG"; fi',
591
- // (crew#737) PIN THE PUSH TO THAT ACCOUNT, whatever gh's machine-wide ACTIVE account is now: with
592
- // no GH_TOKEN exported, read the configured account's own token from gh's keyring
593
- // (`gh auth token --user`) and export it for this phase only — gh (`gh api`, `gh pr create`) and
594
- // gh's git credential helper then act as that account even if another tool runs
595
- // `gh auth switch` mid-run. Never echoed. An account gh holds no token for is REFUSED before
596
- // anything is staged; a gh too old for `--user` (the flag is refused) keeps the active-login
597
- // check below.
598
- 'if [ -n "${GH_ACCOUNT:-}" ] && [ -z "${GH_TOKEN:-}" ]; then',
599
- ' if T=$(gh auth token --hostname github.com --user "$GH_ACCOUNT" 2>/dev/null) && [ -n "$T" ]; then export GH_TOKEN="$T"; unset T; echo "deliver: pinned to $GH_ACCOUNT\'s own gh token for this phase"',
600
- ' elif gh auth token --help 2>/dev/null | grep -q -- "--user"; then echo "deliver: $GH_ACCOUNT is not signed in to gh on this machine; nothing was staged, committed or pushed. Sign in (gh auth login) as $GH_ACCOUNT, or pick another push identity for this repository, then approve to retry the deliver phase"; exit 1',
639
+ `GHREPO='${ghRepo}'`,
640
+ // (crew#940, operator ruling 2026-10-10) THE PUSH IDENTITY IS CHOSEN PER COMMAND. On a github.com
641
+ // origin an exported GH_TOKEN still wins; else, with an identity configured (the setting, the
642
+ // repository's pin, or GH_ACCOUNT), the script reads THAT account's token from gh's keyring
643
+ // itself (`gh auth token --user`) and hands it to its own gh calls and to its git fetch/push —
644
+ // so gh's machine-wide ACTIVE account (which other tools switch) never decides who pushes.
645
+ // An identity gh holds no token for is the shared "credentials not configured" refusal
646
+ // (crew#720's shape): before anything is staged, recoverable, said on the gate card too. Off
647
+ // github.com (GitHub Enterprise, GitLab, a local path) the github.com identity does not apply.
648
+ 'if [ -n "$GHREPO" ]; then',
649
+ ' if [ -n "${GH_TOKEN:-}" ]; then GHT="$GH_TOKEN"; GHPIN=exported',
650
+ ' elif [ -n "${GH_ACCOUNT:-}" ]; then',
651
+ ` command -v gh >/dev/null 2>&1 || _credmissing github "GitHub credentials not configured: gh is not installed and no GH_TOKEN is exported, so $GH_ACCOUNT's token cannot be read. Install gh and run gh auth login as $GH_ACCOUNT, or export GH_TOKEN in the daemon's environment."`,
652
+ ` GHT=$(gh auth token --hostname github.com --user "$GH_ACCOUNT" 2>/dev/null || true); [ -n "$GHT" ] || _credmissing github "GitHub credentials not configured: gh holds no token for $GH_ACCOUNT on this machine and no GH_TOKEN is exported. Run gh auth login as $GH_ACCOUNT on this machine (gh 2.40 or newer), or export GH_TOKEN in the daemon's environment."`,
653
+ ' GHPIN=keyring',
654
+ ' else',
655
+ // (crew#720) Unpinned: gh's own login is the credential, so gh absent or signed out is the
656
+ // same credentials-missing refusal.
657
+ ` if ! command -v gh >/dev/null 2>&1; then _credmissing github "GitHub credentials not configured: gh is not installed and no GH_TOKEN is exported. Install gh and run gh auth login, or export GH_TOKEN in the daemon's environment."; fi`,
658
+ ` gh auth status --hostname github.com >/dev/null 2>&1 || _credmissing github "GitHub credentials not configured: gh is not signed in to github.com and no GH_TOKEN is exported. Run gh auth login on this machine, or export GH_TOKEN in the daemon's environment."`,
659
+ ' fi',
660
+ ' if [ -n "$GHT" ]; then GHGIT="https://github.com/$GHREPO.git"; NETGIT="$GHGIT"; fi',
661
+ // A `url.<base>.insteadOf` / `pushInsteadOf` that rewrites the canonical URL would send the
662
+ // fetch or push somewhere the pin does not reach (ssh, or https carrying its own credential):
663
+ // refuse before anything is staged rather than push under that identity (codex r1/r2).
664
+ ' if [ -n "$GHGIT" ]; then while IFS= read -r RW; do RWV=${RW#* }; [ -n "$RW" ] && [ -n "$RWV" ] || continue; case "$GHGIT" in "$RWV"*) echo "deliver: git config rewrites $RWV (a url.*.insteadOf), so the push would not go to $GHGIT with the pinned token; nothing was staged, committed or pushed. Remove the rewrite for this repository, then approve to retry the deliver phase"; exit 1;; esac; done <<GHRW_EOF',
665
+ '$(git config --get-regexp \'^url\\..*\\.(push)?insteadof$\' 2>/dev/null || true)',
666
+ 'GHRW_EOF',
601
667
  ' fi',
668
+ // The token now lives only in GHT; nothing this script runs that is not a gh or git network
669
+ // call (the npm preflight, git's commit machinery) inherits it.
670
+ ' if [ -n "$GHT" ]; then unset GH_TOKEN; fi',
671
+ 'elif [ -n "${GH_ACCOUNT:-}" ]; then',
672
+ // An origin the composer could not read (GHREPO empty) that IS a github.com remote cannot be
673
+ // pinned: refuse rather than push under whatever credential git holds. The glob is loose on
674
+ // purpose — a look-alike host only refuses, it never pins.
675
+ ' case "$(git remote get-url --push --all origin 2>/dev/null | tr \'A-Z\' \'a-z\' || true)" in *github.com[:/]*) echo "deliver: origin is a github.com remote, but this delivery was composed without reading it, so the push identity $GH_ACCOUNT cannot be pinned; nothing was staged, committed or pushed. Approve to retry once the origin can be read, or reject and relaunch"; exit 1;; esac',
676
+ ' echo "deliver: the push identity $GH_ACCOUNT applies to github.com origins only; this origin is not one, so gh and git use their own credentials"',
602
677
  'fi',
603
- // (crew#720) GITHUB CREDENTIAL PREFLIGHT — parity with Azure DevOps. A github.com origin with no
604
- // exported GH_TOKEN needs a gh signed in to github.com; gh absent or signed out is refused here,
605
- // before anything is staged, with the shared credentials-missing refusal.
606
- `GHREPO='${ghRepo}'`,
607
- 'if [ -n "$GHREPO" ] && [ -z "${GH_TOKEN:-}" ]; then',
608
- ` if ! command -v gh >/dev/null 2>&1; then _credmissing github "GitHub credentials not configured: gh is not installed and no GH_TOKEN is exported. Install gh and run gh auth login, or export GH_TOKEN in the daemon's environment."; fi`,
609
- ` gh auth status --hostname github.com >/dev/null 2>&1 || _credmissing github "GitHub credentials not configured: gh is not signed in to github.com and no GH_TOKEN is exported. Run gh auth login on this machine, or export GH_TOKEN in the daemon's environment."`,
678
+ // WHO PUSHES, said once. A keyring pin IS its account (the token was read for that login), so
679
+ // there is nothing to compare. An exported GH_TOKEN is the one credential that can belong to
680
+ // someone other than the configured identity: that mismatch is still refused.
681
+ 'if [ "$GHPIN" = keyring ]; then L="$GH_ACCOUNT"; echo "deliver: pushing as $GH_ACCOUNT with its own gh token, pinned for this phase (gh\'s active account is not used)"',
682
+ 'else',
683
+ ' L=$(_gh api user -q .login 2>/dev/null || true)',
684
+ ' if [ "$GHPIN" = exported ] && [ -n "${GH_ACCOUNT:-}" ]; then',
685
+ ' [ "$L" = "$GH_ACCOUNT" ] || { echo "deliver: identity mismatch — the push identity is $GH_ACCOUNT but the exported GH_TOKEN authenticates as ${L:-unreadable}; nothing was staged, committed or pushed. Export $GH_ACCOUNT\'s token as GH_TOKEN in the daemon environment (or unset GH_TOKEN so the phase reads $GH_ACCOUNT\'s own gh token), then approve to retry the deliver phase"; exit 1; }',
686
+ ' echo "deliver: pushing as $L (pinned by GH_TOKEN)"',
687
+ ' elif [ "$GHPIN" = exported ]; then echo "deliver: pushing as ${L:-an unknown login} (pinned by GH_TOKEN)"',
688
+ ' elif [ -n "$L" ]; then echo "deliver: pushing as $L (no push identity configured — not pinned)"',
689
+ ' else echo "deliver: pushing as an unknown login (gh not authenticated; no push identity configured — not pinned)"; fi',
610
690
  'fi',
611
- 'L=$(gh api user -q .login 2>/dev/null || true)',
612
- 'if [ -n "${GH_ACCOUNT:-}" ]; then',
613
- ' [ "$L" = "$GH_ACCOUNT" ] || { echo "deliver: identity mismatch — GH_ACCOUNT is $GH_ACCOUNT but gh\'s active login is ${L:-unreadable}; nothing was staged, committed or pushed. Fix the daemon\'s gh login (switch gh\'s active account, or export GH_TOKEN in the daemon environment) and approve to retry the deliver phase"; exit 1; }',
614
- ' if [ -n "${GH_TOKEN:-}" ]; then echo "deliver: pushing as $L (GH_ACCOUNT pinned by GH_TOKEN)"; else echo "deliver: pushing as $L (GH_ACCOUNT from the gh keyring — export GH_TOKEN to pin it)"; fi',
615
- 'elif [ -n "$L" ]; then echo "deliver: pushing as $L (GH_ACCOUNT not set — not pinned)"',
616
- 'else echo "deliver: pushing as an unknown login (gh not authenticated; GH_ACCOUNT not set — not pinned)"; fi',
691
+ // A pinned github.com delivery fetches from and pushes to GHGIT with the pinned token as git's
692
+ // only credential, so git's configured helpers cannot hand it another account: the cross-check
693
+ // below applies to every OTHER delivery, where git still picks its own credential.
694
+ 'if [ -n "$GHGIT" ]; then echo "deliver: git fetches and pushes https://github.com/$GHREPO with the same token (git\'s configured credential helpers are not used)"',
695
+ 'else',
617
696
  // (crew#549 / F-RC1-010) THE CREDENTIAL CROSS-CHECK. `gh api user` says who gh is; it says
618
697
  // NOTHING about the credential `git push` will use. On the RC1 rig those two disagreed —
619
698
  // `git credential fill` handed git one account while `gh api user` reported another — and the
@@ -657,6 +736,7 @@ export function deliverPrScript(intent, opts = {}) {
657
736
  ' *) echo "deliver: origin is not an https remote — no git credential applies (ssh keys or a local path authenticate the push)";;',
658
737
  'esac',
659
738
  'fi',
739
+ 'fi',
660
740
  // REVISION MODE inputs (DES-L9 / crew#550) — baked at compose time from the resolved PR, each
661
741
  // validated against a strict charset before it is spliced into a single-quoted literal. Empty
662
742
  // TARGET ⇒ today's new-PR delivery.
@@ -701,7 +781,7 @@ export function deliverPrScript(intent, opts = {}) {
701
781
  // default is trunk/develop/anything, not just main/master (Copilot on #303).
702
782
  // (crew#720) Azure DevOps: fetched from the validated canonical URL with the scoped header, into
703
783
  // origin's own tracking refs — never through origin's configured URLs (an ssh origin included).
704
- `if [ -n "$ADOGIT" ]; then _gnet fetch -q "$ADOGIT" '+refs/heads/*:refs/remotes/origin/*' || { VERDICT=rejected; : > ${DELIVER_STRANDED_SENTINEL} || true; echo "deliver: Azure DevOps refused the fetch of $ADOWEB (above) — check that the credential can read the repository; nothing was staged, committed or pushed; approve to retry the deliver phase; ${DELIVER_PUSH_REJECTED_MARKER}"; exit 1; }; else git fetch origin; fi`,
784
+ `if [ -n "$ADOGIT" ]; then _gnet fetch -q "$ADOGIT" '+refs/heads/*:refs/remotes/origin/*' || { VERDICT=rejected; : > ${DELIVER_STRANDED_SENTINEL} || true; echo "deliver: Azure DevOps refused the fetch of $ADOWEB (above) — check that the credential can read the repository; nothing was staged, committed or pushed; approve to retry the deliver phase; ${DELIVER_PUSH_REJECTED_MARKER}"; exit 1; }; elif [ -n "$GHGIT" ]; then _gnet fetch -q "$GHGIT" '+refs/heads/*:refs/remotes/origin/*' || { VERDICT=rejected; : > ${DELIVER_STRANDED_SENTINEL} || true; echo "deliver: GitHub refused the fetch of https://github.com/$GHREPO as $L (above) — check that the credential can read the repository; nothing was staged, committed or pushed; approve to retry the deliver phase; ${DELIVER_PUSH_REJECTED_MARKER}"; exit 1; }; else git fetch origin; fi`,
705
785
  // …the way the ENGINE derives it (wicked-core `deliver_lift.rs`, review F-527-003): origin/HEAD
706
786
  // when it resolves to a commit — a DANGLING origin/HEAD (the remote's default branch renamed or
707
787
  // deleted since the clone) is tolerated — else origin/main, else origin/master, else origin/main
@@ -729,7 +809,7 @@ export function deliverPrScript(intent, opts = {}) {
729
809
  // Asked of the REMOTE (`ls-remote`), not of the clone's `origin/*` refs — a plain fetch never
730
810
  // prunes a branch deleted after the PR merged, so the local ref would still resolve and the
731
811
  // push would silently RE-CREATE the branch under a closed PR.
732
- ' git ls-remote --exit-code --heads origin "$TARGET" >/dev/null 2>&1 && git rev-parse --verify -q "origin/$TARGET^{commit}" >/dev/null || { echo "deliver: pull request #$PRNUM\'s branch origin/$TARGET no longer exists on the remote; nothing was staged, committed or pushed"; exit 1; }',
812
+ ' _gnet ls-remote --exit-code --heads "${NETGIT:-origin}" "$TARGET" >/dev/null 2>&1 && git rev-parse --verify -q "origin/$TARGET^{commit}" >/dev/null || { echo "deliver: pull request #$PRNUM\'s branch origin/$TARGET no longer exists on the remote; nothing was staged, committed or pushed"; exit 1; }',
733
813
  ' git merge-base --is-ancestor "origin/$TARGET" "$B" || { echo "deliver: pull request #$PRNUM\'s branch moved since this run based on it (origin/$TARGET is no longer an ancestor of $B); nothing was staged, committed or pushed — launch a new revision on the current head, or rebase $B onto origin/$TARGET by hand and approve to retry"; echo "deliver: pull request #$PRNUM\'s branch moved — refused; nothing was pushed"; exit 1; }',
734
814
  'fi',
735
815
  // (a2) VERIFIED-BASE PIN (wicked-core#431 / #433). Before this script runs, the engine LIFTED the
@@ -1070,7 +1150,10 @@ export function deliverPrScript(intent, opts = {}) {
1070
1150
  // A revision pushes the run branch ONTO the PR's head branch (`$B:refs/heads/$TARGET`) — the
1071
1151
  // PR gains exactly the run's commits; a rejection there is the same recoverable strand.
1072
1152
  // (crew#720) Azure DevOps pushes to the validated canonical URL with the scoped header.
1073
- '_push() { if [ -n "$ADOGIT" ]; then _gnet push "$ADOGIT" "$B:refs/heads/$B"; elif [ -n "$TARGET" ]; then git push origin "$B:refs/heads/$TARGET"; else git push -u origin "$B"; fi; }',
1153
+ // (crew#940) A pinned github.com delivery pushes to its canonical URL the same way, and both
1154
+ // re-fetch the pushed ref into origin's tracking ref (a push to a URL updates none) so the
1155
+ // done checks below read the remote.
1156
+ '_push() { if [ -n "$NETGIT" ]; then _gnet push "$NETGIT" "$B:refs/heads/${TARGET:-$B}" && _gnet fetch -q "$NETGIT" "+refs/heads/${TARGET:-$B}:refs/remotes/origin/${TARGET:-$B}"; elif [ -n "$TARGET" ]; then git push origin "$B:refs/heads/$TARGET"; else git push -u origin "$B"; fi; }',
1074
1157
  // (crew#739) The branch-pushed fact is said in the script's own words, so a later failure (a gh
1075
1158
  // error on `gh pr create`) still records that the branch reached the remote.
1076
1159
  'if PUSHOUT=$(_push 2>&1); then echo "$PUSHOUT"; echo "deliver: pushed $B to origin"; else',
@@ -1102,12 +1185,12 @@ export function deliverPrScript(intent, opts = {}) {
1102
1185
  // rides `gh pr comment` (the body the PR-create path would have used); a comment failure is
1103
1186
  // printed, not fatal — the commit message carries the same record.
1104
1187
  'if [ -n "$TARGET" ]; then',
1105
- ' git fetch -q origin "$TARGET"',
1188
+ ' _gnet fetch -q "${NETGIT:-origin}" "+refs/heads/$TARGET:refs/remotes/origin/$TARGET"',
1106
1189
  ' RT=$(git rev-parse "origin/$TARGET"); LT=$(git rev-parse "$B")',
1107
1190
  ' [ "$RT" = "$LT" ] || { echo "deliver: origin/$TARGET is at ${RT:0:10} after the push, not at $B (${LT:0:10}) — refusing to report a delivery the remote does not show"; exit 1; }',
1108
- ' ST=$(gh pr view "$PRNUM" --json state -q .state 2>/dev/null || true)',
1191
+ ' if [ -n "$ADOGIT" ]; then ST=$(WICKED_ADO_AUTH="$ADOHDR" "$ADONODE" "$TD/ado.mjs" state "$ADOAPI" "$PRNUM" 2>/dev/null || true); else ST=$(_gh pr view "$PRNUM" ${GHREPO:+--repo "github.com/$GHREPO"} --json state -q .state 2>/dev/null || true); fi',
1109
1192
  ' if [ "$ST" = "OPEN" ]; then echo "deliver: pull request #$PRNUM is OPEN and its branch $TARGET is at $B"; else echo "deliver: warning — pull request #$PRNUM reads ${ST:-unknown} (not OPEN) after the push; the commits landed on origin/$TARGET"; fi',
1110
- ' if COUT=$(gh pr comment "$PRNUM" --body-file "$TD/body" 2>&1); then echo "deliver: run record commented on pull request #$PRNUM"; else echo "$COUT"; echo "deliver: could not comment on pull request #$PRNUM — the commits landed; the record is in the commit message"; fi',
1193
+ ' if COUT=$(if [ -n "$ADOGIT" ]; then WICKED_ADO_AUTH="$ADOHDR" "$ADONODE" "$TD/ado.mjs" comment "$ADOAPI" "$PRNUM" "$TD/body"; else _gh pr comment "$PRNUM" ${GHREPO:+--repo "github.com/$GHREPO"} --body-file "$TD/body"; fi 2>&1); then echo "deliver: run record commented on pull request #$PRNUM"; else echo "$COUT"; echo "deliver: could not comment on pull request #$PRNUM — the commits landed; the record is in the commit message"; fi',
1111
1194
  ' URL="$PRURL"',
1112
1195
  'else',
1113
1196
  // (e2) A NON-GITHUB ORIGIN DELIVERS THE BRANCH (F2). `gh pr create` failing used to be
@@ -1134,9 +1217,8 @@ export function deliverPrScript(intent, opts = {}) {
1134
1217
  // branch is adopted (a retry after the push landed). Done is re-derived as on GitHub: a PR URL
1135
1218
  // AND the remote branch (re-fetched from the canonical URL) ahead of the base.
1136
1219
  ' if [ -n "$ADOGIT" ]; then',
1137
- ' _gnet fetch -q "$ADOGIT" "+refs/heads/$B:refs/remotes/origin/$B"',
1138
1220
  ` printf '%s\\n' "$TITLE" > "$TD/title"`,
1139
- ' if ! OUT=$(WICKED_ADO_AUTH="$ADOHDR" "$ADONODE" "$TD/ado.mjs" pr "$ADOAPI" "$ADOWEB" "$B" "$DEF" "$TD/title" "$TD/body"); then echo "deliver: the Azure DevOps pull request for $B was not opened (above) — the branch is pushed; approve to retry the deliver phase (it re-pushes $B and opens or adopts the pull request)"; exit 1; fi',
1221
+ ' if ! OUT=$(WICKED_ADO_AUTH="$ADOHDR" "$ADONODE" "$TD/ado.mjs" pr "$ADOAPI" "$ADOWEB" "$B" "$DEF" "$TD/title" "$TD/body" "$ADOWI"); then echo "deliver: the Azure DevOps pull request for $B was not opened (above) — the branch is pushed; approve to retry the deliver phase (it re-pushes $B and opens or adopts the pull request)"; exit 1; fi',
1140
1222
  ' echo "$OUT"',
1141
1223
  ' case "$OUT" in "deliver: ADO-PR-EXISTS "*) EXISTING=1;; esac',
1142
1224
  " URL=$(printf '%s\\n' \"$OUT\" | grep -Eo 'https://[^[:space:]]+/pullrequest/[0-9]+' | tail -1 || true)",
@@ -1145,7 +1227,7 @@ export function deliverPrScript(intent, opts = {}) {
1145
1227
  ' [ "$P" -ge 1 ] || { echo "deliver: $B is not ahead of $D on the remote after the push — refusing to report a delivery with no commits"; exit 1; }',
1146
1228
  ' if [ -n "${EXISTING:-}" ]; then echo "deliver: pull request $URL already exists for $B (an earlier attempt of this run opened it); the push updated it with $P commit(s) on top of $D — recorded as this run\'s delivery"; fi',
1147
1229
  ' else',
1148
- ' if ! OUT=$(gh pr create ${GHREPO:+--repo "$GHREPO"} --head "$B" --title "$TITLE" --body-file "$TD/body" 2>&1); then',
1230
+ ' if ! OUT=$(_gh pr create ${GHREPO:+--repo "$GHREPO"} --head "$B" --title "$TITLE" --body-file "$TD/body" 2>&1); then',
1149
1231
  ' echo "$OUT"',
1150
1232
  ' case "$OUT" in',
1151
1233
  ' *"git remotes configured for this repository"*"known GitHub host"*)',
@@ -1180,7 +1262,7 @@ export function deliverPrScript(intent, opts = {}) {
1180
1262
  ' [ "$P" -ge 1 ] || { echo "deliver: $B is not ahead of $D on the remote after the push — refusing to report a delivery with no commits"; exit 1; }',
1181
1263
  ' if [ -n "${EXISTING:-}" ]; then',
1182
1264
  ' echo "deliver: pull request $URL already exists for $B (an earlier attempt of this run opened it); the push updated it with $P commit(s) on top of $D — recorded as this run\'s delivery"',
1183
- ' if COUT=$(gh pr comment "$URL" --body-file "$TD/body" 2>&1); then echo "deliver: run record commented on $URL"; else echo "$COUT"; echo "deliver: could not comment on $URL — the commits landed; the record is in the commit message"; fi',
1265
+ ' if COUT=$(_gh pr comment "$URL" --body-file "$TD/body" 2>&1); then echo "deliver: run record commented on $URL"; else echo "$COUT"; echo "deliver: could not comment on $URL — the commits landed; the record is in the commit message"; fi',
1184
1266
  ' fi',
1185
1267
  ' fi',
1186
1268
  'fi',
@@ -1414,13 +1496,22 @@ export function deliverGateInstructions(opts) {
1414
1496
  const configured = (opts.deliverIdentity ?? '').trim();
1415
1497
  const account = configured !== '' ? configured : (opts.ghAccount ?? null);
1416
1498
  const source = configured !== '' ? (opts.deliverIdentitySource === 'repo' ? "this repository's push identity" : 'the deliver identity setting') : 'GH_ACCOUNT';
1417
- const who = account !== null && account !== ''
1418
- ? opts.deliverIdentitySignedIn === false && opts.ghTokenPinned !== true
1419
- ? `Pushes as ${account} (${source}) — NOT signed in to gh on this machine: sign in (gh auth login) as ${account} or pick another push identity first; the phase will refuse and push nothing.`
1499
+ const configuredAccount = account !== null && account !== '';
1500
+ // (crew#940) The identity is pinned per command on a github.com origin: the token is the
1501
+ // account's own (or an exported GH_TOKEN), and git pushes with that same token.
1502
+ const github = githubRepoOf(opts.originUrl) !== null;
1503
+ const pinned = github && (configuredAccount || opts.ghTokenPinned === true);
1504
+ const who = !github
1505
+ ? configuredAccount
1506
+ ? `Push identity: ${account} (${source}) applies to github.com origins only; this push uses whatever credential the remote's host takes.`
1507
+ : 'Push identity: none configured — pushes as whatever login gh holds (set the deliver identity in system settings, or this repository\'s push identity, to pin it).'
1508
+ : configuredAccount
1509
+ ? opts.ghTokenPinned === true
1510
+ ? `Push identity: ${account} (${source}), pinned by GH_TOKEN — the phase refuses if that token authenticates as anyone else.`
1511
+ : `Push identity: ${account} (${source}) — the phase pushes and opens the pull request with ${account}'s own gh token, whatever account gh has active.`
1420
1512
  : opts.ghTokenPinned === true
1421
- ? `Push identity: ${account} (${source}), pinned by GH_TOKEN — the phase refuses if gh's login differs at push time.`
1422
- : `Push identity: ${account} (${source}) — the phase pushes with that account's own gh token, so another tool switching gh's active account does not change it (a gh without \`auth token --user\` falls back to checking the active login). The phase refuses if gh's login differs.`
1423
- : 'Push identity: none configured — pushes as whatever login gh holds (set the deliver identity in system settings, or this repository\'s push identity, to pin it).';
1513
+ ? 'Push identity: the login of the exported GH_TOKEN (no push identity configured).'
1514
+ : 'Push identity: none configured — pushes as whatever login gh holds (set the deliver identity in system settings, or this repository\'s push identity, to pin it).';
1424
1515
  // (crew#720) The provider credential preflight read at compose time: a missing credential is said
1425
1516
  // BEFORE approval, on the card, in the same words the phase refuses with.
1426
1517
  const cred = opts.credentials ?? null;
@@ -1435,10 +1526,10 @@ export function deliverGateInstructions(opts) {
1435
1526
  : '';
1436
1527
  return `${target} ${credSentence}${how}It refuses if origin no longer points at that repository.`.replace(/\s+$/, '');
1437
1528
  }
1438
- // Whatever is configured, the phase also asks git which credential IT would use for the
1439
- // remote's host and refuses when the two logins disagree — the flip that pushed under an
1440
- // unintended account (F-RC1-010).
1441
- return `${target} ${credSentence}${who} It refuses if gh's login and git's credential for the remote disagree.`;
1529
+ // Pinned, git pushes with the same token; otherwise the phase asks git which credential IT would
1530
+ // use for the remote's host and refuses when the two logins disagree (F-RC1-010).
1531
+ const git = pinned ? 'git pushes with the same token, not its own configured credential.' : "It refuses if gh's login and git's credential for the remote disagree.";
1532
+ return `${target} ${credSentence}${who} ${git}`;
1442
1533
  }
1443
1534
  /**
1444
1535
  * The `deliver` PLAN STEP a delivering PRESET launch hands the engine (DES-TEAMING-002 T3,
@@ -1474,7 +1565,6 @@ presetName, phases, runId, intent, launch = {}) {
1474
1565
  originUrl: launch.originUrl ?? null,
1475
1566
  deliverIdentity: launch.deliverIdentity ?? null,
1476
1567
  ...(launch.deliverIdentitySource !== undefined ? { deliverIdentitySource: launch.deliverIdentitySource } : {}),
1477
- ...(launch.deliverIdentitySignedIn !== undefined ? { deliverIdentitySignedIn: launch.deliverIdentitySignedIn } : {}),
1478
1568
  credentials: launch.credentials ?? null,
1479
1569
  });
1480
1570
  return {
@@ -1520,7 +1610,6 @@ export function composeDeliverWorkflow(base, runId, intent, launch = {}) {
1520
1610
  originUrl: launch.originUrl ?? null,
1521
1611
  deliverIdentity: launch.deliverIdentity ?? null,
1522
1612
  ...(launch.deliverIdentitySource !== undefined ? { deliverIdentitySource: launch.deliverIdentitySource } : {}),
1523
- ...(launch.deliverIdentitySignedIn !== undefined ? { deliverIdentitySignedIn: launch.deliverIdentitySignedIn } : {}),
1524
1613
  credentials: launch.credentials ?? null,
1525
1614
  });
1526
1615
  return {