@homeflare/alchemy 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/litellm/client.d.ts +76 -0
- package/dist/litellm/client.d.ts.map +1 -0
- package/dist/litellm/credentials.d.ts +48 -0
- package/dist/litellm/credentials.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +36 -0
- package/dist/litellm/fake-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-stack.d.ts +30 -0
- package/dist/litellm/fake-stack.d.ts.map +1 -0
- package/dist/litellm/generated/pass-through.d.ts +71 -0
- package/dist/litellm/generated/pass-through.d.ts.map +1 -0
- package/dist/litellm/index.d.ts +13 -0
- package/dist/litellm/index.d.ts.map +1 -0
- package/dist/litellm/index.js +270 -0
- package/dist/litellm/index.js.map +14 -0
- package/dist/litellm/pass-through-endpoint.d.ts +94 -0
- package/dist/litellm/pass-through-endpoint.d.ts.map +1 -0
- package/dist/litellm/pass-through-form.d.ts +40 -0
- package/dist/litellm/pass-through-form.d.ts.map +1 -0
- package/dist/litellm/providers.d.ts +16 -0
- package/dist/litellm/providers.d.ts.map +1 -0
- package/dist/postgres/connection.d.ts +50 -0
- package/dist/postgres/connection.d.ts.map +1 -0
- package/dist/postgres/database-attrs.d.ts +79 -0
- package/dist/postgres/database-attrs.d.ts.map +1 -0
- package/dist/postgres/database-sql.d.ts +73 -0
- package/dist/postgres/database-sql.d.ts.map +1 -0
- package/dist/postgres/database.d.ts +63 -0
- package/dist/postgres/database.d.ts.map +1 -0
- package/dist/postgres/errors.d.ts +86 -0
- package/dist/postgres/errors.d.ts.map +1 -0
- package/dist/postgres/fake-sql.d.ts +22 -0
- package/dist/postgres/fake-sql.d.ts.map +1 -0
- package/dist/postgres/index.d.ts +16 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +238 -0
- package/dist/postgres/index.js.map +15 -0
- package/dist/postgres/providers.d.ts +14 -0
- package/dist/postgres/providers.d.ts.map +1 -0
- package/dist/proxmox/index.js.map +2 -2
- package/dist/proxmox/zfs-pool.d.ts +10 -2
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/dist/release/archive.d.ts +6 -2
- package/dist/release/archive.d.ts.map +1 -1
- package/dist/release/binary-form.d.ts +14 -9
- package/dist/release/binary-form.d.ts.map +1 -1
- package/dist/release/binary-pins.d.ts +17 -0
- package/dist/release/binary-pins.d.ts.map +1 -0
- package/dist/release/catalog.d.ts.map +1 -1
- package/dist/release/index.js +90 -40
- package/dist/release/index.js.map +8 -7
- package/dist/release/tar.d.ts +9 -2
- package/dist/release/tar.d.ts.map +1 -1
- package/docs/litellm.md +110 -0
- package/docs/peers.md +8 -1
- package/docs/postgres.md +125 -0
- package/docs/release-binary-catalogs.md +40 -4
- package/docs/release-binary-refusals.md +22 -8
- package/docs/release-binary-upstream.md +33 -15
- package/package.json +10 -1
- package/src/litellm/client.ts +195 -0
- package/src/litellm/credentials.ts +73 -0
- package/src/litellm/fake-litellm.ts +114 -0
- package/src/litellm/fake-stack.ts +100 -0
- package/src/litellm/generated/pass-through.ts +73 -0
- package/src/litellm/index.ts +42 -0
- package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +66 -0
- package/src/litellm/pass-through-endpoint-replace.test.ts +84 -0
- package/src/litellm/pass-through-endpoint.test.ts +208 -0
- package/src/litellm/pass-through-endpoint.ts +258 -0
- package/src/litellm/pass-through-form.test.ts +128 -0
- package/src/litellm/pass-through-form.ts +136 -0
- package/src/litellm/providers.ts +19 -0
- package/src/postgres/connection.ts +62 -0
- package/src/postgres/database-attrs.ts +87 -0
- package/src/postgres/database-bigint-serialization.test.ts +140 -0
- package/src/postgres/database-lifecycle.test.ts +113 -0
- package/src/postgres/database-provenance.test.ts +118 -0
- package/src/postgres/database-refusals.test.ts +135 -0
- package/src/postgres/database-sql.ts +174 -0
- package/src/postgres/database.ts +193 -0
- package/src/postgres/errors.ts +131 -0
- package/src/postgres/fake-sql.ts +123 -0
- package/src/postgres/fixtures/pg-REL_18_6-create-database-synopsis.txt +26 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_database.h.txt +67 -0
- package/src/postgres/index.ts +30 -0
- package/src/postgres/providers.ts +16 -0
- package/src/proxmox/zfs-pool.ts +10 -2
- package/src/release/archive.ts +10 -4
- package/src/release/binary-form.ts +24 -76
- package/src/release/binary-pins.ts +95 -0
- package/src/release/binary-root.test.ts +122 -0
- package/src/release/catalog.ts +10 -2
- package/src/release/tar-root.test.ts +114 -0
- package/src/release/tar.ts +78 -12
package/dist/release/tar.d.ts
CHANGED
|
@@ -6,8 +6,15 @@ export type TarContents = {
|
|
|
6
6
|
/** Every entry name, in archive order — so a refusal can say what the archive did hold. */
|
|
7
7
|
readonly names: readonly string[];
|
|
8
8
|
};
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* A push parser: `push` each chunk of the unzipped stream, then `finish`. Either may throw.
|
|
11
|
+
*
|
|
12
|
+
* @param root When given, exactly one typeflag-'5' entry named `<root>/`, size 0, is accepted and
|
|
13
|
+
* dropped rather than refused as a directory; every OTHER entry name must begin with the SEGMENT
|
|
14
|
+
* `<root>/` (a `<root>-evil/x` sibling does not) and is reported, matched and kept under that
|
|
15
|
+
* prefix stripped off. Omitted, behaviour is byte-identical to a reader with no root at all.
|
|
16
|
+
*/
|
|
17
|
+
export declare const tarReader: (wanted: ReadonlySet<string>, root?: string) => {
|
|
11
18
|
finish: () => TarContents;
|
|
12
19
|
push: (chunk: Uint8Array) => void;
|
|
13
20
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tar.d.ts","sourceRoot":"","sources":["../../src/release/tar.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tar.d.ts","sourceRoot":"","sources":["../../src/release/tar.ts"],"names":[],"mappings":"AAoGA,0CAA0C;AAC1C,eAAO,MAAM,WAAW,UAAW,SAAS,UAAU,EAAE,KAAG,UAQ1D,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,mGAAmG;IACnG,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClD,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,WAAY,WAAW,CAAC,MAAM,CAAC,SAAS,MAAM;kBA6H/C,WAAW;kBAlCT,UAAU,KAAG,IAAI;CA2CvC,CAAC"}
|
package/docs/litellm.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# LiteLLM — `@homeflare/alchemy/litellm`
|
|
2
|
+
|
|
3
|
+
`LiteLLM.PassThroughEndpoint` declares one row of LiteLLM's `/config/pass_through_endpoint`
|
|
4
|
+
family: a route on the proxy that forwards requests to an upstream target, generated from
|
|
5
|
+
LiteLLM **1.100.0**'s own OpenAPI document (`codegen/manifest.json`'s `litellm-openapi` entry —
|
|
6
|
+
vendor code at tag `v1.100.0`, dumped by the vendor's own `gen-api-types.mjs`, cross-checked
|
|
7
|
+
byte-identically against the tag's committed `schema.d.ts`; NOT a live read, because the reference
|
|
8
|
+
proxy's `/openapi.json` could not be reached when this was walked — see the manifest entry's note).
|
|
9
|
+
|
|
10
|
+
## What is DB-backed and API-managed, versus config-file only
|
|
11
|
+
|
|
12
|
+
Measured at 1.100.0 (`schema.prisma`, `pass_through_endpoints.py`, and the mini's own
|
|
13
|
+
`settings.yaml` `store_model_in_db: true`):
|
|
14
|
+
|
|
15
|
+
| Family | Managed by this resource? |
|
|
16
|
+
| ------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
|
|
17
|
+
| Pass-through endpoints | Yes — this package |
|
|
18
|
+
| Models, virtual keys, teams, credentials, access groups | DB-backed, API-managed, but no kit resource yet |
|
|
19
|
+
| `router_settings`, `litellm_settings`, `general_settings.master_key`/`database_url`/`store_model_in_db` | config-file only — read-only through the API |
|
|
20
|
+
| A `config.yaml` pass-through entry (`is_from_config: true`) | Read-only through this resource — see Refusals |
|
|
21
|
+
|
|
22
|
+
## Credentials
|
|
23
|
+
|
|
24
|
+
Reads `LITELLM_PROXY_URL` and `LITELLM_PROXY_API_KEY` at call time — LiteLLM's own variable
|
|
25
|
+
names, the ones its `litellm` CLI reads (`litellm/proxy/client/cli/main.py` at the tag). No
|
|
26
|
+
default URL. The key must be LiteLLM's master key or another `PROXY_ADMIN` key:
|
|
27
|
+
`update_config_general_settings`, the function every mutation goes through, is `PROXY_ADMIN`
|
|
28
|
+
only. Never a prop — see [docs/credentials.md](./credentials.md) for the house rule.
|
|
29
|
+
|
|
30
|
+
## Refusals
|
|
31
|
+
|
|
32
|
+
This resource refuses a plan rather than writing something it cannot safely undo:
|
|
33
|
+
|
|
34
|
+
- **A path already held by an `is_from_config` row.** A DB row on the same path overrides the
|
|
35
|
+
config file at runtime, so creating one would silently take over a route the operator declared
|
|
36
|
+
in `config.yaml`. Change the config file instead, or pick a different path.
|
|
37
|
+
- **A DB row LiteLLM did not give an `id`.** LiteLLM identifies some pass-through endpoints by
|
|
38
|
+
path alone, for backwards compatibility. Update and delete are both id-keyed, so this resource
|
|
39
|
+
cannot address a row like that — it is refused rather than silently adopted into a lifecycle
|
|
40
|
+
that cannot actually manage it.
|
|
41
|
+
- **A literal secret in a forwarded header.** `Authorization`, `x-api-key` and
|
|
42
|
+
`cf-aig-authorization` must contain LiteLLM's own `os.environ/NAME` reference form
|
|
43
|
+
(`set_env_variables_in_header`, `pass_through_endpoints.py` at the tag), never a literal value —
|
|
44
|
+
a house rule (not a vendor one), because a missing variable on the proxy host would otherwise
|
|
45
|
+
leave the literal string in the header with nothing in the API able to detect it.
|
|
46
|
+
|
|
47
|
+
A foreign DB row on the same path that LiteLLM DID give an id is `Unowned`: `read` finds it and
|
|
48
|
+
the plan needs `--adopt` before it is bound.
|
|
49
|
+
|
|
50
|
+
## Nullable fields: `timeout`, `methods`, `guardrails`
|
|
51
|
+
|
|
52
|
+
LiteLLM's update route merges the parsed body with `exclude_none` (measured at 1.100.0), so it
|
|
53
|
+
can never clear a field that is already set — sending neither `null` nor omitting it changes
|
|
54
|
+
anything already stored. Clearing one of these three fields is therefore planned as a
|
|
55
|
+
**replace**: delete the row, then create it again without the field. There is a brief window
|
|
56
|
+
with the route absent, and it exists because the alternative — create-before-delete — would put
|
|
57
|
+
two rows on the same path, which this resource never allows.
|
|
58
|
+
|
|
59
|
+
## The whole-field write, and why every mutation is serialised
|
|
60
|
+
|
|
61
|
+
Every pass-through endpoint lives in ONE `general_settings.pass_through_endpoints` list. Create,
|
|
62
|
+
update and delete are each a read-modify-write of that WHOLE list. `client.ts` wraps every
|
|
63
|
+
mutating call in a per-base-URL `Effect` semaphore, so two of THIS PROCESS's own calls never
|
|
64
|
+
race each other — two endpoints declared in the same deploy both survive.
|
|
65
|
+
|
|
66
|
+
⛔ **What the semaphore does not cover.** It serialises this package's own writes within one
|
|
67
|
+
process. It cannot serialise against the LiteLLM UI, another stack, or a second concurrent
|
|
68
|
+
`alchemy deploy` — the field has no ETag and no vendor-side locking. `reconcile` reads the row
|
|
69
|
+
back after every write rather than trusting the call that just returned, so a lost update is
|
|
70
|
+
visible on the NEXT plan (as drift) even though it cannot be prevented on this one.
|
|
71
|
+
|
|
72
|
+
## Example
|
|
73
|
+
|
|
74
|
+
Estate values — the account id, the gateway id, the upstream key's variable name — are never kit
|
|
75
|
+
constants; they are props your own stack supplies. Placeholders below, per the TypeSafe plugin's
|
|
76
|
+
`ai-gateway.md` reference (Cloudflare's `/accounts/{account_id}/ai/run`, a Workers-AI-scoped
|
|
77
|
+
Bearer token, `cf-aig-gateway-id` and `cf-aig-no-wholesale: true`):
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { LiteLLMPassThroughEndpoint, litellmProviders } from '@homeflare/alchemy/litellm';
|
|
81
|
+
|
|
82
|
+
export class AiGateway extends LiteLLMPassThroughEndpoint('AiGateway', {
|
|
83
|
+
path: '/cloudflare-ai',
|
|
84
|
+
target: 'https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/ai/run',
|
|
85
|
+
headers: {
|
|
86
|
+
Authorization: 'os.environ/CF_AI_GATEWAY_TOKEN',
|
|
87
|
+
'cf-aig-gateway-id': '<GATEWAY_ID>',
|
|
88
|
+
'cf-aig-no-wholesale': 'true',
|
|
89
|
+
},
|
|
90
|
+
timeout: 60,
|
|
91
|
+
}) {}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// …then provide `litellmProviders()` alongside the stack's other providers.
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Not covered
|
|
99
|
+
|
|
100
|
+
- **Models, virtual keys, teams, credentials, access groups.** DB-backed and API-managed at
|
|
101
|
+
1.100.0, but no kit resource yet — deferred; see the unit's own trigger notes.
|
|
102
|
+
- **The Claude OAuth model/key/team slice.** Needs estate answers only Tim can give.
|
|
103
|
+
- **Runtime behaviour of a route forced into the schema for the dashboard.** The byte-identical
|
|
104
|
+
cross-check proves the REQUEST/RESPONSE BODY SHAPES match the tag exactly. It does not prove
|
|
105
|
+
every route the dashboard's generator forces into the schema (`include_in_schema = True`) is
|
|
106
|
+
actually served the same way in production, or that the mini's own `config.yaml`/
|
|
107
|
+
`general_settings` match what `schema.prisma`/`proxy_server.py` assume.
|
|
108
|
+
- **More than one uvicorn worker.** Pass-through routes register themselves on the worker that
|
|
109
|
+
handled the registering request (`add_*_route` on `request.app`); with more than one worker,
|
|
110
|
+
the others' routes are unmeasured by this package.
|
package/docs/peers.md
CHANGED
|
@@ -7,7 +7,7 @@ The install line for `@homeflare/alchemy`, and why every peer on it and every en
|
|
|
7
7
|
```sh
|
|
8
8
|
bun add @homeflare/alchemy alchemy@2.0.0-beta.79 effect@4.0.0-rc.115 \
|
|
9
9
|
@effect/platform-node@4.0.0-rc.115 cloudflare@4.5.0 mime@4.1.0 \
|
|
10
|
-
@distilled.cloud/cloudflare@1.0.0-rc.12
|
|
10
|
+
@distilled.cloud/cloudflare@1.0.0-rc.12 @effect/sql-pg@4.0.0-rc.115
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
🔴 **Why, measured 2026-09-16 on 0.1.0 and re-checked 2026-09-17 against Alchemy 78.**
|
|
@@ -37,3 +37,10 @@ that followed the 78 README does not drop a required line.
|
|
|
37
37
|
subpath would degrade without it — measured 2026-09-16, the subpath does not load at all:
|
|
38
38
|
`Cannot find package 'cloudflare'`. An optional peer should mean a feature is absent, not
|
|
39
39
|
that an import fails.
|
|
40
|
+
|
|
41
|
+
⚠️ **`@effect/sql-pg` (added for `/postgres`, 2026-09-23) is required too, for the same
|
|
42
|
+
reason, not because every subpath imports it.** This package has one flat peer set for the
|
|
43
|
+
whole install, not one per subpath — `peers.test.ts`'s "no peer is marked optional" test
|
|
44
|
+
enforces it. `@effect/sql-pg` is optional on `alchemy`'s own manifest (its module graph
|
|
45
|
+
does not reach `SQL/Postgres` unless a stack imports it); here it stays a plain peer like
|
|
46
|
+
every other one.
|
package/docs/postgres.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# PostgreSQL — `@homeflare/alchemy/postgres`
|
|
2
|
+
|
|
3
|
+
`Postgres.Database`: create-and-assert over a self-hosted cluster, walked against PostgreSQL
|
|
4
|
+
18.6 (`REL_18_6`, commit `724edf9b`). It retires `org.nixos.postgres-reconcile`, a 38-line bash
|
|
5
|
+
loop over 19 databases run against the trust socket.
|
|
6
|
+
|
|
7
|
+
## Why the kit builds this instead of using upstream
|
|
8
|
+
|
|
9
|
+
Upstream `alchemy@2.0.0-beta.79` has no resource for a database in a self-hosted cluster —
|
|
10
|
+
measured against `v2.0.0-beta.79`'s full tree: it ships vendor-API Postgres resources
|
|
11
|
+
(`Planetscale.Postgres.*`, `Neon.PostgresOrigin`, `Prisma.Postgres`, `Fly.Postgres`,
|
|
12
|
+
`Railway.Postgres`) and the runtime binding `alchemy/SQL/Postgres` over `@effect/sql-pg`, but
|
|
13
|
+
nothing that issues `CREATE DATABASE` against a cluster you run yourself. No
|
|
14
|
+
`@distilled.cloud/postgres` or `@distilled.cloud/postgresql` package exists either (registry
|
|
15
|
+
404, checked 2026-09-23). This family builds on the same `@effect/sql-pg` `PgClient` upstream's
|
|
16
|
+
own binding uses, so it stays host-independent and portable — CT100's own Postgres (vault
|
|
17
|
+
decision 21) can use it later, and it is shaped to upstream.
|
|
18
|
+
|
|
19
|
+
## The locked shape
|
|
20
|
+
|
|
21
|
+
Create-and-assert only. `delete` always refuses. There is no `ALTER OWNER` and no `ALTER
|
|
22
|
+
DATABASE` of any kind. There is no password prop — a database carries none in `pg_database`.
|
|
23
|
+
|
|
24
|
+
## Measured path (2026-09-23)
|
|
25
|
+
|
|
26
|
+
Read `pg_hba_file_rules` and `pg_settings` over the trust socket: `listen_addresses` is
|
|
27
|
+
`10.100.1.1`, `ssl` is `on` (min `TLSv1.2`), and the `hostssl` rules cover only `netbox`,
|
|
28
|
+
`discord`, `litellm`, `teslamate` and one `homeflare-*` app group — **no TCP rule reaches the
|
|
29
|
+
`postgres` maintenance database.** Line 1 of `pg_hba_file_rules` is `local all all trust`. So
|
|
30
|
+
today only a stack that deploys on the mini, over the Unix socket at
|
|
31
|
+
`/opt/homeflare/postgres/sockets` (mode `0755`, owner `tim`), can create a database.
|
|
32
|
+
|
|
33
|
+
**A JS client reaches that socket.** Measured live with `@effect/sql-pg@4.0.0-rc.115` under
|
|
34
|
+
Bun, from scratch space, never committed: `PgClient.layer({ host:
|
|
35
|
+
'/opt/homeflare/postgres/sockets', port: 5432, database: 'postgres', username: 'tim' })`
|
|
36
|
+
connects and `SELECT version()` answers `PostgreSQL 18.6 on aarch64-apple-darwin25.6.0…`. The
|
|
37
|
+
catalog SELECT this family issues answered all 24 live non-template databases. `SHOW
|
|
38
|
+
standard_conforming_strings` is `on` — the assumption `quoteStringLiteral` (`database-sql.ts`)
|
|
39
|
+
rests on.
|
|
40
|
+
|
|
41
|
+
The reconciler being retired, `/nix/store/…-postgres-reconcile.sh`, runs `psql -h
|
|
42
|
+
/opt/homeflare/postgres/sockets -U tim -d postgres`; for each of 19 databases it runs `CREATE
|
|
43
|
+
DATABASE "$db" OWNER tim` when missing and asserts `pg_get_userbyid(datdba) = tim`. It never
|
|
44
|
+
drops anything.
|
|
45
|
+
|
|
46
|
+
## `name`
|
|
47
|
+
|
|
48
|
+
At most 63 UTF-8 bytes (`NAMEDATALEN` 64, `pg_config_manual.h` line 29), refused at plan.
|
|
49
|
+
Measured at `scansup.c`'s `truncate_identifier`: past that length the server clips the name with
|
|
50
|
+
`pg_mbcliplen` (byte-oriented, multibyte-aware) and raises only a `NOTICE`
|
|
51
|
+
(`ERRCODE_NAME_TOO_LONG`) — never an error. A create would silently succeed against the
|
|
52
|
+
truncated name, and the next plan's `WHERE datname = $1` (the name exactly as declared) would
|
|
53
|
+
find nothing and try to create it again, forever. Refusing at plan is the only way this stays a
|
|
54
|
+
one-time failure instead of an infinite loop.
|
|
55
|
+
|
|
56
|
+
## Mapping (`database-provenance.test.ts` proves this against the committed fixtures)
|
|
57
|
+
|
|
58
|
+
| prop | `CREATE DATABASE` option | `pg_database` column |
|
|
59
|
+
| ------------------ | ------------------------ | --------------------------------------------------- |
|
|
60
|
+
| `owner` | `OWNER` | `datdba` (via `pg_get_userbyid`) |
|
|
61
|
+
| `encoding` | `ENCODING` | `encoding` |
|
|
62
|
+
| `localeProvider` | `LOCALE_PROVIDER` | `datlocprovider` |
|
|
63
|
+
| `lcCollate` | `LC_COLLATE` | `datcollate` |
|
|
64
|
+
| `lcCtype` | `LC_CTYPE` | `datctype` |
|
|
65
|
+
| `allowConnections` | `ALLOW_CONNECTIONS` | `datallowconn` |
|
|
66
|
+
| `connectionLimit` | `CONNECTION LIMIT` | `datconnlimit` |
|
|
67
|
+
| `isTemplate` | `IS_TEMPLATE` | `datistemplate` |
|
|
68
|
+
| `tablespace` | `TABLESPACE` | `dattablespace` (joined to `pg_tablespace.spcname`) |
|
|
69
|
+
|
|
70
|
+
`datlocprovider` is Postgres's internal 1-byte `"char"` type. Measured live: `@effect/sql-pg`
|
|
71
|
+
decodes it as raw bytes, not text, so the read query maps it with a `CASE` in SQL to the same
|
|
72
|
+
three words `pg_collation.h`'s `collprovider_name` returns for the same codes (`b`→`builtin`,
|
|
73
|
+
`i`→`icu`, `c`→`libc`) before it ever crosses the wire as JSON.
|
|
74
|
+
|
|
75
|
+
### Excluded, and why
|
|
76
|
+
|
|
77
|
+
| option | reason |
|
|
78
|
+
| ------------------- | ---------------------------------------------------------------------------- |
|
|
79
|
+
| `TEMPLATE` | consumed only at create time to copy from; no column records it |
|
|
80
|
+
| `STRATEGY` | consumed only at create time (WAL_LOG/FILE_COPY); not stored |
|
|
81
|
+
| `LOCALE` | a shorthand for `LC_COLLATE`/`LC_CTYPE`; this family declares those directly |
|
|
82
|
+
| `BUILTIN_LOCALE` | folds into `datcollate`/`datctype` like `LOCALE`, no column of its own |
|
|
83
|
+
| `ICU_LOCALE` | `datlocale` exists, but deferred — all 24 live databases are libc |
|
|
84
|
+
| `ICU_RULES` | stored in `daticurules`, no ICU-provider consumer yet |
|
|
85
|
+
| `COLLATION_VERSION` | for `pg_upgrade`; normally omitted so Postgres computes it |
|
|
86
|
+
| `OID` | for `pg_upgrade`'s internal use only; not something a stack should assert |
|
|
87
|
+
|
|
88
|
+
## Identifier quoting: why this file does not reuse `alchemy`'s `sql(value)`
|
|
89
|
+
|
|
90
|
+
`CREATE DATABASE` takes no bind parameters for any value, not even `OWNER` — measured at
|
|
91
|
+
`gram.y`: every `createdb_opt_item` value is `NumericOnly`, `opt_boolean_or_string` or the
|
|
92
|
+
keyword `DEFAULT`, none of which reaches `PARAM` (`$n`). Every value in this statement is
|
|
93
|
+
escaped by hand and run through `.unsafe()`.
|
|
94
|
+
|
|
95
|
+
The database `name` is a `ColId` (`gram.y` line 17327) — one token. Effect's own
|
|
96
|
+
`Statement.defaultEscape('"')`, what `sql(value)` compiles to, ALSO rewrites every `.` in a
|
|
97
|
+
value to `"."`, because it is built for a schema-qualified name like `schema.table`. A database
|
|
98
|
+
named `"my.app"` would come out `"my"."app"` — two tokens where the grammar parses one, a
|
|
99
|
+
syntax error this provider would cause. `quoteIdent` (`database-sql.ts`) is the single-token
|
|
100
|
+
form instead: quote, and double an embedded `"`. Nothing else.
|
|
101
|
+
|
|
102
|
+
Every other `WITH` value (`OWNER`, `TABLESPACE`, `ENCODING`, `LOCALE_PROVIDER`, `LC_COLLATE`,
|
|
103
|
+
`LC_CTYPE`) is a string literal in the grammar, not an identifier: `createdb_opt_item`'s
|
|
104
|
+
string branch is `opt_boolean_or_string → NonReservedWord_or_Sconst → NonReservedWord |
|
|
105
|
+
Sconst`. `quoteStringLiteral` wraps in `'` and doubles an embedded `'`; it never emits a
|
|
106
|
+
backslash, so it does not depend on which way `standard_conforming_strings` points (measured
|
|
107
|
+
`on` above, the PG18 default since 9.1).
|
|
108
|
+
|
|
109
|
+
## House differences from S11/S14
|
|
110
|
+
|
|
111
|
+
`delete` refuses unconditionally (`PostgresDatabaseDropRefused`) and `defaultRemovalPolicy` is
|
|
112
|
+
`retain` — two independent reasons `DROP DATABASE` never runs through this provider. Most
|
|
113
|
+
databases this family will manage are adopted, not created from nothing; a destroy on data is
|
|
114
|
+
Tim's manual act over the socket, never an engine call.
|
|
115
|
+
|
|
116
|
+
## Ownership
|
|
117
|
+
|
|
118
|
+
A database carries no ownership mark in `pg_database`. Per H1 (the house's Snippet-precedent
|
|
119
|
+
rule for marker-less APIs), `read` always answers `Unowned` for a match — every one of the
|
|
120
|
+
mini's 19 already-live databases needs `adopt(true)` in the consuming stack.
|
|
121
|
+
|
|
122
|
+
## Unmeasured
|
|
123
|
+
|
|
124
|
+
Whether `ALTER DATABASE … REFRESH COLLATION VERSION` should ever be offered as an explicit,
|
|
125
|
+
separate operation (not part of `reconcile`) is open — nothing in this family calls it.
|
|
@@ -112,10 +112,46 @@ alertmanager, blackbox_exporter, node_exporter and postgres_exporter
|
|
|
112
112
|
`darwin_arm64` to `darwin_all`), pyroscope and vector. `bao` (OpenBao) is
|
|
113
113
|
walked — see `OPENBAO_RELEASES` above.
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
### The Prometheus family's shape, MEASURED 2026-09-23
|
|
116
|
+
|
|
117
|
+
★ **Now measured, not reasoned.** All four Prometheus-family darwin-arm64
|
|
118
|
+
archives wrap every entry in one directory, so `tar.ts` refused each one
|
|
119
|
+
whole — before this unit, on `entry "<root>/" is a directory`. Walking them
|
|
120
|
+
down needed a reader change, not only a data set: `tarReader(wanted, root)`
|
|
121
|
+
now strips exactly one DECLARED leading directory, pinned as `archive.root`
|
|
122
|
+
(gap 11 below, and tar.ts's own header comment, carry the full detail).
|
|
123
|
+
|
|
124
|
+
Read at `2026-09-23T14:33Z` with `gh api repos/<owner>/<repo>/releases/tags/<tag>`,
|
|
125
|
+
then downloaded to a scratch directory (not executed) and re-hashed:
|
|
126
|
+
|
|
127
|
+
| package (darwin-arm64) | asset | size (B) | `digest` matches | wrapper directory (root) |
|
|
128
|
+
| ------------------------------------------------ | ---------------------------------------------- | ---------- | ----------------- | --------------------------------------- |
|
|
129
|
+
| `prometheus/alertmanager` v0.33.1 | `alertmanager-0.33.1.darwin-arm64.tar.gz` | 37,247,168 | yes (`a94fbe54…`) | `alertmanager-0.33.1.darwin-arm64` |
|
|
130
|
+
| `prometheus/blackbox_exporter` v0.28.0 | `blackbox_exporter-0.28.0.darwin-arm64.tar.gz` | 15,705,022 | yes (`ec6c70cc…`) | `blackbox_exporter-0.28.0.darwin-arm64` |
|
|
131
|
+
| `prometheus/node_exporter` v1.12.1 | `node_exporter-1.12.1.darwin-arm64.tar.gz` | 5,368,643 | yes (`35b246a3…`) | `node_exporter-1.12.1.darwin-arm64` |
|
|
132
|
+
| `prometheus-community/postgres_exporter` v0.20.1 | `postgres_exporter-0.20.1.darwin-arm64.tar.gz` | 10,072,235 | yes (`4dd2b9e7…`) | `postgres_exporter-0.20.1.darwin-arm64` |
|
|
133
|
+
|
|
134
|
+
`tar -tvzf` on each downloaded archive (nothing extracted) showed: entry 1 is
|
|
135
|
+
the wrapper (typeflag `5`, size 0, `runner`/`circleci` owned), then only
|
|
136
|
+
root-level regular files under it — the exporter binary, `LICENSE`, `NOTICE`,
|
|
137
|
+
and for alertmanager also `amtool` and `alertmanager.yml`. No PAX (`x`/`g`) or
|
|
138
|
+
GNU long-name (`L`/`K`) entries in any of the four. Re-run afterward with the
|
|
139
|
+
worktree's OWN `tarReader(wanted, root)` over the same downloaded bytes: every
|
|
140
|
+
archive now parses to completion and returns the named member at its full
|
|
141
|
+
pinned size (e.g. `alertmanager` → 47,058,626 bytes) — see
|
|
142
|
+
`tar-root.test.ts` and `binary-root.test.ts` for the same proof as committed
|
|
143
|
+
fixtures.
|
|
144
|
+
|
|
145
|
+
⛔ **This unit adds the reader capability and its tests, not a new catalog
|
|
146
|
+
entry.** No `PinnedArchive` for alertmanager, blackbox_exporter,
|
|
147
|
+
node_exporter or postgres_exporter exists here yet — that is a follow-up data
|
|
148
|
+
module (its own walk-down of the checksum format: `sha256sums.txt`, archives
|
|
149
|
+
only, so member digests would be `computed` like OpenBao's) and its own PR.
|
|
150
|
+
|
|
151
|
+
⚠️ **`vector` is NOT covered by this measurement or this reader change.**
|
|
152
|
+
Vector's queue entry (above) has its own archive shape, unmeasured here; do
|
|
153
|
+
not assume it also wraps in a single directory without walking it down
|
|
154
|
+
separately.
|
|
119
155
|
|
|
120
156
|
## What this resource is not for
|
|
121
157
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Release binaries — every refusal
|
|
2
2
|
|
|
3
3
|
Status: active
|
|
4
|
-
Verified: 2026-09-
|
|
4
|
+
Verified: 2026-09-23
|
|
5
5
|
|
|
6
6
|
Everything `Release.Binary` refuses, by where it is refused. The guide is
|
|
7
7
|
[release-binary.md](./release-binary.md). ★ One page per subject: the guide says
|
|
@@ -16,13 +16,15 @@ At plan (the probe and `diff`), before anything is touched: a pin that is
|
|
|
16
16
|
missing or not a plain value (an Output pin would be a fetch; a first deploy
|
|
17
17
|
is never diffed, so reconcile refuses it there, before any host call, from the
|
|
18
18
|
props as declared); a repo that is not `owner/name`, or whose name is `.` or
|
|
19
|
-
`..`; a tag, asset or
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
`..`; a tag, asset, name or **root** that is not one safe path segment (`root`
|
|
20
|
+
added 2026-09-23: refuses `''`, `'.'`, `'..'` and anything with a `/`, same as
|
|
21
|
+
a tag or asset); an asset that is not a `.tar.gz` or `.tgz` (a glob is not a
|
|
22
|
+
name); a size that is not an integer from 1 byte to 1 GiB (it is allocated up
|
|
23
|
+
front); a digest that is not 64 lower-case hex; a member that is absolute or
|
|
24
|
+
climbs. Also a relative or unnormalised directory; a mode that is setuid,
|
|
25
|
+
setgid, sticky, group- or world-writable, or not owner-readable and
|
|
26
|
+
owner-executable; an owner or group that is not a valid name or id; a new pin
|
|
27
|
+
at the same path.
|
|
26
28
|
|
|
27
29
|
⚠️ **Not on a first deploy with `directory: dir.path`.** Alchemy neither
|
|
28
30
|
probes nor diffs a create whose props hold an Output, so there every check
|
|
@@ -51,6 +53,18 @@ header checksum, uses base-256, is neither ustar nor GNU, a lone zero block,
|
|
|
51
53
|
data after the end, a truncated archive; the declared member missing; the
|
|
52
54
|
member's SHA-256 not its pin. Every one of these ends `Nothing was written.`
|
|
53
55
|
|
|
56
|
+
**With `archive.root` declared** (tar.ts, 2026-09-23 — the Prometheus family's
|
|
57
|
+
own shape, walked down for this): a second entry named `<root>/`; any entry
|
|
58
|
+
whose first path segment is not exactly `root` — `<root>-evil/x` is a
|
|
59
|
+
different segment, not a prefix match, and is refused the same as a plain
|
|
60
|
+
top-level file beside the wrapper; the `<root>/` entry itself being anything
|
|
61
|
+
but an empty (size-0) directory, including a hard link or symlink at that
|
|
62
|
+
exact name; and the declared root never appearing in the archive at all.
|
|
63
|
+
Every other refusal above — PAX, GNU long-name, `..`, links, devices, a
|
|
64
|
+
duplicate name — still applies to entries _inside_ the root, unchanged.
|
|
65
|
+
**Without `root`,** a directory entry (the wrapper included) is refused
|
|
66
|
+
exactly as it always was: byte-identical to a reader with no root support.
|
|
67
|
+
|
|
54
68
|
After the write: a file that does not read back as declared (a create is
|
|
55
69
|
removed). Errors are tagged (`BinaryRefused`, `DownloadFailed`,
|
|
56
70
|
`ChecksumMismatch`, `ArchiveRefused`) and carry the house refusal sentence.
|
|
@@ -116,21 +116,39 @@ that upstream wants it.
|
|
|
116
116
|
fails with `BinaryRefused`, and the provider would fail with the tag union
|
|
117
117
|
instead of `lift()`'s `Error`. `error-channel.test.ts` fails the moment
|
|
118
118
|
either changes, so this page moves with it.
|
|
119
|
-
11. **The tar reader accepts
|
|
120
|
-
`extractMembers`: macOS's default
|
|
121
|
-
|
|
122
|
-
`com.apple.provenance` xattr), so even a
|
|
123
|
-
`--no-mac-metadata` still writes them;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
119
|
+
11. **The tar reader accepts two measured layouts: flat, and one declared
|
|
120
|
+
root.** Measured 2026-09-22 against `extractMembers`: macOS's default
|
|
121
|
+
`tar czf` (bsdtar 3.5.3) writes a PAX `x` header before every entry (a
|
|
122
|
+
sub-second `mtime` and the `com.apple.provenance` xattr), so even a
|
|
123
|
+
one-file archive is refused whole. `--no-mac-metadata` still writes them;
|
|
124
|
+
`--format ustar` passes. Go's `archive/tar` writing root-level files
|
|
125
|
+
passes. The house accepts this on purpose: the Victoria archives are flat,
|
|
126
|
+
and homeflare-builds writes plain ustar.
|
|
127
|
+
★ **MEASURED 2026-09-23, no longer reasoned**: all four Prometheus-family
|
|
128
|
+
darwin-arm64 archives (`alertmanager-0.33.1.darwin-arm64.tar.gz` — the
|
|
129
|
+
estate pins **0.33.1**, not the 0.34.1 this page previously cited —
|
|
130
|
+
`blackbox_exporter-0.28.0`, `node_exporter-1.12.1`,
|
|
131
|
+
`postgres_exporter-0.20.1`) wrap every entry in exactly one directory
|
|
132
|
+
(typeflag `5`, size 0, first) and carry no PAX or GNU long-name entries
|
|
133
|
+
(release-binary-catalogs.md has the full table and commands). The house
|
|
134
|
+
chose the **narrow fix**, not the generic one below: `tarReader(wanted,
|
|
135
|
+
root)` accepts exactly one entry named `<root>/`, requires every other
|
|
136
|
+
name to begin with that exact segment, and strips the prefix before a name
|
|
137
|
+
is matched, listed or checked for a duplicate — refusing a second
|
|
138
|
+
directory entry, a `<root>-evil/x` sibling (a segment match, not a string
|
|
139
|
+
prefix), anything outside the root, and a `<root>/` that is not an empty
|
|
140
|
+
directory. Without `root` the refusal is unchanged: a directory entry, the
|
|
141
|
+
wrapper included, is still refused whole.
|
|
142
|
+
⚠️ **The generic replacement below is still the more upstreamable shape**
|
|
143
|
+
and was NOT what the house built here: skip directory entries, and links
|
|
144
|
+
that are not the declared member, because nothing is ever written from an
|
|
145
|
+
entry name; read PAX records and refuse only a `path`, `linkpath` or
|
|
146
|
+
`size` record. The member digest still guards the bytes either way. The
|
|
147
|
+
house's narrower `root` prop is easier to review (a vendor's wrapper name
|
|
148
|
+
is pinned, not inferred) but does not reach an archive with no declared
|
|
149
|
+
wrapper, or more than one PAX record kind, the way the generic reader
|
|
150
|
+
would; a PR to upstream would need to pick one, and a house-only
|
|
151
|
+
consumption doesn't have to.
|
|
134
152
|
12. **One source, one tag alphabet, other prop names.** `releaseUrl` fixes
|
|
135
153
|
`https://github.com`. Upstream's GitHub family also reaches GitHub
|
|
136
154
|
Enterprise (`normalizeGitHubBaseUrl`, `GitHub/BaseUrl.ts`). A tag must be
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homeflare/alchemy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Custom Alchemy providers for Cloudflare, Proxmox, OpenBao, Forgejo, Talos, launchd and Caddy, and a verifier that proves adoptions are no-ops.",
|
|
5
5
|
"homepage": "https://github.com/taslabs-net/homeflare-kit/tree/main/packages/alchemy#readme",
|
|
6
6
|
"bugs": "https://github.com/taslabs-net/homeflare-kit/issues",
|
|
@@ -60,6 +60,10 @@
|
|
|
60
60
|
"types": "./dist/netbox/index.d.ts",
|
|
61
61
|
"default": "./dist/netbox/index.js"
|
|
62
62
|
},
|
|
63
|
+
"./postgres": {
|
|
64
|
+
"types": "./dist/postgres/index.d.ts",
|
|
65
|
+
"default": "./dist/postgres/index.js"
|
|
66
|
+
},
|
|
63
67
|
"./linux": {
|
|
64
68
|
"types": "./dist/linux/index.d.ts",
|
|
65
69
|
"default": "./dist/linux/index.js"
|
|
@@ -72,6 +76,10 @@
|
|
|
72
76
|
"types": "./dist/caddy/index.d.ts",
|
|
73
77
|
"default": "./dist/caddy/index.js"
|
|
74
78
|
},
|
|
79
|
+
"./litellm": {
|
|
80
|
+
"types": "./dist/litellm/index.d.ts",
|
|
81
|
+
"default": "./dist/litellm/index.js"
|
|
82
|
+
},
|
|
75
83
|
"./verify": {
|
|
76
84
|
"types": "./dist/verify/index.d.ts",
|
|
77
85
|
"default": "./dist/verify/index.js"
|
|
@@ -84,6 +92,7 @@
|
|
|
84
92
|
"peerDependencies": {
|
|
85
93
|
"@distilled.cloud/cloudflare": "1.0.0-rc.12",
|
|
86
94
|
"@effect/platform-node": "4.0.0-rc.115",
|
|
95
|
+
"@effect/sql-pg": "4.0.0-rc.115",
|
|
87
96
|
"alchemy": "2.0.0-beta.79",
|
|
88
97
|
"cloudflare": ">=4.5.0",
|
|
89
98
|
"effect": "4.0.0-rc.115",
|