@brainervirus/workit-codex 8.13.0 → 8.13.1

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.
@@ -9789,7 +9789,7 @@ function acquireFileLockSync(targetPath, options) {
9789
9789
  }
9790
9790
  }
9791
9791
  // packages/workit-core/src/host-session.ts
9792
- import { createHash as createHash4 } from "node:crypto";
9792
+ import { createHash as createHash4, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
9793
9793
  import fs7 from "node:fs";
9794
9794
  import path7 from "node:path";
9795
9795
 
@@ -9881,9 +9881,10 @@ function hostSessionFromEnv(env) {
9881
9881
  return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
9882
9882
  }
9883
9883
  var ISSUED_PER_LEAD = 64;
9884
- var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/2";
9884
+ var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
9885
9885
  var hash = (value) => createHash4("sha256").update(value).digest("hex").slice(0, 32);
9886
9886
  var userIssuedRoot = () => path7.join(resolveStateDir(), "issued");
9887
+ var userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens");
9887
9888
  function migrateRepoIssued(cwd) {
9888
9889
  if (cwd === undefined)
9889
9890
  return;
@@ -9906,7 +9907,12 @@ function migrateRepoIssued(cwd) {
9906
9907
  const to = path7.join(userIssuedRoot(), lead);
9907
9908
  fs7.mkdirSync(to, { recursive: true, mode: 448 });
9908
9909
  for (const id of fs7.readdirSync(from)) {
9909
- fs7.copyFileSync(path7.join(from, id), path7.join(to, id));
9910
+ try {
9911
+ fs7.copyFileSync(path7.join(from, id), path7.join(to, id), fs7.constants.COPYFILE_EXCL);
9912
+ } catch (error) {
9913
+ if (error.code !== "EEXIST")
9914
+ throw error;
9915
+ }
9910
9916
  fs7.rmSync(path7.join(from, id), { force: true });
9911
9917
  }
9912
9918
  removeIfEmpty(from);
@@ -9926,21 +9932,71 @@ var removeIfEmpty = (dir) => {
9926
9932
  fs7.rmdirSync(dir);
9927
9933
  } catch {}
9928
9934
  };
9935
+ var tokenHash = (token) => createHash4("sha256").update(token).digest("hex");
9929
9936
  function recordIssuedSession(cwd, lead, issued) {
9937
+ const token = randomBytes2(18).toString("base64url");
9930
9938
  try {
9931
9939
  migrateRepoIssued(cwd);
9940
+ const own = hash(issued);
9941
+ const tokens = path7.join(userTokenRoot(), hash(lead));
9942
+ fs7.mkdirSync(tokens, { recursive: true, mode: 448 });
9943
+ fs7.writeFileSync(path7.join(tokens, own), `${tokenHash(token)}
9944
+ `, { mode: 384, flag: "wx" });
9932
9945
  const dir = path7.join(userIssuedRoot(), hash(lead));
9933
9946
  fs7.mkdirSync(dir, { recursive: true, mode: 448 });
9934
- const own = hash(issued);
9935
- fs7.writeFileSync(path7.join(dir, own), ISSUED_SESSIONS_MARKER);
9947
+ fs7.writeFileSync(path7.join(dir, own), `${ISSUED_SESSIONS_MARKER}
9948
+ `, { mode: 384 });
9949
+ pruneIssued(dir, own, tokens);
9950
+ return token;
9951
+ } catch {
9952
+ return null;
9953
+ }
9954
+ }
9955
+ function pruneIssued(dir, own, tokens) {
9956
+ try {
9936
9957
  const entries = fs7.readdirSync(dir);
9937
9958
  if (entries.length <= ISSUED_PER_LEAD)
9938
9959
  return;
9939
9960
  const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs7.statSync(path7.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
9940
- for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
9961
+ for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
9941
9962
  fs7.rmSync(path7.join(dir, name), { force: true });
9963
+ fs7.rmSync(path7.join(tokens, name), { force: true });
9964
+ }
9942
9965
  } catch {}
9943
9966
  }
9967
+ var issuedRecord = (cwd, claimed) => {
9968
+ const cut = claimed.lastIndexOf(":");
9969
+ if (cut <= 0)
9970
+ return null;
9971
+ migrateRepoIssued(cwd);
9972
+ try {
9973
+ return fs7.readFileSync(path7.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
9974
+ } catch {
9975
+ return null;
9976
+ }
9977
+ };
9978
+ var TOKEN_HASH = /^([0-9a-f]{64})$/mu;
9979
+ var issuedToken = (claimed) => {
9980
+ const cut = claimed.lastIndexOf(":");
9981
+ if (cut <= 0)
9982
+ return { stored: null, leadBound: false };
9983
+ const dir = path7.join(userTokenRoot(), hash(claimed.slice(0, cut)));
9984
+ try {
9985
+ const text = fs7.readFileSync(path7.join(dir, hash(claimed)), "utf8");
9986
+ return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
9987
+ } catch {
9988
+ return { stored: null, leadBound: safeList(dir).length > 0 };
9989
+ }
9990
+ };
9991
+ function issuedBinding(cwd, claimed) {
9992
+ const record = issuedRecord(cwd, claimed);
9993
+ const { stored, leadBound } = issuedToken(claimed);
9994
+ if (stored)
9995
+ return "token";
9996
+ if (record === null)
9997
+ return null;
9998
+ return leadBound ? "token" : "legacy";
9999
+ }
9944
10000
  var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
9945
10001
 
9946
10002
  // packages/workit-core/src/core/package-root.ts
@@ -9971,8 +10027,10 @@ var metadataLockSchema = object({
9971
10027
  var FOREIGN_LOCK_TTL_MS = 10 * 60000;
9972
10028
  var UNREADABLE_LOCK_TTL_MS = 30000;
9973
10029
  var RECLAIM_GUARD_TTL_MS = 30000;
9974
- var defaultLockTimeoutMs = 250;
10030
+ var IN_PROCESS_LOCK_TIMEOUT_MS = 250;
10031
+ var defaultLockTimeoutMs = IN_PROCESS_LOCK_TIMEOUT_MS;
9975
10032
  var defaultLockTimeout = () => defaultLockTimeoutMs;
10033
+ var hostAwareLockTimeout = (cliMs) => defaultLockTimeoutMs > IN_PROCESS_LOCK_TIMEOUT_MS ? cliMs : IN_PROCESS_LOCK_TIMEOUT_MS;
9976
10034
  var parseMetadataLock = (raw) => {
9977
10035
  let value;
9978
10036
  try {
@@ -12245,7 +12303,7 @@ import path15 from "node:path";
12245
12303
 
12246
12304
  // packages/workit-core/src/ledger.ts
12247
12305
  import { spawnSync as spawnSync9 } from "node:child_process";
12248
- import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
12306
+ import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
12249
12307
  import fs12 from "node:fs";
12250
12308
  import path14 from "node:path";
12251
12309
 
@@ -13636,7 +13694,7 @@ function ledgerPath(cwd) {
13636
13694
  const root = storeRoot(cwd);
13637
13695
  return root.ok ? { ok: true, value: path14.join(root.value.root, "ledger", "ledger.jsonl") } : root;
13638
13696
  }
13639
- var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
13697
+ var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
13640
13698
  var NETWORK_FS_TYPES = new Set([
13641
13699
  26985,
13642
13700
  20859,
@@ -13686,14 +13744,14 @@ var ledgerLock = {
13686
13744
  return null;
13687
13745
  throw error;
13688
13746
  }
13689
- const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
13747
+ const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
13690
13748
  fs12.writeFileSync(path14.join(lock, LOCK_OWNER), token);
13691
13749
  return token;
13692
13750
  },
13693
13751
  release(lock, token) {
13694
13752
  if (lockToken(lock) !== token)
13695
13753
  return false;
13696
- const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
13754
+ const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
13697
13755
  try {
13698
13756
  fs12.renameSync(lock, tomb);
13699
13757
  } catch {
@@ -13710,7 +13768,7 @@ var ledgerLock = {
13710
13768
  },
13711
13769
  token: lockToken,
13712
13770
  reap(lock, observed) {
13713
- const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
13771
+ const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
13714
13772
  try {
13715
13773
  fs12.renameSync(lock, tomb);
13716
13774
  } catch {
@@ -13728,7 +13786,7 @@ var ledgerLock = {
13728
13786
  return false;
13729
13787
  }
13730
13788
  };
13731
- function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
13789
+ function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
13732
13790
  const lock = `${file}.lock`;
13733
13791
  const deadline = Date.now() + waitMs;
13734
13792
  let token = null;
@@ -13786,10 +13844,18 @@ var lastLine = (fd, size) => {
13786
13844
  }
13787
13845
  return null;
13788
13846
  };
13847
+ var tighten = (fd) => {
13848
+ try {
13849
+ if ((fs12.fstatSync(fd).mode & 63) !== 0)
13850
+ fs12.fchmodSync(fd, 384);
13851
+ } catch {}
13852
+ };
13789
13853
  function appendLine(file, build, options) {
13790
13854
  const write = () => {
13791
- const fd = fs12.openSync(file, "a+");
13855
+ const fd = fs12.openSync(file, "a+", 384);
13792
13856
  try {
13857
+ if (process.platform !== "win32")
13858
+ tighten(fd);
13793
13859
  const size = fs12.fstatSync(fd).size;
13794
13860
  let fence = "";
13795
13861
  if (size > 0) {
@@ -13884,7 +13950,9 @@ function readLedger(cwd, options = {}) {
13884
13950
  if (!file.ok)
13885
13951
  return file;
13886
13952
  let text = "";
13953
+ let stat = null;
13887
13954
  try {
13955
+ stat = fs12.statSync(file.value);
13888
13956
  text = fs12.readFileSync(file.value, "utf8");
13889
13957
  } catch (error) {
13890
13958
  if (error.code === "ENOENT")
@@ -13904,6 +13972,7 @@ function readLedger(cwd, options = {}) {
13904
13972
  const integrity = { chained: 0, legacy: 0, unverified: [] };
13905
13973
  const earlier = new Set([null]);
13906
13974
  let chainStarted = false;
13975
+ const ids = new Set;
13907
13976
  for (const raw of text.split(`
13908
13977
  `)) {
13909
13978
  const trimmed = raw.trim();
@@ -13937,6 +14006,10 @@ function readLedger(cwd, options = {}) {
13937
14006
  reason = "unsigned";
13938
14007
  else
13939
14008
  rowIntegrity = "legacy";
14009
+ if (options.integrity && !reason && ids.has(parsed.id))
14010
+ reason = "duplicate_id";
14011
+ if (options.integrity)
14012
+ ids.add(parsed.id);
13940
14013
  if (reason)
13941
14014
  rowIntegrity = "unverified";
13942
14015
  const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
@@ -13960,7 +14033,8 @@ function readLedger(cwd, options = {}) {
13960
14033
  seq: rows.length + 1,
13961
14034
  superseded: false,
13962
14035
  supersedeIgnored: false,
13963
- ...rowIntegrity ? { integrity: rowIntegrity } : {}
14036
+ ...rowIntegrity ? { integrity: rowIntegrity } : {},
14037
+ ...reason ? { integrityReason: reason } : {}
13964
14038
  };
13965
14039
  rows.push(row);
13966
14040
  if (!options.integrity)
@@ -13988,18 +14062,48 @@ function readLedger(cwd, options = {}) {
13988
14062
  const byId = new Map;
13989
14063
  for (const row of rows) {
13990
14064
  const target = row.supersedes ? byId.get(row.supersedes) : undefined;
13991
- if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
14065
+ if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
13992
14066
  target.superseded = true;
13993
14067
  else if (row.supersedes)
13994
14068
  row.supersedeIgnored = true;
13995
14069
  if (!byId.has(row.id))
13996
14070
  byId.set(row.id, row);
13997
14071
  }
14072
+ if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
14073
+ tamperCache.set(file.value, {
14074
+ size: stat.size,
14075
+ stamp: fileStamp(stat),
14076
+ breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
14077
+ });
13998
14078
  return {
13999
14079
  ok: true,
14000
14080
  value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
14001
14081
  };
14002
14082
  }
14083
+ var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
14084
+ var tamperCache = new Map;
14085
+ var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
14086
+ function ledgerTampering(cwd) {
14087
+ const file = ledgerPath(cwd);
14088
+ if (!file.ok)
14089
+ return [];
14090
+ try {
14091
+ const stat = fs12.statSync(file.value);
14092
+ const cached = tamperCache.get(file.value);
14093
+ if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
14094
+ return cached.breaks;
14095
+ } catch {
14096
+ return [];
14097
+ }
14098
+ const read = readLedger(cwd, { integrity: true });
14099
+ return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
14100
+ }
14101
+ function tamperedUnblock(cwd) {
14102
+ const file = ledgerPath(cwd);
14103
+ const where = file.ok ? file.value : "the ledger file";
14104
+ return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
14105
+ }
14106
+ var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
14003
14107
  function verdictBasis(verdict, current) {
14004
14108
  if (verdict.head && verdict.head === current.head)
14005
14109
  return "fresh";
@@ -14007,7 +14111,7 @@ function verdictBasis(verdict, current) {
14007
14111
  return "carried";
14008
14112
  return "stale";
14009
14113
  }
14010
- var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded;
14114
+ var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
14011
14115
  function trailerSessions(cwd, base, head) {
14012
14116
  if (!base || !head || !safeRef(base) || !safeRef(head))
14013
14117
  return [];
@@ -14088,6 +14192,56 @@ var authoredBy = (authors, session) => {
14088
14192
  return true;
14089
14193
  return false;
14090
14194
  };
14195
+ var TRUST_REASONS = new Set([
14196
+ "unsigned",
14197
+ "hash_mismatch",
14198
+ "broken_link",
14199
+ "duplicate_id",
14200
+ "unissued_session",
14201
+ "unbound_session"
14202
+ ]);
14203
+ var SHELL_VOUCHED_HOSTS = new Set([
14204
+ "cli",
14205
+ "opencode",
14206
+ "pi",
14207
+ "codex_cli",
14208
+ "codex_desktop"
14209
+ ]);
14210
+ function sessionTrust(cwd, row) {
14211
+ const session = row.actor.session;
14212
+ if (!session || row.self === true)
14213
+ return null;
14214
+ const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
14215
+ const host = row.actor.host;
14216
+ const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
14217
+ if (!issuedOnly)
14218
+ return null;
14219
+ const binding = issuedBinding(cwd, session);
14220
+ if (binding === null)
14221
+ return "unissued_session";
14222
+ return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
14223
+ }
14224
+ function trustReasons(cwd, row) {
14225
+ if (row.integrity === "unverified")
14226
+ return [row.integrityReason ?? "unsigned"];
14227
+ if (row.integrity !== "verified")
14228
+ return ["unsigned"];
14229
+ const session = sessionTrust(cwd, row);
14230
+ return session ? [session] : [];
14231
+ }
14232
+ function withIntegrity(cwd, rows) {
14233
+ if (rows.every((row) => row.integrity !== undefined))
14234
+ return rows;
14235
+ const read = readLedger(cwd, { integrity: true });
14236
+ const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
14237
+ return rows.map((row) => {
14238
+ if (row.integrity !== undefined)
14239
+ return row;
14240
+ const fresh = bySeq.get(row.seq);
14241
+ return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
14242
+ });
14243
+ }
14244
+ var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
14091
14245
  var independenceReasons = (row, authors) => {
14092
14246
  const reasons = [];
14093
14247
  if (!row.actor.session)
@@ -14098,15 +14252,17 @@ var independenceReasons = (row, authors) => {
14098
14252
  reasons.push("self");
14099
14253
  return reasons;
14100
14254
  };
14101
- function effectiveOf(rows, authors, sameCode, isCurrent) {
14255
+ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
14102
14256
  let effective = null;
14103
14257
  for (const row of rows) {
14104
14258
  if (!effective) {
14105
14259
  effective = row;
14106
14260
  continue;
14107
14261
  }
14108
- const rowIndependent = independenceReasons(row, authors).length === 0;
14109
- const effIndependent = independenceReasons(effective, authors).length === 0;
14262
+ if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
14263
+ continue;
14264
+ const rowIndependent = reasonsOf(row).length === 0;
14265
+ const effIndependent = reasonsOf(effective).length === 0;
14110
14266
  if (effIndependent && !rowIndependent && isCurrent(effective))
14111
14267
  continue;
14112
14268
  if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
@@ -14120,7 +14276,7 @@ function restackCarries(rows, branch, from, to) {
14120
14276
  return false;
14121
14277
  const edges = new Map;
14122
14278
  for (const row of rows)
14123
- if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
14279
+ if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
14124
14280
  edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
14125
14281
  const seen = new Set([from]);
14126
14282
  const queue = [from];
@@ -14136,7 +14292,8 @@ function restackCarries(rows, branch, from, to) {
14136
14292
  }
14137
14293
  return false;
14138
14294
  }
14139
- function checkVerdicts(cwd, branch, rows) {
14295
+ function checkVerdicts(cwd, branch, ledgerRows) {
14296
+ const rows = withIntegrity(cwd, ledgerRows);
14140
14297
  const head = branchHead(cwd, branch);
14141
14298
  const fallbackBase = defaultBase(cwd);
14142
14299
  const scope = branchScope(cwd, branch);
@@ -14166,21 +14323,47 @@ function checkVerdicts(cwd, branch, rows) {
14166
14323
  return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
14167
14324
  };
14168
14325
  const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
14326
+ const failureAuthorCache = new Map;
14327
+ const failureAuthors = (row) => {
14328
+ let set = failureAuthorCache.get(row.seq);
14329
+ if (!set) {
14330
+ set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
14331
+ failureAuthorCache.set(row.seq, set);
14332
+ }
14333
+ return set;
14334
+ };
14335
+ const reasonCache = new Map;
14336
+ const reasonsOf = (row) => {
14337
+ let reasons = reasonCache.get(row);
14338
+ if (!reasons) {
14339
+ const failing = FAILING.has(String(row.result));
14340
+ reasons = [
14341
+ ...trustReasons(cwd, row),
14342
+ ...independenceReasons(row, failing ? failureAuthors(row) : authors)
14343
+ ];
14344
+ reasonCache.set(row, reasons);
14345
+ }
14346
+ return reasons;
14347
+ };
14348
+ const retired = new Set;
14349
+ for (const row of rows)
14350
+ if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
14351
+ retired.add(row.supersedes);
14169
14352
  const byKind = new Map;
14170
14353
  for (const row of rows)
14171
- if (isLiveVerdict(row) && scope(row)) {
14354
+ if (isLiveVerdict(row, retired) && scope(row)) {
14172
14355
  const kind = typeof row.kind === "string" ? row.kind : "review";
14173
14356
  byKind.set(kind, [...byKind.get(kind) ?? [], row]);
14174
14357
  }
14175
14358
  const verdicts = [];
14176
14359
  const warnings = [];
14177
14360
  for (const [kind, list] of byKind) {
14178
- const row = effectiveOf(list, authors, sameCode, (candidate) => basisOf(candidate) !== "stale");
14361
+ const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
14179
14362
  if (!row)
14180
14363
  continue;
14181
14364
  const basis = basisOf(row);
14182
14365
  const current = basis !== "stale";
14183
- const independence = independenceReasons(row, authors);
14366
+ const independence = reasonsOf(row);
14184
14367
  const reasons = [
14185
14368
  ...current ? [] : ["stale"],
14186
14369
  ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
@@ -14197,23 +14380,31 @@ function checkVerdicts(cwd, branch, rows) {
14197
14380
  });
14198
14381
  if (reasons.length === 0) {
14199
14382
  for (const later of list)
14200
- if (later.seq > row.seq && independenceReasons(later, authors).length > 0 && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
14383
+ if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
14201
14384
  warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
14202
14385
  }
14203
14386
  }
14204
14387
  for (const row of rows)
14205
14388
  if (row.integrity === "unverified" && scope(row))
14206
14389
  warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
14390
+ const tampered = ledgerTampering(cwd);
14391
+ if (tampered.length) {
14392
+ warnings.push(tamperedWarning(cwd, tampered));
14393
+ for (const entry of verdicts) {
14394
+ entry.accepted = false;
14395
+ entry.reasons = [...entry.reasons, "ledger_tampered"];
14396
+ }
14397
+ }
14207
14398
  verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
14208
14399
  const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
14209
14400
  const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
14210
14401
  const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
14211
- const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
14402
+ const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
14212
14403
  accepted: false,
14213
14404
  verdict: failing?.verdict ?? null,
14214
14405
  reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
14215
14406
  };
14216
- const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
14407
+ const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
14217
14408
  return {
14218
14409
  branch,
14219
14410
  head,
@@ -20313,10 +20504,10 @@ function stopObligation(input, descriptor) {
20313
20504
  const branch = currentBranch(input.cwd);
20314
20505
  if (!branch || guardedBranch(input.cwd, branch))
20315
20506
  return null;
20316
- const ledger = readLedger(input.cwd);
20507
+ const ledger = readLedger(input.cwd, { integrity: true });
20317
20508
  if (!ledger.ok)
20318
20509
  return null;
20319
- const all = ledger.value.rows;
20510
+ const all = ledger.value.rows.filter((row) => row.integrity !== "unverified");
20320
20511
  const rows = all.filter((row) => row.branch === branch);
20321
20512
  const mine = rows.filter((row) => sessionOf(row) === session);
20322
20513
  const commits = new Set(mine.filter((row) => row.type === "commit.recorded").map((row) => typeof row.sha === "string" ? row.sha : row.head).filter((sha) => typeof sha === "string"));
@@ -20692,9 +20883,9 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
20692
20883
  if (role !== "judge" || !event.agentId)
20693
20884
  return readOnly;
20694
20885
  const own = subagentSession(session, event.agentId);
20695
- if (session)
20696
- recordIssuedSession(input.cwd || process.cwd(), session, own);
20697
- return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> --session ${own} ...\` so the ledger tells it apart from the author.`;
20886
+ const token = session ? recordIssuedSession(input.cwd || process.cwd(), session, own) : null;
20887
+ const flags = token ? `--session ${own} --token ${token}` : `--session ${own}`;
20888
+ return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> ${flags} ...\` so the ledger tells it apart from the author.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
20698
20889
  };
20699
20890
  function handleHook(input, deps) {
20700
20891
  const { descriptor } = deps;
@@ -37,6 +37,6 @@ if (quiet)
37
37
  process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: quiet } })}
38
38
  `);
39
39
  else {
40
- const { runCodexHook } = await import("./workit-hook-sc45d7h1.js");
40
+ const { runCodexHook } = await import("./workit-hook-j1zd509p.js");
41
41
  await runCodexHook([payload]);
42
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-codex",
3
- "version": "8.13.0",
3
+ "version": "8.13.1",
4
4
  "private": false,
5
5
  "description": "Workit Codex CLI/desktop plugin — shared MCP transport, hooks, method skills, and verifier/reviewer/implementer agents",
6
6
  "keywords": [
@@ -38,9 +38,9 @@
38
38
  "scripts": {
39
39
  "build": "bun scripts/build.ts"
40
40
  },
41
- "dependencies": {
42
- "@brainervirus/workit-core": "^8.13.0",
43
- "@brainervirus/workit-mcp": "^8.13.0"
41
+ "devDependencies": {
42
+ "@brainervirus/workit-core": "^8.13.1",
43
+ "@brainervirus/workit-mcp": "^8.13.1"
44
44
  },
45
45
  "engines": {
46
46
  "node": ">=24"
@@ -72,11 +72,12 @@ Which session id a verdict carries, per host:
72
72
 
73
73
  - Claude Code: subagents inherit your `WORKIT_SESSION_ID`, so the
74
74
  SubagentStart hook names each `verifier` and `reviewer` its own session
75
- (`<lead>:<agent id>`), passed exactly as `--session`: the hook records the
76
- ids it issues, and only those are accepted. An `implementer` is issued
75
+ (`<lead>:<agent id>`) and a token only that subagent sees, passed exactly as
76
+ `--session` and `--token`: the hook records the ids it issues, and only
77
+ those, with their token, are accepted. An `implementer` is issued
77
78
  none: it keeps your id in its env for its workit commands and commits.
78
79
  - Codex: the `workit-verifier` and `workit-reviewer` agents get the same
79
- issued `--session` from SubagentStart; any other verifier passes
80
+ issued `--session` and `--token` from SubagentStart; any other verifier passes
80
81
  `--as verifier`.
81
82
  - OpenCode: a child session's shell already has
82
83
  `WORKIT_SESSION_ID=<lead>:<child>`; Pi: a supervised worker's shell has
@@ -8,7 +8,7 @@ description: Independent review of a diff, branch or PR - intent fidelity and st
8
8
  Review the candidate, never the author's summary of it. A session that wrote a
9
9
  commit on the branch cannot record a passing verdict: if that is you, hand the
10
10
  review to a fresh agent with its own host-derived session: Claude Code and
11
- Codex, the `reviewer` or `verifier` agent (`--session` from SubagentStart);
11
+ Codex, the `reviewer` or `verifier` agent (`--session` and `--token` from SubagentStart);
12
12
  OpenCode, a child session; Pi, a supervised worker. Never hand-mint one such
13
13
  as `<lead>-v1`: it is refused as `forged_session`.
14
14
  An author's `--self` verdict reads self-reviewed and never counts as