@serve.zone/interfaces 31.8.0 → 32.0.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/.smartconfig.json +1 -0
- package/changelog.md +512 -0
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/appstore/types.d.ts +4 -6
- package/dist_ts/appstore/types.js +5 -5
- package/dist_ts/data/backup.d.ts +3 -26
- package/dist_ts/data/cluster.d.ts +36 -11
- package/dist_ts/data/cluster.js +33 -22
- package/dist_ts/data/clusteringress.d.ts +8 -0
- package/dist_ts/data/clusteringress.js +13 -8
- package/dist_ts/data/clusternode.d.ts +121 -74
- package/dist_ts/data/clusternode.js +109 -132
- package/dist_ts/data/clusterruntime.d.ts +39 -0
- package/dist_ts/data/clusterruntime.js +11 -0
- package/dist_ts/data/coremail.d.ts +3 -7
- package/dist_ts/data/coremail.js +2 -2
- package/dist_ts/data/coremail.runtime.d.ts +0 -1
- package/dist_ts/data/coremail.runtime.js +5 -22
- package/dist_ts/data/deploymentadoption.d.ts +0 -2
- package/dist_ts/data/deploymentadoption.js +40 -6
- package/dist_ts/data/deploymentoperation.js +16 -3
- package/dist_ts/data/deploymentpreflight.d.ts +0 -2
- package/dist_ts/data/deploymentpreflight.js +1 -1
- package/dist_ts/data/immutableimage.d.ts +38 -21
- package/dist_ts/data/immutableimage.js +45 -5
- package/dist_ts/data/index.d.ts +5 -1
- package/dist_ts/data/index.js +5 -1
- package/dist_ts/data/isolatedrestore.d.ts +139 -21
- package/dist_ts/data/isolatedrestore.golden.d.ts +0 -1
- package/dist_ts/data/isolatedrestore.golden.js +6 -9
- package/dist_ts/data/isolatedrestore.js +176 -40
- package/dist_ts/data/mail.d.ts +1 -1
- package/dist_ts/data/networkaddressplan.d.ts +78 -0
- package/dist_ts/data/networkaddressplan.js +114 -0
- package/dist_ts/data/networkauthority.d.ts +20 -22
- package/dist_ts/data/networkauthority.js +29 -43
- package/dist_ts/data/networkcredential.d.ts +10 -10
- package/dist_ts/data/networkcredential.js +16 -16
- package/dist_ts/data/networkdelivery.d.ts +18 -18
- package/dist_ts/data/networkdelivery.js +16 -16
- package/dist_ts/data/networklease.d.ts +18 -7
- package/dist_ts/data/networklease.js +29 -7
- package/dist_ts/data/networknode.d.ts +37 -0
- package/dist_ts/data/networknode.js +6 -0
- package/dist_ts/data/networkprojection.d.ts +59 -60
- package/dist_ts/data/networkprojection.js +53 -57
- package/dist_ts/data/networkprotection.d.ts +27 -28
- package/dist_ts/data/networkprotection.js +36 -36
- package/dist_ts/data/networkrouter.d.ts +11 -11
- package/dist_ts/data/networkrouter.js +1 -1
- package/dist_ts/data/networksigning.d.ts +16 -18
- package/dist_ts/data/networksigning.js +20 -21
- package/dist_ts/data/nodecredential.d.ts +4 -6
- package/dist_ts/data/nodeenrollment.d.ts +35 -40
- package/dist_ts/data/nodeenrollment.js +57 -72
- package/dist_ts/data/privatenetwork.d.ts +21 -24
- package/dist_ts/data/privatenetwork.js +39 -54
- package/dist_ts/data/runtimeassignment.d.ts +50 -34
- package/dist_ts/data/runtimeassignment.js +79 -45
- package/dist_ts/data/runtimeconfig.d.ts +21 -22
- package/dist_ts/data/runtimeconfig.js +37 -100
- package/dist_ts/data/runtimesession.d.ts +100 -62
- package/dist_ts/data/runtimesession.js +98 -79
- package/dist_ts/data/runtimeterminalreceipt.d.ts +28 -29
- package/dist_ts/data/runtimeterminalreceipt.js +42 -42
- package/dist_ts/data/secret.d.ts +18 -25
- package/dist_ts/data/secret.js +12 -52
- package/dist_ts/data/servicecredential.d.ts +14 -16
- package/dist_ts/data/servicecredential.js +17 -28
- package/dist_ts/data/serviceruntime.d.ts +128 -0
- package/dist_ts/data/serviceruntime.js +92 -0
- package/dist_ts/data/sparknodereport.d.ts +27 -14
- package/dist_ts/data/sparknodereport.js +35 -28
- package/dist_ts/data/webpush.d.ts +0 -1
- package/dist_ts/index.d.ts +2 -1
- package/dist_ts/index.js +3 -2
- package/dist_ts/platform/objectstorageretention.d.ts +21 -24
- package/dist_ts/platform/objectstorageretention.js +6 -18
- package/dist_ts/platform/storage.d.ts +1 -3
- package/dist_ts/platform/storagemigration.d.ts +0 -7
- package/dist_ts/platform/storagemigration.golden.d.ts +0 -1
- package/dist_ts/platform/storagemigration.golden.js +8 -12
- package/dist_ts/platform/storagemigration.js +6 -51
- package/dist_ts/platform/types.d.ts +2 -2
- package/dist_ts/private/canonicaljson.d.ts +9 -1
- package/dist_ts/private/canonicaljson.js +14 -2
- package/dist_ts/private/dataproperties.d.ts +62 -0
- package/dist_ts/private/dataproperties.js +117 -0
- package/dist_ts/private/networkcontract.d.ts +14 -10
- package/dist_ts/private/networkcontract.js +15 -1
- package/dist_ts/private/producercontract.d.ts +11 -0
- package/dist_ts/private/producercontract.js +21 -0
- package/dist_ts/private/releaseversion.d.ts +37 -0
- package/dist_ts/private/releaseversion.js +48 -0
- package/dist_ts/private/runtimepolicy.d.ts +31 -0
- package/dist_ts/private/runtimepolicy.js +90 -0
- package/dist_ts/private/tokenlist.d.ts +9 -0
- package/dist_ts/private/tokenlist.js +23 -0
- package/dist_ts/protocol.d.ts +137 -0
- package/dist_ts/protocol.js +255 -0
- package/dist_ts/requests/admin.d.ts +8 -8
- package/dist_ts/requests/backup.d.ts +102 -112
- package/dist_ts/requests/cluster.d.ts +47 -10
- package/dist_ts/requests/cluster.js +1 -1
- package/dist_ts/requests/clusteringress.d.ts +9 -0
- package/dist_ts/requests/clusteringress.js +1 -1
- package/dist_ts/requests/config.d.ts +3 -3
- package/dist_ts/requests/corestore.d.ts +0 -3
- package/dist_ts/requests/corestore.js +5 -9
- package/dist_ts/requests/network.d.ts +87 -31
- package/dist_ts/requests/node.d.ts +15 -28
- package/dist_ts/requests/pallet.d.ts +13 -13
- package/dist_ts/requests/runtimesession.d.ts +53 -41
- package/dist_ts/requests/secret.d.ts +4 -5
- package/dist_ts/requests/secret.js +1 -5
- package/dist_ts/requests/service.d.ts +60 -0
- package/dist_ts/runtime.corestore.d.ts +56 -74
- package/dist_ts/runtime.corestore.databaseexportpackage.d.ts +30 -36
- package/dist_ts/runtime.corestore.databaseexportpackage.golden.d.ts +11 -12
- package/dist_ts/runtime.corestore.databaseexportpackage.golden.js +54 -71
- package/dist_ts/runtime.corestore.databaseexportpackage.js +9 -25
- package/dist_ts/runtime.corestore.js +35 -135
- package/dist_ts/runtime.corestore.relay.js +2 -7
- package/dist_ts/runtime.d.ts +38 -48
- package/dist_ts/runtime.js +20 -77
- package/dist_ts/runtime.networkprojection.d.ts +4 -4
- package/dist_ts/runtime.networkprojection.js +18 -18
- package/dist_ts/runtime.workloadinit.d.ts +23 -24
- package/dist_ts/runtime.workloadinit.js +6 -13
- package/package.json +2 -2
- package/readme.md +540 -269
- package/ts/00_commitinfo_data.ts +3 -3
- package/ts/appstore/types.ts +4 -6
- package/ts/data/backup.ts +3 -38
- package/ts/data/cluster.ts +51 -34
- package/ts/data/clusteringress.ts +12 -12
- package/ts/data/clusternode.ts +204 -232
- package/ts/data/clusterruntime.ts +45 -0
- package/ts/data/coremail.runtime.ts +4 -21
- package/ts/data/coremail.ts +3 -7
- package/ts/data/deploymentadoption.ts +46 -4
- package/ts/data/deploymentoperation.ts +16 -1
- package/ts/data/deploymentpreflight.ts +0 -2
- package/ts/data/immutableimage.ts +71 -24
- package/ts/data/index.ts +5 -1
- package/ts/data/isolatedrestore.golden.ts +5 -9
- package/ts/data/isolatedrestore.ts +339 -54
- package/ts/data/mail.ts +1 -1
- package/ts/data/networkaddressplan.ts +175 -0
- package/ts/data/networkauthority.ts +45 -61
- package/ts/data/networkcredential.ts +24 -24
- package/ts/data/networkdelivery.ts +27 -27
- package/ts/data/networklease.ts +40 -13
- package/ts/data/networknode.ts +37 -0
- package/ts/data/networkprojection.ts +119 -124
- package/ts/data/networkprotection.ts +54 -55
- package/ts/data/networkrouter.ts +14 -14
- package/ts/data/networksigning.ts +29 -32
- package/ts/data/nodecredential.ts +4 -6
- package/ts/data/nodeenrollment.ts +96 -114
- package/ts/data/privatenetwork.ts +58 -74
- package/ts/data/runtimeassignment.ts +143 -94
- package/ts/data/runtimeconfig.ts +62 -122
- package/ts/data/runtimesession.ts +185 -138
- package/ts/data/runtimeterminalreceipt.ts +72 -73
- package/ts/data/secret.ts +28 -76
- package/ts/data/servicecredential.ts +33 -43
- package/ts/data/serviceruntime.ts +208 -0
- package/ts/data/sparknodereport.ts +48 -36
- package/ts/data/webpush.ts +0 -1
- package/ts/index.ts +2 -0
- package/ts/platform/objectstorageretention.ts +36 -55
- package/ts/platform/storage.ts +1 -3
- package/ts/platform/storagemigration.golden.ts +7 -11
- package/ts/platform/storagemigration.ts +5 -55
- package/ts/platform/types.ts +2 -2
- package/ts/private/canonicaljson.ts +13 -1
- package/ts/private/dataproperties.ts +139 -0
- package/ts/private/networkcontract.ts +23 -9
- package/ts/private/producercontract.ts +19 -0
- package/ts/private/releaseversion.ts +70 -0
- package/ts/private/runtimepolicy.ts +105 -0
- package/ts/private/tokenlist.ts +30 -0
- package/ts/protocol.ts +364 -0
- package/ts/requests/admin.ts +11 -11
- package/ts/requests/backup.ts +112 -117
- package/ts/requests/cluster.ts +58 -13
- package/ts/requests/clusteringress.ts +9 -0
- package/ts/requests/config.ts +3 -3
- package/ts/requests/corestore.ts +4 -16
- package/ts/requests/network.ts +90 -41
- package/ts/requests/node.ts +21 -38
- package/ts/requests/pallet.ts +17 -17
- package/ts/requests/runtimesession.ts +63 -51
- package/ts/requests/secret.ts +5 -10
- package/ts/requests/service.ts +81 -0
- package/ts/runtime.corestore.databaseexportpackage.golden.ts +77 -95
- package/ts/runtime.corestore.databaseexportpackage.ts +51 -73
- package/ts/runtime.corestore.relay.ts +1 -10
- package/ts/runtime.corestore.ts +118 -268
- package/ts/runtime.networkprojection.ts +30 -30
- package/ts/runtime.ts +83 -158
- package/ts/runtime.workloadinit.ts +36 -48
- package/dist_ts/runtime.cloudlylegacydeploymentsettlement.d.ts +0 -902
- package/dist_ts/runtime.cloudlylegacydeploymentsettlement.golden.d.ts +0 -35
- package/dist_ts/runtime.cloudlylegacydeploymentsettlement.golden.js +0 -315
- package/dist_ts/runtime.cloudlylegacydeploymentsettlement.js +0 -3315
- package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.d.ts +0 -291
- package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.d.ts +0 -30
- package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.js +0 -408
- package/dist_ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.js +0 -1126
- package/ts/runtime.cloudlylegacydeploymentsettlement.golden.ts +0 -421
- package/ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.golden.ts +0 -510
- package/ts/runtime.cloudlylegacydeploymentsettlement.offlineexport.ts +0 -2470
- package/ts/runtime.cloudlylegacydeploymentsettlement.ts +0 -6630
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IRuntimeAssignmentObservation, IRuntimeAssignmentReference, TRuntimeAssignmentDisposition,
|
|
3
|
+
} from './runtimeassignment.js';
|
|
4
|
+
import type { IRuntimeConfigLogPolicy, TRuntimeConfigReadiness } from './runtimeconfig.js';
|
|
5
|
+
import { runtimeNetworkAuthorityContract } from './networkauthority.js';
|
|
6
|
+
import { captureNetwork, networkCanonical, networkCounter, networkId, networkList, networkRecord, networkSorted,
|
|
7
|
+
requireNetwork } from '../private/networkcontract.js';
|
|
8
|
+
import { failProducer, isOneOf, producerSafe, variantOf } from '../private/producercontract.js';
|
|
9
|
+
import { assertRuntimeReadiness, isBoundedInteger, isRuntimeCpuMillis, isRuntimeIdentityNumber, isRuntimeLogPolicy,
|
|
10
|
+
isRuntimeMemoryBytes } from '../private/runtimepolicy.js';
|
|
11
|
+
|
|
12
|
+
/** `nodes` pins the service to exactly these nodes: sorted, unique and never empty. */
|
|
13
|
+
export type TServiceRuntimePlacement =
|
|
14
|
+
| { kind: 'every-node' }
|
|
15
|
+
| { kind: 'nodes'; nodeIds: string[] };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The execution policy a service's runtime configs are built with. It carries everything the
|
|
19
|
+
* runtime config seals except what only the service and its image supply: environment, working
|
|
20
|
+
* directory and target ports. A readiness probe names a target port, which the runtime config then
|
|
21
|
+
* requires the service to declare.
|
|
22
|
+
*/
|
|
23
|
+
export interface IServiceRuntimeProfile {
|
|
24
|
+
/** null explicitly requests no CPU quota. */
|
|
25
|
+
cpuMillis: number | null;
|
|
26
|
+
memoryBytes: number;
|
|
27
|
+
readiness: TRuntimeConfigReadiness;
|
|
28
|
+
logs: IRuntimeConfigLogPolicy;
|
|
29
|
+
readonlyRootfs: boolean;
|
|
30
|
+
/** null runs the image's own OCI user, exactly as a runtime config's null identity pair does. */
|
|
31
|
+
runAs: { user: number; group: number } | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `isolated` gives the workload no network authority. `attached` joins the runtime network with
|
|
36
|
+
* public egress on or off and the platform endpoints it may reach, by id: sorted and unique.
|
|
37
|
+
*/
|
|
38
|
+
export type TServiceRuntimeNetwork =
|
|
39
|
+
| { mode: 'isolated' }
|
|
40
|
+
| { mode: 'attached'; publicEgress: boolean; platformEndpointIds: string[] };
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* What Cloudly runs for one service on a Pallet cluster. Stored complete, never defaulted at read
|
|
44
|
+
* time, and beside the service document rather than inside it, so a hold written by a cutover
|
|
45
|
+
* never races an operator's service edit.
|
|
46
|
+
*/
|
|
47
|
+
export interface IServiceRuntimeSpec {
|
|
48
|
+
/** The service id. */
|
|
49
|
+
id: string;
|
|
50
|
+
/** The service's organization, copied by Cloudly; never caller-chosen. */
|
|
51
|
+
organizationId: string;
|
|
52
|
+
clusterId: string;
|
|
53
|
+
placement: TServiceRuntimePlacement;
|
|
54
|
+
replicasPerNode: number;
|
|
55
|
+
/** While true, the service runs nowhere and every slot keeps its storage. */
|
|
56
|
+
hold: boolean;
|
|
57
|
+
profile: IServiceRuntimeProfile;
|
|
58
|
+
network: TServiceRuntimeNetwork;
|
|
59
|
+
/** Compare-and-swap counter; every write advances it by one. */
|
|
60
|
+
revision: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** What an administrator writes; Cloudly supplies the service id, organization and revision. */
|
|
64
|
+
export type TServiceRuntimeSpecWritableData = Omit<IServiceRuntimeSpec, 'id' | 'organizationId' | 'revision'>;
|
|
65
|
+
|
|
66
|
+
export const serviceRuntimeContract = Object.freeze({
|
|
67
|
+
maximumReplicasPerNode: 64,
|
|
68
|
+
/** A service whose placement desires more replicas than this is refused as `replica-limit`. */
|
|
69
|
+
maximumReplicas: 64,
|
|
70
|
+
/** A node can run workloads only as an egress member of the protected authority, which admits at most this many. */
|
|
71
|
+
maximumPinnedNodes: runtimeNetworkAuthorityContract.maximumEgressAuthorities,
|
|
72
|
+
maximumPlatformEndpoints: runtimeNetworkAuthorityContract.maximumPlatformEndpoints,
|
|
73
|
+
/** The largest `limit` `getClusterRuntimeAssignments` accepts; Cloudly refuses a larger one. */
|
|
74
|
+
maximumAssignmentPageSize: 256,
|
|
75
|
+
} as const);
|
|
76
|
+
|
|
77
|
+
const serviceRuntimeWaitingReasons = ['node-network-pending', 'conflict', 'failure-backoff'] as const;
|
|
78
|
+
const serviceRuntimeRefusals = [
|
|
79
|
+
'cluster-runtime-missing', 'organization-missing', 'image-plan-missing', 'platform-unsupported',
|
|
80
|
+
'secrets-unsupported', 'volumes-unsupported', 'port-protocol-unsupported', 'readiness-target-undeclared',
|
|
81
|
+
'environment-too-large', 'no-eligible-node', 'replica-limit',
|
|
82
|
+
] as const;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Why the producer is not converging yet, though nothing needs an administrator:
|
|
86
|
+
* `node-network-pending` waits for a node's network readiness, `conflict` for a sweep over inputs
|
|
87
|
+
* that changed while it ran, `failure-backoff` for a failed replica's backoff to elapse.
|
|
88
|
+
*/
|
|
89
|
+
export type TServiceRuntimeWaitingReason = typeof serviceRuntimeWaitingReasons[number];
|
|
90
|
+
|
|
91
|
+
/** Why the producer admits nothing for this spec revision. A refusal never retires running slots. */
|
|
92
|
+
export type TServiceRuntimeRefusal = typeof serviceRuntimeRefusals[number];
|
|
93
|
+
|
|
94
|
+
interface IServiceRuntimeStatusBase {
|
|
95
|
+
/** The service id. */
|
|
96
|
+
id: string;
|
|
97
|
+
/** The spec revision this verdict is about. */
|
|
98
|
+
specRevision: number;
|
|
99
|
+
/** Unix milliseconds on Cloudly's clock. Written only when the verdict changes. */
|
|
100
|
+
changedAt: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The producer's verdict on one spec revision. */
|
|
104
|
+
export type IServiceRuntimeStatus = IServiceRuntimeStatusBase & (
|
|
105
|
+
| { state: 'converged' | 'converging'; reason: null }
|
|
106
|
+
| { state: 'waiting'; reason: TServiceRuntimeWaitingReason }
|
|
107
|
+
| { state: 'refused'; reason: TServiceRuntimeRefusal }
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* `delivered` once the node's own evidence, an observation or a terminal receipt, cites this exact
|
|
112
|
+
* revision; `undelivered` until then. Cloudly keeps no acknowledgement journal, so a revision the
|
|
113
|
+
* node admitted but has not yet reported on reads as undelivered.
|
|
114
|
+
*/
|
|
115
|
+
export type TServiceRuntimeAssignmentDelivery = 'undelivered' | 'delivered';
|
|
116
|
+
|
|
117
|
+
/** One replica slot's current assignment, as an administrator reads it. */
|
|
118
|
+
export interface IServiceRuntimeAssignmentView {
|
|
119
|
+
serviceId: string;
|
|
120
|
+
replicaId: string;
|
|
121
|
+
nodeId: string;
|
|
122
|
+
/** The current revision; its generation is the one the disposition belongs to. */
|
|
123
|
+
assignment: IRuntimeAssignmentReference;
|
|
124
|
+
disposition: TRuntimeAssignmentDisposition;
|
|
125
|
+
/** The latest accepted observation of this attempt, which may cite an earlier revision. */
|
|
126
|
+
observation: Pick<IRuntimeAssignmentObservation, 'phase' | 'ready' | 'observedAt' | 'sequence'> | null;
|
|
127
|
+
/** Joined stop and remove receipts of this attempt. */
|
|
128
|
+
terminalReceiptCount: 0 | 1 | 2;
|
|
129
|
+
delivery: TServiceRuntimeAssignmentDelivery;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const sortedIds = (value: unknown, maximum: number): string[] =>
|
|
133
|
+
networkSorted(networkList(value, maximum, networkId), id => id);
|
|
134
|
+
|
|
135
|
+
const placementOf = (value: unknown): TServiceRuntimePlacement => {
|
|
136
|
+
const kind = variantOf(value, 'kind');
|
|
137
|
+
if (kind === 'every-node') {
|
|
138
|
+
networkRecord(value, ['kind']);
|
|
139
|
+
return { kind };
|
|
140
|
+
}
|
|
141
|
+
requireNetwork(kind === 'nodes');
|
|
142
|
+
const nodeIds = sortedIds(networkRecord(value, ['kind', 'nodeIds']).nodeIds, serviceRuntimeContract.maximumPinnedNodes);
|
|
143
|
+
requireNetwork(nodeIds.length > 0);
|
|
144
|
+
return { kind, nodeIds };
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const runAsOf = (value: unknown): IServiceRuntimeProfile['runAs'] => {
|
|
148
|
+
if (value === null) return null;
|
|
149
|
+
const item = networkRecord(value, ['user', 'group']);
|
|
150
|
+
requireNetwork(isRuntimeIdentityNumber(item.user) && isRuntimeIdentityNumber(item.group));
|
|
151
|
+
return { user: item.user, group: item.group };
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const profileOf = (value: unknown): IServiceRuntimeProfile => {
|
|
155
|
+
const item = networkRecord(value, ['cpuMillis', 'memoryBytes', 'readiness', 'logs', 'readonlyRootfs', 'runAs']);
|
|
156
|
+
requireNetwork(isRuntimeCpuMillis(item.cpuMillis) && isRuntimeMemoryBytes(item.memoryBytes)
|
|
157
|
+
&& isRuntimeLogPolicy(item.logs) && typeof item.readonlyRootfs === 'boolean');
|
|
158
|
+
// The profile precedes the service's target ports; the runtime config refuses a target they do not declare.
|
|
159
|
+
assertRuntimeReadiness(item.readiness, () => true, failProducer);
|
|
160
|
+
const { maximumLineBytes, maximumBufferedBytes, retentionMs } = item.logs;
|
|
161
|
+
return { cpuMillis: item.cpuMillis, memoryBytes: item.memoryBytes,
|
|
162
|
+
readiness: JSON.parse(networkCanonical(item.readiness)) as TRuntimeConfigReadiness,
|
|
163
|
+
logs: { maximumLineBytes, maximumBufferedBytes, retentionMs }, readonlyRootfs: item.readonlyRootfs, runAs: runAsOf(item.runAs) };
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const networkOf = (value: unknown): TServiceRuntimeNetwork => {
|
|
167
|
+
const mode = variantOf(value, 'mode');
|
|
168
|
+
if (mode === 'isolated') {
|
|
169
|
+
networkRecord(value, ['mode']);
|
|
170
|
+
return { mode };
|
|
171
|
+
}
|
|
172
|
+
requireNetwork(mode === 'attached');
|
|
173
|
+
const item = networkRecord(value, ['mode', 'publicEgress', 'platformEndpointIds']);
|
|
174
|
+
requireNetwork(typeof item.publicEgress === 'boolean');
|
|
175
|
+
return { mode, publicEgress: item.publicEgress,
|
|
176
|
+
platformEndpointIds: sortedIds(item.platformEndpointIds, serviceRuntimeContract.maximumPlatformEndpoints) };
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Structural snapshot for exact persistence and for a spec write. Whether the cluster, the nodes and
|
|
181
|
+
* the platform endpoints exist, and whether the service declares the readiness target, is Cloudly's.
|
|
182
|
+
*/
|
|
183
|
+
export const snapshotServiceRuntimeSpec = (value: unknown): IServiceRuntimeSpec => producerSafe(() => {
|
|
184
|
+
const item = networkRecord(captureNetwork(value), ['id', 'organizationId', 'clusterId', 'placement',
|
|
185
|
+
'replicasPerNode', 'hold', 'profile', 'network', 'revision']);
|
|
186
|
+
requireNetwork(isBoundedInteger(item.replicasPerNode, 1, serviceRuntimeContract.maximumReplicasPerNode)
|
|
187
|
+
&& typeof item.hold === 'boolean');
|
|
188
|
+
return { id: networkId(item.id), organizationId: networkId(item.organizationId), clusterId: networkId(item.clusterId),
|
|
189
|
+
placement: placementOf(item.placement), replicasPerNode: item.replicasPerNode, hold: item.hold,
|
|
190
|
+
profile: profileOf(item.profile), network: networkOf(item.network), revision: networkCounter(item.revision) };
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
/** Structural snapshot for exact persistence: a reason belongs to exactly one state. */
|
|
194
|
+
export const snapshotServiceRuntimeStatus = (value: unknown): IServiceRuntimeStatus => producerSafe(() => {
|
|
195
|
+
const item = networkRecord(captureNetwork(value), ['id', 'specRevision', 'state', 'reason', 'changedAt']);
|
|
196
|
+
const base: IServiceRuntimeStatusBase = { id: networkId(item.id), specRevision: networkCounter(item.specRevision),
|
|
197
|
+
changedAt: networkCounter(item.changedAt) };
|
|
198
|
+
if (item.state === 'converged' || item.state === 'converging') {
|
|
199
|
+
requireNetwork(item.reason === null);
|
|
200
|
+
return { ...base, state: item.state, reason: null };
|
|
201
|
+
}
|
|
202
|
+
if (item.state === 'waiting') {
|
|
203
|
+
requireNetwork(isOneOf(serviceRuntimeWaitingReasons, item.reason));
|
|
204
|
+
return { ...base, state: item.state, reason: item.reason };
|
|
205
|
+
}
|
|
206
|
+
requireNetwork(item.state === 'refused' && isOneOf(serviceRuntimeRefusals, item.reason));
|
|
207
|
+
return { ...base, state: item.state, reason: item.reason };
|
|
208
|
+
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { isReleaseVersion } from '../private/releaseversion.js';
|
|
2
|
+
import { readExactDataRecord } from '../private/dataproperties.js';
|
|
3
|
+
import { protocolRefusalHttpStatus, readProtocolOffer, type IProtocolOffer } from '../protocol.js';
|
|
4
|
+
|
|
1
5
|
/** Host observations only. Container and workload readiness belong to Pallet. */
|
|
2
|
-
export interface
|
|
3
|
-
schemaVersion: 2;
|
|
6
|
+
export interface ISparkNodeReport {
|
|
4
7
|
observedAt: number;
|
|
5
8
|
metrics: {
|
|
6
9
|
cpuUsagePercent: number;
|
|
@@ -21,51 +24,55 @@ export interface ISparkNodeReportV2 {
|
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
/** HTTPS POST body. Never log, persist, or return nodeToken. */
|
|
24
|
-
export interface
|
|
27
|
+
export interface ISparkNodeHeartbeatRequest {
|
|
25
28
|
nodeId: string;
|
|
26
29
|
nodeToken: string;
|
|
27
|
-
|
|
30
|
+
/** Read and negotiated before the node is authenticated; see `sparkNodeHeartbeatContract`. */
|
|
31
|
+
protocol: IProtocolOffer;
|
|
32
|
+
report: ISparkNodeReport;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
/** Historical acceptance evidence, not current authority or workload readiness. */
|
|
31
|
-
export interface
|
|
32
|
-
schemaVersion: 2;
|
|
36
|
+
export interface ISparkNodeHeartbeatReceipt {
|
|
33
37
|
accepted: true;
|
|
34
38
|
nodeId: string;
|
|
35
39
|
credentialGeneration: number;
|
|
36
40
|
sessionEpoch: number;
|
|
37
41
|
acceptedAt: number;
|
|
42
|
+
/** The controller's offer, so an accepted node judges the session it just reported into. */
|
|
43
|
+
protocol: IProtocolOffer;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
/** Persist only after the server's current Spark credential transaction commits. */
|
|
41
|
-
export interface
|
|
42
|
-
report:
|
|
43
|
-
receipt:
|
|
47
|
+
export interface ISparkNodeReportRecord {
|
|
48
|
+
report: ISparkNodeReport;
|
|
49
|
+
receipt: ISparkNodeHeartbeatReceipt;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The node runtime's own heartbeat route.
|
|
54
|
+
*
|
|
55
|
+
* Judged in the one server order `protocol.readProtocolOffer` states, against this server's
|
|
56
|
+
* `sparkNode` offer and within `maxRequestBytes`. The route carries no typed error, so a refusal
|
|
57
|
+
* is answered with `protocolRefusalStatus` and an `IProtocolRefusal` body — before the report is
|
|
58
|
+
* snapshotted and before the node is authenticated, because a route that authenticated first would
|
|
59
|
+
* answer a peer of another major with a credential verdict it cannot act on.
|
|
60
|
+
*/
|
|
61
|
+
export const sparkNodeHeartbeatContract = Object.freeze({
|
|
62
|
+
endpoint: '/spark/nodes/heartbeat',
|
|
48
63
|
maxRequestBytes: 8192,
|
|
49
64
|
maxResponseBytes: 2048,
|
|
65
|
+
protocolRefusalStatus: protocolRefusalHttpStatus,
|
|
50
66
|
} as const);
|
|
51
67
|
|
|
52
68
|
const fail = (): never => { throw new Error('Spark node heartbeat contract rejected.'); };
|
|
53
69
|
function requireValue(valueArg: unknown): asserts valueArg {
|
|
54
70
|
if (!valueArg) fail();
|
|
55
71
|
}
|
|
72
|
+
/** Exactly these keys, carrying data: the shared reader refuses an accessor instead of running it. */
|
|
56
73
|
const record = (valueArg: unknown, keysArg: readonly string[]): Record<string, unknown> => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
requireValue(prototype === Object.prototype || prototype === null);
|
|
60
|
-
const keys = Reflect.ownKeys(valueArg);
|
|
61
|
-
requireValue(keys.length === keysArg.length);
|
|
62
|
-
const result: Record<string, unknown> = Object.create(null);
|
|
63
|
-
for (const key of keys) {
|
|
64
|
-
requireValue(typeof key === 'string' && keysArg.includes(key));
|
|
65
|
-
const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
|
|
66
|
-
requireValue(descriptor?.enumerable && Object.hasOwn(descriptor, 'value'));
|
|
67
|
-
result[key] = descriptor.value;
|
|
68
|
-
}
|
|
74
|
+
const result = readExactDataRecord(valueArg, keysArg);
|
|
75
|
+
requireValue(result !== null);
|
|
69
76
|
return result;
|
|
70
77
|
};
|
|
71
78
|
const text = (valueArg: unknown, patternArg: RegExp, maxLengthArg: number): string => {
|
|
@@ -73,6 +80,11 @@ const text = (valueArg: unknown, patternArg: RegExp, maxLengthArg: number): stri
|
|
|
73
80
|
&& valueArg.match(patternArg)?.[0] === valueArg);
|
|
74
81
|
return valueArg;
|
|
75
82
|
};
|
|
83
|
+
// The shared grammar, not a copy of it: a stored bundle version has to be one the release
|
|
84
|
+
// comparison can order, and a second rule here would be a second answer to that question.
|
|
85
|
+
const releaseVersion = (valueArg: unknown): string => (
|
|
86
|
+
isReleaseVersion(valueArg) ? valueArg : fail()
|
|
87
|
+
);
|
|
76
88
|
const number = (valueArg: unknown, maxArg = Number.MAX_SAFE_INTEGER): number => {
|
|
77
89
|
requireValue(typeof valueArg === 'number' && Number.isFinite(valueArg)
|
|
78
90
|
&& !Object.is(valueArg, -0) && valueArg >= 0 && valueArg <= maxArg);
|
|
@@ -85,9 +97,8 @@ const integer = (valueArg: unknown, minimumArg: number): number => {
|
|
|
85
97
|
};
|
|
86
98
|
|
|
87
99
|
/** Exact inert JSON records; detaches and freezes before any consumer awaits. */
|
|
88
|
-
export const
|
|
89
|
-
const value = record(valueArg, ['
|
|
90
|
-
requireValue(value.schemaVersion === 2);
|
|
100
|
+
export const snapshotSparkNodeReport = (valueArg: unknown): ISparkNodeReport => {
|
|
101
|
+
const value = record(valueArg, ['observedAt', 'metrics', 'runtime']);
|
|
91
102
|
const metrics = record(value.metrics, ['cpuUsagePercent', 'memoryUsedMB', 'memoryAvailableMB',
|
|
92
103
|
'diskUsedGB', 'diskAvailableGB']);
|
|
93
104
|
const runtime = record(value.runtime, ['hostname', 'platform', 'arch', 'osRelease', 'bundle',
|
|
@@ -98,7 +109,6 @@ export const snapshotSparkNodeReportV2 = (valueArg: unknown): ISparkNodeReportV2
|
|
|
98
109
|
requireValue(hostname.split('.').every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)));
|
|
99
110
|
const bundle = record(runtime.bundle, ['version', 'sourceCommit', 'manifestSha256']);
|
|
100
111
|
return Object.freeze({
|
|
101
|
-
schemaVersion: 2,
|
|
102
112
|
observedAt: integer(value.observedAt, 1),
|
|
103
113
|
metrics: Object.freeze({
|
|
104
114
|
cpuUsagePercent: number(metrics.cpuUsagePercent, 100),
|
|
@@ -109,7 +119,7 @@ export const snapshotSparkNodeReportV2 = (valueArg: unknown): ISparkNodeReportV2
|
|
|
109
119
|
hostname, platform: 'linux', arch: runtime.arch as 'x86_64' | 'aarch64',
|
|
110
120
|
osRelease: text(runtime.osRelease, /^[\x20-\x7e]+$/, 256),
|
|
111
121
|
bundle: Object.freeze({
|
|
112
|
-
version:
|
|
122
|
+
version: releaseVersion(bundle.version),
|
|
113
123
|
sourceCommit: text(bundle.sourceCommit, /^[a-f0-9]{40}$/, 40),
|
|
114
124
|
manifestSha256: text(bundle.manifestSha256, /^[a-f0-9]{64}$/, 64),
|
|
115
125
|
}),
|
|
@@ -118,23 +128,25 @@ export const snapshotSparkNodeReportV2 = (valueArg: unknown): ISparkNodeReportV2
|
|
|
118
128
|
});
|
|
119
129
|
};
|
|
120
130
|
|
|
121
|
-
export const
|
|
122
|
-
const value = record(valueArg, ['nodeId', 'nodeToken', 'report']);
|
|
131
|
+
export const snapshotSparkNodeHeartbeatRequest = (valueArg: unknown): ISparkNodeHeartbeatRequest => {
|
|
132
|
+
const value = record(valueArg, ['nodeId', 'nodeToken', 'protocol', 'report']);
|
|
123
133
|
return Object.freeze({
|
|
124
134
|
nodeId: text(value.nodeId, /^[A-Za-z0-9_-]+$/, 128),
|
|
125
135
|
// Both freshly enrolled and explicitly adopted credentials use this bearer format.
|
|
126
136
|
nodeToken: text(value.nodeToken, /^secureToken[a-f0-9]{64}$/, 75),
|
|
127
|
-
|
|
137
|
+
protocol: readProtocolOffer(value),
|
|
138
|
+
report: snapshotSparkNodeReport(value.report),
|
|
128
139
|
});
|
|
129
140
|
};
|
|
130
141
|
|
|
131
|
-
export const
|
|
132
|
-
const value = record(valueArg, ['
|
|
133
|
-
'sessionEpoch', 'acceptedAt']);
|
|
134
|
-
requireValue(value.
|
|
142
|
+
export const snapshotSparkNodeHeartbeatReceipt = (valueArg: unknown): ISparkNodeHeartbeatReceipt => {
|
|
143
|
+
const value = record(valueArg, ['accepted', 'nodeId', 'credentialGeneration',
|
|
144
|
+
'sessionEpoch', 'acceptedAt', 'protocol']);
|
|
145
|
+
requireValue(value.accepted === true);
|
|
135
146
|
return Object.freeze({
|
|
136
|
-
|
|
147
|
+
accepted: true, nodeId: text(value.nodeId, /^[A-Za-z0-9_-]+$/, 128),
|
|
137
148
|
credentialGeneration: integer(value.credentialGeneration, 1),
|
|
138
149
|
sessionEpoch: integer(value.sessionEpoch, 0), acceptedAt: integer(value.acceptedAt, 1),
|
|
150
|
+
protocol: readProtocolOffer(value),
|
|
139
151
|
});
|
|
140
152
|
};
|
package/ts/data/webpush.ts
CHANGED
|
@@ -101,7 +101,6 @@ export interface IWebPushBinding {
|
|
|
101
101
|
* from their authenticated API after receiving this event.
|
|
102
102
|
*/
|
|
103
103
|
export interface IWebPushNotificationPayload {
|
|
104
|
-
schemaVersion: 1;
|
|
105
104
|
event: 'notificationAvailable';
|
|
106
105
|
eventId: string;
|
|
107
106
|
/** Same-origin application route beginning with a single slash. */
|
package/ts/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as data from './data/index.js';
|
|
|
2
2
|
import * as appstore from './appstore/index.js';
|
|
3
3
|
import * as platform from './platform/index.js';
|
|
4
4
|
import * as platformservice from './platformservice/index.js';
|
|
5
|
+
import * as protocol from './protocol.js';
|
|
5
6
|
import * as requests from './requests/index.js';
|
|
6
7
|
|
|
7
8
|
export {
|
|
@@ -9,5 +10,6 @@ export {
|
|
|
9
10
|
data,
|
|
10
11
|
platform,
|
|
11
12
|
platformservice,
|
|
13
|
+
protocol,
|
|
12
14
|
requests
|
|
13
15
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { createCanonicalJsonSha256Hex } from '../private/canonicaljson.js';
|
|
2
2
|
import { validatePlatformObjectStorageBucketName } from './objectstorage.js';
|
|
3
|
+
import { hasExactKeys } from '../private/dataproperties.js';
|
|
3
4
|
|
|
4
5
|
export const objectStorageRetentionLimits = Object.freeze({
|
|
5
6
|
maximumPolicyIdLength: 96,
|
|
6
7
|
maximumRetentionDurationSeconds: 100 * 366 * 24 * 60 * 60,
|
|
7
8
|
});
|
|
8
9
|
|
|
9
|
-
export interface
|
|
10
|
+
export interface IObjectStorageRetentionIntent {
|
|
10
11
|
mode: 'compliance';
|
|
11
12
|
policyId: string;
|
|
12
13
|
retentionDurationSeconds: number;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
export interface
|
|
16
|
-
version: 1;
|
|
16
|
+
export interface IObjectStorageRetentionCapabilityEvidence {
|
|
17
17
|
supported: true;
|
|
18
18
|
backend: 'standalone' | 'clustered';
|
|
19
19
|
modes: ['compliance'];
|
|
@@ -21,16 +21,15 @@ export interface IObjectStorageRetentionCapabilityEvidenceV1 {
|
|
|
21
21
|
retainedMultipartSupported: false;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export interface
|
|
24
|
+
export interface IObjectStorageRetentionAuthority {
|
|
25
25
|
serviceId: string;
|
|
26
26
|
bindingId: string;
|
|
27
27
|
reconciliationGeneration: number;
|
|
28
28
|
bindingRequestDigest: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export interface
|
|
32
|
-
extends
|
|
33
|
-
version: 1;
|
|
31
|
+
export interface IObjectStorageRetentionSentinelEvidence
|
|
32
|
+
extends IObjectStorageRetentionIntent {
|
|
34
33
|
createdAt: number;
|
|
35
34
|
retainUntil: number;
|
|
36
35
|
payloadSha256: string;
|
|
@@ -38,33 +37,32 @@ extends IObjectStorageRetentionIntentV1 {
|
|
|
38
37
|
etag: string;
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
export interface
|
|
42
|
-
extends
|
|
40
|
+
export interface IObjectStorageRetentionReceipt
|
|
41
|
+
extends IObjectStorageRetentionIntent {
|
|
43
42
|
bucketName: string;
|
|
44
43
|
configuredAt: number;
|
|
45
44
|
sentinelKey: string;
|
|
46
|
-
sentinel:
|
|
45
|
+
sentinel: IObjectStorageRetentionSentinelEvidence;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
export interface
|
|
50
|
-
intent:
|
|
48
|
+
export interface IObjectStorageRetentionEvidence {
|
|
49
|
+
intent: IObjectStorageRetentionIntent;
|
|
51
50
|
intentSha256: string;
|
|
52
|
-
authority:
|
|
53
|
-
capability:
|
|
54
|
-
receipt:
|
|
51
|
+
authority: IObjectStorageRetentionAuthority;
|
|
52
|
+
capability: IObjectStorageRetentionCapabilityEvidence;
|
|
53
|
+
receipt: IObjectStorageRetentionReceipt;
|
|
55
54
|
verifiedAt: number;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
/** Value-free desired intent with optional exact provider evidence. */
|
|
59
|
-
export interface
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
evidence?: IObjectStorageRetentionEvidenceV1;
|
|
58
|
+
export interface IObjectStorageRetentionBinding {
|
|
59
|
+
intent: IObjectStorageRetentionIntent;
|
|
60
|
+
evidence?: IObjectStorageRetentionEvidence;
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
export interface
|
|
66
|
-
extends
|
|
67
|
-
intent:
|
|
63
|
+
export interface IObjectStorageRetentionEvidenceExpectation
|
|
64
|
+
extends IObjectStorageRetentionAuthority {
|
|
65
|
+
intent: IObjectStorageRetentionIntent;
|
|
68
66
|
bucketName: string;
|
|
69
67
|
}
|
|
70
68
|
|
|
@@ -72,7 +70,7 @@ const policyIdPattern = /^[A-Za-z0-9._-]{1,96}$/;
|
|
|
72
70
|
const identifierPattern = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/;
|
|
73
71
|
const sha256Pattern = /^[a-f0-9]{64}$/;
|
|
74
72
|
const etagPattern = /^[a-f0-9]{32}$/;
|
|
75
|
-
const sentinelKeyPattern = /^\.smartstorage-retention\/
|
|
73
|
+
const sentinelKeyPattern = /^\.smartstorage-retention\/sentinel-[a-f0-9]{64}\.json$/;
|
|
76
74
|
|
|
77
75
|
const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => (
|
|
78
76
|
Boolean(valueArg)
|
|
@@ -82,16 +80,6 @@ const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => (
|
|
|
82
80
|
|| Object.getPrototypeOf(valueArg) === null)
|
|
83
81
|
);
|
|
84
82
|
|
|
85
|
-
const hasExactKeys = (
|
|
86
|
-
valueArg: Record<string, unknown>,
|
|
87
|
-
keysArg: readonly string[],
|
|
88
|
-
): boolean => {
|
|
89
|
-
const actualKeys = Object.keys(valueArg).sort();
|
|
90
|
-
const expectedKeys = [...keysArg].sort();
|
|
91
|
-
return actualKeys.length === expectedKeys.length
|
|
92
|
-
&& actualKeys.every((keyArg, indexArg) => keyArg === expectedKeys[indexArg]);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
83
|
const isPositiveSafeInteger = (valueArg: unknown): valueArg is number => (
|
|
96
84
|
Number.isSafeInteger(valueArg) && (valueArg as number) > 0
|
|
97
85
|
);
|
|
@@ -135,7 +123,7 @@ export const createObjectStorageRetentionRetainUntil = (
|
|
|
135
123
|
};
|
|
136
124
|
|
|
137
125
|
export const createObjectStorageRetentionIntentDigestInput = (
|
|
138
|
-
intentArg:
|
|
126
|
+
intentArg: IObjectStorageRetentionIntent,
|
|
139
127
|
): string => {
|
|
140
128
|
const errors = validateObjectStorageRetentionIntent(intentArg);
|
|
141
129
|
if (errors.length > 0) throw new Error(errors[0]);
|
|
@@ -146,12 +134,11 @@ export const createObjectStorageRetentionIntentDigestInput = (
|
|
|
146
134
|
policyId: intentArg.policyId,
|
|
147
135
|
retentionDurationSeconds: intentArg.retentionDurationSeconds,
|
|
148
136
|
},
|
|
149
|
-
version: 1,
|
|
150
137
|
});
|
|
151
138
|
};
|
|
152
139
|
|
|
153
140
|
export const computeObjectStorageRetentionIntentSha256 = async (
|
|
154
|
-
intentArg:
|
|
141
|
+
intentArg: IObjectStorageRetentionIntent,
|
|
155
142
|
): Promise<string> => createCanonicalJsonSha256Hex(
|
|
156
143
|
createObjectStorageRetentionIntentDigestInput(intentArg),
|
|
157
144
|
(reasonArg) => { throw new Error(reasonArg); },
|
|
@@ -162,12 +149,12 @@ export const objectStorageRetentionIntentsEqual = (
|
|
|
162
149
|
rightArg: unknown,
|
|
163
150
|
): boolean => validateObjectStorageRetentionIntent(leftArg).length === 0
|
|
164
151
|
&& validateObjectStorageRetentionIntent(rightArg).length === 0
|
|
165
|
-
&& (leftArg as
|
|
166
|
-
=== (rightArg as
|
|
167
|
-
&& (leftArg as
|
|
168
|
-
=== (rightArg as
|
|
169
|
-
&& (leftArg as
|
|
170
|
-
=== (rightArg as
|
|
152
|
+
&& (leftArg as IObjectStorageRetentionIntent).mode
|
|
153
|
+
=== (rightArg as IObjectStorageRetentionIntent).mode
|
|
154
|
+
&& (leftArg as IObjectStorageRetentionIntent).policyId
|
|
155
|
+
=== (rightArg as IObjectStorageRetentionIntent).policyId
|
|
156
|
+
&& (leftArg as IObjectStorageRetentionIntent).retentionDurationSeconds
|
|
157
|
+
=== (rightArg as IObjectStorageRetentionIntent).retentionDurationSeconds;
|
|
171
158
|
|
|
172
159
|
const retentionIntentFieldsEqual = (
|
|
173
160
|
leftArg: unknown,
|
|
@@ -183,7 +170,7 @@ const retentionIntentFieldsEqual = (
|
|
|
183
170
|
|
|
184
171
|
export const validateObjectStorageRetentionEvidence = async (
|
|
185
172
|
evidenceArg: unknown,
|
|
186
|
-
expectedArg:
|
|
173
|
+
expectedArg: IObjectStorageRetentionEvidenceExpectation,
|
|
187
174
|
): Promise<string[]> => {
|
|
188
175
|
if (!isRecord(evidenceArg)
|
|
189
176
|
|| !hasExactKeys(evidenceArg, [
|
|
@@ -197,7 +184,7 @@ export const validateObjectStorageRetentionEvidence = async (
|
|
|
197
184
|
return ['objectstorage retention evidence must use its exact schema'];
|
|
198
185
|
}
|
|
199
186
|
const errors = validateObjectStorageRetentionIntent(evidenceArg.intent);
|
|
200
|
-
const intent = evidenceArg.intent as
|
|
187
|
+
const intent = evidenceArg.intent as IObjectStorageRetentionIntent;
|
|
201
188
|
if (!retentionIntentFieldsEqual(intent, expectedArg.intent)) {
|
|
202
189
|
errors.push('objectstorage retention evidence intent must equal the expected intent');
|
|
203
190
|
}
|
|
@@ -233,14 +220,12 @@ export const validateObjectStorageRetentionEvidence = async (
|
|
|
233
220
|
const capability = evidenceArg.capability;
|
|
234
221
|
if (!isRecord(capability)
|
|
235
222
|
|| !hasExactKeys(capability, [
|
|
236
|
-
'version',
|
|
237
223
|
'supported',
|
|
238
224
|
'backend',
|
|
239
225
|
'modes',
|
|
240
226
|
'atomicCreateOnly',
|
|
241
227
|
'retainedMultipartSupported',
|
|
242
228
|
])
|
|
243
|
-
|| capability.version !== 1
|
|
244
229
|
|| capability.supported !== true
|
|
245
230
|
|| (capability.backend !== 'standalone' && capability.backend !== 'clustered')
|
|
246
231
|
|| !Array.isArray(capability.modes)
|
|
@@ -283,7 +268,6 @@ export const validateObjectStorageRetentionEvidence = async (
|
|
|
283
268
|
const sentinel = receipt.sentinel;
|
|
284
269
|
if (!isRecord(sentinel)
|
|
285
270
|
|| !hasExactKeys(sentinel, [
|
|
286
|
-
'version',
|
|
287
271
|
'mode',
|
|
288
272
|
'policyId',
|
|
289
273
|
'retentionDurationSeconds',
|
|
@@ -296,7 +280,7 @@ export const validateObjectStorageRetentionEvidence = async (
|
|
|
296
280
|
errors.push('objectstorage retention sentinel evidence must use its exact schema');
|
|
297
281
|
return errors;
|
|
298
282
|
}
|
|
299
|
-
if (
|
|
283
|
+
if (!retentionIntentFieldsEqual(sentinel, intent)) {
|
|
300
284
|
errors.push('objectstorage retention sentinel intent must equal its evidence intent');
|
|
301
285
|
}
|
|
302
286
|
if (!isPositiveSafeInteger(sentinel.createdAt)
|
|
@@ -331,17 +315,14 @@ export const validateObjectStorageRetentionEvidence = async (
|
|
|
331
315
|
|
|
332
316
|
export const validateObjectStorageRetentionBinding = async (
|
|
333
317
|
bindingArg: unknown,
|
|
334
|
-
expectedArg:
|
|
318
|
+
expectedArg: IObjectStorageRetentionEvidenceExpectation,
|
|
335
319
|
): Promise<string[]> => {
|
|
336
320
|
if (!isRecord(bindingArg)
|
|
337
|
-
|| !Object.hasOwn(bindingArg, 'schemaVersion')
|
|
338
321
|
|| !Object.hasOwn(bindingArg, 'intent')
|
|
339
|
-
|| Object.keys(bindingArg).some((keyArg) => !['
|
|
322
|
+
|| Object.keys(bindingArg).some((keyArg) => !['intent', 'evidence'].includes(keyArg))) {
|
|
340
323
|
return ['objectstorage retention binding must use its exact schema'];
|
|
341
324
|
}
|
|
342
|
-
const errors = bindingArg.
|
|
343
|
-
? validateObjectStorageRetentionIntent(bindingArg.intent)
|
|
344
|
-
: ['objectstorage retention binding schemaVersion must be 1'];
|
|
325
|
+
const errors = validateObjectStorageRetentionIntent(bindingArg.intent);
|
|
345
326
|
if (!retentionIntentFieldsEqual(bindingArg.intent, expectedArg.intent)) {
|
|
346
327
|
errors.push('objectstorage retention binding intent must equal the trusted expected intent');
|
|
347
328
|
}
|
|
@@ -356,7 +337,7 @@ export const validateObjectStorageRetentionBinding = async (
|
|
|
356
337
|
|
|
357
338
|
export const validatePlatformBindingObjectStorageRetention = async (
|
|
358
339
|
bindingArg: unknown,
|
|
359
|
-
expectedArg:
|
|
340
|
+
expectedArg: IObjectStorageRetentionEvidenceExpectation,
|
|
360
341
|
): Promise<string[]> => {
|
|
361
342
|
if (!isRecord(bindingArg)) {
|
|
362
343
|
return ['platform objectstorage retention binding must be an object'];
|
package/ts/platform/storage.ts
CHANGED
|
@@ -102,7 +102,6 @@ export type TStorageClassCapabilities =
|
|
|
102
102
|
* addresses, mount options, principals, and credential values are private.
|
|
103
103
|
*/
|
|
104
104
|
export interface IStorageCapabilityAdvertisement {
|
|
105
|
-
schemaVersion: 2;
|
|
106
105
|
featureIds: string[];
|
|
107
106
|
classes: TStorageClassCapabilities[];
|
|
108
107
|
}
|
|
@@ -131,7 +130,7 @@ export interface IObjectStorageFileDelivery {
|
|
|
131
130
|
* JSON object with endpoint, bucket, region, accessKeyId,
|
|
132
131
|
* secretAccessKey, and optional sessionToken fields.
|
|
133
132
|
*/
|
|
134
|
-
format: 'servezone-object-storage
|
|
133
|
+
format: 'servezone-object-storage';
|
|
135
134
|
uid: number;
|
|
136
135
|
gid: number;
|
|
137
136
|
mode: 0o400 | 0o440;
|
|
@@ -181,7 +180,6 @@ export interface IResolvedStorageCapacity {
|
|
|
181
180
|
}
|
|
182
181
|
|
|
183
182
|
export interface IResolvedStorageBindingBase {
|
|
184
|
-
schemaVersion: 2;
|
|
185
183
|
/** Stable binding/allocation identity. */
|
|
186
184
|
id: string;
|
|
187
185
|
serviceId: string;
|