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,219 @@
1
+ /**
2
+ * Request handlers for registering an `external` backend against a Space (spec
3
+ * "Backends"): register (`POST`), replace (`PUT`), and deregister (`DELETE`) a
4
+ * backend record. Authorization is the Space controller's key (capability-only,
5
+ * no policy fallback), exactly like Create Collection / Delete Space.
6
+ *
7
+ * The write body is secret-bearing (`BackendConnectionInput`), but every
8
+ * response returns the **sanitized** `BackendDescriptor` (via
9
+ * `sanitizeBackendRecord`), so a client response can never carry the secret. A
10
+ * registered backend is inert this increment: it is listed but not yet
11
+ * selectable as a Collection's `backend` (the live provider adapter is future
12
+ * work).
13
+ */
14
+ import type { FastifyReply, FastifyRequest } from 'fastify'
15
+ import { fetchSpaceAndVerify } from './spaceContext.js'
16
+ import { assertValidIds } from '../lib/validateId.js'
17
+ import {
18
+ DEFAULT_BACKEND_ID,
19
+ assertProviderAllowed,
20
+ assertValidBackendId,
21
+ buildBackendRecord,
22
+ parseBackendRegistration,
23
+ sanitizeBackendRecord
24
+ } from '../lib/backends.js'
25
+ import { invalidateResolvedBackend } from '../lib/backendRegistry.js'
26
+ import { backendsPath, registeredBackendPath } from '../lib/paths.js'
27
+ import { InvalidRequestBodyError, IdConflictError } from '../errors.js'
28
+
29
+ export class BackendRequest {
30
+ /**
31
+ * POST /space/:spaceId/backends
32
+ * Registers a new `external` backend. Rejects the reserved `default` id and a
33
+ * duplicate id (checked after capability verification so an unauthorized
34
+ * caller cannot probe ids). Responds 201 with the sanitized descriptor.
35
+ * @param request {import('fastify').FastifyRequest}
36
+ * @param reply {import('fastify').FastifyReply}
37
+ * @returns {Promise<FastifyReply>}
38
+ */
39
+ static async post(
40
+ request: FastifyRequest<{
41
+ Params: { spaceId: string }
42
+ Body: unknown
43
+ }>,
44
+ reply: FastifyReply
45
+ ): Promise<FastifyReply> {
46
+ const {
47
+ params: { spaceId },
48
+ body
49
+ } = request
50
+ const { serverUrl, storage } = request.server
51
+ const requestName = 'Register Backend'
52
+
53
+ // Reject path-traversal / non-URL-safe ids before any storage access.
54
+ assertValidIds({ spaceId }, { requestName })
55
+ const registration = parseBackendRegistration(body, { requestName })
56
+ // Fail fast if the server's registration allowlist excludes this provider
57
+ // (permissive when no allowlist is configured).
58
+ assertProviderAllowed({
59
+ provider: registration.provider,
60
+ enabledProviders: request.server.enabledBackendProviders
61
+ })
62
+ assertValidBackendId(registration.id, { requestName })
63
+
64
+ // Verify (capability-only): registering a backend requires a valid
65
+ // capability invocation by the Space controller; no policy fallback.
66
+ await fetchSpaceAndVerify({
67
+ request,
68
+ spaceId,
69
+ targetPath: backendsPath({ spaceId }),
70
+ requestName
71
+ })
72
+
73
+ // After verification (so an unauthorized caller cannot probe ids): the
74
+ // `default` id is reserved for the server backend, and POST must not replace
75
+ // an existing record (create-or-replace by id is PUT's job).
76
+ if (registration.id === DEFAULT_BACKEND_ID) {
77
+ throw new InvalidRequestBodyError({
78
+ requestName,
79
+ detail: `"${DEFAULT_BACKEND_ID}" is the reserved server backend id and cannot be registered.`,
80
+ pointer: '#/id'
81
+ })
82
+ }
83
+ if (await storage.getBackend({ spaceId, backendId: registration.id })) {
84
+ throw new IdConflictError({ kind: 'Backend' })
85
+ }
86
+
87
+ const record = buildBackendRecord(registration)
88
+ await storage.writeBackend({ spaceId, backendId: record.id, record })
89
+
90
+ const createdUrl = new URL(
91
+ registeredBackendPath({ spaceId, backendId: record.id }),
92
+ serverUrl
93
+ ).toString()
94
+ reply.header('Location', createdUrl)
95
+ return reply.status(201).send(sanitizeBackendRecord(record))
96
+ }
97
+
98
+ /**
99
+ * PUT /space/:spaceId/backends/:backendId
100
+ * Upsert (the re-consent path): create-or-replace a backend record at a chosen
101
+ * id. The body `id` (when present) must match the path id, and `default` is
102
+ * rejected. Responds 201 + Location + sanitized body on create, 204 on update.
103
+ * @param request {import('fastify').FastifyRequest}
104
+ * @param reply {import('fastify').FastifyReply}
105
+ * @returns {Promise<FastifyReply>}
106
+ */
107
+ static async put(
108
+ request: FastifyRequest<{
109
+ Params: { spaceId: string; backendId: string }
110
+ Body: unknown
111
+ }>,
112
+ reply: FastifyReply
113
+ ): Promise<FastifyReply> {
114
+ const {
115
+ params: { spaceId, backendId },
116
+ body
117
+ } = request
118
+ const { serverUrl, storage } = request.server
119
+ const requestName = 'Register Backend'
120
+
121
+ // Reject path-traversal / non-URL-safe ids before any storage access.
122
+ assertValidIds({ spaceId }, { requestName })
123
+ assertValidBackendId(backendId, { requestName })
124
+ const registration = parseBackendRegistration(body, { requestName })
125
+ // Fail fast if the server's registration allowlist excludes this provider
126
+ // (permissive when no allowlist is configured).
127
+ assertProviderAllowed({
128
+ provider: registration.provider,
129
+ enabledProviders: request.server.enabledBackendProviders
130
+ })
131
+
132
+ // The backend `id` is immutable: a body `id`, when present, must match the
133
+ // URL id; and `default` is the reserved server backend id.
134
+ if (registration.id !== backendId) {
135
+ throw new InvalidRequestBodyError({
136
+ requestName,
137
+ detail: `Backend "id" (${registration.id}) does not match the URL backend id (${backendId}).`,
138
+ pointer: '#/id'
139
+ })
140
+ }
141
+ if (backendId === DEFAULT_BACKEND_ID) {
142
+ throw new InvalidRequestBodyError({
143
+ requestName,
144
+ detail: `"${DEFAULT_BACKEND_ID}" is the reserved server backend id and cannot be registered.`,
145
+ pointer: '#/id'
146
+ })
147
+ }
148
+
149
+ // Verify (capability-only): no policy fallback for a write.
150
+ await fetchSpaceAndVerify({
151
+ request,
152
+ spaceId,
153
+ targetPath: registeredBackendPath({ spaceId, backendId }),
154
+ requestName
155
+ })
156
+
157
+ const existing = await storage.getBackend({ spaceId, backendId })
158
+ const record = buildBackendRecord(registration)
159
+ await storage.writeBackend({ spaceId, backendId, record })
160
+ // Bust any memoized adapter so the next resolve rebuilds it from the new
161
+ // connection material.
162
+ invalidateResolvedBackend({
163
+ providers: request.server.backendProviders,
164
+ spaceId,
165
+ backendId
166
+ })
167
+
168
+ if (existing) {
169
+ return reply.status(204).send()
170
+ }
171
+ const createdUrl = new URL(
172
+ registeredBackendPath({ spaceId, backendId }),
173
+ serverUrl
174
+ ).toString()
175
+ reply.header('Location', createdUrl)
176
+ return reply.status(201).send(sanitizeBackendRecord(record))
177
+ }
178
+
179
+ /**
180
+ * DELETE /space/:spaceId/backends/:backendId
181
+ * Deregisters a backend. Idempotent (204 even if the record was absent).
182
+ * @param request {import('fastify').FastifyRequest}
183
+ * @param reply {import('fastify').FastifyReply}
184
+ * @returns {Promise<FastifyReply>}
185
+ */
186
+ static async delete(
187
+ request: FastifyRequest<{
188
+ Params: { spaceId: string; backendId: string }
189
+ }>,
190
+ reply: FastifyReply
191
+ ): Promise<FastifyReply> {
192
+ const {
193
+ params: { spaceId, backendId }
194
+ } = request
195
+ const { storage } = request.server
196
+ const requestName = 'Deregister Backend'
197
+
198
+ // Reject path-traversal / non-URL-safe ids before any storage access.
199
+ assertValidIds({ spaceId }, { requestName })
200
+ assertValidBackendId(backendId, { requestName })
201
+
202
+ // Verify (capability-only): no policy fallback for a write.
203
+ await fetchSpaceAndVerify({
204
+ request,
205
+ spaceId,
206
+ targetPath: registeredBackendPath({ spaceId, backendId }),
207
+ requestName
208
+ })
209
+
210
+ await storage.deleteBackend({ spaceId, backendId })
211
+ // Bust any memoized adapter for the now-removed record.
212
+ invalidateResolvedBackend({
213
+ providers: request.server.backendProviders,
214
+ spaceId,
215
+ backendId
216
+ })
217
+ return reply.status(204).send()
218
+ }
219
+ }