@oxy.so/core 1.7.3 → 1.8.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.
@@ -140,9 +140,14 @@ export class OxyInferenceClient {
140
140
  * both see the PUBLIC catalogue; only an internal/system application's
141
141
  * service token sees internal-only routes.
142
142
  *
143
- * **`[]` is a normal answer**, and is the only answer today: the catalogue
144
- * is populated by operators, and a route is not publicly exposed until
145
- * somebody has reviewed the right to resell it.
143
+ * **`[]` is a normal answer**: a route is not publicly exposed until
144
+ * somebody has reviewed the right to resell it. An official application's
145
+ * credential also sees the internal catalogue Oxy keeps in step with
146
+ * Kaana automatically.
147
+ *
148
+ * Each entry's `capabilities.reasoningEfforts` lists the efforts a request
149
+ * may name in `reasoning.effort` (empty: none), and `releasedAt`, when
150
+ * present, is the date the upstream provider published the model.
146
151
  */
147
152
  async listModels(options = {}) {
148
153
  const body = await __classPrivateFieldGet(this, _OxyInferenceClient_instances, "m", _OxyInferenceClient_request).call(this, 'GET', '/v1/models', { ...(options.signal === undefined ? {} : { signal: options.signal }) });
@@ -259,10 +259,13 @@ export function OxyServicesAuthMixin(Base) {
259
259
  /**
260
260
  * Whether this process can prove what it is (ADR 0026).
261
261
  *
262
- * The internal package subpath selects a client implementation for native
263
- * and browser bundlers, and the signer only for Node hosts. The runtime
264
- * Node guard avoids calling attestation on clients; the module boundary
265
- * keeps node:crypto out of their bundle graphs (a guard alone cannot).
262
+ * `#workload-identity` (package.json `imports`) selects a client
263
+ * implementation for native and browser bundlers, and the signer only for
264
+ * Node hosts. The runtime Node guard avoids calling attestation on clients;
265
+ * the module boundary keeps node:crypto out of their bundle graphs (a guard
266
+ * alone cannot). It is a package import, not a self-reference to an export
267
+ * subpath: a self-reference only resolves where `@oxy.so/core` is reachable
268
+ * through `node_modules`, which bundlers inside this workspace are not.
266
269
  *
267
270
  * @internal
268
271
  */
@@ -270,7 +273,7 @@ export function OxyServicesAuthMixin(Base) {
270
273
  if (typeof process === 'undefined' || !process.versions?.node)
271
274
  return false;
272
275
  try {
273
- const { canAttestWorkloadIdentity } = await import('@oxy.so/core/internal/workload-identity');
276
+ const { canAttestWorkloadIdentity } = await import('#workload-identity');
274
277
  return canAttestWorkloadIdentity();
275
278
  }
276
279
  catch {
@@ -298,7 +301,7 @@ export function OxyServicesAuthMixin(Base) {
298
301
  return entry.token;
299
302
  if (entry?.pending)
300
303
  return entry.pending;
301
- const { requestWorkloadServiceToken } = await import('@oxy.so/core/internal/workload-identity');
304
+ const { requestWorkloadServiceToken } = await import('#workload-identity');
302
305
  const seeded = entry ?? {
303
306
  token: '',
304
307
  expiresAt: 0,
@@ -493,6 +493,7 @@ export function OxyServicesUtilityMixin(Base) {
493
493
  return onError(error);
494
494
  return res.status(401).json(error);
495
495
  }
496
+ const tier = decoded.tier === 'internal' ? 'internal' : 'external';
496
497
  // Read delegated user ID from header
497
498
  const oxyUserIdRaw = req.headers['x-oxy-user-id'];
498
499
  const oxyUserId = isExactNonEmptyServiceClaim(oxyUserIdRaw) ? oxyUserIdRaw : null;
@@ -513,12 +514,21 @@ export function OxyServicesUtilityMixin(Base) {
513
514
  return onError(error);
514
515
  return res.status(401).json(error);
515
516
  }
516
- // C3: a service may only act as a user when an explicit
517
- // ServiceActingAs grant exists for that (appId, userId) pair.
518
- // Without the grant we MUST refuse — silently attaching
519
- // `req.userId = oxyUserId` would let any service impersonate
520
- // any user simply by setting the header.
521
- if (oxyUserId) {
517
+ // One of Oxy's own applications acts for a user without a grant:
518
+ // inside the ecosystem that is trust, not consent. It still acts
519
+ // with the USER's authority only — what that user may do is for the
520
+ // receiving service to check.
521
+ if (oxyUserId && tier === 'internal') {
522
+ req.userId = oxyUserId;
523
+ req.user = { id: oxyUserId };
524
+ req.serviceActingAs = { userId: oxyUserId, scopes: [] };
525
+ }
526
+ else if (oxyUserId) {
527
+ // C3: an EXTERNAL service may only act as a user when an explicit
528
+ // ServiceActingAs grant exists for that (appId, userId) pair.
529
+ // Without the grant we MUST refuse — silently attaching
530
+ // `req.userId = oxyUserId` would let any service impersonate
531
+ // any user simply by setting the header.
522
532
  const grant = await oxyInstance.verifyServiceActingAs(appId, oxyUserId);
523
533
  if (!grant || !grant.authorized) {
524
534
  logger.warn('[oxy.auth] Service token rejected — no delegation grant', {
@@ -569,6 +579,7 @@ export function OxyServicesUtilityMixin(Base) {
569
579
  ownerAccountId,
570
580
  scopes: Array.isArray(decoded.scopes) ? decoded.scopes : [],
571
581
  environment,
582
+ tier,
572
583
  };
573
584
  if (debug) {
574
585
  logger.debug(`[oxy.auth] Service token OK app=${decoded.appName} delegateUser=${oxyUserId || '(none)'}`, {
@@ -1001,6 +1012,12 @@ export function OxyServicesUtilityMixin(Base) {
1001
1012
  });
1002
1013
  return;
1003
1014
  }
1015
+ // Oxy's own applications are trusted app to app; scopes are the
1016
+ // external lane.
1017
+ if (req.serviceApp.tier === 'internal') {
1018
+ next();
1019
+ return;
1020
+ }
1004
1021
  const appHasScope = appScopes.includes(scope);
1005
1022
  const delegationHasScope = delegatedScopes.includes(scope);
1006
1023
  const hasRequiredScope = req.serviceActingAs
@@ -1,3 +1,11 @@
1
1
  {
2
- "type": "module"
2
+ "type": "module",
3
+ "imports": {
4
+ "#workload-identity": {
5
+ "react-native": "./server/workloadIdentity.client.js",
6
+ "browser": "./server/workloadIdentity.client.js",
7
+ "node": "./server/workloadIdentity.js",
8
+ "default": "./server/workloadIdentity.client.js"
9
+ }
10
+ }
3
11
  }