@responsibleai/agent-hooks 0.1.0-alpha.5 → 0.1.0-beta.2

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/emitter.d.ts CHANGED
@@ -17,8 +17,15 @@
17
17
  * serial dispatch over isolated snapshots — §7.2: parallel names
18
18
  * isolation semantics, not scheduling.
19
19
  *
20
- * Fail-closed defaults: an `enforce`-mode emission with zero registered
21
- * interceptors yields `deny host_error:no_interceptor` (§7), and
20
+ * An emitter can also be built from a host declaration document (§7.7)
21
+ * through {@link InterceptionEmitter.fromDeclaration} and the path, JSON
22
+ * and value forms. The document is validated and resolved by the Rust
23
+ * core; this module runs the host's kind resolvers (step 11 of §7.7.6),
24
+ * seals the emitter (§7.7.7) and stamps the contract version on every
25
+ * record (§7.7.8).
26
+ *
27
+ * Fail-closed defaults: an `enforce`-mode emission with zero interceptors
28
+ * bound at the point yields `deny host_error:no_interceptor` (§7), and
22
29
  * {@link InterceptionEmitter.emit} **throws** {@link InterceptionBlocked}
23
30
  * on any block — the ignorable-result variant is the explicitly named
24
31
  * {@link InterceptionEmitter.emitUnchecked}.
@@ -27,22 +34,52 @@
27
34
  * on the event loop; sequence assignment and record append are atomic on
28
35
  * a single JS thread. Sharing one emitter across workers is unsupported.
29
36
  */
30
- import { AgentContext, ApprovalResolver, CompositionConfig, EnforcementMode, IdentityProvider, EmitOutcome, InterceptionRecord, Interceptor } from "./index";
37
+ import { AgentContext, ApprovalResolver, CompositionConfig, EnforcementMode, IdentityProvider, EmitOutcome, InterceptionPoint, InterceptionRecord, Interceptor } from "./index";
38
+ import { HostDeclaration, HostRegistry, ResolvedDeclaration } from "./declaration";
31
39
  /** §7 RECOMMENDED interceptor/resolver timeout (milliseconds). */
32
40
  export declare const DEFAULT_TIMEOUT_MS = 5000;
41
+ export declare class EmitterSealed extends Error {
42
+ constructor(what: string);
43
+ }
44
+ /** Envelope facts for {@link InterceptionEmitter.recordHostFailure}:
45
+ * what the host still knows about an emission whose context it could
46
+ * not construct (§10.3 "Host projection failure"). Everything is
47
+ * optional — an absent member records the §10.3 unknown value
48
+ * (`session_id: ""`, `sequence: -1`, `timestamp` absent). */
49
+ export interface HostFailure {
50
+ /** Payload-free failure detail — an exception **type name** or a
51
+ * path, never the content that failed to project (§14 data
52
+ * minimization). Recorded as the verdict `message` (truncated by
53
+ * the §10.3 projection). */
54
+ detail?: string;
55
+ /** `session.id` of the failed emission, when the host knows it. */
56
+ session_id?: string;
57
+ /** The sequence number the failed emission would have carried. The
58
+ * host SHOULD consume the next number from its context source so
59
+ * records stay totally ordered within the session (§10.3). */
60
+ sequence?: number;
61
+ /** RFC 3339 event time, when the host has one. */
62
+ timestamp?: string;
63
+ }
33
64
  export declare class InterceptionEmitter {
34
- private readonly mode;
35
- private readonly resolver;
36
- private readonly timeoutMs;
37
65
  private readonly interceptors;
38
66
  private _records;
39
67
  private composition;
40
68
  private identity;
41
- private readonly names;
42
69
  private approvalRedactor;
43
70
  private recordSink;
44
71
  private maxRecords;
45
72
  private _recordsDropped;
73
+ private readonly mode;
74
+ private resolver;
75
+ /** §7 emitter-wide interceptor bound; `null` disables enforcement. */
76
+ private timeoutMs;
77
+ /** §9 resolver bound (`approval_resolver_ms`, §7.7.3); `null` disables. */
78
+ private resolverTimeoutMs;
79
+ /** §7.7.7: a declaration-built emitter refuses reconfiguration. */
80
+ private sealed;
81
+ /** The resolved declaration this emitter was built from, if any. */
82
+ private _declaration;
46
83
  /**
47
84
  * `timeoutMs` bounds each interceptor `intercept()` and resolver
48
85
  * `resolve()` call (§7, RECOMMENDED default 5000 ms); breach fails
@@ -52,12 +89,43 @@ export declare class InterceptionEmitter {
52
89
  * loop cannot be interrupted. `timeoutMs: null` disables enforcement.
53
90
  */
54
91
  constructor(mode?: EnforcementMode, resolver?: ApprovalResolver | null, timeoutMs?: number | null);
55
- /** Race `fn`'s result against the §7 timeout. */
92
+ /** Build an emitter from a validated declaration: steps 8 to 11 of
93
+ * §7.7.6 (resolve against the registry's surface and names in the
94
+ * core, then run every binding's kind resolver). The result is
95
+ * sealed (§7.7.7) and stamps `declaration` on every record (§7.7.8).
96
+ * Throws {@link DeclarationError} on refusal; no emitter exists then. */
97
+ static fromDeclaration(declaration: HostDeclaration, registry: HostRegistry): InterceptionEmitter;
98
+ /** {@link HostDeclaration.fromPath} then {@link fromDeclaration}. */
99
+ static fromDeclarationPath(path: string, registry: HostRegistry): Promise<InterceptionEmitter>;
100
+ /** {@link HostDeclaration.fromJson} then {@link fromDeclaration}. */
101
+ static fromDeclarationJson(text: string, registry: HostRegistry): InterceptionEmitter;
102
+ /** {@link HostDeclaration.fromValue} then {@link fromDeclaration}. */
103
+ static fromDeclarationValue(value: unknown, registry: HostRegistry): InterceptionEmitter;
104
+ /** The resolved declaration this emitter runs under, when it was
105
+ * built from one (§7.7.7). Its canonical JSON is the equivalence
106
+ * oracle for the construction paths. The value is frozen all the
107
+ * way down: writing through it has no effect on dispatch or records.
108
+ * `null` for an emitter configured in code. */
109
+ get declaration(): ResolvedDeclaration | null;
110
+ /** Step 11 and construction. Every reference is looked up again in
111
+ * the registry's own maps, so bookkeeping drift between the names the
112
+ * core checked and what the registry holds fails closed. */
113
+ private static fromResolved;
114
+ /** §7.7.7: a declaration-built emitter refuses reconfiguration;
115
+ * otherwise the declaration would not be what ran. */
116
+ private unsealed;
117
+ /** Race `fn`'s result against a timeout (§7). `limitMs: null` runs
118
+ * unbounded. */
56
119
  private withTimeout;
57
120
  get records(): readonly InterceptionRecord[];
58
121
  /** Register an interceptor, optionally with a host-chosen
59
- * payload-free `name` recorded on `verdicts[].name` (§10.3). */
60
- register(interceptor: Interceptor, name?: string): this;
122
+ * payload-free `name` recorded on `verdicts[].name` (§10.3) and the
123
+ * points it runs at (`at`; omitted = every point). At point P the
124
+ * interceptors that run are those bound there, in registration
125
+ * order; `interceptors_registered`, `verdicts[].index` and
126
+ * `decided_by` count and index that list (§7.7.8). Throws
127
+ * {@link EmitterSealed} on a declaration-built emitter. */
128
+ register(interceptor: Interceptor, name?: string, at?: Iterable<InterceptionPoint>): this;
61
129
  /**
62
130
  * Declare the composition profile for subsequent emissions (§7.1).
63
131
  * The profile and knob values are validated against the closed §7.2
@@ -88,7 +156,8 @@ export declare class InterceptionEmitter {
88
156
  /** Register a per-emission record callback (§10.3), invoked
89
157
  * synchronously after every emission before buffering; a sink
90
158
  * exception is swallowed (audit delivery is the host's liveness
91
- * concern, not the control plane's). */
159
+ * concern, not the control plane's). Allowed on a sealed emitter: it
160
+ * changes where records go, not what they say (§7.7.7). */
92
161
  setRecordSink(sink: (record: InterceptionRecord) => void): this;
93
162
  /** Bound the in-memory record buffer: when full, the OLDEST record
94
163
  * is dropped and {@link recordsDropped} increments. Unbounded by
@@ -110,6 +179,33 @@ export declare class InterceptionEmitter {
110
179
  * caller MUST inspect `proceeds` and halt the guarded action itself;
111
180
  * prefer {@link emit}. */
112
181
  emitUnchecked(ctx: AgentContext): Promise<InterceptionRecord>;
182
+ /** §10.3/§11 host projection failure: synthesize and deliver the
183
+ * fail-closed record for an emission whose `AgentContext` the host
184
+ * could not construct at all — its own projection to the wire failed
185
+ * before anything existed to {@link emit} (e.g. a tool-call argument
186
+ * getter threw during to-wire conversion at the chat seam). Without
187
+ * this the host can only fail the action closed *recordless*; with
188
+ * it the trail stays complete under host-side faults.
189
+ *
190
+ * The record is the §10.3 rejection shape: the payload-free
191
+ * projection of a `deny host_error:context_invalid` carrying
192
+ * `failure.detail` (payload-free: a type name or path, never
193
+ * content) as its message; `null` identities under the declared
194
+ * provider; `decided_by: null`; no per-interceptor summaries (no
195
+ * interceptor ran); envelope members from {@link HostFailure}, with
196
+ * the §10.3 unknown values (`""`/`-1`) where absent. It takes the
197
+ * next slot in the record stream (sink, then buffer) like any
198
+ * emission. In `enforce` mode the host MUST still fail the action
199
+ * closed; in `evaluate_only` the record documents the host fault
200
+ * without implying enforcement (§8). */
201
+ recordHostFailure(point: InterceptionPoint, failure?: HostFailure): InterceptionRecord;
202
+ /** Deliver a record to the sink and the bounded buffer (§10.3). */
203
+ private deliver;
204
+ /** The interceptors bound at the context's point, in dispatch order
205
+ * (§7.7.8). */
206
+ private active;
207
+ /** The bound a registration runs under: its own, else the emitter's. */
208
+ private limit;
113
209
  /** Profile dispatch (§7.4–§7.5). Returns the combined verdict and
114
210
  * its record metadata. */
115
211
  private dispatch;
@@ -125,7 +221,7 @@ export declare class InterceptionEmitter {
125
221
  * short-circuits; a liftable deny consults the seam, then `stop` or
126
222
  * `resume` per the knob.
127
223
  *
128
- * `perInterceptor` stays index-aligned with registration order (one
224
+ * `perInterceptor` stays index-aligned with dispatch order (one
129
225
  * entry per invoked interceptor, §10.3 summaries); `pool`
130
226
  * additionally holds substituted resolutions for the §7.3 unions. */
131
227
  private dispatchFirstDeny;
@@ -147,12 +243,12 @@ export declare class InterceptionEmitter {
147
243
  private aggregateAndConsult;
148
244
  /** Apply (enforce) or validate (evaluate_only) one transform (§7.4, §8). */
149
245
  private foldTransform;
150
- /** The declared provider's output for `ctx` (§10.1); `null` iff the
151
- * provider is `null`. Throws {@link AgentHooksCoreError} iff the
152
- * provider rejected the context (§10.2 fail-closed value domain). */
153
246
  /** Custom-provider identity, or null when the provider fails (the
154
247
  * emission has already been decided at this point). */
155
248
  private tryCustomIdentity;
249
+ /** The declared provider's output for `ctx` (§10.1); `null` iff the
250
+ * provider is `null`. Throws {@link AgentHooksCoreError} iff the
251
+ * provider rejected the context (§10.2 fail-closed value domain). */
156
252
  private identityOf;
157
253
  /** Consult the approval seam for a liftable deny (§9), when the
158
254
  * profile conditions allow it: `enforce` mode, not `agent_shutdown`,