@serve.zone/interfaces 32.1.0 → 32.3.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.
@@ -1,3 +1,4 @@
1
+ import type { TAppStoreStorageRequest } from '../appstore/types.js';
1
2
  import type { TSecretManagementScope } from '../data/secret.js';
2
3
  /**
3
4
  * Backend-neutral storage vocabulary shared by App Store manifests, control
@@ -142,8 +143,12 @@ export interface IResolvedStorageBindingBase {
142
143
  /** Stable App Store storageRequests[].id. */
143
144
  requestId: string;
144
145
  /**
145
- * Canonical digest of the normalized request. Adapters use it to distinguish
146
- * an idempotent reconciliation from a migration.
146
+ * Canonical digest of the normalized request, exactly as
147
+ * `createStorageRequestSha256` states it: bare lowercase hex over the request
148
+ * alone. Adapters use it to distinguish an idempotent reconciliation from a
149
+ * migration, and a consumer re-derives it to recognise the request it asked
150
+ * for, so the control plane and the consumer call that one function rather
151
+ * than each deriving a value the other cannot reproduce.
147
152
  */
148
153
  requestDigest: string;
149
154
  kind: TStorageResourceKind;
@@ -180,3 +185,44 @@ export interface IResolvedObjectStorageBinding extends IResolvedStorageBindingBa
180
185
  retentionDays?: number;
181
186
  }
182
187
  export type TResolvedStorageBinding = IResolvedFilesystemStorageBinding | IResolvedObjectStorageBinding;
188
+ export declare class StorageContractError extends Error {
189
+ constructor(reasonArg: string);
190
+ }
191
+ /**
192
+ * The one judgement of what a storage request is: the request read against its exact schema and
193
+ * returned in its contract shape — required members present, optional members carried only when
194
+ * the request declares them, and every value within the bounds an adapter can fulfil.
195
+ *
196
+ * A control plane that admits requests, a fulfillment adapter that provisions them, and an App
197
+ * Store front end that assembles them all have to agree on the same acceptance rules; a second
198
+ * copy of them anywhere is a second answer to which requests exist, and the digest one side issues
199
+ * would name a request the other never accepted. `canonicalizeStorageRequest` and
200
+ * `createStorageRequestSha256` are derived from this function rather than the other way round, so
201
+ * a consumer that needs the judged request itself does not have to re-parse its own canonical
202
+ * bytes.
203
+ *
204
+ * The returned request is a fresh value; the input is never read again after normalization and
205
+ * never mutated. Anything that is not a storage request is refused with `StorageContractError`,
206
+ * whose message names the failing field so a consumer can map it to its own status vocabulary.
207
+ */
208
+ export declare const normalizeStorageRequest: (valueArg: unknown) => TAppStoreStorageRequest;
209
+ /**
210
+ * The exact bytes a storage request's digest is taken over: the request normalized to its contract
211
+ * shape, written as strict canonical JSON — keys in UTF-16 code-unit order, no whitespace, no
212
+ * absent member carried as `null`.
213
+ *
214
+ * Anything that is not a storage request is refused rather than hashed, because a digest of an
215
+ * unjudged body would name a request no adapter could fulfil.
216
+ */
217
+ export declare const canonicalizeStorageRequest: (valueArg: unknown) => string;
218
+ /**
219
+ * The storage request digest: bare lowercase hex SHA-256 over the canonical request, with no
220
+ * prefix and nothing but the request in the hashed value.
221
+ *
222
+ * A control plane issues this value on a binding and a consumer re-derives it from the request it
223
+ * declared; the two only agree because they call this one function. A second derivation elsewhere
224
+ * — a prefix, or a wrapper object around the request — is a different value for the same binding,
225
+ * and the consumer would keep rejecting a binding that is in fact its own.
226
+ */
227
+ export declare const createStorageRequestSha256: (valueArg: unknown) => Promise<string>;
228
+ export { storageRequestCanonicalDigestGoldenVectors, type IStorageRequestCanonicalDigestGoldenVector, } from './storage.golden.js';
@@ -0,0 +1,31 @@
1
+ import type { IAppStoreFilesystemStorageRequest, IAppStoreObjectStorageRequest } from '../appstore/types.js';
2
+ export interface IStorageRequestCanonicalDigestGoldenVector<TInput> {
3
+ input: TInput;
4
+ canonicalJson: string;
5
+ sha256: string;
6
+ }
7
+ /**
8
+ * The storage request digest, written down for both kinds and both deliveries.
9
+ *
10
+ * A control plane and its consumer compare this value, so it is stated here as bytes rather than
11
+ * as whatever the current normalizer happens to produce: a member that starts or stops
12
+ * participating, an optional member that starts being carried when it is absent, or a delivery
13
+ * mode written as anything but its number would move these digests and be caught here.
14
+ */
15
+ export declare const storageRequestCanonicalDigestGoldenVectors: {
16
+ filesystem: {
17
+ input: IAppStoreFilesystemStorageRequest;
18
+ canonicalJson: string;
19
+ sha256: string;
20
+ };
21
+ objectStorageLauncherEnvironment: {
22
+ input: IAppStoreObjectStorageRequest;
23
+ canonicalJson: string;
24
+ sha256: string;
25
+ };
26
+ objectStorageFile: {
27
+ input: IAppStoreObjectStorageRequest;
28
+ canonicalJson: string;
29
+ sha256: string;
30
+ };
31
+ };
@@ -0,0 +1,84 @@
1
+ import { deepFreezeValue } from '../private/canonicaljson.js';
2
+ const filesystemRequest = {
3
+ id: 'app-state',
4
+ kind: 'filesystem',
5
+ storageClass: 'databaseFast',
6
+ accessMode: 'ReadWriteOnce',
7
+ reclaimPolicy: 'delete',
8
+ mountPath: '/var/lib/app',
9
+ capacity: {
10
+ request: '20GiB',
11
+ limit: '40GiB',
12
+ },
13
+ protection: {
14
+ backup: 'required',
15
+ snapshots: 'portable',
16
+ consistency: 'applicationConsistent',
17
+ },
18
+ };
19
+ const objectStorageLauncherEnvironmentRequest = {
20
+ id: 'object-data',
21
+ kind: 'objectStorage',
22
+ storageClass: 'backupCapacity',
23
+ accessMode: 'readWrite',
24
+ reclaimPolicy: 'retain',
25
+ delivery: {
26
+ type: 'launcher-environment',
27
+ keys: {
28
+ endpoint: 'APP_S3_ENDPOINT',
29
+ bucket: 'APP_S3_BUCKET',
30
+ region: 'APP_S3_REGION',
31
+ accessKeyId: 'APP_S3_ACCESS_KEY',
32
+ secretAccessKey: 'APP_S3_SECRET_KEY',
33
+ },
34
+ },
35
+ capacity: {
36
+ request: '500GiB',
37
+ },
38
+ protection: {
39
+ backup: 'required',
40
+ versioning: 'required',
41
+ retentionDays: 30,
42
+ },
43
+ };
44
+ const objectStorageFileRequest = {
45
+ id: 'object-archive',
46
+ kind: 'objectStorage',
47
+ storageClass: 'archiveCapacity',
48
+ accessMode: 'readOnly',
49
+ reclaimPolicy: 'delete',
50
+ delivery: {
51
+ type: 'file',
52
+ targetPath: '/run/secrets/object-archive.json',
53
+ format: 'servezone-object-storage',
54
+ uid: 10_000,
55
+ gid: 10_000,
56
+ mode: 0o400,
57
+ },
58
+ };
59
+ /**
60
+ * The storage request digest, written down for both kinds and both deliveries.
61
+ *
62
+ * A control plane and its consumer compare this value, so it is stated here as bytes rather than
63
+ * as whatever the current normalizer happens to produce: a member that starts or stops
64
+ * participating, an optional member that starts being carried when it is absent, or a delivery
65
+ * mode written as anything but its number would move these digests and be caught here.
66
+ */
67
+ export const storageRequestCanonicalDigestGoldenVectors = deepFreezeValue({
68
+ filesystem: {
69
+ input: filesystemRequest,
70
+ canonicalJson: '{"accessMode":"ReadWriteOnce","capacity":{"limit":"40GiB","request":"20GiB"},"id":"app-state","kind":"filesystem","mountPath":"/var/lib/app","protection":{"backup":"required","consistency":"applicationConsistent","snapshots":"portable"},"reclaimPolicy":"delete","storageClass":"databaseFast"}',
71
+ sha256: '0f8dc2e480ade7110be1f1d95f666cc56d5b90110d1c4a1b8b8383e2eeda6fae',
72
+ },
73
+ objectStorageLauncherEnvironment: {
74
+ input: objectStorageLauncherEnvironmentRequest,
75
+ canonicalJson: '{"accessMode":"readWrite","capacity":{"request":"500GiB"},"delivery":{"keys":{"accessKeyId":"APP_S3_ACCESS_KEY","bucket":"APP_S3_BUCKET","endpoint":"APP_S3_ENDPOINT","region":"APP_S3_REGION","secretAccessKey":"APP_S3_SECRET_KEY"},"type":"launcher-environment"},"id":"object-data","kind":"objectStorage","protection":{"backup":"required","retentionDays":30,"versioning":"required"},"reclaimPolicy":"retain","storageClass":"backupCapacity"}',
76
+ sha256: '88bd943800bea75b576612b966484e66ddbf7ec9529a7f29dd1eadc9a36e2b79',
77
+ },
78
+ objectStorageFile: {
79
+ input: objectStorageFileRequest,
80
+ canonicalJson: '{"accessMode":"readOnly","delivery":{"format":"servezone-object-storage","gid":10000,"mode":256,"targetPath":"/run/secrets/object-archive.json","type":"file","uid":10000},"id":"object-archive","kind":"objectStorage","reclaimPolicy":"delete","storageClass":"archiveCapacity"}',
81
+ sha256: '13186abbfe733b3917363e0694d367f2d6aa87a4064445532d7c857ee274f795',
82
+ },
83
+ });
84
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmFnZS5nb2xkZW4uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9wbGF0Zm9ybS9zdG9yYWdlLmdvbGRlbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFJQSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFROUQsTUFBTSxpQkFBaUIsR0FBc0M7SUFDM0QsRUFBRSxFQUFFLFdBQVc7SUFDZixJQUFJLEVBQUUsWUFBWTtJQUNsQixZQUFZLEVBQUUsY0FBYztJQUM1QixVQUFVLEVBQUUsZUFBZTtJQUMzQixhQUFhLEVBQUUsUUFBUTtJQUN2QixTQUFTLEVBQUUsY0FBYztJQUN6QixRQUFRLEVBQUU7UUFDUixPQUFPLEVBQUUsT0FBTztRQUNoQixLQUFLLEVBQUUsT0FBTztLQUNmO0lBQ0QsVUFBVSxFQUFFO1FBQ1YsTUFBTSxFQUFFLFVBQVU7UUFDbEIsU0FBUyxFQUFFLFVBQVU7UUFDckIsV0FBVyxFQUFFLHVCQUF1QjtLQUNyQztDQUNGLENBQUM7QUFFRixNQUFNLHVDQUF1QyxHQUFrQztJQUM3RSxFQUFFLEVBQUUsYUFBYTtJQUNqQixJQUFJLEVBQUUsZUFBZTtJQUNyQixZQUFZLEVBQUUsZ0JBQWdCO0lBQzlCLFVBQVUsRUFBRSxXQUFXO0lBQ3ZCLGFBQWEsRUFBRSxRQUFRO0lBQ3ZCLFFBQVEsRUFBRTtRQUNSLElBQUksRUFBRSxzQkFBc0I7UUFDNUIsSUFBSSxFQUFFO1lBQ0osUUFBUSxFQUFFLGlCQUFpQjtZQUMzQixNQUFNLEVBQUUsZUFBZTtZQUN2QixNQUFNLEVBQUUsZUFBZTtZQUN2QixXQUFXLEVBQUUsbUJBQW1CO1lBQ2hDLGVBQWUsRUFBRSxtQkFBbUI7U0FDckM7S0FDRjtJQUNELFFBQVEsRUFBRTtRQUNSLE9BQU8sRUFBRSxRQUFRO0tBQ2xCO0lBQ0QsVUFBVSxFQUFFO1FBQ1YsTUFBTSxFQUFFLFVBQVU7UUFDbEIsVUFBVSxFQUFFLFVBQVU7UUFDdEIsYUFBYSxFQUFFLEVBQUU7S0FDbEI7Q0FDRixDQUFDO0FBRUYsTUFBTSx3QkFBd0IsR0FBa0M7SUFDOUQsRUFBRSxFQUFFLGdCQUFnQjtJQUNwQixJQUFJLEVBQUUsZUFBZTtJQUNyQixZQUFZLEVBQUUsaUJBQWlCO0lBQy9CLFVBQVUsRUFBRSxVQUFVO0lBQ3RCLGFBQWEsRUFBRSxRQUFRO0lBQ3ZCLFFBQVEsRUFBRTtRQUNSLElBQUksRUFBRSxNQUFNO1FBQ1osVUFBVSxFQUFFLGtDQUFrQztRQUM5QyxNQUFNLEVBQUUsMEJBQTBCO1FBQ2xDLEdBQUcsRUFBRSxNQUFNO1FBQ1gsR0FBRyxFQUFFLE1BQU07UUFDWCxJQUFJLEVBQUUsS0FBSztLQUNaO0NBQ0YsQ0FBQztBQUVGOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLENBQUMsTUFBTSwwQ0FBMEMsR0FBRyxlQUFlLENBQUM7SUFDeEUsVUFBVSxFQUFFO1FBQ1YsS0FBSyxFQUFFLGlCQUFpQjtRQUN4QixhQUFhLEVBQUUsc1NBQXNTO1FBQ3JULE1BQU0sRUFBRSxrRUFBa0U7S0FDM0U7SUFDRCxnQ0FBZ0MsRUFBRTtRQUNoQyxLQUFLLEVBQUUsdUNBQXVDO1FBQzlDLGFBQWEsRUFBRSx3YkFBd2I7UUFDdmMsTUFBTSxFQUFFLGtFQUFrRTtLQUMzRTtJQUNELGlCQUFpQixFQUFFO1FBQ2pCLEtBQUssRUFBRSx3QkFBd0I7UUFDL0IsYUFBYSxFQUFFLG9SQUFvUjtRQUNuUyxNQUFNLEVBQUUsa0VBQWtFO0tBQzNFO0NBQ0YsQ0FBQyxDQUFDIn0=
@@ -1,3 +1,5 @@
1
+ import { canonicalizeStrictJson, createCanonicalJsonSha256Hex, } from '../private/canonicaljson.js';
2
+ import { createStorageContractReaders } from '../private/storagecontract.js';
1
3
  /**
2
4
  * Compatibility bridge for the existing generic platform capability. New
3
5
  * filesystem requests intentionally have no generic IPlatformBinding mapping.
@@ -5,4 +7,58 @@
5
7
  export const storageKindPlatformCapabilityIds = {
6
8
  objectStorage: 'objectstorage',
7
9
  };
8
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmFnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3BsYXRmb3JtL3N0b3JhZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBUUE7OztHQUdHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sZ0NBQWdDLEdBQUc7SUFDOUMsYUFBYSxFQUFFLGVBQWU7Q0FDdEIsQ0FBQyJ9
10
+ export class StorageContractError extends Error {
11
+ constructor(reasonArg) {
12
+ super(`storage contract error: ${reasonArg}`);
13
+ this.name = 'StorageContractError';
14
+ }
15
+ }
16
+ const fail = (reasonArg) => {
17
+ throw new StorageContractError(reasonArg);
18
+ };
19
+ const { normalizeStorageRequest: readStorageRequest } = createStorageContractReaders(fail);
20
+ /**
21
+ * The one judgement of what a storage request is: the request read against its exact schema and
22
+ * returned in its contract shape — required members present, optional members carried only when
23
+ * the request declares them, and every value within the bounds an adapter can fulfil.
24
+ *
25
+ * A control plane that admits requests, a fulfillment adapter that provisions them, and an App
26
+ * Store front end that assembles them all have to agree on the same acceptance rules; a second
27
+ * copy of them anywhere is a second answer to which requests exist, and the digest one side issues
28
+ * would name a request the other never accepted. `canonicalizeStorageRequest` and
29
+ * `createStorageRequestSha256` are derived from this function rather than the other way round, so
30
+ * a consumer that needs the judged request itself does not have to re-parse its own canonical
31
+ * bytes.
32
+ *
33
+ * The returned request is a fresh value; the input is never read again after normalization and
34
+ * never mutated. Anything that is not a storage request is refused with `StorageContractError`,
35
+ * whose message names the failing field so a consumer can map it to its own status vocabulary.
36
+ */
37
+ export const normalizeStorageRequest = (valueArg) => {
38
+ return readStorageRequest(valueArg, 'storage request');
39
+ };
40
+ /**
41
+ * The exact bytes a storage request's digest is taken over: the request normalized to its contract
42
+ * shape, written as strict canonical JSON — keys in UTF-16 code-unit order, no whitespace, no
43
+ * absent member carried as `null`.
44
+ *
45
+ * Anything that is not a storage request is refused rather than hashed, because a digest of an
46
+ * unjudged body would name a request no adapter could fulfil.
47
+ */
48
+ export const canonicalizeStorageRequest = (valueArg) => {
49
+ return canonicalizeStrictJson(normalizeStorageRequest(valueArg), fail);
50
+ };
51
+ /**
52
+ * The storage request digest: bare lowercase hex SHA-256 over the canonical request, with no
53
+ * prefix and nothing but the request in the hashed value.
54
+ *
55
+ * A control plane issues this value on a binding and a consumer re-derives it from the request it
56
+ * declared; the two only agree because they call this one function. A second derivation elsewhere
57
+ * — a prefix, or a wrapper object around the request — is a different value for the same binding,
58
+ * and the consumer would keep rejecting a binding that is in fact its own.
59
+ */
60
+ export const createStorageRequestSha256 = async (valueArg) => {
61
+ return createCanonicalJsonSha256Hex(canonicalizeStorageRequest(valueArg), fail);
62
+ };
63
+ export { storageRequestCanonicalDigestGoldenVectors, } from './storage.golden.js';
64
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmFnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3BsYXRmb3JtL3N0b3JhZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUNMLHNCQUFzQixFQUN0Qiw0QkFBNEIsR0FDN0IsTUFBTSw2QkFBNkIsQ0FBQztBQUNyQyxPQUFPLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQVE3RTs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxnQ0FBZ0MsR0FBRztJQUM5QyxhQUFhLEVBQUUsZUFBZTtDQUN0QixDQUFDO0FBaU9YLE1BQU0sT0FBTyxvQkFBcUIsU0FBUSxLQUFLO0lBQzdDLFlBQW1CLFNBQWlCO1FBQ2xDLEtBQUssQ0FBQywyQkFBMkIsU0FBUyxFQUFFLENBQUMsQ0FBQztRQUM5QyxJQUFJLENBQUMsSUFBSSxHQUFHLHNCQUFzQixDQUFDO0lBQ3JDLENBQUM7Q0FDRjtBQUVELE1BQU0sSUFBSSxHQUFHLENBQUMsU0FBaUIsRUFBUyxFQUFFO0lBQ3hDLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUM1QyxDQUFDLENBQUM7QUFFRixNQUFNLEVBQUUsdUJBQXVCLEVBQUUsa0JBQWtCLEVBQUUsR0FDbkQsNEJBQTRCLENBQUMsSUFBSSxDQUFDLENBQUM7QUFFckM7Ozs7Ozs7Ozs7Ozs7Ozs7R0FnQkc7QUFDSCxNQUFNLENBQUMsTUFBTSx1QkFBdUIsR0FBRyxDQUNyQyxRQUFpQixFQUNRLEVBQUU7SUFDM0IsT0FBTyxrQkFBa0IsQ0FBQyxRQUFRLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztBQUN6RCxDQUFDLENBQUM7QUFFRjs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sMEJBQTBCLEdBQUcsQ0FBQyxRQUFpQixFQUFVLEVBQUU7SUFDdEUsT0FBTyxzQkFBc0IsQ0FBQyx1QkFBdUIsQ0FBQyxRQUFRLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUN6RSxDQUFDLENBQUM7QUFFRjs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sQ0FBQyxNQUFNLDBCQUEwQixHQUFHLEtBQUssRUFDN0MsUUFBaUIsRUFDQSxFQUFFO0lBQ25CLE9BQU8sNEJBQTRCLENBQUMsMEJBQTBCLENBQUMsUUFBUSxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDbEYsQ0FBQyxDQUFDO0FBRUYsT0FBTyxFQUNMLDBDQUEwQyxHQUUzQyxNQUFNLHFCQUFxQixDQUFDIn0=