was-teaching-server 0.9.1

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.
Files changed (295) hide show
  1. package/CHANGELOG.md +1432 -0
  2. package/LICENSE +661 -0
  3. package/README.md +305 -0
  4. package/common/css/materialize.1.0.0.min.css +13 -0
  5. package/dist/auth-header-hooks.d.ts +84 -0
  6. package/dist/auth-header-hooks.d.ts.map +1 -0
  7. package/dist/auth-header-hooks.js +136 -0
  8. package/dist/auth-header-hooks.js.map +1 -0
  9. package/dist/authorize.d.ts +45 -0
  10. package/dist/authorize.d.ts.map +1 -0
  11. package/dist/authorize.js +76 -0
  12. package/dist/authorize.js.map +1 -0
  13. package/dist/backends/filesystem.d.ts +1179 -0
  14. package/dist/backends/filesystem.d.ts.map +1 -0
  15. package/dist/backends/filesystem.js +2727 -0
  16. package/dist/backends/filesystem.js.map +1 -0
  17. package/dist/backends/postgres.d.ts +833 -0
  18. package/dist/backends/postgres.d.ts.map +1 -0
  19. package/dist/backends/postgres.js +2164 -0
  20. package/dist/backends/postgres.js.map +1 -0
  21. package/dist/backends/postgresSchema.d.ts +36 -0
  22. package/dist/backends/postgresSchema.d.ts.map +1 -0
  23. package/dist/backends/postgresSchema.js +203 -0
  24. package/dist/backends/postgresSchema.js.map +1 -0
  25. package/dist/config.default.d.ts +360 -0
  26. package/dist/config.default.d.ts.map +1 -0
  27. package/dist/config.default.js +493 -0
  28. package/dist/config.default.js.map +1 -0
  29. package/dist/corsProxy.d.ts +45 -0
  30. package/dist/corsProxy.d.ts.map +1 -0
  31. package/dist/corsProxy.js +278 -0
  32. package/dist/corsProxy.js.map +1 -0
  33. package/dist/digest.d.ts +45 -0
  34. package/dist/digest.d.ts.map +1 -0
  35. package/dist/digest.js +174 -0
  36. package/dist/digest.js.map +1 -0
  37. package/dist/errors.d.ts +654 -0
  38. package/dist/errors.d.ts.map +1 -0
  39. package/dist/errors.js +960 -0
  40. package/dist/errors.js.map +1 -0
  41. package/dist/index.d.ts +22 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +21 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/lib/atomicFile.d.ts +61 -0
  46. package/dist/lib/atomicFile.d.ts.map +1 -0
  47. package/dist/lib/atomicFile.js +150 -0
  48. package/dist/lib/atomicFile.js.map +1 -0
  49. package/dist/lib/backendRegistry.d.ts +59 -0
  50. package/dist/lib/backendRegistry.d.ts.map +1 -0
  51. package/dist/lib/backendRegistry.js +92 -0
  52. package/dist/lib/backendRegistry.js.map +1 -0
  53. package/dist/lib/backendUsage.d.ts +28 -0
  54. package/dist/lib/backendUsage.d.ts.map +1 -0
  55. package/dist/lib/backendUsage.js +55 -0
  56. package/dist/lib/backendUsage.js.map +1 -0
  57. package/dist/lib/backends.d.ts +142 -0
  58. package/dist/lib/backends.d.ts.map +1 -0
  59. package/dist/lib/backends.js +272 -0
  60. package/dist/lib/backends.js.map +1 -0
  61. package/dist/lib/blindedIndex.d.ts +156 -0
  62. package/dist/lib/blindedIndex.d.ts.map +1 -0
  63. package/dist/lib/blindedIndex.js +338 -0
  64. package/dist/lib/blindedIndex.js.map +1 -0
  65. package/dist/lib/collectionDescription.d.ts +33 -0
  66. package/dist/lib/collectionDescription.d.ts.map +1 -0
  67. package/dist/lib/collectionDescription.js +21 -0
  68. package/dist/lib/collectionDescription.js.map +1 -0
  69. package/dist/lib/cursor.d.ts +18 -0
  70. package/dist/lib/cursor.d.ts.map +1 -0
  71. package/dist/lib/cursor.js +50 -0
  72. package/dist/lib/cursor.js.map +1 -0
  73. package/dist/lib/edvEnvelope.d.ts +50 -0
  74. package/dist/lib/edvEnvelope.d.ts.map +1 -0
  75. package/dist/lib/edvEnvelope.js +106 -0
  76. package/dist/lib/edvEnvelope.js.map +1 -0
  77. package/dist/lib/encryption.d.ts +192 -0
  78. package/dist/lib/encryption.d.ts.map +1 -0
  79. package/dist/lib/encryption.js +383 -0
  80. package/dist/lib/encryption.js.map +1 -0
  81. package/dist/lib/etag.d.ts +33 -0
  82. package/dist/lib/etag.d.ts.map +1 -0
  83. package/dist/lib/etag.js +36 -0
  84. package/dist/lib/etag.js.map +1 -0
  85. package/dist/lib/exportManifest.d.ts +29 -0
  86. package/dist/lib/exportManifest.d.ts.map +1 -0
  87. package/dist/lib/exportManifest.js +77 -0
  88. package/dist/lib/exportManifest.js.map +1 -0
  89. package/dist/lib/importTar.d.ts +81 -0
  90. package/dist/lib/importTar.d.ts.map +1 -0
  91. package/dist/lib/importTar.js +301 -0
  92. package/dist/lib/importTar.js.map +1 -0
  93. package/dist/lib/isJson.d.ts +16 -0
  94. package/dist/lib/isJson.d.ts.map +1 -0
  95. package/dist/lib/isJson.js +17 -0
  96. package/dist/lib/isJson.js.map +1 -0
  97. package/dist/lib/keyEpoch.d.ts +38 -0
  98. package/dist/lib/keyEpoch.d.ts.map +1 -0
  99. package/dist/lib/keyEpoch.js +75 -0
  100. package/dist/lib/keyEpoch.js.map +1 -0
  101. package/dist/lib/keyedMutex.d.ts +28 -0
  102. package/dist/lib/keyedMutex.d.ts.map +1 -0
  103. package/dist/lib/keyedMutex.js +45 -0
  104. package/dist/lib/keyedMutex.js.map +1 -0
  105. package/dist/lib/kmsModule.d.ts +62 -0
  106. package/dist/lib/kmsModule.d.ts.map +1 -0
  107. package/dist/lib/kmsModule.js +327 -0
  108. package/dist/lib/kmsModule.js.map +1 -0
  109. package/dist/lib/kmsRecordCipher.d.ts +82 -0
  110. package/dist/lib/kmsRecordCipher.d.ts.map +1 -0
  111. package/dist/lib/kmsRecordCipher.js +226 -0
  112. package/dist/lib/kmsRecordCipher.js.map +1 -0
  113. package/dist/lib/pagination.d.ts +19 -0
  114. package/dist/lib/pagination.d.ts.map +1 -0
  115. package/dist/lib/pagination.js +21 -0
  116. package/dist/lib/pagination.js.map +1 -0
  117. package/dist/lib/paths.d.ts +273 -0
  118. package/dist/lib/paths.d.ts.map +1 -0
  119. package/dist/lib/paths.js +270 -0
  120. package/dist/lib/paths.js.map +1 -0
  121. package/dist/lib/preconditions.d.ts +65 -0
  122. package/dist/lib/preconditions.d.ts.map +1 -0
  123. package/dist/lib/preconditions.js +112 -0
  124. package/dist/lib/preconditions.js.map +1 -0
  125. package/dist/lib/resourceFileName.d.ts +41 -0
  126. package/dist/lib/resourceFileName.d.ts.map +1 -0
  127. package/dist/lib/resourceFileName.js +58 -0
  128. package/dist/lib/resourceFileName.js.map +1 -0
  129. package/dist/lib/revocations.d.ts +49 -0
  130. package/dist/lib/revocations.d.ts.map +1 -0
  131. package/dist/lib/revocations.js +86 -0
  132. package/dist/lib/revocations.js.map +1 -0
  133. package/dist/lib/validateDid.d.ts +19 -0
  134. package/dist/lib/validateDid.d.ts.map +1 -0
  135. package/dist/lib/validateDid.js +35 -0
  136. package/dist/lib/validateDid.js.map +1 -0
  137. package/dist/lib/validateId.d.ts +66 -0
  138. package/dist/lib/validateId.d.ts.map +1 -0
  139. package/dist/lib/validateId.js +126 -0
  140. package/dist/lib/validateId.js.map +1 -0
  141. package/dist/plugin.d.ts +115 -0
  142. package/dist/plugin.d.ts.map +1 -0
  143. package/dist/plugin.js +134 -0
  144. package/dist/plugin.js.map +1 -0
  145. package/dist/policy.d.ts +78 -0
  146. package/dist/policy.d.ts.map +1 -0
  147. package/dist/policy.js +115 -0
  148. package/dist/policy.js.map +1 -0
  149. package/dist/provisioning.d.ts +38 -0
  150. package/dist/provisioning.d.ts.map +1 -0
  151. package/dist/provisioning.js +72 -0
  152. package/dist/provisioning.js.map +1 -0
  153. package/dist/requests/BackendRequest.d.ts +61 -0
  154. package/dist/requests/BackendRequest.d.ts.map +1 -0
  155. package/dist/requests/BackendRequest.js +152 -0
  156. package/dist/requests/BackendRequest.js.map +1 -0
  157. package/dist/requests/CollectionRequest.d.ts +234 -0
  158. package/dist/requests/CollectionRequest.d.ts.map +1 -0
  159. package/dist/requests/CollectionRequest.js +705 -0
  160. package/dist/requests/CollectionRequest.js.map +1 -0
  161. package/dist/requests/KeyRequest.d.ts +115 -0
  162. package/dist/requests/KeyRequest.d.ts.map +1 -0
  163. package/dist/requests/KeyRequest.js +468 -0
  164. package/dist/requests/KeyRequest.js.map +1 -0
  165. package/dist/requests/KeystoreRequest.d.ts +99 -0
  166. package/dist/requests/KeystoreRequest.d.ts.map +1 -0
  167. package/dist/requests/KeystoreRequest.js +247 -0
  168. package/dist/requests/KeystoreRequest.js.map +1 -0
  169. package/dist/requests/PolicyRequest.d.ts +53 -0
  170. package/dist/requests/PolicyRequest.d.ts.map +1 -0
  171. package/dist/requests/PolicyRequest.js +124 -0
  172. package/dist/requests/PolicyRequest.js.map +1 -0
  173. package/dist/requests/ResourceRequest.d.ts +134 -0
  174. package/dist/requests/ResourceRequest.d.ts.map +1 -0
  175. package/dist/requests/ResourceRequest.js +548 -0
  176. package/dist/requests/ResourceRequest.js.map +1 -0
  177. package/dist/requests/RevocationRequest.d.ts +67 -0
  178. package/dist/requests/RevocationRequest.d.ts.map +1 -0
  179. package/dist/requests/RevocationRequest.js +187 -0
  180. package/dist/requests/RevocationRequest.js.map +1 -0
  181. package/dist/requests/SpaceRequest.d.ts +204 -0
  182. package/dist/requests/SpaceRequest.d.ts.map +1 -0
  183. package/dist/requests/SpaceRequest.js +520 -0
  184. package/dist/requests/SpaceRequest.js.map +1 -0
  185. package/dist/requests/SpacesRepositoryRequest.d.ts +50 -0
  186. package/dist/requests/SpacesRepositoryRequest.d.ts.map +1 -0
  187. package/dist/requests/SpacesRepositoryRequest.js +159 -0
  188. package/dist/requests/SpacesRepositoryRequest.js.map +1 -0
  189. package/dist/requests/collectionContext.d.ts +21 -0
  190. package/dist/requests/collectionContext.d.ts.map +1 -0
  191. package/dist/requests/collectionContext.js +30 -0
  192. package/dist/requests/collectionContext.js.map +1 -0
  193. package/dist/requests/controllerConsent.d.ts +58 -0
  194. package/dist/requests/controllerConsent.d.ts.map +1 -0
  195. package/dist/requests/controllerConsent.js +73 -0
  196. package/dist/requests/controllerConsent.js.map +1 -0
  197. package/dist/requests/keystoreContext.d.ts +68 -0
  198. package/dist/requests/keystoreContext.d.ts.map +1 -0
  199. package/dist/requests/keystoreContext.js +87 -0
  200. package/dist/requests/keystoreContext.js.map +1 -0
  201. package/dist/requests/resourceInput.d.ts +19 -0
  202. package/dist/requests/resourceInput.d.ts.map +1 -0
  203. package/dist/requests/resourceInput.js +102 -0
  204. package/dist/requests/resourceInput.js.map +1 -0
  205. package/dist/requests/spaceContext.d.ts +127 -0
  206. package/dist/requests/spaceContext.d.ts.map +1 -0
  207. package/dist/requests/spaceContext.js +201 -0
  208. package/dist/requests/spaceContext.js.map +1 -0
  209. package/dist/routes.d.ts +55 -0
  210. package/dist/routes.d.ts.map +1 -0
  211. package/dist/routes.js +306 -0
  212. package/dist/routes.js.map +1 -0
  213. package/dist/server.d.ts +20 -0
  214. package/dist/server.d.ts.map +1 -0
  215. package/dist/server.js +60 -0
  216. package/dist/server.js.map +1 -0
  217. package/dist/start.d.ts +7 -0
  218. package/dist/start.d.ts.map +1 -0
  219. package/dist/start.js +61 -0
  220. package/dist/start.js.map +1 -0
  221. package/dist/storage.d.ts +29 -0
  222. package/dist/storage.d.ts.map +1 -0
  223. package/dist/storage.js +47 -0
  224. package/dist/storage.js.map +1 -0
  225. package/dist/types.d.ts +915 -0
  226. package/dist/types.d.ts.map +1 -0
  227. package/dist/types.js +2 -0
  228. package/dist/types.js.map +1 -0
  229. package/dist/views/home.hbs +9 -0
  230. package/dist/views/templates/main.hbs +12 -0
  231. package/dist/zcap.d.ts +267 -0
  232. package/dist/zcap.d.ts.map +1 -0
  233. package/dist/zcap.js +478 -0
  234. package/dist/zcap.js.map +1 -0
  235. package/package.json +109 -0
  236. package/src/auth-header-hooks.ts +163 -0
  237. package/src/authorize.ts +117 -0
  238. package/src/backends/filesystem.ts +3626 -0
  239. package/src/backends/postgres.ts +3073 -0
  240. package/src/backends/postgresSchema.ts +231 -0
  241. package/src/config.default.ts +657 -0
  242. package/src/corsProxy.ts +326 -0
  243. package/src/declarations.d.ts +28 -0
  244. package/src/digest.ts +223 -0
  245. package/src/errors.ts +1122 -0
  246. package/src/index.ts +21 -0
  247. package/src/lib/atomicFile.ts +176 -0
  248. package/src/lib/backendRegistry.ts +139 -0
  249. package/src/lib/backendUsage.ts +79 -0
  250. package/src/lib/backends.ts +351 -0
  251. package/src/lib/blindedIndex.ts +466 -0
  252. package/src/lib/collectionDescription.ts +43 -0
  253. package/src/lib/cursor.ts +52 -0
  254. package/src/lib/edvEnvelope.ts +120 -0
  255. package/src/lib/encryption.ts +472 -0
  256. package/src/lib/etag.ts +40 -0
  257. package/src/lib/exportManifest.ts +115 -0
  258. package/src/lib/importTar.ts +381 -0
  259. package/src/lib/isJson.ts +18 -0
  260. package/src/lib/keyEpoch.ts +89 -0
  261. package/src/lib/keyedMutex.ts +45 -0
  262. package/src/lib/kmsModule.ts +451 -0
  263. package/src/lib/kmsRecordCipher.ts +292 -0
  264. package/src/lib/pagination.ts +22 -0
  265. package/src/lib/paths.ts +382 -0
  266. package/src/lib/preconditions.ts +145 -0
  267. package/src/lib/resourceFileName.ts +69 -0
  268. package/src/lib/revocations.ts +116 -0
  269. package/src/lib/validateDid.ts +41 -0
  270. package/src/lib/validateId.ts +148 -0
  271. package/src/plugin.ts +288 -0
  272. package/src/policy.ts +176 -0
  273. package/src/provisioning.ts +98 -0
  274. package/src/requests/BackendRequest.ts +219 -0
  275. package/src/requests/CollectionRequest.ts +899 -0
  276. package/src/requests/KeyRequest.ts +617 -0
  277. package/src/requests/KeystoreRequest.ts +325 -0
  278. package/src/requests/PolicyRequest.ts +169 -0
  279. package/src/requests/ResourceRequest.ts +649 -0
  280. package/src/requests/RevocationRequest.ts +267 -0
  281. package/src/requests/SpaceRequest.ts +686 -0
  282. package/src/requests/SpacesRepositoryRequest.ts +194 -0
  283. package/src/requests/collectionContext.ts +41 -0
  284. package/src/requests/controllerConsent.ts +108 -0
  285. package/src/requests/keystoreContext.ts +124 -0
  286. package/src/requests/resourceInput.ts +118 -0
  287. package/src/requests/spaceContext.ts +311 -0
  288. package/src/routes.ts +420 -0
  289. package/src/server.ts +69 -0
  290. package/src/start.ts +71 -0
  291. package/src/storage.ts +60 -0
  292. package/src/types.ts +1016 -0
  293. package/src/views/home.hbs +9 -0
  294. package/src/views/templates/main.hbs +12 -0
  295. package/src/zcap.ts +657 -0
@@ -0,0 +1,915 @@
1
+ import type { FastifyBaseLogger, FastifyRequest } from 'fastify';
2
+ import type { Readable } from 'node:stream';
3
+ import type { IDID, IVerificationMethod, IMultikeyMethod, IZcap, IRootZcap, IDelegatedZcap, IVerifier, IPublicMultikey, IMultikeyDocument, IVerificationKeyPair2020 } from '@interop/data-integrity-core';
4
+ import type { IRemoteDocument, IDocumentLoader } from '@interop/data-integrity-core/loader';
5
+ import type { BlindedIndexQuery, BlindedIndexQueryPage } from './lib/blindedIndex.js';
6
+ import type { SpaceDescription, CollectionDescription, CollectionSummary, CollectionResourcesList, ResourceMetadata, ResourceMetadataCustom, BackendDescriptor, BackendConnectionInput, BackendUsage, ImportStats, PolicyDocument } from '@interop/storage-core';
7
+ export type { BlindedIndexQuery, BlindedIndexQueryPage };
8
+ export type { IDID, IVerificationMethod, IMultikeyMethod, IZcap, IRootZcap, IDelegatedZcap, IVerifier, IPublicMultikey, IMultikeyDocument, IVerificationKeyPair2020, IRemoteDocument, IDocumentLoader };
9
+ export type { SpaceDescription, BackendReference, SpaceSummary, SpaceListing, CollectionSummary, CollectionsList, ResourceSummary, CollectionResourcesList, CollectionDescription, CollectionEncryption, CollectionEncryptionEpoch, CollectionEncryptionRecipient, ResourceMetadata, ResourceMetadataCustom, BackendDescriptor, BackendConnectionPublic, BackendConnectionInput, BackendRegistration, BackendState, StorageLimit, CollectionUsage, BackendUsage, SpaceQuotaReport, PolicyDocument, ImportStats, Action, ActionInput, LinkSet, LinkSetEntry } from '@interop/storage-core';
10
+ /** Return shape of `getResource()`. */
11
+ export interface ResourceResult {
12
+ resourceStream: Readable;
13
+ /** resolved content-type of the stored bytes */
14
+ storedResourceType: string;
15
+ /**
16
+ * The Resource's current monotonic `version` -- the value behind its HTTP
17
+ * `ETag` strong validator (`conditional-writes` feature). Absent only for a
18
+ * legacy Resource written before versioning.
19
+ */
20
+ version?: number;
21
+ }
22
+ /**
23
+ * Transport-neutral input to `writeResource`. The request layer resolves a
24
+ * Fastify request into one of these shapes (see `resolveResourceInput` in
25
+ * requests/resourceInput.ts) so that storage backends never depend on Fastify:
26
+ * - `kind: 'json'` carries the parsed JSON value in `data`.
27
+ * - `kind: 'binary'` carries a readable byte stream — a raw blob body, or the
28
+ * file extracted from a multipart upload. `declaredBytes` is the up-front size
29
+ * when known (a raw body's `Content-Length`), used for an early quota
30
+ * pre-flight; it is absent for multipart parts, whose size is unknown until
31
+ * the stream is consumed (the backend's streaming guard enforces the limit).
32
+ *
33
+ * In both cases `contentType` is the content-type the bytes are stored under.
34
+ */
35
+ export type ResourceInput = {
36
+ kind: 'json';
37
+ contentType: string;
38
+ data: unknown;
39
+ } | {
40
+ kind: 'binary';
41
+ contentType: string;
42
+ stream: Readable;
43
+ declaredBytes?: number;
44
+ };
45
+ /**
46
+ * The parsed auth headers attached to `request.zcap` by the `parseAuthHeaders`
47
+ * hook. `headers` is the signed-headers list string (not the request headers
48
+ * object); `created` / `expires` are stringified unix timestamps.
49
+ */
50
+ export interface ParsedZcap {
51
+ keyId: string;
52
+ headers: string;
53
+ signature: string;
54
+ created: string;
55
+ expires: string;
56
+ /** the raw `Capability-Invocation` header value */
57
+ invocation: string;
58
+ /** the raw `Digest` header value (absent on bodyless requests) */
59
+ digest?: string;
60
+ }
61
+ /**
62
+ * The full persisted record for a registered `external` backend (spec
63
+ * "Backends"). Secret-bearing -- its `connection` carries the write-side grant
64
+ * material -- so it is **never** serialized to a client: only the sanitized
65
+ * `BackendDescriptor` projection (`sanitizeBackendRecord` in `lib/backends.ts`)
66
+ * is. Held in usable (plaintext, this increment) form because the server is the
67
+ * token custodian; the read/write split is enforced by `getBackend` being the
68
+ * one storage method that returns this shape.
69
+ */
70
+ export type StoredBackendRecord = BackendDescriptor & {
71
+ managedBy: 'external';
72
+ provider: string;
73
+ connection: BackendConnectionInput;
74
+ };
75
+ /**
76
+ * A WebKMS keystore configuration (the `/kms` facet).
77
+ * The wire shape is protocol-fixed by `@interop/webkms-client`, minus the
78
+ * deliberately-dropped `meterId` / `ipAllowList` fields. Stored verbatim,
79
+ * full-URL `id` included, so the sequence-gated update can round-trip the
80
+ * config unchanged; the storage key is the id's last URL segment (the
81
+ * server-generated local id).
82
+ */
83
+ export interface KeystoreConfig {
84
+ /** full keystore URL (`<serverUrl>/kms/keystores/<localId>`), server-assigned on create */
85
+ id: string;
86
+ /** the DID that controls the keystore (authorizes every invocation on it) */
87
+ controller: IDID;
88
+ /** config revision: must be 0 on create, exactly previous+1 on update */
89
+ sequence: number;
90
+ /** opaque KMS module alias, echoed back; this server hard-wires 'local-v1' */
91
+ kmsModule: string;
92
+ }
93
+ /**
94
+ * The full serialized form of a WebKMS-held key, INCLUDING its secret material
95
+ * (`privateKeyMultibase` for the asymmetric pairs, `secret` for the symmetric
96
+ * keys) -- the `key` unit of a {@link KmsKeyRecord}. Field names are
97
+ * protocol-fixed by the webkms per-type key generators. The
98
+ * `controller` is deliberately NOT part of it: it is always read from the live
99
+ * keystore config at description time, so a controller change takes effect
100
+ * immediately.
101
+ */
102
+ export interface KmsStoredKey {
103
+ '@context': string;
104
+ /** full key URL (`<keystoreId>/keys/<localId>`), server-assigned on generate */
105
+ id: string;
106
+ /** the webkms key type (e.g. `Ed25519VerificationKey2020`) */
107
+ type: string;
108
+ publicKeyMultibase?: string;
109
+ privateKeyMultibase?: string;
110
+ /** symmetric key material (HMAC / AES-KW), base64url */
111
+ secret?: string;
112
+ /** per-key invocation chain bound, enforced at operation time */
113
+ maxCapabilityChainLength?: number;
114
+ /** verbatim description `id` override (e.g. a did:key or did:web URL) */
115
+ publicAlias?: string;
116
+ /** description `id` template, expanded against the key description */
117
+ publicAliasTemplate?: string;
118
+ /**
119
+ * At-rest ONLY: present in place of the secret fields (`privateKeyMultibase`
120
+ * / `secret`) when the record was written under a configured record KEK
121
+ * (`KMS_RECORD_KEK`; see `lib/kmsRecordCipher.ts`). The in-memory
122
+ * `KmsStoredKey` the KMS module operates on is always the DECRYPTED form --
123
+ * the decrypt seam (`decryptKeyRecord`) strips this envelope and restores the
124
+ * secret fields before any operation reads the key. A plaintext record (the
125
+ * default / unconfigured deployment) never carries it.
126
+ */
127
+ encrypted?: KmsEncryptedEnvelope;
128
+ }
129
+ /**
130
+ * The at-rest envelope that replaces the secret-bearing fields of a stored
131
+ * key's `key` when record encryption is enabled (`KMS_RECORD_KEK`; see
132
+ * `lib/kmsRecordCipher.ts`). A fresh per-record content-encryption key (CEK,
133
+ * `A256GCM`) encrypts the serialized secret subset; the CEK is wrapped
134
+ * (`A256KW`) under the config-supplied KEK named by `kekId` -- the rotation
135
+ * seam: a record keeps the `kekId` it was written under, so a rotated-in KEK
136
+ * never forces a rewrite. Secrets never crossed the wire and still don't: this
137
+ * shape lives only on disk, never in a client projection.
138
+ */
139
+ export interface KmsEncryptedEnvelope {
140
+ /** id of the KEK the CEK was wrapped under (`RecordKek.id`) */
141
+ kekId: string;
142
+ /**
143
+ * General JWE (JSON serialization): `A256GCM` content encryption with the
144
+ * CEK wrapped `A256KW` under the KEK. The `protected` header is the JWE AAD.
145
+ */
146
+ jwe: {
147
+ protected: string;
148
+ recipients: Array<{
149
+ header?: Record<string, unknown>;
150
+ encrypted_key: string;
151
+ }>;
152
+ iv: string;
153
+ ciphertext: string;
154
+ tag: string;
155
+ };
156
+ /** the secret-subset serialization inside the JWE (only `json` this increment) */
157
+ encoding: 'json';
158
+ }
159
+ /**
160
+ * A record-encryption key-encryption key (KEK): a raw AES-256 key plus its
161
+ * derived, non-secret id (`RecordKek.id`, a one-way hash of the key material,
162
+ * safe to store per record). Held only in process memory (config env), never in
163
+ * the data tree.
164
+ */
165
+ export interface RecordKek {
166
+ id: string;
167
+ key: Buffer;
168
+ }
169
+ /**
170
+ * The at-rest key-record KEK registry (config `KMS_RECORD_KEK` /
171
+ * `KMS_RECORD_KEKS` / `KMS_RECORD_CURRENT_KEK`): every KEK available to UNWRAP a
172
+ * record (keyed by `RecordKek.id`) plus `currentKekId`,
173
+ * the KEK that WRAPS new records. `currentKekId: null` disables encryption --
174
+ * new records are written plaintext -- while previously registered KEKs stay
175
+ * available for decrypt. Rotation is a config change (register a new KEK, repoint
176
+ * `currentKekId`), never a schema migration.
177
+ */
178
+ export interface KmsRecordKekRegistry {
179
+ keks: Map<string, RecordKek>;
180
+ currentKekId: string | null;
181
+ }
182
+ /**
183
+ * A stored WebKMS key record (the `/kms` facet), a
184
+ * `{keystoreId, localId, meta, key}` shape unique on `(keystoreId, localId)`.
185
+ * Secret-bearing -- `key` carries the full serialized key material -- so a
186
+ * record is **never** serialized to a client: only the sanitized key-description
187
+ * projection built by the KMS module is. The storage layer treats the record as
188
+ * an opaque unit. At rest, `key`'s secret fields are stored plaintext by default
189
+ * or, when `KMS_RECORD_KEK` is configured, replaced by a `key.encrypted`
190
+ * envelope (see `lib/kmsRecordCipher.ts`); either way the in-memory record the
191
+ * KMS module operates on is the decrypted form.
192
+ */
193
+ export interface KmsKeyRecord {
194
+ /** the owning keystore's local id */
195
+ keystoreId: string;
196
+ /** the key's server-generated local id (the last segment of `key.id`) */
197
+ localId: string;
198
+ /** server-managed timestamps (ISO 8601) */
199
+ meta: {
200
+ created: string;
201
+ updated: string;
202
+ };
203
+ key: KmsStoredKey;
204
+ }
205
+ /**
206
+ * The public key-description projection of a KMS-held key, as returned by
207
+ * `GenerateKeyOperation` and `GET <keyId>` (never any secret field). Its `id`
208
+ * is the key URL, or the `publicAlias` / expanded `publicAliasTemplate` when
209
+ * one was set at generate time; `controller` is the live keystore controller.
210
+ */
211
+ export interface KmsKeyDescription {
212
+ '@context': string;
213
+ id: string;
214
+ type: string;
215
+ publicKeyMultibase?: string;
216
+ controller: IDID;
217
+ }
218
+ /**
219
+ * The object a revocation aggregates under, and the unit a revocation lookup
220
+ * is scoped to: a keystore (the `/kms` route family) or a Space (the WAS route
221
+ * families). A revocation stored under one scope has no effect on the other --
222
+ * a chain rooted in a Space is only ever inspected against that Space's store.
223
+ */
224
+ export type RevocationScope = {
225
+ keystoreId: string;
226
+ } | {
227
+ spaceId: string;
228
+ };
229
+ /**
230
+ * A stored zcap revocation, a `{capability, meta}` record. Unique on
231
+ * `(delegator, capability.id)` within
232
+ * its scope (the keystore or Space it is stored under); `meta.expires` is the
233
+ * record's own garbage-collection horizon -- one day past the capability's
234
+ * `expires`, after which the capability is rejected on expiry alone and the
235
+ * record is prunable (the one-day margin covers clock-skew grace periods).
236
+ */
237
+ export interface RevocationRecord {
238
+ /** the full revoked capability, stored verbatim */
239
+ capability: {
240
+ id: string;
241
+ expires?: string;
242
+ [key: string]: unknown;
243
+ };
244
+ meta: {
245
+ /** the party that delegated the revoked capability (its proof creator) */
246
+ delegator: string;
247
+ /**
248
+ * the root object the revocation aggregates under (the keystore URL, or
249
+ * the Space URL for a WAS-route revocation)
250
+ */
251
+ rootTarget: string;
252
+ /** server-managed creation timestamp (ISO 8601) */
253
+ created: string;
254
+ /** GC horizon (ISO 8601); absent when the capability never expires */
255
+ expires?: string;
256
+ };
257
+ }
258
+ /**
259
+ * The `(capabilityId, delegator)` pair identifying one delegated capability in
260
+ * a chain for a revocation-store lookup.
261
+ */
262
+ export interface CapabilitySummary {
263
+ capabilityId: string;
264
+ delegator: string;
265
+ }
266
+ /**
267
+ * A backend-adapter factory: given a registered (secret-bearing)
268
+ * `StoredBackendRecord` and a logger, returns the live `StorageBackend` that
269
+ * speaks to that provider. The teaching server's adapter strategy is Layer 3
270
+ * (not spec), so this type is server-local. Keyed by `record.provider` in the
271
+ * `BackendProviderRegistry`.
272
+ */
273
+ export type BackendProvider = (record: StoredBackendRecord, options: {
274
+ logger: FastifyBaseLogger;
275
+ }) => StorageBackend;
276
+ /** The injected provider-adapter registry, keyed by `record.provider`. */
277
+ export type BackendProviderRegistry = Map<string, BackendProvider>;
278
+ /**
279
+ * The persistence contract a storage backend implements (currently
280
+ * `FileSystemBackend`; the port is designed to admit additional adapters). The
281
+ * active backend is injected into the Fastify instance via
282
+ * `createApp({ backend })` and read in handlers as `request.server.storage`.
283
+ *
284
+ * Invariants:
285
+ * - The getters resolve to a falsy value (not throw) when the target is absent;
286
+ * callers test `if (!description)` and translate that into a 404.
287
+ * - Write methods are upserts (create if absent, overwrite if present); their
288
+ * resolved value is implementation-defined and ignored.
289
+ * - Delete methods are idempotent and resolve once the target is gone.
290
+ * - Resources are identified by `resourceId` alone within a Collection; a
291
+ * Resource has exactly one current representation. `writeResource` replaces
292
+ * any existing representation, including one previously stored under a
293
+ * different content-type. `getResource`'s `contentType` is an advisory hint:
294
+ * single-representation backends return the one representation regardless, and
295
+ * the stored content-type comes back in `ResourceResult.storedResourceType`.
296
+ * Where the content-type lives is an adapter detail (filename segment /
297
+ * map-value field / future SQL column).
298
+ *
299
+ * Note: `exportSpace` resolves a tar-stream `Pack` at runtime, typed here as the
300
+ * `Readable` it extends (tar-stream ships no types).
301
+ */
302
+ export interface StorageBackend {
303
+ /**
304
+ * Optional logger the backend writes diagnostics through (Fastify's pino
305
+ * logger, `FastifyBaseLogger`). `createApp` wires `fastify.log` here; backends
306
+ * default to a silent pino logger until it is set.
307
+ */
308
+ logger?: FastifyBaseLogger;
309
+ /**
310
+ * OPTIONAL startup hook for backends with a connection lifecycle (e.g. the
311
+ * Postgres backend connects and applies its schema migrations here).
312
+ * Awaited once by the plugin composition during registration, before the
313
+ * server starts listening. Backends without startup work omit it.
314
+ */
315
+ init?(): Promise<void>;
316
+ /**
317
+ * OPTIONAL shutdown hook (e.g. draining a connection pool). Wired to the
318
+ * Fastify `onClose` hook by the plugin composition. Backends without
319
+ * teardown work omit it.
320
+ */
321
+ close?(): Promise<void>;
322
+ /**
323
+ * The per-upload size cap in bytes (spec "Quotas", `maxUploadBytes`), or
324
+ * `undefined` for no cap. Enforced by `writeResource`; also read by the
325
+ * request layer to bound the in-memory buffer of a multipart file part (so an
326
+ * oversize multipart upload is rejected before it is fully buffered).
327
+ */
328
+ maxUploadBytes?: number;
329
+ /**
330
+ * The backend's self-description, as advertised at
331
+ * `GET /space/:spaceId/backends`. Synchronous: a backend knows its own
332
+ * characteristics without any I/O.
333
+ */
334
+ describe(): BackendDescriptor;
335
+ /**
336
+ * Measures the storage the given Space consumes on this backend, for the
337
+ * Space Quota report (spec "Quotas"). Resolves a `BackendUsage` entry: the
338
+ * backend's identity plus measured `usageBytes`, derived `state`, and the
339
+ * configured `limit`. The per-Collection `usageByCollection` breakdown is
340
+ * included only when `includeCollections` is set (the spec's opt-in
341
+ * `?include=collections`), so a backend for which the breakdown is expensive
342
+ * computes it only on request. The Space is guaranteed to exist by the request
343
+ * layer before this is called; an absent Space dir reports zero usage.
344
+ */
345
+ reportUsage(options: {
346
+ spaceId: string;
347
+ includeCollections?: boolean;
348
+ }): Promise<BackendUsage>;
349
+ /**
350
+ * Measures the storage a single Collection consumes on this backend, for the
351
+ * per-Collection Quota report (spec "Quotas",
352
+ * `GET /space/{id}/{cid}/quota`). Resolves a `BackendUsage` entry whose
353
+ * `usageBytes` is scoped to the Collection, while `state` / `limit` /
354
+ * `restrictedActions` describe the backend's overall condition (the quota is a
355
+ * per-backend limit); the per-Collection breakdown (`usageByCollection`) is
356
+ * omitted. OPTIONAL: a backend that cannot account per-Collection omits this
357
+ * method, and the request layer returns `unsupported-operation` (501). The
358
+ * Space and Collection are guaranteed to exist by the request layer.
359
+ */
360
+ reportCollectionUsage?(options: {
361
+ spaceId: string;
362
+ collectionId: string;
363
+ }): Promise<BackendUsage>;
364
+ /**
365
+ * Writes a Space Description (full replacement). The server-managed
366
+ * `createdBy` is authoritative, never taken from `spaceDescription`: the
367
+ * backend drops any value carried in that (client-supplied) document and
368
+ * records `createdBy` from the first write's invoker, preserving it verbatim
369
+ * on every later write. Omitting `createdBy` on a first write leaves it
370
+ * unrecorded rather than letting the body supply one.
371
+ */
372
+ writeSpace(options: {
373
+ spaceId: string;
374
+ spaceDescription: SpaceDescription;
375
+ /** DID of the invoker; recorded as `createdBy` on first write only */
376
+ createdBy?: IDID;
377
+ }): Promise<void>;
378
+ getSpaceDescription(options: {
379
+ spaceId: string;
380
+ }): Promise<SpaceDescription | undefined>;
381
+ deleteSpace(options: {
382
+ spaceId: string;
383
+ }): Promise<void>;
384
+ /**
385
+ * Enumerates every Space stored on this backend (the candidate set for the
386
+ * List Spaces operation; the request layer filters it down to the Spaces the
387
+ * caller is authorized to see). Resolves an empty array when nothing is
388
+ * stored yet (must not throw on an absent storage root).
389
+ */
390
+ listSpaces(): Promise<SpaceDescription[]>;
391
+ listCollections(options: {
392
+ spaceId: string;
393
+ }): Promise<CollectionSummary[]>;
394
+ /**
395
+ * Packs the Space as a tar archive: its Collections, Resources (including
396
+ * tombstones), policies, metadata sidecars, and the Space's zcap revocation
397
+ * records (so a revoked capability stays revoked across an export/import
398
+ * round-trip). Backend registration records (secret material) do NOT
399
+ * travel.
400
+ */
401
+ exportSpace(options: {
402
+ spaceId: string;
403
+ }): Promise<Readable>;
404
+ /**
405
+ * Merges a Space-export archive into an existing Space, skip-not-overwrite
406
+ * per item; the archive's revocation records are restored under this
407
+ * Space's scope on the same terms (already-stored records are skipped).
408
+ */
409
+ importSpace(options: {
410
+ spaceId: string;
411
+ tarStream: Readable;
412
+ }): Promise<ImportStats>;
413
+ /**
414
+ * Writes a Collection Description (full replacement), bumping its monotonic
415
+ * description `version` (the `ETag` validator behind conditional Collection
416
+ * writes; the `key-epochs` feature) and returning the new version. `createdBy`
417
+ * is server-managed on the same terms as `writeSpace`'s. When `ifMatch` is
418
+ * supplied it is evaluated atomically with the write: the current description
419
+ * ETag must equal it (an update-if-unchanged compare-and-swap that keeps two
420
+ * concurrent recipient edits from clobbering one another), else
421
+ * `precondition-failed` (412). The version travels only as the `ETag` header
422
+ * -- it is kept OUT of the stored/wire Collection Description body.
423
+ */
424
+ writeCollection(options: {
425
+ spaceId: string;
426
+ collectionId: string;
427
+ collectionDescription: CollectionDescription;
428
+ /** DID of the invoker; recorded as `createdBy` on first write only */
429
+ createdBy?: IDID;
430
+ ifMatch?: string;
431
+ /**
432
+ * Invoked atomically with the write (inside the backend's per-Collection
433
+ * lock / row-locking transaction) against the freshly re-read current
434
+ * description (`undefined` on a create); throwing aborts the write. Carries
435
+ * the request layer's state-transition rails -- e.g. the epoch append-only
436
+ * check -- which are otherwise evaluated against a pre-lock read and could
437
+ * miss a concurrent write.
438
+ */
439
+ assertTransition?: (prior?: CollectionDescription & {
440
+ descriptionVersion?: number;
441
+ }) => void;
442
+ }): Promise<{
443
+ version: number;
444
+ }>;
445
+ /**
446
+ * Reads a Collection Description. Resolves falsy when the Collection does not
447
+ * exist. `descriptionVersion` is the out-of-band `ETag` validator (the handler
448
+ * strips it from the wire body and sets it as the `ETag` header); absent for a
449
+ * legacy Collection written before description versioning.
450
+ */
451
+ getCollectionDescription(options: {
452
+ spaceId: string;
453
+ collectionId: string;
454
+ }): Promise<(CollectionDescription & {
455
+ descriptionVersion?: number;
456
+ }) | undefined>;
457
+ deleteCollection(options: {
458
+ spaceId: string;
459
+ collectionId: string;
460
+ }): Promise<void>;
461
+ /**
462
+ * Lists a Collection's Resources, OPTIONALLY paginated (spec "Pagination").
463
+ * `limit` bounds the page (a backend MAY clamp an oversized value to its own
464
+ * maximum); `cursor` is the opaque token from a prior page's `next`, naming
465
+ * the keyset position to resume from. With neither, the first (or only) page
466
+ * is returned. The result carries `next` -- a ready-to-follow URL with the
467
+ * cursor and limit baked in -- if and only if a further page may follow; its
468
+ * absence marks the last page. A malformed/un-honorable `cursor` rejects with
469
+ * `InvalidCursorError` (400 `invalid-cursor`).
470
+ *
471
+ * `collectionDescription` (the caller's already-fetched control-plane
472
+ * description) supplies the listing's `name` / `type` and encryption flag; a
473
+ * data-plane backend selected by a Collection never holds the description
474
+ * itself (it lives on the control plane), so it MUST be passed in for such a
475
+ * backend.
476
+ */
477
+ listCollectionItems(options: {
478
+ spaceId: string;
479
+ collectionId: string;
480
+ limit?: number;
481
+ cursor?: string;
482
+ collectionDescription?: CollectionDescription;
483
+ }): Promise<CollectionResourcesList>;
484
+ /**
485
+ * Writes a Resource representation, bumping its monotonic `version` (the ETag
486
+ * validator), and returns the new version. When a conditional-write
487
+ * precondition is supplied (`conditional-writes` feature) it is evaluated
488
+ * atomically with the write: `ifMatch` is an update-if-unchanged (the current
489
+ * ETag must equal it), `ifNoneMatch` is a create-if-absent (`If-None-Match:
490
+ * *`); a mismatch rejects with `precondition-failed` (412).
491
+ *
492
+ * A backend carrying the `blinded-index-query` feature also enforces the EDV
493
+ * unique-attribute invariant on JSON writes: an `indexed` blinded attribute
494
+ * marked `unique: true` whose (HMAC key id, name, value) triple is already
495
+ * claimed by another live document in the same Collection rejects with
496
+ * `UniqueAttributeConflictError` (409), evaluated atomically with the write
497
+ * (see `lib/blindedIndex.ts`). Conflicts require `unique: true` on both
498
+ * sides, and a document keeping its own unique attribute across an update
499
+ * never self-conflicts.
500
+ */
501
+ writeResource(options: {
502
+ spaceId: string;
503
+ collectionId: string;
504
+ resourceId: string;
505
+ input: ResourceInput;
506
+ /**
507
+ * DID of the party whose capability invocation authorized this write (the
508
+ * signing key's DID, fragment stripped). Recorded as the Resource's
509
+ * server-managed `createdBy` on the FIRST write and preserved verbatim by
510
+ * every later write, exactly as `createdAt` is -- so it names the creator,
511
+ * not the last writer. Omitted by callers with no resolved invoker (a
512
+ * direct backend call), in which case no `createdBy` is recorded.
513
+ */
514
+ createdBy?: IDID;
515
+ /**
516
+ * The client-declared key epoch this content was encrypted under (the
517
+ * `key-epochs` feature). Stored opaquely on the Resource's metadata and
518
+ * returned by reads; a content write with no epoch CLEARS any stored stamp
519
+ * (the new ciphertext's epoch is unknown). The server never computes or
520
+ * verifies it.
521
+ */
522
+ epoch?: string;
523
+ ifMatch?: string;
524
+ ifNoneMatch?: boolean;
525
+ }): Promise<{
526
+ version: number;
527
+ }>;
528
+ getResource(options: {
529
+ spaceId: string;
530
+ collectionId: string;
531
+ resourceId: string;
532
+ /** advisory hint only; single-representation backends ignore it for lookup */
533
+ contentType?: string;
534
+ }): Promise<ResourceResult>;
535
+ /**
536
+ * Deletes a Resource. When `ifMatch` is supplied (`conditional-writes`), the
537
+ * delete proceeds only if the Resource's current ETag matches, evaluated
538
+ * atomically with the removal; a mismatch rejects with `precondition-failed`
539
+ * (412). Without it, the delete is unconditional and idempotent.
540
+ */
541
+ deleteResource(options: {
542
+ spaceId: string;
543
+ collectionId: string;
544
+ resourceId: string;
545
+ ifMatch?: string;
546
+ }): Promise<void>;
547
+ /**
548
+ * Reads a Resource's Metadata object. `version` / `metaVersion` are the
549
+ * out-of-band ETag validators (the handler strips them from the wire body);
550
+ * the Metadata's own `createdBy` rides along in it.
551
+ */
552
+ getResourceMetadata(options: {
553
+ spaceId: string;
554
+ collectionId: string;
555
+ resourceId: string;
556
+ }): Promise<(ResourceMetadata & {
557
+ version?: number;
558
+ metaVersion?: number;
559
+ }) | undefined>;
560
+ /**
561
+ * Replaces the user-writable `custom` object of a Resource's Metadata (full
562
+ * replacement; pass `{}` to clear). Resolves `undefined` when the Resource
563
+ * does not exist (this operation does not create one) so the handler can 404,
564
+ * else the Resource's new `metaVersion` (the `/meta` ETag validator, bumped
565
+ * on each metadata write independently of the content `version`).
566
+ *
567
+ * On an encrypted Collection `custom` is the opaque encryption envelope (an
568
+ * arbitrary JSON object) rather than a `{ name, tags }` object; the backend
569
+ * stores it verbatim. When `ifMatch` / `ifNoneMatch` is supplied
570
+ * (`conditional-writes`), the write is gated on the current `metaVersion`
571
+ * atomically, rejecting a mismatch with `precondition-failed` (412).
572
+ */
573
+ writeResourceMetadata(options: {
574
+ spaceId: string;
575
+ collectionId: string;
576
+ resourceId: string;
577
+ custom: ResourceMetadataCustom | Record<string, unknown>;
578
+ /**
579
+ * The client-declared key epoch (the `key-epochs` feature), a sibling of
580
+ * `custom`. Unlike `custom` (full replacement), an OMITTED `epoch`
581
+ * PRESERVES the stored value -- the stamp describes the content write, not
582
+ * the metadata write -- while a supplied value replaces it. Stored opaquely.
583
+ */
584
+ epoch?: string;
585
+ ifMatch?: string;
586
+ ifNoneMatch?: boolean;
587
+ }): Promise<{
588
+ metaVersion: number;
589
+ } | undefined>;
590
+ /**
591
+ * OPTIONAL replication change feed (the `changes` query profile.
592
+ * Returns the Collection's JSON-document
593
+ * Resources and tombstones changed strictly after `checkpoint`, in change
594
+ * order (`(updatedAt, resourceId)` ascending), capped at `limit` (a backend
595
+ * MAY clamp an oversized value to its own maximum). With no `checkpoint`, the
596
+ * feed starts from the beginning.
597
+ *
598
+ * Each document carries its monotonic content `version`, its `metaVersion`
599
+ * (when a metadata write has occurred), `updatedAt`, the server-managed
600
+ * `createdBy` (the creator's DID, when one was recorded -- so provenance
601
+ * replicates and does not have to be fetched per Resource from `/meta`), and
602
+ * -- so metadata replicates alongside content -- the user-writable `custom`
603
+ * object (the opaque encryption envelope on an encrypted Collection). A
604
+ * tombstone keeps its `createdBy`, as it keeps its `createdAt`. A
605
+ * metadata-only edit
606
+ * re-surfaces the Resource with a bumped `updatedAt` / `metaVersion` but its
607
+ * `version` / `data` unchanged. A tombstone (soft-deleted Resource) is
608
+ * surfaced with `deleted: true` and no `data` so the delete replicates until
609
+ * clients catch up. Binary (non-JSON) Resources are excluded -- attachment
610
+ * replication is future work. The result's
611
+ * `checkpoint` is the `{ id, updatedAt }` of the last returned document (the
612
+ * keyset position a follow-up call resumes after), or `null` when nothing
613
+ * changed since `checkpoint`.
614
+ *
615
+ * OPTIONAL: a backend that omits this method does not serve the change feed,
616
+ * and the request layer returns `unsupported-operation` (501). The Space and
617
+ * Collection are guaranteed to exist by the request layer.
618
+ */
619
+ changesSince?(options: {
620
+ spaceId: string;
621
+ collectionId: string;
622
+ checkpoint?: {
623
+ id: string;
624
+ updatedAt: string;
625
+ };
626
+ limit: number;
627
+ }): Promise<{
628
+ documents: Array<{
629
+ resourceId: string;
630
+ version: number;
631
+ metaVersion?: number;
632
+ createdBy?: IDID;
633
+ updatedAt: string;
634
+ deleted: boolean;
635
+ data?: unknown;
636
+ custom?: ResourceMetadataCustom | Record<string, unknown>;
637
+ /**
638
+ * The client-declared key epoch the Resource was encrypted under (the
639
+ * `key-epochs` feature), when one was stamped. Rides the feed so a
640
+ * replicating reader picks the right epoch key without a `/meta` fetch.
641
+ */
642
+ epoch?: string;
643
+ }>;
644
+ checkpoint: {
645
+ id: string;
646
+ updatedAt: string;
647
+ } | null;
648
+ }>;
649
+ /**
650
+ * OPTIONAL blinded-index query (the `blinded-index` query profile; the
651
+ * `blinded-index-query` feature token). Evaluates an EDV query -- `equals`
652
+ * (OR across elements of an AND within each element's blinded `{name:
653
+ * value}` pairs) or `has` (every named blinded attribute present) -- against
654
+ * the HMAC-blinded `indexed` entries of the Collection's live JSON
655
+ * documents, scoped to the `query.index` HMAC key id. Matching is opaque
656
+ * string comparison; the backend performs no cryptography. With `count`,
657
+ * resolves only the match total; otherwise a page of the matching stored
658
+ * documents verbatim, in ascending `resourceId` order, paginated with the
659
+ * standard opaque cursor (`cursor` present iff `hasMore`; a malformed one
660
+ * rejects with `invalid-cursor` 400). Both first-party backends answer
661
+ * through `lib/blindedIndex.ts` so semantics cannot drift.
662
+ *
663
+ * OPTIONAL: a backend that omits this method does not serve the profile,
664
+ * and the request layer returns `unsupported-operation` (501). The Space and
665
+ * Collection are guaranteed to exist by the request layer.
666
+ */
667
+ queryByBlindedIndex?(options: {
668
+ spaceId: string;
669
+ collectionId: string;
670
+ query: BlindedIndexQuery;
671
+ count?: boolean;
672
+ limit?: number;
673
+ cursor?: string;
674
+ }): Promise<{
675
+ count: number;
676
+ } | BlindedIndexQueryPage>;
677
+ /**
678
+ * Access-control policy documents. The level is selected by which ids are
679
+ * present: Space (`spaceId`), Collection (`+ collectionId`), or Resource
680
+ * (`+ collectionId + resourceId`). Getters resolve falsy when absent.
681
+ */
682
+ getPolicy(options: {
683
+ spaceId: string;
684
+ collectionId?: string;
685
+ resourceId?: string;
686
+ }): Promise<PolicyDocument | undefined>;
687
+ writePolicy(options: {
688
+ spaceId: string;
689
+ collectionId?: string;
690
+ resourceId?: string;
691
+ policy: PolicyDocument;
692
+ }): Promise<void>;
693
+ deletePolicy(options: {
694
+ spaceId: string;
695
+ collectionId?: string;
696
+ resourceId?: string;
697
+ }): Promise<void>;
698
+ /**
699
+ * Registered `external` backend records (spec "Backends"). The read/write
700
+ * asymmetry is the secret boundary: `getBackend` is the only method that
701
+ * returns the secret-bearing `StoredBackendRecord`; `listBackends` returns
702
+ * sanitized `BackendDescriptor`s. A registered backend is listed but not yet
703
+ * selectable as a Collection's `backend` this increment (the live adapter is
704
+ * future work).
705
+ */
706
+ writeBackend(options: {
707
+ spaceId: string;
708
+ backendId: string;
709
+ record: StoredBackendRecord;
710
+ }): Promise<void>;
711
+ /** The full (secret-bearing) record, or `undefined` when absent. Internal use. */
712
+ getBackend(options: {
713
+ spaceId: string;
714
+ backendId: string;
715
+ }): Promise<StoredBackendRecord | undefined>;
716
+ /** The Space's registered external backends, **sanitized** (no secrets). */
717
+ listBackends(options: {
718
+ spaceId: string;
719
+ }): Promise<BackendDescriptor[]>;
720
+ /** Idempotent: no error when the record is absent. */
721
+ deleteBackend(options: {
722
+ spaceId: string;
723
+ backendId: string;
724
+ }): Promise<void>;
725
+ /**
726
+ * WebKMS keystore configs (the `/kms` facet).
727
+ * Keystores are a sibling tree to Spaces (`data/keystores/<localId>/`),
728
+ * keyed by `keystoreId` -- the server-generated *local* id, i.e. the last
729
+ * segment of the config's full-URL `id`. The protocol defines no keystore
730
+ * delete.
731
+ *
732
+ * Writes a keystore config unconditionally (the create path; local ids are
733
+ * server-generated 128-bit random values, so create never collides). The
734
+ * sequence-gated update path is `updateKeystore`.
735
+ */
736
+ writeKeystore(options: {
737
+ keystoreId: string;
738
+ config: KeystoreConfig;
739
+ }): Promise<void>;
740
+ getKeystore(options: {
741
+ keystoreId: string;
742
+ }): Promise<KeystoreConfig | undefined>;
743
+ /**
744
+ * Replaces a keystore config if and only if, atomically with the write:
745
+ * the keystore exists, `config.sequence` is exactly the stored sequence + 1,
746
+ * and `config.kmsModule` matches the stored one (the module is immutable).
747
+ * Otherwise rejects with the protocol's 409 state conflict
748
+ * (`KeystoreStateConflictError`) -- one merged conflict kind.
749
+ */
750
+ updateKeystore(options: {
751
+ keystoreId: string;
752
+ config: KeystoreConfig;
753
+ }): Promise<void>;
754
+ /**
755
+ * Every stored keystore config whose `controller` matches, sorted by local
756
+ * id (the request layer caps the wire result). Resolves an empty array when
757
+ * nothing is stored yet (must not throw on an absent storage root).
758
+ */
759
+ listKeystoresByController(options: {
760
+ controller: IDID;
761
+ }): Promise<KeystoreConfig[]>;
762
+ /**
763
+ * WebKMS key records, stored under their keystore
764
+ * (`data/keystores/<keystoreId>/keys/<localId>.json`), unique on
765
+ * `(keystoreId, localId)`. The record is opaque to the storage layer -- the
766
+ * at-rest record cipher (`KMS_RECORD_KEK`, `lib/kmsRecordCipher.ts`) applies
767
+ * above the backend, at the KMS orchestration seam, so no schema change is
768
+ * needed here. The protocol defines no key delete or update -- a record is
769
+ * immutable once inserted.
770
+ *
771
+ * Inserts a key record, create-only: rejects with the protocol's 409
772
+ * duplicate conflict (`KeyIdConflictError`) when a record already exists at
773
+ * `(keystoreId, localId)`, atomically with the write.
774
+ */
775
+ insertKey(options: {
776
+ keystoreId: string;
777
+ localId: string;
778
+ record: KmsKeyRecord;
779
+ }): Promise<void>;
780
+ getKey(options: {
781
+ keystoreId: string;
782
+ localId: string;
783
+ }): Promise<KmsKeyRecord | undefined>;
784
+ /**
785
+ * Every stored key record under the keystore, sorted by local id (the
786
+ * request layer caps and paginates the wire result). The record is opaque to
787
+ * storage -- the at-rest cipher applies above the backend (as for `getKey`),
788
+ * so records come back exactly as stored. Resolves an empty array when the
789
+ * keystore has no keys yet (must not throw on an absent keys directory /
790
+ * table).
791
+ */
792
+ listKeys(options: {
793
+ keystoreId: string;
794
+ }): Promise<Array<{
795
+ localId: string;
796
+ record: KmsKeyRecord;
797
+ }>>;
798
+ /**
799
+ * ZCap revocations, stored under their scope -- a keystore
800
+ * (`data/keystores/<keystoreId>/revocations/`) or a Space
801
+ * (`data/space-revocations/<spaceId>/`, kept out of the Space tree so a
802
+ * revocation directory can never be mistaken for, or collide with, a
803
+ * Collection). Unique on `(delegator, capability.id)` within the scope.
804
+ * Neither protocol defines a revocation read or delete: records exist only
805
+ * to be consulted by the chain-inspection hook, and lapse via
806
+ * `meta.expires` (the capability is rejected on its own expiry from then
807
+ * on). Deleting a Space deletes its revocations with it.
808
+ *
809
+ * Inserts a revocation record, create-only: rejects with the protocol's 409
810
+ * duplicate (`DuplicateRevocationError`) when a record already exists at
811
+ * `(meta.delegator, capability.id)`, atomically with the write. The scope
812
+ * must exist: inserting under an absent keystore or Space rejects with
813
+ * `StorageError` (the request layer 404-masks unknown scopes long before
814
+ * this; the Postgres backend's foreign keys enforce the same at the store).
815
+ */
816
+ insertRevocation(options: {
817
+ scope: RevocationScope;
818
+ record: RevocationRecord;
819
+ }): Promise<void>;
820
+ /**
821
+ * True when any of the given capabilities has a stored, unexpired
822
+ * revocation under the scope. Records past their `meta.expires` GC
823
+ * horizon count as not revoked (the capability itself has expired) and may
824
+ * be pruned on the way through.
825
+ */
826
+ isRevoked(options: {
827
+ scope: RevocationScope;
828
+ capabilities: CapabilitySummary[];
829
+ }): Promise<boolean>;
830
+ }
831
+ /**
832
+ * Decision returned by an {@link AuthorizeProvisioning} callback for a
833
+ * provisioning request (`POST /spaces/` or `POST /kms/keystores`):
834
+ * - `verify` -- proceed with normal zcap capability-invocation verification;
835
+ * - `grant` -- the callback itself authorized the request (e.g. a valid
836
+ * onboarding token); skip zcap verification for this request;
837
+ * - `deny` -- refuse provisioning (403).
838
+ */
839
+ export type ProvisioningDecision = 'verify' | 'grant' | 'deny';
840
+ /**
841
+ * Provisioning gate callback: decides whether a request to one of the two open
842
+ * provisioning endpoints (`POST /spaces/`, `POST /kms/keystores`) may proceed.
843
+ * May instead throw a `ProblemError` subclass to return a custom status/body.
844
+ * @param options {object}
845
+ * @param options.request {import('fastify').FastifyRequest} the provisioning request
846
+ * @returns {ProvisioningDecision | Promise<ProvisioningDecision>}
847
+ */
848
+ export type AuthorizeProvisioning = (options: {
849
+ request: FastifyRequest;
850
+ }) => ProvisioningDecision | Promise<ProvisioningDecision>;
851
+ declare module 'fastify' {
852
+ interface FastifyInstance {
853
+ serverUrl: string;
854
+ storage: StorageBackend;
855
+ /**
856
+ * The provider-adapter registry: maps a registered backend's `provider` to
857
+ * the factory that builds its live `StorageBackend` adapter. Read by the
858
+ * resolver (lib/backendRegistry.ts). Empty in production this stage (no
859
+ * real adapter yet); injected in tests. Set by `fastify.decorate` in
860
+ * plugin.ts.
861
+ */
862
+ backendProviders: BackendProviderRegistry;
863
+ /**
864
+ * The optional server-wide registration allowlist: the backend `provider`
865
+ * names a client may register (config `WAS_ENABLED_BACKENDS`). `undefined`
866
+ * means no allowlist -- any provider may be registered (permissive
867
+ * default).
868
+ */
869
+ enabledBackendProviders?: string[];
870
+ /**
871
+ * The at-rest key-record encryption registry (config `KMS_RECORD_KEK` /
872
+ * `KMS_RECORD_KEKS` / `KMS_RECORD_CURRENT_KEK`): the KEK(s) available to
873
+ * unwrap stored WebKMS key records plus the
874
+ * `currentKekId` selecting the one that wraps NEW records. `undefined` (or
875
+ * `currentKekId: null`) means encryption is disabled -- records are written
876
+ * plaintext (the teaching default). Read at the KMS orchestration seam
877
+ * (`KeyRequest`), never inside a backend (records stay opaque to storage).
878
+ * Set by `fastify.decorate` in plugin.ts.
879
+ */
880
+ kmsRecordKek?: KmsRecordKekRegistry;
881
+ /**
882
+ * The optional provisioning gate for the two open provisioning endpoints
883
+ * (`POST /spaces/`, `POST /kms/keystores`). `undefined` means allow (the
884
+ * teaching default -- anyone may provision by proving control of the body's
885
+ * controller DID). Set by `fastify.decorate` in plugin.ts, either from the
886
+ * `authorizeProvisioning` option or the built-in onboarding-token check.
887
+ */
888
+ authorizeProvisioning?: AuthorizeProvisioning;
889
+ }
890
+ interface FastifyRequest {
891
+ /**
892
+ * Set by the provisioning gate when a request to a provisioning endpoint
893
+ * was authorized by the configured provisioning policy (e.g. a valid
894
+ * onboarding token) instead of a capability invocation. When set, the auth
895
+ * and digest hooks and the handler's controller-consent check are skipped
896
+ * (the request carries a Bearer token, not an HTTP Signature).
897
+ */
898
+ provisioningAuthorized?: boolean;
899
+ /**
900
+ * Set by the `parseAuthHeaders` hook when auth headers are present. Absent
901
+ * for anonymous reads (the `requireAuthHeaders` hook lets safe methods
902
+ * through without auth so a fallback policy can grant access).
903
+ */
904
+ zcap?: ParsedZcap;
905
+ /**
906
+ * The exact request body bytes, captured by the `captureRawBody`
907
+ * preParsing hook for JSON/text bodies so `verifyBodyDigest` can recompute
908
+ * the `Digest` header against what the client signed (re-serializing the
909
+ * parsed body is not guaranteed byte-identical). Absent for streamed
910
+ * (multipart / tar) bodies, which are left unbuffered.
911
+ */
912
+ rawBody?: Buffer;
913
+ }
914
+ }
915
+ //# sourceMappingURL=types.d.ts.map