@allocator-one/infraone 1.17.0 → 1.17.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.
@@ -1376,7 +1376,7 @@ async function loadSpec(config) {
1376
1376
  return { spec: await bundledSpec(), fingerprint: null, source: "bundled" };
1377
1377
  }
1378
1378
  async function bundledSpec() {
1379
- const module = await import("./infraone-A3KDADRM.js");
1379
+ const module = await import("./infraone-SLZXBJCV.js");
1380
1380
  const spec = module.default;
1381
1381
  if (!isSpec(spec)) throw new Error("the bundled spec/infraone.json is not an OpenAPI document");
1382
1382
  return spec;
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  performUpdateCheck,
7
7
  selfHeal,
8
8
  updateCheck
9
- } from "./chunk-A4WZAC7L.js";
9
+ } from "./chunk-67NX7MJ2.js";
10
10
  import {
11
11
  ACTIVITY_MAX_LIMIT,
12
12
  EXIT_INTERRUPTED,
@@ -513,7 +513,7 @@ async function retry(ctx, pr, reason) {
513
513
 
514
514
  // src/commands/code/index.ts
515
515
  import { resolve as resolve2 } from "path";
516
- import { statSync } from "fs";
516
+ import { statSync, realpathSync } from "fs";
517
517
 
518
518
  // src/core/code/http.ts
519
519
  import { setTimeout as delay } from "timers/promises";
@@ -749,7 +749,10 @@ var HttpCodeApi = class {
749
749
  return { data: result.data, meta: result.meta };
750
750
  }
751
751
  async getPolicy() {
752
- return decode(policy, (await this.request("GET", "/policy")).data);
752
+ const raw = (await this.request("GET", "/policy")).data;
753
+ if (raw && typeof raw === "object" && "deny_glob_case" in raw && raw.deny_glob_case !== void 0 && raw.deny_glob_case !== "ascii_insensitive")
754
+ throw new CommandError("The server returned an invalid code upload policy; upgrade the client before code sync.");
755
+ return decode(policy, raw);
753
756
  }
754
757
  async putRepository(repoKey) {
755
758
  return decode(
@@ -791,7 +794,7 @@ var HttpCodeApi = class {
791
794
  );
792
795
  }
793
796
  async putBlob(hash2, bytes) {
794
- HASH.parse(hash2);
797
+ if (!HASH.safeParse(hash2).success) throw new CommandError("Invalid code blob SHA256; no source was uploaded.");
795
798
  await this.request("PUT", `/blobs/${hash2}`, bytes);
796
799
  }
797
800
  async getSync(viewId, syncId) {
@@ -822,7 +825,7 @@ import { promisify } from "util";
822
825
  import { realpath } from "fs/promises";
823
826
  var run2 = promisify(execFile);
824
827
  var MAX_GIT_BYTES = 16 * 1024 * 1024;
825
- async function gitBytes(cwd, args, maxBuffer = MAX_GIT_BYTES) {
828
+ async function gitBytes(cwd, args, maxBuffer = MAX_GIT_BYTES, limitMessage = "Git returned more than 16 MiB of metadata; this exceeds the code-sync client limit.") {
826
829
  try {
827
830
  return (await run2(
828
831
  "git",
@@ -839,7 +842,7 @@ async function gitBytes(cwd, args, maxBuffer = MAX_GIT_BYTES) {
839
842
  const code2 = error.code;
840
843
  if (code2 === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || error instanceof Error && /maxBuffer/i.test(error.message))
841
844
  throw new LocalError(
842
- "Git returned more than 16 MiB of metadata; narrow the worktree before code sync."
845
+ limitMessage
843
846
  );
844
847
  throw new LocalError(
845
848
  "Cannot read this Git working tree. Run code commands inside a committed repository with an origin remote."
@@ -908,10 +911,13 @@ async function identity(cwd) {
908
911
  await gitBytes(root, ["rev-parse", "--verify", "HEAD^{commit}"]),
909
912
  "Cannot resolve the repository HEAD commit."
910
913
  );
911
- const remote = decodeGitLine(
912
- await gitBytes(root, ["remote", "get-url", "origin"]),
913
- "repository_not_connected: code indexing requires an origin on github.com shared through your organization\u2019s Harness GitHub connection."
914
- );
914
+ const missingRemote = "repository_not_connected: code indexing requires an origin on github.com shared through your organization\u2019s Harness GitHub connection.";
915
+ let remote;
916
+ try {
917
+ remote = decodeGitLine(await gitBytes(root, ["remote", "get-url", "origin"]), missingRemote);
918
+ } catch {
919
+ throw new LocalError(missingRemote);
920
+ }
915
921
  if (/[\x00-\x1f\x7f-\x9f]/.test(remote))
916
922
  throw new LocalError(
917
923
  "repository_not_connected: code indexing requires an origin on github.com shared through your organization\u2019s Harness GitHub connection."
@@ -963,6 +969,23 @@ async function changedPaths(root, commit) {
963
969
  (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b))
964
970
  );
965
971
  }
972
+ function decodeGitTree(output) {
973
+ if (output.length && output.at(-1) !== 0)
974
+ throw new LocalError("Code sync received incomplete Git tree metadata.");
975
+ const entries = [];
976
+ for (let start = 0, end = output.indexOf(0); end !== -1; end = output.indexOf(0, start)) {
977
+ const record2 = output.subarray(start, end), tab = record2.indexOf(9);
978
+ if (tab < 0) throw new LocalError("Code sync received invalid Git tree metadata.");
979
+ const header = decodeUtf8(record2.subarray(0, tab), "Code sync received invalid Git tree metadata.");
980
+ const match = /^(100644|100755|120000|160000) (blob|commit) ([0-9a-f]{40}|[0-9a-f]{64})$/.exec(header);
981
+ if (!match || match[1] === "160000" !== (match[2] === "commit"))
982
+ throw new LocalError("Code sync received invalid Git tree metadata.");
983
+ const [path] = decodeGitPaths([Buffer.concat([record2.subarray(tab + 1), Buffer.from([0])])]);
984
+ entries.push({ path, regular: match[1] === "100644" || match[1] === "100755" });
985
+ start = end + 1;
986
+ }
987
+ return entries;
988
+ }
966
989
 
967
990
  // src/core/code/scan.ts
968
991
  import { constants as constants2 } from "fs";
@@ -979,10 +1002,13 @@ import { extname } from "path";
979
1002
  var MAX_PATTERN_BYTES = 256;
980
1003
  var MAX_PATTERN_STARS = 8;
981
1004
  var MAX_PATTERNS = 1e3;
1005
+ function trimIgnoreLine(line) {
1006
+ return line.replace(new RegExp("^\\p{White_Space}+|\\p{White_Space}+$", "gu"), "");
1007
+ }
982
1008
  function compileIgnorePatterns(lines, warnings) {
983
1009
  const patterns = [];
984
1010
  for (const [line, raw] of lines.entries()) {
985
- const trimmed = raw.trim();
1011
+ const trimmed = trimIgnoreLine(raw);
986
1012
  if (!trimmed || trimmed.startsWith("#")) continue;
987
1013
  if (trimmed.startsWith("!")) {
988
1014
  warnings.push(
@@ -1010,17 +1036,120 @@ function compileIgnorePatterns(lines, warnings) {
1010
1036
  const pattern = compilePattern(trimmed);
1011
1037
  if (pattern) patterns.push(pattern);
1012
1038
  }
1013
- return patterns;
1039
+ return Object.freeze(patterns);
1014
1040
  }
1015
1041
  function patternLimitError() {
1016
1042
  return new Error("Code upload policy contains an invalid path pattern.");
1017
1043
  }
1018
- function escapeRegex(pattern) {
1019
- return pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1020
- }
1021
- function globBody(pattern) {
1022
- return escapeRegex(pattern).replaceAll("\\*\\*/", "(?:.*/)?").replaceAll("/\\*\\*", "/.*").replaceAll("\\*", "[^/]*").replaceAll("\\?", "[^/]");
1023
- }
1044
+ function tokens(glob) {
1045
+ const result = [];
1046
+ for (const [i, part] of glob.split("**/").entries()) {
1047
+ if (i) result.push("optDirs");
1048
+ for (const [j, piece] of part.split("/**").entries()) {
1049
+ if (j) result.push(47, "starAny");
1050
+ for (const byte of Buffer.from(piece)) {
1051
+ const token = byte === 42 ? "starSeg" : byte === 63 ? "anySeg" : byte;
1052
+ if (token !== "starSeg" || result.at(-1) !== token) result.push(token);
1053
+ }
1054
+ }
1055
+ }
1056
+ return result;
1057
+ }
1058
+ function mask(indices) {
1059
+ const top = indices.at(-1);
1060
+ const bytes = Buffer.alloc((top >> 3) + 1);
1061
+ for (const bit of indices) bytes[bytes.length - 1 - (bit >> 3)] |= 1 << (bit & 7);
1062
+ return BigInt("0x" + bytes.toString("hex"));
1063
+ }
1064
+ var ByteGlob = class {
1065
+ constructor(specs) {
1066
+ this.specs = specs;
1067
+ const positions = /* @__PURE__ */ new Map();
1068
+ const add = (key, index) => {
1069
+ const values = positions.get(key);
1070
+ if (values) values.push(index);
1071
+ else positions.set(key, [index]);
1072
+ };
1073
+ let base2 = 0;
1074
+ for (const [index, spec] of specs.entries()) {
1075
+ const sequence = [...spec.anchored ? [] : ["optPrefix"], ...tokens(spec.glob), ...spec.descendants ? ["optTail"] : []];
1076
+ add("start", base2);
1077
+ add("finals", base2 + sequence.length);
1078
+ this.finalIndices.set(base2 + sequence.length, index);
1079
+ for (const [offset, token] of sequence.entries()) {
1080
+ const bit = base2 + offset;
1081
+ add(token, bit);
1082
+ if (typeof token === "number") {
1083
+ if (spec.fold && (token >= 65 && token <= 90 || token >= 97 && token <= 122)) add(token ^ 32, bit);
1084
+ } else if (token !== "anySeg") add("eps", bit);
1085
+ }
1086
+ base2 += sequence.length + 1;
1087
+ }
1088
+ const masks = new Map([...positions].map(([key, bits]) => [key, mask(bits)]));
1089
+ for (const [key, value] of masks) if (typeof key === "number") this.literals.set(key, value);
1090
+ const get = (key) => masks.get(key) ?? 0n;
1091
+ this.start = get("start");
1092
+ this.finals = get("finals");
1093
+ this.anySeg = get("anySeg");
1094
+ this.starSeg = get("starSeg");
1095
+ this.starAny = get("starAny");
1096
+ this.eps = get("eps");
1097
+ this.optDirs = get("optDirs");
1098
+ this.optPrefix = get("optPrefix");
1099
+ this.optTail = get("optTail");
1100
+ }
1101
+ specs;
1102
+ start;
1103
+ finals;
1104
+ literals = /* @__PURE__ */ new Map();
1105
+ anySeg;
1106
+ starSeg;
1107
+ starAny;
1108
+ eps;
1109
+ optDirs;
1110
+ optPrefix;
1111
+ optTail;
1112
+ finalIndices = /* @__PURE__ */ new Map();
1113
+ close(states, inner) {
1114
+ for (; ; ) {
1115
+ inner |= states & (this.optDirs | this.optPrefix);
1116
+ const closed = states | (states & this.eps) << 1n | (inner & this.optTail) << 1n;
1117
+ if (closed === states) return [states, inner];
1118
+ states = closed;
1119
+ }
1120
+ }
1121
+ step(byte, states, inner) {
1122
+ const advance = (this.literals.get(byte) ?? 0n) | (byte === 47 ? 0n : this.anySeg);
1123
+ const stay = (byte === 47 ? 0n : this.starSeg) | (byte === 10 ? 0n : this.starAny);
1124
+ let next = (states & advance) << 1n | states & stay;
1125
+ let nextInner = byte === 10 ? inner & this.optPrefix : inner;
1126
+ if (byte === 47) {
1127
+ next |= (inner & (this.optDirs | this.optPrefix)) << 1n;
1128
+ nextInner |= states & this.optTail;
1129
+ }
1130
+ return this.close(next, nextInner);
1131
+ }
1132
+ /** Highest matching pattern preserves matchingPattern's last-match result. */
1133
+ matchedIndex(path) {
1134
+ if (!this.start) return null;
1135
+ let [states, inner] = this.close(this.start, 0n);
1136
+ const bytes = Buffer.from(path);
1137
+ let beforeFinalNewline = 0n;
1138
+ for (let i = 0; i < bytes.length; i++) {
1139
+ if (i === bytes.length - 1 && bytes[i] === 10) beforeFinalNewline = states;
1140
+ [states, inner] = this.step(bytes[i], states, inner);
1141
+ if (!states && !inner && !beforeFinalNewline) return null;
1142
+ }
1143
+ const final = (states | beforeFinalNewline) & this.finals;
1144
+ if (!final) return null;
1145
+ const hex = final.toString(16);
1146
+ const highest = (hex.length - 1) * 4 + Math.floor(Math.log2(parseInt(hex[0], 16)));
1147
+ return this.finalIndices.get(highest) ?? null;
1148
+ }
1149
+ test(path) {
1150
+ return this.matchedIndex(path) !== null;
1151
+ }
1152
+ };
1024
1153
  function compilePattern(raw, {
1025
1154
  caseInsensitive = false,
1026
1155
  mode = "ignore",
@@ -1029,7 +1158,7 @@ function compilePattern(raw, {
1029
1158
  maxStars = MAX_PATTERN_STARS,
1030
1159
  rejectUnsupported = false
1031
1160
  } = {}) {
1032
- let pattern = mode === "served" ? raw : raw.trim();
1161
+ let pattern = mode === "served" ? raw : trimIgnoreLine(raw);
1033
1162
  if (pattern === "") return null;
1034
1163
  if (pattern.startsWith("#")) {
1035
1164
  if (rejectUnsupported) throw patternLimitError();
@@ -1051,28 +1180,30 @@ function compilePattern(raw, {
1051
1180
  );
1052
1181
  }
1053
1182
  if (rejectUnsupported && /[\[\]{}]/.test(pattern)) throw patternLimitError();
1054
- const anchored = pattern.includes("/");
1183
+ const rooted = pattern.startsWith("/");
1055
1184
  if (mode === "ignore") pattern = pattern.replace(/^\/+|\/+$/g, "");
1056
- const prefix = anchored ? "^" : "^(?:.*/)?";
1057
- const suffix = mode === "ignore" ? "(?:/.*)?$" : "$";
1058
- return {
1059
- source: mode === "served" ? raw : raw.trim(),
1185
+ const anchored = rooted || pattern.includes("/");
1186
+ return Object.freeze({
1187
+ source: mode === "served" ? raw : trimIgnoreLine(raw),
1060
1188
  negated: false,
1061
- regex: new RegExp(
1062
- prefix + globBody(pattern) + suffix,
1063
- caseInsensitive ? "i" : ""
1064
- )
1065
- };
1189
+ matcher: new ByteGlob([{ glob: pattern, anchored, descendants: mode === "ignore", fold: caseInsensitive }])
1190
+ });
1066
1191
  }
1067
1192
  function compilePatterns(lines, options = {}) {
1068
1193
  const maxPatterns = options.maxPatterns ?? MAX_PATTERNS;
1069
1194
  if (lines.length > maxPatterns) throw patternLimitError();
1070
- return lines.map((line) => compilePattern(line, options)).filter((pattern) => pattern !== null);
1195
+ return Object.freeze(lines.map((line) => compilePattern(line, options)).filter((pattern) => pattern !== null));
1071
1196
  }
1197
+ var matcherCache = /* @__PURE__ */ new WeakMap();
1072
1198
  function matchingPattern(path, patterns) {
1073
- let decided = null;
1074
- for (const pattern of patterns) if (pattern.regex.test(path)) decided = pattern;
1075
- return decided;
1199
+ if (!patterns.length) return null;
1200
+ let matcher = matcherCache.get(patterns);
1201
+ if (!matcher) {
1202
+ matcher = new ByteGlob(patterns.flatMap((pattern) => [...pattern.matcher.specs]));
1203
+ if (Object.isFrozen(patterns)) matcherCache.set(patterns, matcher);
1204
+ }
1205
+ const index = matcher.matchedIndex(path);
1206
+ return index === null ? null : patterns[index];
1076
1207
  }
1077
1208
 
1078
1209
  // src/core/code/policy.ts
@@ -1176,7 +1307,7 @@ function buildPolicy(served, ignore, includeFloor) {
1176
1307
  const literalMarkers = contentMatchers(served.content_patterns);
1177
1308
  return {
1178
1309
  limit: Math.min(served.max_file_bytes, MAX_FILE_BYTES),
1179
- deny: [
1310
+ deny: Object.freeze([
1180
1311
  ...includeFloor ? compilePatterns(DENY, {
1181
1312
  caseInsensitive: true,
1182
1313
  maxPatterns: Infinity,
@@ -1190,7 +1321,7 @@ function buildPolicy(served, ignore, includeFloor) {
1190
1321
  maxStars: Infinity,
1191
1322
  rejectUnsupported: true
1192
1323
  })
1193
- ],
1324
+ ]),
1194
1325
  ignore: compileIgnorePatterns(ignore.split(/\r?\n/), warnings),
1195
1326
  patterns: SECRETS,
1196
1327
  literalMarkers,
@@ -1221,7 +1352,7 @@ function basePolicyFor(served, ignore) {
1221
1352
  function pathReason(path, p) {
1222
1353
  if (!path || Buffer.byteLength(path) > 1024 || /[\\\x00-\x1f\x7f-\x9f]/.test(path) || Buffer.from(path, "utf8").toString("utf8") !== path || path.split("/").some((segment) => ["", ".", ".."].includes(segment)))
1223
1354
  return "invalid_path";
1224
- if (p.deny.some((pattern) => pattern.regex.test(path))) return "excluded_path";
1355
+ if (matchingPattern(path, p.deny)) return "excluded_path";
1225
1356
  if (matchingPattern(path, p.ignore)) return "ignored";
1226
1357
  return p.extensions.has(extname(path)) ? null : "unsupported_language";
1227
1358
  }
@@ -1265,7 +1396,7 @@ async function committedIgnore(root, commit) {
1265
1396
  "The committed .harnesscodeignore must be a regular file before code sync."
1266
1397
  );
1267
1398
  return decodeUtf8(
1268
- await gitBytes(root, ["cat-file", "blob", match[2]], 65537),
1399
+ await gitBytes(root, ["cat-file", "blob", match[2]], 65537, "The committed .harnesscodeignore exceeds 64 KiB; reduce it before code sync."),
1269
1400
  "The committed .harnesscodeignore must be valid UTF-8 before code sync."
1270
1401
  );
1271
1402
  }
@@ -1369,7 +1500,8 @@ async function readSafe(root, path, limit) {
1369
1500
  } catch (error) {
1370
1501
  const code2 = error.code;
1371
1502
  if (code2 === "ENOENT") return { reason: "deleted" };
1372
- if (code2 === "ELOOP" || code2 === "ENOTDIR") return { reason: "symlink" };
1503
+ if (code2 === "ELOOP") return { reason: "symlink" };
1504
+ if (code2 === "ENOTDIR") return { reason: "not_directory" };
1373
1505
  if (error instanceof LocalError) throw error;
1374
1506
  throw new LocalError(
1375
1507
  "Cannot read an overlay file safely; check permissions and retry code sync."
@@ -1379,6 +1511,8 @@ async function readSafe(root, path, limit) {
1379
1511
  async function scanOverlay(id, served) {
1380
1512
  policyFor(served, "");
1381
1513
  const ignore = await readSafe(id.root, ".harnesscodeignore", 65536);
1514
+ if ("reason" in ignore && ignore.reason === "too_large")
1515
+ throw new LocalError("The local .harnesscodeignore exceeds 64 KiB; reduce it before code sync.");
1382
1516
  if ("reason" in ignore && ignore.reason !== "deleted")
1383
1517
  throw new LocalError(
1384
1518
  "Cannot read .harnesscodeignore safely; fix or remove it before code sync."
@@ -1395,12 +1529,9 @@ async function scanOverlay(id, served) {
1395
1529
  const policy2 = policyFor(served, localIgnore);
1396
1530
  const basePolicy = basePolicyFor(served, baseIgnore);
1397
1531
  const paths = await changedPaths(id.root, id.commit);
1398
- const basePaths = new Set(
1399
- decodeGitPaths([
1400
- await gitBytes(id.root, ["ls-tree", "-r", "--name-only", "-z", id.commit])
1401
- ])
1402
- );
1403
- assertDistinctPathIdentities([...basePaths, ...paths]);
1532
+ const tree = decodeGitTree(await gitBytes(id.root, ["ls-tree", "-r", "-z", id.commit]));
1533
+ const basePaths = new Set(tree.filter((entry) => entry.regular).map((entry) => entry.path));
1534
+ assertDistinctPathIdentities([...tree.map((entry) => entry.path), ...paths]);
1404
1535
  const changes = [];
1405
1536
  const deleted = /* @__PURE__ */ new Set();
1406
1537
  const blobs = /* @__PURE__ */ new Map();
@@ -1417,7 +1548,10 @@ async function scanOverlay(id, served) {
1417
1548
  }
1418
1549
  };
1419
1550
  const candidates = new Set(paths);
1420
- const warnings = /* @__PURE__ */ new Set([...policy2.warnings, ...basePolicy.warnings]);
1551
+ const warnings = /* @__PURE__ */ new Set([
1552
+ ...policy2.warnings.map((warning) => warning.startsWith("This harness-cli") ? warning : `Local .harnesscodeignore: ${warning}`),
1553
+ ...basePolicy.warnings.map((warning) => warning.startsWith("This harness-cli") ? warning : `Committed .harnesscodeignore: ${warning}`)
1554
+ ]);
1421
1555
  for (const path of basePaths) {
1422
1556
  const baseReason = pathReason(path, basePolicy);
1423
1557
  const localReason = pathReason(path, policy2);
@@ -1494,15 +1628,25 @@ import {
1494
1628
  mkdir,
1495
1629
  writeFile,
1496
1630
  rename,
1631
+ link,
1497
1632
  unlink,
1498
1633
  rmdir
1499
1634
  } from "fs/promises";
1500
1635
  import { join as join2, resolve, isAbsolute } from "path";
1501
1636
  import { randomUUID } from "crypto";
1502
- function statePath(server, org, root, repoKey, directory) {
1637
+ function canonicalServer(server) {
1503
1638
  const url = new URL(server);
1504
- const canonical = url.origin + url.pathname.replace(/\/+$/, "");
1505
- const base2 = directory ?? process.env["HARNESS_CODE_STATE_DIR"] ?? join2(resolveCacheHome(), "harness", "code");
1639
+ return url.origin + url.pathname.replace(/\/+$/, "");
1640
+ }
1641
+ function validPending(pending) {
1642
+ if (pending === void 0) return true;
1643
+ if (!pending || typeof pending !== "object" || typeof pending.digest !== "string" || !/^[a-f0-9]{64}$/.test(pending.digest)) return false;
1644
+ const request = pending.request;
1645
+ return !!request && typeof request === "object" && request.mode === "full" && typeof request.base_commit === "string" && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(request.base_commit) && typeof request.policy_digest === "string" && request.policy_digest.length > 0 && request.policy_digest.length <= 256 && typeof request.client_sync_id === "string" && request.client_sync_id.length > 0 && Buffer.byteLength(request.client_sync_id) <= 255 && Array.isArray(request.changes) && request.changes.length <= 2e4 && request.changes.every((change) => change && typeof change === "object" && typeof change.path === "string" && (change.op === "delete" || change.op === "put" && typeof change.sha256 === "string" && /^[a-f0-9]{64}$/.test(change.sha256) && Number.isSafeInteger(change.size) && change.size >= 0 && ["100644", "100755"].includes(change.mode))) && (pending.syncId === void 0 || typeof pending.syncId === "string") && (pending.activationAttemptedAt === void 0 || Number.isSafeInteger(pending.activationAttemptedAt) && pending.activationAttemptedAt >= 0);
1646
+ }
1647
+ function statePath(server, org, root, repoKey, directory) {
1648
+ const canonical = canonicalServer(server);
1649
+ const base2 = directory ?? (process.env["HARNESS_CODE_STATE_DIR"] || void 0) ?? join2(resolveCacheHome(), "harness", "code");
1506
1650
  if (!isAbsolute(base2))
1507
1651
  throw new LocalError("HARNESS_CODE_STATE_DIR must be an absolute path.");
1508
1652
  return join2(
@@ -1516,9 +1660,9 @@ async function readState(path, server, org, root, repoKey) {
1516
1660
  const s = JSON.parse(await readFile(path, "utf8"));
1517
1661
  if (typeof s !== "object" || s === null) throw new Error();
1518
1662
  const state2 = s;
1519
- if (state2.org.toLowerCase() !== org.toLowerCase() || state2.root !== root || state2.repoKey !== repoKey || new URL(state2.server).href !== new URL(server).href || typeof state2.viewKey !== "string" || !/^[\da-f-]{36}$/.test(state2.viewKey))
1663
+ if (state2.org.toLowerCase() !== org.toLowerCase() || state2.root !== root || state2.repoKey !== repoKey || canonicalServer(state2.server) !== canonicalServer(server) || !validPending(state2.pending) || typeof state2.viewKey !== "string" || !/^[\da-f-]{36}$/.test(state2.viewKey))
1520
1664
  throw new Error();
1521
- return { ...state2, org: state2.org.toLowerCase() };
1665
+ return { ...state2, server: canonicalServer(state2.server), org: state2.org.toLowerCase() };
1522
1666
  } catch (error) {
1523
1667
  if (error.code === "ENOENT") return null;
1524
1668
  throw new LocalError(
@@ -1529,26 +1673,31 @@ async function readState(path, server, org, root, repoKey) {
1529
1673
  async function initialState(path, server, org, root, repoKey) {
1530
1674
  const previous = await readState(path, server, org, root, repoKey);
1531
1675
  if (previous) return previous;
1532
- const state2 = { server, org: org.toLowerCase(), root, repoKey, viewKey: randomUUID() };
1676
+ const state2 = { server: canonicalServer(server), org: org.toLowerCase(), root, repoKey, viewKey: randomUUID() };
1533
1677
  await mkdir(join2(path, ".."), { recursive: true, mode: 448 });
1678
+ const tmp = `${path}.${randomUUID()}.tmp`;
1534
1679
  try {
1535
- await writeFile(path, JSON.stringify(state2) + "\n", {
1536
- flag: "wx",
1537
- mode: 384
1538
- });
1539
- return state2;
1540
- } catch (error) {
1541
- if (error.code === "EEXIST") {
1680
+ await writeFile(tmp, JSON.stringify(state2) + "\n", { flag: "wx", mode: 384 });
1681
+ try {
1682
+ await link(tmp, path);
1683
+ } catch (error) {
1684
+ if (error.code !== "EEXIST") throw error;
1542
1685
  const raced = await readState(path, server, org, root, repoKey);
1543
- if (raced) return raced;
1686
+ if (!raced) throw new Error("initial state disappeared");
1687
+ return raced;
1544
1688
  }
1689
+ return state2;
1690
+ } catch {
1545
1691
  throw new LocalError("Cannot save isolated code view state.");
1692
+ } finally {
1693
+ await unlink(tmp).catch(() => {
1694
+ });
1546
1695
  }
1547
1696
  }
1548
1697
  async function saveState(path, state2) {
1549
1698
  const tmp = `${path}.${randomUUID()}.tmp`;
1550
1699
  try {
1551
- await writeFile(tmp, JSON.stringify({ ...state2, org: state2.org.toLowerCase() }) + "\n", {
1700
+ await writeFile(tmp, JSON.stringify({ ...state2, server: canonicalServer(state2.server), org: state2.org.toLowerCase() }) + "\n", {
1552
1701
  flag: "wx",
1553
1702
  mode: 384
1554
1703
  });
@@ -1732,11 +1881,32 @@ async function syncCode(api, options) {
1732
1881
  if (error instanceof HttpError && error.status === 404) {
1733
1882
  if (withinActivationRetention(state2.pending?.activationAttemptedAt))
1734
1883
  return forgetReplaced();
1884
+ if (state2.pending?.activationAttemptedAt === void 0)
1885
+ throw new CommandError("The sync disappeared without a recorded activation attempt. Check code status before retrying code sync.");
1735
1886
  throw new CommandError("The previous activation can no longer be verified after retention. Check code status before syncing again.");
1736
1887
  }
1737
1888
  throw error;
1738
1889
  }
1739
1890
  };
1891
+ const reconcileFailure = async (syncId, original) => {
1892
+ try {
1893
+ const recovered = await receipt(syncId);
1894
+ if (!recovered && original instanceof HttpError && original.status >= 400 && original.status < 500 && original.status !== 429 && state2.pending) {
1895
+ delete state2.pending.activationAttemptedAt;
1896
+ await saveState(path, state2);
1897
+ }
1898
+ return recovered;
1899
+ } catch (secondary) {
1900
+ if (secondary instanceof ReplacedSyncError) throw secondary;
1901
+ const primary = secondary instanceof CommandError ? secondary : original;
1902
+ const cause = primary === secondary ? original : secondary;
1903
+ try {
1904
+ if (primary instanceof Error && primary.cause === void 0 && cause !== primary) primary.cause = cause;
1905
+ } catch {
1906
+ }
1907
+ throw primary;
1908
+ }
1909
+ };
1740
1910
  let current = state2.pending;
1741
1911
  const matchesCurrent = current && current.digest === overlay.digest && current.request?.mode === "full" && current.request.base_commit === id.commit && current.request.policy_digest === policy2.policy_digest && typeof current.request.client_sync_id === "string" && JSON.stringify(current.request.changes) === JSON.stringify(overlay.changes);
1742
1912
  if (current?.syncId && current.activationAttemptedAt !== void 0 && !matchesCurrent) {
@@ -1749,7 +1919,7 @@ async function syncCode(api, options) {
1749
1919
  try {
1750
1920
  recovered = await api.activate(view2.view_id, originalSyncId);
1751
1921
  } catch (error) {
1752
- recovered = await receipt(originalSyncId);
1922
+ recovered = await reconcileFailure(originalSyncId, error);
1753
1923
  if (!recovered) throw error;
1754
1924
  }
1755
1925
  }
@@ -1849,9 +2019,8 @@ async function syncCode(api, options) {
1849
2019
  throw new CommandError("The activation retry exceeded its retention window. Check code status before syncing again.");
1850
2020
  return forgetReplaced();
1851
2021
  }
1852
- const ambiguous = activating && (!(error instanceof HttpError) || error.status >= 500 || error.status === 429);
1853
- if (ambiguous) {
1854
- const recovered = await receipt(opened2.sync_id);
2022
+ if (activating) {
2023
+ const recovered = await reconcileFailure(opened2.sync_id, error);
1855
2024
  if (recovered) return finish(opened2.sync_id, recovered, missing.length);
1856
2025
  }
1857
2026
  throw error;
@@ -1898,7 +2067,7 @@ async function statusCode(api, options) {
1898
2067
  "The server resolved a different repository; its base status cannot be trusted."
1899
2068
  );
1900
2069
  const repositoryId = repository?.repository_id ?? state2?.repositoryId;
1901
- if (!state2?.viewId || repository && repositoryId !== state2.repositoryId) {
2070
+ const withoutView = async () => {
1902
2071
  const base3 = options.skipped && repositoryId ? await api.getBase(repositoryId, id.commit, true) : void 0;
1903
2072
  return {
1904
2073
  state: base3?.state === "failed" ? "failed" : base3?.state === "fetching" ? "fetching" : "not_synced",
@@ -1907,8 +2076,15 @@ async function statusCode(api, options) {
1907
2076
  base_commit: id.commit,
1908
2077
  ...base3 ? { base: base3 } : {}
1909
2078
  };
2079
+ };
2080
+ if (!state2?.viewId || repository && repositoryId !== state2.repositoryId) return withoutView();
2081
+ let view2;
2082
+ try {
2083
+ view2 = await api.getView(state2.viewId);
2084
+ } catch (error) {
2085
+ if (error instanceof HttpError && error.status === 404) return withoutView();
2086
+ throw error;
1910
2087
  }
1911
- const view2 = await api.getView(state2.viewId);
1912
2088
  if (view2.repository_id !== state2.repositoryId || view2.view_id !== state2.viewId || view2.view_key !== state2.viewKey)
1913
2089
  throw new CommandError(
1914
2090
  "The server view does not match this repository/worktree; its status cannot be trusted."
@@ -1917,7 +2093,7 @@ async function statusCode(api, options) {
1917
2093
  const current = view2.active?.base_commit === id.commit && view2.active.overlay_digest === overlay.digest && state2.activated?.baseCommit === view2.active.base_commit && state2.activated.overlayDigest === view2.active.overlay_digest && state2.activated.pendingVersion === view2.active.version && state2.activated.policyDigest === policy2.policy_digest;
1918
2094
  const base2 = options.skipped && state2.repositoryId ? await api.getBase(state2.repositoryId, id.commit, true) : void 0;
1919
2095
  return {
1920
- state: base2?.state === "failed" || view2.pending?.state === "failed" ? "failed" : current ? "current" : view2.pending ? "pending" : "stale",
2096
+ state: base2?.state === "failed" || view2.pending?.state === "failed" ? "failed" : current ? "current" : view2.pending ? "pending" : view2.active ? "stale" : "not_synced",
1921
2097
  org_id: policy2.org_id,
1922
2098
  repo_key: id.repoKey,
1923
2099
  base_commit: id.commit,
@@ -2074,27 +2250,26 @@ async function queryCode(api, operation, params, options) {
2074
2250
 
2075
2251
  // src/commands/code/index.ts
2076
2252
  function enterWorktree(options) {
2077
- const cwd = process.cwd();
2078
- const before = statSync(".");
2079
- const declared = statSync(cwd);
2080
- if (before.dev !== declared.dev || before.ino !== declared.ino)
2081
- throw new LocalError("Cannot enter the code worktree safely.");
2082
- const supplied = options.path ?? cwd;
2083
- if (/[\x00-\x1f\x7f-\x9f]/.test(supplied) || Buffer.from(supplied, "utf8").toString("utf8") !== supplied || options.path !== void 0 && supplied.includes("\uFFFD"))
2084
- throw new LocalError("Cannot enter the code worktree safely.");
2085
- const path = resolve2(supplied);
2253
+ const refused = () => options.path === void 0 ? new LocalError("Cannot enter the code worktree safely; check the current directory.") : new UsageError("Cannot enter the code worktree safely; check --path.");
2086
2254
  try {
2087
- const target = statSync(path);
2088
- if (options.path === void 0 && (before.dev !== target.dev || before.ino !== target.ino))
2089
- throw new Error("current directory identity changed");
2255
+ const cwd = process.cwd();
2256
+ const before = statSync(".");
2257
+ const declared = statSync(cwd);
2258
+ if (before.dev !== declared.dev || before.ino !== declared.ino) throw refused();
2259
+ const supplied = options.path ?? cwd;
2260
+ if (/[\x00-\x1f\x7f-\x9f]/.test(supplied) || Buffer.from(supplied, "utf8").toString("utf8") !== supplied || options.path !== void 0 && supplied.includes("\uFFFD")) throw refused();
2261
+ const path = resolve2(supplied), target = statSync(path);
2262
+ if (options.path === void 0 && (before.dev !== target.dev || before.ino !== target.ino)) throw refused();
2090
2263
  process.chdir(path);
2091
2264
  const entered = statSync(".");
2092
- if (entered.dev !== target.dev || entered.ino !== target.ino)
2093
- throw new Error("worktree identity changed");
2265
+ if (entered.dev !== target.dev || entered.ino !== target.ino) throw refused();
2266
+ const canonical = realpathSync(".");
2267
+ const pinned = statSync(canonical);
2268
+ if (pinned.dev !== entered.dev || pinned.ino !== entered.ino) throw refused();
2269
+ return { ...options, path: canonical };
2094
2270
  } catch {
2095
- throw new LocalError("Cannot enter the code worktree; check --path.");
2271
+ throw refused();
2096
2272
  }
2097
- return { ...options, path };
2098
2273
  }
2099
2274
  async function sync(ctx, options = {}) {
2100
2275
  try {
@@ -2964,8 +3139,8 @@ doc.command("update").argument("[id]").option("--title <title>").option("--conte
2964
3139
  });
2965
3140
  }
2966
3141
  );
2967
- var link = program.command("link").description("URLs attached to an issue (distinct from dep relations)");
2968
- link.command("list").argument("[issue]").option("--json", "emit JSON").action(async (issue, opts) => {
3142
+ var link2 = program.command("link").description("URLs attached to an issue (distinct from dep relations)");
3143
+ link2.command("list").argument("[issue]").option("--json", "emit JSON").action(async (issue, opts) => {
2969
3144
  await withConfig(opts.json === true, false, (ctx) => {
2970
3145
  if (issue === void 0)
2971
3146
  throw new UsageError("usage: harness link list <issue>");
@@ -2973,7 +3148,7 @@ link.command("list").argument("[issue]").option("--json", "emit JSON").action(as
2973
3148
  return linkList(ctx, issue);
2974
3149
  });
2975
3150
  });
2976
- link.command("add").argument("[issue]").argument("[url]").option("--title <title>", "link title").option("--json", "emit JSON").action(
3151
+ link2.command("add").argument("[issue]").argument("[url]").option("--title <title>", "link title").option("--json", "emit JSON").action(
2977
3152
  async (issue, url, opts) => {
2978
3153
  await withConfig(opts.json === true, true, (ctx) => {
2979
3154
  if (issue === void 0 || url === void 0) {
@@ -2986,7 +3161,7 @@ link.command("add").argument("[issue]").argument("[url]").option("--title <title
2986
3161
  });
2987
3162
  }
2988
3163
  );
2989
- link.command("update").argument("[issue]").argument("[id-or-url]").option("--url <url>", "replacement URL").option("--title <title>", "replacement title; pass an empty value to clear").option("--json", "emit JSON").action(
3164
+ link2.command("update").argument("[issue]").argument("[id-or-url]").option("--url <url>", "replacement URL").option("--title <title>", "replacement title; pass an empty value to clear").option("--json", "emit JSON").action(
2990
3165
  async (issue, idOrUrl, opts) => {
2991
3166
  await withConfig(opts.json === true, true, (ctx) => {
2992
3167
  if (issue === void 0 || idOrUrl === void 0) {
@@ -2998,7 +3173,7 @@ link.command("update").argument("[issue]").argument("[id-or-url]").option("--url
2998
3173
  });
2999
3174
  }
3000
3175
  );
3001
- link.command("rm").argument("[issue]").argument("[id-or-url]").option("--json", "emit JSON").action(
3176
+ link2.command("rm").argument("[issue]").argument("[id-or-url]").option("--json", "emit JSON").action(
3002
3177
  async (issue, idOrUrl, opts) => {
3003
3178
  await withConfig(opts.json === true, true, (ctx) => {
3004
3179
  if (issue === void 0 || idOrUrl === void 0) {
@@ -8,7 +8,7 @@ import {
8
8
  refreshSpec,
9
9
  resolveConfig,
10
10
  transferFor
11
- } from "./chunk-W7BVOQQG.js";
11
+ } from "./chunk-XA5UDNJI.js";
12
12
  import "./chunk-WSDFLMO6.js";
13
13
  import {
14
14
  CommandError,