@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 +20 -0
- package/dist/publication/index.d.ts +2 -2
- package/dist/publication/index.js +1 -1
- package/dist/publication/publication.d.ts +10 -0
- package/dist/publication/publication.js +34 -0
- package/dist/publication/requirements.d.ts +11 -4
- package/dist/publication/requirements.js +89 -1
- package/package.json +3 -3
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
|
|
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:
|
|
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.
|
|
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.
|
|
137
|
-
"@astrale-os/kernel-dsl": "0.2.0-beta.
|
|
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": {
|