nexus-agents 2.132.0 → 2.133.0

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.
@@ -787,9 +787,9 @@ function createKeyedSerializer() {
787
787
  }
788
788
  function raceWithDeadline(p, role, deadlineMs) {
789
789
  let timer;
790
- const timeoutP = new Promise((resolve) => {
790
+ const timeoutP = new Promise((resolve2) => {
791
791
  timer = setTimeout(() => {
792
- resolve(createErrorVoteResult(role, DEADLINE_MESSAGE, deadlineMs));
792
+ resolve2(createErrorVoteResult(role, DEADLINE_MESSAGE, deadlineMs));
793
793
  }, deadlineMs);
794
794
  });
795
795
  return Promise.race([p, timeoutP]).finally(() => {
@@ -4298,7 +4298,7 @@ function applyErrorPolicy(votes, policy) {
4298
4298
 
4299
4299
  // src/audit/vote-record-store.ts
4300
4300
  import { appendFileSync as appendFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "fs";
4301
- import { dirname, join } from "path";
4301
+ import { dirname, isAbsolute, join, resolve } from "path";
4302
4302
 
4303
4303
  // src/audit/vote-record.ts
4304
4304
  import * as crypto from "crypto";
@@ -4316,10 +4316,17 @@ var VoteRecordCountsSchema = z7.object({
4316
4316
  total: z7.number().int().nonnegative()
4317
4317
  }).strict();
4318
4318
  var VoteRecordSchema = z7.object({
4319
- /** Schema version for forward migrations. */
4320
- version: z7.literal("1.0"),
4319
+ /** Schema version. '1.1' marks the chain→record-set+sequence model (#3927). */
4320
+ version: z7.literal("1.1"),
4321
4321
  /** Unique record id (also usable as a `ratificationVoteRef`). */
4322
4322
  id: z7.string().min(1),
4323
+ /**
4324
+ * Monotonic sequence number (integer ≥ 0). Assigned as (max existing
4325
+ * sequence)+1 at write time. Sorted, the set of sequences must cover
4326
+ * 0..maxSeq with no gap (omission detection); DUPLICATE sequences are a
4327
+ * benign concurrent-fork signal, not tampering.
4328
+ */
4329
+ sequence: z7.number().int().nonnegative(),
4323
4330
  /** ISO-8601 timestamp the vote was recorded. */
4324
4331
  recordedAt: z7.string().min(1),
4325
4332
  /**
@@ -4350,29 +4357,40 @@ var VoteRecordSchema = z7.object({
4350
4357
  voters: z7.array(VoterSummarySchema),
4351
4358
  /** Optional correlation/decision id linking to the cost rollup / trace. */
4352
4359
  correlationId: z7.string().min(1).optional(),
4353
- /** Hash of the previous record in the chain (absent for the first). */
4360
+ /**
4361
+ * ADVISORY hash of the tip record at write time (absent for the first).
4362
+ * Retained for audit texture but NOT covered by `hash` and NOT verified —
4363
+ * the record-set model is position-independent (#3927).
4364
+ */
4354
4365
  previousHash: z7.string().length(64).optional(),
4355
- /** SHA-256 over every field above + previousHash. */
4366
+ /** SHA-256 over every field above EXCEPT `previousHash` (and except `hash`). */
4356
4367
  hash: z7.string().length(64)
4357
4368
  }).strict();
4358
4369
  function computeVoteRecordHash(payload) {
4359
4370
  const canonical = JSON.stringify({
4360
4371
  version: payload.version,
4361
4372
  id: payload.id,
4373
+ sequence: payload.sequence,
4362
4374
  recordedAt: payload.recordedAt,
4363
4375
  proposalHash: payload.proposalHash,
4364
4376
  proposal: payload.proposal,
4365
4377
  strategy: payload.strategy,
4366
4378
  decision: payload.decision,
4367
4379
  approvalPercentage: payload.approvalPercentage,
4368
- voteCounts: payload.voteCounts,
4380
+ // Rebuild voteCounts in schema order (approve, reject, abstain, total) so the
4381
+ // hash does not depend on how the nested object's keys were ordered (#3962).
4382
+ voteCounts: {
4383
+ approve: payload.voteCounts.approve,
4384
+ reject: payload.voteCounts.reject,
4385
+ abstain: payload.voteCounts.abstain,
4386
+ total: payload.voteCounts.total
4387
+ },
4369
4388
  voters: payload.voters.map((v) => ({
4370
4389
  role: v.role,
4371
4390
  decision: v.decision,
4372
4391
  confidence: v.confidence
4373
4392
  })),
4374
- correlationId: payload.correlationId ?? null,
4375
- previousHash: payload.previousHash ?? null
4393
+ correlationId: payload.correlationId ?? null
4376
4394
  });
4377
4395
  return crypto.createHash("sha256").update(canonical).digest("hex");
4378
4396
  }
@@ -4382,6 +4400,7 @@ function hashProposal(proposal) {
4382
4400
 
4383
4401
  // src/audit/vote-record-store.ts
4384
4402
  var VOTE_RECORDS_REL_PATH = "governance/vote-records.jsonl";
4403
+ var VOTE_RECORDS_PATH_ENV = "NEXUS_VOTE_RECORDS_PATH";
4385
4404
  var MAX_PROPOSAL_RECORD_CHARS = 500;
4386
4405
  function outcomeToDecision(result) {
4387
4406
  if (result.outcome === "approved") return "approved";
@@ -4400,8 +4419,9 @@ function toVoterSummaries(votes) {
4400
4419
  function buildVoteRecord(input) {
4401
4420
  const proposalTruncated = input.proposal.length > MAX_PROPOSAL_RECORD_CHARS ? input.proposal.slice(0, MAX_PROPOSAL_RECORD_CHARS) + "..." : input.proposal;
4402
4421
  const payload = {
4403
- version: "1.0",
4422
+ version: "1.1",
4404
4423
  id: input.id,
4424
+ sequence: input.sequence ?? 0,
4405
4425
  recordedAt: input.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4406
4426
  proposalHash: hashProposal(input.proposal),
4407
4427
  proposal: proposalTruncated,
@@ -4420,21 +4440,27 @@ function buildVoteRecord(input) {
4420
4440
  };
4421
4441
  return { ...payload, hash: computeVoteRecordHash(payload) };
4422
4442
  }
4423
- function readLastHash(filePath, logger10) {
4424
- if (!existsSync2(filePath)) return void 0;
4443
+ function readLedgerTip(filePath, logger10) {
4444
+ if (!existsSync2(filePath)) return { maxSequence: -1, lastHash: void 0 };
4425
4445
  try {
4426
- const content = readFileSync2(filePath, "utf-8");
4427
- const lines = content.split("\n").filter((l) => l.trim() !== "");
4428
- const last = lines[lines.length - 1];
4429
- if (last === void 0) return void 0;
4430
- const parsed = VoteRecordSchema.safeParse(JSON.parse(last));
4431
- return parsed.success ? parsed.data.hash : void 0;
4446
+ const { records } = readVoteRecords(filePath);
4447
+ if (records.length === 0) return { maxSequence: -1, lastHash: void 0 };
4448
+ let maxSequence = -1;
4449
+ for (const record of records) {
4450
+ if (record.sequence > maxSequence) maxSequence = record.sequence;
4451
+ }
4452
+ const last = records[records.length - 1];
4453
+ return { maxSequence, lastHash: last?.hash };
4432
4454
  } catch (error) {
4433
- logger10.warn("Failed to read prior vote record hash", { error: getErrorMessage(error) });
4434
- return void 0;
4455
+ logger10.warn("Failed to read vote-record ledger tip", { error: getErrorMessage(error) });
4456
+ return { maxSequence: -1, lastHash: void 0 };
4435
4457
  }
4436
4458
  }
4437
4459
  function resolveVoteRecordsPath() {
4460
+ const envPath = process.env[VOTE_RECORDS_PATH_ENV];
4461
+ if (envPath !== void 0 && envPath.trim() !== "") {
4462
+ return isAbsolute(envPath) ? envPath : resolve(envPath);
4463
+ }
4438
4464
  const root = findRepoRoot(process.cwd());
4439
4465
  if (root === null) return void 0;
4440
4466
  return join(root, VOTE_RECORDS_REL_PATH);
@@ -4443,13 +4469,20 @@ function persistVoteRecord(opts) {
4443
4469
  const logger10 = opts.logger ?? createLogger({ component: "vote-record-store" });
4444
4470
  const filePath = opts.filePath ?? resolveVoteRecordsPath();
4445
4471
  if (filePath === void 0) {
4446
- logger10.debug("No committable repo root; skipping authentic vote record persist");
4472
+ logger10.warn(
4473
+ `Authentic vote record NOT persisted: no repo root found from process.cwd() and no override set. Per #3927 the authoritative population path is caller-commits \u2014 commit the returned record bytes into ${VOTE_RECORDS_REL_PATH} in the promotion PR. To force a server-side write, set ${VOTE_RECORDS_PATH_ENV} to an absolute file path.`,
4474
+ { id: opts.id }
4475
+ );
4447
4476
  return void 0;
4448
4477
  }
4449
4478
  try {
4450
4479
  mkdirSync2(dirname(filePath), { recursive: true });
4451
- const previousHash = readLastHash(filePath, logger10);
4452
- const record = buildVoteRecord({ ...opts, previousHash });
4480
+ const { maxSequence, lastHash } = readLedgerTip(filePath, logger10);
4481
+ const record = buildVoteRecord({
4482
+ ...opts,
4483
+ sequence: maxSequence + 1,
4484
+ previousHash: lastHash
4485
+ });
4453
4486
  appendFileSync2(filePath, JSON.stringify(record) + "\n", "utf-8");
4454
4487
  logger10.info("Persisted authentic vote record", {
4455
4488
  id: record.id,
@@ -4465,6 +4498,22 @@ function persistVoteRecord(opts) {
4465
4498
  return void 0;
4466
4499
  }
4467
4500
  }
4501
+ function readVoteRecords(filePath) {
4502
+ const records = [];
4503
+ const invalidLines = [];
4504
+ if (!existsSync2(filePath)) return { records, invalidLines };
4505
+ const lines = readFileSync2(filePath, "utf-8").split("\n").filter((l) => l.trim() !== "");
4506
+ for (const [i, line] of lines.entries()) {
4507
+ try {
4508
+ const parsed = VoteRecordSchema.safeParse(JSON.parse(line));
4509
+ if (parsed.success) records.push(parsed.data);
4510
+ else invalidLines.push(i + 1);
4511
+ } catch {
4512
+ invalidLines.push(i + 1);
4513
+ }
4514
+ }
4515
+ return { records, invalidLines };
4516
+ }
4468
4517
 
4469
4518
  // src/orchestration/outcomes/outcome-store-persistence.ts
4470
4519
  import { appendFileSync as appendFileSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
@@ -5892,4 +5941,4 @@ export {
5892
5941
  CONSENSUS_VOTE_OUTPUT_SCHEMA,
5893
5942
  registerConsensusVoteTool
5894
5943
  };
5895
- //# sourceMappingURL=chunk-I2SW34K6.js.map
5944
+ //# sourceMappingURL=chunk-4CVITS5D.js.map