nexus-agents 8.58.2 → 8.58.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/{chunk-CZN5JK7D.js → chunk-7DD6X5D7.js} +2 -2
  2. package/dist/{chunk-IIZKJP44.js → chunk-EJVVXHMZ.js} +7 -7
  3. package/dist/{chunk-CSYESWQ2.js → chunk-MEXE444I.js} +3 -3
  4. package/dist/{chunk-JE2TRDX7.js → chunk-TDBR47OD.js} +2 -2
  5. package/dist/{chunk-YF3YIHPZ.js → chunk-XKP6IEHD.js} +664 -592
  6. package/dist/chunk-XKP6IEHD.js.map +1 -0
  7. package/dist/{chunk-Y6MYVL5H.js → chunk-YG6MQYRD.js} +2 -2
  8. package/dist/cli.d.ts +1 -1
  9. package/dist/cli.js +28 -19
  10. package/dist/cli.js.map +1 -1
  11. package/dist/{consensus-vote-PLLP7IJE.js → consensus-vote-PZEJL6KZ.js} +4 -4
  12. package/dist/{consensus-vote-types-ClG_UKij.d.ts → consensus-vote-types-DTUSdB9g.d.ts} +38 -0
  13. package/dist/{improvement-review-NAITF6LS.js → improvement-review-WMOCCVVF.js} +4 -4
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.js +6 -6
  16. package/dist/{setup-data-dir-AHUNLSRM.js → setup-data-dir-34C3UR7A.js} +3 -3
  17. package/package.json +1 -1
  18. package/dist/chunk-YF3YIHPZ.js.map +0 -1
  19. /package/dist/{chunk-CZN5JK7D.js.map → chunk-7DD6X5D7.js.map} +0 -0
  20. /package/dist/{chunk-IIZKJP44.js.map → chunk-EJVVXHMZ.js.map} +0 -0
  21. /package/dist/{chunk-CSYESWQ2.js.map → chunk-MEXE444I.js.map} +0 -0
  22. /package/dist/{chunk-JE2TRDX7.js.map → chunk-TDBR47OD.js.map} +0 -0
  23. /package/dist/{chunk-Y6MYVL5H.js.map → chunk-YG6MQYRD.js.map} +0 -0
  24. /package/dist/{consensus-vote-PLLP7IJE.js.map → consensus-vote-PZEJL6KZ.js.map} +0 -0
  25. /package/dist/{improvement-review-NAITF6LS.js.map → improvement-review-WMOCCVVF.js.map} +0 -0
  26. /package/dist/{setup-data-dir-AHUNLSRM.js.map → setup-data-dir-34C3UR7A.js.map} +0 -0
@@ -25,10 +25,10 @@ import {
25
25
  toolSuccessStructured,
26
26
  withProgressHeartbeat,
27
27
  wrapToolWithTimeout
28
- } from "./chunk-JE2TRDX7.js";
28
+ } from "./chunk-TDBR47OD.js";
29
29
  import {
30
30
  VERSION
31
- } from "./chunk-CZN5JK7D.js";
31
+ } from "./chunk-7DD6X5D7.js";
32
32
  import {
33
33
  UNRESOLVED_MODEL_ID,
34
34
  assessPanelIndependence,
@@ -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",
@@ -1653,9 +2124,9 @@ async function collectRealVotes(options) {
1653
2124
  // src/cli/voter-project.ts
1654
2125
  import { readFileSync, statSync } from "node:fs";
1655
2126
  import { dirname, isAbsolute, join, resolve } from "node:path";
1656
- import { z as z3 } from "zod";
2127
+ import { z as z4 } from "zod";
1657
2128
  var VOTER_PROJECT_PATTERN = /^[A-Za-z0-9._/@-]{1,200}$/;
1658
- var VoterProjectInputSchema = z3.string().regex(VOTER_PROJECT_PATTERN).optional().describe(
2129
+ var VoterProjectInputSchema = z4.string().regex(VOTER_PROJECT_PATTERN).optional().describe(
1659
2130
  "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
2131
  );
1661
2132
  var MAX_ANCESTOR_DEPTH = 64;
@@ -1821,40 +2292,40 @@ function describeVeto(verdict) {
1821
2292
  }
1822
2293
 
1823
2294
  // src/consensus/types-voting-protocol.ts
1824
- import { z as z4 } from "zod";
2295
+ import { z as z5 } from "zod";
1825
2296
  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([
2297
+ var VotingRoundPhaseSchema = z5.enum(["analysis", "deliberation", "consensus"]);
2298
+ var VotingRoundStatusSchema = z5.enum([
1828
2299
  "pending",
1829
2300
  "in_progress",
1830
2301
  "awaiting_votes",
1831
2302
  "completed",
1832
2303
  "aborted"
1833
2304
  ]);
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()
2305
+ var AgentFindingSchema = z5.object({
2306
+ agentId: z5.string(),
2307
+ category: z5.enum(["bug", "security", "performance", "style", "design", "documentation", "other"]),
2308
+ severity: z5.enum(["critical", "major", "minor", "suggestion"]),
2309
+ description: z5.string().min(1),
2310
+ location: z5.string().optional().describe("File path and line range if applicable"),
2311
+ suggestion: z5.string().optional().describe("Recommended fix"),
2312
+ confidence: z5.number().min(0).max(1),
2313
+ timestamp: z5.iso.datetime().optional()
1843
2314
  });
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()
2315
+ var FindingVoteSchema = z5.object({
2316
+ agentId: z5.string(),
2317
+ findingId: z5.string(),
2318
+ agree: z5.boolean(),
2319
+ reasoning: z5.string().optional(),
2320
+ amendedSeverity: z5.enum(["critical", "major", "minor", "suggestion"]).optional()
1850
2321
  });
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)
2322
+ var VotingProtocolConfigSchema = z5.object({
2323
+ committeeSize: z5.number().int().min(2).max(7).default(3),
2324
+ maxRounds: z5.number().int().min(1).max(5).default(3),
2325
+ roundTimeoutMs: z5.number().int().positive().default(VOTING_ROUND_DEFAULT_TIMEOUT_MS),
2326
+ agreementThreshold: z5.number().min(0.5).max(1).default(SUPERMAJORITY_THRESHOLD),
2327
+ enableAntiSycophancy: z5.boolean().default(true),
2328
+ sycophancyThreshold: z5.number().min(0).max(1).default(0.8)
1858
2329
  });
1859
2330
  var DEFAULT_VOTING_PROTOCOL_CONFIG = {
1860
2331
  committeeSize: 3,
@@ -1867,30 +2338,30 @@ var DEFAULT_VOTING_PROTOCOL_CONFIG = {
1867
2338
  };
1868
2339
 
1869
2340
  // 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()
2341
+ import { z as z6 } from "zod";
2342
+ var TaskOutcomeStatusSchema = z6.enum(["success", "failure", "partial", "unknown"]);
2343
+ var WeightedAgentRecordSchema = z6.object({
2344
+ agentId: z6.string().min(1),
2345
+ totalTasks: z6.number().int().nonnegative(),
2346
+ successfulTasks: z6.number().int().nonnegative(),
2347
+ failedTasks: z6.number().int().nonnegative(),
2348
+ partialTasks: z6.number().int().nonnegative(),
2349
+ successRate: z6.number().min(0).max(1),
2350
+ weight: z6.number().min(0).max(1),
2351
+ trustScore: z6.number().min(0).max(1),
2352
+ byzantineFlags: z6.number().int().nonnegative(),
2353
+ lastActive: z6.date(),
2354
+ createdAt: z6.date()
1884
2355
  });
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)
2356
+ var WeightedVotingConfigSchema = z6.object({
2357
+ minWeight: z6.number().min(0).max(1).default(0.1),
2358
+ maxByzantineFraction: z6.number().min(0).max(0.5).default(0.33),
2359
+ weightDecayFactor: z6.number().min(0.5).max(1).default(0.9),
2360
+ weightRecoveryFactor: z6.number().min(1).max(2).default(1.05),
2361
+ minTrustScore: z6.number().min(0).max(1).default(0.3),
2362
+ byzantineFlagThreshold: z6.number().int().positive().default(3),
2363
+ initialWeight: z6.number().min(0).max(1).default(0.5),
2364
+ quorumThreshold: z6.number().min(0.5).max(1).default(SUPERMAJORITY_THRESHOLD)
1894
2365
  });
1895
2366
  var DEFAULT_WEIGHTED_VOTING_CONFIG = {
1896
2367
  minWeight: 0.1,
@@ -1905,7 +2376,7 @@ var DEFAULT_WEIGHTED_VOTING_CONFIG = {
1905
2376
  };
1906
2377
 
1907
2378
  // src/consensus/higher-order-types.ts
1908
- import { z as z6 } from "zod";
2379
+ import { z as z7 } from "zod";
1909
2380
  function createAgentPairKey(agentA, agentB) {
1910
2381
  return agentA < agentB ? `${agentA}:${agentB}` : `${agentB}:${agentA}`;
1911
2382
  }
@@ -1916,52 +2387,52 @@ function parseAgentPairKey(key) {
1916
2387
  }
1917
2388
  return [parts[0], parts[1]];
1918
2389
  }
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()
2390
+ var CorrelationCoefficientSchema = z7.number().min(-1).max(1);
2391
+ var IndependentSubsetSchema = z7.object({
2392
+ id: z7.string(),
2393
+ agentIds: z7.array(z7.string()),
2394
+ independenceScore: z7.number().min(0).max(1),
2395
+ pairCoverage: z7.object({
2396
+ observed: z7.number().int().nonnegative(),
2397
+ total: z7.number().int().nonnegative()
1927
2398
  }),
1928
- observationCount: z6.number().int().nonnegative()
2399
+ observationCount: z7.number().int().nonnegative()
1929
2400
  });
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()
2401
+ var VotingObservationSchema = z7.object({
2402
+ proposalId: z7.string(),
2403
+ agentId: z7.string(),
2404
+ decision: z7.enum(["approve", "reject", "abstain"]),
2405
+ confidence: z7.number().min(0).max(1),
2406
+ alignedWithOutcome: z7.boolean(),
2407
+ timestamp: z7.date(),
2408
+ modelKey: z7.string().optional(),
2409
+ observedModel: z7.string().optional()
1939
2410
  });
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(),
2411
+ var PairwiseVotingHistorySchema = z7.object({
2412
+ pairKey: z7.string(),
2413
+ jointObservations: z7.number().int().nonnegative(),
2414
+ agreements: z7.number().int().nonnegative(),
2415
+ disagreements: z7.number().int().nonnegative(),
1945
2416
  correlation: CorrelationCoefficientSchema,
1946
- lastUpdated: z6.date()
2417
+ lastUpdated: z7.date()
1947
2418
  });
1948
- var HigherOrderVotingConfigSchema = z6.object({
1949
- minObservationsForCorrelation: z6.number().int().positive().default(10),
1950
- correlationThreshold: z6.number().min(0).max(1).default(0.3),
2419
+ var HigherOrderVotingConfigSchema = z7.object({
2420
+ minObservationsForCorrelation: z7.number().int().positive().default(10),
2421
+ correlationThreshold: z7.number().min(0).max(1).default(0.3),
1951
2422
  /**
1952
2423
  * @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
2424
  */
1954
- correlationMaxAgeMs: z6.number().int().positive().default(864e5),
2425
+ correlationMaxAgeMs: z7.number().int().positive().default(864e5),
1955
2426
  // 24 hours
1956
- independenceThreshold: z6.number().min(0).max(1).default(0.2),
1957
- fallbackToSimpleVoting: z6.boolean().default(true),
2427
+ independenceThreshold: z7.number().min(0).max(1).default(0.2),
2428
+ fallbackToSimpleVoting: z7.boolean().default(true),
1958
2429
  /**
1959
2430
  * @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
2431
  */
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)
2432
+ observationDecayFactor: z7.number().min(0).max(1).default(0.95),
2433
+ maxObservationsPerAgent: z7.number().int().positive().default(1e3),
2434
+ maxProposals: z7.number().int().positive().default(5e3),
2435
+ maxTrackedPairs: z7.number().int().positive().default(100)
1965
2436
  });
1966
2437
  var DEFAULT_HIGHER_ORDER_CONFIG = {
1967
2438
  minObservationsForCorrelation: 10,
@@ -1975,25 +2446,25 @@ var DEFAULT_HIGHER_ORDER_CONFIG = {
1975
2446
  maxProposals: 5e3,
1976
2447
  maxTrackedPairs: 100
1977
2448
  };
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()
2449
+ var HigherOrderVotingResultSchema = z7.object({
2450
+ decision: z7.enum(["approve", "reject", "no_consensus"]),
2451
+ posteriorApproval: z7.number().min(0).max(1),
2452
+ posteriorRejection: z7.number().min(0).max(1),
2453
+ effectiveVoteCount: z7.number().nonnegative(),
2454
+ usedCorrelationData: z7.boolean(),
2455
+ method: z7.enum(["ow", "isp", "simple"]),
2456
+ improvementOverBaseline: z7.number(),
2457
+ independentSubsets: z7.array(IndependentSubsetSchema).optional(),
2458
+ downweightedAgents: z7.array(z7.string()),
2459
+ reasoning: z7.string()
1989
2460
  });
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()
2461
+ var CorrelationTrackerStatsSchema = z7.object({
2462
+ totalAgents: z7.number().int().nonnegative(),
2463
+ trackedPairs: z7.number().int().nonnegative(),
2464
+ totalObservations: z7.number().int().nonnegative(),
2465
+ averageCorrelation: z7.number(),
2466
+ independentSubsetCount: z7.number().int().nonnegative(),
2467
+ pairsWithSufficientData: z7.number().int().nonnegative()
1997
2468
  });
1998
2469
 
1999
2470
  // src/consensus/higher-order-helpers.ts
@@ -5005,24 +5476,24 @@ function createCorrelationTracker(config) {
5005
5476
  import * as fs from "node:fs";
5006
5477
 
5007
5478
  // 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()
5479
+ import { z as z8 } from "zod";
5480
+ var PersistedVoteSchema = z8.object({
5481
+ agentId: z8.string(),
5482
+ decision: z8.enum(["approve", "reject", "abstain"]),
5483
+ confidence: z8.number().min(0).max(1),
5484
+ modelKey: z8.string().optional(),
5485
+ observedModel: z8.string().optional()
5015
5486
  });
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()
5487
+ var PersistedProposalSchema = z8.object({
5488
+ proposalId: z8.string(),
5489
+ votes: z8.array(PersistedVoteSchema),
5490
+ outcome: z8.enum(["approved", "rejected"]),
5491
+ timestamp: z8.iso.datetime()
5021
5492
  });
5022
- var PersistedCorrelationDataSchema = z7.object({
5023
- version: z7.number().int().positive(),
5024
- proposals: z7.array(PersistedProposalSchema),
5025
- savedAt: z7.iso.datetime()
5493
+ var PersistedCorrelationDataSchema = z8.object({
5494
+ version: z8.number().int().positive(),
5495
+ proposals: z8.array(PersistedProposalSchema),
5496
+ savedAt: z8.iso.datetime()
5026
5497
  });
5027
5498
  function createPersistedProposal(proposalId, votes, outcome, context) {
5028
5499
  const persistedVotes = [];
@@ -5270,449 +5741,40 @@ import { z as z9 } from "zod";
5270
5741
  function answeringSeats(votes) {
5271
5742
  return votes.filter((v) => v.source === "llm");
5272
5743
  }
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;
5744
+ function resolvedModel(v) {
5745
+ return v.model === void 0 || v.model === "" || v.model === UNRESOLVED_MODEL_ID ? void 0 : v.model;
5746
+ }
5747
+ function seatFallbacks(votes) {
5748
+ const out = [];
5749
+ for (const v of answeringSeats(votes)) {
5750
+ if (v.fallback === void 0) continue;
5751
+ out.push({
5752
+ role: v.role,
5753
+ fallback: v.fallback,
5754
+ toCli: v.cli === void 0 ? "?" : bareCliName(v.cli),
5755
+ toModel: resolvedModel(v)
5756
+ });
5471
5757
  }
5472
5758
  return out;
5473
5759
  }
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
5760
+ function panelDiversityOf(votes) {
5761
+ const models = answeringSeats(votes).map(resolvedModel).filter((m) => m !== void 0);
5762
+ return {
5763
+ distinctModels: countDistinctModels(models),
5764
+ fallbacks: seatFallbacks(votes).length
5710
5765
  };
5711
- const canonical = JSON.stringify(foldOptionalFields(base, payload));
5712
- return crypto.createHash("sha256").update(canonical).digest("hex");
5713
5766
  }
5714
- function hashProposal(proposal) {
5715
- return crypto.createHash("sha256").update(proposal).digest("hex");
5767
+ var MIN_PANEL_FOR_DIVERSITY_WARNING = 3;
5768
+ function singleModelPanelWarning(votes) {
5769
+ if (votes.length < MIN_PANEL_FOR_DIVERSITY_WARNING) return void 0;
5770
+ const answered = answeringSeats(votes);
5771
+ const models = answered.map(resolvedModel);
5772
+ const model = models[0];
5773
+ if (model === void 0 || models.includes(void 0)) return void 0;
5774
+ if (panelDiversityOf(votes).distinctModels !== 1) return void 0;
5775
+ const silent = votes.length - answered.length;
5776
+ const missing = silent > 0 ? ` (${String(silent)} did not answer)` : "";
5777
+ return `All ${String(answered.length)} seats answered on ${model}${missing}; independence is weaker than assigned.`;
5716
5778
  }
5717
5779
 
5718
5780
  // src/mcp/tools/consensus-vote-option-detection.ts
@@ -5876,7 +5938,9 @@ function toAgentVoteSummary(result) {
5876
5938
  // #6094: same rule for a seat that could not read the artifact.
5877
5939
  ...result.source === "unverifiable" ? { unverifiable: true } : {},
5878
5940
  // #6115: and for a seat that answered elsewhere.
5879
- ...result.fallback !== void 0 ? { fallback: result.fallback } : {}
5941
+ ...result.fallback !== void 0 ? { fallback: result.fallback } : {},
5942
+ // #6246: and for what a recovered seat was retried from.
5943
+ ...result.retriedFrom !== void 0 ? { retriedFrom: result.retriedFrom } : {}
5880
5944
  };
5881
5945
  }
5882
5946
  function panelDegradationWarning(errorCount, total) {
@@ -6236,17 +6300,18 @@ function toVoterSummaries(votes) {
6236
6300
  // the fallback the live result stated. `model` alone showed seven
6237
6301
  // identical values on a panel assigned three ways and could not say so.
6238
6302
  ...v.assignedCli !== void 0 ? { assignedCli: v.assignedCli } : {},
6239
- ...v.fallback !== void 0 ? { fallback: projectSeatFallback(v.fallback) } : {}
6303
+ ...v.fallback !== void 0 ? { fallback: projectSeatFallback(v.fallback) } : {},
6304
+ // #6246: what the per-role retry replaced, so the record names both the
6305
+ // seat's final state and the state it recovered from. The live retry
6306
+ // already clipped and de-controlled the cause; this is projection only.
6307
+ ...v.retriedFrom !== void 0 ? { retriedFrom: projectRetriedFrom(v.retriedFrom) } : {}
6240
6308
  });
6241
6309
  }
6242
6310
  return summaries;
6243
6311
  }
6244
6312
  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
- };
6313
+ const { text, truncated } = clipForRecord(reasoning);
6314
+ return { reasoning: text, ...truncated === true ? { reasoningTruncated: true } : {} };
6250
6315
  }
6251
6316
  function recordDecisionFor(input) {
6252
6317
  return input.resolvedDecision ?? outcomeToDecision(input.result, input.votes, input.errorVoided ?? false);
@@ -6260,15 +6325,22 @@ function deriveOptionFields(votes, declaredOptions) {
6260
6325
  return { optionTally: tallied ?? [], optionCoverage: coverageOf(votes) };
6261
6326
  }
6262
6327
  function recordVersion(optionTally, optionCoverage, panelCoverage, voters, recordLevel) {
6328
+ const voterTier = voterTierOf(voters);
6329
+ if (voterTier === "1.12") return "1.12";
6263
6330
  if (recordLevel.errorPolicy !== void 0) return "1.11";
6264
6331
  if (recordLevel.ratifiesPr !== void 0) return "1.10";
6332
+ if (voterTier !== void 0) return voterTier;
6333
+ if (panelCoverage !== void 0) return "1.5";
6334
+ if (optionCoverage !== void 0) return "1.4";
6335
+ return optionTally !== void 0 ? "1.3" : "1.2";
6336
+ }
6337
+ function voterTierOf(voters) {
6338
+ if (voters.some((v) => v.retriedFrom !== void 0)) return "1.12";
6265
6339
  if (voters.some((v) => v.assignedCli !== void 0 || v.fallback !== void 0)) return "1.9";
6266
6340
  if (voters.some((v) => v.model !== void 0 || v.unverifiable === true)) return "1.8";
6267
6341
  if (voters.some((v) => v.retried === true)) return "1.7";
6268
6342
  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";
6343
+ return void 0;
6272
6344
  }
6273
6345
  function panelCoverageOf(votes, always) {
6274
6346
  const erroredRoles = votes.filter((v) => v.source === "error").map((v) => v.role);
@@ -8753,6 +8825,7 @@ export {
8753
8825
  DEFAULT_VOTE_TIMEOUT_MS,
8754
8826
  isRateLimitError,
8755
8827
  PersistentOutcomeStore,
8828
+ VoteRecordPrBindingSchema,
8756
8829
  isAbsentSeat,
8757
8830
  NoAdapterError,
8758
8831
  resolveAdapter,
@@ -8805,7 +8878,6 @@ export {
8805
8878
  createCorrelationTracker,
8806
8879
  seatFallbacks,
8807
8880
  panelDiversityOf,
8808
- VoteRecordPrBindingSchema,
8809
8881
  VotingStrategySchema,
8810
8882
  ErrorPolicySchema,
8811
8883
  VoteThresholdSchema,
@@ -8827,4 +8899,4 @@ export {
8827
8899
  CONSENSUS_VOTE_TOOL_SCHEMA,
8828
8900
  registerConsensusVoteTool
8829
8901
  };
8830
- //# sourceMappingURL=chunk-YF3YIHPZ.js.map
8902
+ //# sourceMappingURL=chunk-XKP6IEHD.js.map