@blokjs/shared 2.1.0 → 2.2.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.
Files changed (46) hide show
  1. package/dist/AgentSessionContracts.d.ts +316 -0
  2. package/dist/AgentSessionContracts.js +331 -0
  3. package/dist/BlokError.d.ts +23 -0
  4. package/dist/BlokError.js +65 -0
  5. package/dist/CapabilityContracts.d.ts +91 -0
  6. package/dist/CapabilityContracts.js +104 -0
  7. package/dist/CapabilityManifest.d.ts +67 -0
  8. package/dist/CapabilityManifest.js +172 -0
  9. package/dist/EnforcementContracts.d.ts +61 -0
  10. package/dist/EnforcementContracts.js +17 -0
  11. package/dist/EnforcementProfileContracts.d.ts +36 -0
  12. package/dist/EnforcementProfileContracts.js +55 -0
  13. package/dist/EvidenceContracts.d.ts +884 -0
  14. package/dist/EvidenceContracts.js +237 -0
  15. package/dist/GitCapabilityContracts.d.ts +103 -0
  16. package/dist/GitCapabilityContracts.js +222 -0
  17. package/dist/GlobalLogger.d.ts +2 -0
  18. package/dist/GlobalLogger.js +4 -0
  19. package/dist/GraphContracts.d.ts +1643 -0
  20. package/dist/GraphContracts.js +333 -0
  21. package/dist/InteractionContracts.d.ts +76 -0
  22. package/dist/InteractionContracts.js +218 -0
  23. package/dist/JoinContracts.d.ts +593 -0
  24. package/dist/JoinContracts.js +329 -0
  25. package/dist/NodeBase.d.ts +20 -0
  26. package/dist/NodeBase.js +57 -6
  27. package/dist/PermissionAlgebra.d.ts +51 -0
  28. package/dist/PermissionAlgebra.js +125 -0
  29. package/dist/PolicyContracts.d.ts +184 -0
  30. package/dist/PolicyContracts.js +1 -0
  31. package/dist/ProcessCapabilityContracts.d.ts +146 -0
  32. package/dist/ProcessCapabilityContracts.js +263 -0
  33. package/dist/RuntimeContracts.d.ts +125 -0
  34. package/dist/RuntimeContracts.js +108 -0
  35. package/dist/SecretContracts.d.ts +43 -0
  36. package/dist/SecretContracts.js +1 -0
  37. package/dist/WasiComponentContracts.d.ts +582 -0
  38. package/dist/WasiComponentContracts.js +192 -0
  39. package/dist/WorkflowBindingContracts.d.ts +1062 -0
  40. package/dist/WorkflowBindingContracts.js +339 -0
  41. package/dist/index.d.ts +33 -2
  42. package/dist/index.js +21 -2
  43. package/dist/types/LoggerContext.d.ts +7 -0
  44. package/dist/utils/Mapper.d.ts +14 -0
  45. package/dist/utils/Mapper.js +32 -0
  46. package/package.json +3 -2
@@ -0,0 +1,184 @@
1
+ import type { CapabilityEffect, CapabilityManifestV1 } from "./CapabilityManifest.js";
2
+ import type { ApprovalContract } from "./EnforcementContracts.js";
3
+ import type { CapabilityAuthority } from "./PermissionAlgebra.js";
4
+ export type ExecutionOrigin = "ordinary" | "agent";
5
+ export interface PrincipalIdentity {
6
+ readonly id: string;
7
+ readonly kind: string;
8
+ }
9
+ export interface SessionIdentity {
10
+ readonly id: string;
11
+ }
12
+ export interface TurnIdentity {
13
+ readonly id: string;
14
+ readonly index?: number;
15
+ }
16
+ /**
17
+ * Bounded lineage for policy and interaction records.
18
+ *
19
+ * The runner may execute a policy request from a nested workflow or a
20
+ * parallel branch. Keeping this metadata on the request means a control-plane
21
+ * consumer can attribute an answer without inferring ownership from a step
22
+ * name (which is only unique within one workflow definition).
23
+ */
24
+ export interface InteractionAttribution {
25
+ /** Stable root execution/session lineage identifier. */
26
+ readonly rootId: string;
27
+ /** Parent run, workflow, or interaction identifier when nested. */
28
+ readonly parentId?: string;
29
+ /** Stable branch/child identifier for parallel or delegated work. */
30
+ readonly branchId?: string;
31
+ /** Zero-based branch position, when the parent fan-out has an index. */
32
+ readonly branchIndex?: number;
33
+ /** Ordered, bounded path of nested workflow/branch labels. */
34
+ readonly branchPath?: readonly string[];
35
+ /** Nesting depth; root executions use zero. */
36
+ readonly depth: number;
37
+ }
38
+ export interface WorkflowIdentity {
39
+ readonly name: string;
40
+ readonly version?: string;
41
+ }
42
+ export interface StepIdentity {
43
+ readonly id: string;
44
+ readonly index?: number;
45
+ readonly attempt?: number;
46
+ }
47
+ /** Scope requested by a node or policy provider; structurally an authority envelope. */
48
+ export type RequestedCapabilityScope = CapabilityAuthority;
49
+ export type PolicyLayerName = "deployment" | "repository" | "workflow" | "phase" | "user";
50
+ export interface PolicyLayer {
51
+ readonly name: PolicyLayerName;
52
+ readonly version: string;
53
+ }
54
+ export interface PolicyRuleMatch {
55
+ readonly layer: PolicyLayerName;
56
+ readonly ruleId: string;
57
+ readonly effect?: "allow" | "deny" | "ask" | "require-sandbox";
58
+ }
59
+ export type PolicyDecisionKind = "allow" | "deny" | "ask" | "require-sandbox";
60
+ export interface PolicyDecision {
61
+ readonly kind: PolicyDecisionKind;
62
+ readonly id: string;
63
+ readonly reasonCode: string;
64
+ readonly reason?: string;
65
+ readonly policyVersion: string;
66
+ }
67
+ export interface SandboxAttestation {
68
+ readonly id: string;
69
+ readonly issuedAt: string;
70
+ readonly expiresAt: string;
71
+ readonly principalId: string;
72
+ readonly sessionId: string;
73
+ readonly workflow: WorkflowIdentity;
74
+ readonly step: StepIdentity;
75
+ readonly effects: readonly CapabilityEffect[];
76
+ readonly profile: string;
77
+ readonly policyDecisionId: string;
78
+ readonly proof: string;
79
+ }
80
+ export interface PolicyContext {
81
+ readonly origin: ExecutionOrigin;
82
+ readonly principal?: PrincipalIdentity;
83
+ readonly session?: SessionIdentity;
84
+ readonly turn?: TurnIdentity;
85
+ readonly attribution?: InteractionAttribution;
86
+ readonly workflow: WorkflowIdentity;
87
+ readonly step: StepIdentity;
88
+ readonly manifest: CapabilityManifestV1 | null;
89
+ readonly scope: RequestedCapabilityScope;
90
+ readonly layers: readonly PolicyLayer[];
91
+ readonly signal?: AbortSignal;
92
+ /** Explicit approval handoff requested by an H1-02 approval step. */
93
+ readonly approval?: ApprovalContract;
94
+ }
95
+ export interface PolicyRequest extends PolicyContext {
96
+ readonly requestId: string;
97
+ /** Durable execution reference when this request is an interaction ask. */
98
+ readonly suspension?: InteractionSuspension;
99
+ }
100
+ export interface PolicyEvaluationResult {
101
+ readonly decision: PolicyDecision;
102
+ readonly matchedRules: readonly PolicyRuleMatch[];
103
+ /** Optional policy ceiling. It may only narrow the request scope. */
104
+ readonly scope?: RequestedCapabilityScope;
105
+ readonly sandbox?: SandboxAttestation;
106
+ }
107
+ export interface AuditRedactionState {
108
+ readonly redacted: boolean;
109
+ readonly truncated: boolean;
110
+ readonly fields: readonly string[];
111
+ }
112
+ export interface AuditEventBase {
113
+ readonly version: "1";
114
+ readonly eventType: "policy.pre" | "policy.post";
115
+ readonly eventId: string;
116
+ readonly timestamp: string;
117
+ readonly correlationId: string;
118
+ readonly decisionId: string;
119
+ readonly principalId?: string;
120
+ readonly sessionId?: string;
121
+ readonly turnId?: string;
122
+ readonly workflow: WorkflowIdentity;
123
+ readonly step: StepIdentity;
124
+ readonly attempt: number;
125
+ readonly runtime?: string;
126
+ readonly transport?: string;
127
+ readonly manifest: CapabilityManifestV1 | null;
128
+ readonly scope: RequestedCapabilityScope;
129
+ readonly layers: readonly PolicyLayer[];
130
+ readonly matchedRules: readonly PolicyRuleMatch[];
131
+ readonly decision: PolicyDecision;
132
+ readonly attribution?: InteractionAttribution;
133
+ readonly sandbox: {
134
+ required: boolean;
135
+ verified: boolean;
136
+ };
137
+ readonly cached: boolean;
138
+ readonly redaction: AuditRedactionState;
139
+ }
140
+ export interface PreExecutionAuditEvent extends AuditEventBase {
141
+ readonly eventType: "policy.pre";
142
+ }
143
+ export interface PostExecutionAuditEvent extends AuditEventBase {
144
+ readonly eventType: "policy.post";
145
+ readonly durationMs: number;
146
+ readonly outcome: "success" | "failure" | "cancelled";
147
+ readonly errorCode?: string;
148
+ }
149
+ export interface PolicyProvider {
150
+ evaluate(request: PolicyRequest): Promise<PolicyEvaluationResult>;
151
+ }
152
+ export interface AuditSink {
153
+ append(event: PreExecutionAuditEvent | PostExecutionAuditEvent | import("./SecretContracts.js").SecretResolutionAuditEvent): Promise<void>;
154
+ }
155
+ export interface InteractionRequest {
156
+ readonly id: string;
157
+ readonly decision: PolicyDecision;
158
+ readonly request: PolicyRequest;
159
+ /**
160
+ * Durable run identity used by the control plane to resume the existing
161
+ * execution. This is a reference to persisted trace state, not a copy of
162
+ * workflow data or secrets.
163
+ */
164
+ readonly suspension?: InteractionSuspension;
165
+ }
166
+ export interface InteractionSuspension {
167
+ readonly runId: string;
168
+ readonly status: "suspended";
169
+ readonly step: StepIdentity;
170
+ readonly cursor: {
171
+ readonly stepIndex: number;
172
+ readonly deep: boolean;
173
+ readonly nodeRunId?: string;
174
+ readonly lastCompletedStepIndex?: number;
175
+ };
176
+ readonly trace: {
177
+ readonly workflow: WorkflowIdentity;
178
+ readonly parentRunId?: string;
179
+ readonly parentNodeRunId?: string;
180
+ };
181
+ }
182
+ export interface InteractionSuspensionPort {
183
+ suspend(request: InteractionRequest): Promise<void>;
184
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,146 @@
1
+ import { capabilityScope } from "./CapabilityContracts.js";
2
+ import type { CapabilityOwner, CapabilityRequestContext, WorkspacePathRef } from "./CapabilityContracts.js";
3
+ import type { PolicyEvaluationResult, PolicyRequest } from "./PolicyContracts.js";
4
+ export declare const PROCESS_CAPABILITY_CONTRACT_VERSION: "1";
5
+ export declare const PROCESS_DEFAULT_LIMITS: {
6
+ readonly maxWallTimeMs: 120000;
7
+ readonly maxCpuTimeMs: 60000;
8
+ readonly maxMemoryBytes: number;
9
+ readonly maxOutputBytes: number;
10
+ readonly maxInputBytes: number;
11
+ readonly maxProcesses: 1;
12
+ };
13
+ export declare const PROCESS_CAPABILITY_IDS: readonly ["process.exec", "process.pty", "shell.exec"];
14
+ export type ProcessCapabilityId = (typeof PROCESS_CAPABILITY_IDS)[number];
15
+ export type ProcessLifecycleStatus = "starting" | "running" | "exited" | "failed" | "cancelled" | "orphaned" | "cleaned";
16
+ export interface ProcessEnvironmentBinding {
17
+ readonly name: string;
18
+ readonly source: "host" | "secret";
19
+ /** Host environment variable name or opaque SecretRef name; never a value. */
20
+ readonly reference: string;
21
+ }
22
+ export interface NetworkDestination {
23
+ readonly protocol: "tcp" | "udp";
24
+ readonly host: string;
25
+ readonly port: number;
26
+ }
27
+ export type ProcessNetworkPolicy = {
28
+ readonly mode: "none";
29
+ } | {
30
+ readonly mode: "allowlist";
31
+ readonly destinations: readonly NetworkDestination[];
32
+ };
33
+ export interface ProcessResourceLimits {
34
+ readonly maxWallTimeMs: number;
35
+ readonly maxCpuTimeMs: number;
36
+ readonly maxMemoryBytes: number;
37
+ readonly maxOutputBytes: number;
38
+ readonly maxInputBytes: number;
39
+ readonly maxProcesses: number;
40
+ }
41
+ export interface ProcessSpecBase {
42
+ readonly version: typeof PROCESS_CAPABILITY_CONTRACT_VERSION;
43
+ readonly cwd: WorkspacePathRef;
44
+ readonly env: readonly ProcessEnvironmentBinding[];
45
+ readonly stdin: "closed" | "provided";
46
+ readonly terminal: "pipe" | "pty";
47
+ readonly limits: ProcessResourceLimits;
48
+ readonly network: ProcessNetworkPolicy;
49
+ readonly background: "foreground" | "durable";
50
+ }
51
+ /** Structured execution. Providers must pass executable/args to spawn with shell disabled. */
52
+ export interface ExecutableProcessSpec extends ProcessSpecBase {
53
+ readonly mode: "executable";
54
+ readonly executable: string;
55
+ readonly args: readonly string[];
56
+ }
57
+ /** Shell parsing is an explicit, separately policy-classified capability. */
58
+ export interface ShellStringProcessSpec extends ProcessSpecBase {
59
+ readonly mode: "shell-string";
60
+ readonly shell: string;
61
+ readonly command: string;
62
+ }
63
+ export type ProcessSpec = ExecutableProcessSpec | ShellStringProcessSpec;
64
+ export interface ProcessStartRequest extends CapabilityRequestContext {
65
+ readonly policy: PolicyRequest;
66
+ readonly spec: ProcessSpec;
67
+ readonly owner: CapabilityOwner;
68
+ }
69
+ export type ProcessStartResult = {
70
+ readonly kind: "started";
71
+ readonly handle: ProcessHandle;
72
+ } | {
73
+ readonly kind: "completed";
74
+ readonly result: ProcessResult;
75
+ };
76
+ export interface ProcessHandle {
77
+ readonly version: typeof PROCESS_CAPABILITY_CONTRACT_VERSION;
78
+ readonly id: string;
79
+ readonly owner: CapabilityOwner;
80
+ readonly specDigest: string;
81
+ readonly status: ProcessLifecycleStatus;
82
+ readonly startedAt: string;
83
+ readonly updatedAt: string;
84
+ readonly pid?: number;
85
+ readonly terminal: "pipe" | "pty";
86
+ readonly background: "foreground" | "durable";
87
+ readonly outputBytes: number;
88
+ readonly outputTruncated: boolean;
89
+ }
90
+ export interface ProcessOutputChunk {
91
+ readonly stream: "stdout" | "stderr";
92
+ readonly sequence: number;
93
+ readonly data: string;
94
+ readonly byteLength: number;
95
+ }
96
+ export interface ProcessOutputSnapshot {
97
+ readonly stdout: string;
98
+ readonly stderr: string;
99
+ readonly capturedBytes: number;
100
+ readonly totalBytes: number;
101
+ readonly truncated: boolean;
102
+ }
103
+ export interface ProcessResult {
104
+ readonly handle: ProcessHandle;
105
+ readonly status: "exited" | "failed" | "cancelled";
106
+ readonly exitCode?: number;
107
+ readonly signal?: string;
108
+ readonly output: ProcessOutputSnapshot;
109
+ readonly durationMs: number;
110
+ }
111
+ export interface ProcessHandleRequest extends CapabilityRequestContext {
112
+ readonly policy: PolicyRequest;
113
+ readonly handle: ProcessHandle;
114
+ }
115
+ export interface ProcessCancellationRequest extends ProcessHandleRequest {
116
+ readonly reason: "user" | "timeout" | "policy" | "session-closed" | "orphan-cleanup";
117
+ readonly gracePeriodMs: number;
118
+ }
119
+ export interface ProcessOrphanCleanupRequest extends CapabilityRequestContext {
120
+ readonly policy: PolicyRequest;
121
+ readonly owner: CapabilityOwner;
122
+ readonly olderThan: string;
123
+ }
124
+ export interface ProcessCapability {
125
+ /** Durable/background starts return a handle; foreground starts may complete inline. */
126
+ start(request: ProcessStartRequest): Promise<ProcessStartResult>;
127
+ inspect(request: ProcessHandleRequest): Promise<ProcessHandle>;
128
+ readOutput(request: ProcessHandleRequest): AsyncIterable<ProcessOutputChunk>;
129
+ cancel(request: ProcessCancellationRequest): Promise<ProcessHandle>;
130
+ cleanupOrphans(request: ProcessOrphanCleanupRequest): Promise<readonly ProcessHandle[]>;
131
+ }
132
+ export declare function parseProcessSpec(value: unknown): ProcessSpec;
133
+ export declare function parseProcessHandle(value: unknown): ProcessHandle;
134
+ export declare function parseProcessOutput(value: unknown): ProcessOutputSnapshot;
135
+ export declare function parseProcessResult(value: unknown): ProcessResult;
136
+ export declare function parseProcessOutputChunk(value: unknown): ProcessOutputChunk;
137
+ export declare function parseProcessCancellationRequest(value: unknown): ProcessCancellationRequest;
138
+ export declare function parseProcessOrphanCleanupRequest(value: unknown): ProcessOrphanCleanupRequest;
139
+ export declare function processCapabilityId(spec: ProcessSpec): ProcessCapabilityId;
140
+ export declare function processCapabilityScope(spec: ProcessSpec): ReturnType<typeof capabilityScope>;
141
+ export declare function assertProcessPolicyAllowed(spec: ProcessSpec, result: PolicyEvaluationResult): void;
142
+ export declare function assertProcessOwner(handle: ProcessHandle, owner: CapabilityOwner): void;
143
+ export declare function assertBoundedOutputChunk(value: ProcessOutputChunk): void;
144
+ export declare function processWorkspaceCwd(spec: ProcessSpec): WorkspacePathRef;
145
+ /** A durable run must remain addressable; a foreground run must be complete. */
146
+ export declare function assertProcessStartResult(spec: ProcessSpec, result: ProcessStartResult): void;
@@ -0,0 +1,263 @@
1
+ import { z } from "zod";
2
+ import { AGENT_CAPABILITY_CONTRACT_VERSION, CAPABILITY_MAX_ID_LENGTH, CAPABILITY_MAX_LIST_ITEMS, CAPABILITY_MAX_OUTPUT_CHUNK_BYTES, CapabilityContractError, CapabilityOwnerSchema, WorkspacePathRefSchema, assertAuthorized, assertOwned, capabilityScope, identifier, parseCapabilityOwner, timestamp, } from "./CapabilityContracts.js";
3
+ export const PROCESS_CAPABILITY_CONTRACT_VERSION = AGENT_CAPABILITY_CONTRACT_VERSION;
4
+ export const PROCESS_DEFAULT_LIMITS = {
5
+ maxWallTimeMs: 120_000,
6
+ maxCpuTimeMs: 60_000,
7
+ maxMemoryBytes: 512 * 1024 * 1024,
8
+ maxOutputBytes: 4 * 1024 * 1024,
9
+ maxInputBytes: 1 * 1024 * 1024,
10
+ maxProcesses: 1,
11
+ };
12
+ export const PROCESS_CAPABILITY_IDS = ["process.exec", "process.pty", "shell.exec"];
13
+ const envName = z
14
+ .string()
15
+ .min(1)
16
+ .max(256)
17
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/);
18
+ const envBindingSchema = z.object({ name: envName, source: z.enum(["host", "secret"]), reference: identifier });
19
+ const networkSchema = z.discriminatedUnion("mode", [
20
+ z.object({ mode: z.literal("none") }),
21
+ z.object({
22
+ mode: z.literal("allowlist"),
23
+ destinations: z
24
+ .array(z.object({
25
+ protocol: z.enum(["tcp", "udp"]),
26
+ host: z
27
+ .string()
28
+ .min(1)
29
+ .max(253)
30
+ .regex(/^[A-Za-z0-9.-]+$/),
31
+ port: z.number().int().min(1).max(65_535),
32
+ }))
33
+ .min(1)
34
+ .max(128),
35
+ }),
36
+ ]);
37
+ const limitsSchema = z.object({
38
+ maxWallTimeMs: z.number().int().positive().max(86_400_000).default(PROCESS_DEFAULT_LIMITS.maxWallTimeMs),
39
+ maxCpuTimeMs: z.number().int().positive().max(86_400_000).default(PROCESS_DEFAULT_LIMITS.maxCpuTimeMs),
40
+ maxMemoryBytes: z
41
+ .number()
42
+ .int()
43
+ .positive()
44
+ .max(8 * 1024 * 1024 * 1024)
45
+ .default(PROCESS_DEFAULT_LIMITS.maxMemoryBytes),
46
+ maxOutputBytes: z
47
+ .number()
48
+ .int()
49
+ .positive()
50
+ .max(256 * 1024 * 1024)
51
+ .default(PROCESS_DEFAULT_LIMITS.maxOutputBytes),
52
+ maxInputBytes: z
53
+ .number()
54
+ .int()
55
+ .positive()
56
+ .max(256 * 1024 * 1024)
57
+ .default(PROCESS_DEFAULT_LIMITS.maxInputBytes),
58
+ maxProcesses: z.number().int().positive().max(256).default(PROCESS_DEFAULT_LIMITS.maxProcesses),
59
+ });
60
+ const baseSchema = z.object({
61
+ version: z.literal(PROCESS_CAPABILITY_CONTRACT_VERSION),
62
+ cwd: WorkspacePathRefSchema,
63
+ env: z.array(envBindingSchema).max(CAPABILITY_MAX_LIST_ITEMS).default([]),
64
+ stdin: z.enum(["closed", "provided"]).default("closed"),
65
+ terminal: z.enum(["pipe", "pty"]).default("pipe"),
66
+ limits: limitsSchema.default(PROCESS_DEFAULT_LIMITS),
67
+ network: networkSchema.default({ mode: "none" }),
68
+ background: z.enum(["foreground", "durable"]).default("foreground"),
69
+ });
70
+ const executableSchema = baseSchema.extend({
71
+ mode: z.literal("executable"),
72
+ executable: z
73
+ .string()
74
+ .min(1)
75
+ .max(CAPABILITY_MAX_ID_LENGTH)
76
+ .regex(/^[^\s\0;&|<>`$]+$/),
77
+ args: z
78
+ .array(z
79
+ .string()
80
+ .max(16_384)
81
+ .refine((value) => !value.includes("\0"), "must not contain NUL"))
82
+ .max(CAPABILITY_MAX_LIST_ITEMS),
83
+ });
84
+ const shellSchema = baseSchema.extend({
85
+ mode: z.literal("shell-string"),
86
+ shell: z
87
+ .string()
88
+ .min(1)
89
+ .max(CAPABILITY_MAX_ID_LENGTH)
90
+ .regex(/^[^\s\0;&|<>`$]+$/),
91
+ command: z
92
+ .string()
93
+ .min(1)
94
+ .max(256 * 1024)
95
+ .refine((value) => !value.includes("\0"), "must not contain NUL"),
96
+ });
97
+ const processSchema = z.discriminatedUnion("mode", [executableSchema, shellSchema]);
98
+ const digestSchema = z
99
+ .string()
100
+ .regex(/^(?:sha256):[0-9a-f]{64}$|^(?:sha512):[0-9a-f]{128}$/i)
101
+ .transform((value) => value.toLowerCase());
102
+ const handleSchema = z.object({
103
+ version: z.literal(PROCESS_CAPABILITY_CONTRACT_VERSION),
104
+ id: identifier,
105
+ owner: CapabilityOwnerSchema,
106
+ specDigest: digestSchema,
107
+ status: z.enum(["starting", "running", "exited", "failed", "cancelled", "orphaned", "cleaned"]),
108
+ startedAt: timestamp,
109
+ updatedAt: timestamp,
110
+ pid: z.number().int().positive().max(4_194_304).optional(),
111
+ terminal: z.enum(["pipe", "pty"]),
112
+ background: z.enum(["foreground", "durable"]),
113
+ outputBytes: z
114
+ .number()
115
+ .int()
116
+ .nonnegative()
117
+ .max(256 * 1024 * 1024),
118
+ outputTruncated: z.boolean(),
119
+ });
120
+ const outputSchema = z.object({
121
+ stdout: z.string(),
122
+ stderr: z.string(),
123
+ capturedBytes: z
124
+ .number()
125
+ .int()
126
+ .nonnegative()
127
+ .max(256 * 1024 * 1024),
128
+ totalBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
129
+ truncated: z.boolean(),
130
+ });
131
+ const resultSchema = z
132
+ .object({
133
+ handle: handleSchema,
134
+ status: z.enum(["exited", "failed", "cancelled"]),
135
+ exitCode: z.number().int().min(-255).max(255).optional(),
136
+ signal: identifier.optional(),
137
+ output: outputSchema,
138
+ durationMs: z.number().int().nonnegative().max(86_400_000),
139
+ })
140
+ .superRefine((value, context) => {
141
+ if (value.handle.status !== value.status)
142
+ context.addIssue({
143
+ code: z.ZodIssueCode.custom,
144
+ path: ["handle", "status"],
145
+ message: "must match result status",
146
+ });
147
+ });
148
+ const chunkSchema = z.object({
149
+ stream: z.enum(["stdout", "stderr"]),
150
+ sequence: z.number().int().nonnegative(),
151
+ data: z.string(),
152
+ byteLength: z.number().int().nonnegative().max(CAPABILITY_MAX_OUTPUT_CHUNK_BYTES),
153
+ });
154
+ function parse(schema, value, label) {
155
+ const result = schema.safeParse(value);
156
+ if (!result.success)
157
+ throw new CapabilityContractError(`${label}: ${result.error.issues.map((issue) => issue.message).join("; ")}`);
158
+ return result.data;
159
+ }
160
+ function immutable(value) {
161
+ const snapshot = structuredClone(value);
162
+ const freeze = (item) => {
163
+ if (item === null || typeof item !== "object" || Object.isFrozen(item))
164
+ return;
165
+ for (const child of Object.values(item))
166
+ freeze(child);
167
+ Object.freeze(item);
168
+ };
169
+ freeze(snapshot);
170
+ return snapshot;
171
+ }
172
+ export function parseProcessSpec(value) {
173
+ const spec = parse(processSchema, value, "process spec");
174
+ const names = new Set();
175
+ for (const binding of spec.env) {
176
+ if (names.has(binding.name))
177
+ throw new CapabilityContractError(`process spec: duplicate environment name ${binding.name}`);
178
+ names.add(binding.name);
179
+ if (binding.source === "host" && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(binding.reference))
180
+ throw new CapabilityContractError(`process spec: host environment reference ${binding.reference} is invalid`);
181
+ }
182
+ return immutable(spec);
183
+ }
184
+ export function parseProcessHandle(value) {
185
+ return immutable(parse(handleSchema, value, "process handle"));
186
+ }
187
+ export function parseProcessOutput(value) {
188
+ const output = parse(outputSchema, value, "process output");
189
+ const capturedBytes = new TextEncoder().encode(`${output.stdout}${output.stderr}`).byteLength;
190
+ if (capturedBytes !== output.capturedBytes)
191
+ throw new CapabilityContractError("process output capturedBytes is incorrect");
192
+ if (output.capturedBytes > output.totalBytes)
193
+ throw new CapabilityContractError("process output capturedBytes exceeds totalBytes");
194
+ return immutable(output);
195
+ }
196
+ export function parseProcessResult(value) {
197
+ const result = parse(resultSchema, value, "process result");
198
+ const output = parseProcessOutput(result.output);
199
+ return immutable({ ...result, output });
200
+ }
201
+ export function parseProcessOutputChunk(value) {
202
+ const chunk = parse(chunkSchema, value, "process output chunk");
203
+ const byteLength = new TextEncoder().encode(chunk.data).byteLength;
204
+ if (byteLength !== chunk.byteLength)
205
+ throw new CapabilityContractError("process output chunk byteLength is incorrect");
206
+ return immutable(chunk);
207
+ }
208
+ export function parseProcessCancellationRequest(value) {
209
+ const schema = z.object({
210
+ policy: z.custom(),
211
+ owner: CapabilityOwnerSchema,
212
+ handle: handleSchema,
213
+ reason: z.enum(["user", "timeout", "policy", "session-closed", "orphan-cleanup"]),
214
+ gracePeriodMs: z.number().int().nonnegative().max(60_000),
215
+ });
216
+ return immutable(parse(schema, value, "process cancellation request"));
217
+ }
218
+ export function parseProcessOrphanCleanupRequest(value) {
219
+ const schema = z.object({
220
+ policy: z.custom(),
221
+ owner: CapabilityOwnerSchema,
222
+ olderThan: timestamp,
223
+ });
224
+ return immutable(parse(schema, value, "process orphan cleanup request"));
225
+ }
226
+ export function processCapabilityId(spec) {
227
+ if (spec.mode === "shell-string")
228
+ return "shell.exec";
229
+ return spec.terminal === "pty" ? "process.pty" : "process.exec";
230
+ }
231
+ export function processCapabilityScope(spec) {
232
+ const effects = ["process"];
233
+ const capabilities = [processCapabilityId(spec)];
234
+ const secrets = spec.env.filter((binding) => binding.source === "secret").map((binding) => binding.reference);
235
+ const withTerminal = spec.terminal === "pty" ? [...effects, "streaming"] : [...effects];
236
+ if (spec.network.mode === "allowlist")
237
+ return capabilityScope([...withTerminal, "network"], capabilities, secrets);
238
+ return capabilityScope(withTerminal, capabilities, secrets);
239
+ }
240
+ export function assertProcessPolicyAllowed(spec, result) {
241
+ if (spec.mode === "shell-string" && result.decision.kind !== "allow")
242
+ throw new CapabilityContractError(`shell-string execution requires explicit allow policy: ${result.decision.kind}`);
243
+ if (spec.mode === "shell-string" && result.decision.kind === "require-sandbox")
244
+ throw new CapabilityContractError("shell-string execution requires an explicit allow decision");
245
+ assertAuthorized(result, { allowSandbox: true });
246
+ }
247
+ export function assertProcessOwner(handle, owner) {
248
+ assertOwned(handle.owner, parseCapabilityOwner(owner));
249
+ }
250
+ export function assertBoundedOutputChunk(value) {
251
+ if (new TextEncoder().encode(value.data).byteLength > CAPABILITY_MAX_OUTPUT_CHUNK_BYTES)
252
+ throw new CapabilityContractError("process output chunk exceeds the hard byte bound");
253
+ }
254
+ export function processWorkspaceCwd(spec) {
255
+ return spec.cwd;
256
+ }
257
+ /** A durable run must remain addressable; a foreground run must be complete. */
258
+ export function assertProcessStartResult(spec, result) {
259
+ if (spec.background === "durable" && result.kind !== "started")
260
+ throw new CapabilityContractError("durable process execution must return a background handle");
261
+ if (spec.background === "foreground" && result.kind !== "completed")
262
+ throw new CapabilityContractError("foreground process execution must complete inline");
263
+ }