attenu-guard 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 (112) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +202 -0
  3. package/README.md +200 -0
  4. package/SECURITY.md +34 -0
  5. package/bin/attenu-guard.js +4 -0
  6. package/dist/cjs/adapters/langgraph.d.ts +174 -0
  7. package/dist/cjs/adapters/langgraph.d.ts.map +1 -0
  8. package/dist/cjs/adapters/langgraph.js +242 -0
  9. package/dist/cjs/adapters/langgraph.js.map +1 -0
  10. package/dist/cjs/audit.d.ts +82 -0
  11. package/dist/cjs/audit.d.ts.map +1 -0
  12. package/dist/cjs/audit.js +195 -0
  13. package/dist/cjs/audit.js.map +1 -0
  14. package/dist/cjs/authority.d.ts +114 -0
  15. package/dist/cjs/authority.d.ts.map +1 -0
  16. package/dist/cjs/authority.js +238 -0
  17. package/dist/cjs/authority.js.map +1 -0
  18. package/dist/cjs/canonical.d.ts +91 -0
  19. package/dist/cjs/canonical.d.ts.map +1 -0
  20. package/dist/cjs/canonical.js +372 -0
  21. package/dist/cjs/canonical.js.map +1 -0
  22. package/dist/cjs/ceilings.d.ts +211 -0
  23. package/dist/cjs/ceilings.d.ts.map +1 -0
  24. package/dist/cjs/ceilings.js +476 -0
  25. package/dist/cjs/ceilings.js.map +1 -0
  26. package/dist/cjs/chain.d.ts +106 -0
  27. package/dist/cjs/chain.d.ts.map +1 -0
  28. package/dist/cjs/chain.js +0 -0
  29. package/dist/cjs/chain.js.map +1 -0
  30. package/dist/cjs/cli.d.ts +16 -0
  31. package/dist/cjs/cli.d.ts.map +1 -0
  32. package/dist/cjs/cli.js +100 -0
  33. package/dist/cjs/cli.js.map +1 -0
  34. package/dist/cjs/evidence.d.ts +163 -0
  35. package/dist/cjs/evidence.d.ts.map +1 -0
  36. package/dist/cjs/evidence.js +409 -0
  37. package/dist/cjs/evidence.js.map +1 -0
  38. package/dist/cjs/guard.d.ts +186 -0
  39. package/dist/cjs/guard.d.ts.map +1 -0
  40. package/dist/cjs/guard.js +432 -0
  41. package/dist/cjs/guard.js.map +1 -0
  42. package/dist/cjs/index.d.ts +52 -0
  43. package/dist/cjs/index.d.ts.map +1 -0
  44. package/dist/cjs/index.js +99 -0
  45. package/dist/cjs/index.js.map +1 -0
  46. package/dist/cjs/package.json +3 -0
  47. package/dist/cjs/reasons.d.ts +93 -0
  48. package/dist/cjs/reasons.d.ts.map +1 -0
  49. package/dist/cjs/reasons.js +139 -0
  50. package/dist/cjs/reasons.js.map +1 -0
  51. package/dist/cjs/strikes.d.ts +24 -0
  52. package/dist/cjs/strikes.d.ts.map +1 -0
  53. package/dist/cjs/strikes.js +30 -0
  54. package/dist/cjs/strikes.js.map +1 -0
  55. package/dist/cjs/wire.d.ts +83 -0
  56. package/dist/cjs/wire.d.ts.map +1 -0
  57. package/dist/cjs/wire.js +165 -0
  58. package/dist/cjs/wire.js.map +1 -0
  59. package/dist/esm/adapters/langgraph.d.ts +174 -0
  60. package/dist/esm/adapters/langgraph.d.ts.map +1 -0
  61. package/dist/esm/adapters/langgraph.js +199 -0
  62. package/dist/esm/adapters/langgraph.js.map +1 -0
  63. package/dist/esm/audit.d.ts +82 -0
  64. package/dist/esm/audit.d.ts.map +1 -0
  65. package/dist/esm/audit.js +189 -0
  66. package/dist/esm/audit.js.map +1 -0
  67. package/dist/esm/authority.d.ts +114 -0
  68. package/dist/esm/authority.d.ts.map +1 -0
  69. package/dist/esm/authority.js +233 -0
  70. package/dist/esm/authority.js.map +1 -0
  71. package/dist/esm/canonical.d.ts +91 -0
  72. package/dist/esm/canonical.d.ts.map +1 -0
  73. package/dist/esm/canonical.js +359 -0
  74. package/dist/esm/canonical.js.map +1 -0
  75. package/dist/esm/ceilings.d.ts +211 -0
  76. package/dist/esm/ceilings.d.ts.map +1 -0
  77. package/dist/esm/ceilings.js +460 -0
  78. package/dist/esm/ceilings.js.map +1 -0
  79. package/dist/esm/chain.d.ts +106 -0
  80. package/dist/esm/chain.d.ts.map +1 -0
  81. package/dist/esm/chain.js +0 -0
  82. package/dist/esm/chain.js.map +1 -0
  83. package/dist/esm/cli.d.ts +16 -0
  84. package/dist/esm/cli.d.ts.map +1 -0
  85. package/dist/esm/cli.js +97 -0
  86. package/dist/esm/cli.js.map +1 -0
  87. package/dist/esm/evidence.d.ts +163 -0
  88. package/dist/esm/evidence.d.ts.map +1 -0
  89. package/dist/esm/evidence.js +398 -0
  90. package/dist/esm/evidence.js.map +1 -0
  91. package/dist/esm/guard.d.ts +186 -0
  92. package/dist/esm/guard.d.ts.map +1 -0
  93. package/dist/esm/guard.js +427 -0
  94. package/dist/esm/guard.js.map +1 -0
  95. package/dist/esm/index.d.ts +52 -0
  96. package/dist/esm/index.d.ts.map +1 -0
  97. package/dist/esm/index.js +42 -0
  98. package/dist/esm/index.js.map +1 -0
  99. package/dist/esm/package.json +3 -0
  100. package/dist/esm/reasons.d.ts +93 -0
  101. package/dist/esm/reasons.d.ts.map +1 -0
  102. package/dist/esm/reasons.js +134 -0
  103. package/dist/esm/reasons.js.map +1 -0
  104. package/dist/esm/strikes.d.ts +24 -0
  105. package/dist/esm/strikes.d.ts.map +1 -0
  106. package/dist/esm/strikes.js +26 -0
  107. package/dist/esm/strikes.js.map +1 -0
  108. package/dist/esm/wire.d.ts +83 -0
  109. package/dist/esm/wire.d.ts.map +1 -0
  110. package/dist/esm/wire.js +159 -0
  111. package/dist/esm/wire.js.map +1 -0
  112. package/package.json +89 -0
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ /**
3
+ * adapters/langgraph.ts — a thin LangGraph.js integration.
4
+ *
5
+ * The adapter is built in two layers, the same shape as the Python one:
6
+ *
7
+ * 1. The AUTHORIZATION-WRAPPING LOGIC is plain TypeScript. It wraps an
8
+ * arbitrary callable so that every call is authorized through a `Guard`
9
+ * first, and it does not import, require or reference `@langchain/langgraph`
10
+ * at all. That is deliberate: a LangGraph node is, by LangGraph's own
11
+ * convention, just a callable — `(state) => partialState` — so wrapping
12
+ * "any callable" is enough to wrap a node or a plain tool function alike,
13
+ * and it keeps the logic testable with no framework installed.
14
+ *
15
+ * 2. The one spot that touches the package itself (`isLangGraphAvailable`)
16
+ * imports it lazily. `addGuardedNode` takes an already-constructed graph
17
+ * and only calls `.addNode(name, callable)` on it, so it needs no import
18
+ * either.
19
+ *
20
+ * ## Which hook this uses, and why
21
+ *
22
+ * The Python adapter wraps node callables. LangGraph.js has no public
23
+ * before-tool-call hook: `ToolNode` resolves the tool itself and invokes it at
24
+ * `dist/prebuilt/tool_node.js:207` (`const output = await tool.invoke(toolCall,
25
+ * runtime)`), inside a `protected runTool`. So this adapter takes the
26
+ * TOOL-WRAPPING form: `guardTool` returns a stand-in for a tool object whose
27
+ * `invoke` authorizes first and calls through only on an allow. The stand-in
28
+ * keeps the original `name`, `description` and `schema`, which is all `ToolNode`
29
+ * looks at when it matches a tool call to a tool, so a wrapped tool drops
30
+ * straight into `new ToolNode([...])` or `createReactAgent({tools})`.
31
+ *
32
+ * A denial throws `AuthorityDenied` — the same error `Guard.enforce` throws —
33
+ * BEFORE the tool body runs, so a poisoned instruction or a runaway plan never
34
+ * reaches the call it is not authorized to make. A graph can catch it around
35
+ * `graph.invoke(...)`, or route around it with LangGraph's own error handling;
36
+ * this adapter does not prescribe which. It guarantees only that the tool body
37
+ * never executes on a denial.
38
+ *
39
+ * ## Delegation
40
+ *
41
+ * Handing work to a sub-agent is the delegation moment. `delegateTo` mints the
42
+ * child `Guard` at the handoff — before the sub-agent's graph is invoked — so
43
+ * the sub-agent's tools are guarded by an authority that is provably a subset of
44
+ * the parent's.
45
+ *
46
+ * const researcher = delegateTo(supervisor, {
47
+ * agentId: "researcher",
48
+ * request: new Authority({ scopes: ["crm.read"], ceilings: [new RowLimit(500)], ttl: 900 }),
49
+ * task: "summarise the Q3 pipeline",
50
+ * });
51
+ * const tools = guardTools(researcher, [crmQuery], { scopes: { crm_query: "crm.read" } });
52
+ */
53
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
54
+ if (k2 === undefined) k2 = k;
55
+ var desc = Object.getOwnPropertyDescriptor(m, k);
56
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
57
+ desc = { enumerable: true, get: function() { return m[k]; } };
58
+ }
59
+ Object.defineProperty(o, k2, desc);
60
+ }) : (function(o, m, k, k2) {
61
+ if (k2 === undefined) k2 = k;
62
+ o[k2] = m[k];
63
+ }));
64
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
65
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
66
+ }) : function(o, v) {
67
+ o["default"] = v;
68
+ });
69
+ var __importStar = (this && this.__importStar) || (function () {
70
+ var ownKeys = function(o) {
71
+ ownKeys = Object.getOwnPropertyNames || function (o) {
72
+ var ar = [];
73
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
74
+ return ar;
75
+ };
76
+ return ownKeys(o);
77
+ };
78
+ return function (mod) {
79
+ if (mod && mod.__esModule) return mod;
80
+ var result = {};
81
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
82
+ __setModuleDefault(result, mod);
83
+ return result;
84
+ };
85
+ })();
86
+ Object.defineProperty(exports, "__esModule", { value: true });
87
+ exports.ATTENU_SCOPE = exports.ATTENU_GUARD = void 0;
88
+ exports.guardNode = guardNode;
89
+ exports.addGuardedNode = addGuardedNode;
90
+ exports.guardTool = guardTool;
91
+ exports.guardTools = guardTools;
92
+ exports.toolArgs = toolArgs;
93
+ exports.delegateTo = delegateTo;
94
+ exports.isLangGraphAvailable = isLangGraphAvailable;
95
+ const guard_js_1 = require("../guard.js");
96
+ /**
97
+ * Wrap a callable so every call is authorized through `guard` first.
98
+ *
99
+ * On call: the context is `contextFn(...args)` if given, else `{}`; then
100
+ * `guard.check(toolScope, {context, tool})`. On a denial this throws
101
+ * `AuthorityDenied` and the wrapped callable is NEVER invoked. Otherwise it
102
+ * calls through with the original arguments and returns the result unchanged —
103
+ * including a promise, which is passed along untouched.
104
+ *
105
+ * Use the Guard for the SPECIFIC agent this callable belongs to, not the
106
+ * orchestrator's broader one, so a denial reflects that node's real, narrowed
107
+ * authority.
108
+ */
109
+ function guardNode(guard, toolScope, fn, options = {}) {
110
+ const resolvedTool = options.tool !== undefined ? options.tool : (fn.name || null);
111
+ const wrapped = function (...args) {
112
+ const context = options.contextFn ? options.contextFn(...args) : {};
113
+ const decision = guard.check(toolScope, {
114
+ context,
115
+ tool: resolvedTool,
116
+ disposition: options.disposition ?? null,
117
+ metered: options.metered ?? false,
118
+ });
119
+ if (!decision.allowed)
120
+ throw new guard_js_1.AuthorityDenied(decision);
121
+ return fn.apply(this, args);
122
+ };
123
+ Object.defineProperties(wrapped, {
124
+ name: { value: fn.name, configurable: true },
125
+ guard: { value: guard, enumerable: true },
126
+ toolScope: { value: toolScope, enumerable: true },
127
+ unwrapped: { value: fn, enumerable: true },
128
+ });
129
+ return wrapped;
130
+ }
131
+ /**
132
+ * Build the guarded wrapper and register it on a graph in one call.
133
+ *
134
+ * `graph` is structurally typed: it only needs `.addNode(name, callable)`,
135
+ * which is `StateGraph`'s real signature — so this helper needs no import of
136
+ * its own and stays testable against a plain stand-in graph.
137
+ */
138
+ function addGuardedNode(graph, name, guard, toolScope, fn, options = {}) {
139
+ const node = guardNode(guard, toolScope, fn, options);
140
+ graph.addNode(name, node);
141
+ return node;
142
+ }
143
+ /**
144
+ * Return a stand-in for `tool` whose `invoke` authorizes through `guard` before
145
+ * the tool body runs. Everything else — `name`, `description`, `schema`, any
146
+ * LangChain internals — is forwarded to the original, so `ToolNode` matches and
147
+ * runs it exactly as it would the unwrapped tool.
148
+ *
149
+ * The context function receives the raw invoke arguments. `ToolNode` passes a
150
+ * tool call object, so the arguments the model proposed are at `input.args`;
151
+ * a direct `tool.invoke({...})` passes them at the top level. `toolArgs` below
152
+ * reads either shape.
153
+ */
154
+ function guardTool(guard, tool, options = {}) {
155
+ const scope = options.scope ?? tool.name;
156
+ const label = options.tool !== undefined ? options.tool : tool.name;
157
+ const guardedInvoke = (input, config) => {
158
+ const context = options.contextFn ? options.contextFn(input, config) : {};
159
+ const decision = guard.check(scope, {
160
+ context,
161
+ tool: label,
162
+ disposition: options.disposition ?? null,
163
+ metered: options.metered ?? false,
164
+ });
165
+ if (!decision.allowed) {
166
+ if (options.onDenied)
167
+ return options.onDenied(decision, input);
168
+ throw new guard_js_1.AuthorityDenied(decision);
169
+ }
170
+ return tool.invoke(input, config);
171
+ };
172
+ return new Proxy(tool, {
173
+ get(target, prop, receiver) {
174
+ if (prop === "invoke")
175
+ return guardedInvoke;
176
+ if (prop === exports.ATTENU_GUARD)
177
+ return guard;
178
+ if (prop === exports.ATTENU_SCOPE)
179
+ return scope;
180
+ const value = Reflect.get(target, prop, receiver);
181
+ return typeof value === "function" ? value.bind(target) : value;
182
+ },
183
+ });
184
+ }
185
+ /** Marker properties, so a caller can tell a guarded tool from a bare one. */
186
+ exports.ATTENU_GUARD = "__attenuGuard";
187
+ exports.ATTENU_SCOPE = "__attenuScope";
188
+ /**
189
+ * Guard a whole tool list in one call — what you hand to `new ToolNode([...])`
190
+ * or `createReactAgent({tools})`.
191
+ */
192
+ function guardTools(guard, tools, options = {}) {
193
+ return tools.map((t) => guardTool(guard, t, {
194
+ scope: options.scopes?.[t.name] ?? t.name,
195
+ contextFn: options.contexts?.[t.name] ?? options.contextFn,
196
+ disposition: options.disposition ?? null,
197
+ metered: options.metered ?? false,
198
+ onDenied: options.onDenied,
199
+ }));
200
+ }
201
+ /**
202
+ * The arguments a tool call carries, whichever shape `invoke` was handed.
203
+ * `ToolNode` passes `{name, args, id, type}`; a direct call passes the args
204
+ * themselves. Useful inside a `contextFn`.
205
+ */
206
+ function toolArgs(input) {
207
+ if (input !== null && typeof input === "object" && "args" in input && !Array.isArray(input)) {
208
+ const args = input.args;
209
+ if (args !== null && typeof args === "object")
210
+ return args;
211
+ }
212
+ return (input ?? {});
213
+ }
214
+ /**
215
+ * Mint the child Guard for a sub-agent handoff. Handing work to a sub-agent IS
216
+ * the delegation moment: call this before the sub-agent's graph is invoked, so
217
+ * its tools are guarded by an authority that is provably a subset of the
218
+ * parent's. A request wider than the parent holds comes back narrowed.
219
+ *
220
+ * Throws `AuthorityError` if the handoff is structurally refused — the parent
221
+ * is revoked or expired, the agent is banned, or the chain's depth or fanout
222
+ * ceiling would be exceeded.
223
+ */
224
+ function delegateTo(parent, options) {
225
+ return parent.delegate(options.agentId, options.request, options.task);
226
+ }
227
+ /**
228
+ * True iff `@langchain/langgraph` is importable here. The import is lazy and
229
+ * happens only when this is called, so merely importing this adapter — or using
230
+ * `guardNode` / `guardTool` — never requires LangGraph to be installed.
231
+ */
232
+ async function isLangGraphAvailable() {
233
+ const specifier = "@langchain/langgraph";
234
+ try {
235
+ await Promise.resolve(`${specifier}`).then(s => __importStar(require(s)));
236
+ return true;
237
+ }
238
+ catch {
239
+ return false;
240
+ }
241
+ }
242
+ //# sourceMappingURL=langgraph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"langgraph.js","sourceRoot":"","sources":["../../../src/adapters/langgraph.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CH,8BA2BC;AAcD,wCAWC;AA+BD,8BAgCC;AAkBD,gCAcC;AAOD,4BAMC;AAkBD,gCAEC;AAOD,oDAQC;AA5OD,0CAA0D;AA4B1D;;;;;;;;;;;;GAYG;AACH,SAAgB,SAAS,CACvB,KAAY,EACZ,SAAiB,EACjB,EAAK,EACL,UAAwB,EAAE;IAE1B,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;IAEnF,MAAM,OAAO,GAAG,UAAyB,GAAG,IAAW;QACrD,MAAM,OAAO,GAAY,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE;YACtC,OAAO;YACP,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;YACxC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,KAAK;SAClC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,OAAO;YAAE,MAAM,IAAI,0BAAe,CAAC,QAAQ,CAAC,CAAC;QAC3D,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAmB,CAAC;IAEpB,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE;QAC/B,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;QAC5C,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;QACzC,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE;QACjD,SAAS,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;KAC3C,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC;AAOD;;;;;;GAMG;AACH,SAAgB,cAAc,CAC5B,KAAgB,EAChB,IAAY,EACZ,KAAY,EACZ,SAAiB,EACjB,EAAK,EACL,UAAwB,EAAE;IAE1B,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IACtD,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC;AACd,CAAC;AAoBD;;;;;;;;;;GAUG;AACH,SAAgB,SAAS,CACvB,KAAY,EACZ,IAAO,EACP,UAA4B,EAAE;IAE9B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAEpE,MAAM,aAAa,GAAG,CAAC,KAAU,EAAE,MAAY,EAAE,EAAE;QACjD,MAAM,OAAO,GAAY,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACnF,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE;YAClC,OAAO;YACP,IAAI,EAAE,KAAK;YACX,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;YACxC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,KAAK;SAClC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACtB,IAAI,OAAO,CAAC,QAAQ;gBAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC/D,MAAM,IAAI,0BAAe,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC,CAAC;IAEF,OAAO,IAAI,KAAK,CAAC,IAAI,EAAE;QACrB,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ;YACxB,IAAI,IAAI,KAAK,QAAQ;gBAAE,OAAO,aAAa,CAAC;YAC5C,IAAI,IAAI,KAAK,oBAAY;gBAAE,OAAO,KAAK,CAAC;YACxC,IAAI,IAAI,KAAK,oBAAY;gBAAE,OAAO,KAAK,CAAC;YACxC,MAAM,KAAK,GAAY,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC3D,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAE,KAA8B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAC5F,CAAC;KACF,CAAM,CAAC;AACV,CAAC;AAED,8EAA8E;AACjE,QAAA,YAAY,GAAG,eAAe,CAAC;AAC/B,QAAA,YAAY,GAAG,eAAe,CAAC;AAU5C;;;GAGG;AACH,SAAgB,UAAU,CACxB,KAAY,EACZ,KAAmB,EACnB,UAA6B,EAAE;IAE/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACrB,SAAS,CAAC,KAAK,EAAE,CAAC,EAAE;QAClB,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI;QACzC,SAAS,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,SAAS;QAC1D,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;QACxC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,KAAK;QACjC,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC,CACH,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAgB,QAAQ,CAAC,KAAU;IACjC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5F,MAAM,IAAI,GAAI,KAA2B,CAAC,IAAI,CAAC;QAC/C,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAA2B,CAAC;IACpF,CAAC;IACD,OAAO,CAAC,KAAK,IAAI,EAAE,CAAwB,CAAC;AAC9C,CAAC;AAQD;;;;;;;;;GASG;AACH,SAAgB,UAAU,CAAC,MAAa,EAAE,OAAwB;IAChE,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,oBAAoB;IACxC,MAAM,SAAS,GAAG,sBAAsB,CAAC;IACzC,IAAI,CAAC;QACH,yBAAa,SAAS,uCAAC,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Hash-chained audit log — the open, verifiable record of every authority decision.
3
+ *
4
+ * Each event is appended as one JSON line whose `hash` covers the event plus the
5
+ * previous line's hash. Any insertion, deletion or reordering breaks the chain
6
+ * and is detectable offline by anyone, with no vendor in the loop.
7
+ *
8
+ * The schema is versioned and published as `schema/agent-audit.schema.json` in
9
+ * the Python distribution, so other tools (SIEMs, observability) can ingest it.
10
+ * The hashes computed here are byte-identical to the Python library's for
11
+ * identical content — see `canonical.ts` for how that is guaranteed.
12
+ */
13
+ import { type CJson } from "./canonical.js";
14
+ import type { Signer } from "./wire.js";
15
+ export declare const SCHEMA_VERSION = 1;
16
+ export declare const GENESIS: string;
17
+ /** One line of the ledger. */
18
+ export type LedgerEntry = Record<string, CJson>;
19
+ /** A local destination that receives every entry after the file write. */
20
+ export interface Sink {
21
+ write(entry: LedgerEntry): void;
22
+ }
23
+ /** A signed external commitment to a chain head. */
24
+ export interface Anchor {
25
+ v: number;
26
+ chain_id: string;
27
+ seq: number;
28
+ head: string;
29
+ ts: number | string;
30
+ kid: string | null;
31
+ sig: string;
32
+ /** Set by `exportBundle`; not part of the signed body. */
33
+ verified?: boolean;
34
+ [key: string]: CJson | undefined;
35
+ }
36
+ /** `sha256(prev_hash || canonical(event-without-hash))`, as hex. */
37
+ export declare function hashEntry(prevHash: string, payload: LedgerEntry): string;
38
+ export interface AuditLogInit {
39
+ /** Where to append the `.jsonl` ledger. Omit for an in-memory log. */
40
+ path?: string | null;
41
+ /** Local sinks; each gets every entry after the file write. */
42
+ sinks?: readonly Sink[];
43
+ }
44
+ /** Append-only, hash-chained decision log. */
45
+ export declare class AuditLog {
46
+ readonly path: string | null;
47
+ private readonly sinks;
48
+ private prev;
49
+ private seq;
50
+ private readonly _entries;
51
+ /**
52
+ * Timestamps are injected by the caller (the `Guard` uses a monotonic
53
+ * counter) so the log stays deterministic in tests and reproducible in
54
+ * replay. In production the runtime supplies a trusted timestamp.
55
+ */
56
+ constructor(init?: AuditLogInit | string | null);
57
+ append(event: string, ts: number | string, fields?: LedgerEntry): LedgerEntry;
58
+ get entries(): LedgerEntry[];
59
+ get length(): number;
60
+ [Symbol.iterator](): Iterator<LedgerEntry>;
61
+ /** `[seq, hash]` of the last entry — the chain head; `[-1, GENESIS]` if empty. */
62
+ head(): [number, string];
63
+ private chainIdHint;
64
+ /**
65
+ * A signed external COMMITMENT to the chain head. Publish it out of band; a
66
+ * later `verifyAnchor` then catches a log that was fully rewritten and
67
+ * re-hashed, which plain `verify` cannot — a consistent rewrite reproduces
68
+ * its own hashes. The signed head hash is the fixed point.
69
+ */
70
+ anchor(signer: Signer, ts?: number | string): Anchor;
71
+ /** The chain reproduces AND its head matches a SIGNED anchor. */
72
+ static verifyAnchor(entries: readonly LedgerEntry[], anchor: Record<string, CJson> | null | undefined, signer: Signer): [boolean, string | null];
73
+ /** Recompute the chain. Returns `[ok, firstBadReason]`. */
74
+ static verify(entries: readonly LedgerEntry[]): [boolean, string | null];
75
+ /** Read a `.jsonl` ledger, keeping every number's original literal. */
76
+ static load(path: string): LedgerEntry[];
77
+ /** Parse `.jsonl` text into entries. Blank lines are skipped. */
78
+ static parseLines(text: string): LedgerEntry[];
79
+ }
80
+ /** The first `chain_id` any entry carries, or `"chain"`. */
81
+ export declare function chainIdOf(entries: readonly LedgerEntry[]): string;
82
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ /**
3
+ * Hash-chained audit log — the open, verifiable record of every authority decision.
4
+ *
5
+ * Each event is appended as one JSON line whose `hash` covers the event plus the
6
+ * previous line's hash. Any insertion, deletion or reordering breaks the chain
7
+ * and is detectable offline by anyone, with no vendor in the loop.
8
+ *
9
+ * The schema is versioned and published as `schema/agent-audit.schema.json` in
10
+ * the Python distribution, so other tools (SIEMs, observability) can ingest it.
11
+ * The hashes computed here are byte-identical to the Python library's for
12
+ * identical content — see `canonical.ts` for how that is guaranteed.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.AuditLog = exports.GENESIS = exports.SCHEMA_VERSION = void 0;
16
+ exports.hashEntry = hashEntry;
17
+ exports.chainIdOf = chainIdOf;
18
+ const node_crypto_1 = require("node:crypto");
19
+ const node_fs_1 = require("node:fs");
20
+ const node_path_1 = require("node:path");
21
+ const canonical_js_1 = require("./canonical.js");
22
+ exports.SCHEMA_VERSION = 1;
23
+ exports.GENESIS = "0".repeat(64);
24
+ /** `sha256(prev_hash || canonical(event-without-hash))`, as hex. */
25
+ function hashEntry(prevHash, payload) {
26
+ const h = (0, node_crypto_1.createHash)("sha256");
27
+ h.update(Buffer.from(prevHash, "utf8"));
28
+ h.update((0, canonical_js_1.canonicalBytes)(payload));
29
+ return h.digest("hex");
30
+ }
31
+ function asNumber(value) {
32
+ const plain = (0, canonical_js_1.toPlain)(value);
33
+ return typeof plain === "number" ? plain : undefined;
34
+ }
35
+ function withoutHash(entry) {
36
+ const out = {};
37
+ for (const [k, v] of Object.entries(entry)) {
38
+ if (k !== "hash")
39
+ out[k] = v;
40
+ }
41
+ return out;
42
+ }
43
+ /** Append-only, hash-chained decision log. */
44
+ class AuditLog {
45
+ path;
46
+ sinks;
47
+ prev = exports.GENESIS;
48
+ seq = 0;
49
+ _entries = [];
50
+ /**
51
+ * Timestamps are injected by the caller (the `Guard` uses a monotonic
52
+ * counter) so the log stays deterministic in tests and reproducible in
53
+ * replay. In production the runtime supplies a trusted timestamp.
54
+ */
55
+ constructor(init = {}) {
56
+ const opts = typeof init === "string" || init === null ? { path: init } : init;
57
+ this.path = opts.path ?? null;
58
+ this.sinks = opts.sinks ?? [];
59
+ if (this.path) {
60
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(this.path), { recursive: true });
61
+ (0, node_fs_1.writeFileSync)(this.path, ""); // fresh log
62
+ }
63
+ }
64
+ append(event, ts, fields = {}) {
65
+ const payload = {
66
+ v: exports.SCHEMA_VERSION,
67
+ seq: this.seq,
68
+ ts,
69
+ event,
70
+ ...fields,
71
+ prev_hash: this.prev,
72
+ };
73
+ payload["hash"] = hashEntry(this.prev, payload);
74
+ this.prev = payload["hash"];
75
+ this.seq += 1;
76
+ this._entries.push(payload);
77
+ if (this.path) {
78
+ // Python writes the ledger with `json.dumps(payload, sort_keys=True)` —
79
+ // its DEFAULT separators, not the compact pair the hash is taken over.
80
+ (0, node_fs_1.appendFileSync)(this.path, (0, canonical_js_1.pyJsonDumps)(payload) + "\n");
81
+ }
82
+ for (const sink of this.sinks)
83
+ sink.write(payload);
84
+ return payload;
85
+ }
86
+ get entries() {
87
+ return this._entries.slice();
88
+ }
89
+ get length() {
90
+ return this._entries.length;
91
+ }
92
+ [Symbol.iterator]() {
93
+ return this.entries[Symbol.iterator]();
94
+ }
95
+ // ---- verification -----------------------------------------------------
96
+ /** `[seq, hash]` of the last entry — the chain head; `[-1, GENESIS]` if empty. */
97
+ head() {
98
+ if (this._entries.length === 0)
99
+ return [-1, exports.GENESIS];
100
+ const last = this._entries[this._entries.length - 1];
101
+ return [asNumber(last["seq"]), last["hash"]];
102
+ }
103
+ chainIdHint() {
104
+ return chainIdOf(this._entries);
105
+ }
106
+ /**
107
+ * A signed external COMMITMENT to the chain head. Publish it out of band; a
108
+ * later `verifyAnchor` then catches a log that was fully rewritten and
109
+ * re-hashed, which plain `verify` cannot — a consistent rewrite reproduces
110
+ * its own hashes. The signed head hash is the fixed point.
111
+ */
112
+ anchor(signer, ts = 0) {
113
+ const [seq, head] = this.head();
114
+ const body = { v: exports.SCHEMA_VERSION, chain_id: this.chainIdHint(), seq, head, ts };
115
+ return {
116
+ ...body,
117
+ kid: signer.kid ?? null,
118
+ sig: signer.sign((0, canonical_js_1.canonicalBytes)(body)).toString("hex"),
119
+ };
120
+ }
121
+ /** The chain reproduces AND its head matches a SIGNED anchor. */
122
+ static verifyAnchor(entries, anchor, signer) {
123
+ const a = anchor ?? {};
124
+ const body = {};
125
+ for (const k of ["v", "chain_id", "seq", "head", "ts"])
126
+ body[k] = a[k] ?? null;
127
+ const sigHex = (0, canonical_js_1.toPlain)(a["sig"]) ?? "";
128
+ if (typeof sigHex !== "string" || !/^(?:[0-9a-fA-F]{2})*$/.test(sigHex)) {
129
+ return [false, "anchor signature not hex"];
130
+ }
131
+ const sig = Buffer.from(sigHex, "hex");
132
+ const kid = (0, canonical_js_1.toPlain)(a["kid"]);
133
+ if (!signer.verify((0, canonical_js_1.canonicalBytes)(body), sig, kid)) {
134
+ return [false, "anchor signature invalid"];
135
+ }
136
+ const [ok, err] = AuditLog.verify(entries);
137
+ if (!ok)
138
+ return [false, err];
139
+ if (entries.length === 0)
140
+ return [asNumber(a["seq"]) === -1, null];
141
+ const last = entries[entries.length - 1];
142
+ if (last["hash"] !== (0, canonical_js_1.toPlain)(a["head"]) || asNumber(last["seq"]) !== asNumber(a["seq"])) {
143
+ return [false, "anchor head does not match the ledger head (ledger rewritten?)"];
144
+ }
145
+ return [true, null];
146
+ }
147
+ /** Recompute the chain. Returns `[ok, firstBadReason]`. */
148
+ static verify(entries) {
149
+ let prev = exports.GENESIS;
150
+ let expectedSeq = 0;
151
+ for (const e of entries) {
152
+ const seq = asNumber(e["seq"]);
153
+ if (seq !== expectedSeq) {
154
+ return [false, `seq gap at ${expectedSeq} (got ${formatSeq(e["seq"])})`];
155
+ }
156
+ const stored = e["hash"];
157
+ const payload = withoutHash(e);
158
+ if ((0, canonical_js_1.toPlain)(payload["prev_hash"]) !== prev) {
159
+ return [false, `prev_hash mismatch at seq ${expectedSeq}`];
160
+ }
161
+ if (hashEntry(prev, payload) !== stored) {
162
+ return [false, `hash mismatch at seq ${expectedSeq}`];
163
+ }
164
+ prev = stored;
165
+ expectedSeq += 1;
166
+ }
167
+ return [true, null];
168
+ }
169
+ /** Read a `.jsonl` ledger, keeping every number's original literal. */
170
+ static load(path) {
171
+ return AuditLog.parseLines((0, node_fs_1.readFileSync)(path, "utf8"));
172
+ }
173
+ /** Parse `.jsonl` text into entries. Blank lines are skipped. */
174
+ static parseLines(text) {
175
+ return text
176
+ .split(/\r?\n/)
177
+ .filter((line) => line.trim() !== "")
178
+ .map((line) => (0, canonical_js_1.parseJson)(line));
179
+ }
180
+ }
181
+ exports.AuditLog = AuditLog;
182
+ function formatSeq(value) {
183
+ const plain = (0, canonical_js_1.toPlain)(value);
184
+ return plain === undefined || plain === null ? "None" : String(plain);
185
+ }
186
+ /** The first `chain_id` any entry carries, or `"chain"`. */
187
+ function chainIdOf(entries) {
188
+ for (const e of entries) {
189
+ const id = (0, canonical_js_1.toPlain)(e["chain_id"]);
190
+ if (typeof id === "string" && id)
191
+ return id;
192
+ }
193
+ return "chain";
194
+ }
195
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Authority — the core value object.
3
+ *
4
+ * An Authority is an immutable capability: a set of scopes plus typed `Ceiling`
5
+ * bounds and a TTL. The single most important operation is `meet`: the greatest
6
+ * authority that is within BOTH a parent's authority and a requested one. A
7
+ * child of a delegation can never hold more than the meet — attenuation is a
8
+ * lattice operation, enforced in code, not a convention.
9
+ *
10
+ * The guarantee everything else rests on:
11
+ *
12
+ * meet(parent, requested) <= parent for ALL requested.
13
+ *
14
+ * There is no code path by which a derived child authority can exceed its
15
+ * parent. That is what makes model-proposed authority safe to accept: the
16
+ * proposal is only ever an input to `meet`, and `meet` can only shrink.
17
+ *
18
+ * `isNarrowerThan` is exactly the wire protocol's subsumption relation
19
+ * (draft-asor-wimse-agent-delegation-chain-00), so a chain that verifies offline
20
+ * is one the library would have permitted, and the reverse.
21
+ */
22
+ import { type CJson, type Json } from "./canonical.js";
23
+ import { type Ceiling, type Context } from "./ceilings.js";
24
+ import { Decision } from "./reasons.js";
25
+ /**
26
+ * Raised for STRUCTURAL failures — bad input or invalid chain state, such as
27
+ * delegating from a revoked or expired node, or a depth/fanout overflow.
28
+ * Deliberately distinct from a policy denial: a denial is a normal outcome,
29
+ * expressed as a `Decision`. A structural error means the caller did something
30
+ * invalid; a denial means the caller asked for something the authority model
31
+ * legitimately refuses.
32
+ */
33
+ export declare class AuthorityError extends Error {
34
+ readonly reason: string;
35
+ readonly detail: Record<string, Json>;
36
+ constructor(message: string, reason: string, detail?: Record<string, Json>);
37
+ }
38
+ export interface AuthorityInit {
39
+ scopes?: Iterable<string>;
40
+ ceilings?: Iterable<Ceiling>;
41
+ /** Seconds this authority remains valid from issuance; `null` is unbounded. */
42
+ ttl?: number | null;
43
+ }
44
+ /** The wire form of an Authority. */
45
+ export interface AuthorityWire {
46
+ scopes: string[];
47
+ constraints: Record<string, Json>[];
48
+ ttl: number | null;
49
+ [key: string]: CJson;
50
+ }
51
+ export declare class Authority {
52
+ /**
53
+ * Permission strings, e.g. `crm.read`. Scopes support one level of prefix
54
+ * wildcard: `crm.*` covers `crm.read` and `crm.write`. A child requesting
55
+ * `crm.read` under a parent holding `crm.*` is allowed; the reverse is not.
56
+ */
57
+ readonly scopes: ReadonlySet<string>;
58
+ /**
59
+ * At most one ceiling per `key` (last one wins), sorted by key for a
60
+ * deterministic wire form and integrity seal. A dimension with no ceiling is
61
+ * unbounded on that dimension unless a parent in the chain bounds it —
62
+ * attenuation can only add or tighten bounds, never remove one.
63
+ */
64
+ readonly ceilings: readonly Ceiling[];
65
+ /** Seconds from issuance. `null` is unbounded (discouraged). */
66
+ readonly ttl: number | null;
67
+ constructor(init?: AuthorityInit);
68
+ private byKey;
69
+ /** The ceiling bound to `key`, or `undefined`. */
70
+ ceiling(key: string): Ceiling | undefined;
71
+ /** Does a held scope cover a requested scope? Supports one `x.*` wildcard. */
72
+ static scopeCovers(held: string, requested: string): boolean;
73
+ coversScope(requested: string): boolean;
74
+ /**
75
+ * The greatest authority within BOTH `this` and `other` — the attenuation.
76
+ * This is the only way a child authority is constructed. It is commutative
77
+ * and can only ever shrink relative to either input.
78
+ */
79
+ meet(other: Authority): Authority;
80
+ /**
81
+ * `this <= other`: is `this` provably no more powerful than `other` in every
82
+ * dimension? True iff:
83
+ *
84
+ * 1. every scope of `this` is covered by `other` (wildcard-aware);
85
+ * 2. for every ceiling in `other` there is a ceiling of the same key in
86
+ * `this` that `other`'s ceiling subsumes. A ceiling present in `other`
87
+ * and ABSENT here means `this` is unbounded on that dimension, i.e. more
88
+ * powerful, so the relation is false. This holds for any ceiling key,
89
+ * including ones outside the built-in registry, which is what makes the
90
+ * relation sound for custom ceilings too;
91
+ * 3. `this.ttl` is not null and (`other.ttl` is null or `this.ttl <= other.ttl`).
92
+ *
93
+ * This is exactly the wire subsumption relation: the library relation and the
94
+ * token relation are the same relation.
95
+ */
96
+ isNarrowerThan(other: Authority): boolean;
97
+ /**
98
+ * Is `scope` permitted under this authority, given a request context such as
99
+ * `{rows: 5000, egress: "none"}`?
100
+ *
101
+ * Checks scope coverage AND every ceiling this authority holds, collecting
102
+ * every failing reason — not just the first — so a single evaluation can
103
+ * explain everything wrong with a request. A ceiling whose context field is
104
+ * absent is not asserting anything on this call and is treated as satisfied.
105
+ */
106
+ permits(scope: string, ctx?: Context | null): Decision;
107
+ withTtl(ttl: number): Authority;
108
+ toWire(): AuthorityWire;
109
+ static fromWire(wire: CJson): Authority;
110
+ /** A stable, human-readable one-liner. */
111
+ describe(): string;
112
+ toString(): string;
113
+ }
114
+ //# sourceMappingURL=authority.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authority.d.ts","sourceRoot":"","sources":["../../src/authority.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAA6C,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAClG,OAAO,EAAgD,KAAK,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,QAAQ,EAAsB,MAAM,cAAc,CAAC;AAE5D;;;;;;;GAOG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBAE1B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAM;CAM/E;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC7B,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;IACpC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAGnB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;CACtB;AAED,qBAAa,SAAS;IACpB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAErC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IAEtC,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;gBAEhB,IAAI,GAAE,aAAkB;IAUpC,OAAO,CAAC,KAAK;IAMb,kDAAkD;IAClD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAMzC,8EAA8E;IAC9E,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAM5D,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IASvC;;;;OAIG;IACH,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS;IAsCjC;;;;;;;;;;;;;;;OAeG;IACH,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO;IAkBzC;;;;;;;;OAQG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,OAAO,GAAG,IAAW,GAAG,QAAQ;IA0B5D,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;IAM/B,MAAM,IAAI,aAAa;IAQvB,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,GAAG,SAAS;IAYvC,0CAA0C;IAC1C,QAAQ,IAAI,MAAM;IAMlB,QAAQ,IAAI,MAAM;CAGnB"}