@homeflare/alchemy 0.37.1 → 0.37.3
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/backup-job-config.d.ts +95 -0
- package/dist/proxmox/backup-job-config.d.ts.map +1 -0
- package/dist/proxmox/backup-job-distilled.d.ts +46 -0
- package/dist/proxmox/backup-job-distilled.d.ts.map +1 -0
- package/dist/proxmox/backup-job.d.ts +9 -26
- package/dist/proxmox/backup-job.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs-distilled.d.ts +17 -10
- 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 +3 -3
- package/dist/proxmox/distilled-pbs.d.ts +12 -0
- package/dist/proxmox/distilled-pbs.d.ts.map +1 -0
- package/dist/proxmox/fake-pbs-jobs.d.ts +23 -0
- package/dist/proxmox/fake-pbs-jobs.d.ts.map +1 -0
- package/dist/proxmox/fake-pbs-notify.d.ts.map +1 -1
- package/dist/proxmox/fake-pve.d.ts +1 -1
- package/dist/proxmox/fake-pve.d.ts.map +1 -1
- package/dist/proxmox/group-wire.d.ts +2 -2
- package/dist/proxmox/group-wire.d.ts.map +1 -1
- package/dist/proxmox/index.js +1382 -506
- package/dist/proxmox/index.js.map +51 -27
- package/dist/proxmox/live-core-fixtures.d.ts +40 -0
- package/dist/proxmox/live-core-fixtures.d.ts.map +1 -0
- package/dist/proxmox/metric-server-config.d.ts +49 -0
- package/dist/proxmox/metric-server-config.d.ts.map +1 -0
- package/dist/proxmox/metric-server-distilled.d.ts +43 -0
- package/dist/proxmox/metric-server-distilled.d.ts.map +1 -0
- package/dist/proxmox/metric-server.d.ts +2 -10
- package/dist/proxmox/metric-server.d.ts.map +1 -1
- package/dist/proxmox/node-network-wire.d.ts +1 -1
- package/dist/proxmox/node-network-wire.d.ts.map +1 -1
- package/dist/proxmox/notification-matcher-distilled.d.ts +9 -0
- package/dist/proxmox/notification-matcher-distilled.d.ts.map +1 -0
- package/dist/proxmox/notification-matcher-lifecycle.d.ts +27 -0
- package/dist/proxmox/notification-matcher-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/notification-matcher.d.ts +5 -5
- package/dist/proxmox/notification-matcher.d.ts.map +1 -1
- package/dist/proxmox/notification-sdk-input.d.ts +22 -0
- package/dist/proxmox/notification-sdk-input.d.ts.map +1 -0
- package/dist/proxmox/notification-target-distilled.d.ts +12 -0
- package/dist/proxmox/notification-target-distilled.d.ts.map +1 -0
- package/dist/proxmox/notification-target-form.d.ts +2 -0
- package/dist/proxmox/notification-target-form.d.ts.map +1 -1
- package/dist/proxmox/notification-target-lifecycle.d.ts +29 -0
- package/dist/proxmox/notification-target-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/notification-target-spec.d.ts +5 -0
- package/dist/proxmox/notification-target-spec.d.ts.map +1 -0
- package/dist/proxmox/notification-target.d.ts +4 -5
- package/dist/proxmox/notification-target.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore-endpoint.d.ts +1 -1
- package/dist/proxmox/pbs-datastore-form.d.ts +18 -42
- package/dist/proxmox/pbs-datastore-form.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore-guard.d.ts +7 -4
- package/dist/proxmox/pbs-datastore-guard.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore-lifecycle.d.ts +56 -0
- package/dist/proxmox/pbs-datastore-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/pbs-datastore-wire.d.ts +21 -0
- package/dist/proxmox/pbs-datastore-wire.d.ts.map +1 -0
- package/dist/proxmox/pbs-datastore.d.ts +5 -6
- package/dist/proxmox/pbs-datastore.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-input.d.ts +18 -0
- package/dist/proxmox/pbs-notification-input.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-matcher-distilled.d.ts +11 -0
- package/dist/proxmox/pbs-notification-matcher-distilled.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-matcher.d.ts +4 -3
- package/dist/proxmox/pbs-notification-matcher.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-target-distilled.d.ts +20 -0
- package/dist/proxmox/pbs-notification-target-distilled.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-target-lifecycle.d.ts +4 -4
- package/dist/proxmox/pbs-notification-target-lifecycle.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-target.d.ts +1 -1
- package/dist/proxmox/pbs-prune-job-lifecycle.d.ts +22 -0
- package/dist/proxmox/pbs-prune-job-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/pbs-prune-job-spec.d.ts +5 -0
- package/dist/proxmox/pbs-prune-job-spec.d.ts.map +1 -0
- package/dist/proxmox/pbs-prune-job-wire.d.ts +11 -0
- package/dist/proxmox/pbs-prune-job-wire.d.ts.map +1 -0
- package/dist/proxmox/pbs-prune-job.d.ts +9 -8
- package/dist/proxmox/pbs-prune-job.d.ts.map +1 -1
- package/dist/proxmox/pbs-sync-job-attributes.d.ts +34 -0
- package/dist/proxmox/pbs-sync-job-attributes.d.ts.map +1 -0
- package/dist/proxmox/pbs-sync-job-form.d.ts +21 -22
- package/dist/proxmox/pbs-sync-job-form.d.ts.map +1 -1
- package/dist/proxmox/pbs-sync-job-lifecycle.d.ts +22 -0
- package/dist/proxmox/pbs-sync-job-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/pbs-sync-job-wire.d.ts +11 -0
- package/dist/proxmox/pbs-sync-job-wire.d.ts.map +1 -0
- package/dist/proxmox/pbs-sync-job.d.ts +9 -39
- package/dist/proxmox/pbs-sync-job.d.ts.map +1 -1
- package/dist/proxmox/pbs-verify-job-lifecycle.d.ts +22 -0
- package/dist/proxmox/pbs-verify-job-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/pbs-verify-job-spec.d.ts +16 -0
- package/dist/proxmox/pbs-verify-job-spec.d.ts.map +1 -0
- package/dist/proxmox/pbs-verify-job-wire.d.ts +11 -0
- package/dist/proxmox/pbs-verify-job-wire.d.ts.map +1 -0
- package/dist/proxmox/pbs-verify-job.d.ts +12 -12
- package/dist/proxmox/pbs-verify-job.d.ts.map +1 -1
- package/dist/proxmox/pool.d.ts +10 -1
- package/dist/proxmox/pool.d.ts.map +1 -1
- package/dist/proxmox/storage-wire.d.ts +2 -2
- package/dist/proxmox/storage-wire.d.ts.map +1 -1
- package/dist/proxmox/user-wire.d.ts +2 -2
- package/dist/proxmox/user-wire.d.ts.map +1 -1
- 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/pbs-distilled-jobs.md +31 -0
- package/docs/pbs-notifications.md +8 -2
- package/docs/proxmox-notification-sdk.md +28 -0
- package/docs/proxmox-sdk-migration.md +60 -0
- package/docs/proxmox.md +9 -3
- package/docs/upstream-conformance-families.md +6 -2
- package/package.json +3 -3
- package/src/proxmox/adopt-noop.test.ts +4 -5
- package/src/proxmox/backup-job-config.ts +95 -0
- package/src/proxmox/backup-job-distilled.ts +86 -0
- package/src/proxmox/backup-job.ts +78 -116
- 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 +41 -13
- package/src/proxmox/ceph-fs-wire.ts +5 -93
- package/src/proxmox/ceph-fs.ts +7 -7
- package/src/proxmox/constraints.test.ts +9 -8
- package/src/proxmox/distilled-pbs.test.ts +87 -0
- package/src/proxmox/distilled-pbs.ts +50 -0
- package/src/proxmox/fake-pbs-jobs.ts +99 -0
- package/src/proxmox/fake-pbs-notify.ts +4 -1
- package/src/proxmox/fake-pve.ts +3 -2
- package/src/proxmox/live-core-fixtures.ts +126 -0
- package/src/proxmox/live-core-lifecycle.test.ts +115 -0
- package/src/proxmox/live-core-wire.test.ts +190 -0
- package/src/proxmox/metric-server-config.ts +87 -0
- package/src/proxmox/metric-server-distilled.ts +81 -0
- package/src/proxmox/metric-server.ts +77 -88
- package/src/proxmox/notification-distilled.test.ts +162 -0
- package/src/proxmox/notification-matcher-distilled.ts +60 -0
- package/src/proxmox/notification-matcher-lifecycle.ts +71 -0
- package/src/proxmox/notification-matcher.ts +9 -26
- package/src/proxmox/notification-replacement.test.ts +190 -0
- package/src/proxmox/notification-sdk-input.ts +39 -0
- package/src/proxmox/notification-target-distilled.ts +160 -0
- package/src/proxmox/notification-target-form.ts +2 -0
- package/src/proxmox/notification-target-lifecycle.ts +75 -0
- package/src/proxmox/notification-target-spec.ts +68 -0
- package/src/proxmox/notification-target.ts +6 -68
- package/src/proxmox/pbs-datastore-endpoint.ts +1 -1
- package/src/proxmox/pbs-datastore-form.ts +18 -49
- package/src/proxmox/pbs-datastore-guard.ts +7 -4
- package/src/proxmox/pbs-datastore-lifecycle.ts +114 -0
- package/src/proxmox/pbs-datastore-wire.ts +94 -0
- package/src/proxmox/pbs-datastore.ts +7 -133
- package/src/proxmox/pbs-jobs-delete.test.ts +40 -0
- package/src/proxmox/pbs-jobs-sdk.test.ts +215 -0
- package/src/proxmox/pbs-notification-errors.test.ts +93 -0
- package/src/proxmox/pbs-notification-input.ts +25 -0
- package/src/proxmox/pbs-notification-mail.test.ts +64 -0
- package/src/proxmox/pbs-notification-matcher-distilled.ts +65 -0
- package/src/proxmox/pbs-notification-matcher.ts +70 -17
- package/src/proxmox/pbs-notification-target-distilled.ts +124 -0
- package/src/proxmox/pbs-notification-target-lifecycle.ts +26 -17
- package/src/proxmox/pbs-notification-target.ts +1 -1
- package/src/proxmox/pbs-notifications-stack.test.ts +25 -0
- package/src/proxmox/pbs-prune-job-lifecycle.ts +52 -0
- package/src/proxmox/pbs-prune-job-spec.ts +53 -0
- package/src/proxmox/pbs-prune-job-wire.ts +68 -0
- package/src/proxmox/pbs-prune-job.ts +10 -58
- package/src/proxmox/pbs-sync-job-attributes.ts +33 -0
- package/src/proxmox/pbs-sync-job-form.ts +7 -6
- package/src/proxmox/pbs-sync-job-lifecycle.ts +46 -0
- package/src/proxmox/pbs-sync-job-wire.ts +71 -0
- package/src/proxmox/pbs-sync-job.ts +10 -41
- package/src/proxmox/pbs-verify-job-lifecycle.ts +52 -0
- package/src/proxmox/pbs-verify-job-spec.ts +76 -0
- package/src/proxmox/pbs-verify-job-wire.ts +58 -0
- package/src/proxmox/pbs-verify-job.ts +13 -85
- package/src/proxmox/pool.ts +92 -10
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Alchemy changelog archive 11
|
|
2
|
+
|
|
3
|
+
[Current changelog](../../CHANGELOG.md) · [Archive index](./README.md)
|
|
4
|
+
|
|
5
|
+
Create-and-assert only: every optional prop (`encoding`, `localeProvider`, `lcCollate`,
|
|
6
|
+
`lcCtype`, `allowConnections`, `connectionLimit`, `isTemplate`, `tablespace`) is asserted once
|
|
7
|
+
at create and compared against the live row on every later plan — a mismatch is a typed
|
|
8
|
+
`PostgresDatabaseDrift` refusal, never an `ALTER DATABASE`. `name` is refused at plan past 63
|
|
9
|
+
UTF-8 bytes (`NAMEDATALEN`), because the server would otherwise silently truncate it with only
|
|
10
|
+
a `NOTICE`. A rename is refused at plan; `diff` never answers `replace` (a replace here is DROP
|
|
11
|
+
then CREATE, on data). `delete` always refuses with a typed tag and `defaultRemovalPolicy` is
|
|
12
|
+
`retain` — dropping a database stays a human act on the host. `CREATE DATABASE` takes no bind
|
|
13
|
+
parameters at all (measured at `gram.y`), so every value is quoted by hand: a single-token
|
|
14
|
+
identifier quoter for the name (deliberately NOT `alchemy`'s own `sql(value)`, which
|
|
15
|
+
dot-splits a qualified name and would break on a name containing `.`), and a string-literal
|
|
16
|
+
quoter for the rest. `read` always answers `Unowned` for a match — a database carries no
|
|
17
|
+
ownership mark, so an adopting stack needs `adopt(true)`.
|
|
18
|
+
|
|
19
|
+
Measured path (2026-09-23): only a Unix socket reaches the maintenance database on the mini —
|
|
20
|
+
no `pg_hba` rule opens it over TCP. A JS client reaches that socket: `@effect/sql-pg` under
|
|
21
|
+
Bun, live-checked from scratch space, connected and read all 24 live databases.
|
|
22
|
+
|
|
23
|
+
- [#160](https://github.com/taslabs-net/homeflare-kit/pull/160) [`26f45bd`](https://github.com/taslabs-net/homeflare-kit/commit/26f45bda0108744994c63b611ca27492aef715fe) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `Release.Binary` can now install from a directory-wrapped vendor archive. `tarReader(wanted, root)`
|
|
24
|
+
(`packages/alchemy/src/release/tar.ts`) accepts an optional `root`: exactly one declared leading
|
|
25
|
+
directory (typeflag `5`, size 0) is stripped from every entry name before it is matched, listed or
|
|
26
|
+
checked for a duplicate. Every entry outside that declared root, a second directory entry, a
|
|
27
|
+
`<root>-evil/x` sibling (a segment match, not a string prefix), and a `<root>/` entry that is not
|
|
28
|
+
an empty directory are refused whole, same as every existing refusal (PAX, GNU long-name, `..`,
|
|
29
|
+
links, devices). Without `root`, behaviour is unchanged: a directory entry — the wrapper included —
|
|
30
|
+
is still refused exactly as it always was.
|
|
31
|
+
|
|
32
|
+
`ReleaseArchive.root?: string` (`binary-form.ts`) carries the pin; `pinProblems` (split out to the
|
|
33
|
+
new `binary-pins.ts` to stay under the file's 250-line cap, re-exported so no importer moves)
|
|
34
|
+
refuses a root that is not one safe path segment. `catalogBinary` (`catalog.ts`) carries
|
|
35
|
+
`archive.root` through when a catalog entry has one. A state row from before this change has no
|
|
36
|
+
`root`, and `undefined === undefined`, so it is not treated as a moved pin; declaring or changing a
|
|
37
|
+
root is.
|
|
38
|
+
|
|
39
|
+
Measured 2026-09-23 by downloading each vendor's own GitHub release asset into a scratch directory
|
|
40
|
+
(never executed) and re-hashing: all four Prometheus-family darwin-arm64 archives —
|
|
41
|
+
`alertmanager` v0.33.1 (37,247,168 B), `blackbox_exporter` v0.28.0 (15,705,022 B), `node_exporter`
|
|
42
|
+
v1.12.1 (5,368,643 B), `prometheus-community/postgres_exporter` v0.20.1 (10,072,235 B) — recompute
|
|
43
|
+
to GitHub's own asset `digest`, wrap every entry in exactly one directory named
|
|
44
|
+
`<binary>-<version>.darwin-arm64/`, and carry no PAX or GNU long-name entries. The worktree's own
|
|
45
|
+
`tarReader(wanted, root)` was re-run against those same downloaded bytes and now parses each to
|
|
46
|
+
completion, returning the named member at its full pinned size (`docs/release-binary-catalogs.md`
|
|
47
|
+
has the full table and commands; `tar-root.test.ts` and `binary-root.test.ts` hold the same proof
|
|
48
|
+
as committed fixtures).
|
|
49
|
+
|
|
50
|
+
This unit adds the reader capability and its tests only. `VICTORIA_RELEASES` and
|
|
51
|
+
`OPENBAO_RELEASES` are unchanged — no catalog entry for alertmanager, blackbox_exporter,
|
|
52
|
+
node_exporter or postgres_exporter exists yet; that is its own data-set walk-down and PR.
|
|
53
|
+
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- [#161](https://github.com/taslabs-net/homeflare-kit/pull/161) [`5500f3e`](https://github.com/taslabs-net/homeflare-kit/commit/5500f3e83e90b9e7a0bbad457527f994a741d0d2) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Correct the last stale claim in `Proxmox.ZfsPool`'s docs, per Tim's decision 28 (2026-09-23).
|
|
57
|
+
|
|
58
|
+
PR 154 already corrected `zfs-pool.ts`'s header, which previously said destroy was refused and
|
|
59
|
+
`delete` made no API call — false, since `destroyPool` sends a real `DELETE` under
|
|
60
|
+
`.pipe(RemovalPolicy.destroy())`, guarded only by the resource's default `retain` removal policy.
|
|
61
|
+
That fix missed one line: the file's "PRIVILEGES, FROM THE SCHEMA" paragraph still said `delete`
|
|
62
|
+
"needs nothing at all, since it calls nothing" — the same mistake, left uncorrected in a second
|
|
63
|
+
place. It now says `delete` needs `Sys.Modify` on `/`, the same as `reconcile`'s POST, per
|
|
64
|
+
`ceph-osd.ts`'s own privilege comparison (measured against the same 2026-09-13 apidoc read),
|
|
65
|
+
which names `disks/zfs` as one of the sibling families whose write verbs — not only the create —
|
|
66
|
+
carry a `Sys.Modify` check.
|
|
67
|
+
|
|
68
|
+
No behaviour change. `RemovalPolicy.destroy()` reaching a single `DELETE`, and `retain` sending
|
|
69
|
+
none, are already pinned in `zfs-pool-adopt.test.ts` (added by PR 154); this PR touches docs only.
|
|
70
|
+
|
|
71
|
+
## 0.23.0
|
|
72
|
+
|
|
73
|
+
### Minor Changes
|
|
74
|
+
|
|
75
|
+
- [#154](https://github.com/taslabs-net/homeflare-kit/pull/154) [`2044487`](https://github.com/taslabs-net/homeflare-kit/commit/2044487b81fecb635da785b0b2637daf2c5b20fe) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Export `ProxmoxApiToken` and `ProxmoxZfsPool` from `@homeflare/alchemy/proxmox`, per Tim's
|
|
76
|
+
decision 9 (2026-09-23): the kit builds an ApiToken export that is metadata-only and a ZfsPool
|
|
77
|
+
export that is adopt-only. Neither can create what it does not already have to adopt.
|
|
78
|
+
|
|
79
|
+
`ProxmoxApiToken` adopts and manages `comment`, `expire` and `privsep` — the whole of a token's
|
|
80
|
+
policy that PVE will report back — and refuses to mint a new one: the secret exists for one HTTP
|
|
81
|
+
response and then nowhere (measured from the vendor schema), and this estate's state store
|
|
82
|
+
persists attributes unencrypted, so a token created here would be a live credential nobody holds.
|
|
83
|
+
`reconcile` dies by name when the token is absent, naming the two ways to get a usable one
|
|
84
|
+
(`pveum user token add`, or OpenBao's `proxmox-c1` mount) instead. `shape()` is now typed against
|
|
85
|
+
the generated `AccessUsersUseridTokenTokenidPutParams` and `PostParams`, so a schema drift fails
|
|
86
|
+
`tsc` here rather than surfacing as a 400 on a live cluster.
|
|
87
|
+
|
|
88
|
+
`ProxmoxZfsPool`'s `devices` and `raidlevel` are now optional: omitting both declares an
|
|
89
|
+
adopt-only pool, for one PVE's own POST schema cannot fully describe — a stripe layout has no
|
|
90
|
+
`raidlevel` to declare. `ZfsRaidLevel` and `ZfsCompression` are now generated aliases of
|
|
91
|
+
`NodesNodeDisksZfsPostParams`'s fields rather than hand-typed, so PVE adding or removing a
|
|
92
|
+
`raidlevel` value is caught by `tsc`, not discovered on a live cluster. `createPool` refuses
|
|
93
|
+
before any POST when an adopt-only declaration's pool is not already there. `zfs-pool.ts`'s
|
|
94
|
+
header previously said destroy was refused and `delete` made no API call — that was false
|
|
95
|
+
(`destroyPool` sends a real `DELETE`, guarded only by the resource's default `retain` removal
|
|
96
|
+
policy); the header is corrected, and `delete` is unchanged.
|
|
97
|
+
|
|
98
|
+
Walked down against the manifest-verified `pve-apidoc` cache, pve-manager 9.2.11/f6997e698c7933ea,
|
|
99
|
+
sha256 `9def8f13611184ee1c7d0399713130dfc4a065701d0d91a69b9c03df929344e9` — re-sliced for this
|
|
100
|
+
change with `codegen/apidoc.ts`'s own parser (2026-09-23), rather than assumed from the header's
|
|
101
|
+
2026-09-13 measurement. The four endpoint definitions this change touches (`POST`/`GET`/`DELETE`
|
|
102
|
+
`/nodes/{node}/disks/zfs[/{name}]`, `GET`/`POST`/`PUT`/`DELETE`
|
|
103
|
+
`/access/users/{userid}/token/{tokenid}`) are byte-identical against the cached 9.2.4 apidoc, so
|
|
104
|
+
nothing here is pinned to a version drift between them.
|
|
105
|
+
|
|
106
|
+
### Patch Changes
|
|
107
|
+
|
|
108
|
+
- [#152](https://github.com/taslabs-net/homeflare-kit/pull/152) [`0a19365`](https://github.com/taslabs-net/homeflare-kit/commit/0a19365c9370f315e0fa0dba6700934e4a7022fc) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Fix the Proxmox type generator (`codegen/`) to read a property's own `oneOf`, not just its outer `optional`. Measured 2026-09-23 against pve-manager 9.2.11: six PVE SDN fabric request parameters and seven return fields under `/cluster/sdn/fabrics/*` (`delete`, `redistribute`, `interfaces`) are spelled `{oneOf: […], type: 'array'}` with no outer `optional`, one branch per routing protocol, every branch `optional: 1` — a property that is optional in every case the vendor states, which the generator previously read as required. `isOptional` (exported from `codegen/tsmap.ts`) now treats a property as optional when it says so itself or when every `oneOf` branch does (one branch without `optional` still keeps it required), and an array property with no `items` of its own but a `oneOf` gets its element type from the deduplicated union of each branch's own mapped `items`. `codegen/emit.ts`'s constraint-table required flag routes through the same helper, so a future fabric Resource is not refused at plan time for omitting a key the vendor never requires. Regeneration touches only `packages/alchemy/src/proxmox/generated/pve/cluster-sdn-fabrics.ts` (+23/−13); nothing in `packages/` imports its types yet. `codegen/TYPES.md` records what stays unclaimed: the branches' own per-protocol `instance-types` are not read, so the element type is a superset across protocols rather than a discriminated union — that decision belongs to the `SdnFabric`/`SdnFabricNode` family this unblocks.
|
|
109
|
+
|
|
110
|
+
## 0.22.0
|
|
111
|
+
|
|
112
|
+
### Minor Changes
|
|
113
|
+
|
|
114
|
+
- [#148](https://github.com/taslabs-net/homeflare-kit/pull/148) [`2a8fc6e`](https://github.com/taslabs-net/homeflare-kit/commit/2a8fc6ef5f634c52a7dfa59f3f0e3464d83aea7c) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Refuse two of a stack's own launchd jobs sharing one port, before either is declared: `@homeflare/alchemy/launchd` now exports `claimPorts(claims)` and the pure `portClaimProblems(claims)` it runs on. Like `catalogBinary()`, this is a plain function the stack program calls itself (a provider never sees its sibling resources, and never diffs a first create whose props still hold an unresolved `Output` — every mini job's does), so a colliding declaration fails the plan before anything is fetched, written or `launchctl`'d. It keys on the port number alone, mirroring `lib-ports.nix`'s `assertNoCollision` in the house repo, the check it replaces for a job once that job moves off Nix — address and protocol are not part of the key, matched against that registry's own strictness rather than a new, looser rule. Walked against Darwin 27.2.0 (macOS 27.2): a wildcard bind and a specific-address bind on the same port both succeed at the OS level (measured with SO_REUSEADDR, which Go sets on every darwin listener unconditionally), so `claimPorts` refuses that pairing deliberately rather than relying on the OS to catch it. It sees only the claims a stack passes it — a forgotten job, or a daemon still on Nix, stays invisible until its port is added as a claim.
|
|
115
|
+
|
|
116
|
+
- [#149](https://github.com/taslabs-net/homeflare-kit/pull/149) [`6b8d88f`](https://github.com/taslabs-net/homeflare-kit/commit/6b8d88f9d3b68286f2d83a64358ecb1e84909629) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `@homeflare/alchemy/release` gets its second vendor data set, `OPENBAO_RELEASES`: OpenBao 2.6.2 for darwin_arm64, walked down 2026-09-23 (`docs/release-binary-openbao.md` has every command). OpenBao's `checksums.txt` lists archives and SBOMs only, never the `bao` binary inside the archive — the first vendor the kit has pinned where the member digest is not a vendor fact. `catalog.ts` gains an optional `computed` record per archive for exactly that case (hashed from an archive whose own SHA-256 already matched the pin, filed separately from `members` and never both at once — a member pinned in both is refused) and an optional `checksums.signature` record for a vendor that publishes a detached signature over its checksum file; `catalogProblems`, `catalogBinary` and `identifyBinary` all read vendor digests first, then computed ones. Both fields are optional and `VICTORIA_RELEASES` is unchanged, so nothing that already builds against this package needs to change. The `checksums.txt` signature was checked once with `gpgv` against the key published at openbao.org; the accompanying Sigstore bundle was read but not verified (`cosign` is not installed where this was walked down), and is recorded as an unverified identity rather than a passing check. `docs/release-binary.md`'s "Declaring one" example also gets a real bug fixed: `ReleaseBinary('vmalert')` followed by `LaunchdJob('vmalert')` shares one alchemy@2.0.0-beta.79 FQN, so the second declaration silently returns the first resource instead of registering a job — the example now uses distinct ids and says why.
|
|
117
|
+
|
|
118
|
+
## 0.21.0
|
|
119
|
+
|
|
120
|
+
### Minor Changes
|
|
121
|
+
|
|
122
|
+
- [#138](https://github.com/taslabs-net/homeflare-kit/pull/138) [`ef4108a`](https://github.com/taslabs-net/homeflare-kit/commit/ef4108a54c3700485cedb102833f16da54f02d3e) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Install vendor binaries from pinned release archives: `@homeflare/alchemy/release`. `ReleaseBinary` puts one binary out of a GitHub release archive into a directory the stack declares, and installs nothing else. The pinned archive is its props — repository, tag, exact asset name, size, the archive's SHA-256, the archive member and the member's own SHA-256 — so the resource knows no vendor. Each vendor's pinned versions are data kept beside it; `VICTORIA_RELEASES` is the first set, covering `victoria-metrics`, `victoria-logs`, `victoria-traces` and the `vmutils` tools (`vmagent`, `vmalert`, …) at the versions the Mac host runs, each digest copied from the vendor's checksum file with its URL and date. `catalogBinary(VICTORIA_RELEASES, { package, version, platform, binary })` turns an entry into props, and refuses a version the data set does not pin — in the stack program, so the plan fails before anything is fetched. Every pin must be a plain value in the stack program: one wired from another resource's Output (a checksum file read during the deploy) is refused before any request, on a first deploy too. Every download is checked twice: the archive before anything is unpacked, then the binary before it is written. Asset names are matched exactly, so the `-enterprise` and `-cluster` archives next to them are never picked. Only the declared member is extracted, and an archive holding any link, `..` or absolute entry is refused whole. The bytes reach the host only through the existing `HostRunner` write. A new pin at the same path is refused rather than overwriting a running binary; give each version its own directory (`catalogDirectory`). A binary someone already placed is recognised by its SHA-256, never by running it, and is taken over only under `--adopt`, without a download — at apply too, where the plan could not ask. A file with other bytes at the path is never taken over, `--adopt` or not, so a plan never prints `adopted` for a binary it would overwrite. A renamed declaration keeps its binary: declare the rename with `renamedFrom()`, and under `--adopt` the old name's delete leaves a path the new name installed in the same deploy. It never starts anything: the stack's job puts the binary's `path` in its argv. `HostFile` now shares its whole-file convergence with it, with no change in behaviour.
|
|
123
|
+
|
|
124
|
+
### Patch Changes
|
|
125
|
+
|
|
126
|
+
- [#138](https://github.com/taslabs-net/homeflare-kit/pull/138) [`ef4108a`](https://github.com/taslabs-net/homeflare-kit/commit/ef4108a54c3700485cedb102833f16da54f02d3e) Thanks [@taslabs-net](https://github.com/taslabs-net)! - A `HostFile` or `ReleaseBinary` whose path is respelled to the same file — through a symlinked parent such as `/etc` and `/private/etc`, or by case alone on case-insensitive APFS — is now planned as an `update` that keeps the file. Before, it was planned as a `replace` whose cleanup deleted the old path, which was the same file: the deploy succeeded and the file was gone until the next one. `HostRunner.stat` may now report `dev` and `ino` (the local runner does); a runner that does not gets a check after the move, so a lost file fails the deploy instead. A create whose read-back throws (not only one that mismatches) is now removed. `ReleaseBinary` also refuses a mode its owner cannot read, and a directory that group or other may write. It refuses, too, a second resource installing the same path in one deploy: two owners of one file meant that dropping either one deleted the file the other still declared.
|
|
127
|
+
|
|
128
|
+
## 0.20.0
|
|
129
|
+
|
|
130
|
+
### Minor Changes
|
|
131
|
+
|
|
132
|
+
- [#139](https://github.com/taslabs-net/homeflare-kit/pull/139) [`70e8899`](https://github.com/taslabs-net/homeflare-kit/commit/70e8899fe70cf5426bcbf11b498a658b60f9488b) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Export `ProxmoxCephDaemon`, `ProxmoxCephFs` and `ProxmoxCephOsd` from `@homeflare/alchemy/proxmox`, so a stack can adopt a live cluster's Ceph monitors, managers, metadata servers, CephFS and OSDs. Each is adopt-only by shape: none has an update path, the daemon and filesystem compare nothing so they can never plan a replace, and an OSD is created only when `dev` is declared. All three retain on destroy, so removing a declaration drops its state row and never sends a DELETE. `ProxmoxCephFlag` stays Provider-only on purpose: a declared flag reasserts a maintenance toggle such as `noout` on every deploy.
|
|
133
|
+
|
|
134
|
+
### Patch Changes
|
|
135
|
+
|
|
136
|
+
- [#135](https://github.com/taslabs-net/homeflare-kit/pull/135) [`925454b`](https://github.com/taslabs-net/homeflare-kit/commit/925454b5d9fbefea061a3a204e4b0093a79c0bdc) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Two ways a `Remote.File` managed region could break its own promise, and a systemd rename that
|
|
137
|
+
refused too late. All three found by reviewing the merged diff and reproduced against the fake
|
|
138
|
+
Linux host before anything was changed.
|
|
139
|
+
|
|
140
|
+
🔴 **A REGION RENAME LEFT THE OLD BLOCK IN THE FILE FOREVER.** Only `path` was identity, so changing
|
|
141
|
+
`region.name` — or its `comment` token, which is part of the marker line — planned a routine
|
|
142
|
+
`update`: the new markers were spliced in, the old ones were never touched, and `delete` could only
|
|
143
|
+
ever look for the name in state, which was now the new one. Reproduced: a vendor file ended up
|
|
144
|
+
carrying two `BEGIN` blocks and destroying the resource removed one of them. For the named
|
|
145
|
+
consumers that is two `anchor` lines in a packet filter and a duplicate entry in a host table, with
|
|
146
|
+
nothing in the stack able to take either back. A rename is now a MOVE: the new block is written and
|
|
147
|
+
verified, then the old one is removed, and the stored digest is re-read afterwards so it describes
|
|
148
|
+
the file that is actually there.
|
|
149
|
+
|
|
150
|
+
🔴 **DROPPING `region` TOOK OVER A FILE THIS RESOURCE DID NOT OWN.** Same cause, worse effect: the
|
|
151
|
+
plan said `update` and the apply replaced every byte of the other owner's file with this resource's
|
|
152
|
+
few lines. That is the one thing the managed-region design exists to make impossible. A flip between
|
|
153
|
+
owning the whole file and owning a block — in either direction, at the same path — is now a
|
|
154
|
+
PLAN-TIME REFUSAL, because neither order is safe: writing the whole file first destroys the other
|
|
155
|
+
owner's bytes before anything can be undone, and removing the block first destroys our own claim and
|
|
156
|
+
then refuses. Destroy the resource and declare a new one.
|
|
157
|
+
|
|
158
|
+
🔴 **A `Systemd.Unit` RENAME WHOSE `content` WAS STILL AN OUTPUT WAS NOT CHECKED AT ALL.** The
|
|
159
|
+
resolved rename is checked in the plan since the systemd preflight; the branch `diffHandler` takes
|
|
160
|
+
while `content` is unresolved — exactly the deploy that templates a rendered config's digest into
|
|
161
|
+
the unit — still returned `{ action: 'replace', deleteFirst: true }` with no check, and Alchemy
|
|
162
|
+
deletes the old unit BEFORE reconciling the new one. A rename onto a masked name therefore took the
|
|
163
|
+
service down and only then refused. The half of the check that needs only the new name — the old
|
|
164
|
+
unit deletable, the new one writable and not masked — now runs there too. ⛔ This forbids nothing
|
|
165
|
+
that used to work: the identical refusal was always going to fire in reconcile, just later and with
|
|
166
|
+
nothing running.
|
|
167
|
+
|
|
168
|
+
- [#142](https://github.com/taslabs-net/homeflare-kit/pull/142) [`1905f18`](https://github.com/taslabs-net/homeflare-kit/commit/1905f18c30f7ae2a6d7294d588bf18396b64f4ed) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Two new docs, and no code change.
|
|
169
|
+
|
|
170
|
+
`docs/provider-standard.md` is the kit-side statement of the house standard for a custom
|
|
171
|
+
Alchemy provider. It keeps the standard's rule numbers, and each rule is cited upstream at
|
|
172
|
+
`alchemy@2.0.0-beta.79`. It covers four things. First, use upstream's resource when one
|
|
173
|
+
exists. Second, route every vendor call through `@distilled.cloud/<vendor>` when that
|
|
174
|
+
package exists. Third, use Alchemy's own helpers (`alchemy/Util/sha256`, `Util/poll`,
|
|
175
|
+
`Util/AtomicFile`, `Diff`, `Tags`, `PhysicalName`, `AdoptPolicy`, `Auth` and `Test/Bun`)
|
|
176
|
+
rather than house copies. Fourth, keep `src/**` provider code runtime-portable, because
|
|
177
|
+
this package builds with `--target node`, while tests, fakes, scripts and codegen stay
|
|
178
|
+
Bun-native. The page also records, per family, the vendor version each one was walked
|
|
179
|
+
against and where that record lives. Six families record it only in prose.
|
|
180
|
+
|
|
181
|
+
`docs/upstream-conformance.md` is the audit of every family against that standard, as a
|
|
182
|
+
ranked ledger. It was measured read-only on `925454b`. The findings, in rank order:
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Alchemy changelog archive 12
|
|
2
|
+
|
|
3
|
+
[Current changelog](../../CHANGELOG.md) · [Archive index](./README.md)
|
|
4
|
+
|
|
5
|
+
1. `R2BucketLock` uses `Effect.orDie` and `Effect.promise`. Its reconcile trusts `output`
|
|
6
|
+
rather than the live lock, and it sits on a second Cloudflare SDK where
|
|
7
|
+
`@distilled.cloud/cloudflare/r2` already has the lock operations.
|
|
8
|
+
2. `forgejo/client.ts` is hand-rolled, while `@distilled.cloud/forgejo@1.0.0-rc.12` is
|
|
9
|
+
generated against Forgejo 16.0.3.
|
|
10
|
+
3. `MeshNode` is a deliberate twin of `Cloudflare.Tunnel.WarpConnector`.
|
|
11
|
+
4. Shipped provider code calls `Bun.*` or the `node:*` modules upstream bans (14 of 17
|
|
12
|
+
listed files; the other 3 use only synchronous `node:crypto` or `Buffer`, which upstream
|
|
13
|
+
allows inside `Effect.sync`), and 51 test files run on `node:test` instead of
|
|
14
|
+
`bun:test`.
|
|
15
|
+
|
|
16
|
+
What the ledger records is the gap for each finding. It changes nothing.
|
|
17
|
+
|
|
18
|
+
## 0.19.1
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- [#128](https://github.com/taslabs-net/homeflare-kit/pull/128) [`ba55148`](https://github.com/taslabs-net/homeflare-kit/commit/ba5514820e32f9d546f1a5eb0f92c7f156f2a978) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Estate topology out of the constraint proofs. PR [#118](https://github.com/taslabs-net/homeflare-kit/issues/118)'s create-form proofs used the real
|
|
23
|
+
declarations verbatim, which put a metrics hostname, a cluster's `api-path-prefix` and three Ceph
|
|
24
|
+
pool names into `src` — and `src` ships in the npm tarball of a public repository, so they would
|
|
25
|
+
have stayed in the git history forever. `lxc-harness.ts` states the rule and these tests did not
|
|
26
|
+
follow it: a production-SHAPED declaration with placeholder values, because the proof is about
|
|
27
|
+
which keys the create form sends and which bounds they face, never about the strings.
|
|
28
|
+
|
|
29
|
+
No behaviour changes; the same forms are checked against the same tables.
|
|
30
|
+
|
|
31
|
+
- [#136](https://github.com/taslabs-net/homeflare-kit/pull/136) [`64d4c36`](https://github.com/taslabs-net/homeflare-kit/commit/64d4c36091054fa05b716f1d4029c1feea955289) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Check a systemd rename at plan time. A unit's name or directory change is a delete-first replace, and Alchemy deletes the old unit before reconciling the new one, so a masked name, a unit file someone else owns, or a runner that will not write the new path used to be noticed only after the old unit was already stopped. Those checks now run while planning, and again at apply when the new name was still an Output and the diff could not see the rename. A file byte-identical to this declaration's render stays exempt: it is a deploy that died between write and reload.
|
|
32
|
+
|
|
33
|
+
## 0.19.0
|
|
34
|
+
|
|
35
|
+
### Minor Changes
|
|
36
|
+
|
|
37
|
+
- [#120](https://github.com/taslabs-net/homeflare-kit/pull/120) [`512bf1a`](https://github.com/taslabs-net/homeflare-kit/commit/512bf1a6a973bdbd1c9688d295dcf8a067820a36) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Linux hosts on the existing HostRunner seam: `@homeflare/alchemy/linux`.
|
|
38
|
+
|
|
39
|
+
The kit could declare a guest and nothing inside it. This adds the families that gap
|
|
40
|
+
was missing, on the same seam the launchd subpath already drives a Mac through — so
|
|
41
|
+
`HostFile`'s ownership rules, `checkWrite` and the adoption doctrine come along unchanged.
|
|
42
|
+
|
|
43
|
+
- `sshRunner({ host })` — a Linux `HostRunner` over the operator's own ssh config.
|
|
44
|
+
⛔ `BatchMode=yes` and host verification untouched; ⛔ every remote script reports its
|
|
45
|
+
status behind a per-runner nonce, so a dropped connection is an Error and never a
|
|
46
|
+
"nothing is there"; ⛔ `privileged: false` — nothing calls sudo.
|
|
47
|
+
- `HostDirectory` — because no file resource creates a parent. One directory, never a
|
|
48
|
+
chain; delete is `rmdir`, never recursive.
|
|
49
|
+
- `RemoteFile` — a whole file, or one MANAGED REGION (`BEGIN`/`END` markers) inside a
|
|
50
|
+
file this resource does not own. ⛔ Every byte outside the markers stays identical, the
|
|
51
|
+
file's own mode and owner are copied back, and a delete removes only the block.
|
|
52
|
+
- `SystemdUnit` / `SystemdTimer` — unit file, `daemon-reload`, enable/disable,
|
|
53
|
+
start/stop. ⛔ A deploy NEVER mass-restarts: a unit restarts only when its own file
|
|
54
|
+
changed, when state or systemd says the loaded copy is stale, or when a digest the
|
|
55
|
+
declaration listed in `restartOn` changed. An adopted unit that already matches is not
|
|
56
|
+
restarted, reloaded or started.
|
|
57
|
+
|
|
58
|
+
`systemctl` and `stat` shapes measured read-only on Debian 13 / systemd 257, 2026-09-22;
|
|
59
|
+
the write subcommands are reasoned and read back rather than assumed. Unit files render
|
|
60
|
+
verbatim — there is no machine-readable directive schema to generate from, so the kit
|
|
61
|
+
invents none. Guide: `docs/linux-host.md`.
|
|
62
|
+
|
|
63
|
+
## 0.18.0
|
|
64
|
+
|
|
65
|
+
### Minor Changes
|
|
66
|
+
|
|
67
|
+
- [#118](https://github.com/taslabs-net/homeflare-kit/pull/118) [`5ae4e91`](https://github.com/taslabs-net/homeflare-kit/commit/5ae4e91919a101da3d8605a069c65e0301d0cccc) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Every Proxmox family that writes to the vendor is now checked against the vendor's own schema —
|
|
68
|
+
35 of 35, up from 19, covering 75 endpoints instead of 37. A family left unwired was a write this
|
|
69
|
+
package made with nothing between the declaration and the server's 400, which is the shape of the
|
|
70
|
+
2026-09-22 `deploy:pbs` incident this feature exists for.
|
|
71
|
+
|
|
72
|
+
Newly wired: `Proxmox.ApiToken`, `CephDaemon`, `CephFlag`, `CephFs`, `CephOsd`, `CephPool`, `Lxc`,
|
|
73
|
+
`MetricServer`, `NetworkApply`, `NodeNetwork`, `NotificationTarget`, `SdnApply`, `SdnSubnet`, `Vm`,
|
|
74
|
+
`ZfsPool`, and `Pbs.NotificationTarget` — which was missing from the sweep list and carries the
|
|
75
|
+
incident's own rule, `comment: maxLength 128`, on all three of its creates. Families that write
|
|
76
|
+
their own handlers (`CephOsd`, `Lxc`, the two applies, `Pbs.NotificationTarget`) reach the same
|
|
77
|
+
check by name through `guardForm`, as `Pbs.Datastore` already did; families with a spec declare
|
|
78
|
+
`endpoint`. `tests/constraint-wiring.test.ts` derives the census from the ownership ledger, so a
|
|
79
|
+
family added without an endpoint fails there rather than on a deploy, and each newly wired family
|
|
80
|
+
has a proof test that runs its REAL create form through the vendor's create table and requires no
|
|
81
|
+
violations.
|
|
82
|
+
|
|
83
|
+
🔴 **A live bug this found.** `Proxmox.NodeNetwork`'s create form never sent `iface`, which PVE
|
|
84
|
+
marks required on `POST /nodes/{node}/network` while `{node}` is its only path parameter. Every
|
|
85
|
+
interface create this package could have made would have 400ed; nothing caught it because the
|
|
86
|
+
estate's interfaces were all adopted, which takes the PUT path. `createBody` now sends it, and the
|
|
87
|
+
PUT still does not — there `iface` is the path.
|
|
88
|
+
|
|
89
|
+
⛔ **Presence of a vendor-required parameter is now demanded only when a create is really about to
|
|
90
|
+
happen**, not whenever the create form is built. `Proxmox.NotificationTarget` cannot send gotify's
|
|
91
|
+
`token` or smtp's `password` — they are write-only secrets and props are persisted unencrypted — so
|
|
92
|
+
the documented workflow is to create the target out of band and then declare it. Under the old
|
|
93
|
+
unconditional check that adopt-then-update would have been refused forever; now it plans clean,
|
|
94
|
+
while asking to CREATE a gotify target fails at plan with PVE's own `token: required`. The guards
|
|
95
|
+
move into `resource-guard.ts` and are exported from `pveOperations`, so `CephPool`'s hand-written
|
|
96
|
+
reconcile gets them too.
|
|
97
|
+
|
|
98
|
+
An **action** endpoint with no form is wired as well (`PUT /cluster/sdn`, `PUT /nodes/{node}/network`):
|
|
99
|
+
the table is empty, but the key is resolved against the vendor schema at generation time, so a PVE
|
|
100
|
+
that moves or withdraws an apply fails `bun run check` instead of an `ifreload -a` on three nodes.
|
|
101
|
+
|
|
102
|
+
`PveSpec['endpoint']` now also admits a function of props, for the two families whose endpoint is
|
|
103
|
+
chosen by a prop — `NotificationTarget`'s four PVE types and `CephDaemon`'s mds/mgr/mon, each with
|
|
104
|
+
its own parameter schema. Every key it can return is still a literal in this package's source,
|
|
105
|
+
because the generator finds endpoints by scanning text.
|
|
106
|
+
|
|
107
|
+
Generator changes that came with the volume: `/cluster` and `/nodes/{node}` are split one level
|
|
108
|
+
further down, because they are routes rather than areas — PVE's own viewer expands them — so the
|
|
109
|
+
tables are now 18 files (`pve-cluster-sdn.ts`, `pve-nodes-ceph.ts`, …), all inside the 250-line
|
|
110
|
+
house cap. The generator deletes a file it no longer produces, `tests/schema-manifest.test.ts`
|
|
111
|
+
enumerates the directory instead of a hand-written list and checks every table is claimed by the
|
|
112
|
+
manifest entry it came from, and two PVE bounds published as JSON strings (`bwlimit`'s
|
|
113
|
+
`minimum: "0"`, `count`'s `maximum: "16777216"`) are parsed to numbers — a faithful reading of a
|
|
114
|
+
stated value; a bound that is not a number at all is still dropped rather than guessed at.
|
|
115
|
+
|
|
116
|
+
## 0.17.0
|
|
117
|
+
|
|
118
|
+
### Minor Changes
|
|
119
|
+
|
|
120
|
+
- [#114](https://github.com/taslabs-net/homeflare-kit/pull/114) [`3316006`](https://github.com/taslabs-net/homeflare-kit/commit/3316006513196622551d4cc041986089dc28ffc4) Thanks [@taslabs-net](https://github.com/taslabs-net)! - The Proxmox API type generator, written from the vendor schemas, and the widening it removes.
|
|
121
|
+
`packages/alchemy/src/proxmox/generated/{pve,pbs}.ts` carried the header
|
|
122
|
+
`Run: bun codegen/generate.ts` from the day they were committed, and
|
|
123
|
+
`git log --oneline --all -- 'codegen/generate*'` is empty at every commit: that file existed
|
|
124
|
+
nowhere. The mapping was therefore readable only as its own 8,196 lines of output — nobody could
|
|
125
|
+
reproduce it, correct it, or say which schema version it described. Because nobody could read it,
|
|
126
|
+
nobody noticed what it did: it kept `type`, `enum` and `optional`, dropped every `maxLength`,
|
|
127
|
+
`minLength`, `minimum`, `maximum`, `pattern`, `format`, `typetext`, `default` and description, and
|
|
128
|
+
covered 407 of PVE's 678 endpoints and 46 of PBS's 367 with no record of which 407 or why.
|
|
129
|
+
|
|
130
|
+
`bun codegen/types.ts` is that generator, with `--check`, the same manifest and the same
|
|
131
|
+
sha256-as-identity rule as `codegen/constraints.ts`. It emits every endpoint both products
|
|
132
|
+
document — 678 PVE and 367 PBS, 1,619 exported types — split across 89 files by the vendor's own
|
|
133
|
+
path and packed back up so the split is no deeper than the 250-line house cap requires. Every file
|
|
134
|
+
names its manifest entry, the product version the host reported and the sha256 of the bytes it was
|
|
135
|
+
read from. `generated/pve.ts` and `generated/pbs.ts` stay as `export *` barrels, so no import in
|
|
136
|
+
this package or any consumer moves.
|
|
137
|
+
|
|
138
|
+
⛔ **An integer request parameter is `` `${number}` ``, not `string`.** `pbs:POST /config/verify`'s
|
|
139
|
+
`max-depth` is `integer, minimum 0, maximum 7` in PBS's schema and was `'max-depth'?: string` in
|
|
140
|
+
the type, which accepts `'banana'`; 480 PVE and 151 PBS parameters were widened that way. They are
|
|
141
|
+
now the wire spelling of a number: still assignable to `PveForm`, still carried unchanged through
|
|
142
|
+
`violations`' bound check, and no longer satisfied by an arbitrary string. It is deliberately NOT
|
|
143
|
+
`number`: `client.ts` sends `application/x-www-form-urlencoded` and types the body
|
|
144
|
+
`Record<string, readonly string[] | string>`, so a `number` could not be handed to `pve()` at all,
|
|
145
|
+
and `constraints.ts` iterates a form value on `typeof value === 'string'`. ⚠️ `String(n)` does not
|
|
146
|
+
typecheck against it — write `` `${n}` ``. A boolean parameter stays `'0' | '1'`, which is the
|
|
147
|
+
encoding `values.ts`'s `flag()` already produces rather than a widening. Responses are JSON and
|
|
148
|
+
keep their real `number` and `boolean | 0 | 1`.
|
|
149
|
+
|
|
150
|
+
The old output is reproduced before it is changed, which is what makes the diff reviewable: run
|
|
151
|
+
against the same two schemas with integers left widened, the pipeline re-emits all 646 PVE and 69
|
|
152
|
+
PBS declarations identically, with two recorded exceptions — `NodesNodeLxcVmidConfigGetReturn`'s
|
|
153
|
+
`lxc` becomes `readonly (readonly string[])[]` rather than a readonly array of mutable ones, and 21
|
|
154
|
+
declarations break lines differently because `oxfmt` had reformatted the committed files before
|
|
155
|
+
every `generated` directory reached its ignore list. The naming is the old generator's, reproduced
|
|
156
|
+
rather than improved: `ClusterBackupIdIncluded_volumesGetReturn` keeps its underscore, because
|
|
157
|
+
renaming sixty exported types in the commit that changes what the types mean would hide the second
|
|
158
|
+
change inside the first.
|
|
159
|
+
|
|
160
|
+
⛔ Parameter schemas wrapped in `allOf`/`oneOf` are read through `codegen/parameters.ts` (PR [#113](https://github.com/taslabs-net/homeflare-kit/issues/113)),
|
|
161
|
+
not asked for as `parameters.properties`. `POST /cluster/ha/rules` and `PUT /cluster/ha/rules/{rule}`
|
|
162
|
+
are the two PVE endpoints that need it; a reader that misses them emits a type with no fields, which
|
|
163
|
+
is indistinguishable from an endpoint that takes nothing. An unresolvable schema gets a doc comment
|
|
164
|
+
naming the construct and **no** `Params` type — neither product needs that on these versions.
|
|
165
|
+
|
|
166
|
+
⛔ "Closed object" is spelled differently by the two products, and a test for one lies about the
|
|
167
|
+
other. Measured over both whole schemas: PVE writes numbers (`additionalProperties: 0` on 617
|
|
168
|
+
objects, `1` on 21, absent on 352), PBS writes booleans (`false` on 560, `true` on 36). An absent
|
|
169
|
+
`additionalProperties` is open — the vendor never promised the list was exhaustive.
|
|
170
|
+
|
|
171
|
+
⛔ Eight PVE files are over the house cap and cannot be split. Each holds the endpoints of one
|
|
172
|
+
vendor path whose parameters carry enums of hundreds of members — `rootfs`, `mp0`…`mp255`,
|
|
173
|
+
`unused0`…`unused255` for the volume moves, the ACME DNS provider list, the QEMU CPU model list. A
|
|
174
|
+
single type declaration is the smallest unit there is; dropping the enum would widen the parameter
|
|
175
|
+
back to `string`. Each says so in its own header and `tests/schema-types.test.ts` pins the list.
|
|
176
|
+
|
|
177
|
+
`tests/schema-type-mapping.test.ts` holds the mapping to shapes lifted from the committed files and
|
|
178
|
+
needs no schema cache, so it runs on CI. `tests/schema-types.test.ts` checks provenance, the
|
|
179
|
+
barrel against the files on disk, the cap, the coverage counts and five endpoints the old generator
|
|
180
|
+
omitted, and runs `bun codegen/types.ts --check` when the cache is present — skipping with the
|
|
181
|
+
refresh command when it is not. `codegen/TYPES.md` carries the reasoning.
|
|
182
|
+
|
|
183
|
+
## 0.16.1
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Alchemy changelog archive 13
|
|
2
|
+
|
|
3
|
+
[Current changelog](../../CHANGELOG.md) · [Archive index](./README.md)
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#113](https://github.com/taslabs-net/homeflare-kit/pull/113) [`4fe8cef`](https://github.com/taslabs-net/homeflare-kit/commit/4fe8cef3154ad0f373b291f413dcbd570602e6fa) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `Proxmox.HaRule`'s constraint table was empty and nothing said so.
|
|
8
|
+
|
|
9
|
+
MEASURED 2026-09-22: PVE spells `POST /cluster/ha/rules` as `parameters: {allOf: [{properties:
|
|
10
|
+
{rule}}, {oneOf: [node-affinity, resource-affinity]}]}` — a discriminated union. The apidoc reader
|
|
11
|
+
asked for `parameters.properties`, got `undefined`, and emitted `{}`. A wired family's plan-time
|
|
12
|
+
guard therefore checked **nothing**, and an empty table is indistinguishable from an endpoint whose
|
|
13
|
+
parameters happen to carry no rules. `comment` there has a `maxLength` of 4096 and `affinity` an
|
|
14
|
+
enum of two.
|
|
15
|
+
|
|
16
|
+
`codegen/parameters.ts` reads both combinators, and their logic is their meaning. `allOf` branches
|
|
17
|
+
all apply, so their properties MERGE — a key claimed by two branches would have to satisfy both,
|
|
18
|
+
which this does not compute, so it stops rather than picking one. `oneOf` branches are
|
|
19
|
+
ALTERNATIVES, so they INTERSECT: only what every branch states identically survives, because
|
|
20
|
+
enforcing a rule from one branch would refuse a legal declaration of the other kind. `nodes` and
|
|
21
|
+
`strict` exist only on node-affinity and are therefore not enforced. ⚠️ `optional` is intersected
|
|
22
|
+
toward optional rather than field-by-field: its ABSENCE means required, so dropping a disagreeing
|
|
23
|
+
`optional` would have read as required and refused every legal node-affinity rule, whose `affinity`
|
|
24
|
+
is optional where resource-affinity's is not.
|
|
25
|
+
|
|
26
|
+
⛔ And a parameter schema this file cannot read is now recorded as `unresolved` and **stops the
|
|
27
|
+
generator** for any endpoint this package writes to, rather than producing the empty table that hid
|
|
28
|
+
the problem. `tests/schema-manifest.test.ts` covers the reader directly.
|
|
29
|
+
|
|
30
|
+
⛔ `docs/api-coverage.*` had the identical blind spot from its own parser: it reported
|
|
31
|
+
`/cluster/ha/rules` as having **zero** parameters and zero gaps. `scripts/api-schema.ts` now reads
|
|
32
|
+
the combinators through the same resolver — 5 parameters, 2 unenforced, both `format` names.
|
|
33
|
+
⚠️ Two parsers for one file format is the deeper defect; merging them is its own change.
|
|
34
|
+
|
|
35
|
+
- [#116](https://github.com/taslabs-net/homeflare-kit/pull/116) [`54479fc`](https://github.com/taslabs-net/homeflare-kit/commit/54479fc7792076fb0e718b55588cc815823818c2) Thanks [@taslabs-net](https://github.com/taslabs-net)! - `Netbox.Prefix` no longer erases prose it did not declare.
|
|
36
|
+
|
|
37
|
+
🔴 **The bug, found by review rather than by an incident.** The resource sent `description: ''`
|
|
38
|
+
whenever the prop was absent. On a create that is invisible — the field was empty anyway. ⛔ On an
|
|
39
|
+
**adopt** it is data loss: NetBox is the estate's record of DECISIONS, so a prefix's description is
|
|
40
|
+
usually the only written trace of why that range exists. The first deploy that adopted one would
|
|
41
|
+
have PATCHed it to empty, `matches` would have reported drift, the plan would have said `update`,
|
|
42
|
+
and the diff would have read as converging a declaration rather than deleting a sentence.
|
|
43
|
+
|
|
44
|
+
★ **The tell was an inconsistency inside the same file, not a failure.** Optional foreign keys were
|
|
45
|
+
already omitted when undeclared, with a comment explaining that sending `null` would clear a tenant
|
|
46
|
+
somebody set in the UI. Free text had the identical hazard and the opposite treatment. Two fields,
|
|
47
|
+
one hazard, two answers — that gap is the defect.
|
|
48
|
+
|
|
49
|
+
★ **The line is now drawn at what the vendor itself defaults.** `status`, `is_pool` and
|
|
50
|
+
`mark_utilized` have defaults in NetBox's schema, so omitting one genuinely means "the default" and
|
|
51
|
+
settling it says what NetBox would have done anyway. `description`, `comments` and the optional
|
|
52
|
+
foreign keys have no such default — the schema's `''` is the absence of a value, not a decision —
|
|
53
|
+
so they are omitted from the body and left uncompared until declared.
|
|
54
|
+
|
|
55
|
+
⛔ **Whatever `matches` compares, `body` must send**, or the plan says `update` forever: the PATCH
|
|
56
|
+
omits the field, so the next read is unchanged. The two moved together here and
|
|
57
|
+
`prefix-form.test.ts` asserts the invariant.
|
|
58
|
+
|
|
59
|
+
⚠️ **The cost, stated:** prose can no longer be cleared by omission. Clearing it is
|
|
60
|
+
`description: ''`, written on purpose — the readable way to say a destructive thing.
|
|
61
|
+
|
|
62
|
+
`body` and `matches` are extracted to `prefix-form.ts` so both are pure functions a test can call
|
|
63
|
+
with a literal, the way the Proxmox families keep their `*-form.ts` beside the resource.
|
|
64
|
+
|
|
65
|
+
## 0.16.0
|
|
66
|
+
|
|
67
|
+
### Minor Changes
|
|
68
|
+
|
|
69
|
+
- [#110](https://github.com/taslabs-net/homeflare-kit/pull/110) [`9fa5800`](https://github.com/taslabs-net/homeflare-kit/commit/9fa5800c8a1d2d3fb831d29ae64e9145e82fe48f) Thanks [@taslabs-net](https://github.com/taslabs-net)! - A NetBox provider, generated from NetBox's own OpenAPI document — `@homeflare/alchemy/netbox`.
|
|
70
|
+
|
|
71
|
+
`Netbox.Prefix` declares one IP prefix and the decision recorded against it. It is the first object
|
|
72
|
+
class for three measurable reasons: `WritablePrefixRequest.required` is exactly `["prefix"]`, so it
|
|
73
|
+
is the only interesting NetBox object with **no foreign-key prerequisite** (a VLAN needs `vid` and
|
|
74
|
+
a group; a Device needs a role, a type and a site — four more Resources before the first one can be
|
|
75
|
+
declared); a prefix is the atom of what NetBox is for, the record of what the network was DECIDED
|
|
76
|
+
to be; and `status: 'deprecated'` is how a retired range stops being folklore in an SSH config
|
|
77
|
+
comment and becomes a line with a reviewable diff.
|
|
78
|
+
|
|
79
|
+
⛔ **The constraint tables are generated, never hand-typed.** `bun codegen/netbox.ts` reads NetBox
|
|
80
|
+
4.7.0's OpenAPI 3.0.3 document, verifies its sha256 against `codegen/manifest.json`, and emits the
|
|
81
|
+
committed tables plus `docs/netbox-coverage.md`. A key naming an endpoint the vendor does not have
|
|
82
|
+
stops the generator. This is the same pipeline that exists because a PBS deploy adopted ten objects
|
|
83
|
+
and then failed its one create on a `maxLength: 128` the generated type did not carry — NetBox gets
|
|
84
|
+
it **before** its first write rather than after.
|
|
85
|
+
|
|
86
|
+
⛔ **Two of NetBox's seven regexes are not JavaScript in meaning, and both compile cleanly.**
|
|
87
|
+
Measured over the whole document: `^[-\w]+$` (`slug`) and `^[\w.@+-]+$` (`username`). Python's `\w`
|
|
88
|
+
is Unicode on a `str`, so Django accepts `zürich-core` and a verbatim JavaScript copy refuses it —
|
|
89
|
+
a plan blaming the operator for a legal value, which is worse than the server-side 400 the table
|
|
90
|
+
replaces. ⚠️ The `u` flag does not fix it. Both are dropped, recorded as `patternSource` with no
|
|
91
|
+
`pattern`, and the generated header says nothing enforces them.
|
|
92
|
+
|
|
93
|
+
⚠️ **The document was pinned to the vendor's release tag, not read from an instance, and the
|
|
94
|
+
manifest says why.** The reference instance could not answer `/api/schema/`. The published document
|
|
95
|
+
was then cross-checked against a snapshot the estate took from its own instance while it was up:
|
|
96
|
+
1256 operations on each side, `(method, path)` sets identical with zero difference. ⛔ That verifies
|
|
97
|
+
the path surface only — the snapshot discards request bodies, which is the half this generates —
|
|
98
|
+
so every constraint rests on the vendor document alone.
|
|
99
|
+
|
|
100
|
+
Also here:
|
|
101
|
+
|
|
102
|
+
- Adopt-first by construction: `reconcile` locates before it writes, and ⛔ an ambiguous identity
|
|
103
|
+
**fails** rather than binding to whichever row NetBox ordered first.
|
|
104
|
+
- 🔴 **One guessed filter shape was caught before it shipped, and the fix is structural.** The
|
|
105
|
+
prefix locate first narrowed server-side with `vrf_id=null`, the sentinel NetBox uses for "no
|
|
106
|
+
foreign key" — `FILTERS_NULL_CHOICE_VALUE = 'null'` is real. ⛔ But in the vendor's own source
|
|
107
|
+
at v4.7.0, `PrefixFilterSet.vrf_id` is a plain `ModelMultipleChoiceFilter` with no `null_value`:
|
|
108
|
+
it never opted in, so `'null'` fails queryset validation and NetBox answers **400 on every plan
|
|
109
|
+
for every global-table prefix**. `locate` now sends only filters the document declares and a new
|
|
110
|
+
`identifies` picks the row in this process, where the rule is readable and testable offline.
|
|
111
|
+
- `retain` on removal for every family, because deleting a NetBox row reparents children and
|
|
112
|
+
detaches IP assignments; the `delete` handler is fully implemented anyway.
|
|
113
|
+
- Read/write shape asymmetry handled in `values.ts` — `status` is written as `"active"` and read
|
|
114
|
+
back as `{value, label}`; a foreign key is written as `4` and read back as `{id, url, display}`.
|
|
115
|
+
Comparing those directly reports drift on every plan, forever.
|
|
116
|
+
- ⛔ No credential is ever a prop. `NETBOX_URL` and `NETBOX_TOKEN` are read at call time, and
|
|
117
|
+
`Authorization: Token`, not `Bearer` — a wrong scheme and a wrong credential look identical in
|
|
118
|
+
the response.
|
|
119
|
+
- ★ **`codegen/param-rules.ts` gains a dialect table, and `emit.ts` is untouched.** There are
|
|
120
|
+
exactly two vendor facts about a pattern — which dialect it is written in, and whether the
|
|
121
|
+
vendor anchors it — so they live together per product rather than as a string every function
|
|
122
|
+
switches on. ⛔ NetBox gets the Django translator and no anchoring: its own 7 patterns already
|
|
123
|
+
carry `^…$` and Django validates with `re.search`, so re-anchoring would invent a rule. PVE is
|
|
124
|
+
the opposite case and keeps its measured anchoring. Every existing Proxmox table regenerates
|
|
125
|
+
byte-identical.
|
|
126
|
+
|
|
127
|
+
### Patch Changes
|
|
128
|
+
|
|
129
|
+
- [#108](https://github.com/taslabs-net/homeflare-kit/pull/108) [`6586b0d`](https://github.com/taslabs-net/homeflare-kit/commit/6586b0decb5201e57f9e086619d1f658e5bce94d) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Adversarial review of the vendor-constraint guard, same day it shipped: two rule kinds were passing
|
|
130
|
+
through it unchecked, and the coverage report did not know the guard existed.
|
|
131
|
+
|
|
132
|
+
⛔ **PVE anchors every pattern and the tables did not.** MEASURED read-only on a cluster node,
|
|
133
|
+
`/usr/share/perl5/PVE/JSONSchema.pm:1636`: `if ($value !~ m/^$pattern$/)`. The published pattern is
|
|
134
|
+
the INSIDE of an anchored match — PVE ships `[A-Za-z][A-Za-z0-9\-\_]+` for a firewall alias name —
|
|
135
|
+
and `RegExp.test` is a search, so `ok name!` matched on its `ok`, planned clean and was rejected by
|
|
136
|
+
PVE with the 400 the guard exists to prevent. All eleven PVE patterns in the tables were toothless
|
|
137
|
+
this way. The anchoring is textual rather than `(?:…)`, because Perl's is: three of PVE's 72
|
|
138
|
+
patterns carry a top-level `|`, and `^a|b$` is not `^(?:a|b)$`. `\n?` before the `$` is Perl's `$`,
|
|
139
|
+
which matches before a final newline where JavaScript's does not — without it the guard would refuse
|
|
140
|
+
values PVE accepts, which is worse than the 400. PBS is untouched: all 37 of its patterns already
|
|
141
|
+
carry their own `^…$` and Rust's `is_match` is a search.
|
|
142
|
+
|
|
143
|
+
⛔ **An array states its rules on `items`, and the emitter read only the parameter.** 30 tabled
|
|
144
|
+
parameters are arrays and 11 state real limits one level down — PBS `target` (2–32 chars, a name
|
|
145
|
+
pattern), `associated-key`, the `delete` enums, PVE `secondary-controllers` (max 64). `violations`
|
|
146
|
+
was already checking every element of a repeated key against a row that had no rules in it. Those
|
|
147
|
+
rules now merge into the row, which says `each: true`; `required` is never taken from `items`.
|
|
148
|
+
|
|
149
|
+
⛔ **`patternFlags` is emitted instead of discarded.** `translatePattern` lifts PBS's leading `(?m)`
|
|
150
|
+
to a flag and the first generation returned it and threw it away, so a multi-line rule would have
|
|
151
|
+
been enforced with single-line semantics. No tabled endpoint uses one today; this is the guard for
|
|
152
|
+
the day one does.
|
|
153
|
+
|
|
154
|
+
⛔ **`docs/api-coverage.md` called the 128-character comment unenforced.** It was generated from the
|
|
155
|
+
vendor schema alone, hours after the tables started enforcing 321 rows of it, so the gap column
|
|
156
|
+
counted every rule the guard had just closed — including the one the report opens by describing.
|
|
157
|
+
`POST /config/verify` now reads `unenforced: []`, PBS's owned gap falls 144 → 62 and PVE's 545 →
|
|
158
|
+
490, and a `format` is still never subtracted because the tables record the name and check nothing.
|
|
159
|
+
|
|
160
|
+
⛔ **The two manifests named two different PVE schemas.** `codegen/manifest.json` said 9.2.11 and
|
|
161
|
+
`schemas/manifest.json` said 9.2.4 — both true of this genuinely mixed-version cluster, differing by
|
|
162
|
+
two write endpoints, and nothing said so. Both now name the same bytes and the same versioned cache
|
|
163
|
+
filename, and `tests/schema-manifest.test.ts` fails if they ever diverge again.
|
|
164
|
+
|
|
165
|
+
New tests: `constraints-dialect.test.ts` holds a mutant for each newly enforced kind, and
|
|
166
|
+
`constraints-live.test.ts` runs all ten objects of the live PBS inventory — the real ids, stores,
|
|
167
|
+
schedules and retention values, comment text replaced by same-length filler because this package is
|
|
168
|
+
public — through their create AND update tables expecting zero violations, which is the false
|
|
169
|
+
positive this feature could itself cause. `constraints.test.ts` is split at the 250-line house cap.
|
|
170
|
+
|
|
171
|
+
## 0.15.0
|
|
172
|
+
|
|
173
|
+
### Minor Changes
|
|
174
|
+
|
|
175
|
+
- [#106](https://github.com/taslabs-net/homeflare-kit/pull/106) [`fc026e7`](https://github.com/taslabs-net/homeflare-kit/commit/fc026e7b81d937f614515abe8b2a2d7bb2a2d019) Thanks [@taslabs-net](https://github.com/taslabs-net)! - Forward every `Bao.*` family's `Props` and `Attributes` types from `@homeflare/alchemy/openbao`.
|
|
176
|
+
|
|
177
|
+
Each family already re-exported its own types from its module, but the barrel forwarded only the
|
|
178
|
+
VALUES for seven of them — `BaoAuthMethod`, `BaoCloudflareRole`, `BaoMount`, `BaoPkiRole`,
|
|
179
|
+
`BaoPolicy`, `BaoProxmoxRole` and `BaoSshRole` — plus `BaoAuthRoleAttributes`,
|
|
180
|
+
`BaoPluginAttributes`, `BaoJwtRoleAttributes` and `BaoJwtCallbackMode`.
|