@astrale-os/kernel-protocol 0.5.0-beta.70 → 0.5.0-beta.71

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.
@@ -1,15 +1,4 @@
1
- export declare namespace Delegate {
2
- /** One wire leaf; Identity set algebra is not supported. */
3
- type Expr = {
4
- readonly kind: 'identity';
5
- readonly self: true;
6
- } | {
7
- readonly kind: 'identity';
8
- readonly credential: string;
9
- };
10
- }
11
1
  export interface Delegate {
12
2
  readonly ttlSeconds: number;
13
- readonly attenuation?: Delegate.Expr;
14
3
  }
15
4
  export declare function acceptDelegate(input: unknown): Delegate;
@@ -1,29 +1,12 @@
1
- import { AuthValueError, grant } from '@astrale-os/kernel-core/auth';
2
1
  import { exactDataObject, exactFields, invalid, required } from './admission.js';
3
- const FIELDS = new Set(['ttlSeconds', 'attenuation']);
2
+ import { withoutSelfAttenuation } from './legacy/delegate.js';
3
+ const FIELDS = new Set(['ttlSeconds']);
4
4
  export function acceptDelegate(input) {
5
- const object = exactDataObject(input, 'delegate');
5
+ const object = withoutSelfAttenuation(exactDataObject(input, 'delegate'));
6
6
  exactFields(object, FIELDS, 'delegate');
7
7
  required(object, ['ttlSeconds'], 'delegate');
8
8
  if (!Number.isSafeInteger(object.ttlSeconds) || object.ttlSeconds < 1) {
9
9
  invalid('delegate.ttlSeconds must be a positive safe integer');
10
10
  }
11
- // Omission and `self` both carry the authenticated caller unchanged; nothing else is admitted.
12
- let attenuation;
13
- if (object.attenuation !== undefined) {
14
- try {
15
- attenuation = grant.acceptUnresolved({ v: 1, expr: object.attenuation }).expr;
16
- }
17
- catch (cause) {
18
- if (cause instanceof AuthValueError)
19
- invalid('delegate.attenuation must be self');
20
- throw cause;
21
- }
22
- if (!('self' in attenuation))
23
- invalid('delegate.attenuation must be self');
24
- }
25
- return Object.freeze({
26
- ttlSeconds: object.ttlSeconds,
27
- ...(attenuation === undefined ? {} : { attenuation: attenuation }),
28
- });
11
+ return Object.freeze({ ttlSeconds: object.ttlSeconds });
29
12
  }
@@ -1,5 +1,5 @@
1
1
  export { BINARY_DELIVERY_FIELD, BINARY_HEADERS_FIELD, decodeBinaryHeaderNames, decodeInvocationId, encodeBinaryHeaderNames, encodeInvocationId, isApplicationBinaryHeader, MAXIMUM_INVOCATION_FIELD_BYTES, INVOCATION_FIELD, REQUEST_ID_FIELD, STREAMING_BINARY_DELIVERY, } from './binary.js';
2
2
  export { codecForContentType, negotiateResponseCodec } from './negotiation.js';
3
- export { BINARY_LENGTH_FIELD, BINARY_REQUEST_CONTENT_TYPE, decodeBinaryRequestEnvelope, encodeBinaryRequestEnvelope, MAXIMUM_REQUEST_ENVELOPE_BYTES, REQUEST_ENVELOPE_FIELD, } from './request.js';
3
+ export { BINARY_LENGTH_FIELD, BINARY_REQUEST_CONTENT_TYPE, decodeBinaryLength, decodeBinaryRequestEnvelope, encodeBinaryRequestEnvelope, MAXIMUM_REQUEST_ENVELOPE_BYTES, REQUEST_ENVELOPE_FIELD, } from './request.js';
4
4
  export { encodeStreamFrame, LENGTH_PREFIX_V1, STREAM_FRAMING_FIELD } from './stream.js';
5
5
  export { BAGGAGE_FIELD, decodeTraceHeaders, encodeTraceHeaders, TRACEPARENT_FIELD, TRACESTATE_FIELD, } from './trace.js';
@@ -1,5 +1,5 @@
1
1
  export { BINARY_DELIVERY_FIELD, BINARY_HEADERS_FIELD, decodeBinaryHeaderNames, decodeInvocationId, encodeBinaryHeaderNames, encodeInvocationId, isApplicationBinaryHeader, MAXIMUM_INVOCATION_FIELD_BYTES, INVOCATION_FIELD, REQUEST_ID_FIELD, STREAMING_BINARY_DELIVERY, } from './binary.js';
2
2
  export { codecForContentType, negotiateResponseCodec } from './negotiation.js';
3
- export { BINARY_LENGTH_FIELD, BINARY_REQUEST_CONTENT_TYPE, decodeBinaryRequestEnvelope, encodeBinaryRequestEnvelope, MAXIMUM_REQUEST_ENVELOPE_BYTES, REQUEST_ENVELOPE_FIELD, } from './request.js';
3
+ export { BINARY_LENGTH_FIELD, BINARY_REQUEST_CONTENT_TYPE, decodeBinaryLength, decodeBinaryRequestEnvelope, encodeBinaryRequestEnvelope, MAXIMUM_REQUEST_ENVELOPE_BYTES, REQUEST_ENVELOPE_FIELD, } from './request.js';
4
4
  export { encodeStreamFrame, LENGTH_PREFIX_V1, STREAM_FRAMING_FIELD } from './stream.js';
5
5
  export { BAGGAGE_FIELD, decodeTraceHeaders, encodeTraceHeaders, TRACEPARENT_FIELD, TRACESTATE_FIELD, } from './trace.js';
@@ -3,6 +3,8 @@ export declare const REQUEST_ENVELOPE_FIELD: 'x-astrale-request';
3
3
  export declare const BINARY_LENGTH_FIELD: 'x-astrale-binary-length';
4
4
  export declare const BINARY_REQUEST_CONTENT_TYPE: 'application/octet-stream';
5
5
  export declare const MAXIMUM_REQUEST_ENVELOPE_BYTES = 8192;
6
+ /** Decode one declared byte count: a canonical unsigned decimal within the safe integer range. */
7
+ export declare function decodeBinaryLength(input: string): number | undefined;
6
8
  /** Encode one canonical V2 Request into the binary-request HTTP field. */
7
9
  export declare function encodeBinaryRequestEnvelope(input: Request): string;
8
10
  /** Decode one bounded base64url JSON V2 Request from the binary-request HTTP field. */
@@ -7,6 +7,14 @@ export const BINARY_REQUEST_CONTENT_TYPE = 'application/octet-stream';
7
7
  export const MAXIMUM_REQUEST_ENVELOPE_BYTES = 8_192;
8
8
  const BASE64URL = /^[A-Za-z0-9_-]+$/u;
9
9
  const MAXIMUM_ENCODED_BYTES = Math.ceil(MAXIMUM_REQUEST_ENVELOPE_BYTES / 3) * 4;
10
+ const DECIMAL = /^(?:0|[1-9][0-9]*)$/u;
11
+ /** Decode one declared byte count: a canonical unsigned decimal within the safe integer range. */
12
+ export function decodeBinaryLength(input) {
13
+ if (!DECIMAL.test(input))
14
+ return undefined;
15
+ const length = Number(input);
16
+ return Number.isSafeInteger(length) ? length : undefined;
17
+ }
10
18
  /** Encode one canonical V2 Request into the binary-request HTTP field. */
11
19
  export function encodeBinaryRequestEnvelope(input) {
12
20
  const request = decodeRequest(input);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A source `Delegate` may still carry `attenuation` with the self expression
3
+ * `{ kind: 'identity', self: true }`, the one value published Clients and SDK Workers send. It names
4
+ * the caller every Delegate preserves anyway, so `acceptDelegate` drops it and returns the same
5
+ * `Delegate` as without it. Every other value, `undefined` and `null` included, stays refused.
6
+ *
7
+ * @deprecated Omit `attenuation`; a Delegate always preserves the authenticated caller (ADR-3).
8
+ *
9
+ * Short-term consumers: Domain Workers built with `@astrale-os/sdk` 0.6.0-beta.0 to beta.3, whose
10
+ * `Function.admit` callback sends it (the Shell v3 Worker included), and callers that pass it in
11
+ * `delegate` to `@astrale-os/kernel-client` 0.6.0-beta.85 or earlier.
12
+ *
13
+ * Removal, in a separate breaking release, once those Workers are redeployed with a later SDK and
14
+ * every consumer runs a Client that omits the field: delete this file, its call in `../delegate.ts`
15
+ * and its vectors in INVOCATION-DELEGATE-OMISSION-EVIDENCE.
16
+ */
17
+ export declare function withoutSelfAttenuation(object: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * A source `Delegate` may still carry `attenuation` with the self expression
3
+ * `{ kind: 'identity', self: true }`, the one value published Clients and SDK Workers send. It names
4
+ * the caller every Delegate preserves anyway, so `acceptDelegate` drops it and returns the same
5
+ * `Delegate` as without it. Every other value, `undefined` and `null` included, stays refused.
6
+ *
7
+ * @deprecated Omit `attenuation`; a Delegate always preserves the authenticated caller (ADR-3).
8
+ *
9
+ * Short-term consumers: Domain Workers built with `@astrale-os/sdk` 0.6.0-beta.0 to beta.3, whose
10
+ * `Function.admit` callback sends it (the Shell v3 Worker included), and callers that pass it in
11
+ * `delegate` to `@astrale-os/kernel-client` 0.6.0-beta.85 or earlier.
12
+ *
13
+ * Removal, in a separate breaking release, once those Workers are redeployed with a later SDK and
14
+ * every consumer runs a Client that omits the field: delete this file, its call in `../delegate.ts`
15
+ * and its vectors in INVOCATION-DELEGATE-OMISSION-EVIDENCE.
16
+ */
17
+ export function withoutSelfAttenuation(object) {
18
+ if (!Object.hasOwn(object, 'attenuation') || !isSelf(object.attenuation))
19
+ return object;
20
+ const { attenuation: _self, ...rest } = object;
21
+ return rest;
22
+ }
23
+ function isSelf(value) {
24
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
25
+ return false;
26
+ const prototype = Object.getPrototypeOf(value);
27
+ if (prototype !== Object.prototype && prototype !== null)
28
+ return false;
29
+ if (Reflect.ownKeys(value).length !== 2)
30
+ return false;
31
+ const kind = Object.getOwnPropertyDescriptor(value, 'kind');
32
+ const self = Object.getOwnPropertyDescriptor(value, 'self');
33
+ return (kind?.enumerable === true &&
34
+ kind.value === 'identity' &&
35
+ self?.enumerable === true &&
36
+ self.value === true);
37
+ }
@@ -9,7 +9,7 @@ export { placeDelivery } from './delivery-declaration.js';
9
9
  export type { Authentication as DeliveryAuthentication, Endpoint as DeliveryEndpoint, } from './delivery-endpoint.js';
10
10
  export type { BuildDigest, ReleaseDigest, Sha256Digest } from './digest.js';
11
11
  export { ReleaseError, type ReleaseErrorCode } from './error.js';
12
- export type { Manifest } from './manifest.js';
12
+ export type { LocalizedText, Manifest } from './manifest.js';
13
13
  export { ClassOperationOrder, admitRequirements, capabilityKeys, validateRequirements, type CapabilityAuthorityRequirements, type CoreCapabilityRequirement, type CapabilityRequirements, type ClassCapabilityRequirement, type ClassOperation, type NonEmpty, type Requirements, } from './requirements.js';
14
14
  export type { BodyPick, CredentialSource, HttpMethod, HttpRoute, HttpRouteDeclaration, RouteBody, RouteHeaders, SingleCredentialSource, } from './route.js';
15
15
  export type { Schema } from './schema.js';
@@ -1,5 +1,9 @@
1
1
  import { type ViewRef } from '@astrale-os/kernel-dsl/v1/addressing';
2
+ /** A display name per language, keyed by a BCP 47 language tag in canonical case (`fr`, `pt-BR`). */
3
+ export type LocalizedText = Readonly<Record<string, string>>;
2
4
  export interface Manifest {
5
+ /** The application's display name, per language. */
6
+ readonly title?: LocalizedText;
3
7
  readonly logo?: string;
4
8
  readonly entrypoint?: ViewRef;
5
9
  }
@@ -1,11 +1,19 @@
1
1
  import { acceptDefinitionRef } from '@astrale-os/kernel-dsl/v1/addressing';
2
2
  import { fail } from './error.js';
3
3
  import { exact, record } from './shape.js';
4
+ /** A language, optionally narrowed to a region: `fr`, `en`, `pt-BR`. */
5
+ const LANGUAGE_TAG = /^[a-z]{2,3}(?:-[A-Z]{2})?$/u;
6
+ const MAXIMUM_TITLE_LANGUAGES = 32;
7
+ const MAXIMUM_TITLE_LENGTH = 120;
8
+ // C0 and C1 controls; a display name is one line of text.
9
+ // oxlint-disable-next-line no-control-regex
10
+ const CONTROL = /[\u0000-\u001f\u007f-\u009f]/u;
4
11
  export function admitManifest(input) {
5
12
  if (input === undefined)
6
13
  return undefined;
7
14
  const value = record(input, 'RELEASE_INVALID', 'Manifest is invalid.');
8
- exact(value, ['logo', 'entrypoint'], 'RELEASE_INVALID');
15
+ exact(value, ['title', 'logo', 'entrypoint'], 'RELEASE_INVALID');
16
+ const title = value.title === undefined ? undefined : admitTitle(value.title);
9
17
  if (value.logo !== undefined && typeof value.logo !== 'string') {
10
18
  fail('RELEASE_INVALID', 'Manifest logo must be a string.');
11
19
  }
@@ -24,7 +32,35 @@ export function admitManifest(input) {
24
32
  entrypoint = ref;
25
33
  }
26
34
  return Object.freeze({
35
+ ...(title === undefined ? {} : { title }),
27
36
  ...(value.logo === undefined ? {} : { logo: value.logo }),
28
37
  ...(entrypoint === undefined ? {} : { entrypoint }),
29
38
  });
30
39
  }
40
+ /**
41
+ * A release is canonical, so a title is admitted exactly as written or refused: never trimmed,
42
+ * re-cased or truncated, which would make its digest differ from what the author signed.
43
+ */
44
+ function admitTitle(input) {
45
+ const value = record(input, 'RELEASE_INVALID', 'Manifest title is invalid.');
46
+ const languages = Object.keys(value);
47
+ if (languages.length === 0 || languages.length > MAXIMUM_TITLE_LANGUAGES) {
48
+ fail('RELEASE_INVALID', 'Manifest title must name between 1 and 32 languages.');
49
+ }
50
+ const title = {};
51
+ for (const language of languages) {
52
+ if (!LANGUAGE_TAG.test(language)) {
53
+ fail('RELEASE_INVALID', 'Manifest title languages must be canonical BCP 47 tags.');
54
+ }
55
+ const text = value[language];
56
+ if (typeof text !== 'string' ||
57
+ text.length === 0 ||
58
+ text.length > MAXIMUM_TITLE_LENGTH ||
59
+ text.trim() !== text ||
60
+ CONTROL.test(text)) {
61
+ fail('RELEASE_INVALID', 'Manifest title text must be one trimmed line of 1 to 120 characters.');
62
+ }
63
+ title[language] = text;
64
+ }
65
+ return Object.freeze(title);
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-protocol",
3
- "version": "0.5.0-beta.70",
3
+ "version": "0.5.0-beta.71",
4
4
  "description": "Portable Astrale invocation, publication, installation, routing, and issuer protocols",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -173,8 +173,8 @@
173
173
  "access": "public"
174
174
  },
175
175
  "dependencies": {
176
- "@astrale-os/kernel-core": "0.9.0-beta.64",
177
- "@astrale-os/kernel-dsl": "0.2.0-beta.46",
176
+ "@astrale-os/kernel-core": "0.9.0-beta.65",
177
+ "@astrale-os/kernel-dsl": "0.2.0-beta.47",
178
178
  "@noble/hashes": "2.3.0"
179
179
  },
180
180
  "devDependencies": {