@serve.zone/interfaces 28.0.0 → 28.2.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.
- package/changelog.md +27 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/data/index.d.ts +1 -0
- package/dist_ts/data/index.js +2 -1
- package/dist_ts/data/nodeenrollment.d.ts +152 -0
- package/dist_ts/data/nodeenrollment.js +255 -0
- package/dist_ts/requests/index.d.ts +2 -1
- package/dist_ts/requests/index.js +3 -2
- package/dist_ts/requests/node.d.ts +18 -0
- package/dist_ts/requests/pallet.d.ts +23 -0
- package/dist_ts/requests/pallet.js +2 -0
- package/package.json +1 -1
- package/readme.md +61 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/data/index.ts +1 -0
- package/ts/data/nodeenrollment.ts +399 -0
- package/ts/requests/index.ts +2 -0
- package/ts/requests/node.ts +27 -0
- package/ts/requests/pallet.ts +38 -0
package/changelog.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-09-06 - 28.2.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add Pallet node enrollment IPC contracts (pallet-enrollment)
|
|
8
|
+
- Add Pallet node enrollment preparation, activation, and confirmation data contracts with snapshot and binding helpers.
|
|
9
|
+
- Export requests.pallet typed methods for prepare, bind, and activate enrollment flows.
|
|
10
|
+
- Document Pallet IPC framing limits and validation responsibilities with test coverage for enrollment semantics.
|
|
11
|
+
|
|
12
|
+
## 2026-09-06 - 28.1.0
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- add independent node enrollment contracts (node-enrollment)
|
|
17
|
+
- Export node enrollment data interfaces and helpers for strict snapshots, canonical digests, proof verification, acknowledgement binding, and state snapshots.
|
|
18
|
+
- Add node TypedRequest contracts for enrollment state checks and node enrollment transactions.
|
|
19
|
+
- Document node enrollment semantics and add tests for digest binding, proof validation, acknowledgement validation, and TypedRequest ownership.
|
|
20
|
+
|
|
21
|
+
### Fixes
|
|
22
|
+
|
|
23
|
+
- use qualified pnpm 11.25.0 for exact release packaging (package)
|
|
24
|
+
- Select the GitZone-qualified version permitted by the existing release-age policy.
|
|
25
|
+
- Remove the inactive pnpm 12 bootstrap lock document without changing application dependencies.
|
|
26
|
+
- clarify pnpm release packaging changelog entry (changelog)
|
|
27
|
+
- Consolidates duplicate pnpm pinning notes into a single package fix entry.
|
|
28
|
+
- Documents qualified pnpm 11.25.0 selection for release packaging.
|
|
29
|
+
|
|
3
30
|
## 2026-09-05 - 28.0.0
|
|
4
31
|
|
|
5
32
|
### Breaking Changes
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/interfaces',
|
|
6
|
-
version: '28.
|
|
6
|
+
version: '28.2.0',
|
|
7
7
|
description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
|
package/dist_ts/data/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export * from './isolatedrestore.js';
|
|
|
21
21
|
export * from './isolatedrestore.golden.js';
|
|
22
22
|
export * from './mail.js';
|
|
23
23
|
export * from './nodecredential.js';
|
|
24
|
+
export * from './nodeenrollment.js';
|
|
24
25
|
export * from './registry.js';
|
|
25
26
|
export * from './runtimeassignment.js';
|
|
26
27
|
export * from './secret.js';
|
package/dist_ts/data/index.js
CHANGED
|
@@ -21,6 +21,7 @@ export * from './isolatedrestore.js';
|
|
|
21
21
|
export * from './isolatedrestore.golden.js';
|
|
22
22
|
export * from './mail.js';
|
|
23
23
|
export * from './nodecredential.js';
|
|
24
|
+
export * from './nodeenrollment.js';
|
|
24
25
|
export * from './registry.js';
|
|
25
26
|
export * from './runtimeassignment.js';
|
|
26
27
|
export * from './secret.js';
|
|
@@ -37,4 +38,4 @@ export * from './traffic.js';
|
|
|
37
38
|
export * from './user.js';
|
|
38
39
|
export * from './version.js';
|
|
39
40
|
export * from './webpush.js';
|
|
40
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
41
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxlQUFlLENBQUM7QUFDOUIsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMseUJBQXlCLENBQUM7QUFDeEMsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxVQUFVLENBQUM7QUFDekIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxZQUFZLENBQUM7QUFDM0IsY0FBYyx1QkFBdUIsQ0FBQztBQUN0QyxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLDZCQUE2QixDQUFDO0FBQzVDLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsZ0JBQWdCLENBQUM7QUFDL0IsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxrQkFBa0IsQ0FBQztBQUNqQyxjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLG1CQUFtQixDQUFDO0FBQ2xDLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxXQUFXLENBQUM7QUFDMUIsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxjQUFjLENBQUMifQ==
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { TNodeCredentialPurpose } from './nodecredential.js';
|
|
2
|
+
export interface INodeEnrollmentCredentialProposalV1 {
|
|
3
|
+
purpose: TNodeCredentialPurpose;
|
|
4
|
+
installationId: string;
|
|
5
|
+
mutationId: string;
|
|
6
|
+
/** SHA-256 of the independently generated, durably prepared bearer UTF-8 bytes. */
|
|
7
|
+
tokenHash: string;
|
|
8
|
+
expectedGeneration: number;
|
|
9
|
+
expectedSessionEpoch: number;
|
|
10
|
+
}
|
|
11
|
+
/** Value-free, immutable proposal. Hashes are still sensitive transport fields. */
|
|
12
|
+
export interface INodeEnrollmentV1 {
|
|
13
|
+
schemaVersion: 1;
|
|
14
|
+
/** 32 CSPRNG bytes encoded as lowercase hexadecimal; retain across every retry. */
|
|
15
|
+
enrollmentId: string;
|
|
16
|
+
cloudlyOrigin: string;
|
|
17
|
+
hostname: string;
|
|
18
|
+
bootstrap: {
|
|
19
|
+
kind: 'jump';
|
|
20
|
+
proofHash: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'adopt-existing';
|
|
23
|
+
nodeId: string;
|
|
24
|
+
proofHash: string;
|
|
25
|
+
};
|
|
26
|
+
credentials: [
|
|
27
|
+
INodeEnrollmentCredentialProposalV1 & {
|
|
28
|
+
purpose: 'spark';
|
|
29
|
+
},
|
|
30
|
+
INodeEnrollmentCredentialProposalV1 & {
|
|
31
|
+
purpose: 'pallet';
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
export interface INodeEnrollmentRequestV1 {
|
|
36
|
+
enrollment: INodeEnrollmentV1;
|
|
37
|
+
/** Never persist on the server, log, put in URLs, or expose in transport hooks. */
|
|
38
|
+
proof: {
|
|
39
|
+
kind: 'bootstrap' | 'pending-spark';
|
|
40
|
+
bearer: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface INodeEnrollmentCredentialAcknowledgementV1 {
|
|
44
|
+
purpose: TNodeCredentialPurpose;
|
|
45
|
+
installationId: string;
|
|
46
|
+
mutationId: string;
|
|
47
|
+
tokenHash: string;
|
|
48
|
+
generation: number;
|
|
49
|
+
}
|
|
50
|
+
/** Immutable commit evidence, not authorization or workload readiness. */
|
|
51
|
+
export interface INodeEnrollmentAcknowledgementV1 {
|
|
52
|
+
schemaVersion: 1;
|
|
53
|
+
enrollmentId: string;
|
|
54
|
+
cloudlyOrigin: string;
|
|
55
|
+
nodeId: string;
|
|
56
|
+
requestDigest: string;
|
|
57
|
+
credentials: [
|
|
58
|
+
INodeEnrollmentCredentialAcknowledgementV1 & {
|
|
59
|
+
purpose: 'spark';
|
|
60
|
+
},
|
|
61
|
+
INodeEnrollmentCredentialAcknowledgementV1 & {
|
|
62
|
+
purpose: 'pallet';
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
/** Returned only after current Spark authentication and proof that Pallet is absent. */
|
|
67
|
+
export interface INodeEnrollmentStateV1 {
|
|
68
|
+
schemaVersion: 1;
|
|
69
|
+
nodeId: string;
|
|
70
|
+
spark: {
|
|
71
|
+
generation: number;
|
|
72
|
+
sessionEpoch: number;
|
|
73
|
+
};
|
|
74
|
+
pallet: 'absent';
|
|
75
|
+
}
|
|
76
|
+
/** Root-authenticated, node-local IPC only. Initial Pallet authority is always absent. */
|
|
77
|
+
export interface IPalletNodeEnrollmentPreparationV1 {
|
|
78
|
+
schemaVersion: 1;
|
|
79
|
+
purpose: 'pallet';
|
|
80
|
+
installationId: string;
|
|
81
|
+
mutationId: string;
|
|
82
|
+
cloudlyOrigin: string;
|
|
83
|
+
nodeId: string | null;
|
|
84
|
+
}
|
|
85
|
+
/** Full exact activation wire body; Cloudly authentication remains the coordinator's responsibility. */
|
|
86
|
+
export interface IPalletNodeEnrollmentActivationV1 {
|
|
87
|
+
enrollment: INodeEnrollmentV1;
|
|
88
|
+
acknowledgement: INodeEnrollmentAcknowledgementV1;
|
|
89
|
+
}
|
|
90
|
+
interface IPalletNodeEnrollmentConfirmationBaseV1 {
|
|
91
|
+
schemaVersion: 1;
|
|
92
|
+
purpose: 'pallet';
|
|
93
|
+
enrollmentId: string;
|
|
94
|
+
cloudlyOrigin: string;
|
|
95
|
+
requestDigest: string;
|
|
96
|
+
}
|
|
97
|
+
/** The exact pending binding is durable now, not merely a historical receipt. */
|
|
98
|
+
export interface IPalletNodeEnrollmentBoundV1 extends IPalletNodeEnrollmentConfirmationBaseV1 {
|
|
99
|
+
phase: 'bound';
|
|
100
|
+
/** Null before Cloudly assigns a fresh Jump node; exact existing node for adoption. */
|
|
101
|
+
nodeId: string | null;
|
|
102
|
+
}
|
|
103
|
+
/** Exact CURRENT local Pallet identity, not Spark activation or remote authorization. */
|
|
104
|
+
export interface IPalletNodeEnrollmentActiveV1 extends IPalletNodeEnrollmentConfirmationBaseV1 {
|
|
105
|
+
phase: 'active';
|
|
106
|
+
nodeId: string;
|
|
107
|
+
}
|
|
108
|
+
export type TPalletNodeEnrollmentConfirmationV1 = IPalletNodeEnrollmentBoundV1 | IPalletNodeEnrollmentActiveV1;
|
|
109
|
+
/** Wire bounds, not a listener, permission check, timeout implementation or storage API. */
|
|
110
|
+
export declare const palletNodeEnrollmentV1Contract: Readonly<{
|
|
111
|
+
readonly socketPath: "/run/serve.zone/pallet/control.sock";
|
|
112
|
+
readonly frame: "u32be-length-prefixed-utf8-json-then-write-half-close";
|
|
113
|
+
readonly requestsPerConnection: 1;
|
|
114
|
+
readonly maxRequestBytes: 32768;
|
|
115
|
+
readonly maxResponseBytes: 16384;
|
|
116
|
+
}>;
|
|
117
|
+
export declare const nodeEnrollmentV1Contract: Readonly<{
|
|
118
|
+
readonly digestDomain: "serve.zone/node-enrollment-v1";
|
|
119
|
+
readonly localActivation: "independently-durable-convergence";
|
|
120
|
+
readonly bootstrapProof: "first-commit-only";
|
|
121
|
+
readonly pendingSparkProof: "committed-exact-receipt-only";
|
|
122
|
+
}>;
|
|
123
|
+
/** Detached frozen snapshot, captured synchronously before hashing or other awaits. */
|
|
124
|
+
export declare const snapshotNodeEnrollmentV1: (valueArg: unknown) => INodeEnrollmentV1;
|
|
125
|
+
export declare const computeNodeEnrollmentV1Digest: (valueArg: INodeEnrollmentV1) => Promise<string>;
|
|
126
|
+
export declare const snapshotNodeEnrollmentRequestV1: (valueArg: unknown) => INodeEnrollmentRequestV1;
|
|
127
|
+
/**
|
|
128
|
+
* Pure proof binding/mode check only, NOT authentication. The server must derive
|
|
129
|
+
* receiptState from its owned transaction, compare the complete stored proposal,
|
|
130
|
+
* and fence current Jump/Spark/Pallet authority before any commit or replay.
|
|
131
|
+
*/
|
|
132
|
+
export declare const verifyNodeEnrollmentProofV1: (valueArg: INodeEnrollmentRequestV1, receiptStateArg: "absent" | "committed") => Promise<boolean>;
|
|
133
|
+
export declare const snapshotNodeEnrollmentAcknowledgementV1: (valueArg: unknown) => INodeEnrollmentAcknowledgementV1;
|
|
134
|
+
/** Content binding only. Authenticate Cloudly first; persist only the returned snapshot. */
|
|
135
|
+
export declare const bindNodeEnrollmentAcknowledgementV1: (enrollmentArg: INodeEnrollmentV1, acknowledgementArg: INodeEnrollmentAcknowledgementV1) => Promise<INodeEnrollmentAcknowledgementV1>;
|
|
136
|
+
export declare const snapshotNodeEnrollmentStateV1: (valueArg: unknown) => INodeEnrollmentStateV1;
|
|
137
|
+
/** Decoded JSON/data records only, captured synchronously; never invokes getters. */
|
|
138
|
+
export declare const snapshotPalletNodeEnrollmentPreparationV1: (valueArg: unknown) => IPalletNodeEnrollmentPreparationV1;
|
|
139
|
+
/** Content binding only. Authenticate the local Pallet transport before trusting this proposal. */
|
|
140
|
+
export declare const bindPalletNodeEnrollmentPreparationV1: (preparationArg: IPalletNodeEnrollmentPreparationV1, proposalArg: unknown) => INodeEnrollmentV1["credentials"][1];
|
|
141
|
+
/** Capture the exact wrapper and both nested values before any asynchronous content binding. */
|
|
142
|
+
export declare const snapshotPalletNodeEnrollmentActivationV1: (valueArg: unknown) => IPalletNodeEnrollmentActivationV1;
|
|
143
|
+
export declare const snapshotPalletNodeEnrollmentConfirmationV1: (valueArg: unknown) => TPalletNodeEnrollmentConfirmationV1;
|
|
144
|
+
/** Pure binding only. Pallet must prove its durable pending binding before issuing this response. */
|
|
145
|
+
export declare const bindPalletNodeEnrollmentBoundV1: (enrollmentArg: INodeEnrollmentV1, confirmationArg: IPalletNodeEnrollmentBoundV1) => Promise<IPalletNodeEnrollmentBoundV1>;
|
|
146
|
+
/**
|
|
147
|
+
* Pure binding only, NOT authentication or a database check. The root-authenticated
|
|
148
|
+
* Pallet owner must first prove its exact CURRENT local identity, not just a receipt.
|
|
149
|
+
* This does not assert Spark activation, current Cloudly authority or workload readiness.
|
|
150
|
+
*/
|
|
151
|
+
export declare const bindPalletNodeEnrollmentActiveV1: (activationArg: IPalletNodeEnrollmentActivationV1, confirmationArg: IPalletNodeEnrollmentActiveV1) => Promise<IPalletNodeEnrollmentActiveV1>;
|
|
152
|
+
export {};
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { canonicalizeStrictJson, createCanonicalJsonSha256Hex, createSha256Hex, deepFreezeValue, } from '../private/canonicaljson.js';
|
|
2
|
+
/** Wire bounds, not a listener, permission check, timeout implementation or storage API. */
|
|
3
|
+
export const palletNodeEnrollmentV1Contract = Object.freeze({
|
|
4
|
+
socketPath: '/run/serve.zone/pallet/control.sock',
|
|
5
|
+
frame: 'u32be-length-prefixed-utf8-json-then-write-half-close',
|
|
6
|
+
requestsPerConnection: 1,
|
|
7
|
+
maxRequestBytes: 32768,
|
|
8
|
+
maxResponseBytes: 16384,
|
|
9
|
+
});
|
|
10
|
+
export const nodeEnrollmentV1Contract = Object.freeze({
|
|
11
|
+
digestDomain: 'serve.zone/node-enrollment-v1',
|
|
12
|
+
localActivation: 'independently-durable-convergence',
|
|
13
|
+
bootstrapProof: 'first-commit-only',
|
|
14
|
+
pendingSparkProof: 'committed-exact-receipt-only',
|
|
15
|
+
});
|
|
16
|
+
const fail = () => { throw new Error('Node enrollment contract rejected.'); };
|
|
17
|
+
function requireValue(valueArg) {
|
|
18
|
+
if (!valueArg)
|
|
19
|
+
fail();
|
|
20
|
+
}
|
|
21
|
+
/** Decoded JSON/data records only. No input accessor is executed or preserved. */
|
|
22
|
+
const record = (valueArg, keysArg) => {
|
|
23
|
+
requireValue(valueArg !== null && typeof valueArg === 'object' && !Array.isArray(valueArg));
|
|
24
|
+
const prototype = Object.getPrototypeOf(valueArg);
|
|
25
|
+
requireValue(prototype === Object.prototype || prototype === null);
|
|
26
|
+
const keys = Reflect.ownKeys(valueArg);
|
|
27
|
+
requireValue(keys.length === keysArg.length);
|
|
28
|
+
const snapshot = Object.create(null);
|
|
29
|
+
for (const key of keys) {
|
|
30
|
+
requireValue(typeof key === 'string' && keysArg.includes(key));
|
|
31
|
+
const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
|
|
32
|
+
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
33
|
+
snapshot[key] = descriptor.value;
|
|
34
|
+
}
|
|
35
|
+
return snapshot;
|
|
36
|
+
};
|
|
37
|
+
const pair = (valueArg) => {
|
|
38
|
+
requireValue(Array.isArray(valueArg) && Object.getPrototypeOf(valueArg) === Array.prototype);
|
|
39
|
+
requireValue(Reflect.ownKeys(valueArg).length === 3 && valueArg.length === 2);
|
|
40
|
+
const first = Object.getOwnPropertyDescriptor(valueArg, '0');
|
|
41
|
+
const second = Object.getOwnPropertyDescriptor(valueArg, '1');
|
|
42
|
+
requireValue(first?.enumerable && Object.hasOwn(first, 'value'));
|
|
43
|
+
requireValue(second?.enumerable && Object.hasOwn(second, 'value'));
|
|
44
|
+
return [first.value, second.value];
|
|
45
|
+
};
|
|
46
|
+
const id = (valueArg) => {
|
|
47
|
+
requireValue(typeof valueArg === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(valueArg));
|
|
48
|
+
return valueArg;
|
|
49
|
+
};
|
|
50
|
+
const hash = (valueArg) => {
|
|
51
|
+
requireValue(typeof valueArg === 'string' && /^[a-f0-9]{64}$/.test(valueArg));
|
|
52
|
+
return valueArg;
|
|
53
|
+
};
|
|
54
|
+
const counter = (valueArg, minimumArg = 0, incrementArg = false) => {
|
|
55
|
+
requireValue(typeof valueArg === 'number' && Number.isSafeInteger(valueArg)
|
|
56
|
+
&& !Object.is(valueArg, -0) && valueArg >= minimumArg
|
|
57
|
+
&& (!incrementArg || valueArg < Number.MAX_SAFE_INTEGER));
|
|
58
|
+
return valueArg;
|
|
59
|
+
};
|
|
60
|
+
const origin = (valueArg) => {
|
|
61
|
+
requireValue(typeof valueArg === 'string' && valueArg.length <= 2048);
|
|
62
|
+
let url;
|
|
63
|
+
try {
|
|
64
|
+
url = new URL(valueArg);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return fail();
|
|
68
|
+
}
|
|
69
|
+
requireValue(url.protocol === 'https:' && !url.username && !url.password && url.origin === valueArg);
|
|
70
|
+
return valueArg;
|
|
71
|
+
};
|
|
72
|
+
const hostname = (valueArg) => {
|
|
73
|
+
requireValue(typeof valueArg === 'string' && valueArg.length <= 253 && valueArg.length > 0);
|
|
74
|
+
requireValue(valueArg.split('.').every((labelArg) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(labelArg)));
|
|
75
|
+
return valueArg;
|
|
76
|
+
};
|
|
77
|
+
const proposal = (valueArg, purposeArg) => {
|
|
78
|
+
const value = record(valueArg, ['purpose', 'installationId', 'mutationId', 'tokenHash',
|
|
79
|
+
'expectedGeneration', 'expectedSessionEpoch']);
|
|
80
|
+
requireValue(value.purpose === purposeArg);
|
|
81
|
+
return { purpose: purposeArg, installationId: id(value.installationId), mutationId: id(value.mutationId),
|
|
82
|
+
tokenHash: hash(value.tokenHash), expectedGeneration: counter(value.expectedGeneration, 0, true),
|
|
83
|
+
expectedSessionEpoch: counter(value.expectedSessionEpoch, 0, true) };
|
|
84
|
+
};
|
|
85
|
+
const distinctOwners = (sparkArg, palletArg) => {
|
|
86
|
+
requireValue(sparkArg.installationId !== palletArg.installationId
|
|
87
|
+
&& sparkArg.mutationId !== palletArg.mutationId && sparkArg.tokenHash !== palletArg.tokenHash);
|
|
88
|
+
};
|
|
89
|
+
/** Detached frozen snapshot, captured synchronously before hashing or other awaits. */
|
|
90
|
+
export const snapshotNodeEnrollmentV1 = (valueArg) => {
|
|
91
|
+
const value = record(valueArg, ['schemaVersion', 'enrollmentId', 'cloudlyOrigin', 'hostname',
|
|
92
|
+
'bootstrap', 'credentials']);
|
|
93
|
+
requireValue(value.schemaVersion === 1);
|
|
94
|
+
// Read the discriminant from its data descriptor, never an input getter.
|
|
95
|
+
requireValue(value.bootstrap !== null && typeof value.bootstrap === 'object');
|
|
96
|
+
const kind = Object.getOwnPropertyDescriptor(value.bootstrap, 'kind');
|
|
97
|
+
requireValue(kind && Object.hasOwn(kind, 'value'));
|
|
98
|
+
const bootstrap = record(value.bootstrap, kind.value === 'jump'
|
|
99
|
+
? ['kind', 'proofHash'] : ['kind', 'nodeId', 'proofHash']);
|
|
100
|
+
requireValue(bootstrap.kind === 'jump' || bootstrap.kind === 'adopt-existing');
|
|
101
|
+
const [sparkInput, palletInput] = pair(value.credentials);
|
|
102
|
+
const spark = proposal(sparkInput, 'spark');
|
|
103
|
+
const pallet = proposal(palletInput, 'pallet');
|
|
104
|
+
distinctOwners(spark, pallet);
|
|
105
|
+
requireValue(pallet.expectedGeneration === 0 && pallet.expectedSessionEpoch === 0);
|
|
106
|
+
requireValue(bootstrap.kind === 'jump'
|
|
107
|
+
? spark.expectedGeneration === 0 && spark.expectedSessionEpoch === 0
|
|
108
|
+
: spark.expectedGeneration > 0);
|
|
109
|
+
return deepFreezeValue({ schemaVersion: 1, enrollmentId: hash(value.enrollmentId),
|
|
110
|
+
cloudlyOrigin: origin(value.cloudlyOrigin), hostname: hostname(value.hostname),
|
|
111
|
+
bootstrap: bootstrap.kind === 'jump'
|
|
112
|
+
? { kind: 'jump', proofHash: hash(bootstrap.proofHash) }
|
|
113
|
+
: { kind: 'adopt-existing', nodeId: id(bootstrap.nodeId), proofHash: hash(bootstrap.proofHash) },
|
|
114
|
+
credentials: [spark, pallet] });
|
|
115
|
+
};
|
|
116
|
+
export const computeNodeEnrollmentV1Digest = async (valueArg) => {
|
|
117
|
+
const payload = snapshotNodeEnrollmentV1(valueArg);
|
|
118
|
+
return createCanonicalJsonSha256Hex(canonicalizeStrictJson({
|
|
119
|
+
domain: nodeEnrollmentV1Contract.digestDomain, payload,
|
|
120
|
+
}, fail), fail);
|
|
121
|
+
};
|
|
122
|
+
export const snapshotNodeEnrollmentRequestV1 = (valueArg) => {
|
|
123
|
+
const value = record(valueArg, ['enrollment', 'proof']);
|
|
124
|
+
const enrollment = snapshotNodeEnrollmentV1(value.enrollment);
|
|
125
|
+
const proof = record(value.proof, ['kind', 'bearer']);
|
|
126
|
+
requireValue(proof.kind === 'bootstrap' || proof.kind === 'pending-spark');
|
|
127
|
+
requireValue(typeof proof.bearer === 'string');
|
|
128
|
+
requireValue(proof.kind === 'bootstrap' && enrollment.bootstrap.kind === 'jump'
|
|
129
|
+
? /^[A-Za-z0-9_-]{16}$/.test(proof.bearer) : /^secureToken[a-f0-9]{64}$/.test(proof.bearer));
|
|
130
|
+
return deepFreezeValue({ enrollment, proof: { kind: proof.kind, bearer: proof.bearer } });
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Pure proof binding/mode check only, NOT authentication. The server must derive
|
|
134
|
+
* receiptState from its owned transaction, compare the complete stored proposal,
|
|
135
|
+
* and fence current Jump/Spark/Pallet authority before any commit or replay.
|
|
136
|
+
*/
|
|
137
|
+
export const verifyNodeEnrollmentProofV1 = async (valueArg, receiptStateArg) => {
|
|
138
|
+
try {
|
|
139
|
+
const { enrollment, proof } = snapshotNodeEnrollmentRequestV1(valueArg);
|
|
140
|
+
if (!(proof.kind === 'bootstrap' && receiptStateArg === 'absent')
|
|
141
|
+
&& !(proof.kind === 'pending-spark' && receiptStateArg === 'committed'))
|
|
142
|
+
return false;
|
|
143
|
+
// This helper hashes exact UTF-8 text; no JSON quoting or normalization.
|
|
144
|
+
const actual = await createSha256Hex(new TextEncoder().encode(proof.bearer), fail);
|
|
145
|
+
return actual === (proof.kind === 'bootstrap'
|
|
146
|
+
? enrollment.bootstrap.proofHash : enrollment.credentials[0].tokenHash);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const credentialAcknowledgement = (valueArg, purposeArg) => {
|
|
153
|
+
const value = record(valueArg, ['purpose', 'installationId', 'mutationId', 'tokenHash', 'generation']);
|
|
154
|
+
requireValue(value.purpose === purposeArg);
|
|
155
|
+
return { purpose: purposeArg, installationId: id(value.installationId), mutationId: id(value.mutationId),
|
|
156
|
+
tokenHash: hash(value.tokenHash), generation: counter(value.generation, 1) };
|
|
157
|
+
};
|
|
158
|
+
export const snapshotNodeEnrollmentAcknowledgementV1 = (valueArg) => {
|
|
159
|
+
const value = record(valueArg, ['schemaVersion', 'enrollmentId', 'cloudlyOrigin', 'nodeId',
|
|
160
|
+
'requestDigest', 'credentials']);
|
|
161
|
+
requireValue(value.schemaVersion === 1);
|
|
162
|
+
const [sparkInput, palletInput] = pair(value.credentials);
|
|
163
|
+
const spark = credentialAcknowledgement(sparkInput, 'spark');
|
|
164
|
+
const pallet = credentialAcknowledgement(palletInput, 'pallet');
|
|
165
|
+
distinctOwners(spark, pallet);
|
|
166
|
+
requireValue(pallet.generation === 1);
|
|
167
|
+
return deepFreezeValue({ schemaVersion: 1, enrollmentId: hash(value.enrollmentId),
|
|
168
|
+
cloudlyOrigin: origin(value.cloudlyOrigin), nodeId: id(value.nodeId),
|
|
169
|
+
requestDigest: hash(value.requestDigest), credentials: [spark, pallet] });
|
|
170
|
+
};
|
|
171
|
+
/** Content binding only. Authenticate Cloudly first; persist only the returned snapshot. */
|
|
172
|
+
export const bindNodeEnrollmentAcknowledgementV1 = async (enrollmentArg, acknowledgementArg) => {
|
|
173
|
+
const enrollment = snapshotNodeEnrollmentV1(enrollmentArg);
|
|
174
|
+
const ack = snapshotNodeEnrollmentAcknowledgementV1(acknowledgementArg);
|
|
175
|
+
requireValue(ack.enrollmentId === enrollment.enrollmentId && ack.cloudlyOrigin === enrollment.cloudlyOrigin
|
|
176
|
+
&& (enrollment.bootstrap.kind === 'jump' || ack.nodeId === enrollment.bootstrap.nodeId));
|
|
177
|
+
for (const index of [0, 1]) {
|
|
178
|
+
const expected = enrollment.credentials[index];
|
|
179
|
+
const actual = ack.credentials[index];
|
|
180
|
+
requireValue(actual.purpose === expected.purpose && actual.installationId === expected.installationId
|
|
181
|
+
&& actual.mutationId === expected.mutationId && actual.tokenHash === expected.tokenHash
|
|
182
|
+
&& actual.generation === expected.expectedGeneration + 1);
|
|
183
|
+
}
|
|
184
|
+
requireValue(ack.requestDigest === await computeNodeEnrollmentV1Digest(enrollment));
|
|
185
|
+
return ack;
|
|
186
|
+
};
|
|
187
|
+
export const snapshotNodeEnrollmentStateV1 = (valueArg) => {
|
|
188
|
+
const value = record(valueArg, ['schemaVersion', 'nodeId', 'spark', 'pallet']);
|
|
189
|
+
requireValue(value.schemaVersion === 1 && value.pallet === 'absent');
|
|
190
|
+
const spark = record(value.spark, ['generation', 'sessionEpoch']);
|
|
191
|
+
return deepFreezeValue({ schemaVersion: 1, nodeId: id(value.nodeId), pallet: 'absent',
|
|
192
|
+
spark: { generation: counter(spark.generation, 1, true), sessionEpoch: counter(spark.sessionEpoch, 0, true) } });
|
|
193
|
+
};
|
|
194
|
+
/** Decoded JSON/data records only, captured synchronously; never invokes getters. */
|
|
195
|
+
export const snapshotPalletNodeEnrollmentPreparationV1 = (valueArg) => {
|
|
196
|
+
const value = record(valueArg, ['schemaVersion', 'purpose', 'installationId', 'mutationId',
|
|
197
|
+
'cloudlyOrigin', 'nodeId']);
|
|
198
|
+
requireValue(value.schemaVersion === 1 && value.purpose === 'pallet');
|
|
199
|
+
return deepFreezeValue({ schemaVersion: 1, purpose: 'pallet',
|
|
200
|
+
installationId: id(value.installationId), mutationId: id(value.mutationId),
|
|
201
|
+
cloudlyOrigin: origin(value.cloudlyOrigin), nodeId: value.nodeId === null ? null : id(value.nodeId) });
|
|
202
|
+
};
|
|
203
|
+
/** Content binding only. Authenticate the local Pallet transport before trusting this proposal. */
|
|
204
|
+
export const bindPalletNodeEnrollmentPreparationV1 = (preparationArg, proposalArg) => {
|
|
205
|
+
const preparation = snapshotPalletNodeEnrollmentPreparationV1(preparationArg);
|
|
206
|
+
const result = proposal(proposalArg, 'pallet');
|
|
207
|
+
requireValue(result.installationId === preparation.installationId
|
|
208
|
+
&& result.mutationId === preparation.mutationId
|
|
209
|
+
&& result.expectedGeneration === 0 && result.expectedSessionEpoch === 0);
|
|
210
|
+
return deepFreezeValue(result);
|
|
211
|
+
};
|
|
212
|
+
/** Capture the exact wrapper and both nested values before any asynchronous content binding. */
|
|
213
|
+
export const snapshotPalletNodeEnrollmentActivationV1 = (valueArg) => {
|
|
214
|
+
const value = record(valueArg, ['enrollment', 'acknowledgement']);
|
|
215
|
+
return deepFreezeValue({ enrollment: snapshotNodeEnrollmentV1(value.enrollment),
|
|
216
|
+
acknowledgement: snapshotNodeEnrollmentAcknowledgementV1(value.acknowledgement) });
|
|
217
|
+
};
|
|
218
|
+
export const snapshotPalletNodeEnrollmentConfirmationV1 = (valueArg) => {
|
|
219
|
+
const value = record(valueArg, ['schemaVersion', 'purpose', 'enrollmentId', 'cloudlyOrigin',
|
|
220
|
+
'requestDigest', 'phase', 'nodeId']);
|
|
221
|
+
requireValue(value.schemaVersion === 1 && value.purpose === 'pallet'
|
|
222
|
+
&& (value.phase === 'bound' || value.phase === 'active'));
|
|
223
|
+
const common = { schemaVersion: 1, purpose: 'pallet',
|
|
224
|
+
enrollmentId: hash(value.enrollmentId), cloudlyOrigin: origin(value.cloudlyOrigin),
|
|
225
|
+
requestDigest: hash(value.requestDigest) };
|
|
226
|
+
return deepFreezeValue(value.phase === 'bound'
|
|
227
|
+
? { ...common, phase: 'bound', nodeId: value.nodeId === null ? null : id(value.nodeId) }
|
|
228
|
+
: { ...common, phase: 'active', nodeId: id(value.nodeId) });
|
|
229
|
+
};
|
|
230
|
+
/** Pure binding only. Pallet must prove its durable pending binding before issuing this response. */
|
|
231
|
+
export const bindPalletNodeEnrollmentBoundV1 = async (enrollmentArg, confirmationArg) => {
|
|
232
|
+
const enrollment = snapshotNodeEnrollmentV1(enrollmentArg);
|
|
233
|
+
const confirmation = snapshotPalletNodeEnrollmentConfirmationV1(confirmationArg);
|
|
234
|
+
requireValue(confirmation.phase === 'bound' && confirmation.enrollmentId === enrollment.enrollmentId
|
|
235
|
+
&& confirmation.cloudlyOrigin === enrollment.cloudlyOrigin
|
|
236
|
+
&& confirmation.nodeId === (enrollment.bootstrap.kind === 'jump' ? null : enrollment.bootstrap.nodeId));
|
|
237
|
+
requireValue(confirmation.requestDigest === await computeNodeEnrollmentV1Digest(enrollment));
|
|
238
|
+
return confirmation;
|
|
239
|
+
};
|
|
240
|
+
/**
|
|
241
|
+
* Pure binding only, NOT authentication or a database check. The root-authenticated
|
|
242
|
+
* Pallet owner must first prove its exact CURRENT local identity, not just a receipt.
|
|
243
|
+
* This does not assert Spark activation, current Cloudly authority or workload readiness.
|
|
244
|
+
*/
|
|
245
|
+
export const bindPalletNodeEnrollmentActiveV1 = async (activationArg, confirmationArg) => {
|
|
246
|
+
// Capture every caller-owned value before the asynchronous shared acknowledgement binder.
|
|
247
|
+
const { enrollment, acknowledgement } = snapshotPalletNodeEnrollmentActivationV1(activationArg);
|
|
248
|
+
const confirmation = snapshotPalletNodeEnrollmentConfirmationV1(confirmationArg);
|
|
249
|
+
requireValue(confirmation.phase === 'active');
|
|
250
|
+
const ack = await bindNodeEnrollmentAcknowledgementV1(enrollment, acknowledgement);
|
|
251
|
+
requireValue(confirmation.enrollmentId === ack.enrollmentId && confirmation.cloudlyOrigin === ack.cloudlyOrigin
|
|
252
|
+
&& confirmation.nodeId === ack.nodeId && confirmation.requestDigest === ack.requestDigest);
|
|
253
|
+
return confirmation;
|
|
254
|
+
};
|
|
255
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWVucm9sbG1lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL25vZGVlbnJvbGxtZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFDTCxzQkFBc0IsRUFDdEIsNEJBQTRCLEVBQzVCLGVBQWUsRUFDZixlQUFlLEdBQ2hCLE1BQU0sNkJBQTZCLENBQUM7QUF1R3JDLDRGQUE0RjtBQUM1RixNQUFNLENBQUMsTUFBTSw4QkFBOEIsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQzFELFVBQVUsRUFBRSxxQ0FBcUM7SUFDakQsS0FBSyxFQUFFLHVEQUF1RDtJQUM5RCxxQkFBcUIsRUFBRSxDQUFDO0lBQ3hCLGVBQWUsRUFBRSxLQUFLO0lBQ3RCLGdCQUFnQixFQUFFLEtBQUs7Q0FDZixDQUFDLENBQUM7QUFFWixNQUFNLENBQUMsTUFBTSx3QkFBd0IsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ3BELFlBQVksRUFBRSwrQkFBK0I7SUFDN0MsZUFBZSxFQUFFLG1DQUFtQztJQUNwRCxjQUFjLEVBQUUsbUJBQW1CO0lBQ25DLGlCQUFpQixFQUFFLDhCQUE4QjtDQUN6QyxDQUFDLENBQUM7QUFFWixNQUFNLElBQUksR0FBRyxHQUFVLEVBQUUsR0FBRyxNQUFNLElBQUksS0FBSyxDQUFDLG9DQUFvQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDckYsU0FBUyxZQUFZLENBQUMsUUFBaUI7SUFDckMsSUFBSSxDQUFDLFFBQVE7UUFBRSxJQUFJLEVBQUUsQ0FBQztBQUN4QixDQUFDO0FBRUQsa0ZBQWtGO0FBQ2xGLE1BQU0sTUFBTSxHQUFHLENBQUMsUUFBaUIsRUFBRSxPQUEwQixFQUEyQixFQUFFO0lBQ3hGLFlBQVksQ0FBQyxRQUFRLEtBQUssSUFBSSxJQUFJLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztJ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
|
|
@@ -22,6 +22,7 @@ import * as mailRequests from './mail.js';
|
|
|
22
22
|
import * as migrationRequests from './migration.js';
|
|
23
23
|
import * as networkRequests from './network.js';
|
|
24
24
|
import * as nodeRequests from './node.js';
|
|
25
|
+
import * as palletRequests from './pallet.js';
|
|
25
26
|
import * as platformRequests from './platform.js';
|
|
26
27
|
import * as routingRequests from './routing.js';
|
|
27
28
|
import * as secretRequests from './secret.js';
|
|
@@ -32,7 +33,7 @@ import * as statusRequests from './status.js';
|
|
|
32
33
|
import * as taskRequests from './task.js';
|
|
33
34
|
import * as versionRequests from './version.js';
|
|
34
35
|
import * as webPushRequests from './webpush.js';
|
|
35
|
-
export { adminRequests as admin, appStoreRequests as appstore, baremetalRequests as baremetal, baseOsRequests as baseos, backupRequests as backup, certificateRequests as certificate, clusterRequests as cluster, configRequests as config, coreMailRequests as coremail, corestoreRequests as corestore, deploymentRequests as deployment, dnsRequests as dns, domainRequests as domain, externalRegistryRequests as externalRegistry, gatewayRequests as gateway, hostedAppRequests as hostedapp, identityRequests as identity, imageRequests as image, informRequests as inform, logRequests as log, mailRequests as mail, migrationRequests as migration, networkRequests as network, nodeRequests as node, platformRequests as platform, routingRequests as routing, secretRequests as secret, serverRequests as server, serviceRequests as service, settingsRequests as settings, statusRequests as status, taskRequests as task, versionRequests as version, webPushRequests as webpush, };
|
|
36
|
+
export { adminRequests as admin, appStoreRequests as appstore, baremetalRequests as baremetal, baseOsRequests as baseos, backupRequests as backup, certificateRequests as certificate, clusterRequests as cluster, configRequests as config, coreMailRequests as coremail, corestoreRequests as corestore, deploymentRequests as deployment, dnsRequests as dns, domainRequests as domain, externalRegistryRequests as externalRegistry, gatewayRequests as gateway, hostedAppRequests as hostedapp, identityRequests as identity, imageRequests as image, informRequests as inform, logRequests as log, mailRequests as mail, migrationRequests as migration, networkRequests as network, nodeRequests as node, palletRequests as pallet, platformRequests as platform, routingRequests as routing, secretRequests as secret, serverRequests as server, serviceRequests as service, settingsRequests as settings, statusRequests as status, taskRequests as task, versionRequests as version, webPushRequests as webpush, };
|
|
36
37
|
export * from './inform.js';
|
|
37
38
|
export * from './hostedapp.js';
|
|
38
39
|
export * from './mail.js';
|
|
@@ -23,6 +23,7 @@ import * as mailRequests from './mail.js';
|
|
|
23
23
|
import * as migrationRequests from './migration.js';
|
|
24
24
|
import * as networkRequests from './network.js';
|
|
25
25
|
import * as nodeRequests from './node.js';
|
|
26
|
+
import * as palletRequests from './pallet.js';
|
|
26
27
|
import * as platformRequests from './platform.js';
|
|
27
28
|
import * as routingRequests from './routing.js';
|
|
28
29
|
import * as secretRequests from './secret.js';
|
|
@@ -33,9 +34,9 @@ import * as statusRequests from './status.js';
|
|
|
33
34
|
import * as taskRequests from './task.js';
|
|
34
35
|
import * as versionRequests from './version.js';
|
|
35
36
|
import * as webPushRequests from './webpush.js';
|
|
36
|
-
export { adminRequests as admin, appStoreRequests as appstore, baremetalRequests as baremetal, baseOsRequests as baseos, backupRequests as backup, certificateRequests as certificate, clusterRequests as cluster, configRequests as config, coreMailRequests as coremail, corestoreRequests as corestore, deploymentRequests as deployment, dnsRequests as dns, domainRequests as domain, externalRegistryRequests as externalRegistry, gatewayRequests as gateway, hostedAppRequests as hostedapp, identityRequests as identity, imageRequests as image, informRequests as inform, logRequests as log, mailRequests as mail, migrationRequests as migration, networkRequests as network, nodeRequests as node, platformRequests as platform, routingRequests as routing, secretRequests as secret, serverRequests as server, serviceRequests as service, settingsRequests as settings, statusRequests as status, taskRequests as task, versionRequests as version, webPushRequests as webpush, };
|
|
37
|
+
export { adminRequests as admin, appStoreRequests as appstore, baremetalRequests as baremetal, baseOsRequests as baseos, backupRequests as backup, certificateRequests as certificate, clusterRequests as cluster, configRequests as config, coreMailRequests as coremail, corestoreRequests as corestore, deploymentRequests as deployment, dnsRequests as dns, domainRequests as domain, externalRegistryRequests as externalRegistry, gatewayRequests as gateway, hostedAppRequests as hostedapp, identityRequests as identity, imageRequests as image, informRequests as inform, logRequests as log, mailRequests as mail, migrationRequests as migration, networkRequests as network, nodeRequests as node, palletRequests as pallet, platformRequests as platform, routingRequests as routing, secretRequests as secret, serverRequests as server, serviceRequests as service, settingsRequests as settings, statusRequests as status, taskRequests as task, versionRequests as version, webPushRequests as webpush, };
|
|
37
38
|
export * from './inform.js';
|
|
38
39
|
export * from './hostedapp.js';
|
|
39
40
|
export * from './mail.js';
|
|
40
41
|
export * from './webpush.js';
|
|
41
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
42
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9yZXF1ZXN0cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUV6QyxPQUFPLEtBQUssYUFBYSxNQUFNLFlBQVksQ0FBQztBQUM1QyxPQUFPLEtBQUssZ0JBQWdCLE1BQU0sZUFBZSxDQUFDO0FBQ2xELE9BQU8sS0FBSyxpQkFBaUIsTUFBTSxnQkFBZ0IsQ0FBQztBQUNwRCxPQUFPLEtBQUssY0FBYyxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEtBQUssY0FBYyxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEtBQUssbUJBQW1CLE1BQU0sa0JBQWtCLENBQUM7QUFDeEQsT0FBTyxLQUFLLGVBQWUsTUFBTSxjQUFjLENBQUM7QUFDaEQsT0FBTyxLQUFLLGNBQWMsTUFBTSxhQUFhLENBQUM7QUFDOUMsT0FBTyxLQUFLLGdCQUFnQixNQUFNLGVBQWUsQ0FBQztBQUNsRCxPQUFPLEtBQUssaUJBQWlCLE1BQU0sZ0JBQWdCLENBQUM7QUFDcEQsT0FBTyxLQUFLLGtCQUFrQixNQUFNLGlCQUFpQixDQUFDO0FBQ3RELE9BQU8sS0FBSyxXQUFXLE1BQU0sVUFBVSxDQUFDO0FBQ3hDLE9BQU8sS0FBSyxjQUFjLE1BQU0sYUFBYSxDQUFDO0FBQzlDLE9BQU8sS0FBSyx3QkFBd0IsTUFBTSx1QkFBdUIsQ0FBQztBQUNsRSxPQUFPLEtBQUssZUFBZSxNQUFNLGNBQWMsQ0FBQztBQUNoRCxPQUFPLEtBQUssaUJBQWlCLE1BQU0sZ0JBQWdCLENBQUM7QUFDcEQsT0FBTyxLQUFLLGdCQUFnQixNQUFNLGVBQWUsQ0FBQztBQUNsRCxPQUFPLEtBQUssYUFBYSxNQUFNLFlBQVksQ0FBQztBQUM1QyxPQUFPLEtBQUssY0FBYyxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEtBQUssV0FBVyxNQUFNLFVBQVUsQ0FBQztBQUN4QyxPQUFPLEtBQUssWUFBWSxNQUFNLFdBQVcsQ0FBQztBQUMxQyxPQUFPLEtBQUssaUJBQWlCLE1BQU0sZ0JBQWdCLENBQUM7QUFDcEQsT0FBTyxLQUFLLGVBQWUsTUFBTSxjQUFjLENBQUM7QUFDaEQsT0FBTyxLQUFLLFlBQVksTUFBTSxXQUFXLENBQUM7QUFDMUMsT0FBTyxLQUFLLGNBQWMsTUFBTSxhQUFhLENBQUM7QUFDOUMsT0FBTyxLQUFLLGdCQUFnQixNQUFNLGVBQWUsQ0FBQztBQUNsRCxPQUFPLEtBQUssZUFBZSxNQUFNLGNBQWMsQ0FBQztBQUNoRCxPQUFPLEtBQUssY0FBYyxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEtBQUssY0FBYyxNQUFNLGFBQWEsQ0FBQztBQUM5QyxPQUFPLEtBQUssZUFBZSxNQUFNLGNBQWMsQ0FBQztBQUNoRCxPQUFPLEtBQUssZ0JBQWdCLE1BQU0sZUFBZSxDQUFDO0FBQ2xELE9BQU8sS0FBSyxjQUFjLE1BQU0sYUFBYSxDQUFDO0FBQzlDLE9BQU8sS0FBSyxZQUFZLE1BQU0sV0FBVyxDQUFDO0FBQzFDLE9BQU8sS0FBSyxlQUFlLE1BQU0sY0FBYyxDQUFDO0FBQ2hELE9BQU8sS0FBSyxlQUFlLE1BQU0sY0FBYyxDQUFDO0FBRWhELE9BQU8sRUFDTCxhQUFhLElBQUksS0FBSyxFQUN0QixnQkFBZ0IsSUFBSSxRQUFRLEVBQzVCLGlCQUFpQixJQUFJLFNBQVMsRUFDOUIsY0FBYyxJQUFJLE1BQU0sRUFDeEIsY0FBYyxJQUFJLE1BQU0sRUFDeEIsbUJBQW1CLElBQUksV0FBVyxFQUNsQyxlQUFlLElBQUksT0FBTyxFQUMxQixjQUFjLElBQUksTUFBTSxFQUN4QixnQkFBZ0IsSUFBSSxRQUFRLEVBQzVCLGlCQUFpQixJQUFJLFNBQVMsRUFDOUIsa0JBQWtCLElBQUksVUFBVSxFQUNoQyxXQUFXLElBQUksR0FBRyxFQUNsQixjQUFjLElBQUksTUFBTSxFQUN4Qix3QkFBd0IsSUFBSSxnQkFBZ0IsRUFDNUMsZUFBZSxJQUFJLE9BQU8sRUFDMUIsaUJBQWlCLElBQUksU0FBUyxFQUM5QixnQkFBZ0IsSUFBSSxRQUFRLEVBQzVCLGFBQWEsSUFBSSxLQUFLLEVBQ3RCLGNBQWMsSUFBSSxNQUFNLEVBQ3hCLFdBQVcsSUFBSSxHQUFHLEVBQ2xCLFlBQVksSUFBSSxJQUFJLEVBQ3BCLGlCQUFpQixJQUFJLFNBQVMsRUFDOUIsZUFBZSxJQUFJLE9BQU8sRUFDMUIsWUFBWSxJQUFJLElBQUksRUFDcEIsY0FBYyxJQUFJLE1BQU0sRUFDeEIsZ0JBQWdCLElBQUksUUFBUSxFQUM1QixlQUFlLElBQUksT0FBTyxFQUMxQixjQUFjLElBQUksTUFBTSxFQUN4QixjQUFjLElBQUksTUFBTSxFQUN4QixlQUFlLElBQUksT0FBTyxFQUMxQixnQkFBZ0IsSUFBSSxRQUFRLEVBQzVCLGNBQWMsSUFBSSxNQUFNLEVBQ3hCLFlBQVksSUFBSSxJQUFJLEVBQ3BCLGVBQWUsSUFBSSxPQUFPLEVBQzFCLGVBQWUsSUFBSSxPQUFPLEdBQzNCLENBQUM7QUFFRixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMsY0FBYyxDQUFDIn0=
|
|
@@ -5,6 +5,24 @@ import type { IClusterNode, IClusterNodeMetrics, INodeAction, INodeMetricsSample
|
|
|
5
5
|
import type { IDeployment } from '../data/deployment.js';
|
|
6
6
|
import type { IIdentity } from '../data/user.js';
|
|
7
7
|
import type { INodeCredentialMetadataV1, INodeCredentialMutationPreconditionsV1, INodeCredentialMutationResultV1, TNodeCredentialPurpose } from '../data/nodecredential.js';
|
|
8
|
+
import type { INodeEnrollmentRequestV1, INodeEnrollmentAcknowledgementV1, INodeEnrollmentStateV1 } from '../data/nodeenrollment.js';
|
|
9
|
+
/** Node-self adoption preconditions, not an admin or workload RBAC grant. */
|
|
10
|
+
export interface IReq_Spark_Cloudly_GetNodeEnrollmentStateV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Spark_Cloudly_GetNodeEnrollmentStateV1> {
|
|
11
|
+
method: 'getNodeEnrollmentStateV1';
|
|
12
|
+
request: {
|
|
13
|
+
nodeId: string;
|
|
14
|
+
nodeToken: string;
|
|
15
|
+
};
|
|
16
|
+
/** Reject unless Spark is currently authenticated and Pallet authority is absent. */
|
|
17
|
+
response: INodeEnrollmentStateV1;
|
|
18
|
+
}
|
|
19
|
+
/** One server transaction; each node-local owner durably activates independently. */
|
|
20
|
+
export interface IReq_Spark_Cloudly_EnrollNodeV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Spark_Cloudly_EnrollNodeV1> {
|
|
21
|
+
method: 'enrollNodeV1';
|
|
22
|
+
request: INodeEnrollmentRequestV1;
|
|
23
|
+
/** Replays require the exact receipt AND both current credential authorities. */
|
|
24
|
+
response: INodeEnrollmentAcknowledgementV1;
|
|
25
|
+
}
|
|
8
26
|
/** Requires verified platform-infrastructure admin authority; returns public data only. */
|
|
9
27
|
export interface IRequest_Any_Cloudly_GetNodeConfig extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IRequest_Any_Cloudly_GetNodeConfig> {
|
|
10
28
|
method: 'getNodeConfig';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import type { INodeEnrollmentV1, IPalletNodeEnrollmentActivationV1, IPalletNodeEnrollmentPreparationV1, IPalletNodeEnrollmentBoundV1, IPalletNodeEnrollmentActiveV1 } from '../data/nodeenrollment.js';
|
|
3
|
+
/** Root-authenticated node-local IPC only; never expose on a network router. */
|
|
4
|
+
export interface IReq_Spark_Pallet_PrepareNodeEnrollmentV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Spark_Pallet_PrepareNodeEnrollmentV1> {
|
|
5
|
+
method: 'preparePalletNodeEnrollmentV1';
|
|
6
|
+
request: IPalletNodeEnrollmentPreparationV1;
|
|
7
|
+
/** Return only a current pending proposal, never bearer material or a historical preparation. */
|
|
8
|
+
response: INodeEnrollmentV1['credentials'][1];
|
|
9
|
+
}
|
|
10
|
+
/** First enrollment only, not generic credential rotation. */
|
|
11
|
+
export interface IReq_Spark_Pallet_BindNodeEnrollmentV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Spark_Pallet_BindNodeEnrollmentV1> {
|
|
12
|
+
method: 'bindPalletNodeEnrollmentV1';
|
|
13
|
+
request: INodeEnrollmentV1;
|
|
14
|
+
/** Issued only after proving the exact durable pending binding. */
|
|
15
|
+
response: IPalletNodeEnrollmentBoundV1;
|
|
16
|
+
}
|
|
17
|
+
export interface IReq_Spark_Pallet_ActivateNodeEnrollmentV1 extends plugins.typedrequestInterfaces.implementsTR<plugins.typedrequestInterfaces.ITypedRequest, IReq_Spark_Pallet_ActivateNodeEnrollmentV1> {
|
|
18
|
+
method: 'activatePalletNodeEnrollmentV1';
|
|
19
|
+
/** The trusted Spark coordinator must authenticate Cloudly before submitting its acknowledgement. */
|
|
20
|
+
request: IPalletNodeEnrollmentActivationV1;
|
|
21
|
+
/** Prove the exact current local identity after activation; a historical receipt is insufficient. */
|
|
22
|
+
response: IPalletNodeEnrollmentActiveV1;
|
|
23
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGFsbGV0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvcmVxdWVzdHMvcGFsbGV0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDIn0=
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -74,6 +74,67 @@ and fence subsequent control effects against current durable credential/session
|
|
|
74
74
|
authority. Node identity storage and transport integration must be qualified
|
|
75
75
|
before enabling rotation or Pallet enrollment.
|
|
76
76
|
|
|
77
|
+
### Independent Node Enrollment
|
|
78
|
+
|
|
79
|
+
`requests.node` defines `getNodeEnrollmentStateV1` for current Spark-authenticated
|
|
80
|
+
adoption preconditions and `enrollNodeV1` for atomic Spark/Pallet enrollment. These
|
|
81
|
+
contracts do not implement endpoints, authentication, a database or a daemon.
|
|
82
|
+
|
|
83
|
+
The enrollment proposal binds the canonical HTTPS Cloudly origin, hostname,
|
|
84
|
+
enrollment identity, original bootstrap proof hash and an exact ordered pair of
|
|
85
|
+
independently prepared Spark/Pallet credential hashes and CAS counters. Fresh Jump
|
|
86
|
+
starts both at zero; existing-node adoption rotates current Spark and requires
|
|
87
|
+
Pallet authority to be absent. `snapshotNodeEnrollmentV1` returns a detached,
|
|
88
|
+
frozen snapshot; `computeNodeEnrollmentV1Digest` hashes domain-separated strict
|
|
89
|
+
canonical JSON. Neither helper generates or persists credential material.
|
|
90
|
+
|
|
91
|
+
Transport proof is excluded from the digest to permit exact lost-response
|
|
92
|
+
recovery. Bootstrap proof must hash to the original proof hash and is allowed only
|
|
93
|
+
before a receipt exists. Pending-Spark proof must hash to the prepared Spark hash
|
|
94
|
+
and is allowed only for an existing exact receipt. `verifyNodeEnrollmentProofV1`
|
|
95
|
+
checks this binding and mode only: Cloudly must derive receipt state itself and
|
|
96
|
+
fence live bootstrap/credential authority in the same owned transaction. Current
|
|
97
|
+
Jump codes are 12 random bytes encoded as 16 canonical base64url characters.
|
|
98
|
+
|
|
99
|
+
`bindNodeEnrollmentAcknowledgementV1` checks the complete digest, both owners,
|
|
100
|
+
exact next generations and unchanged adopted node ID, returning a detached frozen
|
|
101
|
+
acknowledgement. Authenticate Cloudly before accepting it. The acknowledgement is
|
|
102
|
+
immutable commit evidence, not ongoing authority or workload readiness. Server
|
|
103
|
+
replay must still verify both active credential generations and hashes; reporter
|
|
104
|
+
reconnect alone does not invalidate the credential. Spark and Pallet commit their
|
|
105
|
+
local activation independently and converge after restart; there is no shared
|
|
106
|
+
plaintext vault or cross-local-database ACID claim. Never log proof/request bodies
|
|
107
|
+
or hashes, put them in URLs, or expose them through transport hooks.
|
|
108
|
+
|
|
109
|
+
`requests.pallet` defines the node-local `preparePalletNodeEnrollmentV1`,
|
|
110
|
+
`bindPalletNodeEnrollmentV1` and `activatePalletNodeEnrollmentV1` methods. They
|
|
111
|
+
belong only on the protected root-owned Unix control socket, never a network
|
|
112
|
+
router. Preparation requests initial Pallet authority (zero generation and
|
|
113
|
+
session epoch) and returns only its independently retained credential hash.
|
|
114
|
+
`snapshotPalletNodeEnrollmentPreparationV1` and
|
|
115
|
+
`bindPalletNodeEnrollmentPreparationV1` validate that exact exchange.
|
|
116
|
+
|
|
117
|
+
Local confirmations explicitly distinguish a durable pending binding from the
|
|
118
|
+
exact current active Pallet identity. `bindPalletNodeEnrollmentBoundV1` verifies
|
|
119
|
+
the whole enrollment digest and its pre-assignment node ID (null for fresh Jump).
|
|
120
|
+
`snapshotPalletNodeEnrollmentActivationV1` captures the exact activation request,
|
|
121
|
+
rejecting extra wrapper fields as well as malformed nested data.
|
|
122
|
+
`bindPalletNodeEnrollmentActiveV1` takes that complete activation request and
|
|
123
|
+
verifies the full Cloudly acknowledgement, including both owners and the assigned
|
|
124
|
+
node ID. Both confirmation binders return detached frozen
|
|
125
|
+
snapshots captured before hashing. These are pure content checks: authenticate
|
|
126
|
+
the local transport separately, and have Pallet prove its current owned state
|
|
127
|
+
before issuing a confirmation. A historical receipt is insufficient, and Pallet
|
|
128
|
+
activation does not imply Spark activation or workload readiness.
|
|
129
|
+
|
|
130
|
+
`palletNodeEnrollmentV1Contract` specifies one four-byte unsigned big-endian
|
|
131
|
+
length-prefixed UTF-8 JSON frame per direction, followed by write-half-close, at
|
|
132
|
+
most 32768 request bytes and 16384 response bytes excluding the prefix. Implement
|
|
133
|
+
the listener with half-open response support, strict EOF/trailing-byte rejection,
|
|
134
|
+
bounded time/concurrency, root-only directory/socket permissions and drained
|
|
135
|
+
cleanup. The package supplies no listener, permission checks or persistent state.
|
|
136
|
+
No bearer, generic rotation operation or database access is part of this IPC API.
|
|
137
|
+
|
|
77
138
|
### Node-Bound Runtime Assignments
|
|
78
139
|
|
|
79
140
|
The `data` namespace exports `IRuntimeAssignmentV1`, canonical digest helpers,
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/data/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export * from './isolatedrestore.js';
|
|
|
21
21
|
export * from './isolatedrestore.golden.js';
|
|
22
22
|
export * from './mail.js';
|
|
23
23
|
export * from './nodecredential.js';
|
|
24
|
+
export * from './nodeenrollment.js';
|
|
24
25
|
export * from './registry.js';
|
|
25
26
|
export * from './runtimeassignment.js';
|
|
26
27
|
export * from './secret.js';
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import type { TNodeCredentialPurpose } from './nodecredential.js';
|
|
2
|
+
import {
|
|
3
|
+
canonicalizeStrictJson,
|
|
4
|
+
createCanonicalJsonSha256Hex,
|
|
5
|
+
createSha256Hex,
|
|
6
|
+
deepFreezeValue,
|
|
7
|
+
} from '../private/canonicaljson.js';
|
|
8
|
+
|
|
9
|
+
export interface INodeEnrollmentCredentialProposalV1 {
|
|
10
|
+
purpose: TNodeCredentialPurpose;
|
|
11
|
+
installationId: string;
|
|
12
|
+
mutationId: string;
|
|
13
|
+
/** SHA-256 of the independently generated, durably prepared bearer UTF-8 bytes. */
|
|
14
|
+
tokenHash: string;
|
|
15
|
+
expectedGeneration: number;
|
|
16
|
+
expectedSessionEpoch: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Value-free, immutable proposal. Hashes are still sensitive transport fields. */
|
|
20
|
+
export interface INodeEnrollmentV1 {
|
|
21
|
+
schemaVersion: 1;
|
|
22
|
+
/** 32 CSPRNG bytes encoded as lowercase hexadecimal; retain across every retry. */
|
|
23
|
+
enrollmentId: string;
|
|
24
|
+
cloudlyOrigin: string;
|
|
25
|
+
hostname: string;
|
|
26
|
+
bootstrap:
|
|
27
|
+
| { kind: 'jump'; proofHash: string }
|
|
28
|
+
| { kind: 'adopt-existing'; nodeId: string; proofHash: string };
|
|
29
|
+
credentials: [
|
|
30
|
+
INodeEnrollmentCredentialProposalV1 & { purpose: 'spark' },
|
|
31
|
+
INodeEnrollmentCredentialProposalV1 & { purpose: 'pallet' },
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface INodeEnrollmentRequestV1 {
|
|
36
|
+
enrollment: INodeEnrollmentV1;
|
|
37
|
+
/** Never persist on the server, log, put in URLs, or expose in transport hooks. */
|
|
38
|
+
proof: { kind: 'bootstrap' | 'pending-spark'; bearer: string };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface INodeEnrollmentCredentialAcknowledgementV1 {
|
|
42
|
+
purpose: TNodeCredentialPurpose;
|
|
43
|
+
installationId: string;
|
|
44
|
+
mutationId: string;
|
|
45
|
+
tokenHash: string;
|
|
46
|
+
generation: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Immutable commit evidence, not authorization or workload readiness. */
|
|
50
|
+
export interface INodeEnrollmentAcknowledgementV1 {
|
|
51
|
+
schemaVersion: 1;
|
|
52
|
+
enrollmentId: string;
|
|
53
|
+
cloudlyOrigin: string;
|
|
54
|
+
nodeId: string;
|
|
55
|
+
requestDigest: string;
|
|
56
|
+
credentials: [
|
|
57
|
+
INodeEnrollmentCredentialAcknowledgementV1 & { purpose: 'spark' },
|
|
58
|
+
INodeEnrollmentCredentialAcknowledgementV1 & { purpose: 'pallet' },
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Returned only after current Spark authentication and proof that Pallet is absent. */
|
|
63
|
+
export interface INodeEnrollmentStateV1 {
|
|
64
|
+
schemaVersion: 1;
|
|
65
|
+
nodeId: string;
|
|
66
|
+
spark: { generation: number; sessionEpoch: number };
|
|
67
|
+
pallet: 'absent';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Root-authenticated, node-local IPC only. Initial Pallet authority is always absent. */
|
|
71
|
+
export interface IPalletNodeEnrollmentPreparationV1 {
|
|
72
|
+
schemaVersion: 1;
|
|
73
|
+
purpose: 'pallet';
|
|
74
|
+
installationId: string;
|
|
75
|
+
mutationId: string;
|
|
76
|
+
cloudlyOrigin: string;
|
|
77
|
+
nodeId: string | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Full exact activation wire body; Cloudly authentication remains the coordinator's responsibility. */
|
|
81
|
+
export interface IPalletNodeEnrollmentActivationV1 {
|
|
82
|
+
enrollment: INodeEnrollmentV1;
|
|
83
|
+
acknowledgement: INodeEnrollmentAcknowledgementV1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface IPalletNodeEnrollmentConfirmationBaseV1 {
|
|
87
|
+
schemaVersion: 1;
|
|
88
|
+
purpose: 'pallet';
|
|
89
|
+
enrollmentId: string;
|
|
90
|
+
cloudlyOrigin: string;
|
|
91
|
+
requestDigest: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The exact pending binding is durable now, not merely a historical receipt. */
|
|
95
|
+
export interface IPalletNodeEnrollmentBoundV1 extends IPalletNodeEnrollmentConfirmationBaseV1 {
|
|
96
|
+
phase: 'bound';
|
|
97
|
+
/** Null before Cloudly assigns a fresh Jump node; exact existing node for adoption. */
|
|
98
|
+
nodeId: string | null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Exact CURRENT local Pallet identity, not Spark activation or remote authorization. */
|
|
102
|
+
export interface IPalletNodeEnrollmentActiveV1 extends IPalletNodeEnrollmentConfirmationBaseV1 {
|
|
103
|
+
phase: 'active';
|
|
104
|
+
nodeId: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type TPalletNodeEnrollmentConfirmationV1 =
|
|
108
|
+
| IPalletNodeEnrollmentBoundV1 | IPalletNodeEnrollmentActiveV1;
|
|
109
|
+
|
|
110
|
+
/** Wire bounds, not a listener, permission check, timeout implementation or storage API. */
|
|
111
|
+
export const palletNodeEnrollmentV1Contract = Object.freeze({
|
|
112
|
+
socketPath: '/run/serve.zone/pallet/control.sock',
|
|
113
|
+
frame: 'u32be-length-prefixed-utf8-json-then-write-half-close',
|
|
114
|
+
requestsPerConnection: 1,
|
|
115
|
+
maxRequestBytes: 32768,
|
|
116
|
+
maxResponseBytes: 16384,
|
|
117
|
+
} as const);
|
|
118
|
+
|
|
119
|
+
export const nodeEnrollmentV1Contract = Object.freeze({
|
|
120
|
+
digestDomain: 'serve.zone/node-enrollment-v1',
|
|
121
|
+
localActivation: 'independently-durable-convergence',
|
|
122
|
+
bootstrapProof: 'first-commit-only',
|
|
123
|
+
pendingSparkProof: 'committed-exact-receipt-only',
|
|
124
|
+
} as const);
|
|
125
|
+
|
|
126
|
+
const fail = (): never => { throw new Error('Node enrollment contract rejected.'); };
|
|
127
|
+
function requireValue(valueArg: unknown): asserts valueArg {
|
|
128
|
+
if (!valueArg) fail();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Decoded JSON/data records only. No input accessor is executed or preserved. */
|
|
132
|
+
const record = (valueArg: unknown, keysArg: readonly string[]): Record<string, unknown> => {
|
|
133
|
+
requireValue(valueArg !== null && typeof valueArg === 'object' && !Array.isArray(valueArg));
|
|
134
|
+
const prototype = Object.getPrototypeOf(valueArg);
|
|
135
|
+
requireValue(prototype === Object.prototype || prototype === null);
|
|
136
|
+
const keys = Reflect.ownKeys(valueArg);
|
|
137
|
+
requireValue(keys.length === keysArg.length);
|
|
138
|
+
const snapshot: Record<string, unknown> = Object.create(null);
|
|
139
|
+
for (const key of keys) {
|
|
140
|
+
requireValue(typeof key === 'string' && keysArg.includes(key));
|
|
141
|
+
const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
|
|
142
|
+
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
143
|
+
snapshot[key] = descriptor.value;
|
|
144
|
+
}
|
|
145
|
+
return snapshot;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const pair = (valueArg: unknown): [unknown, unknown] => {
|
|
149
|
+
requireValue(Array.isArray(valueArg) && Object.getPrototypeOf(valueArg) === Array.prototype);
|
|
150
|
+
requireValue(Reflect.ownKeys(valueArg).length === 3 && valueArg.length === 2);
|
|
151
|
+
const first = Object.getOwnPropertyDescriptor(valueArg, '0');
|
|
152
|
+
const second = Object.getOwnPropertyDescriptor(valueArg, '1');
|
|
153
|
+
requireValue(first?.enumerable && Object.hasOwn(first, 'value'));
|
|
154
|
+
requireValue(second?.enumerable && Object.hasOwn(second, 'value'));
|
|
155
|
+
return [first.value, second.value];
|
|
156
|
+
};
|
|
157
|
+
const id = (valueArg: unknown): string => {
|
|
158
|
+
requireValue(typeof valueArg === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(valueArg));
|
|
159
|
+
return valueArg;
|
|
160
|
+
};
|
|
161
|
+
const hash = (valueArg: unknown): string => {
|
|
162
|
+
requireValue(typeof valueArg === 'string' && /^[a-f0-9]{64}$/.test(valueArg));
|
|
163
|
+
return valueArg;
|
|
164
|
+
};
|
|
165
|
+
const counter = (valueArg: unknown, minimumArg = 0, incrementArg = false): number => {
|
|
166
|
+
requireValue(typeof valueArg === 'number' && Number.isSafeInteger(valueArg)
|
|
167
|
+
&& !Object.is(valueArg, -0) && valueArg >= minimumArg
|
|
168
|
+
&& (!incrementArg || valueArg < Number.MAX_SAFE_INTEGER));
|
|
169
|
+
return valueArg;
|
|
170
|
+
};
|
|
171
|
+
const origin = (valueArg: unknown): string => {
|
|
172
|
+
requireValue(typeof valueArg === 'string' && valueArg.length <= 2048);
|
|
173
|
+
let url: URL;
|
|
174
|
+
try { url = new URL(valueArg); } catch { return fail(); }
|
|
175
|
+
requireValue(url.protocol === 'https:' && !url.username && !url.password && url.origin === valueArg);
|
|
176
|
+
return valueArg;
|
|
177
|
+
};
|
|
178
|
+
const hostname = (valueArg: unknown): string => {
|
|
179
|
+
requireValue(typeof valueArg === 'string' && valueArg.length <= 253 && valueArg.length > 0);
|
|
180
|
+
requireValue(valueArg.split('.').every((labelArg) =>
|
|
181
|
+
/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(labelArg)));
|
|
182
|
+
return valueArg;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const proposal = <TPurpose extends TNodeCredentialPurpose>(
|
|
186
|
+
valueArg: unknown, purposeArg: TPurpose,
|
|
187
|
+
): INodeEnrollmentCredentialProposalV1 & { purpose: TPurpose } => {
|
|
188
|
+
const value = record(valueArg, ['purpose', 'installationId', 'mutationId', 'tokenHash',
|
|
189
|
+
'expectedGeneration', 'expectedSessionEpoch']);
|
|
190
|
+
requireValue(value.purpose === purposeArg);
|
|
191
|
+
return { purpose: purposeArg, installationId: id(value.installationId), mutationId: id(value.mutationId),
|
|
192
|
+
tokenHash: hash(value.tokenHash), expectedGeneration: counter(value.expectedGeneration, 0, true),
|
|
193
|
+
expectedSessionEpoch: counter(value.expectedSessionEpoch, 0, true) };
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const distinctOwners = (
|
|
197
|
+
sparkArg: INodeEnrollmentCredentialAcknowledgementV1 | INodeEnrollmentCredentialProposalV1,
|
|
198
|
+
palletArg: INodeEnrollmentCredentialAcknowledgementV1 | INodeEnrollmentCredentialProposalV1,
|
|
199
|
+
): void => {
|
|
200
|
+
requireValue(sparkArg.installationId !== palletArg.installationId
|
|
201
|
+
&& sparkArg.mutationId !== palletArg.mutationId && sparkArg.tokenHash !== palletArg.tokenHash);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** Detached frozen snapshot, captured synchronously before hashing or other awaits. */
|
|
205
|
+
export const snapshotNodeEnrollmentV1 = (valueArg: unknown): INodeEnrollmentV1 => {
|
|
206
|
+
const value = record(valueArg, ['schemaVersion', 'enrollmentId', 'cloudlyOrigin', 'hostname',
|
|
207
|
+
'bootstrap', 'credentials']);
|
|
208
|
+
requireValue(value.schemaVersion === 1);
|
|
209
|
+
// Read the discriminant from its data descriptor, never an input getter.
|
|
210
|
+
requireValue(value.bootstrap !== null && typeof value.bootstrap === 'object');
|
|
211
|
+
const kind = Object.getOwnPropertyDescriptor(value.bootstrap, 'kind');
|
|
212
|
+
requireValue(kind && Object.hasOwn(kind, 'value'));
|
|
213
|
+
const bootstrap = record(value.bootstrap, kind.value === 'jump'
|
|
214
|
+
? ['kind', 'proofHash'] : ['kind', 'nodeId', 'proofHash']);
|
|
215
|
+
requireValue(bootstrap.kind === 'jump' || bootstrap.kind === 'adopt-existing');
|
|
216
|
+
const [sparkInput, palletInput] = pair(value.credentials);
|
|
217
|
+
const spark = proposal(sparkInput, 'spark');
|
|
218
|
+
const pallet = proposal(palletInput, 'pallet');
|
|
219
|
+
distinctOwners(spark, pallet);
|
|
220
|
+
requireValue(pallet.expectedGeneration === 0 && pallet.expectedSessionEpoch === 0);
|
|
221
|
+
requireValue(bootstrap.kind === 'jump'
|
|
222
|
+
? spark.expectedGeneration === 0 && spark.expectedSessionEpoch === 0
|
|
223
|
+
: spark.expectedGeneration > 0);
|
|
224
|
+
return deepFreezeValue({ schemaVersion: 1, enrollmentId: hash(value.enrollmentId),
|
|
225
|
+
cloudlyOrigin: origin(value.cloudlyOrigin), hostname: hostname(value.hostname),
|
|
226
|
+
bootstrap: bootstrap.kind === 'jump'
|
|
227
|
+
? { kind: 'jump', proofHash: hash(bootstrap.proofHash) }
|
|
228
|
+
: { kind: 'adopt-existing', nodeId: id(bootstrap.nodeId), proofHash: hash(bootstrap.proofHash) },
|
|
229
|
+
credentials: [spark, pallet] });
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const computeNodeEnrollmentV1Digest = async (valueArg: INodeEnrollmentV1): Promise<string> => {
|
|
233
|
+
const payload = snapshotNodeEnrollmentV1(valueArg);
|
|
234
|
+
return createCanonicalJsonSha256Hex(canonicalizeStrictJson({
|
|
235
|
+
domain: nodeEnrollmentV1Contract.digestDomain, payload,
|
|
236
|
+
}, fail), fail);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export const snapshotNodeEnrollmentRequestV1 = (valueArg: unknown): INodeEnrollmentRequestV1 => {
|
|
240
|
+
const value = record(valueArg, ['enrollment', 'proof']);
|
|
241
|
+
const enrollment = snapshotNodeEnrollmentV1(value.enrollment);
|
|
242
|
+
const proof = record(value.proof, ['kind', 'bearer']);
|
|
243
|
+
requireValue(proof.kind === 'bootstrap' || proof.kind === 'pending-spark');
|
|
244
|
+
requireValue(typeof proof.bearer === 'string');
|
|
245
|
+
requireValue(proof.kind === 'bootstrap' && enrollment.bootstrap.kind === 'jump'
|
|
246
|
+
? /^[A-Za-z0-9_-]{16}$/.test(proof.bearer) : /^secureToken[a-f0-9]{64}$/.test(proof.bearer));
|
|
247
|
+
return deepFreezeValue({ enrollment, proof: { kind: proof.kind, bearer: proof.bearer } });
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Pure proof binding/mode check only, NOT authentication. The server must derive
|
|
252
|
+
* receiptState from its owned transaction, compare the complete stored proposal,
|
|
253
|
+
* and fence current Jump/Spark/Pallet authority before any commit or replay.
|
|
254
|
+
*/
|
|
255
|
+
export const verifyNodeEnrollmentProofV1 = async (
|
|
256
|
+
valueArg: INodeEnrollmentRequestV1, receiptStateArg: 'absent' | 'committed',
|
|
257
|
+
): Promise<boolean> => {
|
|
258
|
+
try {
|
|
259
|
+
const { enrollment, proof } = snapshotNodeEnrollmentRequestV1(valueArg);
|
|
260
|
+
if (!(proof.kind === 'bootstrap' && receiptStateArg === 'absent')
|
|
261
|
+
&& !(proof.kind === 'pending-spark' && receiptStateArg === 'committed')) return false;
|
|
262
|
+
// This helper hashes exact UTF-8 text; no JSON quoting or normalization.
|
|
263
|
+
const actual = await createSha256Hex(new TextEncoder().encode(proof.bearer), fail);
|
|
264
|
+
return actual === (proof.kind === 'bootstrap'
|
|
265
|
+
? enrollment.bootstrap.proofHash : enrollment.credentials[0].tokenHash);
|
|
266
|
+
} catch { return false; }
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const credentialAcknowledgement = <TPurpose extends TNodeCredentialPurpose>(
|
|
270
|
+
valueArg: unknown, purposeArg: TPurpose,
|
|
271
|
+
): INodeEnrollmentCredentialAcknowledgementV1 & { purpose: TPurpose } => {
|
|
272
|
+
const value = record(valueArg, ['purpose', 'installationId', 'mutationId', 'tokenHash', 'generation']);
|
|
273
|
+
requireValue(value.purpose === purposeArg);
|
|
274
|
+
return { purpose: purposeArg, installationId: id(value.installationId), mutationId: id(value.mutationId),
|
|
275
|
+
tokenHash: hash(value.tokenHash), generation: counter(value.generation, 1) };
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export const snapshotNodeEnrollmentAcknowledgementV1 = (
|
|
279
|
+
valueArg: unknown,
|
|
280
|
+
): INodeEnrollmentAcknowledgementV1 => {
|
|
281
|
+
const value = record(valueArg, ['schemaVersion', 'enrollmentId', 'cloudlyOrigin', 'nodeId',
|
|
282
|
+
'requestDigest', 'credentials']);
|
|
283
|
+
requireValue(value.schemaVersion === 1);
|
|
284
|
+
const [sparkInput, palletInput] = pair(value.credentials);
|
|
285
|
+
const spark = credentialAcknowledgement(sparkInput, 'spark');
|
|
286
|
+
const pallet = credentialAcknowledgement(palletInput, 'pallet');
|
|
287
|
+
distinctOwners(spark, pallet);
|
|
288
|
+
requireValue(pallet.generation === 1);
|
|
289
|
+
return deepFreezeValue({ schemaVersion: 1, enrollmentId: hash(value.enrollmentId),
|
|
290
|
+
cloudlyOrigin: origin(value.cloudlyOrigin), nodeId: id(value.nodeId),
|
|
291
|
+
requestDigest: hash(value.requestDigest), credentials: [spark, pallet] });
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/** Content binding only. Authenticate Cloudly first; persist only the returned snapshot. */
|
|
295
|
+
export const bindNodeEnrollmentAcknowledgementV1 = async (
|
|
296
|
+
enrollmentArg: INodeEnrollmentV1, acknowledgementArg: INodeEnrollmentAcknowledgementV1,
|
|
297
|
+
): Promise<INodeEnrollmentAcknowledgementV1> => {
|
|
298
|
+
const enrollment = snapshotNodeEnrollmentV1(enrollmentArg);
|
|
299
|
+
const ack = snapshotNodeEnrollmentAcknowledgementV1(acknowledgementArg);
|
|
300
|
+
requireValue(ack.enrollmentId === enrollment.enrollmentId && ack.cloudlyOrigin === enrollment.cloudlyOrigin
|
|
301
|
+
&& (enrollment.bootstrap.kind === 'jump' || ack.nodeId === enrollment.bootstrap.nodeId));
|
|
302
|
+
for (const index of [0, 1] as const) {
|
|
303
|
+
const expected = enrollment.credentials[index];
|
|
304
|
+
const actual = ack.credentials[index];
|
|
305
|
+
requireValue(actual.purpose === expected.purpose && actual.installationId === expected.installationId
|
|
306
|
+
&& actual.mutationId === expected.mutationId && actual.tokenHash === expected.tokenHash
|
|
307
|
+
&& actual.generation === expected.expectedGeneration + 1);
|
|
308
|
+
}
|
|
309
|
+
requireValue(ack.requestDigest === await computeNodeEnrollmentV1Digest(enrollment));
|
|
310
|
+
return ack;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export const snapshotNodeEnrollmentStateV1 = (valueArg: unknown): INodeEnrollmentStateV1 => {
|
|
314
|
+
const value = record(valueArg, ['schemaVersion', 'nodeId', 'spark', 'pallet']);
|
|
315
|
+
requireValue(value.schemaVersion === 1 && value.pallet === 'absent');
|
|
316
|
+
const spark = record(value.spark, ['generation', 'sessionEpoch']);
|
|
317
|
+
return deepFreezeValue({ schemaVersion: 1, nodeId: id(value.nodeId), pallet: 'absent',
|
|
318
|
+
spark: { generation: counter(spark.generation, 1, true), sessionEpoch: counter(spark.sessionEpoch, 0, true) } });
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/** Decoded JSON/data records only, captured synchronously; never invokes getters. */
|
|
322
|
+
export const snapshotPalletNodeEnrollmentPreparationV1 = (
|
|
323
|
+
valueArg: unknown,
|
|
324
|
+
): IPalletNodeEnrollmentPreparationV1 => {
|
|
325
|
+
const value = record(valueArg, ['schemaVersion', 'purpose', 'installationId', 'mutationId',
|
|
326
|
+
'cloudlyOrigin', 'nodeId']);
|
|
327
|
+
requireValue(value.schemaVersion === 1 && value.purpose === 'pallet');
|
|
328
|
+
return deepFreezeValue({ schemaVersion: 1, purpose: 'pallet',
|
|
329
|
+
installationId: id(value.installationId), mutationId: id(value.mutationId),
|
|
330
|
+
cloudlyOrigin: origin(value.cloudlyOrigin), nodeId: value.nodeId === null ? null : id(value.nodeId) });
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/** Content binding only. Authenticate the local Pallet transport before trusting this proposal. */
|
|
334
|
+
export const bindPalletNodeEnrollmentPreparationV1 = (
|
|
335
|
+
preparationArg: IPalletNodeEnrollmentPreparationV1, proposalArg: unknown,
|
|
336
|
+
): INodeEnrollmentV1['credentials'][1] => {
|
|
337
|
+
const preparation = snapshotPalletNodeEnrollmentPreparationV1(preparationArg);
|
|
338
|
+
const result = proposal(proposalArg, 'pallet');
|
|
339
|
+
requireValue(result.installationId === preparation.installationId
|
|
340
|
+
&& result.mutationId === preparation.mutationId
|
|
341
|
+
&& result.expectedGeneration === 0 && result.expectedSessionEpoch === 0);
|
|
342
|
+
return deepFreezeValue(result);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/** Capture the exact wrapper and both nested values before any asynchronous content binding. */
|
|
346
|
+
export const snapshotPalletNodeEnrollmentActivationV1 = (
|
|
347
|
+
valueArg: unknown,
|
|
348
|
+
): IPalletNodeEnrollmentActivationV1 => {
|
|
349
|
+
const value = record(valueArg, ['enrollment', 'acknowledgement']);
|
|
350
|
+
return deepFreezeValue({ enrollment: snapshotNodeEnrollmentV1(value.enrollment),
|
|
351
|
+
acknowledgement: snapshotNodeEnrollmentAcknowledgementV1(value.acknowledgement) });
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export const snapshotPalletNodeEnrollmentConfirmationV1 = (
|
|
355
|
+
valueArg: unknown,
|
|
356
|
+
): TPalletNodeEnrollmentConfirmationV1 => {
|
|
357
|
+
const value = record(valueArg, ['schemaVersion', 'purpose', 'enrollmentId', 'cloudlyOrigin',
|
|
358
|
+
'requestDigest', 'phase', 'nodeId']);
|
|
359
|
+
requireValue(value.schemaVersion === 1 && value.purpose === 'pallet'
|
|
360
|
+
&& (value.phase === 'bound' || value.phase === 'active'));
|
|
361
|
+
const common = { schemaVersion: 1 as const, purpose: 'pallet' as const,
|
|
362
|
+
enrollmentId: hash(value.enrollmentId), cloudlyOrigin: origin(value.cloudlyOrigin),
|
|
363
|
+
requestDigest: hash(value.requestDigest) };
|
|
364
|
+
return deepFreezeValue(value.phase === 'bound'
|
|
365
|
+
? { ...common, phase: 'bound', nodeId: value.nodeId === null ? null : id(value.nodeId) }
|
|
366
|
+
: { ...common, phase: 'active', nodeId: id(value.nodeId) });
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
/** Pure binding only. Pallet must prove its durable pending binding before issuing this response. */
|
|
370
|
+
export const bindPalletNodeEnrollmentBoundV1 = async (
|
|
371
|
+
enrollmentArg: INodeEnrollmentV1, confirmationArg: IPalletNodeEnrollmentBoundV1,
|
|
372
|
+
): Promise<IPalletNodeEnrollmentBoundV1> => {
|
|
373
|
+
const enrollment = snapshotNodeEnrollmentV1(enrollmentArg);
|
|
374
|
+
const confirmation = snapshotPalletNodeEnrollmentConfirmationV1(confirmationArg);
|
|
375
|
+
requireValue(confirmation.phase === 'bound' && confirmation.enrollmentId === enrollment.enrollmentId
|
|
376
|
+
&& confirmation.cloudlyOrigin === enrollment.cloudlyOrigin
|
|
377
|
+
&& confirmation.nodeId === (enrollment.bootstrap.kind === 'jump' ? null : enrollment.bootstrap.nodeId));
|
|
378
|
+
requireValue(confirmation.requestDigest === await computeNodeEnrollmentV1Digest(enrollment));
|
|
379
|
+
return confirmation;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Pure binding only, NOT authentication or a database check. The root-authenticated
|
|
384
|
+
* Pallet owner must first prove its exact CURRENT local identity, not just a receipt.
|
|
385
|
+
* This does not assert Spark activation, current Cloudly authority or workload readiness.
|
|
386
|
+
*/
|
|
387
|
+
export const bindPalletNodeEnrollmentActiveV1 = async (
|
|
388
|
+
activationArg: IPalletNodeEnrollmentActivationV1,
|
|
389
|
+
confirmationArg: IPalletNodeEnrollmentActiveV1,
|
|
390
|
+
): Promise<IPalletNodeEnrollmentActiveV1> => {
|
|
391
|
+
// Capture every caller-owned value before the asynchronous shared acknowledgement binder.
|
|
392
|
+
const { enrollment, acknowledgement } = snapshotPalletNodeEnrollmentActivationV1(activationArg);
|
|
393
|
+
const confirmation = snapshotPalletNodeEnrollmentConfirmationV1(confirmationArg);
|
|
394
|
+
requireValue(confirmation.phase === 'active');
|
|
395
|
+
const ack = await bindNodeEnrollmentAcknowledgementV1(enrollment, acknowledgement);
|
|
396
|
+
requireValue(confirmation.enrollmentId === ack.enrollmentId && confirmation.cloudlyOrigin === ack.cloudlyOrigin
|
|
397
|
+
&& confirmation.nodeId === ack.nodeId && confirmation.requestDigest === ack.requestDigest);
|
|
398
|
+
return confirmation;
|
|
399
|
+
};
|
package/ts/requests/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ import * as mailRequests from './mail.js';
|
|
|
24
24
|
import * as migrationRequests from './migration.js';
|
|
25
25
|
import * as networkRequests from './network.js';
|
|
26
26
|
import * as nodeRequests from './node.js';
|
|
27
|
+
import * as palletRequests from './pallet.js';
|
|
27
28
|
import * as platformRequests from './platform.js';
|
|
28
29
|
import * as routingRequests from './routing.js';
|
|
29
30
|
import * as secretRequests from './secret.js';
|
|
@@ -60,6 +61,7 @@ export {
|
|
|
60
61
|
migrationRequests as migration,
|
|
61
62
|
networkRequests as network,
|
|
62
63
|
nodeRequests as node,
|
|
64
|
+
palletRequests as pallet,
|
|
63
65
|
platformRequests as platform,
|
|
64
66
|
routingRequests as routing,
|
|
65
67
|
secretRequests as secret,
|
package/ts/requests/node.ts
CHANGED
|
@@ -10,6 +10,33 @@ import type {
|
|
|
10
10
|
INodeCredentialMutationResultV1,
|
|
11
11
|
TNodeCredentialPurpose,
|
|
12
12
|
} from '../data/nodecredential.js';
|
|
13
|
+
import type {
|
|
14
|
+
INodeEnrollmentRequestV1,
|
|
15
|
+
INodeEnrollmentAcknowledgementV1,
|
|
16
|
+
INodeEnrollmentStateV1,
|
|
17
|
+
} from '../data/nodeenrollment.js';
|
|
18
|
+
|
|
19
|
+
/** Node-self adoption preconditions, not an admin or workload RBAC grant. */
|
|
20
|
+
export interface IReq_Spark_Cloudly_GetNodeEnrollmentStateV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
21
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
22
|
+
IReq_Spark_Cloudly_GetNodeEnrollmentStateV1
|
|
23
|
+
> {
|
|
24
|
+
method: 'getNodeEnrollmentStateV1';
|
|
25
|
+
request: { nodeId: string; nodeToken: string };
|
|
26
|
+
/** Reject unless Spark is currently authenticated and Pallet authority is absent. */
|
|
27
|
+
response: INodeEnrollmentStateV1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One server transaction; each node-local owner durably activates independently. */
|
|
31
|
+
export interface IReq_Spark_Cloudly_EnrollNodeV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
32
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
33
|
+
IReq_Spark_Cloudly_EnrollNodeV1
|
|
34
|
+
> {
|
|
35
|
+
method: 'enrollNodeV1';
|
|
36
|
+
request: INodeEnrollmentRequestV1;
|
|
37
|
+
/** Replays require the exact receipt AND both current credential authorities. */
|
|
38
|
+
response: INodeEnrollmentAcknowledgementV1;
|
|
39
|
+
}
|
|
13
40
|
|
|
14
41
|
/** Requires verified platform-infrastructure admin authority; returns public data only. */
|
|
15
42
|
export interface IRequest_Any_Cloudly_GetNodeConfig extends plugins.typedrequestInterfaces.implementsTR<
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import type {
|
|
3
|
+
INodeEnrollmentV1, IPalletNodeEnrollmentActivationV1, IPalletNodeEnrollmentPreparationV1,
|
|
4
|
+
IPalletNodeEnrollmentBoundV1, IPalletNodeEnrollmentActiveV1,
|
|
5
|
+
} from '../data/nodeenrollment.js';
|
|
6
|
+
|
|
7
|
+
/** Root-authenticated node-local IPC only; never expose on a network router. */
|
|
8
|
+
export interface IReq_Spark_Pallet_PrepareNodeEnrollmentV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
9
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
10
|
+
IReq_Spark_Pallet_PrepareNodeEnrollmentV1
|
|
11
|
+
> {
|
|
12
|
+
method: 'preparePalletNodeEnrollmentV1';
|
|
13
|
+
request: IPalletNodeEnrollmentPreparationV1;
|
|
14
|
+
/** Return only a current pending proposal, never bearer material or a historical preparation. */
|
|
15
|
+
response: INodeEnrollmentV1['credentials'][1];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** First enrollment only, not generic credential rotation. */
|
|
19
|
+
export interface IReq_Spark_Pallet_BindNodeEnrollmentV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
20
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
21
|
+
IReq_Spark_Pallet_BindNodeEnrollmentV1
|
|
22
|
+
> {
|
|
23
|
+
method: 'bindPalletNodeEnrollmentV1';
|
|
24
|
+
request: INodeEnrollmentV1;
|
|
25
|
+
/** Issued only after proving the exact durable pending binding. */
|
|
26
|
+
response: IPalletNodeEnrollmentBoundV1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface IReq_Spark_Pallet_ActivateNodeEnrollmentV1 extends plugins.typedrequestInterfaces.implementsTR<
|
|
30
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
31
|
+
IReq_Spark_Pallet_ActivateNodeEnrollmentV1
|
|
32
|
+
> {
|
|
33
|
+
method: 'activatePalletNodeEnrollmentV1';
|
|
34
|
+
/** The trusted Spark coordinator must authenticate Cloudly before submitting its acknowledgement. */
|
|
35
|
+
request: IPalletNodeEnrollmentActivationV1;
|
|
36
|
+
/** Prove the exact current local identity after activation; a historical receipt is insufficient. */
|
|
37
|
+
response: IPalletNodeEnrollmentActiveV1;
|
|
38
|
+
}
|