@homeflare/alchemy 0.27.4 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +2 -0
  2. package/dist/caddy/admin-address.d.ts +27 -0
  3. package/dist/caddy/admin-address.d.ts.map +1 -0
  4. package/dist/caddy/admin-calls.d.ts +25 -28
  5. package/dist/caddy/admin-calls.d.ts.map +1 -1
  6. package/dist/caddy/admin.d.ts +28 -38
  7. package/dist/caddy/admin.d.ts.map +1 -1
  8. package/dist/caddy/caddy-http-client.d.ts +25 -0
  9. package/dist/caddy/caddy-http-client.d.ts.map +1 -0
  10. package/dist/caddy/config-errors.d.ts +24 -0
  11. package/dist/caddy/config-errors.d.ts.map +1 -0
  12. package/dist/caddy/config-lifecycle.d.ts +20 -49
  13. package/dist/caddy/config-lifecycle.d.ts.map +1 -1
  14. package/dist/caddy/config-reconcile.d.ts +45 -0
  15. package/dist/caddy/config-reconcile.d.ts.map +1 -0
  16. package/dist/caddy/config.d.ts +4 -3
  17. package/dist/caddy/config.d.ts.map +1 -1
  18. package/dist/caddy/fake-caddy.d.ts +10 -2
  19. package/dist/caddy/fake-caddy.d.ts.map +1 -1
  20. package/dist/caddy/index.d.ts +3 -3
  21. package/dist/caddy/index.d.ts.map +1 -1
  22. package/dist/caddy/index.js +487 -444
  23. package/dist/caddy/index.js.map +16 -12
  24. package/dist/caddy/local-admin.d.ts +20 -34
  25. package/dist/caddy/local-admin.d.ts.map +1 -1
  26. package/dist/caddy/providers.d.ts +19 -3
  27. package/dist/caddy/providers.d.ts.map +1 -1
  28. package/dist/cloudflare/mesh-node-token.d.ts +1 -1
  29. package/dist/cloudflare/mesh-node-token.d.ts.map +1 -1
  30. package/dist/forgejo/branch-protection.d.ts +4 -4
  31. package/dist/forgejo/org-actions-secrets.d.ts +4 -4
  32. package/dist/forgejo/org-label.d.ts +4 -4
  33. package/dist/forgejo/org-team.d.ts +4 -4
  34. package/dist/forgejo/repo-webhook.d.ts +4 -4
  35. package/dist/forgejo/repository.d.ts +4 -4
  36. package/dist/forgejo/team-member.d.ts +4 -4
  37. package/dist/github/declare-repo-baseline.d.ts +1 -1
  38. package/dist/github/declare-repo-baseline.d.ts.map +1 -1
  39. package/dist/github/index.d.ts +9 -5
  40. package/dist/github/index.d.ts.map +1 -1
  41. package/dist/github/index.js +161 -25
  42. package/dist/github/index.js.map +15 -13
  43. package/dist/github/repo-baseline-data.d.ts +10 -1
  44. package/dist/github/repo-baseline-data.d.ts.map +1 -1
  45. package/dist/github/repo-policy.d.ts +1 -1
  46. package/dist/github/repo-policy.d.ts.map +1 -1
  47. package/dist/github/repository-ruleset-errors.d.ts +37 -3
  48. package/dist/github/repository-ruleset-errors.d.ts.map +1 -1
  49. package/dist/github/repository-ruleset-form.d.ts.map +1 -1
  50. package/dist/github/repository-ruleset-guards.d.ts +15 -2
  51. package/dist/github/repository-ruleset-guards.d.ts.map +1 -1
  52. package/dist/github/repository-ruleset-narrowing-guards.d.ts +39 -0
  53. package/dist/github/repository-ruleset-narrowing-guards.d.ts.map +1 -0
  54. package/dist/github/repository-ruleset-octokit.d.ts +49 -0
  55. package/dist/github/repository-ruleset-octokit.d.ts.map +1 -1
  56. package/dist/github/repository-ruleset-probe.d.ts +2 -1
  57. package/dist/github/repository-ruleset-probe.d.ts.map +1 -1
  58. package/dist/github/repository-ruleset-props.d.ts +91 -0
  59. package/dist/github/repository-ruleset-props.d.ts.map +1 -0
  60. package/dist/github/repository-ruleset-providers.d.ts +42 -0
  61. package/dist/github/repository-ruleset-providers.d.ts.map +1 -0
  62. package/dist/github/repository-ruleset-reconcile.d.ts.map +1 -1
  63. package/dist/github/repository-ruleset.d.ts +28 -34
  64. package/dist/github/repository-ruleset.d.ts.map +1 -1
  65. package/dist/litellm/fake-litellm.d.ts +27 -16
  66. package/dist/litellm/fake-litellm.d.ts.map +1 -1
  67. package/dist/litellm/fake-stack.d.ts +9 -4
  68. package/dist/litellm/fake-stack.d.ts.map +1 -1
  69. package/dist/litellm/index.d.ts +7 -5
  70. package/dist/litellm/index.d.ts.map +1 -1
  71. package/dist/litellm/index.js +53 -123
  72. package/dist/litellm/index.js.map +8 -8
  73. package/dist/litellm/operations.d.ts +60 -0
  74. package/dist/litellm/operations.d.ts.map +1 -0
  75. package/dist/litellm/pass-through-endpoint.d.ts +10 -28
  76. package/dist/litellm/pass-through-endpoint.d.ts.map +1 -1
  77. package/dist/litellm/pass-through-errors.d.ts +37 -0
  78. package/dist/litellm/pass-through-errors.d.ts.map +1 -0
  79. package/dist/litellm/pass-through-form.d.ts +11 -4
  80. package/dist/litellm/pass-through-form.d.ts.map +1 -1
  81. package/dist/litellm/providers.d.ts +7 -5
  82. package/dist/litellm/providers.d.ts.map +1 -1
  83. package/dist/netbox/prefix.d.ts +4 -4
  84. package/dist/paperless/custom-field.d.ts +21 -21
  85. package/dist/paperless/custom-field.d.ts.map +1 -1
  86. package/dist/paperless/document-type.d.ts +34 -4
  87. package/dist/paperless/document-type.d.ts.map +1 -1
  88. package/dist/paperless/fake-paperless.d.ts +36 -19
  89. package/dist/paperless/fake-paperless.d.ts.map +1 -1
  90. package/dist/paperless/index.d.ts +7 -2
  91. package/dist/paperless/index.d.ts.map +1 -1
  92. package/dist/paperless/index.js +164 -228
  93. package/dist/paperless/index.js.map +10 -13
  94. package/dist/paperless/matching-locate.d.ts +42 -20
  95. package/dist/paperless/matching-locate.d.ts.map +1 -1
  96. package/dist/paperless/matching-types.d.ts +47 -18
  97. package/dist/paperless/matching-types.d.ts.map +1 -1
  98. package/dist/paperless/matching.d.ts +30 -27
  99. package/dist/paperless/matching.d.ts.map +1 -1
  100. package/dist/paperless/providers.d.ts +13 -7
  101. package/dist/paperless/providers.d.ts.map +1 -1
  102. package/dist/paperless/storage-path.d.ts +34 -4
  103. package/dist/paperless/storage-path.d.ts.map +1 -1
  104. package/dist/paperless/tag.d.ts +43 -8
  105. package/dist/paperless/tag.d.ts.map +1 -1
  106. package/dist/proxmox/acl.d.ts +30 -23
  107. package/dist/proxmox/acl.d.ts.map +1 -1
  108. package/dist/proxmox/distilled-pve.d.ts +89 -0
  109. package/dist/proxmox/distilled-pve.d.ts.map +1 -0
  110. package/dist/proxmox/index.js +663 -609
  111. package/dist/proxmox/index.js.map +12 -11
  112. package/dist/proxmox/members.d.ts +29 -10
  113. package/dist/proxmox/members.d.ts.map +1 -1
  114. package/docs/caddy-admin.md +1 -1
  115. package/docs/caddy.md +3 -2
  116. package/docs/credentials.md +16 -0
  117. package/docs/litellm.md +21 -13
  118. package/docs/paperless.md +12 -0
  119. package/docs/peers.md +5 -0
  120. package/docs/repo-policy.md +16 -1
  121. package/docs/repository-ruleset.md +53 -13
  122. package/docs/upstream-conformance.md +126 -10
  123. package/package.json +9 -2
  124. package/src/caddy/admin-address.ts +71 -0
  125. package/src/caddy/admin-calls.ts +48 -100
  126. package/src/caddy/admin.ts +37 -41
  127. package/src/caddy/adopt-scope.test.ts +1 -1
  128. package/src/caddy/caddy-http-client.ts +229 -0
  129. package/src/caddy/config-adopt-provider.test.ts +139 -0
  130. package/src/caddy/config-adopt.test.ts +60 -126
  131. package/src/caddy/config-errors.ts +47 -0
  132. package/src/caddy/config-lifecycle.test.ts +55 -42
  133. package/src/caddy/config-lifecycle.ts +72 -162
  134. package/src/caddy/config-reconcile.ts +138 -0
  135. package/src/caddy/config.ts +36 -42
  136. package/src/caddy/fake-caddy.ts +20 -2
  137. package/src/caddy/index.ts +3 -8
  138. package/src/caddy/local-admin.test.ts +35 -21
  139. package/src/caddy/local-admin.ts +60 -167
  140. package/src/caddy/providers.test.ts +14 -6
  141. package/src/caddy/providers.ts +20 -4
  142. package/src/github/declare-repo-baseline.ts +6 -6
  143. package/src/github/index.ts +10 -4
  144. package/src/github/repo-baseline-data.test.ts +13 -0
  145. package/src/github/repo-baseline-data.ts +11 -2
  146. package/src/github/repo-policy-stack.test.ts +59 -0
  147. package/src/github/repo-policy.ts +3 -2
  148. package/src/github/repository-ruleset-errors.ts +51 -4
  149. package/src/github/repository-ruleset-exact-adopt-k1.test.ts +109 -0
  150. package/src/github/repository-ruleset-exact-adopt.test.ts +184 -0
  151. package/src/github/repository-ruleset-form.ts +7 -3
  152. package/src/github/repository-ruleset-guards.ts +11 -4
  153. package/src/github/repository-ruleset-narrowing-guards.ts +141 -0
  154. package/src/github/repository-ruleset-narrowing.test.ts +210 -0
  155. package/src/github/repository-ruleset-octokit.test.ts +141 -0
  156. package/src/github/repository-ruleset-octokit.ts +121 -30
  157. package/src/github/repository-ruleset-probe.ts +2 -1
  158. package/src/github/repository-ruleset-props.ts +95 -0
  159. package/src/github/repository-ruleset-providers.ts +46 -0
  160. package/src/github/repository-ruleset-reconcile.ts +35 -0
  161. package/src/github/repository-ruleset-wire.test.ts +38 -27
  162. package/src/github/repository-ruleset-write-refusals.test.ts +6 -26
  163. package/src/github/repository-ruleset.ts +53 -43
  164. package/src/litellm/fake-litellm.ts +83 -78
  165. package/src/litellm/fake-stack.ts +13 -5
  166. package/src/litellm/index.ts +7 -21
  167. package/src/litellm/operations.ts +149 -0
  168. package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +17 -20
  169. package/src/litellm/pass-through-endpoint-replace.test.ts +3 -4
  170. package/src/litellm/pass-through-endpoint.test.ts +8 -8
  171. package/src/litellm/pass-through-endpoint.ts +21 -31
  172. package/src/litellm/pass-through-errors.ts +37 -0
  173. package/src/litellm/pass-through-form.ts +20 -10
  174. package/src/litellm/providers.ts +8 -7
  175. package/src/paperless/custom-field.test.ts +49 -40
  176. package/src/paperless/custom-field.ts +57 -33
  177. package/src/paperless/document-type.test.ts +48 -0
  178. package/src/paperless/document-type.ts +51 -22
  179. package/src/paperless/fake-paperless.ts +63 -72
  180. package/src/paperless/index.ts +7 -13
  181. package/src/paperless/matching-locate.test.ts +102 -0
  182. package/src/paperless/matching-locate.ts +80 -86
  183. package/src/paperless/matching-types.ts +49 -16
  184. package/src/paperless/matching.ts +77 -90
  185. package/src/paperless/providers.ts +14 -10
  186. package/src/paperless/storage-path.test.ts +56 -0
  187. package/src/paperless/storage-path.ts +53 -24
  188. package/src/paperless/tag-identity.test.ts +127 -0
  189. package/src/paperless/tag.test.ts +100 -0
  190. package/src/paperless/tag.ts +62 -32
  191. package/src/proxmox/acl.test.ts +145 -0
  192. package/src/proxmox/acl.ts +128 -150
  193. package/src/proxmox/distilled-pve.test.ts +231 -0
  194. package/src/proxmox/distilled-pve.ts +147 -0
  195. package/src/proxmox/members-timeout.test.ts +161 -0
  196. package/src/proxmox/members.test.ts +9 -0
  197. package/src/proxmox/members.ts +36 -2
  198. package/dist/litellm/client.d.ts +0 -76
  199. package/dist/litellm/client.d.ts.map +0 -1
  200. package/dist/litellm/credentials.d.ts +0 -48
  201. package/dist/litellm/credentials.d.ts.map +0 -1
  202. package/dist/litellm/generated/pass-through.d.ts +0 -71
  203. package/dist/litellm/generated/pass-through.d.ts.map +0 -1
  204. package/dist/paperless/client.d.ts +0 -37
  205. package/dist/paperless/client.d.ts.map +0 -1
  206. package/dist/paperless/credentials.d.ts +0 -40
  207. package/dist/paperless/credentials.d.ts.map +0 -1
  208. package/dist/paperless/errors.d.ts +0 -49
  209. package/dist/paperless/errors.d.ts.map +0 -1
  210. package/src/litellm/client.ts +0 -195
  211. package/src/litellm/credentials.ts +0 -73
  212. package/src/litellm/generated/pass-through.ts +0 -73
  213. package/src/paperless/client.ts +0 -88
  214. package/src/paperless/credentials.ts +0 -69
  215. package/src/paperless/errors.ts +0 -62
  216. package/src/paperless/matching-identity.test.ts +0 -194
  217. package/src/paperless/matching.test.ts +0 -128
@@ -1,22 +1,35 @@
1
1
  /**
2
- * A loopback `Bun.serve` fake of LiteLLM's `/config/pass_through_endpoint` routes — TEST ONLY,
3
- * never imported by `index.ts` (the netbox/proxmox house convention for test-only helpers).
2
+ * A fake of LiteLLM's `/config/pass_through_endpoint` routes for tests — TEST ONLY, never imported
3
+ * by `index.ts` (the netbox/forgejo house convention for test-only helpers). Mirrors
4
+ * `../netbox/fake-netbox.ts`/`../forgejo/fake-forgejo.ts`: a `fetch` function handed to Effect's
5
+ * real `FetchHttpClient` through its `Fetch` reference, so a test exercises distilled's REAL path
6
+ * assembly, JSON encode/decode and status→typed-error matching (`BadRequest`, `NotFound`, …) —
7
+ * nothing about that matching is re-implemented here, only the wire responses a scenario needs.
4
8
  *
5
9
  * ★ MODELS THE WHOLE-FIELD READ-MODIFY-WRITE ON PURPOSE. `general_settings.pass_through_endpoints`
6
- * is ONE field (client.ts's header), so every mutation here reads the CURRENT row list, waits
7
- * (so two concurrent requests can interleave), then writes back what it captured — not what is
8
- * there by the time it writes. Two `create` calls that race lose one row, exactly like the
9
- * vendor field would without the client's per-base-URL semaphore serialising them.
10
+ * is ONE field (operations.ts's header), so every mutation here reads the CURRENT row list,
11
+ * awaits (so two concurrent calls can interleave — no real socket needed, `fetch` is just an
12
+ * async function the runtime awaits), then writes back what it captured — not what is there by
13
+ * the time it writes. Two `create` calls that race lose one row, exactly like the vendor field
14
+ * would without `operations.ts`'s per-base-URL semaphore serialising them.
10
15
  * ⚠️ WHAT THIS FAKE DOES NOT MODEL: PROXY_ADMIN vs. a lesser role for most routes (any correct
11
- * bearer token is accepted); and update-of-a-missing-id, which is not measured at the tag — this
12
- * fake 400s it, the same status a missing delete gets, as a documented TEST SIMPLIFICATION, not a
13
- * vendor fact (docs/litellm.md says so too). `forbidDelete` is the one exception: it models the
14
- * real asymmetry client.ts's header describes — `DELETE` routes through
15
- * `update_config_general_settings`, which 400s `not_allowed_access` for a non-PROXY_ADMIN caller
16
- * (or a disconnected DB) on a row that is genuinely still live, the SAME status a missing-id
17
- * delete gets (proxy_server.py:16389-16416 at v1.100.0).
16
+ * bearer token is accepted). `forbidDelete` is the one exception: it models the real asymmetry
17
+ * `operations.ts`'s header describes — `DELETE` routes through `update_config_general_settings`,
18
+ * which 400s `not_allowed_access` for a non-PROXY_ADMIN caller (or a disconnected DB) on a row
19
+ * that is genuinely still live, the SAME status a missing-id delete gets (proxy_server.py:
20
+ * 16389-16416 at v1.100.0).
21
+ * ⚠️ UPDATE-OF-A-MISSING-ID IS 404, NOT 400 — corrected by this migration. The retired hand-rolled
22
+ * fake 400'd it as "not measured at the tag, a documented test simplification". The distilled
23
+ * SDK's own `patches/misc/update_pass_through_endpoints_*.json` cites the real source
24
+ * (`pass_through_endpoints.py`'s `update_pass_through_endpoints` at the tag): unlike DELETE, this
25
+ * route raises a genuine, unambiguous `HTTPException(404)` for a missing id, distinct from the
26
+ * 400 `update_config_general_settings` raises for the trailing auth/DB-disconnected write. This
27
+ * fake now matches that measured vendor behaviour.
18
28
  */
19
- import type { PassThroughGenericEndpoint } from './generated/pass-through.ts';
29
+ import type { PassThroughGenericEndpoint } from '@distilled.cloud/litellm/misc';
30
+
31
+ /** RFC 2606 placeholder — never a real host. Shared across tests, like `../netbox/fake-netbox.ts`. */
32
+ export const FAKE_BASE = 'https://litellm.example.com';
20
33
 
21
34
  export interface FakeRequest {
22
35
  readonly method: string;
@@ -24,15 +37,17 @@ export interface FakeRequest {
24
37
  }
25
38
 
26
39
  export interface FakeLitellm {
27
- readonly url: string;
40
+ readonly fetch: typeof globalThis.fetch;
28
41
  readonly rows: () => readonly PassThroughGenericEndpoint[];
29
42
  readonly requests: () => readonly FakeRequest[];
30
- readonly stop: () => void;
31
43
  }
32
44
 
33
45
  const RACE_WINDOW_MS = 15;
34
46
  const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
35
47
 
48
+ const json = (status: number, body: unknown) =>
49
+ new Response(JSON.stringify(body), { headers: { 'content-type': 'application/json' }, status });
50
+
36
51
  export const startFakeLitellm = (options?: {
37
52
  readonly masterKey?: string;
38
53
  readonly seed?: readonly PassThroughGenericEndpoint[];
@@ -44,73 +59,63 @@ export const startFakeLitellm = (options?: {
44
59
  let rows: PassThroughGenericEndpoint[] = [...(options?.seed ?? [])];
45
60
  const requests: FakeRequest[] = [];
46
61
 
47
- const json = (status: number, body: unknown) =>
48
- new Response(JSON.stringify(body), { headers: { 'content-type': 'application/json' }, status });
62
+ const fetch = (async (input: string | URL | Request, init?: RequestInit) => {
63
+ const request =
64
+ input instanceof Request ? new Request(input, init) : new Request(String(input), init);
65
+ const url = new URL(request.url);
66
+ requests.push({ method: request.method, path: `${url.pathname}${url.search}` });
67
+ const auth = request.headers.get('authorization');
68
+ if (auth !== `Bearer ${masterKey}`) return json(401, { detail: 'invalid api key' });
69
+ if (
70
+ url.pathname !== '/config/pass_through_endpoint' &&
71
+ !url.pathname.startsWith('/config/pass_through_endpoint/')
72
+ ) {
73
+ return json(404, { detail: 'not found' });
74
+ }
49
75
 
50
- const server = Bun.serve({
51
- // ⚠️ Loopback, not the wildcard default — see proxmox/fake-pve-lxc.ts for the race.
52
- hostname: '127.0.0.1',
53
- port: 0,
54
- fetch: async (req) => {
55
- const url = new URL(req.url);
56
- requests.push({ method: req.method, path: url.pathname });
57
- const auth = req.headers.get('authorization');
58
- if (auth !== `Bearer ${masterKey}`) return json(401, { detail: 'invalid api key' });
59
- if (
60
- url.pathname !== '/config/pass_through_endpoint' &&
61
- !url.pathname.startsWith('/config/pass_through_endpoint/')
62
- ) {
63
- return json(404, { detail: 'not found' });
64
- }
76
+ if (request.method === 'GET') {
77
+ const endpointId = url.searchParams.get('endpoint_id');
78
+ const filtered = endpointId === null ? rows : rows.filter((row) => row.id === endpointId);
79
+ return json(200, { endpoints: filtered });
80
+ }
65
81
 
66
- if (req.method === 'GET') {
67
- const endpointId = url.searchParams.get('endpoint_id');
68
- const filtered = endpointId === null ? rows : rows.filter((row) => row.id === endpointId);
69
- return json(200, { endpoints: filtered });
70
- }
82
+ if (request.method === 'POST' && url.pathname === '/config/pass_through_endpoint') {
83
+ const body = (await request.json()) as PassThroughGenericEndpoint;
84
+ const before = rows; // ★ captured before the window — the race the semaphore prevents
85
+ await sleep(RACE_WINDOW_MS);
86
+ rows = [...before, { ...body, is_from_config: false }];
87
+ return json(200, { body: {} });
88
+ }
71
89
 
72
- if (req.method === 'POST' && url.pathname === '/config/pass_through_endpoint') {
73
- const body = (await req.json()) as PassThroughGenericEndpoint;
74
- const before = rows; // ★ captured before the window — the race the semaphore prevents
75
- await sleep(RACE_WINDOW_MS);
76
- rows = [...before, { ...body, is_from_config: false }];
77
- return json(200, {});
78
- }
79
-
80
- if (req.method === 'POST' && url.pathname.startsWith('/config/pass_through_endpoint/')) {
81
- const id = decodeURIComponent(url.pathname.slice('/config/pass_through_endpoint/'.length));
82
- const patch = (await req.json()) as Partial<PassThroughGenericEndpoint>;
83
- const before = rows;
84
- await sleep(RACE_WINDOW_MS);
85
- const index = before.findIndex((row) => row.id === id);
86
- if (index === -1) return json(400, { detail: `No pass-through endpoint with id=${id}` });
87
- // ⚠️ `exclude_none`-shaped: only keys the patch actually carries are merged, matching
88
- // client.ts's `updateBody` never sending an unset/null field (pass-through-form.ts).
89
- const merged = { ...before[index], ...patch };
90
- rows = before.map((row, i) => (i === index ? (merged as PassThroughGenericEndpoint) : row));
91
- return json(200, {});
92
- }
90
+ if (request.method === 'POST' && url.pathname.startsWith('/config/pass_through_endpoint/')) {
91
+ const id = decodeURIComponent(url.pathname.slice('/config/pass_through_endpoint/'.length));
92
+ const patch = (await request.json()) as Partial<PassThroughGenericEndpoint>;
93
+ const before = rows;
94
+ await sleep(RACE_WINDOW_MS);
95
+ const index = before.findIndex((row) => row.id === id);
96
+ // ⚠️ 404, not 400 — see the file header.
97
+ if (index === -1) return json(404, { detail: `Endpoint with ID '${id}' not found` });
98
+ // ⚠️ `exclude_none`-shaped: only keys the patch actually carries are merged, matching
99
+ // operations.ts's `updateBody` never sending an unset/null field (pass-through-form.ts).
100
+ const merged = { ...before[index], ...patch };
101
+ rows = before.map((row, i) => (i === index ? (merged as PassThroughGenericEndpoint) : row));
102
+ return json(200, { body: {} });
103
+ }
93
104
 
94
- if (req.method === 'DELETE' && url.pathname === '/config/pass_through_endpoint') {
95
- const id = url.searchParams.get('endpoint_id');
96
- if (forbidDelete) return json(400, { error: 'not_allowed_access' });
97
- const before = rows;
98
- await sleep(RACE_WINDOW_MS);
99
- if (!before.some((row) => row.id === id)) {
100
- return json(400, { detail: `No pass-through endpoint with id=${String(id)}` });
101
- }
102
- rows = before.filter((row) => row.id !== id);
103
- return json(200, { endpoints: rows });
105
+ if (request.method === 'DELETE' && url.pathname === '/config/pass_through_endpoint') {
106
+ const id = url.searchParams.get('endpoint_id');
107
+ if (forbidDelete) return json(400, { error: 'not_allowed_access' });
108
+ const before = rows;
109
+ await sleep(RACE_WINDOW_MS);
110
+ if (!before.some((row) => row.id === id)) {
111
+ return json(400, { detail: `No pass-through endpoint with id=${String(id)}` });
104
112
  }
113
+ rows = before.filter((row) => row.id !== id);
114
+ return json(200, { endpoints: rows });
115
+ }
105
116
 
106
- return json(405, { detail: `unhandled ${req.method} ${url.pathname}` });
107
- },
108
- });
117
+ return json(405, { detail: `unhandled ${request.method} ${url.pathname}` });
118
+ }) as typeof globalThis.fetch;
109
119
 
110
- return {
111
- requests: () => [...requests],
112
- rows: () => [...rows],
113
- stop: () => server.stop(true),
114
- url: server.url.toString().replace(/\/$/, ''),
115
- };
120
+ return { fetch, requests: () => [...requests], rows: () => [...rows] };
116
121
  };
@@ -12,13 +12,13 @@
12
12
  * ★ NOT alchemy/Test/Bun — same reasoning as openbao/fake-stack.ts: no CLI profile/credentials
13
13
  * files, no file logger. Just the provider, an in-memory state store, and the stack's services.
14
14
  */
15
+ import { credentials } from '@distilled.cloud/litellm/Credentials';
15
16
  import * as Alchemy from 'alchemy';
16
17
  import { AdoptPolicy } from 'alchemy/AdoptPolicy';
17
18
  import { provideFreshArtifactStore } from 'alchemy/Artifacts';
18
19
  import * as Effect from 'effect/Effect';
19
20
  import * as Layer from 'effect/Layer';
20
21
  import * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';
21
- import { type LitellmCreds, litellmCredentialsLayerFor } from './credentials.ts';
22
22
  import { LiteLLMPassThroughEndpointProvider } from './pass-through-endpoint.ts';
23
23
 
24
24
  /** The planned action for each resource, deletions included, keyed by FQN. */
@@ -53,13 +53,20 @@ const actionsOf = (plan: PlanView): Planned => {
53
53
  /**
54
54
  * ⚠️ CREDENTIALS/HTTPCLIENT ARE PROVIDED AROUND `deploy`, NOT BAKED INTO THE PROVIDER LAYER.
55
55
  * `Provider.effect`'s own construction effect is `Effect.succeed(...)` (pass-through-endpoint.ts)
56
- * — it never actually reads `LitellmCredentials`/`HttpClient`; only the HANDLERS it returns do,
57
- * and those run later, whenever `Apply.ts` calls `provider.reconcile`/`read`/`delete`. Effect
56
+ * — it never actually reads `Credentials`/`HttpClient`; only the HANDLERS it returns do, and
57
+ * those run later, whenever `Apply.ts` calls `provider.reconcile`/`read`/`delete`. Effect
58
58
  * resolves a `yield*`'s service from whatever ambient context wraps the CALL SITE, so the layers
59
59
  * those handlers need must wrap the whole plan+apply pipeline below, exactly where openbao's
60
60
  * fake-stack.ts wraps `BaoEnv` around its own `deploy`.
61
+ * ★ `fetchFn` IS `startFakeLitellm(...).fetch` (fake-litellm.ts) — this harness never opens a real
62
+ * socket; `FetchHttpClient.Fetch` is the seam the SDK's protocol layer calls through, same as
63
+ * `../netbox/fake-netbox.ts`'s `fakeNetboxLayer`.
61
64
  */
62
- export const fakeStack = (creds: LitellmCreds, name = 'PassThroughStack'): FakeStack => {
65
+ export const fakeStack = (
66
+ creds: { readonly apiKey: string; readonly baseUrl: string },
67
+ fetchFn: typeof globalThis.fetch,
68
+ name = 'PassThroughStack',
69
+ ): FakeStack => {
63
70
  const state = Alchemy.inMemoryState();
64
71
  const providers = LiteLLMPassThroughEndpointProvider();
65
72
  const stack = Alchemy.Stack as unknown as (
@@ -83,8 +90,9 @@ export const fakeStack = (creds: LitellmCreds, name = 'PassThroughStack'): FakeS
83
90
  }).pipe(
84
91
  options.adopt === undefined ? (e) => e : Effect.provideService(AdoptPolicy, options.adopt),
85
92
  Effect.provideService(Alchemy.Stage, 'test'),
86
- Effect.provide(litellmCredentialsLayerFor(creds)),
93
+ Effect.provide(credentials(creds)),
87
94
  Effect.provide(FetchHttpClient.layer),
95
+ Effect.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn)),
88
96
  Effect.scoped,
89
97
  );
90
98
 
@@ -2,32 +2,18 @@
2
2
  * LiteLLM providers for Alchemy.
3
3
  *
4
4
  * ⛔ THIS BARREL IS THE PUBLIC API, deliberately smaller than the directory — `fake-litellm.ts`
5
- * and the internals `pass-through-form.ts`/`client.ts` need but a consumer should not depend on
6
- * directly are not re-exported here (netbox/index.ts's own rule).
5
+ * and the internals `pass-through-form.ts`/`operations.ts` need but a consumer should not depend
6
+ * on directly are not re-exported here (netbox/index.ts's own rule).
7
+ * ★ CREDENTIALS AND TYPES NOW COME FROM `@distilled.cloud/litellm` ITSELF (2026-09-24, moved off a
8
+ * hand-rolled `client.ts`/`credentials.ts` the same way `/netbox` and `/forgejo` did) — a
9
+ * consumer building its own credentials layer imports `Credentials`/`CredentialsFromEnv`/
10
+ * `credentials` from `@distilled.cloud/litellm/Credentials` directly, not through this barrel.
7
11
  */
8
- export {
9
- LITELLM_PROXY_API_KEY_ENV,
10
- LITELLM_PROXY_URL_ENV,
11
- LitellmCredentials,
12
- type LitellmCreds,
13
- LitellmCredentialsError,
14
- litellmCredentialsLayer,
15
- litellmCredentialsLayerFor,
16
- } from './credentials.ts';
17
- export {
18
- LITELLM_OPERATIONS,
19
- LitellmBadRequestError,
20
- type LitellmError,
21
- LitellmHttpError,
22
- type LitellmRequirements,
23
- LitellmTransportError,
24
- LitellmUnauthorizedError,
25
- } from './client.ts';
26
12
  export type {
27
13
  PassThroughEndpointResponse,
28
14
  PassThroughGenericEndpoint,
29
15
  PassThroughGuardrailSettings,
30
- } from './generated/pass-through.ts';
16
+ } from '@distilled.cloud/litellm/misc';
31
17
  export {
32
18
  LiteLLMPassThroughEndpoint,
33
19
  LiteLLMPassThroughEndpointProvider,
@@ -0,0 +1,149 @@
1
+ /**
2
+ * The four LiteLLM `/config/pass_through_endpoint*` calls this package writes to and reads from —
3
+ * now through `@distilled.cloud/litellm`'s typed operations (S23), not a hand-rolled `HttpClient`
4
+ * client. `client.ts` is retired; nothing else in this package imported it.
5
+ *
6
+ * ⛔ STILL WRAPPED IN A PER-BASE-URL SEMAPHORE, UNCHANGED FROM `client.ts`. LiteLLM stores every
7
+ * pass-through endpoint in ONE `general_settings.pass_through_endpoints` list
8
+ * (`pass_through_endpoints.py` at tag v1.100.0, mirrored in the SDK's own `misc.ts` docstrings),
9
+ * so create/update/delete are each a read-modify-write of the WHOLE list through
10
+ * `update_config_general_settings`. Two of this package's own calls racing each other — two
11
+ * endpoints created in the same deploy — would each read the list, add their own row and write
12
+ * it back, and the second write would silently drop the first row. The SDK has no opinion on
13
+ * this: it is a quirk of how THIS vendor stores the resource, not a distilled protocol concern,
14
+ * so it stays here, one layer above the typed calls. One permit per proxy serialises this
15
+ * package's own writes; it cannot serialise against the LiteLLM UI or a second deploy process,
16
+ * which is why `pass-through-endpoint.ts`'s `reconcile` reads back after writing rather than
17
+ * trusting the call that just returned (docs/litellm.md).
18
+ * ⛔ NO STATUS SNIFFING (S21). Every failure the SDK's own typed operations declare — `BadRequest`
19
+ * (400), `NotFound` (404 on update only), `UnprocessableEntity` (422) plus the shared
20
+ * `Unauthorized`/`TooManyRequests`/`InternalServerError`/`BadGateway`/`ServiceUnavailable`/
21
+ * `GatewayTimeout` every operation carries — is a distilled patch's doing
22
+ * (`@distilled.cloud/litellm`'s own `patches/misc/*.json`, applied at generation time), never a
23
+ * status check here or in `pass-through-endpoint.ts`. Deleting an id LiteLLM does not have
24
+ * answers `BadRequest` (measured against the tag's `pass_through_endpoint_delete` — the id is
25
+ * looked up in the list and a miss raises `HTTPException(400)`, not 404) — but so does a
26
+ * genuinely-live row when the caller isn't PROXY_ADMIN, or the DB is disconnected. Telling those
27
+ * apart by re-listing, not by the `BadRequest`'s message text, is `pass-through-endpoint.ts`'s
28
+ * `deletePassThroughEndpoint`'s own job (S21 — a status-and-a-real-read decision, never body
29
+ * text) — this file only forwards the typed error.
30
+ */
31
+ import * as misc from '@distilled.cloud/litellm/misc';
32
+ import { Credentials } from '@distilled.cloud/litellm/Credentials';
33
+ import type { LitellmOpContext } from '@distilled.cloud/litellm/Protocol';
34
+ import type { ConfigError } from '@distilled.cloud/litellm/Errors';
35
+ import * as Effect from 'effect/Effect';
36
+ import * as Semaphore from 'effect/Semaphore';
37
+
38
+ export type { LitellmOpContext } from '@distilled.cloud/litellm/Protocol';
39
+ export type LitellmRequirements = LitellmOpContext;
40
+ export type {
41
+ PassThroughEndpointResponse,
42
+ PassThroughGenericEndpoint,
43
+ PassThroughGuardrailSettings,
44
+ } from '@distilled.cloud/litellm/misc';
45
+
46
+ const semaphores = new Map<string, Semaphore.Semaphore>();
47
+
48
+ /** One semaphore per base URL, created on first use. Never `Hash.structure` (S18) — a plain Map key. */
49
+ const semaphoreFor = (baseUrl: string): Effect.Effect<Semaphore.Semaphore> =>
50
+ Effect.sync(() => {
51
+ const existing = semaphores.get(baseUrl);
52
+ if (existing !== undefined) return existing;
53
+ const created = Semaphore.makeUnsafe(1);
54
+ semaphores.set(baseUrl, created);
55
+ return created;
56
+ });
57
+
58
+ /**
59
+ * Serialises a write against this call's own base URL — see the file header.
60
+ *
61
+ * Resolving `Credentials` can itself fail typed (`ConfigError` — S20/S24, credentials.ts), so
62
+ * the return type carries that alongside the wrapped operation's own error `E`; every call
63
+ * site's declared error type already includes `ConfigError` as a member of the SDK's shared
64
+ * `LitellmOpError` (protocol.ts), so this widening is a no-op for callers.
65
+ */
66
+ const mutate = <A, E>(
67
+ io: Effect.Effect<A, E, LitellmOpContext>,
68
+ ): Effect.Effect<A, E | ConfigError, LitellmOpContext> =>
69
+ Effect.gen(function* () {
70
+ const resolve = yield* Credentials;
71
+ const creds = yield* resolve;
72
+ const semaphore = yield* semaphoreFor(creds.apiBaseUrl);
73
+ return yield* semaphore.withPermits(1)(io);
74
+ });
75
+
76
+ /** GET /config/pass_through_endpoint — every row, config-file and DB alike. Not under the semaphore: reads don't race the field. */
77
+ export const listPassThroughEndpoints = (): Effect.Effect<
78
+ readonly misc.PassThroughGenericEndpoint[],
79
+ misc.GetPassThroughEndpointsConfigPassThroughEndpointGetError,
80
+ LitellmOpContext
81
+ > =>
82
+ misc
83
+ .getPassThroughEndpointsConfigPassThroughEndpointGet({})
84
+ .pipe(Effect.map((response) => response.endpoints));
85
+
86
+ /** POST /config/pass_through_endpoint. `body` carries `id` — see pass-through-form.ts. */
87
+ export const createPassThroughEndpoint = (
88
+ body: misc.PassThroughGenericEndpoint,
89
+ ): Effect.Effect<
90
+ void,
91
+ misc.CreatePassThroughEndpointsConfigPassThroughEndpointPostError,
92
+ LitellmOpContext
93
+ > => mutate(misc.createPassThroughEndpointsConfigPassThroughEndpointPost(body).pipe(Effect.asVoid));
94
+
95
+ /**
96
+ * POST /config/pass_through_endpoint/{endpoint_id}. Merges with `exclude_none` — see
97
+ * pass-through-form.ts. `body` is `pass-through-form.ts`'s own `UpdateBody`, not a blanket
98
+ * `Partial<>` — `path`/`target` are the two fields the SDK's request type still requires
99
+ * non-optionally on an update.
100
+ */
101
+ export const updatePassThroughEndpoint = (
102
+ endpointId: string,
103
+ body: Partial<Omit<misc.PassThroughGenericEndpoint, 'path' | 'target'>> &
104
+ Pick<misc.PassThroughGenericEndpoint, 'path' | 'target'>,
105
+ ): Effect.Effect<
106
+ void,
107
+ misc.UpdatePassThroughEndpointsConfigPassThroughEndpointEndpointIdPostError,
108
+ LitellmOpContext
109
+ > =>
110
+ mutate(
111
+ misc
112
+ .updatePassThroughEndpointsConfigPassThroughEndpointEndpointIdPost({
113
+ ...body,
114
+ endpoint_id: endpointId,
115
+ })
116
+ .pipe(Effect.asVoid),
117
+ );
118
+
119
+ /**
120
+ * DELETE /config/pass_through_endpoint?endpoint_id=… — idempotent BY THIS FUNCTION, not by the
121
+ * vendor. ⚠️ Measured at v1.100.0: deleting an id the list does not have raises `HTTPException(400)`
122
+ * from `pass_through_endpoint_delete`, which the SDK decodes as a typed `BadRequest` — but the SAME
123
+ * route also raises the SAME `BadRequest` for `not_allowed_access` (a non-PROXY_ADMIN caller) or a
124
+ * disconnected DB, on a row that never got deleted. A `BadRequest` alone cannot tell the two apart,
125
+ * so THIS FUNCTION DOESN'T GUESS FROM THE TAG ALONE: on `BadRequest` it re-lists (a GET — no
126
+ * PROXY_ADMIN required) and only treats the delete as already-done if `endpointId` is genuinely
127
+ * absent; otherwise the delete failed and this re-fails with the ORIGINAL error, not a message
128
+ * match (S21 — a status-and-a-real-read decision, never body text).
129
+ */
130
+ export const deletePassThroughEndpoint = (
131
+ endpointId: string,
132
+ ): Effect.Effect<
133
+ void,
134
+ misc.DeletePassThroughEndpointsConfigPassThroughEndpointDeleteError,
135
+ LitellmOpContext
136
+ > =>
137
+ mutate(
138
+ misc
139
+ .deletePassThroughEndpointsConfigPassThroughEndpointDelete({ endpoint_id: endpointId })
140
+ .pipe(
141
+ Effect.asVoid,
142
+ Effect.catchTag('BadRequest', (original) =>
143
+ Effect.gen(function* () {
144
+ const rows = yield* listPassThroughEndpoints();
145
+ if (rows.some((row) => row.id === endpointId)) return yield* Effect.fail(original);
146
+ }),
147
+ ),
148
+ ),
149
+ );
@@ -13,15 +13,16 @@
13
13
  * "already gone" only if the row is actually absent.
14
14
  */
15
15
  import { describe, expect, test } from 'bun:test';
16
+ import { credentials } from '@distilled.cloud/litellm/Credentials';
16
17
  import * as Effect from 'effect/Effect';
18
+ import * as Layer from 'effect/Layer';
17
19
  import * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';
20
+ import { FAKE_BASE, startFakeLitellm } from './fake-litellm.ts';
18
21
  import {
19
22
  type LitellmRequirements,
20
23
  deletePassThroughEndpoint,
21
24
  listPassThroughEndpoints,
22
- } from './client.ts';
23
- import { litellmCredentialsLayerFor } from './credentials.ts';
24
- import { startFakeLitellm } from './fake-litellm.ts';
25
+ } from './operations.ts';
25
26
 
26
27
  const MASTER_KEY = 'sk-test-master';
27
28
  const PROPS = {
@@ -32,35 +33,31 @@ const PROPS = {
32
33
  target: 'https://api.bria.ai',
33
34
  };
34
35
 
35
- const run = <A, E>(baseUrl: string, effect: Effect.Effect<A, E, LitellmRequirements>): Promise<A> =>
36
+ const run = <A, E>(
37
+ fetchFn: typeof globalThis.fetch,
38
+ effect: Effect.Effect<A, E, LitellmRequirements>,
39
+ ): Promise<A> =>
36
40
  Effect.runPromise(
37
41
  effect.pipe(
38
42
  Effect.provide(FetchHttpClient.layer),
39
- Effect.provide(litellmCredentialsLayerFor({ apiKey: MASTER_KEY, baseUrl })),
43
+ Effect.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn)),
44
+ Effect.provide(credentials({ apiKey: MASTER_KEY, baseUrl: FAKE_BASE })),
40
45
  ) as Effect.Effect<A, E, never>,
41
46
  );
42
47
 
43
48
  describe('a delete forbidden for a reason other than "already gone"', () => {
44
49
  test('is not swallowed: it fails, and the row is still live', async () => {
45
50
  const fake = startFakeLitellm({ forbidDelete: true, masterKey: MASTER_KEY, seed: [PROPS] });
46
- try {
47
- const result = run(fake.url, deletePassThroughEndpoint('live-1'));
48
- await expect(result).rejects.toThrow();
49
- const rows = await run(fake.url, listPassThroughEndpoints());
50
- expect(rows.some((r) => r.id === 'live-1')).toBe(true);
51
- } finally {
52
- fake.stop();
53
- }
51
+ const result = run(fake.fetch, deletePassThroughEndpoint('live-1'));
52
+ await expect(result).rejects.toThrow();
53
+ const rows = await run(fake.fetch, listPassThroughEndpoints());
54
+ expect(rows.some((r) => r.id === 'live-1')).toBe(true);
54
55
  });
55
56
 
56
57
  test('a genuinely-gone id still succeeds with no error (idempotent delete preserved)', async () => {
57
58
  const fake = startFakeLitellm({ forbidDelete: true, masterKey: MASTER_KEY });
58
- try {
59
- await expect(
60
- run(fake.url, deletePassThroughEndpoint('never-existed')),
61
- ).resolves.toBeUndefined();
62
- } finally {
63
- fake.stop();
64
- }
59
+ await expect(
60
+ run(fake.fetch, deletePassThroughEndpoint('never-existed')),
61
+ ).resolves.toBeUndefined();
65
62
  });
66
63
  });
@@ -15,11 +15,11 @@
15
15
  * caught this: it calls `h.reconcile` directly with the SAME instanceId and the OLD output, a
16
16
  * call shape the real engine never produces for a `replace`.
17
17
  */
18
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
18
+ import { beforeEach, describe, expect, test } from 'bun:test';
19
19
  import * as RemovalPolicy from 'alchemy/RemovalPolicy';
20
20
  import * as Effect from 'effect/Effect';
21
+ import { FAKE_BASE, type FakeLitellm, startFakeLitellm } from './fake-litellm.ts';
21
22
  import { type FakeStack, fakeStack, writesOf } from './fake-stack.ts';
22
- import { type FakeLitellm, startFakeLitellm } from './fake-litellm.ts';
23
23
  import { LiteLLMPassThroughEndpoint } from './pass-through-endpoint.ts';
24
24
  import type { PassThroughEndpointProps } from './pass-through-form.ts';
25
25
 
@@ -29,9 +29,8 @@ let fake: FakeLitellm;
29
29
  let stack: FakeStack;
30
30
  beforeEach(() => {
31
31
  fake = startFakeLitellm({ masterKey: MASTER_KEY });
32
- stack = fakeStack({ apiKey: MASTER_KEY, baseUrl: fake.url });
32
+ stack = fakeStack({ apiKey: MASTER_KEY, baseUrl: FAKE_BASE }, fake.fetch);
33
33
  });
34
- afterEach(() => fake.stop());
35
34
 
36
35
  /** ★ `RemovalPolicy.destroy()`, deliberately: under the (non-default) `retain`, `deleteFirst`'s own
37
36
  * old-generation delete is skipped by the engine (Apply.ts's `deleteOldGenerations`), so a path
@@ -2,15 +2,16 @@
2
2
  * `LiteLLM.PassThroughEndpoint`'s lifecycle handlers, called directly against `fake-litellm.ts` —
3
3
  * the loopback-fake pattern this kit uses in place of `alchemy/Test/*` (H12, no live writes).
4
4
  */
5
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
5
+ import { beforeEach, describe, expect, test } from 'bun:test';
6
+ import { credentials } from '@distilled.cloud/litellm/Credentials';
6
7
  import { Unowned } from 'alchemy/AdoptPolicy';
7
8
  import { Stack } from 'alchemy/Stack';
8
9
  import { Stage } from 'alchemy/Stage';
9
10
  import * as Effect from 'effect/Effect';
11
+ import * as Layer from 'effect/Layer';
10
12
  import * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';
11
- import { litellmCredentialsLayerFor } from './credentials.ts';
12
- import { type FakeLitellm, startFakeLitellm } from './fake-litellm.ts';
13
- import { createPassThroughEndpoint } from './client.ts';
13
+ import { FAKE_BASE, type FakeLitellm, startFakeLitellm } from './fake-litellm.ts';
14
+ import { createPassThroughEndpoint } from './operations.ts';
14
15
  import {
15
16
  LitellmConfigPathConflictError,
16
17
  LitellmLiteralSecretHeaderError,
@@ -22,11 +23,11 @@ const MASTER_KEY = 'sk-test-master';
22
23
  const INSTANCE_ID = 'a'.repeat(32);
23
24
  const FAKE_STACK = { actions: {}, bindings: {}, name: 's', resources: {}, stage: 'test' } as never;
24
25
 
26
+ /** No `.stop()` any more — `fake.fetch` is a plain function, no real socket to close. */
25
27
  let fake: FakeLitellm;
26
28
  beforeEach(() => {
27
29
  fake = startFakeLitellm({ masterKey: MASTER_KEY });
28
30
  });
29
- afterEach(() => fake.stop());
30
31
 
31
32
  const run = <A, E>(effect: Effect.Effect<A, E, never>): Promise<A> =>
32
33
  Effect.runPromise(effect as Effect.Effect<A, E, never>);
@@ -34,7 +35,8 @@ const run = <A, E>(effect: Effect.Effect<A, E, never>): Promise<A> =>
34
35
  const withServices = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
35
36
  effect.pipe(
36
37
  Effect.provide(FetchHttpClient.layer),
37
- Effect.provide(litellmCredentialsLayerFor({ apiKey: MASTER_KEY, baseUrl: fake.url })),
38
+ Effect.provide(Layer.succeed(FetchHttpClient.Fetch, fake.fetch)),
39
+ Effect.provide(credentials({ apiKey: MASTER_KEY, baseUrl: FAKE_BASE })),
38
40
  Effect.provideService(Stage, 'test'),
39
41
  Effect.provideService(Stack, FAKE_STACK),
40
42
  ) as Effect.Effect<A, E, never>;
@@ -150,7 +152,6 @@ describe('two endpoints created in one deploy', () => {
150
152
 
151
153
  describe('a path held by an is_from_config row', () => {
152
154
  test('is refused at read, before any write', async () => {
153
- fake.stop();
154
155
  fake = startFakeLitellm({
155
156
  masterKey: MASTER_KEY,
156
157
  seed: [{ ...PROPS, id: 'cfg-1', is_from_config: true }],
@@ -167,7 +168,6 @@ describe('a path held by an is_from_config row', () => {
167
168
 
168
169
  describe('a foreign DB row on the same path', () => {
169
170
  test('is Unowned, and read works with output undefined', async () => {
170
- fake.stop();
171
171
  fake = startFakeLitellm({
172
172
  masterKey: MASTER_KEY,
173
173
  seed: [{ ...PROPS, id: 'foreign-1', is_from_config: false }],