@ashlr/hub 3.2.7 → 3.3.2

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 (192) hide show
  1. package/CHANGELOG.md +314 -2
  2. package/README.md +54 -38
  3. package/dist/build-identity.json +2 -2
  4. package/dist/cli/audit.js +18 -0
  5. package/dist/cli/audit.js.map +1 -1
  6. package/dist/cli/backlog.d.ts +0 -5
  7. package/dist/cli/backlog.js +18 -0
  8. package/dist/cli/backlog.js.map +1 -1
  9. package/dist/cli/comms.js +20 -0
  10. package/dist/cli/comms.js.map +1 -1
  11. package/dist/cli/completions.d.ts +6 -1
  12. package/dist/cli/completions.js +33 -10
  13. package/dist/cli/completions.js.map +1 -1
  14. package/dist/cli/daemon.js +10 -3
  15. package/dist/cli/daemon.js.map +1 -1
  16. package/dist/cli/dashboard.js +5 -4
  17. package/dist/cli/dashboard.js.map +1 -1
  18. package/dist/cli/demo.d.ts +0 -27
  19. package/dist/cli/demo.js +17 -0
  20. package/dist/cli/demo.js.map +1 -1
  21. package/dist/cli/help.js +22 -9
  22. package/dist/cli/help.js.map +1 -1
  23. package/dist/cli/inbox.d.ts +14 -2
  24. package/dist/cli/inbox.js +137 -23
  25. package/dist/cli/inbox.js.map +1 -1
  26. package/dist/cli/onboard.d.ts +0 -13
  27. package/dist/cli/onboard.js +20 -0
  28. package/dist/cli/onboard.js.map +1 -1
  29. package/dist/cli/resources.js +15 -0
  30. package/dist/cli/resources.js.map +1 -1
  31. package/dist/cli/run.js +1 -1
  32. package/dist/cli/run.js.map +1 -1
  33. package/dist/cli/sandbox.js +43 -0
  34. package/dist/cli/sandbox.js.map +1 -1
  35. package/dist/cli/serve.js +7 -3
  36. package/dist/cli/serve.js.map +1 -1
  37. package/dist/cli/setup.d.ts +5 -3
  38. package/dist/cli/setup.js +14 -7
  39. package/dist/cli/setup.js.map +1 -1
  40. package/dist/cli/tui.d.ts +0 -6
  41. package/dist/cli/tui.js +14 -0
  42. package/dist/cli/tui.js.map +1 -1
  43. package/dist/cli/update.js +3 -1
  44. package/dist/cli/update.js.map +1 -1
  45. package/dist/core/config.js +70 -0
  46. package/dist/core/config.js.map +1 -1
  47. package/dist/core/daemon/loop.js +83 -3
  48. package/dist/core/daemon/loop.js.map +1 -1
  49. package/dist/core/daemon/runtime-activation-resident-start-protocol.d.ts +141 -0
  50. package/dist/core/daemon/runtime-activation-resident-start-protocol.js +462 -0
  51. package/dist/core/daemon/runtime-activation-resident-start-protocol.js.map +1 -0
  52. package/dist/core/daemon/runtime-activation-resident-start-runtime.d.ts +27 -0
  53. package/dist/core/daemon/runtime-activation-resident-start-runtime.js +28 -0
  54. package/dist/core/daemon/runtime-activation-resident-start-runtime.js.map +1 -0
  55. package/dist/core/daemon/runtime-activation-stopped-consumer.d.ts +139 -0
  56. package/dist/core/daemon/runtime-activation-stopped-consumer.js +995 -0
  57. package/dist/core/daemon/runtime-activation-stopped-consumer.js.map +1 -0
  58. package/dist/core/daemon/runtime-activation-stopped-runtime.d.ts +36 -0
  59. package/dist/core/daemon/runtime-activation-stopped-runtime.js +134 -0
  60. package/dist/core/daemon/runtime-activation-stopped-runtime.js.map +1 -0
  61. package/dist/core/daemon/runtime-activation-transaction.d.ts +41 -17
  62. package/dist/core/daemon/runtime-activation-transaction.js +80 -11
  63. package/dist/core/daemon/runtime-activation-transaction.js.map +1 -1
  64. package/dist/core/daemon/runtime-release-dependency-inventory.js +1 -0
  65. package/dist/core/daemon/runtime-release-dependency-inventory.js.map +1 -1
  66. package/dist/core/daemon/service-install-authority.d.ts +1 -1
  67. package/dist/core/daemon/service-install-authority.js +4 -2
  68. package/dist/core/daemon/service-install-authority.js.map +1 -1
  69. package/dist/core/dashboard.d.ts +10 -0
  70. package/dist/core/dashboard.js +103 -28
  71. package/dist/core/dashboard.js.map +1 -1
  72. package/dist/core/effective-config.js +7 -0
  73. package/dist/core/effective-config.js.map +1 -1
  74. package/dist/core/fleet/automerge-pass.js +57 -73
  75. package/dist/core/fleet/automerge-pass.js.map +1 -1
  76. package/dist/core/fleet/decisions-ledger.js +36 -13
  77. package/dist/core/fleet/decisions-ledger.js.map +1 -1
  78. package/dist/core/fleet/external-skill-git-capture.js +9 -1
  79. package/dist/core/fleet/external-skill-git-capture.js.map +1 -1
  80. package/dist/core/fleet/judge-decision-metadata.d.ts +8 -1
  81. package/dist/core/fleet/judge-decision-metadata.js +14 -1
  82. package/dist/core/fleet/judge-decision-metadata.js.map +1 -1
  83. package/dist/core/fleet/judge-trace.js +2 -1
  84. package/dist/core/fleet/judge-trace.js.map +1 -1
  85. package/dist/core/fleet/manager.d.ts +21 -1
  86. package/dist/core/fleet/manager.js +137 -41
  87. package/dist/core/fleet/manager.js.map +1 -1
  88. package/dist/core/fleet/post-merge-credit-label.d.ts +10 -0
  89. package/dist/core/fleet/post-merge-credit-label.js +13 -0
  90. package/dist/core/fleet/post-merge-credit-label.js.map +1 -0
  91. package/dist/core/fleet/post-merge-credit.d.ts +119 -12
  92. package/dist/core/fleet/post-merge-credit.js +353 -14
  93. package/dist/core/fleet/post-merge-credit.js.map +1 -1
  94. package/dist/core/fleet/self-improve.d.ts +38 -5
  95. package/dist/core/fleet/self-improve.js +201 -12
  96. package/dist/core/fleet/self-improve.js.map +1 -1
  97. package/dist/core/fleet/skill-library.d.ts +21 -2
  98. package/dist/core/fleet/skill-library.js +54 -12
  99. package/dist/core/fleet/skill-library.js.map +1 -1
  100. package/dist/core/fleet/worked-ledger.d.ts +22 -0
  101. package/dist/core/fleet/worked-ledger.js +32 -0
  102. package/dist/core/fleet/worked-ledger.js.map +1 -1
  103. package/dist/core/goals/store.js +60 -6
  104. package/dist/core/goals/store.js.map +1 -1
  105. package/dist/core/inbox/apply.js +2 -1
  106. package/dist/core/inbox/apply.js.map +1 -1
  107. package/dist/core/inbox/merge.d.ts +10 -1
  108. package/dist/core/inbox/merge.js +97 -18
  109. package/dist/core/inbox/merge.js.map +1 -1
  110. package/dist/core/integrations/github.d.ts +2 -0
  111. package/dist/core/integrations/github.js +2 -1
  112. package/dist/core/integrations/github.js.map +1 -1
  113. package/dist/core/learn/reflect.d.ts +42 -4
  114. package/dist/core/learn/reflect.js +66 -5
  115. package/dist/core/learn/reflect.js.map +1 -1
  116. package/dist/core/onboard.d.ts +2 -0
  117. package/dist/core/onboard.js +9 -3
  118. package/dist/core/onboard.js.map +1 -1
  119. package/dist/core/run/engines.d.ts +8 -0
  120. package/dist/core/run/engines.js +9 -1
  121. package/dist/core/run/engines.js.map +1 -1
  122. package/dist/core/run/learned-router.js +28 -3
  123. package/dist/core/run/learned-router.js.map +1 -1
  124. package/dist/core/run/orchestrator.d.ts +7 -2
  125. package/dist/core/run/orchestrator.js +88 -60
  126. package/dist/core/run/orchestrator.js.map +1 -1
  127. package/dist/core/swarm/rollback.d.ts +11 -0
  128. package/dist/core/swarm/rollback.js +54 -0
  129. package/dist/core/swarm/rollback.js.map +1 -1
  130. package/dist/core/swarm/runner.js +94 -8
  131. package/dist/core/swarm/runner.js.map +1 -1
  132. package/dist/core/types.d.ts +103 -3
  133. package/dist/core/types.js.map +1 -1
  134. package/dist/core/web/api.d.ts +6 -0
  135. package/dist/core/web/api.js +174 -18
  136. package/dist/core/web/api.js.map +1 -1
  137. package/dist/core/web/control.d.ts +20 -0
  138. package/dist/core/web/control.js +44 -32
  139. package/dist/core/web/control.js.map +1 -1
  140. package/dist/core/web/fleet-status-cache.d.ts +68 -0
  141. package/dist/core/web/fleet-status-cache.js +120 -0
  142. package/dist/core/web/fleet-status-cache.js.map +1 -0
  143. package/dist/core/web/public/app.js +5 -1
  144. package/dist/core/web/public/next/assets/index-CDHQraxS.css +1 -0
  145. package/dist/core/web/public/next/assets/index-zVKD9krw.js +69 -0
  146. package/dist/core/web/public/next/assets/index-zVKD9krw.js.map +1 -0
  147. package/dist/core/web/public/next/index.html +15 -0
  148. package/dist/core/web/read-session-revocations.d.ts +26 -0
  149. package/dist/core/web/read-session-revocations.js +48 -0
  150. package/dist/core/web/read-session-revocations.js.map +1 -0
  151. package/dist/core/web/server.js +73 -27
  152. package/dist/core/web/server.js.map +1 -1
  153. package/dist/core/web/static.d.ts +1 -1
  154. package/dist/core/web/static.js +54 -20
  155. package/dist/core/web/static.js.map +1 -1
  156. package/dist/release-dependency-inventory.json +1 -1
  157. package/docs/RUNTIME_ACTIVATION_AUTHORITY.md +115 -21
  158. package/docs/contracts/CONTRACT-M521.md +102 -0
  159. package/node_modules/fast-uri/index.js +201 -48
  160. package/node_modules/fast-uri/lib/schemes.js +9 -4
  161. package/node_modules/fast-uri/lib/utils.js +388 -91
  162. package/node_modules/fast-uri/package.json +1 -1
  163. package/node_modules/fast-uri/test/component-safe-serialization.test.js +105 -0
  164. package/node_modules/fast-uri/test/equal.test.js +31 -3
  165. package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +2 -0
  166. package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
  167. package/node_modules/fast-uri/test/ipv6-validation.test.js +90 -0
  168. package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
  169. package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
  170. package/node_modules/fast-uri/test/parse.test.js +7 -3
  171. package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
  172. package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
  173. package/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
  174. package/node_modules/fast-uri/test/security-normalization.test.js +101 -0
  175. package/node_modules/fast-uri/test/security.test.js +75 -3
  176. package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
  177. package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
  178. package/node_modules/qs/.github/THREAT_MODEL.md +3 -3
  179. package/node_modules/qs/CHANGELOG.md +26 -0
  180. package/node_modules/qs/README.md +19 -1
  181. package/node_modules/qs/dist/qs.js +25 -25
  182. package/node_modules/qs/eslint.config.mjs +1 -1
  183. package/node_modules/qs/lib/parse.js +16 -6
  184. package/node_modules/qs/lib/stringify.js +23 -8
  185. package/node_modules/qs/lib/utils.js +57 -11
  186. package/node_modules/qs/package.json +8 -7
  187. package/node_modules/qs/test/parse.js +419 -0
  188. package/node_modules/qs/test/stringify.js +317 -0
  189. package/node_modules/qs/test/utils.js +206 -0
  190. package/package.json +24 -6
  191. package/schema/config.schema.json +800 -5
  192. package/node_modules/which/CHANGELOG.md +0 -166
@@ -159,6 +159,20 @@ function decisionReadIsHealthy(decisions) {
159
159
  const MERGE_BRANCH_PREFIX = 'ashlr/merge/';
160
160
  /** Timeout for git operations (ms). */
161
161
  const GIT_TIMEOUT = 30_000;
162
+ /**
163
+ * Gate 6.5 (self-eval parity, M54/M86): how many times to run test:invariants
164
+ * per flag state before declaring it red. >1 tolerates a one-off transient
165
+ * blip (lock timing, momentary contention) without weakening the gate — a
166
+ * genuinely broken invariant still fails every attempt. Kept small (one
167
+ * retry, short delay): sustained multi-minute contention from a concurrent
168
+ * test run can't be "waited out" here anyway — cross-tick retry (the
169
+ * proposal simply stays pending and is re-evaluated next pass, see
170
+ * automerge-pass.ts) is what actually absorbs that, and this must stay
171
+ * short enough to never turn a fast fail-closed refusal into a slow one.
172
+ */
173
+ const SELF_EVAL_PARITY_RETRY_ATTEMPTS = 2;
174
+ /** Delay between self-eval parity retry attempts (ms). */
175
+ const SELF_EVAL_PARITY_RETRY_DELAY_MS = 500;
162
176
  // ---------------------------------------------------------------------------
163
177
  // git helpers — arg arrays, no shell
164
178
  // ---------------------------------------------------------------------------
@@ -612,7 +626,23 @@ export function evaluateAutoMergeReadinessPreflight(proposal, cfg) {
612
626
  return block('proposal has no repo');
613
627
  if (proposal.verifyResult?.passed === false) {
614
628
  const failed = proposal.verifyResult.failed?.filter(Boolean).join('; ');
615
- return block(`known verification failure: proposal.verifyResult.passed is false${failed ? ` (${failed})` : ''}`);
629
+ const category = proposal.verifyResult.failureCategory;
630
+ // A verifier that could NOT be executed (missing binary, exit 127, spawn
631
+ // ENOENT) or that hit a bounded timeout/infra hiccup is a DIFFERENT
632
+ // outcome from a verifier that ran and found the diff broken. Both stay
633
+ // fail-closed (ready:false — this NEVER makes an unverified change
634
+ // eligible to merge), but only a real 'code' failure (or an
635
+ // unclassified/legacy record) may accrue toward stuckPassCount/
636
+ // autoArchive. An infra-flavored failure must not permanently kill a
637
+ // proposal whose only crime was that the verifier was momentarily
638
+ // unavailable — it stays pending and is retried next pass.
639
+ const explicitCodeFailure = category === 'code';
640
+ const advisories = !explicitCodeFailure
641
+ ? [
642
+ `verifier-unavailable: verification could not run to completion (${category}) — not counted toward auto-archive; will retry`,
643
+ ]
644
+ : [];
645
+ return block(`known verification failure: proposal.verifyResult.passed is false${failed ? ` (${failed})` : ''}`, advisories, explicitCodeFailure);
616
646
  }
617
647
  const trustBasis = cfg.foundry?.['autoMerge']
618
648
  ? cfg.foundry['autoMerge']?.['trustBasis'] ?? 'tier'
@@ -1417,6 +1447,7 @@ export function verifyResultFromProposalResult(result, source = 'auto-merge', ve
1417
1447
  return {
1418
1448
  passed: result.ok,
1419
1449
  ...(result.ok ? {} : { failed: [result.detail] }),
1450
+ ...(result.ok || result.failureCategory === undefined ? {} : { failureCategory: result.failureCategory }),
1420
1451
  detail: result.detail,
1421
1452
  ran: [...result.ran],
1422
1453
  ...(result.browser ? { browser: result.browser } : {}),
@@ -1534,6 +1565,7 @@ function verifyResultFromStored(result) {
1534
1565
  ...(result.browser ? { browser: result.browser } : {}),
1535
1566
  ...(result.baseBranch ? { baseBranch: result.baseBranch } : {}),
1536
1567
  ...(result.baseHead ? { baseHead: result.baseHead } : {}),
1568
+ ...(result.failureCategory ? { failureCategory: result.failureCategory } : {}),
1537
1569
  };
1538
1570
  }
1539
1571
  function truncateEvidenceText(text, limit = 500) {
@@ -1685,6 +1717,8 @@ export async function verifyProposal(proposal, cfg) {
1685
1717
  }
1686
1718
  // Apply the diff to the worktree ONLY AFTER capturing the base-derived
1687
1719
  // command list, then run those (immutable) commands against the patched tree.
1720
+ // Apply exactly to the verified index. Context drift is a refusal, not
1721
+ // authority to synthesize a three-way result that was never proposed.
1688
1722
  patchFile = writeTmpFile(diff);
1689
1723
  try {
1690
1724
  gitRun(tmpDir, ['apply', '--index', patchFile]);
@@ -1711,6 +1745,7 @@ export async function verifyProposal(proposal, cfg) {
1711
1745
  detail: `required verify '${vc.kind}' failed (exit ${res.exitCode}): ${res.command}`,
1712
1746
  baseBranch: base,
1713
1747
  baseHead,
1748
+ ...(res.failureCategory ? { failureCategory: res.failureCategory } : {}),
1714
1749
  };
1715
1750
  }
1716
1751
  // M281: for test commands, only block if NEW failures were introduced.
@@ -1788,6 +1823,7 @@ export async function verifyProposal(proposal, cfg) {
1788
1823
  detail: `verify '${vc.kind}' failed (exit ${res.exitCode}): ${res.command}`,
1789
1824
  baseBranch: base,
1790
1825
  baseHead,
1826
+ ...(res.failureCategory ? { failureCategory: res.failureCategory } : {}),
1791
1827
  };
1792
1828
  }
1793
1829
  }
@@ -2467,11 +2503,12 @@ export async function autoMergeProposal(id, cfg, invocation = {}) {
2467
2503
  let maxLinesForEvidence = 0;
2468
2504
  // ── Gate 5.5 (M86): scope cap — only small, fully-bounded diffs auto-merge
2469
2505
  // to main. Defaults are conservative; bounded config keys allow opt-in relaxation.
2470
- // We require risk==='low' here (Gate 5 already enforces ≤maxRisk, but
2471
- // maxRisk could be raised to 'medium' — scope cap applies ONLY when risk is
2472
- // strictly 'low', so even a cfg.maxRisk='medium' run is scoped to low-risk
2473
- // diffs for the size check). File + line counts reuse the same diff parser
2474
- // as classifyRisk so the two gates are consistent.
2506
+ // NOTE (was stale): this used to hard-require risk==='low' regardless of
2507
+ // cfg.maxRisk, silently overriding an operator's higher maxRisk for the
2508
+ // scope check even though Gate 5 above had already cleared it. M295 (below)
2509
+ // fixed that — the size cap now applies up to the CONFIGURED maxRisk, so
2510
+ // the two gates agree. File + line counts reuse the same diff parser as
2511
+ // classifyRisk so the two gates are consistent.
2475
2512
  {
2476
2513
  const autoMergeCfg = cfg.foundry
2477
2514
  ?.autoMerge;
@@ -2593,12 +2630,32 @@ export async function autoMergeProposal(id, cfg, invocation = {}) {
2593
2630
  : 'npm';
2594
2631
  if (scriptsForParity['test:invariants']) {
2595
2632
  // Fast path: run only the invariant suite.
2633
+ //
2634
+ // Bounded retry (merge-pipeline reliability): test:invariants
2635
+ // includes concurrency/timing/disk-cap tests (h3/h5) that are
2636
+ // legitimately flaky under host contention — a second live daemon
2637
+ // tick, another sandboxed agent's test run, or transient disk
2638
+ // pressure can make ONE pass momentarily red for reasons that have
2639
+ // nothing to do with the proposal's diff. Unlike the main verify
2640
+ // path (Gate 6, M281/M293) there is no per-proposal baseline to
2641
+ // diff against here — this check runs against the UNPATCHED repo
2642
+ // tree, so a single flaky run previously meant 3 non-parity
2643
+ // refusals permanently auto-archived an otherwise-good proposal.
2644
+ // A genuinely broken invariant fails EVERY attempt and is still
2645
+ // refused — this only tolerates transient noise, never a real break.
2596
2646
  const invariantCmd = {
2597
2647
  kind: 'test',
2598
2648
  cmd: [pm, 'run', 'test:invariants'],
2599
2649
  };
2600
- const res = await runVerifyCommandAsync(invariantCmd, repo, parityCfg);
2601
- return res.ok;
2650
+ for (let attempt = 1; attempt <= SELF_EVAL_PARITY_RETRY_ATTEMPTS; attempt++) {
2651
+ const res = await runVerifyCommandAsync(invariantCmd, repo, parityCfg);
2652
+ if (res.ok)
2653
+ return true;
2654
+ if (attempt < SELF_EVAL_PARITY_RETRY_ATTEMPTS) {
2655
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, SELF_EVAL_PARITY_RETRY_DELAY_MS));
2656
+ }
2657
+ }
2658
+ return false;
2602
2659
  }
2603
2660
  // Fallback: no targeted script — run all detected verify commands.
2604
2661
  const cmds = detectVerifyCommands(repo, 'merge');
@@ -2608,8 +2665,18 @@ export async function autoMergeProposal(id, cfg, invocation = {}) {
2608
2665
  return true;
2609
2666
  }
2610
2667
  for (const vc of cmds) {
2611
- const res = await runVerifyCommandAsync(vc, repo, parityCfg);
2612
- if (!res.ok && vc.required !== false)
2668
+ const attempts = vc.kind === 'test' ? SELF_EVAL_PARITY_RETRY_ATTEMPTS : 1;
2669
+ let ok = false;
2670
+ for (let attempt = 1; attempt <= attempts; attempt++) {
2671
+ const res = await runVerifyCommandAsync(vc, repo, parityCfg);
2672
+ ok = res.ok;
2673
+ if (ok)
2674
+ break;
2675
+ if (attempt < attempts) {
2676
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, SELF_EVAL_PARITY_RETRY_DELAY_MS));
2677
+ }
2678
+ }
2679
+ if (!ok && vc.required !== false)
2613
2680
  return false;
2614
2681
  }
2615
2682
  return true;
@@ -2741,7 +2808,7 @@ export async function autoMergeProposal(id, cfg, invocation = {}) {
2741
2808
  let inlineAttestation;
2742
2809
  const ts = new Date().toISOString();
2743
2810
  const inlineReviewerIndependence = evaluateReviewerIndependence(proposal, inlineJudgeEngine);
2744
- if (verdict.verdict === 'ship' && verdict.wouldMerge === true && isFrontierJudge(inlineJudgeEngine) &&
2811
+ if (verdict.considered === true && verdict.verdict === 'ship' && verdict.wouldMerge === true && isFrontierJudge(inlineJudgeEngine) &&
2745
2812
  inlineReviewerIndependence.independent) {
2746
2813
  try {
2747
2814
  const { signJudgeAttestation: signAtt, hashDiff: hd } = await import('../foundry/provenance.js');
@@ -2772,22 +2839,31 @@ export async function autoMergeProposal(id, cfg, invocation = {}) {
2772
2839
  action: 'judged',
2773
2840
  engine: inlineJudgeEngine,
2774
2841
  model: inlineJudgeEngine,
2775
- verdict: verdict.verdict,
2776
- detail: verdict.wouldMerge && inlineReviewerIndependence.independent ? 'would-merge' : '',
2842
+ verdict: verdict.considered === true ? verdict.verdict : 'review',
2843
+ // M507: a judgeFailure verdict is a synthetic fail-closed fallback, not
2844
+ // a considered judgment. It must be distinguishable in the ledger from a
2845
+ // real 'review' — conflating them is what made ~22% of judge calls look
2846
+ // like deliberate rejections. Mirrors automerge-pass.ts:243 so BOTH judge
2847
+ // paths record the same way. `wouldMerge` is always false on a failure,
2848
+ // so this never displaces the 'would-merge' marker.
2849
+ detail: verdict.judgeFailure || verdict.considered !== true
2850
+ ? (verdict.judgeFailure === 'network' ? 'judge-network-failure' : 'judge-parse-failure')
2851
+ : (verdict.wouldMerge && inlineReviewerIndependence.independent ? 'would-merge' : ''),
2777
2852
  ...(verdict.semanticEvents ? { semanticEvents: verdict.semanticEvents } : {}),
2778
2853
  ...(inlineAttestation !== undefined ? { judgeAttestation: inlineAttestation } : {}),
2779
2854
  ...(inlineAttestation !== undefined
2780
2855
  ? { judgeAttestationIssuedAt: ts, judgeAttestationIntent: 'would-merge' }
2781
2856
  : {}),
2782
2857
  });
2783
- if (verdict.verdict === 'ship' && verdict.wouldMerge === true &&
2858
+ if (verdict.considered === true && verdict.verdict === 'ship' && verdict.wouldMerge === true &&
2784
2859
  !inlineReviewerIndependence.independent) {
2785
2860
  return refuse(`manager quality gate: ${inlineReviewerIndependence.reason}`, repo);
2786
2861
  }
2862
+ const consideredVerdict = verdict.considered === true;
2787
2863
  managerVerdict = {
2788
- verdict: verdict.verdict,
2789
- wouldMerge: verdict.wouldMerge,
2790
- reasonCode: judgeDecisionReasonCode(verdict.verdict, verdict.wouldMerge && inlineReviewerIndependence.independent),
2864
+ verdict: consideredVerdict ? verdict.verdict : 'review',
2865
+ wouldMerge: consideredVerdict ? verdict.wouldMerge : false,
2866
+ reasonCode: judgeDecisionReasonCode(consideredVerdict ? verdict.verdict : 'review', consideredVerdict && verdict.wouldMerge && inlineReviewerIndependence.independent),
2791
2867
  };
2792
2868
  }
2793
2869
  catch {
@@ -3385,7 +3461,10 @@ export async function autoMergeProposal(id, cfg, invocation = {}) {
3385
3461
  mergeNote = 'PR opened; host auto-merge refused because canonical GitHub origin changed';
3386
3462
  }
3387
3463
  else {
3388
- finalProtectedHandoffConfirmed = true;
3464
+ // Protected PR handoff is the terminal autonomous action. A host
3465
+ // merge requires separate authority and remains intentionally
3466
+ // unavailable in this release.
3467
+ finalProtectedHandoffConfirmed = Boolean(url);
3389
3468
  mergeNote = 'PR opened; host auto-merge is disabled until durable revocation is available';
3390
3469
  }
3391
3470
  }