nexus-agents 8.58.2 → 8.58.4

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 (132) hide show
  1. package/dist/{ast-rule-runner-PIEUVYH5.js → ast-rule-runner-NIX2R2ER.js} +3 -3
  2. package/dist/{child-mcp-config-ZXP5YKTJ.js → child-mcp-config-LBCDGXVO.js} +2 -2
  3. package/dist/{chunk-YF3YIHPZ.js → chunk-3A5BXLPV.js} +695 -621
  4. package/dist/chunk-3A5BXLPV.js.map +1 -0
  5. package/dist/{chunk-MHXSHTHG.js → chunk-3JJHGGTT.js} +2 -2
  6. package/dist/{chunk-3JOMLFC7.js → chunk-4FFTWLHX.js} +10 -8
  7. package/dist/{chunk-3JOMLFC7.js.map → chunk-4FFTWLHX.js.map} +1 -1
  8. package/dist/{chunk-CGEWEJDT.js → chunk-5HAKYKCY.js} +2 -2
  9. package/dist/{chunk-QSSP62D3.js → chunk-5RSH7N6W.js} +2 -2
  10. package/dist/{chunk-HGR5L7C4.js → chunk-67AADYKZ.js} +2 -2
  11. package/dist/{chunk-JF77LAYJ.js → chunk-6DYJTPER.js} +3 -3
  12. package/dist/{chunk-RNWUIQTZ.js → chunk-AKC7SODA.js} +2 -2
  13. package/dist/{chunk-4UEZ2U5Q.js → chunk-AQHLUZ44.js} +2 -2
  14. package/dist/{chunk-PMWQSCYE.js → chunk-ARUHRGGB.js} +2 -2
  15. package/dist/{chunk-QMGMD6JB.js → chunk-CDT2CX3J.js} +7 -7
  16. package/dist/{chunk-NFGBBUZ5.js → chunk-GDOEI664.js} +4 -4
  17. package/dist/{chunk-3PJ3QW6E.js → chunk-GESWDHFD.js} +2 -2
  18. package/dist/{chunk-3YICA7KW.js → chunk-HGLRQ4N5.js} +4 -4
  19. package/dist/{chunk-54TOPQFO.js → chunk-HILSAACO.js} +3 -3
  20. package/dist/{chunk-NNWFXJGL.js → chunk-HRXWESJB.js} +3 -3
  21. package/dist/{chunk-JE2TRDX7.js → chunk-IK5FLVX4.js} +4 -4
  22. package/dist/{chunk-VZSMNIRN.js → chunk-J5MWJGDC.js} +2 -2
  23. package/dist/{chunk-TQERTSKG.js → chunk-KFOYGHUC.js} +2 -2
  24. package/dist/{chunk-UGJUQEGG.js → chunk-KMEGWPIU.js} +2 -2
  25. package/dist/{chunk-CSYESWQ2.js → chunk-KT26CDSA.js} +9 -9
  26. package/dist/{chunk-CZQRMQEI.js → chunk-MH2IGA2C.js} +2 -2
  27. package/dist/{chunk-6VKXYQ4V.js → chunk-MTKFVOKA.js} +2 -2
  28. package/dist/{chunk-IIZKJP44.js → chunk-PCGPMJBA.js} +36 -36
  29. package/dist/{chunk-FYN3HLCV.js → chunk-PSRLUHF2.js} +2 -2
  30. package/dist/{chunk-Y6MYVL5H.js → chunk-QV7WOPZY.js} +3 -3
  31. package/dist/{chunk-FLFAVKKO.js → chunk-S7Y4BHTF.js} +2 -2
  32. package/dist/{chunk-GNF77S63.js → chunk-SHH7CZOP.js} +2 -2
  33. package/dist/{chunk-SZXIRRHP.js → chunk-TZ6FVGJZ.js} +2 -2
  34. package/dist/{chunk-E3TCGLNB.js → chunk-VUB6RPPH.js} +2 -2
  35. package/dist/{chunk-ZOGZ3N2I.js → chunk-X7NUFNVJ.js} +5 -5
  36. package/dist/{chunk-CZN5JK7D.js → chunk-XFUWBS6A.js} +2 -2
  37. package/dist/{chunk-WV7KAIVF.js → chunk-YDVOA4I3.js} +5 -5
  38. package/dist/{chunk-BXP5K5TU.js → chunk-YLBVRBVK.js} +4 -4
  39. package/dist/{chunk-BCX7SDBW.js → chunk-ZTX7DLWE.js} +6 -6
  40. package/dist/{cli-circuit-breaker-ZH2WM7XG.js → cli-circuit-breaker-G7JVCCHA.js} +3 -3
  41. package/dist/cli.d.ts +1 -1
  42. package/dist/cli.js +65 -56
  43. package/dist/cli.js.map +1 -1
  44. package/dist/{composite-router-TUSPD2LH.js → composite-router-ZBNPUNRE.js} +2 -2
  45. package/dist/{consensus-vote-PLLP7IJE.js → consensus-vote-GEVSVYV6.js} +17 -17
  46. package/dist/{consensus-vote-types-ClG_UKij.d.ts → consensus-vote-types-DTUSdB9g.d.ts} +38 -0
  47. package/dist/{context-retriever-ZCA22Y2N.js → context-retriever-J34XBWSS.js} +8 -8
  48. package/dist/{doctor-deep-ZXVTSYI7.js → doctor-deep-ITUREQZO.js} +3 -3
  49. package/dist/{doctor-live-HZKYNHYG.js → doctor-live-UVODP772.js} +10 -10
  50. package/dist/{expert-bridge-CH5WJXKF.js → expert-bridge-QX2UR2BM.js} +4 -4
  51. package/dist/{factory-42S5Q2RE.js → factory-EYT7ROYW.js} +5 -5
  52. package/dist/{factory-S7FJJ7OM.js → factory-WOUE5TFB.js} +9 -9
  53. package/dist/{improvement-review-NAITF6LS.js → improvement-review-25ML3PFG.js} +6 -6
  54. package/dist/index.d.ts +2 -2
  55. package/dist/index.js +32 -32
  56. package/dist/{init-opencode-HRC7UQEN.js → init-opencode-K7SFAFPP.js} +6 -6
  57. package/dist/issue-triage-ULMBQTMC.js +17 -0
  58. package/dist/{pr-reviewer-helpers-UIRMJ6ZL.js → pr-reviewer-helpers-AS7QF6EZ.js} +4 -4
  59. package/dist/{registry-command-7ONLPRER.js → registry-command-TBKBERPQ.js} +2 -2
  60. package/dist/{repo-security-plan-U3O7HMYZ.js → repo-security-plan-IL67SCKX.js} +3 -3
  61. package/dist/{research-helpers-synthesize-LQFCG5WB.js → research-helpers-synthesize-567ZPTCF.js} +4 -4
  62. package/dist/{routing-memory-75GUJNG6.js → routing-memory-RHDT5HIP.js} +2 -2
  63. package/dist/{session-memory-ZO5R4OCY.js → session-memory-UPX3KPG6.js} +3 -3
  64. package/dist/{setup-config-HIXX6M2Q.js → setup-config-HKQKKAIB.js} +3 -3
  65. package/dist/{setup-custom-api-5HFRWSFY.js → setup-custom-api-OBAFCX4H.js} +4 -4
  66. package/dist/setup-data-dir-S2YYGJSS.js +31 -0
  67. package/dist/{tool-memory-LGCIIQNG.js → tool-memory-NFO4G45N.js} +5 -5
  68. package/dist/{unified-registry-3GX75GJH.js → unified-registry-RQ34NS65.js} +10 -10
  69. package/dist/{weather-report-CR3M57AH.js → weather-report-5UHJLCEZ.js} +2 -2
  70. package/package.json +1 -1
  71. package/dist/chunk-YF3YIHPZ.js.map +0 -1
  72. package/dist/issue-triage-Z7I7DSB3.js +0 -17
  73. package/dist/setup-data-dir-AHUNLSRM.js +0 -31
  74. /package/dist/{ast-rule-runner-PIEUVYH5.js.map → ast-rule-runner-NIX2R2ER.js.map} +0 -0
  75. /package/dist/{child-mcp-config-ZXP5YKTJ.js.map → child-mcp-config-LBCDGXVO.js.map} +0 -0
  76. /package/dist/{chunk-MHXSHTHG.js.map → chunk-3JJHGGTT.js.map} +0 -0
  77. /package/dist/{chunk-CGEWEJDT.js.map → chunk-5HAKYKCY.js.map} +0 -0
  78. /package/dist/{chunk-QSSP62D3.js.map → chunk-5RSH7N6W.js.map} +0 -0
  79. /package/dist/{chunk-HGR5L7C4.js.map → chunk-67AADYKZ.js.map} +0 -0
  80. /package/dist/{chunk-JF77LAYJ.js.map → chunk-6DYJTPER.js.map} +0 -0
  81. /package/dist/{chunk-RNWUIQTZ.js.map → chunk-AKC7SODA.js.map} +0 -0
  82. /package/dist/{chunk-4UEZ2U5Q.js.map → chunk-AQHLUZ44.js.map} +0 -0
  83. /package/dist/{chunk-PMWQSCYE.js.map → chunk-ARUHRGGB.js.map} +0 -0
  84. /package/dist/{chunk-QMGMD6JB.js.map → chunk-CDT2CX3J.js.map} +0 -0
  85. /package/dist/{chunk-NFGBBUZ5.js.map → chunk-GDOEI664.js.map} +0 -0
  86. /package/dist/{chunk-3PJ3QW6E.js.map → chunk-GESWDHFD.js.map} +0 -0
  87. /package/dist/{chunk-3YICA7KW.js.map → chunk-HGLRQ4N5.js.map} +0 -0
  88. /package/dist/{chunk-54TOPQFO.js.map → chunk-HILSAACO.js.map} +0 -0
  89. /package/dist/{chunk-NNWFXJGL.js.map → chunk-HRXWESJB.js.map} +0 -0
  90. /package/dist/{chunk-JE2TRDX7.js.map → chunk-IK5FLVX4.js.map} +0 -0
  91. /package/dist/{chunk-VZSMNIRN.js.map → chunk-J5MWJGDC.js.map} +0 -0
  92. /package/dist/{chunk-TQERTSKG.js.map → chunk-KFOYGHUC.js.map} +0 -0
  93. /package/dist/{chunk-UGJUQEGG.js.map → chunk-KMEGWPIU.js.map} +0 -0
  94. /package/dist/{chunk-CSYESWQ2.js.map → chunk-KT26CDSA.js.map} +0 -0
  95. /package/dist/{chunk-CZQRMQEI.js.map → chunk-MH2IGA2C.js.map} +0 -0
  96. /package/dist/{chunk-6VKXYQ4V.js.map → chunk-MTKFVOKA.js.map} +0 -0
  97. /package/dist/{chunk-IIZKJP44.js.map → chunk-PCGPMJBA.js.map} +0 -0
  98. /package/dist/{chunk-FYN3HLCV.js.map → chunk-PSRLUHF2.js.map} +0 -0
  99. /package/dist/{chunk-Y6MYVL5H.js.map → chunk-QV7WOPZY.js.map} +0 -0
  100. /package/dist/{chunk-FLFAVKKO.js.map → chunk-S7Y4BHTF.js.map} +0 -0
  101. /package/dist/{chunk-GNF77S63.js.map → chunk-SHH7CZOP.js.map} +0 -0
  102. /package/dist/{chunk-SZXIRRHP.js.map → chunk-TZ6FVGJZ.js.map} +0 -0
  103. /package/dist/{chunk-E3TCGLNB.js.map → chunk-VUB6RPPH.js.map} +0 -0
  104. /package/dist/{chunk-ZOGZ3N2I.js.map → chunk-X7NUFNVJ.js.map} +0 -0
  105. /package/dist/{chunk-CZN5JK7D.js.map → chunk-XFUWBS6A.js.map} +0 -0
  106. /package/dist/{chunk-WV7KAIVF.js.map → chunk-YDVOA4I3.js.map} +0 -0
  107. /package/dist/{chunk-BXP5K5TU.js.map → chunk-YLBVRBVK.js.map} +0 -0
  108. /package/dist/{chunk-BCX7SDBW.js.map → chunk-ZTX7DLWE.js.map} +0 -0
  109. /package/dist/{cli-circuit-breaker-ZH2WM7XG.js.map → cli-circuit-breaker-G7JVCCHA.js.map} +0 -0
  110. /package/dist/{composite-router-TUSPD2LH.js.map → composite-router-ZBNPUNRE.js.map} +0 -0
  111. /package/dist/{consensus-vote-PLLP7IJE.js.map → consensus-vote-GEVSVYV6.js.map} +0 -0
  112. /package/dist/{context-retriever-ZCA22Y2N.js.map → context-retriever-J34XBWSS.js.map} +0 -0
  113. /package/dist/{doctor-deep-ZXVTSYI7.js.map → doctor-deep-ITUREQZO.js.map} +0 -0
  114. /package/dist/{doctor-live-HZKYNHYG.js.map → doctor-live-UVODP772.js.map} +0 -0
  115. /package/dist/{expert-bridge-CH5WJXKF.js.map → expert-bridge-QX2UR2BM.js.map} +0 -0
  116. /package/dist/{factory-42S5Q2RE.js.map → factory-EYT7ROYW.js.map} +0 -0
  117. /package/dist/{factory-S7FJJ7OM.js.map → factory-WOUE5TFB.js.map} +0 -0
  118. /package/dist/{improvement-review-NAITF6LS.js.map → improvement-review-25ML3PFG.js.map} +0 -0
  119. /package/dist/{init-opencode-HRC7UQEN.js.map → init-opencode-K7SFAFPP.js.map} +0 -0
  120. /package/dist/{issue-triage-Z7I7DSB3.js.map → issue-triage-ULMBQTMC.js.map} +0 -0
  121. /package/dist/{pr-reviewer-helpers-UIRMJ6ZL.js.map → pr-reviewer-helpers-AS7QF6EZ.js.map} +0 -0
  122. /package/dist/{registry-command-7ONLPRER.js.map → registry-command-TBKBERPQ.js.map} +0 -0
  123. /package/dist/{repo-security-plan-U3O7HMYZ.js.map → repo-security-plan-IL67SCKX.js.map} +0 -0
  124. /package/dist/{research-helpers-synthesize-LQFCG5WB.js.map → research-helpers-synthesize-567ZPTCF.js.map} +0 -0
  125. /package/dist/{routing-memory-75GUJNG6.js.map → routing-memory-RHDT5HIP.js.map} +0 -0
  126. /package/dist/{session-memory-ZO5R4OCY.js.map → session-memory-UPX3KPG6.js.map} +0 -0
  127. /package/dist/{setup-config-HIXX6M2Q.js.map → setup-config-HKQKKAIB.js.map} +0 -0
  128. /package/dist/{setup-custom-api-5HFRWSFY.js.map → setup-custom-api-OBAFCX4H.js.map} +0 -0
  129. /package/dist/{setup-data-dir-AHUNLSRM.js.map → setup-data-dir-S2YYGJSS.js.map} +0 -0
  130. /package/dist/{tool-memory-LGCIIQNG.js.map → tool-memory-NFO4G45N.js.map} +0 -0
  131. /package/dist/{unified-registry-3GX75GJH.js.map → unified-registry-RQ34NS65.js.map} +0 -0
  132. /package/dist/{weather-report-CR3M57AH.js.map → weather-report-5UHJLCEZ.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  computeCostDetail,
3
3
  priceBasisOf
4
- } from "./chunk-QSSP62D3.js";
4
+ } from "./chunk-5RSH7N6W.js";
5
5
  import {
6
6
  allOf
7
7
  } from "./chunk-OQB3XC5O.js";
@@ -25,20 +25,20 @@ import {
25
25
  toolSuccessStructured,
26
26
  withProgressHeartbeat,
27
27
  wrapToolWithTimeout
28
- } from "./chunk-JE2TRDX7.js";
28
+ } from "./chunk-IK5FLVX4.js";
29
29
  import {
30
30
  VERSION
31
- } from "./chunk-CZN5JK7D.js";
31
+ } from "./chunk-XFUWBS6A.js";
32
32
  import {
33
33
  UNRESOLVED_MODEL_ID,
34
34
  assessPanelIndependence,
35
35
  countDistinctModels,
36
36
  createEvent,
37
37
  getGlobalRegistry
38
- } from "./chunk-BXP5K5TU.js";
38
+ } from "./chunk-YLBVRBVK.js";
39
39
  import {
40
40
  getToolMemory
41
- } from "./chunk-3YICA7KW.js";
41
+ } from "./chunk-HGLRQ4N5.js";
42
42
  import {
43
43
  authRemediation,
44
44
  classifyExtractedError,
@@ -47,7 +47,7 @@ import {
47
47
  isTimeoutText,
48
48
  sleep,
49
49
  withTimeout
50
- } from "./chunk-3JOMLFC7.js";
50
+ } from "./chunk-4FFTWLHX.js";
51
51
  import {
52
52
  AgentError,
53
53
  CLI_NAMES,
@@ -77,7 +77,7 @@ import {
77
77
  registerPersistentOutcomeStoreFactory,
78
78
  resolveClassGuardMs,
79
79
  resolveVoteTimeout
80
- } from "./chunk-JF77LAYJ.js";
80
+ } from "./chunk-6DYJTPER.js";
81
81
  import {
82
82
  ensureLearningDir,
83
83
  getOutcomesFile
@@ -280,6 +280,463 @@ function reportPanelModelIndependence(modelIds, roleCount, phase, logger11) {
280
280
  );
281
281
  }
282
282
 
283
+ // src/audit/vote-record.ts
284
+ import * as crypto from "node:crypto";
285
+ import { z as z2 } from "zod";
286
+ var VoteRecordDecisionSchema = z2.enum(["approved", "rejected", "no_quorum"]);
287
+ var VoteRecordOptionCoverageSchema = z2.object({
288
+ /** Every approving voter, selecting or not — the tally's denominator. */
289
+ approverCount: z2.number().int().nonnegative(),
290
+ /** Approvers whose selection matched a declared option. */
291
+ selectedCount: z2.number().int().nonnegative(),
292
+ /** Approvers with no usable selection; their choice is unmeasured. */
293
+ unattributedApprovals: z2.number().int().nonnegative()
294
+ }).strict();
295
+ var VoteRecordPanelCoverageSchema = z2.object({
296
+ /** Voters the panel asked for. */
297
+ requested: z2.number().int().nonnegative(),
298
+ /** Voters that returned a usable vote — the denominator of `voteCounts`. */
299
+ responded: z2.number().int().nonnegative(),
300
+ /** Voters that errored or timed out. */
301
+ errored: z2.number().int().nonnegative(),
302
+ /** The roles behind `errored`, in panel order. */
303
+ erroredRoles: z2.array(z2.string().min(1).max(100))
304
+ }).strict();
305
+ var MAX_VOTER_REASONING_CHARS = 2e4;
306
+ function clipForRecord(text) {
307
+ if (text.length <= MAX_VOTER_REASONING_CHARS) return { text };
308
+ return { text: text.slice(0, MAX_VOTER_REASONING_CHARS), truncated: true };
309
+ }
310
+ var RetriedFromRecordSchema = z2.object({
311
+ /** The first pass's `source`: errored, or answered without reading. */
312
+ source: z2.enum(["error", "unverifiable"]),
313
+ /** The first pass's `error` string, when it had one; already clipped. */
314
+ error: z2.string().max(MAX_VOTER_REASONING_CHARS).optional(),
315
+ /** True when `error` was clipped to {@link MAX_VOTER_REASONING_CHARS}. */
316
+ errorTruncated: z2.literal(true).optional()
317
+ }).strict();
318
+ var SeatFallbackRecordSchema = z2.object({
319
+ /** The CLI the seat was assigned to, bare (`claude`, not `cli-claude`). */
320
+ fromCli: z2.string().min(1).max(100),
321
+ /** The model the assigned adapter had detected, when it had one. */
322
+ fromModel: z2.string().min(1).max(200).optional(),
323
+ /** The adapter-layer class of the error that moved the seat. */
324
+ reason: z2.enum(["rate-limit", "capacity", "auth", "timeout", "sandbox", "unknown"])
325
+ }).strict();
326
+ var VoterSummarySchema = z2.object({
327
+ role: z2.string().min(1).max(100),
328
+ decision: z2.enum(["approve", "reject", "abstain"]),
329
+ confidence: z2.number().min(0).max(1),
330
+ /**
331
+ * The voter's stated grounds (#5373, schema 1.6).
332
+ *
333
+ * `generateVoteHash` already hashed `{role, decision, reasoning}` and then
334
+ * discarded the text, so the chain attested to a value it did not store and
335
+ * nobody could re-verify the hash without re-obtaining the reasoning. On
336
+ * #5228 a contrarian rejection was clipped mid-sentence in the terminal, the
337
+ * grounds were unrecoverable, and the same defect resurfaced a round later —
338
+ * one round of a 7-voter panel is 7-13 minutes of model time, and the
339
+ * objection was right both times.
340
+ *
341
+ * Stored whole rather than sampled. Warn-mode near-misses are emitted per
342
+ * TOOL CALL, thousands a day; votes are emitted per PANEL, dozens a day at
343
+ * most, so ~17 KB per vote is not the same growth problem.
344
+ *
345
+ * Absence is not an empty argument: an errored voter has no entry in
346
+ * `voters` at all (see `panelCoverage`), so `reasoning: ''` means a live
347
+ * voter returned nothing, which is itself a signal.
348
+ */
349
+ reasoning: z2.string().max(MAX_VOTER_REASONING_CHARS).optional(),
350
+ /** True when `reasoning` was clipped to {@link MAX_VOTER_REASONING_CHARS}. */
351
+ reasoningTruncated: z2.literal(true).optional(),
352
+ /**
353
+ * True when this seat was recovered by the per-role retry (#6050, schema 1.7).
354
+ *
355
+ * `voter-retry.ts` has set `retried: true` on recovered results since it was
356
+ * written, and `vote-types.ts` states the reason: the flag is "what makes the
357
+ * recovery visible instead of indistinguishable from a clean first attempt".
358
+ * It had one producer and ZERO consumers — both summarizers and the record
359
+ * dropped it — so the field existed, was documented, and never reached
360
+ * anything that could act on it.
361
+ *
362
+ * A retried seat is weaker evidence than a first-pass one: the model was
363
+ * unavailable or timed out, and the recovery ran under different conditions.
364
+ * For a ratification vote on a governor-path change, "7 of 7 answered" and
365
+ * "6 answered, 1 recovered on retry" are different facts about the scrutiny
366
+ * the change received, and the record stated the first for both.
367
+ *
368
+ * `literal(true)` and optional, on the `reasoningTruncated` rule: a clean
369
+ * seat carries no key, so its entry re-hashes byte-identical and every
370
+ * historical record still verifies.
371
+ */
372
+ retried: z2.literal(true).optional(),
373
+ /**
374
+ * Registry model id the seat ran on (#6091, schema 1.8).
375
+ *
376
+ * The seat→model mapping is round-robin over the available CLIs plus
377
+ * `NEXUS_VOTER_MODEL_<ROLE>` pins, so it is not recoverable from the
378
+ * ledger after the fact: 1844 voter entries across 278 records carried no
379
+ * model. "When an agent's output becomes evidence, its provenance travels
380
+ * with it" — a tally that says `scope_steward: approve` without saying
381
+ * which model answered cannot be audited for the #6068 failure class.
382
+ * Present-only, on the `retried` rule; absent when the result carried no
383
+ * model or only the pending-detection placeholder.
384
+ */
385
+ model: z2.string().min(1).max(200).optional(),
386
+ /**
387
+ * True when the seat could not read the artifact (#6094, schema 1.8).
388
+ *
389
+ * `decision` is `abstain` for such a seat, so without this flag the record
390
+ * could not tell "read it and abstained" from "never saw it" — and one
391
+ * ledger entry (`vote-1789058788915-6lfrbuq`) shows a seat that APPROVED
392
+ * on the proposal text after failing to read. `literal(true)` and
393
+ * present-only, so every pre-1.8 entry re-hashes byte-identical.
394
+ */
395
+ unverifiable: z2.literal(true).optional(),
396
+ /**
397
+ * The CLI the round-robin or `NEXUS_VOTER_MODEL_<ROLE>` pin chose for the
398
+ * seat, as a bare name (#6115, schema 1.9). `model` says where a seat
399
+ * answered; this says where it was MEANT to. Three consecutive 7-seat
400
+ * ratification panels on 2026-09-13 answered every seat on one gemini
401
+ * model while the assignment was three claude, two codex, two gemini,
402
+ * and the 1.8 record showed seven identical `model` values with nothing
403
+ * that said five of them were substitutes. Present-only, on the `model`
404
+ * rule; absent on a result built outside the panel launcher.
405
+ */
406
+ assignedCli: z2.string().min(1).max(100).optional(),
407
+ /**
408
+ * Present only when the seat answered on a different CLI or model than
409
+ * assigned (#6115, schema 1.9): the exact `SeatFallback` shape the live
410
+ * result carries, rebuilt field-by-field. `fromCli` is the assigned CLI,
411
+ * `fromModel` the model it had detected (absent when it never did — the
412
+ * pending-detection placeholder is not a model), `reason` the adapter
413
+ * layer's error class. The vocabulary is compile-checked against the live
414
+ * `FallbackReason` in vote-record.test.ts; a class one side learns alone
415
+ * would either refuse every record that carries it (#6054) or accept one
416
+ * no producer can write.
417
+ */
418
+ fallback: SeatFallbackRecordSchema.optional(),
419
+ /**
420
+ * Present only when the per-role retry REPLACED this seat (#6246, schema
421
+ * 1.12): the first pass's source and, when it had one, its clipped error
422
+ * string. `retried` (1.7) says a recovery happened; this says what it
423
+ * recovered from. On the #6241 panel a seat recorded `retried: true,
424
+ * unverifiable: true` after a first pass that errored on two response-parse
425
+ * failures, and nothing in the record joined the two — a reader of the
426
+ * ledger and the log together concluded the parse errors had been
427
+ * misclassified (#6244). Rebuilt field-by-field on the `fallback` rule; the
428
+ * `source` vocabulary is compile-checked against the live `RetriedFrom`.
429
+ */
430
+ retriedFrom: RetriedFromRecordSchema.optional()
431
+ }).strict();
432
+ function defineVoterKeys(keys) {
433
+ return keys;
434
+ }
435
+ var VOTER_SUMMARY_KEYS = defineVoterKeys([
436
+ "role",
437
+ "decision",
438
+ "confidence",
439
+ "reasoning",
440
+ "reasoningTruncated",
441
+ "retried",
442
+ // 1.8 (#6091, #6094): appended after `retried`, present-only, so a 1.7
443
+ // entry projects byte-identically.
444
+ "model",
445
+ "unverifiable",
446
+ // 1.9 (#6115): appended after `unverifiable`, present-only, so a 1.8 entry
447
+ // projects byte-identically. `fallback` is the first nested voter field;
448
+ // `projectSeatFallback` rebuilds it in its own canonical order.
449
+ "assignedCli",
450
+ "fallback",
451
+ // 1.12 (#6246): appended after `fallback`, present-only, so a 1.11 entry
452
+ // projects byte-identically. Nested; `projectRetriedFrom` rebuilds it in
453
+ // its own canonical order.
454
+ "retriedFrom"
455
+ ]);
456
+ function noUnprojectedKeys(rest) {
457
+ return rest;
458
+ }
459
+ function projectSeatFallback(f) {
460
+ const { fromCli, fromModel, reason, ...rest } = f;
461
+ noUnprojectedKeys(rest);
462
+ return {
463
+ fromCli,
464
+ ...fromModel !== void 0 ? { fromModel } : {},
465
+ reason
466
+ };
467
+ }
468
+ function projectRetriedFrom(r) {
469
+ const { source, error, errorTruncated, ...rest } = r;
470
+ noUnprojectedKeys(rest);
471
+ return {
472
+ source,
473
+ ...error !== void 0 ? { error } : {},
474
+ ...errorTruncated !== void 0 ? { errorTruncated } : {}
475
+ };
476
+ }
477
+ function projectVoterField(v, key) {
478
+ if (key === "fallback") {
479
+ return v.fallback === void 0 ? void 0 : projectSeatFallback(v.fallback);
480
+ }
481
+ if (key === "retriedFrom") {
482
+ return v.retriedFrom === void 0 ? void 0 : projectRetriedFrom(v.retriedFrom);
483
+ }
484
+ return v[key];
485
+ }
486
+ function projectVoterSummary(v) {
487
+ const out = {};
488
+ for (const key of VOTER_SUMMARY_KEYS) {
489
+ const value = projectVoterField(v, key);
490
+ if (value !== void 0) out[key] = value;
491
+ }
492
+ return out;
493
+ }
494
+ var VoteRecordCountsSchema = z2.object({
495
+ approve: z2.number().int().nonnegative(),
496
+ reject: z2.number().int().nonnegative(),
497
+ abstain: z2.number().int().nonnegative(),
498
+ total: z2.number().int().nonnegative()
499
+ }).strict();
500
+ var VoteRecordOptionCountSchema = z2.object({
501
+ /** The option label as declared in the proposal (e.g. 'A'). */
502
+ option: z2.string().min(1),
503
+ /** How many voters selected it. */
504
+ count: z2.number().int().nonnegative()
505
+ }).strict();
506
+ var GIT_SHA_PATTERN = /^[0-9a-f]{40}$/;
507
+ var VoteRecordPrBindingSchema = z2.object({
508
+ pr: z2.number().int().positive(),
509
+ headSha: z2.string().regex(GIT_SHA_PATTERN, "expected a full 40-hex lowercase commit sha")
510
+ }).strict();
511
+ function projectPrBinding(b) {
512
+ return { pr: b.pr, headSha: b.headSha };
513
+ }
514
+ var VoteRecordErrorPolicySchema = z2.enum([
515
+ "reduce_denominator",
516
+ "count_as_abstain",
517
+ "fail_closed",
518
+ "absolute_quorum"
519
+ ]);
520
+ var VoteRecordSchema = z2.object({
521
+ /**
522
+ * Schema version. '1.1' marked the chain→record-set+sequence model (#3927);
523
+ * '1.2' adds the optional `ratifies` subject-binding field (#3927 item 1);
524
+ * '1.10' adds the optional `ratifiesPr` PR-binding (#5130); '1.11' the
525
+ * optional `errorPolicy` (#6211); '1.12' the per-voter `retriedFrom`
526
+ * (#6246). Every tier is accepted — a 1.1 record
527
+ * (no `ratifies`) verifies unchanged because each optional is folded into
528
+ * the self-hash ONLY when present (see {@link computeVoteRecordHash}).
529
+ * Tiers are labels, not ordered numbers: '1.10' follows '1.9' by
530
+ * convention only and nothing compares them.
531
+ */
532
+ version: z2.enum([
533
+ "1.1",
534
+ "1.2",
535
+ "1.3",
536
+ "1.4",
537
+ "1.5",
538
+ "1.6",
539
+ "1.7",
540
+ "1.8",
541
+ "1.9",
542
+ "1.10",
543
+ "1.11",
544
+ "1.12"
545
+ ]),
546
+ /** Unique record id (also usable as a `ratificationVoteRef`). */
547
+ id: z2.string().min(1),
548
+ /**
549
+ * Monotonic sequence number (integer ≥ 0). Assigned as (max existing
550
+ * sequence)+1 at write time. Sorted, the set of sequences must cover
551
+ * 0..maxSeq with no gap (omission detection); DUPLICATE sequences are a
552
+ * benign concurrent-fork signal, not tampering.
553
+ */
554
+ sequence: z2.number().int().nonnegative(),
555
+ /** ISO-8601 timestamp the vote was recorded. */
556
+ recordedAt: z2.string().min(1),
557
+ /**
558
+ * SHA-256 of the FULL proposal text. The proposal itself is truncated for
559
+ * the human record (`proposal`), but the hash binds the record to the exact
560
+ * proposal voted on — a later edit of `proposal` that changes meaning is
561
+ * detectable by recomputing this hash from the original.
562
+ */
563
+ proposalHash: z2.string().length(64),
564
+ /** Truncated proposal text for the human/reviewer record. */
565
+ proposal: z2.string(),
566
+ /** The voting strategy used. */
567
+ strategy: z2.enum([
568
+ "simple_majority",
569
+ "supermajority",
570
+ "unanimous",
571
+ "higher_order",
572
+ "opinion_wise",
573
+ "proof_of_learning"
574
+ ]),
575
+ /** The resolved decision. */
576
+ decision: VoteRecordDecisionSchema,
577
+ /** Approval fraction as a percentage (0-100). */
578
+ approvalPercentage: z2.number().min(0).max(100),
579
+ /** Vote-count breakdown. */
580
+ voteCounts: VoteRecordCountsSchema,
581
+ /** Per-voter {role, decision, confidence} summary. */
582
+ voters: z2.array(VoterSummarySchema),
583
+ /** Optional correlation/decision id linking to the cost rollup / trace. */
584
+ correlationId: z2.string().min(1).optional(),
585
+ /**
586
+ * Per-option distribution for a multi-option proposal (#4452, schema 1.3).
587
+ *
588
+ * Present only when the vote declared `options`. Absent on an ordinary
589
+ * yes/no vote, and absent on every 1.1/1.2 record — which is why it is
590
+ * folded into the self-hash ONLY when present (see
591
+ * {@link computeVoteRecordHash}), exactly as `ratifies` was.
592
+ *
593
+ * Without this, a 6-1 or 5-2 option split is recorded as `approve: 7` and
594
+ * reads as unanimous. Threshold semantics invert too: `unanimous` becomes
595
+ * the EASIEST bar to clear, because every engaged voter approves while
596
+ * choosing different things.
597
+ */
598
+ // No `.min(1)` (#6049). The rule this field's own docstring states is
599
+ // "present only when the vote declared `options`" -- keyed on DECLARATION,
600
+ // not on whether anyone picked one. `.min(1)` encoded the older, narrower
601
+ // rule, so once the builder started emitting `[]` for "declared, nothing
602
+ // attributable" the record appended fine and then failed to parse on read:
603
+ // `too_small, minimum 1`. The write path does not validate, so the line
604
+ // landed in the ledger and `parseVoteRecordsText` silently dropped it --
605
+ // the case most worth auditing became the one no reader could see.
606
+ //
607
+ // Relaxing is a pure widening: no previously-valid record changes meaning
608
+ // or hash, and no persisted record can currently hold an empty tally.
609
+ optionTally: z2.array(VoteRecordOptionCountSchema).optional(),
610
+ /**
611
+ * Selection coverage for a multi-option vote (#4472, schema 1.4).
612
+ *
613
+ * `optionTally` alone cannot distinguish dissent from absence: `4 pick X
614
+ * + 3 unreadable` and a genuine 4/3 split both leave a leading share of
615
+ * 57%. Recording how many approvers produced no usable selection is what
616
+ * makes a partial measurement legible AS partial — the condition every
617
+ * voter attached to adopting the "credit no option" rule.
618
+ */
619
+ optionCoverage: VoteRecordOptionCoverageSchema.optional(),
620
+ /**
621
+ * Panel coverage (#5738, schema 1.5). Present only when at least one
622
+ * requested voter failed to return a vote; absent keeps a clean panel on
623
+ * the pre-1.5 projection, so every historical record still verifies.
624
+ */
625
+ panelCoverage: VoteRecordPanelCoverageSchema.optional(),
626
+ /**
627
+ * The loop/strategy subject this vote RATIFIES (#3927 item 1). Present only on
628
+ * a ratification vote; set at vote time and bound into the self-hash (so it is
629
+ * tamper-evident). The authority-tier promotion gate
630
+ * (`scripts/check-authority-tier-drift.ts`) resolves a transition's
631
+ * `ratificationVoteRef` to a record and requires `ratifies === transition.subject`
632
+ * (with `decision === 'approved'` and `strategy === 'higher_order'`). Absent on
633
+ * an ordinary (non-ratification) vote; a 1.1 record never carries it.
634
+ */
635
+ ratifies: z2.string().min(1).optional(),
636
+ /**
637
+ * The PR this vote RATIFIES, bound to the head the panel saw (#5130 step 1,
638
+ * schema 1.10; panel decision Q1 option A, 5 of 6). Present only on a
639
+ * governor-path ratification vote; hash-covered like `ratifies`, so a
640
+ * record cannot be repointed at a different PR or a later push without
641
+ * breaking its hash. The governor gate (step 2, #5779/#5131) resolves the
642
+ * record from the committed ledger and requires `pr` to be the PR under
643
+ * review and `headSha` to be its head (or `head^` when `head` touches only
644
+ * the ledger — the caller-commits tip). Absent on every pre-1.10 record.
645
+ *
646
+ * A SEPARATE key rather than a structured encoding of `ratifies`: that
647
+ * field is the authority-ladder SUBJECT, compared by string equality to
648
+ * `transition.subject` and grouped by `conflictingRatifiedSubjects`; no
649
+ * resolver parses it. Overloading it would give one field two consumers
650
+ * with two equality rules. The two may coexist on one record.
651
+ */
652
+ ratifiesPr: VoteRecordPrBindingSchema.optional(),
653
+ /**
654
+ * The error policy the panel actually ran under (#6211, schema 1.11): the
655
+ * EFFECTIVE policy after `consensus_vote` applied the per-strategy default
656
+ * (`input.errorPolicy ?? getDefaultErrorPolicy(strategy)`), not the raw
657
+ * input the caller may have omitted. Hash-covered, present-only.
658
+ *
659
+ * Before this tier the policy reached the RESPONSE only, so the governor
660
+ * ledger gate (`scripts/governor-ledger-evidence.ts`) could not say which
661
+ * policy a whole-panel ratification was configured with: under
662
+ * `absolute_quorum` an errored seat voids the vote, so the one
663
+ * ledger-observable trace of a different policy was an approved record
664
+ * with `panelCoverage.errored > 0` — and a whole panel under
665
+ * `reduce_denominator` looked exactly like one under `absolute_quorum`.
666
+ * The gate's `wrong-error-policy` verdict reads this field; a record
667
+ * without it (every pre-1.11 record) keeps the panel-coverage inference
668
+ * and is reported as `errorPolicy: unrecorded`.
669
+ */
670
+ errorPolicy: VoteRecordErrorPolicySchema.optional(),
671
+ /**
672
+ * ADVISORY hash of the tip record at write time (absent for the first).
673
+ * Retained for audit texture but NOT covered by `hash` and NOT verified —
674
+ * the record-set model is position-independent (#3927).
675
+ */
676
+ previousHash: z2.string().length(64).optional(),
677
+ /** SHA-256 over every field above EXCEPT `previousHash` (and except `hash`). */
678
+ hash: z2.string().length(64)
679
+ }).strict();
680
+ function foldOptionalFields(base, payload) {
681
+ const withTally = payload.optionTally !== void 0 ? {
682
+ ...base,
683
+ optionTally: payload.optionTally.map((o) => ({ option: o.option, count: o.count }))
684
+ } : base;
685
+ const withCoverage = payload.optionCoverage !== void 0 ? {
686
+ ...withTally,
687
+ optionCoverage: {
688
+ approverCount: payload.optionCoverage.approverCount,
689
+ selectedCount: payload.optionCoverage.selectedCount,
690
+ unattributedApprovals: payload.optionCoverage.unattributedApprovals
691
+ }
692
+ } : withTally;
693
+ const withPanel = payload.panelCoverage !== void 0 ? {
694
+ ...withCoverage,
695
+ panelCoverage: {
696
+ requested: payload.panelCoverage.requested,
697
+ responded: payload.panelCoverage.responded,
698
+ errored: payload.panelCoverage.errored,
699
+ erroredRoles: [...payload.panelCoverage.erroredRoles]
700
+ }
701
+ } : withCoverage;
702
+ const withRatifies = payload.ratifies !== void 0 ? { ...withPanel, ratifies: payload.ratifies } : withPanel;
703
+ const withPrBinding = payload.ratifiesPr !== void 0 ? { ...withRatifies, ratifiesPr: projectPrBinding(payload.ratifiesPr) } : withRatifies;
704
+ return payload.errorPolicy !== void 0 ? { ...withPrBinding, errorPolicy: payload.errorPolicy } : withPrBinding;
705
+ }
706
+ function computeVoteRecordHash(payload) {
707
+ const base = {
708
+ version: payload.version,
709
+ id: payload.id,
710
+ sequence: payload.sequence,
711
+ recordedAt: payload.recordedAt,
712
+ proposalHash: payload.proposalHash,
713
+ proposal: payload.proposal,
714
+ strategy: payload.strategy,
715
+ decision: payload.decision,
716
+ approvalPercentage: payload.approvalPercentage,
717
+ // Rebuild voteCounts in schema order (approve, reject, abstain, total) so the
718
+ // hash does not depend on how the nested object's keys were ordered (#3962).
719
+ voteCounts: {
720
+ approve: payload.voteCounts.approve,
721
+ reject: payload.voteCounts.reject,
722
+ abstain: payload.voteCounts.abstain,
723
+ total: payload.voteCounts.total
724
+ },
725
+ // `reasoning` / `reasoningTruncated` (#5373, schema 1.6) are appended per
726
+ // entry ONLY when present, on the same rule as the record-level optional
727
+ // fields: a pre-1.6 voter entry re-hashes byte-identical, so every
728
+ // historical record still verifies, while editing or removing a stored
729
+ // reasoning flips the hash.
730
+ voters: payload.voters.map(projectVoterSummary),
731
+ correlationId: payload.correlationId ?? null
732
+ };
733
+ const canonical = JSON.stringify(foldOptionalFields(base, payload));
734
+ return crypto.createHash("sha256").update(canonical).digest("hex");
735
+ }
736
+ function hashProposal(proposal) {
737
+ return crypto.createHash("sha256").update(proposal).digest("hex");
738
+ }
739
+
283
740
  // src/cli/voter-unverifiable.ts
284
741
  var UNVERIFIABLE_STDERR_RE = /\bbwrap: |RTM_NEWADDR|\bsandbox\b[^\n]*\b(?:denied|failed|not permitted)\b/i;
285
742
  var UNVERIFIABLE_REASONING_RE = /\bbwrap: |RTM_NEWADDR|\b(?:repository|source|shell) (?:reads?|access|execution|inspection) failed\b|\bcould not (?:read|inspect) the artifact\b/i;
@@ -317,6 +774,13 @@ function isAbsentSeat(v) {
317
774
  }
318
775
 
319
776
  // src/cli/voter-retry.ts
777
+ var CONTROL_CHARS_RE = /[\x00-\x1f\x7f-\x9f]/g;
778
+ function retriedFromOf(first) {
779
+ const source = first.source === "unverifiable" ? "unverifiable" : "error";
780
+ if (first.error === void 0) return { source };
781
+ const { text, truncated } = clipForRecord(first.error.replace(CONTROL_CHARS_RE, " "));
782
+ return { source, error: text, ...truncated === true ? { errorTruncated: true } : {} };
783
+ }
320
784
  var DEFAULT_ERRORED_ROLE_BACKOFF_MS = 3e3;
321
785
  async function retryErroredRoles(first, relaunch, logger11, backoffMs) {
322
786
  const erroredRoles = first.filter(isAbsentSeat).map((v) => v.role);
@@ -328,10 +792,17 @@ async function retryErroredRoles(first, relaunch, logger11, backoffMs) {
328
792
  });
329
793
  if (backoffMs > 0) await sleep(backoffMs);
330
794
  const retriedResults = await relaunch(erroredRoles);
795
+ const firstByRole = new Map(first.map((v) => [v.role, v]));
331
796
  const recovered = /* @__PURE__ */ new Map();
332
797
  for (const r of retriedResults) {
333
798
  if (r.source === "error") continue;
334
- recovered.set(r.role, { ...r, retried: true });
799
+ const prior = firstByRole.get(r.role);
800
+ const from = prior !== void 0 && isAbsentSeat(prior) ? retriedFromOf(prior) : void 0;
801
+ recovered.set(r.role, {
802
+ ...r,
803
+ retried: true,
804
+ ...from !== void 0 ? { retriedFrom: from } : {}
805
+ });
335
806
  }
336
807
  if (recovered.size === 0) {
337
808
  logger11.warn("Per-role retry recovered no voter \u2014 the panel stays degraded", { erroredRoles });
@@ -666,7 +1137,7 @@ var SIMULATED_VOTE_REASONING = {
666
1137
  };
667
1138
 
668
1139
  // src/cli/voter-response.ts
669
- import { z as z2 } from "zod";
1140
+ import { z as z3 } from "zod";
670
1141
 
671
1142
  // src/consensus/option-tally.ts
672
1143
  var SUPERMAJORITY_SHARE = 2 / 3;
@@ -726,26 +1197,26 @@ var SyntheticVoteError = class extends Error {
726
1197
  }
727
1198
  rawOutput;
728
1199
  };
729
- var RawFindingSchema = z2.object({
730
- summary: z2.string().min(1).max(500).describe("One-line summary of the issue"),
731
- location: z2.string().min(1).max(200).describe("path/file.ext:line"),
732
- severity: z2.enum(["critical", "high", "medium", "low"]).default("medium"),
733
- gate: z2.object({
734
- reread_cited_line: z2.enum(["passed", "failed", "skipped"]).default("skipped"),
735
- traced_call_path: z2.enum(["passed", "failed", "skipped"]).default("skipped"),
736
- named_assertion: z2.string().default("").describe("Concrete failing assertion \u2014 substantive, not a rubber-stamp word"),
737
- ruled_out_language_non_issue: z2.enum(["passed", "failed", "skipped"]).default("skipped")
1200
+ var RawFindingSchema = z3.object({
1201
+ summary: z3.string().min(1).max(500).describe("One-line summary of the issue"),
1202
+ location: z3.string().min(1).max(200).describe("path/file.ext:line"),
1203
+ severity: z3.enum(["critical", "high", "medium", "low"]).default("medium"),
1204
+ gate: z3.object({
1205
+ reread_cited_line: z3.enum(["passed", "failed", "skipped"]).default("skipped"),
1206
+ traced_call_path: z3.enum(["passed", "failed", "skipped"]).default("skipped"),
1207
+ named_assertion: z3.string().default("").describe("Concrete failing assertion \u2014 substantive, not a rubber-stamp word"),
1208
+ ruled_out_language_non_issue: z3.enum(["passed", "failed", "skipped"]).default("skipped")
738
1209
  }),
739
- claim: z2.string().min(1).max(2e3).describe("What is wrong and why it justifies blocking")
1210
+ claim: z3.string().min(1).max(2e3).describe("What is wrong and why it justifies blocking")
740
1211
  });
741
- var VoteResponseSchema = z2.object({
742
- decision: z2.enum(["approve", "reject", "abstain"]).describe("Your vote decision"),
743
- reasoning: z2.string().min(10).max(4e3).describe("Explanation for your vote (10-4000 chars)"),
744
- confidence: z2.number().min(0).max(1).describe("Confidence level 0-1"),
745
- conditions: z2.array(z2.string()).optional().describe("Optional conditions for approval"),
1212
+ var VoteResponseSchema = z3.object({
1213
+ decision: z3.enum(["approve", "reject", "abstain"]).describe("Your vote decision"),
1214
+ reasoning: z3.string().min(10).max(4e3).describe("Explanation for your vote (10-4000 chars)"),
1215
+ confidence: z3.number().min(0).max(1).describe("Confidence level 0-1"),
1216
+ conditions: z3.array(z3.string()).optional().describe("Optional conditions for approval"),
746
1217
  /** Structured rejection categories for reject→refine→re-vote loops (Issue #1213). */
747
- rejectionCategories: z2.array(
748
- z2.enum([
1218
+ rejectionCategories: z3.array(
1219
+ z3.enum([
749
1220
  "YAGNI",
750
1221
  "DRY_VIOLATION",
751
1222
  "OVER_ENGINEERING",
@@ -757,11 +1228,11 @@ var VoteResponseSchema = z2.object({
757
1228
  ).optional().describe("Rejection reason categories when decision is reject"),
758
1229
  /** Top-level structured findings for PR-review mode (#2245 v4 follow-up).
759
1230
  * Replaces the YAML-in-reasoning encoding that proved lossy. */
760
- findings: z2.array(RawFindingSchema).optional().describe("Structured findings (PR review only)"),
1231
+ findings: z3.array(RawFindingSchema).optional().describe("Structured findings (PR review only)"),
761
1232
  /** Chosen option when the proposal declared `options` (#4472). Validated
762
1233
  * against the declared list by the caller; an unmatched value is dropped
763
1234
  * rather than recorded, so a parse miss surfaces as absent. */
764
- selectedOption: z2.string().optional().describe("Which declared option you choose")
1235
+ selectedOption: z3.string().optional().describe("Which declared option you choose")
765
1236
  });
766
1237
  var VOTE_JSON_SCHEMA = {
767
1238
  type: "object",
@@ -903,12 +1374,12 @@ Example PR-review request_changes response with structured findings:
903
1374
  }
904
1375
  ]
905
1376
  }`;
906
- function buildVotePrompt(proposal, options) {
1377
+ function buildVotePrompt(proposal, options, workspace) {
907
1378
  return `Evaluate the following proposal and provide your vote.
908
1379
 
909
1380
  PROPOSAL:
910
1381
  ${proposal}
911
- ${buildOptionsBlock(options)}
1382
+ ${buildOptionsBlock(options)}${buildWorkspaceBlock(workspace)}
912
1383
  In addition to your role-specific criteria, assess these workflow-test dimensions:
913
1384
  - Testability: Can the proposed changes be verified with automated tests?
914
1385
  - Workflow integration: Does this fit into existing CI/make/test workflows?
@@ -932,6 +1403,14 @@ OPTIONS \u2014 choose exactly ONE of these and name it verbatim in \`selectedOpt
932
1403
  ${list}
933
1404
  `;
934
1405
  }
1406
+ function buildWorkspaceBlock(workspace) {
1407
+ if (workspace === void 0 || workspace === "") return "";
1408
+ return `
1409
+ REPOSITORY ACCESS:
1410
+ The repository under review is the working directory of this panel: ${workspace}
1411
+ Every seat's file and shell tools run there, and reading it is expected. When the proposal refers to something in that repository \u2014 a PR head, a diff, the files it touches \u2014 read it there before you vote: the proposal text describes the artifact and is not a substitute for it. If your tools cannot read that directory, follow the UNVERIFIABLE rule in your instructions.
1412
+ `;
1413
+ }
935
1414
  function buildSelectionFieldDoc(options) {
936
1415
  if (options === void 0 || options.length === 0) return "";
937
1416
  return "- selectedOption: REQUIRED. Exactly one option from the OPTIONS list above, copied verbatim. Do not invent an option, combine two, or leave it blank \u2014 your selection is what the threshold is measured over.";
@@ -1155,14 +1634,17 @@ function buildVoteRequest({
1155
1634
  timeoutMs,
1156
1635
  withResponseFormat,
1157
1636
  options,
1158
- project
1637
+ project,
1638
+ workspace
1159
1639
  }) {
1160
1640
  const base = {
1161
1641
  messages: [
1162
1642
  // #6110: the seat judges the caller's target project, not this
1163
1643
  // repository's. `undefined` renders the `nexus-agents` default.
1164
1644
  { role: "system", content: getVoterPrompts(project)[role] },
1165
- { role: "user", content: buildVotePrompt(proposal, options) }
1645
+ // #6254: and the working directory the seats run in, so a seat with
1646
+ // file tools knows where the artifact is and that reading it is expected.
1647
+ { role: "user", content: buildVotePrompt(proposal, options, workspace) }
1166
1648
  ],
1167
1649
  // 4000 (#4131): headroom so a findings-bearing verdict (JSON envelope +
1168
1650
  // reasoning + structured findings) isn't cut mid-JSON by the token cap and
@@ -1219,8 +1701,8 @@ async function runVoteCompletion(args) {
1219
1701
  };
1220
1702
  }
1221
1703
  async function executeSingleVoteAttempt(role, proposal, adapter, timeoutMs, context = {}) {
1222
- const { options, project } = context;
1223
- const completionArgs = { role, proposal, adapter, timeoutMs, options, project };
1704
+ const { options, project, workspace } = context;
1705
+ const completionArgs = { role, proposal, adapter, timeoutMs, options, project, workspace };
1224
1706
  let completion = await runVoteCompletion({ ...completionArgs, withResponseFormat: true });
1225
1707
  if (!completion.ok && isStructuredOutputUnsupported(completion.error)) {
1226
1708
  completion = await runVoteCompletion({ ...completionArgs, withResponseFormat: false });
@@ -1251,7 +1733,7 @@ function logAbandonedRetries(logger11, role, attempt, maxRetries) {
1251
1733
  });
1252
1734
  }
1253
1735
  async function executeWithRetries(opts) {
1254
- const { role, proposal, adapter, logger: logger11, timeoutMs, maxRetries, options, project } = opts;
1736
+ const { role, proposal, adapter, logger: logger11, timeoutMs, maxRetries, options, project, workspace } = opts;
1255
1737
  let lastError = "";
1256
1738
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
1257
1739
  if (attempt > 0) {
@@ -1264,7 +1746,8 @@ async function executeWithRetries(opts) {
1264
1746
  const attemptStart = Date.now();
1265
1747
  const result = await executeSingleVoteAttempt(role, proposal, adapter, timeoutMs, {
1266
1748
  options,
1267
- project
1749
+ project,
1750
+ workspace
1268
1751
  });
1269
1752
  const attemptMs = Date.now() - attemptStart;
1270
1753
  if (result.ok) {
@@ -1457,14 +1940,15 @@ function resolveVoteExecution(options) {
1457
1940
  maxRetries: options?.maxRetries ?? VOTE_TIMEOUTS.maxRetries,
1458
1941
  allowSimulation: options?.allowSimulation ?? false,
1459
1942
  declaredOptions: options?.declaredOptions,
1460
- project: options?.project
1943
+ project: options?.project,
1944
+ workspace: options?.workspace
1461
1945
  };
1462
1946
  }
1463
1947
  async function executeAgentVote(role, proposal, adapter, logger11, options) {
1464
1948
  const start = getTimeProvider().now();
1465
- const { timeoutMs, maxRetries, allowSimulation, declaredOptions, project } = resolveVoteExecution(options);
1949
+ const { allowSimulation, declaredOptions, ...settings } = resolveVoteExecution(options);
1466
1950
  logger11.info("Executing vote", { role, model: adapter.modelId, provider: adapter.providerId });
1467
- const retryOptions = { role, proposal, adapter, logger: logger11, timeoutMs, maxRetries, project };
1951
+ const retryOptions = { role, proposal, adapter, logger: logger11, ...settings };
1468
1952
  const result = await executeWithRetries({ ...retryOptions, options: declaredOptions });
1469
1953
  const processingTimeMs = getTimeProvider().now() - start;
1470
1954
  if (result.ok) {
@@ -1607,8 +2091,6 @@ async function assignPanelSeats(fallbackAdapter, options, logger11) {
1607
2091
  async function collectRealVotes(options) {
1608
2092
  const logger11 = options.logger ?? defaultLogger;
1609
2093
  const { roles, proposal, simulate, allowSimulation } = options;
1610
- const timeoutMs = options.timeoutMs ?? resolveVoteTimeout();
1611
- const maxRetries = options.maxRetries ?? VOTE_TIMEOUTS.maxRetries;
1612
2094
  if (simulate === true) {
1613
2095
  logger11.info("Using simulation mode (explicitly requested)");
1614
2096
  return createSimulatedVotes(roles, proposal);
@@ -1618,16 +2100,7 @@ async function collectRealVotes(options) {
1618
2100
  return createSimulatedVotes(roles, proposal, "No adapter available");
1619
2101
  const roleAdapters = await assignPanelSeats(adapterResult.adapter, options, logger11);
1620
2102
  warnIfCodexConcurrencyExceeded(roleAdapters, logger11);
1621
- const voteOptions = {
1622
- timeoutMs,
1623
- maxRetries,
1624
- allowSimulation: allowSimulation ?? false,
1625
- // #4472: reaches executeAgentVote, which puts the declared options in the
1626
- // prompt and resolves each voter's selection against them.
1627
- declaredOptions: options.declaredOptions,
1628
- // #6110: likewise the target project, into every seat's system prompt.
1629
- project: options.project
1630
- };
2103
+ const voteOptions = resolveVoteExecution({ ...options, workspace: process.cwd() });
1631
2104
  const interDelay = options.interAgentDelayMs ?? DEFAULT_INTER_AGENT_DELAY_MS;
1632
2105
  const launchInput = {
1633
2106
  roles,
@@ -1653,9 +2126,9 @@ async function collectRealVotes(options) {
1653
2126
  // src/cli/voter-project.ts
1654
2127
  import { readFileSync, statSync } from "node:fs";
1655
2128
  import { dirname, isAbsolute, join, resolve } from "node:path";
1656
- import { z as z3 } from "zod";
2129
+ import { z as z4 } from "zod";
1657
2130
  var VOTER_PROJECT_PATTERN = /^[A-Za-z0-9._/@-]{1,200}$/;
1658
- var VoterProjectInputSchema = z3.string().regex(VOTER_PROJECT_PATTERN).optional().describe(
2131
+ var VoterProjectInputSchema = z4.string().regex(VOTER_PROJECT_PATTERN).optional().describe(
1659
2132
  "The project the panel is judging (#6110), e.g. `acme/widgets` \u2014 it replaces `nexus-agents` in every voter's system prompt, so a consuming repository is not judged against this one's mission and governance files. When omitted the name is DERIVED from the server's working directory (the `origin` remote as `owner/repo`, else the nearest `package.json` name) and falls back to `nexus-agents`; the response discloses which on `project.source`. Letters, digits and `._/@-` only, at most 200 characters."
1660
2133
  );
1661
2134
  var MAX_ANCESTOR_DEPTH = 64;
@@ -1821,40 +2294,40 @@ function describeVeto(verdict) {
1821
2294
  }
1822
2295
 
1823
2296
  // src/consensus/types-voting-protocol.ts
1824
- import { z as z4 } from "zod";
2297
+ import { z as z5 } from "zod";
1825
2298
  var VOTING_ROUND_DEFAULT_TIMEOUT_MS = OPERATION_CLASSES["multi-llm-panel"].guardMs;
1826
- var VotingRoundPhaseSchema = z4.enum(["analysis", "deliberation", "consensus"]);
1827
- var VotingRoundStatusSchema = z4.enum([
2299
+ var VotingRoundPhaseSchema = z5.enum(["analysis", "deliberation", "consensus"]);
2300
+ var VotingRoundStatusSchema = z5.enum([
1828
2301
  "pending",
1829
2302
  "in_progress",
1830
2303
  "awaiting_votes",
1831
2304
  "completed",
1832
2305
  "aborted"
1833
2306
  ]);
1834
- var AgentFindingSchema = z4.object({
1835
- agentId: z4.string(),
1836
- category: z4.enum(["bug", "security", "performance", "style", "design", "documentation", "other"]),
1837
- severity: z4.enum(["critical", "major", "minor", "suggestion"]),
1838
- description: z4.string().min(1),
1839
- location: z4.string().optional().describe("File path and line range if applicable"),
1840
- suggestion: z4.string().optional().describe("Recommended fix"),
1841
- confidence: z4.number().min(0).max(1),
1842
- timestamp: z4.iso.datetime().optional()
2307
+ var AgentFindingSchema = z5.object({
2308
+ agentId: z5.string(),
2309
+ category: z5.enum(["bug", "security", "performance", "style", "design", "documentation", "other"]),
2310
+ severity: z5.enum(["critical", "major", "minor", "suggestion"]),
2311
+ description: z5.string().min(1),
2312
+ location: z5.string().optional().describe("File path and line range if applicable"),
2313
+ suggestion: z5.string().optional().describe("Recommended fix"),
2314
+ confidence: z5.number().min(0).max(1),
2315
+ timestamp: z5.iso.datetime().optional()
1843
2316
  });
1844
- var FindingVoteSchema = z4.object({
1845
- agentId: z4.string(),
1846
- findingId: z4.string(),
1847
- agree: z4.boolean(),
1848
- reasoning: z4.string().optional(),
1849
- amendedSeverity: z4.enum(["critical", "major", "minor", "suggestion"]).optional()
2317
+ var FindingVoteSchema = z5.object({
2318
+ agentId: z5.string(),
2319
+ findingId: z5.string(),
2320
+ agree: z5.boolean(),
2321
+ reasoning: z5.string().optional(),
2322
+ amendedSeverity: z5.enum(["critical", "major", "minor", "suggestion"]).optional()
1850
2323
  });
1851
- var VotingProtocolConfigSchema = z4.object({
1852
- committeeSize: z4.number().int().min(2).max(7).default(3),
1853
- maxRounds: z4.number().int().min(1).max(5).default(3),
1854
- roundTimeoutMs: z4.number().int().positive().default(VOTING_ROUND_DEFAULT_TIMEOUT_MS),
1855
- agreementThreshold: z4.number().min(0.5).max(1).default(SUPERMAJORITY_THRESHOLD),
1856
- enableAntiSycophancy: z4.boolean().default(true),
1857
- sycophancyThreshold: z4.number().min(0).max(1).default(0.8)
2324
+ var VotingProtocolConfigSchema = z5.object({
2325
+ committeeSize: z5.number().int().min(2).max(7).default(3),
2326
+ maxRounds: z5.number().int().min(1).max(5).default(3),
2327
+ roundTimeoutMs: z5.number().int().positive().default(VOTING_ROUND_DEFAULT_TIMEOUT_MS),
2328
+ agreementThreshold: z5.number().min(0.5).max(1).default(SUPERMAJORITY_THRESHOLD),
2329
+ enableAntiSycophancy: z5.boolean().default(true),
2330
+ sycophancyThreshold: z5.number().min(0).max(1).default(0.8)
1858
2331
  });
1859
2332
  var DEFAULT_VOTING_PROTOCOL_CONFIG = {
1860
2333
  committeeSize: 3,
@@ -1867,30 +2340,30 @@ var DEFAULT_VOTING_PROTOCOL_CONFIG = {
1867
2340
  };
1868
2341
 
1869
2342
  // src/consensus/types-weighted-voting.ts
1870
- import { z as z5 } from "zod";
1871
- var TaskOutcomeStatusSchema = z5.enum(["success", "failure", "partial", "unknown"]);
1872
- var WeightedAgentRecordSchema = z5.object({
1873
- agentId: z5.string().min(1),
1874
- totalTasks: z5.number().int().nonnegative(),
1875
- successfulTasks: z5.number().int().nonnegative(),
1876
- failedTasks: z5.number().int().nonnegative(),
1877
- partialTasks: z5.number().int().nonnegative(),
1878
- successRate: z5.number().min(0).max(1),
1879
- weight: z5.number().min(0).max(1),
1880
- trustScore: z5.number().min(0).max(1),
1881
- byzantineFlags: z5.number().int().nonnegative(),
1882
- lastActive: z5.date(),
1883
- createdAt: z5.date()
2343
+ import { z as z6 } from "zod";
2344
+ var TaskOutcomeStatusSchema = z6.enum(["success", "failure", "partial", "unknown"]);
2345
+ var WeightedAgentRecordSchema = z6.object({
2346
+ agentId: z6.string().min(1),
2347
+ totalTasks: z6.number().int().nonnegative(),
2348
+ successfulTasks: z6.number().int().nonnegative(),
2349
+ failedTasks: z6.number().int().nonnegative(),
2350
+ partialTasks: z6.number().int().nonnegative(),
2351
+ successRate: z6.number().min(0).max(1),
2352
+ weight: z6.number().min(0).max(1),
2353
+ trustScore: z6.number().min(0).max(1),
2354
+ byzantineFlags: z6.number().int().nonnegative(),
2355
+ lastActive: z6.date(),
2356
+ createdAt: z6.date()
1884
2357
  });
1885
- var WeightedVotingConfigSchema = z5.object({
1886
- minWeight: z5.number().min(0).max(1).default(0.1),
1887
- maxByzantineFraction: z5.number().min(0).max(0.5).default(0.33),
1888
- weightDecayFactor: z5.number().min(0.5).max(1).default(0.9),
1889
- weightRecoveryFactor: z5.number().min(1).max(2).default(1.05),
1890
- minTrustScore: z5.number().min(0).max(1).default(0.3),
1891
- byzantineFlagThreshold: z5.number().int().positive().default(3),
1892
- initialWeight: z5.number().min(0).max(1).default(0.5),
1893
- quorumThreshold: z5.number().min(0.5).max(1).default(SUPERMAJORITY_THRESHOLD)
2358
+ var WeightedVotingConfigSchema = z6.object({
2359
+ minWeight: z6.number().min(0).max(1).default(0.1),
2360
+ maxByzantineFraction: z6.number().min(0).max(0.5).default(0.33),
2361
+ weightDecayFactor: z6.number().min(0.5).max(1).default(0.9),
2362
+ weightRecoveryFactor: z6.number().min(1).max(2).default(1.05),
2363
+ minTrustScore: z6.number().min(0).max(1).default(0.3),
2364
+ byzantineFlagThreshold: z6.number().int().positive().default(3),
2365
+ initialWeight: z6.number().min(0).max(1).default(0.5),
2366
+ quorumThreshold: z6.number().min(0.5).max(1).default(SUPERMAJORITY_THRESHOLD)
1894
2367
  });
1895
2368
  var DEFAULT_WEIGHTED_VOTING_CONFIG = {
1896
2369
  minWeight: 0.1,
@@ -1905,7 +2378,7 @@ var DEFAULT_WEIGHTED_VOTING_CONFIG = {
1905
2378
  };
1906
2379
 
1907
2380
  // src/consensus/higher-order-types.ts
1908
- import { z as z6 } from "zod";
2381
+ import { z as z7 } from "zod";
1909
2382
  function createAgentPairKey(agentA, agentB) {
1910
2383
  return agentA < agentB ? `${agentA}:${agentB}` : `${agentB}:${agentA}`;
1911
2384
  }
@@ -1916,52 +2389,52 @@ function parseAgentPairKey(key) {
1916
2389
  }
1917
2390
  return [parts[0], parts[1]];
1918
2391
  }
1919
- var CorrelationCoefficientSchema = z6.number().min(-1).max(1);
1920
- var IndependentSubsetSchema = z6.object({
1921
- id: z6.string(),
1922
- agentIds: z6.array(z6.string()),
1923
- independenceScore: z6.number().min(0).max(1),
1924
- pairCoverage: z6.object({
1925
- observed: z6.number().int().nonnegative(),
1926
- total: z6.number().int().nonnegative()
2392
+ var CorrelationCoefficientSchema = z7.number().min(-1).max(1);
2393
+ var IndependentSubsetSchema = z7.object({
2394
+ id: z7.string(),
2395
+ agentIds: z7.array(z7.string()),
2396
+ independenceScore: z7.number().min(0).max(1),
2397
+ pairCoverage: z7.object({
2398
+ observed: z7.number().int().nonnegative(),
2399
+ total: z7.number().int().nonnegative()
1927
2400
  }),
1928
- observationCount: z6.number().int().nonnegative()
2401
+ observationCount: z7.number().int().nonnegative()
1929
2402
  });
1930
- var VotingObservationSchema = z6.object({
1931
- proposalId: z6.string(),
1932
- agentId: z6.string(),
1933
- decision: z6.enum(["approve", "reject", "abstain"]),
1934
- confidence: z6.number().min(0).max(1),
1935
- alignedWithOutcome: z6.boolean(),
1936
- timestamp: z6.date(),
1937
- modelKey: z6.string().optional(),
1938
- observedModel: z6.string().optional()
2403
+ var VotingObservationSchema = z7.object({
2404
+ proposalId: z7.string(),
2405
+ agentId: z7.string(),
2406
+ decision: z7.enum(["approve", "reject", "abstain"]),
2407
+ confidence: z7.number().min(0).max(1),
2408
+ alignedWithOutcome: z7.boolean(),
2409
+ timestamp: z7.date(),
2410
+ modelKey: z7.string().optional(),
2411
+ observedModel: z7.string().optional()
1939
2412
  });
1940
- var PairwiseVotingHistorySchema = z6.object({
1941
- pairKey: z6.string(),
1942
- jointObservations: z6.number().int().nonnegative(),
1943
- agreements: z6.number().int().nonnegative(),
1944
- disagreements: z6.number().int().nonnegative(),
2413
+ var PairwiseVotingHistorySchema = z7.object({
2414
+ pairKey: z7.string(),
2415
+ jointObservations: z7.number().int().nonnegative(),
2416
+ agreements: z7.number().int().nonnegative(),
2417
+ disagreements: z7.number().int().nonnegative(),
1945
2418
  correlation: CorrelationCoefficientSchema,
1946
- lastUpdated: z6.date()
2419
+ lastUpdated: z7.date()
1947
2420
  });
1948
- var HigherOrderVotingConfigSchema = z6.object({
1949
- minObservationsForCorrelation: z6.number().int().positive().default(10),
1950
- correlationThreshold: z6.number().min(0).max(1).default(0.3),
2421
+ var HigherOrderVotingConfigSchema = z7.object({
2422
+ minObservationsForCorrelation: z7.number().int().positive().default(10),
2423
+ correlationThreshold: z7.number().min(0).max(1).default(0.3),
1951
2424
  /**
1952
2425
  * @deprecated Ignored since 8.x: correlation evidence is lifetime and partitioned by the role's pinned model (#5555); nothing reads this value. Removed in the next major (#5564).
1953
2426
  */
1954
- correlationMaxAgeMs: z6.number().int().positive().default(864e5),
2427
+ correlationMaxAgeMs: z7.number().int().positive().default(864e5),
1955
2428
  // 24 hours
1956
- independenceThreshold: z6.number().min(0).max(1).default(0.2),
1957
- fallbackToSimpleVoting: z6.boolean().default(true),
2429
+ independenceThreshold: z7.number().min(0).max(1).default(0.2),
2430
+ fallbackToSimpleVoting: z7.boolean().default(true),
1958
2431
  /**
1959
2432
  * @deprecated Ignored since 8.x: correlation evidence is lifetime and partitioned by the role's pinned model (#5555); nothing reads this value. Removed in the next major (#5564).
1960
2433
  */
1961
- observationDecayFactor: z6.number().min(0).max(1).default(0.95),
1962
- maxObservationsPerAgent: z6.number().int().positive().default(1e3),
1963
- maxProposals: z6.number().int().positive().default(5e3),
1964
- maxTrackedPairs: z6.number().int().positive().default(100)
2434
+ observationDecayFactor: z7.number().min(0).max(1).default(0.95),
2435
+ maxObservationsPerAgent: z7.number().int().positive().default(1e3),
2436
+ maxProposals: z7.number().int().positive().default(5e3),
2437
+ maxTrackedPairs: z7.number().int().positive().default(100)
1965
2438
  });
1966
2439
  var DEFAULT_HIGHER_ORDER_CONFIG = {
1967
2440
  minObservationsForCorrelation: 10,
@@ -1975,25 +2448,25 @@ var DEFAULT_HIGHER_ORDER_CONFIG = {
1975
2448
  maxProposals: 5e3,
1976
2449
  maxTrackedPairs: 100
1977
2450
  };
1978
- var HigherOrderVotingResultSchema = z6.object({
1979
- decision: z6.enum(["approve", "reject", "no_consensus"]),
1980
- posteriorApproval: z6.number().min(0).max(1),
1981
- posteriorRejection: z6.number().min(0).max(1),
1982
- effectiveVoteCount: z6.number().nonnegative(),
1983
- usedCorrelationData: z6.boolean(),
1984
- method: z6.enum(["ow", "isp", "simple"]),
1985
- improvementOverBaseline: z6.number(),
1986
- independentSubsets: z6.array(IndependentSubsetSchema).optional(),
1987
- downweightedAgents: z6.array(z6.string()),
1988
- reasoning: z6.string()
2451
+ var HigherOrderVotingResultSchema = z7.object({
2452
+ decision: z7.enum(["approve", "reject", "no_consensus"]),
2453
+ posteriorApproval: z7.number().min(0).max(1),
2454
+ posteriorRejection: z7.number().min(0).max(1),
2455
+ effectiveVoteCount: z7.number().nonnegative(),
2456
+ usedCorrelationData: z7.boolean(),
2457
+ method: z7.enum(["ow", "isp", "simple"]),
2458
+ improvementOverBaseline: z7.number(),
2459
+ independentSubsets: z7.array(IndependentSubsetSchema).optional(),
2460
+ downweightedAgents: z7.array(z7.string()),
2461
+ reasoning: z7.string()
1989
2462
  });
1990
- var CorrelationTrackerStatsSchema = z6.object({
1991
- totalAgents: z6.number().int().nonnegative(),
1992
- trackedPairs: z6.number().int().nonnegative(),
1993
- totalObservations: z6.number().int().nonnegative(),
1994
- averageCorrelation: z6.number(),
1995
- independentSubsetCount: z6.number().int().nonnegative(),
1996
- pairsWithSufficientData: z6.number().int().nonnegative()
2463
+ var CorrelationTrackerStatsSchema = z7.object({
2464
+ totalAgents: z7.number().int().nonnegative(),
2465
+ trackedPairs: z7.number().int().nonnegative(),
2466
+ totalObservations: z7.number().int().nonnegative(),
2467
+ averageCorrelation: z7.number(),
2468
+ independentSubsetCount: z7.number().int().nonnegative(),
2469
+ pairsWithSufficientData: z7.number().int().nonnegative()
1997
2470
  });
1998
2471
 
1999
2472
  // src/consensus/higher-order-helpers.ts
@@ -5005,24 +5478,24 @@ function createCorrelationTracker(config) {
5005
5478
  import * as fs from "node:fs";
5006
5479
 
5007
5480
  // src/consensus/correlation-persistence-records.ts
5008
- import { z as z7 } from "zod";
5009
- var PersistedVoteSchema = z7.object({
5010
- agentId: z7.string(),
5011
- decision: z7.enum(["approve", "reject", "abstain"]),
5012
- confidence: z7.number().min(0).max(1),
5013
- modelKey: z7.string().optional(),
5014
- observedModel: z7.string().optional()
5481
+ import { z as z8 } from "zod";
5482
+ var PersistedVoteSchema = z8.object({
5483
+ agentId: z8.string(),
5484
+ decision: z8.enum(["approve", "reject", "abstain"]),
5485
+ confidence: z8.number().min(0).max(1),
5486
+ modelKey: z8.string().optional(),
5487
+ observedModel: z8.string().optional()
5015
5488
  });
5016
- var PersistedProposalSchema = z7.object({
5017
- proposalId: z7.string(),
5018
- votes: z7.array(PersistedVoteSchema),
5019
- outcome: z7.enum(["approved", "rejected"]),
5020
- timestamp: z7.iso.datetime()
5489
+ var PersistedProposalSchema = z8.object({
5490
+ proposalId: z8.string(),
5491
+ votes: z8.array(PersistedVoteSchema),
5492
+ outcome: z8.enum(["approved", "rejected"]),
5493
+ timestamp: z8.iso.datetime()
5021
5494
  });
5022
- var PersistedCorrelationDataSchema = z7.object({
5023
- version: z7.number().int().positive(),
5024
- proposals: z7.array(PersistedProposalSchema),
5025
- savedAt: z7.iso.datetime()
5495
+ var PersistedCorrelationDataSchema = z8.object({
5496
+ version: z8.number().int().positive(),
5497
+ proposals: z8.array(PersistedProposalSchema),
5498
+ savedAt: z8.iso.datetime()
5026
5499
  });
5027
5500
  function createPersistedProposal(proposalId, votes, outcome, context) {
5028
5501
  const persistedVotes = [];
@@ -5270,449 +5743,40 @@ import { z as z9 } from "zod";
5270
5743
  function answeringSeats(votes) {
5271
5744
  return votes.filter((v) => v.source === "llm");
5272
5745
  }
5273
- function resolvedModel(v) {
5274
- return v.model === void 0 || v.model === "" || v.model === UNRESOLVED_MODEL_ID ? void 0 : v.model;
5275
- }
5276
- function seatFallbacks(votes) {
5277
- const out = [];
5278
- for (const v of answeringSeats(votes)) {
5279
- if (v.fallback === void 0) continue;
5280
- out.push({
5281
- role: v.role,
5282
- fallback: v.fallback,
5283
- toCli: v.cli === void 0 ? "?" : bareCliName(v.cli),
5284
- toModel: resolvedModel(v)
5285
- });
5286
- }
5287
- return out;
5288
- }
5289
- function panelDiversityOf(votes) {
5290
- const models = answeringSeats(votes).map(resolvedModel).filter((m) => m !== void 0);
5291
- return {
5292
- distinctModels: countDistinctModels(models),
5293
- fallbacks: seatFallbacks(votes).length
5294
- };
5295
- }
5296
- var MIN_PANEL_FOR_DIVERSITY_WARNING = 3;
5297
- function singleModelPanelWarning(votes) {
5298
- if (votes.length < MIN_PANEL_FOR_DIVERSITY_WARNING) return void 0;
5299
- const answered = answeringSeats(votes);
5300
- const models = answered.map(resolvedModel);
5301
- const model = models[0];
5302
- if (model === void 0 || models.includes(void 0)) return void 0;
5303
- if (panelDiversityOf(votes).distinctModels !== 1) return void 0;
5304
- const silent = votes.length - answered.length;
5305
- const missing = silent > 0 ? ` (${String(silent)} did not answer)` : "";
5306
- return `All ${String(answered.length)} seats answered on ${model}${missing}; independence is weaker than assigned.`;
5307
- }
5308
-
5309
- // src/audit/vote-record.ts
5310
- import * as crypto from "node:crypto";
5311
- import { z as z8 } from "zod";
5312
- var VoteRecordDecisionSchema = z8.enum(["approved", "rejected", "no_quorum"]);
5313
- var VoteRecordOptionCoverageSchema = z8.object({
5314
- /** Every approving voter, selecting or not — the tally's denominator. */
5315
- approverCount: z8.number().int().nonnegative(),
5316
- /** Approvers whose selection matched a declared option. */
5317
- selectedCount: z8.number().int().nonnegative(),
5318
- /** Approvers with no usable selection; their choice is unmeasured. */
5319
- unattributedApprovals: z8.number().int().nonnegative()
5320
- }).strict();
5321
- var VoteRecordPanelCoverageSchema = z8.object({
5322
- /** Voters the panel asked for. */
5323
- requested: z8.number().int().nonnegative(),
5324
- /** Voters that returned a usable vote — the denominator of `voteCounts`. */
5325
- responded: z8.number().int().nonnegative(),
5326
- /** Voters that errored or timed out. */
5327
- errored: z8.number().int().nonnegative(),
5328
- /** The roles behind `errored`, in panel order. */
5329
- erroredRoles: z8.array(z8.string().min(1).max(100))
5330
- }).strict();
5331
- var MAX_VOTER_REASONING_CHARS = 2e4;
5332
- var SeatFallbackRecordSchema = z8.object({
5333
- /** The CLI the seat was assigned to, bare (`claude`, not `cli-claude`). */
5334
- fromCli: z8.string().min(1).max(100),
5335
- /** The model the assigned adapter had detected, when it had one. */
5336
- fromModel: z8.string().min(1).max(200).optional(),
5337
- /** The adapter-layer class of the error that moved the seat. */
5338
- reason: z8.enum(["rate-limit", "capacity", "auth", "timeout", "sandbox", "unknown"])
5339
- }).strict();
5340
- var VoterSummarySchema = z8.object({
5341
- role: z8.string().min(1).max(100),
5342
- decision: z8.enum(["approve", "reject", "abstain"]),
5343
- confidence: z8.number().min(0).max(1),
5344
- /**
5345
- * The voter's stated grounds (#5373, schema 1.6).
5346
- *
5347
- * `generateVoteHash` already hashed `{role, decision, reasoning}` and then
5348
- * discarded the text, so the chain attested to a value it did not store and
5349
- * nobody could re-verify the hash without re-obtaining the reasoning. On
5350
- * #5228 a contrarian rejection was clipped mid-sentence in the terminal, the
5351
- * grounds were unrecoverable, and the same defect resurfaced a round later —
5352
- * one round of a 7-voter panel is 7-13 minutes of model time, and the
5353
- * objection was right both times.
5354
- *
5355
- * Stored whole rather than sampled. Warn-mode near-misses are emitted per
5356
- * TOOL CALL, thousands a day; votes are emitted per PANEL, dozens a day at
5357
- * most, so ~17 KB per vote is not the same growth problem.
5358
- *
5359
- * Absence is not an empty argument: an errored voter has no entry in
5360
- * `voters` at all (see `panelCoverage`), so `reasoning: ''` means a live
5361
- * voter returned nothing, which is itself a signal.
5362
- */
5363
- reasoning: z8.string().max(MAX_VOTER_REASONING_CHARS).optional(),
5364
- /** True when `reasoning` was clipped to {@link MAX_VOTER_REASONING_CHARS}. */
5365
- reasoningTruncated: z8.literal(true).optional(),
5366
- /**
5367
- * True when this seat was recovered by the per-role retry (#6050, schema 1.7).
5368
- *
5369
- * `voter-retry.ts` has set `retried: true` on recovered results since it was
5370
- * written, and `vote-types.ts` states the reason: the flag is "what makes the
5371
- * recovery visible instead of indistinguishable from a clean first attempt".
5372
- * It had one producer and ZERO consumers — both summarizers and the record
5373
- * dropped it — so the field existed, was documented, and never reached
5374
- * anything that could act on it.
5375
- *
5376
- * A retried seat is weaker evidence than a first-pass one: the model was
5377
- * unavailable or timed out, and the recovery ran under different conditions.
5378
- * For a ratification vote on a governor-path change, "7 of 7 answered" and
5379
- * "6 answered, 1 recovered on retry" are different facts about the scrutiny
5380
- * the change received, and the record stated the first for both.
5381
- *
5382
- * `literal(true)` and optional, on the `reasoningTruncated` rule: a clean
5383
- * seat carries no key, so its entry re-hashes byte-identical and every
5384
- * historical record still verifies.
5385
- */
5386
- retried: z8.literal(true).optional(),
5387
- /**
5388
- * Registry model id the seat ran on (#6091, schema 1.8).
5389
- *
5390
- * The seat→model mapping is round-robin over the available CLIs plus
5391
- * `NEXUS_VOTER_MODEL_<ROLE>` pins, so it is not recoverable from the
5392
- * ledger after the fact: 1844 voter entries across 278 records carried no
5393
- * model. "When an agent's output becomes evidence, its provenance travels
5394
- * with it" — a tally that says `scope_steward: approve` without saying
5395
- * which model answered cannot be audited for the #6068 failure class.
5396
- * Present-only, on the `retried` rule; absent when the result carried no
5397
- * model or only the pending-detection placeholder.
5398
- */
5399
- model: z8.string().min(1).max(200).optional(),
5400
- /**
5401
- * True when the seat could not read the artifact (#6094, schema 1.8).
5402
- *
5403
- * `decision` is `abstain` for such a seat, so without this flag the record
5404
- * could not tell "read it and abstained" from "never saw it" — and one
5405
- * ledger entry (`vote-1789058788915-6lfrbuq`) shows a seat that APPROVED
5406
- * on the proposal text after failing to read. `literal(true)` and
5407
- * present-only, so every pre-1.8 entry re-hashes byte-identical.
5408
- */
5409
- unverifiable: z8.literal(true).optional(),
5410
- /**
5411
- * The CLI the round-robin or `NEXUS_VOTER_MODEL_<ROLE>` pin chose for the
5412
- * seat, as a bare name (#6115, schema 1.9). `model` says where a seat
5413
- * answered; this says where it was MEANT to. Three consecutive 7-seat
5414
- * ratification panels on 2026-09-13 answered every seat on one gemini
5415
- * model while the assignment was three claude, two codex, two gemini,
5416
- * and the 1.8 record showed seven identical `model` values with nothing
5417
- * that said five of them were substitutes. Present-only, on the `model`
5418
- * rule; absent on a result built outside the panel launcher.
5419
- */
5420
- assignedCli: z8.string().min(1).max(100).optional(),
5421
- /**
5422
- * Present only when the seat answered on a different CLI or model than
5423
- * assigned (#6115, schema 1.9): the exact `SeatFallback` shape the live
5424
- * result carries, rebuilt field-by-field. `fromCli` is the assigned CLI,
5425
- * `fromModel` the model it had detected (absent when it never did — the
5426
- * pending-detection placeholder is not a model), `reason` the adapter
5427
- * layer's error class. The vocabulary is compile-checked against the live
5428
- * `FallbackReason` in vote-record.test.ts; a class one side learns alone
5429
- * would either refuse every record that carries it (#6054) or accept one
5430
- * no producer can write.
5431
- */
5432
- fallback: SeatFallbackRecordSchema.optional()
5433
- }).strict();
5434
- function defineVoterKeys(keys) {
5435
- return keys;
5436
- }
5437
- var VOTER_SUMMARY_KEYS = defineVoterKeys([
5438
- "role",
5439
- "decision",
5440
- "confidence",
5441
- "reasoning",
5442
- "reasoningTruncated",
5443
- "retried",
5444
- // 1.8 (#6091, #6094): appended after `retried`, present-only, so a 1.7
5445
- // entry projects byte-identically.
5446
- "model",
5447
- "unverifiable",
5448
- // 1.9 (#6115): appended after `unverifiable`, present-only, so a 1.8 entry
5449
- // projects byte-identically. `fallback` is the first nested voter field;
5450
- // `projectSeatFallback` rebuilds it in its own canonical order.
5451
- "assignedCli",
5452
- "fallback"
5453
- ]);
5454
- function noUnprojectedKeys(rest) {
5455
- return rest;
5456
- }
5457
- function projectSeatFallback(f) {
5458
- const { fromCli, fromModel, reason, ...rest } = f;
5459
- noUnprojectedKeys(rest);
5460
- return {
5461
- fromCli,
5462
- ...fromModel !== void 0 ? { fromModel } : {},
5463
- reason
5464
- };
5465
- }
5466
- function projectVoterSummary(v) {
5467
- const out = {};
5468
- for (const key of VOTER_SUMMARY_KEYS) {
5469
- const value = key === "fallback" ? v.fallback === void 0 ? void 0 : projectSeatFallback(v.fallback) : v[key];
5470
- if (value !== void 0) out[key] = value;
5746
+ function resolvedModel(v) {
5747
+ return v.model === void 0 || v.model === "" || v.model === UNRESOLVED_MODEL_ID ? void 0 : v.model;
5748
+ }
5749
+ function seatFallbacks(votes) {
5750
+ const out = [];
5751
+ for (const v of answeringSeats(votes)) {
5752
+ if (v.fallback === void 0) continue;
5753
+ out.push({
5754
+ role: v.role,
5755
+ fallback: v.fallback,
5756
+ toCli: v.cli === void 0 ? "?" : bareCliName(v.cli),
5757
+ toModel: resolvedModel(v)
5758
+ });
5471
5759
  }
5472
5760
  return out;
5473
5761
  }
5474
- var VoteRecordCountsSchema = z8.object({
5475
- approve: z8.number().int().nonnegative(),
5476
- reject: z8.number().int().nonnegative(),
5477
- abstain: z8.number().int().nonnegative(),
5478
- total: z8.number().int().nonnegative()
5479
- }).strict();
5480
- var VoteRecordOptionCountSchema = z8.object({
5481
- /** The option label as declared in the proposal (e.g. 'A'). */
5482
- option: z8.string().min(1),
5483
- /** How many voters selected it. */
5484
- count: z8.number().int().nonnegative()
5485
- }).strict();
5486
- var GIT_SHA_PATTERN = /^[0-9a-f]{40}$/;
5487
- var VoteRecordPrBindingSchema = z8.object({
5488
- pr: z8.number().int().positive(),
5489
- headSha: z8.string().regex(GIT_SHA_PATTERN, "expected a full 40-hex lowercase commit sha")
5490
- }).strict();
5491
- function projectPrBinding(b) {
5492
- return { pr: b.pr, headSha: b.headSha };
5493
- }
5494
- var VoteRecordErrorPolicySchema = z8.enum([
5495
- "reduce_denominator",
5496
- "count_as_abstain",
5497
- "fail_closed",
5498
- "absolute_quorum"
5499
- ]);
5500
- var VoteRecordSchema = z8.object({
5501
- /**
5502
- * Schema version. '1.1' marked the chain→record-set+sequence model (#3927);
5503
- * '1.2' adds the optional `ratifies` subject-binding field (#3927 item 1);
5504
- * '1.10' adds the optional `ratifiesPr` PR-binding (#5130); '1.11' the
5505
- * optional `errorPolicy` (#6211). Every tier is accepted — a 1.1 record
5506
- * (no `ratifies`) verifies unchanged because each optional is folded into
5507
- * the self-hash ONLY when present (see {@link computeVoteRecordHash}).
5508
- * Tiers are labels, not ordered numbers: '1.10' follows '1.9' by
5509
- * convention only and nothing compares them.
5510
- */
5511
- version: z8.enum([
5512
- "1.1",
5513
- "1.2",
5514
- "1.3",
5515
- "1.4",
5516
- "1.5",
5517
- "1.6",
5518
- "1.7",
5519
- "1.8",
5520
- "1.9",
5521
- "1.10",
5522
- "1.11"
5523
- ]),
5524
- /** Unique record id (also usable as a `ratificationVoteRef`). */
5525
- id: z8.string().min(1),
5526
- /**
5527
- * Monotonic sequence number (integer ≥ 0). Assigned as (max existing
5528
- * sequence)+1 at write time. Sorted, the set of sequences must cover
5529
- * 0..maxSeq with no gap (omission detection); DUPLICATE sequences are a
5530
- * benign concurrent-fork signal, not tampering.
5531
- */
5532
- sequence: z8.number().int().nonnegative(),
5533
- /** ISO-8601 timestamp the vote was recorded. */
5534
- recordedAt: z8.string().min(1),
5535
- /**
5536
- * SHA-256 of the FULL proposal text. The proposal itself is truncated for
5537
- * the human record (`proposal`), but the hash binds the record to the exact
5538
- * proposal voted on — a later edit of `proposal` that changes meaning is
5539
- * detectable by recomputing this hash from the original.
5540
- */
5541
- proposalHash: z8.string().length(64),
5542
- /** Truncated proposal text for the human/reviewer record. */
5543
- proposal: z8.string(),
5544
- /** The voting strategy used. */
5545
- strategy: z8.enum([
5546
- "simple_majority",
5547
- "supermajority",
5548
- "unanimous",
5549
- "higher_order",
5550
- "opinion_wise",
5551
- "proof_of_learning"
5552
- ]),
5553
- /** The resolved decision. */
5554
- decision: VoteRecordDecisionSchema,
5555
- /** Approval fraction as a percentage (0-100). */
5556
- approvalPercentage: z8.number().min(0).max(100),
5557
- /** Vote-count breakdown. */
5558
- voteCounts: VoteRecordCountsSchema,
5559
- /** Per-voter {role, decision, confidence} summary. */
5560
- voters: z8.array(VoterSummarySchema),
5561
- /** Optional correlation/decision id linking to the cost rollup / trace. */
5562
- correlationId: z8.string().min(1).optional(),
5563
- /**
5564
- * Per-option distribution for a multi-option proposal (#4452, schema 1.3).
5565
- *
5566
- * Present only when the vote declared `options`. Absent on an ordinary
5567
- * yes/no vote, and absent on every 1.1/1.2 record — which is why it is
5568
- * folded into the self-hash ONLY when present (see
5569
- * {@link computeVoteRecordHash}), exactly as `ratifies` was.
5570
- *
5571
- * Without this, a 6-1 or 5-2 option split is recorded as `approve: 7` and
5572
- * reads as unanimous. Threshold semantics invert too: `unanimous` becomes
5573
- * the EASIEST bar to clear, because every engaged voter approves while
5574
- * choosing different things.
5575
- */
5576
- // No `.min(1)` (#6049). The rule this field's own docstring states is
5577
- // "present only when the vote declared `options`" -- keyed on DECLARATION,
5578
- // not on whether anyone picked one. `.min(1)` encoded the older, narrower
5579
- // rule, so once the builder started emitting `[]` for "declared, nothing
5580
- // attributable" the record appended fine and then failed to parse on read:
5581
- // `too_small, minimum 1`. The write path does not validate, so the line
5582
- // landed in the ledger and `parseVoteRecordsText` silently dropped it --
5583
- // the case most worth auditing became the one no reader could see.
5584
- //
5585
- // Relaxing is a pure widening: no previously-valid record changes meaning
5586
- // or hash, and no persisted record can currently hold an empty tally.
5587
- optionTally: z8.array(VoteRecordOptionCountSchema).optional(),
5588
- /**
5589
- * Selection coverage for a multi-option vote (#4472, schema 1.4).
5590
- *
5591
- * `optionTally` alone cannot distinguish dissent from absence: `4 pick X
5592
- * + 3 unreadable` and a genuine 4/3 split both leave a leading share of
5593
- * 57%. Recording how many approvers produced no usable selection is what
5594
- * makes a partial measurement legible AS partial — the condition every
5595
- * voter attached to adopting the "credit no option" rule.
5596
- */
5597
- optionCoverage: VoteRecordOptionCoverageSchema.optional(),
5598
- /**
5599
- * Panel coverage (#5738, schema 1.5). Present only when at least one
5600
- * requested voter failed to return a vote; absent keeps a clean panel on
5601
- * the pre-1.5 projection, so every historical record still verifies.
5602
- */
5603
- panelCoverage: VoteRecordPanelCoverageSchema.optional(),
5604
- /**
5605
- * The loop/strategy subject this vote RATIFIES (#3927 item 1). Present only on
5606
- * a ratification vote; set at vote time and bound into the self-hash (so it is
5607
- * tamper-evident). The authority-tier promotion gate
5608
- * (`scripts/check-authority-tier-drift.ts`) resolves a transition's
5609
- * `ratificationVoteRef` to a record and requires `ratifies === transition.subject`
5610
- * (with `decision === 'approved'` and `strategy === 'higher_order'`). Absent on
5611
- * an ordinary (non-ratification) vote; a 1.1 record never carries it.
5612
- */
5613
- ratifies: z8.string().min(1).optional(),
5614
- /**
5615
- * The PR this vote RATIFIES, bound to the head the panel saw (#5130 step 1,
5616
- * schema 1.10; panel decision Q1 option A, 5 of 6). Present only on a
5617
- * governor-path ratification vote; hash-covered like `ratifies`, so a
5618
- * record cannot be repointed at a different PR or a later push without
5619
- * breaking its hash. The governor gate (step 2, #5779/#5131) resolves the
5620
- * record from the committed ledger and requires `pr` to be the PR under
5621
- * review and `headSha` to be its head (or `head^` when `head` touches only
5622
- * the ledger — the caller-commits tip). Absent on every pre-1.10 record.
5623
- *
5624
- * A SEPARATE key rather than a structured encoding of `ratifies`: that
5625
- * field is the authority-ladder SUBJECT, compared by string equality to
5626
- * `transition.subject` and grouped by `conflictingRatifiedSubjects`; no
5627
- * resolver parses it. Overloading it would give one field two consumers
5628
- * with two equality rules. The two may coexist on one record.
5629
- */
5630
- ratifiesPr: VoteRecordPrBindingSchema.optional(),
5631
- /**
5632
- * The error policy the panel actually ran under (#6211, schema 1.11): the
5633
- * EFFECTIVE policy after `consensus_vote` applied the per-strategy default
5634
- * (`input.errorPolicy ?? getDefaultErrorPolicy(strategy)`), not the raw
5635
- * input the caller may have omitted. Hash-covered, present-only.
5636
- *
5637
- * Before this tier the policy reached the RESPONSE only, so the governor
5638
- * ledger gate (`scripts/governor-ledger-evidence.ts`) could not say which
5639
- * policy a whole-panel ratification was configured with: under
5640
- * `absolute_quorum` an errored seat voids the vote, so the one
5641
- * ledger-observable trace of a different policy was an approved record
5642
- * with `panelCoverage.errored > 0` — and a whole panel under
5643
- * `reduce_denominator` looked exactly like one under `absolute_quorum`.
5644
- * The gate's `wrong-error-policy` verdict reads this field; a record
5645
- * without it (every pre-1.11 record) keeps the panel-coverage inference
5646
- * and is reported as `errorPolicy: unrecorded`.
5647
- */
5648
- errorPolicy: VoteRecordErrorPolicySchema.optional(),
5649
- /**
5650
- * ADVISORY hash of the tip record at write time (absent for the first).
5651
- * Retained for audit texture but NOT covered by `hash` and NOT verified —
5652
- * the record-set model is position-independent (#3927).
5653
- */
5654
- previousHash: z8.string().length(64).optional(),
5655
- /** SHA-256 over every field above EXCEPT `previousHash` (and except `hash`). */
5656
- hash: z8.string().length(64)
5657
- }).strict();
5658
- function foldOptionalFields(base, payload) {
5659
- const withTally = payload.optionTally !== void 0 ? {
5660
- ...base,
5661
- optionTally: payload.optionTally.map((o) => ({ option: o.option, count: o.count }))
5662
- } : base;
5663
- const withCoverage = payload.optionCoverage !== void 0 ? {
5664
- ...withTally,
5665
- optionCoverage: {
5666
- approverCount: payload.optionCoverage.approverCount,
5667
- selectedCount: payload.optionCoverage.selectedCount,
5668
- unattributedApprovals: payload.optionCoverage.unattributedApprovals
5669
- }
5670
- } : withTally;
5671
- const withPanel = payload.panelCoverage !== void 0 ? {
5672
- ...withCoverage,
5673
- panelCoverage: {
5674
- requested: payload.panelCoverage.requested,
5675
- responded: payload.panelCoverage.responded,
5676
- errored: payload.panelCoverage.errored,
5677
- erroredRoles: [...payload.panelCoverage.erroredRoles]
5678
- }
5679
- } : withCoverage;
5680
- const withRatifies = payload.ratifies !== void 0 ? { ...withPanel, ratifies: payload.ratifies } : withPanel;
5681
- const withPrBinding = payload.ratifiesPr !== void 0 ? { ...withRatifies, ratifiesPr: projectPrBinding(payload.ratifiesPr) } : withRatifies;
5682
- return payload.errorPolicy !== void 0 ? { ...withPrBinding, errorPolicy: payload.errorPolicy } : withPrBinding;
5683
- }
5684
- function computeVoteRecordHash(payload) {
5685
- const base = {
5686
- version: payload.version,
5687
- id: payload.id,
5688
- sequence: payload.sequence,
5689
- recordedAt: payload.recordedAt,
5690
- proposalHash: payload.proposalHash,
5691
- proposal: payload.proposal,
5692
- strategy: payload.strategy,
5693
- decision: payload.decision,
5694
- approvalPercentage: payload.approvalPercentage,
5695
- // Rebuild voteCounts in schema order (approve, reject, abstain, total) so the
5696
- // hash does not depend on how the nested object's keys were ordered (#3962).
5697
- voteCounts: {
5698
- approve: payload.voteCounts.approve,
5699
- reject: payload.voteCounts.reject,
5700
- abstain: payload.voteCounts.abstain,
5701
- total: payload.voteCounts.total
5702
- },
5703
- // `reasoning` / `reasoningTruncated` (#5373, schema 1.6) are appended per
5704
- // entry ONLY when present, on the same rule as the record-level optional
5705
- // fields: a pre-1.6 voter entry re-hashes byte-identical, so every
5706
- // historical record still verifies, while editing or removing a stored
5707
- // reasoning flips the hash.
5708
- voters: payload.voters.map(projectVoterSummary),
5709
- correlationId: payload.correlationId ?? null
5762
+ function panelDiversityOf(votes) {
5763
+ const models = answeringSeats(votes).map(resolvedModel).filter((m) => m !== void 0);
5764
+ return {
5765
+ distinctModels: countDistinctModels(models),
5766
+ fallbacks: seatFallbacks(votes).length
5710
5767
  };
5711
- const canonical = JSON.stringify(foldOptionalFields(base, payload));
5712
- return crypto.createHash("sha256").update(canonical).digest("hex");
5713
5768
  }
5714
- function hashProposal(proposal) {
5715
- return crypto.createHash("sha256").update(proposal).digest("hex");
5769
+ var MIN_PANEL_FOR_DIVERSITY_WARNING = 3;
5770
+ function singleModelPanelWarning(votes) {
5771
+ if (votes.length < MIN_PANEL_FOR_DIVERSITY_WARNING) return void 0;
5772
+ const answered = answeringSeats(votes);
5773
+ const models = answered.map(resolvedModel);
5774
+ const model = models[0];
5775
+ if (model === void 0 || models.includes(void 0)) return void 0;
5776
+ if (panelDiversityOf(votes).distinctModels !== 1) return void 0;
5777
+ const silent = votes.length - answered.length;
5778
+ const missing = silent > 0 ? ` (${String(silent)} did not answer)` : "";
5779
+ return `All ${String(answered.length)} seats answered on ${model}${missing}; independence is weaker than assigned.`;
5716
5780
  }
5717
5781
 
5718
5782
  // src/mcp/tools/consensus-vote-option-detection.ts
@@ -5876,7 +5940,9 @@ function toAgentVoteSummary(result) {
5876
5940
  // #6094: same rule for a seat that could not read the artifact.
5877
5941
  ...result.source === "unverifiable" ? { unverifiable: true } : {},
5878
5942
  // #6115: and for a seat that answered elsewhere.
5879
- ...result.fallback !== void 0 ? { fallback: result.fallback } : {}
5943
+ ...result.fallback !== void 0 ? { fallback: result.fallback } : {},
5944
+ // #6246: and for what a recovered seat was retried from.
5945
+ ...result.retriedFrom !== void 0 ? { retriedFrom: result.retriedFrom } : {}
5880
5946
  };
5881
5947
  }
5882
5948
  function panelDegradationWarning(errorCount, total) {
@@ -6090,7 +6156,7 @@ function logPartialProposal(bounded, log) {
6090
6156
  }
6091
6157
  async function runContrarianCheck(proposal, log) {
6092
6158
  try {
6093
- const { executeExpert } = await import("./expert-bridge-CH5WJXKF.js");
6159
+ const { executeExpert } = await import("./expert-bridge-QX2UR2BM.js");
6094
6160
  const bounded = boundArtifactForReview(proposal, CONTRARIAN_PROPOSAL_BUDGET, "proposal");
6095
6161
  logPartialProposal(bounded, log);
6096
6162
  const result = await executeExpert("architecture", buildContrarianPrompt(bounded));
@@ -6236,17 +6302,18 @@ function toVoterSummaries(votes) {
6236
6302
  // the fallback the live result stated. `model` alone showed seven
6237
6303
  // identical values on a panel assigned three ways and could not say so.
6238
6304
  ...v.assignedCli !== void 0 ? { assignedCli: v.assignedCli } : {},
6239
- ...v.fallback !== void 0 ? { fallback: projectSeatFallback(v.fallback) } : {}
6305
+ ...v.fallback !== void 0 ? { fallback: projectSeatFallback(v.fallback) } : {},
6306
+ // #6246: what the per-role retry replaced, so the record names both the
6307
+ // seat's final state and the state it recovered from. The live retry
6308
+ // already clipped and de-controlled the cause; this is projection only.
6309
+ ...v.retriedFrom !== void 0 ? { retriedFrom: projectRetriedFrom(v.retriedFrom) } : {}
6240
6310
  });
6241
6311
  }
6242
6312
  return summaries;
6243
6313
  }
6244
6314
  function reasoningFields(reasoning) {
6245
- if (reasoning.length <= MAX_VOTER_REASONING_CHARS) return { reasoning };
6246
- return {
6247
- reasoning: reasoning.slice(0, MAX_VOTER_REASONING_CHARS),
6248
- reasoningTruncated: true
6249
- };
6315
+ const { text, truncated } = clipForRecord(reasoning);
6316
+ return { reasoning: text, ...truncated === true ? { reasoningTruncated: true } : {} };
6250
6317
  }
6251
6318
  function recordDecisionFor(input) {
6252
6319
  return input.resolvedDecision ?? outcomeToDecision(input.result, input.votes, input.errorVoided ?? false);
@@ -6260,15 +6327,22 @@ function deriveOptionFields(votes, declaredOptions) {
6260
6327
  return { optionTally: tallied ?? [], optionCoverage: coverageOf(votes) };
6261
6328
  }
6262
6329
  function recordVersion(optionTally, optionCoverage, panelCoverage, voters, recordLevel) {
6330
+ const voterTier = voterTierOf(voters);
6331
+ if (voterTier === "1.12") return "1.12";
6263
6332
  if (recordLevel.errorPolicy !== void 0) return "1.11";
6264
6333
  if (recordLevel.ratifiesPr !== void 0) return "1.10";
6334
+ if (voterTier !== void 0) return voterTier;
6335
+ if (panelCoverage !== void 0) return "1.5";
6336
+ if (optionCoverage !== void 0) return "1.4";
6337
+ return optionTally !== void 0 ? "1.3" : "1.2";
6338
+ }
6339
+ function voterTierOf(voters) {
6340
+ if (voters.some((v) => v.retriedFrom !== void 0)) return "1.12";
6265
6341
  if (voters.some((v) => v.assignedCli !== void 0 || v.fallback !== void 0)) return "1.9";
6266
6342
  if (voters.some((v) => v.model !== void 0 || v.unverifiable === true)) return "1.8";
6267
6343
  if (voters.some((v) => v.retried === true)) return "1.7";
6268
6344
  if (voters.some((v) => v.reasoning !== void 0)) return "1.6";
6269
- if (panelCoverage !== void 0) return "1.5";
6270
- if (optionCoverage !== void 0) return "1.4";
6271
- return optionTally !== void 0 ? "1.3" : "1.2";
6345
+ return void 0;
6272
6346
  }
6273
6347
  function panelCoverageOf(votes, always) {
6274
6348
  const erroredRoles = votes.filter((v) => v.source === "error").map((v) => v.role);
@@ -8753,6 +8827,7 @@ export {
8753
8827
  DEFAULT_VOTE_TIMEOUT_MS,
8754
8828
  isRateLimitError,
8755
8829
  PersistentOutcomeStore,
8830
+ VoteRecordPrBindingSchema,
8756
8831
  isAbsentSeat,
8757
8832
  NoAdapterError,
8758
8833
  resolveAdapter,
@@ -8805,7 +8880,6 @@ export {
8805
8880
  createCorrelationTracker,
8806
8881
  seatFallbacks,
8807
8882
  panelDiversityOf,
8808
- VoteRecordPrBindingSchema,
8809
8883
  VotingStrategySchema,
8810
8884
  ErrorPolicySchema,
8811
8885
  VoteThresholdSchema,
@@ -8827,4 +8901,4 @@ export {
8827
8901
  CONSENSUS_VOTE_TOOL_SCHEMA,
8828
8902
  registerConsensusVoteTool
8829
8903
  };
8830
- //# sourceMappingURL=chunk-YF3YIHPZ.js.map
8904
+ //# sourceMappingURL=chunk-3A5BXLPV.js.map