@serve.zone/interfaces 32.1.0 → 32.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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-09-18 - 32.2.0
4
+
5
+ ### Features
6
+
7
+ - Add `platform.createStorageRequestSha256(value)` and `platform.canonicalizeStorageRequest(value)`,
8
+ the contract's own statement of `IResolvedStorageBindingBase.requestDigest`: the request alone,
9
+ normalized to its contract shape, written as strict canonical JSON and hashed to bare lowercase
10
+ hex. A control plane issues that value on a binding and its consumer re-derives it, so both now
11
+ call one function instead of each deriving a value the other cannot reproduce — a prefix or a
12
+ wrapper object around the request is a different value for the same binding. Anything that is not
13
+ a storage request is refused with the new `platform.StorageContractError` rather than hashed.
14
+ `platform.storageRequestCanonicalDigestGoldenVectors` writes the digest down for a filesystem
15
+ request and for both object-storage deliveries.
16
+ - The storage request normalizer is now one implementation for both kinds, shared by
17
+ `platform.storage` and `platform.storagemigration`, so
18
+ `createObjectStorageMigrationTargetRequestSha256` and `createStorageRequestSha256` cannot drift.
19
+ Migration contract errors keep their own class and messages.
20
+
3
21
  ## 2026-09-18 - 32.1.0
4
22
 
5
23
  ### Features
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '32.1.0',
6
+ version: '32.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=
@@ -142,8 +142,12 @@ export interface IResolvedStorageBindingBase {
142
142
  /** Stable App Store storageRequests[].id. */
143
143
  requestId: string;
144
144
  /**
145
- * Canonical digest of the normalized request. Adapters use it to distinguish
146
- * an idempotent reconciliation from a migration.
145
+ * Canonical digest of the normalized request, exactly as
146
+ * `createStorageRequestSha256` states it: bare lowercase hex over the request
147
+ * alone. Adapters use it to distinguish an idempotent reconciliation from a
148
+ * migration, and a consumer re-derives it to recognise the request it asked
149
+ * for, so the control plane and the consumer call that one function rather
150
+ * than each deriving a value the other cannot reproduce.
147
151
  */
148
152
  requestDigest: string;
149
153
  kind: TStorageResourceKind;
@@ -180,3 +184,26 @@ export interface IResolvedObjectStorageBinding extends IResolvedStorageBindingBa
180
184
  retentionDays?: number;
181
185
  }
182
186
  export type TResolvedStorageBinding = IResolvedFilesystemStorageBinding | IResolvedObjectStorageBinding;
187
+ export declare class StorageContractError extends Error {
188
+ constructor(reasonArg: string);
189
+ }
190
+ /**
191
+ * The exact bytes a storage request's digest is taken over: the request normalized to its contract
192
+ * shape, written as strict canonical JSON — keys in UTF-16 code-unit order, no whitespace, no
193
+ * absent member carried as `null`.
194
+ *
195
+ * Anything that is not a storage request is refused rather than hashed, because a digest of an
196
+ * unjudged body would name a request no adapter could fulfil.
197
+ */
198
+ export declare const canonicalizeStorageRequest: (valueArg: unknown) => string;
199
+ /**
200
+ * The storage request digest: bare lowercase hex SHA-256 over the canonical request, with no
201
+ * prefix and nothing but the request in the hashed value.
202
+ *
203
+ * A control plane issues this value on a binding and a consumer re-derives it from the request it
204
+ * declared; the two only agree because they call this one function. A second derivation elsewhere
205
+ * — a prefix, or a wrapper object around the request — is a different value for the same binding,
206
+ * and the consumer would keep rejecting a binding that is in fact its own.
207
+ */
208
+ export declare const createStorageRequestSha256: (valueArg: unknown) => Promise<string>;
209
+ 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,38 @@
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 } = createStorageContractReaders(fail);
20
+ /**
21
+ * The exact bytes a storage request's digest is taken over: the request normalized to its contract
22
+ * shape, written as strict canonical JSON — keys in UTF-16 code-unit order, no whitespace, no
23
+ * absent member carried as `null`.
24
+ *
25
+ * Anything that is not a storage request is refused rather than hashed, because a digest of an
26
+ * unjudged body would name a request no adapter could fulfil.
27
+ */
28
+ export const canonicalizeStorageRequest = (valueArg) => {
29
+ return canonicalizeStrictJson(normalizeStorageRequest(valueArg, 'storage request'), fail);
30
+ };
31
+ /**
32
+ * The storage request digest: bare lowercase hex SHA-256 over the canonical request, with no
33
+ * prefix and nothing but the request in the hashed value.
34
+ *
35
+ * A control plane issues this value on a binding and a consumer re-derives it from the request it
36
+ * declared; the two only agree because they call this one function. A second derivation elsewhere
37
+ * — a prefix, or a wrapper object around the request — is a different value for the same binding,
38
+ * and the consumer would keep rejecting a binding that is in fact its own.
39
+ */
40
+ export const createStorageRequestSha256 = async (valueArg) => {
41
+ return createCanonicalJsonSha256Hex(canonicalizeStorageRequest(valueArg), fail);
42
+ };
43
+ export { storageRequestCanonicalDigestGoldenVectors, } from './storage.golden.js';
44
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmFnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3BsYXRmb3JtL3N0b3JhZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUNMLHNCQUFzQixFQUN0Qiw0QkFBNEIsR0FDN0IsTUFBTSw2QkFBNkIsQ0FBQztBQUNyQyxPQUFPLEVBQUUsNEJBQTRCLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQVE3RTs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxnQ0FBZ0MsR0FBRztJQUM5QyxhQUFhLEVBQUUsZUFBZTtDQUN0QixDQUFDO0FBaU9YLE1BQU0sT0FBTyxvQkFBcUIsU0FBUSxLQUFLO0lBQzdDLFlBQW1CLFNBQWlCO1FBQ2xDLEtBQUssQ0FBQywyQkFBMkIsU0FBUyxFQUFFLENBQUMsQ0FBQztRQUM5QyxJQUFJLENBQUMsSUFBSSxHQUFHLHNCQUFzQixDQUFDO0lBQ3JDLENBQUM7Q0FDRjtBQUVELE1BQU0sSUFBSSxHQUFHLENBQUMsU0FBaUIsRUFBUyxFQUFFO0lBQ3hDLE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUM1QyxDQUFDLENBQUM7QUFFRixNQUFNLEVBQUUsdUJBQXVCLEVBQUUsR0FBRyw0QkFBNEIsQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUV2RTs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sMEJBQTBCLEdBQUcsQ0FBQyxRQUFpQixFQUFVLEVBQUU7SUFDdEUsT0FBTyxzQkFBc0IsQ0FDM0IsdUJBQXVCLENBQUMsUUFBUSxFQUFFLGlCQUFpQixDQUFDLEVBQ3BELElBQUksQ0FDTCxDQUFDO0FBQ0osQ0FBQyxDQUFDO0FBRUY7Ozs7Ozs7O0dBUUc7QUFDSCxNQUFNLENBQUMsTUFBTSwwQkFBMEIsR0FBRyxLQUFLLEVBQzdDLFFBQWlCLEVBQ0EsRUFBRTtJQUNuQixPQUFPLDRCQUE0QixDQUFDLDBCQUEwQixDQUFDLFFBQVEsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBQ2xGLENBQUMsQ0FBQztBQUVGLE9BQU8sRUFDTCwwQ0FBMEMsR0FFM0MsTUFBTSxxQkFBcUIsQ0FBQyJ9