@homeflare/alchemy 0.41.0 → 0.43.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 (115) hide show
  1. package/dist/litellm/budget-errors.d.ts +36 -0
  2. package/dist/litellm/budget-errors.d.ts.map +1 -0
  3. package/dist/litellm/budget-form.d.ts +50 -0
  4. package/dist/litellm/budget-form.d.ts.map +1 -0
  5. package/dist/litellm/budget-operations.d.ts +20 -0
  6. package/dist/litellm/budget-operations.d.ts.map +1 -0
  7. package/dist/litellm/budget.d.ts +61 -0
  8. package/dist/litellm/budget.d.ts.map +1 -0
  9. package/dist/litellm/fake-litellm.d.ts +5 -0
  10. package/dist/litellm/fake-litellm.d.ts.map +1 -1
  11. package/dist/litellm/index.d.ts +2 -0
  12. package/dist/litellm/index.d.ts.map +1 -1
  13. package/dist/litellm/index.js +125 -2
  14. package/dist/litellm/index.js.map +9 -5
  15. package/dist/litellm/operations.d.ts +22 -0
  16. package/dist/litellm/operations.d.ts.map +1 -1
  17. package/dist/litellm/providers.d.ts +1 -1
  18. package/dist/litellm/providers.d.ts.map +1 -1
  19. package/dist/openbao/auth-role-form.d.ts +9 -0
  20. package/dist/openbao/auth-role-form.d.ts.map +1 -1
  21. package/dist/openbao/auth-role-wire.d.ts.map +1 -1
  22. package/dist/openbao/fake-engines-roles.d.ts.map +1 -1
  23. package/dist/openbao/index.js +9 -6
  24. package/dist/openbao/index.js.map +4 -4
  25. package/dist/postgres/connection.d.ts +20 -2
  26. package/dist/postgres/connection.d.ts.map +1 -1
  27. package/dist/postgres/database-sql.d.ts +1 -1
  28. package/dist/postgres/database-sql.d.ts.map +1 -1
  29. package/dist/postgres/database.d.ts +1 -1
  30. package/dist/postgres/database.d.ts.map +1 -1
  31. package/dist/postgres/index.d.ts +5 -3
  32. package/dist/postgres/index.d.ts.map +1 -1
  33. package/dist/postgres/index.js +124 -38
  34. package/dist/postgres/index.js.map +8 -7
  35. package/dist/postgres/providers.d.ts +4 -2
  36. package/dist/postgres/providers.d.ts.map +1 -1
  37. package/dist/postgres/psql-executor.d.ts +21 -0
  38. package/dist/postgres/psql-executor.d.ts.map +1 -0
  39. package/dist/proxmox/acl.d.ts.map +1 -1
  40. package/dist/proxmox/api-token.d.ts.map +1 -1
  41. package/dist/proxmox/ceph-flag-types.d.ts +57 -0
  42. package/dist/proxmox/ceph-flag-types.d.ts.map +1 -0
  43. package/dist/proxmox/ceph-flag-wire.d.ts +77 -0
  44. package/dist/proxmox/ceph-flag-wire.d.ts.map +1 -0
  45. package/dist/proxmox/ceph-flag.d.ts +15 -50
  46. package/dist/proxmox/ceph-flag.d.ts.map +1 -1
  47. package/dist/proxmox/ceph-osd-write.d.ts +10 -13
  48. package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
  49. package/dist/proxmox/credential-errors.d.ts +18 -4
  50. package/dist/proxmox/credential-errors.d.ts.map +1 -1
  51. package/dist/proxmox/credentials.d.ts +35 -1
  52. package/dist/proxmox/credentials.d.ts.map +1 -1
  53. package/dist/proxmox/group.d.ts.map +1 -1
  54. package/dist/proxmox/index.js +805 -757
  55. package/dist/proxmox/index.js.map +21 -19
  56. package/dist/proxmox/lease-cache.d.ts +67 -5
  57. package/dist/proxmox/lease-cache.d.ts.map +1 -1
  58. package/dist/proxmox/mint.d.ts +4 -2
  59. package/dist/proxmox/mint.d.ts.map +1 -1
  60. package/dist/proxmox/node-network.d.ts.map +1 -1
  61. package/dist/proxmox/role.d.ts.map +1 -1
  62. package/dist/proxmox/storage.d.ts.map +1 -1
  63. package/dist/proxmox/unreadable-read.d.ts +8 -1
  64. package/dist/proxmox/unreadable-read.d.ts.map +1 -1
  65. package/dist/proxmox/user.d.ts.map +1 -1
  66. package/dist/proxmox/zfs-pool.d.ts.map +1 -1
  67. package/docs/credentials.md +14 -1
  68. package/docs/litellm-budget.md +55 -0
  69. package/docs/litellm.md +7 -6
  70. package/docs/postgres.md +22 -0
  71. package/docs/proxmox-sdk-migration.md +45 -0
  72. package/package.json +3 -3
  73. package/src/litellm/budget-errors.ts +38 -0
  74. package/src/litellm/budget-form.ts +94 -0
  75. package/src/litellm/budget-operations.ts +51 -0
  76. package/src/litellm/budget.test.ts +105 -0
  77. package/src/litellm/budget.ts +127 -0
  78. package/src/litellm/fake-litellm.ts +50 -1
  79. package/src/litellm/index.ts +13 -0
  80. package/src/litellm/operations.ts +1 -1
  81. package/src/litellm/providers.ts +4 -1
  82. package/src/openbao/auth-role-form.test.ts +37 -0
  83. package/src/openbao/auth-role-form.ts +12 -1
  84. package/src/openbao/auth-role-wire.test.ts +1 -0
  85. package/src/openbao/auth-role-wire.ts +13 -1
  86. package/src/openbao/distilled-retry.test.ts +1 -0
  87. package/src/openbao/fake-engines-roles.ts +7 -1
  88. package/src/postgres/connection.ts +37 -3
  89. package/src/postgres/database-sql.ts +4 -3
  90. package/src/postgres/database.ts +3 -2
  91. package/src/postgres/index.ts +10 -3
  92. package/src/postgres/providers.ts +15 -2
  93. package/src/postgres/psql-executor.test.ts +106 -0
  94. package/src/postgres/psql-executor.ts +130 -0
  95. package/src/proxmox/acl.ts +7 -1
  96. package/src/proxmox/api-token.ts +7 -1
  97. package/src/proxmox/ceph-flag-types.ts +70 -0
  98. package/src/proxmox/ceph-flag-wire.test.ts +72 -0
  99. package/src/proxmox/ceph-flag-wire.ts +123 -0
  100. package/src/proxmox/ceph-flag.ts +77 -130
  101. package/src/proxmox/ceph-osd-write.ts +10 -13
  102. package/src/proxmox/credential-errors.ts +19 -5
  103. package/src/proxmox/credentials.test.ts +73 -1
  104. package/src/proxmox/credentials.ts +72 -1
  105. package/src/proxmox/group.ts +7 -1
  106. package/src/proxmox/lease-cache-mint.test.ts +70 -0
  107. package/src/proxmox/lease-cache.test.ts +83 -7
  108. package/src/proxmox/lease-cache.ts +74 -10
  109. package/src/proxmox/mint.ts +12 -5
  110. package/src/proxmox/node-network.ts +7 -1
  111. package/src/proxmox/role.ts +7 -1
  112. package/src/proxmox/storage.ts +7 -1
  113. package/src/proxmox/unreadable-read.ts +16 -4
  114. package/src/proxmox/user.ts +7 -1
  115. package/src/proxmox/zfs-pool.ts +7 -1
@@ -47,23 +47,49 @@ import * as Cache from 'effect/Cache';
47
47
  import * as Duration from 'effect/Duration';
48
48
  import * as Effect from 'effect/Effect';
49
49
  import * as Exit from 'effect/Exit';
50
- import { type ApiTarget, type PveCredential, type PveRole } from './credentials.ts';
50
+ import { type ApiTarget, type BaoEnvironment, type PveCredential, type PveRole } from './credentials.ts';
51
51
  /**
52
52
  * What a credential is cached under.
53
53
  *
54
- * ⛔ KEYED ON THE OPENBAO MOUNT AND ROLE, NOT ON A MEMBER HOSTNAME. A PVE API token is
55
- * cluster-wide — `user.cfg` is replicated — so node-b, node-c and node-d share one credential. Keying on
56
- * `api` would mint three identical tokens when failover rotates members.
54
+ * ⛔ KEYED ON THE OPENBAO MOUNT AND TIER, NOT ON A MEMBER HOSTNAME OR ON `role` (M3, red team on
55
+ * K-T1). A PVE API token is cluster-wide — `user.cfg` is replicated — so node-b, node-c and
56
+ * node-d share one credential. Keying on `api` would mint three identical tokens when failover
57
+ * rotates members. `role` USED to be part of this key too, but the minted credential depends
58
+ * only on `(mount, scheme, tier)` — `role` only ever fed `mintTier` to re-derive the tier the
59
+ * key already carries — so `roles: { read: 'x', provision: 'x' }` (one tier for both roles, the
60
+ * shape `docs/credentials.md` itself recommends) used to open TWO cache entries and mint TWO
61
+ * byte-for-byte interchangeable tokens for what is provably one credential. Dropping `role`
62
+ * collapses that back to one entry without changing the default case, where `tier === role`
63
+ * already.
57
64
  * ★ THE MOUNT CANNOT COLLIDE ACROSS CLUSTERS: `proxmox-c1`, `proxmox-c2` and `pbs-c1` are
58
65
  * distinct OpenBao mounts with distinct policies; two estates never share a mount name.
59
66
  * ★ A PLAIN OBJECT IS A SAFE KEY BECAUSE EFFECT COMPARES KEYS STRUCTURALLY — two separately built
60
67
  * literals with the same fields are one entry, which the probe above measured.
68
+ *
69
+ * ⛔ `tier` IS THE KEY, NOT `role` (K-T1). `targetForKey` below has to RECONSTRUCT a target from
70
+ * the key alone (it does not keep the caller's original `ApiTarget` around), and a
71
+ * `PveTarget.roles` override lives on THAT original target, not on `role` — so without `tier`
72
+ * in the key, every `leased()` call for an overridden role would resolve its override once at
73
+ * the first `keyOf`, then reuse whatever `targetForKey` happens to reconstruct on a cache miss,
74
+ * and TWO DIFFERENT overrides sharing one `{mount, scheme}` (e.g. one consumer's default `read`
75
+ * tier and another's `roles: {read: 'x'}` override, same mount) would collide into ONE cache
76
+ * entry and hand each other's credential to the wrong caller. Resolving the tier at `keyOf` —
77
+ * where the real target is still in hand — and keying on it closes that: two different tiers
78
+ * are two different entries, always.
61
79
  */
62
80
  export type LeaseKey = {
63
81
  readonly mount: string;
64
- readonly role: PveRole;
65
82
  readonly scheme: ApiTarget['scheme'];
83
+ readonly tier: string;
66
84
  };
85
+ /**
86
+ * ⚠️ EXPORTED FOR THE TEST (I1, red team on K-T1). This is the one line the whole PR exists
87
+ * for — `tier: mintTier(target, role)` is what makes an overridden `PveTarget.roles` reach the
88
+ * cached path at all. Nothing else in this file's test suite calls `keyOf` or `leased`
89
+ * directly, so without this export a regression here (e.g. reverting to `tier: role`) passed
90
+ * the whole suite silently.
91
+ */
92
+ export declare const keyOf: (target: ApiTarget, role: PveRole) => LeaseKey;
67
93
  /**
68
94
  * How long a freshly minted credential may be handed out again.
69
95
  *
@@ -87,6 +113,42 @@ export declare const timeToLive: (exit: Exit.Exit<PveCredential, unknown>) => Du
87
113
  * ⚠️ EXPORTED FOR THE TEST, which hands it a counting mint instead of calling OpenBao.
88
114
  */
89
115
  export declare const makeLeases: <E, R>(mintFor: (key: LeaseKey) => Effect.Effect<PveCredential, E, R>) => Effect.Effect<Cache.Cache<LeaseKey, PveCredential, E, R>, never, never>;
116
+ /**
117
+ * ⛔ CARRIES `key.tier` BACK IN AS A `roles` OVERRIDE (K-T1), SO `mint`'S OWN `mintTier` CALL
118
+ * REPRODUCES THE SAME TIER `keyOf` ALREADY RESOLVED. This target is synthetic — it is never the
119
+ * caller's real `PveTarget` (`members`/`api` are throwaway placeholders `mint` never reads) —
120
+ * and, since M3 dropped `role` from `LeaseKey`, it no longer even knows which semantic role
121
+ * produced `key.tier`. It doesn't need to: `roles: { read: key.tier }` plus always minting with
122
+ * the fixed role `'read'` (below) makes `mintTier(targetForKey(key), 'read') === key.tier`
123
+ * unconditionally, because `mintTier` only ever consults the ONE role it's called with. Which
124
+ * role the ORIGINAL caller asked for is irrelevant here — `keyOf` already folded that into
125
+ * `key.tier` before this function ever sees the key.
126
+ * ⚠️ THE PBS BRANCH MINTS WITH `key.tier` ITSELF AS THE ROLE, NOT `'read'`. A `PbsTarget` has no
127
+ * `roles` override (see `credentials.ts`'s ⛔ on `PbsTarget`), so `mintTier` for `scheme:'pbs'`
128
+ * always returns its `role` argument unchanged — meaning `key.tier` for a PBS key IS the literal
129
+ * role name (`'read'`/`'provision'`) `keyOf` recorded. Passing anything else here would ask
130
+ * OpenBao for the wrong tier.
131
+ * ⚠️ EXPORTED FOR THE TEST, which checks that round trip directly rather than only through the
132
+ * cache's externally visible behaviour.
133
+ */
134
+ export declare const targetForKey: (key: LeaseKey) => ApiTarget;
135
+ /**
136
+ * ⚠️ EXPORTED FOR THE TEST (second red-team pass on K-T1). `targetForKey`'s own tests call
137
+ * `mintTier(targetForKey(key), 'read')` to check the round trip, which hand-reproduces this
138
+ * function's fixed choice of `'read'` as the pve mint role rather than exercising it — change
139
+ * that choice here and those tests keep passing while a live cache miss mints the wrong tier.
140
+ * Testing this function directly (against a fake OpenBao) pins the choice itself.
141
+ *
142
+ * ⚠️ `env` DEFAULTS TO `process.env`, SAME AS `mint` ITSELF (credentials.ts) — the cache's real
143
+ * caller below (`Effect.runSync(makeLeases(mintForKey))`) always calls this with one argument,
144
+ * so the default is what production runs on. The parameter exists only so the test can pass a
145
+ * fake OpenBao's address without touching the real environment.
146
+ */
147
+ export declare const mintForKey: (key: LeaseKey, env?: BaoEnvironment) => Effect.Effect<{
148
+ leaseSeconds: number;
149
+ secret: string;
150
+ tokenId: string;
151
+ }, Error | import("./credential-errors.ts").PveCredentialDenied, import("effect/unstable/http/HttpClient").HttpClient>;
90
152
  /**
91
153
  * A credential for `role` on `target`: a still-valid lease when one is kept, one shared mint when
92
154
  * not.
@@ -1 +1 @@
1
- {"version":3,"file":"lease-cache.d.ts","sourceRoot":"","sources":["../../src/proxmox/lease-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAgBpF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;CACtC,CAAC;AAQF;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,SAAU,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,KAAG,QAAQ,CAAC,QAG3D,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,4EACX,CAAC;AASrF;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,WAAY,SAAS,QAAQ,OAAO,qJAA2C,CAAC"}
1
+ {"version":3,"file":"lease-cache.d.ts","sourceRoot":"","sources":["../../src/proxmox/lease-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AACpC,OAAO,EACL,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,OAAO,EAEb,MAAM,kBAAkB,CAAC;AAgB1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,WAAY,SAAS,QAAQ,OAAO,KAAG,QAIvD,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,SAAU,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,KAAG,QAAQ,CAAC,QAG3D,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,4EACX,CAAC;AAErF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY,QAAS,QAAQ,KAAG,SAGoC,CAAC;AAElF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,QAAS,QAAQ,QAAO,cAAc;;;;sHACwB,CAAC;AAItF;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,WAAY,SAAS,QAAQ,OAAO,qJAA2C,CAAC"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Minting one PVE/PBS credential from OpenBao: `GET /v1/<mount>/creds/<role>`, alive for five
2
+ * Minting one PVE/PBS credential from OpenBao: `GET /v1/<mount>/creds/<tier>`, alive for five
3
3
  * minutes. Split out of `credentials.ts` (2026-09-24) to keep that file's TYPES under the
4
4
  * 250-line cap once `PveCredentialDenied`'s catchers needed a real header there; the reasoning
5
5
  * for WHY the estate mints rather than stores a token is still credentials.ts's own header.
@@ -12,7 +12,9 @@ import * as HttpClient from 'effect/unstable/http/HttpClient';
12
12
  import { PveCredentialDenied } from './credential-errors.ts';
13
13
  import type { ApiTarget, BaoEnvironment, PveCredential, PveRole } from './credentials.ts';
14
14
  /**
15
- * Mint one credential for `role`: `GET /v1/<mount>/creds/<role>` on OpenBao's HTTP API.
15
+ * Mint one credential for `role`: `GET /v1/<mount>/creds/<tier>` on OpenBao's HTTP API, where
16
+ * `tier` is `mintTier(target, role)` (credentials.ts) — `role` itself unless `target.roles`
17
+ * overrides it (K-T1).
16
18
  *
17
19
  * 🔴 IT SHELLED OUT TO `bao read -format=json` UNTIL 2026-09-14. The case for the CLI was that one
18
20
  * client could not disagree with `bao kv get` about what this machine may do. <estate>/openbao then
@@ -1 +1 @@
1
- {"version":3,"file":"mint.d.ts","sourceRoot":"","sources":["../../src/proxmox/mint.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAIxC,OAAO,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAE9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAM1F;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,IAAI,WAAY,SAAS,QAAQ,OAAO,QAAO,cAAc;;;;sDAoGtE,CAAC;AAEL;;;;;GAKG;AACH,eAAO,MAAM,aAAa,eAAgB,aAAa,UAAU,SAAS,KAAG,MAGf,CAAC"}
1
+ {"version":3,"file":"mint.d.ts","sourceRoot":"","sources":["../../src/proxmox/mint.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAIxC,OAAO,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAE9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAM1F;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,IAAI,WAAY,SAAS,QAAQ,OAAO,QAAO,cAAc;;;;sDAwGtE,CAAC;AAEL;;;;;GAKG;AACH,eAAO,MAAM,aAAa,eAAgB,aAAa,UAAU,SAAS,KAAG,MAGf,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"node-network.d.ts","sourceRoot":"","sources":["../../src/proxmox/node-network.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EAGL,KAAK,gBAAgB,EAGtB,MAAM,wBAAwB,CAAC;AAOhC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1D,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAIrF,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,WAAW,kBAAmB,SAAQ,QAAQ,CAClD,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,gGAAgG;AAChG,eAAO,MAAM,kBAAkB,qDAE7B,CAAC;AAiCH,eAAO,MAAM,0BAA0B,wIAuFpC,CAAC"}
1
+ {"version":3,"file":"node-network.d.ts","sourceRoot":"","sources":["../../src/proxmox/node-network.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EAGL,KAAK,gBAAgB,EAGtB,MAAM,wBAAwB,CAAC;AAOhC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1D,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAIrF,YAAY,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,WAAW,kBAAmB,SAAQ,QAAQ,CAClD,qBAAqB,EACrB,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,gGAAgG;AAChG,eAAO,MAAM,kBAAkB,qDAE7B,CAAC;AAiCH,eAAO,MAAM,0BAA0B,wIA4FpC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"role.d.ts","sourceRoot":"","sources":["../../src/proxmox/role.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAa7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAStE,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAwBjE,eAAO,MAAM,mBAAmB,iIA+E7B,CAAC"}
1
+ {"version":3,"file":"role.d.ts","sourceRoot":"","sources":["../../src/proxmox/role.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAa7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAUtE,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAwBjE,eAAO,MAAM,mBAAmB,iIAoF7B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/proxmox/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAYtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,gGAAgG;AAChG,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;AAElD,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,6FAA6F;IAC7F,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAC9C,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,qGAAqG;AACrG,eAAO,MAAM,cAAc,iDAEzB,CAAC;AAEH,eAAO,MAAM,sBAAsB,oIAuGhC,CAAC"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/proxmox/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAatE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,gGAAgG;AAChG,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;AAElD,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,6FAA6F;IAC7F,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAC9C,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,qGAAqG;AACrG,eAAO,MAAM,cAAc,iDAEzB,CAAC;AAEH,eAAO,MAAM,sBAAsB,oIA4GhC,CAAC"}
@@ -45,6 +45,13 @@ export declare const readOrUnreadable: <A, E, R>(effect: Effect.Effect<A, E | Pv
45
45
  * in `bun run plan`'s own output (Effect's default logger), even though the Diff type has
46
46
  * nowhere to carry the distinction through to the printed action. See the header for why a log
47
47
  * line, not a failed diff or a fourth Diff action, is what "the plan reports" means here.
48
+ *
49
+ * ⛔ M6 (Opus red team, K-T1): NAMES `mount` AND `tier`, NOT JUST THE RESOURCE. Before this, the
50
+ * line read "no grant for this credential's role" with nothing to act on — an operator debugging
51
+ * a scoped-down `PveTarget.roles` override (the whole point of K-T1) had to go re-derive which
52
+ * mount and tier the read actually asked OpenBao for before they could even open that mount's
53
+ * policy. `PveCredentialDenied`'s own message already made this exact call — reading `tier`, not
54
+ * `role`, per that class's own ⛔ — so this warning takes the same two fields for the same reason.
48
55
  */
49
- export declare const unreadableWarning: (type: string, identity: string) => Effect.Effect<void>;
56
+ export declare const unreadableWarning: (type: string, identity: string, mount: string, tier: string) => Effect.Effect<void>;
50
57
  //# sourceMappingURL=unreadable-read.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"unreadable-read.d.ts","sourceRoot":"","sources":["../../src/proxmox/unreadable-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,sFAAsF;AACtF,eAAO,MAAM,UAAU,eAA2B,CAAC;AACnD,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,UAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,mBAAmB,EAAE,CAAC,CAAC,KACnD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,CAAC,CACkD,CAAC;AAExF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,SAAU,MAAM,YAAY,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAKlF,CAAC"}
1
+ {"version":3,"file":"unreadable-read.d.ts","sourceRoot":"","sources":["../../src/proxmox/unreadable-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAElE,sFAAsF;AACtF,eAAO,MAAM,UAAU,eAA2B,CAAC;AACnD,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,UAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,mBAAmB,EAAE,CAAC,CAAC,KACnD,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,CAAC,CACkD,CAAC;AAExF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,SACtB,MAAM,YACF,MAAM,SACT,MAAM,QACP,MAAM,KACX,MAAM,CAAC,MAAM,CAAC,IAAI,CAKlB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/proxmox/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AActE,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAEjE,eAAO,MAAM,mBAAmB,iIAgG7B,CAAC"}
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/proxmox/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAetE,MAAM,WAAW,SAAU,SAAQ,UAAU;IAC3C;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,WAAY,SAAQ,QAAQ,CAC3C,cAAc,EACd,SAAS,EACT,cAAc,EACd,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,eAAO,MAAM,WAAW,8CAAwC,CAAC;AAEjE,eAAO,MAAM,mBAAmB,iIAqG7B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"zfs-pool.d.ts","sourceRoot":"","sources":["../../src/proxmox/zfs-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvE,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvE;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAC9C,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,mGAAmG;AACnG,eAAO,MAAM,cAAc,iDAEzB,CAAC;AAEH,eAAO,MAAM,sBAAsB,oIAyEhC,CAAC"}
1
+ {"version":3,"file":"zfs-pool.d.ts","sourceRoot":"","sources":["../../src/proxmox/zfs-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAItE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvE,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvE;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ,CAC9C,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,eAAe,CAChB;CAAG;AAEJ,mGAAmG;AACnG,eAAO,MAAM,cAAc,iDAEzB,CAAC;AAEH,eAAO,MAAM,sBAAsB,oIA8EhC,CAAC"}
@@ -12,7 +12,20 @@ a denied grant rather than a missing file, and `Bearer ` + nothing 401s in a way
12
12
  like a bad credential.
13
13
 
14
14
  The Proxmox subpath mints every call's token from an OpenBao mount instead; see
15
- [proxmox.md](./proxmox.md).
15
+ [proxmox.md](./proxmox.md). Every call still asks for one of two semantic roles, `read` or
16
+ `provision` (`PveRole`, `credentials.ts`) — a `PveTarget` can optionally override which OpenBao
17
+ mint TIER NAME each one actually resolves to (`roles: { read?, provision? }`, K-T1, 2026-09-27):
18
+ one consumer with its own least-privilege tier for both roles on the SAME mount sets
19
+ `roles: { read: 'my-tier', provision: 'my-tier' }` and every call this package already makes
20
+ mints that tier instead, with no new call site anywhere in the package. `credentials.ts`'s
21
+ `mintTier` is the one place that resolution happens; `mint.ts` and `lease-cache.ts` both call it
22
+ rather than reading `role`/`target.roles` directly — see `mintTier`'s own header for why the
23
+ lease cache specifically needed it too, not just the mint call.
24
+
25
+ ⛔ **Override both roles or neither.** An unmentioned role is not scoped down — it still mints
26
+ the estate-wide `read`/`provision` tier. Setting only `roles: { read: 'my-tier' }` leaves every
27
+ reconcile/delete on that target minting the full-privilege `provision` credential while the plan
28
+ output looks entirely scoped down.
16
29
 
17
30
  The Forgejo subpath (2026-09-23, moved onto `@distilled.cloud/forgejo`) resolves credentials
18
31
  through the package's own `CredentialsFromEnv` layer, which reads `FORGEJO_URL` / `FORGEJO_TOKEN`
@@ -0,0 +1,55 @@
1
+ # LiteLLM — `LiteLLM.Budget`
2
+
3
+ One row of LiteLLM's `LiteLLM_BudgetTable` (`/budget/*`): the "tier" that virtual keys and teams
4
+ bind to (`hf-tier-agent`, `hf-tier-interactive`, …). Typed calls come from
5
+ `@distilled.cloud/litellm/budget_management`; credentials and refusals are the family's, see
6
+ [litellm.md](./litellm.md).
7
+
8
+ ## Monitoring, not limits
9
+
10
+ `max_budget` is a **hard cap**: LiteLLM's own field doc says "Requests will fail if this budget
11
+ (in USD) is exceeded". On 2026-09-28 an undeclared $60/day cap on `hf-tier-interactive` (made
12
+ through the API on 09-06, in no repo) 429'd every call of the claude2 fleet. So:
13
+
14
+ - `softBudget` is the prop to reach for: it "will NOT fail if this is exceeded" and fires the alert.
15
+ - `maxBudget` is **never defaulted**, and the plan is refused
16
+ (`LitellmBudgetHardCapUnjustifiedError`) unless `maxBudgetReason` is set. The reason lives in the
17
+ declaration, so a hard cap is a reviewed line of source, not an API side effect.
18
+
19
+ ```ts
20
+ export class Interactive extends LiteLLMBudget('Interactive', {
21
+ budgetId: 'hf-tier-interactive', // adopts the live row (needs --adopt)
22
+ budgetDuration: '1d',
23
+ softBudget: 60,
24
+ rpmLimit: 60,
25
+ tpmLimit: 6_000_000,
26
+ }) {}
27
+ ```
28
+
29
+ Declaring an adopted hard tier **without** `maxBudget` clears the live cap: the update sends an
30
+ explicit `null` for every limit the declaration omits.
31
+
32
+ ## Behaviour
33
+
34
+ | Concern | Rule |
35
+ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
36
+ | Identity | `budgetId` (else a deterministic physical name). A different declared id is a `replace`. |
37
+ | Adopt | A live row with the id and no state is `Unowned`; needs `--adopt`. |
38
+ | Removal | `defaultRemovalPolicy: 'retain'`. Opt in with `RemovalPolicy.destroy()`. Delete is idempotent: a `BadRequest` is swallowed only if a re-list shows it gone. |
39
+ | Read | `/budget/list` (whole table). `rpm_limit`/`tpm_limit` come back as decimal strings (`BigInt?`), converted to numbers. |
40
+ | Write check | Reconcile reads back; a limit the row still carries that the declaration dropped fails with `LitellmBudgetFieldNotClearedError`. |
41
+
42
+ ## Version: distilled copy is 1.100.0, the proxy is 1.103.0
43
+
44
+ The SDK is generated at v1.100.0. For the budget routes the request bodies used here
45
+ (`budget_id`, `soft_budget`, `max_budget`, `budget_duration`, `rpm_limit`, `tpm_limit`) are all in
46
+ that schema. **Not measured against 1.103.0** (no live proxy read was made, and the source for it
47
+ is not cloned): whether `/budget/update` accepts an explicit `null` to clear a limit, and what
48
+ happens on create of an existing id. The fake models 400 for both. The read-back check above
49
+ exists so an `exclude_none` merge fails loudly instead of leaving a hard cap in place.
50
+
51
+ ## Not modelled
52
+
53
+ `max_parallel_requests`, `model_max_budget` and `budget_reset_at`: the row read back
54
+ (`BudgetListItem`) does not carry them, so a declaration could never be diffed. Add them once a
55
+ read that returns them is measured. Keys and teams that reference a tier are separate resources.
package/docs/litellm.md CHANGED
@@ -16,12 +16,13 @@ against the tag's committed `schema.d.ts`; NOT a live read, because the referenc
16
16
  Measured at 1.100.0 (`schema.prisma`, `pass_through_endpoints.py`, and the mini's own
17
17
  `settings.yaml` `store_model_in_db: true`):
18
18
 
19
- | Family | Managed by this resource? |
20
- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
21
- | Pass-through endpoints | Yes — this package |
22
- | Models, virtual keys, teams, credentials, access groups | DB-backed, API-managed, but no kit resource yet |
23
- | `router_settings`, `litellm_settings`, `general_settings.master_key`/`database_url`/`store_model_in_db` | config-file only — read-only through the API |
24
- | A `config.yaml` pass-through entry (`is_from_config: true`) | Read-only through this resource — see Refusals |
19
+ | Family | Managed by this resource? |
20
+ | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
21
+ | Pass-through endpoints | Yes — this package |
22
+ | Budgets (`/budget/*`) | Yes — `LiteLLM.Budget`, [litellm-budget.md](./litellm-budget.md) |
23
+ | Models, virtual keys, teams, credentials, access groups | DB-backed, API-managed, but no kit resource yet |
24
+ | `router_settings`, `litellm_settings`, `general_settings.master_key`/`database_url`/`store_model_in_db` | config-file only — read-only through the API |
25
+ | A `config.yaml` pass-through entry (`is_from_config: true`) | Read-only through this resource — see Refusals |
25
26
 
26
27
  ## Credentials
27
28
 
package/docs/postgres.md CHANGED
@@ -123,3 +123,25 @@ mini's 19 already-live databases needs `adopt(true)` in the consuming stack.
123
123
 
124
124
  Whether `ALTER DATABASE … REFRESH COLLATION VERSION` should ever be offered as an explicit,
125
125
  separate operation (not part of `reconcile`) is open — nothing in this family calls it.
126
+
127
+ ## Runner transport (loopback-only clusters)
128
+
129
+ A cluster reachable only by a command — CT100's rootful podman container `postgres`, loopback
130
+ listener, local socket, user `postgres`, no password — cannot take a wire connection from the
131
+ mini. `postgresRunnerProviders({ run, database, username, template? })` (or
132
+ `postgresRunnerConnection` on its own) runs the same `Postgres.Database` reconcile through a
133
+ caller-supplied `PsqlRunner`, which prepends the transport, e.g. `ssh <host> sudo -n podman
134
+ exec -i postgres <argv>` with `stdin` piped through. The kit holds no host, container or sudo
135
+ rule; the consuming stack's runner does.
136
+
137
+ - Same props, same create-and-assert, same adopt/retain and drift semantics — only the
138
+ `PgExecutor` differs (`psql-executor.ts`).
139
+ - `psql` has no bind parameters, so string params are inlined as `quoteStringLiteral`
140
+ literals; a non-string param is refused. Rows come back through `json_agg`, so `oid` and
141
+ `datconnlimit` are numbers as on the socket path.
142
+ - `CREATE DATABASE … TEMPLATE "template0"` is the runner default (`template` overrides):
143
+ CT100's `template1` is ParadeDB's and would copy postgis/pg_ivm/paradedb objects. The socket
144
+ path adds no `TEMPLATE` clause, unchanged.
145
+ - Not covered: there is no `Postgres.Role` resource in the kit yet, so the owner role must
146
+ exist (`PostgresDatabaseOwnerMissing` otherwise). NetBox's 11 lost triggers on `pg_restore`
147
+ are a restore concern, out of this resource's scope.
@@ -44,6 +44,51 @@ SdnVnet, SdnSubnet, HaResource, HaRule and CephFlag.
44
44
  Their previous measured-none records
45
45
  are historical inventories, not a current census or permission to skip migration.
46
46
 
47
+ **Measured 2026-09-28, by parser** (Bun's `Transpiler.scanImports` — a real import-graph
48
+ read, not a text match — walked one hop through local imports so a family that only
49
+ reaches the hand client via the shared `resource.ts` factory is still attributed
50
+ correctly; run against `packages/alchemy/src/proxmox` and `src/openbao`):
51
+
52
+ | Package | distilled-only | hand-client-only | mixed | no transport import (pure logic/form/test) |
53
+ | --------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
54
+ | `alchemy/src/proxmox` (165 files) | 71 | 9 — `resource.ts` (the shared `pveHandlers` factory itself), `sdn-zone.ts`, `sdn-vnet.ts`, `sdn-subnet.ts`, `sdn-apply.ts`, `sdn-apply-read.ts`, `ha-resource.ts`, `ha-rule.ts`, `ceph-flag.ts` | 1 — `index.ts` (barrel, re-exports both) | 84 |
55
+ | `alchemy/src/openbao` (80 files) | 2 | 36 (every `Bao*` family but Policy) | 5 — `distilled.ts`, `policy-wire.ts`, `policy-identity.ts`, `auth-role-wire.ts`, `auth-role-identity.ts` | 37 |
56
+
57
+ The proxmox count confirms the doc text above with one addition: nine, not eight,
58
+ files are hand-client-only. Eight back the seven named families (SdnApply's family
59
+ spans two files, `sdn-apply.ts` and `sdn-apply-read.ts`); the ninth is `resource.ts`
60
+ — the shared `pveHandlers`/`pveOperations` factory itself, which imports
61
+ `./client.ts` and no `@distilled.cloud/*` module, so
62
+ the one-hop rule that correctly attributes the seven named families' hand-client use
63
+ attributes this file's own import the same way. It does not mean every distilled-only
64
+ family still calls through it: 17 of the 71 import `resource.ts`, and all 17 are
65
+ type-only (`PveRequirements`/`WithTarget`, or `WithPbsTarget` for three PBS files —
66
+ types are erased at build time, invisible to `scanImports`, so the parser correctly
67
+ leaves them out of this table). `ceph-osd.ts` is one of those 17, not an exception;
68
+ it is the only one of the 71 that even mentions `pveHandlers` at all, in a comment
69
+ explaining why it deliberately does not use the factory — read in full, it calls
70
+ neither factory function. No other PVE/PBS family has regressed to hand-client
71
+ since the 28-family migration
72
+ landed.
73
+
74
+ **OpenBao is far less migrated than this repo's own comments could suggest** — only
75
+ `BaoPolicy`'s wire/identity pair and the `distilled.ts` wrapper use distilled transport
76
+ at all today; every other family (`BaoMount`, `BaoAuthMethod`, `BaoAuthRole`'s form
77
+ paths, `BaoPkiRole`, `BaoSshRole`, `BaoCloudflareRole`, `BaoProxmoxRole`, `BaoJwtRole`,
78
+ `BaoJwtAuthConfig`, `BaoMfaTotpMethod`, `BaoMfaLoginEnforcement`, `BaoPlugin`,
79
+ rename/remount/role-reconcile helpers) is still on the hand `bao-http.ts` client.
80
+ `mount-wire.ts` specifically is blocked on more than volume: the distilled SDK's
81
+ `mounts.ts` operations all declare `errors: []` (no typed absence error), unlike the
82
+ sibling `policies.ts`'s `PolicyNotFound` — a clean migration needs that SDK gap patched
83
+ first, in the distilled clone, per [the interim route](./distilled-interim.md)'s
84
+ build-SDK-first step. Two `codex/`-prefixed worktrees already exist in the distilled
85
+ clone (`openbao-mount-map`, `openbao-policy-runtime`) with unmerged work toward exactly
86
+ this: `openbao-mount-map`'s six commits generate the OpenBao SDK from a disposable local
87
+ 2.6.2 server and add a typed mount-inventory response shape (`protocol-mount-tables.ts`,
88
+ provenance in `patches/mount-tables.md`), but its `mounts.ts` operations still declare
89
+ `errors: []` — the inventory-shape fix and the typed-absence-error fix are two separate
90
+ gaps, and only the first is done there.
91
+
47
92
  - NetworkApply reads `listNodeNetwork`. A string `changes` sibling is preserved by
48
93
  the protocol and decoded by that operation's union. The diff is counted and does
49
94
  not enter state or logs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homeflare/alchemy",
3
- "version": "0.41.0",
3
+ "version": "0.43.0",
4
4
  "description": "Custom Alchemy providers for Cloudflare, Proxmox, OpenBao, Forgejo, Talos, launchd and Caddy, and a verifier that proves adoptions are no-ops.",
5
5
  "homepage": "https://github.com/taslabs-net/homeflare-kit/tree/main/packages/alchemy#readme",
6
6
  "bugs": "https://github.com/taslabs-net/homeflare-kit/issues",
@@ -128,12 +128,12 @@
128
128
  "dependencies": {
129
129
  "@distilled.cloud/caddy": "npm:@homeflare/distilled-caddy@0.2.2",
130
130
  "@distilled.cloud/grafana": "npm:@homeflare/distilled-grafana@0.2.0",
131
- "@distilled.cloud/litellm": "npm:@homeflare/distilled-litellm@0.2.0",
131
+ "@distilled.cloud/litellm": "npm:@homeflare/distilled-litellm@0.3.0",
132
132
  "@distilled.cloud/netbox": "npm:@homeflare/distilled-netbox@0.3.0",
133
133
  "@distilled.cloud/openbao": "npm:@homeflare/distilled-openbao@0.2.2",
134
134
  "@distilled.cloud/opnsense": "npm:@homeflare/distilled-opnsense@0.3.0",
135
135
  "@distilled.cloud/paperless-ngx": "npm:@homeflare/distilled-paperless-ngx@0.3.0",
136
- "@distilled.cloud/proxmox": "npm:@homeflare/distilled-proxmox@0.3.3",
136
+ "@distilled.cloud/proxmox": "npm:@homeflare/distilled-proxmox@0.3.4",
137
137
  "@distilled.cloud/proxmox-backup": "npm:@homeflare/distilled-proxmox-backup@0.3.1",
138
138
  "@distilled.cloud/unifi-network": "npm:@homeflare/distilled-unifi-network@0.4.0"
139
139
  },
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The typed refusals `LiteLLM.Budget` raises on top of `@distilled.cloud/litellm`'s own SDK errors.
3
+ */
4
+ import type * as budgets from '@distilled.cloud/litellm/budget_management';
5
+ import * as Data from 'effect/Data';
6
+
7
+ /** A `maxBudget` (a HARD cap: LiteLLM answers 429 once it is spent) declared with no reason. */
8
+ export class LitellmBudgetHardCapUnjustifiedError extends Data.TaggedError(
9
+ 'LitellmBudgetHardCapUnjustifiedError',
10
+ )<{
11
+ readonly budgetId: string | undefined;
12
+ }> {}
13
+
14
+ /**
15
+ * The write returned no error but the row still carries a limit the declaration does not.
16
+ * `/budget/update` may merge with `exclude_none` (the way `/config/pass_through_endpoint` does),
17
+ * in which case a limit can never be cleared by an update. Refused, never claimed as done.
18
+ */
19
+ export class LitellmBudgetFieldNotClearedError extends Data.TaggedError(
20
+ 'LitellmBudgetFieldNotClearedError',
21
+ )<{
22
+ readonly budgetId: string;
23
+ readonly fields: readonly string[];
24
+ }> {}
25
+
26
+ /** `/budget/info` answered something that is not a list of budget rows. */
27
+ export class LitellmBudgetUnreadableError extends Data.TaggedError('LitellmBudgetUnreadableError')<{
28
+ readonly budgetId: string;
29
+ }> {}
30
+
31
+ export type BudgetError =
32
+ | budgets.PostInfoBudgetBudgetInfoError
33
+ | budgets.PostNewBudgetBudgetNewError
34
+ | budgets.UpdateBudgetBudgetUpdatePostError
35
+ | budgets.DeleteBudgetBudgetDeletePostError
36
+ | LitellmBudgetHardCapUnjustifiedError
37
+ | LitellmBudgetFieldNotClearedError
38
+ | LitellmBudgetUnreadableError;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Props, wire bodies and comparison for `LiteLLM.Budget`, testable without a server.
3
+ *
4
+ * ⛔ NO SILENT `max_budget`. LiteLLM's own field doc: "Requests will fail if this budget (in USD)
5
+ * is exceeded" — a hard cap. On 2026-09-28 an undeclared $60/day `max_budget` on
6
+ * `hf-tier-interactive` 429'd the whole claude2 fleet. So `maxBudget` is never defaulted and is
7
+ * refused unless `maxBudgetReason` says why; `softBudget` is the monitoring field (it "will NOT
8
+ * fail if this is exceeded", it fires the alert).
9
+ * ⚠️ NOT MODELLED: `max_parallel_requests`, `model_max_budget`, `budget_reset_at`. The row read
10
+ * back (`BudgetListItem`, generated at LiteLLM 1.100.0) has none of them, so a declaration of
11
+ * them could never be diffed. Add them when a read that returns them is measured.
12
+ */
13
+ import type * as budgets from '@distilled.cloud/litellm/budget_management';
14
+
15
+ export interface BudgetProps {
16
+ /** Live budget id. Set it to adopt an existing row (`hf-tier-agent`); default is a deterministic physical name. */
17
+ readonly budgetId?: string;
18
+ /** Alert threshold in USD. Requests keep flowing when exceeded. The monitoring field. */
19
+ readonly softBudget?: number;
20
+ /** HARD cap in USD: requests fail (429) once spent. Refused unless `maxBudgetReason` is set. */
21
+ readonly maxBudget?: number;
22
+ /** Why a hard cap is wanted. Required with `maxBudget`; recorded in the declaration only. */
23
+ readonly maxBudgetReason?: string;
24
+ /** Reset period, e.g. `'1d'`, `'28d'`. */
25
+ readonly budgetDuration?: string;
26
+ /** Requests per minute. */
27
+ readonly rpmLimit?: number;
28
+ /** Tokens per minute. */
29
+ readonly tpmLimit?: number;
30
+ }
31
+
32
+ export interface BudgetAttributes {
33
+ readonly budgetId: string;
34
+ readonly softBudget: number | null;
35
+ readonly maxBudget: number | null;
36
+ readonly budgetDuration: string | null;
37
+ readonly rpmLimit: number | null;
38
+ readonly tpmLimit: number | null;
39
+ }
40
+
41
+ /** Declared field → wire field. The single list every comparison and body walks. */
42
+ const FIELDS = [
43
+ ['softBudget', 'soft_budget'],
44
+ ['maxBudget', 'max_budget'],
45
+ ['budgetDuration', 'budget_duration'],
46
+ ['rpmLimit', 'rpm_limit'],
47
+ ['tpmLimit', 'tpm_limit'],
48
+ ] as const;
49
+
50
+ export const isHardCapUnjustified = (props: BudgetProps): boolean =>
51
+ props.maxBudget !== undefined && (props.maxBudgetReason ?? '').trim() === '';
52
+
53
+ /** Create body: only what is declared. */
54
+ export const createBody = (
55
+ props: BudgetProps,
56
+ budgetId: string,
57
+ ): budgets.PostNewBudgetBudgetNewRequest => {
58
+ const body: Record<string, unknown> = { budget_id: budgetId };
59
+ for (const [prop, wire] of FIELDS) if (props[prop] !== undefined) body[wire] = props[prop];
60
+ return body as budgets.PostNewBudgetBudgetNewRequest;
61
+ };
62
+
63
+ /** Update body: declared fields, plus an explicit `null` for a live limit the declaration dropped. */
64
+ export const updateBody = (
65
+ props: BudgetProps,
66
+ live: BudgetAttributes,
67
+ ): budgets.UpdateBudgetBudgetUpdatePostRequest => {
68
+ const body: Record<string, unknown> = { budget_id: live.budgetId };
69
+ for (const [prop, wire] of FIELDS) {
70
+ if (props[prop] !== undefined) body[wire] = props[prop];
71
+ else if (live[prop] !== null) body[wire] = null;
72
+ }
73
+ return body as budgets.UpdateBudgetBudgetUpdatePostRequest;
74
+ };
75
+
76
+ /** Wire names of the fields where live and declared disagree. */
77
+ export const differing = (live: BudgetAttributes, props: BudgetProps): readonly string[] =>
78
+ FIELDS.filter(([prop]) => live[prop] !== (props[prop] ?? null)).map(([, wire]) => wire);
79
+
80
+ const numberOrNull = (value: unknown): number | null =>
81
+ value === null || value === undefined || value === '' ? null : Number(value);
82
+
83
+ /**
84
+ * One row of `/budget/info`. `tpm_limit`/`rpm_limit` are `BigInt?` in LiteLLM's schema and come
85
+ * back as decimal strings (the generated `BudgetListItem` says so), hence `Number()`.
86
+ */
87
+ export const toAttributes = (row: Record<string, unknown>): BudgetAttributes => ({
88
+ budgetDuration: typeof row['budget_duration'] === 'string' ? row['budget_duration'] : null,
89
+ budgetId: String(row['budget_id']),
90
+ maxBudget: numberOrNull(row['max_budget']),
91
+ rpmLimit: numberOrNull(row['rpm_limit']),
92
+ softBudget: numberOrNull(row['soft_budget']),
93
+ tpmLimit: numberOrNull(row['tpm_limit']),
94
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The four LiteLLM `/budget/*` calls `LiteLLM.Budget` uses, through the SDK's typed operations.
3
+ *
4
+ * ★ READS GO THROUGH `/budget/list` (the whole table, a bare array), not `/budget/info`. Both
5
+ * answer `body: unknown` in the generated schema, and what `/budget/info` does for an id that is
6
+ * not there is unmeasured; a list answers absence unambiguously.
7
+ * ⚠️ Unlike pass-through endpoints, each budget is its own DB row, so no semaphore.
8
+ * ⚠️ DELETE is idempotent BY THIS FUNCTION: a `BadRequest` re-lists and is swallowed only when the
9
+ * id is genuinely absent, never on message text (pass-through `operations.ts`' rule).
10
+ */
11
+ import * as budgets from '@distilled.cloud/litellm/budget_management';
12
+ import * as Effect from 'effect/Effect';
13
+ import { type LitellmOpContext, throughFetch } from './operations.ts';
14
+ import { type BudgetAttributes, toAttributes } from './budget-form.ts';
15
+ import { LitellmBudgetUnreadableError } from './budget-errors.ts';
16
+
17
+ export const listBudgets = (): Effect.Effect<
18
+ readonly BudgetAttributes[],
19
+ budgets.ListBudgetBudgetListGetError | LitellmBudgetUnreadableError,
20
+ LitellmOpContext
21
+ > =>
22
+ throughFetch(budgets.listBudgetBudgetListGet({})).pipe(
23
+ // ⚠️ MEASURED with the fake: the SDK types this `{ body: unknown }` but hands a bare JSON array
24
+ // back as the array itself, so accept both shapes and refuse anything else.
25
+ Effect.flatMap((response) => {
26
+ const rows: unknown = Array.isArray(response)
27
+ ? response
28
+ : (response as { body?: unknown }).body;
29
+ return Array.isArray(rows)
30
+ ? Effect.succeed(rows.map((row) => toAttributes(row as Record<string, unknown>)))
31
+ : Effect.fail(new LitellmBudgetUnreadableError({ budgetId: '*' }));
32
+ }),
33
+ );
34
+
35
+ export const createBudget = (body: budgets.PostNewBudgetBudgetNewRequest) =>
36
+ throughFetch(budgets.postNewBudgetBudgetNew(body)).pipe(Effect.asVoid);
37
+
38
+ export const updateBudget = (body: budgets.UpdateBudgetBudgetUpdatePostRequest) =>
39
+ throughFetch(budgets.updateBudgetBudgetUpdatePost(body)).pipe(Effect.asVoid);
40
+
41
+ export const deleteBudget = (id: string) =>
42
+ throughFetch(budgets.deleteBudgetBudgetDeletePost({ id })).pipe(
43
+ Effect.asVoid,
44
+ Effect.catchTag('BadRequest', (original) =>
45
+ listBudgets().pipe(
46
+ Effect.flatMap((rows) =>
47
+ rows.some((row) => row.budgetId === id) ? Effect.fail(original) : Effect.void,
48
+ ),
49
+ ),
50
+ ),
51
+ );