@astrale-os/kernel-protocol 0.5.0-beta.30 → 0.5.0-beta.31

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.
package/CHANGELOG.md CHANGED
@@ -24,6 +24,26 @@
24
24
  * the first supported V2 release is a coordinated `1.0.0`, not a compatible continuation of the
25
25
  current `0.x` line; package and wire versions remain independent as specified in
26
26
  [the protocol architecture](./.spec/architecture.md)
27
+ ## [0.5.0-beta.31](https://github.com/astrale-os/kernel/compare/kernel-protocol-v0.5.0-beta.30...kernel-protocol-v0.5.0-beta.31) (2026-08-31)
28
+
29
+
30
+ ### Features
31
+
32
+ * **auth:** enforce two-gate Function authority ([#561](https://github.com/astrale-os/kernel/issues/561)) ([c681d4b](https://github.com/astrale-os/kernel/commit/c681d4b391520906b2cad9171cd251962b200faf))
33
+
34
+
35
+ ### Bug Fixes
36
+
37
+ * **runtime:** recover legacy Publication authority safely ([#577](https://github.com/astrale-os/kernel/issues/577)) ([76e432a](https://github.com/astrale-os/kernel/commit/76e432a710f579c103a245260fb4952e7dc58ac8))
38
+
39
+
40
+ ### Dependencies
41
+
42
+ * The following workspace dependencies were updated
43
+ * dependencies
44
+ * @astrale-os/kernel-core bumped to 0.9.0-beta.29
45
+ * @astrale-os/kernel-dsl bumped to 0.2.0-beta.22
46
+
27
47
  ## [0.5.0-beta.30](https://github.com/astrale-os/kernel/compare/kernel-protocol-v0.5.0-beta.29...kernel-protocol-v0.5.0-beta.30) (2026-08-30)
28
48
 
29
49
 
@@ -1,4 +1,4 @@
1
- export { MEDIA_TYPE, PATH, url, seal, accept, encode, type Publication } from './publication.js';
1
+ export { MEDIA_TYPE, PATH, url, seal, accept, acceptStored, encode, type Publication, type StoredPublication, } from './publication.js';
2
2
  export { acceptBundle, decodeBundle } from './bundle-admission.js';
3
3
  export { BUNDLE_MEDIA_TYPE, describeBundle, type BundleDescriptor } from './bundle.js';
4
4
  export { verifyBundleBytes } from './bundle-evidence.js';
@@ -10,7 +10,7 @@ export { PublicationError, type PublicationErrorCode } from './error.js';
10
10
  export * as delivery from './delivery/index.js';
11
11
  export type { Authentication as DeliveryAuthentication, Endpoint as DeliveryEndpoint, } from './delivery-endpoint.js';
12
12
  export type { Manifest } from './manifest.js';
13
- export { ClassOperationOrder, admitRequirements, validateRequirements, type CapabilityAuthorityRequirements, type CapabilityRequirements, type ClassCapabilityRequirement, type ClassOperation, type NonEmpty, type PublicationRequirements, } from './requirements.js';
13
+ export { ClassOperationOrder, admitRequirements, validateRequirements, type CapabilityAuthorityRequirements, type CapabilityProfileRequirement, type CapabilityRequirements, type ClassCapabilityRequirement, type ClassOperation, type NonEmpty, type PublicationRequirements, } from './requirements.js';
14
14
  export { acceptReference, compareReference, compareReferences, matchesReference, reference, type PublicationRef, type PublicationReferenceRelation, } from './reference.js';
15
15
  export type { BodyPick, CredentialSource, HttpMethod, HttpRoute, HttpRouteDeclaration, RouteBody, RouteHeaders, SingleCredentialSource, } from './route.js';
16
16
  export type { Schema } from './schema.js';
@@ -1,4 +1,4 @@
1
- export { MEDIA_TYPE, PATH, url, seal, accept, encode } from './publication.js';
1
+ export { MEDIA_TYPE, PATH, url, seal, accept, acceptStored, encode, } from './publication.js';
2
2
  export { acceptBundle, decodeBundle } from './bundle-admission.js';
3
3
  export { BUNDLE_MEDIA_TYPE, describeBundle } from './bundle.js';
4
4
  export { verifyBundleBytes } from './bundle-evidence.js';
@@ -31,6 +31,16 @@ export interface Publication {
31
31
  readonly routes: readonly HttpRoute[];
32
32
  readonly delivery?: DeliveryEndpoint;
33
33
  }
34
+ /** Canonical semantic Publication plus its exact coordinate in durable storage. */
35
+ export interface StoredPublication {
36
+ readonly publication: Publication;
37
+ readonly deployedEtag: Etag;
38
+ }
34
39
  export declare function seal(input: Content): Publication;
35
40
  export declare function accept(input: unknown): Publication;
41
+ /**
42
+ * Admit a canonical Publication retained before the admin-to-authority vocabulary cutover.
43
+ * Network admission remains strict; this one-way reader exists only for already-trusted storage.
44
+ */
45
+ export declare function acceptStored(input: unknown): StoredPublication;
36
46
  export declare function encode(input: Publication): Uint8Array;
@@ -44,6 +44,40 @@ export function accept(input) {
44
44
  }
45
45
  return publication;
46
46
  }
47
+ /**
48
+ * Admit a canonical Publication retained before the admin-to-authority vocabulary cutover.
49
+ * Network admission remains strict; this one-way reader exists only for already-trusted storage.
50
+ */
51
+ export function acceptStored(input) {
52
+ try {
53
+ const publication = accept(input);
54
+ return deepFreeze({ publication, deployedEtag: publication.etag });
55
+ }
56
+ catch (currentFailure) {
57
+ const value = record(input, 'PUBLICATION_INVALID', 'Expected a Publication object.');
58
+ const requirements = record(value.requirements, 'PUBLICATION_INVALID', 'Publication requirements are invalid.');
59
+ const capabilities = record(requirements.capabilities, 'PUBLICATION_INVALID', 'Publication capability requirements are invalid.');
60
+ if (capabilities.admin === undefined)
61
+ throw currentFailure;
62
+ if (capabilities.authority !== undefined) {
63
+ fail('PUBLICATION_INVALID', 'Publication cannot mix admin and authority requirements.');
64
+ }
65
+ const { etag, ...content } = value;
66
+ const admittedEtag = admitDigest(etag);
67
+ if (admittedEtag !== computeEtag(content)) {
68
+ fail('PUBLICATION_ETAG_MISMATCH', 'Publication ETag does not match its canonical content.');
69
+ }
70
+ const { admin, ...currentCapabilities } = capabilities;
71
+ const admitted = acceptPublication({
72
+ ...value,
73
+ requirements: {
74
+ ...requirements,
75
+ capabilities: { ...currentCapabilities, authority: admin },
76
+ },
77
+ }, true);
78
+ return deepFreeze({ publication: seal(withoutEtag(admitted)), deployedEtag: admittedEtag });
79
+ }
80
+ }
47
81
  export function encode(input) {
48
82
  return portable.json.encode(input);
49
83
  }
@@ -1,6 +1,6 @@
1
1
  import type { AuthorityOperation } from '@astrale-os/kernel-core/schema/authority/operation';
2
- import type { schema } from '@astrale-os/kernel-dsl/v1';
3
- import type { ClassRef, Key as SemanticKey } from '@astrale-os/kernel-dsl/v1/addressing';
2
+ import type { schema as schemaLanguage } from '@astrale-os/kernel-dsl/v1';
3
+ import type { ClassRef, CoreRef, Key as SemanticKey } from '@astrale-os/kernel-dsl/v1/addressing';
4
4
  export type NonEmpty<Value> = readonly [Value, ...Value[]];
5
5
  export declare const ClassOperationOrder: readonly ["create", "read", "traverse", "update", "delete"];
6
6
  export type ClassOperation = (typeof ClassOperationOrder)[number];
@@ -16,7 +16,14 @@ export interface CapabilityRequirements {
16
16
  readonly functions?: NonEmpty<SemanticKey.Callable>;
17
17
  readonly classes?: NonEmpty<ClassCapabilityRequirement>;
18
18
  readonly authority?: CapabilityAuthorityRequirements;
19
- readonly admin?: never;
19
+ readonly profiles?: NonEmpty<CapabilityProfileRequirement>;
20
+ }
21
+ /** Installation-owned authority materialized onto one local Core Identity. */
22
+ export interface CapabilityProfileRequirement {
23
+ readonly subject: CoreRef;
24
+ readonly functions?: NonEmpty<SemanticKey.Callable>;
25
+ readonly classes?: NonEmpty<ClassCapabilityRequirement>;
26
+ readonly authority?: CapabilityAuthorityRequirements;
20
27
  }
21
28
  export interface PublicationRequirements {
22
29
  readonly capabilities: CapabilityRequirements;
@@ -24,4 +31,4 @@ export interface PublicationRequirements {
24
31
  /** Admit the exact closed Publication v2 requirements object. */
25
32
  export declare function admitRequirements(input: unknown): PublicationRequirements;
26
33
  /** Bind explicit capability targets to exact functional dependencies in the bundle closure. */
27
- export declare function validateRequirements(root: schema.DomainSchema, closure: readonly schema.DomainSchema[], requirements: PublicationRequirements): void;
34
+ export declare function validateRequirements(root: schemaLanguage.DomainSchema, closure: readonly schemaLanguage.DomainSchema[], requirements: PublicationRequirements): void;
@@ -1,4 +1,6 @@
1
+ import { K } from '@astrale-os/kernel-core/schema';
1
2
  import { AuthorityOperationOrder } from '@astrale-os/kernel-core/schema/authority/operation';
3
+ import { schema as schemaApi } from '@astrale-os/kernel-dsl/v1';
2
4
  import { acceptDefinitionRef, callableAddress, Key } from '@astrale-os/kernel-dsl/v1/addressing';
3
5
  import { revision } from '@astrale-os/kernel-dsl/v1/schema/revision';
4
6
  import { array, deepFreeze, exact, record, unique } from './content.js';
@@ -15,7 +17,7 @@ export function admitRequirements(input) {
15
17
  const requirements = record(input, 'PUBLICATION_INVALID', 'Publication requirements are invalid.');
16
18
  exact(requirements, ['capabilities'], 'PUBLICATION_INVALID');
17
19
  const capabilities = record(requirements.capabilities, 'PUBLICATION_INVALID', 'Publication capability requirements are invalid.');
18
- exact(capabilities, ['authority', 'classes', 'functions'], 'PUBLICATION_INVALID');
20
+ exact(capabilities, ['authority', 'classes', 'functions', 'profiles'], 'PUBLICATION_INVALID');
19
21
  const functions = capabilities.functions === undefined
20
22
  ? undefined
21
23
  : nonEmpty(capabilities.functions, 'Function capability requirements').map(admitCallable);
@@ -33,6 +35,13 @@ export function admitRequirements(input) {
33
35
  const authority = capabilities.authority === undefined
34
36
  ? undefined
35
37
  : admitAuthorityRequirements(capabilities.authority);
38
+ const profiles = capabilities.profiles === undefined
39
+ ? undefined
40
+ : nonEmpty(capabilities.profiles, 'Capability profiles').map(admitCapabilityProfile);
41
+ if (profiles !== undefined) {
42
+ unique(profiles.map(({ subject }) => Key.of(subject)), 'PUBLICATION_INVALID', 'Capability profile subject');
43
+ canonical(profiles, (left, right) => compare(Key.of(left.subject), Key.of(right.subject)), 'Capability profiles');
44
+ }
36
45
  return deepFreeze({
37
46
  capabilities: {
38
47
  ...(functions === undefined
@@ -42,9 +51,45 @@ export function admitRequirements(input) {
42
51
  ? {}
43
52
  : { classes: Object.freeze(classes) }),
44
53
  ...(authority === undefined ? {} : { authority }),
54
+ ...(profiles === undefined
55
+ ? {}
56
+ : { profiles: Object.freeze(profiles) }),
45
57
  },
46
58
  });
47
59
  }
60
+ function admitCapabilityProfile(input) {
61
+ const profile = record(input, 'PUBLICATION_INVALID', 'Capability profile requirement is invalid.');
62
+ exact(profile, ['authority', 'classes', 'functions', 'subject'], 'PUBLICATION_INVALID');
63
+ const subject = admitCoreRef(profile.subject);
64
+ const functions = profile.functions === undefined
65
+ ? undefined
66
+ : nonEmpty(profile.functions, 'Capability profile Function requirements').map(admitCallable);
67
+ if (functions !== undefined) {
68
+ unique(functions, 'PUBLICATION_INVALID', 'Capability profile Function requirement');
69
+ canonical(functions, compare, 'Capability profile Function requirements');
70
+ }
71
+ const classes = profile.classes === undefined
72
+ ? undefined
73
+ : nonEmpty(profile.classes, 'Capability profile Class requirements').map(admitClassRequirement);
74
+ if (classes !== undefined) {
75
+ unique(classes.map((entry) => classKey(entry.class)), 'PUBLICATION_INVALID', 'Capability profile Class requirement');
76
+ canonical(classes, (left, right) => compare(classKey(left.class), classKey(right.class)), 'Capability profile Class requirements');
77
+ }
78
+ const authority = profile.authority === undefined ? undefined : admitAuthorityRequirements(profile.authority);
79
+ if (functions === undefined && classes === undefined && authority === undefined) {
80
+ fail('PUBLICATION_INVALID', 'Capability profile requirements cannot be empty.');
81
+ }
82
+ return Object.freeze({
83
+ subject,
84
+ ...(functions === undefined
85
+ ? {}
86
+ : { functions: Object.freeze(functions) }),
87
+ ...(classes === undefined
88
+ ? {}
89
+ : { classes: Object.freeze(classes) }),
90
+ ...(authority === undefined ? {} : { authority }),
91
+ });
92
+ }
48
93
  /** Bind explicit capability targets to exact functional dependencies in the bundle closure. */
49
94
  export function validateRequirements(root, closure, requirements) {
50
95
  const dependencies = exactDependencyClosure(root, closure);
@@ -61,6 +106,36 @@ export function validateRequirements(root, closure, requirements) {
61
106
  fail('PUBLICATION_INVALID', 'Class capability requirement names an unknown dependency Class.');
62
107
  }
63
108
  }
109
+ const resolvedRoot = requirements.capabilities.profiles === undefined
110
+ ? undefined
111
+ : schemaApi.resolve(schemaApi.accept(root, {
112
+ get(origin, expectedRevision) {
113
+ return closure.find((dependency) => dependency.origin === origin && revision(dependency) === expectedRevision);
114
+ },
115
+ }));
116
+ for (const profile of requirements.capabilities.profiles ?? []) {
117
+ const subject = resolvedRoot.core.nodes[profile.subject.name];
118
+ if (profile.subject.origin !== root.origin ||
119
+ subject === undefined ||
120
+ !subject.class.satisfies(K.classes.Identity)) {
121
+ fail('PUBLICATION_INVALID', 'Capability profile subject must name one local Core Identity.');
122
+ }
123
+ for (const callable of profile.functions ?? []) {
124
+ const dependency = capabilityOwner(root, dependencies, callable, 'Function');
125
+ if (!hasCallable(dependency, callable)) {
126
+ fail('PUBLICATION_INVALID', 'Capability profile Function requirement names an unknown dependency callable.');
127
+ }
128
+ }
129
+ for (const requirement of profile.classes ?? []) {
130
+ const key = Key.of(requirement.class);
131
+ const owner = requirement.class.origin === root.origin
132
+ ? root
133
+ : capabilityOwner(root, dependencies, key, 'Class');
134
+ if (owner.classes[requirement.class.name] === undefined) {
135
+ fail('PUBLICATION_INVALID', 'Capability profile Class requirement names an unknown dependency Class.');
136
+ }
137
+ }
138
+ }
64
139
  }
65
140
  function exactDependencyClosure(root, closure) {
66
141
  const supplied = new Map(closure.map((dependency) => [dependency.origin, dependency]));
@@ -136,6 +211,19 @@ function admitClassRef(input) {
136
211
  fail('PUBLICATION_INVALID', 'Class capability reference is invalid.');
137
212
  return value;
138
213
  }
214
+ function admitCoreRef(input) {
215
+ let value;
216
+ try {
217
+ value = acceptDefinitionRef(input);
218
+ }
219
+ catch {
220
+ fail('PUBLICATION_INVALID', 'Capability profile subject is invalid.');
221
+ }
222
+ if (value.kind !== 'core') {
223
+ fail('PUBLICATION_INVALID', 'Capability profile subject must be one local Core reference.');
224
+ }
225
+ return value;
226
+ }
139
227
  function nonEmpty(input, label) {
140
228
  const values = [...array(input, 'PUBLICATION_INVALID')];
141
229
  if (values.length === 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrale-os/kernel-protocol",
3
- "version": "0.5.0-beta.30",
3
+ "version": "0.5.0-beta.31",
4
4
  "description": "Portable Astrale invocation, publication, installation, routing, and issuer protocols",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -133,8 +133,8 @@
133
133
  "access": "public"
134
134
  },
135
135
  "dependencies": {
136
- "@astrale-os/kernel-core": "0.9.0-beta.28",
137
- "@astrale-os/kernel-dsl": "0.2.0-beta.21",
136
+ "@astrale-os/kernel-core": "0.9.0-beta.29",
137
+ "@astrale-os/kernel-dsl": "0.2.0-beta.22",
138
138
  "@noble/hashes": "2.3.0"
139
139
  },
140
140
  "devDependencies": {