@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
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { INTERACTION_MAX_PAYLOAD_BYTES, INTERACTION_MAX_PAYLOAD_DEPTH, INTERACTION_MAX_PAYLOAD_ITEMS, INTERACTION_MAX_STRING_LENGTH, parseInteractionPayload, } from "./InteractionContracts.js";
|
|
3
|
+
/** Version of the language-neutral evidence wire contract. */
|
|
4
|
+
export const EVIDENCE_CONTRACT_VERSION = "1";
|
|
5
|
+
/** Evidence uses the same bounded JSON value envelope as H1-01 answers. */
|
|
6
|
+
export const EVIDENCE_MAX_RECORD_BYTES = INTERACTION_MAX_PAYLOAD_BYTES;
|
|
7
|
+
export const EVIDENCE_MAX_PAYLOAD_DEPTH = INTERACTION_MAX_PAYLOAD_DEPTH;
|
|
8
|
+
export const EVIDENCE_MAX_PAYLOAD_ITEMS = INTERACTION_MAX_PAYLOAD_ITEMS;
|
|
9
|
+
export const EVIDENCE_MAX_STRING_LENGTH = INTERACTION_MAX_STRING_LENGTH;
|
|
10
|
+
export const EVIDENCE_MAX_CHECKS = 32;
|
|
11
|
+
export const EVIDENCE_MAX_REQUIREMENTS = 64;
|
|
12
|
+
export const EVIDENCE_PRODUCER_KINDS = ["capability", "deterministic-step", "runner"];
|
|
13
|
+
export const EVIDENCE_VERIFIER_KINDS = ["capability", "human", "runner"];
|
|
14
|
+
export const EVIDENCE_VERIFICATION_STATUSES = ["verified", "failed", "unverified", "expired"];
|
|
15
|
+
export const EVIDENCE_VERIFICATION_METHODS = [
|
|
16
|
+
"artifact-digest",
|
|
17
|
+
"schema-check",
|
|
18
|
+
"deterministic-check",
|
|
19
|
+
"capability-attestation",
|
|
20
|
+
"human-approval",
|
|
21
|
+
];
|
|
22
|
+
export const EVIDENCE_CHECK_OUTCOMES = ["passed", "failed"];
|
|
23
|
+
export class EvidenceContractError extends Error {
|
|
24
|
+
issues;
|
|
25
|
+
constructor(issues) {
|
|
26
|
+
super(`Invalid evidence contract: ${issues.join("; ")}`);
|
|
27
|
+
this.name = "EvidenceContractError";
|
|
28
|
+
this.issues = [...issues];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const IDENTIFIER = /^[A-Za-z][A-Za-z0-9._:/-]{0,127}$/;
|
|
32
|
+
const VERSION = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
|
|
33
|
+
const CLAIM_CODE = /^[a-z][a-z0-9._:-]{0,127}$/;
|
|
34
|
+
const DIGEST = /^(sha256):[0-9a-f]{64}$|^(sha512):[0-9a-f]{128}$/i;
|
|
35
|
+
const PRODUCER_KIND = z.enum(EVIDENCE_PRODUCER_KINDS);
|
|
36
|
+
const VERIFIER_KIND = z.enum(EVIDENCE_VERIFIER_KINDS);
|
|
37
|
+
const VERIFICATION_STATUS = z.enum(EVIDENCE_VERIFICATION_STATUSES);
|
|
38
|
+
const VERIFICATION_METHOD = z.enum(EVIDENCE_VERIFICATION_METHODS);
|
|
39
|
+
const CHECK_OUTCOME = z.enum(EVIDENCE_CHECK_OUTCOMES);
|
|
40
|
+
const identifier = z.string().min(1).max(128).regex(IDENTIFIER, "must be a bounded identifier");
|
|
41
|
+
const version = z.string().min(1).max(128).regex(VERSION, "must be a bounded version");
|
|
42
|
+
const claimCode = z.string().min(1).max(128).regex(CLAIM_CODE, "must be a machine-readable claim code");
|
|
43
|
+
const timestamp = z.string().min(1).max(64);
|
|
44
|
+
export const ArtifactIdentitySchema = z.object({
|
|
45
|
+
id: identifier,
|
|
46
|
+
kind: identifier,
|
|
47
|
+
});
|
|
48
|
+
export const ArtifactVersionIdentitySchema = z.object({
|
|
49
|
+
artifact: ArtifactIdentitySchema,
|
|
50
|
+
version,
|
|
51
|
+
digest: z
|
|
52
|
+
.string()
|
|
53
|
+
.max(140)
|
|
54
|
+
.regex(DIGEST, "must be a sha256: or sha512: digest with the complete hexadecimal length")
|
|
55
|
+
.transform((value) => value.toLowerCase()),
|
|
56
|
+
});
|
|
57
|
+
export const EvidenceProducerIdentitySchema = z.object({
|
|
58
|
+
kind: PRODUCER_KIND,
|
|
59
|
+
id: identifier,
|
|
60
|
+
});
|
|
61
|
+
const workflowIdentity = z.object({
|
|
62
|
+
name: identifier,
|
|
63
|
+
version: version.optional(),
|
|
64
|
+
});
|
|
65
|
+
const stepIdentity = z.object({
|
|
66
|
+
id: identifier,
|
|
67
|
+
index: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
68
|
+
attempt: z.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
69
|
+
});
|
|
70
|
+
export const EvidenceTraceIdentitySchema = z.object({
|
|
71
|
+
runId: identifier,
|
|
72
|
+
nodeRunId: identifier.optional(),
|
|
73
|
+
});
|
|
74
|
+
export const EvidenceProvenanceIdentitySchema = z.object({
|
|
75
|
+
producer: EvidenceProducerIdentitySchema,
|
|
76
|
+
workflow: workflowIdentity,
|
|
77
|
+
step: stepIdentity,
|
|
78
|
+
trace: EvidenceTraceIdentitySchema,
|
|
79
|
+
interactionId: identifier.optional(),
|
|
80
|
+
});
|
|
81
|
+
export const EvidenceCheckSchema = z.object({
|
|
82
|
+
code: claimCode,
|
|
83
|
+
outcome: CHECK_OUTCOME,
|
|
84
|
+
});
|
|
85
|
+
export const EvidenceVerifierIdentitySchema = z.object({
|
|
86
|
+
kind: VERIFIER_KIND,
|
|
87
|
+
id: identifier,
|
|
88
|
+
});
|
|
89
|
+
export const EvidenceVerificationResultSchema = z
|
|
90
|
+
.object({
|
|
91
|
+
status: VERIFICATION_STATUS,
|
|
92
|
+
verifier: EvidenceVerifierIdentitySchema,
|
|
93
|
+
method: VERIFICATION_METHOD,
|
|
94
|
+
checkedAt: timestamp,
|
|
95
|
+
checks: z.array(EvidenceCheckSchema).min(1).max(EVIDENCE_MAX_CHECKS),
|
|
96
|
+
reasonCode: claimCode.optional(),
|
|
97
|
+
})
|
|
98
|
+
.superRefine((value, context) => {
|
|
99
|
+
const failed = value.checks.some((check) => check.outcome === "failed");
|
|
100
|
+
if (value.status === "verified" && failed) {
|
|
101
|
+
context.addIssue({
|
|
102
|
+
code: z.ZodIssueCode.custom,
|
|
103
|
+
path: ["checks"],
|
|
104
|
+
message: "verified results cannot contain failed checks",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (value.status === "verified" && value.method === "human-approval" && value.verifier.kind !== "human") {
|
|
108
|
+
context.addIssue({
|
|
109
|
+
code: z.ZodIssueCode.custom,
|
|
110
|
+
path: ["verifier", "kind"],
|
|
111
|
+
message: "human-approval must be verified by a human",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (value.status === "verified" &&
|
|
115
|
+
value.method === "capability-attestation" &&
|
|
116
|
+
value.verifier.kind !== "capability") {
|
|
117
|
+
context.addIssue({
|
|
118
|
+
code: z.ZodIssueCode.custom,
|
|
119
|
+
path: ["verifier", "kind"],
|
|
120
|
+
message: "capability-attestation must be verified by a capability",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
export const EvidencePayloadSchema = z.unknown().transform((value, context) => {
|
|
125
|
+
try {
|
|
126
|
+
return parseInteractionPayload(value, "evidence payload");
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
context.addIssue({
|
|
130
|
+
code: z.ZodIssueCode.custom,
|
|
131
|
+
message: error instanceof Error ? error.message : "invalid payload",
|
|
132
|
+
});
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
export const EvidenceRecordSchema = z.object({
|
|
137
|
+
version: z.literal(EVIDENCE_CONTRACT_VERSION),
|
|
138
|
+
id: identifier,
|
|
139
|
+
kind: identifier,
|
|
140
|
+
claim: claimCode,
|
|
141
|
+
artifact: ArtifactVersionIdentitySchema,
|
|
142
|
+
provenance: EvidenceProvenanceIdentitySchema,
|
|
143
|
+
verification: EvidenceVerificationResultSchema,
|
|
144
|
+
observedAt: timestamp,
|
|
145
|
+
expiresAt: timestamp.optional(),
|
|
146
|
+
payload: EvidencePayloadSchema.optional(),
|
|
147
|
+
});
|
|
148
|
+
export const EvidenceRequirementSchema = z.object({
|
|
149
|
+
type: z.literal("evidence"),
|
|
150
|
+
id: identifier,
|
|
151
|
+
kind: identifier,
|
|
152
|
+
claim: claimCode.optional(),
|
|
153
|
+
artifactKind: identifier.optional(),
|
|
154
|
+
producers: z.array(PRODUCER_KIND).min(1).max(EVIDENCE_PRODUCER_KINDS.length),
|
|
155
|
+
verification: z.literal("verified"),
|
|
156
|
+
});
|
|
157
|
+
export const ApprovalRequirementSchema = z.object({
|
|
158
|
+
type: z.literal("approval"),
|
|
159
|
+
id: identifier,
|
|
160
|
+
interactionId: identifier,
|
|
161
|
+
status: z.literal("answered"),
|
|
162
|
+
});
|
|
163
|
+
export const CompletionRequirementSchema = z.discriminatedUnion("type", [
|
|
164
|
+
EvidenceRequirementSchema,
|
|
165
|
+
ApprovalRequirementSchema,
|
|
166
|
+
]);
|
|
167
|
+
export const CompletionContractSchema = z
|
|
168
|
+
.object({
|
|
169
|
+
version: z.literal(EVIDENCE_CONTRACT_VERSION),
|
|
170
|
+
id: identifier,
|
|
171
|
+
mode: z.enum(["all", "any"]),
|
|
172
|
+
requirements: z.array(CompletionRequirementSchema).min(1).max(EVIDENCE_MAX_REQUIREMENTS),
|
|
173
|
+
})
|
|
174
|
+
.superRefine((value, context) => {
|
|
175
|
+
const ids = new Set();
|
|
176
|
+
for (const [index, requirement] of value.requirements.entries()) {
|
|
177
|
+
if (ids.has(requirement.id)) {
|
|
178
|
+
context.addIssue({
|
|
179
|
+
code: z.ZodIssueCode.custom,
|
|
180
|
+
path: ["requirements", index, "id"],
|
|
181
|
+
message: "requirement ids must be unique",
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
ids.add(requirement.id);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
function byteLength(value) {
|
|
188
|
+
return new TextEncoder().encode(value).byteLength;
|
|
189
|
+
}
|
|
190
|
+
function parseSchema(schema, value, label) {
|
|
191
|
+
const result = schema.safeParse(value);
|
|
192
|
+
if (!result.success) {
|
|
193
|
+
throw new EvidenceContractError(result.error.issues.map((issue) => `${label}${issue.path.length > 0 ? `.${issue.path.join(".")}` : ""} ${issue.message}`));
|
|
194
|
+
}
|
|
195
|
+
return result.data;
|
|
196
|
+
}
|
|
197
|
+
function assertRecordSize(value, label) {
|
|
198
|
+
let serialized;
|
|
199
|
+
try {
|
|
200
|
+
serialized = JSON.stringify(value);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
throw new EvidenceContractError([`${label} must be JSON-serializable`]);
|
|
204
|
+
}
|
|
205
|
+
if (byteLength(serialized) > EVIDENCE_MAX_RECORD_BYTES) {
|
|
206
|
+
throw new EvidenceContractError([`${label} exceeds ${EVIDENCE_MAX_RECORD_BYTES} bytes`]);
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
/** Parse one bounded structured fact payload without accepting model prose as a record. */
|
|
211
|
+
export function parseEvidencePayload(value) {
|
|
212
|
+
return parseSchema(EvidencePayloadSchema, value, "evidence payload");
|
|
213
|
+
}
|
|
214
|
+
/** Parse and normalize an evidence record at the trusted boundary. */
|
|
215
|
+
export function parseEvidenceRecord(value) {
|
|
216
|
+
return assertRecordSize(parseSchema(EvidenceRecordSchema, value, "evidence record"), "evidence record");
|
|
217
|
+
}
|
|
218
|
+
/** Parse and normalize a completion contract at the workflow/load boundary. */
|
|
219
|
+
export function parseCompletionContract(value) {
|
|
220
|
+
return assertRecordSize(parseSchema(CompletionContractSchema, value, "completion contract"), "completion contract");
|
|
221
|
+
}
|
|
222
|
+
export function serializeEvidenceRecord(value) {
|
|
223
|
+
return JSON.stringify(parseEvidenceRecord(value));
|
|
224
|
+
}
|
|
225
|
+
export function serializeCompletionContract(value) {
|
|
226
|
+
return JSON.stringify(parseCompletionContract(value));
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* A model message is descriptive context only. It cannot be adapted into a
|
|
230
|
+
* trusted record because the only accepted producer kinds are capability,
|
|
231
|
+
* deterministic-step, and runner.
|
|
232
|
+
*/
|
|
233
|
+
export function rejectModelEvidence(_value) {
|
|
234
|
+
throw new EvidenceContractError([
|
|
235
|
+
"model prose is not evidence; provide a capability- or deterministic-step-produced artifact and verification result",
|
|
236
|
+
]);
|
|
237
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { capabilityScope } from "./CapabilityContracts.js";
|
|
2
|
+
import type { CapabilityOwner, CapabilityRequestContext, WorkspacePathRef } from "./CapabilityContracts.js";
|
|
3
|
+
import type { PolicyEvaluationResult, PolicyRequest } from "./PolicyContracts.js";
|
|
4
|
+
import type { RepositoryIdentity } from "./WorkflowBindingContracts.js";
|
|
5
|
+
export declare const GIT_CAPABILITY_CONTRACT_VERSION: "1";
|
|
6
|
+
export declare const GIT_MAX_CHANGED_FILES = 4096;
|
|
7
|
+
export declare const GIT_MAX_BRANCH_NAME_LENGTH = 256;
|
|
8
|
+
export declare const GIT_OPERATIONS: readonly ["repository.inspect", "worktree.create", "worktree.inspect", "worktree.diff", "worktree.cleanup"];
|
|
9
|
+
export type GitOperation = (typeof GIT_OPERATIONS)[number];
|
|
10
|
+
/** Deliberately not part of GitCapability: repository rewriting is denied. */
|
|
11
|
+
export declare const GIT_DESTRUCTIVE_OPERATIONS: readonly ["repository.reset", "repository.clean", "repository.checkout", "repository.rebase", "repository.merge", "branch.delete", "worktree.force-cleanup"];
|
|
12
|
+
export type GitDestructiveOperation = (typeof GIT_DESTRUCTIVE_OPERATIONS)[number];
|
|
13
|
+
export type GitChangeStatus = "added" | "copied" | "deleted" | "modified" | "renamed" | "type-changed" | "untracked";
|
|
14
|
+
export type GitWorktreeStatus = "active" | "cleanup-pending" | "cleaned";
|
|
15
|
+
export interface GitRevisionIdentity {
|
|
16
|
+
readonly commit: string;
|
|
17
|
+
readonly ref?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface GitDirtyState {
|
|
20
|
+
readonly status: "clean" | "dirty";
|
|
21
|
+
/** Hash of the status/content identity observed at the boundary. */
|
|
22
|
+
readonly fingerprint: string;
|
|
23
|
+
readonly changedPaths: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
/** Repository facts captured before a task starts. */
|
|
26
|
+
export interface GitRepositoryIdentity {
|
|
27
|
+
readonly repository: RepositoryIdentity;
|
|
28
|
+
readonly checkout: WorkspacePathRef;
|
|
29
|
+
readonly head: GitRevisionIdentity;
|
|
30
|
+
readonly dirty: GitDirtyState;
|
|
31
|
+
readonly owner: CapabilityOwner;
|
|
32
|
+
}
|
|
33
|
+
export interface GitWorktreeCreateRequest extends CapabilityRequestContext {
|
|
34
|
+
readonly policy: PolicyRequest;
|
|
35
|
+
readonly repository: GitRepositoryIdentity;
|
|
36
|
+
readonly base: GitRevisionIdentity;
|
|
37
|
+
readonly branch: string;
|
|
38
|
+
readonly path?: WorkspacePathRef;
|
|
39
|
+
/** Required so a dirty primary checkout can never be discarded implicitly. */
|
|
40
|
+
readonly preserveSourceChanges: true;
|
|
41
|
+
}
|
|
42
|
+
export interface GitWorktreeCleanupRequest extends GitCapabilityRequest {
|
|
43
|
+
/** Cleanup may not discard task changes; dirty worktrees stay recoverable. */
|
|
44
|
+
readonly preserveChanges: true;
|
|
45
|
+
readonly worktree: GitWorktreeIdentity;
|
|
46
|
+
}
|
|
47
|
+
export interface GitWorktreeIdentity {
|
|
48
|
+
readonly version: typeof GIT_CAPABILITY_CONTRACT_VERSION;
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly repository: GitRepositoryIdentity;
|
|
51
|
+
readonly path: WorkspacePathRef;
|
|
52
|
+
readonly branch: string;
|
|
53
|
+
readonly base: GitRevisionIdentity;
|
|
54
|
+
readonly head: GitRevisionIdentity;
|
|
55
|
+
readonly sourceDirty: GitDirtyState;
|
|
56
|
+
readonly owner: CapabilityOwner;
|
|
57
|
+
readonly status: GitWorktreeStatus;
|
|
58
|
+
readonly createdAt: string;
|
|
59
|
+
readonly cleanedAt?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface GitDiffFile {
|
|
62
|
+
readonly path: string;
|
|
63
|
+
readonly status: GitChangeStatus;
|
|
64
|
+
readonly previousPath?: string;
|
|
65
|
+
readonly contentHash: string;
|
|
66
|
+
readonly sizeBytes?: number;
|
|
67
|
+
}
|
|
68
|
+
export interface GitDiffEvidence {
|
|
69
|
+
readonly version: typeof GIT_CAPABILITY_CONTRACT_VERSION;
|
|
70
|
+
readonly repository: RepositoryIdentity;
|
|
71
|
+
readonly worktree: GitWorktreeIdentity;
|
|
72
|
+
readonly base: GitRevisionIdentity;
|
|
73
|
+
readonly head: GitRevisionIdentity;
|
|
74
|
+
readonly dirty: GitDirtyState;
|
|
75
|
+
readonly files: readonly GitDiffFile[];
|
|
76
|
+
/** Hash of the canonical diff/evidence record, not an unverified claim. */
|
|
77
|
+
readonly evidenceHash: string;
|
|
78
|
+
readonly capturedAt: string;
|
|
79
|
+
}
|
|
80
|
+
export interface GitCapabilityRequest extends CapabilityRequestContext {
|
|
81
|
+
readonly policy: PolicyRequest;
|
|
82
|
+
readonly operation: GitOperation;
|
|
83
|
+
readonly repository: GitRepositoryIdentity;
|
|
84
|
+
readonly worktree?: GitWorktreeIdentity;
|
|
85
|
+
}
|
|
86
|
+
export interface GitCapability {
|
|
87
|
+
inspectRepository(request: GitCapabilityRequest): Promise<GitRepositoryIdentity>;
|
|
88
|
+
createWorktree(request: GitWorktreeCreateRequest): Promise<GitWorktreeIdentity>;
|
|
89
|
+
inspectWorktree(request: GitCapabilityRequest): Promise<GitWorktreeIdentity>;
|
|
90
|
+
diff(request: GitCapabilityRequest): Promise<GitDiffEvidence>;
|
|
91
|
+
cleanup(request: GitWorktreeCleanupRequest): Promise<GitWorktreeIdentity>;
|
|
92
|
+
}
|
|
93
|
+
export declare function parseGitDirtyState(value: unknown): GitDirtyState;
|
|
94
|
+
export declare function parseGitRepositoryIdentity(value: unknown): GitRepositoryIdentity;
|
|
95
|
+
export declare function parseGitWorktreeIdentity(value: unknown): GitWorktreeIdentity;
|
|
96
|
+
export declare function parseGitDiffEvidence(value: unknown): GitDiffEvidence;
|
|
97
|
+
export declare function parseGitWorktreeCreateRequest(value: unknown): GitWorktreeCreateRequest;
|
|
98
|
+
export declare function gitCapabilityScope(operation: GitOperation): ReturnType<typeof capabilityScope>;
|
|
99
|
+
export declare function assertGitOperationAllowed(operation: string): asserts operation is GitOperation;
|
|
100
|
+
export declare function parseGitOperation(value: unknown): GitOperation;
|
|
101
|
+
export declare function assertGitPolicyAllowed(result: PolicyEvaluationResult): void;
|
|
102
|
+
export declare function assertGitOwner(repository: GitRepositoryIdentity, owner: CapabilityOwner): void;
|
|
103
|
+
export declare function workspacePathForGit(value: unknown): WorkspacePathRef;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AGENT_CAPABILITY_CONTRACT_VERSION, CAPABILITY_MAX_LIST_ITEMS, CapabilityContractError, CapabilityOwnerSchema, WorkspacePathRefSchema, assertAuthorized, assertOwned, capabilityScope, identifier, parseCapabilityOwner, parseWorkspacePathRef, timestamp, path as workspacePath, } from "./CapabilityContracts.js";
|
|
3
|
+
export const GIT_CAPABILITY_CONTRACT_VERSION = AGENT_CAPABILITY_CONTRACT_VERSION;
|
|
4
|
+
export const GIT_MAX_CHANGED_FILES = 4_096;
|
|
5
|
+
export const GIT_MAX_BRANCH_NAME_LENGTH = 256;
|
|
6
|
+
export const GIT_OPERATIONS = [
|
|
7
|
+
"repository.inspect",
|
|
8
|
+
"worktree.create",
|
|
9
|
+
"worktree.inspect",
|
|
10
|
+
"worktree.diff",
|
|
11
|
+
"worktree.cleanup",
|
|
12
|
+
];
|
|
13
|
+
/** Deliberately not part of GitCapability: repository rewriting is denied. */
|
|
14
|
+
export const GIT_DESTRUCTIVE_OPERATIONS = [
|
|
15
|
+
"repository.reset",
|
|
16
|
+
"repository.clean",
|
|
17
|
+
"repository.checkout",
|
|
18
|
+
"repository.rebase",
|
|
19
|
+
"repository.merge",
|
|
20
|
+
"branch.delete",
|
|
21
|
+
"worktree.force-cleanup",
|
|
22
|
+
];
|
|
23
|
+
const revisionSchema = z.object({
|
|
24
|
+
commit: z
|
|
25
|
+
.string()
|
|
26
|
+
.regex(/^[0-9a-f]{7,64}$/i)
|
|
27
|
+
.transform((value) => value.toLowerCase()),
|
|
28
|
+
ref: identifier.optional(),
|
|
29
|
+
});
|
|
30
|
+
const dirtyStateSchema = z
|
|
31
|
+
.object({
|
|
32
|
+
status: z.enum(["clean", "dirty"]),
|
|
33
|
+
fingerprint: z
|
|
34
|
+
.string()
|
|
35
|
+
.regex(/^(?:sha256):[0-9a-f]{64}$|^(?:sha512):[0-9a-f]{128}$/i)
|
|
36
|
+
.transform((value) => value.toLowerCase()),
|
|
37
|
+
changedPaths: z.array(workspacePath).max(CAPABILITY_MAX_LIST_ITEMS),
|
|
38
|
+
})
|
|
39
|
+
.superRefine((value, context) => {
|
|
40
|
+
if (value.status === "dirty" && value.changedPaths.length === 0)
|
|
41
|
+
context.addIssue({
|
|
42
|
+
code: z.ZodIssueCode.custom,
|
|
43
|
+
path: ["changedPaths"],
|
|
44
|
+
message: "dirty state must identify changed paths",
|
|
45
|
+
});
|
|
46
|
+
if (value.status === "clean" && value.changedPaths.length > 0)
|
|
47
|
+
context.addIssue({
|
|
48
|
+
code: z.ZodIssueCode.custom,
|
|
49
|
+
path: ["changedPaths"],
|
|
50
|
+
message: "clean state cannot identify changed paths",
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
const repositorySchema = z.object({
|
|
54
|
+
repository: z.object({ provider: identifier, id: identifier, revision: z.string().max(256).optional() }),
|
|
55
|
+
checkout: WorkspacePathRefSchema,
|
|
56
|
+
head: revisionSchema,
|
|
57
|
+
dirty: dirtyStateSchema,
|
|
58
|
+
owner: CapabilityOwnerSchema,
|
|
59
|
+
});
|
|
60
|
+
const worktreeSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
version: z.literal(GIT_CAPABILITY_CONTRACT_VERSION),
|
|
63
|
+
id: identifier,
|
|
64
|
+
repository: repositorySchema,
|
|
65
|
+
path: WorkspacePathRefSchema,
|
|
66
|
+
branch: z
|
|
67
|
+
.string()
|
|
68
|
+
.min(1)
|
|
69
|
+
.max(GIT_MAX_BRANCH_NAME_LENGTH)
|
|
70
|
+
.regex(/^[A-Za-z0-9._/-]+$/),
|
|
71
|
+
base: revisionSchema,
|
|
72
|
+
head: revisionSchema,
|
|
73
|
+
sourceDirty: dirtyStateSchema,
|
|
74
|
+
owner: CapabilityOwnerSchema,
|
|
75
|
+
status: z.enum(["active", "cleanup-pending", "cleaned"]),
|
|
76
|
+
createdAt: timestamp,
|
|
77
|
+
cleanedAt: timestamp.optional(),
|
|
78
|
+
})
|
|
79
|
+
.superRefine((value, context) => {
|
|
80
|
+
if (value.status === "cleaned" && value.cleanedAt === undefined)
|
|
81
|
+
context.addIssue({
|
|
82
|
+
code: z.ZodIssueCode.custom,
|
|
83
|
+
path: ["cleanedAt"],
|
|
84
|
+
message: "cleaned worktree must record cleanedAt",
|
|
85
|
+
});
|
|
86
|
+
if (value.status !== "cleaned" && value.cleanedAt !== undefined)
|
|
87
|
+
context.addIssue({
|
|
88
|
+
code: z.ZodIssueCode.custom,
|
|
89
|
+
path: ["cleanedAt"],
|
|
90
|
+
message: "active worktree cannot record cleanedAt",
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
const diffFileSchema = z.object({
|
|
94
|
+
path: workspacePath,
|
|
95
|
+
status: z.enum(["added", "copied", "deleted", "modified", "renamed", "type-changed", "untracked"]),
|
|
96
|
+
previousPath: workspacePath.optional(),
|
|
97
|
+
contentHash: z
|
|
98
|
+
.string()
|
|
99
|
+
.regex(/^(?:sha256):[0-9a-f]{64}$|^(?:sha512):[0-9a-f]{128}$/i)
|
|
100
|
+
.transform((value) => value.toLowerCase()),
|
|
101
|
+
sizeBytes: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
102
|
+
});
|
|
103
|
+
const diffEvidenceSchema = z
|
|
104
|
+
.object({
|
|
105
|
+
version: z.literal(GIT_CAPABILITY_CONTRACT_VERSION),
|
|
106
|
+
repository: z.object({ provider: identifier, id: identifier, revision: z.string().max(256).optional() }),
|
|
107
|
+
worktree: worktreeSchema,
|
|
108
|
+
base: revisionSchema,
|
|
109
|
+
head: revisionSchema,
|
|
110
|
+
dirty: dirtyStateSchema,
|
|
111
|
+
files: z.array(diffFileSchema).max(GIT_MAX_CHANGED_FILES),
|
|
112
|
+
evidenceHash: z
|
|
113
|
+
.string()
|
|
114
|
+
.regex(/^(?:sha256):[0-9a-f]{64}$|^(?:sha512):[0-9a-f]{128}$/i)
|
|
115
|
+
.transform((value) => value.toLowerCase()),
|
|
116
|
+
capturedAt: timestamp,
|
|
117
|
+
})
|
|
118
|
+
.superRefine((value, context) => {
|
|
119
|
+
if (value.repository.provider !== value.worktree.repository.repository.provider ||
|
|
120
|
+
value.repository.id !== value.worktree.repository.repository.id)
|
|
121
|
+
context.addIssue({
|
|
122
|
+
code: z.ZodIssueCode.custom,
|
|
123
|
+
path: ["repository"],
|
|
124
|
+
message: "must match worktree repository identity",
|
|
125
|
+
});
|
|
126
|
+
if (value.base.commit !== value.worktree.base.commit)
|
|
127
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ["base"], message: "must match worktree base revision" });
|
|
128
|
+
if (value.head.commit !== value.worktree.head.commit)
|
|
129
|
+
context.addIssue({ code: z.ZodIssueCode.custom, path: ["head"], message: "must match worktree head revision" });
|
|
130
|
+
if (value.dirty.fingerprint !== value.worktree.sourceDirty.fingerprint)
|
|
131
|
+
context.addIssue({
|
|
132
|
+
code: z.ZodIssueCode.custom,
|
|
133
|
+
path: ["dirty"],
|
|
134
|
+
message: "must match worktree source dirty identity",
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
function parse(schema, value, label) {
|
|
138
|
+
const result = schema.safeParse(value);
|
|
139
|
+
if (!result.success)
|
|
140
|
+
throw new CapabilityContractError(`${label}: ${result.error.issues.map((issue) => issue.message).join("; ")}`);
|
|
141
|
+
return result.data;
|
|
142
|
+
}
|
|
143
|
+
function immutable(value) {
|
|
144
|
+
const snapshot = structuredClone(value);
|
|
145
|
+
const freeze = (item) => {
|
|
146
|
+
if (item === null || typeof item !== "object" || Object.isFrozen(item))
|
|
147
|
+
return;
|
|
148
|
+
for (const child of Object.values(item))
|
|
149
|
+
freeze(child);
|
|
150
|
+
Object.freeze(item);
|
|
151
|
+
};
|
|
152
|
+
freeze(snapshot);
|
|
153
|
+
return snapshot;
|
|
154
|
+
}
|
|
155
|
+
export function parseGitDirtyState(value) {
|
|
156
|
+
const parsed = parse(dirtyStateSchema, value, "git dirty state");
|
|
157
|
+
return immutable({ ...parsed, changedPaths: [...new Set(parsed.changedPaths)].sort() });
|
|
158
|
+
}
|
|
159
|
+
export function parseGitRepositoryIdentity(value) {
|
|
160
|
+
const parsed = parse(repositorySchema, value, "git repository identity");
|
|
161
|
+
return immutable({ ...parsed, dirty: parseGitDirtyState(parsed.dirty) });
|
|
162
|
+
}
|
|
163
|
+
export function parseGitWorktreeIdentity(value) {
|
|
164
|
+
const parsed = parse(worktreeSchema, value, "git worktree identity");
|
|
165
|
+
return immutable({
|
|
166
|
+
...parsed,
|
|
167
|
+
repository: { ...parsed.repository, dirty: parseGitDirtyState(parsed.repository.dirty) },
|
|
168
|
+
sourceDirty: parseGitDirtyState(parsed.sourceDirty),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
export function parseGitDiffEvidence(value) {
|
|
172
|
+
const parsed = parse(diffEvidenceSchema, value, "git diff evidence");
|
|
173
|
+
return immutable({
|
|
174
|
+
...parsed,
|
|
175
|
+
dirty: parseGitDirtyState(parsed.dirty),
|
|
176
|
+
worktree: parseGitWorktreeIdentity(parsed.worktree),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
export function parseGitWorktreeCreateRequest(value) {
|
|
180
|
+
const schema = z.object({
|
|
181
|
+
policy: z.custom(),
|
|
182
|
+
owner: CapabilityOwnerSchema,
|
|
183
|
+
repository: repositorySchema,
|
|
184
|
+
base: revisionSchema,
|
|
185
|
+
branch: z
|
|
186
|
+
.string()
|
|
187
|
+
.min(1)
|
|
188
|
+
.max(GIT_MAX_BRANCH_NAME_LENGTH)
|
|
189
|
+
.regex(/^[A-Za-z0-9._/-]+$/),
|
|
190
|
+
path: WorkspacePathRefSchema.optional(),
|
|
191
|
+
preserveSourceChanges: z.literal(true),
|
|
192
|
+
});
|
|
193
|
+
const parsed = parse(schema, value, "git worktree create request");
|
|
194
|
+
return immutable({ ...parsed, repository: parseGitRepositoryIdentity(parsed.repository) });
|
|
195
|
+
}
|
|
196
|
+
export function gitCapabilityScope(operation) {
|
|
197
|
+
if (operation === "worktree.create" || operation === "worktree.cleanup")
|
|
198
|
+
return capabilityScope(["read", "write"], [`git.${operation}`]);
|
|
199
|
+
return capabilityScope(["read"], [`git.${operation}`]);
|
|
200
|
+
}
|
|
201
|
+
export function assertGitOperationAllowed(operation) {
|
|
202
|
+
if (GIT_OPERATIONS.includes(operation))
|
|
203
|
+
return;
|
|
204
|
+
if (GIT_DESTRUCTIVE_OPERATIONS.includes(operation))
|
|
205
|
+
throw new CapabilityContractError(`destructive git operation is denied: ${operation}`);
|
|
206
|
+
throw new CapabilityContractError(`unsupported git operation: ${operation}`);
|
|
207
|
+
}
|
|
208
|
+
export function parseGitOperation(value) {
|
|
209
|
+
if (typeof value !== "string")
|
|
210
|
+
throw new CapabilityContractError("git operation must be a string");
|
|
211
|
+
assertGitOperationAllowed(value);
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
export function assertGitPolicyAllowed(result) {
|
|
215
|
+
assertAuthorized(result, { allowSandbox: true });
|
|
216
|
+
}
|
|
217
|
+
export function assertGitOwner(repository, owner) {
|
|
218
|
+
assertOwned(repository.owner, parseCapabilityOwner(owner));
|
|
219
|
+
}
|
|
220
|
+
export function workspacePathForGit(value) {
|
|
221
|
+
return parseWorkspacePathRef(value);
|
|
222
|
+
}
|
package/dist/GlobalLogger.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export default abstract class GlobalLogger implements LoggerContext {
|
|
|
5
5
|
abstract log(message: string): void;
|
|
6
6
|
abstract logLevel(level: string, message: string): void;
|
|
7
7
|
abstract error(message: string, stack: string): void;
|
|
8
|
+
/** Loggers that filter by level override this; the base emits everything. */
|
|
9
|
+
isLevelEnabled(_level?: string): boolean;
|
|
8
10
|
getLogs(): string[];
|
|
9
11
|
getLogsAsText(): string;
|
|
10
12
|
getLogsAsBase64(): string;
|
package/dist/GlobalLogger.js
CHANGED