@oxy.so/core 4.0.0 → 4.2.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 (95) hide show
  1. package/dist/cjs/HttpService.js +142 -33
  2. package/dist/cjs/api/accounts.js +49 -10
  3. package/dist/cjs/api/apps.js +2 -2
  4. package/dist/cjs/api/auth.js +24 -3
  5. package/dist/cjs/api/billing.js +35 -0
  6. package/dist/cjs/boot/sessionColdBoot.js +28 -57
  7. package/dist/cjs/inference/OxyInferenceClient.js +53 -20
  8. package/dist/cjs/server/OxyServer.js +107 -51
  9. package/dist/cjs/server/agentAccount.js +19 -0
  10. package/dist/cjs/server/auth.js +19 -0
  11. package/dist/cjs/server/capabilityTicket.js +3 -41
  12. package/dist/cjs/server/index.js +10 -2
  13. package/dist/cjs/server/liveCapabilityTicket.js +48 -0
  14. package/dist/cjs/server/middleware.js +60 -18
  15. package/dist/cjs/server/namespaces.js +57 -0
  16. package/dist/cjs/server/serviceTokenLifetime.js +13 -0
  17. package/dist/cjs/session/SessionClient.js +90 -12
  18. package/dist/cjs/session/authStateStore.js +130 -39
  19. package/dist/cjs/session/refresh.js +152 -30
  20. package/dist/cjs/session/sharedDeviceCredential.js +17 -1
  21. package/dist/cjs/utils/officialOrigins.js +7 -18
  22. package/dist/esm/HttpService.js +143 -34
  23. package/dist/esm/api/accounts.js +38 -0
  24. package/dist/esm/api/apps.js +2 -2
  25. package/dist/esm/api/auth.js +23 -3
  26. package/dist/esm/api/billing.js +35 -0
  27. package/dist/esm/boot/sessionColdBoot.js +29 -58
  28. package/dist/esm/inference/OxyInferenceClient.js +46 -13
  29. package/dist/esm/server/OxyServer.js +107 -51
  30. package/dist/esm/server/agentAccount.js +16 -0
  31. package/dist/esm/server/auth.js +18 -0
  32. package/dist/esm/server/capabilityTicket.js +1 -39
  33. package/dist/esm/server/index.js +4 -1
  34. package/dist/esm/server/liveCapabilityTicket.js +45 -0
  35. package/dist/esm/server/middleware.js +60 -18
  36. package/dist/esm/server/namespaces.js +57 -0
  37. package/dist/esm/server/serviceTokenLifetime.js +9 -0
  38. package/dist/esm/session/SessionClient.js +90 -12
  39. package/dist/esm/session/authStateStore.js +128 -38
  40. package/dist/esm/session/refresh.js +151 -30
  41. package/dist/esm/session/sharedDeviceCredential.js +17 -1
  42. package/dist/esm/utils/officialOrigins.js +2 -13
  43. package/dist/types/HttpService.d.ts +26 -12
  44. package/dist/types/api/accounts.d.ts +38 -0
  45. package/dist/types/api/agency.d.ts +3 -1
  46. package/dist/types/api/apps.d.ts +3 -1
  47. package/dist/types/api/auth.d.ts +12 -1
  48. package/dist/types/api/billing.d.ts +12 -0
  49. package/dist/types/api/session.d.ts +3 -1
  50. package/dist/types/boot/sessionColdBoot.d.ts +1 -1
  51. package/dist/types/index.d.ts +1 -1
  52. package/dist/types/inference/OxyInferenceClient.d.ts +12 -0
  53. package/dist/types/inference/index.d.ts +1 -1
  54. package/dist/types/server/OxyServer.d.ts +16 -9
  55. package/dist/types/server/agentAccount.d.ts +14 -0
  56. package/dist/types/server/auth.d.ts +18 -0
  57. package/dist/types/server/capabilityTicket.d.ts +2 -4
  58. package/dist/types/server/index.d.ts +7 -1
  59. package/dist/types/server/liveCapabilityTicket.d.ts +15 -0
  60. package/dist/types/server/middleware.d.ts +15 -6
  61. package/dist/types/server/namespaces.d.ts +78 -2
  62. package/dist/types/server/serviceTokenLifetime.d.ts +7 -0
  63. package/dist/types/session/SessionClient.d.ts +8 -0
  64. package/dist/types/session/authStateStore.d.ts +15 -0
  65. package/dist/types/session/index.d.ts +1 -1
  66. package/dist/types/session/refresh.d.ts +32 -7
  67. package/dist/types/session/sharedDeviceCredential.d.ts +3 -1
  68. package/dist/types/utils/officialOrigins.d.ts +1 -5
  69. package/package.json +6 -6
  70. package/src/HttpService.ts +138 -50
  71. package/src/api/accounts.ts +22 -0
  72. package/src/api/agency.ts +3 -1
  73. package/src/api/apps.ts +2 -2
  74. package/src/api/auth.ts +31 -2
  75. package/src/api/billing.ts +71 -9
  76. package/src/api/session.ts +3 -1
  77. package/src/boot/sessionColdBoot.ts +27 -72
  78. package/src/index.ts +1 -1
  79. package/src/inference/OxyInferenceClient.ts +50 -5
  80. package/src/inference/index.ts +1 -0
  81. package/src/server/OxyServer.ts +114 -53
  82. package/src/server/agentAccount.ts +28 -0
  83. package/src/server/auth.ts +25 -0
  84. package/src/server/capabilityTicket.ts +2 -42
  85. package/src/server/index.ts +13 -0
  86. package/src/server/liveCapabilityTicket.ts +47 -0
  87. package/src/server/middleware.ts +72 -25
  88. package/src/server/namespaces.ts +99 -2
  89. package/src/server/serviceTokenLifetime.ts +13 -0
  90. package/src/session/SessionClient.ts +85 -12
  91. package/src/session/authStateStore.ts +130 -46
  92. package/src/session/index.ts +1 -0
  93. package/src/session/refresh.ts +144 -28
  94. package/src/session/sharedDeviceCredential.ts +14 -2
  95. package/src/utils/officialOrigins.ts +2 -13
@@ -17,6 +17,7 @@
17
17
  * no per-request "acting-as" header. Identity is carried by the session/token,
18
18
  * so a switch propagates through reload and cross-domain exactly like a login.
19
19
  */
20
+ import { type AgentKeyOperation, type AgentKeyOperationProof } from '@oxy.so/contracts';
20
21
  import type { User } from '../models/interfaces';
21
22
  import type { AccountCategoryId, AccountKind, ChildAccountKind } from '@oxy.so/contracts';
22
23
  import type { SessionLoginResponse } from '../models/session';
@@ -340,10 +341,47 @@ export declare class AccountMembersApi {
340
341
  */
341
342
  remove(accountId: string, memberId: string): Promise<AccountSuccessResult>;
342
343
  }
344
+ /** Current account governors and autonomous rotation use action-bound proofs. */
345
+ export declare class AccountAgentKeysApi {
346
+ private readonly ctx;
347
+ constructor(ctx: OxyContext);
348
+ list(accountId: string): Promise<{
349
+ keys: {
350
+ id: string;
351
+ revokedAt: string | null;
352
+ publicKey: string;
353
+ label: string;
354
+ enrolledByUserId: string | null;
355
+ enrollmentMethod: "governor" | "rotation" | "recovery";
356
+ linkedAt: string;
357
+ lastUsedAt: string | null;
358
+ }[];
359
+ }>;
360
+ requestChallenge(accountId: string, operation: AgentKeyOperation): Promise<{
361
+ claims: {
362
+ expiresAt: number;
363
+ accountId: string;
364
+ version: 1;
365
+ audience: "oxy-api/agent";
366
+ action: "agent_signin" | "agent_enroll" | "agent_rotate" | "agent_recover" | "agent_governance";
367
+ challenge: string;
368
+ actorId: string;
369
+ authMethodId: string | null;
370
+ publicKey: string;
371
+ payloadDigest: string;
372
+ };
373
+ governorPublicKey: string | null;
374
+ }>;
375
+ execute(accountId: string, operation: AgentKeyOperation, proof: AgentKeyOperationProof): Promise<{
376
+ methodId: string | null;
377
+ revokedMethodIds: string[];
378
+ }>;
379
+ }
343
380
  export declare class AccountsApi {
344
381
  private readonly ctx;
345
382
  /** Who can act on an account, and with what role. */
346
383
  readonly members: AccountMembersApi;
384
+ readonly agentKeys: AccountAgentKeysApi;
347
385
  constructor(ctx: OxyContext);
348
386
  /**
349
387
  * List the accounts the caller can access: their own personal (root)
@@ -52,7 +52,9 @@ export interface CapabilityExecutionAuthorization {
52
52
  ownerAccountId: string;
53
53
  coordinatorApplicationId: string;
54
54
  coordinatorCredentialId: string;
55
- actorType: 'alia' | 'agent';
55
+ actorType: 'alia' | 'agent' | 'requester';
56
+ requesterSessionId?: string | null;
57
+ requesterSessionBindingDigest?: string | null;
56
58
  actorAccountId: string | null;
57
59
  resourceApp: string;
58
60
  effectiveAccountId: string;
@@ -422,7 +422,9 @@ export declare class AppsApi {
422
422
  * consent, authorize, and device-flow approval screens before any session
423
423
  * exists.
424
424
  */
425
- getPublic(clientId: string): Promise<PublicApplication>;
425
+ getPublic(clientId: string, options?: {
426
+ cache?: boolean;
427
+ }): Promise<PublicApplication>;
426
428
  /**
427
429
  * Bust every cached application list — the unscoped entry and every
428
430
  * owner-scoped `GET:/applications?ownerAccountId=<id>` variant — plus any
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import type { User } from '../models/interfaces';
19
19
  import type { CommonsDenyReason, DeviceProof, LoginResult } from '@oxy.so/contracts';
20
- import { type EmailSignInPending, type EmailSignInStartResponse, type EmailVerificationConfirmResponse, type EmailVerificationStartRequest, type EmailVerificationStartResponse, type ReauthAction, type ReauthProof, type SignInMethods, type SignInStepResult, type TotpEnrollResponse } from '@oxy.so/contracts';
20
+ import { type AgentProofClaims, type AgentSignature, type EmailSignInPending, type EmailSignInStartResponse, type EmailVerificationConfirmResponse, type EmailVerificationStartRequest, type EmailVerificationStartResponse, type ReauthAction, type ReauthProof, type SignInMethods, type SignInStepResult, type TotpEnrollResponse } from '@oxy.so/contracts';
21
21
  import type { OxyContext } from '../client/context';
22
22
  import type { SessionLoginResponse } from '../models/session';
23
23
  import type { PublicApplication } from './apps';
@@ -425,11 +425,14 @@ export declare class AuthOAuthApi {
425
425
  * out, so the app can run the OAuth flow again. It never degrades into a
426
426
  * session that looks alive and cannot refresh.
427
427
  */
428
+ /** `plantTokens: false` defers bearer installation to a validated provider commit. */
428
429
  exchangeCode(params: {
429
430
  code: string;
430
431
  clientId: string;
431
432
  redirectUri: string;
432
433
  codeVerifier: string;
434
+ }, options?: {
435
+ plantTokens?: boolean;
433
436
  }): Promise<OAuthTokenExchangeResult>;
434
437
  /**
435
438
  * Fetch OpenID Connect userinfo for the current bearer (`GET /auth/oauth/userinfo`).
@@ -608,6 +611,13 @@ export declare class AuthCommonsApi {
608
611
  */
609
612
  deny(authorizeCode: string, reason?: CommonsDenyReason): Promise<CommonsSignInActionResult>;
610
613
  }
614
+ /** Bot entry uses caller-owned keys and the normal account session lane. */
615
+ export declare class AuthAgentApi {
616
+ private readonly ctx;
617
+ constructor(ctx: OxyContext);
618
+ requestChallenge(publicKey: string): Promise<AgentProofClaims>;
619
+ verify(publicKey: string, proof: AgentSignature): Promise<LoginResult>;
620
+ }
611
621
  export declare class AuthApi {
612
622
  private readonly ctx;
613
623
  /** Email sign-in and email confirmation codes. */
@@ -620,6 +630,7 @@ export declare class AuthApi {
620
630
  readonly oauth: AuthOAuthApi;
621
631
  /** "Sign in with Oxy" handoff to Commons. */
622
632
  readonly commons: AuthCommonsApi;
633
+ readonly agent: AuthAgentApi;
623
634
  constructor(ctx: OxyContext);
624
635
  /**
625
636
  * Register a new identity with public key authentication
@@ -9,6 +9,8 @@
9
9
  * `payment.controller`, `wallet.routes` in `packages/api`); `Date` columns
10
10
  * arrive as ISO-8601 strings.
11
11
  */
12
+ import { type SubjectProductAccess, type SubjectProductAccessQuery } from '@oxy.so/contracts';
13
+ import { type ProductSubscriptionSummary, type SubscriptionCreditGrant, type ProductSubscriptionCancellationResult } from '@oxy.so/contracts';
12
14
  import type { OxyContext } from '../client/context';
13
15
  /** Subscription tier. */
14
16
  export type SubscriptionPlan = 'basic' | 'pro' | 'business';
@@ -97,6 +99,16 @@ export interface WalletTransactionsPage {
97
99
  export declare class BillingApi {
98
100
  protected readonly ctx: OxyContext;
99
101
  constructor(ctx: OxyContext);
102
+ /** Product rights only; requires a production application-bound user session. Never cached. */
103
+ productAccess(query: SubjectProductAccessQuery): Promise<SubjectProductAccess>;
104
+ /** Named commercial sources for the current subject; no inferred plan collapse. */
105
+ productSubscriptions(): Promise<ProductSubscriptionSummary[]>;
106
+ /** Per-grant credit provenance; no interpretation of the historical mixed balance. */
107
+ creditGrants(): Promise<SubscriptionCreditGrant[]>;
108
+ /** Payer-only named-source cancellation. Resolves on provider acceptance, including pending local reconciliation. Use cancelProductSubscriptionWithStatus for detailed completion state. */
109
+ cancelProductSubscription(sourceId: string, expectedSubjectAccountId?: string): Promise<void>;
110
+ /** Provider acceptance versus completed local cancellation, for user-facing flows. */
111
+ cancelProductSubscriptionWithStatus(sourceId: string, expectedSubjectAccountId?: string): Promise<ProductSubscriptionCancellationResult>;
100
112
  /** The signed-in user's payment history. Never cached. */
101
113
  payments(): Promise<Payment[]>;
102
114
  /** A user's subscription (default: the signed-in user's). */
@@ -1,4 +1,4 @@
1
- import type { DeviceProof } from '@oxy.so/contracts';
1
+ import type { AccountActorChain, DeviceProof } from '@oxy.so/contracts';
2
2
  import type { OxyContext } from '../client/context';
3
3
  import type { ClientSession } from '../models/session';
4
4
  import type { User } from '../models/interfaces';
@@ -15,6 +15,8 @@ export interface SessionValidation {
15
15
  user: User;
16
16
  sessionId?: string;
17
17
  source?: string;
18
+ /** Who acted, and as whom, read off the session row (issue #1520). */
19
+ actor?: AccountActorChain;
18
20
  }
19
21
  export declare class SessionApi {
20
22
  private readonly ctx;
@@ -42,7 +42,7 @@
42
42
  */
43
43
  import { type ColdBootOutcome } from '../utils/coldBoot';
44
44
  import { type IdentityBinding } from '../session/identitySession';
45
- import { type SharedDeviceCredentialStore } from '../session/sharedDeviceCredential';
45
+ import type { SharedDeviceCredentialStore } from '../session/sharedDeviceCredential';
46
46
  import type { OxyServices } from '../OxyServices';
47
47
  import type { AuthStateStore } from '../session/authStateStore';
48
48
  import type { DeviceSessionState } from '@oxy.so/contracts';
@@ -24,7 +24,7 @@ export { OxyServices, OXY_API_URL, OXY_CLOUD_URL } from './OxyServices';
24
24
  export type { OxyConfig, LinkedHttpClient } from './OxyServices';
25
25
  export { OxyApiError, OxyAuthenticationError, OxyAuthenticationTimeoutError, AssetUrlResolutionError, ServiceAssetMetadataError, } from './OxyServices.errors';
26
26
  export type { HttpMethod } from './client/context';
27
- export type { RequestOptions, AuthenticatedResponseRequest, AuthRefreshReason, AuthRefreshHandler } from './HttpService';
27
+ export type { RequestOptions, ResponseRequest, ResponseTransport, AuthenticatedResponseRequest, AuthRefreshReason, AuthRefreshHandler } from './HttpService';
28
28
  export type { SessionApi, DeviceCredentialProvider, SessionValidation } from './api/session';
29
29
  export type { AuthApi, ChallengeResponse, RegistrationRequest, ChallengeVerifyRequest, PublicKeyCheckResponse, OAuthUserInfoResponse, OAuthTokenExchangeResult, CommonsSignInHandle, CommonsSignInStatus, CommonsSignInPurpose, CommonsOAuthContext, CommonsApprovalInfo, CommonsApprovalSubjectAccount, CommonsSignInActionResult, CommonsOAuthFinalizeResult, CommonsDeliveryResult, SignInDeviceOptions, ClaimedSession, } from './api/auth';
30
30
  export type { UsersApi, ResolveExternalUserInput, DeleteAccountProof } from './api/users';
@@ -1,3 +1,4 @@
1
+ import type { MeteredGeneration } from '@oxy.so/contracts';
1
2
  /**
2
3
  * The Oxy inference client — one surface, two credential lanes (issue #972,
3
4
  * workstream 15).
@@ -49,6 +50,7 @@
49
50
  * schema, so a rename on either side fails a build rather than a customer's
50
51
  * request.
51
52
  */
53
+ import { type DecisionRequest, type DecisionSuccess } from '@oxy.so/contracts';
52
54
  import type { CurrencyCode, ExactDecimal, InferenceEnvironment, InferenceErrorCode, InferenceFinishReason, InferenceMessage, InferenceReasoning, InferenceRequestOutcome, InferenceAudioMediaType, InferenceStreamEvent, InferenceSpeechParameters, ModelCatalogueEntry, ResponseFormat, RoutingPolicyReference, RoutingProfile, ToolChoice, ToolDefinition, UnitPrice, UsageQuantity, UsageSource } from '@oxy.so/contracts';
53
55
  /** The base URL of the Oxy API, when a caller names none. */
54
56
  export declare const OXY_INFERENCE_BASE_URL = "https://api.oxy.so";
@@ -222,6 +224,8 @@ export interface OxyGenerationReceipt {
222
224
  readonly platformFeeOnly: boolean;
223
225
  readonly settledAt: string;
224
226
  }
227
+ /** The endpoint returns either a customer receipt or internal technical usage. */
228
+ export type OxyGenerationRecord = OxyGenerationReceipt | MeteredGeneration;
225
229
  /**
226
230
  * Anything the inference API refused.
227
231
  *
@@ -336,6 +340,8 @@ export declare class OxyInferenceClient {
336
340
  * `code`, `retryable` and `requestId`.
337
341
  */
338
342
  respond(request: OxyResponsesRequest, options?: OxyInferenceRequestOptions): Promise<OxyInferenceResponse>;
343
+ /** Typed nonstreaming decisions. Idempotency conflicts are never replayed or retried. */
344
+ decide(request: DecisionRequest, options?: OxyInferenceRequestOptions): Promise<DecisionSuccess>;
339
345
  /** Generate audio bytes through Oxy's authenticated inference edge. */
340
346
  speech(request: OxySpeechRequest, options?: OxyInferenceRequestOptions): Promise<OxySpeechResponse>;
341
347
  /**
@@ -369,5 +375,11 @@ export declare class OxyInferenceClient {
369
375
  */
370
376
  getGeneration(id: string, options?: {
371
377
  signal?: AbortSignal;
378
+ delegatedUserId?: string;
372
379
  }): Promise<OxyGenerationReceipt>;
380
+ /** Read a financial receipt (v1) or an internal technical record (v2). */
381
+ getGenerationRecord(id: string, options?: {
382
+ signal?: AbortSignal;
383
+ delegatedUserId?: string;
384
+ }): Promise<OxyGenerationRecord>;
373
385
  }
@@ -14,7 +14,7 @@
14
14
  import type { OxyServices } from '../OxyServices';
15
15
  import { OxyInferenceClient, type OxyInferenceClientOptions } from './OxyInferenceClient';
16
16
  export { OXY_INFERENCE_BASE_URL, OxyInferenceClient, OxyInferenceError, OxyInferenceProtocolError, } from './OxyInferenceClient';
17
- export type { OxyGenerationReceipt, OxyInferenceClientOptions, OxyInferenceCredential, OxyInferenceFetch, OxyInferenceRequestOptions, OxyInferenceResponse, OxyResponsesRequest, OxySpeechRequest, OxySpeechResponse, } from './OxyInferenceClient';
17
+ export type { OxyGenerationReceipt, OxyGenerationRecord, OxyInferenceClientOptions, OxyInferenceCredential, OxyInferenceFetch, OxyInferenceRequestOptions, OxyInferenceResponse, OxyResponsesRequest, OxySpeechRequest, OxySpeechResponse, } from './OxyInferenceClient';
18
18
  /**
19
19
  * An inference client bound to `oxy`'s session: it calls the same API origin
20
20
  * with the session's current bearer on every request.
@@ -36,6 +36,14 @@ export interface OxyServerConfig extends OxyConfig {
36
36
  */
37
37
  export declare const ANONYMOUS_SERVICE_TOKEN_RETRY_MS = 30000;
38
38
  /** `POST /auth/service-token`. */
39
+ export interface ActingAsVerificationOptions {
40
+ /** Only explicit read-only callers may reuse authority; effects always pass false. */
41
+ cache?: boolean;
42
+ /** Taken from verified service claims by middleware, never from a user header. */
43
+ credentialId?: string;
44
+ ownerAccountId?: string;
45
+ environment?: string;
46
+ }
39
47
  export interface ServiceTokenResponse {
40
48
  token: string;
41
49
  expiresIn: number;
@@ -55,6 +63,7 @@ export declare class OxyServer extends OxyServices {
55
63
  private serviceApiSecret;
56
64
  private readonly actingAs;
57
65
  private readonly actingAsPending;
66
+ private readonly actingAsGenerations;
58
67
  /** Public keys only; never private material. */
59
68
  private readonly jwksCache;
60
69
  /**
@@ -124,16 +133,13 @@ export declare class OxyServer extends OxyServices {
124
133
  actAs?: string;
125
134
  }): Promise<T>;
126
135
  /**
127
- * Whether service app `appId` holds an active delegation grant to act for
128
- * `userId`; the grant's scopes, or `null`. Used by `middleware.auth()` for
129
- * `X-Oxy-User-Id`.
130
- *
131
- * Answers are cached per `appId:userId` (a grant for 5 minutes — the
132
- * revocation window; a denial or failed lookup for 1 minute), bounded to the
133
- * 1000 most recent pairs, and concurrent lookups of one pair share a single
134
- * request.
136
+ * Resolve authority live by default. Explicit read-only reuse lasts at most
137
+ * 60s positive / 10s negative, anchored to request start; errors never cache.
138
+ * Effects do not share an older in-flight read. Durable epoch plus local
139
+ * completion ordering prevents ABA and late positive replies overwriting a
140
+ * newer refusal even when app/credential revocation leaves epoch unchanged.
135
141
  */
136
- verifyActingAs(appId: string, userId: string): Promise<ServiceActingAsVerification | null>;
142
+ verifyActingAs(appId: string, userId: string, options?: ActingAsVerificationOptions): Promise<ServiceActingAsVerification | null>;
137
143
  /**
138
144
  * Account events addressed to this application (Oxy account deletions every
139
145
  * relying application must honour, OxyHQ/Mention#1169).
@@ -176,6 +182,7 @@ export declare class OxyServer extends OxyServices {
176
182
  /** A token obtained by attestation, cached like a credential's (one identity per process). */
177
183
  private workloadServiceToken;
178
184
  private lookupActingAs;
185
+ private clearActingAsPair;
179
186
  private rememberActingAs;
180
187
  private verifyAccountEvent;
181
188
  /** The `appId` claim of this service's own token. */
@@ -0,0 +1,14 @@
1
+ /** Autonomous entry helper. The signer owns its private key (for example in a KMS). */
2
+ import { type LoginResult } from '@oxy.so/contracts';
3
+ import type { OxyServices } from '../OxyServices';
4
+ export interface AgentAccountSigner {
5
+ /** Compressed or uncompressed SEC1 secp256k1 public key; no private-key field exists. */
6
+ publicKey: string;
7
+ signMessage(message: string): Promise<string>;
8
+ }
9
+ /** Explicit sign-in; no timer, automatic escalation or service credential lane. */
10
+ export declare function signInAgentAccount(options: {
11
+ client: Pick<OxyServices, 'auth'>;
12
+ accountId: string;
13
+ signer: AgentAccountSigner;
14
+ }): Promise<LoginResult>;
@@ -1,5 +1,6 @@
1
1
  import type { NextFunction, Request, RequestHandler, Response } from 'express';
2
2
  import type { AuthMiddlewareOptions, OxyAuthRefusal, OxyMiddleware } from './middleware';
3
+ import type { AccountActorChain } from '@oxy.so/contracts';
3
4
  import { OXY_SERVICE_ENVIRONMENTS, type OxyServiceEnvironment } from '../utils/oxyServiceEnvironment';
4
5
  export { OXY_SERVICE_ENVIRONMENTS };
5
6
  export type { OxyServiceEnvironment, OxyAuthRefusal };
@@ -37,6 +38,12 @@ export interface OxyAuthRequest extends Request {
37
38
  sessionId?: string | null;
38
39
  serviceApp?: OxyServiceAppContext;
39
40
  serviceActingAs?: OxyServiceActingAsContext;
41
+ /**
42
+ * Who acted, and as whom, on a USER session — read off the session row by the
43
+ * Oxy API, never off a header. `null`/absent when the request is not a user
44
+ * session or the API did not report it. Read it with {@link getOxyActor}.
45
+ */
46
+ oxyActor?: AccountActorChain | null;
40
47
  /**
41
48
  * Why the presented credential was refused, when one was and the request
42
49
  * still reached the handler (the optional path). Read it with
@@ -57,6 +64,17 @@ export interface OxyAuthMiddlewareOptions {
57
64
  auth?: AuthMiddlewareOptions;
58
65
  }
59
66
  export declare function getOxyUserId(req: Request): string | null;
67
+ /**
68
+ * Who acted, and as whom, on this user session (issue #1520), or `null` when
69
+ * the session authority did not report it.
70
+ *
71
+ * An audit records `actorAccountId` as the actor and `effectiveAccountId`
72
+ * (equal to {@link getOxyUserId}) as the account spoken as. A bot acting with
73
+ * nobody operating it is its own actor; a person acting as an organization or
74
+ * a bot is the actor of a `delegated` chain. Never substitute the effective
75
+ * account's owner for either: when this is `null`, record the actor as unknown.
76
+ */
77
+ export declare function getOxyActor(req: Request): AccountActorChain | null;
60
78
  export declare function isOxyAuthenticated(req: Request): req is OxyAuthenticatedRequest;
61
79
  /**
62
80
  * The principal a request is CHARGED to, and the identifiers a receipt needs.
@@ -1,5 +1,5 @@
1
1
  import { type KeyObject } from 'node:crypto';
2
- import { type CapabilityTicketClaims, type GrantLimit, type PolicyDecision } from '@oxy.so/contracts';
2
+ import { type CapabilityTicketClaims, type PolicyDecision } from '@oxy.so/contracts';
3
3
  import type { Request, RequestHandler } from 'express';
4
4
  type UnsignedCapabilityTicketClaims = Omit<CapabilityTicketClaims, 'iss' | 'iat' | 'exp' | 'jti'>;
5
5
  export interface CapabilityTicketSigningOptions {
@@ -27,8 +27,6 @@ export declare class CapabilityTicketError extends Error {
27
27
  readonly code: 'malformed' | 'invalid_signature' | 'unknown_key' | 'invalid_claims' | 'expired' | 'not_yet_valid' | 'wrong_audience' | 'wrong_issuer' | 'ttl_exceeded';
28
28
  constructor(code: 'malformed' | 'invalid_signature' | 'unknown_key' | 'invalid_claims' | 'expired' | 'not_yet_valid' | 'wrong_audience' | 'wrong_issuer' | 'ttl_exceeded', message: string);
29
29
  }
30
- /** Enforces the signed per-action bounds before a domain handler runs. */
31
- export declare function inputSatisfiesCapabilityLimits(tool: string, input: Record<string, unknown>, limits: readonly GrantLimit[]): boolean;
32
30
  export declare function issueCapabilityTicket(claims: UnsignedCapabilityTicketClaims, options: CapabilityTicketSigningOptions): string;
33
31
  export declare function verifyCapabilityTicket(token: string, options: CapabilityTicketVerificationOptions): CapabilityTicketClaims;
34
32
  export declare function readCapabilityAuthorization(authorization: string | undefined): string | null;
@@ -38,4 +36,4 @@ export declare function readCapabilityAuthorization(authorization: string | unde
38
36
  * planning and execution.
39
37
  */
40
38
  export declare function createCapabilityTicketMiddleware(options: CapabilityTicketMiddlewareOptions): RequestHandler;
41
- export {};
39
+ export { inputSatisfiesCapabilityLimits } from '@oxy.so/contracts';
@@ -17,9 +17,10 @@
17
17
  export { OxyServer, ServiceCredentialMismatchError, ANONYMOUS_SERVICE_TOKEN_RETRY_MS } from './OxyServer';
18
18
  export type { OxyServerConfig, ServiceTokenResponse } from './OxyServer';
19
19
  export { ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces';
20
+ export type { CapabilityTicketGrant, CapabilityTicketIntrospection, CreateExecutionAuthorizationInput, ServiceCapabilityCatalog, } from './namespaces';
20
21
  export { OxyAccountEventError, OXY_ACCOUNT_DELETED_EVENT_URI } from './middleware';
21
22
  export type { AuthMiddlewareOptions, OxyAccountEvent, OxyAccountEventFeedItem, OxyAccountEventFeedPage, OxyMiddleware, ServiceActingAsVerification, ServiceApp, VerifyAccountEventOptions, } from './middleware';
22
- export { createOptionalOxyAuth, createOxyAuthMiddleware, getOxyAuthRefusal, getOxyBillingPrincipal, getOxyDelegatedUserId, getOxyRequestAttribution, getOxyUserId, getRequiredOxyBillingPrincipal, getRequiredOxyUserId, isOxyAuthenticated, requireOxyAuth, OXY_SERVICE_ENVIRONMENTS, } from './auth';
23
+ export { createOptionalOxyAuth, createOxyAuthMiddleware, getOxyActor, getOxyAuthRefusal, getOxyBillingPrincipal, getOxyDelegatedUserId, getOxyRequestAttribution, getOxyUserId, getRequiredOxyBillingPrincipal, getRequiredOxyUserId, isOxyAuthenticated, requireOxyAuth, OXY_SERVICE_ENVIRONMENTS, } from './auth';
23
24
  export type { OxyAuthHost } from './auth';
24
25
  export type { OxyAuthenticatedRequest, OxyAuthRefusal, OxyAuthMiddlewareOptions, OxyAuthRequest, OxyBillingPrincipal, OxyRequestAttribution, OxyRequestUser, OxyServiceActingAsContext, OxyServiceAppContext, OxyServiceEnvironment, } from './auth';
25
26
  export { createOxyRateLimit } from './rateLimit';
@@ -35,6 +36,8 @@ export type { CapabilityTicketMiddlewareOptions, CapabilityTicketRequest, Capabi
35
36
  export { createOxyJwksKeyResolver, createOxyRequesterAssertionAuth, OXY_REQUESTER_ASSERTION_DEFAULT_ISSUER, OXY_REQUESTER_ASSERTION_HEADER, OXY_REQUESTER_ASSERTION_MAX_TTL_SECONDS, OXY_REQUESTER_ASSERTION_TYPE, OxyRequesterAssertionError, oxyRequesterAssertionClaimsSchema, readOxyRequesterAssertionKeyId, signOxyRequesterAssertion, verifyOxyRequesterAssertion, } from './requesterAssertion';
36
37
  export type { OxyJwksKeyResolverOptions, OxyRequesterAssertionAuthOptions, OxyRequesterAssertionClaims, OxyRequesterAssertionErrorCode, OxyRequesterAssertionIntrospection, OxyRequesterAssertionIntrospector, OxyRequesterAssertionRequest, OxyRequesterAssertionVerificationOptions, OxyRequesterContext, } from './requesterAssertion';
37
38
  export { verifySecret } from './verifySecret';
39
+ export { createLiveCapabilityTicketVerifier } from './liveCapabilityTicket';
40
+ export type { LiveCapabilityTicketVerifierOptions } from './liveCapabilityTicket';
38
41
  export { createOxyUserInvalidationHandler, publishOxyUserInvalidation, } from './userInvalidation';
39
42
  export type { OxyInvalidationPublisher, OxyUserInvalidationHandlerOptions, } from './userInvalidation';
40
43
  export { evictOxyIdentityCache, oxyUserByIdCacheKey, OXY_IDENTITY_CACHE_PREFIXES } from '../utils/identityCacheSweep';
@@ -52,3 +55,6 @@ export { observeNodeHttp, withoutNodeHttpObservation } from './trafficNodeHttp';
52
55
  */
53
56
  export { canAttestWorkloadIdentity, requestWorkloadServiceToken } from './workloadIdentity';
54
57
  export type { WorkloadServiceToken, WorkloadServiceTokenOptions } from './workloadIdentity';
58
+ export { OXY_SERVICE_TOKEN_MAX_LIFETIME_SECONDS, hasBoundedServiceTokenLifetime } from './serviceTokenLifetime';
59
+ export { signInAgentAccount } from './agentAccount';
60
+ export type { AgentAccountSigner } from './agentAccount';
@@ -0,0 +1,15 @@
1
+ import { type CapabilityTicketClaims } from '@oxy.so/contracts';
2
+ import { type CapabilityTicketVerificationOptions } from './capabilityTicket';
3
+ import type { CapabilityTicketIntrospection } from './namespaces';
4
+ export interface LiveCapabilityTicketVerifierOptions extends CapabilityTicketVerificationOptions {
5
+ issuer: string;
6
+ introspect: (ticket: string, options: {
7
+ signal: AbortSignal;
8
+ }) => Promise<CapabilityTicketIntrospection>;
9
+ /** Network bound only; does not establish an authority freshness policy. */
10
+ timeoutMs?: number;
11
+ }
12
+ /** Local signature + live receiving-service introspection; no cache or retry. */
13
+ export declare function createLiveCapabilityTicketVerifier(options: LiveCapabilityTicketVerifierOptions): (ticket: string, input?: {
14
+ signal?: AbortSignal;
15
+ }) => Promise<CapabilityTicketClaims>;
@@ -6,6 +6,7 @@
6
6
  * Node only; reachable solely from `@oxy.so/core/server`.
7
7
  */
8
8
  import type { OxyRequestUser } from './auth';
9
+ import { type AccountActorChain } from '@oxy.so/contracts';
9
10
  import type { ApiError, User } from '../models/interfaces';
10
11
  import { type OxyServiceEnvironment } from '../utils/oxyServiceEnvironment';
11
12
  /**
@@ -29,6 +30,8 @@ import { type OxyServiceEnvironment } from '../utils/oxyServiceEnvironment';
29
30
  * authoritative store stays server-side.
30
31
  */
31
32
  export interface ServiceActingAsVerification {
33
+ /** Decimal durable generation, never lossy JS number. */
34
+ epoch?: string;
32
35
  authorized: boolean;
33
36
  scopes: string[];
34
37
  }
@@ -113,11 +116,10 @@ export interface ServiceApp {
113
116
  appName: string;
114
117
  scopes: string[];
115
118
  /**
116
- * `internal` when the caller is one of Oxy's own applications: app to app it
117
- * is trusted outright — {@link requireScope} passes and it may act for a user
118
- * without a delegation grant. `external` keeps every scope and grant rule.
119
- * What the USER may do (their accounts, plan, limits) is still for the
120
- * receiving service to decide. A token without the claim reads as `external`.
119
+ * Classification only. Both tiers obey scope ceilings and live offline
120
+ * delegation; neither a brand nor a header proves a user's authority.
121
+ * Resource authorization remains with the receiving service. A missing
122
+ * claim reads as external.
121
123
  */
122
124
  tier: 'internal' | 'external';
123
125
  /** The credentialId of the specific service credential that minted this token. */
@@ -225,8 +227,14 @@ export interface OxyMiddlewareHost {
225
227
  }): Promise<{
226
228
  valid: boolean;
227
229
  user?: User;
230
+ actor?: unknown;
228
231
  } | null>;
229
- verifyActingAs(appId: string, userId: string): Promise<ServiceActingAsVerification | null>;
232
+ verifyActingAs(appId: string, userId: string, options?: {
233
+ cache?: boolean;
234
+ credentialId?: string;
235
+ ownerAccountId?: string;
236
+ environment?: string;
237
+ }): Promise<ServiceActingAsVerification | null>;
230
238
  }
231
239
  /** Build the middleware set for one server. See `OxyServer.middleware`. */
232
240
  export declare function createOxyMiddleware(host: OxyMiddlewareHost): {
@@ -276,6 +284,7 @@ interface AuthReq {
276
284
  userId: string;
277
285
  scopes: string[];
278
286
  };
287
+ oxyActor?: AccountActorChain | null;
279
288
  oxyAuthRefusal?: OxyAuthRefusal;
280
289
  }
281
290
  interface AuthRes {
@@ -4,12 +4,12 @@
4
4
  * `server.assets.metadataByIds(...)` sits beside every client method of
5
5
  * `oxy.assets`.
6
6
  */
7
- import type { CreateOxyNotificationRequest, AwardReputationInput, ReputationTransaction, ServiceLinkedAccountListResponse } from '@oxy.so/contracts';
7
+ import { type ForegroundExecutionAuthorizationInput, type ActorRef, type AppCapabilityCatalog, type AutonomyLevel, type CapabilityTicketClaims, type CapabilityTicketRequest, type GrantLimit, type PolicyDecision, type ResourceRef, type CreateOxyNotificationRequest, type AwardReputationInput, type ReputationTransaction, type ServiceLinkedAccountListResponse } from '@oxy.so/contracts';
8
8
  import type { Notification, ServiceAssetMetadata, ServiceAssetMetadataBySha, ServiceLinkedDownloadUrl } from '../models/interfaces';
9
9
  import { AssetsApi } from '../api/assets';
10
10
  import { NotificationsApi } from '../api/notifications';
11
11
  import { LinkedAccountsApi } from '../api/linkedAccounts';
12
- import { AgencyApi, type RequesterAssertionGrant, type RequesterAssertionIntrospection } from '../api/agency';
12
+ import { AgencyApi, type CapabilityExecutionAuthorization, type RequesterAssertionGrant, type RequesterAssertionIntrospection } from '../api/agency';
13
13
  import { ReputationApi } from '../api/reputation';
14
14
  export declare class ServerAssetsApi extends AssetsApi {
15
15
  /**
@@ -71,7 +71,82 @@ export declare class ServerLinkedAccountsApi extends LinkedAccountsApi {
71
71
  */
72
72
  forUser(userId: string): Promise<ServiceLinkedAccountListResponse>;
73
73
  }
74
+ /** Projection of the existing service-authenticated registry response. */
75
+ export interface ServiceCapabilityCatalog {
76
+ id: string;
77
+ appSlug: string;
78
+ version: string;
79
+ digest: string;
80
+ audience: string;
81
+ catalog: AppCapabilityCatalog;
82
+ }
83
+ export interface CapabilityTicketGrant {
84
+ decision: PolicyDecision;
85
+ ticket?: string;
86
+ claims?: CapabilityTicketClaims;
87
+ }
88
+ export interface CapabilityTicketIntrospection {
89
+ active: boolean;
90
+ claims?: CapabilityTicketClaims;
91
+ decision?: PolicyDecision;
92
+ error?: string;
93
+ }
94
+ interface ExecutionAuthorizationTerms {
95
+ ownerAccountId: string;
96
+ coordinatorApplicationId: string;
97
+ coordinatorCredentialId: string;
98
+ actor: ActorRef;
99
+ resource: ResourceRef;
100
+ tool: string;
101
+ limits?: GrantLimit[];
102
+ expiresAt: string;
103
+ }
104
+ export type CreateExecutionAuthorizationInput = ExecutionAuthorizationTerms & ({
105
+ kind: 'direct_request';
106
+ runId: string;
107
+ stepId?: string;
108
+ automationId?: never;
109
+ maximumAutonomy: Exclude<AutonomyLevel, 'autonomous'>;
110
+ } | {
111
+ kind: 'automation';
112
+ automationId: string;
113
+ runId?: never;
114
+ stepId?: never;
115
+ maximumAutonomy: AutonomyLevel;
116
+ });
74
117
  export declare class ServerAgencyApi extends AgencyApi {
118
+ /** Live registry discovery with this service's existing capability scopes. */
119
+ serviceCatalogs(input?: {
120
+ appId?: string;
121
+ signal?: AbortSignal;
122
+ }): Promise<ServiceCapabilityCatalog[]>;
123
+ /** Existing Oxy execution authority is required; this call creates none. */
124
+ issueCapabilityTicket(input: CapabilityTicketRequest, options?: {
125
+ signal?: AbortSignal;
126
+ }): Promise<CapabilityTicketGrant>;
127
+ /** Receiving app authenticates live introspection using its own service credential. */
128
+ introspectCapabilityTicket(ticket: string, options?: {
129
+ signal?: AbortSignal;
130
+ }): Promise<CapabilityTicketIntrospection>;
131
+ /**
132
+ * Authenticates the existing user-authority route with the requester's bearer.
133
+ * The service bearer and attribution headers cannot substitute for that user.
134
+ * The token goes only to this client's configured Oxy authority; never retry.
135
+ */
136
+ createExecutionAuthorization(input: CreateExecutionAuthorizationInput, options: {
137
+ requesterToken: string;
138
+ signal?: AbortSignal;
139
+ }): Promise<CapabilityExecutionAuthorization>;
140
+ /** Revoke a direct approval on its canonical requester-authenticated lane. */
141
+ revokeExecutionAuthorization(authorizationId: string, options: {
142
+ requesterToken: string;
143
+ signal?: AbortSignal;
144
+ }): Promise<void>;
145
+ /** Independent service proof plus present requester bearer, sent only to Oxy. */
146
+ createForegroundExecutionAuthorization(input: ForegroundExecutionAuthorizationInput, options: {
147
+ requesterToken: string;
148
+ signal?: AbortSignal;
149
+ }): Promise<CapabilityExecutionAuthorization>;
75
150
  /**
76
151
  * Product backend → Oxy: trade the signed-in requester's access token for a
77
152
  * one-use assertion naming `agentId` (ADR 0025), authenticated with this
@@ -104,3 +179,4 @@ export declare class ServerReputationApi extends ReputationApi {
104
179
  */
105
180
  award(input: AwardReputationInput): Promise<ReputationTransaction>;
106
181
  }
182
+ export {};
@@ -0,0 +1,7 @@
1
+ /** Approved I03 ceiling shared by issuance and both JWT verifiers. */
2
+ export declare const OXY_SERVICE_TOKEN_MAX_LIFETIME_SECONDS = 300;
3
+ /** Call only after signature verification; cache checks alone confer no authority. */
4
+ export declare function hasBoundedServiceTokenLifetime(claims: {
5
+ iat?: unknown;
6
+ exp?: unknown;
7
+ }, nowSeconds?: number): boolean;
@@ -37,6 +37,8 @@ export interface SessionClientHost {
37
37
  getCurrentAccountId(): string | null;
38
38
  }
39
39
  export interface SessionClientOptions {
40
+ /** Await durable local logout intent after an explicit removal or an applied nonempty→empty device transition. */
41
+ onFullExplicitSignOut?: () => Promise<void>;
40
42
  transport?: TokenTransport;
41
43
  /**
42
44
  * Invoked when an APPLIED state has zero accounts — i.e. a device
@@ -107,6 +109,8 @@ export declare class SessionClient {
107
109
  protected socket: MinimalSocket | null;
108
110
  private tokenUnsub;
109
111
  private started;
112
+ private lifecycleGeneration;
113
+ private fullSignOutPersistence;
110
114
  /** Same-origin cross-tab state-propagation channel; null on platforms without BroadcastChannel. */
111
115
  private channel;
112
116
  /** App-facing subscriptions to named server-pushed socket events. */
@@ -114,6 +118,10 @@ export declare class SessionClient {
114
118
  /** Event names already bound on the CURRENT socket instance. */
115
119
  private readonly boundServerEvents;
116
120
  constructor(host: SessionClientHost, options?: SessionClientOptions);
121
+ private persistFullSignOut;
122
+ private requestDevice;
123
+ /** Stop and discard only this client's local device projection; no server revocation. */
124
+ resetLocalState(): void;
117
125
  getState(): DeviceSessionState | null;
118
126
  /**
119
127
  * The device directory, or `null` when this client has never read one.