@homeflare/alchemy 0.37.0 → 0.37.2
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.
- package/dist/proxmox/ceph-daemon-wire.d.ts +5 -7
- package/dist/proxmox/ceph-daemon-wire.d.ts.map +1 -1
- package/dist/proxmox/ceph-daemon.d.ts +7 -8
- package/dist/proxmox/ceph-daemon.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs-distilled.d.ts +31 -5
- package/dist/proxmox/ceph-fs-distilled.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs-wire.d.ts +5 -22
- package/dist/proxmox/ceph-fs-wire.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs.d.ts +5 -6
- package/dist/proxmox/ceph-fs.d.ts.map +1 -1
- package/dist/proxmox/ceph-pool-settle.d.ts +9 -8
- package/dist/proxmox/ceph-pool-settle.d.ts.map +1 -1
- package/dist/proxmox/ceph-pool-wire.d.ts +11 -20
- package/dist/proxmox/ceph-pool-wire.d.ts.map +1 -1
- package/dist/proxmox/group-wire.d.ts +8 -13
- package/dist/proxmox/group-wire.d.ts.map +1 -1
- package/dist/proxmox/index.js +296 -326
- package/dist/proxmox/index.js.map +14 -14
- package/dist/proxmox/node-network-wire.d.ts +1 -1
- package/dist/proxmox/node-network-wire.d.ts.map +1 -1
- package/dist/proxmox/storage-wire.d.ts +8 -11
- package/dist/proxmox/storage-wire.d.ts.map +1 -1
- package/dist/proxmox/unreadable-read.d.ts +7 -7
- package/dist/proxmox/user-wire.d.ts +8 -11
- package/dist/proxmox/user-wire.d.ts.map +1 -1
- package/dist/proxmox/user.d.ts +5 -5
- package/dist/proxmox/zfs-pool-wire.d.ts +1 -1
- package/dist/proxmox/zfs-pool-wire.d.ts.map +1 -1
- package/docs/changelog/README.md +22 -0
- package/docs/changelog/archive-01.md +176 -0
- package/docs/changelog/archive-02.md +180 -0
- package/docs/changelog/archive-03.md +179 -0
- package/docs/changelog/archive-04.md +178 -0
- package/docs/changelog/archive-05.md +177 -0
- package/docs/changelog/archive-06.md +180 -0
- package/docs/changelog/archive-07.md +180 -0
- package/docs/changelog/archive-08.md +178 -0
- package/docs/changelog/archive-09.md +181 -0
- package/docs/changelog/archive-10.md +182 -0
- package/docs/changelog/archive-11.md +182 -0
- package/docs/changelog/archive-12.md +183 -0
- package/docs/changelog/archive-13.md +180 -0
- package/docs/changelog/archive-14.md +178 -0
- package/docs/changelog/archive-15.md +181 -0
- package/docs/changelog/archive-16.md +182 -0
- package/docs/changelog/archive-17.md +183 -0
- package/docs/changelog/archive-18.md +55 -0
- package/docs/distilled-interim.md +15 -3
- package/docs/provider-standard.md +24 -6
- package/docs/proxmox.md +26 -0
- package/docs/upstream-conformance-bun.md +67 -0
- package/docs/upstream-conformance-conforms.md +55 -0
- package/docs/upstream-conformance-families.md +95 -0
- package/docs/upstream-conformance-grafana.md +106 -0
- package/docs/upstream-conformance.md +20 -298
- package/package.json +3 -3
- package/src/proxmox/ceph-daemon-wire.ts +4 -7
- package/src/proxmox/ceph-daemon.ts +7 -8
- package/src/proxmox/ceph-fs-delete.test.ts +5 -8
- package/src/proxmox/ceph-fs-destroy-sdk.test.ts +98 -0
- package/src/proxmox/ceph-fs-distilled.ts +45 -19
- package/src/proxmox/ceph-fs-wire.ts +5 -93
- package/src/proxmox/ceph-fs.ts +13 -15
- package/src/proxmox/ceph-pool-settle.ts +11 -10
- package/src/proxmox/ceph-pool-wire.ts +11 -20
- package/src/proxmox/ceph-pool-write.test.ts +9 -8
- package/src/proxmox/group-wire.ts +7 -12
- package/src/proxmox/read-failure-propagation.test.ts +5 -5
- package/src/proxmox/storage-wire.ts +9 -10
- package/src/proxmox/typed-absence.test.ts +146 -0
- package/src/proxmox/unreadable-read.ts +7 -7
- package/src/proxmox/user-wire.ts +7 -10
- package/src/proxmox/user.ts +5 -5
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Alchemy changelog archive 6
|
|
2
|
+
|
|
3
|
+
[Current changelog](../../CHANGELOG.md) · [Archive index](./README.md)
|
|
4
|
+
|
|
5
|
+
**Clears this family's own cries-wolf false updates**, MEASURED live 2026-09-24: on the agent
|
|
6
|
+
(`read`-role) lane, `bun run plan` showed all 5 storages (`cephfs-tb4`, `cephtb4`, `local`,
|
|
7
|
+
`local-zfs`, `pbs`) as `update` with no warning, because this family reads with `provision`
|
|
8
|
+
(`Datastore.Allocate` on `/storage` — `Datastore.Audit` is not enough) and the pre-migration
|
|
9
|
+
`pveOperations.read` folded a REFUSED `provision` mint into "absent" exactly like every family
|
|
10
|
+
before PR 231/239's fix — `unreadable-read.ts`'s own header already named this family as one of
|
|
11
|
+
the two the fix was written for. Now wired the same way: a refused mint reports `noop` with a
|
|
12
|
+
logged warning.
|
|
13
|
+
|
|
14
|
+
**A missing storage is a 500, not a 404** — MEASURED against the live cluster (TB4, admin lane,
|
|
15
|
+
read-only `GET /storage/hf-measure-nonexistent-probe`): `{"message":"storage '...' does not
|
|
16
|
+
exist\n"}` at HTTP 500, the same shape user.ts/group.ts measured for a missing user/group. So
|
|
17
|
+
`storage-wire.ts` carries the same dual-path read PR 239 established for those two families:
|
|
18
|
+
`readStorageOrFail` (non-folding, used by `diff`, so a genuine transient failure propagates and
|
|
19
|
+
fails the plan loudly) and `readStorage` (folding, used by `read`/`reconcile`'s create-detection).
|
|
20
|
+
`read`'s own provider hook also branches on `output` from the start (PR 239's own follow-up
|
|
21
|
+
finding, applied here proactively): `output === undefined` (Plan.ts's adoption probe, Apply.ts's
|
|
22
|
+
delete recovery — nothing confirmed exists yet) keeps the fold; `output !== undefined`
|
|
23
|
+
(`Drift.ts`'s already-confirmed row) uses the non-folding read instead, so `alchemy drift` never
|
|
24
|
+
reports a transient failure as a silent `{action: 'missing'}` either.
|
|
25
|
+
|
|
26
|
+
**Two measured gaps in `@distilled.cloud/proxmox`'s generated schema, handled rather than
|
|
27
|
+
papered over:**
|
|
28
|
+
|
|
29
|
+
- No `maxfiles` field at all (grepped across the whole package). Silently dropping a declared
|
|
30
|
+
value would be the exact class of bug this migration exists to close, so `StorageProps.maxfiles`
|
|
31
|
+
is now `never` — a compile error, the same treatment `StorageLocator`'s `password`/`keyring`/
|
|
32
|
+
`encryption-key` already get — rather than a silent no-op. None of this cluster's 5 live
|
|
33
|
+
storages set it; a consumer that genuinely needs it should file the gap upstream in the SDK's
|
|
34
|
+
generator, not work around it here.
|
|
35
|
+
- Every hyphenated PVE field name becomes an underscore in distilled's generated TypeScript
|
|
36
|
+
(`prune-backups` -> `prune_backups`, `fs-name` -> `fs_name`, and so on for every plugin field
|
|
37
|
+
`StorageLocator`'s free-form bag can carry — `cephfs-tb4`'s own live locator uses `fs-name`).
|
|
38
|
+
No other migrated family has a hyphenated field, so this is new here. `storage-form.ts`'s
|
|
39
|
+
`underscored` translates a form built with PVE's own names into distilled's shape before the
|
|
40
|
+
actual SDK call, never before the form the generated vendor-constraint tables check
|
|
41
|
+
(`guardWrite`, keyed by PVE's own hyphenated names), which would otherwise silently stop
|
|
42
|
+
inspecting the very fields it exists to catch. MEASURED both ways (a `storage-read-failure.test.ts`
|
|
43
|
+
case run once with the translation and once without it): the wire itself carries PVE's own
|
|
44
|
+
hyphenated name either way — distilled's own "unknown key" passthrough happens to re-encode an
|
|
45
|
+
untranslated key correctly for a body-bearing POST/PUT (unlike the GET case that caused PR 239's
|
|
46
|
+
regression, where no body should exist at all). The translation is still the right call: it
|
|
47
|
+
routes every field through distilled's own declared, typed property rather than its passthrough
|
|
48
|
+
fallback, so whatever per-field wire logic a future plugin field carries beyond a plain rename
|
|
49
|
+
runs correctly rather than silently not running.
|
|
50
|
+
|
|
51
|
+
`storage-read-failure.test.ts` pins all of this against real-shaped fixtures (live TB4 response
|
|
52
|
+
bodies, measured 2026-09-24, nothing secret): the cries-wolf fix, `diff`/`verify()` rejecting on a
|
|
53
|
+
transient failure instead of a false `update`, `alchemy drift` rejecting instead of a silent
|
|
54
|
+
`missing`, and — added after an adversarial review found every test above only ever adopted an
|
|
55
|
+
EXISTING storage, never exercising `reconcile`'s actual create path — a brand-new storage's create
|
|
56
|
+
translating a hyphenated locator field for the real SDK call, and a vendor-constraint violation on
|
|
57
|
+
a hyphenated locator field refused before any write. Each assertion confirmed to fail against the
|
|
58
|
+
pre-fix code (or, for the translation claim, against the untranslated code) before landing.
|
|
59
|
+
`verify/fake-engine.ts` gained a `drift` capability (driving the real `Alchemy.Drift.detect`) in
|
|
60
|
+
PR 239, reused here; that PR's `read-failure-propagation.test.ts` already established the
|
|
61
|
+
transient-failure/drift test pattern for User/Group.
|
|
62
|
+
|
|
63
|
+
**Expected after this releases and the consumer bumps:** on the agent lane, `bun run plan`'s
|
|
64
|
+
5 storage rows return to `noop`; ACLs and users/groups (PR 231/239) stay unaffected.
|
|
65
|
+
|
|
66
|
+
## 0.31.2
|
|
67
|
+
|
|
68
|
+
### Patch Changes
|
|
69
|
+
|
|
70
|
+
- [#239](https://github.com/taslabs-net/homeflare-kit/pull/239) [`1e8985f`](https://github.com/taslabs-net/homeflare-kit/commit/1e8985fc8359304b2c6d03df0fba00781e5e32a0) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Fixes a live regression from kit 0.31.1 (PR 231, the `proxmox/access`
|
|
71
|
+
distilled migration): on the agent (`read`-role) lane, `bun run plan` showed
|
|
72
|
+
every `Proxmox.User`/`Proxmox.Group` row as `update` with no warning — a
|
|
73
|
+
silent false diff, not the loud "read was refused" case that migration was
|
|
74
|
+
meant to fix.
|
|
75
|
+
|
|
76
|
+
**Root cause, measured against the live cluster.** `user.ts`/`group.ts`
|
|
77
|
+
called `access.getAccessUser(props)`/`access.getAccessGroup(props)` with the
|
|
78
|
+
whole declared props object, not just the schema's own `{userid}`/
|
|
79
|
+
`{groupid}` label field. distilled's `buildRequest` treats any OTHER key on
|
|
80
|
+
that object (`target`, `comment`, every other prop this family carries) as
|
|
81
|
+
an "unknown key" and JSON-encodes it onto the request as a body — on what
|
|
82
|
+
must stay a bodyless GET. A permissive client tolerates this; Node/Bun's own
|
|
83
|
+
`fetch` refuses it outright
|
|
84
|
+
(`TypeError [ERR_INVALID_ARG_VALUE]: fetch() request with GET/HEAD method
|
|
85
|
+
cannot have body`), which `runPveWith` retried across every cluster member
|
|
86
|
+
and exhausted identically (`PveClusterExhausted`). The old
|
|
87
|
+
`Effect.orElseSucceed(() => undefined)` then folded that failure into
|
|
88
|
+
"absent", forcing a false `update` on every account and every group — the
|
|
89
|
+
same cries-wolf bug PR 231 had just fixed for a refused credential, now
|
|
90
|
+
triggered by a genuine transport failure instead. Fixed by calling both
|
|
91
|
+
operations with only their declared label field.
|
|
92
|
+
|
|
93
|
+
**The fold itself was too broad, independent of the request-shape bug.**
|
|
94
|
+
`orElseSucceed` turning ANY read failure into "absent" is unsound at `diff`
|
|
95
|
+
time: only a genuine not-found may mean absent, every other failure must
|
|
96
|
+
fail the plan loudly rather than silently forcing a write. `acl.ts` (already
|
|
97
|
+
migrated pre-231) and `role.ts` never needed a fold at all — an ACL/role's
|
|
98
|
+
absence is always a successful list read that just doesn't contain the row,
|
|
99
|
+
so any thrown failure was already a bug; the fold there is now removed
|
|
100
|
+
outright. `api-token.ts`'s absence signal is likewise a measured
|
|
101
|
+
success-path check (`expire`/`privsep` both undefined on a 200), so its
|
|
102
|
+
fold is also removed outright.
|
|
103
|
+
|
|
104
|
+
`user.ts`/`group.ts` are different: a genuinely missing user or group is a
|
|
105
|
+
thrown 500 on this cluster (`"no such user"`/`"no such group"`, not a clean
|
|
106
|
+
404), MEASURED live — so `reconcile`'s create workflow still needs "absent"
|
|
107
|
+
derived from exactly that failure, or a brand-new declaration could never be
|
|
108
|
+
created. Each now has a dual-path read in its `*-wire.ts` sibling: a
|
|
109
|
+
non-folding `read...OrFail` (used by `diff`, so a genuine transient failure
|
|
110
|
+
propagates and fails the plan loudly) and a folding wrapper kept only for
|
|
111
|
+
`read`/`reconcile` (where folding costs at most a redundant, loudly-refused
|
|
112
|
+
create, never a silent wrong write).
|
|
113
|
+
|
|
114
|
+
**A second, deeper instance of the same bug class, found by adversarial
|
|
115
|
+
review of this fix rather than measured live.** The engine calls a
|
|
116
|
+
provider's `read` hook from four places — an adoption probe and
|
|
117
|
+
interrupted-create recovery in `Plan.ts`, delete recovery in `Apply.ts`, and
|
|
118
|
+
`Drift.ts` (`alchemy drift`/`sync`/`deploy --detect-drift`) — and only the
|
|
119
|
+
first three are "nothing confirmed exists yet" cases where `read`'s own
|
|
120
|
+
fold is safe. `Drift.ts` calls `read` on an ALREADY-CONFIRMED row (its own
|
|
121
|
+
persisted `output`), the same situation `diff` handles, and a folded
|
|
122
|
+
failure there is reported as `{action: 'missing'}` with no error anywhere —
|
|
123
|
+
worse than the original bug, since nothing even logs a warning. Fixed by
|
|
124
|
+
having `user.ts`/`group.ts`'s `read` hook branch on the input's own
|
|
125
|
+
`output` field (documented on `Provider.read`'s own type as "current state
|
|
126
|
+
-> synced state"): `output === undefined` still uses the folding read
|
|
127
|
+
(unchanged, for the three recovery/adoption cases), `output !== undefined`
|
|
128
|
+
now uses the same non-folding `read...OrFail` `diff` already calls.
|
|
129
|
+
|
|
130
|
+
`read-failure-propagation.test.ts` pins all three fixes end to end against
|
|
131
|
+
real-shaped PVE fixtures (live response bodies, nothing secret): a GET that
|
|
132
|
+
would carry a body now fails the assertion outright; a transient 500 on an
|
|
133
|
+
already-adopted `Proxmox.User`/`Proxmox.Group` now rejects `verify()`
|
|
134
|
+
instead of resolving with a false `update`; and (a new `drift` capability
|
|
135
|
+
on the shared `fake-engine.ts` test harness, driving the real
|
|
136
|
+
`Alchemy.Drift.detect`) the same transient failure now rejects `alchemy
|
|
137
|
+
drift` instead of silently reporting the resource missing — each of the
|
|
138
|
+
three new/changed tests was confirmed to fail against the pre-fix code
|
|
139
|
+
before this change landed.
|
|
140
|
+
|
|
141
|
+
**Expected after this releases and the consumer bumps:** on the agent
|
|
142
|
+
lane, `bun run plan`'s users/groups return to `noop`, ACLs stay
|
|
143
|
+
`noop`-with-warning, and the five pre-existing storage rows (a separate,
|
|
144
|
+
not-yet-migrated family) are unaffected.
|
|
145
|
+
|
|
146
|
+
- [#234](https://github.com/taslabs-net/homeflare-kit/pull/234) [`3357caa`](https://github.com/taslabs-net/homeflare-kit/commit/3357caa7c9a412365d3959bbb306628329eba4f8) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `@distilled.cloud/grafana` is not published upstream yet, so this package
|
|
147
|
+
now aliases it onto `@homeflare/distilled-grafana` (0.1.0 pre-release,
|
|
148
|
+
built the distilled way and shipped from this monorepo — see
|
|
149
|
+
`docs/distilled-interim.md`) as a plain `dependencies` entry instead of a
|
|
150
|
+
`1.0.0-rc.12` peer — nothing changes for a consumer's install (the peer
|
|
151
|
+
line is simply gone from the README and the smoke install, the same way
|
|
152
|
+
`/netbox` and `/litellm` already read). `Grafana.Datasource`'s props,
|
|
153
|
+
attributes and generated calls are unchanged — its existing tests pass
|
|
154
|
+
unmodified.
|
|
155
|
+
|
|
156
|
+
The alias also picks up `@distilled.cloud/grafana`'s newly-added folder,
|
|
157
|
+
dashboard and alerting-provisioning operations (see the
|
|
158
|
+
`@homeflare/distilled-grafana` changeset), which fixes the SDK-level gap
|
|
159
|
+
`docs/grafana.md` and `docs/upstream-conformance.md` recorded. No new house
|
|
160
|
+
`Resource` is added here — `Grafana.Folder`/`Dashboard`/`AlertRule`/etc. on
|
|
161
|
+
top of these operations is follow-up work, not part of this change.
|
|
162
|
+
|
|
163
|
+
⚠️ **Deliberate deviation from `docs/distilled-interim.md`'s step 5.** That
|
|
164
|
+
doc has the interim package publish and get confirmed live
|
|
165
|
+
(`npm view @homeflare/distilled-grafana version`) in its own PR _before_ a
|
|
166
|
+
second PR adds the alias — exactly to dodge the propagation window
|
|
167
|
+
`scripts/publish.ts`'s own comments document twice (2026-09-15,
|
|
168
|
+
`kit@0.1.1`/`cloudflare@0.1.1`: "Your package is being processed and may
|
|
169
|
+
take a few minutes to become available"). This PR does both at once, on
|
|
170
|
+
purpose, so the next teams building `Grafana.Folder`/`Dashboard`/`AlertRule`
|
|
171
|
+
aren't blocked on a second release cycle. `scripts/publish.ts` has no
|
|
172
|
+
dependency-aware ordering, so **after this releases, confirm
|
|
173
|
+
`npm view @homeflare/distilled-grafana version` resolves before anyone
|
|
174
|
+
depends on the new `@homeflare/alchemy` version** — a few minutes' wait,
|
|
175
|
+
not a code change, and self-healing either way (the publish script is
|
|
176
|
+
idempotent and a stuck install just needs a retry).
|
|
177
|
+
|
|
178
|
+
## 0.31.1
|
|
179
|
+
|
|
180
|
+
### Patch Changes
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Alchemy changelog archive 7
|
|
2
|
+
|
|
3
|
+
[Current changelog](../../CHANGELOG.md) · [Archive index](./README.md)
|
|
4
|
+
|
|
5
|
+
- [#231](https://github.com/taslabs-net/homeflare-kit/pull/231) [`11705f2`](https://github.com/taslabs-net/homeflare-kit/commit/11705f2b88d19335be5a115e06f7f6ca883b63b3) Thanks [@taslabs-net](https://github.com/taslabs-net)! - The `proxmox/*` family's `access` sub-area (decision 43's serial proxmox
|
|
6
|
+
walk-down, PR 2 after `Proxmox.Acl` in PR 209/220) now calls
|
|
7
|
+
`@distilled.cloud/proxmox`'s typed `access.*` operations instead of
|
|
8
|
+
`client.ts`'s hand-rolled, generic `pve()` call: `Proxmox.User`, `Proxmox.Group`,
|
|
9
|
+
`Proxmox.Role` and `Proxmox.ApiToken`. Each gets its own hand-written
|
|
10
|
+
reconcile (read/diff/reconcile/delete), the way `acl.ts` established, rather
|
|
11
|
+
than the shared `pveHandlers`/`pveOperations` factory — a `*-wire.ts` sibling
|
|
12
|
+
per family holds the pure wire-shape functions (create/update forms,
|
|
13
|
+
attributes, `matches`), keeping every file under the house's 250-line cap.
|
|
14
|
+
`client.ts`/`pveOperations` are untouched and still serve every other PVE
|
|
15
|
+
family; nothing in `nodes`, `storage`, `notifications` or PBS has moved yet.
|
|
16
|
+
|
|
17
|
+
`Proxmox.Role` reads via `GET /access/roles` (the list), not the item
|
|
18
|
+
`GET /access/roles/{roleid}` this family used before: distilled's generated
|
|
19
|
+
schema for the item response enumerates a FIXED set of ~47 known privilege
|
|
20
|
+
field names, and a privilege outside that set would silently vanish from
|
|
21
|
+
state on every read. The list's `privs` field is the same plain comma string
|
|
22
|
+
PVE's item read also disagreed with the index about pre-migration, with no
|
|
23
|
+
fixed enumeration to fall behind — `role-wire.ts`'s `find` does the item
|
|
24
|
+
read's old client-side job. Every other family in this PR still reads the
|
|
25
|
+
item it read before.
|
|
26
|
+
|
|
27
|
+
A `distilled-guard.ts` module (`asForm`/`guardWrite`) is the generalized
|
|
28
|
+
form of `acl.ts`'s own local `guardWrite`, needed because distilled's
|
|
29
|
+
generated request interfaces have no index signature and TypeScript refuses
|
|
30
|
+
`Record<string, string | undefined>` for them directly — `body: object`
|
|
31
|
+
plus one internal cast is the fix, applied once here rather than at every
|
|
32
|
+
call site.
|
|
33
|
+
|
|
34
|
+
**The cries-wolf fix** (measured 2026-09-24: `bun run plan` on the agent lane
|
|
35
|
+
showed "19 to update" — 14 ACLs and 5 storages — because a REFUSED read
|
|
36
|
+
folded into "absent" and `diff` forced `update` without comparing a field).
|
|
37
|
+
`credentials.ts`'s `mint` now fails a 403 from OpenBao (a denied
|
|
38
|
+
role — "this identity's AppRole has no grant") with a new typed
|
|
39
|
+
`PveCredentialDenied` (`credential-errors.ts`), split out from the newly
|
|
40
|
+
`mint.ts`-housed `mint`/`authorization` to keep `credentials.ts` under the
|
|
41
|
+
line cap. A new `unreadable-read.ts` module's `readOrUnreadable` `catchTag`s
|
|
42
|
+
exactly that one tag — never any other read failure — turning it into an
|
|
43
|
+
`UNREADABLE` sentinel `diff` can tell apart from a genuine absence. Upstream's
|
|
44
|
+
`Diff` type is exactly `NoopDiff | UpdateDiff | ReplaceDiff`
|
|
45
|
+
(`packages/alchemy/src/Diff.ts@v2.0.0-beta.79`) with no fourth action, and
|
|
46
|
+
`Plan.ts`'s resource pass fails the WHOLE plan if even one resource's `diff`
|
|
47
|
+
throws — so `diff` now reports `{action: 'noop'}` plus a logged warning for
|
|
48
|
+
an unreadable row, never a failed plan and never a forced write. Wired into
|
|
49
|
+
`acl.ts` (the already-migrated family) and every family in this PR;
|
|
50
|
+
`unreadable-read.test.ts` proves it end to end through Alchemy's real Plan
|
|
51
|
+
and Apply, with a fake OpenBao that denies `provision` mid-test. `storage.ts`
|
|
52
|
+
and the other five false updates stay open until `nodes`/`storage` migrates.
|
|
53
|
+
|
|
54
|
+
Distilled's `{userid}`/`{tokenid}` label substitution percent-encodes them
|
|
55
|
+
(`iac@pve` -> `iac%40pve`), where `client.ts`'s plain string concatenation
|
|
56
|
+
sent the `@` literally — a real PVE decodes both the same way, so this is
|
|
57
|
+
not a behaviour change a stack observes, but every fake cluster with an `@`
|
|
58
|
+
in a userid (`user.test.ts`, `api-token-adopt.test.ts`,
|
|
59
|
+
`provision-declare.test.ts`) now decodes the path before matching it.
|
|
60
|
+
|
|
61
|
+
State did not move: every Props/Attributes interface is unchanged, and the
|
|
62
|
+
existing cross-family `adopt-noop.test.ts` cases (now extended with `Group`
|
|
63
|
+
and `User` rows for the new hand-written reconciles) and
|
|
64
|
+
`api-token-adopt.test.ts` pass with the same assertions the pre-migration
|
|
65
|
+
code made. New tests (`group.test.ts`, `user.test.ts`, `role.test.ts`)
|
|
66
|
+
drive a fake PVE server through the real distilled protocol, covering
|
|
67
|
+
create, drift-correction, delete, retain-by-default and the read-back guard.
|
|
68
|
+
|
|
69
|
+
`codegen/constraints.ts` regenerated: `Proxmox.ApiToken` no longer names its
|
|
70
|
+
(always-unreachable) create endpoint anywhere in source, so the generated
|
|
71
|
+
tables and the hand-written ownership ledger
|
|
72
|
+
(`scripts/proxmox-ownership-pve.ts`) both drop that one claim — 74 tabled
|
|
73
|
+
endpoints, not 75.
|
|
74
|
+
|
|
75
|
+
## 0.31.0
|
|
76
|
+
|
|
77
|
+
### Minor Changes
|
|
78
|
+
|
|
79
|
+
- [#228](https://github.com/taslabs-net/homeflare-kit/pull/228) [`25610e8`](https://github.com/taslabs-net/homeflare-kit/commit/25610e890ac360456c318dd8dbefd0d54ccf0d3e) Thanks [@taslabs-net](https://github.com/taslabs-net)! - New `@homeflare/alchemy/discord` subpath, built directly on `@distilled.cloud/discord`
|
|
80
|
+
(1.0.0-rc.12) — no hand-rolled client ever existed for this vendor to retire.
|
|
81
|
+
`Discord.ApplicationCommand` (global) and `Discord.GuildApplicationCommand` (guild-scoped)
|
|
82
|
+
declare a slash/user/message command; `reconcile` is one upsert call, matching Discord's own
|
|
83
|
+
create-endpoint semantics (a command with the same name overwrites the old one), the same shape
|
|
84
|
+
`Cloudflare.Snippets.Snippet`'s `putSnippet` uses upstream. `read` follows that same upstream
|
|
85
|
+
reference for a marker-less API exactly: a cold match is `Unowned`, never a silent adopt, and
|
|
86
|
+
both convenience constructors (`applicationCommand`, `guildApplicationCommand`) pipe `adopt(true)`
|
|
87
|
+
by default. Rate limits are handled entirely by the SDK's own bounded default retry policy
|
|
88
|
+
(`Schedule.recurs(8)`, honoring a `429`'s `Retry-After`); this family adds no retry logic of its
|
|
89
|
+
own. Credentials are `DISCORD_BOT_TOKEN`/`DISCORD_TOKEN`, read at call time through the SDK's own
|
|
90
|
+
`CredentialsFromEnv`, never a prop.
|
|
91
|
+
|
|
92
|
+
Walked read-only against the live target, `hf-discord-halibut.service` on CT100: the bot
|
|
93
|
+
self-registers nine guild-scoped commands at every startup (Sapphire's `BulkOverwrite`), so
|
|
94
|
+
`docs/discord.md` documents the ownership conflict and the handover sequence rather than
|
|
95
|
+
declaring `Discord.GuildApplicationCommand` against Halibut's own guilds — that would fight the
|
|
96
|
+
bot's own registration, not replace it. Guild roles, channels and webhooks are not built: Halibut
|
|
97
|
+
uses none of them (measured on CT100), and the engine in `resource.ts` generalizes cleanly to a
|
|
98
|
+
future spec file if a stack ever needs one.
|
|
99
|
+
|
|
100
|
+
SDK gaps recorded in `docs/discord.md`: every generated operation's typed error union is narrower
|
|
101
|
+
than the protocol's own status map (`Forbidden`/`NotFound`/`BadRequest`/`Conflict` are built at
|
|
102
|
+
runtime but not in the exported type — measured via `tsc`, not assumed); `options` is passed
|
|
103
|
+
through opaquely rather than re-modeled from the ~40-type generated union; there is no vendor
|
|
104
|
+
constraint table (unlike NetBox/Paperless); global command propagation (up to an hour, Discord's
|
|
105
|
+
own docs) is not polled.
|
|
106
|
+
|
|
107
|
+
## 0.30.0
|
|
108
|
+
|
|
109
|
+
### Minor Changes
|
|
110
|
+
|
|
111
|
+
- [#229](https://github.com/taslabs-net/homeflare-kit/pull/229) [`b4d714e`](https://github.com/taslabs-net/homeflare-kit/commit/b4d714eff527c8c875afca2cb529d5f312bd1efa) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Added `caddy/formatCaddyfile(text)`: pipes a Caddyfile through the LOCAL
|
|
112
|
+
`caddy fmt -` binary (stdin in, formatted text out) — the same formatter the
|
|
113
|
+
`caddy fmt` CLI command runs. There is no admin API endpoint for this
|
|
114
|
+
(`cmd/commandfuncs.go` is CLI-only), so it shells out with
|
|
115
|
+
`ChildProcessSpawner` (S19) rather than going through `@distilled.cloud/caddy`,
|
|
116
|
+
and it never reimplements the formatter in TypeScript: a missing or failing
|
|
117
|
+
binary fails the Effect with a typed `CaddyFmtNotFound` / `CaddyFmtFailed`,
|
|
118
|
+
it never silently returns the input unformatted.
|
|
119
|
+
|
|
120
|
+
Also: `Caddy.Config`'s plan (`diffConfig`) and deploy (`CaddyConfigProvider`'s
|
|
121
|
+
`reconcile`) now surface the adapter's "Caddyfile input is not formatted"
|
|
122
|
+
warning as its own clear line naming the fix, separately from any other
|
|
123
|
+
adapter warnings — it used to be silent at plan time, and just another line
|
|
124
|
+
in the pile at deploy time. Nothing here changes what gets loaded onto Caddy:
|
|
125
|
+
formatting should never change the adapted JSON digest.ts compares (reasoned
|
|
126
|
+
from the Caddyfile grammar; not measured against a real Caddy in this
|
|
127
|
+
package — see docs/caddy-fmt.md).
|
|
128
|
+
|
|
129
|
+
- [#226](https://github.com/taslabs-net/homeflare-kit/pull/226) [`bfc71a3`](https://github.com/taslabs-net/homeflare-kit/commit/bfc71a31e811aab0ccdbbc5ce82ee6803c7ad277) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Add the Google Workspace provider family (`@homeflare/alchemy/google-workspace`): `Group`,
|
|
130
|
+
`GroupMember`, `DomainAlias` and `OrgUnit` over the Admin SDK Directory API, built on
|
|
131
|
+
`@distilled.cloud/google-workspace@1.0.0-rc.12`'s typed `admin_directory_v1` operations
|
|
132
|
+
(S23 — no hand-rolled client). Adopt-first by get-by-key, `retain` on removal for everything
|
|
133
|
+
but membership, and no `User` resource (Google's own `User` schema carries a `password` field).
|
|
134
|
+
Credential setup — domain-wide delegation, the least OAuth scopes each resource needs, and
|
|
135
|
+
where the service-account key lives in OpenBao — is in
|
|
136
|
+
`packages/alchemy/docs/google-workspace.md`.
|
|
137
|
+
|
|
138
|
+
- [#225](https://github.com/taslabs-net/homeflare-kit/pull/225) [`a68c021`](https://github.com/taslabs-net/homeflare-kit/commit/a68c0212f951d3492e5fde981f7f334f777d21fe) Thanks [@taslabs-net](https://github.com/taslabs-net)! - New family: `@homeflare/alchemy/grafana`. `Grafana.Datasource` declares one data source, keyed by
|
|
139
|
+
`uid`, calling `@distilled.cloud/grafana`'s typed `addDataSource`/`getDataSourceByUID`/
|
|
140
|
+
`updateDataSourceByUID`/`deleteDataSourceByUID` operations, `catchTag('NotFound', ...)` in place of
|
|
141
|
+
a status check. Credentials are a `GrafanaTarget` (instance origin + a token env var NAME, never a
|
|
142
|
+
literal value) resolved lazily per call, parameterized per instance rather than fixed like
|
|
143
|
+
Forgejo's — this estate runs more than one Grafana. `grafanaProviders(target)` composes the
|
|
144
|
+
provider with its credentials, mirroring `litellmProviders`.
|
|
145
|
+
|
|
146
|
+
Only `Datasource` ships: `@distilled.cloud/grafana@1.0.0-rc.12` has no create/update/delete
|
|
147
|
+
operations for folders, dashboards, alert rules or contact points, measured against its published
|
|
148
|
+
types — recorded in `docs/grafana.md` and `docs/upstream-conformance.md` rather than worked
|
|
149
|
+
around with a hand-rolled client for the missing pieces.
|
|
150
|
+
|
|
151
|
+
Built and measured, read-only, against the live target `teslamate-grafana.service` on CT100
|
|
152
|
+
(Grafana 13.1.3, `teslamate/grafana:4.2.0`): one file-provisioned datasource, image-baked
|
|
153
|
+
dashboards, no folders, alert rules or contact points. No stack yet imports this subpath.
|
|
154
|
+
|
|
155
|
+
## 0.29.1
|
|
156
|
+
|
|
157
|
+
### Patch Changes
|
|
158
|
+
|
|
159
|
+
- [#223](https://github.com/taslabs-net/homeflare-kit/pull/223) [`dc37600`](https://github.com/taslabs-net/homeflare-kit/commit/dc37600d0bb1c72ed2711351e498949af81271c6) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `declareRepoBaseline` and `declareRepoPolicy` declared `GitHub.Repository` and their
|
|
160
|
+
`RepositoryRuleset` as two independent resources with no dependency edge between them (K5,
|
|
161
|
+
kit PR 216). `repoBaselineSettings`/`repoPolicy` already gate `allowAutoMerge` on
|
|
162
|
+
`checks.length > 0`, but with nothing ordering the two resources, a deploy that moves a repo
|
|
163
|
+
from `checks: []` to a non-empty list could apply the repository (turning auto-merge on) before
|
|
164
|
+
the ruleset (adding the required check) — or the ruleset apply could fail outright, leaving
|
|
165
|
+
auto-merge on with nothing required. `gh pr merge --auto` merges a CLEAN pull request the instant
|
|
166
|
+
nothing is outstanding, so that window is exactly the fail-open state K5 exists to prevent.
|
|
167
|
+
|
|
168
|
+
Both callers now thread the ruleset's `rulesetId` through `allowAutoMerge`'s own value
|
|
169
|
+
(`repo-auto-merge-gate.ts`'s `gateAutoMergeOnRuleset`, via `alchemy/Output`'s `map`) whenever a
|
|
170
|
+
declaration turns auto-merge on, instead of writing it as a literal. Alchemy orders resources by
|
|
171
|
+
Output references in props (`Plan.ts`'s `Output.upstreamAny`, `Apply.ts`'s `waitForDeps` —
|
|
172
|
+
see the kit's own `alchemy-output-refs-order-resources` memory), so this makes the engine apply
|
|
173
|
+
the ruleset first, with no change to the value actually sent (`allowAutoMerge` is still exactly
|
|
174
|
+
`true`). A ruleset apply failure now leaves the repository's `reconcile` — and `allowAutoMerge`
|
|
175
|
+
— untouched, proved by a new engine-level ordering test and failure test for each caller.
|
|
176
|
+
|
|
177
|
+
The reverse transition (checks/approvals removed) needs no matching edge: neither caller ever
|
|
178
|
+
declares a rule's removal explicitly, only omits it, and `repository-ruleset-guards.ts`'s
|
|
179
|
+
`requiredChecksOmissionRefusal`/`undeclaredLiveRuleRefusal` already refuse — regardless of apply
|
|
180
|
+
order — to drop a still-live required rule by omission.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Alchemy changelog archive 8
|
|
2
|
+
|
|
3
|
+
[Current changelog](../../CHANGELOG.md) · [Archive index](./README.md)
|
|
4
|
+
|
|
5
|
+
Known trade-off (found by adversarial review, 2026-09-24): forcing the ruleset first only helps
|
|
6
|
+
once the repository already exists. A repo and its ruleset created together for the FIRST time,
|
|
7
|
+
with `checks` non-empty from day one, now fails clearly instead of racing — GitHub's own ruleset
|
|
8
|
+
API 404s on a repository that does not exist yet, and the engine now guarantees that order rather
|
|
9
|
+
than leaving it to chance. Both `declareRepoBaseline` and `declareRepoPolicy` document this and a
|
|
10
|
+
new test proves the failure is explicit, not a silent fail-open; the fix is for an ALREADY-LIVE
|
|
11
|
+
repo moving from no checks to some, which is K5's own scenario and the documented use of both
|
|
12
|
+
functions.
|
|
13
|
+
|
|
14
|
+
## 0.29.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- [#222](https://github.com/taslabs-net/homeflare-kit/pull/222) [`1e6285f`](https://github.com/taslabs-net/homeflare-kit/commit/1e6285fb229dca7455afa5b52e1c79ca3286dc26) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Declare `@distilled.cloud/discord`, `@distilled.cloud/google-workspace` and `@distilled.cloud/grafana` (1.0.0-rc.12) as exact-pinned peers, ahead of the Discord, Google Workspace and Grafana provider families. Per the peer contract they are required, and they appear in the README install line and the smoke install.
|
|
19
|
+
|
|
20
|
+
- [#208](https://github.com/taslabs-net/homeflare-kit/pull/208) [`2d28264`](https://github.com/taslabs-net/homeflare-kit/commit/2d28264e71d9f141ff0771f57dab49d6daa79904) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `GitHub.RepositoryRuleset`'s `rules.pullRequest.extraApprovalForUnattributedChanges` now accepts
|
|
21
|
+
`true`, not only `false`. Re-read live 2026-09-23, 8 rulesets carry
|
|
22
|
+
`require_extra_approval_for_unattributed_changes: true` (aop, cloudflareforms,
|
|
23
|
+
doesthishelp-workeropen, homeflare-anyauth, homeflare-desktop, loggarr, magictransit,
|
|
24
|
+
proxmox-tb4), so this resource could not declare their exact shape before: it would either omit
|
|
25
|
+
the field (leaving it unmanaged, refused once a caller also carries a `bypassActors`/rule
|
|
26
|
+
declaration for the same ruleset) or send `false`, which is real drift against a live `true` on
|
|
27
|
+
every plan.
|
|
28
|
+
|
|
29
|
+
The wire builder now sends whatever is declared instead of hardcoding `false`; `undefined` still
|
|
30
|
+
means "no opinion" (GitHub defaults an absent key to `true`). `repoBaselineRuleset()` in
|
|
31
|
+
repo-baseline-data.ts is unchanged — the house baseline still pins `false` for the repos that
|
|
32
|
+
fit it; a caller with a live `true` declares `RepositoryRuleset` directly with the live value,
|
|
33
|
+
the same pattern kit PR 205 established for `bypassActors` and rule presence.
|
|
34
|
+
|
|
35
|
+
Tested against aop's full live shape (`gh api repos/taslabs-net/aop/rulesets/14572279`,
|
|
36
|
+
re-read 2026-09-23): declaring the live value (including `true`) is now a genuine zero-write
|
|
37
|
+
adopt, and declaring `false` against a live `true` is still real drift, never a silent noop.
|
|
38
|
+
|
|
39
|
+
- [#214](https://github.com/taslabs-net/homeflare-kit/pull/214) [`9af4441`](https://github.com/taslabs-net/homeflare-kit/commit/9af4441462743476da58ee06af877ec0b346aa1f) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `GitHub.RepositoryRuleset`'s "never-reported context" guard (`refuseUnreportedContexts` /
|
|
40
|
+
`hasContextReportedSuccess`) checked only the default branch's current tip. The house's rendered
|
|
41
|
+
CI (`packages/config/src/repo-shape/ci.ts`) triggers on `pull_request` only, deliberately (no
|
|
42
|
+
`push: [main]` — a squash-merged commit re-testing an already-green PR was ~41% of the mini's CI
|
|
43
|
+
load, measured 2026-09-15..22), so `ci`, `secret scan` and `CodeQL` never report on the tip
|
|
44
|
+
itself. The guard refused adding any of them everywhere, including homeflare-builds' pending
|
|
45
|
+
first ruleset.
|
|
46
|
+
|
|
47
|
+
`hasContextReportedSuccess` now falls back to up to `RECENT_MERGED_PR_LIMIT` (10) recent merged
|
|
48
|
+
pull requests' head SHAs — one bounded list call, never the repo's full PR history — checked only
|
|
49
|
+
if the tip itself has no reported success. A context that has never reported success ANYWHERE
|
|
50
|
+
(not the tip, not any recent merged head) is still refused: the guard's whole purpose is
|
|
51
|
+
unchanged, only where it is willing to look for evidence widened.
|
|
52
|
+
|
|
53
|
+
The core logic (`contextReportedSuccess`, `hasRefReportedSuccess`, `recentMergedHeadShas`) is now
|
|
54
|
+
exported as plain functions over a real `OctokitClient`, not only reachable through the
|
|
55
|
+
`GitHubCredentials`-gated `RulesetOctokit` — the same pure-function seam `desiredWireRuleset`
|
|
56
|
+
already uses — so `repository-ruleset-octokit.test.ts` measures it against a real `@octokit/rest`
|
|
57
|
+
instance with a fetch shim (no network), mirroring the H15 wire test: accepting a context that
|
|
58
|
+
reports only on a recent merged head, refusing one that reports nowhere, and refusing one whose
|
|
59
|
+
only success is older than the bound (the limit is real, not decorative).
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- [#219](https://github.com/taslabs-net/homeflare-kit/pull/219) [`40c08eb`](https://github.com/taslabs-net/homeflare-kit/commit/40c08ebfc3ca04de7338deb95b217fbdce6c50d7) Thanks [@taslabs-net](https://github.com/taslabs-net)! - The `caddy/*` family (`Caddy.Config`, `caddyWithFile`, `localCaddyAdmin`) now calls
|
|
64
|
+
`@distilled.cloud/caddy`'s typed `admin` operations (`adaptConfig`, `loadConfig`,
|
|
65
|
+
`getConfig`) instead of a hand-rolled promise-based `CaddyAdmin.request()` interface.
|
|
66
|
+
Every operation's error channel is `catchTag`-able (`Caddy.CaddyOpError`); `isUnreachable`
|
|
67
|
+
(caddy-http-client.ts) is a type guard over the SDK's own `HttpClientError`, replacing the
|
|
68
|
+
old `CaddyUnreachableError` class, and gates the same "plan without Caddy" escape hatch
|
|
69
|
+
config.ts always had.
|
|
70
|
+
|
|
71
|
+
The measured local transport survives unchanged in shape: loopback TCP or a unix socket,
|
|
72
|
+
Host/Origin headers as the Caddy CLI sends them, retrying only a connection nothing
|
|
73
|
+
accepted (`ECONNREFUSED`/`ENOENT`) — now built as an Effect `HttpClient.HttpClient` layer
|
|
74
|
+
(`caddy-http-client.ts`) the SDK's protocol runs over, over `node:http` (not
|
|
75
|
+
`FetchHttpClient`: only `node:http` dials a unix socket on both Bun and Node). The
|
|
76
|
+
`LoadRefused` 200-trap (a refused `/load` that still answers 200 with the error appended
|
|
77
|
+
after the adapter's warnings) is first-class in the SDK's own `protocol.ts`, not
|
|
78
|
+
re-detected here.
|
|
79
|
+
|
|
80
|
+
Two real gaps this migration found and fixed at the source, never worked around in the
|
|
81
|
+
resource:
|
|
82
|
+
|
|
83
|
+
- `@distilled.cloud/caddy`'s `AdaptConfig`/`LoadConfig` JSON-encoded the Caddyfile TEXT and
|
|
84
|
+
overwrote the caller's `Content-Type` — fixed in the distilled clone's `protocol.ts` and
|
|
85
|
+
shipped as `@homeflare/distilled-caddy@0.2.1` (sibling changeset).
|
|
86
|
+
- The SDK's own default retry policy treats any transport-level `HttpClientError` as
|
|
87
|
+
retryable, including a `POST /load` that was already accepted before the connection
|
|
88
|
+
reset — `local-admin.ts` now disables it (`Caddy.Retry.Retry`) so caddy-http-client.ts's
|
|
89
|
+
own narrower ECONNREFUSED/ENOENT-only retry is the only one in play.
|
|
90
|
+
|
|
91
|
+
`providers.ts`'s `caddyProviders()` (and this family's own tests) also fix a wiring bug the
|
|
92
|
+
migration surfaced: `CaddyConfigProvider().pipe(Layer.provide(caddyAdminLayer(...)))`
|
|
93
|
+
seals `caddyAdminLayer`'s services away from the provider's `read`/`diff`/`reconcile`
|
|
94
|
+
handlers once they are built, so the engine's later call to any of them died with "Service
|
|
95
|
+
not found". `Layer.provideMerge` keeps those services live for every later call.
|
|
96
|
+
|
|
97
|
+
State did not move: props and attributes stay byte-identical (`configSha256`, `endpoint`,
|
|
98
|
+
`sourceFile`) — proven by the family's existing tests (updated only where the typed-error
|
|
99
|
+
message text itself changed, from the old ad hoc `method path -> status` strings to the
|
|
100
|
+
SDK's own tagged errors) plus fake-caddy.ts, a real HTTP server, so every test already
|
|
101
|
+
drives the real distilled wire protocol, not a re-implementation of it.
|
|
102
|
+
|
|
103
|
+
- [#209](https://github.com/taslabs-net/homeflare-kit/pull/209) [`1eaf80f`](https://github.com/taslabs-net/homeflare-kit/commit/1eaf80f9ef2ad5e616f1a459ed6396819be15e6c) Thanks [@taslabs-net](https://github.com/taslabs-net)! - The `proxmox/*` family's `Proxmox.Acl` (the first resource in the PVE
|
|
104
|
+
`access/ACL` sub-area, per decision 43's serial proxmox walk-down) now calls
|
|
105
|
+
`@distilled.cloud/proxmox`'s typed `access.listAccessAcl`/`access.putAccessAcl`
|
|
106
|
+
operations instead of `client.ts`'s hand-rolled, generic `pve()` call. A new
|
|
107
|
+
`distilled-pve.ts` module (`runPve`/`runPveWith`) replaces `pve()`/`pveWith`
|
|
108
|
+
for a migrated family: the same OpenBao lease reuse (`lease-cache.ts`,
|
|
109
|
+
unchanged) and the same cluster-member failover safety rule (`members.ts`,
|
|
110
|
+
unchanged — a write may repeat only on a provably pre-send transport
|
|
111
|
+
failure, never on an HTTP answer or a post-connect timeout), now re-running
|
|
112
|
+
the whole typed operation per member instead of retrying one already-built
|
|
113
|
+
request. `client.ts`/`pveOperations` are untouched and still serve every
|
|
114
|
+
other PVE family; `Proxmox.Acl` is the first to move off them, chosen
|
|
115
|
+
because it was already the one family that didn't fit the generic
|
|
116
|
+
path+form shape (no create/delete verb — PVE has only GET/PUT on
|
|
117
|
+
`/access/acl`).
|
|
118
|
+
|
|
119
|
+
One dead branch is removed, not preserved: the old code's generic factory
|
|
120
|
+
POSTed on a "live read undefined" case that its own comments called
|
|
121
|
+
unreachable on a healthy cluster and documented as actively misleading (PVE
|
|
122
|
+
has no POST here, so the 501 it got back said nothing about the real
|
|
123
|
+
failure). `@distilled.cloud/proxmox` has no `createAccessAcl` at all — the
|
|
124
|
+
vendor schema has no POST for the generator to make one from — so there is
|
|
125
|
+
nothing to call that way any more. `reconcile` now always PUTs, the only
|
|
126
|
+
write PVE actually implements for this path, so a read that genuinely fails
|
|
127
|
+
surfaces its real cause instead of a confusing 501.
|
|
128
|
+
|
|
129
|
+
A trap found while writing the member-failover test: `@distilled.cloud/core`'s
|
|
130
|
+
default retry policy retries transport failures automatically (not just
|
|
131
|
+
5xx answers), which could have resent a write to the same member several
|
|
132
|
+
times before this file's own cluster failover ever saw a result to
|
|
133
|
+
classify — the hand-rolled client never auto-retried anything. `distilled-pve.ts`
|
|
134
|
+
disables the SDK's retry (`Retry.none`) on every attempt, keeping
|
|
135
|
+
member failover the only place a request repeats, matching the original
|
|
136
|
+
behavior exactly.
|
|
137
|
+
|
|
138
|
+
State did not move: `AclProps`/`AclAttributes` are unchanged, and the
|
|
139
|
+
existing cross-family `adopt-noop.test.ts` case for `Proxmox.Acl` (an
|
|
140
|
+
already-matching grant is read-only) passes unmodified against the new
|
|
141
|
+
code. New tests (`acl.test.ts`, `distilled-pve.test.ts`) drive a fake PVE
|
|
142
|
+
server through the real distilled protocol — real path assembly, real
|
|
143
|
+
form-urlencoded PUT bodies, real `{"data": ...}` envelope — covering
|
|
144
|
+
create, drift-correction, delete, identity-replace, the read-back guard,
|
|
145
|
+
and cluster-member failover for both a read and a write.
|
|
146
|
+
|
|
147
|
+
This is PR 1 of a serial, sub-area-by-sub-area migration
|
|
148
|
+
(access/ACL, then nodes/storage, then notifications, then PBS, …) of the
|
|
149
|
+
~98-resource PVE family plus the PBS resources onto
|
|
150
|
+
`@distilled.cloud/proxmox`/`@distilled.cloud/proxmox-backup`. The other PVE
|
|
151
|
+
and PBS resources still call `client.ts`'s `pve()`, unchanged; `client.ts`
|
|
152
|
+
is deleted only once nothing imports it.
|
|
153
|
+
|
|
154
|
+
- [#215](https://github.com/taslabs-net/homeflare-kit/pull/215) [`72c448d`](https://github.com/taslabs-net/homeflare-kit/commit/72c448dbaec8f2acb7b5109aa7387a4409185fe8) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Fix a review finding on the `litellm/*` distilled migration (`@homeflare/distilled-litellm`'s
|
|
155
|
+
`CredentialsFromEnv`, S20): a missing or misspelled `LITELLM_PROXY_URL`/`LITELLM_PROXY_API_KEY`
|
|
156
|
+
used to die as an unrecoverable Effect defect and crash the whole engine, instead of the typed
|
|
157
|
+
`ConfigError` `LitellmOpError` already declared — a regression this migration made newly
|
|
158
|
+
reachable from `LiteLLM.PassThroughEndpoint`'s `read`/`reconcile`/`delete` (the retired
|
|
159
|
+
hand-rolled `credentials.ts` failed typed). Fixed at the source, per house rule: the distilled
|
|
160
|
+
`litellm` package's own `credentials.ts` no longer ends in `Effect.orDie`, copied forward into
|
|
161
|
+
`@homeflare/distilled-litellm` unchanged. No prop, attribute or public API changed.
|
|
162
|
+
|
|
163
|
+
`netbox/*`'s `CredentialsFromEnv` has the identical shape and is not fixed by this changeset —
|
|
164
|
+
tracked separately (`docs/upstream-conformance.md` finding 9).
|
|
165
|
+
|
|
166
|
+
- [#210](https://github.com/taslabs-net/homeflare-kit/pull/210) [`2002713`](https://github.com/taslabs-net/homeflare-kit/commit/20027131b716058de035bd1d8423054f3e9dbff0) Thanks [@taslabs-net](https://github.com/taslabs-net)! - The `litellm/*` family (`LiteLLM.PassThroughEndpoint`) now calls `@distilled.cloud/litellm`'s
|
|
167
|
+
typed `misc` operations instead of a hand-rolled `Effect HttpClient` client. The old
|
|
168
|
+
status-carrying `LitellmBadRequestError`/`LitellmUnauthorizedError`/`LitellmHttpError`/
|
|
169
|
+
`LitellmTransportError` are gone: every failure the SDK's four operations declare (`BadRequest`,
|
|
170
|
+
`NotFound` on update, `UnprocessableEntity`, plus the shared default HTTP errors) is the SDK's own
|
|
171
|
+
typed error, `catchTag`'d. `deletePassThroughEndpoint`'s re-list-on-ambiguous-delete trick is
|
|
172
|
+
unchanged, now keyed on the `BadRequest` tag instead of a status code. The per-base-URL write
|
|
173
|
+
semaphore LiteLLM's whole-list storage forces is unchanged, moved into the new `operations.ts`.
|
|
174
|
+
`client.ts` and the kit's own hand-generated `generated/pass-through.ts` are both deleted — the
|
|
175
|
+
SDK's `misc.PassThroughGenericEndpoint` is the same shape, generated from the same LiteLLM 1.100.0
|
|
176
|
+
OpenAPI document. The `codegen/litellm.ts` generator and `tests/litellm-manifest.test.ts` that
|
|
177
|
+
kept that file current are retired with it; the `litellm-openapi` manifest entry stays, recorded
|
|
178
|
+
as consumed by nothing, the same pattern the two UniFi entries already establish.
|