@homeflare/alchemy 0.36.0 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/argocd/cluster.d.ts +102 -0
- package/dist/argocd/cluster.d.ts.map +1 -0
- package/dist/argocd/credentials.d.ts +37 -0
- package/dist/argocd/credentials.d.ts.map +1 -0
- package/dist/argocd/fake-argocd.d.ts +42 -0
- package/dist/argocd/fake-argocd.d.ts.map +1 -0
- package/dist/argocd/git-credentials.d.ts +29 -0
- package/dist/argocd/git-credentials.d.ts.map +1 -0
- package/dist/argocd/index.d.ts +19 -0
- package/dist/argocd/index.d.ts.map +1 -0
- package/dist/argocd/index.js +306 -0
- package/dist/argocd/index.js.map +17 -0
- package/dist/argocd/providers.d.ts +13 -0
- package/dist/argocd/providers.d.ts.map +1 -0
- package/dist/argocd/repo-creds.d.ts +67 -0
- package/dist/argocd/repo-creds.d.ts.map +1 -0
- package/dist/argocd/repository.d.ts +87 -0
- package/dist/argocd/repository.d.ts.map +1 -0
- package/dist/argocd/resource.d.ts +90 -0
- package/dist/argocd/resource.d.ts.map +1 -0
- package/dist/cloudflare/index.js +4 -4
- package/dist/cloudflare/index.js.map +3 -3
- package/dist/cloudflare/r2-bucket-lock.d.ts +14 -5
- package/dist/cloudflare/r2-bucket-lock.d.ts.map +1 -1
- package/dist/litellm/index.js.map +2 -2
- package/dist/litellm/operations.d.ts.map +1 -1
- package/dist/proxmox/ceph-daemon-form.d.ts +4 -5
- package/dist/proxmox/ceph-daemon-form.d.ts.map +1 -1
- package/dist/proxmox/ceph-daemon-wire.d.ts +31 -0
- package/dist/proxmox/ceph-daemon-wire.d.ts.map +1 -0
- package/dist/proxmox/ceph-daemon.d.ts +40 -6
- package/dist/proxmox/ceph-daemon.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs-distilled.d.ts +33 -0
- package/dist/proxmox/ceph-fs-distilled.d.ts.map +1 -0
- package/dist/proxmox/ceph-fs-wire.d.ts +20 -11
- package/dist/proxmox/ceph-fs-wire.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs.d.ts +30 -6
- package/dist/proxmox/ceph-fs.d.ts.map +1 -1
- package/dist/proxmox/ceph-osd-tree.d.ts +12 -3
- package/dist/proxmox/ceph-osd-tree.d.ts.map +1 -1
- package/dist/proxmox/ceph-osd-write.d.ts +17 -2
- package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
- package/dist/proxmox/ceph-osd.d.ts +6 -0
- package/dist/proxmox/ceph-osd.d.ts.map +1 -1
- package/dist/proxmox/ceph-pool-settle.d.ts +19 -12
- package/dist/proxmox/ceph-pool-settle.d.ts.map +1 -1
- package/dist/proxmox/ceph-pool-wire.d.ts +37 -0
- package/dist/proxmox/ceph-pool-wire.d.ts.map +1 -0
- package/dist/proxmox/ceph-pool.d.ts +10 -5
- package/dist/proxmox/ceph-pool.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 +605 -483
- package/dist/proxmox/index.js.map +20 -17
- package/dist/proxmox/storage-wire.d.ts +15 -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/docs/argocd-kubernetes.md +100 -0
- package/docs/argocd.md +139 -0
- package/docs/credentials.md +9 -0
- package/docs/distilled-interim.md +15 -3
- package/docs/litellm.md +17 -0
- package/docs/peers.md +7 -0
- package/docs/provider-standard.md +24 -6
- package/docs/proxmox.md +20 -0
- package/docs/unifi.md +45 -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 +8 -3
- package/src/argocd/cluster.test.ts +120 -0
- package/src/argocd/cluster.ts +222 -0
- package/src/argocd/credentials.ts +38 -0
- package/src/argocd/fake-argocd.ts +60 -0
- package/src/argocd/git-credentials.ts +71 -0
- package/src/argocd/index.ts +40 -0
- package/src/argocd/providers.ts +17 -0
- package/src/argocd/repo-creds.test.ts +123 -0
- package/src/argocd/repo-creds.ts +115 -0
- package/src/argocd/repository.test.ts +167 -0
- package/src/argocd/repository.ts +165 -0
- package/src/argocd/resource.ts +150 -0
- package/src/cloudflare/r2-bucket-lock.test.ts +54 -18
- package/src/cloudflare/r2-bucket-lock.ts +25 -18
- package/src/litellm/operations.ts +8 -4
- package/src/proxmox/ceph-daemon-form.ts +7 -8
- package/src/proxmox/ceph-daemon-wire.ts +112 -0
- package/src/proxmox/ceph-daemon-write.test.ts +158 -0
- package/src/proxmox/ceph-daemon.ts +70 -53
- package/src/proxmox/ceph-fs-delete.test.ts +81 -0
- package/src/proxmox/ceph-fs-distilled.ts +100 -0
- package/src/proxmox/ceph-fs-wire.ts +17 -31
- package/src/proxmox/ceph-fs-write.test.ts +67 -0
- package/src/proxmox/ceph-fs.ts +68 -73
- package/src/proxmox/ceph-osd-tree.ts +14 -5
- package/src/proxmox/ceph-osd-write.test.ts +61 -0
- package/src/proxmox/ceph-osd-write.ts +33 -6
- package/src/proxmox/ceph-osd.ts +6 -0
- package/src/proxmox/ceph-pool-settle.ts +24 -17
- package/src/proxmox/ceph-pool-wire.ts +101 -0
- package/src/proxmox/ceph-pool-write.test.ts +146 -0
- package/src/proxmox/ceph-pool.ts +87 -98
- 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
- package/src/proxmox/distilled-task-await.test.ts +0 -124
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
* on a lane that cannot prove one is needed and (b) never takes the rest of the plan down with
|
|
19
19
|
* it — the "noop-with-warning" reading of the fix, over the alternative of a typed plan error.
|
|
20
20
|
*
|
|
21
|
-
* ⛔ ONLY THE CREDENTIAL DENIAL IS CAUGHT HERE.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* ⛔ ONLY THE CREDENTIAL DENIAL IS CAUGHT HERE. Other errors propagate to the caller.
|
|
22
|
+
* Legacy callers may still fold them, but the typed-absence follow-up makes User/Group/
|
|
23
|
+
* Storage catch only their SDK missing-object tag. Never infer a resource is absent from
|
|
24
|
+
* this sentinel. See credential-errors.ts's own ⛔ for why the OpenBao mint's 403 qualifies.
|
|
25
25
|
*/
|
|
26
26
|
import * as Effect from 'effect/Effect';
|
|
27
27
|
import type { PveCredentialDenied } from './credential-errors.ts';
|
|
@@ -31,9 +31,9 @@ export const UNREADABLE = Symbol('pve-unreadable');
|
|
|
31
31
|
export type Unreadable = typeof UNREADABLE;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Catches ONLY `PveCredentialDenied` and
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* Catches ONLY `PveCredentialDenied` and returns the sentinel. Compose this BEFORE the
|
|
35
|
+
* caller's absence handling so a credential refusal cannot become a genuine absence.
|
|
36
|
+
* This helper itself classifies neither SDK missing-object tags nor transport failures.
|
|
37
37
|
*
|
|
38
38
|
* ⚠️ `E | PveCredentialDenied` IN, `E` OUT — not a generic `E` narrowed by `Exclude`. `catchTag`
|
|
39
39
|
* needs the tag to be STATICALLY present in the error union to typecheck at all; a bare
|
package/src/proxmox/user-wire.ts
CHANGED
|
@@ -130,18 +130,15 @@ export const readUserOrFail = (props: UserProps) =>
|
|
|
130
130
|
).pipe(Effect.map((live) => (live === UNREADABLE ? UNREADABLE : attributesOf(live, props))));
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
|
-
* ⛔
|
|
134
|
-
* missing
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* folding a TRANSIENT failure into "absent" at PLAN TIME is what forced a false `update` with
|
|
140
|
-
* nothing compared. `diff` calls `readUserOrFail` directly, so a genuine failure there
|
|
141
|
-
* propagates and fails the whole plan loudly instead.
|
|
133
|
+
* ⛔ ONLY `UserNotFound` MEANS ABSENT. The initial distilled transport swap preserved a
|
|
134
|
+
* catch-all fold because PVE reports missing objects as HTTP 500. SDK PR 265 now types that
|
|
135
|
+
* measured message, so permission errors, unrelated 500s and exhausted transports propagate
|
|
136
|
+
* instead of triggering a speculative create (decision 49 follow-up, 2026-09-24).
|
|
137
|
+
* ★ `readUserOrFail` remains the strict path used for an already-confirmed state row. This
|
|
138
|
+
* change narrows cold reads/reconcile without changing that existing drift/diff contract.
|
|
142
139
|
*/
|
|
143
140
|
export const readUser = (props: UserProps) =>
|
|
144
|
-
readUserOrFail(props).pipe(Effect.
|
|
141
|
+
readUserOrFail(props).pipe(Effect.catchTag('UserNotFound', () => Effect.succeed(undefined)));
|
|
145
142
|
|
|
146
143
|
/** `read`/`reconcile` return `Attributes | undefined`; only `diff` tells `UNREADABLE` apart. */
|
|
147
144
|
export const dropUnreadable = (live: UserAttributes | Unreadable | undefined) =>
|
package/src/proxmox/user.ts
CHANGED
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
* · `Sys.Audit` (or `User.Modify`) for the read, which the `read` role's auditor already has.
|
|
35
35
|
*
|
|
36
36
|
* ⚠️ A MISSING USER IS A 500, NOT A 404 — MEASURED against the live cluster, "no such user
|
|
37
|
-
* ('x@pve')".
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `
|
|
41
|
-
* cries-wolf
|
|
37
|
+
* ('x@pve')". The first SDK migration exposed it as `InternalServerError`, so its create
|
|
38
|
+
* workflow needed a catch-all fold. SDK PR #265 now recognizes `UserNotFound`; `readUser`
|
|
39
|
+
* catches only that tag, letting other cold-read/reconcile failures propagate. The strict
|
|
40
|
+
* `readUserOrFail` path for confirmed rows remains unchanged: its original transient-failure
|
|
41
|
+
* fix stopped the cries-wolf update with nothing compared (user-wire.ts).
|
|
42
42
|
*
|
|
43
43
|
* ⚠️ THE WIRE URL FOR THIS FAMILY CHANGED, THOUGH NOTHING A CALLER SEES DID. MEASURED
|
|
44
44
|
* (user.test.ts): distilled's `{userid}` label substitution percent-encodes it —
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exercises `@distilled.cloud/proxmox`'s `awaitTask` (the async-task
|
|
3
|
-
* 200-trap — see that package's `src/protocol.ts` header) against a FAKE
|
|
4
|
-
* `HttpClient`, no live PVE node involved.
|
|
5
|
-
*
|
|
6
|
-
* ⚠️ THIS PACKAGE IS CONSUMED VIA THE INTERIM-PACKAGE ALIAS
|
|
7
|
-
* (`packages/alchemy/package.json`'s `dependencies`:
|
|
8
|
-
* `"@distilled.cloud/proxmox": "npm:@homeflare/distilled-proxmox@0.2.0"`)
|
|
9
|
-
* — see `packages/alchemy/docs/distilled-interim.md`. Nothing in
|
|
10
|
-
* `packages/alchemy/src/proxmox/*` (the existing 98-resource family) is
|
|
11
|
-
* touched or migrated onto it here; this file is standalone proof the
|
|
12
|
-
* package works end to end, ahead of migrating any hand-written resource
|
|
13
|
-
* onto it (a later PR).
|
|
14
|
-
*/
|
|
15
|
-
import { describe, expect, test } from 'bun:test';
|
|
16
|
-
import * as Effect from 'effect/Effect';
|
|
17
|
-
import * as Layer from 'effect/Layer';
|
|
18
|
-
import * as HttpClient from 'effect/unstable/http/HttpClient';
|
|
19
|
-
import * as HttpClientResponse from 'effect/unstable/http/HttpClientResponse';
|
|
20
|
-
import * as Proxmox from '@distilled.cloud/proxmox';
|
|
21
|
-
|
|
22
|
-
const credentials = Proxmox.credentials({
|
|
23
|
-
tokenId: 'root@pam!test',
|
|
24
|
-
secret: 'test-secret',
|
|
25
|
-
baseUrl: 'https://pve.test:8006',
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
/** A fake PVE that answers `running` (no `exitstatus`) `pendingPolls` times, then `finalBody`. */
|
|
29
|
-
const fakePve = (pendingPolls: number, finalBody: Record<string, unknown>) => {
|
|
30
|
-
let calls = 0;
|
|
31
|
-
return Layer.succeed(
|
|
32
|
-
HttpClient.HttpClient,
|
|
33
|
-
HttpClient.make((request) =>
|
|
34
|
-
Effect.sync(() => {
|
|
35
|
-
calls++;
|
|
36
|
-
const body =
|
|
37
|
-
calls <= pendingPolls
|
|
38
|
-
? {
|
|
39
|
-
status: 'running',
|
|
40
|
-
id: 'qmstart',
|
|
41
|
-
node: 'pve1',
|
|
42
|
-
pid: 100,
|
|
43
|
-
pstart: 1,
|
|
44
|
-
starttime: 1,
|
|
45
|
-
type: 'qmstart',
|
|
46
|
-
upid: 'UPID:pve1:...',
|
|
47
|
-
user: 'root@pam',
|
|
48
|
-
}
|
|
49
|
-
: finalBody;
|
|
50
|
-
return HttpClientResponse.fromWeb(
|
|
51
|
-
request,
|
|
52
|
-
new Response(JSON.stringify({ data: body }), { status: 200 }),
|
|
53
|
-
);
|
|
54
|
-
}),
|
|
55
|
-
),
|
|
56
|
-
);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const runAwaitTask = (pendingPolls: number, finalBody: Record<string, unknown>) =>
|
|
60
|
-
Effect.runPromise(
|
|
61
|
-
Proxmox.awaitTask(
|
|
62
|
-
{ node: 'pve1', upid: 'UPID:pve1:00000001:00000001:00000001:qmstart:100:root@pam:' },
|
|
63
|
-
{ pollInterval: '5 millis' },
|
|
64
|
-
).pipe(Effect.provide(Layer.mergeAll(fakePve(pendingPolls, finalBody), credentials))),
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const OK_STATUS = {
|
|
68
|
-
status: 'stopped',
|
|
69
|
-
exitstatus: 'OK',
|
|
70
|
-
id: 'qmstart',
|
|
71
|
-
node: 'pve1',
|
|
72
|
-
pid: 100,
|
|
73
|
-
pstart: 1,
|
|
74
|
-
starttime: 1,
|
|
75
|
-
type: 'qmstart',
|
|
76
|
-
upid: 'UPID:pve1:...',
|
|
77
|
-
user: 'root@pam',
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
describe("awaitTask (against a fake PVE, per protocol.ts's async-task trap)", () => {
|
|
81
|
-
test("resolves once exitstatus is exactly OK, after polling through 'running'", async () => {
|
|
82
|
-
const result = await runAwaitTask(2, OK_STATUS);
|
|
83
|
-
expect(result.exitstatus).toBe('OK');
|
|
84
|
-
expect(result.status).toBe('stopped');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('resolves immediately when the task is already done on the first poll', async () => {
|
|
88
|
-
const result = await runAwaitTask(0, OK_STATUS);
|
|
89
|
-
expect(result.exitstatus).toBe('OK');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('fails with ProxmoxTaskFailed for any exitstatus other than exactly OK', async () => {
|
|
93
|
-
const failed = await Effect.runPromise(
|
|
94
|
-
Proxmox.awaitTask(
|
|
95
|
-
{ node: 'pve1', upid: 'UPID:pve1:00000001:00000001:00000001:qmstart:100:root@pam:' },
|
|
96
|
-
{ pollInterval: '5 millis' },
|
|
97
|
-
).pipe(
|
|
98
|
-
Effect.provide(
|
|
99
|
-
Layer.mergeAll(fakePve(0, { ...OK_STATUS, exitstatus: 'job errors' }), credentials),
|
|
100
|
-
),
|
|
101
|
-
Effect.flip,
|
|
102
|
-
),
|
|
103
|
-
);
|
|
104
|
-
expect(failed).toBeInstanceOf(Proxmox.ProxmoxTaskFailed);
|
|
105
|
-
expect(failed).toMatchObject({ node: 'pve1', exitstatus: 'job errors' });
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// ⛔ "OK (warnings)" is NOT a success — see task.ts's exact-equality note.
|
|
109
|
-
test("fails (does not swallow) 'OK (warnings)'", async () => {
|
|
110
|
-
const failed = await Effect.runPromise(
|
|
111
|
-
Proxmox.awaitTask(
|
|
112
|
-
{ node: 'pve1', upid: 'UPID:pve1:00000001:00000001:00000001:qmstart:100:root@pam:' },
|
|
113
|
-
{ pollInterval: '5 millis' },
|
|
114
|
-
).pipe(
|
|
115
|
-
Effect.provide(
|
|
116
|
-
Layer.mergeAll(fakePve(0, { ...OK_STATUS, exitstatus: 'OK (warnings)' }), credentials),
|
|
117
|
-
),
|
|
118
|
-
Effect.flip,
|
|
119
|
-
),
|
|
120
|
-
);
|
|
121
|
-
expect(failed).toBeInstanceOf(Proxmox.ProxmoxTaskFailed);
|
|
122
|
-
expect(failed).toMatchObject({ exitstatus: 'OK (warnings)' });
|
|
123
|
-
});
|
|
124
|
-
});
|