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