@north-light/cli 0.3.494 → 0.3.496

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 (37) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/limits.d.ts +15 -1
  4. package/dist/builtin-memory/crouter-sdk/release-notes.md +9 -1
  5. package/dist/commands/node/create.js +1 -1
  6. package/dist/commands/sys/limits.js +3 -3
  7. package/dist/core/peer/client.d.ts +1 -1
  8. package/dist/core/peer/directory.d.ts +1 -1
  9. package/dist/core/peer/token.d.ts +0 -2
  10. package/dist/core/peer/token.js +2 -2
  11. package/dist/core/runtime/fleet.d.ts +12 -0
  12. package/dist/core/runtime/fleet.js +1 -1
  13. package/dist/core/runtime/host.js +3 -3
  14. package/dist/core/runtime/memory-admission.d.ts +38 -0
  15. package/dist/core/runtime/memory-admission.js +1 -0
  16. package/dist/core/runtime/revive.js +2 -2
  17. package/dist/core/runtime/spawn.js +2 -2
  18. package/dist/daemon/api/handlers/limits.js +1 -1
  19. package/dist/daemon/api/principal.d.ts +2 -2
  20. package/dist/daemon/api/router.d.ts +1 -1
  21. package/dist/daemon/peer/ingress.d.ts +2 -2
  22. package/dist/daemon/peer/relay-client.d.ts +1 -1
  23. package/dist/daemon/reconcilers/broker-supervision.js +4 -4
  24. package/dist/launcher/limits/cgroup.d.ts +5 -1
  25. package/dist/launcher/limits/cgroup.js +2 -2
  26. package/dist/launcher/limits/manager.d.ts +27 -4
  27. package/dist/launcher/limits/manager.js +1 -1
  28. package/dist/launcher/limits/policy.d.ts +43 -0
  29. package/dist/launcher/limits/policy.js +1 -1
  30. package/dist/launcher/main.js +2 -2
  31. package/dist/launcher/spec.d.ts +31 -1
  32. package/docs/sdk/release-notes.md +9 -1
  33. package/package.json +2 -2
  34. package/packages/crouter-identity/dist/index.d.ts +8 -92
  35. package/packages/crouter-identity/dist/index.js +9 -94
  36. package/packages/crouter-identity/package.json +1 -1
  37. package/runtime.lock.json +12 -12
@@ -1,14 +1,5 @@
1
1
  import { type JSONWebKeySet, type JWTPayload } from 'jose';
2
- export interface AccessTokenClaims extends JWTPayload {
3
- sub: string;
4
- grantee: string;
5
- client_id: string;
6
- grant: string;
7
- /** The grant revision the directory minted against: a freshness floor, never authorization (contract 1). */
8
- rev: number;
9
- exp: number;
10
- }
11
- /** Thrown by `verifyAccessToken` for a signed token with no `rev` claim: the directory predates the claim. */
2
+ /** Thrown by `verifyCallToken` for an `app:` link with a `grant` and no `rev`: the directory predates the claim. */
12
3
  export declare class MissingRevClaimError extends Error {
13
4
  constructor();
14
5
  }
@@ -67,81 +58,6 @@ export declare class JwksCache {
67
58
  private fetchKeys;
68
59
  forKid(kid: string | undefined): Promise<JSONWebKeySet>;
69
60
  }
70
- /** @deprecated Use `verifyCallToken`; removed after the peer-token rollout window (plan I2). */
71
- export declare function verifyAccessToken(options: {
72
- token: string;
73
- jwks: JSONWebKeySet | JwksCache;
74
- issuer: string;
75
- audience: string;
76
- }): Promise<AccessTokenClaims>;
77
- /** The claims of a machine token: one machine of an account calling another (or the account relay). It names the
78
- * person and the calling machine and carries no grant, so it authorizes nothing on its own. */
79
- export interface MachineTokenClaims extends JWTPayload {
80
- iss: string;
81
- /** The single audience: a peer machine URL or the relay origin. */
82
- aud: string;
83
- /** The account's person, `user:<id>`. */
84
- sub: string;
85
- /** Always equal to `sub`. */
86
- grantee: string;
87
- client_id: string;
88
- /** The calling machine, `runtime:<id>`. */
89
- machine_id: string;
90
- exp: number;
91
- }
92
- /**
93
- * Verifies a machine token: ES256 with typ `at+jwt`, signature against `jwks`, `iss` = `issuer`, `aud` a string equal
94
- * to `audience` (the sole audience), and `exp`. `sub` must be `user:<id>`, `grantee` must equal `sub`, `machine_id`
95
- * must be `runtime:<id>` and `client_id` a non-empty string. A token carrying a `grant`, `rev` or `scope` claim is
96
- * refused, so an app grant token never verifies here; `verifyAccessToken` requires an `app:` grantee, so a machine
97
- * token never verifies there.
98
- */
99
- /** @deprecated Use `verifyCallToken`; removed after the peer-token rollout window (plan I2). */
100
- export declare function verifyMachineToken(options: {
101
- token: string;
102
- jwks: JSONWebKeySet | JwksCache;
103
- issuer: string;
104
- audience: string;
105
- }): Promise<MachineTokenClaims>;
106
- /** One level of the outbound token's RFC 8693 `act` nesting: the actor, and the actor it acted for. */
107
- export interface OutboundActor {
108
- sub: string;
109
- act?: OutboundActor;
110
- }
111
- /** The claims of the outbound token the directory mints for one provider call (contract 3). */
112
- export interface OutboundTokenClaims extends JWTPayload {
113
- iss: string;
114
- aud: string;
115
- /** The user the call is made for, `user:<id>`. */
116
- sub: string;
117
- /** The principal the grant was issued to, `app:<id>` or the user. */
118
- grantee: string;
119
- /** The runtime's registered OAuth client id. */
120
- client_id: string;
121
- /** The provider's `<provider>:<group>` strings the caller holds, space-separated; may be empty. */
122
- scope: string;
123
- initiating_app: string;
124
- /** Audit only; nothing authorizes on it. */
125
- act: OutboundActor;
126
- iat: number;
127
- exp: number;
128
- jti: string;
129
- }
130
- /**
131
- * Verifies the outbound token a provider receives (contract 3 D3-TOK-12): ES256 or RS256 with
132
- * a `kid`, typ `at+jwt`, signature against `jwks` (a `JwksCache` refetches once on an unknown
133
- * `kid`), `iss` = `issuer`, `aud` exactly `audience` (a trailing-slash difference is refused),
134
- * and `exp`. `sub` must be a `user:` principal and `grantee` and `initiating_app`
135
- * an `app:` or `user:` principal; the remaining claims are checked for presence and type only; which of them gates a
136
- * call is the caller's rule (D3-TOK-13 gates on `scope` alone, and nothing gates on `act`).
137
- */
138
- /** @deprecated Use `verifyCallToken`; removed after the peer-token rollout window (plan I2). */
139
- export declare function verifyOutboundToken(options: {
140
- token: string;
141
- jwks: JSONWebKeySet | JwksCache;
142
- issuer: string;
143
- audience: string;
144
- }): Promise<OutboundTokenClaims>;
145
61
  /** One link of a call token's actor chain. `grant` and `rev` appear only on an `app:` link. */
146
62
  export interface CallLink {
147
63
  sub: string;
@@ -157,10 +73,10 @@ export interface CallTokenClaims extends JWTPayload {
157
73
  client_id: string;
158
74
  exp: number;
159
75
  iat: number;
160
- /** The RFC 8693 actor chain as signed; absent on the legacy shapes. */
76
+ /** The RFC 8693 actor chain as signed. */
161
77
  act?: unknown;
162
78
  scope?: string;
163
- /** Legacy same-account machine token (removed at the flag flip). */
79
+ /** Only on a peer token minted while the directory still wrote it (until its flag flip); when present it names the presenter. */
164
80
  machine_id?: string;
165
81
  grantee?: string;
166
82
  initiating_app?: string;
@@ -172,7 +88,7 @@ export interface CallToken {
172
88
  /** `user:<id>`. */
173
89
  sub: string;
174
90
  aud: string;
175
- /** The actor chain, index 0 = the presenter. Legacy shapes are normalized to the same form. */
91
+ /** The actor chain, index 0 = the presenter. */
176
92
  chain: CallLink[];
177
93
  /** Present only on provider tokens. */
178
94
  scope?: string;
@@ -182,10 +98,10 @@ export interface CallToken {
182
98
  * Verifies a call token of any use (peer, app access, outbound provider, run notice) and returns its actor chain,
183
99
  * presenter first. Checks in order: header `typ` `at+jwt`, `alg` ES256 or RS256, non-empty `kid`; signature; `iss`;
184
100
  * `aud` a string equal to `audience`; `exp` and `iat` numbers; `sub` = `user:<id>`; `client_id` non-empty; `scope`
185
- * a string when present; then the chain. Without `act` the legacy machine token (`machine_id`, `grantee` = `sub`)
186
- * and the legacy app token (`app:` `grantee` + `grant` + `rev`) are normalized to a one-link chain; with `act` any
187
- * `machine_id` must equal the first link, any top-level `grant` an app link's, and, on a token without `scope`, any `grantee`
188
- * and `rev` the app link's (or, with no app link, `grantee` = `sub` and no `rev`). It authorizes nothing.
101
+ * a string when present; then the chain. A token with no `act` is refused: the legacy machine and app shapes are not
102
+ * accepted. Any `machine_id` must equal the first link, any top-level `grant` an app link's, and, on a token without
103
+ * `scope`, any `grantee` and `rev` the app link's (or, with no app link, `grantee` = `sub` and no `rev`). It authorizes
104
+ * nothing.
189
105
  * Throws `MissingRevClaimError` for an `app:` link carrying `grant` and no `rev`.
190
106
  */
191
107
  export declare function verifyCallToken(options: {
@@ -1,5 +1,5 @@
1
1
  import { createLocalJWKSet, decodeProtectedHeader, jwtVerify } from 'jose';
2
- /** Thrown by `verifyAccessToken` for a signed token with no `rev` claim: the directory predates the claim. */
2
+ /** Thrown by `verifyCallToken` for an `app:` link with a `grant` and no `rev`: the directory predates the claim. */
3
3
  export class MissingRevClaimError extends Error {
4
4
  constructor() { super('access token has no rev claim: the directory is older than this runtime'); this.name = 'MissingRevClaimError'; }
5
5
  }
@@ -215,85 +215,9 @@ function validatePrincipal(value, kind) {
215
215
  function keySet(jwks, kid) {
216
216
  return jwks instanceof JwksCache ? jwks.forKid(kid) : Promise.resolve(jwks);
217
217
  }
218
- /** @deprecated Use `verifyCallToken`; removed after the peer-token rollout window (plan I2). */
219
- export async function verifyAccessToken(options) {
220
- const header = decodeProtectedHeader(options.token);
221
- if (header.typ !== 'at+jwt' || header.alg !== 'ES256')
222
- throw new Error('Invalid access token header');
223
- const keys = await keySet(options.jwks, header.kid);
224
- const { payload } = await jwtVerify(options.token, createLocalJWKSet(keys), {
225
- issuer: options.issuer, audience: options.audience, algorithms: ['ES256'], typ: 'at+jwt',
226
- });
227
- if (payload.aud !== options.audience || !validatePrincipal(payload.sub, 'user') || !validatePrincipal(payload.grantee, 'app') ||
228
- typeof payload.client_id !== 'string' || !id.test(payload.client_id) || typeof payload.grant !== 'string' ||
229
- typeof payload.exp !== 'number') {
230
- throw new Error('Invalid access token claims');
231
- }
232
- if (payload.rev === undefined)
233
- throw new MissingRevClaimError();
234
- if (typeof payload.rev !== 'number' || !Number.isInteger(payload.rev) || payload.rev < 1)
235
- throw new Error('Invalid access token claims');
236
- return payload;
237
- }
238
- /**
239
- * Verifies a machine token: ES256 with typ `at+jwt`, signature against `jwks`, `iss` = `issuer`, `aud` a string equal
240
- * to `audience` (the sole audience), and `exp`. `sub` must be `user:<id>`, `grantee` must equal `sub`, `machine_id`
241
- * must be `runtime:<id>` and `client_id` a non-empty string. A token carrying a `grant`, `rev` or `scope` claim is
242
- * refused, so an app grant token never verifies here; `verifyAccessToken` requires an `app:` grantee, so a machine
243
- * token never verifies there.
244
- */
245
- /** @deprecated Use `verifyCallToken`; removed after the peer-token rollout window (plan I2). */
246
- export async function verifyMachineToken(options) {
247
- const header = decodeProtectedHeader(options.token);
248
- if (header.typ !== 'at+jwt' || header.alg !== 'ES256')
249
- throw new Error('Invalid machine token header');
250
- const keys = await keySet(options.jwks, header.kid);
251
- const { payload } = await jwtVerify(options.token, createLocalJWKSet(keys), {
252
- issuer: options.issuer, audience: options.audience, algorithms: ['ES256'], typ: 'at+jwt', requiredClaims: ['exp'],
253
- });
254
- if (payload.aud !== options.audience || !validatePrincipal(payload.sub, 'user') || payload.grantee !== payload.sub ||
255
- !validatePrincipal(payload.machine_id, 'runtime') || !nonEmptyString(payload.client_id) || typeof payload.exp !== 'number' ||
256
- payload.grant !== undefined || payload.rev !== undefined || payload.scope !== undefined) {
257
- throw new Error('Invalid machine token claims');
258
- }
259
- return payload;
260
- }
261
218
  function nonEmptyString(value) {
262
219
  return typeof value === 'string' && value !== '';
263
220
  }
264
- function appOrUser(value) {
265
- return validatePrincipal(value, 'app') || validatePrincipal(value, 'user');
266
- }
267
- function isActor(value) {
268
- return typeof value === 'object' && value !== null && !Array.isArray(value) && nonEmptyString(value.sub);
269
- }
270
- /**
271
- * Verifies the outbound token a provider receives (contract 3 D3-TOK-12): ES256 or RS256 with
272
- * a `kid`, typ `at+jwt`, signature against `jwks` (a `JwksCache` refetches once on an unknown
273
- * `kid`), `iss` = `issuer`, `aud` exactly `audience` (a trailing-slash difference is refused),
274
- * and `exp`. `sub` must be a `user:` principal and `grantee` and `initiating_app`
275
- * an `app:` or `user:` principal; the remaining claims are checked for presence and type only; which of them gates a
276
- * call is the caller's rule (D3-TOK-13 gates on `scope` alone, and nothing gates on `act`).
277
- */
278
- /** @deprecated Use `verifyCallToken`; removed after the peer-token rollout window (plan I2). */
279
- export async function verifyOutboundToken(options) {
280
- const header = decodeProtectedHeader(options.token);
281
- if (header.typ !== 'at+jwt' || (header.alg !== 'ES256' && header.alg !== 'RS256'))
282
- throw new Error('Invalid outbound token header');
283
- if (!nonEmptyString(header.kid))
284
- throw new Error('Outbound token has no key id');
285
- const keys = await keySet(options.jwks, header.kid);
286
- const { payload } = await jwtVerify(options.token, createLocalJWKSet(keys), {
287
- issuer: options.issuer, audience: options.audience, algorithms: ['ES256', 'RS256'], typ: 'at+jwt', requiredClaims: ['exp'],
288
- });
289
- if (payload.aud !== options.audience || typeof payload.exp !== 'number' || typeof payload.iat !== 'number' ||
290
- !validatePrincipal(payload.sub, 'user') || !appOrUser(payload.grantee) || !nonEmptyString(payload.client_id) ||
291
- typeof payload.scope !== 'string' || !appOrUser(payload.initiating_app) || !isActor(payload.act) ||
292
- !nonEmptyString(payload.jti)) {
293
- throw new Error('Invalid outbound token claims');
294
- }
295
- return payload;
296
- }
297
221
  const MAX_CHAIN_DEPTH = 8;
298
222
  const DEVELOPER_CREDENTIAL_LINK = /^developer-credential:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
299
223
  function isDeveloperCredential(value) {
@@ -319,17 +243,8 @@ function chainOf(payload) {
319
243
  return bad();
320
244
  return { sub, grant, rev };
321
245
  };
322
- if (p.act === undefined) {
323
- if (p.machine_id !== undefined) {
324
- if (!validatePrincipal(p.machine_id, 'runtime') || p.grantee !== p.sub ||
325
- p.grant !== undefined || p.rev !== undefined || p.scope !== undefined)
326
- return bad();
327
- return [{ sub: p.machine_id }];
328
- }
329
- if (validatePrincipal(p.grantee, 'app') && typeof p.grant === 'string')
330
- return [appLink(p.grantee, p.grant, p.rev)];
246
+ if (p.act === undefined)
331
247
  return bad();
332
- }
333
248
  const chain = [];
334
249
  let node = p.act;
335
250
  while (node !== undefined) {
@@ -359,9 +274,9 @@ function chainOf(payload) {
359
274
  return bad();
360
275
  if (p.grant !== undefined && !chain.some((l) => l.sub.startsWith('app:') && l.grant === p.grant))
361
276
  return bad();
362
- // A superset token's other legacy fields must say what its chain says, or the old and new verifiers authorize different
363
- // identities from one signed token. Provider tokens (`scope`) are exempt: their `grantee` is the person or app the call
364
- // runs for, not a link of the chain.
277
+ // A token's top-level claims (kept for providers pinned to an older `@north-light/plugin`, which read them instead of
278
+ // `act`) must say what its chain says, or the old and new verifiers authorize different identities from one signed token.
279
+ // Provider tokens (`scope`) are exempt: their `grantee` is the person or app the call runs for, not a link of the chain.
365
280
  if (p.scope === undefined) {
366
281
  const appLinks = chain.filter((l) => l.sub.startsWith('app:'));
367
282
  const appLink = appLinks.find((l) => l.grant !== undefined && l.grant === p.grant) ?? appLinks[0];
@@ -384,10 +299,10 @@ function chainOf(payload) {
384
299
  * Verifies a call token of any use (peer, app access, outbound provider, run notice) and returns its actor chain,
385
300
  * presenter first. Checks in order: header `typ` `at+jwt`, `alg` ES256 or RS256, non-empty `kid`; signature; `iss`;
386
301
  * `aud` a string equal to `audience`; `exp` and `iat` numbers; `sub` = `user:<id>`; `client_id` non-empty; `scope`
387
- * a string when present; then the chain. Without `act` the legacy machine token (`machine_id`, `grantee` = `sub`)
388
- * and the legacy app token (`app:` `grantee` + `grant` + `rev`) are normalized to a one-link chain; with `act` any
389
- * `machine_id` must equal the first link, any top-level `grant` an app link's, and, on a token without `scope`, any `grantee`
390
- * and `rev` the app link's (or, with no app link, `grantee` = `sub` and no `rev`). It authorizes nothing.
302
+ * a string when present; then the chain. A token with no `act` is refused: the legacy machine and app shapes are not
303
+ * accepted. Any `machine_id` must equal the first link, any top-level `grant` an app link's, and, on a token without
304
+ * `scope`, any `grantee` and `rev` the app link's (or, with no app link, `grantee` = `sub` and no `rev`). It authorizes
305
+ * nothing.
391
306
  * Throws `MissingRevClaimError` for an `app:` link carrying `grant` and no `rev`.
392
307
  */
393
308
  export async function verifyCallToken(options) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.494",
3
+ "version": "0.3.496",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.494",
3
+ "version": "0.3.496",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.494",
9
+ "version": "0.3.496",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -18,7 +18,7 @@
18
18
  "@earendil-works/pi-ai": "1.1.0",
19
19
  "@earendil-works/pi-coding-agent": "1.1.0",
20
20
  "@earendil-works/pi-tui": "1.1.0",
21
- "@north-light/identity": "0.3.494",
21
+ "@north-light/identity": "0.3.496",
22
22
  "cron-parser": "^5.6.0",
23
23
  "esbuild": "^0.27.7",
24
24
  "jose": "^6.2.12",
@@ -12116,17 +12116,17 @@
12116
12116
  },
12117
12117
  "packages/crouter-api": {
12118
12118
  "name": "@north-light/api",
12119
- "version": "0.3.494",
12119
+ "version": "0.3.496",
12120
12120
  "license": "UNLICENSED"
12121
12121
  },
12122
12122
  "packages/crouter-env-docker": {
12123
12123
  "name": "@north-light/env-docker",
12124
- "version": "0.3.494",
12124
+ "version": "0.3.496",
12125
12125
  "license": "UNLICENSED"
12126
12126
  },
12127
12127
  "packages/crouter-identity": {
12128
12128
  "name": "@north-light/identity",
12129
- "version": "0.3.494",
12129
+ "version": "0.3.496",
12130
12130
  "license": "UNLICENSED",
12131
12131
  "dependencies": {
12132
12132
  "jose": "^6.2.1"
@@ -12134,20 +12134,20 @@
12134
12134
  },
12135
12135
  "packages/crouter-plugin": {
12136
12136
  "name": "@north-light/plugin",
12137
- "version": "0.3.494",
12137
+ "version": "0.3.496",
12138
12138
  "license": "UNLICENSED",
12139
12139
  "dependencies": {
12140
- "@north-light/api": "0.3.494",
12141
- "@north-light/identity": "0.3.494"
12140
+ "@north-light/api": "0.3.496",
12141
+ "@north-light/identity": "0.3.496"
12142
12142
  }
12143
12143
  },
12144
12144
  "packages/crouter-sdk": {
12145
12145
  "name": "@north-light/sdk",
12146
- "version": "0.3.494",
12146
+ "version": "0.3.496",
12147
12147
  "license": "UNLICENSED",
12148
12148
  "dependencies": {
12149
- "@north-light/api": "0.3.494",
12150
- "@north-light/identity": "0.3.494",
12149
+ "@north-light/api": "0.3.496",
12150
+ "@north-light/identity": "0.3.496",
12151
12151
  "jose": "^6.2.1"
12152
12152
  },
12153
12153
  "bin": {