harnery 0.10.0 → 0.12.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/README.md +1 -1
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +2 -0
- package/dist/commands/policy.d.ts +6 -0
- package/dist/commands/policy.d.ts.map +1 -0
- package/dist/commands/policy.js +42 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +62 -2
- package/dist/core/policy/evaluate.d.ts +14 -0
- package/dist/core/policy/evaluate.d.ts.map +1 -0
- package/dist/core/policy/evaluate.js +238 -0
- package/dist/core/policy/file.d.ts +3 -0
- package/dist/core/policy/file.d.ts.map +1 -0
- package/dist/core/policy/file.js +20 -0
- package/dist/core/policy/index.d.ts +5 -0
- package/dist/core/policy/index.d.ts.map +1 -0
- package/dist/core/policy/index.js +3 -0
- package/dist/core/policy/types.d.ts +91 -0
- package/dist/core/policy/types.d.ts.map +1 -0
- package/dist/core/policy/types.js +1 -0
- package/dist/core/workflow/engine.d.ts +5 -0
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +412 -47
- package/dist/core/workflow/index.d.ts +5 -0
- package/dist/core/workflow/index.d.ts.map +1 -0
- package/dist/core/workflow/index.js +3 -0
- package/dist/core/workflow/proof.d.ts +49 -0
- package/dist/core/workflow/proof.d.ts.map +1 -0
- package/dist/core/workflow/proof.js +375 -0
- package/dist/core/workflow/types.d.ts +180 -0
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/types.js +1 -1
- package/package.json +16 -6
- package/src/commander.ts +2 -0
- package/src/commands/policy.ts +50 -0
- package/src/commands/workflow.ts +82 -2
- package/src/core/policy/evaluate.ts +311 -0
- package/src/core/policy/file.ts +20 -0
- package/src/core/policy/index.ts +26 -0
- package/src/core/policy/types.ts +107 -0
- package/src/core/workflow/engine.ts +452 -53
- package/src/core/workflow/index.ts +44 -0
- package/src/core/workflow/proof.ts +485 -0
- package/src/core/workflow/types.ts +210 -0
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
- **Multi-agent coordination:** per-agent heartbeats in `.harnery/active/`, claim-time and commit-time guards, the canonical event stream, councils, a decision docket, per-agent scratchpads, and harness adapters for Claude Code / Cursor / Codex.
|
|
23
23
|
- **Cross-machine presence:** sessions on every machine that shares a repo see each other, labeled by machine. Zero-config over the repo's own git remote (presence refs), with an optional live-socket upgrade via an end-to-end-encrypted relay — the public `relay.harnery.com`, or self-host with `harn relay serve` / the bundled Cloudflare Worker (`relay/worker/`). The relay sees only ciphertext in opaquely-named rooms.
|
|
24
|
-
- **Coordination-aware workflows:** `harn workflow run <script>` executes bounded, schema-gated multi-subagent workflow scripts — plain JS stages fanning out to headless harness-CLI subagents that are born coordination-registered (heartbeats, events, claim guards), with deterministic code deciding the routing between stages.
|
|
24
|
+
- **Coordination-aware workflows:** `harn workflow run <script>` executes bounded, schema-gated multi-subagent workflow scripts — plain JS stages fanning out to headless harness-CLI subagents that are born coordination-registered (heartbeats, events, claim guards), with deterministic code deciding the routing between stages. An optional host-owned policy gates dispatch and declared external mutations with fail-closed `ALLOW`, `ASK`, and `DENY` decisions recorded in the run proof.
|
|
25
25
|
- **Standalone web UI:** `harn web up` boots a local Next.js dashboard for the coord layer, councils, per-project state, and a local coding-agent status view (Claude Code / Codex / Cursor usage, backed by `harn devtools`). Ships with the git clone, not the npm package (see [Install](#install)).
|
|
26
26
|
- **Backup + sync:** `harn backup` snapshots `.harnery/` via [restic](https://restic.net/); `harn sync` keeps a curated subset live across machines via [rclone](https://rclone.org/) (Google Drive or any rclone remote).
|
|
27
27
|
|
package/dist/commander.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"commander.d.ts","sourceRoot":"","sources":["../src/commander.ts"],"names":[],"mappings":"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"}
|
package/dist/commander.js
CHANGED
|
@@ -41,6 +41,7 @@ import { registerGrepCommand } from "./commands/grep.js";
|
|
|
41
41
|
import { registerHarnessCommand } from "./commands/harness.js";
|
|
42
42
|
import { registerInitCommand } from "./commands/init.js";
|
|
43
43
|
import { registerOutlineCommand } from "./commands/outline.js";
|
|
44
|
+
import { registerPolicyCommand } from "./commands/policy.js";
|
|
44
45
|
import { registerPresenceCommand } from "./commands/presence.js";
|
|
45
46
|
import { registerReadCommand } from "./commands/read.js";
|
|
46
47
|
import { registerRelayCommand } from "./commands/relay.js";
|
|
@@ -109,6 +110,7 @@ export function createHarneryProgram(opts = {}) {
|
|
|
109
110
|
registerEditBatchCommand(program, emit);
|
|
110
111
|
registerGrepCommand(program, emit, opts.context);
|
|
111
112
|
registerHarnessCommand(program, emit);
|
|
113
|
+
registerPolicyCommand(program, emit);
|
|
112
114
|
registerCookiesCommand(program, emit);
|
|
113
115
|
registerFetchCommand(program, emit, opts.context);
|
|
114
116
|
registerReadCommand(program, emit);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { EmitContext } from "../commander.js";
|
|
3
|
+
import type { NormalizedPolicy } from "../core/policy/index.js";
|
|
4
|
+
export declare function registerPolicyCommand(program: Command, emit: EmitContext): void;
|
|
5
|
+
export declare function renderPolicy(policy: Readonly<NormalizedPolicy>, digest: string): string;
|
|
6
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/commands/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAOhE,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAwB/E;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAcvF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { loadPolicyFile, policyDigest } from "../core/policy/index.js";
|
|
2
|
+
export function registerPolicyCommand(program, emit) {
|
|
3
|
+
const command = program
|
|
4
|
+
.command("policy")
|
|
5
|
+
.description("Inspect host-enforced workflow policy documents.");
|
|
6
|
+
command
|
|
7
|
+
.command("check <file>")
|
|
8
|
+
.description("Validate and print a normalized policy without running a workflow.")
|
|
9
|
+
.option("--json", "Emit the normalized policy and digest as JSON")
|
|
10
|
+
.action((file, opts) => {
|
|
11
|
+
try {
|
|
12
|
+
const policy = loadPolicyFile(file);
|
|
13
|
+
const result = { policy, sha256: policyDigest(policy) };
|
|
14
|
+
if (opts.json) {
|
|
15
|
+
emit.config({ format: "json" });
|
|
16
|
+
emit.data(result);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
emit.text(renderPolicy(policy, result.sha256));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
emit.error({ code: "policy_invalid", message: error.message });
|
|
24
|
+
emit.setExitCode(1);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function renderPolicy(policy, digest) {
|
|
29
|
+
const lines = [
|
|
30
|
+
`${policy.name} (schema ${policy.schema_version})`,
|
|
31
|
+
`sha256: ${digest}`,
|
|
32
|
+
`cost ceiling: ${policy.max_cost_usd === undefined ? "none" : `$${policy.max_cost_usd.toFixed(4)}`}`,
|
|
33
|
+
`unknown cost: ${policy.unknown_cost}`,
|
|
34
|
+
`network: ${policy.network}`,
|
|
35
|
+
`external actions: ${policy.external_actions}`,
|
|
36
|
+
`harnesses: ${policy.allowed_harnesses?.join(", ") ?? "any"}`,
|
|
37
|
+
`models: ${policy.allowed_models?.join(", ") ?? "any"}`,
|
|
38
|
+
`paths: ${policy.allowed_paths?.join(", ") ?? "any"}`,
|
|
39
|
+
`isolation: ${policy.allowed_isolation?.join(", ") ?? "any"}`,
|
|
40
|
+
];
|
|
41
|
+
return `${lines.join("\n")}\n`;
|
|
42
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/commands/workflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../src/commands/workflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAkCnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAuKjF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { workflowSubscriptionOnly } from "../core/config.js";
|
|
2
2
|
import { createBuiltinHarnessRegistry } from "../core/harnesses/index.js";
|
|
3
3
|
import { findCoordRoot } from "../core/hooks/resolve/coord-root.js";
|
|
4
|
+
import { loadPolicyFile } from "../core/policy/index.js";
|
|
4
5
|
export function registerWorkflowCommand(program, emit) {
|
|
5
6
|
const registry = createBuiltinHarnessRegistry();
|
|
6
7
|
const harnesses = registry.ids();
|
|
@@ -9,7 +10,7 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
9
10
|
.description("Run bounded, schema-gated multi-subagent workflow scripts.");
|
|
10
11
|
workflow
|
|
11
12
|
.command("run <script>")
|
|
12
|
-
.description("Execute a workflow script (plain JS: `export default async ({agent, parallel, stage, log}) => …`). " +
|
|
13
|
+
.description("Execute a workflow script (plain JS: `export default async ({agent, parallel, stage, log, evidence, authorize}) => …`). " +
|
|
13
14
|
"Subagents spawn as headless harness-CLI subprocesses, coordination-registered.")
|
|
14
15
|
.option("--max-agents <n>", "Total-agent ceiling for the run (default 50)")
|
|
15
16
|
.option("--concurrency <n>", "Concurrent-subagent cap (default 4)")
|
|
@@ -20,6 +21,8 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
20
21
|
"harness has no stored login (repo default via config.jsonc workflow.subscriptionOnly)")
|
|
21
22
|
.option("--allow-api-billing", "Permit an exported API key to override a stored subscription login (per-token billing); " +
|
|
22
23
|
"without this the engine refuses that silent-override state")
|
|
24
|
+
.option("--policy <file>", "Host policy JSON/JSONC applied before dispatch and external mutation")
|
|
25
|
+
.option("--isolation <mode>", "Host-created execution boundary: shared | worktree | sandbox | remote (default shared)")
|
|
23
26
|
.option("--json", "Emit the full RunReport as JSON")
|
|
24
27
|
.action(async (script, opts) => {
|
|
25
28
|
const coordRoot = findCoordRoot();
|
|
@@ -37,6 +40,14 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
37
40
|
});
|
|
38
41
|
process.exit(1);
|
|
39
42
|
}
|
|
43
|
+
if (opts.isolation &&
|
|
44
|
+
!["shared", "worktree", "sandbox", "remote"].includes(opts.isolation)) {
|
|
45
|
+
emit.error({
|
|
46
|
+
code: "bad_isolation",
|
|
47
|
+
message: `unknown isolation ${JSON.stringify(opts.isolation)} (expected: shared | worktree | sandbox | remote)`,
|
|
48
|
+
});
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
40
51
|
// Flag beats config; the two flags contradict each other by design.
|
|
41
52
|
const subscriptionOnly = opts.subscriptionOnly || workflowSubscriptionOnly(coordRoot);
|
|
42
53
|
if (subscriptionOnly && opts.allowApiBilling) {
|
|
@@ -59,6 +70,18 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
59
70
|
maxAgents: opts.maxAgents ? Number.parseInt(opts.maxAgents, 10) : undefined,
|
|
60
71
|
concurrency: opts.concurrency ? Number.parseInt(opts.concurrency, 10) : undefined,
|
|
61
72
|
cwd: opts.cwd,
|
|
73
|
+
harnessEvidence: Object.fromEntries(registry
|
|
74
|
+
.list()
|
|
75
|
+
.map((adapter) => [
|
|
76
|
+
adapter.profile.id,
|
|
77
|
+
{ toolEvidence: adapter.profile.capabilities.toolEvidence },
|
|
78
|
+
])),
|
|
79
|
+
policy: opts.policy ? loadPolicyFile(opts.policy) : undefined,
|
|
80
|
+
isolation: opts.isolation,
|
|
81
|
+
// CLI harness subprocesses inherit the host network. A policy that
|
|
82
|
+
// forbids network must therefore deny dispatch unless a future host
|
|
83
|
+
// adapter creates and declares a network-disabled boundary.
|
|
84
|
+
networkAccess: "enabled",
|
|
62
85
|
});
|
|
63
86
|
if (opts.json) {
|
|
64
87
|
emit.config({ format: "json" });
|
|
@@ -71,11 +94,48 @@ export function registerWorkflowCommand(program, emit) {
|
|
|
71
94
|
: "";
|
|
72
95
|
emit.text(`run ${report.runId} (${report.name}) finished: ${report.agentsSpawned} agent(s)${cachedPart}, ` +
|
|
73
96
|
`$${report.costUsd.toFixed(4)}, ${Math.round(report.durationMs / 1000)}s\n${billingPart}` +
|
|
97
|
+
`acceptance: ${report.acceptance.satisfied} satisfied, ${report.acceptance.unsatisfied} unsatisfied, ` +
|
|
98
|
+
`${report.acceptance.unknown} unknown\n` +
|
|
74
99
|
`journal: ${report.journalPath}\n` +
|
|
100
|
+
`proof: ${report.proofPath}\n` +
|
|
75
101
|
`result: ${typeof report.result === "string" ? report.result : JSON.stringify(report.result, null, 2)}\n`);
|
|
76
102
|
}
|
|
77
103
|
catch (err) {
|
|
78
|
-
|
|
104
|
+
const proofPath = typeof err === "object" && err !== null && "proofPath" in err
|
|
105
|
+
? String(err.proofPath)
|
|
106
|
+
: undefined;
|
|
107
|
+
emit.error({
|
|
108
|
+
code: "workflow_failed",
|
|
109
|
+
message: `${err.message}${proofPath ? `\nproof: ${proofPath}` : ""}`,
|
|
110
|
+
});
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
workflow
|
|
115
|
+
.command("proof <run-id>")
|
|
116
|
+
.description("Show the bounded proof packet for a completed workflow run.")
|
|
117
|
+
.option("--json", "Emit the stored proof packet as JSON")
|
|
118
|
+
.action(async (runId, opts) => {
|
|
119
|
+
const coordRoot = findCoordRoot();
|
|
120
|
+
if (!coordRoot) {
|
|
121
|
+
emit.error({
|
|
122
|
+
code: "no_coord_root",
|
|
123
|
+
message: "no .harnery/ coordination root found; run `init` first",
|
|
124
|
+
});
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const { readWorkflowProof, renderWorkflowProof } = await import("../core/workflow/proof.js");
|
|
129
|
+
const proof = readWorkflowProof(coordRoot, runId);
|
|
130
|
+
if (opts.json) {
|
|
131
|
+
emit.config({ format: "json" });
|
|
132
|
+
emit.data(proof);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
emit.text(renderWorkflowProof(proof));
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
emit.error({ code: "workflow_proof_failed", message: err.message });
|
|
79
139
|
process.exit(1);
|
|
80
140
|
}
|
|
81
141
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { NormalizedPolicy, PolicyEvaluation, PolicyIsolation, PolicyRequest, PolicyRequestSummary, PolicySpec } from "./types.js";
|
|
2
|
+
export declare class PolicyDeniedError extends Error {
|
|
3
|
+
readonly decisionId: string;
|
|
4
|
+
constructor(message: string, decisionId: string);
|
|
5
|
+
}
|
|
6
|
+
export declare function normalizePolicy(input: PolicySpec, options?: {
|
|
7
|
+
baseDir?: string;
|
|
8
|
+
}): Readonly<NormalizedPolicy>;
|
|
9
|
+
export declare function policyDigest(policy: NormalizedPolicy): string;
|
|
10
|
+
export declare function evaluatePolicy(policy: Readonly<NormalizedPolicy>, rawRequest: PolicyRequest): PolicyEvaluation;
|
|
11
|
+
/** Bound and normalize the receipt-safe subset before it reaches a journal or proof. */
|
|
12
|
+
export declare function summarizePolicyRequest(input: PolicyRequest): PolicyRequestSummary;
|
|
13
|
+
export type { PolicyIsolation };
|
|
14
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../../src/core/policy/evaluate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,oBAAoB,EAEpB,UAAU,EAEX,MAAM,YAAY,CAAC;AAqBpB,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;CAKhD;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACjC,QAAQ,CAAC,gBAAgB,CAAC,CAwB5B;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAE7D;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,EAClC,UAAU,EAAE,aAAa,GACxB,gBAAgB,CA8FlB;AAED,wFAAwF;AACxF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,aAAa,GAAG,oBAAoB,CA8BjF;AAyGD,YAAY,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { POLICY_SCHEMA_VERSION } from "./types.js";
|
|
4
|
+
const VERDICTS = ["allow", "ask", "deny"];
|
|
5
|
+
const ISOLATIONS = ["shared", "worktree", "sandbox", "remote"];
|
|
6
|
+
const MAX_NAME = 200;
|
|
7
|
+
const MAX_FIELD = 500;
|
|
8
|
+
const MAX_LIST = 100;
|
|
9
|
+
const POLICY_FIELDS = new Set([
|
|
10
|
+
"schema_version",
|
|
11
|
+
"name",
|
|
12
|
+
"max_cost_usd",
|
|
13
|
+
"unknown_cost",
|
|
14
|
+
"allowed_harnesses",
|
|
15
|
+
"allowed_models",
|
|
16
|
+
"allowed_paths",
|
|
17
|
+
"network",
|
|
18
|
+
"external_actions",
|
|
19
|
+
"allowed_isolation",
|
|
20
|
+
]);
|
|
21
|
+
export class PolicyDeniedError extends Error {
|
|
22
|
+
decisionId;
|
|
23
|
+
constructor(message, decisionId) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "PolicyDeniedError";
|
|
26
|
+
this.decisionId = decisionId;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function normalizePolicy(input, options = {}) {
|
|
30
|
+
if (!isRecord(input))
|
|
31
|
+
throw new Error("policy must be an object");
|
|
32
|
+
const unknownFields = Object.keys(input).filter((field) => !POLICY_FIELDS.has(field));
|
|
33
|
+
if (unknownFields.length > 0) {
|
|
34
|
+
throw new Error(`policy contains unknown field(s): ${unknownFields.sort().join(", ")}`);
|
|
35
|
+
}
|
|
36
|
+
const schemaVersion = input.schema_version ?? POLICY_SCHEMA_VERSION;
|
|
37
|
+
if (schemaVersion !== POLICY_SCHEMA_VERSION) {
|
|
38
|
+
throw new Error(`unsupported policy schema_version ${JSON.stringify(schemaVersion)}`);
|
|
39
|
+
}
|
|
40
|
+
const baseDir = resolve(options.baseDir ?? process.cwd());
|
|
41
|
+
const normalized = {
|
|
42
|
+
schema_version: POLICY_SCHEMA_VERSION,
|
|
43
|
+
name: boundedString(input.name ?? "workflow policy", "policy name", MAX_NAME),
|
|
44
|
+
max_cost_usd: optionalMoney(input.max_cost_usd, "max_cost_usd"),
|
|
45
|
+
unknown_cost: enumValue(input.unknown_cost ?? "deny", ["ask", "deny"], "unknown_cost"),
|
|
46
|
+
allowed_harnesses: optionalStringList(input.allowed_harnesses, "allowed_harnesses"),
|
|
47
|
+
allowed_models: optionalStringList(input.allowed_models, "allowed_models"),
|
|
48
|
+
allowed_paths: optionalPathList(input.allowed_paths, baseDir),
|
|
49
|
+
network: enumValue(input.network ?? "deny", VERDICTS, "network"),
|
|
50
|
+
external_actions: enumValue(input.external_actions ?? "deny", VERDICTS, "external_actions"),
|
|
51
|
+
allowed_isolation: optionalEnumList(input.allowed_isolation, ISOLATIONS, "allowed_isolation"),
|
|
52
|
+
};
|
|
53
|
+
return deepFreeze(normalized);
|
|
54
|
+
}
|
|
55
|
+
export function policyDigest(policy) {
|
|
56
|
+
return createHash("sha256").update(JSON.stringify(policy)).digest("hex");
|
|
57
|
+
}
|
|
58
|
+
export function evaluatePolicy(policy, rawRequest) {
|
|
59
|
+
const request = summarizePolicyRequest(rawRequest);
|
|
60
|
+
const rules = [];
|
|
61
|
+
const add = (code, verdict, reason) => {
|
|
62
|
+
rules.push({ code, verdict, reason });
|
|
63
|
+
};
|
|
64
|
+
if (policy.allowed_harnesses && request.phase === "dispatch") {
|
|
65
|
+
if (!request.harness || !policy.allowed_harnesses.includes(request.harness)) {
|
|
66
|
+
add("harness_not_allowed", "deny", `harness ${JSON.stringify(request.harness ?? "unknown")} is not allowed`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (policy.allowed_models && request.phase === "dispatch") {
|
|
70
|
+
if (!request.model || !policy.allowed_models.includes(request.model)) {
|
|
71
|
+
add("model_not_allowed", "deny", `model ${JSON.stringify(request.model ?? "unknown")} is not allowed`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (policy.allowed_paths) {
|
|
75
|
+
if (!request.path || !policy.allowed_paths.some((root) => contains(root, request.path))) {
|
|
76
|
+
add("path_not_allowed", "deny", `path ${JSON.stringify(request.path ?? "unknown")} is outside the allowed roots`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (policy.allowed_isolation && !policy.allowed_isolation.includes(request.isolation)) {
|
|
80
|
+
add("isolation_not_allowed", "deny", `isolation ${JSON.stringify(request.isolation)} is not allowed`);
|
|
81
|
+
}
|
|
82
|
+
if (request.network_access !== "disabled" && policy.network !== "allow") {
|
|
83
|
+
add(request.network_access === "unknown" ? "network_unknown" : "network_restricted", policy.network, request.network_access === "unknown"
|
|
84
|
+
? "network access is unknown"
|
|
85
|
+
: "network access requires policy authorization");
|
|
86
|
+
}
|
|
87
|
+
if (request.phase === "external_mutation" && policy.external_actions !== "allow") {
|
|
88
|
+
add("external_action_restricted", policy.external_actions, "external mutation requires policy authorization");
|
|
89
|
+
}
|
|
90
|
+
if (policy.max_cost_usd !== undefined && request.phase === "dispatch") {
|
|
91
|
+
const current = request.current_cost_usd ?? 0;
|
|
92
|
+
if (current >= policy.max_cost_usd) {
|
|
93
|
+
add("cost_budget_exhausted", "deny", `run cost $${current.toFixed(4)} has reached the $${policy.max_cost_usd.toFixed(4)} ceiling`);
|
|
94
|
+
}
|
|
95
|
+
else if (request.projected_cost_usd === null || request.projected_cost_usd === undefined) {
|
|
96
|
+
add("dispatch_cost_unknown", policy.unknown_cost, `dispatch cost is unknown under a $${policy.max_cost_usd.toFixed(4)} ceiling`);
|
|
97
|
+
}
|
|
98
|
+
else if (current + request.projected_cost_usd > policy.max_cost_usd) {
|
|
99
|
+
add("cost_budget_exceeded", "deny", `projected run cost $${(current + request.projected_cost_usd).toFixed(4)} exceeds the $${policy.max_cost_usd.toFixed(4)} ceiling`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (rules.length === 0) {
|
|
103
|
+
rules.push({ code: "policy_allow", verdict: "allow", reason: "all configured rules allow" });
|
|
104
|
+
}
|
|
105
|
+
const verdict = rules.some((rule) => rule.verdict === "deny")
|
|
106
|
+
? "deny"
|
|
107
|
+
: rules.some((rule) => rule.verdict === "ask")
|
|
108
|
+
? "ask"
|
|
109
|
+
: "allow";
|
|
110
|
+
const decisive = rules.filter((rule) => rule.verdict === verdict);
|
|
111
|
+
return {
|
|
112
|
+
verdict,
|
|
113
|
+
reason: decisive.map((rule) => rule.reason).join("; "),
|
|
114
|
+
rules,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Bound and normalize the receipt-safe subset before it reaches a journal or proof. */
|
|
118
|
+
export function summarizePolicyRequest(input) {
|
|
119
|
+
if (!isRecord(input))
|
|
120
|
+
throw new Error("policy request must be an object");
|
|
121
|
+
const phase = enumValue(input.phase, ["dispatch", "external_mutation"], "policy phase");
|
|
122
|
+
const isolation = enumValue(input.isolation, ISOLATIONS, "policy isolation");
|
|
123
|
+
const networkAccess = enumValue(input.network_access, ["enabled", "disabled", "unknown"], "policy network_access");
|
|
124
|
+
return {
|
|
125
|
+
phase,
|
|
126
|
+
action: boundedString(input.action, "policy action", MAX_FIELD),
|
|
127
|
+
path: optionalBounded(input.path, "policy path"),
|
|
128
|
+
harness: optionalBounded(input.harness, "policy harness"),
|
|
129
|
+
model: optionalBounded(input.model, "policy model"),
|
|
130
|
+
effort: optionalBounded(input.effort, "policy effort"),
|
|
131
|
+
max_attempts: optionalWholeNumber(input.max_attempts, "policy max_attempts", 1),
|
|
132
|
+
max_turns: optionalWholeNumber(input.max_turns, "policy max_turns", 1),
|
|
133
|
+
timeout_ms: optionalWholeNumber(input.timeout_ms, "policy timeout_ms", 1),
|
|
134
|
+
prompt_bytes: optionalWholeNumber(input.prompt_bytes, "policy prompt_bytes", 0),
|
|
135
|
+
isolation,
|
|
136
|
+
network_access: networkAccess,
|
|
137
|
+
service: optionalBounded(input.service, "policy service"),
|
|
138
|
+
target: optionalBounded(safeTarget(input.target), "policy target"),
|
|
139
|
+
current_cost_usd: optionalMoney(input.current_cost_usd, "current_cost_usd"),
|
|
140
|
+
projected_cost_usd: input.projected_cost_usd === null
|
|
141
|
+
? null
|
|
142
|
+
: optionalMoney(input.projected_cost_usd, "projected_cost_usd"),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function contains(root, candidate) {
|
|
146
|
+
const rel = relative(resolve(root), resolve(candidate));
|
|
147
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
148
|
+
}
|
|
149
|
+
function optionalPathList(value, baseDir) {
|
|
150
|
+
const values = optionalStringList(value, "allowed_paths");
|
|
151
|
+
if (!values)
|
|
152
|
+
return undefined;
|
|
153
|
+
return [...new Set(values.map((item) => resolve(baseDir, item)))].sort();
|
|
154
|
+
}
|
|
155
|
+
function optionalStringList(value, field) {
|
|
156
|
+
if (value === undefined)
|
|
157
|
+
return undefined;
|
|
158
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_LIST) {
|
|
159
|
+
throw new Error(`${field} must be a non-empty array with at most ${MAX_LIST} entries`);
|
|
160
|
+
}
|
|
161
|
+
return [
|
|
162
|
+
...new Set(value.map((item, index) => boundedString(item, `${field}[${index}]`, MAX_FIELD))),
|
|
163
|
+
].sort();
|
|
164
|
+
}
|
|
165
|
+
function optionalEnumList(value, allowed, field) {
|
|
166
|
+
if (value === undefined)
|
|
167
|
+
return undefined;
|
|
168
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_LIST) {
|
|
169
|
+
throw new Error(`${field} must be a non-empty array with at most ${MAX_LIST} entries`);
|
|
170
|
+
}
|
|
171
|
+
return [...new Set(value.map((item) => enumValue(item, allowed, field)))].sort();
|
|
172
|
+
}
|
|
173
|
+
function enumValue(value, values, field) {
|
|
174
|
+
if (typeof value !== "string" || !values.includes(value)) {
|
|
175
|
+
throw new Error(`${field} must be one of: ${values.join(", ")}`);
|
|
176
|
+
}
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
function optionalMoney(value, field) {
|
|
180
|
+
if (value === undefined)
|
|
181
|
+
return undefined;
|
|
182
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
183
|
+
throw new Error(`${field} must be a finite non-negative number`);
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
function optionalWholeNumber(value, field, minimum) {
|
|
188
|
+
if (value === undefined)
|
|
189
|
+
return undefined;
|
|
190
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
191
|
+
throw new Error(`${field} must be a safe integer greater than or equal to ${minimum}`);
|
|
192
|
+
}
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
function boundedString(value, field, max) {
|
|
196
|
+
if (typeof value !== "string")
|
|
197
|
+
throw new Error(`${field} must be a string`);
|
|
198
|
+
const normalized = withoutControls(value).replace(/\s+/g, " ").trim();
|
|
199
|
+
if (!normalized)
|
|
200
|
+
throw new Error(`${field} must not be empty`);
|
|
201
|
+
if (normalized.length > max)
|
|
202
|
+
throw new Error(`${field} exceeds ${max} characters`);
|
|
203
|
+
return normalized;
|
|
204
|
+
}
|
|
205
|
+
function optionalBounded(value, field) {
|
|
206
|
+
return value === undefined ? undefined : boundedString(value, field, MAX_FIELD);
|
|
207
|
+
}
|
|
208
|
+
function safeTarget(value) {
|
|
209
|
+
if (typeof value !== "string")
|
|
210
|
+
return value;
|
|
211
|
+
try {
|
|
212
|
+
const url = new URL(value);
|
|
213
|
+
return `${url.origin}${url.pathname}`;
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
const query = value.indexOf("?");
|
|
217
|
+
const fragment = value.indexOf("#");
|
|
218
|
+
const boundary = Math.min(query === -1 ? value.length : query, fragment === -1 ? value.length : fragment);
|
|
219
|
+
return value.slice(0, boundary);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function isRecord(value) {
|
|
223
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
224
|
+
}
|
|
225
|
+
function withoutControls(value) {
|
|
226
|
+
return Array.from(value, (character) => {
|
|
227
|
+
const code = character.charCodeAt(0);
|
|
228
|
+
return code < 32 || code === 127 ? " " : character;
|
|
229
|
+
}).join("");
|
|
230
|
+
}
|
|
231
|
+
function deepFreeze(value) {
|
|
232
|
+
if (value && typeof value === "object") {
|
|
233
|
+
for (const child of Object.values(value))
|
|
234
|
+
deepFreeze(child);
|
|
235
|
+
Object.freeze(value);
|
|
236
|
+
}
|
|
237
|
+
return value;
|
|
238
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../src/core/policy/file.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAc,MAAM,YAAY,CAAC;AAE/D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAavE"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { stripJsonComments } from "../config.js";
|
|
4
|
+
import { normalizePolicy } from "./evaluate.js";
|
|
5
|
+
export function loadPolicyFile(path) {
|
|
6
|
+
const absolute = resolve(path);
|
|
7
|
+
let parsed;
|
|
8
|
+
try {
|
|
9
|
+
parsed = JSON.parse(stripJsonComments(readFileSync(absolute, "utf8")));
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
throw new Error(`cannot parse policy at ${absolute}: ${error.message}`);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return normalizePolicy(parsed, { baseDir: dirname(absolute) });
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
throw new Error(`invalid policy at ${absolute}: ${error.message}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { evaluatePolicy, normalizePolicy, PolicyDeniedError, policyDigest, summarizePolicyRequest, } from "./evaluate.js";
|
|
2
|
+
export { loadPolicyFile } from "./file.js";
|
|
3
|
+
export type { DispatchCostEstimator, ExternalMutationRequest, NormalizedPolicy, PolicyAskResolution, PolicyAskResolver, PolicyDecision, PolicyEvaluation, PolicyIsolation, PolicyNetworkAccess, PolicyPhase, PolicyRequest, PolicyRequestSummary, PolicyRuleResult, PolicySpec, PolicyVerdict, } from "./types.js";
|
|
4
|
+
export { POLICY_SCHEMA_VERSION } from "./types.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/policy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,sBAAsB,GACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export declare const POLICY_SCHEMA_VERSION: 1;
|
|
2
|
+
export type PolicyVerdict = "allow" | "ask" | "deny";
|
|
3
|
+
export type PolicyPhase = "dispatch" | "external_mutation";
|
|
4
|
+
export type PolicyIsolation = "shared" | "worktree" | "sandbox" | "remote";
|
|
5
|
+
export type PolicyNetworkAccess = "enabled" | "disabled" | "unknown";
|
|
6
|
+
/** Host-owned policy document. Workflow metadata cannot supply this value. */
|
|
7
|
+
export interface PolicySpec {
|
|
8
|
+
schema_version?: typeof POLICY_SCHEMA_VERSION;
|
|
9
|
+
name?: string;
|
|
10
|
+
max_cost_usd?: number;
|
|
11
|
+
/** Applied when a cost ceiling exists but the host cannot price a dispatch. */
|
|
12
|
+
unknown_cost?: Exclude<PolicyVerdict, "allow">;
|
|
13
|
+
allowed_harnesses?: readonly string[];
|
|
14
|
+
allowed_models?: readonly string[];
|
|
15
|
+
/** Absolute paths, or paths relative to the policy file. */
|
|
16
|
+
allowed_paths?: readonly string[];
|
|
17
|
+
network?: PolicyVerdict;
|
|
18
|
+
external_actions?: PolicyVerdict;
|
|
19
|
+
/** Allowed host-declared execution boundaries. No strength ordering is inferred. */
|
|
20
|
+
allowed_isolation?: readonly PolicyIsolation[];
|
|
21
|
+
}
|
|
22
|
+
export interface NormalizedPolicy {
|
|
23
|
+
schema_version: typeof POLICY_SCHEMA_VERSION;
|
|
24
|
+
name: string;
|
|
25
|
+
max_cost_usd?: number;
|
|
26
|
+
unknown_cost: Exclude<PolicyVerdict, "allow">;
|
|
27
|
+
allowed_harnesses?: readonly string[];
|
|
28
|
+
allowed_models?: readonly string[];
|
|
29
|
+
allowed_paths?: readonly string[];
|
|
30
|
+
network: PolicyVerdict;
|
|
31
|
+
external_actions: PolicyVerdict;
|
|
32
|
+
allowed_isolation?: readonly PolicyIsolation[];
|
|
33
|
+
}
|
|
34
|
+
export interface PolicyRequest {
|
|
35
|
+
phase: PolicyPhase;
|
|
36
|
+
action: string;
|
|
37
|
+
path?: string;
|
|
38
|
+
harness?: string;
|
|
39
|
+
model?: string;
|
|
40
|
+
effort?: string;
|
|
41
|
+
max_attempts?: number;
|
|
42
|
+
max_turns?: number;
|
|
43
|
+
timeout_ms?: number;
|
|
44
|
+
/** Prompt size for host estimation. Prompt content is never included. */
|
|
45
|
+
prompt_bytes?: number;
|
|
46
|
+
isolation: PolicyIsolation;
|
|
47
|
+
network_access: PolicyNetworkAccess;
|
|
48
|
+
service?: string;
|
|
49
|
+
target?: string;
|
|
50
|
+
current_cost_usd?: number;
|
|
51
|
+
projected_cost_usd?: number | null;
|
|
52
|
+
}
|
|
53
|
+
export type PolicyRequestSummary = PolicyRequest;
|
|
54
|
+
export interface PolicyRuleResult {
|
|
55
|
+
code: string;
|
|
56
|
+
verdict: PolicyVerdict;
|
|
57
|
+
reason: string;
|
|
58
|
+
}
|
|
59
|
+
export interface PolicyEvaluation {
|
|
60
|
+
verdict: PolicyVerdict;
|
|
61
|
+
reason: string;
|
|
62
|
+
rules: PolicyRuleResult[];
|
|
63
|
+
}
|
|
64
|
+
export interface PolicyAskResolution {
|
|
65
|
+
verdict: "allow" | "deny";
|
|
66
|
+
reason?: string;
|
|
67
|
+
}
|
|
68
|
+
export type PolicyAskResolver = (request: Readonly<PolicyRequestSummary>, evaluation: Readonly<PolicyEvaluation>) => Promise<PolicyAskResolution> | PolicyAskResolution;
|
|
69
|
+
export interface PolicyDecision {
|
|
70
|
+
id: string;
|
|
71
|
+
checked_at: string;
|
|
72
|
+
policy: string;
|
|
73
|
+
phase: PolicyPhase;
|
|
74
|
+
initial_verdict: PolicyVerdict;
|
|
75
|
+
verdict: "allow" | "deny";
|
|
76
|
+
resolved_by: "policy" | "host" | "fail_closed";
|
|
77
|
+
reason: string;
|
|
78
|
+
rule_codes: string[];
|
|
79
|
+
request: PolicyRequestSummary;
|
|
80
|
+
}
|
|
81
|
+
export interface ExternalMutationRequest {
|
|
82
|
+
/** Short verb phrase, for example "publish release" or "write report". */
|
|
83
|
+
action: string;
|
|
84
|
+
path?: string;
|
|
85
|
+
network?: boolean;
|
|
86
|
+
service?: string;
|
|
87
|
+
/** Bounded destination descriptor. Do not include credentials or payloads. */
|
|
88
|
+
target?: string;
|
|
89
|
+
}
|
|
90
|
+
export type DispatchCostEstimator = (request: Readonly<PolicyRequestSummary>) => Promise<number | null> | number | null;
|
|
91
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/policy/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const POLICY_SCHEMA_VERSION = 1;
|
|
@@ -17,5 +17,10 @@
|
|
|
17
17
|
* cost, duration, and child session id (the resume + web-UI substrate).
|
|
18
18
|
*/
|
|
19
19
|
import type { EngineOpts, RunReport } from "./types.js";
|
|
20
|
+
export declare class WorkflowRunError extends Error {
|
|
21
|
+
readonly runId: string;
|
|
22
|
+
readonly proofPath: string;
|
|
23
|
+
constructor(message: string, runId: string, proofPath: string, cause?: unknown);
|
|
24
|
+
}
|
|
20
25
|
export declare function runWorkflow(scriptPath: string, opts: EngineOpts): Promise<RunReport>;
|
|
21
26
|
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAuBH,OAAO,KAAK,EAEV,UAAU,EAEV,SAAS,EAOV,MAAM,YAAY,CAAC;AAYpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM/E;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAsmB1F"}
|