@zhivex-ai/harness 1.4.0 → 1.4.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.
- package/CHANGELOG.md +29 -10
- package/README.md +7 -5
- package/SUPPORT.md +2 -2
- package/contracts/client-protocol-v1.schema.json +593 -0
- package/contracts/engine-api.json +156 -0
- package/contracts/public-api.json +12 -0
- package/contracts/stable-api-signatures.json +33 -33
- package/dist/acp-cli.js +54 -20
- package/dist/audit/index.d.ts +3 -0
- package/dist/audit/index.js +482 -0
- package/dist/audit/pack.d.ts +72 -0
- package/dist/audit-pack-e4vaq7np.js +532 -0
- package/dist/cli/arguments.d.ts +3 -1
- package/dist/cli/audit-pack.d.ts +24 -0
- package/dist/cli/cli-credentials.d.ts +6 -0
- package/dist/cli/cli-profiles.d.ts +4 -2
- package/dist/cli/node-engine.d.ts +2 -0
- package/dist/{cli-2h37qyvg.js → cli-1cf4dcnj.js} +245 -642
- package/dist/{cli-entry-w0hterjs.js → cli-entry-3s63zn4w.js} +40 -13
- package/dist/cli-entry-7f0cen32.js +9990 -0
- package/dist/cli-entry-a890x3kq.js +10348 -0
- package/dist/{cli-entry-20w6nh0k.js → cli-entry-hdtfef27.js} +439 -19
- package/dist/cli-entry-rgs6bxmy.js +484 -0
- package/dist/cli.js +27 -2
- package/dist/client/json-contracts.d.ts +4 -0
- package/dist/client/protocol.d.ts +133 -0
- package/dist/client/task-control-host.d.ts +400 -0
- package/dist/client/task-projection-host.d.ts +12 -0
- package/dist/client/task-projection.d.ts +168 -0
- package/dist/compat/code-support.js +28 -22
- package/dist/{config-qt69hkqj.js → config-r9fdhce6.js} +3 -3
- package/dist/{config-j9624t6t.js → config-vyajmksy.js} +1 -1
- package/dist/engine/acp.js +15 -11
- package/dist/engine/client.js +14 -10
- package/dist/engine/desktop-providers-v1.d.ts +2 -0
- package/dist/engine/desktop-providers-v1.js +23 -3
- package/dist/engine/desktop-state-v1.js +11 -8
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.js +101 -554
- package/dist/engine/mcp-stdio-v1.js +8 -6
- package/dist/engine/models.js +76 -4
- package/dist/engine/protocol.d.ts +2 -0
- package/dist/engine/protocol.js +110 -0
- package/dist/engine/service.js +15 -11
- package/dist/hostile-repository-demo.js +1095 -505
- package/dist/{index-937cb7y8.js → index-02pqtbbg.js} +559 -33
- package/dist/{index-pp474137.js → index-0zs2dz26.js} +766 -931
- package/dist/{index-8zjmnyw5.js → index-1265j7t2.js} +73 -6
- package/dist/index-4fx5f3k5.js +269 -0
- package/dist/{index-nmjdwd4p.js → index-4gt045j0.js} +109 -1
- package/dist/{index-6dft2vev.js → index-6599mpmy.js} +5 -3
- package/dist/{index-tfzta544.js → index-74kkpyqk.js} +9 -7
- package/dist/{index-61gthdz6.js → index-8sm2a1wa.js} +1 -1
- package/dist/index-c39197wq.js +552 -0
- package/dist/{index-5jhgatga.js → index-crta5wzv.js} +7 -6
- package/dist/{index-m34q25jj.js → index-d9zkmhz3.js} +143 -824
- package/dist/{index-pdmevfxv.js → index-dtb2c6ds.js} +5 -259
- package/dist/index-f6mqa5gk.js +1535 -0
- package/dist/index-j6zxnc8m.js +490 -0
- package/dist/{index-rkbf7dqg.js → index-mv8sb1ns.js} +5 -3
- package/dist/{index-xqxeypt5.js → index-mzydbzp8.js} +76 -4
- package/dist/{index-wg1sd3at.js → index-qfsrrnz6.js} +1 -1
- package/dist/{index-m82cfspw.js → index-sk3cap35.js} +877 -963
- package/dist/index-sstgcsr0.js +511 -0
- package/dist/index-wm9xeq1n.js +329 -0
- package/dist/index-xdbkcccf.js +719 -0
- package/dist/{index-s2k2edts.js → index-zxg4ve4j.js} +41 -1548
- package/dist/index.d.ts +3 -0
- package/dist/index.js +93 -77
- package/dist/internal/desktop/providers.d.ts +2 -0
- package/dist/models/catalog.d.ts +2 -0
- package/dist/persistence/sqlite-database.d.ts +1 -0
- package/dist/providers/azure-openai.d.ts +14 -0
- package/dist/providers/bedrock.d.ts +23 -0
- package/dist/providers/providers.d.ts +1 -1
- package/dist/runtime/durable-review-group.d.ts +33 -0
- package/dist/runtime/harness.d.ts +11 -0
- package/dist/runtime/run-interruption.d.ts +7 -0
- package/dist/runtime/shared-budget.d.ts +30 -0
- package/dist/runtime/task-continuity.d.ts +300 -77
- package/dist/service-cli.js +15 -11
- package/dist/{service-client-cli-fn54ev6w.js → service-client-cli-eqrdc9zq.js} +400 -305
- package/dist/verify-historical-migrations.js +1095 -505
- package/dist/zhx.js +27 -2
- package/docs/AUDIT_PACK.md +99 -0
- package/docs/AZURE_OPENAI.md +53 -0
- package/docs/BEDROCK.md +56 -0
- package/docs/CLI.md +15 -0
- package/docs/CLIENT_PROTOCOL.md +178 -0
- package/docs/CODE_RELEASE.md +31 -1
- package/docs/CODE_WEB_TASKS.md +116 -0
- package/docs/CONTEXT_ENGINEERING.md +59 -0
- package/docs/CREDENTIALS.md +21 -0
- package/docs/DURABLE_OPERATIONS.md +93 -1
- package/docs/ENGINE_API.md +5 -2
- package/docs/EXTENSIBILITY.md +2 -2
- package/docs/FIRST_USE.md +6 -6
- package/docs/MINIMUM_TASK_AUTHORITY.md +3 -3
- package/docs/PROJECT_MEMORY.md +1 -1
- package/docs/README.md +6 -1
- package/docs/STABILITY.md +3 -1
- package/docs/SUPPORT_MATRIX.md +1 -1
- package/docs/SUPPORT_MATRIX_1_1.md +1 -1
- package/docs/USAGE.md +3 -1
- package/docs/VERTEX.md +2 -2
- package/docs/WEB_LIMITS.md +4 -3
- package/docs/adr/0002-isolated-mcp-stdio.md +6 -3
- package/docs/support-matrix-1.1.json +3 -1
- package/docs/support-matrix.json +6 -4
- package/evaluations/code-task-ui.json +20 -0
- package/evaluations/representative-assembly-matrix.json +10 -1
- package/evaluations/task-projection.json +10 -0
- package/evaluations/task-recovery-lab.json +60 -0
- package/examples/README.md +3 -3
- package/package.json +23 -13
- package/dist/cli-entry-4ngptr62.js +0 -19900
- package/dist/index-m49jx72n.js +0 -220
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEditProposal,
|
|
3
|
+
editChangesSchema,
|
|
4
|
+
workspaceFilePathSchema
|
|
5
|
+
} from "./index-xdbkcccf.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 };
|