@oxy.so/core 4.2.0 → 4.4.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.
@@ -395,6 +395,17 @@ class OxyInferenceClient {
395
395
  const body = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'GET', `/v1/generations/${encodeURIComponent(id)}`, options);
396
396
  return body.data;
397
397
  }
398
+ /**
399
+ * Recover the existing record using the original credential and key. This
400
+ * performs only GET; 404 is unknown/pending, never proof of zero cost and
401
+ * never an instruction to repeat decide(). Lost answers are not retained.
402
+ */
403
+ async getGenerationRecordByIdempotencyKey(key, options = {}) {
404
+ if (key.length === 0 || key.length > 128)
405
+ throw new Error('Invalid original idempotency key.');
406
+ const body = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'GET', '/v1/generations/by-idempotency-key', { ...options, idempotencyKey: key });
407
+ return body.data;
408
+ }
398
409
  }
399
410
  exports.OxyInferenceClient = OxyInferenceClient;
400
411
  _OxyInferenceClient_baseURL = new WeakMap(), _OxyInferenceClient_credential = new WeakMap(), _OxyInferenceClient_fetch = new WeakMap(), _OxyInferenceClient_instances = new WeakSet(), _OxyInferenceClient_bearer =
@@ -162,6 +162,12 @@ class OxyServer extends OxyServices_1.OxyServices {
162
162
  this._serverAgency = new namespaces_1.ServerAgencyApi(this.context);
163
163
  return this._serverAgency;
164
164
  }
165
+ /** Application management, plus the receiver-bound Alia machine-key lookup. */
166
+ get apps() {
167
+ if (!this._serverApps)
168
+ this._serverApps = new namespaces_1.ServerAppsApi(this.context);
169
+ return this._serverApps;
170
+ }
165
171
  /** Reputation reads, plus `award`. */
166
172
  get reputation() {
167
173
  if (!this._serverReputation)
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OXY_ALIA_MACHINE_SCOPES = exports.OXY_ALIA_RESOURCE_APPLICATION_ID = void 0;
4
+ exports.isOxyAliaMachinePrincipal = isOxyAliaMachinePrincipal;
5
+ exports.getOxyAliaMachineCredentialBearer = getOxyAliaMachineCredentialBearer;
6
+ exports.createOxyAliaMachineCredentialAuth = createOxyAliaMachineCredentialAuth;
7
+ /** Stable resource identity, shared with the Oxy control plane. Not a display name. */
8
+ exports.OXY_ALIA_RESOURCE_APPLICATION_ID = '6a2f851751b784a86fd0e934';
9
+ exports.OXY_ALIA_MACHINE_SCOPES = ['alia:chat', 'inference:invoke'];
10
+ function id(value) {
11
+ return typeof value === 'string' && value.length > 0 && value.length <= 128 && value.trim() === value;
12
+ }
13
+ /** Strict response boundary: extra user/tier fields cannot smuggle authority. */
14
+ function isOxyAliaMachinePrincipal(value) {
15
+ if (!value || typeof value !== 'object' || Array.isArray(value))
16
+ return false;
17
+ const p = value;
18
+ const keys = ['kind', 'audience', 'applicationId', 'credentialId', 'ownerAccountId', 'environment', 'scopes'];
19
+ return Object.keys(p).length === keys.length && keys.every(key => Object.prototype.hasOwnProperty.call(p, key))
20
+ && p.kind === 'machine' && p.audience === exports.OXY_ALIA_RESOURCE_APPLICATION_ID
21
+ && id(p.applicationId) && id(p.credentialId) && id(p.ownerAccountId)
22
+ && typeof p.environment === 'string' && ['production', 'staging', 'development'].includes(p.environment)
23
+ && Array.isArray(p.scopes) && p.scopes.length === 2
24
+ && exports.OXY_ALIA_MACHINE_SCOPES.every(scope => p.scopes.includes(scope));
25
+ }
26
+ // Keep the forwarding credential out of enumerable request/principal objects,
27
+ // logs, response serialization and persistent SDK/session state. No cache.
28
+ const forwardingCredentials = new WeakMap();
29
+ /** Only for the request-scoped Oxy inference client; never for a user API. */
30
+ function getOxyAliaMachineCredentialBearer(req) {
31
+ const bound = forwardingCredentials.get(req);
32
+ return bound && req.machineCredential === bound.principal ? bound.bearer : null;
33
+ }
34
+ /** Opt into Alia's app-only chat lane on its two chat routes, never globally. */
35
+ function createOxyAliaMachineCredentialAuth(server, options) {
36
+ return async (req, res, next) => {
37
+ const auth = req.headers.authorization;
38
+ const token = typeof auth === 'string' && auth.startsWith('Bearer ') ? auth.slice(7) : '';
39
+ if (!token.startsWith('oxy_sk_') || token.length > 2048) {
40
+ res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
41
+ return;
42
+ }
43
+ const existing = req;
44
+ if (existing.user || existing.userId || existing.serviceApp || existing.oxyRequester
45
+ || req.machineCredential
46
+ || req.headers['x-oxy-user-id'] !== undefined || req.headers['x-oxy-requester-assertion'] !== undefined) {
47
+ res.status(403).json({ error: 'MACHINE_USER_DELEGATION_UNSUPPORTED' });
48
+ return;
49
+ }
50
+ try {
51
+ const answer = await server.apps.introspectAliaMachineCredential(token);
52
+ if (!answer.active || !isOxyAliaMachinePrincipal(answer.principal) || answer.principal.environment !== options.environment) {
53
+ res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
54
+ return;
55
+ }
56
+ const principal = Object.freeze({ ...answer.principal, scopes: Object.freeze(['alia:chat', 'inference:invoke']) });
57
+ req.machineCredential = principal;
58
+ forwardingCredentials.set(req, { principal, bearer: token });
59
+ next();
60
+ }
61
+ catch {
62
+ // Transport errors may contain request data; never expose or log them.
63
+ res.status(503).json({ error: 'MACHINE_CREDENTIAL_VERIFIER_UNAVAILABLE' });
64
+ }
65
+ };
66
+ }
@@ -16,8 +16,8 @@
16
16
  * ```
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.readCapabilityAuthorization = exports.issueCapabilityTicket = exports.inputSatisfiesCapabilityLimits = exports.createCapabilityTicketMiddleware = exports.CapabilityTicketError = exports.OXY_CSP_BASELINE = exports.inlineScriptCspHash = exports.formatOxyCspPolicy = exports.extractInlineScripts = exports.cspSourcesFor = exports.createOxySecurityHeaders = exports.buildOxyPagesHeaders = exports.buildOxyCspDirectives = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.OXY_SERVICE_ENVIRONMENTS = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getRequiredOxyBillingPrincipal = exports.getOxyUserId = exports.getOxyRequestAttribution = exports.getOxyDelegatedUserId = exports.getOxyBillingPrincipal = exports.getOxyAuthRefusal = exports.getOxyActor = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = exports.OXY_ACCOUNT_DELETED_EVENT_URI = exports.OxyAccountEventError = exports.ServerReputationApi = exports.ServerNotificationsApi = exports.ServerLinkedAccountsApi = exports.ServerAssetsApi = exports.ServerAgencyApi = exports.ANONYMOUS_SERVICE_TOKEN_RETRY_MS = exports.ServiceCredentialMismatchError = exports.OxyServer = void 0;
20
- exports.signInAgentAccount = exports.hasBoundedServiceTokenLifetime = exports.OXY_SERVICE_TOKEN_MAX_LIFETIME_SECONDS = exports.requestWorkloadServiceToken = exports.canAttestWorkloadIdentity = exports.withoutNodeHttpObservation = exports.observeNodeHttp = exports.createEcosystemTraffic = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.registrableApex = exports.OXY_IDENTITY_CACHE_PREFIXES = exports.oxyUserByIdCacheKey = exports.evictOxyIdentityCache = exports.publishOxyUserInvalidation = exports.createOxyUserInvalidationHandler = exports.createLiveCapabilityTicketVerifier = exports.verifySecret = exports.verifyOxyRequesterAssertion = exports.signOxyRequesterAssertion = exports.readOxyRequesterAssertionKeyId = exports.oxyRequesterAssertionClaimsSchema = exports.OxyRequesterAssertionError = exports.OXY_REQUESTER_ASSERTION_TYPE = exports.OXY_REQUESTER_ASSERTION_MAX_TTL_SECONDS = exports.OXY_REQUESTER_ASSERTION_HEADER = exports.OXY_REQUESTER_ASSERTION_DEFAULT_ISSUER = exports.createOxyRequesterAssertionAuth = exports.createOxyJwksKeyResolver = exports.verifyCapabilityTicket = void 0;
19
+ exports.inlineScriptCspHash = exports.formatOxyCspPolicy = exports.extractInlineScripts = exports.cspSourcesFor = exports.createOxySecurityHeaders = exports.buildOxyPagesHeaders = exports.buildOxyCspDirectives = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.OXY_SERVICE_ENVIRONMENTS = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getRequiredOxyBillingPrincipal = exports.getOxyUserId = exports.getOxyRequestAttribution = exports.getOxyDelegatedUserId = exports.getOxyBillingPrincipal = exports.getOxyAuthRefusal = exports.getOxyActor = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = exports.OXY_ACCOUNT_DELETED_EVENT_URI = exports.OxyAccountEventError = exports.OXY_ALIA_MACHINE_SCOPES = exports.OXY_ALIA_RESOURCE_APPLICATION_ID = exports.isOxyAliaMachinePrincipal = exports.getOxyAliaMachineCredentialBearer = exports.createOxyAliaMachineCredentialAuth = exports.ServerReputationApi = exports.ServerNotificationsApi = exports.ServerLinkedAccountsApi = exports.ServerAssetsApi = exports.ServerAgencyApi = exports.ServerAppsApi = exports.ANONYMOUS_SERVICE_TOKEN_RETRY_MS = exports.ServiceCredentialMismatchError = exports.OxyServer = void 0;
20
+ exports.signInAgentAccount = exports.hasBoundedServiceTokenLifetime = exports.OXY_SERVICE_TOKEN_MAX_LIFETIME_SECONDS = exports.requestWorkloadServiceToken = exports.canAttestWorkloadIdentity = exports.withoutNodeHttpObservation = exports.observeNodeHttp = exports.createEcosystemTraffic = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isLoopbackOrigin = exports.registrableApex = exports.OXY_IDENTITY_CACHE_PREFIXES = exports.oxyUserByIdCacheKey = exports.evictOxyIdentityCache = exports.publishOxyUserInvalidation = exports.createOxyUserInvalidationHandler = exports.createLiveCapabilityTicketVerifier = exports.verifySecret = exports.verifyOxyRequesterAssertion = exports.signOxyRequesterAssertion = exports.readOxyRequesterAssertionKeyId = exports.oxyRequesterAssertionClaimsSchema = exports.OxyRequesterAssertionError = exports.OXY_REQUESTER_ASSERTION_TYPE = exports.OXY_REQUESTER_ASSERTION_MAX_TTL_SECONDS = exports.OXY_REQUESTER_ASSERTION_HEADER = exports.OXY_REQUESTER_ASSERTION_DEFAULT_ISSUER = exports.createOxyRequesterAssertionAuth = exports.createOxyJwksKeyResolver = exports.verifyCapabilityTicket = exports.readCapabilityAuthorization = exports.issueCapabilityTicket = exports.inputSatisfiesCapabilityLimits = exports.createCapabilityTicketMiddleware = exports.CapabilityTicketError = exports.OXY_CSP_BASELINE = void 0;
21
21
  // The backend client: OxyServices plus the service-token lane, middleware,
22
22
  // account events and the service-only namespace methods.
23
23
  var OxyServer_1 = require("./OxyServer");
@@ -25,11 +25,18 @@ Object.defineProperty(exports, "OxyServer", { enumerable: true, get: function ()
25
25
  Object.defineProperty(exports, "ServiceCredentialMismatchError", { enumerable: true, get: function () { return OxyServer_1.ServiceCredentialMismatchError; } });
26
26
  Object.defineProperty(exports, "ANONYMOUS_SERVICE_TOKEN_RETRY_MS", { enumerable: true, get: function () { return OxyServer_1.ANONYMOUS_SERVICE_TOKEN_RETRY_MS; } });
27
27
  var namespaces_1 = require("./namespaces");
28
+ Object.defineProperty(exports, "ServerAppsApi", { enumerable: true, get: function () { return namespaces_1.ServerAppsApi; } });
28
29
  Object.defineProperty(exports, "ServerAgencyApi", { enumerable: true, get: function () { return namespaces_1.ServerAgencyApi; } });
29
30
  Object.defineProperty(exports, "ServerAssetsApi", { enumerable: true, get: function () { return namespaces_1.ServerAssetsApi; } });
30
31
  Object.defineProperty(exports, "ServerLinkedAccountsApi", { enumerable: true, get: function () { return namespaces_1.ServerLinkedAccountsApi; } });
31
32
  Object.defineProperty(exports, "ServerNotificationsApi", { enumerable: true, get: function () { return namespaces_1.ServerNotificationsApi; } });
32
33
  Object.defineProperty(exports, "ServerReputationApi", { enumerable: true, get: function () { return namespaces_1.ServerReputationApi; } });
34
+ var aliaMachineCredential_1 = require("./aliaMachineCredential");
35
+ Object.defineProperty(exports, "createOxyAliaMachineCredentialAuth", { enumerable: true, get: function () { return aliaMachineCredential_1.createOxyAliaMachineCredentialAuth; } });
36
+ Object.defineProperty(exports, "getOxyAliaMachineCredentialBearer", { enumerable: true, get: function () { return aliaMachineCredential_1.getOxyAliaMachineCredentialBearer; } });
37
+ Object.defineProperty(exports, "isOxyAliaMachinePrincipal", { enumerable: true, get: function () { return aliaMachineCredential_1.isOxyAliaMachinePrincipal; } });
38
+ Object.defineProperty(exports, "OXY_ALIA_RESOURCE_APPLICATION_ID", { enumerable: true, get: function () { return aliaMachineCredential_1.OXY_ALIA_RESOURCE_APPLICATION_ID; } });
39
+ Object.defineProperty(exports, "OXY_ALIA_MACHINE_SCOPES", { enumerable: true, get: function () { return aliaMachineCredential_1.OXY_ALIA_MACHINE_SCOPES; } });
33
40
  var middleware_1 = require("./middleware");
34
41
  Object.defineProperty(exports, "OxyAccountEventError", { enumerable: true, get: function () { return middleware_1.OxyAccountEventError; } });
35
42
  Object.defineProperty(exports, "OXY_ACCOUNT_DELETED_EVENT_URI", { enumerable: true, get: function () { return middleware_1.OXY_ACCOUNT_DELETED_EVENT_URI; } });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ServerReputationApi = exports.ServerAgencyApi = exports.ServerLinkedAccountsApi = exports.ServerNotificationsApi = exports.ServerAssetsApi = void 0;
3
+ exports.ServerReputationApi = exports.ServerAgencyApi = exports.ServerLinkedAccountsApi = exports.ServerNotificationsApi = exports.ServerAssetsApi = exports.ServerAppsApi = void 0;
4
4
  /**
5
5
  * The server-side halves of the namespaces: what exists only with a service
6
6
  * credential. `OxyServer` returns these from its namespace getters, so
@@ -13,6 +13,8 @@ const notifications_1 = require("../api/notifications");
13
13
  const linkedAccounts_1 = require("../api/linkedAccounts");
14
14
  const agency_1 = require("../api/agency");
15
15
  const reputation_1 = require("../api/reputation");
16
+ const apps_1 = require("../api/apps");
17
+ const aliaMachineCredential_1 = require("./aliaMachineCredential");
16
18
  const OxyServices_errors_1 = require("../OxyServices.errors");
17
19
  const errorUtils_1 = require("../utils/errorUtils");
18
20
  const logger_1 = require("../logger");
@@ -30,6 +32,28 @@ function lane(ctx) {
30
32
  }
31
33
  return ctx.service;
32
34
  }
35
+ class ServerAppsApi extends apps_1.AppsApi {
36
+ constructor(serverCtx) {
37
+ super(serverCtx);
38
+ this.serverCtx = serverCtx;
39
+ }
40
+ /** Live receiver-bound lookup using THIS resource server's own service token. */
41
+ async introspectAliaMachineCredential(token) {
42
+ if (!token.startsWith('oxy_sk_') || token.length > 2048)
43
+ return { active: false };
44
+ const answer = await lane(this.serverCtx).request('POST', '/internal/alia/machine-credentials/introspect', { token }, { cache: false, retry: false });
45
+ if (!answer || typeof answer !== 'object' || Array.isArray(answer))
46
+ throw new Error('Invalid machine credential verdict');
47
+ const record = answer;
48
+ if (record.active === false && Object.keys(record).length === 1)
49
+ return { active: false };
50
+ if (record.active !== true || Object.keys(record).length !== 2 || !(0, aliaMachineCredential_1.isOxyAliaMachinePrincipal)(record.principal)) {
51
+ throw new Error('Invalid machine credential verdict');
52
+ }
53
+ return { active: true, principal: record.principal };
54
+ }
55
+ }
56
+ exports.ServerAppsApi = ServerAppsApi;
33
57
  function chunk(items, size) {
34
58
  const chunks = [];
35
59
  for (let i = 0; i < items.length; i += size)
@@ -390,6 +390,17 @@ export class OxyInferenceClient {
390
390
  const body = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'GET', `/v1/generations/${encodeURIComponent(id)}`, options);
391
391
  return body.data;
392
392
  }
393
+ /**
394
+ * Recover the existing record using the original credential and key. This
395
+ * performs only GET; 404 is unknown/pending, never proof of zero cost and
396
+ * never an instruction to repeat decide(). Lost answers are not retained.
397
+ */
398
+ async getGenerationRecordByIdempotencyKey(key, options = {}) {
399
+ if (key.length === 0 || key.length > 128)
400
+ throw new Error('Invalid original idempotency key.');
401
+ const body = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'GET', '/v1/generations/by-idempotency-key', { ...options, idempotencyKey: key });
402
+ return body.data;
403
+ }
393
404
  }
394
405
  _OxyInferenceClient_baseURL = new WeakMap(), _OxyInferenceClient_credential = new WeakMap(), _OxyInferenceClient_fetch = new WeakMap(), _OxyInferenceClient_instances = new WeakSet(), _OxyInferenceClient_bearer =
395
406
  /** The bearer for this request, from whichever lane was configured. */
@@ -43,7 +43,7 @@ import { OxyServices } from '../OxyServices.js';
43
43
  import { logger } from '../logger/index.js';
44
44
  import { canAttestWorkloadIdentity, requestWorkloadServiceToken } from './workloadIdentity.js';
45
45
  import { createOxyMiddleware, OXY_ACCOUNT_DELETED_EVENT_URI, OXY_JWT_ISSUER, OxyAccountEventError, parseJsonSegment, resolveServiceTokenPublicKey, } from './middleware.js';
46
- import { ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces.js';
46
+ import { ServerAppsApi, ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces.js';
47
47
  /**
48
48
  * How long a failed service-token mint sends session-less requests anonymous
49
49
  * before the next attempt, under `serviceIdentity: 'when-anonymous'`.
@@ -158,6 +158,12 @@ export class OxyServer extends OxyServices {
158
158
  this._serverAgency = new ServerAgencyApi(this.context);
159
159
  return this._serverAgency;
160
160
  }
161
+ /** Application management, plus the receiver-bound Alia machine-key lookup. */
162
+ get apps() {
163
+ if (!this._serverApps)
164
+ this._serverApps = new ServerAppsApi(this.context);
165
+ return this._serverApps;
166
+ }
161
167
  /** Reputation reads, plus `award`. */
162
168
  get reputation() {
163
169
  if (!this._serverReputation)
@@ -0,0 +1,60 @@
1
+ /** Stable resource identity, shared with the Oxy control plane. Not a display name. */
2
+ export const OXY_ALIA_RESOURCE_APPLICATION_ID = '6a2f851751b784a86fd0e934';
3
+ export const OXY_ALIA_MACHINE_SCOPES = ['alia:chat', 'inference:invoke'];
4
+ function id(value) {
5
+ return typeof value === 'string' && value.length > 0 && value.length <= 128 && value.trim() === value;
6
+ }
7
+ /** Strict response boundary: extra user/tier fields cannot smuggle authority. */
8
+ export function isOxyAliaMachinePrincipal(value) {
9
+ if (!value || typeof value !== 'object' || Array.isArray(value))
10
+ return false;
11
+ const p = value;
12
+ const keys = ['kind', 'audience', 'applicationId', 'credentialId', 'ownerAccountId', 'environment', 'scopes'];
13
+ return Object.keys(p).length === keys.length && keys.every(key => Object.prototype.hasOwnProperty.call(p, key))
14
+ && p.kind === 'machine' && p.audience === OXY_ALIA_RESOURCE_APPLICATION_ID
15
+ && id(p.applicationId) && id(p.credentialId) && id(p.ownerAccountId)
16
+ && typeof p.environment === 'string' && ['production', 'staging', 'development'].includes(p.environment)
17
+ && Array.isArray(p.scopes) && p.scopes.length === 2
18
+ && OXY_ALIA_MACHINE_SCOPES.every(scope => p.scopes.includes(scope));
19
+ }
20
+ // Keep the forwarding credential out of enumerable request/principal objects,
21
+ // logs, response serialization and persistent SDK/session state. No cache.
22
+ const forwardingCredentials = new WeakMap();
23
+ /** Only for the request-scoped Oxy inference client; never for a user API. */
24
+ export function getOxyAliaMachineCredentialBearer(req) {
25
+ const bound = forwardingCredentials.get(req);
26
+ return bound && req.machineCredential === bound.principal ? bound.bearer : null;
27
+ }
28
+ /** Opt into Alia's app-only chat lane on its two chat routes, never globally. */
29
+ export function createOxyAliaMachineCredentialAuth(server, options) {
30
+ return async (req, res, next) => {
31
+ const auth = req.headers.authorization;
32
+ const token = typeof auth === 'string' && auth.startsWith('Bearer ') ? auth.slice(7) : '';
33
+ if (!token.startsWith('oxy_sk_') || token.length > 2048) {
34
+ res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
35
+ return;
36
+ }
37
+ const existing = req;
38
+ if (existing.user || existing.userId || existing.serviceApp || existing.oxyRequester
39
+ || req.machineCredential
40
+ || req.headers['x-oxy-user-id'] !== undefined || req.headers['x-oxy-requester-assertion'] !== undefined) {
41
+ res.status(403).json({ error: 'MACHINE_USER_DELEGATION_UNSUPPORTED' });
42
+ return;
43
+ }
44
+ try {
45
+ const answer = await server.apps.introspectAliaMachineCredential(token);
46
+ if (!answer.active || !isOxyAliaMachinePrincipal(answer.principal) || answer.principal.environment !== options.environment) {
47
+ res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
48
+ return;
49
+ }
50
+ const principal = Object.freeze({ ...answer.principal, scopes: Object.freeze(['alia:chat', 'inference:invoke']) });
51
+ req.machineCredential = principal;
52
+ forwardingCredentials.set(req, { principal, bearer: token });
53
+ next();
54
+ }
55
+ catch {
56
+ // Transport errors may contain request data; never expose or log them.
57
+ res.status(503).json({ error: 'MACHINE_CREDENTIAL_VERIFIER_UNAVAILABLE' });
58
+ }
59
+ };
60
+ }
@@ -17,7 +17,8 @@
17
17
  // The backend client: OxyServices plus the service-token lane, middleware,
18
18
  // account events and the service-only namespace methods.
19
19
  export { OxyServer, ServiceCredentialMismatchError, ANONYMOUS_SERVICE_TOKEN_RETRY_MS } from './OxyServer.js';
20
- export { ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces.js';
20
+ export { ServerAppsApi, ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces.js';
21
+ export { createOxyAliaMachineCredentialAuth, getOxyAliaMachineCredentialBearer, isOxyAliaMachinePrincipal, OXY_ALIA_RESOURCE_APPLICATION_ID, OXY_ALIA_MACHINE_SCOPES, } from './aliaMachineCredential.js';
21
22
  export { OxyAccountEventError, OXY_ACCOUNT_DELETED_EVENT_URI } from './middleware.js';
22
23
  export { createOptionalOxyAuth, createOxyAuthMiddleware, getOxyActor, getOxyAuthRefusal, getOxyBillingPrincipal, getOxyDelegatedUserId, getOxyRequestAttribution, getOxyUserId, getRequiredOxyBillingPrincipal, getRequiredOxyUserId, isOxyAuthenticated, requireOxyAuth, OXY_SERVICE_ENVIRONMENTS, } from './auth.js';
23
24
  export { createOxyRateLimit } from './rateLimit.js';
@@ -10,6 +10,8 @@ import { NotificationsApi } from '../api/notifications.js';
10
10
  import { LinkedAccountsApi } from '../api/linkedAccounts.js';
11
11
  import { AgencyApi } from '../api/agency.js';
12
12
  import { ReputationApi } from '../api/reputation.js';
13
+ import { AppsApi } from '../api/apps.js';
14
+ import { isOxyAliaMachinePrincipal } from './aliaMachineCredential.js';
13
15
  import { ServiceAssetMetadataError, ServiceLinkedDownloadUrlError } from '../OxyServices.errors.js';
14
16
  import { extractErrorStatus } from '../utils/errorUtils.js';
15
17
  import { logger } from '../logger/index.js';
@@ -27,6 +29,27 @@ function lane(ctx) {
27
29
  }
28
30
  return ctx.service;
29
31
  }
32
+ export class ServerAppsApi extends AppsApi {
33
+ constructor(serverCtx) {
34
+ super(serverCtx);
35
+ this.serverCtx = serverCtx;
36
+ }
37
+ /** Live receiver-bound lookup using THIS resource server's own service token. */
38
+ async introspectAliaMachineCredential(token) {
39
+ if (!token.startsWith('oxy_sk_') || token.length > 2048)
40
+ return { active: false };
41
+ const answer = await lane(this.serverCtx).request('POST', '/internal/alia/machine-credentials/introspect', { token }, { cache: false, retry: false });
42
+ if (!answer || typeof answer !== 'object' || Array.isArray(answer))
43
+ throw new Error('Invalid machine credential verdict');
44
+ const record = answer;
45
+ if (record.active === false && Object.keys(record).length === 1)
46
+ return { active: false };
47
+ if (record.active !== true || Object.keys(record).length !== 2 || !isOxyAliaMachinePrincipal(record.principal)) {
48
+ throw new Error('Invalid machine credential verdict');
49
+ }
50
+ return { active: true, principal: record.principal };
51
+ }
52
+ }
30
53
  function chunk(items, size) {
31
54
  const chunks = [];
32
55
  for (let i = 0; i < items.length; i += size)
@@ -382,4 +382,13 @@ export declare class OxyInferenceClient {
382
382
  signal?: AbortSignal;
383
383
  delegatedUserId?: string;
384
384
  }): Promise<OxyGenerationRecord>;
385
+ /**
386
+ * Recover the existing record using the original credential and key. This
387
+ * performs only GET; 404 is unknown/pending, never proof of zero cost and
388
+ * never an instruction to repeat decide(). Lost answers are not retained.
389
+ */
390
+ getGenerationRecordByIdempotencyKey(key: string, options?: {
391
+ signal?: AbortSignal;
392
+ delegatedUserId?: string;
393
+ }): Promise<OxyGenerationRecord>;
385
394
  }
@@ -2,7 +2,7 @@ import { OxyServices, type OxyConfig } from '../OxyServices';
2
2
  import type { HttpMethod } from '../client/context';
3
3
  import type { RequestOptions } from '../HttpService';
4
4
  import { type OxyAccountEvent, type OxyAccountEventFeedPage, type OxyMiddleware, type ServiceActingAsVerification, type VerifyAccountEventOptions } from './middleware';
5
- import { ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi } from './namespaces';
5
+ import { ServerAppsApi, ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi } from './namespaces';
6
6
  export interface OxyServerConfig extends OxyConfig {
7
7
  /** This service's application credential. Omit to use workload identity (ADR 0026). */
8
8
  serviceAuth?: {
@@ -87,6 +87,7 @@ export declare class OxyServer extends OxyServices {
87
87
  private _serverNotifications?;
88
88
  private _serverLinkedAccounts?;
89
89
  private _serverAgency?;
90
+ private _serverApps?;
90
91
  private _serverReputation?;
91
92
  /** Files, plus the service-only metadata and linked-URL lookups. */
92
93
  get assets(): ServerAssetsApi;
@@ -96,6 +97,8 @@ export declare class OxyServer extends OxyServices {
96
97
  get linkedAccounts(): ServerLinkedAccountsApi;
97
98
  /** Delegated capabilities, plus `introspectRequesterAssertion`. */
98
99
  get agency(): ServerAgencyApi;
100
+ /** Application management, plus the receiver-bound Alia machine-key lookup. */
101
+ get apps(): ServerAppsApi;
99
102
  /** Reputation reads, plus `award`. */
100
103
  get reputation(): ServerReputationApi;
101
104
  /**
@@ -0,0 +1,37 @@
1
+ import type { Request, RequestHandler } from 'express';
2
+ import type { OxyServiceEnvironment } from './auth';
3
+ /** Stable resource identity, shared with the Oxy control plane. Not a display name. */
4
+ export declare const OXY_ALIA_RESOURCE_APPLICATION_ID = "6a2f851751b784a86fd0e934";
5
+ export declare const OXY_ALIA_MACHINE_SCOPES: readonly ["alia:chat", "inference:invoke"];
6
+ /** App-only assistant turn: no human subject, service tier or delegated authority. */
7
+ export interface OxyAliaMachinePrincipal {
8
+ readonly kind: 'machine';
9
+ readonly audience: typeof OXY_ALIA_RESOURCE_APPLICATION_ID;
10
+ readonly applicationId: string;
11
+ readonly credentialId: string;
12
+ readonly ownerAccountId: string;
13
+ readonly environment: OxyServiceEnvironment;
14
+ readonly scopes: readonly ['alia:chat', 'inference:invoke'];
15
+ }
16
+ export type AliaMachineCredentialIntrospection = {
17
+ active: false;
18
+ } | {
19
+ active: true;
20
+ principal: OxyAliaMachinePrincipal;
21
+ };
22
+ export interface OxyAliaMachineCredentialHost {
23
+ readonly apps: {
24
+ introspectAliaMachineCredential(token: string): Promise<AliaMachineCredentialIntrospection>;
25
+ };
26
+ }
27
+ export interface OxyAliaMachineRequest extends Request {
28
+ machineCredential?: OxyAliaMachinePrincipal;
29
+ }
30
+ /** Strict response boundary: extra user/tier fields cannot smuggle authority. */
31
+ export declare function isOxyAliaMachinePrincipal(value: unknown): value is OxyAliaMachinePrincipal;
32
+ /** Only for the request-scoped Oxy inference client; never for a user API. */
33
+ export declare function getOxyAliaMachineCredentialBearer(req: Request): string | null;
34
+ /** Opt into Alia's app-only chat lane on its two chat routes, never globally. */
35
+ export declare function createOxyAliaMachineCredentialAuth(server: OxyAliaMachineCredentialHost, options: {
36
+ environment: OxyServiceEnvironment;
37
+ }): RequestHandler;
@@ -16,7 +16,9 @@
16
16
  */
17
17
  export { OxyServer, ServiceCredentialMismatchError, ANONYMOUS_SERVICE_TOKEN_RETRY_MS } from './OxyServer';
18
18
  export type { OxyServerConfig, ServiceTokenResponse } from './OxyServer';
19
- export { ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces';
19
+ export { ServerAppsApi, ServerAgencyApi, ServerAssetsApi, ServerLinkedAccountsApi, ServerNotificationsApi, ServerReputationApi, } from './namespaces';
20
+ export { createOxyAliaMachineCredentialAuth, getOxyAliaMachineCredentialBearer, isOxyAliaMachinePrincipal, OXY_ALIA_RESOURCE_APPLICATION_ID, OXY_ALIA_MACHINE_SCOPES, } from './aliaMachineCredential';
21
+ export type { AliaMachineCredentialIntrospection, OxyAliaMachinePrincipal, OxyAliaMachineRequest, OxyAliaMachineCredentialHost, } from './aliaMachineCredential';
20
22
  export type { CapabilityTicketGrant, CapabilityTicketIntrospection, CreateExecutionAuthorizationInput, ServiceCapabilityCatalog, } from './namespaces';
21
23
  export { OxyAccountEventError, OXY_ACCOUNT_DELETED_EVENT_URI } from './middleware';
22
24
  export type { AuthMiddlewareOptions, OxyAccountEvent, OxyAccountEventFeedItem, OxyAccountEventFeedPage, OxyMiddleware, ServiceActingAsVerification, ServiceApp, VerifyAccountEventOptions, } from './middleware';
@@ -5,12 +5,21 @@
5
5
  * `oxy.assets`.
6
6
  */
7
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
+ import type { OxyContext } from '../client/context';
8
9
  import type { Notification, ServiceAssetMetadata, ServiceAssetMetadataBySha, ServiceLinkedDownloadUrl } from '../models/interfaces';
9
10
  import { AssetsApi } from '../api/assets';
10
11
  import { NotificationsApi } from '../api/notifications';
11
12
  import { LinkedAccountsApi } from '../api/linkedAccounts';
12
13
  import { AgencyApi, type CapabilityExecutionAuthorization, type RequesterAssertionGrant, type RequesterAssertionIntrospection } from '../api/agency';
13
14
  import { ReputationApi } from '../api/reputation';
15
+ import { AppsApi } from '../api/apps';
16
+ import { type AliaMachineCredentialIntrospection } from './aliaMachineCredential';
17
+ export declare class ServerAppsApi extends AppsApi {
18
+ private readonly serverCtx;
19
+ constructor(serverCtx: OxyContext);
20
+ /** Live receiver-bound lookup using THIS resource server's own service token. */
21
+ introspectAliaMachineCredential(token: string): Promise<AliaMachineCredentialIntrospection>;
22
+ }
14
23
  export declare class ServerAssetsApi extends AssetsApi {
15
24
  /**
16
25
  * Resolve many asset ids to their content-addressed metadata (`sha256`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/core",
3
- "version": "4.2.0",
3
+ "version": "4.4.0",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -179,7 +179,7 @@
179
179
  "@oxy.so/telemetry": "^1.2.0",
180
180
  "@noble/ciphers": "^1.3.0",
181
181
  "@noble/hashes": "^1.8.0",
182
- "@oxy.so/contracts": "^4.9.0",
182
+ "@oxy.so/contracts": "^4.9.1",
183
183
  "@oxy.so/protocol": "^1.2.2",
184
184
  "@scure/bip39": "^1.6.0",
185
185
  "buffer": "^6.0.3",
@@ -712,6 +712,22 @@ export class OxyInferenceClient {
712
712
  return body.data;
713
713
  }
714
714
 
715
+ /**
716
+ * Recover the existing record using the original credential and key. This
717
+ * performs only GET; 404 is unknown/pending, never proof of zero cost and
718
+ * never an instruction to repeat decide(). Lost answers are not retained.
719
+ */
720
+ async getGenerationRecordByIdempotencyKey(
721
+ key: string,
722
+ options: { signal?: AbortSignal; delegatedUserId?: string } = {},
723
+ ): Promise<OxyGenerationRecord> {
724
+ if (key.length === 0 || key.length > 128) throw new Error('Invalid original idempotency key.');
725
+ const body = await this.#request<{ data: OxyGenerationRecord }>(
726
+ 'GET', '/v1/generations/by-idempotency-key', { ...options, idempotencyKey: key },
727
+ );
728
+ return body.data;
729
+ }
730
+
715
731
  /** The bearer for this request, from whichever lane was configured. */
716
732
  async #bearer(): Promise<string> {
717
733
  const value =
@@ -59,6 +59,7 @@ import {
59
59
  type VerifyAccountEventOptions,
60
60
  } from './middleware';
61
61
  import {
62
+ ServerAppsApi,
62
63
  ServerAgencyApi,
63
64
  ServerAssetsApi,
64
65
  ServerLinkedAccountsApi,
@@ -217,6 +218,7 @@ export class OxyServer extends OxyServices {
217
218
  private _serverNotifications?: ServerNotificationsApi;
218
219
  private _serverLinkedAccounts?: ServerLinkedAccountsApi;
219
220
  private _serverAgency?: ServerAgencyApi;
221
+ private _serverApps?: ServerAppsApi;
220
222
  private _serverReputation?: ServerReputationApi;
221
223
 
222
224
  /** Files, plus the service-only metadata and linked-URL lookups. */
@@ -239,6 +241,11 @@ export class OxyServer extends OxyServices {
239
241
  if (!this._serverAgency) this._serverAgency = new ServerAgencyApi(this.context);
240
242
  return this._serverAgency;
241
243
  }
244
+ /** Application management, plus the receiver-bound Alia machine-key lookup. */
245
+ override get apps(): ServerAppsApi {
246
+ if (!this._serverApps) this._serverApps = new ServerAppsApi(this.context);
247
+ return this._serverApps;
248
+ }
242
249
  /** Reputation reads, plus `award`. */
243
250
  override get reputation(): ServerReputationApi {
244
251
  if (!this._serverReputation) this._serverReputation = new ServerReputationApi(this.context);
@@ -695,4 +702,3 @@ export class OxyServer extends OxyServices {
695
702
  return appId;
696
703
  }
697
704
  }
698
-
@@ -0,0 +1,94 @@
1
+ import type { Request, RequestHandler } from 'express';
2
+ import type { OxyServiceEnvironment } from './auth';
3
+
4
+ /** Stable resource identity, shared with the Oxy control plane. Not a display name. */
5
+ export const OXY_ALIA_RESOURCE_APPLICATION_ID = '6a2f851751b784a86fd0e934';
6
+ export const OXY_ALIA_MACHINE_SCOPES = ['alia:chat', 'inference:invoke'] as const;
7
+
8
+ /** App-only assistant turn: no human subject, service tier or delegated authority. */
9
+ export interface OxyAliaMachinePrincipal {
10
+ readonly kind: 'machine';
11
+ readonly audience: typeof OXY_ALIA_RESOURCE_APPLICATION_ID;
12
+ readonly applicationId: string;
13
+ readonly credentialId: string;
14
+ readonly ownerAccountId: string;
15
+ readonly environment: OxyServiceEnvironment;
16
+ readonly scopes: readonly ['alia:chat', 'inference:invoke'];
17
+ }
18
+
19
+ export type AliaMachineCredentialIntrospection =
20
+ | { active: false }
21
+ | { active: true; principal: OxyAliaMachinePrincipal };
22
+
23
+ export interface OxyAliaMachineCredentialHost {
24
+ readonly apps: {
25
+ introspectAliaMachineCredential(token: string): Promise<AliaMachineCredentialIntrospection>;
26
+ };
27
+ }
28
+
29
+ export interface OxyAliaMachineRequest extends Request {
30
+ machineCredential?: OxyAliaMachinePrincipal;
31
+ }
32
+
33
+ function id(value: unknown): value is string {
34
+ return typeof value === 'string' && value.length > 0 && value.length <= 128 && value.trim() === value;
35
+ }
36
+
37
+ /** Strict response boundary: extra user/tier fields cannot smuggle authority. */
38
+ export function isOxyAliaMachinePrincipal(value: unknown): value is OxyAliaMachinePrincipal {
39
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
40
+ const p = value as Record<string, unknown>;
41
+ const keys = ['kind', 'audience', 'applicationId', 'credentialId', 'ownerAccountId', 'environment', 'scopes'];
42
+ return Object.keys(p).length === keys.length && keys.every(key => Object.prototype.hasOwnProperty.call(p, key))
43
+ && p.kind === 'machine' && p.audience === OXY_ALIA_RESOURCE_APPLICATION_ID
44
+ && id(p.applicationId) && id(p.credentialId) && id(p.ownerAccountId)
45
+ && typeof p.environment === 'string' && ['production', 'staging', 'development'].includes(p.environment)
46
+ && Array.isArray(p.scopes) && p.scopes.length === 2
47
+ && OXY_ALIA_MACHINE_SCOPES.every(scope => (p.scopes as unknown[]).includes(scope));
48
+ }
49
+
50
+ // Keep the forwarding credential out of enumerable request/principal objects,
51
+ // logs, response serialization and persistent SDK/session state. No cache.
52
+ const forwardingCredentials = new WeakMap<Request, { principal: OxyAliaMachinePrincipal; bearer: string }>();
53
+
54
+ /** Only for the request-scoped Oxy inference client; never for a user API. */
55
+ export function getOxyAliaMachineCredentialBearer(req: Request): string | null {
56
+ const bound = forwardingCredentials.get(req);
57
+ return bound && (req as OxyAliaMachineRequest).machineCredential === bound.principal ? bound.bearer : null;
58
+ }
59
+
60
+ /** Opt into Alia's app-only chat lane on its two chat routes, never globally. */
61
+ export function createOxyAliaMachineCredentialAuth(
62
+ server: OxyAliaMachineCredentialHost,
63
+ options: { environment: OxyServiceEnvironment },
64
+ ): RequestHandler {
65
+ return async (req, res, next) => {
66
+ const auth = req.headers.authorization;
67
+ const token = typeof auth === 'string' && auth.startsWith('Bearer ') ? auth.slice(7) : '';
68
+ if (!token.startsWith('oxy_sk_') || token.length > 2048) {
69
+ res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
70
+ return;
71
+ }
72
+ const existing = req as Request & { user?: unknown; userId?: unknown; serviceApp?: unknown; oxyRequester?: unknown };
73
+ if (existing.user || existing.userId || existing.serviceApp || existing.oxyRequester
74
+ || (req as OxyAliaMachineRequest).machineCredential
75
+ || req.headers['x-oxy-user-id'] !== undefined || req.headers['x-oxy-requester-assertion'] !== undefined) {
76
+ res.status(403).json({ error: 'MACHINE_USER_DELEGATION_UNSUPPORTED' });
77
+ return;
78
+ }
79
+ try {
80
+ const answer = await server.apps.introspectAliaMachineCredential(token);
81
+ if (!answer.active || !isOxyAliaMachinePrincipal(answer.principal) || answer.principal.environment !== options.environment) {
82
+ res.status(401).json({ error: 'MACHINE_CREDENTIAL_INVALID' });
83
+ return;
84
+ }
85
+ const principal = Object.freeze({ ...answer.principal, scopes: Object.freeze(['alia:chat', 'inference:invoke'] as const) });
86
+ (req as OxyAliaMachineRequest).machineCredential = principal;
87
+ forwardingCredentials.set(req, { principal, bearer: token });
88
+ next();
89
+ } catch {
90
+ // Transport errors may contain request data; never expose or log them.
91
+ res.status(503).json({ error: 'MACHINE_CREDENTIAL_VERIFIER_UNAVAILABLE' });
92
+ }
93
+ };
94
+ }
@@ -20,12 +20,26 @@
20
20
  export { OxyServer, ServiceCredentialMismatchError, ANONYMOUS_SERVICE_TOKEN_RETRY_MS } from './OxyServer';
21
21
  export type { OxyServerConfig, ServiceTokenResponse } from './OxyServer';
22
22
  export {
23
+ ServerAppsApi,
23
24
  ServerAgencyApi,
24
25
  ServerAssetsApi,
25
26
  ServerLinkedAccountsApi,
26
27
  ServerNotificationsApi,
27
28
  ServerReputationApi,
28
29
  } from './namespaces';
30
+ export {
31
+ createOxyAliaMachineCredentialAuth,
32
+ getOxyAliaMachineCredentialBearer,
33
+ isOxyAliaMachinePrincipal,
34
+ OXY_ALIA_RESOURCE_APPLICATION_ID,
35
+ OXY_ALIA_MACHINE_SCOPES,
36
+ } from './aliaMachineCredential';
37
+ export type {
38
+ AliaMachineCredentialIntrospection,
39
+ OxyAliaMachinePrincipal,
40
+ OxyAliaMachineRequest,
41
+ OxyAliaMachineCredentialHost,
42
+ } from './aliaMachineCredential';
29
43
  export type {
30
44
  CapabilityTicketGrant,
31
45
  CapabilityTicketIntrospection,
@@ -18,6 +18,8 @@ import { NotificationsApi } from '../api/notifications';
18
18
  import { LinkedAccountsApi } from '../api/linkedAccounts';
19
19
  import { AgencyApi, type CapabilityExecutionAuthorization, type RequesterAssertionGrant, type RequesterAssertionIntrospection } from '../api/agency';
20
20
  import { ReputationApi } from '../api/reputation';
21
+ import { AppsApi } from '../api/apps';
22
+ import { isOxyAliaMachinePrincipal, type AliaMachineCredentialIntrospection } from './aliaMachineCredential';
21
23
  import { ServiceAssetMetadataError, ServiceLinkedDownloadUrlError } from '../OxyServices.errors';
22
24
  import { extractErrorStatus } from '../utils/errorUtils';
23
25
  import { logger } from '../logger';
@@ -39,6 +41,25 @@ function lane(ctx: OxyContext): ServiceLane {
39
41
  return ctx.service;
40
42
  }
41
43
 
44
+ export class ServerAppsApi extends AppsApi {
45
+ constructor(private readonly serverCtx: OxyContext) { super(serverCtx); }
46
+ /** Live receiver-bound lookup using THIS resource server's own service token. */
47
+ async introspectAliaMachineCredential(token: string): Promise<AliaMachineCredentialIntrospection> {
48
+ if (!token.startsWith('oxy_sk_') || token.length > 2048) return { active: false };
49
+ const answer = await lane(this.serverCtx).request<unknown>(
50
+ 'POST', '/internal/alia/machine-credentials/introspect', { token },
51
+ { cache: false, retry: false },
52
+ );
53
+ if (!answer || typeof answer !== 'object' || Array.isArray(answer)) throw new Error('Invalid machine credential verdict');
54
+ const record = answer as Record<string, unknown>;
55
+ if (record.active === false && Object.keys(record).length === 1) return { active: false };
56
+ if (record.active !== true || Object.keys(record).length !== 2 || !isOxyAliaMachinePrincipal(record.principal)) {
57
+ throw new Error('Invalid machine credential verdict');
58
+ }
59
+ return { active: true, principal: record.principal };
60
+ }
61
+ }
62
+
42
63
  function chunk<T>(items: T[], size: number): T[][] {
43
64
  const chunks: T[][] = [];
44
65
  for (let i = 0; i < items.length; i += size) chunks.push(items.slice(i, i + size));