@agentpolicyspecification/core 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.
Files changed (94) hide show
  1. package/LICENSE +187 -0
  2. package/README.md +13 -0
  3. package/coverage/clover.xml +458 -0
  4. package/coverage/coverage-final.json +7 -0
  5. package/coverage/lcov-report/base.css +224 -0
  6. package/coverage/lcov-report/block-navigation.js +87 -0
  7. package/coverage/lcov-report/favicon.png +0 -0
  8. package/coverage/lcov-report/index.html +146 -0
  9. package/coverage/lcov-report/prettify.css +1 -0
  10. package/coverage/lcov-report/prettify.js +2 -0
  11. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  12. package/coverage/lcov-report/sorter.js +210 -0
  13. package/coverage/lcov-report/src/core/errors.ts.html +217 -0
  14. package/coverage/lcov-report/src/core/index.html +146 -0
  15. package/coverage/lcov-report/src/core/policy.ts.html +142 -0
  16. package/coverage/lcov-report/src/core/types.ts.html +364 -0
  17. package/coverage/lcov-report/src/engine/aps-engine.ts.html +703 -0
  18. package/coverage/lcov-report/src/engine/index.html +131 -0
  19. package/coverage/lcov-report/src/engine/policy-set.ts.html +115 -0
  20. package/coverage/lcov-report/src/index.html +116 -0
  21. package/coverage/lcov-report/src/index.ts.html +244 -0
  22. package/coverage/lcov.info +558 -0
  23. package/dist/core/errors.d.ts +29 -0
  24. package/dist/core/errors.d.ts.map +1 -0
  25. package/dist/core/errors.js +21 -0
  26. package/dist/core/errors.js.map +1 -0
  27. package/dist/core/policy.d.ts +17 -0
  28. package/dist/core/policy.d.ts.map +1 -0
  29. package/dist/core/policy.js +2 -0
  30. package/dist/core/policy.js.map +1 -0
  31. package/dist/core/types.d.ts +67 -0
  32. package/dist/core/types.d.ts.map +1 -0
  33. package/dist/core/types.js +3 -0
  34. package/dist/core/types.js.map +1 -0
  35. package/dist/engine/aps-engine.d.ts +22 -0
  36. package/dist/engine/aps-engine.d.ts.map +1 -0
  37. package/dist/engine/aps-engine.js +167 -0
  38. package/dist/engine/aps-engine.js.map +1 -0
  39. package/dist/engine/policy-set.d.ts +9 -0
  40. package/dist/engine/policy-set.d.ts.map +1 -0
  41. package/dist/engine/policy-set.js +2 -0
  42. package/dist/engine/policy-set.js.map +1 -0
  43. package/dist/generated/base.d.ts +7 -0
  44. package/dist/generated/base.d.ts.map +1 -0
  45. package/dist/generated/base.js +4 -0
  46. package/dist/generated/base.js.map +1 -0
  47. package/dist/generated/dsl-policy.d.ts +130 -0
  48. package/dist/generated/dsl-policy.d.ts.map +1 -0
  49. package/dist/generated/dsl-policy.js +4 -0
  50. package/dist/generated/dsl-policy.js.map +1 -0
  51. package/dist/generated/input-context.d.ts +48 -0
  52. package/dist/generated/input-context.d.ts.map +1 -0
  53. package/dist/generated/input-context.js +4 -0
  54. package/dist/generated/input-context.js.map +1 -0
  55. package/dist/generated/output-context.d.ts +42 -0
  56. package/dist/generated/output-context.d.ts.map +1 -0
  57. package/dist/generated/output-context.js +4 -0
  58. package/dist/generated/output-context.js.map +1 -0
  59. package/dist/generated/policy-decision.d.ts +95 -0
  60. package/dist/generated/policy-decision.d.ts.map +1 -0
  61. package/dist/generated/policy-decision.js +4 -0
  62. package/dist/generated/policy-decision.js.map +1 -0
  63. package/dist/generated/policy-set.d.ts +139 -0
  64. package/dist/generated/policy-set.d.ts.map +1 -0
  65. package/dist/generated/policy-set.js +4 -0
  66. package/dist/generated/policy-set.js.map +1 -0
  67. package/dist/generated/tool-call-context.d.ts +52 -0
  68. package/dist/generated/tool-call-context.d.ts.map +1 -0
  69. package/dist/generated/tool-call-context.js +4 -0
  70. package/dist/generated/tool-call-context.js.map +1 -0
  71. package/dist/index.d.ts +13 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +3 -0
  74. package/dist/index.js.map +1 -0
  75. package/examples/basic-usage.ts +89 -0
  76. package/jest.config.js +20 -0
  77. package/package.json +46 -0
  78. package/scripts/generate-types.mjs +24 -0
  79. package/src/core/errors.ts +44 -0
  80. package/src/core/policy.ts +19 -0
  81. package/src/core/types.ts +93 -0
  82. package/src/engine/aps-engine.ts +206 -0
  83. package/src/engine/policy-set.ts +10 -0
  84. package/src/generated/base.ts +9 -0
  85. package/src/generated/dsl-policy.ts +133 -0
  86. package/src/generated/input-context.ts +51 -0
  87. package/src/generated/output-context.ts +45 -0
  88. package/src/generated/policy-decision.ts +98 -0
  89. package/src/generated/policy-set.ts +142 -0
  90. package/src/generated/tool-call-context.ts +55 -0
  91. package/src/index.ts +53 -0
  92. package/test/aps-engine.test.ts +264 -0
  93. package/tsconfig.json +22 -0
  94. package/tsconfig.test.json +10 -0
@@ -0,0 +1,206 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import type { InputPolicy, OutputPolicy, ToolCallPolicy } from "../core/policy.js";
3
+ import type { InputContext } from "../generated/input-context.js";
4
+ import type { OutputContext } from "../generated/output-context.js";
5
+ import type { ToolCallContext } from "../generated/tool-call-context.js";
6
+ import type { PolicyDecision } from "../generated/policy-decision.js";
7
+ import type { PolicySet as JsonPolicySet, PolicyEntry } from "../generated/policy-set.js";
8
+ import { AuditRecord, InterceptionPoint, PolicyDenialError, PolicyEvaluationError } from "../core/errors.js";
9
+ import type { PolicySet } from "./policy-set.js";
10
+
11
+ export type AuditHandler = (record: AuditRecord) => void | Promise<void>;
12
+
13
+ export interface ApsEngineOptions {
14
+ policySet: PolicySet;
15
+ onAudit?: AuditHandler;
16
+ }
17
+
18
+ export class ApsEngine {
19
+ private readonly policySet: PolicySet;
20
+ private readonly onAudit: AuditHandler | undefined;
21
+
22
+ constructor({ policySet, onAudit }: ApsEngineOptions) {
23
+ this.policySet = policySet;
24
+ this.onAudit = onAudit;
25
+ }
26
+
27
+ static async fromJson(absolutePath: string, options?: Pick<ApsEngineOptions, "onAudit">): Promise<ApsEngine> {
28
+ const raw = await readFile(absolutePath, "utf-8");
29
+ const jsonPolicySet = JSON.parse(raw) as JsonPolicySet;
30
+
31
+ const input: InputPolicy[] = [];
32
+ const tool_call: ToolCallPolicy[] = [];
33
+ const output: OutputPolicy[] = [];
34
+
35
+ for (const [index, entry] of jsonPolicySet.policies.entries()) {
36
+ const id = `policy-${index}`;
37
+ const appliesTo = entry.applies_to as string[] | undefined;
38
+ const appliesToAll = !appliesTo || appliesTo.length === 0;
39
+
40
+ if (appliesToAll || appliesTo.includes("input")) {
41
+ input.push({ id, evaluate: (ctx) => evaluateEntry(entry, ctx) });
42
+ }
43
+
44
+ if (appliesToAll || appliesTo.includes("tool_call")) {
45
+ tool_call.push({
46
+ id,
47
+ evaluate: (ctx) => {
48
+ const tools = entry.tools as string[] | undefined;
49
+ if (tools && tools.length > 0 && !tools.includes(ctx.tool_name)) {
50
+ return Promise.resolve<PolicyDecision>({ decision: "allow" });
51
+ }
52
+ return evaluateEntry(entry, ctx);
53
+ },
54
+ });
55
+ }
56
+
57
+ if (appliesToAll || appliesTo.includes("output")) {
58
+ output.push({ id, evaluate: (ctx) => evaluateEntry(entry, ctx) });
59
+ }
60
+ }
61
+
62
+ return new ApsEngine({ policySet: { input, tool_call, output }, ...options });
63
+ }
64
+
65
+ async evaluateInput(context: InputContext): Promise<void> {
66
+ await this.runPolicies(this.policySet.input ?? [], context, "input");
67
+ }
68
+
69
+ async evaluateToolCall(context: ToolCallContext): Promise<void> {
70
+ await this.runPolicies(this.policySet.tool_call ?? [], context, "tool_call");
71
+ }
72
+
73
+ async evaluateOutput(context: OutputContext): Promise<void> {
74
+ await this.runPolicies(this.policySet.output ?? [], context, "output");
75
+ }
76
+
77
+ private async runPolicies(policies: InputPolicy[], context: InputContext, interceptionPoint: "input"): Promise<void>;
78
+ private async runPolicies(policies: ToolCallPolicy[], context: ToolCallContext, interceptionPoint: "tool_call"): Promise<void>;
79
+ private async runPolicies(policies: OutputPolicy[], context: OutputContext, interceptionPoint: "output"): Promise<void>;
80
+ private async runPolicies(
81
+ policies: (InputPolicy | ToolCallPolicy | OutputPolicy)[],
82
+ context: InputContext | ToolCallContext | OutputContext,
83
+ interceptionPoint: InterceptionPoint
84
+ ): Promise<void> {
85
+ for (const policy of policies) {
86
+ let decision: PolicyDecision;
87
+
88
+ try {
89
+ decision = await (policy as { evaluate(ctx: typeof context): Promise<PolicyDecision> | PolicyDecision }).evaluate(context);
90
+ } catch (err) {
91
+ console.error('err: ', err);
92
+ if (err instanceof PolicyEvaluationError) {
93
+ await this.audit({
94
+ policy_id: policy.id,
95
+ decision: "evaluation_error",
96
+ interception_point: interceptionPoint,
97
+ reason: String(err.cause),
98
+ context,
99
+ timestamp: new Date().toISOString(),
100
+ });
101
+ }
102
+
103
+ if ((this.policySet.on_error ?? "deny") === "deny") {
104
+ throw err instanceof PolicyEvaluationError
105
+ ? err
106
+ : new PolicyEvaluationError({ policy_id: policy.id, interception_point: interceptionPoint, cause: err });
107
+ }
108
+
109
+ continue;
110
+ }
111
+
112
+ if (decision.decision === "audit") {
113
+ await this.audit({
114
+ policy_id: policy.id,
115
+ decision: "audit",
116
+ interception_point: interceptionPoint,
117
+ reason: decision.reason,
118
+ context,
119
+ timestamp: new Date().toISOString(),
120
+ });
121
+ continue;
122
+ }
123
+
124
+ if (decision.decision === "deny") {
125
+ await this.audit({
126
+ policy_id: policy.id,
127
+ decision: "deny",
128
+ interception_point: interceptionPoint,
129
+ reason: decision.reason,
130
+ context,
131
+ timestamp: new Date().toISOString(),
132
+ });
133
+
134
+ throw new PolicyDenialError({
135
+ policy_id: decision.policy_id ?? policy.id,
136
+ interception_point: interceptionPoint,
137
+ reason: decision.reason ?? "Policy denied without reason",
138
+ });
139
+ }
140
+
141
+ // allow, redact, transform — log and continue
142
+ // (redact/transform mutation is left to the runtime adapter layer)
143
+ if (decision.decision !== "allow") {
144
+ await this.audit({
145
+ policy_id: policy.id,
146
+ decision: decision.decision,
147
+ interception_point: interceptionPoint,
148
+ reason: undefined,
149
+ context,
150
+ timestamp: new Date().toISOString(),
151
+ });
152
+ }
153
+ }
154
+ }
155
+
156
+ private async audit(record: AuditRecord): Promise<void> {
157
+ if (this.onAudit) {
158
+ await this.onAudit(record);
159
+ }
160
+ }
161
+ }
162
+
163
+ // ── fromJson helpers ──────────────────────────────────────────────────────────
164
+
165
+ function evaluateEntry(entry: PolicyEntry, ctx: unknown): Promise<PolicyDecision> {
166
+ if (!evaluateCondition(entry.condition, ctx)) {
167
+ return Promise.resolve<PolicyDecision>({ decision: "allow" });
168
+ }
169
+ return Promise.resolve(buildDecision(entry, ctx));
170
+ }
171
+
172
+ function evaluateCondition(condition: PolicyEntry["condition"], ctx: unknown): boolean {
173
+ const cond = condition as unknown as Record<string, unknown>;
174
+ if ("always" in cond) return true;
175
+ const field = cond.field as string;
176
+ const value = resolveField(ctx, field);
177
+ if ("equals" in cond) return value === cond.equals;
178
+ if ("contains" in cond) return (cond.contains as string[]).some(v => String(value).toLowerCase().includes(v.toLowerCase()));
179
+ if ("not_in" in cond) return !(cond.not_in as unknown[]).includes(value);
180
+ if ("greater_than" in cond) return Number(value) > (cond.greater_than as number);
181
+ return false;
182
+ }
183
+
184
+ function resolveField(obj: unknown, fieldPath: string): unknown {
185
+ return fieldPath.split(".").reduce<unknown>((acc, key) => (acc as Record<string, unknown>)?.[key], obj);
186
+ }
187
+
188
+ function buildDecision(entry: PolicyEntry, ctx: unknown): PolicyDecision {
189
+ if (entry.action === "allow") return { decision: "allow" };
190
+ if (entry.action === "deny") {
191
+ return {
192
+ decision: "deny",
193
+ ...(entry.reason ? { reason: entry.reason } : {}),
194
+ };
195
+ }
196
+ return {
197
+ decision: "transform",
198
+ transformation: {
199
+ operations: Object.entries(entry.transformation ?? {}).map(([field, template]) => ({
200
+ field,
201
+ op: "set" as const,
202
+ value: template.replace(/\{\{(.+?)\}\}/g, (_: string, expr: string) => String(resolveField(ctx, expr.trim()) ?? "")) as unknown as { [k: string]: unknown },
203
+ })),
204
+ },
205
+ };
206
+ }
@@ -0,0 +1,10 @@
1
+ import type { InputPolicy, OutputPolicy, ToolCallPolicy } from "../core/policy.js";
2
+
3
+ export type OnErrorBehavior = "deny" | "allow";
4
+
5
+ export interface PolicySet {
6
+ on_error?: OnErrorBehavior;
7
+ input?: InputPolicy[];
8
+ tool_call?: ToolCallPolicy[];
9
+ output?: OutputPolicy[];
10
+ }
@@ -0,0 +1,9 @@
1
+ /* eslint-disable */
2
+ // This file is auto-generated from base.schema.json. Do not edit manually.
3
+
4
+ /**
5
+ * Base schema for the Agent Policy Specification v0.1.0. All other APS schemas extend this schema. Defines shared types used across the specification.
6
+ */
7
+ export interface ApsBase {
8
+ [k: string]: unknown;
9
+ }
@@ -0,0 +1,133 @@
1
+ /* eslint-disable */
2
+ // This file is auto-generated from dsl-policy.schema.json. Do not edit manually.
3
+
4
+ /**
5
+ * A condition that is evaluated against the context
6
+ */
7
+ export type Condition = EqualsCondition | ContainsCondition | NotInCondition | GreaterThanCondition | AlwaysCondition;
8
+
9
+ /**
10
+ * A single Agent Policy Specification DSL policy rule
11
+ */
12
+ export interface DSLPolicy {
13
+ condition: Condition;
14
+ /**
15
+ * The action to take when the condition matches
16
+ */
17
+ action: "allow" | "deny" | "redact" | "transform" | "audit";
18
+ /**
19
+ * Optional human-readable reason for the action, typically used with deny
20
+ */
21
+ reason?: string;
22
+ /**
23
+ * Redaction instructions to apply when action is 'redact'.
24
+ *
25
+ * @minItems 1
26
+ */
27
+ redactions?: [
28
+ {
29
+ /**
30
+ * Dot-notation path to the field being redacted (e.g. 'response.content').
31
+ */
32
+ field: string;
33
+ /**
34
+ * 'mask' replaces with a fixed string, 'remove' deletes the field, 'replace' substitutes matched patterns.
35
+ */
36
+ strategy: "mask" | "remove" | "replace";
37
+ /**
38
+ * Replacement string. Required when strategy is 'mask' or 'replace'.
39
+ */
40
+ replacement?: string;
41
+ /**
42
+ * Regex pattern identifying the content to redact. Required when strategy is 'replace'.
43
+ */
44
+ pattern?: string;
45
+ },
46
+ ...{
47
+ /**
48
+ * Dot-notation path to the field being redacted (e.g. 'response.content').
49
+ */
50
+ field: string;
51
+ /**
52
+ * 'mask' replaces with a fixed string, 'remove' deletes the field, 'replace' substitutes matched patterns.
53
+ */
54
+ strategy: "mask" | "remove" | "replace";
55
+ /**
56
+ * Replacement string. Required when strategy is 'mask' or 'replace'.
57
+ */
58
+ replacement?: string;
59
+ /**
60
+ * Regex pattern identifying the content to redact. Required when strategy is 'replace'.
61
+ */
62
+ pattern?: string;
63
+ }[]
64
+ ];
65
+ /**
66
+ * Field transformations to apply when action is 'transform'. Keys are dot-notation field paths, values are template strings supporting {{field.path}} interpolation.
67
+ */
68
+ transformation?: {
69
+ [k: string]: string;
70
+ };
71
+ }
72
+ /**
73
+ * Matches when the resolved field value strictly equals the operand
74
+ */
75
+ export interface EqualsCondition {
76
+ /**
77
+ * Dot-notation path to the field in the context (e.g. 'tool_name', 'messages.0.content')
78
+ */
79
+ field: string;
80
+ /**
81
+ * The value to compare against using strict equality
82
+ */
83
+ equals: {
84
+ [k: string]: unknown;
85
+ };
86
+ }
87
+ /**
88
+ * Matches when the resolved field value contains any of the given substrings (case-insensitive)
89
+ */
90
+ export interface ContainsCondition {
91
+ /**
92
+ * Dot-notation path to the field in the context
93
+ */
94
+ field: string;
95
+ /**
96
+ * List of substrings to search for
97
+ *
98
+ * @minItems 1
99
+ */
100
+ contains: [string, ...string[]];
101
+ }
102
+ /**
103
+ * Matches when the resolved field value is not present in the given list
104
+ */
105
+ export interface NotInCondition {
106
+ /**
107
+ * Dot-notation path to the field in the context
108
+ */
109
+ field: string;
110
+ /**
111
+ * List of values the field must not be equal to
112
+ */
113
+ not_in: unknown[];
114
+ }
115
+ /**
116
+ * Matches when the resolved field value is numerically greater than the operand
117
+ */
118
+ export interface GreaterThanCondition {
119
+ /**
120
+ * Dot-notation path to the field in the context
121
+ */
122
+ field: string;
123
+ /**
124
+ * The numeric threshold
125
+ */
126
+ greater_than: number;
127
+ }
128
+ /**
129
+ * Always matches, regardless of context
130
+ */
131
+ export interface AlwaysCondition {
132
+ always: true;
133
+ }
@@ -0,0 +1,51 @@
1
+ /* eslint-disable */
2
+ // This file is auto-generated from input-context.schema.json. Do not edit manually.
3
+
4
+ /**
5
+ * The evaluation input provided to policies at the Input Interception point.
6
+ */
7
+ export type InputContext = ApsBase & {
8
+ /**
9
+ * The ordered message history to be forwarded to the LLM runtime.
10
+ */
11
+ messages: Message[];
12
+ metadata: Metadata;
13
+ };
14
+
15
+ /**
16
+ * Base schema for the Agent Policy Specification v0.1.0. All other APS schemas extend this schema. Defines shared types used across the specification.
17
+ */
18
+ export interface ApsBase {
19
+ [k: string]: unknown;
20
+ }
21
+ /**
22
+ * A single message in a conversation.
23
+ */
24
+ export interface Message {
25
+ /**
26
+ * The role of the message author.
27
+ */
28
+ role: "system" | "user" | "assistant";
29
+ /**
30
+ * The text content of the message.
31
+ */
32
+ content: string;
33
+ }
34
+ /**
35
+ * Common metadata attached to every APS context object.
36
+ */
37
+ export interface Metadata {
38
+ /**
39
+ * Unique identifier for the agent that owns this session.
40
+ */
41
+ agent_id: string;
42
+ /**
43
+ * Unique identifier for the current session.
44
+ */
45
+ session_id: string;
46
+ /**
47
+ * ISO 8601 timestamp of when the interception occurred.
48
+ */
49
+ timestamp: string;
50
+ [k: string]: unknown;
51
+ }
@@ -0,0 +1,45 @@
1
+ /* eslint-disable */
2
+ // This file is auto-generated from output-context.schema.json. Do not edit manually.
3
+
4
+ /**
5
+ * The evaluation input provided to policies at the Output Interception point.
6
+ */
7
+ export type OutputContext = ApsBase & {
8
+ response: AssistantMessage;
9
+ metadata: Metadata;
10
+ };
11
+
12
+ /**
13
+ * Base schema for the Agent Policy Specification v0.1.0. All other APS schemas extend this schema. Defines shared types used across the specification.
14
+ */
15
+ export interface ApsBase {
16
+ [k: string]: unknown;
17
+ }
18
+ /**
19
+ * A message produced by the LLM (role must be 'assistant').
20
+ */
21
+ export interface AssistantMessage {
22
+ role: "assistant";
23
+ /**
24
+ * The text content of the assistant message.
25
+ */
26
+ content: string;
27
+ }
28
+ /**
29
+ * Common metadata attached to every APS context object.
30
+ */
31
+ export interface Metadata {
32
+ /**
33
+ * Unique identifier for the agent that owns this session.
34
+ */
35
+ agent_id: string;
36
+ /**
37
+ * Unique identifier for the current session.
38
+ */
39
+ session_id: string;
40
+ /**
41
+ * ISO 8601 timestamp of when the interception occurred.
42
+ */
43
+ timestamp: string;
44
+ [k: string]: unknown;
45
+ }
@@ -0,0 +1,98 @@
1
+ /* eslint-disable */
2
+ // This file is auto-generated from policy-decision.schema.json. Do not edit manually.
3
+
4
+ /**
5
+ * The result produced by a policy evaluation at any interception point.
6
+ */
7
+ export type PolicyDecision = AllowDecision | DenyDecision | RedactDecision | TransformDecision | AuditDecision;
8
+
9
+ export interface AllowDecision {
10
+ decision: "allow";
11
+ /**
12
+ * When true, an audit record is also produced for this interaction.
13
+ */
14
+ audit?: boolean;
15
+ }
16
+ export interface DenyDecision {
17
+ decision: "deny";
18
+ /**
19
+ * Human-readable explanation for the denial. MAY be omitted for security-sensitive denials.
20
+ */
21
+ reason?: string;
22
+ /**
23
+ * Identifier of the policy that produced this denial.
24
+ */
25
+ policy_id?: string;
26
+ /**
27
+ * When true, an audit record is also produced for this interaction.
28
+ */
29
+ audit?: boolean;
30
+ }
31
+ export interface RedactDecision {
32
+ decision: "redact";
33
+ /**
34
+ * @minItems 1
35
+ */
36
+ redactions: [Redaction, ...Redaction[]];
37
+ /**
38
+ * When true, an audit record is also produced for this interaction.
39
+ */
40
+ audit?: boolean;
41
+ }
42
+ export interface Redaction {
43
+ /**
44
+ * Dot-notation path to the field being redacted (e.g. 'response.content').
45
+ */
46
+ field: string;
47
+ /**
48
+ * 'mask' replaces with a fixed string, 'remove' deletes the field, 'replace' substitutes matched patterns.
49
+ */
50
+ strategy: "mask" | "remove" | "replace";
51
+ /**
52
+ * Replacement string. Required when strategy is 'mask' or 'replace'.
53
+ */
54
+ replacement?: string;
55
+ /**
56
+ * Regex pattern identifying the content to redact. Required when strategy is 'replace'.
57
+ */
58
+ pattern?: string;
59
+ }
60
+ export interface TransformDecision {
61
+ decision: "transform";
62
+ transformation: Transformation;
63
+ /**
64
+ * When true, an audit record is also produced for this interaction.
65
+ */
66
+ audit?: boolean;
67
+ }
68
+ export interface Transformation {
69
+ /**
70
+ * Ordered list of transformation operations to apply.
71
+ */
72
+ operations: {
73
+ /**
74
+ * 'set' replaces the field value, 'prepend'/'append' adds content to a string field.
75
+ */
76
+ op: "set" | "prepend" | "append";
77
+ /**
78
+ * Dot-notation path to the field to transform.
79
+ */
80
+ field: string;
81
+ /**
82
+ * The value to apply. Type must match the target field.
83
+ */
84
+ value: {
85
+ [k: string]: unknown;
86
+ };
87
+ }[];
88
+ }
89
+ /**
90
+ * Produces only an audit record; the interaction proceeds unchanged.
91
+ */
92
+ export interface AuditDecision {
93
+ decision: "audit";
94
+ /**
95
+ * Optional note to include in the audit record.
96
+ */
97
+ reason?: string;
98
+ }
@@ -0,0 +1,142 @@
1
+ /* eslint-disable */
2
+ // This file is auto-generated from policy-set.schema.json. Do not edit manually.
3
+
4
+ /**
5
+ * A condition evaluated against the context
6
+ */
7
+ export type Condition = EqualsCondition | ContainsCondition | NotInCondition | GreaterThanCondition | AlwaysCondition;
8
+
9
+ /**
10
+ * A collection of DSL policies with interception point and tool scope bindings
11
+ */
12
+ export interface PolicySet {
13
+ /**
14
+ * The APS specification version this policy set targets (e.g. '0.1.0').
15
+ */
16
+ aps_version: string;
17
+ /**
18
+ * The list of policies in this set
19
+ */
20
+ policies: PolicyEntry[];
21
+ }
22
+ /**
23
+ * A DSL policy rule with optional scope restrictions
24
+ */
25
+ export interface PolicyEntry {
26
+ condition: Condition;
27
+ /**
28
+ * The action to take when the condition matches
29
+ */
30
+ action: "allow" | "deny" | "redact" | "transform" | "audit";
31
+ /**
32
+ * Optional human-readable reason, typically used with deny
33
+ */
34
+ reason?: string;
35
+ /**
36
+ * Redaction instructions to apply when action is 'redact'.
37
+ *
38
+ * @minItems 1
39
+ */
40
+ redactions?: [
41
+ {
42
+ /**
43
+ * Dot-notation path to the field being redacted (e.g. 'response.content').
44
+ */
45
+ field: string;
46
+ /**
47
+ * 'mask' replaces with a fixed string, 'remove' deletes the field, 'replace' substitutes matched patterns.
48
+ */
49
+ strategy: "mask" | "remove" | "replace";
50
+ /**
51
+ * Replacement string. Required when strategy is 'mask' or 'replace'.
52
+ */
53
+ replacement?: string;
54
+ /**
55
+ * Regex pattern identifying the content to redact. Required when strategy is 'replace'.
56
+ */
57
+ pattern?: string;
58
+ },
59
+ ...{
60
+ /**
61
+ * Dot-notation path to the field being redacted (e.g. 'response.content').
62
+ */
63
+ field: string;
64
+ /**
65
+ * 'mask' replaces with a fixed string, 'remove' deletes the field, 'replace' substitutes matched patterns.
66
+ */
67
+ strategy: "mask" | "remove" | "replace";
68
+ /**
69
+ * Replacement string. Required when strategy is 'mask' or 'replace'.
70
+ */
71
+ replacement?: string;
72
+ /**
73
+ * Regex pattern identifying the content to redact. Required when strategy is 'replace'.
74
+ */
75
+ pattern?: string;
76
+ }[]
77
+ ];
78
+ /**
79
+ * Field transformations when action is 'transform'. Keys are dot-notation field paths, values are template strings supporting {{field.path}} interpolation.
80
+ */
81
+ transformation?: {
82
+ [k: string]: string;
83
+ };
84
+ /**
85
+ * Interception points this policy applies to. Omit to apply to all points.
86
+ *
87
+ * @minItems 1
88
+ */
89
+ applies_to?: ["input" | "output" | "tool_call", ...("input" | "output" | "tool_call")[]];
90
+ /**
91
+ * Tool names this policy applies to. Only evaluated when applies_to includes 'tool_call'. Omit to apply to all tools.
92
+ */
93
+ tools?: string[];
94
+ }
95
+ /**
96
+ * Matches when the resolved field value strictly equals the operand
97
+ */
98
+ export interface EqualsCondition {
99
+ /**
100
+ * Dot-notation path to the field in the context (e.g. 'tool_name', 'messages.0.content')
101
+ */
102
+ field: string;
103
+ /**
104
+ * The value to compare against using strict equality
105
+ */
106
+ equals: {
107
+ [k: string]: unknown;
108
+ };
109
+ }
110
+ /**
111
+ * Matches when the resolved field value contains any of the given substrings (case-insensitive)
112
+ */
113
+ export interface ContainsCondition {
114
+ field: string;
115
+ /**
116
+ * @minItems 1
117
+ */
118
+ contains: [string, ...string[]];
119
+ }
120
+ /**
121
+ * Matches when the resolved field value is not present in the given list
122
+ */
123
+ export interface NotInCondition {
124
+ field: string;
125
+ /**
126
+ * List of values the field must not be equal to
127
+ */
128
+ not_in: unknown[];
129
+ }
130
+ /**
131
+ * Matches when the resolved field value is numerically greater than the operand
132
+ */
133
+ export interface GreaterThanCondition {
134
+ field: string;
135
+ greater_than: number;
136
+ }
137
+ /**
138
+ * Always matches, regardless of context
139
+ */
140
+ export interface AlwaysCondition {
141
+ always: true;
142
+ }