@pushary/server 1.4.0 → 1.5.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.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { H as AskDecision, I as AskResult, K as BoundKeyResult, M as BoundKeySummary, C as Campaign, g as CampaignStats, O as CampaignsResource, B as CancelDecisionResult, e as CreateCampaign, w as CreateDecision, l as CreateFlow, h as CreateTemplate, z as Decision, A as DecisionAnswerResult, E as DecisionCallback, x as DecisionReachability, y as DecisionResult, v as DecisionStatus, D as DecisionType, Y as DecisionsResource, G as EnrollResult, F as Flow, n as FlowListParams, q as FlowStatus, r as FlowStep, s as FlowStepConfig, t as FlowStepInput, p as FlowStepType, o as FlowTriggerType, X as FlowsResource, J as IssueBoundKey, Z as KeysResource, L as ListParams, V as NotificationsResource, u as PaginatedResponse, a as PusharyConfig, P as PusharyServer, R as RevokeKeyResult, a0 as SIGNATURE_HEADER, j as SendNotification, k as SendResult, S as Subscriber, d as SubscriberCount, b as SubscriberListParams, N as SubscribersResource, T as Template, Q as TemplatesResource, f as UpdateCampaign, m as UpdateFlow, U as UpdateSubscriber, i as UpdateTemplate, W as WebhookSecret, c as createPusharyServer, a1 as deterministicKey, a2 as isApproved, $ as parseDecisionCallback, _ as verifyWebhookSignature } from './util-Ihfh4f82.js';
1
+ export { A as ActionPresentation, a6 as AskDecision, a7 as AskResult, O as AuthorizationAuthority, N as AuthorizationBinding, M as AuthorizationEvaluation, I as AuthorizationQuery, H as AuthorizationVerdict, J as AuthorizeRequest, K as AuthorizeResult, a9 as BoundKeyResult, aa as BoundKeySummary, C as Campaign, g as CampaignStats, ad as CampaignsResource, a2 as CancelDecisionResult, R as ConsumeAuthorizationResult, Q as ConsumedAuthorization, e as CreateCampaign, Z as CreateDecision, l as CreateFlow, h as CreateTemplate, a0 as Decision, a1 as DecisionAnswerResult, a3 as DecisionCallback, x as DecisionDeliverability, z as DecisionParameters, y as DecisionReach, _ as DecisionReachability, $ as DecisionResult, v as DecisionStatus, w as DecisionSubject, D as DecisionType, ah as DecisionsResource, a5 as EnrollResult, V as ExecutionOutcome, W as ExecutionReceipt, F as Flow, n as FlowListParams, q as FlowStatus, r as FlowStep, s as FlowStepConfig, t as FlowStepInput, p as FlowStepType, o as FlowTriggerType, ag as FlowsResource, a8 as IssueBoundKey, ai as KeysResource, L as ListParams, af as NotificationsResource, u as PaginatedResponse, Y as PermitExecutionState, X as PermitRefusal, B as PresentedChange, E as PresentedFormat, a as PusharyConfig, P as PusharyServer, ab as RevokeKeyResult, al as SIGNATURE_HEADER, G as SandboxOutcome, j as SendNotification, k as SendResult, S as Subscriber, d as SubscriberCount, b as SubscriberListParams, ac as SubscribersResource, T as Template, ae as TemplatesResource, f as UpdateCampaign, m as UpdateFlow, U as UpdateSubscriber, i as UpdateTemplate, a4 as WebhookSecret, c as createPusharyServer, am as deterministicKey, an as isApproved, ak as parseDecisionCallback, aj as verifyWebhookSignature } from './util-DeGBh5at.js';
2
2
 
3
3
  /**
4
4
  * Thrown for any non-2xx response from the API.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  isApproved,
7
7
  parseDecisionCallback,
8
8
  verifyWebhookSignature
9
- } from "./chunk-22FUZ6F5.js";
9
+ } from "./chunk-E6DWMKLZ.js";
10
10
  export {
11
11
  PusharyApiError,
12
12
  SIGNATURE_HEADER,
@@ -206,7 +206,128 @@ interface FlowListParams extends ListParams {
206
206
  }
207
207
  type DecisionType = 'confirm' | 'select' | 'input';
208
208
  type DecisionStatus = 'pending' | 'answered' | 'expired' | 'cancelled';
209
- interface CreateDecision {
209
+ /**
210
+ * Typed arguments of an action. Scalars only: a rule reads these, and a nested
211
+ * object has no comparable value to read.
212
+ */
213
+ type DecisionParameters = Readonly<Record<string, string | number | boolean>>;
214
+ /**
215
+ * How to render one parameter to the person deciding.
216
+ *
217
+ * Declared here rather than imported, for the same reason AuthorizationVerdict
218
+ * is: this package is MIT and must not depend on Pushary's private core, so the
219
+ * wire values are the contract between them.
220
+ *
221
+ * A currency parameter holds MINOR units — 4800 is EUR 48.00. A float cannot
222
+ * hold money exactly and a pre-formatted string cannot be compared by a rule.
223
+ */
224
+ type PresentedFormat = {
225
+ readonly kind: 'text';
226
+ } | {
227
+ readonly kind: 'currency';
228
+ readonly currency: string;
229
+ } | {
230
+ readonly kind: 'quantity';
231
+ readonly unit?: string;
232
+ } | {
233
+ readonly kind: 'timestamp';
234
+ } | {
235
+ readonly kind: 'boolean';
236
+ };
237
+ interface PresentedChange {
238
+ /**
239
+ * A key of `parameters`. The value is read from there and never carried here,
240
+ * so what the person sees and what a rule reads cannot be different numbers.
241
+ */
242
+ readonly parameter: string;
243
+ /** What to call it to a person: "Refund amount", not "amount_minor". */
244
+ readonly label: string;
245
+ readonly format: PresentedFormat;
246
+ }
247
+ /**
248
+ * The action in business language, for the surfaces a person actually reads.
249
+ *
250
+ * Optional. A caller that sends none keeps the free-text `question` it has
251
+ * always sent; only a caller that claims a presentation is held to it, and an
252
+ * invalid one is refused rather than half-rendered.
253
+ */
254
+ interface ActionPresentation {
255
+ /** The action as a person would name it. "Refund order #4471". */
256
+ readonly label: string;
257
+ /** What actually happens. "Returns EUR 48.00 to the customer's card." */
258
+ readonly effect: string;
259
+ readonly changes?: readonly PresentedChange[];
260
+ /** Optional. Why this one is worth stopping for. */
261
+ readonly risk?: string;
262
+ }
263
+ interface DecisionSubject {
264
+ readonly toolName?: string;
265
+ readonly toolTarget?: string;
266
+ /**
267
+ * Who the action runs on behalf of, e.g. `user:u_44` or `team:billing`. This is
268
+ * the principal, which is not necessarily the person who approves: `externalId`
269
+ * names the recipient of the question, `actor` names whose authority the action
270
+ * claims. Max 120 characters.
271
+ */
272
+ readonly actor?: string;
273
+ /** Which deployment the action runs against, e.g. `production`. Max 40 characters. */
274
+ readonly environment?: string;
275
+ /**
276
+ * Typed arguments of the action, e.g. `{ amount: 4800, currency: 'EUR' }`.
277
+ * Scalars only, at most 32 keys. Recorded on the audit row so an approval can be
278
+ * read back against what it approved.
279
+ *
280
+ * Rejected as a whole (400) if any value is not a string, a finite number or a
281
+ * boolean, rather than being partly dropped: a rule that reads a missing
282
+ * parameter can only ever decide more permissively than one that reads it.
283
+ */
284
+ readonly parameters?: DecisionParameters;
285
+ /**
286
+ * What the person deciding sees, in business language. Its `changes` name
287
+ * parameters rather than carrying values, so display and policy read the same
288
+ * numbers. Refused (400) if it names a parameter you did not send, names one
289
+ * whose name marks it secret, or gives a value a format cannot render.
290
+ */
291
+ readonly presentation?: ActionPresentation;
292
+ /**
293
+ * Return a short-lived `approvalUrl` you can deliver yourself, over the email,
294
+ * SMS or in-app inbox you already have for this end-user.
295
+ *
296
+ * Off by default: a capability URL returned on every create is a capability URL
297
+ * in every log, for the majority of decisions that never needed one. The link
298
+ * is bound to this decision, this site and this recipient, and expires.
299
+ */
300
+ readonly approvalUrl?: boolean;
301
+ }
302
+ /** Whether a decision can reach this person, and by whose hands. */
303
+ type DecisionDeliverability = 'push' | 'fallback' | 'unreachable';
304
+ interface DecisionReach {
305
+ readonly externalId: string;
306
+ /**
307
+ * `push` — Pushary delivers it. `fallback` — nobody is enrolled, but you know
308
+ * this end-user: open the decision with `approvalUrl: true` and deliver the
309
+ * link yourself. `unreachable` — no end-user was named, so there is nobody to
310
+ * reach and no link to mint.
311
+ */
312
+ readonly deliverability: DecisionDeliverability;
313
+ readonly reachable: boolean;
314
+ readonly reachableChannels: number;
315
+ readonly deviceCount: number;
316
+ readonly webEndpoints: number;
317
+ readonly hint: string;
318
+ }
319
+ /**
320
+ * What the sandbox's simulated approver does.
321
+ *
322
+ * `approve` is the default. `no_answer` leaves the decision open, which is how
323
+ * you exercise your own fail-closed path without waiting on a real phone.
324
+ *
325
+ * Read only by a key issued on your workspace's sandbox site, where nothing is
326
+ * delivered and nobody is asked. Ignored in production, where the approver is a
327
+ * person.
328
+ */
329
+ type SandboxOutcome = 'approve' | 'deny' | 'no_answer';
330
+ interface CreateDecision extends DecisionSubject {
210
331
  readonly question: string;
211
332
  readonly type?: DecisionType;
212
333
  readonly options?: readonly string[];
@@ -222,6 +343,108 @@ interface CreateDecision {
222
343
  readonly idempotencyKey?: string;
223
344
  readonly poweredBy?: boolean;
224
345
  readonly requireReachable?: boolean;
346
+ readonly sandboxOutcome?: SandboxOutcome;
347
+ }
348
+ type AuthorizationVerdict = 'allow' | 'deny' | 'requires_human';
349
+ interface AuthorizationQuery extends DecisionSubject {
350
+ /** The action being authorized. Required: a rule has to name it. */
351
+ readonly toolName: string;
352
+ /** Your own id for the end-user this action belongs to. */
353
+ readonly externalId?: string;
354
+ /**
355
+ * Shown to the person, if policy defers to one. Defaults to the action and its
356
+ * target, so a caller that only names the action still asks a readable question.
357
+ */
358
+ readonly question?: string;
359
+ readonly agentName?: string;
360
+ }
361
+ interface AuthorizeRequest extends AuthorizationQuery {
362
+ /** Forwarded to the ask, and used only when policy defers to a person. */
363
+ readonly timeoutMs?: number;
364
+ readonly expiresInSeconds?: number;
365
+ readonly idempotencyKey?: string;
366
+ readonly requireReachable?: boolean;
367
+ }
368
+ interface AuthorizeResult {
369
+ /** Fail-closed. False for a denial, and false when nobody answered. */
370
+ readonly approved: boolean;
371
+ /** What settled it: a rule, or a person. */
372
+ readonly resolvedBy: 'policy' | 'human';
373
+ /** The rule that decided, or null when no rule named the action. */
374
+ readonly policy: string | null;
375
+ readonly reason: string;
376
+ /** The decision opened when policy deferred to a person, else null. */
377
+ readonly decisionId: string | null;
378
+ /** The audit row a policy-settled authorization produced, else null. */
379
+ readonly authorizationId: string | null;
380
+ }
381
+ interface AuthorizationEvaluation {
382
+ readonly verdict: AuthorizationVerdict;
383
+ readonly policy: string | null;
384
+ readonly reason: string;
385
+ readonly authorizationId: string | null;
386
+ }
387
+ /**
388
+ * The exact subject an authorization was recorded against.
389
+ *
390
+ * Carried from the gate to the consume rather than rebuilt, because the two have
391
+ * to name the same action and rebuilding it is a second chance for them not to.
392
+ * The server digests this and what it recorded, and refuses unless they match.
393
+ */
394
+ interface AuthorizationBinding {
395
+ /** The settled authorization: a policy verdict's id, or an approved decision's id. */
396
+ readonly authorizationId: string;
397
+ readonly toolName: string;
398
+ readonly toolTarget?: string;
399
+ readonly actor?: string;
400
+ readonly environment?: string;
401
+ readonly externalId?: string;
402
+ readonly parameters?: DecisionParameters;
403
+ }
404
+ /**
405
+ * Who allowed it, frozen when it was allowed.
406
+ *
407
+ * A rule can be edited and a member can lose their seat. An authorization that
408
+ * can only say "something allowed this" is not an audit trail, so the deciding
409
+ * authority is copied onto the permit and never updated.
410
+ */
411
+ type AuthorizationAuthority = {
412
+ readonly kind: 'policy';
413
+ readonly rulePattern?: string;
414
+ readonly authorizationRuleId?: string;
415
+ readonly policyId?: string;
416
+ } | {
417
+ readonly kind: 'human';
418
+ readonly answeredAt: string;
419
+ readonly answerSource?: string;
420
+ readonly approverUserId?: string;
421
+ readonly approverExternalId?: string;
422
+ };
423
+ /** Why a consume was refused. Every branch is a refusal; none of them means allowed. */
424
+ type PermitRefusal = 'not_authorized' | 'action_mismatch' | 'expired' | 'already_consumed';
425
+ type PermitExecutionState = 'running' | 'succeeded' | 'failed';
426
+ interface ConsumedAuthorization {
427
+ readonly permitId: string;
428
+ readonly authority: AuthorizationAuthority;
429
+ /** ISO-8601. The permit is spent already; this is when it would have stopped being spendable. */
430
+ readonly expiresAt: string;
431
+ }
432
+ type ConsumeAuthorizationResult = {
433
+ readonly consumed: true;
434
+ readonly permit: ConsumedAuthorization;
435
+ } | {
436
+ readonly consumed: false;
437
+ readonly refusal: PermitRefusal;
438
+ readonly reason: string;
439
+ /** On `already_consumed`, what the winning execution did. */
440
+ readonly executionState?: PermitExecutionState;
441
+ };
442
+ type ExecutionOutcome = 'succeeded' | 'failed';
443
+ interface ExecutionReceipt {
444
+ readonly permitId: string;
445
+ readonly outcome: ExecutionOutcome;
446
+ /** Bounded and redacted server-side. What the action did, or why it did not. */
447
+ readonly summary?: string;
225
448
  }
226
449
  interface DecisionReachability {
227
450
  readonly reachable?: boolean;
@@ -283,7 +506,7 @@ interface EnrollResult {
283
506
  readonly universalLink: string;
284
507
  readonly expiresInSeconds: number;
285
508
  }
286
- interface AskDecision {
509
+ interface AskDecision extends DecisionSubject {
287
510
  readonly question: string;
288
511
  readonly type?: DecisionType;
289
512
  readonly options?: readonly string[];
@@ -297,6 +520,9 @@ interface AskDecision {
297
520
  readonly poweredBy?: boolean;
298
521
  readonly callbackUrl?: string;
299
522
  readonly requireReachable?: boolean;
523
+ readonly sandboxOutcome?: SandboxOutcome;
524
+ readonly presentation?: ActionPresentation;
525
+ readonly approvalUrl?: boolean;
300
526
  readonly timeoutMs?: number;
301
527
  }
302
528
  interface AskResult extends DecisionReachability {
@@ -401,7 +627,14 @@ interface PusharyServer {
401
627
  readonly flows: FlowsResource;
402
628
  readonly decisions: DecisionsResource;
403
629
  readonly keys: KeysResource;
630
+ readonly authorize: (input: AuthorizeRequest) => Promise<AuthorizeResult>;
631
+ readonly evaluateAuthorization: (input: AuthorizationQuery) => Promise<AuthorizationEvaluation>;
632
+ readonly consumeAuthorization: (binding: AuthorizationBinding) => Promise<ConsumeAuthorizationResult>;
633
+ readonly recordExecution: (receipt: ExecutionReceipt) => Promise<{
634
+ readonly executionState: PermitExecutionState;
635
+ }>;
404
636
  readonly enroll: (externalId: string) => Promise<EnrollResult>;
637
+ readonly reachability: (externalId: string) => Promise<DecisionReach>;
405
638
  }
406
639
  declare const createPusharyServer: (config: PusharyConfig) => PusharyServer;
407
640
 
@@ -416,4 +649,4 @@ declare const isApproved: (decision: {
416
649
  readonly value: string | null;
417
650
  }) => boolean;
418
651
 
419
- export { parseDecisionCallback as $, type DecisionAnswerResult as A, type CancelDecisionResult as B, type Campaign as C, type DecisionType as D, type DecisionCallback as E, type Flow as F, type EnrollResult as G, type AskDecision as H, type AskResult as I, type IssueBoundKey as J, type BoundKeyResult as K, type ListParams as L, type BoundKeySummary as M, type SubscribersResource as N, type CampaignsResource as O, type PusharyServer as P, type TemplatesResource as Q, type RevokeKeyResult as R, type Subscriber as S, type Template as T, type UpdateSubscriber as U, type NotificationsResource as V, type WebhookSecret as W, type FlowsResource as X, type DecisionsResource as Y, type KeysResource as Z, verifyWebhookSignature as _, type PusharyConfig as a, SIGNATURE_HEADER as a0, deterministicKey as a1, isApproved as a2, type SubscriberListParams as b, createPusharyServer as c, type SubscriberCount as d, type CreateCampaign as e, type UpdateCampaign as f, type CampaignStats as g, type CreateTemplate as h, type UpdateTemplate as i, type SendNotification as j, type SendResult as k, type CreateFlow as l, type UpdateFlow as m, type FlowListParams as n, type FlowTriggerType as o, type FlowStepType as p, type FlowStatus as q, type FlowStep as r, type FlowStepConfig as s, type FlowStepInput as t, type PaginatedResponse as u, type DecisionStatus as v, type CreateDecision as w, type DecisionReachability as x, type DecisionResult as y, type Decision as z };
652
+ export { type DecisionResult as $, type ActionPresentation as A, type PresentedChange as B, type Campaign as C, type DecisionType as D, type PresentedFormat as E, type Flow as F, type SandboxOutcome as G, type AuthorizationVerdict as H, type AuthorizationQuery as I, type AuthorizeRequest as J, type AuthorizeResult as K, type ListParams as L, type AuthorizationEvaluation as M, type AuthorizationBinding as N, type AuthorizationAuthority as O, type PusharyServer as P, type ConsumedAuthorization as Q, type ConsumeAuthorizationResult as R, type Subscriber as S, type Template as T, type UpdateSubscriber as U, type ExecutionOutcome as V, type ExecutionReceipt as W, type PermitRefusal as X, type PermitExecutionState as Y, type CreateDecision as Z, type DecisionReachability as _, type PusharyConfig as a, type Decision as a0, type DecisionAnswerResult as a1, type CancelDecisionResult as a2, type DecisionCallback as a3, type WebhookSecret as a4, type EnrollResult as a5, type AskDecision as a6, type AskResult as a7, type IssueBoundKey as a8, type BoundKeyResult as a9, type BoundKeySummary as aa, type RevokeKeyResult as ab, type SubscribersResource as ac, type CampaignsResource as ad, type TemplatesResource as ae, type NotificationsResource as af, type FlowsResource as ag, type DecisionsResource as ah, type KeysResource as ai, verifyWebhookSignature as aj, parseDecisionCallback as ak, SIGNATURE_HEADER as al, deterministicKey as am, isApproved as an, type SubscriberListParams as b, createPusharyServer as c, type SubscriberCount as d, type CreateCampaign as e, type UpdateCampaign as f, type CampaignStats as g, type CreateTemplate as h, type UpdateTemplate as i, type SendNotification as j, type SendResult as k, type CreateFlow as l, type UpdateFlow as m, type FlowListParams as n, type FlowTriggerType as o, type FlowStepType as p, type FlowStatus as q, type FlowStep as r, type FlowStepConfig as s, type FlowStepInput as t, type PaginatedResponse as u, type DecisionStatus as v, type DecisionSubject as w, type DecisionDeliverability as x, type DecisionReach as y, type DecisionParameters as z };
@@ -206,7 +206,128 @@ interface FlowListParams extends ListParams {
206
206
  }
207
207
  type DecisionType = 'confirm' | 'select' | 'input';
208
208
  type DecisionStatus = 'pending' | 'answered' | 'expired' | 'cancelled';
209
- interface CreateDecision {
209
+ /**
210
+ * Typed arguments of an action. Scalars only: a rule reads these, and a nested
211
+ * object has no comparable value to read.
212
+ */
213
+ type DecisionParameters = Readonly<Record<string, string | number | boolean>>;
214
+ /**
215
+ * How to render one parameter to the person deciding.
216
+ *
217
+ * Declared here rather than imported, for the same reason AuthorizationVerdict
218
+ * is: this package is MIT and must not depend on Pushary's private core, so the
219
+ * wire values are the contract between them.
220
+ *
221
+ * A currency parameter holds MINOR units — 4800 is EUR 48.00. A float cannot
222
+ * hold money exactly and a pre-formatted string cannot be compared by a rule.
223
+ */
224
+ type PresentedFormat = {
225
+ readonly kind: 'text';
226
+ } | {
227
+ readonly kind: 'currency';
228
+ readonly currency: string;
229
+ } | {
230
+ readonly kind: 'quantity';
231
+ readonly unit?: string;
232
+ } | {
233
+ readonly kind: 'timestamp';
234
+ } | {
235
+ readonly kind: 'boolean';
236
+ };
237
+ interface PresentedChange {
238
+ /**
239
+ * A key of `parameters`. The value is read from there and never carried here,
240
+ * so what the person sees and what a rule reads cannot be different numbers.
241
+ */
242
+ readonly parameter: string;
243
+ /** What to call it to a person: "Refund amount", not "amount_minor". */
244
+ readonly label: string;
245
+ readonly format: PresentedFormat;
246
+ }
247
+ /**
248
+ * The action in business language, for the surfaces a person actually reads.
249
+ *
250
+ * Optional. A caller that sends none keeps the free-text `question` it has
251
+ * always sent; only a caller that claims a presentation is held to it, and an
252
+ * invalid one is refused rather than half-rendered.
253
+ */
254
+ interface ActionPresentation {
255
+ /** The action as a person would name it. "Refund order #4471". */
256
+ readonly label: string;
257
+ /** What actually happens. "Returns EUR 48.00 to the customer's card." */
258
+ readonly effect: string;
259
+ readonly changes?: readonly PresentedChange[];
260
+ /** Optional. Why this one is worth stopping for. */
261
+ readonly risk?: string;
262
+ }
263
+ interface DecisionSubject {
264
+ readonly toolName?: string;
265
+ readonly toolTarget?: string;
266
+ /**
267
+ * Who the action runs on behalf of, e.g. `user:u_44` or `team:billing`. This is
268
+ * the principal, which is not necessarily the person who approves: `externalId`
269
+ * names the recipient of the question, `actor` names whose authority the action
270
+ * claims. Max 120 characters.
271
+ */
272
+ readonly actor?: string;
273
+ /** Which deployment the action runs against, e.g. `production`. Max 40 characters. */
274
+ readonly environment?: string;
275
+ /**
276
+ * Typed arguments of the action, e.g. `{ amount: 4800, currency: 'EUR' }`.
277
+ * Scalars only, at most 32 keys. Recorded on the audit row so an approval can be
278
+ * read back against what it approved.
279
+ *
280
+ * Rejected as a whole (400) if any value is not a string, a finite number or a
281
+ * boolean, rather than being partly dropped: a rule that reads a missing
282
+ * parameter can only ever decide more permissively than one that reads it.
283
+ */
284
+ readonly parameters?: DecisionParameters;
285
+ /**
286
+ * What the person deciding sees, in business language. Its `changes` name
287
+ * parameters rather than carrying values, so display and policy read the same
288
+ * numbers. Refused (400) if it names a parameter you did not send, names one
289
+ * whose name marks it secret, or gives a value a format cannot render.
290
+ */
291
+ readonly presentation?: ActionPresentation;
292
+ /**
293
+ * Return a short-lived `approvalUrl` you can deliver yourself, over the email,
294
+ * SMS or in-app inbox you already have for this end-user.
295
+ *
296
+ * Off by default: a capability URL returned on every create is a capability URL
297
+ * in every log, for the majority of decisions that never needed one. The link
298
+ * is bound to this decision, this site and this recipient, and expires.
299
+ */
300
+ readonly approvalUrl?: boolean;
301
+ }
302
+ /** Whether a decision can reach this person, and by whose hands. */
303
+ type DecisionDeliverability = 'push' | 'fallback' | 'unreachable';
304
+ interface DecisionReach {
305
+ readonly externalId: string;
306
+ /**
307
+ * `push` — Pushary delivers it. `fallback` — nobody is enrolled, but you know
308
+ * this end-user: open the decision with `approvalUrl: true` and deliver the
309
+ * link yourself. `unreachable` — no end-user was named, so there is nobody to
310
+ * reach and no link to mint.
311
+ */
312
+ readonly deliverability: DecisionDeliverability;
313
+ readonly reachable: boolean;
314
+ readonly reachableChannels: number;
315
+ readonly deviceCount: number;
316
+ readonly webEndpoints: number;
317
+ readonly hint: string;
318
+ }
319
+ /**
320
+ * What the sandbox's simulated approver does.
321
+ *
322
+ * `approve` is the default. `no_answer` leaves the decision open, which is how
323
+ * you exercise your own fail-closed path without waiting on a real phone.
324
+ *
325
+ * Read only by a key issued on your workspace's sandbox site, where nothing is
326
+ * delivered and nobody is asked. Ignored in production, where the approver is a
327
+ * person.
328
+ */
329
+ type SandboxOutcome = 'approve' | 'deny' | 'no_answer';
330
+ interface CreateDecision extends DecisionSubject {
210
331
  readonly question: string;
211
332
  readonly type?: DecisionType;
212
333
  readonly options?: readonly string[];
@@ -222,6 +343,108 @@ interface CreateDecision {
222
343
  readonly idempotencyKey?: string;
223
344
  readonly poweredBy?: boolean;
224
345
  readonly requireReachable?: boolean;
346
+ readonly sandboxOutcome?: SandboxOutcome;
347
+ }
348
+ type AuthorizationVerdict = 'allow' | 'deny' | 'requires_human';
349
+ interface AuthorizationQuery extends DecisionSubject {
350
+ /** The action being authorized. Required: a rule has to name it. */
351
+ readonly toolName: string;
352
+ /** Your own id for the end-user this action belongs to. */
353
+ readonly externalId?: string;
354
+ /**
355
+ * Shown to the person, if policy defers to one. Defaults to the action and its
356
+ * target, so a caller that only names the action still asks a readable question.
357
+ */
358
+ readonly question?: string;
359
+ readonly agentName?: string;
360
+ }
361
+ interface AuthorizeRequest extends AuthorizationQuery {
362
+ /** Forwarded to the ask, and used only when policy defers to a person. */
363
+ readonly timeoutMs?: number;
364
+ readonly expiresInSeconds?: number;
365
+ readonly idempotencyKey?: string;
366
+ readonly requireReachable?: boolean;
367
+ }
368
+ interface AuthorizeResult {
369
+ /** Fail-closed. False for a denial, and false when nobody answered. */
370
+ readonly approved: boolean;
371
+ /** What settled it: a rule, or a person. */
372
+ readonly resolvedBy: 'policy' | 'human';
373
+ /** The rule that decided, or null when no rule named the action. */
374
+ readonly policy: string | null;
375
+ readonly reason: string;
376
+ /** The decision opened when policy deferred to a person, else null. */
377
+ readonly decisionId: string | null;
378
+ /** The audit row a policy-settled authorization produced, else null. */
379
+ readonly authorizationId: string | null;
380
+ }
381
+ interface AuthorizationEvaluation {
382
+ readonly verdict: AuthorizationVerdict;
383
+ readonly policy: string | null;
384
+ readonly reason: string;
385
+ readonly authorizationId: string | null;
386
+ }
387
+ /**
388
+ * The exact subject an authorization was recorded against.
389
+ *
390
+ * Carried from the gate to the consume rather than rebuilt, because the two have
391
+ * to name the same action and rebuilding it is a second chance for them not to.
392
+ * The server digests this and what it recorded, and refuses unless they match.
393
+ */
394
+ interface AuthorizationBinding {
395
+ /** The settled authorization: a policy verdict's id, or an approved decision's id. */
396
+ readonly authorizationId: string;
397
+ readonly toolName: string;
398
+ readonly toolTarget?: string;
399
+ readonly actor?: string;
400
+ readonly environment?: string;
401
+ readonly externalId?: string;
402
+ readonly parameters?: DecisionParameters;
403
+ }
404
+ /**
405
+ * Who allowed it, frozen when it was allowed.
406
+ *
407
+ * A rule can be edited and a member can lose their seat. An authorization that
408
+ * can only say "something allowed this" is not an audit trail, so the deciding
409
+ * authority is copied onto the permit and never updated.
410
+ */
411
+ type AuthorizationAuthority = {
412
+ readonly kind: 'policy';
413
+ readonly rulePattern?: string;
414
+ readonly authorizationRuleId?: string;
415
+ readonly policyId?: string;
416
+ } | {
417
+ readonly kind: 'human';
418
+ readonly answeredAt: string;
419
+ readonly answerSource?: string;
420
+ readonly approverUserId?: string;
421
+ readonly approverExternalId?: string;
422
+ };
423
+ /** Why a consume was refused. Every branch is a refusal; none of them means allowed. */
424
+ type PermitRefusal = 'not_authorized' | 'action_mismatch' | 'expired' | 'already_consumed';
425
+ type PermitExecutionState = 'running' | 'succeeded' | 'failed';
426
+ interface ConsumedAuthorization {
427
+ readonly permitId: string;
428
+ readonly authority: AuthorizationAuthority;
429
+ /** ISO-8601. The permit is spent already; this is when it would have stopped being spendable. */
430
+ readonly expiresAt: string;
431
+ }
432
+ type ConsumeAuthorizationResult = {
433
+ readonly consumed: true;
434
+ readonly permit: ConsumedAuthorization;
435
+ } | {
436
+ readonly consumed: false;
437
+ readonly refusal: PermitRefusal;
438
+ readonly reason: string;
439
+ /** On `already_consumed`, what the winning execution did. */
440
+ readonly executionState?: PermitExecutionState;
441
+ };
442
+ type ExecutionOutcome = 'succeeded' | 'failed';
443
+ interface ExecutionReceipt {
444
+ readonly permitId: string;
445
+ readonly outcome: ExecutionOutcome;
446
+ /** Bounded and redacted server-side. What the action did, or why it did not. */
447
+ readonly summary?: string;
225
448
  }
226
449
  interface DecisionReachability {
227
450
  readonly reachable?: boolean;
@@ -283,7 +506,7 @@ interface EnrollResult {
283
506
  readonly universalLink: string;
284
507
  readonly expiresInSeconds: number;
285
508
  }
286
- interface AskDecision {
509
+ interface AskDecision extends DecisionSubject {
287
510
  readonly question: string;
288
511
  readonly type?: DecisionType;
289
512
  readonly options?: readonly string[];
@@ -297,6 +520,9 @@ interface AskDecision {
297
520
  readonly poweredBy?: boolean;
298
521
  readonly callbackUrl?: string;
299
522
  readonly requireReachable?: boolean;
523
+ readonly sandboxOutcome?: SandboxOutcome;
524
+ readonly presentation?: ActionPresentation;
525
+ readonly approvalUrl?: boolean;
300
526
  readonly timeoutMs?: number;
301
527
  }
302
528
  interface AskResult extends DecisionReachability {
@@ -401,7 +627,14 @@ interface PusharyServer {
401
627
  readonly flows: FlowsResource;
402
628
  readonly decisions: DecisionsResource;
403
629
  readonly keys: KeysResource;
630
+ readonly authorize: (input: AuthorizeRequest) => Promise<AuthorizeResult>;
631
+ readonly evaluateAuthorization: (input: AuthorizationQuery) => Promise<AuthorizationEvaluation>;
632
+ readonly consumeAuthorization: (binding: AuthorizationBinding) => Promise<ConsumeAuthorizationResult>;
633
+ readonly recordExecution: (receipt: ExecutionReceipt) => Promise<{
634
+ readonly executionState: PermitExecutionState;
635
+ }>;
404
636
  readonly enroll: (externalId: string) => Promise<EnrollResult>;
637
+ readonly reachability: (externalId: string) => Promise<DecisionReach>;
405
638
  }
406
639
  declare const createPusharyServer: (config: PusharyConfig) => PusharyServer;
407
640
 
@@ -416,4 +649,4 @@ declare const isApproved: (decision: {
416
649
  readonly value: string | null;
417
650
  }) => boolean;
418
651
 
419
- export { parseDecisionCallback as $, type DecisionAnswerResult as A, type CancelDecisionResult as B, type Campaign as C, type DecisionType as D, type DecisionCallback as E, type Flow as F, type EnrollResult as G, type AskDecision as H, type AskResult as I, type IssueBoundKey as J, type BoundKeyResult as K, type ListParams as L, type BoundKeySummary as M, type SubscribersResource as N, type CampaignsResource as O, type PusharyServer as P, type TemplatesResource as Q, type RevokeKeyResult as R, type Subscriber as S, type Template as T, type UpdateSubscriber as U, type NotificationsResource as V, type WebhookSecret as W, type FlowsResource as X, type DecisionsResource as Y, type KeysResource as Z, verifyWebhookSignature as _, type PusharyConfig as a, SIGNATURE_HEADER as a0, deterministicKey as a1, isApproved as a2, type SubscriberListParams as b, createPusharyServer as c, type SubscriberCount as d, type CreateCampaign as e, type UpdateCampaign as f, type CampaignStats as g, type CreateTemplate as h, type UpdateTemplate as i, type SendNotification as j, type SendResult as k, type CreateFlow as l, type UpdateFlow as m, type FlowListParams as n, type FlowTriggerType as o, type FlowStepType as p, type FlowStatus as q, type FlowStep as r, type FlowStepConfig as s, type FlowStepInput as t, type PaginatedResponse as u, type DecisionStatus as v, type CreateDecision as w, type DecisionReachability as x, type DecisionResult as y, type Decision as z };
652
+ export { type DecisionResult as $, type ActionPresentation as A, type PresentedChange as B, type Campaign as C, type DecisionType as D, type PresentedFormat as E, type Flow as F, type SandboxOutcome as G, type AuthorizationVerdict as H, type AuthorizationQuery as I, type AuthorizeRequest as J, type AuthorizeResult as K, type ListParams as L, type AuthorizationEvaluation as M, type AuthorizationBinding as N, type AuthorizationAuthority as O, type PusharyServer as P, type ConsumedAuthorization as Q, type ConsumeAuthorizationResult as R, type Subscriber as S, type Template as T, type UpdateSubscriber as U, type ExecutionOutcome as V, type ExecutionReceipt as W, type PermitRefusal as X, type PermitExecutionState as Y, type CreateDecision as Z, type DecisionReachability as _, type PusharyConfig as a, type Decision as a0, type DecisionAnswerResult as a1, type CancelDecisionResult as a2, type DecisionCallback as a3, type WebhookSecret as a4, type EnrollResult as a5, type AskDecision as a6, type AskResult as a7, type IssueBoundKey as a8, type BoundKeyResult as a9, type BoundKeySummary as aa, type RevokeKeyResult as ab, type SubscribersResource as ac, type CampaignsResource as ad, type TemplatesResource as ae, type NotificationsResource as af, type FlowsResource as ag, type DecisionsResource as ah, type KeysResource as ai, verifyWebhookSignature as aj, parseDecisionCallback as ak, SIGNATURE_HEADER as al, deterministicKey as am, isApproved as an, type SubscriberListParams as b, createPusharyServer as c, type SubscriberCount as d, type CreateCampaign as e, type UpdateCampaign as f, type CampaignStats as g, type CreateTemplate as h, type UpdateTemplate as i, type SendNotification as j, type SendResult as k, type CreateFlow as l, type UpdateFlow as m, type FlowListParams as n, type FlowTriggerType as o, type FlowStepType as p, type FlowStatus as q, type FlowStep as r, type FlowStepConfig as s, type FlowStepInput as t, type PaginatedResponse as u, type DecisionStatus as v, type DecisionSubject as w, type DecisionDeliverability as x, type DecisionReach as y, type DecisionParameters as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/server",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Pushary Server SDK: send push notifications and add human-in-the-loop approvals to your AI agent (pause on a decision until a specific human answers).",
5
5
  "author": "Pushary <business@pushary.com>",
6
6
  "homepage": "https://pushary.com",