@axtary/actionpass 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1204 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { decodeProtectedHeader, SignJWT, jwtVerify, } from "jose";
5
+ import { z } from "zod";
6
+ export const ACTION_SCHEMA_VERSION = "axtary.action.v0";
7
+ export const ACTIONPASS_VERSION = "axtary.actionpass.v0";
8
+ export const APPROVAL_ARTIFACT_VERSION = "axtary.approval.v0";
9
+ export const ACTIONPASS_REVOCATION_VERSION = "axtary.actionpass_revocation.v0";
10
+ export const ACTIONPASS_TRUST_STORE_VERSION = "axtary.actionpass_trust_store.v0";
11
+ export const LEDGER_SCHEMA_VERSION = "axtary.ledger.v0";
12
+ export const LEDGER_PROVIDER_EVIDENCE_VERSION = "axtary.ledger_provider_evidence.v0";
13
+ export const LEDGER_EXECUTION_OUTCOME_VERSION = "axtary.ledger_execution_outcome.v0";
14
+ export const DEFAULT_EXPIRES_IN_SECONDS = 600;
15
+ export const DEFAULT_SIGNING_ALGORITHM = "ES256";
16
+ const JsonValueSchema = z.lazy(() => z.union([
17
+ z.string(),
18
+ z.number().finite(),
19
+ z.boolean(),
20
+ z.null(),
21
+ z.array(JsonValueSchema),
22
+ z.record(z.string(), JsonValueSchema),
23
+ ]));
24
+ export const AxtaryDecisionSchema = z.enum(["allow", "deny", "step_up"]);
25
+ export const NormalizedActionSchema = z.object({
26
+ schemaVersion: z.literal(ACTION_SCHEMA_VERSION).default(ACTION_SCHEMA_VERSION),
27
+ actor: z.object({
28
+ agentId: z.string().min(1),
29
+ humanOwner: z.string().min(1),
30
+ runtime: z.string().min(1),
31
+ tenant: z.string().min(1).optional(),
32
+ }),
33
+ intent: z.object({
34
+ taskId: z.string().min(1),
35
+ declaredGoal: z.string().min(1),
36
+ maxDelegationDepth: z.number().int().nonnegative().optional(),
37
+ }),
38
+ capability: z.object({
39
+ tool: z.string().min(1),
40
+ resource: z.string().min(1),
41
+ payload: z.record(z.string(), JsonValueSchema),
42
+ constraints: z.record(z.string(), JsonValueSchema).optional(),
43
+ }),
44
+ toolDefinition: z
45
+ .object({
46
+ serverIdentity: z.string().min(1),
47
+ schemaVersion: z.string().min(1),
48
+ definitionHash: z.string().min(1),
49
+ })
50
+ .optional(),
51
+ budget: z
52
+ .object({
53
+ reservationId: z.string().min(1).optional(),
54
+ limit: z.record(z.string(), JsonValueSchema).optional(),
55
+ commitStatus: z.enum(["pending", "committed", "rolled_back"]).optional(),
56
+ })
57
+ .optional(),
58
+ });
59
+ export const PolicyDecisionSchema = z.object({
60
+ decision: AxtaryDecisionSchema,
61
+ reasons: z.array(z.string().min(1)),
62
+ policy: z.object({
63
+ nativeRule: z.string().min(1),
64
+ version: z.string().min(1),
65
+ cedarCompatible: z.boolean(),
66
+ opaCompatible: z.boolean(),
67
+ }),
68
+ constraints: z.object({
69
+ expiresInSeconds: z.number().int().positive(),
70
+ maxFilesChanged: z.number().int().nonnegative(),
71
+ blockedPaths: z.array(z.string().min(1)),
72
+ }),
73
+ });
74
+ export const ApprovalSchema = z.object({
75
+ mode: z.enum(["none", "human", "policy_override"]),
76
+ approvedBy: z.string().min(1).optional(),
77
+ approvalArtifact: z.string().min(1).optional(),
78
+ approvalArtifactHash: z.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
79
+ actionHash: z.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
80
+ payloadHash: z.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
81
+ approvedAt: z.string().datetime().optional(),
82
+ });
83
+ export const ApprovalArtifactSchema = z.object({
84
+ schemaVersion: z.literal(APPROVAL_ARTIFACT_VERSION),
85
+ id: z.string().min(1),
86
+ mode: z.enum(["human", "policy_override"]),
87
+ approvedBy: z.string().min(1),
88
+ approvedAt: z.string().datetime(),
89
+ actionHash: z.string().regex(/^sha256:[a-f0-9]{64}$/),
90
+ payloadHash: z.string().regex(/^sha256:[a-f0-9]{64}$/),
91
+ taskId: z.string().min(1),
92
+ tool: z.string().min(1),
93
+ resource: z.string().min(1),
94
+ reason: z.string().min(1).optional(),
95
+ expiresAt: z.string().datetime().optional(),
96
+ });
97
+ export const ActionPassClaimsSchema = z.object({
98
+ apv: z.literal(ACTIONPASS_VERSION),
99
+ iss: z.string().min(1),
100
+ sub: z.string().min(1),
101
+ aud: z.union([z.string().min(1), z.array(z.string().min(1))]),
102
+ exp: z.number().int().positive(),
103
+ nbf: z.number().int().positive(),
104
+ iat: z.number().int().positive(),
105
+ jti: z.string().min(1),
106
+ tenant: z.string().min(1),
107
+ humanOwner: z.string().min(1),
108
+ agentRuntime: z.string().min(1),
109
+ intent: NormalizedActionSchema.shape.intent,
110
+ capability: z.object({
111
+ tool: z.string().min(1),
112
+ resource: z.string().min(1),
113
+ constraints: z.record(z.string(), JsonValueSchema).optional(),
114
+ }),
115
+ decision: AxtaryDecisionSchema,
116
+ reasons: z.array(z.string().min(1)),
117
+ policy: PolicyDecisionSchema.shape.policy,
118
+ payloadHash: z.string().regex(/^sha256:[a-f0-9]{64}$/),
119
+ approval: ApprovalSchema.optional(),
120
+ audit: z.object({
121
+ traceId: z.string().min(1),
122
+ parentPassId: z.string().min(1).nullable(),
123
+ ledgerHash: z.string().min(1).nullable(),
124
+ }),
125
+ });
126
+ export const ActionPassRevocationSchema = z.object({
127
+ schemaVersion: z.literal(ACTIONPASS_REVOCATION_VERSION),
128
+ passId: z.string().min(1),
129
+ revokedAt: z.string().datetime(),
130
+ revokedBy: z.string().min(1).optional(),
131
+ reason: z.string().min(1).optional(),
132
+ });
133
+ export const ActionPassVerificationKeyRecordSchema = z.object({
134
+ kid: z.string().min(1),
135
+ publicJwk: z.record(z.string(), JsonValueSchema),
136
+ algorithm: z.string().min(1).optional(),
137
+ status: z.enum(["active", "retired", "revoked"]).default("active"),
138
+ notBefore: z.string().datetime().optional(),
139
+ expiresAt: z.string().datetime().optional(),
140
+ });
141
+ export const ActionPassTrustStoreSchema = z.object({
142
+ schemaVersion: z.literal(ACTIONPASS_TRUST_STORE_VERSION),
143
+ keys: z.array(ActionPassVerificationKeyRecordSchema).default([]),
144
+ revocations: z.array(ActionPassRevocationSchema).default([]),
145
+ });
146
+ export const LedgerProviderEvidenceDiffLineSchema = z.object({
147
+ type: z.enum(["context", "addition", "deletion"]),
148
+ content: z.string(),
149
+ oldLine: z.number().int().positive().optional(),
150
+ newLine: z.number().int().positive().optional(),
151
+ });
152
+ export const LedgerProviderEvidenceDiffFileSchema = z.object({
153
+ path: z.string().min(1),
154
+ oldPath: z.string().min(1).optional(),
155
+ status: z.enum(["added", "modified", "deleted", "renamed"]),
156
+ additions: z.number().int().nonnegative().default(0),
157
+ deletions: z.number().int().nonnegative().default(0),
158
+ hunks: z
159
+ .array(z.object({
160
+ header: z.string().min(1),
161
+ lines: z.array(LedgerProviderEvidenceDiffLineSchema),
162
+ }))
163
+ .default([]),
164
+ });
165
+ export const LedgerProviderSchema = z.enum([
166
+ "github",
167
+ "slack",
168
+ "linear",
169
+ "docs",
170
+ "mcp",
171
+ "aws",
172
+ "gcp",
173
+ "jira",
174
+ ]);
175
+ export const LedgerProviderEvidenceDiffSchema = z.object({
176
+ provider: LedgerProviderSchema,
177
+ summary: z.string().min(1).optional(),
178
+ files: z.array(LedgerProviderEvidenceDiffFileSchema).default([]),
179
+ });
180
+ export const LedgerProviderEvidenceFieldChangeSchema = z.object({
181
+ field: z.string().min(1),
182
+ before: JsonValueSchema.optional(),
183
+ after: JsonValueSchema.optional(),
184
+ });
185
+ export const LedgerProviderEvidenceSchema = z.object({
186
+ schemaVersion: z.literal(LEDGER_PROVIDER_EVIDENCE_VERSION),
187
+ provider: LedgerProviderSchema,
188
+ tool: z.string().min(1),
189
+ operation: z.string().min(1),
190
+ resource: z
191
+ .object({
192
+ provider: LedgerProviderSchema,
193
+ kind: z.string().min(1),
194
+ id: z.string().min(1),
195
+ label: z.string().min(1).optional(),
196
+ url: z.string().url().optional(),
197
+ })
198
+ .strict(),
199
+ normalized: z.record(z.string(), JsonValueSchema).default({}),
200
+ fieldChanges: z.array(LedgerProviderEvidenceFieldChangeSchema).default([]),
201
+ diff: LedgerProviderEvidenceDiffSchema.optional(),
202
+ });
203
+ export const LedgerExecutionOutcomeSchema = z
204
+ .object({
205
+ schemaVersion: z.literal(LEDGER_EXECUTION_OUTCOME_VERSION),
206
+ status: z.enum(["succeeded", "failed"]),
207
+ provider: LedgerProviderSchema.optional(),
208
+ resultId: z.string().min(1).optional(),
209
+ resultUrl: z.string().url().optional(),
210
+ resourceId: z.string().min(1).optional(),
211
+ resourceLabel: z.string().min(1).optional(),
212
+ failureReason: z.string().min(1).optional(),
213
+ errorClass: z.string().min(1).optional(),
214
+ sideEffectProof: z.record(z.string(), JsonValueSchema).default({}),
215
+ })
216
+ .strict();
217
+ export const LedgerRecordSchema = z.object({
218
+ schemaVersion: z.literal(LEDGER_SCHEMA_VERSION),
219
+ id: z.string().min(1),
220
+ occurredAt: z.string().datetime(),
221
+ actionHash: z.string().regex(/^sha256:[a-f0-9]{64}$/),
222
+ payloadHash: z.string().regex(/^sha256:[a-f0-9]{64}$/),
223
+ decision: AxtaryDecisionSchema,
224
+ reasons: z.array(z.string().min(1)),
225
+ policy: PolicyDecisionSchema.shape.policy,
226
+ providerEvidence: LedgerProviderEvidenceSchema.optional(),
227
+ executionOutcome: LedgerExecutionOutcomeSchema.optional(),
228
+ traceId: z.string().min(1).optional(),
229
+ actionPassId: z.string().min(1).nullable(),
230
+ correlationId: z.string().min(1).optional(),
231
+ previousLedgerHash: z.string().regex(/^sha256:[a-f0-9]{64}$/).nullable(),
232
+ ledgerHash: z.string().regex(/^sha256:[a-f0-9]{64}$/),
233
+ });
234
+ export const demoAction = NormalizedActionSchema.parse({
235
+ actor: {
236
+ agentId: "agent:codex-prod",
237
+ humanOwner: "user:asrar@company.com",
238
+ runtime: "codex-cli",
239
+ tenant: "org:company",
240
+ },
241
+ intent: {
242
+ taskId: "AXT-418",
243
+ declaredGoal: "Open a PR that fixes the auth redirect bug",
244
+ },
245
+ capability: {
246
+ tool: "github.pull_requests.create",
247
+ resource: "repo:company/web-app",
248
+ payload: {
249
+ baseBranch: "main",
250
+ filesChanged: ["src/app/login/page.tsx", "src/lib/auth/session.ts"],
251
+ touchesProduction: false,
252
+ },
253
+ },
254
+ });
255
+ export function parseNormalizedAction(action) {
256
+ return NormalizedActionSchema.parse(action);
257
+ }
258
+ export function evaluateAction(actionInput, options = {}) {
259
+ const action = parseNormalizedAction(actionInput);
260
+ const payload = action.capability.payload;
261
+ const filesChanged = getStringArray(payload.filesChanged);
262
+ const requiredBaseBranch = options.requiredBaseBranch ?? "main";
263
+ const maxFilesChanged = options.maxFilesChanged ?? 12;
264
+ const blockedPathPrefixes = options.blockedPathPrefixes ?? [
265
+ "infra/prod/",
266
+ "billing/",
267
+ ".env",
268
+ ];
269
+ const policy = {
270
+ nativeRule: "github_pr_coding_agent_v0",
271
+ version: options.policyVersion ?? "2026-05-30",
272
+ cedarCompatible: true,
273
+ opaCompatible: true,
274
+ };
275
+ const constraints = {
276
+ expiresInSeconds: DEFAULT_EXPIRES_IN_SECONDS,
277
+ maxFilesChanged,
278
+ blockedPaths: ["infra/prod/**", "billing/**", ".env*"],
279
+ };
280
+ const stepUpReasons = [];
281
+ if (action.capability.tool !== (options.allowedTool ?? "github.pull_requests.create")) {
282
+ return {
283
+ decision: "deny",
284
+ reasons: [
285
+ `unsupported_tool: ${action.capability.tool} is not enabled by this policy`,
286
+ ],
287
+ policy,
288
+ constraints,
289
+ };
290
+ }
291
+ if (payload.baseBranch !== requiredBaseBranch) {
292
+ stepUpReasons.push(`base_branch_mismatch: expected ${requiredBaseBranch}`);
293
+ }
294
+ if (filesChanged.length > maxFilesChanged) {
295
+ stepUpReasons.push("file_count_exceeds_policy_limit");
296
+ }
297
+ if (filesChanged.some((path) => blockedPathPrefixes.some((prefix) => path.startsWith(prefix)))) {
298
+ stepUpReasons.push("payload_touches_protected_path");
299
+ }
300
+ if (payload.touchesProduction === true) {
301
+ stepUpReasons.push("payload_declares_production_impact");
302
+ }
303
+ if (stepUpReasons.length > 0) {
304
+ return {
305
+ decision: "step_up",
306
+ reasons: stepUpReasons,
307
+ policy,
308
+ constraints,
309
+ };
310
+ }
311
+ return {
312
+ decision: "allow",
313
+ reasons: ["payload_is_inside_current_actionpass_constraints"],
314
+ policy,
315
+ constraints,
316
+ };
317
+ }
318
+ export async function authorize(input) {
319
+ const action = parseNormalizedAction(input.action);
320
+ const decision = evaluateAction(action, input.policy);
321
+ const payloadHash = hashPayload(action.capability.payload);
322
+ const traceId = input.traceId ?? `trace_${randomUUID()}`;
323
+ const actionPass = decision.decision === "allow"
324
+ ? await issueActionPass({
325
+ action,
326
+ decision,
327
+ issuer: input.issuer,
328
+ tenant: input.tenant,
329
+ signingKey: input.signingKey,
330
+ keyId: input.keyId,
331
+ algorithm: input.algorithm,
332
+ now: input.now,
333
+ approval: input.approval,
334
+ approvalArtifact: input.approvalArtifact,
335
+ traceId,
336
+ })
337
+ : null;
338
+ const ledger = recordDecision({
339
+ action,
340
+ decision,
341
+ actionPassId: actionPass?.claims.jti ?? null,
342
+ previousLedgerHash: input.previousLedgerHash ?? null,
343
+ occurredAt: input.now,
344
+ traceId,
345
+ });
346
+ return {
347
+ action,
348
+ decision,
349
+ payloadHash,
350
+ actionPass,
351
+ ledger,
352
+ };
353
+ }
354
+ export function createApprovalArtifact(input) {
355
+ const action = parseNormalizedAction(input.action);
356
+ const artifact = ApprovalArtifactSchema.parse({
357
+ schemaVersion: APPROVAL_ARTIFACT_VERSION,
358
+ id: input.id ?? `approval_${randomUUID()}`,
359
+ mode: input.mode,
360
+ approvedBy: input.approvedBy,
361
+ approvedAt: (input.approvedAt ?? new Date()).toISOString(),
362
+ actionHash: hashAction(action),
363
+ payloadHash: hashPayload(action.capability.payload),
364
+ taskId: action.intent.taskId,
365
+ tool: action.capability.tool,
366
+ resource: action.capability.resource,
367
+ reason: input.reason,
368
+ expiresAt: input.expiresAt?.toISOString(),
369
+ });
370
+ const artifactHash = hashJson(artifact);
371
+ return {
372
+ artifact,
373
+ artifactHash,
374
+ approval: {
375
+ mode: artifact.mode,
376
+ approvedBy: artifact.approvedBy,
377
+ approvedAt: artifact.approvedAt,
378
+ approvalArtifact: artifact.id,
379
+ approvalArtifactHash: artifactHash,
380
+ actionHash: artifact.actionHash,
381
+ payloadHash: artifact.payloadHash,
382
+ },
383
+ };
384
+ }
385
+ export async function issueActionPass(input) {
386
+ const action = parseNormalizedAction(input.action);
387
+ const decision = PolicyDecisionSchema.parse(input.decision);
388
+ if (decision.decision !== "allow") {
389
+ throw new Error("ActionPass can only be issued for an allow decision.");
390
+ }
391
+ const now = input.now ?? new Date();
392
+ const issuedAt = toNumericDate(now);
393
+ const expiresInSeconds = input.expiresInSeconds ?? decision.constraints.expiresInSeconds;
394
+ const expiresAt = issuedAt + expiresInSeconds;
395
+ const passId = `ap_${randomUUID()}`;
396
+ const actionHash = hashAction(action);
397
+ const payloadHash = hashPayload(action.capability.payload);
398
+ const approval = bindApproval({
399
+ action,
400
+ actionHash,
401
+ payloadHash,
402
+ approval: input.approval,
403
+ approvalArtifact: input.approvalArtifact,
404
+ now,
405
+ });
406
+ const claims = ActionPassClaimsSchema.parse({
407
+ apv: ACTIONPASS_VERSION,
408
+ iss: input.issuer,
409
+ sub: action.actor.agentId,
410
+ aud: action.capability.resource,
411
+ exp: expiresAt,
412
+ nbf: issuedAt,
413
+ iat: issuedAt,
414
+ jti: passId,
415
+ tenant: input.tenant,
416
+ humanOwner: action.actor.humanOwner,
417
+ agentRuntime: action.actor.runtime,
418
+ intent: action.intent,
419
+ capability: {
420
+ tool: action.capability.tool,
421
+ resource: action.capability.resource,
422
+ constraints: action.capability.constraints,
423
+ },
424
+ decision: decision.decision,
425
+ reasons: decision.reasons,
426
+ policy: decision.policy,
427
+ payloadHash,
428
+ approval,
429
+ audit: {
430
+ traceId: input.traceId ?? `trace_${randomUUID()}`,
431
+ parentPassId: input.parentPassId ?? null,
432
+ ledgerHash: input.ledgerHash ?? null,
433
+ },
434
+ });
435
+ const token = await new SignJWT(claims)
436
+ .setProtectedHeader({
437
+ alg: input.algorithm ?? DEFAULT_SIGNING_ALGORITHM,
438
+ typ: "axtary-actionpass+jwt",
439
+ kid: input.keyId,
440
+ })
441
+ .sign(input.signingKey);
442
+ return { token, claims };
443
+ }
444
+ export async function verifyActionPass(input) {
445
+ try {
446
+ const action = parseNormalizedAction(input.action);
447
+ const verificationKey = await resolveVerificationKey(input);
448
+ const { payload } = await jwtVerify(input.token, verificationKey, {
449
+ issuer: input.issuer,
450
+ audience: input.audience ?? action.capability.resource,
451
+ algorithms: input.algorithms ?? [DEFAULT_SIGNING_ALGORITHM],
452
+ clockTolerance: input.clockTolerance,
453
+ currentDate: input.currentDate,
454
+ });
455
+ const claims = ActionPassClaimsSchema.parse(payload);
456
+ const payloadHash = hashPayload(action.capability.payload);
457
+ const bindingError = validatePassBinding(claims, action, payloadHash);
458
+ if (bindingError) {
459
+ return {
460
+ valid: false,
461
+ reason: bindingError,
462
+ };
463
+ }
464
+ const revocationError = await validateRevocation(input, claims);
465
+ if (revocationError) {
466
+ return {
467
+ valid: false,
468
+ reason: revocationError,
469
+ };
470
+ }
471
+ return {
472
+ valid: true,
473
+ claims,
474
+ payloadHash,
475
+ };
476
+ }
477
+ catch (error) {
478
+ return {
479
+ valid: false,
480
+ reason: error instanceof Error ? error.message : "verification_failed",
481
+ };
482
+ }
483
+ }
484
+ export function revokeActionPass(input) {
485
+ return ActionPassRevocationSchema.parse({
486
+ schemaVersion: ACTIONPASS_REVOCATION_VERSION,
487
+ passId: input.passId,
488
+ revokedAt: (input.revokedAt ?? new Date()).toISOString(),
489
+ revokedBy: input.revokedBy,
490
+ reason: input.reason,
491
+ });
492
+ }
493
+ export class LocalActionPassTrustStore {
494
+ filePath;
495
+ constructor(filePath) {
496
+ this.filePath = filePath;
497
+ }
498
+ async read() {
499
+ return readTrustStore(this.filePath);
500
+ }
501
+ async write(dataInput) {
502
+ const data = ActionPassTrustStoreSchema.parse({
503
+ schemaVersion: ACTIONPASS_TRUST_STORE_VERSION,
504
+ keys: dataInput.keys ?? [],
505
+ revocations: dataInput.revocations ?? [],
506
+ });
507
+ await writeTrustStore(this.filePath, data);
508
+ return data;
509
+ }
510
+ async putVerificationKey(keyInput) {
511
+ const key = ActionPassVerificationKeyRecordSchema.parse(keyInput);
512
+ const data = await this.read();
513
+ const nextKeys = [
514
+ ...data.keys.filter((entry) => entry.kid !== key.kid),
515
+ key,
516
+ ].sort((left, right) => left.kid.localeCompare(right.kid));
517
+ await this.write({
518
+ ...data,
519
+ keys: nextKeys,
520
+ });
521
+ return key;
522
+ }
523
+ async revokeActionPass(input) {
524
+ const revocation = "schemaVersion" in input
525
+ ? ActionPassRevocationSchema.parse(input)
526
+ : revokeActionPass(input);
527
+ const data = await this.read();
528
+ const nextRevocations = [
529
+ ...data.revocations.filter((entry) => entry.passId !== revocation.passId),
530
+ revocation,
531
+ ].sort((left, right) => left.passId.localeCompare(right.passId));
532
+ await this.write({
533
+ ...data,
534
+ revocations: nextRevocations,
535
+ });
536
+ return revocation;
537
+ }
538
+ async getVerificationKey(keyId, now = new Date()) {
539
+ if (!keyId) {
540
+ return undefined;
541
+ }
542
+ const data = await this.read();
543
+ const key = data.keys.find((entry) => entry.kid === keyId);
544
+ if (!key || !isTrustedVerificationKey(key, now)) {
545
+ return undefined;
546
+ }
547
+ return key.publicJwk;
548
+ }
549
+ async isPassRevoked(passId) {
550
+ const data = await this.read();
551
+ return data.revocations.some((entry) => entry.passId === passId);
552
+ }
553
+ }
554
+ export async function verifyActionPassWithTrustStore(input) {
555
+ const store = typeof input.trustStore === "string"
556
+ ? new LocalActionPassTrustStore(input.trustStore)
557
+ : input.trustStore;
558
+ return verifyActionPass({
559
+ ...input,
560
+ verificationKeys: (keyId) => store.getVerificationKey(keyId),
561
+ isRevoked: (claims) => store.isPassRevoked(claims.jti),
562
+ });
563
+ }
564
+ export function recordDecision(input) {
565
+ const action = parseNormalizedAction(input.action);
566
+ const decision = PolicyDecisionSchema.parse(input.decision);
567
+ const payloadHash = hashPayload(action.capability.payload);
568
+ const actionHash = hashAction(action);
569
+ const traceId = input.traceId ?? `trace_${randomUUID()}`;
570
+ const recordWithoutHash = {
571
+ schemaVersion: LEDGER_SCHEMA_VERSION,
572
+ id: `ledger_${randomUUID()}`,
573
+ occurredAt: (input.occurredAt ?? new Date()).toISOString(),
574
+ actionHash,
575
+ payloadHash,
576
+ decision: decision.decision,
577
+ reasons: decision.reasons,
578
+ policy: decision.policy,
579
+ providerEvidence: input.providerEvidence ?? providerEvidenceForAction(action),
580
+ executionOutcome: input.executionOutcome,
581
+ traceId,
582
+ actionPassId: input.actionPassId ?? null,
583
+ correlationId: input.correlationId ?? undefined,
584
+ previousLedgerHash: input.previousLedgerHash ?? null,
585
+ };
586
+ return LedgerRecordSchema.parse({
587
+ ...recordWithoutHash,
588
+ ledgerHash: hashJson(recordWithoutHash),
589
+ });
590
+ }
591
+ export function hashPayload(payload) {
592
+ return hashJson(JsonValueSchema.parse(payload));
593
+ }
594
+ export function hashAction(action) {
595
+ return hashJson(parseNormalizedAction(action));
596
+ }
597
+ export function providerEvidenceForAction(actionInput) {
598
+ const action = parseNormalizedAction(actionInput);
599
+ const payload = action.capability.payload;
600
+ switch (action.capability.tool) {
601
+ case "github.pull_requests.create":
602
+ return githubPullRequestEvidence(action, payload);
603
+ case "github.contents.read":
604
+ return githubContentEvidence(action, payload, "content_read");
605
+ case "github.contents.write":
606
+ return githubContentEvidence(action, payload, "content_write");
607
+ case "github.branches.create":
608
+ return githubBranchEvidence(action, payload);
609
+ case "slack.chat.postMessage":
610
+ return slackMessageEvidence(action, payload);
611
+ case "linear.issues.read":
612
+ return linearIssueEvidence(action, payload, "issue_read");
613
+ case "linear.comments.create":
614
+ return linearIssueEvidence(action, payload, "comment_create");
615
+ case "linear.issues.update":
616
+ return linearIssueEvidence(action, payload, "issue_update");
617
+ case "jira.issues.read":
618
+ return issueTrackerEvidence(action, payload, "jira", "issue_read");
619
+ case "jira.comments.create":
620
+ return issueTrackerEvidence(action, payload, "jira", "comment_create");
621
+ case "jira.issues.update":
622
+ return issueTrackerEvidence(action, payload, "jira", "issue_update");
623
+ case "docs.documents.search":
624
+ return docsEvidence(action, payload, "document_search");
625
+ case "docs.documents.read":
626
+ return docsEvidence(action, payload, "document_read");
627
+ case "mcp.tool.call":
628
+ return mcpEvidence(action, payload);
629
+ case "aws.identity.get":
630
+ return awsEvidence(action, payload, "identity_get");
631
+ case "aws.s3.objects.list":
632
+ return awsEvidence(action, payload, "s3_objects_list");
633
+ case "gcp.projects.get":
634
+ return gcpEvidence(action, payload, "project_get");
635
+ case "gcp.storage.objects.list":
636
+ return gcpEvidence(action, payload, "storage_objects_list");
637
+ default:
638
+ return undefined;
639
+ }
640
+ }
641
+ export function hashJson(value) {
642
+ return `sha256:${createHash("sha256")
643
+ .update(stableStringify(value))
644
+ .digest("hex")}`;
645
+ }
646
+ export function stableStringify(value) {
647
+ return JSON.stringify(canonicalize(value));
648
+ }
649
+ function validatePassBinding(claims, action, payloadHash) {
650
+ if (claims.sub !== action.actor.agentId) {
651
+ return "agent_id_mismatch";
652
+ }
653
+ if (claims.humanOwner !== action.actor.humanOwner) {
654
+ return "human_owner_mismatch";
655
+ }
656
+ if (claims.agentRuntime !== action.actor.runtime) {
657
+ return "agent_runtime_mismatch";
658
+ }
659
+ if (claims.intent.taskId !== action.intent.taskId) {
660
+ return "task_id_mismatch";
661
+ }
662
+ if (claims.capability.tool !== action.capability.tool) {
663
+ return "tool_mismatch";
664
+ }
665
+ if (claims.capability.resource !== action.capability.resource) {
666
+ return "resource_mismatch";
667
+ }
668
+ if (claims.payloadHash !== payloadHash) {
669
+ return "payload_hash_mismatch";
670
+ }
671
+ if (claims.approval?.payloadHash && claims.approval.payloadHash !== payloadHash) {
672
+ return "approval_payload_hash_mismatch";
673
+ }
674
+ if (claims.approval?.actionHash && claims.approval.actionHash !== hashAction(action)) {
675
+ return "approval_action_hash_mismatch";
676
+ }
677
+ return null;
678
+ }
679
+ async function readTrustStore(filePath) {
680
+ const text = await readTrustStoreFile(filePath);
681
+ if (!text.trim()) {
682
+ return ActionPassTrustStoreSchema.parse({
683
+ schemaVersion: ACTIONPASS_TRUST_STORE_VERSION,
684
+ });
685
+ }
686
+ return ActionPassTrustStoreSchema.parse(JSON.parse(text));
687
+ }
688
+ async function writeTrustStore(filePath, data) {
689
+ await mkdir(dirname(filePath), { recursive: true });
690
+ const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
691
+ await writeFile(tmpPath, `${JSON.stringify(data, null, 2)}\n`, {
692
+ encoding: "utf8",
693
+ flag: "wx",
694
+ });
695
+ await rename(tmpPath, filePath);
696
+ }
697
+ async function readTrustStoreFile(filePath) {
698
+ try {
699
+ return await readFile(filePath, "utf8");
700
+ }
701
+ catch (error) {
702
+ if (isNodeError(error) && error.code === "ENOENT") {
703
+ return "";
704
+ }
705
+ throw error;
706
+ }
707
+ }
708
+ function isTrustedVerificationKey(key, now) {
709
+ if (key.status === "revoked") {
710
+ return false;
711
+ }
712
+ if (key.notBefore && Date.parse(key.notBefore) > now.getTime()) {
713
+ return false;
714
+ }
715
+ if (key.expiresAt && Date.parse(key.expiresAt) < now.getTime()) {
716
+ return false;
717
+ }
718
+ return true;
719
+ }
720
+ async function resolveVerificationKey(input) {
721
+ if (!input.verificationKeys) {
722
+ if (!input.verificationKey) {
723
+ throw new Error("verification_key_required");
724
+ }
725
+ return input.verificationKey;
726
+ }
727
+ const { kid } = decodeProtectedHeader(input.token);
728
+ const key = typeof input.verificationKeys === "function"
729
+ ? await input.verificationKeys(kid)
730
+ : kid
731
+ ? input.verificationKeys[kid]
732
+ : undefined;
733
+ if (!key) {
734
+ throw new Error(kid ? `verification_key_not_found:${kid}` : "verification_key_id_required");
735
+ }
736
+ return key;
737
+ }
738
+ async function validateRevocation(input, claims) {
739
+ if (input.revokedPassIds) {
740
+ for (const passId of input.revokedPassIds) {
741
+ if (passId === claims.jti) {
742
+ return "actionpass_revoked";
743
+ }
744
+ }
745
+ }
746
+ if (input.revocations) {
747
+ for (const revocationInput of input.revocations) {
748
+ const revocation = ActionPassRevocationSchema.parse(revocationInput);
749
+ if (revocation.passId === claims.jti) {
750
+ return "actionpass_revoked";
751
+ }
752
+ }
753
+ }
754
+ if (input.isRevoked) {
755
+ const result = await input.isRevoked(claims);
756
+ if (result === true) {
757
+ return "actionpass_revoked";
758
+ }
759
+ if (typeof result === "string" && result.length > 0) {
760
+ return result;
761
+ }
762
+ }
763
+ return null;
764
+ }
765
+ function bindApproval(input) {
766
+ const approval = input.approval
767
+ ? ApprovalSchema.parse(input.approval)
768
+ : undefined;
769
+ const artifact = input.approvalArtifact
770
+ ? ApprovalArtifactSchema.parse(input.approvalArtifact)
771
+ : undefined;
772
+ if (!approval && !artifact) {
773
+ return undefined;
774
+ }
775
+ if (approval && approval.mode !== "none" && !approval.approvedBy && !artifact) {
776
+ throw new Error("approval_approver_required");
777
+ }
778
+ if (approval?.payloadHash && approval.payloadHash !== input.payloadHash) {
779
+ throw new Error("approval_payload_hash_mismatch");
780
+ }
781
+ if (approval?.actionHash && approval.actionHash !== input.actionHash) {
782
+ throw new Error("approval_action_hash_mismatch");
783
+ }
784
+ if (approval &&
785
+ approval.mode !== "none" &&
786
+ !approval.payloadHash &&
787
+ !approval.actionHash &&
788
+ !artifact) {
789
+ throw new Error("approval_binding_required");
790
+ }
791
+ if (!artifact) {
792
+ return approval;
793
+ }
794
+ const artifactError = validateApprovalArtifact(artifact, input.action, input.now);
795
+ if (artifactError) {
796
+ throw new Error(artifactError);
797
+ }
798
+ const artifactHash = hashJson(artifact);
799
+ if (approval?.approvalArtifactHash &&
800
+ approval.approvalArtifactHash !== artifactHash) {
801
+ throw new Error("approval_artifact_hash_mismatch");
802
+ }
803
+ return ApprovalSchema.parse({
804
+ mode: approval?.mode ?? artifact.mode,
805
+ approvedBy: approval?.approvedBy ?? artifact.approvedBy,
806
+ approvedAt: approval?.approvedAt ?? artifact.approvedAt,
807
+ approvalArtifact: approval?.approvalArtifact ?? artifact.id,
808
+ approvalArtifactHash: artifactHash,
809
+ actionHash: artifact.actionHash,
810
+ payloadHash: artifact.payloadHash,
811
+ });
812
+ }
813
+ export function validateApprovalArtifact(artifactInput, actionInput, now = new Date()) {
814
+ const artifact = ApprovalArtifactSchema.parse(artifactInput);
815
+ const action = parseNormalizedAction(actionInput);
816
+ if (artifact.actionHash !== hashAction(action)) {
817
+ return "approval_action_hash_mismatch";
818
+ }
819
+ if (artifact.payloadHash !== hashPayload(action.capability.payload)) {
820
+ return "approval_payload_hash_mismatch";
821
+ }
822
+ if (artifact.taskId !== action.intent.taskId) {
823
+ return "approval_task_id_mismatch";
824
+ }
825
+ if (artifact.tool !== action.capability.tool) {
826
+ return "approval_tool_mismatch";
827
+ }
828
+ if (artifact.resource !== action.capability.resource) {
829
+ return "approval_resource_mismatch";
830
+ }
831
+ if (artifact.expiresAt && Date.parse(artifact.expiresAt) < now.getTime()) {
832
+ return "approval_artifact_expired";
833
+ }
834
+ return null;
835
+ }
836
+ function githubPullRequestEvidence(action, payload) {
837
+ const files = changedFilesFromPayload(payload);
838
+ const diff = providerDiffFromPayload("github", payload) ?? {
839
+ provider: "github",
840
+ summary: `${files.length} changed ${files.length === 1 ? "file" : "files"}`,
841
+ files: files.map((path) => ({
842
+ path,
843
+ status: "modified",
844
+ additions: 0,
845
+ deletions: 0,
846
+ hunks: [],
847
+ })),
848
+ };
849
+ return LedgerProviderEvidenceSchema.parse({
850
+ schemaVersion: LEDGER_PROVIDER_EVIDENCE_VERSION,
851
+ provider: "github",
852
+ tool: action.capability.tool,
853
+ operation: "pull_request_create",
854
+ resource: providerResource("github", "repo", action.capability.resource),
855
+ normalized: compactJsonRecord({
856
+ title: getString(payload.title) ?? action.intent.declaredGoal,
857
+ baseBranch: getString(payload.baseBranch),
858
+ headBranch: getString(payload.headBranch) ?? getString(payload.branch),
859
+ filesChanged: files,
860
+ touchesProduction: getBoolean(payload.touchesProduction),
861
+ testsPassed: getBoolean(payload.testsPassed),
862
+ draft: getBoolean(payload.draft),
863
+ }),
864
+ diff,
865
+ });
866
+ }
867
+ function githubContentEvidence(action, payload, operation) {
868
+ const path = getString(payload.path) ?? "unknown";
869
+ const diff = operation === "content_write"
870
+ ? providerDiffFromPayload("github", payload) ?? {
871
+ provider: "github",
872
+ summary: "1 changed file",
873
+ files: [
874
+ {
875
+ path,
876
+ status: "modified",
877
+ additions: 0,
878
+ deletions: 0,
879
+ hunks: [],
880
+ },
881
+ ],
882
+ }
883
+ : undefined;
884
+ return LedgerProviderEvidenceSchema.parse({
885
+ schemaVersion: LEDGER_PROVIDER_EVIDENCE_VERSION,
886
+ provider: "github",
887
+ tool: action.capability.tool,
888
+ operation,
889
+ resource: providerResource("github", "file", `${action.capability.resource}:${path}`),
890
+ normalized: compactJsonRecord({
891
+ path,
892
+ branch: getString(payload.branch) ?? getString(payload.ref),
893
+ message: getString(payload.message),
894
+ contentLength: operation === "content_write"
895
+ ? getString(payload.content)?.length ?? null
896
+ : null,
897
+ }),
898
+ diff,
899
+ });
900
+ }
901
+ function githubBranchEvidence(action, payload) {
902
+ const branch = getString(payload.branch) ?? "unknown";
903
+ return LedgerProviderEvidenceSchema.parse({
904
+ schemaVersion: LEDGER_PROVIDER_EVIDENCE_VERSION,
905
+ provider: "github",
906
+ tool: action.capability.tool,
907
+ operation: "branch_create",
908
+ resource: providerResource("github", "branch", `${action.capability.resource}:${branch}`),
909
+ normalized: compactJsonRecord({
910
+ branch,
911
+ baseBranch: getString(payload.baseBranch),
912
+ sha: getString(payload.sha),
913
+ }),
914
+ });
915
+ }
916
+ function slackMessageEvidence(action, payload) {
917
+ const channel = getString(payload.channel) ?? "unknown";
918
+ const message = getString(payload.text) ?? getString(payload.message) ?? "";
919
+ return LedgerProviderEvidenceSchema.parse({
920
+ schemaVersion: LEDGER_PROVIDER_EVIDENCE_VERSION,
921
+ provider: "slack",
922
+ tool: action.capability.tool,
923
+ operation: "message_post",
924
+ resource: providerResource("slack", "channel", `${action.capability.resource}:${channel}`, channel),
925
+ normalized: compactJsonRecord({
926
+ channel,
927
+ message: redactCredentialLikeText(message),
928
+ messageLength: message.length,
929
+ threadTs: getString(payload.threadTs),
930
+ externalRecipients: getStringArray(payload.externalRecipients),
931
+ mentionedUsers: getStringArray(payload.mentionedUsers),
932
+ mentionedGroups: getStringArray(payload.mentionedGroups),
933
+ links: getStringArray(payload.links),
934
+ attachmentCount: getArrayLength(payload.attachments),
935
+ unfurlLinks: getBoolean(payload.unfurlLinks),
936
+ unfurlMedia: getBoolean(payload.unfurlMedia),
937
+ }),
938
+ });
939
+ }
940
+ function linearIssueEvidence(action, payload, operation) {
941
+ return issueTrackerEvidence(action, payload, "linear", operation);
942
+ }
943
+ function issueTrackerEvidence(action, payload, provider, operation) {
944
+ const issueKey = getString(payload.issueKey) ?? getString(payload.issueId) ?? "unknown";
945
+ const projectKey = getString(payload.projectKey) ?? issueKey.split("-", 1)[0];
946
+ return LedgerProviderEvidenceSchema.parse({
947
+ schemaVersion: LEDGER_PROVIDER_EVIDENCE_VERSION,
948
+ provider,
949
+ tool: action.capability.tool,
950
+ operation,
951
+ resource: providerResource(provider, "issue", `${action.capability.resource}:${issueKey}`, issueKey),
952
+ normalized: compactJsonRecord({
953
+ issueKey,
954
+ projectKey,
955
+ title: getString(payload.title),
956
+ status: getString(payload.status),
957
+ stateId: getString(payload.stateId),
958
+ priority: getNumber(payload.priority),
959
+ commentBody: operation === "comment_create"
960
+ ? redactCredentialLikeText(getString(payload.body) ?? "")
961
+ : null,
962
+ }),
963
+ fieldChanges: linearFieldChanges(payload),
964
+ });
965
+ }
966
+ function docsEvidence(action, payload, operation) {
967
+ const workspace = getString(payload.workspace) ?? "local";
968
+ const path = getString(payload.path);
969
+ const query = getString(payload.query);
970
+ const resourceId = path
971
+ ? `${action.capability.resource}:${workspace}:${path}`
972
+ : `${action.capability.resource}:${workspace}:search`;
973
+ return LedgerProviderEvidenceSchema.parse({
974
+ schemaVersion: LEDGER_PROVIDER_EVIDENCE_VERSION,
975
+ provider: "docs",
976
+ tool: action.capability.tool,
977
+ operation,
978
+ resource: providerResource("docs", path ? "document" : "workspace", resourceId, path ?? workspace),
979
+ normalized: compactJsonRecord({
980
+ workspace,
981
+ path,
982
+ query,
983
+ maxResults: getNumber(payload.maxResults),
984
+ maxBytes: getNumber(payload.maxBytes),
985
+ }),
986
+ });
987
+ }
988
+ function mcpEvidence(action, payload) {
989
+ const serverIdentity = action.toolDefinition?.serverIdentity ??
990
+ getString(payload.serverIdentity) ??
991
+ "mcp:unknown";
992
+ const toolName = getString(payload.toolName) ?? action.capability.tool;
993
+ return LedgerProviderEvidenceSchema.parse({
994
+ schemaVersion: LEDGER_PROVIDER_EVIDENCE_VERSION,
995
+ provider: "mcp",
996
+ tool: action.capability.tool,
997
+ operation: "tool_call",
998
+ resource: providerResource("mcp", "tool", `${serverIdentity}:${toolName}`, toolName),
999
+ normalized: compactJsonRecord({
1000
+ serverIdentity,
1001
+ schemaVersion: action.toolDefinition?.schemaVersion,
1002
+ definitionHash: action.toolDefinition?.definitionHash,
1003
+ toolName,
1004
+ }),
1005
+ });
1006
+ }
1007
+ function awsEvidence(action, payload, operation) {
1008
+ const bucket = getString(payload.bucket);
1009
+ const accountId = getString(payload.accountId);
1010
+ const region = getString(payload.region);
1011
+ const resourceId = bucket
1012
+ ? `aws:s3:${region ?? "unknown"}:${bucket}`
1013
+ : accountId
1014
+ ? `aws:account:${accountId}`
1015
+ : action.capability.resource;
1016
+ return LedgerProviderEvidenceSchema.parse({
1017
+ schemaVersion: LEDGER_PROVIDER_EVIDENCE_VERSION,
1018
+ provider: "aws",
1019
+ tool: action.capability.tool,
1020
+ operation,
1021
+ resource: providerResource("aws", bucket ? "s3_bucket" : "identity", resourceId, bucket ?? accountId),
1022
+ normalized: compactJsonRecord({
1023
+ accountId,
1024
+ region,
1025
+ bucket,
1026
+ prefix: getString(payload.prefix),
1027
+ maxKeys: getNumber(payload.maxKeys),
1028
+ }),
1029
+ });
1030
+ }
1031
+ function gcpEvidence(action, payload, operation) {
1032
+ const projectId = getString(payload.projectId);
1033
+ const bucket = getString(payload.bucket);
1034
+ const resourceId = bucket
1035
+ ? `gcp:storage:${bucket}`
1036
+ : projectId
1037
+ ? `gcp:project:${projectId}`
1038
+ : action.capability.resource;
1039
+ return LedgerProviderEvidenceSchema.parse({
1040
+ schemaVersion: LEDGER_PROVIDER_EVIDENCE_VERSION,
1041
+ provider: "gcp",
1042
+ tool: action.capability.tool,
1043
+ operation,
1044
+ resource: providerResource("gcp", bucket ? "storage_bucket" : "project", resourceId, bucket ?? projectId),
1045
+ normalized: compactJsonRecord({
1046
+ projectId,
1047
+ bucket,
1048
+ prefix: getString(payload.prefix),
1049
+ maxResults: getNumber(payload.maxResults),
1050
+ }),
1051
+ });
1052
+ }
1053
+ function providerResource(provider, kind, id, label) {
1054
+ return {
1055
+ provider,
1056
+ kind,
1057
+ id,
1058
+ ...(label ? { label } : {}),
1059
+ };
1060
+ }
1061
+ function providerDiffFromPayload(provider, payload) {
1062
+ const diff = isJsonObject(payload.diff) ? payload.diff : null;
1063
+ if (!diff)
1064
+ return undefined;
1065
+ return LedgerProviderEvidenceDiffSchema.parse({
1066
+ provider,
1067
+ summary: getString(diff.summary) ?? undefined,
1068
+ files: getObjectArray(diff.files).map((file) => ({
1069
+ path: getString(file.path) ?? "unknown",
1070
+ oldPath: getString(file.oldPath) ?? undefined,
1071
+ status: parseDiffStatus(getString(file.status)),
1072
+ additions: getNumber(file.additions) ?? 0,
1073
+ deletions: getNumber(file.deletions) ?? 0,
1074
+ hunks: getObjectArray(file.hunks).map((hunk) => ({
1075
+ header: getString(hunk.header) ?? "@@",
1076
+ lines: getObjectArray(hunk.lines).map((line) => ({
1077
+ type: parseDiffLineType(getString(line.type)),
1078
+ content: redactCredentialLikeText(getString(line.content) ?? ""),
1079
+ oldLine: getNumber(line.oldLine) ?? undefined,
1080
+ newLine: getNumber(line.newLine) ?? undefined,
1081
+ })),
1082
+ })),
1083
+ })),
1084
+ });
1085
+ }
1086
+ function changedFilesFromPayload(payload) {
1087
+ const changes = getObjectArray(payload.changes)
1088
+ .map((change) => getString(change.path))
1089
+ .filter((path) => Boolean(path));
1090
+ return [...new Set([...getStringArray(payload.filesChanged), ...changes])];
1091
+ }
1092
+ function linearFieldChanges(payload) {
1093
+ return [
1094
+ fieldChange("title", payload.fromTitle, payload.title),
1095
+ fieldChange("description", payload.fromDescription, payload.description),
1096
+ fieldChange("status", payload.fromStatus, payload.status),
1097
+ fieldChange("stateId", payload.fromStateId, payload.stateId),
1098
+ fieldChange("priority", payload.fromPriority, payload.priority),
1099
+ ].filter((change) => change !== null);
1100
+ }
1101
+ function fieldChange(field, before, after) {
1102
+ if (before === undefined && after === undefined)
1103
+ return null;
1104
+ return LedgerProviderEvidenceFieldChangeSchema.parse({
1105
+ field,
1106
+ before: sanitizeJsonValue(before),
1107
+ after: sanitizeJsonValue(after),
1108
+ });
1109
+ }
1110
+ function compactJsonRecord(input) {
1111
+ return Object.fromEntries(Object.entries(input)
1112
+ .map(([key, value]) => [key, sanitizeJsonValue(value)])
1113
+ .filter((entry) => entry[1] !== undefined));
1114
+ }
1115
+ function sanitizeJsonValue(value) {
1116
+ if (value === undefined || value === null)
1117
+ return undefined;
1118
+ if (typeof value === "string")
1119
+ return redactCredentialLikeText(value);
1120
+ if (Array.isArray(value)) {
1121
+ return value
1122
+ .map((item) => sanitizeJsonValue(item))
1123
+ .filter((item) => item !== undefined);
1124
+ }
1125
+ if (isJsonObject(value)) {
1126
+ return compactJsonRecord(value);
1127
+ }
1128
+ return value;
1129
+ }
1130
+ function redactCredentialLikeText(value) {
1131
+ return value
1132
+ .replace(/\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g, "[redacted-github-token]")
1133
+ .replace(/\bxox[baprs]-[A-Za-z0-9-]{20,}\b/g, "[redacted-slack-token]")
1134
+ .replace(/\bsk-[A-Za-z0-9_-]{20,}\b/g, "[redacted-api-key]")
1135
+ .replace(/\bAKIA[0-9A-Z]{16}\b/g, "[redacted-aws-access-key]")
1136
+ .replace(/\b[A-Za-z0-9+/]{40}\b/g, "[redacted-token-like-value]");
1137
+ }
1138
+ function parseDiffStatus(value) {
1139
+ if (value === "added" ||
1140
+ value === "modified" ||
1141
+ value === "deleted" ||
1142
+ value === "renamed") {
1143
+ return value;
1144
+ }
1145
+ return "modified";
1146
+ }
1147
+ function parseDiffLineType(value) {
1148
+ if (value === "addition" || value === "deletion" || value === "context") {
1149
+ return value;
1150
+ }
1151
+ return "context";
1152
+ }
1153
+ function getString(value) {
1154
+ return typeof value === "string" ? value : null;
1155
+ }
1156
+ function getStringArray(value) {
1157
+ if (!Array.isArray(value)) {
1158
+ return [];
1159
+ }
1160
+ return value.filter((item) => typeof item === "string");
1161
+ }
1162
+ function getNumber(value) {
1163
+ return typeof value === "number" ? value : null;
1164
+ }
1165
+ function getBoolean(value) {
1166
+ return typeof value === "boolean" ? value : null;
1167
+ }
1168
+ function getArrayLength(value) {
1169
+ return Array.isArray(value) ? value.length : null;
1170
+ }
1171
+ function getObjectArray(value) {
1172
+ if (!Array.isArray(value)) {
1173
+ return [];
1174
+ }
1175
+ return value.filter((item) => isJsonObject(item));
1176
+ }
1177
+ function isJsonObject(value) {
1178
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1179
+ }
1180
+ function toNumericDate(date) {
1181
+ return Math.floor(date.getTime() / 1000);
1182
+ }
1183
+ function canonicalize(value) {
1184
+ if (Array.isArray(value)) {
1185
+ return value.map((item) => canonicalize(item));
1186
+ }
1187
+ if (isPlainObject(value)) {
1188
+ return Object.fromEntries(Object.entries(value)
1189
+ .filter(([, entryValue]) => entryValue !== undefined)
1190
+ .sort(([left], [right]) => left.localeCompare(right))
1191
+ .map(([key, entryValue]) => [key, canonicalize(entryValue)]));
1192
+ }
1193
+ return value;
1194
+ }
1195
+ function isPlainObject(value) {
1196
+ return (typeof value === "object" &&
1197
+ value !== null &&
1198
+ !Array.isArray(value) &&
1199
+ Object.getPrototypeOf(value) === Object.prototype);
1200
+ }
1201
+ function isNodeError(error) {
1202
+ return error instanceof Error && "code" in error;
1203
+ }
1204
+ //# sourceMappingURL=index.js.map