@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.
Files changed (95) hide show
  1. package/README.md +15 -1
  2. package/dist/litellm/client.d.ts +76 -0
  3. package/dist/litellm/client.d.ts.map +1 -0
  4. package/dist/litellm/credentials.d.ts +48 -0
  5. package/dist/litellm/credentials.d.ts.map +1 -0
  6. package/dist/litellm/fake-litellm.d.ts +36 -0
  7. package/dist/litellm/fake-litellm.d.ts.map +1 -0
  8. package/dist/litellm/fake-stack.d.ts +30 -0
  9. package/dist/litellm/fake-stack.d.ts.map +1 -0
  10. package/dist/litellm/generated/pass-through.d.ts +71 -0
  11. package/dist/litellm/generated/pass-through.d.ts.map +1 -0
  12. package/dist/litellm/index.d.ts +13 -0
  13. package/dist/litellm/index.d.ts.map +1 -0
  14. package/dist/litellm/index.js +270 -0
  15. package/dist/litellm/index.js.map +14 -0
  16. package/dist/litellm/pass-through-endpoint.d.ts +94 -0
  17. package/dist/litellm/pass-through-endpoint.d.ts.map +1 -0
  18. package/dist/litellm/pass-through-form.d.ts +40 -0
  19. package/dist/litellm/pass-through-form.d.ts.map +1 -0
  20. package/dist/litellm/providers.d.ts +16 -0
  21. package/dist/litellm/providers.d.ts.map +1 -0
  22. package/dist/postgres/connection.d.ts +50 -0
  23. package/dist/postgres/connection.d.ts.map +1 -0
  24. package/dist/postgres/database-attrs.d.ts +79 -0
  25. package/dist/postgres/database-attrs.d.ts.map +1 -0
  26. package/dist/postgres/database-sql.d.ts +73 -0
  27. package/dist/postgres/database-sql.d.ts.map +1 -0
  28. package/dist/postgres/database.d.ts +63 -0
  29. package/dist/postgres/database.d.ts.map +1 -0
  30. package/dist/postgres/errors.d.ts +86 -0
  31. package/dist/postgres/errors.d.ts.map +1 -0
  32. package/dist/postgres/fake-sql.d.ts +22 -0
  33. package/dist/postgres/fake-sql.d.ts.map +1 -0
  34. package/dist/postgres/index.d.ts +16 -0
  35. package/dist/postgres/index.d.ts.map +1 -0
  36. package/dist/postgres/index.js +238 -0
  37. package/dist/postgres/index.js.map +15 -0
  38. package/dist/postgres/providers.d.ts +14 -0
  39. package/dist/postgres/providers.d.ts.map +1 -0
  40. package/dist/proxmox/index.js.map +2 -2
  41. package/dist/proxmox/zfs-pool.d.ts +10 -2
  42. package/dist/proxmox/zfs-pool.d.ts.map +1 -1
  43. package/dist/release/archive.d.ts +6 -2
  44. package/dist/release/archive.d.ts.map +1 -1
  45. package/dist/release/binary-form.d.ts +14 -9
  46. package/dist/release/binary-form.d.ts.map +1 -1
  47. package/dist/release/binary-pins.d.ts +17 -0
  48. package/dist/release/binary-pins.d.ts.map +1 -0
  49. package/dist/release/catalog.d.ts.map +1 -1
  50. package/dist/release/index.js +90 -40
  51. package/dist/release/index.js.map +8 -7
  52. package/dist/release/tar.d.ts +9 -2
  53. package/dist/release/tar.d.ts.map +1 -1
  54. package/docs/litellm.md +110 -0
  55. package/docs/peers.md +8 -1
  56. package/docs/postgres.md +125 -0
  57. package/docs/release-binary-catalogs.md +40 -4
  58. package/docs/release-binary-refusals.md +22 -8
  59. package/docs/release-binary-upstream.md +33 -15
  60. package/package.json +10 -1
  61. package/src/litellm/client.ts +195 -0
  62. package/src/litellm/credentials.ts +73 -0
  63. package/src/litellm/fake-litellm.ts +114 -0
  64. package/src/litellm/fake-stack.ts +100 -0
  65. package/src/litellm/generated/pass-through.ts +73 -0
  66. package/src/litellm/index.ts +42 -0
  67. package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +66 -0
  68. package/src/litellm/pass-through-endpoint-replace.test.ts +84 -0
  69. package/src/litellm/pass-through-endpoint.test.ts +208 -0
  70. package/src/litellm/pass-through-endpoint.ts +258 -0
  71. package/src/litellm/pass-through-form.test.ts +128 -0
  72. package/src/litellm/pass-through-form.ts +136 -0
  73. package/src/litellm/providers.ts +19 -0
  74. package/src/postgres/connection.ts +62 -0
  75. package/src/postgres/database-attrs.ts +87 -0
  76. package/src/postgres/database-bigint-serialization.test.ts +140 -0
  77. package/src/postgres/database-lifecycle.test.ts +113 -0
  78. package/src/postgres/database-provenance.test.ts +118 -0
  79. package/src/postgres/database-refusals.test.ts +135 -0
  80. package/src/postgres/database-sql.ts +174 -0
  81. package/src/postgres/database.ts +193 -0
  82. package/src/postgres/errors.ts +131 -0
  83. package/src/postgres/fake-sql.ts +123 -0
  84. package/src/postgres/fixtures/pg-REL_18_6-create-database-synopsis.txt +26 -0
  85. package/src/postgres/fixtures/pg-REL_18_6-pg_database.h.txt +67 -0
  86. package/src/postgres/index.ts +30 -0
  87. package/src/postgres/providers.ts +16 -0
  88. package/src/proxmox/zfs-pool.ts +10 -2
  89. package/src/release/archive.ts +10 -4
  90. package/src/release/binary-form.ts +24 -76
  91. package/src/release/binary-pins.ts +95 -0
  92. package/src/release/binary-root.test.ts +122 -0
  93. package/src/release/catalog.ts +10 -2
  94. package/src/release/tar-root.test.ts +114 -0
  95. package/src/release/tar.ts +78 -12
@@ -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
- /** A push parser: `push` each chunk of the unzipped stream, then `finish`. Either may throw. */
10
- export declare const tarReader: (wanted: ReadonlySet<string>) => {
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":"AAqFA,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,gGAAgG;AAChG,eAAO,MAAM,SAAS,WAAY,WAAW,CAAC,MAAM,CAAC;kBAoFhC,WAAW;kBAlCT,UAAU,KAAG,IAAI;CAwCvC,CAAC"}
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"}
@@ -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.
@@ -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
- ⚠️ REASONED NOT MEASURED: the Prometheus-family archives wrap their files in a
116
- directory, and `tar.ts` refuses a directory entry (and any PAX header) for the
117
- whole archive. Walking those down includes a reader change, not only a data
118
- set ([release-binary-upstream.md](./release-binary-upstream.md), gap 11).
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-22
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 name that is not one safe path segment; an asset that is
20
- not a `.tar.gz` or `.tgz` (a glob is not a name); a size that is not an
21
- integer from 1 byte to 1 GiB (it is allocated up front); a digest that is not
22
- 64 lower-case hex; a member that is absolute or climbs. Also a relative or
23
- unnormalised directory; a mode that is setuid, setgid, sticky, group- or
24
- world-writable, or not owner-readable and owner-executable; an owner or group
25
- that is not a valid name or id; a new pin at the same path.
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 one measured layout.** Measured 2026-09-22 against
120
- `extractMembers`: macOS's default `tar czf` (bsdtar 3.5.3) writes a PAX `x`
121
- header before every entry (a sub-second `mtime` and the
122
- `com.apple.provenance` xattr), so even a one-file archive is refused whole.
123
- `--no-mac-metadata` still writes them; `--format ustar` passes. A wrapping
124
- directory entry is refused whole. Go's `archive/tar` writing root-level
125
- files passes. The house accepts this on purpose: the Victoria archives are
126
- flat, and homeflare-builds writes plain ustar. ⚠️ REASONED NOT MEASURED:
127
- the Prometheus family's archives (`alertmanager-0.34.1.darwin-arm64.tar.gz`)
128
- wrap their files in a directory, so the queue in
129
- release-binary-catalogs.md needs a reader change, not only a data set.
130
- Generic replacement: skip directory entries, and links that are not the
131
- declared member, because nothing is ever written from an entry name; read
132
- PAX records and refuse only a `path`, `linkpath` or `size` record. The
133
- member digest still guards the bytes.
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.23.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",