@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.
- package/CHANGELOG.md +3 -0
- package/README.md +1 -1
- package/SUPPORT.md +1 -1
- package/contracts/stable-api-signatures.json +7 -7
- package/dist/acp-cli.js +10 -10
- package/dist/approvals/approval-diff.d.ts +2 -1
- package/dist/approvals/approval-preview.d.ts +1 -1
- package/dist/audit/index.js +6 -6
- package/dist/{audit-pack-e4vaq7np.js → audit-pack-kvw7ng34.js} +3 -3
- package/dist/{cli-entry-rgs6bxmy.js → cli-entry-75j3r49b.js} +2 -2
- package/dist/{cli-entry-7f0cen32.js → cli-entry-928p8h41.js} +135 -58
- package/dist/{cli-entry-a890x3kq.js → cli-entry-h2yc9x37.js} +7 -5
- package/dist/{cli-entry-3s63zn4w.js → cli-entry-q8bgkn9m.js} +1 -1
- package/dist/{cli-1cf4dcnj.js → cli-vyrytn3m.js} +14 -8
- package/dist/cli.js +2 -2
- package/dist/compat/code-support.js +8 -8
- package/dist/engine/acp.js +11 -11
- package/dist/engine/client.js +10 -10
- package/dist/engine/desktop-state-v1.js +6 -6
- package/dist/engine/index.js +15 -15
- package/dist/engine/mcp-stdio-v1.js +5 -5
- package/dist/engine/service.js +11 -11
- package/dist/hostile-repository-demo.js +138 -60
- package/dist/{index-dtb2c6ds.js → index-5zbq9ztj.js} +2 -2
- package/dist/{index-6599mpmy.js → index-63gbp0cz.js} +3 -3
- package/dist/{index-xdbkcccf.js → index-6dxrbnxm.js} +2 -2
- package/dist/{index-0zs2dz26.js → index-6fp7kdpx.js} +10 -9
- package/dist/{index-mv8sb1ns.js → index-96ezrvg2.js} +55 -13
- package/dist/{index-wm9xeq1n.js → index-bt2p8x7r.js} +1 -1
- package/dist/{index-sk3cap35.js → index-eps3c3g6.js} +1 -1
- package/dist/{index-zxg4ve4j.js → index-fqqz1yt8.js} +77 -4
- package/dist/{index-d9zkmhz3.js → index-ggstwr0v.js} +53 -18
- package/dist/{index-j6zxnc8m.js → index-jpdvn09j.js} +5 -5
- package/dist/{index-4fx5f3k5.js → index-pq3kz1r8.js} +1 -1
- package/dist/{index-02pqtbbg.js → index-vc7yxz50.js} +10 -10
- package/dist/{index-crta5wzv.js → index-vvzc5ezz.js} +4 -4
- package/dist/{index-f6mqa5gk.js → index-wwthv10a.js} +11 -39
- package/dist/{index-74kkpyqk.js → index-xfedewpt.js} +12 -6
- package/dist/{index-sstgcsr0.js → index-yx52bt4a.js} +1 -1
- package/dist/index.js +14 -14
- package/dist/integrations/mcp-stdio-tools.d.ts +4 -1
- package/dist/runtime/budget-ledger-scope.d.ts +14 -0
- package/dist/service-cli.js +11 -11
- package/dist/{service-client-cli-eqrdc9zq.js → service-client-cli-v6qptg9x.js} +4 -4
- package/dist/tools/workspace.d.ts +2 -2
- package/dist/verify-historical-migrations.js +143 -62
- package/dist/workspace/edit-contracts.d.ts +2 -2
- package/dist/workspace/path-policy.d.ts +2 -1
- package/dist/workspace/workspace.d.ts +13 -0
- package/dist/zhx.js +2 -2
- package/docs/CODE_RELEASE.md +28 -6
- package/docs/CODE_WEB_TASKS.md +1 -1
- package/docs/ENGINE_API.md +1 -1
- package/docs/EXTENSIBILITY.md +6 -2
- package/docs/FIRST_USE.md +6 -6
- package/docs/PROJECT_MEMORY.md +1 -1
- package/docs/PUBLIC_SECURITY.md +14 -1
- package/docs/REPOSITORY_EDITING.md +1 -1
- package/docs/TOOL_POLICY.md +4 -1
- package/docs/adr/0002-isolated-mcp-stdio.md +1 -1
- package/evaluations/representative-assembly-matrix.json +10 -1
- package/examples/README.md +3 -3
- package/package.json +1 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { type McpClient, type ToolSet } from '@zhivex-ai/core';
|
|
2
2
|
import type { McpStdioLaunchProposal } from './mcp-stdio-admission.js';
|
|
3
3
|
import { createJournaledMcpClient } from '../persistence/mcp-execution-journal.js';
|
|
4
|
-
/** Host tool bridge. Existing agent execution supplies durable interrupt approvals.
|
|
4
|
+
/** Host tool bridge. Tool results are untrusted data. Existing agent execution supplies durable interrupt approvals.
|
|
5
|
+
* flagEncodedSecrets is optional. Credential-bearing servers use it to mark encodings and high-entropy tokens; it does not rewrite discovery schemas. */
|
|
5
6
|
export declare function createIsolatedMcpTools(options: {
|
|
6
7
|
client: McpClient & {
|
|
7
8
|
close(): Promise<void>;
|
|
8
9
|
};
|
|
9
10
|
proposal: Readonly<McpStdioLaunchProposal>;
|
|
10
11
|
sensitiveValues?: readonly string[];
|
|
12
|
+
/** When set, tool results flag known encodings of sensitiveValues and other high-entropy encoded strings. */
|
|
13
|
+
flagEncodedSecrets?: boolean;
|
|
11
14
|
journal?: Pick<Parameters<typeof createJournaledMcpClient>[0], 'store' | 'runId' | 'scope'>;
|
|
12
15
|
}): Promise<ToolSet>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AgentRunStore, AgentStoreScope } from "@zhivex-ai/core";
|
|
2
|
+
/** SDK coordinators address ledgers with this namespace. The durable owner appends the source namespace. */
|
|
3
|
+
export declare const BUDGET_LEDGER_NAMESPACE = "__zhivex_budget__";
|
|
4
|
+
export declare const BUDGET_OWNER_NAMESPACE_KEY = "budgetOwnerNamespace";
|
|
5
|
+
export declare const isReservedBudgetNamespace: (namespace: string | undefined) => boolean;
|
|
6
|
+
/** Bind the caller's full namespace into the ledger owner. Collapsing every namespace to the SDK marker lets one tenant read another's ledger by run id. */
|
|
7
|
+
export declare const budgetLedgerNamespace: (namespace: string) => string;
|
|
8
|
+
/**
|
|
9
|
+
* A named source becomes `__zhivex_budget__:<namespace>`.
|
|
10
|
+
* An omitted namespace uses the trailing-colon owner, which `budgetLedgerNamespace` never produces, so it cannot join a named ledger.
|
|
11
|
+
*/
|
|
12
|
+
export declare const budgetLedgerScope: (scope: Pick<AgentStoreScope, "tenantId" | "userId" | "namespace">) => AgentStoreScope;
|
|
13
|
+
/** Translate the SDK's collapsed namespace onto the bound owner before the file or SQLite store. */
|
|
14
|
+
export declare const bindBudgetLedgerStore: (primary: AgentRunStore, scope: AgentStoreScope, budget?: AgentRunStore) => AgentRunStore;
|
package/dist/service-cli.js
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
import {
|
|
3
3
|
recoverHarnessLocalService,
|
|
4
4
|
startHarnessLocalService
|
|
5
|
-
} from "./index-
|
|
6
|
-
import"./index-
|
|
7
|
-
import"./index-
|
|
5
|
+
} from "./index-vvzc5ezz.js";
|
|
6
|
+
import"./index-vc7yxz50.js";
|
|
7
|
+
import"./index-63gbp0cz.js";
|
|
8
8
|
import {
|
|
9
9
|
createHarness
|
|
10
|
-
} from "./index-
|
|
11
|
-
import"./index-
|
|
12
|
-
import"./index-
|
|
13
|
-
import"./index-
|
|
10
|
+
} from "./index-6fp7kdpx.js";
|
|
11
|
+
import"./index-wwthv10a.js";
|
|
12
|
+
import"./index-5zbq9ztj.js";
|
|
13
|
+
import"./index-ggstwr0v.js";
|
|
14
14
|
import"./index-1265j7t2.js";
|
|
15
15
|
import"./index-c39197wq.js";
|
|
16
16
|
import"./index-mzydbzp8.js";
|
|
17
|
-
import"./index-
|
|
18
|
-
import"./index-
|
|
19
|
-
import"./index-
|
|
20
|
-
import"./index-
|
|
17
|
+
import"./index-bt2p8x7r.js";
|
|
18
|
+
import"./index-fqqz1yt8.js";
|
|
19
|
+
import"./index-pq3kz1r8.js";
|
|
20
|
+
import"./index-6dxrbnxm.js";
|
|
21
21
|
import"./index-4gt045j0.js";
|
|
22
22
|
import"./index-bpc596v3.js";
|
|
23
23
|
import"./index-fm84men1.js";
|
|
@@ -9,17 +9,17 @@ import {
|
|
|
9
9
|
navigateConsole,
|
|
10
10
|
printPolicyInspection,
|
|
11
11
|
sanitizeTerminalText
|
|
12
|
-
} from "./cli-entry-
|
|
12
|
+
} from "./cli-entry-h2yc9x37.js";
|
|
13
13
|
import {
|
|
14
14
|
CLI_EVENT_SCHEMA_VERSION,
|
|
15
15
|
CLI_JSON_SCHEMA_VERSION,
|
|
16
16
|
changeEnvelopeSchema,
|
|
17
17
|
harnessPolicyDecisionEventSchema,
|
|
18
18
|
serializeStreamResult
|
|
19
|
-
} from "./cli-entry-
|
|
19
|
+
} from "./cli-entry-928p8h41.js";
|
|
20
20
|
import {
|
|
21
21
|
HARNESS_VERSION
|
|
22
|
-
} from "./cli-entry-
|
|
22
|
+
} from "./cli-entry-q8bgkn9m.js";
|
|
23
23
|
import {
|
|
24
24
|
HARNESS_ERROR_CODES,
|
|
25
25
|
HARNESS_ERROR_SCHEMA_VERSION,
|
|
@@ -195,7 +195,7 @@ var harnessTaskProjectionSchema = z2.strictObject({
|
|
|
195
195
|
// src/approvals/approval-diff.ts
|
|
196
196
|
import { z as z3 } from "zod";
|
|
197
197
|
var sha = z3.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
198
|
-
var file = z3.object({ path: z3.string().max(1024), expectedDigest: sha.nullable(), before: z3.string().nullable(), after: z3.string().nullable(), afterDigest: sha.nullable(), beforeMode: z3.number().int().optional(), afterMode: z3.number().int().optional(), operation: z3.enum(["create", "update", "delete"]).optional() }).strict();
|
|
198
|
+
var file = z3.object({ path: z3.string().max(1024), expectedDigest: sha.nullable(), before: z3.string().nullable(), after: z3.string().nullable(), afterDigest: sha.nullable(), beforeMode: z3.number().int().optional(), afterMode: z3.number().int().optional(), operation: z3.enum(["create", "update", "delete", "restore"]).optional() }).strict();
|
|
199
199
|
var archive = z3.object({ approvalId: z3.string().max(256), digest: z3.string().regex(/^[a-f0-9]{64}$/), proposalId: sha, files: z3.array(file).min(1).max(50) }).strict();
|
|
200
200
|
var archives = z3.array(archive).max(512);
|
|
201
201
|
var maxBytes = 2 * 1024 * 1024;
|
|
@@ -89,8 +89,8 @@ export declare const createWorkspaceTools: (workspace: Workspace, allowedChecks:
|
|
|
89
89
|
}, z.core.$strict>, import("@zhivex-ai/core").JsonValue, unknown>;
|
|
90
90
|
restore_file: import("@zhivex-ai/core").ToolDefinition<z.ZodObject<{
|
|
91
91
|
quarantineId: z.ZodString;
|
|
92
|
-
destination: z.
|
|
93
|
-
expectedDigest: z.
|
|
92
|
+
destination: z.ZodString;
|
|
93
|
+
expectedDigest: z.ZodString;
|
|
94
94
|
}, z.core.$strict>, import("@zhivex-ai/core").JsonValue, unknown>;
|
|
95
95
|
run_check: import("@zhivex-ai/core").ToolDefinition<z.ZodObject<{
|
|
96
96
|
check: z.ZodString;
|
|
@@ -5567,8 +5567,8 @@ var quarantineFileInputSchema = z11.strictObject({
|
|
|
5567
5567
|
});
|
|
5568
5568
|
var restoreFileInputSchema = z11.strictObject({
|
|
5569
5569
|
quarantineId: z11.string().min(1).max(200).regex(/^[A-Za-z0-9._:-]+$/),
|
|
5570
|
-
destination: workspaceFilePathSchema.
|
|
5571
|
-
expectedDigest: fileDigestSchema.
|
|
5570
|
+
destination: workspaceFilePathSchema.describe("Required workspace path for the quarantined bytes. Restore does not fall back to the original path."),
|
|
5571
|
+
expectedDigest: fileDigestSchema.describe("Required sha256 of the quarantined bytes the operator reviewed.")
|
|
5572
5572
|
});
|
|
5573
5573
|
var mutationOperationSchema = z11.enum(["create", "update", "move", "quarantine", "restore"]);
|
|
5574
5574
|
var mutationAuditEntrySchema = z11.strictObject({
|
|
@@ -5908,10 +5908,10 @@ var runPortableProcess = async (command, options = {}) => {
|
|
|
5908
5908
|
// src/workspace/path-policy.ts
|
|
5909
5909
|
var HARD_IGNORES = new Set([".git", ".next", ".turbo", ".zhivex-harness", "coverage", "dist", "node_modules"]);
|
|
5910
5910
|
var isSensitiveName = (name) => {
|
|
5911
|
-
const normalized = name.
|
|
5911
|
+
const normalized = name.toLowerCase();
|
|
5912
5912
|
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));
|
|
5913
5913
|
};
|
|
5914
|
-
var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.
|
|
5914
|
+
var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.toLowerCase()) || isSensitiveName(segment));
|
|
5915
5915
|
|
|
5916
5916
|
// src/workspace/workspace.ts
|
|
5917
5917
|
var IGNORE_FILES = [".gitignore", ".zhivex-harnessignore"];
|
|
@@ -6191,7 +6191,7 @@ class Workspace {
|
|
|
6191
6191
|
throw new HarnessWorkspaceError(`The path escapes the workspace: ${relativePath}`);
|
|
6192
6192
|
const relative = wirePath(path11.relative(this.root, candidate));
|
|
6193
6193
|
if (relative && isHardIgnored(relative)) {
|
|
6194
|
-
const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.
|
|
6194
|
+
const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.toLowerCase()) || isSensitiveName(segment));
|
|
6195
6195
|
throw new HarnessWorkspaceError(`The path is protected by the harness policy: ${blocked}`);
|
|
6196
6196
|
}
|
|
6197
6197
|
return candidate;
|
|
@@ -7174,30 +7174,65 @@ class Workspace {
|
|
|
7174
7174
|
const audit = await this.audit({ operation: "quarantine", path: parsed.path, beforeDigest: source.digest, quarantineId });
|
|
7175
7175
|
return { quarantineId, path: parsed.path, digest: source.digest, audit };
|
|
7176
7176
|
}
|
|
7177
|
-
async
|
|
7178
|
-
return withWorkspaceMutation(this.root, () => this.restoreQuarantinedLocked(input));
|
|
7179
|
-
}
|
|
7180
|
-
async restoreQuarantinedLocked(input) {
|
|
7181
|
-
const parsed = restoreFileInputSchema.parse(input);
|
|
7182
|
-
this.checkAuditCapacity();
|
|
7177
|
+
async readRestorableQuarantine(quarantineId) {
|
|
7183
7178
|
const directory = await this.secureQuarantineDirectory(false);
|
|
7184
7179
|
let manifest;
|
|
7185
7180
|
try {
|
|
7186
|
-
const manifestFile = await this.readInternalRegularFile(path11.join(directory, `${
|
|
7181
|
+
const manifestFile = await this.readInternalRegularFile(path11.join(directory, `${quarantineId}.json`), "Quarantine manifest", 64 * 1024);
|
|
7187
7182
|
manifest = JSON.parse(manifestFile.contents.toString("utf8"));
|
|
7188
7183
|
} catch {
|
|
7189
|
-
throw new HarnessWorkspaceError(`Quarantine entry was not found: ${
|
|
7184
|
+
throw new HarnessWorkspaceError(`Quarantine entry was not found: ${quarantineId}.`);
|
|
7190
7185
|
}
|
|
7191
|
-
if (manifest.schemaVersion !== 1 || manifest.quarantineId !==
|
|
7192
|
-
throw new HarnessWorkspaceError(`Quarantine entry is not restorable: ${
|
|
7186
|
+
if (manifest.schemaVersion !== 1 || manifest.quarantineId !== quarantineId || manifest.status !== "quarantined") {
|
|
7187
|
+
throw new HarnessWorkspaceError(`Quarantine entry is not restorable: ${quarantineId}.`);
|
|
7193
7188
|
}
|
|
7194
|
-
const dataPath = path11.join(directory, `${
|
|
7189
|
+
const dataPath = path11.join(directory, `${quarantineId}.data`);
|
|
7195
7190
|
const dataFile = await this.readInternalRegularFile(dataPath, "Quarantine payload", MAX_FILE_BYTES);
|
|
7196
7191
|
const digest3 = digestBytes(dataFile.contents);
|
|
7197
|
-
if (digest3 !== manifest.digest
|
|
7192
|
+
if (digest3 !== manifest.digest) {
|
|
7193
|
+
throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${quarantineId}.`);
|
|
7194
|
+
}
|
|
7195
|
+
return { manifest, dataPath, dataFile, digest: digest3 };
|
|
7196
|
+
}
|
|
7197
|
+
async previewRestore(input) {
|
|
7198
|
+
const parsed = restoreFileInputSchema.parse(input);
|
|
7199
|
+
const quarantined = await this.readRestorableQuarantine(parsed.quarantineId);
|
|
7200
|
+
if (parsed.expectedDigest !== quarantined.digest) {
|
|
7198
7201
|
throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${parsed.quarantineId}.`);
|
|
7199
7202
|
}
|
|
7200
|
-
const
|
|
7203
|
+
const destination = await this.safePath(parsed.destination, { allowMissing: true });
|
|
7204
|
+
if (destination.exists)
|
|
7205
|
+
throw new HarnessWorkspaceError(`Restore destination already exists: ${parsed.destination}.`);
|
|
7206
|
+
let after;
|
|
7207
|
+
try {
|
|
7208
|
+
after = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(quarantined.dataFile.contents);
|
|
7209
|
+
} catch {
|
|
7210
|
+
throw new HarnessWorkspaceError("Preview requires valid UTF-8 text.");
|
|
7211
|
+
}
|
|
7212
|
+
return {
|
|
7213
|
+
proposalId: parsed.expectedDigest,
|
|
7214
|
+
files: [{
|
|
7215
|
+
path: parsed.destination,
|
|
7216
|
+
expectedDigest: parsed.expectedDigest,
|
|
7217
|
+
before: null,
|
|
7218
|
+
after,
|
|
7219
|
+
afterDigest: quarantined.digest,
|
|
7220
|
+
operation: "restore"
|
|
7221
|
+
}]
|
|
7222
|
+
};
|
|
7223
|
+
}
|
|
7224
|
+
async restoreQuarantined(input) {
|
|
7225
|
+
return withWorkspaceMutation(this.root, () => this.restoreQuarantinedLocked(input));
|
|
7226
|
+
}
|
|
7227
|
+
async restoreQuarantinedLocked(input) {
|
|
7228
|
+
const parsed = restoreFileInputSchema.parse(input);
|
|
7229
|
+
this.checkAuditCapacity();
|
|
7230
|
+
const quarantined = await this.readRestorableQuarantine(parsed.quarantineId);
|
|
7231
|
+
if (parsed.expectedDigest !== quarantined.digest) {
|
|
7232
|
+
throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${parsed.quarantineId}.`);
|
|
7233
|
+
}
|
|
7234
|
+
const { manifest, dataPath, dataFile, digest: digest3 } = quarantined;
|
|
7235
|
+
const destinationPath = parsed.destination;
|
|
7201
7236
|
const destination = await this.safePath(destinationPath, { allowMissing: true });
|
|
7202
7237
|
if (destination.exists)
|
|
7203
7238
|
throw new HarnessWorkspaceError(`Restore destination already exists: ${destinationPath}.`);
|
|
@@ -8420,7 +8455,7 @@ var importPatch = async (runId, expectedPatchId, host, base, current2, maxFileWr
|
|
|
8420
8455
|
const rollbackErrors = [];
|
|
8421
8456
|
for (const item of [...quarantined].reverse()) {
|
|
8422
8457
|
try {
|
|
8423
|
-
await host.restoreQuarantined({ quarantineId: item.quarantineId, expectedDigest: item.digest });
|
|
8458
|
+
await host.restoreQuarantined({ quarantineId: item.quarantineId, destination: item.path, expectedDigest: item.digest });
|
|
8424
8459
|
} catch (rollbackError) {
|
|
8425
8460
|
rollbackErrors.push(rollbackError);
|
|
8426
8461
|
}
|
|
@@ -9718,6 +9753,79 @@ import { createAgentRunLedger } from "@zhivex-ai/agents/control-plane";
|
|
|
9718
9753
|
import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
|
|
9719
9754
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
9720
9755
|
import { z as z18 } from "zod";
|
|
9756
|
+
|
|
9757
|
+
// src/runtime/budget-ledger-scope.ts
|
|
9758
|
+
var BUDGET_LEDGER_NAMESPACE = "__zhivex_budget__";
|
|
9759
|
+
var BUDGET_OWNER_NAMESPACE_KEY = "budgetOwnerNamespace";
|
|
9760
|
+
var isReservedBudgetNamespace = (namespace) => namespace === BUDGET_LEDGER_NAMESPACE || (namespace?.startsWith(`${BUDGET_LEDGER_NAMESPACE}:`) ?? false);
|
|
9761
|
+
var budgetLedgerNamespace = (namespace) => {
|
|
9762
|
+
if (!namespace || isReservedBudgetNamespace(namespace)) {
|
|
9763
|
+
throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
|
|
9764
|
+
}
|
|
9765
|
+
return `${BUDGET_LEDGER_NAMESPACE}:${namespace}`;
|
|
9766
|
+
};
|
|
9767
|
+
var budgetLedgerScope = (scope) => {
|
|
9768
|
+
if (scope.namespace && isReservedBudgetNamespace(scope.namespace)) {
|
|
9769
|
+
throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
|
|
9770
|
+
}
|
|
9771
|
+
return {
|
|
9772
|
+
tenantId: scope.tenantId,
|
|
9773
|
+
...scope.userId ? { userId: scope.userId } : {},
|
|
9774
|
+
namespace: scope.namespace ? budgetLedgerNamespace(scope.namespace) : `${BUDGET_LEDGER_NAMESPACE}:`
|
|
9775
|
+
};
|
|
9776
|
+
};
|
|
9777
|
+
var sameIdentity = (left, right) => left?.tenantId === right.tenantId && (left.userId ?? undefined) === (right.userId ?? undefined);
|
|
9778
|
+
var bindBudgetLedgerStore = (primary, scope, budget = primary) => {
|
|
9779
|
+
const bound = budgetLedgerScope(scope);
|
|
9780
|
+
const ownerNamespace = scope.namespace ?? "";
|
|
9781
|
+
const isSdkBudget = (value) => sameIdentity(value, bound) && value?.namespace === BUDGET_LEDGER_NAMESPACE;
|
|
9782
|
+
const isBoundBudget = (value) => sameIdentity(value, bound) && value?.namespace === bound.namespace;
|
|
9783
|
+
const assertId = (id) => {
|
|
9784
|
+
if (!/^budget_[a-f0-9]{64}$/.test(id))
|
|
9785
|
+
throw new HarnessStateConflictError("Invalid budget ledger identity.");
|
|
9786
|
+
};
|
|
9787
|
+
const bindState = (state) => {
|
|
9788
|
+
const metadata = { ...state.metadata ?? {} };
|
|
9789
|
+
const owner = metadata[BUDGET_OWNER_NAMESPACE_KEY];
|
|
9790
|
+
if (owner !== undefined && owner !== ownerNamespace) {
|
|
9791
|
+
throw new HarnessStateConflictError("Budget ledger is bound to another namespace.");
|
|
9792
|
+
}
|
|
9793
|
+
return {
|
|
9794
|
+
...state,
|
|
9795
|
+
scope: bound,
|
|
9796
|
+
metadata: { ...metadata, [BUDGET_OWNER_NAMESPACE_KEY]: ownerNamespace }
|
|
9797
|
+
};
|
|
9798
|
+
};
|
|
9799
|
+
const assertStored = (state) => {
|
|
9800
|
+
if (!state)
|
|
9801
|
+
return state;
|
|
9802
|
+
const owner = state.metadata?.[BUDGET_OWNER_NAMESPACE_KEY];
|
|
9803
|
+
if (state.scope?.namespace !== bound.namespace || owner !== ownerNamespace) {
|
|
9804
|
+
throw new HarnessStateConflictError("Budget ledger is not bound to this namespace.");
|
|
9805
|
+
}
|
|
9806
|
+
return state;
|
|
9807
|
+
};
|
|
9808
|
+
return {
|
|
9809
|
+
...primary,
|
|
9810
|
+
load: async (id, requestedScope) => {
|
|
9811
|
+
if (!isSdkBudget(requestedScope) && !isBoundBudget(requestedScope))
|
|
9812
|
+
return primary.load(id, requestedScope);
|
|
9813
|
+
assertId(id);
|
|
9814
|
+
return assertStored(await budget.load(id, bound));
|
|
9815
|
+
},
|
|
9816
|
+
save: async (state, options) => {
|
|
9817
|
+
if (!isSdkBudget(state.scope) && !isBoundBudget(state.scope))
|
|
9818
|
+
return primary.save(state, options);
|
|
9819
|
+
assertId(state.runId);
|
|
9820
|
+
if (state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true) {
|
|
9821
|
+
throw new HarnessStateConflictError("Only SDK budget ledger states may use the reserved namespace.");
|
|
9822
|
+
}
|
|
9823
|
+
return budget.save(bindState(state), options);
|
|
9824
|
+
}
|
|
9825
|
+
};
|
|
9826
|
+
};
|
|
9827
|
+
|
|
9828
|
+
// src/runtime/task-budget.ts
|
|
9721
9829
|
import {
|
|
9722
9830
|
createAgentBudgetCoordinator,
|
|
9723
9831
|
fingerprintAgentHarness as fingerprintAgentHarness2,
|
|
@@ -9836,10 +9944,10 @@ class TaskBudget {
|
|
|
9836
9944
|
this.accountRunId = TaskBudget.accountId(scope, taskId);
|
|
9837
9945
|
this.policy = structuredClone(account.policy);
|
|
9838
9946
|
const budgetId = this.accountRunId;
|
|
9839
|
-
this.coordinator = createAgentBudgetCoordinator({ store, scope, budgetId, limits: this.policy.limits });
|
|
9947
|
+
this.coordinator = createAgentBudgetCoordinator({ store: bindBudgetLedgerStore(store, scope), scope, budgetId, limits: this.policy.limits });
|
|
9840
9948
|
this.coordinatorId = this.coordinator.id;
|
|
9841
9949
|
this.budgetRunId = `budget_${fingerprintAgentHarness2({ budgetId, scope }).slice("sha256:".length)}`;
|
|
9842
|
-
this.budgetScope =
|
|
9950
|
+
this.budgetScope = budgetLedgerScope(scope);
|
|
9843
9951
|
}
|
|
9844
9952
|
static accountId(scope, taskId) {
|
|
9845
9953
|
return `task_budget_${fingerprintAgentHarness2({ scope, taskId }).slice("sha256:".length)}`;
|
|
@@ -10354,37 +10462,7 @@ class TaskBudget {
|
|
|
10354
10462
|
}
|
|
10355
10463
|
|
|
10356
10464
|
// src/persistence/operations.ts
|
|
10357
|
-
var
|
|
10358
|
-
tenantId: config.scope.tenantId,
|
|
10359
|
-
...config.scope.userId ? { userId: config.scope.userId } : {},
|
|
10360
|
-
namespace: "__zhivex_budget__"
|
|
10361
|
-
});
|
|
10362
|
-
var withBudgetLedgerStore = (primary, budget, config) => {
|
|
10363
|
-
const scope = budgetScopeFor(config);
|
|
10364
|
-
const budgetScope = (value) => value?.namespace === scope.namespace && value.tenantId === scope.tenantId && (value.userId ?? undefined) === (scope.userId ?? undefined);
|
|
10365
|
-
const assertId = (id) => {
|
|
10366
|
-
if (!/^budget_[a-f0-9]{64}$/.test(id))
|
|
10367
|
-
throw new HarnessStateConflictError("Invalid budget ledger identity.");
|
|
10368
|
-
};
|
|
10369
|
-
return {
|
|
10370
|
-
...primary,
|
|
10371
|
-
load: async (id, requestedScope) => {
|
|
10372
|
-
if (!budgetScope(requestedScope))
|
|
10373
|
-
return primary.load(id, requestedScope);
|
|
10374
|
-
assertId(id);
|
|
10375
|
-
return budget.load(id, requestedScope);
|
|
10376
|
-
},
|
|
10377
|
-
save: async (state, options) => {
|
|
10378
|
-
if (!budgetScope(state.scope))
|
|
10379
|
-
return primary.save(state, options);
|
|
10380
|
-
assertId(state.runId);
|
|
10381
|
-
if (state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true) {
|
|
10382
|
-
throw new HarnessStateConflictError("Only SDK budget ledger states may use the reserved namespace.");
|
|
10383
|
-
}
|
|
10384
|
-
return budget.save(state, options);
|
|
10385
|
-
}
|
|
10386
|
-
};
|
|
10387
|
-
};
|
|
10465
|
+
var withBudgetLedgerStore = (primary, budget, config) => bindBudgetLedgerStore(primary, config.scope, budget);
|
|
10388
10466
|
var HARNESS_SQLITE_FILE = "operations.sqlite";
|
|
10389
10467
|
var HARNESS_OPERATIONS_SCHEMA_VERSION = 1;
|
|
10390
10468
|
var runSummary = (state) => {
|
|
@@ -10555,13 +10633,12 @@ var migrateLegacyFileRuns = async (directory, target, config) => {
|
|
|
10555
10633
|
return result;
|
|
10556
10634
|
};
|
|
10557
10635
|
var openHarnessPersistence = async (config, options = {}) => {
|
|
10558
|
-
|
|
10559
|
-
throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
|
|
10636
|
+
const budgetScope = budgetLedgerScope(config.scope);
|
|
10560
10637
|
await validateStateDirectory(config.workspace, config.stateDirectory);
|
|
10561
10638
|
await ensurePrivateStateDirectory(config.stateDirectory);
|
|
10562
10639
|
await protectStateFromGit(config.workspace, config.stateDirectory);
|
|
10563
10640
|
if (config.storeBackend === "file") {
|
|
10564
|
-
const store = withBudgetLedgerStore(createFileAgentRunStore({ directory: config.stateDirectory, scope: config.scope }), createFileAgentRunStore({ directory: config.stateDirectory, scope:
|
|
10641
|
+
const store = withBudgetLedgerStore(createFileAgentRunStore({ directory: config.stateDirectory, scope: config.scope }), createFileAgentRunStore({ directory: config.stateDirectory, scope: budgetScope }), config);
|
|
10565
10642
|
const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
|
|
10566
10643
|
return {
|
|
10567
10644
|
backend: "file",
|
|
@@ -10606,7 +10683,7 @@ var openHarnessPersistence = async (config, options = {}) => {
|
|
|
10606
10683
|
database.exec("PRAGMA foreign_keys = ON");
|
|
10607
10684
|
await chmod3(databasePath, 384);
|
|
10608
10685
|
const databaseLike = sqliteAdapter(database);
|
|
10609
|
-
const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope:
|
|
10686
|
+
const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScope }), config);
|
|
10610
10687
|
database.exec(`CREATE TRIGGER IF NOT EXISTS zhivex_task_budget_retain_run
|
|
10611
10688
|
BEFORE DELETE ON zhivex_agent_runs
|
|
10612
10689
|
WHEN json_type(OLD.state_json, '$.metadata.zhivexTaskBudgetAccountV1') IS NOT NULL
|
|
@@ -10774,7 +10851,7 @@ var projectMemoryBinding = (workspace, scope) => {
|
|
|
10774
10851
|
segment(scope.userId);
|
|
10775
10852
|
if (scope.namespace !== undefined)
|
|
10776
10853
|
segment(scope.namespace);
|
|
10777
|
-
if (scope.namespace
|
|
10854
|
+
if (isReservedBudgetNamespace(scope.namespace))
|
|
10778
10855
|
throw new HarnessConfigError("Reserved memory namespace.");
|
|
10779
10856
|
const key = (kind, value) => createHash15("sha256").update(kind).update("\x00").update(value).digest("hex");
|
|
10780
10857
|
const workspaceKey = key("workspace", workspace);
|
|
@@ -11166,7 +11243,8 @@ var coordinatorIds = (runs) => new Set(runs.flatMap(({ state }) => {
|
|
|
11166
11243
|
var validateBudgetLedger = (config, run, identities) => {
|
|
11167
11244
|
const state = validatedRunState(run.state, `Budget ledger ${run.key}`);
|
|
11168
11245
|
const identity = state.metadata?.budgetIdentity;
|
|
11169
|
-
|
|
11246
|
+
const ownerNamespace = config.scope.namespace;
|
|
11247
|
+
if (!ownerNamespace || state.scope?.namespace !== budgetLedgerNamespace(ownerNamespace) || state.metadata?.[BUDGET_OWNER_NAMESPACE_KEY] !== ownerNamespace || state.scope?.tenantId !== config.scope.tenantId || (state.scope?.userId ?? undefined) !== (config.scope.userId ?? undefined) || state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true || typeof identity !== "string" || !identities.has(identity) || !/^budget_[a-f0-9]{64}$/.test(state.runId) || !sdkRunKeyMatches(run.key, state.scope, state.runId)) {
|
|
11170
11248
|
throw new HarnessStateConflictError("Budget ledger is not bound to an exported run and durable scope.");
|
|
11171
11249
|
}
|
|
11172
11250
|
assertTerminalState(state, `Budget ledger ${run.key}`);
|
|
@@ -11300,7 +11378,9 @@ var readPayload = async (config, recordedWorkspace, accessLease) => {
|
|
|
11300
11378
|
const metadata = state.metadata;
|
|
11301
11379
|
if (typeof metadata?.budgetIdentity !== "string" || !identities.has(metadata.budgetIdentity)) {
|
|
11302
11380
|
const scope = state.scope;
|
|
11303
|
-
|
|
11381
|
+
const sameTenant = scope?.tenantId === config.scope.tenantId && (scope?.userId ?? undefined) === (config.scope.userId ?? undefined);
|
|
11382
|
+
const boundNamespace = config.scope.namespace ? budgetLedgerNamespace(config.scope.namespace) : undefined;
|
|
11383
|
+
if (sameTenant && (scope?.namespace === BUDGET_LEDGER_NAMESPACE || scope?.namespace === boundNamespace)) {
|
|
11304
11384
|
throw new HarnessStateConflictError("An unlinked shared budget ledger cannot be safely assigned to this backup scope; retain the original database.");
|
|
11305
11385
|
}
|
|
11306
11386
|
return [];
|
|
@@ -15140,7 +15220,8 @@ var resolvers = {
|
|
|
15140
15220
|
const parsed = moveFileInputSchema.parse(input);
|
|
15141
15221
|
return [parsed.source, parsed.destination];
|
|
15142
15222
|
},
|
|
15143
|
-
quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path]
|
|
15223
|
+
quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path],
|
|
15224
|
+
restore_file: (input) => [restoreFileInputSchema.parse(input).destination]
|
|
15144
15225
|
};
|
|
15145
15226
|
var validateBuiltinToolPolicy = (policy, tools) => {
|
|
15146
15227
|
for (const rule of policy.rules)
|
|
@@ -15274,7 +15355,7 @@ import { z as z34 } from "zod";
|
|
|
15274
15355
|
// package.json
|
|
15275
15356
|
var package_default = {
|
|
15276
15357
|
name: "@zhivex-ai/harness",
|
|
15277
|
-
version: "1.
|
|
15358
|
+
version: "1.5.0",
|
|
15278
15359
|
description: "Governed, provider-portable runtime for coding agents with durable approvals and isolated repository execution.",
|
|
15279
15360
|
type: "module",
|
|
15280
15361
|
license: "MIT",
|
|
@@ -16799,7 +16880,7 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
|
|
|
16799
16880
|
}),
|
|
16800
16881
|
restore_file: tool5({
|
|
16801
16882
|
name: "restore_file",
|
|
16802
|
-
description: "Restore a quarantined file to
|
|
16883
|
+
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.",
|
|
16803
16884
|
schema: restoreFileInputSchema,
|
|
16804
16885
|
...mutationApproval,
|
|
16805
16886
|
execute: async (input, context) => serializeJsonValue9(editContractDocument("restore-result", await (harnessExecutionSession(context)?.workspace ?? workspace).restoreQuarantined(input)))
|
|
@@ -70,8 +70,8 @@ export declare const quarantineFileInputSchema: z.ZodObject<{
|
|
|
70
70
|
export type QuarantineFileInput = z.infer<typeof quarantineFileInputSchema>;
|
|
71
71
|
export declare const restoreFileInputSchema: z.ZodObject<{
|
|
72
72
|
quarantineId: z.ZodString;
|
|
73
|
-
destination: z.
|
|
74
|
-
expectedDigest: z.
|
|
73
|
+
destination: z.ZodString;
|
|
74
|
+
expectedDigest: z.ZodString;
|
|
75
75
|
}, z.core.$strict>;
|
|
76
76
|
export type RestoreFileInput = z.infer<typeof restoreFileInputSchema>;
|
|
77
77
|
export declare const mutationOperationSchema: z.ZodEnum<{
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/** Shared lexical exclusions for workspace access and acceptance preflight.
|
|
1
|
+
/** Shared lexical exclusions for workspace access and acceptance preflight.
|
|
2
|
+
* Comparisons use toLowerCase so a Turkish locale cannot map ASCII I to dotless ı and skip .git or id_rsa. */
|
|
2
3
|
export declare const HARD_IGNORES: Set<string>;
|
|
3
4
|
export declare const isSensitiveName: (name: string) => boolean;
|
|
4
5
|
export declare const isHardIgnored: (relativePath: string) => boolean;
|
|
@@ -214,6 +214,19 @@ export declare class Workspace {
|
|
|
214
214
|
private readInternalRegularFile;
|
|
215
215
|
quarantineFile(input: QuarantineFileInput, assertActive?: () => Promise<void>): Promise<QuarantineFileResult>;
|
|
216
216
|
private quarantineFileLocked;
|
|
217
|
+
private readRestorableQuarantine;
|
|
218
|
+
/** Read-only preview of a pinned restore. Destination and digest are required, and nothing is written. */
|
|
219
|
+
previewRestore(input: unknown): Promise<{
|
|
220
|
+
proposalId: string;
|
|
221
|
+
files: {
|
|
222
|
+
path: string;
|
|
223
|
+
expectedDigest: string;
|
|
224
|
+
before: null;
|
|
225
|
+
after: string;
|
|
226
|
+
afterDigest: string;
|
|
227
|
+
operation: "restore";
|
|
228
|
+
}[];
|
|
229
|
+
}>;
|
|
217
230
|
restoreQuarantined(input: RestoreFileInput): Promise<RestoreFileResult>;
|
|
218
231
|
private restoreQuarantinedLocked;
|
|
219
232
|
runCheck(check: HarnessCheck, expectedScript: string, allowedChecks?: readonly string[]): Promise<CommandResult>;
|
package/dist/zhx.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
HARNESS_VERSION,
|
|
4
4
|
NODE_ENGINE_RANGE,
|
|
5
5
|
shortCliHelp
|
|
6
|
-
} from "./cli-entry-
|
|
6
|
+
} from "./cli-entry-q8bgkn9m.js";
|
|
7
7
|
import {
|
|
8
8
|
__require
|
|
9
9
|
} from "./cli-entry-1bx83sw4.js";
|
|
@@ -41,6 +41,6 @@ if (argv.length === 1 && ["--version", "-v", "version"].includes(argv[0])) {
|
|
|
41
41
|
process.stdout.write(`${shortCliHelp(HARNESS_VERSION)}
|
|
42
42
|
`);
|
|
43
43
|
} else {
|
|
44
|
-
const { runCli } = await import("./cli-
|
|
44
|
+
const { runCli } = await import("./cli-vyrytn3m.js");
|
|
45
45
|
await runCli(argv);
|
|
46
46
|
}
|
package/docs/CODE_RELEASE.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Code release procedure
|
|
2
2
|
|
|
3
|
+
## Pending patch: 2026-10-16
|
|
4
|
+
|
|
5
|
+
The source version is `1.5.0`, pending publication to `latest`. This checkout
|
|
6
|
+
prepares Harness `1.5.0` and Code `0.4.0` for npm `latest`, with Code pinned
|
|
7
|
+
exactly to the engine and private Desktop bound to the same Harness version.
|
|
8
|
+
Published `latest` is Harness `1.4.1` and Code `0.3.2`. It adds the `v1.5.0`
|
|
9
|
+
representative mapping with the same Meta, Qwen and OpenAI models as `v1.4.1`
|
|
10
|
+
and does not change provider/API tiers. Annotated `v1.5.0` then `code-v0.4.0`
|
|
11
|
+
on the same reviewed main SHA, `channel=latest`, run through the unchanged
|
|
12
|
+
protected workflows and their environment approvals.
|
|
13
|
+
|
|
3
14
|
## Pending patch: 2026-10-14
|
|
4
15
|
|
|
5
16
|
This checkout prepares Code `0.3.1`, pinned exactly to Harness `1.4.1`, for npm
|
|
@@ -278,9 +289,17 @@ support remain unchanged; estimated budgets are not guaranteed financial caps.
|
|
|
278
289
|
verification as ordered in [the Harness release procedure](https://github.com/Zhivex/zhivex-harness/blob/main/docs/RELEASE.md).
|
|
279
290
|
From a clean checkout of that main commit, create an annotated
|
|
280
291
|
`code-v0.2.0` tag and push it. Never move or overwrite release tags.
|
|
281
|
-
4. Dispatch `release-code.yml` **
|
|
282
|
-
`channel=latest`, `mode=oidc`, and `confirm_publication=true`.
|
|
283
|
-
|
|
292
|
+
4. Dispatch `release-code.yml` **from that exact tag ref**, with input `tag`
|
|
293
|
+
matching it and `channel=latest`, `mode=oidc`, and `confirm_publication=true`.
|
|
294
|
+
A no-permission job is defense in depth, not the trust boundary, because
|
|
295
|
+
GitHub loads it from the dispatched ref. When it still runs, it requires
|
|
296
|
+
`GITHUB_REF` to be exactly `refs/tags/code-v0.2.0`, resolves that annotated
|
|
297
|
+
tag to the workflow commit, and requires the commit to be reachable from
|
|
298
|
+
`main` before provenance OIDC, provider-free validation, or npm publication
|
|
299
|
+
starts. Dispatch from `main` or any other ref is rejected, including when
|
|
300
|
+
the commit matches the tag. Publication still depends on the `npm`
|
|
301
|
+
environment allowing only selected `v*` and `code-v*` tags, and on the tag
|
|
302
|
+
ruleset in [Public repository security](PUBLIC_SECURITY.md).
|
|
284
303
|
|
|
285
304
|
```sh
|
|
286
305
|
gh workflow run release-code.yml --ref code-v0.2.0 \
|
|
@@ -350,10 +369,13 @@ In npm package settings, configure GitHub Actions with organization `Zhivex`,
|
|
|
350
369
|
repository `zhivex-harness`, workflow filename `release-code.yml`, and environment
|
|
351
370
|
`npm`. Enable the allowed action `npm publish`; the OIDC job uses direct
|
|
352
371
|
publishing after GitHub approval, not `npm stage publish`. Preserve the GitHub
|
|
353
|
-
environment's required human approval
|
|
372
|
+
environment's required human approval and the selected-tag deployment rule in
|
|
373
|
+
[Public repository security](PUBLIC_SECURITY.md). That environment rule, with
|
|
374
|
+
the maintainer-only tag ruleset, is the trust boundary. The in-repo resolver is
|
|
375
|
+
defense in depth, not the trust boundary. Then prepare a
|
|
354
376
|
new Code stable or RC version and annotated tag through the same PR and main CI process.
|
|
355
|
-
Dispatch
|
|
356
|
-
`confirm_publication=true`. Stable versions require `latest` and a stable exact
|
|
377
|
+
Dispatch the workflow from that exact tag ref, with the matching channel, `mode=oidc` and
|
|
378
|
+
`confirm_publication=true`. A dispatch from `main` is rejected. Stable versions require `latest` and a stable exact
|
|
357
379
|
Harness dependency; canonical RC versions require `next`. Version/channel drift
|
|
358
380
|
fails before engine downloads. An RC may pin a stable engine. Stable publication
|
|
359
381
|
does not upgrade beta/experimental APIs, provisional providers or Desktop alpha.
|
package/docs/CODE_WEB_TASKS.md
CHANGED
|
@@ -11,7 +11,7 @@ action on a fresh review ticket; model prose and a completed run are not accepta
|
|
|
11
11
|
|
|
12
12
|
## Operator flow
|
|
13
13
|
|
|
14
|
-
1. Open `zhivex-code web`,
|
|
14
|
+
1. Open `zhivex-code web`, open Sessions, choose an allowed project and create an empty session.
|
|
15
15
|
Choose **Define task**. Enter the goal, exact existing editable files, package
|
|
16
16
|
checks and explicit input/output/total token limits for the entire task.
|
|
17
17
|
2. Review each proposed edit or check through the existing exact approval controls.
|
package/docs/ENGINE_API.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Engine API and Code integration
|
|
2
2
|
|
|
3
3
|
The terminal-independent package entrypoints shipped in Harness 1.3.0 and remain
|
|
4
|
-
in published Harness 1.4.0 (npm `latest`). This checkout is Harness 1.
|
|
4
|
+
in published Harness 1.4.0 (npm `latest`). This checkout is Harness 1.5.0, pending publication. They are not present in Harness 1.2.0.
|
|
5
5
|
The protected workflow validated the exact stable tarball, live gates and
|
|
6
6
|
registry integrity/provenance; local candidate smoke remains contributor evidence. Do not install registry 1.2.0 and
|
|
7
7
|
expect the new entrypoints to exist.
|
package/docs/EXTENSIBILITY.md
CHANGED
|
@@ -81,14 +81,18 @@ Library callers can inject a custom `McpClient`. Trusted read-only annotations a
|
|
|
81
81
|
|
|
82
82
|
## MCP result boundary
|
|
83
83
|
|
|
84
|
-
MCP descriptions and results are untrusted. The harness:
|
|
84
|
+
MCP descriptions and results are untrusted data, including text that looks like an identifier, digest, or configuration value. The harness:
|
|
85
85
|
|
|
86
86
|
- bounds discovery pages, discovered tools, time, and response bytes;
|
|
87
87
|
- validates declared input and structured-output schemas through the SDK MCP registry;
|
|
88
88
|
- propagates abort signals, timeout intent, and idempotency keys;
|
|
89
89
|
- rejects common prompt-injection directives before they enter model context;
|
|
90
90
|
- records server permissions and risk in tool metadata;
|
|
91
|
-
- promotes network, write, or external-side-effect calls to durable interrupt approval
|
|
91
|
+
- promotes network, write, or external-side-effect calls to durable interrupt approval;
|
|
92
|
+
- removes exact secret values before a result is published;
|
|
93
|
+
- when a server was admitted with secret references, flags known encodings of those secrets and other high-entropy encoded tokens in tool results.
|
|
94
|
+
|
|
95
|
+
That flagging is optional and incomplete. Split, encrypted, or low-entropy encodings can still appear. Discovery schemas are rejected rather than rewritten when they contain an exact secret. Treat every MCP result as data, not as instructions and not as proof that a credential is absent.
|
|
92
96
|
|
|
93
97
|
The injection filter is deliberately conservative and may reject legitimate security documentation containing imperative attack text. Use a separate trusted preprocessing boundary if that content must be analyzed; do not disable the harness boundary for an untrusted server.
|
|
94
98
|
|
package/docs/FIRST_USE.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# First use
|
|
2
2
|
|
|
3
|
-
This guide targets stable `1.
|
|
3
|
+
This guide targets stable `1.5.0`, pending publication to npm `latest`. The versioned install commands below become available only after protected publication. npm `latest` remains stable 1.4.1 until that publication.
|
|
4
4
|
|
|
5
5
|
## Install and start
|
|
6
6
|
|
|
7
7
|
Install Node.js 22.13.0 or newer and Git. Then:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install -g @zhivex-ai/harness@1.
|
|
10
|
+
npm install -g @zhivex-ai/harness@1.5.0
|
|
11
11
|
cd /path/to/your/project
|
|
12
12
|
zhx
|
|
13
13
|
```
|
|
@@ -82,16 +82,16 @@ For command execution, configure [OCI isolation](EXECUTION_ENVIRONMENTS.md).
|
|
|
82
82
|
With Bun 1.4.0 or newer and a supported Node runtime:
|
|
83
83
|
|
|
84
84
|
```sh
|
|
85
|
-
bun add --global @zhivex-ai/harness@1.
|
|
85
|
+
bun add --global @zhivex-ai/harness@1.5.0
|
|
86
86
|
zhx
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
For a disposable version/help/environment check without a global installation:
|
|
90
90
|
|
|
91
91
|
```sh
|
|
92
|
-
bunx @zhivex-ai/harness@1.
|
|
93
|
-
bunx @zhivex-ai/harness@1.
|
|
94
|
-
bunx @zhivex-ai/harness@1.
|
|
92
|
+
bunx @zhivex-ai/harness@1.5.0 --version
|
|
93
|
+
bunx @zhivex-ai/harness@1.5.0 --help
|
|
94
|
+
bunx @zhivex-ai/harness@1.5.0 doctor
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
For source development, use the [contributor guide](https://github.com/Zhivex/zhivex-harness/blob/main/CONTRIBUTING.md).
|