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,1179 @@
1
+ import { Transform, type Readable } from 'node:stream';
2
+ import type { FastifyBaseLogger } from 'fastify';
3
+ import type { BlindedIndexQuery, BlindedIndexQueryPage } from '../lib/blindedIndex.js';
4
+ import type { SpaceDescription, CollectionDescription, CollectionSummary, CollectionResourcesList, ResourceResult, ResourceMetadata, ResourceMetadataCustom, ResourceInput, ImportStats, PolicyDocument, BackendDescriptor, BackendUsage, CollectionUsage, StorageBackend, StoredBackendRecord, KeystoreConfig, KmsKeyRecord, RevocationRecord, RevocationScope, CapabilitySummary, IDID } from '../types.js';
5
+ /**
6
+ * The on-disk shape of a Resource's metadata sidecar (`.meta.<resourceId>.json`,
7
+ * see `metaSidecarFileName`). Only the server-managed timestamps, the monotonic
8
+ * `version`, and the user-writable `custom` object are persisted; `contentType`
9
+ * / `size` are always derived from the stored representation, never duplicated
10
+ * here.
11
+ *
12
+ * `createdBy` is the DID of whoever created the Resource (spec "Resource
13
+ * Metadata Data Model"): an OPTIONAL server-managed property, absent when no
14
+ * creator was recorded.
15
+ *
16
+ * `version` is the per-Resource monotonic counter that backs the HTTP `ETag`
17
+ * strong validator (see `formatEtag`): it starts at 1 on first content write and
18
+ * increments on each subsequent content write. It is `undefined` only for a
19
+ * Resource written before versioning existed (a legacy sidecar), in which case
20
+ * the backend treats the current version as 0.
21
+ *
22
+ * `metaVersion` is the independent monotonic counter for the `/meta`
23
+ * sub-resource (spec V2 metadata versioning): it starts at 1 on first metadata
24
+ * write and increments on each subsequent one, backing the `/meta` ETag. It is
25
+ * kept separate from `version` so a metadata-only edit does not bump the content
26
+ * ETag (preserving the content-ETag contract), and is `undefined` until the
27
+ * first metadata write. A content write preserves it unchanged.
28
+ *
29
+ * `deleted` marks a **tombstone**: a soft delete that drops the content
30
+ * representation but keeps the sidecar so the change feed (replication) still
31
+ * surfaces it. A
32
+ * tombstone has no `r.<id>...` content file, so it is invisible to every normal
33
+ * read path (which gates on the content file via `_findFile`); only the
34
+ * (future) change feed reads it. `contentType` records the representation's
35
+ * last-known content-type, which the content filename no longer carries once it
36
+ * is gone -- present only on a tombstone (a live Resource derives its
37
+ * content-type from the filename).
38
+ */
39
+ interface MetaSidecar {
40
+ createdAt: string;
41
+ updatedAt: string;
42
+ createdBy?: IDID;
43
+ version?: number;
44
+ metaVersion?: number;
45
+ custom?: ResourceMetadataCustom | Record<string, unknown>;
46
+ epoch?: string;
47
+ deleted?: boolean;
48
+ contentType?: string;
49
+ }
50
+ /**
51
+ * Builds the on-disk filename for a Resource's metadata sidecar:
52
+ * `.meta.<resourceId>.json`. A dot-file kept alongside the resource
53
+ * representation in the Collection dir (the same convention as `.policy.` /
54
+ * `.collection.`), holding the timestamps and user-writable `custom` object.
55
+ * @param resourceId {string}
56
+ * @returns {string}
57
+ */
58
+ export declare function metaSidecarFileName(resourceId: string): string;
59
+ export declare class FileSystemBackend implements StorageBackend {
60
+ spacesDir: string;
61
+ /**
62
+ * Root of the WebKMS keystore tree (`data/keystores/<localId>/`), a sibling
63
+ * of `spacesDir` -- the `/kms` facet is deliberately separable from Spaces
64
+ * (own route family, own storage tree).
65
+ */
66
+ keystoresDir: string;
67
+ /**
68
+ * Root of the Space zcap revocation tree (`data/space-revocations/<spaceId>/`),
69
+ * a sibling of `spacesDir` rather than a subdirectory of each Space. Space
70
+ * revocations deliberately live OUTSIDE the Space's own directory because
71
+ * `listCollections` and `_countLiveResources` treat every subdirectory of a
72
+ * Space dir as a Collection -- a `revocations/` dir nested inside a Space would
73
+ * surface as a phantom Collection (and could collide with a real one), so it
74
+ * gets its own root.
75
+ */
76
+ spaceRevocationsDir: string;
77
+ logger: FastifyBaseLogger;
78
+ /**
79
+ * Per-Space storage capacity, in bytes (spec "Quotas"). `undefined` means no
80
+ * configured limit -- the backend reports an unlimited quota (state always
81
+ * `ok`) and skips write-path enforcement. A finite value drives the
82
+ * `near-limit` / `over-quota` state thresholds (see `reportUsage`) and is
83
+ * enforced on the write path: `writeResource` and `importSpace` reject writes
84
+ * that would push a Space over capacity with `QuotaExceededError` (507). The
85
+ * constructor normalizes a non-finite ctor option (`Infinity`) to `undefined`.
86
+ */
87
+ capacityBytes?: number;
88
+ /**
89
+ * Largest single upload the backend accepts, in bytes (spec "Quotas", the
90
+ * `maxUploadBytes` constraint). `undefined` means no per-upload cap. Distinct
91
+ * from `capacityBytes` (the cumulative per-Space quota): a write larger than
92
+ * this cap is rejected with `PayloadTooLargeError` (413) even when the Space
93
+ * has ample headroom, while smaller writes still succeed. Advertised in quota
94
+ * reports under `constraints.maxUploadBytes` and enforced on `writeResource`.
95
+ * The constructor normalizes an unset ctor option to
96
+ * {@link DEFAULT_MAX_UPLOAD_BYTES} (a default-on cap) and a non-finite option
97
+ * (`Infinity`) to `undefined` (explicitly no cap).
98
+ */
99
+ maxUploadBytes?: number;
100
+ /**
101
+ * Max Spaces a single controller may create (spec "Quotas", a default-on
102
+ * count quota). `undefined` means no cap. Enforced on the Space create path
103
+ * (`writeSpace`): a new Space whose `controller` already owns this many
104
+ * Spaces is rejected with `CountQuotaExceededError` (507); overwriting an
105
+ * existing Space never trips it. The constructor normalizes an unset ctor
106
+ * option to {@link DEFAULT_MAX_SPACES_PER_CONTROLLER} and a non-finite option
107
+ * (`Infinity`) to `undefined` (explicitly no cap). Soft under concurrency,
108
+ * like the byte quota.
109
+ */
110
+ maxSpacesPerController?: number;
111
+ /**
112
+ * Max Collections a single Space may hold (spec "Quotas", a default-on count
113
+ * quota). `undefined` means no cap. Enforced on the Collection create path
114
+ * (`writeCollection`); overwriting an existing Collection description never
115
+ * trips it. Normalized like {@link maxSpacesPerController}.
116
+ */
117
+ maxCollectionsPerSpace?: number;
118
+ /**
119
+ * Max live Resources a single Space may hold across all its Collections (spec
120
+ * "Quotas", a default-on count quota). `undefined` means no cap. Enforced on
121
+ * the Resource create path (`writeResource`); a tombstone does not count, and
122
+ * a write over an existing live Resource never trips it. Normalized like
123
+ * {@link maxSpacesPerController}.
124
+ */
125
+ maxResourcesPerSpace?: number;
126
+ /**
127
+ * Per-Resource write serialization (the `conditional-writes` feature). A
128
+ * content write or delete that carries a precondition reads the current
129
+ * `version`, evaluates `If-Match` / `If-None-Match`, and writes -- all under
130
+ * this lock, keyed per Resource -- so two concurrent writers cannot both
131
+ * observe the same prior version and both succeed. Single-instance only.
132
+ */
133
+ private _writeMutex;
134
+ /**
135
+ * Per-Space usage totals for the write-path quota pre-flight, so
136
+ * `_assertSpaceHeadroom` does not spawn `du` (a whole-Space tree walk) on
137
+ * every resource write. Entries live `QUOTA_USAGE_CACHE_TTL` ms; each
138
+ * accepted write adds its incoming bytes to the cached total, and deletes
139
+ * invalidate the Space's entry. Quota reports (`reportUsage`) always
140
+ * re-measure. Single-instance only, like `_writeMutex`.
141
+ */
142
+ private _usageCache;
143
+ constructor({ dataDir, logger, capacityBytes, maxUploadBytes, maxSpacesPerController, maxCollectionsPerSpace, maxResourcesPerSpace }: {
144
+ dataDir: string;
145
+ logger?: FastifyBaseLogger;
146
+ capacityBytes?: number;
147
+ maxUploadBytes?: number;
148
+ maxSpacesPerController?: number;
149
+ maxCollectionsPerSpace?: number;
150
+ maxResourcesPerSpace?: number;
151
+ });
152
+ /**
153
+ * Self-description advertised at `GET /space/:spaceId/backends`. The
154
+ * filesystem backend is the single server-configured default: it stores both
155
+ * JSON documents and binary blobs on disk, so its data survives restarts.
156
+ *
157
+ * It advertises the `conditional-writes` affordance: it exposes a per-Resource
158
+ * `version` as an HTTP `ETag` validator and honors `If-Match` / `If-None-Match`
159
+ * write preconditions atomically (returning `412 precondition-failed` on a
160
+ * mismatch). The remaining `features` vocabulary token (`chunked-streams`)
161
+ * is not implemented yet and is added when it lands.
162
+ * (Client-side encryption is deliberately not a backend feature: encrypted
163
+ * documents are opaque client-encrypted JSON this backend already stores
164
+ * faithfully, with no server cooperation.)
165
+ * @returns {Required<Omit<BackendDescriptor, 'provider' | 'connection'>>}
166
+ */
167
+ describe(): Required<Omit<BackendDescriptor, 'provider' | 'connection'>>;
168
+ /**
169
+ * Measures disk usage under the Space dir with `du`, returning the grand total
170
+ * and a per-Collection breakdown in one pass. `du -d 1 -B 1` (GNU coreutils)
171
+ * reports each immediate subdirectory (one per Collection) plus the Space dir
172
+ * itself (the total, which also covers top-level Space files such as the
173
+ * `.space.` / `.policy.` documents), all in bytes. An absent Space dir (ENOENT
174
+ * before the dir is provisioned) reports zero usage rather than throwing.
175
+ * @param spaceDir {string}
176
+ * @returns {Promise<{ total: number, byCollection: CollectionUsage[] }>}
177
+ */
178
+ _diskUsage(spaceDir: string): Promise<{
179
+ total: number;
180
+ byCollection: CollectionUsage[];
181
+ }>;
182
+ /**
183
+ * Measures the bytes this Space consumes on disk for the Space Quota report
184
+ * (spec "Quotas"). `usageBytes` is the `du` total under the Space dir
185
+ * (Collection dirs plus top-level Space files); `usageByCollection` breaks the
186
+ * per-Collection totals out (they sum to slightly less than `usageBytes`,
187
+ * since the Space-level files belong to no Collection).
188
+ *
189
+ * The per-Collection `usageByCollection` breakdown is included only when
190
+ * `includeCollections` is set -- the spec's `?include=collections` opt-in (see
191
+ * the `quotas` handler, which now tolerates the query string via the
192
+ * `allowTargetQuery` ZCap path). On the filesystem the breakdown is free (the
193
+ * one `du -d 1` pass yields it alongside the total), but it is still omitted by
194
+ * default to keep the hot-path payload lean and match the wire contract.
195
+ *
196
+ * `state` / `restrictedActions` derive from usage vs `capacityBytes`: an
197
+ * unlimited backend is always `ok`; a finite capacity yields `near-limit` at
198
+ * `QUOTA_NEAR_LIMIT_FRACTION` of capacity and `over-quota` (with reads/deletes
199
+ * still allowed, but `POST`/`PUT` restricted) at or above full.
200
+ * @param options {object}
201
+ * @param options.spaceId {string}
202
+ * @param [options.includeCollections] {boolean} include the per-Collection
203
+ * breakdown (spec `?include=collections`)
204
+ * @returns {Promise<BackendUsage>}
205
+ */
206
+ reportUsage({ spaceId, includeCollections }: {
207
+ spaceId: string;
208
+ includeCollections?: boolean;
209
+ }): Promise<BackendUsage>;
210
+ /**
211
+ * Measures the bytes a single Collection consumes on disk for the
212
+ * per-Collection Quota report (spec "Quotas", `GET /space/{id}/{cid}/quota`).
213
+ * `usageBytes` is scoped to the Collection (its slice of the one-pass
214
+ * `_diskUsage` breakdown; zero if the Collection dir is empty or absent),
215
+ * while `state` / `limit` / `restrictedActions` describe the backend's overall
216
+ * condition (derived from the Space total -- the quota is a per-backend limit,
217
+ * not per-Collection). The per-Collection `usageByCollection` breakdown is
218
+ * omitted (a single Collection is the whole report).
219
+ * @param options {object}
220
+ * @param options.spaceId {string}
221
+ * @param options.collectionId {string}
222
+ * @returns {Promise<BackendUsage>}
223
+ */
224
+ reportCollectionUsage({ spaceId, collectionId }: {
225
+ spaceId: string;
226
+ collectionId: string;
227
+ }): Promise<BackendUsage>;
228
+ /**
229
+ * Builds the backend-identity and condition fields shared by the Space and
230
+ * per-Collection quota reports. `usageBytes` is what the report shows (the
231
+ * Space total or a single Collection's slice); `spaceTotalBytes` drives the
232
+ * `state` / `restrictedActions`, which are backend-wide (the quota is a
233
+ * per-Space limit) and so always measured against the Space total. The
234
+ * `constraints.maxUploadBytes` cap is advertised when configured.
235
+ * @param options {object}
236
+ * @param options.usageBytes {number} the usage figure to report
237
+ * @param options.spaceTotalBytes {number} the Space total, for state
238
+ * @returns {Omit<BackendUsage, 'measuredAt' | 'usageByCollection'>}
239
+ */
240
+ _backendUsageFields({ usageBytes, spaceTotalBytes }: {
241
+ usageBytes: number;
242
+ spaceTotalBytes: number;
243
+ }): Omit<BackendUsage, 'measuredAt' | 'usageByCollection'>;
244
+ /**
245
+ * Quota pre-flight for the write path (spec "Quotas"): measures the Space's
246
+ * current on-disk usage and returns the remaining headroom in bytes against
247
+ * `capacityBytes`. Throws `QuotaExceededError` (507) when the Space is already
248
+ * at or over capacity, or when a known `incomingBytes` would not fit. Callers
249
+ * pass the configured `capacityBytes` explicitly (an unlimited backend skips
250
+ * enforcement entirely and never calls this).
251
+ *
252
+ * This is a soft limit under concurrency: two simultaneous writes can each pass
253
+ * against the same usage snapshot and jointly overshoot. The per-write
254
+ * streaming guard (`_quotaGuard`) still bounds each individual write.
255
+ *
256
+ * The `du` measurement (a whole-Space tree walk) is cached per Space for
257
+ * `QUOTA_USAGE_CACHE_TTL` ms (see `_usageCache`): between re-measurements
258
+ * each accepted write's `incomingBytes` is added to the cached total, so a
259
+ * burst of writes costs one tree walk, not one per write.
260
+ * @param options {object}
261
+ * @param options.spaceId {string}
262
+ * @param options.capacityBytes {number} the configured per-Space limit
263
+ * @param [options.incomingBytes] {number} known size of the pending write
264
+ * @returns {Promise<number>} remaining headroom in bytes
265
+ */
266
+ _assertSpaceHeadroom({ spaceId, capacityBytes, incomingBytes }: {
267
+ spaceId: string;
268
+ capacityBytes: number;
269
+ incomingBytes?: number;
270
+ }): Promise<number>;
271
+ /**
272
+ * A pass-through `Transform` that counts the bytes flowing through it and
273
+ * aborts the pipeline with `QuotaExceededError` (507) once the cumulative total
274
+ * would exceed `headroomBytes`. Hard-caps a streamed blob write whose size is
275
+ * not known up front (so the pre-flight check alone cannot catch it), e.g. a
276
+ * multipart upload or a body without `Content-Length`.
277
+ * @param options {object}
278
+ * @param options.spaceId {string}
279
+ * @param options.capacityBytes {number} the configured per-Space limit
280
+ * @param options.headroomBytes {number} max bytes this write may add
281
+ * @returns {Transform}
282
+ */
283
+ _quotaGuard({ spaceId, capacityBytes, headroomBytes }: {
284
+ spaceId: string;
285
+ capacityBytes: number;
286
+ headroomBytes: number;
287
+ }): Transform;
288
+ /**
289
+ * A pass-through `Transform` that counts the bytes flowing through it and
290
+ * aborts the pipeline with `PayloadTooLargeError` (413) once the cumulative
291
+ * total exceeds `maxUploadBytes`. Caps a single streamed upload whose size is
292
+ * not known up front (e.g. a multipart part or a body without
293
+ * `Content-Length`), independently of the cumulative Space quota.
294
+ * @param options {object}
295
+ * @param options.maxUploadBytes {number} the per-upload cap in bytes
296
+ * @returns {Transform}
297
+ */
298
+ _uploadCapGuard({ maxUploadBytes }: {
299
+ maxUploadBytes: number;
300
+ }): Transform;
301
+ /**
302
+ * Defense in depth: asserts that a built path stays within the given storage
303
+ * root (`spacesDir` by default; `keystoresDir` for the keystore tree), so a
304
+ * malformed id that somehow slips past request-layer validation can never
305
+ * escape it (path traversal). The request and tar-import layers reject such
306
+ * ids first; this is the last line of defense.
307
+ * @param targetPath {string}
308
+ * @param [rootDir] {string} the containing root; defaults to `spacesDir`
309
+ * @returns {void}
310
+ */
311
+ _assertContained(targetPath: string, rootDir?: string): void;
312
+ _spaceDir(spaceId: string): string;
313
+ /**
314
+ * The directory holding one Space's zcap revocation records, under the
315
+ * sibling `spaceRevocationsDir` root (see that property's doc for why it is
316
+ * not inside the Space dir), guarded against escaping it.
317
+ * @param spaceId {string}
318
+ * @returns {string}
319
+ */
320
+ _spaceRevocationDir(spaceId: string): string;
321
+ _collectionDir({ spaceId, collectionId }: {
322
+ spaceId: string;
323
+ collectionId: string;
324
+ }): string;
325
+ /**
326
+ * @param options {object}
327
+ * @param options.spaceId {string}
328
+ * @returns {Promise<string>} Created space storage directory path.
329
+ */
330
+ _ensureSpaceDir({ spaceId }: {
331
+ spaceId: string;
332
+ }): Promise<string>;
333
+ /**
334
+ * @param options {object}
335
+ * @param options.spaceId {string}
336
+ * @param options.collectionId {string}
337
+ * @returns {Promise<string>} Created collection storage directory path.
338
+ */
339
+ _ensureCollectionDir({ spaceId, collectionId }: {
340
+ spaceId: string;
341
+ collectionId: string;
342
+ }): Promise<string>;
343
+ /**
344
+ * Lists every on-disk file belonging to a single Resource: the
345
+ * representation(s) whose name starts with `r.<encodedResourceId>.` in the
346
+ * Collection dir. The trailing `.` anchors to the filename's segment boundary
347
+ * (`r.<encodedResourceId>.<encodedType>.<ext>`) so a resourceId that is a
348
+ * prefix of another (e.g. `note` vs `notebook`) does not match the longer one;
349
+ * the id is dot-escaped to match the stored name (see `fileNameFor`). A Resource
350
+ * normally has a single current representation, so this usually returns one
351
+ * path; it returns more only transiently while a prior representation under a
352
+ * different content-type is being pruned. An absent Collection dir resolves an
353
+ * empty list (it holds no such files).
354
+ * @param options {object}
355
+ * @param options.collectionDir {string}
356
+ * @param options.resourceId {string}
357
+ * @returns {Promise<string[]>} full paths, in directory order
358
+ */
359
+ _resourceFilesFor({ collectionDir, resourceId }: {
360
+ collectionDir: string;
361
+ resourceId: string;
362
+ }): Promise<string[]>;
363
+ /**
364
+ * @param options {object}
365
+ * @param options.collectionDir {string}
366
+ * @param options.resourceId {string}
367
+ * @returns {Promise<string|undefined>} First matching resource file path.
368
+ */
369
+ _findFile({ collectionDir, resourceId }: {
370
+ collectionDir: string;
371
+ resourceId: string;
372
+ }): Promise<string | undefined>;
373
+ /**
374
+ * @param options {object}
375
+ * @param options.spaceId {string}
376
+ * @param options.spaceDescription {SpaceDescription}
377
+ * @param [options.createdBy] {string} DID of the invoker, recorded as the
378
+ * Space's `createdBy` on first write only
379
+ * @returns {Promise<void>} Resolved value is implementation-defined and ignored.
380
+ */
381
+ writeSpace({ spaceId, spaceDescription, createdBy }: {
382
+ spaceId: string;
383
+ spaceDescription: SpaceDescription;
384
+ createdBy?: IDID;
385
+ }): Promise<void>;
386
+ /**
387
+ * @param options {object}
388
+ * @param options.spaceId {string}
389
+ * @returns {Promise<SpaceDescription|undefined>}
390
+ * Resolves falsy when the Space does not exist (must not throw).
391
+ */
392
+ getSpaceDescription({ spaceId }: {
393
+ spaceId: string;
394
+ }): Promise<SpaceDescription | undefined>;
395
+ /**
396
+ * Removes a Space and everything scoped to it. The Space's zcap revocations
397
+ * live in a sibling root (`spaceRevocationsDir`), not under the Space dir, so
398
+ * they are removed explicitly here rather than falling out of the Space dir
399
+ * rm.
400
+ * @param options {object}
401
+ * @param options.spaceId {string}
402
+ * @returns {Promise<void>}
403
+ */
404
+ deleteSpace({ spaceId }: {
405
+ spaceId: string;
406
+ }): Promise<void>;
407
+ /**
408
+ * Enumerates every Space stored on this backend (each immediate subdirectory
409
+ * of the spaces root), sorted by Space id. An absent spaces root (nothing
410
+ * stored yet) resolves an empty list, not an error; a directory without a
411
+ * readable description file (e.g. a partially deleted Space) is skipped.
412
+ * @returns {Promise<SpaceDescription[]>}
413
+ */
414
+ listSpaces(): Promise<SpaceDescription[]>;
415
+ /**
416
+ * Counts the live Resources across every Collection of a Space, for the
417
+ * Resource count quota (`maxResourcesPerSpace`). Enumerates each Collection
418
+ * dir and counts distinct Resource ids that have a live representation file
419
+ * (`r.<id>...`); a tombstone (a `.meta.` sidecar with no `r.` file) does not
420
+ * count. An absent Space dir counts zero (not yet provisioned). Soft under
421
+ * concurrency, like the byte quota -- measured at check time by enumeration.
422
+ * @param options {object}
423
+ * @param options.spaceId {string}
424
+ * @returns {Promise<number>} the number of live Resources
425
+ */
426
+ _countLiveResources({ spaceId }: {
427
+ spaceId: string;
428
+ }): Promise<number>;
429
+ /**
430
+ * @param options {object}
431
+ * @param options.spaceId {string}
432
+ * @returns {Promise<CollectionSummary[]>}
433
+ */
434
+ listCollections({ spaceId }: {
435
+ spaceId: string;
436
+ }): Promise<CollectionSummary[]>;
437
+ /**
438
+ * @param options {object}
439
+ * @param options.spaceId {string}
440
+ * @returns {Promise<Readable>} tar-stream pack
441
+ */
442
+ exportSpace({ spaceId }: {
443
+ spaceId: string;
444
+ }): Promise<Readable>;
445
+ /**
446
+ * Merges a WAS space-export tarball into an existing Space (collections and
447
+ * resources that already exist are skipped, not overwritten).
448
+ * @param options {object}
449
+ * @param options.spaceId {string}
450
+ * @param options.tarStream {Readable}
451
+ * @returns {Promise<ImportStats>}
452
+ */
453
+ importSpace({ spaceId, tarStream }: {
454
+ spaceId: string;
455
+ tarStream: Readable;
456
+ }): Promise<ImportStats>;
457
+ /**
458
+ * @param options {object}
459
+ * @param options.spaceId {string}
460
+ * @param options.collectionId {string}
461
+ * @param options.collectionDescription {CollectionDescription}
462
+ * @param [options.createdBy] {string} DID of the invoker, recorded as the
463
+ * Collection's `createdBy` on first write only
464
+ * @param [options.ifMatch] {string} an `If-Match` compare-and-swap on the
465
+ * current description version (the `key-epochs` feature); a stale validator
466
+ * throws `PreconditionFailedError` (412)
467
+ * @returns {Promise<{ version: number }>} the Collection's new description
468
+ * version (the `ETag` validator)
469
+ */
470
+ writeCollection({ spaceId, collectionId, collectionDescription, createdBy, ifMatch, assertTransition }: {
471
+ spaceId: string;
472
+ collectionId: string;
473
+ collectionDescription: CollectionDescription;
474
+ createdBy?: IDID;
475
+ ifMatch?: string;
476
+ assertTransition?: (prior?: CollectionDescription & {
477
+ descriptionVersion?: number;
478
+ }) => void;
479
+ }): Promise<{
480
+ version: number;
481
+ }>;
482
+ /**
483
+ * Builds the per-Collection-description serialization key for `_writeMutex`
484
+ * (`desc:<spaceId>/<collectionId>`), so a Collection Description compare-and-
485
+ * swap serializes with itself while staying disjoint from the per-Resource and
486
+ * unique-scan lock namespaces.
487
+ * @param options {object}
488
+ * @param options.spaceId {string}
489
+ * @param options.collectionId {string}
490
+ * @returns {string}
491
+ */
492
+ _collectionDescLockKey({ spaceId, collectionId }: {
493
+ spaceId: string;
494
+ collectionId: string;
495
+ }): string;
496
+ /**
497
+ * Writes a Collection's description file (creating the Collection dir if
498
+ * needed), with NO count-quota check. The count guard lives in the public
499
+ * `writeCollection`; `importSpace` tracks the Space's Collection count itself
500
+ * (measured once up front) and calls this directly, so it does not
501
+ * re-enumerate the Space per created Collection.
502
+ * @param options {object}
503
+ * @param options.spaceId {string}
504
+ * @param options.collectionId {string}
505
+ * @param options.collectionDescription {CollectionDescription}
506
+ * @param [options.descriptionVersion] {number} the monotonic description
507
+ * version to persist (the `ETag` validator; the `key-epochs` feature). Kept
508
+ * OUT of the wire body -- stored under a reserved `_version` member that
509
+ * `getCollectionDescription` strips and re-surfaces as `descriptionVersion`.
510
+ * When omitted (the import path), a `_version` already on the incoming
511
+ * description is preserved, else it defaults to `1`.
512
+ * @returns {Promise<void>}
513
+ */
514
+ _persistCollection({ spaceId, collectionId, collectionDescription, descriptionVersion }: {
515
+ spaceId: string;
516
+ collectionId: string;
517
+ collectionDescription: CollectionDescription;
518
+ descriptionVersion?: number;
519
+ }): Promise<void>;
520
+ /**
521
+ * @param options {object}
522
+ * @param options.spaceId {string}
523
+ * @param options.collectionId {string}
524
+ * @returns {Promise<CollectionDescription|undefined>}
525
+ * Resolves falsy when the Collection does not exist (must not throw).
526
+ */
527
+ getCollectionDescription({ spaceId, collectionId }: {
528
+ spaceId: string;
529
+ collectionId: string;
530
+ }): Promise<(CollectionDescription & {
531
+ descriptionVersion?: number;
532
+ }) | undefined>;
533
+ /**
534
+ * @param options {object}
535
+ * @param options.spaceId {string}
536
+ * @param options.collectionId {string}
537
+ * @returns {Promise<void>}
538
+ */
539
+ deleteCollection({ spaceId, collectionId }: {
540
+ spaceId: string;
541
+ collectionId: string;
542
+ }): Promise<void>;
543
+ /**
544
+ * Lists a Collection's Resources, OPTIONALLY cursor-paginated (spec
545
+ * "Pagination"). Items are returned in a stable total order -- ascending by
546
+ * `resourceId`, read straight from the `r.<resourceId>.<type>.<ext>` filename
547
+ * (the keyset). A `cursor` resumes the scan at the first id strictly greater
548
+ * than the cursor's anchor, so paging stays correct even if the anchor id was
549
+ * deleted between pages; `limit` bounds the page (clamped to
550
+ * `[1, MAX_PAGE_SIZE]`, default `DEFAULT_PAGE_SIZE`). `next` is built (and
551
+ * present) only when a further page may follow.
552
+ * @param options {object}
553
+ * @param options.spaceId {string}
554
+ * @param options.collectionId {string}
555
+ * @param [options.limit] {number} requested page size
556
+ * @param [options.cursor] {string} opaque cursor from a prior page's `next`
557
+ * @returns {Promise<CollectionResourcesList>}
558
+ */
559
+ listCollectionItems({ spaceId, collectionId, limit, cursor, collectionDescription: providedDescription }: {
560
+ spaceId: string;
561
+ collectionId: string;
562
+ limit?: number;
563
+ cursor?: string;
564
+ collectionDescription?: CollectionDescription;
565
+ }): Promise<CollectionResourcesList>;
566
+ /**
567
+ * Writes a resource representation (JSON value or byte stream) to disk, under
568
+ * the per-Resource write lock (the `conditional-writes` feature), and bumps
569
+ * the Resource's monotonic `version`. The new version is returned so the
570
+ * request layer can surface it as the response `ETag`.
571
+ *
572
+ * When a conditional-write precondition is supplied it is evaluated against
573
+ * the Resource's current state atomically with the write (under the lock),
574
+ * throwing `PreconditionFailedError` (412) on a mismatch:
575
+ * - `ifNoneMatch` (a create-if-absent `If-None-Match: *`) fails if the
576
+ * Resource already exists.
577
+ * - `ifMatch` (an update-if-unchanged `If-Match: "<etag>"`) fails if the
578
+ * Resource is absent or its current `ETag` does not equal `ifMatch`.
579
+ * `ifNoneMatch` takes precedence when both are supplied (RFC9110).
580
+ *
581
+ * @param options {object}
582
+ * @param options.spaceId {string}
583
+ * @param options.collectionId {string}
584
+ * @param options.resourceId {string}
585
+ * @param options.input {ResourceInput}
586
+ * @param [options.createdBy] {string} DID of the invoker, recorded as the
587
+ * Resource's `createdBy` on first write only
588
+ * @param [options.ifMatch] {string} `If-Match` precondition (a quoted ETag)
589
+ * @param [options.ifNoneMatch] {boolean} `If-None-Match: *` (create-if-absent)
590
+ * @returns {Promise<{ version: number }>} the Resource's new version
591
+ */
592
+ writeResource({ spaceId, collectionId, resourceId, input, createdBy, epoch, ifMatch, ifNoneMatch }: {
593
+ spaceId: string;
594
+ collectionId: string;
595
+ resourceId: string;
596
+ input: ResourceInput;
597
+ createdBy?: IDID;
598
+ epoch?: string;
599
+ ifMatch?: string;
600
+ ifNoneMatch?: boolean;
601
+ }): Promise<{
602
+ version: number;
603
+ }>;
604
+ /**
605
+ * The critical section of `writeResource`, run under the per-Resource lock:
606
+ * evaluates any precondition, writes the representation, prunes a stale
607
+ * representation under a different content-type, and persists the bumped
608
+ * `version` in the sidecar. See `writeResource` for the parameters.
609
+ * @returns {Promise<{ version: number }>}
610
+ */
611
+ private _writeResourceLocked;
612
+ /**
613
+ * Builds the per-Resource serialization key for `_writeMutex`
614
+ * (`<spaceId>/<collectionId>/<resourceId>`), so conditional writes to distinct
615
+ * Resources run concurrently while writes to the same Resource are ordered.
616
+ * @param options {object}
617
+ * @param options.spaceId {string}
618
+ * @param options.collectionId {string}
619
+ * @param options.resourceId {string}
620
+ * @returns {string}
621
+ */
622
+ _resourceLockKey({ spaceId, collectionId, resourceId }: {
623
+ spaceId: string;
624
+ collectionId: string;
625
+ resourceId: string;
626
+ }): string;
627
+ /**
628
+ * The Collection-level mutex key, held by unique-blinded-attribute writes to
629
+ * serialize their conflict scans. Namespaced (`unique:` prefix) so it can
630
+ * never collide with a per-Resource key.
631
+ * @param options {object}
632
+ * @param options.spaceId {string}
633
+ * @param options.collectionId {string}
634
+ * @returns {string}
635
+ */
636
+ _collectionLockKey({ spaceId, collectionId }: {
637
+ spaceId: string;
638
+ collectionId: string;
639
+ }): string;
640
+ /**
641
+ * Evaluates a conditional-write precondition against a Resource's current
642
+ * on-disk state. MUST be called inside the per-Resource write lock so the
643
+ * check is atomic with the write that follows. Throws
644
+ * `PreconditionFailedError` (412) when the precondition is not met.
645
+ * @param options {object}
646
+ * @param options.collectionDir {string}
647
+ * @param options.resourceId {string}
648
+ * @param [options.ifMatch] {string} a quoted ETag (`If-Match`)
649
+ * @param [options.ifNoneMatch] {boolean} `If-None-Match: *` (create-if-absent)
650
+ * @returns {Promise<void>}
651
+ */
652
+ _assertWritePrecondition({ collectionDir, resourceId, ifMatch, ifNoneMatch }: {
653
+ collectionDir: string;
654
+ resourceId: string;
655
+ ifMatch?: string;
656
+ ifNoneMatch?: boolean;
657
+ }): Promise<void>;
658
+ /**
659
+ * @param options {object}
660
+ * @param options.spaceId {string}
661
+ * @param options.collectionId {string}
662
+ * @param options.resourceId {string}
663
+ * @param [options.contentType] {string}
664
+ * @returns {Promise<ResourceResult>} includes the Resource's current
665
+ * `version` (the ETag validator) when one is recorded in its sidecar.
666
+ */
667
+ getResource({ spaceId, collectionId, resourceId }: {
668
+ spaceId: string;
669
+ collectionId: string;
670
+ resourceId: string;
671
+ /**
672
+ * `contentType` is advisory and ignored for lookup: a Resource has a
673
+ * single current representation, resolved by `resourceId` alone.
674
+ */
675
+ contentType?: string;
676
+ }): Promise<ResourceResult>;
677
+ /**
678
+ * Builds the on-disk path for a Resource's metadata sidecar
679
+ * (`.meta.<resourceId>.json`) in its Collection dir.
680
+ * @param options {object}
681
+ * @param options.collectionDir {string}
682
+ * @param options.resourceId {string}
683
+ * @returns {string}
684
+ */
685
+ _metaSidecarPath({ collectionDir, resourceId }: {
686
+ collectionDir: string;
687
+ resourceId: string;
688
+ }): string;
689
+ /**
690
+ * Reads a Resource's metadata sidecar. Resolves `undefined` when none has been
691
+ * written yet (e.g. a Resource created before sidecars existed).
692
+ * @param options {object}
693
+ * @param options.collectionDir {string}
694
+ * @param options.resourceId {string}
695
+ * @returns {Promise<MetaSidecar|undefined>}
696
+ */
697
+ _readMetaSidecar({ collectionDir, resourceId }: {
698
+ collectionDir: string;
699
+ resourceId: string;
700
+ }): Promise<MetaSidecar | undefined>;
701
+ /**
702
+ * Writes a Resource's metadata sidecar (full replacement).
703
+ * @param options {object}
704
+ * @param options.collectionDir {string}
705
+ * @param options.resourceId {string}
706
+ * @param options.sidecar {MetaSidecar}
707
+ * @returns {Promise<void>}
708
+ */
709
+ _writeMetaSidecar({ collectionDir, resourceId, sidecar }: {
710
+ collectionDir: string;
711
+ resourceId: string;
712
+ sidecar: MetaSidecar;
713
+ }): Promise<void>;
714
+ /**
715
+ * Reads the metadata of a Resource's current representation: the REQUIRED
716
+ * server-managed fields (`contentType`, `size`, both derived from the stored
717
+ * file), plus the OPTIONAL `createdAt` / `updatedAt` timestamps and the
718
+ * user-writable `custom` object read from the sidecar. For a Resource written
719
+ * before sidecars existed, the timestamps fall back to the file's birth/modify
720
+ * times and `custom` is omitted. Resolves `undefined` when the Resource is
721
+ * absent (including a delete race on `stat`).
722
+ *
723
+ * Also surfaces the Resource's content `version` and its `metaVersion` (the
724
+ * two ETag validators) when recorded in the sidecar, so the request layer can
725
+ * set the `ETag` header: HEAD / the resource itself use the content `version`,
726
+ * while `GET /meta` uses `metaVersion`. Both are out-of-band fields the request
727
+ * layer reads for the header; neither is part of the Resource Metadata wire
728
+ * body.
729
+ * @param options {object}
730
+ * @param options.spaceId {string}
731
+ * @param options.collectionId {string}
732
+ * @param options.resourceId {string}
733
+ * @returns {Promise<(ResourceMetadata & { version?: number; metaVersion?:
734
+ * number }) | undefined>}
735
+ */
736
+ getResourceMetadata({ spaceId, collectionId, resourceId }: {
737
+ spaceId: string;
738
+ collectionId: string;
739
+ resourceId: string;
740
+ }): Promise<(ResourceMetadata & {
741
+ version?: number;
742
+ metaVersion?: number;
743
+ }) | undefined>;
744
+ /**
745
+ * Replaces the user-writable `custom` object of a Resource's metadata sidecar
746
+ * (full replacement; `{}` clears it), bumping `updatedAt` and the independent
747
+ * `metaVersion` (the `/meta` ETag). Does not create a Resource: resolves
748
+ * `undefined` when the Resource is absent so the handler can 404. The content
749
+ * `version` and the two REQUIRED server-managed fields are untouched (a
750
+ * metadata write does not change the stored representation, preserving the
751
+ * content ETag contract). On an encrypted Collection `custom` is the opaque
752
+ * encryption envelope, stored verbatim.
753
+ *
754
+ * Runs under the per-Resource write lock -- the same lock content writes take
755
+ * -- so an `If-Match` / `If-None-Match` precondition (evaluated on
756
+ * `metaVersion`) is atomic with the write and serializes with concurrent
757
+ * content/metadata writes to the same Resource. A precondition mismatch throws
758
+ * `PreconditionFailedError` (412).
759
+ * @param options {object}
760
+ * @param options.spaceId {string}
761
+ * @param options.collectionId {string}
762
+ * @param options.resourceId {string}
763
+ * @param options.custom {ResourceMetadataCustom | Record<string, unknown>}
764
+ * @param [options.ifMatch] {string} `If-Match` on the current `metaVersion`
765
+ * @param [options.ifNoneMatch] {boolean} `If-None-Match: *` -- write only if
766
+ * no metadata has been written yet (`metaVersion` unset)
767
+ * @returns {Promise<{ metaVersion: number } | undefined>} the new
768
+ * `metaVersion`, or `undefined` when the Resource does not exist
769
+ */
770
+ writeResourceMetadata({ spaceId, collectionId, resourceId, custom, epoch, ifMatch, ifNoneMatch }: {
771
+ spaceId: string;
772
+ collectionId: string;
773
+ resourceId: string;
774
+ custom: ResourceMetadataCustom | Record<string, unknown>;
775
+ epoch?: string;
776
+ ifMatch?: string;
777
+ ifNoneMatch?: boolean;
778
+ }): Promise<{
779
+ metaVersion: number;
780
+ } | undefined>;
781
+ /**
782
+ * Soft-deletes a Resource: drops its content representation but keeps the
783
+ * sidecar as a **tombstone** (`deleted: true`, a bumped `version` and
784
+ * `updatedAt`, the last-known `contentType` retained) so the change feed
785
+ * (replication) still surfaces it until clients catch up (GC of tombstones is
786
+ * future work). With no content file left, the tombstone is invisible to
787
+ * every normal read path
788
+ * (`getResource` / `getResourceMetadata` / `listCollectionItems` all gate on
789
+ * the content file via `_findFile`, so they 404 / skip it), making soft delete
790
+ * transparent to the existing API.
791
+ *
792
+ * When `ifMatch` is supplied (the `conditional-writes` feature), the delete
793
+ * proceeds only if the Resource exists and its current `ETag` matches, else
794
+ * `PreconditionFailedError` (412). The whole read-modify-write runs under the
795
+ * per-Resource write lock so it serializes with concurrent writes. The delete
796
+ * is idempotent: an already-absent Resource (never created, or an existing
797
+ * tombstone) is a no-op, leaving any tombstone's change-feed entry stable.
798
+ * @param options {object}
799
+ * @param options.spaceId {string}
800
+ * @param options.collectionId {string}
801
+ * @param options.resourceId {string}
802
+ * @param [options.ifMatch] {string} `If-Match` precondition (a quoted ETag)
803
+ * @returns {Promise<void>}
804
+ */
805
+ deleteResource({ spaceId, collectionId, resourceId, ifMatch }: {
806
+ spaceId: string;
807
+ collectionId: string;
808
+ resourceId: string;
809
+ ifMatch?: string;
810
+ }): Promise<void>;
811
+ /**
812
+ * Replication change feed (the `changes` query profile; see the
813
+ * `StorageBackend.changesSince` contract.
814
+ * Enumerates the Collection once, builds a lightweight descriptor for every
815
+ * JSON-document Resource (live) and JSON tombstone, orders them by
816
+ * `(updatedAt, resourceId)`, seeks strictly past `checkpoint`, takes a page of
817
+ * `limit`, and reads JSON bodies ONLY for that page. O(n) over the Collection
818
+ * per call (it must read every sidecar to order by `updatedAt`) -- acceptable
819
+ * for this teaching backend; an indexed backend would answer it from an
820
+ * `updatedAt` index.
821
+ * @param options {object}
822
+ * @param options.spaceId {string}
823
+ * @param options.collectionId {string}
824
+ * @param [options.checkpoint] {{ id: string, updatedAt: string }} resume position
825
+ * @param options.limit {number} page cap (clamped to the backend maximum)
826
+ * @returns {Promise<{ documents: Array<object>, checkpoint: object | null }>}
827
+ */
828
+ changesSince({ spaceId, collectionId, checkpoint, limit }: {
829
+ spaceId: string;
830
+ collectionId: string;
831
+ checkpoint?: {
832
+ id: string;
833
+ updatedAt: string;
834
+ };
835
+ limit: number;
836
+ }): Promise<{
837
+ documents: Array<{
838
+ resourceId: string;
839
+ version: number;
840
+ metaVersion?: number;
841
+ createdBy?: IDID;
842
+ updatedAt: string;
843
+ deleted: boolean;
844
+ data?: unknown;
845
+ custom?: ResourceMetadataCustom | Record<string, unknown>;
846
+ epoch?: string;
847
+ }>;
848
+ checkpoint: {
849
+ id: string;
850
+ updatedAt: string;
851
+ } | null;
852
+ }>;
853
+ /**
854
+ * Blinded-index query (the `blinded-index` query profile; see the
855
+ * `StorageBackend.queryByBlindedIndex` contract). Enumerates the Collection
856
+ * dir, reads and parses every live JSON Resource, and hands the candidates
857
+ * to the shared evaluator (`lib/blindedIndex.ts`) for matching, ordering,
858
+ * and cursor pagination. O(n) over the Collection per call, with every JSON
859
+ * body read -- acceptable for this teaching backend; an indexed backend
860
+ * would answer from flattened attribute tokens. Tombstones are excluded
861
+ * naturally (no live content file); binary Resources and unparsable JSON
862
+ * are skipped.
863
+ * @param options {object}
864
+ * @param options.spaceId {string}
865
+ * @param options.collectionId {string}
866
+ * @param options.query {BlindedIndexQuery}
867
+ * @param [options.count] {boolean} return only the match count
868
+ * @param [options.limit] {number} requested page size
869
+ * @param [options.cursor] {string} opaque cursor from a prior page
870
+ * @returns {Promise<{ count: number } | BlindedIndexQueryPage>}
871
+ */
872
+ queryByBlindedIndex({ spaceId, collectionId, query, count, limit, cursor }: {
873
+ spaceId: string;
874
+ collectionId: string;
875
+ query: BlindedIndexQuery;
876
+ count?: boolean;
877
+ limit?: number;
878
+ cursor?: string;
879
+ }): Promise<{
880
+ count: number;
881
+ } | BlindedIndexQueryPage>;
882
+ /**
883
+ * Reads and parses every live JSON Resource of a Collection -- the candidate
884
+ * set for both the blinded-index query and the unique-attribute conflict
885
+ * scan. Tombstones are excluded naturally (no live content file); binary
886
+ * Resources, unparsable JSON, and (optionally) one excluded Resource are
887
+ * skipped. An absent Collection dir resolves empty.
888
+ * @param options {object}
889
+ * @param options.spaceId {string}
890
+ * @param options.collectionId {string}
891
+ * @param [options.excludeResourceId] {string} omit this Resource (the
892
+ * conflict scan excludes the document being written)
893
+ * @returns {Promise<Array<{ resourceId: string, document: unknown }>>}
894
+ */
895
+ private _readJsonCandidates;
896
+ /**
897
+ * Builds the on-disk path for a policy document. Stored as a dot-file keyed by
898
+ * the entity id, alongside the matching `.space.` / `.collection.` description:
899
+ * Space policy in the space dir, Collection and Resource policy in the
900
+ * collection dir (the keying id differs, so they never collide).
901
+ * @param options {object}
902
+ * @param options.spaceId {string}
903
+ * @param [options.collectionId] {string}
904
+ * @param [options.resourceId] {string}
905
+ * @returns {string}
906
+ */
907
+ _policyFile({ spaceId, collectionId, resourceId }: {
908
+ spaceId: string;
909
+ collectionId?: string;
910
+ resourceId?: string;
911
+ }): string;
912
+ /**
913
+ * @param options {object}
914
+ * @param options.spaceId {string}
915
+ * @param [options.collectionId] {string}
916
+ * @param [options.resourceId] {string}
917
+ * @returns {Promise<PolicyDocument|undefined>}
918
+ * Resolves falsy when no policy is set at that level (must not throw).
919
+ */
920
+ getPolicy({ spaceId, collectionId, resourceId }: {
921
+ spaceId: string;
922
+ collectionId?: string;
923
+ resourceId?: string;
924
+ }): Promise<PolicyDocument | undefined>;
925
+ /**
926
+ * @param options {object}
927
+ * @param options.spaceId {string}
928
+ * @param [options.collectionId] {string}
929
+ * @param [options.resourceId] {string}
930
+ * @param options.policy {PolicyDocument}
931
+ * @returns {Promise<void>}
932
+ */
933
+ writePolicy({ spaceId, collectionId, resourceId, policy }: {
934
+ spaceId: string;
935
+ collectionId?: string;
936
+ resourceId?: string;
937
+ policy: PolicyDocument;
938
+ }): Promise<void>;
939
+ /**
940
+ * @param options {object}
941
+ * @param options.spaceId {string}
942
+ * @param [options.collectionId] {string}
943
+ * @param [options.resourceId] {string}
944
+ * @returns {Promise<void>} idempotent (no error if absent)
945
+ */
946
+ deletePolicy({ spaceId, collectionId, resourceId }: {
947
+ spaceId: string;
948
+ collectionId?: string;
949
+ resourceId?: string;
950
+ }): Promise<void>;
951
+ /**
952
+ * Builds the on-disk path for a registered backend record: a
953
+ * `.backend.<backendId>.json` dot-file in the Space dir (the same per-file
954
+ * convention as `.policy.` / `.space.`). One file per backend id.
955
+ * @param options {object}
956
+ * @param options.spaceId {string}
957
+ * @param options.backendId {string}
958
+ * @returns {string}
959
+ */
960
+ _backendFile({ spaceId, backendId }: {
961
+ spaceId: string;
962
+ backendId: string;
963
+ }): string;
964
+ /**
965
+ * Persists a full (secret-bearing) backend-registration record. Upsert.
966
+ * @param options {object}
967
+ * @param options.spaceId {string}
968
+ * @param options.backendId {string}
969
+ * @param options.record {StoredBackendRecord}
970
+ * @returns {Promise<void>}
971
+ */
972
+ writeBackend({ spaceId, backendId, record }: {
973
+ spaceId: string;
974
+ backendId: string;
975
+ record: StoredBackendRecord;
976
+ }): Promise<void>;
977
+ /**
978
+ * Reads the full (secret-bearing) record for one backend, for internal use
979
+ * (existence checks, the future provider adapter). Resolves `undefined` when
980
+ * absent. The only method that exposes secret connection material.
981
+ * @param options {object}
982
+ * @param options.spaceId {string}
983
+ * @param options.backendId {string}
984
+ * @returns {Promise<StoredBackendRecord|undefined>}
985
+ */
986
+ getBackend({ spaceId, backendId }: {
987
+ spaceId: string;
988
+ backendId: string;
989
+ }): Promise<StoredBackendRecord | undefined>;
990
+ /**
991
+ * Enumerates the Space's registered backends and returns them **sanitized**
992
+ * (each mapped through `sanitizeBackendRecord`, so the secret connection
993
+ * material never reaches the listing), sorted by id. An absent Space dir
994
+ * reports no registered backends rather than throwing.
995
+ * @param options {object}
996
+ * @param options.spaceId {string}
997
+ * @returns {Promise<BackendDescriptor[]>}
998
+ */
999
+ listBackends({ spaceId }: {
1000
+ spaceId: string;
1001
+ }): Promise<BackendDescriptor[]>;
1002
+ /**
1003
+ * Removes a registered backend record. Idempotent (no error if absent).
1004
+ * @param options {object}
1005
+ * @param options.spaceId {string}
1006
+ * @param options.backendId {string}
1007
+ * @returns {Promise<void>}
1008
+ */
1009
+ deleteBackend({ spaceId, backendId }: {
1010
+ spaceId: string;
1011
+ backendId: string;
1012
+ }): Promise<void>;
1013
+ /**
1014
+ * WebKMS keystores (the `/kms` facet). A sibling tree to Spaces:
1015
+ * `keystores/<localId>/` holds a keystore's `config.json` now, and its key
1016
+ * records / revocations in later tracks -- hence a directory per keystore
1017
+ * rather than a flat file.
1018
+ *
1019
+ * The directory holding one keystore's records, contained in `keystoresDir`.
1020
+ * @param keystoreId {string} the keystore's server-generated local id
1021
+ * @returns {string}
1022
+ */
1023
+ _keystoreDir(keystoreId: string): string;
1024
+ _keystoreConfigFile(keystoreId: string): string;
1025
+ /**
1026
+ * Persists a keystore config unconditionally (the create path -- local ids
1027
+ * are server-generated random values, so create never collides). The
1028
+ * sequence-gated update path is `updateKeystore`.
1029
+ * @param options {object}
1030
+ * @param options.keystoreId {string} the keystore's local id
1031
+ * @param options.config {KeystoreConfig}
1032
+ * @returns {Promise<void>}
1033
+ */
1034
+ writeKeystore({ keystoreId, config }: {
1035
+ keystoreId: string;
1036
+ config: KeystoreConfig;
1037
+ }): Promise<void>;
1038
+ /**
1039
+ * @param options {object}
1040
+ * @param options.keystoreId {string} the keystore's local id
1041
+ * @returns {Promise<KeystoreConfig|undefined>}
1042
+ * Resolves falsy when the keystore does not exist (must not throw).
1043
+ */
1044
+ getKeystore({ keystoreId }: {
1045
+ keystoreId: string;
1046
+ }): Promise<KeystoreConfig | undefined>;
1047
+ /**
1048
+ * Replaces a keystore config, gated atomically (under the per-keystore write
1049
+ * mutex) on: the keystore existing, `config.sequence` being exactly the
1050
+ * stored sequence + 1, and `config.kmsModule` matching the stored one (the
1051
+ * module is immutable). Any other state rejects with the protocol's single
1052
+ * merged 409 conflict.
1053
+ * @param options {object}
1054
+ * @param options.keystoreId {string} the keystore's local id
1055
+ * @param options.config {KeystoreConfig}
1056
+ * @returns {Promise<void>}
1057
+ */
1058
+ updateKeystore({ keystoreId, config }: {
1059
+ keystoreId: string;
1060
+ config: KeystoreConfig;
1061
+ }): Promise<void>;
1062
+ /**
1063
+ * Every stored keystore config whose `controller` matches, sorted by local
1064
+ * id (the request layer caps the wire result). An absent keystores root
1065
+ * (nothing stored yet) resolves an empty list; a directory without a
1066
+ * readable config file is skipped.
1067
+ * @param options {object}
1068
+ * @param options.controller {IDID}
1069
+ * @returns {Promise<KeystoreConfig[]>}
1070
+ */
1071
+ listKeystoresByController({ controller }: {
1072
+ controller: IDID;
1073
+ }): Promise<KeystoreConfig[]>;
1074
+ /**
1075
+ * The file holding one key record, contained in its keystore's `keys/`
1076
+ * subdirectory. The record is a plain JSON file (not a metadata store):
1077
+ * records are immutable once inserted, so there is no read-modify-write to
1078
+ * protect.
1079
+ * @param options {object}
1080
+ * @param options.keystoreId {string} the owning keystore's local id
1081
+ * @param options.localId {string} the key's local id
1082
+ * @returns {string}
1083
+ */
1084
+ _keyFile({ keystoreId, localId }: {
1085
+ keystoreId: string;
1086
+ localId: string;
1087
+ }): string;
1088
+ /**
1089
+ * Inserts a key record, create-only: the exclusive-create write (`wx`)
1090
+ * enforces the `(keystoreId, localId)` uniqueness atomically, rejecting a
1091
+ * duplicate with the protocol's 409 (`KeyIdConflictError`).
1092
+ * @param options {object}
1093
+ * @param options.keystoreId {string} the owning keystore's local id
1094
+ * @param options.localId {string} the key's local id
1095
+ * @param options.record {KmsKeyRecord} the full (secret-bearing) record
1096
+ * @returns {Promise<void>}
1097
+ */
1098
+ insertKey({ keystoreId, localId, record }: {
1099
+ keystoreId: string;
1100
+ localId: string;
1101
+ record: KmsKeyRecord;
1102
+ }): Promise<void>;
1103
+ /**
1104
+ * @param options {object}
1105
+ * @param options.keystoreId {string} the owning keystore's local id
1106
+ * @param options.localId {string} the key's local id
1107
+ * @returns {Promise<KmsKeyRecord|undefined>}
1108
+ * Resolves falsy when the key does not exist (must not throw).
1109
+ */
1110
+ getKey({ keystoreId, localId }: {
1111
+ keystoreId: string;
1112
+ localId: string;
1113
+ }): Promise<KmsKeyRecord | undefined>;
1114
+ /**
1115
+ * Every stored key record under the keystore (`keys/*.json`), sorted by local
1116
+ * id (the file name's stem). An absent keystore or `keys/` directory (no keys
1117
+ * yet) resolves an empty list; a non-`.json` entry is skipped. The record is
1118
+ * returned verbatim -- the at-rest cipher applies above the backend.
1119
+ * @param options {object}
1120
+ * @param options.keystoreId {string} the owning keystore's local id
1121
+ * @returns {Promise<Array<{ localId: string, record: KmsKeyRecord }>>}
1122
+ */
1123
+ listKeys({ keystoreId }: {
1124
+ keystoreId: string;
1125
+ }): Promise<Array<{
1126
+ localId: string;
1127
+ record: KmsKeyRecord;
1128
+ }>>;
1129
+ /**
1130
+ * The file holding one zcap revocation record. For a keystore scope it lives
1131
+ * in that keystore's `revocations/` subdirectory; for a Space scope it lives
1132
+ * under the sibling `spaceRevocationsDir` root (NOT inside the Space dir --
1133
+ * see the `spaceRevocationsDir` property doc). The file name folds the
1134
+ * `(delegator, capabilityId)` unique key into a SHA-256 digest (the shared
1135
+ * `revocationFileName` codec).
1136
+ * @param options {object}
1137
+ * @param options.scope {RevocationScope} the owning keystore or Space
1138
+ * @param options.delegator {string} the revoked capability's delegator
1139
+ * @param options.capabilityId {string} the revoked capability's id
1140
+ * @returns {string}
1141
+ */
1142
+ _revocationFile({ scope, delegator, capabilityId }: {
1143
+ scope: RevocationScope;
1144
+ delegator: string;
1145
+ capabilityId: string;
1146
+ }): string;
1147
+ /**
1148
+ * Inserts a revocation record, create-only: the exclusive-create write
1149
+ * (`wx`) enforces the `(delegator, capability.id)` uniqueness atomically,
1150
+ * rejecting a duplicate with the protocol's 409
1151
+ * (`DuplicateRevocationError`).
1152
+ * @param options {object}
1153
+ * @param options.scope {RevocationScope} the owning keystore or Space
1154
+ * @param options.record {RevocationRecord} the revocation to store
1155
+ * @returns {Promise<void>}
1156
+ */
1157
+ insertRevocation({ scope, record }: {
1158
+ scope: RevocationScope;
1159
+ record: RevocationRecord;
1160
+ }): Promise<void>;
1161
+ /**
1162
+ * True when any of the given capabilities has a stored, unexpired
1163
+ * revocation under the scope (keystore or Space). A record past its
1164
+ * `meta.expires` GC horizon is pruned on the way through and counts as not
1165
+ * revoked -- the capability itself has expired, so verification already
1166
+ * rejects it on expiry (this is the filesystem analogue of a TTL index).
1167
+ * @param options {object}
1168
+ * @param options.scope {RevocationScope} the owning keystore or Space
1169
+ * @param options.capabilities {CapabilitySummary[]} the
1170
+ * `(capabilityId, delegator)` pairs to check
1171
+ * @returns {Promise<boolean>}
1172
+ */
1173
+ isRevoked({ scope, capabilities }: {
1174
+ scope: RevocationScope;
1175
+ capabilities: CapabilitySummary[];
1176
+ }): Promise<boolean>;
1177
+ }
1178
+ export {};
1179
+ //# sourceMappingURL=filesystem.d.ts.map