@zhivex-ai/harness 1.4.1 → 1.5.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +1 -1
  3. package/SUPPORT.md +1 -1
  4. package/contracts/stable-api-signatures.json +7 -7
  5. package/dist/acp-cli.js +10 -10
  6. package/dist/approvals/approval-diff.d.ts +2 -1
  7. package/dist/approvals/approval-preview.d.ts +1 -1
  8. package/dist/audit/index.js +6 -6
  9. package/dist/{audit-pack-e4vaq7np.js → audit-pack-kvw7ng34.js} +3 -3
  10. package/dist/{cli-entry-rgs6bxmy.js → cli-entry-75j3r49b.js} +2 -2
  11. package/dist/{cli-entry-7f0cen32.js → cli-entry-928p8h41.js} +135 -58
  12. package/dist/{cli-entry-a890x3kq.js → cli-entry-h2yc9x37.js} +7 -5
  13. package/dist/{cli-entry-3s63zn4w.js → cli-entry-q8bgkn9m.js} +1 -1
  14. package/dist/{cli-1cf4dcnj.js → cli-vyrytn3m.js} +14 -8
  15. package/dist/cli.js +2 -2
  16. package/dist/compat/code-support.js +8 -8
  17. package/dist/engine/acp.js +11 -11
  18. package/dist/engine/client.js +10 -10
  19. package/dist/engine/desktop-state-v1.js +6 -6
  20. package/dist/engine/index.js +15 -15
  21. package/dist/engine/mcp-stdio-v1.js +5 -5
  22. package/dist/engine/service.js +11 -11
  23. package/dist/hostile-repository-demo.js +138 -60
  24. package/dist/{index-dtb2c6ds.js → index-5zbq9ztj.js} +2 -2
  25. package/dist/{index-6599mpmy.js → index-63gbp0cz.js} +3 -3
  26. package/dist/{index-xdbkcccf.js → index-6dxrbnxm.js} +2 -2
  27. package/dist/{index-0zs2dz26.js → index-6fp7kdpx.js} +10 -9
  28. package/dist/{index-mv8sb1ns.js → index-96ezrvg2.js} +55 -13
  29. package/dist/{index-wm9xeq1n.js → index-bt2p8x7r.js} +1 -1
  30. package/dist/{index-sk3cap35.js → index-eps3c3g6.js} +1 -1
  31. package/dist/{index-zxg4ve4j.js → index-fqqz1yt8.js} +77 -4
  32. package/dist/{index-d9zkmhz3.js → index-ggstwr0v.js} +53 -18
  33. package/dist/{index-j6zxnc8m.js → index-jpdvn09j.js} +5 -5
  34. package/dist/{index-4fx5f3k5.js → index-pq3kz1r8.js} +1 -1
  35. package/dist/{index-02pqtbbg.js → index-vc7yxz50.js} +10 -10
  36. package/dist/{index-crta5wzv.js → index-vvzc5ezz.js} +4 -4
  37. package/dist/{index-f6mqa5gk.js → index-wwthv10a.js} +11 -39
  38. package/dist/{index-74kkpyqk.js → index-xfedewpt.js} +12 -6
  39. package/dist/{index-sstgcsr0.js → index-yx52bt4a.js} +1 -1
  40. package/dist/index.js +14 -14
  41. package/dist/integrations/mcp-stdio-tools.d.ts +4 -1
  42. package/dist/runtime/budget-ledger-scope.d.ts +14 -0
  43. package/dist/service-cli.js +11 -11
  44. package/dist/{service-client-cli-eqrdc9zq.js → service-client-cli-v6qptg9x.js} +4 -4
  45. package/dist/tools/workspace.d.ts +2 -2
  46. package/dist/verify-historical-migrations.js +143 -62
  47. package/dist/workspace/edit-contracts.d.ts +2 -2
  48. package/dist/workspace/path-policy.d.ts +2 -1
  49. package/dist/workspace/workspace.d.ts +13 -0
  50. package/dist/zhx.js +2 -2
  51. package/docs/CODE_RELEASE.md +28 -6
  52. package/docs/CODE_WEB_TASKS.md +1 -1
  53. package/docs/ENGINE_API.md +1 -1
  54. package/docs/EXTENSIBILITY.md +6 -2
  55. package/docs/FIRST_USE.md +6 -6
  56. package/docs/PROJECT_MEMORY.md +1 -1
  57. package/docs/PUBLIC_SECURITY.md +14 -1
  58. package/docs/REPOSITORY_EDITING.md +1 -1
  59. package/docs/TOOL_POLICY.md +4 -1
  60. package/docs/adr/0002-isolated-mcp-stdio.md +1 -1
  61. package/evaluations/representative-assembly-matrix.json +10 -1
  62. package/examples/README.md +3 -3
  63. package/package.json +1 -1
@@ -3816,8 +3816,8 @@ var quarantineFileInputSchema = z6.strictObject({
3816
3816
  });
3817
3817
  var restoreFileInputSchema = z6.strictObject({
3818
3818
  quarantineId: z6.string().min(1).max(200).regex(/^[A-Za-z0-9._:-]+$/),
3819
- destination: workspaceFilePathSchema.optional(),
3820
- expectedDigest: fileDigestSchema.optional()
3819
+ destination: workspaceFilePathSchema.describe("Required workspace path for the quarantined bytes. Restore does not fall back to the original path."),
3820
+ expectedDigest: fileDigestSchema.describe("Required sha256 of the quarantined bytes the operator reviewed.")
3821
3821
  });
3822
3822
  var mutationOperationSchema = z6.enum(["create", "update", "move", "quarantine", "restore"]);
3823
3823
  var mutationAuditEntrySchema = z6.strictObject({
@@ -4157,10 +4157,10 @@ var runPortableProcess = async (command, options = {}) => {
4157
4157
  // src/workspace/path-policy.ts
4158
4158
  var HARD_IGNORES = new Set([".git", ".next", ".turbo", ".zhivex-harness", "coverage", "dist", "node_modules"]);
4159
4159
  var isSensitiveName = (name) => {
4160
- const normalized = name.toLocaleLowerCase();
4160
+ const normalized = name.toLowerCase();
4161
4161
  return normalized === ".env" || normalized.startsWith(".env.") && normalized !== ".env.example" || normalized === ".npmrc" || normalized === "id_rsa" || normalized === "id_ed25519" || [".key", ".pem", ".p12", ".pfx"].some((extension) => normalized.endsWith(extension));
4162
4162
  };
4163
- var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.toLocaleLowerCase()) || isSensitiveName(segment));
4163
+ var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.toLowerCase()) || isSensitiveName(segment));
4164
4164
 
4165
4165
  // src/workspace/workspace.ts
4166
4166
  var IGNORE_FILES = [".gitignore", ".zhivex-harnessignore"];
@@ -4440,7 +4440,7 @@ class Workspace {
4440
4440
  throw new HarnessWorkspaceError(`The path escapes the workspace: ${relativePath}`);
4441
4441
  const relative = wirePath(path8.relative(this.root, candidate));
4442
4442
  if (relative && isHardIgnored(relative)) {
4443
- const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.toLocaleLowerCase()) || isSensitiveName(segment));
4443
+ const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.toLowerCase()) || isSensitiveName(segment));
4444
4444
  throw new HarnessWorkspaceError(`The path is protected by the harness policy: ${blocked}`);
4445
4445
  }
4446
4446
  return candidate;
@@ -5423,30 +5423,65 @@ class Workspace {
5423
5423
  const audit = await this.audit({ operation: "quarantine", path: parsed.path, beforeDigest: source.digest, quarantineId });
5424
5424
  return { quarantineId, path: parsed.path, digest: source.digest, audit };
5425
5425
  }
5426
- async restoreQuarantined(input) {
5427
- return withWorkspaceMutation(this.root, () => this.restoreQuarantinedLocked(input));
5428
- }
5429
- async restoreQuarantinedLocked(input) {
5430
- const parsed = restoreFileInputSchema.parse(input);
5431
- this.checkAuditCapacity();
5426
+ async readRestorableQuarantine(quarantineId) {
5432
5427
  const directory = await this.secureQuarantineDirectory(false);
5433
5428
  let manifest;
5434
5429
  try {
5435
- const manifestFile = await this.readInternalRegularFile(path8.join(directory, `${parsed.quarantineId}.json`), "Quarantine manifest", 64 * 1024);
5430
+ const manifestFile = await this.readInternalRegularFile(path8.join(directory, `${quarantineId}.json`), "Quarantine manifest", 64 * 1024);
5436
5431
  manifest = JSON.parse(manifestFile.contents.toString("utf8"));
5437
5432
  } catch {
5438
- throw new HarnessWorkspaceError(`Quarantine entry was not found: ${parsed.quarantineId}.`);
5433
+ throw new HarnessWorkspaceError(`Quarantine entry was not found: ${quarantineId}.`);
5439
5434
  }
5440
- if (manifest.schemaVersion !== 1 || manifest.quarantineId !== parsed.quarantineId || manifest.status !== "quarantined") {
5441
- throw new HarnessWorkspaceError(`Quarantine entry is not restorable: ${parsed.quarantineId}.`);
5435
+ if (manifest.schemaVersion !== 1 || manifest.quarantineId !== quarantineId || manifest.status !== "quarantined") {
5436
+ throw new HarnessWorkspaceError(`Quarantine entry is not restorable: ${quarantineId}.`);
5442
5437
  }
5443
- const dataPath = path8.join(directory, `${parsed.quarantineId}.data`);
5438
+ const dataPath = path8.join(directory, `${quarantineId}.data`);
5444
5439
  const dataFile = await this.readInternalRegularFile(dataPath, "Quarantine payload", MAX_FILE_BYTES);
5445
5440
  const digest2 = digestBytes(dataFile.contents);
5446
- if (digest2 !== manifest.digest || parsed.expectedDigest && parsed.expectedDigest !== digest2) {
5441
+ if (digest2 !== manifest.digest) {
5442
+ throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${quarantineId}.`);
5443
+ }
5444
+ return { manifest, dataPath, dataFile, digest: digest2 };
5445
+ }
5446
+ async previewRestore(input) {
5447
+ const parsed = restoreFileInputSchema.parse(input);
5448
+ const quarantined = await this.readRestorableQuarantine(parsed.quarantineId);
5449
+ if (parsed.expectedDigest !== quarantined.digest) {
5447
5450
  throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${parsed.quarantineId}.`);
5448
5451
  }
5449
- const destinationPath = parsed.destination ?? manifest.originalPath;
5452
+ const destination = await this.safePath(parsed.destination, { allowMissing: true });
5453
+ if (destination.exists)
5454
+ throw new HarnessWorkspaceError(`Restore destination already exists: ${parsed.destination}.`);
5455
+ let after;
5456
+ try {
5457
+ after = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(quarantined.dataFile.contents);
5458
+ } catch {
5459
+ throw new HarnessWorkspaceError("Preview requires valid UTF-8 text.");
5460
+ }
5461
+ return {
5462
+ proposalId: parsed.expectedDigest,
5463
+ files: [{
5464
+ path: parsed.destination,
5465
+ expectedDigest: parsed.expectedDigest,
5466
+ before: null,
5467
+ after,
5468
+ afterDigest: quarantined.digest,
5469
+ operation: "restore"
5470
+ }]
5471
+ };
5472
+ }
5473
+ async restoreQuarantined(input) {
5474
+ return withWorkspaceMutation(this.root, () => this.restoreQuarantinedLocked(input));
5475
+ }
5476
+ async restoreQuarantinedLocked(input) {
5477
+ const parsed = restoreFileInputSchema.parse(input);
5478
+ this.checkAuditCapacity();
5479
+ const quarantined = await this.readRestorableQuarantine(parsed.quarantineId);
5480
+ if (parsed.expectedDigest !== quarantined.digest) {
5481
+ throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${parsed.quarantineId}.`);
5482
+ }
5483
+ const { manifest, dataPath, dataFile, digest: digest2 } = quarantined;
5484
+ const destinationPath = parsed.destination;
5450
5485
  const destination = await this.safePath(destinationPath, { allowMissing: true });
5451
5486
  if (destination.exists)
5452
5487
  throw new HarnessWorkspaceError(`Restore destination already exists: ${destinationPath}.`);
@@ -6671,7 +6706,7 @@ var importPatch = async (runId, expectedPatchId, host, base, current, maxFileWri
6671
6706
  const rollbackErrors = [];
6672
6707
  for (const item of [...quarantined].reverse()) {
6673
6708
  try {
6674
- await host.restoreQuarantined({ quarantineId: item.quarantineId, expectedDigest: item.digest });
6709
+ await host.restoreQuarantined({ quarantineId: item.quarantineId, destination: item.path, expectedDigest: item.digest });
6675
6710
  } catch (rollbackError) {
6676
6711
  rollbackErrors.push(rollbackError);
6677
6712
  }
@@ -7342,6 +7377,79 @@ var taskUsageAdmission = new AsyncLocalStorage3;
7342
7377
  import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
7343
7378
  import { randomUUID as randomUUID5 } from "node:crypto";
7344
7379
  import { z as z12 } from "zod";
7380
+
7381
+ // src/runtime/budget-ledger-scope.ts
7382
+ var BUDGET_LEDGER_NAMESPACE = "__zhivex_budget__";
7383
+ var BUDGET_OWNER_NAMESPACE_KEY = "budgetOwnerNamespace";
7384
+ var isReservedBudgetNamespace = (namespace) => namespace === BUDGET_LEDGER_NAMESPACE || (namespace?.startsWith(`${BUDGET_LEDGER_NAMESPACE}:`) ?? false);
7385
+ var budgetLedgerNamespace = (namespace) => {
7386
+ if (!namespace || isReservedBudgetNamespace(namespace)) {
7387
+ throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
7388
+ }
7389
+ return `${BUDGET_LEDGER_NAMESPACE}:${namespace}`;
7390
+ };
7391
+ var budgetLedgerScope = (scope) => {
7392
+ if (scope.namespace && isReservedBudgetNamespace(scope.namespace)) {
7393
+ throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
7394
+ }
7395
+ return {
7396
+ tenantId: scope.tenantId,
7397
+ ...scope.userId ? { userId: scope.userId } : {},
7398
+ namespace: scope.namespace ? budgetLedgerNamespace(scope.namespace) : `${BUDGET_LEDGER_NAMESPACE}:`
7399
+ };
7400
+ };
7401
+ var sameIdentity = (left, right) => left?.tenantId === right.tenantId && (left.userId ?? undefined) === (right.userId ?? undefined);
7402
+ var bindBudgetLedgerStore = (primary, scope, budget = primary) => {
7403
+ const bound = budgetLedgerScope(scope);
7404
+ const ownerNamespace = scope.namespace ?? "";
7405
+ const isSdkBudget = (value) => sameIdentity(value, bound) && value?.namespace === BUDGET_LEDGER_NAMESPACE;
7406
+ const isBoundBudget = (value) => sameIdentity(value, bound) && value?.namespace === bound.namespace;
7407
+ const assertId = (id) => {
7408
+ if (!/^budget_[a-f0-9]{64}$/.test(id))
7409
+ throw new HarnessStateConflictError("Invalid budget ledger identity.");
7410
+ };
7411
+ const bindState = (state) => {
7412
+ const metadata = { ...state.metadata ?? {} };
7413
+ const owner = metadata[BUDGET_OWNER_NAMESPACE_KEY];
7414
+ if (owner !== undefined && owner !== ownerNamespace) {
7415
+ throw new HarnessStateConflictError("Budget ledger is bound to another namespace.");
7416
+ }
7417
+ return {
7418
+ ...state,
7419
+ scope: bound,
7420
+ metadata: { ...metadata, [BUDGET_OWNER_NAMESPACE_KEY]: ownerNamespace }
7421
+ };
7422
+ };
7423
+ const assertStored = (state) => {
7424
+ if (!state)
7425
+ return state;
7426
+ const owner = state.metadata?.[BUDGET_OWNER_NAMESPACE_KEY];
7427
+ if (state.scope?.namespace !== bound.namespace || owner !== ownerNamespace) {
7428
+ throw new HarnessStateConflictError("Budget ledger is not bound to this namespace.");
7429
+ }
7430
+ return state;
7431
+ };
7432
+ return {
7433
+ ...primary,
7434
+ load: async (id, requestedScope) => {
7435
+ if (!isSdkBudget(requestedScope) && !isBoundBudget(requestedScope))
7436
+ return primary.load(id, requestedScope);
7437
+ assertId(id);
7438
+ return assertStored(await budget.load(id, bound));
7439
+ },
7440
+ save: async (state, options) => {
7441
+ if (!isSdkBudget(state.scope) && !isBoundBudget(state.scope))
7442
+ return primary.save(state, options);
7443
+ assertId(state.runId);
7444
+ if (state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true) {
7445
+ throw new HarnessStateConflictError("Only SDK budget ledger states may use the reserved namespace.");
7446
+ }
7447
+ return budget.save(bindState(state), options);
7448
+ }
7449
+ };
7450
+ };
7451
+
7452
+ // src/runtime/task-budget.ts
7345
7453
  import {
7346
7454
  createAgentBudgetCoordinator,
7347
7455
  fingerprintAgentHarness as fingerprintAgentHarness2,
@@ -8871,10 +8979,10 @@ class TaskBudget {
8871
8979
  this.accountRunId = TaskBudget.accountId(scope, taskId);
8872
8980
  this.policy = structuredClone(account.policy);
8873
8981
  const budgetId = this.accountRunId;
8874
- this.coordinator = createAgentBudgetCoordinator({ store, scope, budgetId, limits: this.policy.limits });
8982
+ this.coordinator = createAgentBudgetCoordinator({ store: bindBudgetLedgerStore(store, scope), scope, budgetId, limits: this.policy.limits });
8875
8983
  this.coordinatorId = this.coordinator.id;
8876
8984
  this.budgetRunId = `budget_${fingerprintAgentHarness2({ budgetId, scope }).slice("sha256:".length)}`;
8877
- this.budgetScope = { tenantId: scope.tenantId, ...scope.userId ? { userId: scope.userId } : {}, namespace: "__zhivex_budget__" };
8985
+ this.budgetScope = budgetLedgerScope(scope);
8878
8986
  }
8879
8987
  static accountId(scope, taskId) {
8880
8988
  return `task_budget_${fingerprintAgentHarness2({ scope, taskId }).slice("sha256:".length)}`;
@@ -13762,37 +13870,7 @@ import {
13762
13870
  createSqliteAgentRunStore
13763
13871
  } from "@zhivex-ai/agents/ops";
13764
13872
  import { createAgentRunLedger } from "@zhivex-ai/agents/control-plane";
13765
- var budgetScopeFor = (config) => ({
13766
- tenantId: config.scope.tenantId,
13767
- ...config.scope.userId ? { userId: config.scope.userId } : {},
13768
- namespace: "__zhivex_budget__"
13769
- });
13770
- var withBudgetLedgerStore = (primary, budget, config) => {
13771
- const scope = budgetScopeFor(config);
13772
- const budgetScope = (value) => value?.namespace === scope.namespace && value.tenantId === scope.tenantId && (value.userId ?? undefined) === (scope.userId ?? undefined);
13773
- const assertId = (id) => {
13774
- if (!/^budget_[a-f0-9]{64}$/.test(id))
13775
- throw new HarnessStateConflictError("Invalid budget ledger identity.");
13776
- };
13777
- return {
13778
- ...primary,
13779
- load: async (id, requestedScope) => {
13780
- if (!budgetScope(requestedScope))
13781
- return primary.load(id, requestedScope);
13782
- assertId(id);
13783
- return budget.load(id, requestedScope);
13784
- },
13785
- save: async (state, options) => {
13786
- if (!budgetScope(state.scope))
13787
- return primary.save(state, options);
13788
- assertId(state.runId);
13789
- if (state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true) {
13790
- throw new HarnessStateConflictError("Only SDK budget ledger states may use the reserved namespace.");
13791
- }
13792
- return budget.save(state, options);
13793
- }
13794
- };
13795
- };
13873
+ var withBudgetLedgerStore = (primary, budget, config) => bindBudgetLedgerStore(primary, config.scope, budget);
13796
13874
  var HARNESS_SQLITE_FILE = "operations.sqlite";
13797
13875
  var HARNESS_OPERATIONS_SCHEMA_VERSION = 1;
13798
13876
  var runSummary = (state) => {
@@ -13963,13 +14041,12 @@ var migrateLegacyFileRuns = async (directory, target, config) => {
13963
14041
  return result;
13964
14042
  };
13965
14043
  var openHarnessPersistence = async (config, options = {}) => {
13966
- if (config.scope.namespace === "__zhivex_budget__")
13967
- throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
14044
+ const budgetScope = budgetLedgerScope(config.scope);
13968
14045
  await validateStateDirectory(config.workspace, config.stateDirectory);
13969
14046
  await ensurePrivateStateDirectory(config.stateDirectory);
13970
14047
  await protectStateFromGit(config.workspace, config.stateDirectory);
13971
14048
  if (config.storeBackend === "file") {
13972
- const store = withBudgetLedgerStore(createFileAgentRunStore({ directory: config.stateDirectory, scope: config.scope }), createFileAgentRunStore({ directory: config.stateDirectory, scope: budgetScopeFor(config) }), config);
14049
+ const store = withBudgetLedgerStore(createFileAgentRunStore({ directory: config.stateDirectory, scope: config.scope }), createFileAgentRunStore({ directory: config.stateDirectory, scope: budgetScope }), config);
13973
14050
  const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
13974
14051
  return {
13975
14052
  backend: "file",
@@ -14014,7 +14091,7 @@ var openHarnessPersistence = async (config, options = {}) => {
14014
14091
  database.exec("PRAGMA foreign_keys = ON");
14015
14092
  await chmod3(databasePath, 384);
14016
14093
  const databaseLike = sqliteAdapter(database);
14017
- const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
14094
+ const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScope }), config);
14018
14095
  database.exec(`CREATE TRIGGER IF NOT EXISTS zhivex_task_budget_retain_run
14019
14096
  BEFORE DELETE ON zhivex_agent_runs
14020
14097
  WHEN json_type(OLD.state_json, '$.metadata.zhivexTaskBudgetAccountV1') IS NOT NULL
@@ -14105,7 +14182,7 @@ var projectMemoryBinding = (workspace, scope) => {
14105
14182
  segment(scope.userId);
14106
14183
  if (scope.namespace !== undefined)
14107
14184
  segment(scope.namespace);
14108
- if (scope.namespace === "__zhivex_budget__")
14185
+ if (isReservedBudgetNamespace(scope.namespace))
14109
14186
  throw new HarnessConfigError("Reserved memory namespace.");
14110
14187
  const key = (kind, value) => createHash24("sha256").update(kind).update("\x00").update(value).digest("hex");
14111
14188
  const workspaceKey = key("workspace", workspace);
@@ -14663,7 +14740,8 @@ var resolvers = {
14663
14740
  const parsed = moveFileInputSchema.parse(input);
14664
14741
  return [parsed.source, parsed.destination];
14665
14742
  },
14666
- quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path]
14743
+ quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path],
14744
+ restore_file: (input) => [restoreFileInputSchema.parse(input).destination]
14667
14745
  };
14668
14746
  var validateBuiltinToolPolicy = (policy, tools) => {
14669
14747
  for (const rule of policy.rules)
@@ -14797,7 +14875,7 @@ import { z as z32 } from "zod";
14797
14875
  // package.json
14798
14876
  var package_default = {
14799
14877
  name: "@zhivex-ai/harness",
14800
- version: "1.4.1",
14878
+ version: "1.5.0",
14801
14879
  description: "Governed, provider-portable runtime for coding agents with durable approvals and isolated repository execution.",
14802
14880
  type: "module",
14803
14881
  license: "MIT",
@@ -16322,7 +16400,7 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
16322
16400
  }),
16323
16401
  restore_file: tool5({
16324
16402
  name: "restore_file",
16325
- description: "Restore a quarantined file to its original path or an explicit safe destination without overwriting content unexpectedly.",
16403
+ description: "Restore a quarantined file to the required destination after the reviewed expectedDigest matches the quarantined bytes. Restore does not fall back to the original path and never overwrites an existing file.",
16326
16404
  schema: restoreFileInputSchema,
16327
16405
  ...mutationApproval,
16328
16406
  execute: async (input, context) => serializeJsonValue9(editContractDocument("restore-result", await (harnessExecutionSession(context)?.workspace ?? workspace).restoreQuarantined(input)))
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  HARNESS_VERSION
3
- } from "./index-4fx5f3k5.js";
3
+ } from "./index-pq3kz1r8.js";
4
4
  import {
5
5
  readRegularFileNoFollow
6
- } from "./index-xdbkcccf.js";
6
+ } from "./index-6dxrbnxm.js";
7
7
  import {
8
8
  HarnessConfigError,
9
9
  HarnessError,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  withWorkspaceMutation
3
- } from "./index-d9zkmhz3.js";
3
+ } from "./index-ggstwr0v.js";
4
4
  import {
5
5
  TERMINAL_SESSION_RUN_STATUSES
6
- } from "./index-zxg4ve4j.js";
6
+ } from "./index-fqqz1yt8.js";
7
7
  import {
8
8
  SqliteDatabase,
9
9
  createEditProposal,
@@ -11,7 +11,7 @@ import {
11
11
  readRegularFileNoFollow,
12
12
  statRegularFileNoFollow,
13
13
  workspaceFilePathSchema
14
- } from "./index-xdbkcccf.js";
14
+ } from "./index-6dxrbnxm.js";
15
15
 
16
16
  // src/persistence/workspace-checkpoints.ts
17
17
  import { createHash, randomUUID } from "node:crypto";
@@ -96,8 +96,8 @@ var quarantineFileInputSchema = z.strictObject({
96
96
  });
97
97
  var restoreFileInputSchema = z.strictObject({
98
98
  quarantineId: z.string().min(1).max(200).regex(/^[A-Za-z0-9._:-]+$/),
99
- destination: workspaceFilePathSchema.optional(),
100
- expectedDigest: fileDigestSchema.optional()
99
+ destination: workspaceFilePathSchema.describe("Required workspace path for the quarantined bytes. Restore does not fall back to the original path."),
100
+ expectedDigest: fileDigestSchema.describe("Required sha256 of the quarantined bytes the operator reviewed.")
101
101
  });
102
102
  var mutationOperationSchema = z.enum(["create", "update", "move", "quarantine", "restore"]);
103
103
  var mutationAuditEntrySchema = z.strictObject({
@@ -20,7 +20,7 @@ import {
20
20
  runtimeManifest,
21
21
  taskSources,
22
22
  tokenUsageCheckpointStore
23
- } from "./index-f6mqa5gk.js";
23
+ } from "./index-wwthv10a.js";
24
24
  import {
25
25
  HARNESS_MCP_CONFIG_SCHEMA_VERSION,
26
26
  closeMcpHostSession,
@@ -29,7 +29,7 @@ import {
29
29
  mcpConfigurationFingerprintInput,
30
30
  normalizeHarnessMcpConfiguration,
31
31
  resolveMcpHostSession
32
- } from "./index-dtb2c6ds.js";
32
+ } from "./index-5zbq9ztj.js";
33
33
  import {
34
34
  EnvironmentPatchDriftError,
35
35
  Workspace,
@@ -40,7 +40,7 @@ import {
40
40
  isHardIgnored,
41
41
  replacementEditSchema,
42
42
  resolvePackageCheckCommand
43
- } from "./index-d9zkmhz3.js";
43
+ } from "./index-ggstwr0v.js";
44
44
  import {
45
45
  DEFAULT_PROVIDER_REGISTRY,
46
46
  HARNESS_CONFIG_SCHEMA_VERSION,
@@ -60,7 +60,7 @@ import {
60
60
  publishHarnessPolicyDecision,
61
61
  publishPendingPolicyDecision,
62
62
  readApprovalDecisions
63
- } from "./index-wm9xeq1n.js";
63
+ } from "./index-bt2p8x7r.js";
64
64
  import {
65
65
  MODEL_BUDGET_KEY,
66
66
  TASK_ACCEPTANCE_EVIDENCE_KEY,
@@ -85,10 +85,10 @@ import {
85
85
  withFreshSystemInstructions,
86
86
  withRuntimeInstruction,
87
87
  workBudgetReached
88
- } from "./index-zxg4ve4j.js";
88
+ } from "./index-fqqz1yt8.js";
89
89
  import {
90
90
  HARNESS_VERSION
91
- } from "./index-4fx5f3k5.js";
91
+ } from "./index-pq3kz1r8.js";
92
92
  import {
93
93
  FileSizeLimitError,
94
94
  MAX_EDIT_CHANGES,
@@ -108,7 +108,7 @@ import {
108
108
  validateEditProposal,
109
109
  withTaskAcceptanceDelivery,
110
110
  workspaceFilePathSchema
111
- } from "./index-xdbkcccf.js";
111
+ } from "./index-6dxrbnxm.js";
112
112
  import {
113
113
  HarnessConfigError,
114
114
  HarnessError,
@@ -4199,7 +4199,8 @@ var resolvers = {
4199
4199
  const parsed = moveFileInputSchema.parse(input);
4200
4200
  return [parsed.source, parsed.destination];
4201
4201
  },
4202
- quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path]
4202
+ quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path],
4203
+ restore_file: (input) => [restoreFileInputSchema.parse(input).destination]
4203
4204
  };
4204
4205
  var validateBuiltinToolPolicy = (policy, tools) => {
4205
4206
  for (const rule of policy.rules)
@@ -4656,7 +4657,7 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
4656
4657
  }),
4657
4658
  restore_file: tool4({
4658
4659
  name: "restore_file",
4659
- description: "Restore a quarantined file to its original path or an explicit safe destination without overwriting content unexpectedly.",
4660
+ description: "Restore a quarantined file to the required destination after the reviewed expectedDigest matches the quarantined bytes. Restore does not fall back to the original path and never overwrites an existing file.",
4660
4661
  schema: restoreFileInputSchema,
4661
4662
  ...mutationApproval,
4662
4663
  execute: async (input, context) => serializeJsonValue6(editContractDocument("restore-result", await (harnessExecutionSession(context)?.workspace ?? workspace).restoreQuarantined(input)))
@@ -3,15 +3,15 @@ import {
3
3
  combineMcpHostSessions,
4
4
  createHarnessMcpTools,
5
5
  issueMcpHostSession
6
- } from "./index-dtb2c6ds.js";
6
+ } from "./index-5zbq9ztj.js";
7
7
  import {
8
8
  collectSnapshotInventory,
9
9
  readSnapshotFile,
10
10
  runPortableProcess
11
- } from "./index-d9zkmhz3.js";
11
+ } from "./index-ggstwr0v.js";
12
12
  import {
13
13
  readRegularFileNoFollow
14
- } from "./index-xdbkcccf.js";
14
+ } from "./index-6dxrbnxm.js";
15
15
 
16
16
  // src/execution/mcp-workspace-snapshot.ts
17
17
  import { createHash } from "node:crypto";
@@ -1254,6 +1254,38 @@ var resultSchema = z3.object({
1254
1254
  isError: z3.boolean().optional(),
1255
1255
  _meta: record.optional()
1256
1256
  }).strict().refine((value) => value.content !== undefined || value.structuredContent !== undefined);
1257
+ var ENCODED_MARK = "[ENCODED]";
1258
+ var ENTROPY_MARK = "[HIGH_ENTROPY]";
1259
+ function shannonEntropy(value) {
1260
+ const counts = new Map;
1261
+ for (const char of value)
1262
+ counts.set(char, (counts.get(char) ?? 0) + 1);
1263
+ let entropy = 0;
1264
+ for (const count of counts.values()) {
1265
+ const p = count / value.length;
1266
+ entropy -= p * Math.log2(p);
1267
+ }
1268
+ return entropy;
1269
+ }
1270
+ function secretEncodings(secret) {
1271
+ if (Buffer.byteLength(secret) < 8)
1272
+ return [];
1273
+ const bytes = Buffer.from(secret, "utf8");
1274
+ const base64 = bytes.toString("base64");
1275
+ const base64Url = bytes.toString("base64url");
1276
+ const hex = bytes.toString("hex");
1277
+ return [...new Set([base64, base64.replace(/=+$/u, ""), base64Url, hex, hex.toUpperCase()])].filter((value) => value.length >= 8);
1278
+ }
1279
+ function flagEncodedSecrets(value, secrets) {
1280
+ let text = value;
1281
+ for (const encoding of secrets.flatMap(secretEncodings).sort((left, right) => right.length - left.length)) {
1282
+ text = text.split(encoding).join(ENCODED_MARK);
1283
+ }
1284
+ const flag = (token) => shannonEntropy(token.replace(/=+$/u, "")) >= 3.5 ? ENTROPY_MARK : token;
1285
+ text = text.replace(/[A-Za-z0-9+/_-]{24,}={0,2}/g, flag);
1286
+ text = text.replace(/\b[a-fA-F0-9]{32,}\b/g, flag);
1287
+ return text;
1288
+ }
1257
1289
  function compileSchema(schema) {
1258
1290
  const pending = [{ value: schema, depth: 0 }];
1259
1291
  let nodes = 0;
@@ -1286,21 +1318,25 @@ async function createIsolatedMcpTools(options) {
1286
1318
  throw new McpStdioClientError("protocol", message, effectful);
1287
1319
  };
1288
1320
  const secrets = [...new Set(options.sensitiveValues ?? [])].filter(Boolean).sort((a, b) => b.length - a.length);
1321
+ const flagResults = options.flagEncodedSecrets === true;
1289
1322
  const redaction = createRedactionPolicy({ includeEmails: true });
1290
- const text = (value) => redaction.redactText(secrets.reduce((current, secret) => current.split(secret).join("[REDACTED]"), value));
1291
- const scrub = (value, depth = 0, budget = { nodes: 0 }) => {
1323
+ const text = (value, flag) => {
1324
+ const redacted = redaction.redactText(secrets.reduce((current, secret) => current.split(secret).join("[REDACTED]"), value));
1325
+ return flag ? flagEncodedSecrets(redacted, secrets) : redacted;
1326
+ };
1327
+ const scrub = (value, flag, depth = 0, budget = { nodes: 0 }) => {
1292
1328
  if (depth > 32 || ++budget.nodes > 20000)
1293
1329
  throw new Error("MCP payload complexity limit exceeded.");
1294
1330
  if (typeof value === "string")
1295
- return text(value);
1331
+ return text(value, flag);
1296
1332
  if (Array.isArray(value))
1297
- return value.map((item) => scrub(item, depth + 1, budget));
1333
+ return value.map((item) => scrub(item, flag, depth + 1, budget));
1298
1334
  if (value && typeof value === "object")
1299
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [text(key), scrub(item, depth + 1, budget)]));
1335
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [text(key, flag), scrub(item, flag, depth + 1, budget)]));
1300
1336
  return value;
1301
1337
  };
1302
- const bounded = (value) => {
1303
- const clean = scrub(value);
1338
+ const bounded = (value, flag) => {
1339
+ const clean = scrub(value, flag);
1304
1340
  if (Buffer.byteLength(JSON.stringify(value)) > proposal.limits.maxOutputBytes)
1305
1341
  throw new Error("MCP output limit exceeded.");
1306
1342
  return clean;
@@ -1319,7 +1355,7 @@ async function createIsolatedMcpTools(options) {
1319
1355
  const output = outputValidators.get(input.name);
1320
1356
  if (output && !parsed.isError && !output.safeParse(parsed.structuredContent).success)
1321
1357
  throw new Error("Output schema");
1322
- return bounded(parsed);
1358
+ return bounded(parsed, flagResults);
1323
1359
  } catch {
1324
1360
  return invalidResponse("Isolated MCP result validation failed.", true);
1325
1361
  }
@@ -1330,7 +1366,7 @@ async function createIsolatedMcpTools(options) {
1330
1366
  async listTools(input, callOptions) {
1331
1367
  try {
1332
1368
  const response = await options.client.listTools(input, callOptions);
1333
- const clean = bounded(response);
1369
+ const clean = bounded(response, false);
1334
1370
  if (JSON.stringify(clean) !== JSON.stringify(response))
1335
1371
  throw new Error("Sensitive discovery");
1336
1372
  const parsed = listing.parse(response);
@@ -1722,7 +1758,13 @@ async function launchMcpToolsOwned(options, groupRecoveryComplete) {
1722
1758
  return value;
1723
1759
  } });
1724
1760
  try {
1725
- const tools = await createIsolatedMcpTools({ client, proposal, sensitiveValues, journal });
1761
+ const tools = await createIsolatedMcpTools({
1762
+ client,
1763
+ proposal,
1764
+ sensitiveValues,
1765
+ journal,
1766
+ flagEncodedSecrets: Object.keys(proposal.secretReferences).length > 0
1767
+ });
1726
1768
  const hostSession = issueMcpHostSession({ tools, ...journal, proposal, close: () => client.close() });
1727
1769
  return Object.freeze({ tools, hostSession, close: () => closeMcpHostSession(hostSession) });
1728
1770
  } catch {
@@ -2,7 +2,7 @@ import {
2
2
  createEditProposal,
3
3
  editChangesSchema,
4
4
  workspaceFilePathSchema
5
- } from "./index-xdbkcccf.js";
5
+ } from "./index-6dxrbnxm.js";
6
6
 
7
7
  // src/runtime/tool-policy.ts
8
8
  import { createHash } from "node:crypto";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  changeEnvelopeSchema
3
- } from "./index-sstgcsr0.js";
3
+ } from "./index-yx52bt4a.js";
4
4
  import {
5
5
  PROVIDERS,
6
6
  reasoningEffortSchema