@zhivex-ai/harness 1.4.0 → 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 (128) hide show
  1. package/CHANGELOG.md +32 -10
  2. package/README.md +7 -5
  3. package/SUPPORT.md +2 -2
  4. package/contracts/client-protocol-v1.schema.json +593 -0
  5. package/contracts/engine-api.json +156 -0
  6. package/contracts/public-api.json +12 -0
  7. package/contracts/stable-api-signatures.json +36 -36
  8. package/dist/acp-cli.js +54 -20
  9. package/dist/approvals/approval-diff.d.ts +2 -1
  10. package/dist/approvals/approval-preview.d.ts +1 -1
  11. package/dist/audit/index.d.ts +3 -0
  12. package/dist/audit/index.js +482 -0
  13. package/dist/audit/pack.d.ts +72 -0
  14. package/dist/audit-pack-kvw7ng34.js +532 -0
  15. package/dist/cli/arguments.d.ts +3 -1
  16. package/dist/cli/audit-pack.d.ts +24 -0
  17. package/dist/cli/cli-credentials.d.ts +6 -0
  18. package/dist/cli/cli-profiles.d.ts +4 -2
  19. package/dist/cli/node-engine.d.ts +2 -0
  20. package/dist/cli-entry-75j3r49b.js +484 -0
  21. package/dist/cli-entry-928p8h41.js +10067 -0
  22. package/dist/cli-entry-h2yc9x37.js +10350 -0
  23. package/dist/{cli-entry-20w6nh0k.js → cli-entry-hdtfef27.js} +439 -19
  24. package/dist/{cli-entry-w0hterjs.js → cli-entry-q8bgkn9m.js} +40 -13
  25. package/dist/{cli-2h37qyvg.js → cli-vyrytn3m.js} +253 -644
  26. package/dist/cli.js +27 -2
  27. package/dist/client/json-contracts.d.ts +4 -0
  28. package/dist/client/protocol.d.ts +133 -0
  29. package/dist/client/task-control-host.d.ts +400 -0
  30. package/dist/client/task-projection-host.d.ts +12 -0
  31. package/dist/client/task-projection.d.ts +168 -0
  32. package/dist/compat/code-support.js +28 -22
  33. package/dist/{config-qt69hkqj.js → config-r9fdhce6.js} +3 -3
  34. package/dist/{config-j9624t6t.js → config-vyajmksy.js} +1 -1
  35. package/dist/engine/acp.js +15 -11
  36. package/dist/engine/client.js +14 -10
  37. package/dist/engine/desktop-providers-v1.d.ts +2 -0
  38. package/dist/engine/desktop-providers-v1.js +23 -3
  39. package/dist/engine/desktop-state-v1.js +11 -8
  40. package/dist/engine/index.d.ts +3 -0
  41. package/dist/engine/index.js +101 -554
  42. package/dist/engine/mcp-stdio-v1.js +8 -6
  43. package/dist/engine/models.js +76 -4
  44. package/dist/engine/protocol.d.ts +2 -0
  45. package/dist/engine/protocol.js +110 -0
  46. package/dist/engine/service.js +15 -11
  47. package/dist/hostile-repository-demo.js +1232 -564
  48. package/dist/{index-8zjmnyw5.js → index-1265j7t2.js} +73 -6
  49. package/dist/{index-nmjdwd4p.js → index-4gt045j0.js} +109 -1
  50. package/dist/{index-pdmevfxv.js → index-5zbq9ztj.js} +5 -259
  51. package/dist/{index-6dft2vev.js → index-63gbp0cz.js} +5 -3
  52. package/dist/index-6dxrbnxm.js +719 -0
  53. package/dist/{index-pp474137.js → index-6fp7kdpx.js} +769 -933
  54. package/dist/{index-61gthdz6.js → index-8sm2a1wa.js} +1 -1
  55. package/dist/{index-rkbf7dqg.js → index-96ezrvg2.js} +57 -13
  56. package/dist/index-bt2p8x7r.js +329 -0
  57. package/dist/index-c39197wq.js +552 -0
  58. package/dist/{index-m82cfspw.js → index-eps3c3g6.js} +877 -963
  59. package/dist/{index-s2k2edts.js → index-fqqz1yt8.js} +110 -1544
  60. package/dist/{index-m34q25jj.js → index-ggstwr0v.js} +192 -838
  61. package/dist/index-jpdvn09j.js +490 -0
  62. package/dist/{index-xqxeypt5.js → index-mzydbzp8.js} +76 -4
  63. package/dist/index-pq3kz1r8.js +269 -0
  64. package/dist/{index-wg1sd3at.js → index-qfsrrnz6.js} +1 -1
  65. package/dist/{index-937cb7y8.js → index-vc7yxz50.js} +561 -35
  66. package/dist/{index-5jhgatga.js → index-vvzc5ezz.js} +7 -6
  67. package/dist/index-wwthv10a.js +1507 -0
  68. package/dist/{index-tfzta544.js → index-xfedewpt.js} +17 -9
  69. package/dist/index-yx52bt4a.js +511 -0
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.js +93 -77
  72. package/dist/integrations/mcp-stdio-tools.d.ts +4 -1
  73. package/dist/internal/desktop/providers.d.ts +2 -0
  74. package/dist/models/catalog.d.ts +2 -0
  75. package/dist/persistence/sqlite-database.d.ts +1 -0
  76. package/dist/providers/azure-openai.d.ts +14 -0
  77. package/dist/providers/bedrock.d.ts +23 -0
  78. package/dist/providers/providers.d.ts +1 -1
  79. package/dist/runtime/budget-ledger-scope.d.ts +14 -0
  80. package/dist/runtime/durable-review-group.d.ts +33 -0
  81. package/dist/runtime/harness.d.ts +11 -0
  82. package/dist/runtime/run-interruption.d.ts +7 -0
  83. package/dist/runtime/shared-budget.d.ts +30 -0
  84. package/dist/runtime/task-continuity.d.ts +300 -77
  85. package/dist/service-cli.js +15 -11
  86. package/dist/{service-client-cli-fn54ev6w.js → service-client-cli-v6qptg9x.js} +400 -305
  87. package/dist/tools/workspace.d.ts +2 -2
  88. package/dist/verify-historical-migrations.js +1237 -566
  89. package/dist/workspace/edit-contracts.d.ts +2 -2
  90. package/dist/workspace/path-policy.d.ts +2 -1
  91. package/dist/workspace/workspace.d.ts +13 -0
  92. package/dist/zhx.js +27 -2
  93. package/docs/AUDIT_PACK.md +99 -0
  94. package/docs/AZURE_OPENAI.md +53 -0
  95. package/docs/BEDROCK.md +56 -0
  96. package/docs/CLI.md +15 -0
  97. package/docs/CLIENT_PROTOCOL.md +178 -0
  98. package/docs/CODE_RELEASE.md +59 -7
  99. package/docs/CODE_WEB_TASKS.md +116 -0
  100. package/docs/CONTEXT_ENGINEERING.md +59 -0
  101. package/docs/CREDENTIALS.md +21 -0
  102. package/docs/DURABLE_OPERATIONS.md +93 -1
  103. package/docs/ENGINE_API.md +5 -2
  104. package/docs/EXTENSIBILITY.md +8 -4
  105. package/docs/FIRST_USE.md +6 -6
  106. package/docs/MINIMUM_TASK_AUTHORITY.md +3 -3
  107. package/docs/PROJECT_MEMORY.md +1 -1
  108. package/docs/PUBLIC_SECURITY.md +14 -1
  109. package/docs/README.md +6 -1
  110. package/docs/REPOSITORY_EDITING.md +1 -1
  111. package/docs/STABILITY.md +3 -1
  112. package/docs/SUPPORT_MATRIX.md +1 -1
  113. package/docs/SUPPORT_MATRIX_1_1.md +1 -1
  114. package/docs/TOOL_POLICY.md +4 -1
  115. package/docs/USAGE.md +3 -1
  116. package/docs/VERTEX.md +2 -2
  117. package/docs/WEB_LIMITS.md +4 -3
  118. package/docs/adr/0002-isolated-mcp-stdio.md +7 -4
  119. package/docs/support-matrix-1.1.json +3 -1
  120. package/docs/support-matrix.json +6 -4
  121. package/evaluations/code-task-ui.json +20 -0
  122. package/evaluations/representative-assembly-matrix.json +19 -1
  123. package/evaluations/task-projection.json +10 -0
  124. package/evaluations/task-recovery-lab.json +60 -0
  125. package/examples/README.md +3 -3
  126. package/package.json +23 -13
  127. package/dist/cli-entry-4ngptr62.js +0 -19900
  128. package/dist/index-m49jx72n.js +0 -220
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DEFAULT_PROVIDER_REGISTRY,
3
3
  HARNESS_SUBAGENT_PROFILES
4
- } from "./index-8zjmnyw5.js";
4
+ } from "./index-1265j7t2.js";
5
5
 
6
6
  // src/providers/routing.ts
7
7
  var routeError = (value) => new Error(`Invalid route: ${value}. Use <profile>=<provider>[:<model>].`);
@@ -3,13 +3,15 @@ import {
3
3
  combineMcpHostSessions,
4
4
  createHarnessMcpTools,
5
5
  issueMcpHostSession
6
- } from "./index-pdmevfxv.js";
6
+ } from "./index-5zbq9ztj.js";
7
7
  import {
8
8
  collectSnapshotInventory,
9
- readRegularFileNoFollow,
10
9
  readSnapshotFile,
11
10
  runPortableProcess
12
- } from "./index-m34q25jj.js";
11
+ } from "./index-ggstwr0v.js";
12
+ import {
13
+ readRegularFileNoFollow
14
+ } from "./index-6dxrbnxm.js";
13
15
 
14
16
  // src/execution/mcp-workspace-snapshot.ts
15
17
  import { createHash } from "node:crypto";
@@ -1252,6 +1254,38 @@ var resultSchema = z3.object({
1252
1254
  isError: z3.boolean().optional(),
1253
1255
  _meta: record.optional()
1254
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
+ }
1255
1289
  function compileSchema(schema) {
1256
1290
  const pending = [{ value: schema, depth: 0 }];
1257
1291
  let nodes = 0;
@@ -1284,21 +1318,25 @@ async function createIsolatedMcpTools(options) {
1284
1318
  throw new McpStdioClientError("protocol", message, effectful);
1285
1319
  };
1286
1320
  const secrets = [...new Set(options.sensitiveValues ?? [])].filter(Boolean).sort((a, b) => b.length - a.length);
1321
+ const flagResults = options.flagEncodedSecrets === true;
1287
1322
  const redaction = createRedactionPolicy({ includeEmails: true });
1288
- const text = (value) => redaction.redactText(secrets.reduce((current, secret) => current.split(secret).join("[REDACTED]"), value));
1289
- 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 }) => {
1290
1328
  if (depth > 32 || ++budget.nodes > 20000)
1291
1329
  throw new Error("MCP payload complexity limit exceeded.");
1292
1330
  if (typeof value === "string")
1293
- return text(value);
1331
+ return text(value, flag);
1294
1332
  if (Array.isArray(value))
1295
- return value.map((item) => scrub(item, depth + 1, budget));
1333
+ return value.map((item) => scrub(item, flag, depth + 1, budget));
1296
1334
  if (value && typeof value === "object")
1297
- 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)]));
1298
1336
  return value;
1299
1337
  };
1300
- const bounded = (value) => {
1301
- const clean = scrub(value);
1338
+ const bounded = (value, flag) => {
1339
+ const clean = scrub(value, flag);
1302
1340
  if (Buffer.byteLength(JSON.stringify(value)) > proposal.limits.maxOutputBytes)
1303
1341
  throw new Error("MCP output limit exceeded.");
1304
1342
  return clean;
@@ -1317,7 +1355,7 @@ async function createIsolatedMcpTools(options) {
1317
1355
  const output = outputValidators.get(input.name);
1318
1356
  if (output && !parsed.isError && !output.safeParse(parsed.structuredContent).success)
1319
1357
  throw new Error("Output schema");
1320
- return bounded(parsed);
1358
+ return bounded(parsed, flagResults);
1321
1359
  } catch {
1322
1360
  return invalidResponse("Isolated MCP result validation failed.", true);
1323
1361
  }
@@ -1328,7 +1366,7 @@ async function createIsolatedMcpTools(options) {
1328
1366
  async listTools(input, callOptions) {
1329
1367
  try {
1330
1368
  const response = await options.client.listTools(input, callOptions);
1331
- const clean = bounded(response);
1369
+ const clean = bounded(response, false);
1332
1370
  if (JSON.stringify(clean) !== JSON.stringify(response))
1333
1371
  throw new Error("Sensitive discovery");
1334
1372
  const parsed = listing.parse(response);
@@ -1720,7 +1758,13 @@ async function launchMcpToolsOwned(options, groupRecoveryComplete) {
1720
1758
  return value;
1721
1759
  } });
1722
1760
  try {
1723
- 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
+ });
1724
1768
  const hostSession = issueMcpHostSession({ tools, ...journal, proposal, close: () => client.close() });
1725
1769
  return Object.freeze({ tools, hostSession, close: () => closeMcpHostSession(hostSession) });
1726
1770
  } catch {
@@ -0,0 +1,329 @@
1
+ import {
2
+ createEditProposal,
3
+ editChangesSchema,
4
+ workspaceFilePathSchema
5
+ } from "./index-6dxrbnxm.js";
6
+
7
+ // src/runtime/tool-policy.ts
8
+ import { createHash } from "node:crypto";
9
+ import { z } from "zod";
10
+ var normalizedPath = workspaceFilePathSchema.refine((value) => !value.includes("\\"), "Use forward slashes.");
11
+ var nameSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/);
12
+ var harnessToolPolicySchema = z.strictObject({
13
+ schemaVersion: z.literal(1),
14
+ explicitReview: z.strictObject({ schemaVersion: z.literal(1) }).optional(),
15
+ rules: z.array(z.strictObject({
16
+ id: nameSchema,
17
+ tools: z.array(nameSchema).min(1).max(128),
18
+ paths: z.array(normalizedPath).min(1).max(256).optional(),
19
+ decision: z.enum(["allow", "ask_user", "deny"]),
20
+ reason: z.string().min(1).max(512)
21
+ })).max(128)
22
+ }).superRefine((policy, context) => {
23
+ if (new Set(policy.rules.map((rule) => rule.id)).size !== policy.rules.length) {
24
+ context.addIssue({ code: "custom", message: "Tool policy rule ids must be unique." });
25
+ }
26
+ });
27
+ var rank = { allow: 0, ask_user: 1, deny: 2 };
28
+ var createHarnessToolPolicy = (input) => {
29
+ const policy = harnessToolPolicySchema.parse(input);
30
+ const canonical = { schemaVersion: 1, ...policy.explicitReview ? { explicitReview: policy.explicitReview } : {}, rules: policy.rules.map((rule) => ({
31
+ ...rule,
32
+ tools: [...new Set(rule.tools)].sort(),
33
+ ...rule.paths ? { paths: [...new Set(rule.paths)].sort() } : {}
34
+ })).sort((a, b) => a.id.localeCompare(b.id)) };
35
+ const digest = `sha256:${createHash("sha256").update(JSON.stringify(canonical)).digest("hex")}`;
36
+ const evaluate = (request) => {
37
+ if (request.hardDenied)
38
+ return { decision: "deny", ruleIds: [], reason: "Denied by the harness permission boundary." };
39
+ const paths = request.paths?.map((value) => normalizedPath.parse(value)) ?? [];
40
+ if (paths.length === 0 && canonical.rules.some((rule) => rule.tools.includes(request.toolName) && rule.paths)) {
41
+ return { decision: "deny", ruleIds: [], reason: "Path-scoped policy cannot evaluate a request without resolved paths." };
42
+ }
43
+ const matching = canonical.rules.filter((rule) => rule.tools.includes(request.toolName) && (!rule.paths || paths.some((target) => rule.paths?.includes(target))));
44
+ let decision = request.requiresApproval ? "ask_user" : "allow";
45
+ for (const rule of matching)
46
+ if (rank[rule.decision] > rank[decision])
47
+ decision = rule.decision;
48
+ const decisive = matching.filter((rule) => rule.decision === decision);
49
+ return { decision, ruleIds: decisive.map((rule) => rule.id), reason: decisive.length > 0 ? decisive.map((rule) => rule.reason).join("; ") : request.requiresApproval ? "Existing tool approval is required." : "No additional restriction from application policy." };
50
+ };
51
+ return { digest, evaluate, policy: structuredClone(canonical) };
52
+ };
53
+
54
+ class HarnessToolPolicyDeniedError extends Error {
55
+ policyDecision;
56
+ constructor(policyDecision) {
57
+ super(`Tool denied by application policy${policyDecision.ruleIds.length ? ` (${policyDecision.ruleIds.join(", ")})` : ""}: ${policyDecision.reason}`);
58
+ this.policyDecision = policyDecision;
59
+ this.name = "HarnessToolPolicyDeniedError";
60
+ }
61
+ }
62
+ var applyHarnessToolPolicy = (tools, input, options = {}) => {
63
+ const engine = createHarnessToolPolicy(input);
64
+ if (engine.policy.rules.some((rule) => rule.paths) && !options.resolvePaths) {
65
+ throw new Error("Path-scoped tool policy requires a trusted resolvePaths callback.");
66
+ }
67
+ const result = {};
68
+ for (const [name, definition] of Object.entries(tools)) {
69
+ const additionalApproval = engine.policy.rules.some((rule) => rule.tools.includes(name) && rule.decision === "ask_user");
70
+ const requiresApproval = definition.requiresApproval === true || additionalApproval;
71
+ const execute = "execute" in definition ? definition.execute : undefined;
72
+ if (!execute && engine.policy.rules.some((rule) => rule.tools.includes(name))) {
73
+ throw new Error(`Tool policy cannot wrap provider-executed tool ${name}.`);
74
+ }
75
+ result[name] = {
76
+ ...definition,
77
+ ...requiresApproval ? {
78
+ independent: false,
79
+ requiresApproval: true,
80
+ approvalMode: "interrupt",
81
+ approvalVersion: `${("approvalVersion" in definition ? definition.approvalVersion : undefined) ?? "policy"}:${engine.digest}`
82
+ } : {},
83
+ metadata: { ...definition.metadata, harnessToolPolicyDigest: engine.digest },
84
+ ...execute ? { execute: async (toolInput, context) => {
85
+ const decision = engine.evaluate({
86
+ toolName: name,
87
+ ...options.resolvePaths ? { paths: options.resolvePaths(name, toolInput) } : {},
88
+ requiresApproval
89
+ });
90
+ await options.onDecision?.(name, decision);
91
+ if (decision.decision === "deny")
92
+ throw new HarnessToolPolicyDeniedError(decision);
93
+ return execute(toolInput, context);
94
+ } } : {}
95
+ };
96
+ }
97
+ return result;
98
+ };
99
+
100
+ // src/runtime/policy-inspection.ts
101
+ import { createRedactionPolicy } from "@zhivex-ai/agents";
102
+ var redaction = createRedactionPolicy({ includeEmails: true });
103
+ var sanitizePolicyText = (text) => redaction.redactText(text).replace(/\b(?:sk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]+/gi, "[REDACTED]").replace(/[\x00-\x1f\x7f-\x9f]/g, "").replace(/(?:[A-Za-z]:\\|\/)[^\s;,]+/g, "[PRIVATE_PATH]");
104
+ var inspections = new WeakMap;
105
+ function bindHarnessPolicyInspection(host, input) {
106
+ if (inspections.has(host))
107
+ throw new Error("POLICY_INSPECTION_ALREADY_BOUND");
108
+ const compiled = input.policy ? createHarnessToolPolicy(input.policy) : undefined;
109
+ const execution = input.config.execution;
110
+ const limits = {
111
+ maxSteps: input.config.maxSteps,
112
+ timeoutMs: input.config.timeoutMs,
113
+ ...input.config.unlimitedDuration === undefined ? {} : { unlimitedDuration: input.config.unlimitedDuration },
114
+ budget: structuredClone(input.config.budget),
115
+ ...execution.backend === "oci" ? { oci: {
116
+ maxProcessRuntimeMs: execution.maxProcessRuntimeMs,
117
+ maxProcessOutputBytes: execution.maxProcessOutputBytes,
118
+ maxMemoryMb: execution.maxMemoryMb,
119
+ maxPids: execution.maxPids,
120
+ maxCpus: execution.maxCpus,
121
+ maxWorkspaceBytes: execution.maxWorkspaceBytes,
122
+ maxFileWriteBytes: execution.maxFileWriteBytes,
123
+ tmpfsMb: execution.tmpfsMb
124
+ } } : {}
125
+ };
126
+ inspections.set(host, {
127
+ schemaVersion: 1,
128
+ kind: "policy-inspection",
129
+ digest: compiled?.digest ?? null,
130
+ source: compiled ? input.operatorFile ? "operator-file" : "application" : "baseline",
131
+ explicitReviewRequired: compiled?.policy.explicitReview?.schemaVersion === 1,
132
+ tools: Object.entries(input.tools).sort(([a], [b]) => a.localeCompare(b)).map(([name, tool]) => ({ name: sanitizePolicyText(name), requiresApproval: tool.requiresApproval === true })),
133
+ restrictions: (compiled?.policy.rules ?? []).map((rule) => ({
134
+ ruleId: sanitizePolicyText(rule.id),
135
+ tools: rule.tools.map(sanitizePolicyText),
136
+ decision: rule.decision,
137
+ reason: sanitizePolicyText(rule.reason),
138
+ pathCount: rule.paths?.length ?? 0
139
+ })),
140
+ execution: { configuredBackend: execution.backend, activeBackend: input.hasOciEnvironment ? "oci" : "none", evidence: "configuration-only" },
141
+ limits
142
+ });
143
+ }
144
+ function inspectHarnessPolicy(host) {
145
+ const inspection = inspections.get(host);
146
+ if (!inspection)
147
+ throw new Error("POLICY_INSPECTION_HOST_UNAVAILABLE");
148
+ return structuredClone(inspection);
149
+ }
150
+
151
+ // src/approvals/approval-history.ts
152
+ import { createHash as createHash2 } from "node:crypto";
153
+ import { z as z2 } from "zod";
154
+ var APPROVAL_HISTORY_KEY = "clientApprovalDecisionsV1";
155
+ var canonical = (value) => Array.isArray(value) ? `[${value.map(canonical).join(",")}]` : value && typeof value === "object" ? `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}` : JSON.stringify(value) ?? "null";
156
+ var approvalInputDigest = (value) => createHash2("sha256").update(canonical(value)).digest("hex");
157
+ var approvalProvenanceSchema = z2.strictObject({
158
+ schemaVersion: z2.literal(1),
159
+ origin: z2.enum(["interactive", "automatic", "application", "unknown"]),
160
+ channel: z2.string().regex(/^[a-z][a-z0-9._-]{0,79}$/),
161
+ policyDigest: z2.string().regex(/^sha256:[a-f0-9]{64}$/).nullable()
162
+ });
163
+ var rowSchema = z2.object({ approvalId: z2.string().max(256), digest: z2.string().regex(/^[a-f0-9]{64}$/), toolCallId: z2.string().max(256).optional(), name: z2.string().max(256), inputDigest: z2.string().regex(/^[a-f0-9]{64}$/).optional(), approved: z2.boolean(), decidedAt: z2.number().int().nonnegative(), reviewedRevision: z2.number().int().nonnegative(), provenance: approvalProvenanceSchema.optional() }).strict();
164
+ function readApprovalDecisions(state) {
165
+ const value = state.metadata?.[APPROVAL_HISTORY_KEY];
166
+ if (value === undefined)
167
+ return [];
168
+ return z2.array(rowSchema).max(512).parse(value).map((row) => ({ ...row, provenance: row.provenance ?? {
169
+ schemaVersion: 1,
170
+ origin: "unknown",
171
+ channel: "unknown",
172
+ policyDigest: null
173
+ } }));
174
+ }
175
+ function approvalDecisionViews(state, journal, offset = 0) {
176
+ return readApprovalDecisions(state).slice(offset, offset + 25).map((row) => {
177
+ if (!row.approved)
178
+ return { ...row, status: "rejected" };
179
+ const entry = journal.find((entry2) => entry2.runId === state.runId && entry2.providerToolCallId === row.toolCallId && entry2.toolName === row.name && row.inputDigest !== undefined && approvalInputDigest(entry2.input) === row.inputDigest);
180
+ if (!entry)
181
+ return { ...row, status: ["created", "running", "waiting_approval", "cancel_requested"].includes(state.status) ? "approved" : "unknown" };
182
+ const evidence = { journalRevision: entry.revision, ...entry.completedAt === undefined ? {} : { completedAt: entry.completedAt } };
183
+ if (entry.status === "failed")
184
+ return { ...row, status: "failed", evidence };
185
+ if (entry.status !== "completed")
186
+ return { ...row, status: "unknown", evidence };
187
+ const out = entry.output && typeof entry.output === "object" && !Array.isArray(entry.output) ? entry.output : {};
188
+ if (row.name === "run_check") {
189
+ if (typeof out.exitCode !== "number" || !Number.isInteger(out.exitCode))
190
+ return { ...row, status: "unknown", evidence };
191
+ evidence.exitCode = out.exitCode;
192
+ if (typeof out.timedOut === "boolean")
193
+ evidence.timedOut = out.timedOut;
194
+ return { ...row, status: out.exitCode === 0 && !out.timedOut ? "succeeded" : "failed", evidence };
195
+ }
196
+ if (["apply_environment_patch", "verify_and_apply_environment_patch", "verify_and_apply_reviewed_edits"].includes(row.name)) {
197
+ const sha = z2.string().regex(/^sha256:[a-f0-9]{64}$/);
198
+ const importedSchema = z2.object({ kind: z2.literal("environment-patch-import"), patchId: sha, runId: z2.string(), changes: z2.array(z2.object({ path: z2.string().max(1024), beforeDigest: sha.optional(), afterDigest: sha.optional() })).max(50) });
199
+ const args = entry.input && typeof entry.input === "object" && !Array.isArray(entry.input) ? entry.input : {};
200
+ const verified = row.name !== "apply_environment_patch";
201
+ const imported = importedSchema.safeParse(verified ? out.imported : out);
202
+ if (!imported.success || imported.data.runId !== state.runId)
203
+ return { ...row, status: "unknown", evidence };
204
+ if (verified) {
205
+ const proof = z2.object({ kind: z2.enum(["verified-environment-patch-import", "verified-reviewed-edit-import"]), patchId: sha, proposalId: sha.optional(), verification: z2.object({ command: z2.array(z2.string()).min(1).max(257), exitCode: z2.literal(0), timedOut: z2.literal(false) }) }).safeParse(out);
206
+ if (!proof.success || proof.data.patchId !== imported.data.patchId || JSON.stringify(proof.data.verification.command) !== JSON.stringify([args.command, ...Array.isArray(args.args) ? args.args : []]))
207
+ return { ...row, status: "unknown", evidence };
208
+ if (row.name === "verify_and_apply_reviewed_edits") {
209
+ const changes = editChangesSchema.safeParse(args.changes);
210
+ if (!changes.success || proof.data.kind !== "verified-reviewed-edit-import" || createEditProposal({ changes: changes.data }).proposalId !== proof.data.proposalId)
211
+ return { ...row, status: "unknown", evidence };
212
+ evidence.reviewedProposalId = proof.data.proposalId;
213
+ } else if (proof.data.kind !== "verified-environment-patch-import" || args.patchId !== proof.data.patchId)
214
+ return { ...row, status: "unknown", evidence };
215
+ evidence.verifiedPatchId = proof.data.patchId;
216
+ evidence.command = proof.data.verification.command;
217
+ evidence.exitCode = 0;
218
+ evidence.timedOut = false;
219
+ } else if (args.patchId !== imported.data.patchId)
220
+ return { ...row, status: "unknown", evidence };
221
+ evidence.proposalId = imported.data.patchId;
222
+ evidence.effects = imported.data.changes.map((change) => ({ ...change, beforeDigest: change.beforeDigest ?? null, afterDigest: change.afterDigest ?? null }));
223
+ return { ...row, status: "applied", evidence };
224
+ }
225
+ if (["apply_patch", "apply_reviewed_edits", "apply_reviewed_replacement"].includes(row.name)) {
226
+ const receipt = z2.object({ kind: z2.literal("patch-result"), result: z2.object({ proposalId: z2.string().regex(/^sha256:[a-f0-9]{64}$/), changes: z2.array(z2.object({ path: z2.string().max(1024), beforeDigest: z2.string().regex(/^sha256:[a-f0-9]{64}$/).nullable().optional(), afterDigest: z2.string().regex(/^sha256:[a-f0-9]{64}$/).nullable() })).min(1).max(50) }) }).safeParse(out);
227
+ if (!receipt.success)
228
+ return { ...row, status: "unknown", evidence };
229
+ evidence.proposalId = receipt.data.result.proposalId;
230
+ evidence.effects = receipt.data.result.changes.map((change) => ({ ...change, beforeDigest: change.beforeDigest ?? null }));
231
+ return { ...row, status: "applied", evidence };
232
+ }
233
+ return { ...row, status: "succeeded", evidence };
234
+ });
235
+ }
236
+
237
+ // src/runtime/policy-decisions.ts
238
+ import { AsyncLocalStorage } from "node:async_hooks";
239
+ import { z as z3 } from "zod";
240
+ var harnessPolicyDecisionEventSchema = z3.strictObject({
241
+ schemaVersion: z3.literal(1),
242
+ type: z3.literal("policy-decision"),
243
+ phase: z3.enum(["approval-request", "tool-entry"]).default("tool-entry"),
244
+ toolName: z3.string().max(512),
245
+ decision: z3.enum(["allow", "ask_user", "deny"]),
246
+ ruleIds: z3.array(z3.string().max(512)).max(128),
247
+ reason: z3.string().max(4096),
248
+ reasonTruncated: z3.boolean(),
249
+ policyDigest: z3.string().regex(/^sha256:[a-f0-9]{64}$/).nullable(),
250
+ source: z3.enum(["operator-file", "application", "baseline"]),
251
+ approvalRequired: z3.boolean(),
252
+ explicitReviewRequired: z3.boolean(),
253
+ executionBackend: z3.enum(["none", "oci"]),
254
+ evidence: z3.literal("policy-evaluation")
255
+ });
256
+ var observers = new AsyncLocalStorage;
257
+ var observeHarnessPolicyDecisions = (observer, work) => observers.run(observer, work);
258
+ async function publishHarnessPolicyDecision(host, toolName, decision, phase = "tool-entry") {
259
+ const observer = observers.getStore();
260
+ if (!observer)
261
+ return;
262
+ const view = inspectHarnessPolicy(host);
263
+ const reason = sanitizePolicyText(decision.reason);
264
+ const event = harnessPolicyDecisionEventSchema.parse({
265
+ schemaVersion: 1,
266
+ type: "policy-decision",
267
+ phase,
268
+ toolName: sanitizePolicyText(toolName).slice(0, 512),
269
+ decision: decision.decision,
270
+ ruleIds: decision.ruleIds.map((id) => sanitizePolicyText(id).slice(0, 512)),
271
+ reason: reason.slice(0, 4096),
272
+ reasonTruncated: reason.length > 4096,
273
+ policyDigest: view.digest,
274
+ source: view.source,
275
+ approvalRequired: decision.decision === "ask_user",
276
+ explicitReviewRequired: decision.decision === "ask_user" && view.explicitReviewRequired,
277
+ executionBackend: view.execution.activeBackend,
278
+ evidence: "policy-evaluation"
279
+ });
280
+ await observer(event);
281
+ }
282
+ function observeBaselineToolPolicy(tools, host) {
283
+ const result = {};
284
+ for (const [name, definition] of Object.entries(tools)) {
285
+ const execute = "execute" in definition ? definition.execute : undefined;
286
+ result[name] = { ...definition, ...execute ? { execute: async (input, context) => {
287
+ const owner = host();
288
+ if (owner)
289
+ await publishHarnessPolicyDecision(owner, name, {
290
+ decision: definition.requiresApproval === true ? "ask_user" : "allow",
291
+ ruleIds: [],
292
+ reason: definition.requiresApproval === true ? "Existing tool approval is required; entering the executor after approval." : "No additional application restriction; tool permission checks still apply."
293
+ });
294
+ return execute(input, context);
295
+ } } : {} };
296
+ }
297
+ return result;
298
+ }
299
+ async function publishPendingPolicyDecision(host, toolName, promoted) {
300
+ if (!observers.getStore())
301
+ return;
302
+ const view = inspectHarnessPolicy(host);
303
+ const rules = view.restrictions.filter((rule) => rule.tools.includes(toolName) && rule.decision === "ask_user");
304
+ await publishHarnessPolicyDecision(host, toolName, {
305
+ decision: "ask_user",
306
+ ruleIds: rules.map((rule) => rule.ruleId),
307
+ reason: promoted ? "Approval promoted from a child run; review is pending and no execution is certified." : rules.length ? rules.map((rule) => rule.reason).join("; ") + (rules.some((rule) => rule.pathCount > 0) ? "; Path-scoped ask rules conservatively require review for this tool." : "") : "Existing tool approval is required; review is pending and no execution is certified."
308
+ }, "approval-request");
309
+ }
310
+ function createPolicyDecisionEvidence() {
311
+ const events = [];
312
+ let observed = 0;
313
+ let bytes = 0;
314
+ return {
315
+ append(event) {
316
+ observed++;
317
+ const size = Buffer.byteLength(JSON.stringify(event));
318
+ if (events.length < 64 && bytes + size <= 256 * 1024) {
319
+ events.push(structuredClone(event));
320
+ bytes += size;
321
+ }
322
+ },
323
+ snapshot() {
324
+ return { schemaVersion: 1, scope: "invocation", observed, truncated: observed > events.length, events: structuredClone(events) };
325
+ }
326
+ };
327
+ }
328
+
329
+ export { APPROVAL_HISTORY_KEY, approvalInputDigest, readApprovalDecisions, approvalDecisionViews, harnessToolPolicySchema, createHarnessToolPolicy, applyHarnessToolPolicy, bindHarnessPolicyInspection, inspectHarnessPolicy, harnessPolicyDecisionEventSchema, observeHarnessPolicyDecisions, publishHarnessPolicyDecision, observeBaselineToolPolicy, publishPendingPolicyDecision, createPolicyDecisionEvidence };