@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.
- package/dist/AgentSessionContracts.d.ts +316 -0
- package/dist/AgentSessionContracts.js +331 -0
- package/dist/BlokError.d.ts +23 -0
- package/dist/BlokError.js +65 -0
- package/dist/CapabilityContracts.d.ts +91 -0
- package/dist/CapabilityContracts.js +104 -0
- package/dist/CapabilityManifest.d.ts +67 -0
- package/dist/CapabilityManifest.js +172 -0
- package/dist/EnforcementContracts.d.ts +61 -0
- package/dist/EnforcementContracts.js +17 -0
- package/dist/EnforcementProfileContracts.d.ts +36 -0
- package/dist/EnforcementProfileContracts.js +55 -0
- package/dist/EvidenceContracts.d.ts +884 -0
- package/dist/EvidenceContracts.js +237 -0
- package/dist/GitCapabilityContracts.d.ts +103 -0
- package/dist/GitCapabilityContracts.js +222 -0
- package/dist/GlobalLogger.d.ts +2 -0
- package/dist/GlobalLogger.js +4 -0
- package/dist/GraphContracts.d.ts +1643 -0
- package/dist/GraphContracts.js +333 -0
- package/dist/InteractionContracts.d.ts +76 -0
- package/dist/InteractionContracts.js +218 -0
- package/dist/JoinContracts.d.ts +593 -0
- package/dist/JoinContracts.js +329 -0
- package/dist/NodeBase.d.ts +20 -0
- package/dist/NodeBase.js +57 -6
- package/dist/PermissionAlgebra.d.ts +51 -0
- package/dist/PermissionAlgebra.js +125 -0
- package/dist/PolicyContracts.d.ts +184 -0
- package/dist/PolicyContracts.js +1 -0
- package/dist/ProcessCapabilityContracts.d.ts +146 -0
- package/dist/ProcessCapabilityContracts.js +263 -0
- package/dist/RuntimeContracts.d.ts +125 -0
- package/dist/RuntimeContracts.js +108 -0
- package/dist/SecretContracts.d.ts +43 -0
- package/dist/SecretContracts.js +1 -0
- package/dist/WasiComponentContracts.d.ts +582 -0
- package/dist/WasiComponentContracts.js +192 -0
- package/dist/WorkflowBindingContracts.d.ts +1062 -0
- package/dist/WorkflowBindingContracts.js +339 -0
- package/dist/index.d.ts +33 -2
- package/dist/index.js +21 -2
- package/dist/types/LoggerContext.d.ts +7 -0
- package/dist/utils/Mapper.d.ts +14 -0
- package/dist/utils/Mapper.js +32 -0
- package/package.json +3 -2
package/dist/BlokError.js
CHANGED
|
@@ -352,6 +352,71 @@ export const WORKFLOW_INPUT_VALIDATION = "WORKFLOW_INPUT_VALIDATION";
|
|
|
352
352
|
export function isNonRetryableValidationError(err) {
|
|
353
353
|
return err instanceof GlobalError && err.context.name === WORKFLOW_INPUT_VALIDATION;
|
|
354
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* How far a `cause` chain is walked when classifying an error. Bounded so a
|
|
357
|
+
* self-referential or pathologically deep chain can't spin.
|
|
358
|
+
*/
|
|
359
|
+
const CAUSE_WALK_DEPTH = 8;
|
|
360
|
+
/**
|
|
361
|
+
* Selective retry — THE matcher for a step's `retry.nonRetryableErrorNames`.
|
|
362
|
+
* Inspects the error name through wrapped causes: both plain `Error.name` and
|
|
363
|
+
* `GlobalError.context.name` count, and `cause` chains are walked to a bounded
|
|
364
|
+
* depth so an enriched or re-thrown error cannot hide a non-retryable
|
|
365
|
+
* classification.
|
|
366
|
+
*
|
|
367
|
+
* `RunnerSteps` calls this to short-circuit STEP retries, then records the
|
|
368
|
+
* verdict on the propagating error with {@link markNonRetryableStepError}.
|
|
369
|
+
* Transports read the verdict back with {@link isNonRetryableStepError} rather
|
|
370
|
+
* than re-deriving it, so JOB-level retry (#679 — BullMQ and every other worker
|
|
371
|
+
* adapter) agrees with step-level retry by construction instead of via a second
|
|
372
|
+
* implementation that could drift.
|
|
373
|
+
*/
|
|
374
|
+
export function isNonRetryableError(error, names) {
|
|
375
|
+
if (!names || names.length === 0)
|
|
376
|
+
return false;
|
|
377
|
+
let current = error;
|
|
378
|
+
for (let depth = 0; depth < CAUSE_WALK_DEPTH && current && typeof current === "object"; depth++) {
|
|
379
|
+
const candidate = current;
|
|
380
|
+
if (typeof candidate.name === "string" && names.includes(candidate.name))
|
|
381
|
+
return true;
|
|
382
|
+
if (candidate.context &&
|
|
383
|
+
typeof candidate.context === "object" &&
|
|
384
|
+
typeof candidate.context.name === "string" &&
|
|
385
|
+
names.includes(candidate.context.name)) {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
current = candidate.cause;
|
|
389
|
+
}
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Property the runner stamps on a failure {@link isNonRetryableError} matched.
|
|
394
|
+
* Sticky-TRUE only: its absence means "never classified", never "retryable" —
|
|
395
|
+
* the runner re-wraps errors several times (step enrichment → GlobalError
|
|
396
|
+
* unwrap) and an intermediate wrap whose own step declared nothing must not
|
|
397
|
+
* erase an inner verdict.
|
|
398
|
+
*/
|
|
399
|
+
const NON_RETRYABLE_STEP_ERROR = "_blokNonRetryable";
|
|
400
|
+
/** Record a {@link isNonRetryableError} match on `error` (no-op for non-objects). */
|
|
401
|
+
export function markNonRetryableStepError(error) {
|
|
402
|
+
if (error && typeof error === "object") {
|
|
403
|
+
error[NON_RETRYABLE_STEP_ERROR] = true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* True when the runner classified this failure — or one it wraps — as declared
|
|
408
|
+
* non-retryable. Walks `cause` to the same bounded depth as
|
|
409
|
+
* {@link isNonRetryableError} so an outer wrap cannot hide the verdict.
|
|
410
|
+
*/
|
|
411
|
+
export function isNonRetryableStepError(error) {
|
|
412
|
+
let current = error;
|
|
413
|
+
for (let depth = 0; depth < CAUSE_WALK_DEPTH && current && typeof current === "object"; depth++) {
|
|
414
|
+
if (current[NON_RETRYABLE_STEP_ERROR] === true)
|
|
415
|
+
return true;
|
|
416
|
+
current = current.cause;
|
|
417
|
+
}
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
355
420
|
/**
|
|
356
421
|
* ADR 0015 — thrown by the input gate in `TriggerBase.run()` when the invoking
|
|
357
422
|
* trigger's payload fails the workflow's declared `input` Zod.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { PolicyContext, PolicyDecision, PolicyEvaluationResult, PolicyProvider } from "./PolicyContracts.js";
|
|
3
|
+
import type { PrincipalIdentity, RequestedCapabilityScope } from "./PolicyContracts.js";
|
|
4
|
+
/** Version shared by the H3 capability contracts. */
|
|
5
|
+
export declare const AGENT_CAPABILITY_CONTRACT_VERSION: "1";
|
|
6
|
+
export declare const CAPABILITY_MAX_ID_LENGTH = 128;
|
|
7
|
+
export declare const CAPABILITY_MAX_PATH_LENGTH = 4096;
|
|
8
|
+
export declare const CAPABILITY_MAX_LIST_ITEMS = 1024;
|
|
9
|
+
export declare const CAPABILITY_MAX_OUTPUT_CHUNK_BYTES: number;
|
|
10
|
+
export interface CapabilityOwner {
|
|
11
|
+
readonly principal: PrincipalIdentity;
|
|
12
|
+
readonly sessionId: string;
|
|
13
|
+
readonly turnId?: string;
|
|
14
|
+
readonly taskId: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* An opaque reference into the workspace capability (#927). H3 contracts do
|
|
18
|
+
* not canonicalize paths or access the host filesystem; the filesystem
|
|
19
|
+
* capability owns that boundary.
|
|
20
|
+
*/
|
|
21
|
+
export interface WorkspacePathRef {
|
|
22
|
+
readonly workspaceId: string;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
}
|
|
25
|
+
export interface CapabilityRequestContext {
|
|
26
|
+
readonly policy: PolicyContext;
|
|
27
|
+
readonly owner: CapabilityOwner;
|
|
28
|
+
}
|
|
29
|
+
/** Every trusted adapter must authorize before doing host-side work. */
|
|
30
|
+
export interface CapabilityAuthorizationPort {
|
|
31
|
+
authorize(request: PolicyContext): Promise<PolicyEvaluationResult>;
|
|
32
|
+
readonly provider?: PolicyProvider;
|
|
33
|
+
}
|
|
34
|
+
export declare class CapabilityContractError extends Error {
|
|
35
|
+
readonly code = "CAPABILITY_CONTRACT_INVALID";
|
|
36
|
+
constructor(message: string);
|
|
37
|
+
}
|
|
38
|
+
declare const identifier: z.ZodString;
|
|
39
|
+
declare const path: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, string, string>;
|
|
40
|
+
declare const timestamp: z.ZodEffects<z.ZodString, string, string>;
|
|
41
|
+
declare const digest: z.ZodEffects<z.ZodString, string, string>;
|
|
42
|
+
export declare const CapabilityOwnerSchema: z.ZodObject<{
|
|
43
|
+
principal: z.ZodObject<{
|
|
44
|
+
id: z.ZodString;
|
|
45
|
+
kind: z.ZodString;
|
|
46
|
+
}, "strip", z.ZodTypeAny, {
|
|
47
|
+
id: string;
|
|
48
|
+
kind: string;
|
|
49
|
+
}, {
|
|
50
|
+
id: string;
|
|
51
|
+
kind: string;
|
|
52
|
+
}>;
|
|
53
|
+
sessionId: z.ZodString;
|
|
54
|
+
turnId: z.ZodOptional<z.ZodString>;
|
|
55
|
+
taskId: z.ZodString;
|
|
56
|
+
}, "strip", z.ZodTypeAny, {
|
|
57
|
+
sessionId: string;
|
|
58
|
+
principal: {
|
|
59
|
+
id: string;
|
|
60
|
+
kind: string;
|
|
61
|
+
};
|
|
62
|
+
taskId: string;
|
|
63
|
+
turnId?: string | undefined;
|
|
64
|
+
}, {
|
|
65
|
+
sessionId: string;
|
|
66
|
+
principal: {
|
|
67
|
+
id: string;
|
|
68
|
+
kind: string;
|
|
69
|
+
};
|
|
70
|
+
taskId: string;
|
|
71
|
+
turnId?: string | undefined;
|
|
72
|
+
}>;
|
|
73
|
+
export declare const WorkspacePathRefSchema: z.ZodObject<{
|
|
74
|
+
workspaceId: z.ZodString;
|
|
75
|
+
path: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>, string, string>;
|
|
76
|
+
}, "strip", z.ZodTypeAny, {
|
|
77
|
+
path: string;
|
|
78
|
+
workspaceId: string;
|
|
79
|
+
}, {
|
|
80
|
+
path: string;
|
|
81
|
+
workspaceId: string;
|
|
82
|
+
}>;
|
|
83
|
+
export declare function parseCapabilityOwner(value: unknown): CapabilityOwner;
|
|
84
|
+
export declare function parseWorkspacePathRef(value: unknown): WorkspacePathRef;
|
|
85
|
+
export declare function parseCapabilityAuthorization(decision: PolicyDecision, result?: PolicyEvaluationResult): PolicyEvaluationResult;
|
|
86
|
+
export declare function assertAuthorized(result: PolicyEvaluationResult, options?: {
|
|
87
|
+
allowSandbox?: boolean;
|
|
88
|
+
}): void;
|
|
89
|
+
export declare function assertOwned(owner: CapabilityOwner, expected: CapabilityOwner): void;
|
|
90
|
+
export declare function capabilityScope(effects: RequestedCapabilityScope["effects"], capabilities: readonly string[], secrets?: readonly string[]): RequestedCapabilityScope;
|
|
91
|
+
export { digest, identifier, path, timestamp };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Version shared by the H3 capability contracts. */
|
|
3
|
+
export const AGENT_CAPABILITY_CONTRACT_VERSION = "1";
|
|
4
|
+
export const CAPABILITY_MAX_ID_LENGTH = 128;
|
|
5
|
+
export const CAPABILITY_MAX_PATH_LENGTH = 4_096;
|
|
6
|
+
export const CAPABILITY_MAX_LIST_ITEMS = 1_024;
|
|
7
|
+
export const CAPABILITY_MAX_OUTPUT_CHUNK_BYTES = 64 * 1024;
|
|
8
|
+
export class CapabilityContractError extends Error {
|
|
9
|
+
code = "CAPABILITY_CONTRACT_INVALID";
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "CapabilityContractError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const identifier = z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(CAPABILITY_MAX_ID_LENGTH)
|
|
19
|
+
.regex(/^[A-Za-z][A-Za-z0-9._:/-]*$/);
|
|
20
|
+
const path = z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.max(CAPABILITY_MAX_PATH_LENGTH)
|
|
24
|
+
.refine((value) => !value.includes("\0"), "must not contain NUL")
|
|
25
|
+
.refine((value) => !value.startsWith("/") && !/^[A-Za-z]:[\\/]/.test(value), "must be workspace-relative")
|
|
26
|
+
.refine((value) => !value.split(/[\\/]+/).includes(".."), "must not escape the workspace");
|
|
27
|
+
const timestamp = z.string().refine((value) => {
|
|
28
|
+
if (!Number.isFinite(Date.parse(value)))
|
|
29
|
+
return false;
|
|
30
|
+
return new Date(value).toISOString() === value;
|
|
31
|
+
}, "must be a canonical ISO timestamp");
|
|
32
|
+
const sessionId = z.string().min(1).max(CAPABILITY_MAX_ID_LENGTH);
|
|
33
|
+
const digest = z
|
|
34
|
+
.string()
|
|
35
|
+
.regex(/^(?:sha256):[0-9a-f]{64}$|^(?:sha512):[0-9a-f]{128}$/i)
|
|
36
|
+
.transform((value) => value.toLowerCase());
|
|
37
|
+
const ownerSchema = z.object({
|
|
38
|
+
principal: z.object({ id: identifier, kind: identifier }),
|
|
39
|
+
// Session IDs are opaque references and the control plane uses UUIDs.
|
|
40
|
+
sessionId,
|
|
41
|
+
turnId: identifier.optional(),
|
|
42
|
+
taskId: identifier,
|
|
43
|
+
});
|
|
44
|
+
const workspacePathSchema = z.object({ workspaceId: identifier, path });
|
|
45
|
+
export const CapabilityOwnerSchema = ownerSchema;
|
|
46
|
+
export const WorkspacePathRefSchema = workspacePathSchema;
|
|
47
|
+
function parse(schema, value, label) {
|
|
48
|
+
const result = schema.safeParse(value);
|
|
49
|
+
if (!result.success) {
|
|
50
|
+
throw new CapabilityContractError(result.error.issues
|
|
51
|
+
.map((issue) => `${label}${issue.path.length > 0 ? `.${issue.path.join(".")}` : ""} ${issue.message}`)
|
|
52
|
+
.join("; "));
|
|
53
|
+
}
|
|
54
|
+
return result.data;
|
|
55
|
+
}
|
|
56
|
+
function immutable(value) {
|
|
57
|
+
const snapshot = structuredClone(value);
|
|
58
|
+
const freeze = (item) => {
|
|
59
|
+
if (item === null || typeof item !== "object" || Object.isFrozen(item))
|
|
60
|
+
return;
|
|
61
|
+
for (const child of Object.values(item))
|
|
62
|
+
freeze(child);
|
|
63
|
+
Object.freeze(item);
|
|
64
|
+
};
|
|
65
|
+
freeze(snapshot);
|
|
66
|
+
return snapshot;
|
|
67
|
+
}
|
|
68
|
+
export function parseCapabilityOwner(value) {
|
|
69
|
+
return immutable(parse(ownerSchema, value, "capability owner"));
|
|
70
|
+
}
|
|
71
|
+
export function parseWorkspacePathRef(value) {
|
|
72
|
+
return immutable(parse(workspacePathSchema, value, "workspace path"));
|
|
73
|
+
}
|
|
74
|
+
export function parseCapabilityAuthorization(decision, result) {
|
|
75
|
+
if (!result || result.decision.id !== decision.id) {
|
|
76
|
+
throw new CapabilityContractError("authorization result does not match the policy decision");
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
export function assertAuthorized(result, options = {}) {
|
|
81
|
+
if (result.decision.kind === "allow")
|
|
82
|
+
return;
|
|
83
|
+
if (options.allowSandbox && result.decision.kind === "require-sandbox" && result.sandbox?.proof)
|
|
84
|
+
return;
|
|
85
|
+
throw new CapabilityContractError(`capability execution is not authorized: ${result.decision.kind}`);
|
|
86
|
+
}
|
|
87
|
+
export function assertOwned(owner, expected) {
|
|
88
|
+
if (owner.principal.id !== expected.principal.id ||
|
|
89
|
+
owner.principal.kind !== expected.principal.kind ||
|
|
90
|
+
owner.sessionId !== expected.sessionId ||
|
|
91
|
+
owner.taskId !== expected.taskId ||
|
|
92
|
+
(owner.turnId ?? "") !== (expected.turnId ?? "")) {
|
|
93
|
+
throw new CapabilityContractError("capability handle is owned by a different task context");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function capabilityScope(effects, capabilities, secrets = []) {
|
|
97
|
+
return {
|
|
98
|
+
effects: [...new Set(effects)].sort(),
|
|
99
|
+
capabilities: [...new Set(capabilities)].sort(),
|
|
100
|
+
secrets: [...new Set(secrets)].sort(),
|
|
101
|
+
fragments: {},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export { digest, identifier, path, timestamp };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-neutral operational metadata for nodes and workflows (ADR 0003).
|
|
3
|
+
*
|
|
4
|
+
* Schemas describe values; this manifest describes risk. It is deliberately
|
|
5
|
+
* data-only so every SDK can emit the same JSON through gRPC `ListNodes`.
|
|
6
|
+
* Unknown object fields are ignored when parsing v1, allowing additive wire
|
|
7
|
+
* evolution, while an unknown VERSION remains ineligible for agent execution.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CAPABILITY_MANIFEST_VERSION: "1";
|
|
10
|
+
export declare const CAPABILITY_EFFECTS: readonly ["read", "write", "network", "filesystem", "process", "secret", "streaming", "destructive"];
|
|
11
|
+
export declare const CAPABILITY_CLASSIFICATIONS: readonly ["agent-compatible", "trusted-legacy", "denied-to-agents"];
|
|
12
|
+
export declare const CAPABILITY_DETERMINISM: readonly ["deterministic", "time-dependent", "random", "external", "unknown"];
|
|
13
|
+
export declare const CAPABILITY_IDEMPOTENCY: readonly ["idempotent", "conditionally-idempotent", "non-idempotent", "unknown"];
|
|
14
|
+
export declare const CAPABILITY_MATURITY: readonly ["stable", "beta", "experimental", "deprecated"];
|
|
15
|
+
export type CapabilityEffect = (typeof CAPABILITY_EFFECTS)[number];
|
|
16
|
+
export type CapabilityClassification = (typeof CAPABILITY_CLASSIFICATIONS)[number];
|
|
17
|
+
export type CapabilityDeterminism = (typeof CAPABILITY_DETERMINISM)[number];
|
|
18
|
+
export type CapabilityIdempotency = (typeof CAPABILITY_IDEMPOTENCY)[number];
|
|
19
|
+
export type CapabilityMaturity = (typeof CAPABILITY_MATURITY)[number];
|
|
20
|
+
export interface CapabilityResourceBounds {
|
|
21
|
+
maxDurationMs?: number;
|
|
22
|
+
maxMemoryBytes?: number;
|
|
23
|
+
maxInputBytes?: number;
|
|
24
|
+
maxOutputBytes?: number;
|
|
25
|
+
maxConcurrency?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface CapabilityManifestV1 {
|
|
28
|
+
version: typeof CAPABILITY_MANIFEST_VERSION;
|
|
29
|
+
/** Agent compatibility is explicit; absence never implies safety. */
|
|
30
|
+
classification: CapabilityClassification;
|
|
31
|
+
/** Empty means pure: no externally observable operational effect. */
|
|
32
|
+
effects: CapabilityEffect[];
|
|
33
|
+
/** Fine-grained capability identifiers, e.g. `network.http` or `fs.workspace.read`. */
|
|
34
|
+
capabilities: string[];
|
|
35
|
+
/** Opaque secret reference NAMES only. Never put credentials here. */
|
|
36
|
+
secrets: string[];
|
|
37
|
+
determinism: CapabilityDeterminism;
|
|
38
|
+
idempotency: CapabilityIdempotency;
|
|
39
|
+
maturity: CapabilityMaturity;
|
|
40
|
+
resources?: CapabilityResourceBounds;
|
|
41
|
+
/** Optional applicability constraints. Empty/absent means unrestricted. */
|
|
42
|
+
runtimes?: string[];
|
|
43
|
+
triggers?: string[];
|
|
44
|
+
}
|
|
45
|
+
export type CapabilityManifestStatus = "declared" | "missing" | "invalid";
|
|
46
|
+
export interface CapabilityManifestAssessment {
|
|
47
|
+
status: CapabilityManifestStatus;
|
|
48
|
+
manifest: CapabilityManifestV1 | null;
|
|
49
|
+
errors: string[];
|
|
50
|
+
agentEligible: boolean;
|
|
51
|
+
reason: "eligible" | "missing-manifest" | "invalid-manifest" | "trusted-legacy" | "denied-to-agents";
|
|
52
|
+
}
|
|
53
|
+
export declare class CapabilityManifestError extends Error {
|
|
54
|
+
readonly errors: readonly string[];
|
|
55
|
+
constructor(errors: readonly string[]);
|
|
56
|
+
}
|
|
57
|
+
/** Parse and normalize a v1 manifest. Additive unknown fields are ignored. */
|
|
58
|
+
export declare function parseCapabilityManifest(value: unknown): CapabilityManifestV1;
|
|
59
|
+
/** Stable JSON bytes/order for catalog reflection and cross-runtime fixtures. */
|
|
60
|
+
export declare function serializeCapabilityManifest(value: unknown): string;
|
|
61
|
+
/**
|
|
62
|
+
* Catalog/policy boundary. Missing and invalid metadata are explicit and
|
|
63
|
+
* agent-ineligible; ordinary execution remains unchanged until policy opts in.
|
|
64
|
+
*/
|
|
65
|
+
export declare function assessCapabilityManifest(value: unknown): CapabilityManifestAssessment;
|
|
66
|
+
/** Fail-closed helper for the future agent execution policy boundary. */
|
|
67
|
+
export declare function requireAgentEligibleManifest(value: unknown): CapabilityManifestV1;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-neutral operational metadata for nodes and workflows (ADR 0003).
|
|
3
|
+
*
|
|
4
|
+
* Schemas describe values; this manifest describes risk. It is deliberately
|
|
5
|
+
* data-only so every SDK can emit the same JSON through gRPC `ListNodes`.
|
|
6
|
+
* Unknown object fields are ignored when parsing v1, allowing additive wire
|
|
7
|
+
* evolution, while an unknown VERSION remains ineligible for agent execution.
|
|
8
|
+
*/
|
|
9
|
+
export const CAPABILITY_MANIFEST_VERSION = "1";
|
|
10
|
+
export const CAPABILITY_EFFECTS = [
|
|
11
|
+
"read",
|
|
12
|
+
"write",
|
|
13
|
+
"network",
|
|
14
|
+
"filesystem",
|
|
15
|
+
"process",
|
|
16
|
+
"secret",
|
|
17
|
+
"streaming",
|
|
18
|
+
"destructive",
|
|
19
|
+
];
|
|
20
|
+
export const CAPABILITY_CLASSIFICATIONS = ["agent-compatible", "trusted-legacy", "denied-to-agents"];
|
|
21
|
+
export const CAPABILITY_DETERMINISM = ["deterministic", "time-dependent", "random", "external", "unknown"];
|
|
22
|
+
export const CAPABILITY_IDEMPOTENCY = ["idempotent", "conditionally-idempotent", "non-idempotent", "unknown"];
|
|
23
|
+
export const CAPABILITY_MATURITY = ["stable", "beta", "experimental", "deprecated"];
|
|
24
|
+
export class CapabilityManifestError extends Error {
|
|
25
|
+
errors;
|
|
26
|
+
constructor(errors) {
|
|
27
|
+
super(`Invalid capability manifest: ${errors.join("; ")}`);
|
|
28
|
+
this.name = "CapabilityManifestError";
|
|
29
|
+
this.errors = [...errors];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const IDENTIFIER = /^[A-Za-z][A-Za-z0-9._:/-]{0,127}$/;
|
|
33
|
+
const EFFECT_SET = new Set(CAPABILITY_EFFECTS);
|
|
34
|
+
const CLASSIFICATION_SET = new Set(CAPABILITY_CLASSIFICATIONS);
|
|
35
|
+
const DETERMINISM_SET = new Set(CAPABILITY_DETERMINISM);
|
|
36
|
+
const IDEMPOTENCY_SET = new Set(CAPABILITY_IDEMPOTENCY);
|
|
37
|
+
const MATURITY_SET = new Set(CAPABILITY_MATURITY);
|
|
38
|
+
const RESOURCE_KEYS = ["maxDurationMs", "maxMemoryBytes", "maxInputBytes", "maxOutputBytes", "maxConcurrency"];
|
|
39
|
+
function isRecord(value) {
|
|
40
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
41
|
+
}
|
|
42
|
+
function enumValue(value, path, allowed, errors) {
|
|
43
|
+
if (typeof value !== "string" || !allowed.has(value)) {
|
|
44
|
+
errors.push(`${path} must be one of: ${[...allowed].join(", ")}`);
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function stringList(value, path, errors, allowed) {
|
|
50
|
+
if (!Array.isArray(value)) {
|
|
51
|
+
errors.push(`${path} must be an array`);
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const result = new Set();
|
|
55
|
+
for (let i = 0; i < value.length; i++) {
|
|
56
|
+
const item = value[i];
|
|
57
|
+
if (typeof item !== "string" || (!allowed && !IDENTIFIER.test(item)) || (allowed && !allowed.has(item))) {
|
|
58
|
+
errors.push(`${path}[${i}] is not a valid ${allowed ? "value" : "identifier"}`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
result.add(item);
|
|
62
|
+
}
|
|
63
|
+
return [...result].sort();
|
|
64
|
+
}
|
|
65
|
+
function resourceBounds(value, errors) {
|
|
66
|
+
if (value === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
if (!isRecord(value)) {
|
|
69
|
+
errors.push("resources must be an object");
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const result = {};
|
|
73
|
+
for (const key of RESOURCE_KEYS) {
|
|
74
|
+
const raw = value[key];
|
|
75
|
+
if (raw === undefined)
|
|
76
|
+
continue;
|
|
77
|
+
if (typeof raw !== "number" || !Number.isSafeInteger(raw) || raw <= 0) {
|
|
78
|
+
errors.push(`resources.${key} must be a positive safe integer`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
result[key] = raw;
|
|
82
|
+
}
|
|
83
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
84
|
+
}
|
|
85
|
+
/** Parse and normalize a v1 manifest. Additive unknown fields are ignored. */
|
|
86
|
+
export function parseCapabilityManifest(value) {
|
|
87
|
+
const errors = [];
|
|
88
|
+
if (!isRecord(value))
|
|
89
|
+
throw new CapabilityManifestError(["manifest must be an object"]);
|
|
90
|
+
if (value.version !== CAPABILITY_MANIFEST_VERSION) {
|
|
91
|
+
errors.push(`version must be ${CAPABILITY_MANIFEST_VERSION}`);
|
|
92
|
+
}
|
|
93
|
+
const classification = enumValue(value.classification, "classification", CLASSIFICATION_SET, errors);
|
|
94
|
+
const determinism = enumValue(value.determinism, "determinism", DETERMINISM_SET, errors);
|
|
95
|
+
const idempotency = enumValue(value.idempotency, "idempotency", IDEMPOTENCY_SET, errors);
|
|
96
|
+
const maturity = enumValue(value.maturity, "maturity", MATURITY_SET, errors);
|
|
97
|
+
const effects = stringList(value.effects, "effects", errors, EFFECT_SET);
|
|
98
|
+
const capabilities = stringList(value.capabilities, "capabilities", errors);
|
|
99
|
+
const secrets = stringList(value.secrets, "secrets", errors);
|
|
100
|
+
const resources = resourceBounds(value.resources, errors);
|
|
101
|
+
const runtimes = value.runtimes === undefined ? undefined : stringList(value.runtimes, "runtimes", errors);
|
|
102
|
+
const triggers = value.triggers === undefined ? undefined : stringList(value.triggers, "triggers", errors);
|
|
103
|
+
if (errors.length > 0 || !classification || !determinism || !idempotency || !maturity) {
|
|
104
|
+
throw new CapabilityManifestError(errors);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
version: CAPABILITY_MANIFEST_VERSION,
|
|
108
|
+
classification,
|
|
109
|
+
effects,
|
|
110
|
+
capabilities,
|
|
111
|
+
secrets,
|
|
112
|
+
determinism,
|
|
113
|
+
idempotency,
|
|
114
|
+
maturity,
|
|
115
|
+
...(resources ? { resources } : {}),
|
|
116
|
+
...(runtimes ? { runtimes } : {}),
|
|
117
|
+
...(triggers ? { triggers } : {}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Stable JSON bytes/order for catalog reflection and cross-runtime fixtures. */
|
|
121
|
+
export function serializeCapabilityManifest(value) {
|
|
122
|
+
return JSON.stringify(parseCapabilityManifest(value));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Catalog/policy boundary. Missing and invalid metadata are explicit and
|
|
126
|
+
* agent-ineligible; ordinary execution remains unchanged until policy opts in.
|
|
127
|
+
*/
|
|
128
|
+
export function assessCapabilityManifest(value) {
|
|
129
|
+
if (value === undefined || value === null || value === "") {
|
|
130
|
+
return {
|
|
131
|
+
status: "missing",
|
|
132
|
+
manifest: null,
|
|
133
|
+
errors: [],
|
|
134
|
+
agentEligible: false,
|
|
135
|
+
reason: "missing-manifest",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const manifest = parseCapabilityManifest(value);
|
|
140
|
+
const eligible = manifest.classification === "agent-compatible";
|
|
141
|
+
const reason = eligible
|
|
142
|
+
? "eligible"
|
|
143
|
+
: manifest.classification === "trusted-legacy"
|
|
144
|
+
? "trusted-legacy"
|
|
145
|
+
: "denied-to-agents";
|
|
146
|
+
return {
|
|
147
|
+
status: "declared",
|
|
148
|
+
manifest,
|
|
149
|
+
errors: [],
|
|
150
|
+
agentEligible: eligible,
|
|
151
|
+
reason,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
const errors = error instanceof CapabilityManifestError ? [...error.errors] : [String(error)];
|
|
156
|
+
return {
|
|
157
|
+
status: "invalid",
|
|
158
|
+
manifest: null,
|
|
159
|
+
errors,
|
|
160
|
+
agentEligible: false,
|
|
161
|
+
reason: "invalid-manifest",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Fail-closed helper for the future agent execution policy boundary. */
|
|
166
|
+
export function requireAgentEligibleManifest(value) {
|
|
167
|
+
const assessment = assessCapabilityManifest(value);
|
|
168
|
+
if (assessment.agentEligible && assessment.manifest)
|
|
169
|
+
return assessment.manifest;
|
|
170
|
+
const detail = assessment.errors.length > 0 ? assessment.errors : [assessment.reason];
|
|
171
|
+
throw new CapabilityManifestError(detail);
|
|
172
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* H1-02 contracts shared by authoring, the runner, and control-plane adapters.
|
|
3
|
+
*
|
|
4
|
+
* These are deliberately data-only. A model can return data that resembles a
|
|
5
|
+
* trusted record, but the runner decides whether the producing step is allowed
|
|
6
|
+
* to establish trusted provenance.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ENFORCEMENT_CONTRACT_VERSION: "1";
|
|
9
|
+
export type OutputTrust = "model" | "trusted";
|
|
10
|
+
export interface AgentCompletionContract {
|
|
11
|
+
readonly required?: boolean;
|
|
12
|
+
/** Dot-separated output path; defaults to `completed`. */
|
|
13
|
+
readonly path?: string;
|
|
14
|
+
/** Expected value at `path`; defaults to `true`. */
|
|
15
|
+
readonly equals?: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface AgentStepContract {
|
|
18
|
+
readonly version: typeof ENFORCEMENT_CONTRACT_VERSION;
|
|
19
|
+
readonly objective: string;
|
|
20
|
+
readonly completion: AgentCompletionContract;
|
|
21
|
+
}
|
|
22
|
+
export interface ApprovalContract {
|
|
23
|
+
readonly version: typeof ENFORCEMENT_CONTRACT_VERSION;
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
readonly scope?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface AssertionGateContract {
|
|
28
|
+
readonly version: typeof ENFORCEMENT_CONTRACT_VERSION;
|
|
29
|
+
readonly path?: string;
|
|
30
|
+
readonly equals?: unknown;
|
|
31
|
+
readonly truthy?: boolean;
|
|
32
|
+
readonly message?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface EvidenceGateRequirement {
|
|
35
|
+
readonly artifactId: string;
|
|
36
|
+
readonly artifactVersion: string;
|
|
37
|
+
readonly producerStepId: string;
|
|
38
|
+
}
|
|
39
|
+
export interface EvidenceGateContract {
|
|
40
|
+
readonly version: typeof ENFORCEMENT_CONTRACT_VERSION;
|
|
41
|
+
readonly requirements: readonly EvidenceGateRequirement[];
|
|
42
|
+
}
|
|
43
|
+
export interface TrustedEvidence {
|
|
44
|
+
readonly version: typeof ENFORCEMENT_CONTRACT_VERSION;
|
|
45
|
+
readonly provenance: "trusted";
|
|
46
|
+
readonly producer: {
|
|
47
|
+
readonly stepId: string;
|
|
48
|
+
readonly workflow: string;
|
|
49
|
+
};
|
|
50
|
+
readonly artifact: {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly version: string;
|
|
53
|
+
};
|
|
54
|
+
readonly verified: true;
|
|
55
|
+
readonly verification?: string;
|
|
56
|
+
}
|
|
57
|
+
export declare class EnforcementViolationError extends Error {
|
|
58
|
+
readonly reasonCode: string;
|
|
59
|
+
readonly code = "ENFORCEMENT_REJECTED";
|
|
60
|
+
constructor(reasonCode: string, message: string);
|
|
61
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* H1-02 contracts shared by authoring, the runner, and control-plane adapters.
|
|
3
|
+
*
|
|
4
|
+
* These are deliberately data-only. A model can return data that resembles a
|
|
5
|
+
* trusted record, but the runner decides whether the producing step is allowed
|
|
6
|
+
* to establish trusted provenance.
|
|
7
|
+
*/
|
|
8
|
+
export const ENFORCEMENT_CONTRACT_VERSION = "1";
|
|
9
|
+
export class EnforcementViolationError extends Error {
|
|
10
|
+
reasonCode;
|
|
11
|
+
code = "ENFORCEMENT_REJECTED";
|
|
12
|
+
constructor(reasonCode, message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.reasonCode = reasonCode;
|
|
15
|
+
this.name = "EnforcementViolationError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Version of the language-neutral enforcement profile contract. */
|
|
3
|
+
export declare const ENFORCEMENT_PROFILE_CONTRACT_VERSION: "1";
|
|
4
|
+
export declare const ENFORCEMENT_PROFILES: readonly ["advisory", "guided", "strict"];
|
|
5
|
+
export type EnforcementProfile = (typeof ENFORCEMENT_PROFILES)[number];
|
|
6
|
+
export interface EnforcementProfileSemantics {
|
|
7
|
+
readonly deviations: "record" | "authorized-override" | "reject";
|
|
8
|
+
readonly transitions: "advisory" | "enforced";
|
|
9
|
+
readonly inRunOverride: "allowed" | "forbidden";
|
|
10
|
+
}
|
|
11
|
+
/** Machine-readable semantics; keep this table in sync with ADR 0002. */
|
|
12
|
+
export declare const ENFORCEMENT_PROFILE_SEMANTICS: Readonly<Record<EnforcementProfile, EnforcementProfileSemantics>>;
|
|
13
|
+
export declare const EnforcementProfileSchema: z.ZodEnum<["advisory", "guided", "strict"]>;
|
|
14
|
+
export interface EnforcementProfileContract {
|
|
15
|
+
readonly version: typeof ENFORCEMENT_PROFILE_CONTRACT_VERSION;
|
|
16
|
+
readonly profile: EnforcementProfile;
|
|
17
|
+
}
|
|
18
|
+
export declare const EnforcementProfileContractSchema: z.ZodObject<{
|
|
19
|
+
version: z.ZodLiteral<"1">;
|
|
20
|
+
profile: z.ZodEnum<["advisory", "guided", "strict"]>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
version: "1";
|
|
23
|
+
profile: "strict" | "advisory" | "guided";
|
|
24
|
+
}, {
|
|
25
|
+
version: "1";
|
|
26
|
+
profile: "strict" | "advisory" | "guided";
|
|
27
|
+
}>;
|
|
28
|
+
export declare class EnforcementProfileContractError extends Error {
|
|
29
|
+
readonly issues: readonly string[];
|
|
30
|
+
constructor(issues: readonly string[]);
|
|
31
|
+
}
|
|
32
|
+
export declare function parseEnforcementProfile(value: unknown): EnforcementProfile;
|
|
33
|
+
export declare function parseEnforcementProfileContract(value: unknown): EnforcementProfileContract;
|
|
34
|
+
/** Return the immutable, machine-readable behavior for one profile. */
|
|
35
|
+
export declare function enforcementProfileSemantics(value: unknown): EnforcementProfileSemantics;
|
|
36
|
+
export declare function serializeEnforcementProfileContract(value: unknown): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Version of the language-neutral enforcement profile contract. */
|
|
3
|
+
export const ENFORCEMENT_PROFILE_CONTRACT_VERSION = "1";
|
|
4
|
+
export const ENFORCEMENT_PROFILES = ["advisory", "guided", "strict"];
|
|
5
|
+
/** Machine-readable semantics; keep this table in sync with ADR 0002. */
|
|
6
|
+
export const ENFORCEMENT_PROFILE_SEMANTICS = {
|
|
7
|
+
advisory: {
|
|
8
|
+
deviations: "record",
|
|
9
|
+
transitions: "advisory",
|
|
10
|
+
inRunOverride: "allowed",
|
|
11
|
+
},
|
|
12
|
+
guided: {
|
|
13
|
+
deviations: "authorized-override",
|
|
14
|
+
transitions: "enforced",
|
|
15
|
+
inRunOverride: "allowed",
|
|
16
|
+
},
|
|
17
|
+
strict: {
|
|
18
|
+
deviations: "reject",
|
|
19
|
+
transitions: "enforced",
|
|
20
|
+
inRunOverride: "forbidden",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export const EnforcementProfileSchema = z.enum(ENFORCEMENT_PROFILES);
|
|
24
|
+
export const EnforcementProfileContractSchema = z.object({
|
|
25
|
+
version: z.literal(ENFORCEMENT_PROFILE_CONTRACT_VERSION),
|
|
26
|
+
profile: EnforcementProfileSchema,
|
|
27
|
+
});
|
|
28
|
+
export class EnforcementProfileContractError extends Error {
|
|
29
|
+
issues;
|
|
30
|
+
constructor(issues) {
|
|
31
|
+
super(`Invalid enforcement profile contract: ${issues.join("; ")}`);
|
|
32
|
+
this.name = "EnforcementProfileContractError";
|
|
33
|
+
this.issues = [...issues];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function parseSchema(schema, value, label) {
|
|
37
|
+
const result = schema.safeParse(value);
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
throw new EnforcementProfileContractError(result.error.issues.map((issue) => `${label}${issue.path.length > 0 ? `.${issue.path.join(".")}` : ""} ${issue.message}`));
|
|
40
|
+
}
|
|
41
|
+
return result.data;
|
|
42
|
+
}
|
|
43
|
+
export function parseEnforcementProfile(value) {
|
|
44
|
+
return parseSchema(EnforcementProfileSchema, value, "enforcement profile");
|
|
45
|
+
}
|
|
46
|
+
export function parseEnforcementProfileContract(value) {
|
|
47
|
+
return parseSchema(EnforcementProfileContractSchema, value, "enforcement profile contract");
|
|
48
|
+
}
|
|
49
|
+
/** Return the immutable, machine-readable behavior for one profile. */
|
|
50
|
+
export function enforcementProfileSemantics(value) {
|
|
51
|
+
return ENFORCEMENT_PROFILE_SEMANTICS[parseEnforcementProfile(value)];
|
|
52
|
+
}
|
|
53
|
+
export function serializeEnforcementProfileContract(value) {
|
|
54
|
+
return JSON.stringify(parseEnforcementProfileContract(value));
|
|
55
|
+
}
|