diff --git a/SHARED.md b/SHARED.md index a75f139..d823a70 100644 --- a/SHARED.md +++ b/SHARED.md @@ -22,10 +22,11 @@ the inventory underneath it is **generated**, and the number it produces is a | Package | Install | Replaces | |---|---|---| -| [`ai-forms`](https://github.com/catomean/ai-forms) | `npm i github:catomean/ai-forms#v0.1.0` | per-app "fill this form from prose" + conversational refinement. Headless — ships **no markup**, so each app keeps its own styling. | -| [`ai-kit`](https://github.com/catomean/ai-kit) | `npm i github:catomean/ai-kit#v0.4.0` | **the AI layer, in one install** — which model to call, whether the vendor still lists it, the three kinds of 429, fair-share of a free tier, and (re-exported) `ai-forms`. Renamed from `ai-ration` 2026-08-26: the name described one of five modules, and the package had one adopter while five repos that skipped it went down together to a retired model id. | -| [`threadkit`](https://github.com/catomean/threadkit) | `npm i threadkit` | multi-participant message threads where *permission is participation*, not a role or an ownership column. Headless pure functions, so "who may read this" is unit-testable instead of buried in a `WHERE` clause. AI participants obey the same visibility rules. **ESM-only.** | -| [`limitkit`](https://github.com/catomean/limitkit) | `npm i github:catomean/limitkit#v0.1.0` | the fleet's **12 hand-rolled rate limiters** (this file's own "next extraction" row). Sliding/fixed windows over an injectable two-method `Store`; **bounded** memory default (the unbounded-Map leak is impossible by construction); standard `X-RateLimit-*` + `Retry-After` headers — what orangecat's ADR-0002 specified seven months before anything enforced it; `clientIp()`. Refusals count nothing, so a hammered key recovers. Ships no middleware and **no limit values** — how many attempts a route allows is app semantics, asserted locally. | +| [`ai-forms`](https://github.com/bitbaum/ai-forms) | `npm i github:bitbaum/ai-forms#v0.1.0` | per-app "fill this form from prose" + conversational refinement. Headless — ships **no markup**, so each app keeps its own styling. | +| [`ai-kit`](https://github.com/bitbaum/ai-kit) | `npm i github:bitbaum/ai-kit#v0.4.0` | **the AI layer, in one install** — which model to call, whether the vendor still lists it, the three kinds of 429, fair-share of a free tier, and (re-exported) `ai-forms`. Renamed from `ai-ration` 2026-08-26: the name described one of five modules, and the package had one adopter while five repos that skipped it went down together to a retired model id. | +| [`threadkit`](https://github.com/bitbaum/threadkit) | `npm i threadkit` | multi-participant message threads where *permission is participation*, not a role or an ownership column. Headless pure functions, so "who may read this" is unit-testable instead of buried in a `WHERE` clause. AI participants obey the same visibility rules. **ESM-only.** | +| [`sitekit`](https://github.com/bitbaum/sitekit) | `npm i github:bitbaum/sitekit#v0.1.1` | **a website as data** — the closed section union (Zod as SSOT, path-addressed errors a generator can act on), one set of React renderers emitting semantic classes only (tokens stay per-site: uniform system, divergent aesthetics), and per-field provenance (`scraped`/`operator`/`inferred`/`not-found`) so `assertDeliverable()` makes "we fabricate no facts" a check instead of a promise. Extracted from substrata per orangecat ADR-0003; the planned `siteFromUrl()` extractor targets this schema. RSC-native — the Link seam stays a server-component prop (learned in v0.1.1 when the first consumer's build refused a component function crossing 'use client'). **ESM-only.** | +| [`limitkit`](https://github.com/bitbaum/limitkit) | `npm i github:bitbaum/limitkit#v0.1.0` | the fleet's **12 hand-rolled rate limiters** (this file's own "next extraction" row). Sliding/fixed windows over an injectable two-method `Store`; **bounded** memory default (the unbounded-Map leak is impossible by construction); standard `X-RateLimit-*` + `Retry-After` headers — what orangecat's ADR-0002 specified seven months before anything enforced it; `clientIp()`. Refusals count nothing, so a hammered key recovers. Ships no middleware and **no limit values** — how many attempts a route allows is app semantics, asserted locally. | **Adopted:** `ai-forms` — fleetcrown, evig, aoz-housing, surf-your-life. `ai-kit` — fleetcrown, aoz-housing, truthseeker, botsmann, **and this repo** @@ -60,6 +61,12 @@ never by "these are all shared utilities". `threadkit` — **nobody yet**. Map). **Next adopter should be orangecat** — it closes ADR-0002 by making its Upstash client a 12-line `Store` adapter and deleting three of its four implementations. +`sitekit` — substrata (proving consumer, converted the day the package +shipped: −577 lines of local renderer, and its test now validates the whole +site against the shared schema, so a breaking schema change fails in the +consumer's CI first). **Next: camille-boulangerie, aslan-tattoo, wild-spirit** +— the three real sites that make the union's gaps (images, contact, menus) +visible on sites we own before the schema is pointed at strangers. **Not yet:** orangecat and kivvi still carry their own form-assist; kivvi, evig, botsmann still carry their own provider layers. @@ -74,7 +81,7 @@ CI: installed from the public registry into an empty project, imported, and got 17 named exports plus `dist/index.d.ts`. It exists because of the bug -[`single-tenant-prod-hides-unscoped-queries`](https://github.com/catomean/dotfiles) +[`single-tenant-prod-hides-unscoped-queries`](https://github.com/bitbaum/dotfiles) records: role-derived access is correct at one doctor / one tenant / one org and silently wrong at two. `canRead(thread, user)` cannot express that bug, because there is no role to check. @@ -113,11 +120,11 @@ Ranked by (copies × how identical the logic is). Counts from | Concern | Files | Why it is a good candidate | |---|---|---| | `auto-merge-sweep.sh` | ~~22~~ **6** | **EXTRACTED 2026-08-16/20.** Sixteen repos call the canonical as a reusable workflow, each verified to actually *run* it (a sweep that fails to start looks exactly like one with nothing to do). The six remaining are deliberate: dotfiles is the canonical home and runs it directly; ai-forms, datacat, petvity, solon had dirty working trees owned by other sessions when swept — convert when clear. The two repos that had ever *tested* their copies (evig, orangecat) had that coverage ported into the canonical suite **before** deletion: 17 cases, mutation-proven. | -| rate limiting | **14 → adopting** | **Extracted 2026-08-20 as [`limitkit`](https://github.com/catomean/limitkit)** (see registry above). fleetcrown converted as the proving consumer; 13 files remain across 8 repos, orangecat first in line (its ADR-0002 becomes a 12-line `Store` adapter + three deletions). The ratchet holds the count until each adoption lands. | +| rate limiting | **14 → adopting** | **Extracted 2026-08-20 as [`limitkit`](https://github.com/bitbaum/limitkit)** (see registry above). fleetcrown converted as the proving consumer; 13 files remain across 8 repos, orangecat first in line (its ADR-0002 becomes a 12-line `Store` adapter + three deletions). The ratchet holds the count until each adoption lands. | | AI provider client | **16** | evig 7, orangecat 5. `ai-kit` already owns the hard part (chain, 429, budget); these are the callers. **Priced 2026-08-26, re-priced 2026-08-27:** Groq retired the llama-3.x family and the damage was far wider than the first count. Seven repos were broken, not five — the audit could not see two of them — and inside a repo the id was written down **two to four times**. Kivvi took three PRs to remove one retired id: it lived in the provider registry, an app's inline fetch body, the fallback chain, and a client hook's `FALLBACK_MODEL`. Each pass only found the copies the tooling could see. That is the cost of duplication measured rather than argued. fleetcrown, which adopted the package, was unaffected throughout. | | logger | **10** | sbb-lost-found alone has 4. | | health route | **8** | Identical shape in 8 repos; a 20-line contract. | -| ~~`@ai-native-cms/core`~~ | — | **Withdrawn — measurement error.** `catomean/revampit` *redirects* to `catomean/evig` (renamed in the pivot); the "two repos" with byte-identical trees were two clones of ONE repo. Nothing to extract. Two directories are not two repos: check `git remote -v` before reporting cross-repo duplication. | +| ~~`@ai-native-cms/core`~~ | — | **Withdrawn — measurement error.** `bitbaum/revampit` *redirects* to `bitbaum/evig` (renamed in the pivot); the "two repos" with byte-identical trees were two clones of ONE repo. Nothing to extract. Two directories are not two repos: check `git remote -v` before reporting cross-repo duplication. | ## What must NOT be centralized