From ef8c62d56db37792100dace96943f06a2b2cb809 Mon Sep 17 00:00:00 2001 From: sebi Date: Thu, 20 Aug 2026 06:52:14 -0500 Subject: [PATCH 1/2] docs: 4.0.0 is on the registry, and twenty pages still said 3.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release published: 30/30 on npm at 4.0.0, every tarball attested, `_npmUser: GitHub Actions`. Verified against the registry rather than the workflow's exit code — `bun run scripts/registry-audit.ts --json`. The docs had not moved. This restamps them, and corrects four claims that were wrong rather than merely stale. Twelve rows in wiki/Known-Gaps.md said "fixed on `main`", which the page itself defines as "fixed in the repository and in NO published release — treat these rows as open and take the workaround". All twelve shipped in 4.0.0, so every one of those workarounds was advice to work around a bug that no longer exists in anything you can install. `[Unreleased]` is empty. wiki/Upgrading.md offered no path off 3.0.0 at all. It now carries a `3.0.0 → 4.0.0, entry by entry` section for all 25 `BREAKING —` entries, led by the three that change behaviour whether or not you edit anything: `on delete` now reaching the generated SQL (any app that ever declared a rule generates different DDL), `llm()`'s semantic cache scope defaulting to the calling actor instead of `'global'`, and `reapBranches()` no longer sweeping another app's databases. Home.md, FAQ.md, Getting-Started.md and Upgrading.md each hand-stamped a version, which _Footer.md forbids by name — it is the only page allowed to, because it renders under all 46 and one release should bump one line. They now name the command instead. That is the fix that stops this recurring; restamping them 4.0.0 would just queue the same commit for the next tag. Two claims became true when 4.0.0 shipped and are left alone: "on 3.0.0 and below, `x new` writes no chart" and the same for the `.dockerignore` `.env` patterns. Both now describe history correctly. Also recorded, because they cost time this release: - `git push --follow-tags` pushes ANNOTATED tags only. `v4.0.0` was first created with a bare `git tag`, the push said nothing, and the tag never left the machine — so the GitHub Release could not be cut against it, and no Release means no publish. PUBLISHING.md writes `git tag -a`. - The release workflow stops at `waiting` on the `npm-publish` environment until a named reviewer approves. That is not a failure, and it is the last point an irreversible publish can be stopped. - registry-audit filed #221 during the release window — correctly, while the publish sat behind that gate. Now closed, and the audit is green. Milestone 11: `x new` writes `docker/helm` as of 4.0.0, so every artifact the milestone names now exists. The proof — the demo app on Compose AND K8s from one image with an invisible rolling restart — is still open, and CLAUDE.md and docs/idea/14-roadmap.md now say which half is which. Gate: bun run verify — 14/18, 4 skipped (drift, contract-diff, budgets, seo; app-scoped, no app at the framework root). Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 102 ++++++++++++++++---------- PUBLISHING.md | 35 +++++---- README.md | 12 +-- docs/idea/14-roadmap.md | 2 +- docs/idea/README.md | 17 +++-- llms.txt | 4 +- wiki/Actions.md | 2 +- wiki/Admin-Dashboard.md | 2 +- wiki/Building-Your-Own-Base.md | 2 +- wiki/Configuration.md | 4 +- wiki/FAQ.md | 26 ++++--- wiki/Getting-Started.md | 2 +- wiki/Home.md | 10 +-- wiki/Jobs-And-Workflows.md | 8 +- wiki/Known-Gaps.md | 41 ++++++----- wiki/Migrations-And-Backfills.md | 2 +- wiki/Queries-And-Live-Queries.md | 4 +- wiki/Realtime.md | 2 +- wiki/Tutorial-04-Jobs-And-Realtime.md | 4 +- wiki/Tutorial-06-Growing-Up.md | 4 +- wiki/Upgrading.md | 72 +++++++++++++++--- 21 files changed, 221 insertions(+), 136 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e571ec35..2dcb613b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,55 +12,73 @@ This repo is the framework itself: a monorepo of `@ultimat3/*` packages, the `x` CLI binary: `x`. npm scope: `@ultimat3`. Import paths: `@ultimat3/`. -**Status:** 3.0.0, released, `As of 2026-08-19`. 29 `@ultimat3/*` packages plus the unscoped +**Status:** 4.0.0, released, `As of 2026-08-20`. 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` — 30 in all — **versioned** in lockstep and **published** in lockstep: one version, one commit, one tag, 30 tarballs. **Repository, tag and registry agree.** Never read a number here as the installable one; run the -command beside it — that is the only thing here that cannot go stale. +command beside it — that is the only thing here that cannot go stale. One command checks the whole +row at once: `bun run scripts/registry-audit.ts --json`, which answers `30/30 publishable packages +are on npm at 4.0.0, every one attested` or names each gap with a runnable `fix:`. -| Fact | State, `As of 2026-08-19` | Read it yourself | +| Fact | State, `As of 2026-08-20` | Read it yourself | |---|---|---| -| Repository version | 3.0.0, every workspace stamped | `bun run scripts/release.ts --check 3.0.0` | +| Repository version | 4.0.0, every workspace stamped | `bun run scripts/release.ts --check 4.0.0` | | Publishable workspaces | 30 | `bun run scripts/release-workflow.ts --json` | -| On the registry | **all 30 at 3.0.0**, no holes | `bun run scripts/release-workflow.ts --json # the derived list; check every name against npm view` | -| npm `latest` | **3.0.0** — `bunx create-ultimate myapp` installs it | `npm view @ultimat3/core version` | -| Provenance | every 3.0.0 tarball attested, `_npmUser: GitHub Actions` | `npm view @ultimat3/core@3.0.0 dist.attestations _npmUser` | -| Tag and Release | `v3.0.0` pushed, GitHub Release published — the Release is what triggers the workflow | `git tag --list 'v3.*'` | +| On the registry | **all 30 at 4.0.0**, no holes | `bun run scripts/registry-audit.ts --json` | +| npm `latest` | **4.0.0** — `bunx create-ultimate myapp` installs it | `npm view @ultimat3/core version` | +| Provenance | every 4.0.0 tarball attested, `_npmUser: GitHub Actions` | `npm view @ultimat3/core@4.0.0 dist.attestations _npmUser` | +| Tag and Release | `v4.0.0` pushed **annotated**, GitHub Release published — the Release is what triggers the workflow | `git tag --list 'v4.*'` | | OIDC trusted publisher | attached to all 30, with `Environment: npm-publish` | `NPM_CONFIG_OTP= bun run scripts/trust-publishers.ts --check --json` — without a fresh OTP every package reads as missing | -**`@ultimat3/scraping` is on the registry, and it was the last publication hole.** It was bootstrapped -by hand at 2.0.0 on 2026-08-19 — `npm publish --access public --provenance=false`, the one-time step -every package needs before a trusted publisher can attach — and npm now answers `E403 … cannot -publish over the previously published versions: 2.0.0` on a retry. `@ultimat3/flags` was the same -shape and was closed the same way at 2.0.0. Publication is a step apart from versioning; the publish -list is **derived** from `scripts/list-workspaces.ts`, which is what keeps a new package from being -silently absent from it. Step 1 of [`PUBLISHING.md`](PUBLISHING.md) comes due again for the next -package added after a release run, and nothing else. - -**3.0.0 is a major** because a five-agent bug sweep landed breaking changes to documented APIs: -`mfa.required` refused and narrowed to the literal `false`, `enrolTotp(auth, input)`, `appErrorStatus` -removed, `SocketRegistry.sweepIdle()` → `idle()`, `SyncSocket.lastSeenAt` → `lastSeenMonotonicMs`, -two `SQL_OUTBOX_*` constants gaining a parameter, `DESCRIPTION_MIN_LENGTH` deleted, and a metric -redeclared with different bounds now refused. The 3.0.0 section of [`CHANGELOG.md`](CHANGELOG.md) -carries **10** entries marked `BREAKING —` and ships no codemod, so each one is a manual edit its own -entry names. 2.0.0 was the **first** major and carried 33. - -**Every package has an OIDC trusted publisher, re-attached 2026-08-19 with the environment set.** -`developerz-ai` / `ultimate` / `release.yml` / environment `npm-publish`, publish permission, all 30, -verified per package with `npx -y npm@12 trust list --json` — `npm trust` shipped in **npm 12** -and Bun's bundled npm answers it as an unknown command, which is why `scripts/trust-publishers.ts` -pins the runner. That attachment is what let [`.github/workflows/release.yml`](.github/workflows/release.yml) -publish 3.0.0: 30 tarballs, each attested, `_npmUser: GitHub Actions`. +**A lightweight tag is not a release trigger, and `--follow-tags` will not push one.** `v4.0.0` was +first created with a bare `git tag v4.0.0`; `git push --follow-tags` pushed the commit, said nothing, +and left the tag local — `--follow-tags` pushes **annotated** tags only. The GitHub Release could +then not be created against a ref the remote did not have. `git tag -a` is the only form +[`PUBLISHING.md`](PUBLISHING.md) writes, for this reason. + +**There are no publication holes, and `scripts/registry-audit.ts` is what keeps it that way.** +`@ultimat3/scraping` was the last one, bootstrapped by hand at 2.0.0 on 2026-08-19 — +`npm publish --access public --provenance=false`, the one-time step every package needs before a +trusted publisher can attach — and npm now answers `E403 … cannot publish over the previously +published versions: 2.0.0` on a retry. `@ultimat3/flags` was the same shape and was closed the same +way. Publication is a step apart from versioning, so the two can disagree silently: the audit runs +in CI and files a `registry-drift` issue when they do, which is exactly what it did during 4.0.0's +release window while the publish sat behind the `npm-publish` environment gate (issue #221, closed +when the run finished). The publish list itself is **derived** from `scripts/list-workspaces.ts`, +which is what keeps a new package from being silently absent from it. Step 1 of `PUBLISHING.md` +comes due again for the next package added after a release run, and nothing else. + +**4.0.0 is a major** because a sweep to close every known gap landed breaking changes to documented +APIs. The 4.0.0 section of [`CHANGELOG.md`](CHANGELOG.md) carries **25** entries marked `BREAKING —` +and ships no codemod, so each one is a manual edit its own entry names — [`wiki/Upgrading.md`](wiki/Upgrading.md) +walks them. The shape of the sweep: things **declared and never wired** were either wired or deleted +(`PrecacheAsset.critical`, `realtime.heartbeatMs`, `CaptureOptions.timeoutMs`, `PERIODIC_SYNC_TAG`, +`requiresApp`), and things that **answered the wrong thing** were corrected (`on delete` reaching +the generated SQL, `in` with a non-array operand, `t.date` accepting an offsetless date-time, +`isValidCron` accepting an unsatisfiable day/month pair, a local disk's signed URLs carrying the +driver kind rather than the registered disk name). 2.0.0 was the **first** major and carried 33; +3.0.0 carried 10. + +**Every package has an OIDC trusted publisher.** `developerz-ai` / `ultimate` / `release.yml` / +environment `npm-publish`, publish permission, all 30, verified per package with +`npx -y npm@12 trust list --json` — `npm trust` shipped in **npm 12** and Bun's bundled npm +answers it as an unknown command, which is why `scripts/trust-publishers.ts` pins the runner. That +attachment is what let [`.github/workflows/release.yml`](.github/workflows/release.yml) publish +3.0.0 and then 4.0.0: 30 tarballs per release, each attested, `_npmUser: GitHub Actions`. + +**The `npm-publish` environment needs a human to approve the run.** The release workflow reaches +`waiting` and publishes nothing until a named reviewer approves the pending deployment — the last +point at which an irreversible publish can be stopped. `gh run view ` reports `waiting`, not a +failure. **2.0.0 is the one release with no provenance**: no publisher was attached, so the OIDC exchange had nothing to verify against, the workflow could not publish, and 2.0.0 went out by hand — -`_npmUser: sebyx07`, no `dist.attestations`, where 1.1.0, 1.2.0 and 3.0.0 carry both. Not "for the -first time" — this file said that until 2026-08-19 and `CHANGELOG.md`'s 3.0.0 header still does: -1.1.0 and 1.2.0 published under **earlier** publisher configurations, one per package, and +`_npmUser: sebyx07`, no `dist.attestations`, where 1.1.0, 1.2.0, 3.0.0 and 4.0.0 carry both. Not +"for the first time" — this file said that until 2026-08-19 and `CHANGELOG.md`'s 3.0.0 header still +does: 1.1.0 and 1.2.0 published under **earlier** publisher configurations, one per package, and `npm view @ultimat3/core@1.2.0 _npmUser.trustedPublisher` answers an `oidcConfigId` that differs from -3.0.0's. What is new on 2026-08-19 is that all 30 carry a publisher **and** carry -`Environment: npm-publish` with it. 1.0.0 was the manual bootstrap. +3.0.0's. 1.0.0 was the manual bootstrap. Semver applies — a breaking change to a documented API needs a major, and the eight primitive shapes, the `x` CLI surface and the tier table are as stable as the `X_*` codes already were. @@ -101,9 +119,13 @@ figure. [`scripts/bench/restart-bench.ts`](scripts/bench/restart-bench.ts), resu Open: roadmap milestone 11's two-platform deploy proof — 1.1.0 gave a scaffolded app a real deployable artifact (`packages/cli/src/serve.ts`; `x new` writes `apps/web/server.ts`, `prerender.ts`, a Dockerfile and `docker-compose.prod.yml`; `ROLE=migrate` runs release-phase -migrations), but the demo app on Compose **and** K8s from one image with an invisible rolling -restart is still not demonstrated — and until 2.0.0 the chart could not have demonstrated it, -because `sync`'s readiness probe polled a port the process never opened. Of the four known gaps +migrations), and **4.0.0 gave it a chart** — `x new` writes `docker/helm`, 8 files, one +`Deployment` per role, so `x deploy --method helm` runs `helm upgrade --install` against it with +nothing to copy in. What is still missing is the **proof**, which is the milestone: the demo app on +Compose **and** K8s from one image, with an invisible rolling restart, has not been demonstrated. +Two things had to be true before it could be, and each was false in turn — until 2.0.0 `sync`'s +readiness probe polled a port the process never opened, and until 4.0.0 a scaffolded app had no +chart at all and `--method helm` exited `X_NOT_IMPLEMENTED`. Of the four known gaps named in [`CHANGELOG.md`](CHANGELOG.md), **all four are closed in 2.0.0**, `As of 2026-08`: | Gap | State | @@ -266,7 +288,7 @@ Everything in the framework is one of these. **If a feature doesn't fit one of t ## CI -Free GitHub Actions runners (`ubuntu-latest`) — never a paid runner. `ci.yml` runs three jobs, each answering a question no other job answers: `verify` (the gate, `x verify` verbatim — lint, typecheck, boundaries and every suite are its steps, never a second job), `reference-app-verify` (the app gate, on its ratchet) and `scaffold-smoke` (`x new` → `bun install` → `x verify` outside the checkout). Target under 5 minutes. Every job starts with `./.github/actions/setup` — bun, the install cache, a frozen install. Releases publish to npm via **OIDC trusted publishing**, with provenance — which 2.0.0 did not get, because no trusted publisher existed for the exchange to verify against. All 30 were attached on 2026-08-19, and 3.0.0 is the first release since 1.2.0 that the workflow published: `npm view @ultimat3/core@3.0.0 dist.attestations _npmUser`. See [`PUBLISHING.md`](PUBLISHING.md). +Free GitHub Actions runners (`ubuntu-latest`) — never a paid runner. `ci.yml` runs three jobs, each answering a question no other job answers: `verify` (the gate, `x verify` verbatim — lint, typecheck, boundaries and every suite are its steps, never a second job), `reference-app-verify` (the app gate, on its ratchet) and `scaffold-smoke` (`x new` → `bun install` → `x verify` outside the checkout). Target under 5 minutes. Every job starts with `./.github/actions/setup` — bun, the install cache, a frozen install. Releases publish to npm via **OIDC trusted publishing**, with provenance — which 2.0.0 did not get, because no trusted publisher existed for the exchange to verify against. All 30 were attached on 2026-08-19, and 3.0.0 and 4.0.0 both went out through the workflow: `npm view @ultimat3/core@4.0.0 dist.attestations _npmUser`. A fourth workflow, `registry-audit.yml`, runs `scripts/registry-audit.ts` on a schedule and files a `registry-drift` issue when the tree's stamped version and the registry disagree — it is not a `ci.yml` job because it asks about the **registry**, which no commit changes. See [`PUBLISHING.md`](PUBLISHING.md). ## Note diff --git a/PUBLISHING.md b/PUBLISHING.md index ea52bcd5..9a4d1b6f 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -8,17 +8,23 @@ Releases use **OIDC trusted publishing** from GitHub Actions mints a short-lived token from the run's OIDC identity and attaches a provenance attestation automatically. -**`As of 2026-08-19`: 30 workspaces publish, all 30 are on the registry at 3.0.0, and every 3.0.0 -tarball carries a provenance attestation.** `v3.0.0` is tagged and pushed, its GitHub Release is -published, and the run that Release triggered is what put those 30 versions on npm — the first -workflow-published release since 1.2.0. +**`As of 2026-08-20`: 30 workspaces publish, all 30 are on the registry at 4.0.0, and every 4.0.0 +tarball carries a provenance attestation.** `v4.0.0` is tagged and pushed, its GitHub Release is +published, and the run that Release triggered is what put those 30 versions on npm — the second +workflow-published release in a row, after 3.0.0 ended the gap since 1.2.0. + +One command answers the whole table below at once: **`bun run scripts/registry-audit.ts --json`**, +which resolves every derived name against npm and either says `30/30 publishable packages are on npm +at 4.0.0, every one attested` or names each gap with a runnable `fix:`. `registry-audit.yml` runs it +daily and files a `registry-drift` issue on a gap. | Fact | Read it yourself | |---|---| | what npm serves | `npm view @ultimat3/core version` | -| attested, and by whom | `npm view @ultimat3/core@3.0.0 dist.attestations _npmUser` | +| attested, and by whom | `npm view @ultimat3/core@4.0.0 dist.attestations _npmUser` | | the derived publish list, in order | `bun run scripts/release-workflow.ts --json` | -| the repository is stamped at one version | `bun run scripts/release.ts --check 3.0.0` | +| the repository is stamped at one version | `bun run scripts/release.ts --check 4.0.0` | +| every package is on npm at that version | `bun run scripts/registry-audit.ts --json` | **There is no publication hole today, and there have been two.** `@ultimat3/flags` was the first: the workflow listed its `-w` flags by hand and omitted it. That list is now **derived** from @@ -123,14 +129,14 @@ approval-gated environment, which is the half GitHub cannot enforce, and it must ## Human steps outside this file -**`As of 2026-08-19` all four are done, for all 30 packages, and 3.0.0 went out through the -workflow because of it** — every 3.0.0 tarball carries an attestation and `_npmUser: GitHub Actions` -(`npm view @ultimat3/core@3.0.0 dist.attestations _npmUser`). +**`As of 2026-08-20` all four are done, for all 30 packages, and 3.0.0 and then 4.0.0 both went out +through the workflow because of it** — every tarball in both carries an attestation and +`_npmUser: GitHub Actions` (`npm view @ultimat3/core@4.0.0 dist.attestations _npmUser`). **2.0.0 is the exception in the line, and its cause was step 3.** With no trusted publisher attached, the OIDC exchange had nothing to verify against, so the workflow could not publish and 2.0.0 went out by hand: every `@ultimat3/*` package at 2.0.0 carries `_npmUser: sebyx07` and **no -`dist.attestations`**, while 1.1.0, 1.2.0 and 3.0.0 carry both. +`dist.attestations`**, while 1.1.0, 1.2.0, 3.0.0 and 4.0.0 carry both. **Steps 3 and 4 on 2026-08-19 were not the first publishers this repo ever had.** 1.1.0 and 1.2.0 published under earlier ones — `npm view @ultimat3/core@1.2.0 _npmUser.trustedPublisher` answers an @@ -168,7 +174,8 @@ one that breaks a release. **None of them fails loudly if you skip it** — that is the whole problem, and it is why each is written out with what skipping it actually costs. Step 1 is the only one that breaks a release, and it breaks it deep into the run, after packages have published irreversibly. **Nothing is owed -today** `As of 2026-08-19` — the 3.0.0 run reached all 30. Steps 1, 3 and 4 come due again for the +today** `As of 2026-08-20` — the 4.0.0 run reached all 30, and `bun run scripts/registry-audit.ts +--json` is how you confirm that rather than believing this sentence. Steps 1, 3 and 4 come due again for the next package added after a release run: `bun run scripts/release-workflow.ts --json` lists what the next run will reach, and `npm view version` on a name it lists is how you find the one that is not there yet. @@ -224,8 +231,8 @@ package can be silently absent again; the loud failure is the feature. **This happened once already, and was closed.** `@ultimat3/flags` was the same shape — never published, in the derived list, fifth of 29 — and 2.0.0 closed it with exactly this command. Its version history is the shape a bootstrap leaves behind, and it is two versions long: `npm view -@ultimat3/flags versions` answers `2.0.0` (hand-published, no attestation) and `3.0.0` (the -workflow, with provenance). `@ultimat3/scraping` reads identically. +@ultimat3/flags versions` answers `2.0.0` (hand-published, no attestation) and then `3.0.0` and +`4.0.0` (the workflow, with provenance). `@ultimat3/scraping` reads identically. ### 2. Create the `npm-publish` environment with required reviewers @@ -315,7 +322,7 @@ pass `verify` while the tag says `v1.10.1` — and every publish then dies `EPUB version already on the registry, one package at a time, halfway through a release. `--check` anchors the comparison to the version read off the tag. -`As of 2026-08-19` `git describe` answers `v3.0.0` and `bun run scripts/release.ts --check 3.0.0` +`As of 2026-08-20` `git describe` answers `v4.0.0` and `bun run scripts/release.ts --check 4.0.0` passes, and the check itself is a step of the workflow ([`.github/workflows/release.yml`](.github/workflows/release.yml), the `bun run scripts/release.ts --check` run). Earlier in 2026-08 this repo was in exactly the broken state: `git describe` answered diff --git a/README.md b/README.md index 4e4041b2..dbc6f4fa 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![CI](https://github.com/developerz-ai/ultimate/actions/workflows/ci.yml/badge.svg)](https://github.com/developerz-ai/ultimate/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Bun](https://img.shields.io/badge/bun-%E2%89%A5%201.3-black.svg?logo=bun)](https://bun.sh) -[![Version](https://img.shields.io/badge/version-3.0.0-blue.svg)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/version-4.0.0-blue.svg)](CHANGELOG.md) @@ -49,13 +49,13 @@ are the difference between an agent that ships and one that thrashes. Reproduce it: `bun run scripts/bench/restart-bench.ts --clients 10000 --probe-interval-ms 200` — the committed report and the run's own transcript are in [`scripts/bench/results/`](scripts/bench/results/). -**Not claimed at 3.0.0:** +**Not claimed at 4.0.0:** | Open | Where it stands | |---|---| | **Two-platform deploy proof** | 1.1.0 gave a scaffolded app a real deployable artifact — `x new` writes `apps/web/server.ts`, `prerender.ts`, a Dockerfile and `docker-compose.prod.yml`, and `ROLE=migrate` runs release-phase migrations. The **proof** is still open: the demo app on Compose **and** K8s from one image, with an invisible rolling restart, is [milestone 11](docs/idea/14-roadmap.md) and has not been demonstrated | | **Known gaps shipped in 1.1.0** | all four are fixed in 2.0.0 — but a fix and a proof are different things, and one of the four still lacks the proof. `x build --target binary` no longer crashes at import — the version read is lazy and `x build` passes `--define ULTIMATE_FRAMEWORK_VERSION`, and [`docker/Dockerfile`](docker/Dockerfile) passes it too and ends in `/out/app --version`, so a binary that cannot answer fails the image build rather than the first command an operator runs. The target is still unproven end to end: booting is not serving, and no scaffolded app has been compiled and served from a bare VM · `docker-compose.prod.yml` no longer pairs a published host port with `replicas` above 1: `web` and `sync` declare `replicas: 1` in all four files and each header names the two ways up, which makes the one-box ceiling declared rather than broken, not lifted · the shared cache tier's Lua invalidation no longer `DEL`s keys it never declares in `KEYS` · `resolveEnvironment` exists only in `core`, and `@ultimat3/seo` exports neither it nor `SeoEnvironment` — a **breaking** change, which is part of why 2.0.0 is a major. Detail in [CHANGELOG.md](CHANGELOG.md), per-row workarounds for the 1.x packages in [Known gaps](https://github.com/developerz-ai/ultimate/wiki/Known-Gaps) | -| **Deferred past 3.0.0** | realtime tier 3 local-first (`persist: true`), the plugin API, multi-region replication, the Redis/NATS **job** drivers — none of them ships in 3.0.0 either, each behind the interface that ships today. The job drivers throw `X_NOT_IMPLEMENTED` with a runnable `fix:` rather than pretending to work | +| **Deferred past 4.0.0** | realtime tier 3 local-first (`persist: true`), the plugin API, multi-region replication, the Redis/NATS **job** drivers — none of them ships in 4.0.0 either, each behind the interface that ships today. The job drivers throw `X_NOT_IMPLEMENTED` with a runnable `fix:` rather than pretending to work | **Never claimed:** no adoption numbers, no production deployments, no testimonials. None exist yet, and this file will say so until they do. @@ -350,13 +350,13 @@ Render mode is a route-level property, not a global one. A landing page is stati ## Realtime — a ladder, not a cliff -Three tiers, the same mutator shape at every rung. Tier 2 → tier 3 is a config flag, not a rewrite. Tiers 1–2 ship today; tier 3 is not in 3.0.0 and lands in a later major, behind the interfaces that are already here. +Three tiers, the same mutator shape at every rung. Tier 2 → tier 3 is a config flag, not a rewrite. Tiers 1–2 ship today; tier 3 is not in 4.0.0 and lands in a later major, behind the interfaces that are already here. | Tier | What | Covers | |---|---|---| | 1 · **Channels** | `ctx.publish(topic, msg)` over Bun's native WS pub/sub | presence, cursors, notifications | | 2 · **Live queries** | declare server-side with a policy, receive a Solid signal | **90% of "realtime app"** | -| 3 · **Local-first** *(not in 3.0.0)* | optimistic mutators, OPFS SQLite, offline queue, rebase | offline writes that reconcile | +| 3 · **Local-first** *(not in 4.0.0)* | optimistic mutators, OPFS SQLite, offline queue, rebase | offline writes that reconcile | → [Realtime design and its honest limits](docs/idea/03-realtime.md) @@ -387,7 +387,7 @@ The same app code on one PaaS dyno and on a replicated cluster. Climbing is a dr | Auth | **Better Auth**, wrapped | MIT, self-hosted, with our policy layer on top | | Frontend | **SolidJS, pinned `1.9.14`** + our own router | fine-grained reactivity on the stable line; Solid 2 is still `2.0.0-beta.N`, and we vendor the router rather than track an alpha | | Styling | **SCSS modules + design tokens** | no Tailwind (diff noise), no CSS-in-JS (runtime cost) | -| Jobs | Postgres queue default; Redis/NATS drivers not in 3.0.0 | zero-infra start, a real scale path behind one interface | +| Jobs | Postgres queue default; Redis/NATS drivers not in 4.0.0 | zero-infra start, a real scale path behind one interface | | Observability | **OpenTelemetry, always on** | one trace across HTTP → job → live query | **Excluded on purpose:** GraphQL · multi-runtime · multi-ORM · a second CSS solution · React Server Components · a plugin API in 1.x · vendor edge/KV primitives. diff --git a/docs/idea/14-roadmap.md b/docs/idea/14-roadmap.md index b625fd15..764186a6 100644 --- a/docs/idea/14-roadmap.md +++ b/docs/idea/14-roadmap.md @@ -34,7 +34,7 @@ Milestones 12–14 exist as a **design, not a plan in progress** — see [Design | Open | Why it is not closed | |---|---| -| **Two-platform deploy proof** (milestone 11) | `x build --target docker\|binary\|static`, `docker/docker-compose.{dev,prod}.yml` and `docker/helm` all exist. Running the demo app on Hetzner+Compose **and** a K8s cluster from one image, with an invisible rolling restart, needs real infrastructure and has not been done | +| **Two-platform deploy proof** (milestone 11) | `x build --target docker\|binary\|static`, `docker/docker-compose.{dev,prod}.yml` and `docker/helm` all exist, and `As of 2026-08-20` **a scaffolded app gets its own chart** — `x new` writes `docker/helm`, 8 files, one `Deployment` per role, so `x deploy --method helm` no longer exits `X_NOT_IMPLEMENTED` (4.0.0). Every artifact the milestone names now exists. What is missing is the proof itself: running the demo app on Hetzner+Compose **and** a K8s cluster from one image, with an invisible rolling restart, needs real infrastructure and has not been done | **Closed since**: a scaffolded app now has a deployable artifact. [`packages/cli/src/serve.ts`](../../packages/cli/src/serve.ts) boots a role with no dev watcher and no `/_x`, `ROLE=migrate` applies migrations through the db ledger and exits — the release phase a PaaS asks for — and `x new` writes `apps/web/server.ts`, `apps/web/prerender.ts`, `docker/Dockerfile`, its `.dockerignore` and `docker/docker-compose.prod.yml` ([`templates/scaffold-app.ts`](../../packages/cli/src/templates/scaffold-app.ts), [`templates/scaffold-container.ts`](../../packages/cli/src/templates/scaffold-container.ts)). That was the missing half of "one command produces something you can run"; it is **not** the two-platform proof, which is a measurement on real infrastructure and remains open. diff --git a/docs/idea/README.md b/docs/idea/README.md index 502557b0..16c0043a 100644 --- a/docs/idea/README.md +++ b/docs/idea/README.md @@ -60,16 +60,17 @@ Consequences of each in [`00-thesis.md`](./00-thesis.md); axiom 8 in full in [`1 ## Status -`As of 2026-08-19`: **repository, tag and registry are all at 3.0.0.** The release ran. Resolve every row below rather than believing it — a version in a doc is a snapshot, a command is not. +`As of 2026-08-20`: **repository, tag and registry all agree.** The release ran. Resolve every row below rather than believing it — a version in a doc is a snapshot, a command is not, and `bun run scripts/registry-audit.ts --json` checks the whole table in one call. -| Fact | State, `As of 2026-08-19` | Read it yourself | +| Fact | State, `As of 2026-08-20` | Read it yourself | |---|---|---| -| Versioned | 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` — 30 in all — at 3.0.0 in lockstep, one commit. A major: 10 changelog entries marked `BREAKING —` from a five-agent bug sweep, no codemod. 2.0.0 was the first major and carried 33 | `bun run scripts/release.ts --check 3.0.0` | -| Tagged | `v3.0.0` is on origin, and its GitHub Release is published — that Release is what triggers the workflow | `git tag --list 'v3.*'` | -| Published | **`latest` is 3.0.0** — that is what `bunx create-ultimate myapp` installs | `npm view @ultimat3/core version` | -| Publication holes | **none.** All 30 workspaces are on the registry at 3.0.0. `@ultimat3/scraping` was the last never-published package and was bootstrapped by hand at 2.0.0, exactly as `@ultimat3/flags` had been — the one-time step in [`PUBLISHING.md`](../../PUBLISHING.md) that every package needs before a trusted publisher can attach | `bun run scripts/release-workflow.ts --json` for the derived list, then `npm view` each name — one package proves one package | -| Provenance | every 3.0.0 tarball carries an attestation and `_npmUser: GitHub Actions`; **2.0.0 carries neither**, having gone out by hand | `npm view @ultimat3/core@3.0.0 dist.attestations`, `npm view @ultimat3/core@2.0.0 dist.attestations` | -| OIDC trusted publisher | attached to all 30 on 2026-08-19, with `Environment: npm-publish` — that attachment is what let the workflow publish 3.0.0 at all, and its absence is why 2.0.0 has no provenance. Not the first ever: 1.1.0 and 1.2.0 published under earlier publisher configurations (a different `oidcConfigId` per package) | `NPM_CONFIG_OTP= bun run scripts/trust-publishers.ts --check --json` — every package, and without a fresh code they all read as missing. Per version: `npm view @ultimat3/core@1.2.0 _npmUser.trustedPublisher` | +| Versioned | 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` — 30 in all — in lockstep, one commit. The current line is a major: 25 changelog entries marked `BREAKING —` from a sweep that closed every known gap, no codemod. 2.0.0 was the first major and carried 33; 3.0.0 carried 10 | `bun run scripts/release.ts --check ` | +| Tagged | the tag is on origin **annotated**, and its GitHub Release is published — that Release is what triggers the workflow. `git push --follow-tags` pushes annotated tags only, so a `git tag` with no `-a` never leaves the machine | `git tag --list 'v4.*'` | +| Published | `latest` is what `bunx create-ultimate myapp` installs | `npm view @ultimat3/core version` | +| Publication holes | **none.** All 30 workspaces are on the registry at the stamped version. `@ultimat3/scraping` was the last never-published package and was bootstrapped by hand at 2.0.0, exactly as `@ultimat3/flags` had been — the one-time step in [`PUBLISHING.md`](../../PUBLISHING.md) that every package needs before a trusted publisher can attach | `bun run scripts/registry-audit.ts --json` — it resolves the derived list against npm and names each gap with a runnable `fix:` | +| Provenance | every 3.0.0 and 4.0.0 tarball carries an attestation and `_npmUser: GitHub Actions`; **2.0.0 carries neither**, having gone out by hand | `npm view @ultimat3/core dist.attestations`, `npm view @ultimat3/core@2.0.0 dist.attestations` | +| OIDC trusted publisher | attached to all 30 on 2026-08-19, with `Environment: npm-publish` — that attachment is what let the workflow publish 3.0.0 and then 4.0.0, and its absence is why 2.0.0 has no provenance. Not the first ever: 1.1.0 and 1.2.0 published under earlier publisher configurations (a different `oidcConfigId` per package) | `NPM_CONFIG_OTP= bun run scripts/trust-publishers.ts --check --json` — every package, and without a fresh code they all read as missing. Per version: `npm view @ultimat3/core@1.2.0 _npmUser.trustedPublisher` | +| Release approval | the workflow stops at `waiting` on the `npm-publish` environment until a named reviewer approves the pending deployment — the last point an irreversible publish can be stopped | `gh run view --json status` | Docs `00`–`15`, `18` and `19` describe what exists; `16` and `17` are design only and say so in every claim. diff --git a/llms.txt b/llms.txt index 8b1ed1af..bf5d5e60 100644 --- a/llms.txt +++ b/llms.txt @@ -1,6 +1,6 @@ # Ultimate -> A full-stack, Bun-only, opinionated web framework: Rails' philosophy applied to Bun + Postgres + SolidJS, where the primary developer is an AI agent and the secondary developer is a tired senior engineer working through their own AI agent and AI reviewer. Eight primitives, one authz system, and errors that carry an exact fix command. **3.0.0 in the repository, on the `v3.0.0` tag, and on npm** `As of 2026-08-19` — 29 `@ultimat3/*` packages plus the unscoped `create-ultimate`, 30 in all, versioned in lockstep at one version, one commit, one tag, and all 30 published at 3.0.0 by [`.github/workflows/release.yml`](.github/workflows/release.yml) over OIDC with a provenance attestation. `bunx create-ultimate myapp` installs 3.0.0. Resolve it, do not trust this line — `npm view @ultimat3/core version` for the version, `npm view @ultimat3/core@3.0.0 dist.attestations` for the attestation, `bun run scripts/release-workflow.ts --json` for the 30 names that must move together. **No publication holes**: `@ultimat3/scraping` was the last never-published package and was bootstrapped by hand at 2.0.0 (`npm publish --access public --provenance=false`), the one-time step every package needs before a trusted publisher can attach, exactly as `@ultimat3/flags` had been (issue #84). Semver applies from 1.0.0: a breaking change to a documented API needs a major, which is what 3.0.0 is — 10 entries in its changelog section are marked `BREAKING —`, from a five-agent bug sweep, and none ships a codemod; 2.0.0 was the first major and carried 33. 3.0.0 is the first release the workflow published since 1.2.0: 2.0.0 went out by hand, with no trusted publisher attached for the OIDC exchange to verify against, and is the one release whose tarballs carry no attestation. Install with `bunx create-ultimate myapp`. +> A full-stack, Bun-only, opinionated web framework: Rails' philosophy applied to Bun + Postgres + SolidJS, where the primary developer is an AI agent and the secondary developer is a tired senior engineer working through their own AI agent and AI reviewer. Eight primitives, one authz system, and errors that carry an exact fix command. **One version in the repository, on its annotated tag, and on npm** `As of 2026-08-20` (`npm view @ultimat3/core version`) — 29 `@ultimat3/*` packages plus the unscoped `create-ultimate`, 30 in all, versioned in lockstep at one version, one commit, one tag, and all 30 published at 3.0.0 by [`.github/workflows/release.yml`](.github/workflows/release.yml) over OIDC with a provenance attestation. `bunx create-ultimate myapp` installs 3.0.0. Resolve it, do not trust this line — `npm view @ultimat3/core version` for the version, `npm view @ultimat3/core@3.0.0 dist.attestations` for the attestation, `bun run scripts/release-workflow.ts --json` for the 30 names that must move together. **No publication holes**: `@ultimat3/scraping` was the last never-published package and was bootstrapped by hand at 2.0.0 (`npm publish --access public --provenance=false`), the one-time step every package needs before a trusted publisher can attach, exactly as `@ultimat3/flags` had been (issue #84). Semver applies from 1.0.0: a breaking change to a documented API needs a major, which is what 3.0.0 is — 10 entries in its changelog section are marked `BREAKING —`, from a five-agent bug sweep, and none ships a codemod; 2.0.0 was the first major and carried 33. 3.0.0 is the first release the workflow published since 1.2.0: 2.0.0 went out by hand, with no trusted publisher attached for the OIDC exchange to verify against, and is the one release whose tarballs carry no attestation. Install with `bunx create-ultimate myapp`. Everything in the framework is one of eight primitives: `entity`, `policy`, `action`, `mutator`, `query`, `job`, `route`, `task`. One `action` declaration projects into six artifacts (HTTP route, OpenAPI operation, typed client function, job handle, MCP tool, test scaffold) that all share the same `policy` — there is never a second authorization system. The CLI binary is `x`; every command and every error has a `--json` form, and every framework error carries a stable `X_*` code, a concrete cause, and the exact command that fixes it. `x verify` is the single gate, 17 steps in cost order: typecheck, lint, boundaries, filesize, package-shape, errors, unit, contract, live, job, e2e, eval, drift, contract-diff, budgets, manifest, roadmap. The stack is locked (Bun >= 1.3, Postgres with no ORM dependency — `entity()` projects to hand-written SQL through `postgresDriver()`, Standard Schema behind `t` with a dependency-free builtin provider as the shipped default, Better Auth, SolidJS pinned at `1.9.14` on the stable line rather than the `2.0.0-beta.N` prerelease, SCSS modules + tokens, OpenTelemetry always on) and the exclusions are permanent (GraphQL, multi-runtime, multi-ORM, a second CSS system, RSC, vendor edge/KV primitives, a plugin API). An app extends the framework by **wrapping**, never by forking, patching or petitioning: `entity()`, `action()`, `mutator()`, `query()` and `job()` are plain functions returning values, registration happens inside the call, `isAction` is structural, and no `x verify` step matches source text for a primitive call — so an app's own factory (`tenantEntity`, `auditedMutator`) yields primitives the registry, the manifest, the five projections, admin and MCP treat identically. Axiom 8: Ultimate ships mechanism; your app ships convention — mechanisms and *structural* conventions (file naming, the four surfaces, the tier order, one flat i18n catalog per locale) ship as build errors, *business* conventions (an org model, a plan tier, what an audit row says) never do. Deployment is containers only: one image, six roles selected by `ROLE`. @@ -61,7 +61,7 @@ Everything in the framework is one of eight primitives: `entity`, `policy`, `act - [action](https://raw.githubusercontent.com/developerz-ai/ultimate/main/packages/action/src/index.ts): server-authoritative mutation; six generated artifacts per declaration. - [query](https://raw.githubusercontent.com/developerz-ai/ultimate/main/packages/query/src/index.ts): reads, optionally `live: true`; requires deterministic, bounded SQL. - [jobs](https://raw.githubusercontent.com/developerz-ai/ultimate/main/packages/jobs/src/index.ts): durable steps, required `idempotencyKey`, `pg` driver by default. -- [realtime](https://raw.githubusercontent.com/developerz-ai/ultimate/main/packages/realtime/src/index.ts): channels, live-query transport, mutator rebase, cursors. Tiers 1–2 ship at 1.0.0; tier 3 local-first (`persist: true`) is not in 3.0.0 and stays deferred behind the same interfaces. Capacity is measured and published for **one node only**: 50,000 WebSocket clients on a single `sync` node over `InProcessTransport`, `SIGKILL`ed with no drain — all 50,000 reconnected, 49,981 received a channel patch in-window, first patch on the reconnected socket at p50 54.0s / p90 105.5s / max 145.7s, 156,851 connect attempts shed by the `AcceptBudget` ([restart-bench.ts](https://raw.githubusercontent.com/developerz-ai/ultimate/main/scripts/bench/restart-bench.ts), [50k-restart.json](https://raw.githubusercontent.com/developerz-ai/ultimate/main/scripts/bench/results/50k-restart.json)). Per-node recovery, not throughput; the run never crossed NATS, so it is not a multi-node result. Multi-node figures remain targets. +- [realtime](https://raw.githubusercontent.com/developerz-ai/ultimate/main/packages/realtime/src/index.ts): channels, live-query transport, mutator rebase, cursors. Tiers 1–2 ship at 1.0.0; tier 3 local-first (`persist: true`) is not in 4.0.0 and stays deferred behind the same interfaces. Capacity is measured and published for **one node only**: 50,000 WebSocket clients on a single `sync` node over `InProcessTransport`, `SIGKILL`ed with no drain — all 50,000 reconnected, 49,981 received a channel patch in-window, first patch on the reconnected socket at p50 54.0s / p90 105.5s / max 145.7s, 156,851 connect attempts shed by the `AcceptBudget` ([restart-bench.ts](https://raw.githubusercontent.com/developerz-ai/ultimate/main/scripts/bench/restart-bench.ts), [50k-restart.json](https://raw.githubusercontent.com/developerz-ai/ultimate/main/scripts/bench/results/50k-restart.json)). Per-node recovery, not throughput; the run never crossed NATS, so it is not a multi-node result. Multi-node figures remain targets. - [render](https://raw.githubusercontent.com/developerz-ai/ultimate/main/packages/render/src/index.ts): `defineRoute`, the five render modes, hydration timing, budgets. - [cache](https://raw.githubusercontent.com/developerz-ai/ultimate/main/packages/cache/src/index.ts): four tiers and the entity-tag invalidation graph. - [seo](https://raw.githubusercontent.com/developerz-ai/ultimate/main/packages/seo/src/index.ts): typed `meta`, `ld.*` JSON-LD helpers, sitemap/robots/feeds. diff --git a/wiki/Actions.md b/wiki/Actions.md index 877c057b..e35cb8f3 100644 --- a/wiki/Actions.md +++ b/wiki/Actions.md @@ -125,7 +125,7 @@ export const likePost = mutator({ | Authz | the `server` half carries the policy; the `local` half is presentation only and never a security boundary | | Never | let `local` do I/O, randomness, or `Date.now()` | -**Replayability rule:** `local` is re-executed on every rebase — after each server confirmation, on reconnect, and when a conflicting remote write arrives. It must be a pure function of `(tx, input)`. I/O, `Math.random()`, `crypto.randomUUID()`, or a wall-clock read makes the local timeline diverge from the server's, and the divergence surfaces as flicker, then as wrong data. Ids and timestamps come from the input, generated once at call time. Tier 3 local-first (`persist: true`) is not in 3.0.0; mutators work today at realtime tiers 1–2 ([Realtime](Realtime)). +**Replayability rule:** `local` is re-executed on every rebase — after each server confirmation, on reconnect, and when a conflicting remote write arrives. It must be a pure function of `(tx, input)`. I/O, `Math.random()`, `crypto.randomUUID()`, or a wall-clock read makes the local timeline diverge from the server's, and the divergence surfaces as flicker, then as wrong data. Ids and timestamps come from the input, generated once at call time. Tier 3 local-first (`persist: true`) is not in 4.0.0; mutators work today at realtime tiers 1–2 ([Realtime](Realtime)). ## Errors diff --git a/wiki/Admin-Dashboard.md b/wiki/Admin-Dashboard.md index 306f4fdc..f26bab5c 100644 --- a/wiki/Admin-Dashboard.md +++ b/wiki/Admin-Dashboard.md @@ -104,7 +104,7 @@ Own routes in your own app. Never fork the framework. | Branding | `defineTheme({ colors, radius, font })` — the one override seam, validated not escaped → [Theming](Theming) | | Hide an entity | the entity's policy denies `admin:read` — visibility is authz, not configuration | -No plugin API, in 1.x, 2.x or 3.0.0 ([axiom](Home)). The extension point is that the admin is your app. +No plugin API, in 1.x, 2.x, 3.x or 4.0.0 ([axiom](Home)). The extension point is that the admin is your app. ## Deployment diff --git a/wiki/Building-Your-Own-Base.md b/wiki/Building-Your-Own-Base.md index 9a82b151..f37088a6 100644 --- a/wiki/Building-Your-Own-Base.md +++ b/wiki/Building-Your-Own-Base.md @@ -9,7 +9,7 @@ wait for. Everything downstream treats the result identically — registry, manifest, projections, admin, MCP, `x verify`. -**Re-run against 3.0.0** `As of 2026-08-19`: every fenced example on this page compiles under the +**Re-run against 4.0.0** `As of 2026-08-20`: every fenced example on this page compiles under the repository's own `tsconfig.base.json` (`strict`, `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax`), and both compile errors quoted under [Two caveats](#two-caveats) are `tsc`'s verbatim words at that setting. The `tenantEntity` example diff --git a/wiki/Configuration.md b/wiki/Configuration.md index c01d6708..de4fae04 100644 --- a/wiki/Configuration.md +++ b/wiki/Configuration.md @@ -73,7 +73,7 @@ Better Auth, wrapped. Sessions live in Postgres. Authorization is **not** here | field | type | default | notes | |---|---|---|---| -| `jobs.driver` | `'postgres' \| 'redis' \| 'nats'` | `'postgres'` | `postgres` needs no extra infra and is the only shipped production driver. **`redis` and `nats` are not in 3.0.0** — the stubs throw `X_NOT_IMPLEMENTED` ([Jobs and workflows](Jobs-And-Workflows)) | +| `jobs.driver` | `'postgres' \| 'redis' \| 'nats'` | `'postgres'` | `postgres` needs no extra infra and is the only shipped production driver. **`redis` and `nats` are not in 4.0.0** — the stubs throw `X_NOT_IMPLEMENTED` ([Jobs and workflows](Jobs-And-Workflows)) | | `jobs.queues` | `string[]` | `['default']` | a `worker` runs one pool per queue in `WORKER_QUEUES` | | `jobs.concurrency` | `number` | `8` | per pool, per process | | `jobs.retry.attempts` | `number` | `5` | per-job `retry` overrides | @@ -90,7 +90,7 @@ Better Auth, wrapped. Sessions live in Postgres. Authorization is **not** here | field | type | default | notes | |---|---|---|---| | `realtime.enabled` | `boolean` | `false` | off unless the app turns it on | -| `realtime.tier` | `'channels' \| 'live-queries' \| 'local-first'` | `'channels'` | **names, not numbers**. `channels` and `live-queries` ship; `local-first` is not in 3.0.0 ([Realtime](Realtime)) | +| `realtime.tier` | `'channels' \| 'live-queries' \| 'local-first'` | `'channels'` | **names, not numbers**. `channels` and `live-queries` ship; `local-first` is not in 4.0.0 ([Realtime](Realtime)) | | `realtime.transport` | `'memory' \| 'nats' \| 'redis'` | `'memory'` | `memory` = in-process, single node, dev and small deploys. `redis` type-checks and is never built — `selectTransport` resolves in-process or NATS only | | `realtime.urlEnv` | `string` | — | the **env key name**, never a URL. Required unless `memory`; missing → `X_CONFIG_INVALID` | diff --git a/wiki/FAQ.md b/wiki/FAQ.md index 0f934607..e455be9d 100644 --- a/wiki/FAQ.md +++ b/wiki/FAQ.md @@ -8,18 +8,20 @@ Honest answers. Where something is not built yet, it says so. **`As of 2026-08`.** Stable API, semver from here. 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` — **30 in all** — are **versioned** in lockstep: one version, one commit, one tag. -**Publication is a separate step from versioning, and 3.0.0's has run.** Repository, tag and registry are the same version; there are no publication holes — every one of the 30 is on npm. +**Publication is a separate step from versioning, and the current release's has run.** Repository, tag and registry are the same version; there are no publication holes — every one of the 30 is on npm. -| Fact | State `As of 2026-08-19` | Resolve it yourself | +| Fact | State `As of 2026-08-20` | Resolve it yourself | |---|---|---| -| What you can install | **3.0.0** — `bunx create-ultimate myapp` gives you it | `npm view @ultimat3/core version` | -| Repository version | **3.0.0**, all 30 stamped in one commit | the top section of [`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md) | -| Tagged | `v3.0.0` is on origin, and the GitHub Release for it is published | [the repository's tags](https://github.com/developerz-ai/ultimate/tags) | -| On the registry | **all 30**, at 3.0.0 | `npm view @ultimat3/scraping version` | -| Provenance | 1.1.0, 1.2.0 and 3.0.0 carry an attestation; **2.0.0 does not** | `npm view @ultimat3/core@3.0.0 dist.attestations` | -| Who published it | `GitHub Actions`, over OIDC, on all 30 at 3.0.0 | `npm view @ultimat3/core@3.0.0 _npmUser` | +| What you can install | whatever `latest` is — `bunx create-ultimate myapp` gives you it | `npm view @ultimat3/core version` | +| Repository version | all 30 stamped in one commit | the top section of [`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md) | +| Tagged | the tag is on origin, annotated, and its GitHub Release is published | [the repository's tags](https://github.com/developerz-ai/ultimate/tags) | +| On the registry | **all 30**, at that version | `npm view @ultimat3/scraping version` | +| Provenance | 1.1.0, 1.2.0, 3.0.0 and 4.0.0 carry an attestation; **2.0.0 does not** | `npm view @ultimat3/core dist.attestations` | +| Who published it | `GitHub Actions`, over OIDC, on all 30 | `npm view @ultimat3/core _npmUser` | -3.0.0 is the first release the workflow published since 1.2.0. 2.0.0 is the exception in the line: it was hand-published, with no trusted publisher attached for the OIDC exchange to verify against, so its tarballs carry no attestation and `_npmUser: sebyx07`. 1.0.0 was the manual bootstrap. `@ultimat3/scraping` and `@ultimat3/flags` were the two never-published packages and both are closed — each by the one-time manual bootstrap every package needs before a trusted publisher can attach. +Only the [footer](_Footer) stamps the number, so this table names the command instead of a version that goes stale on the next tag. In the repository, `bun run scripts/registry-audit.ts --json` checks the whole table in one call, and `registry-audit.yml` runs it daily and opens an issue on any disagreement. + +3.0.0 and 4.0.0 both went out through the workflow. 2.0.0 is the exception in the line: it was hand-published, with no trusted publisher attached for the OIDC exchange to verify against, so its tarballs carry no attestation and `_npmUser: sebyx07`. 1.0.0 was the manual bootstrap. `@ultimat3/scraping` and `@ultimat3/flags` were the two never-published packages and both are closed — each by the one-time manual bootstrap every package needs before a trusted publisher can attach. That is exactly what the version claims — a stable API under semver, not a promise about your infrastructure. @@ -29,7 +31,7 @@ What it does **not** claim: |---|---| | A multi-node realtime result | the 50k forced-restart benchmark **is** measured and committed, but on **one** `sync` node over `InProcessTransport` — it never crossed NATS. Fanout across nodes, throughput, and per-node socket capacity are all still targets, not results ([Realtime](Realtime)) | | The two-platform deploy proof | all three build targets ship — `x build --target docker`, `x build --target binary`, `x build --target static` — and so do the compose files and the Helm chart. The demo app running on Compose **and** K8s from one image, with a rolling restart invisible to connected clients, is milestone 11's remaining item ([Deployment](Deployment)) | -| Not in 3.0.0 | realtime tier 3 (`persist: true`, local-first), the plugin API, multi-region replication, and the Redis/NATS **job** drivers — all behind the interfaces that ship today. The job drivers throw `X_NOT_IMPLEMENTED` with a runnable `fix:` line rather than pretending to work | +| Not in 4.0.0 | realtime tier 3 (`persist: true`, local-first), the plugin API, multi-region replication, and the Redis/NATS **job** drivers — all behind the interfaces that ship today. The job drivers throw `X_NOT_IMPLEMENTED` with a runnable `fix:` line rather than pretending to work | ### What is actually finished? @@ -119,7 +121,7 @@ Yes. `realtime.tier: 1` with `transport: 'memory'` is the default, and a tier-1 ### What happens if the sync engine doesn't work out? -It is roughly **70% of total effort** and the single largest risk. Tiers 1–2 shipped in milestone 6 and are under semver; tier 3 local-first is not in 3.0.0. The reconnect benchmark that gated topology — 50k sockets, a forced `sync` restart, recovery time and DB load — **is measured at 1.1.0**: all 50,000 reconnected, 49,981 received a channel patch inside the window, p50 54.0s / p90 105.5s, 156,851 connect attempts shed before any query path ([Realtime](Realtime)). That is **reachability** — first patch on the reconnected socket — not consistency; the delivery half is a separate 10,000-client run, **1,666,882 patches received, 0 observed sequence gaps** — a lower bound, since a hole is only visible between two frames one connection received ([Realtime](Realtime)) — and `As of 2026-08` the only run that counts lost patches at all. Both were run on **one** node, so multi-node fanout is still unproven. If the incremental matcher turns out to be the bottleneck, wrapping an existing protocol (Zero's) is an accepted fallback. +It is roughly **70% of total effort** and the single largest risk. Tiers 1–2 shipped in milestone 6 and are under semver; tier 3 local-first is not in 4.0.0. The reconnect benchmark that gated topology — 50k sockets, a forced `sync` restart, recovery time and DB load — **is measured at 1.1.0**: all 50,000 reconnected, 49,981 received a channel patch inside the window, p50 54.0s / p90 105.5s, 156,851 connect attempts shed before any query path ([Realtime](Realtime)). That is **reachability** — first patch on the reconnected socket — not consistency; the delivery half is a separate 10,000-client run, **1,666,882 patches received, 0 observed sequence gaps** — a lower bound, since a hole is only visible between two frames one connection received ([Realtime](Realtime)) — and `As of 2026-08` the only run that counts lost patches at all. Both were run on **one** node, so multi-node fanout is still unproven. If the incremental matcher turns out to be the bottleneck, wrapping an existing protocol (Zero's) is an accepted fallback. ### Why ship realtime last if it's the differentiator? @@ -133,7 +135,7 @@ Stated risk, not a hidden one. `As of 2026-08` long-running Bun processes are le ### Where do plugins fit? -Nowhere — the plugin API is not in 1.x, not in 2.x and not in 3.0.0. Semver covers the documented surface, not internals, and a plugin API freezes internals permanently. Fork the blessed path if you need something else; extension points earn their existence from real forks, not from speculation. +Nowhere — the plugin API is not in 1.x, not in 2.x, not in 3.x and not in 4.0.0. Semver covers the documented surface, not internals, and a plugin API freezes internals permanently. Fork the blessed path if you need something else; extension points earn their existence from real forks, not from speculation. ### Will you add an adapter for my host or my ORM? diff --git a/wiki/Getting-Started.md b/wiki/Getting-Started.md index 1fc23ef7..2bac0b2f 100644 --- a/wiki/Getting-Started.md +++ b/wiki/Getting-Started.md @@ -177,4 +177,4 @@ $ x verify ## Status -`As of 2026-08`. Stable API — semver from here ([Upgrading](Upgrading)). The repository holds 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` and versions all 30 in lockstep at **3.0.0** — a major, 10 entries marked `BREAKING —` from a five-agent bug sweep and no codemod ([Upgrading](Upgrading)); 2.0.0 was the first major and carried 33. **What `bunx create-ultimate myapp` installs is 3.0.0** `As of 2026-08-19`: npm's `latest` is 3.0.0 for all 30 workspaces, each published by the release workflow over OIDC with a provenance attestation. Resolve it, do not trust this line — `npm view @ultimat3/core version`, and `npm view @ultimat3/core@3.0.0 dist.attestations` for the attestation. **No publication holes**: `@ultimat3/scraping` was the last never-published package, bootstrapped by hand at 2.0.0, so `bun add @ultimat3/scraping` resolves. Milestones 0–10 are ✅; milestone 11 is 🚧, open on its two-platform deploy proof. Realtime tiers 1–2 ship; tier 3 (local-first) is not in 3.0.0. The 50k-socket forced-restart benchmark **is measured and committed** — first patch on the reconnected socket at p50 54.0s / p90 105.5s, on one node; delivery is a second run, 10,000 clients, 1,666,882 patches, 0 observed sequence gaps ([Realtime](Realtime)). Status markers come from [`docs/idea/14-roadmap.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/idea/14-roadmap.md). See [FAQ](FAQ). +`As of 2026-08`. Stable API — semver from here ([Upgrading](Upgrading)). The repository holds 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` and versions all 30 in lockstep — the current line is a major, 25 entries marked `BREAKING —` from a sweep that closed every known gap and no codemod ([Upgrading](Upgrading)); 2.0.0 was the first major and carried 33, 3.0.0 carried 10. Only the [footer](_Footer) stamps the number. **What `bunx create-ultimate myapp` installs is 3.0.0** `As of 2026-08-19`: npm's `latest` is 3.0.0 for all 30 workspaces, each published by the release workflow over OIDC with a provenance attestation. Resolve it, do not trust this line — `npm view @ultimat3/core version`, and `npm view @ultimat3/core@3.0.0 dist.attestations` for the attestation. **No publication holes**: `@ultimat3/scraping` was the last never-published package, bootstrapped by hand at 2.0.0, so `bun add @ultimat3/scraping` resolves. Milestones 0–10 are ✅; milestone 11 is 🚧, open on its two-platform deploy proof. Realtime tiers 1–2 ship; tier 3 (local-first) is not in 3.0.0. The 50k-socket forced-restart benchmark **is measured and committed** — first patch on the reconnected socket at p50 54.0s / p90 105.5s, on one node; delivery is a second run, 10,000 clients, 1,666,882 patches, 0 observed sequence gaps ([Realtime](Realtime)). Status markers come from [`docs/idea/14-roadmap.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/idea/14-roadmap.md). See [FAQ](FAQ). diff --git a/wiki/Home.md b/wiki/Home.md index 2ff5f5f0..0047de96 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -2,17 +2,17 @@ A full-stack, Bun-only, opinionated framework: Rails' philosophy applied to Bun + Postgres + SolidJS, where the primary developer is an AI agent and the secondary developer is a tired senior engineer working through their own AI agent and AI reviewer. -**`As of 2026-08`.** 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` — 30 in all — version in lockstep at **3.0.0**, a major: 10 changelog entries are marked `BREAKING —` from a five-agent bug sweep and none ships a codemod ([Upgrading](Upgrading)). 2.0.0 was the first major and carried 33. +**`As of 2026-08-20`.** 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` — 30 in all — versioned in lockstep, and the current line is a major: 25 changelog entries are marked `BREAKING —` from a sweep that closed every known gap, and none ships a codemod ([Upgrading](Upgrading)). 2.0.0 was the first major and carried 33; 3.0.0 carried 10. The **[footer](_Footer)** is the only page here that stamps a version — one release bumps one line, and a stamp on a second page is 46 hand-copies of one fact. -**Repository and registry are both at 3.0.0** `As of 2026-08-19`. `bunx create-ultimate myapp` installs 3.0.0; all 30 workspaces resolve at it, each published by [`release.yml`](https://github.com/developerz-ai/ultimate/blob/main/.github/workflows/release.yml) over OIDC with a provenance attestation. Resolve it rather than believing this sentence: +**Repository and registry agree, and there are no publication holes** `As of 2026-08-20`. `bunx create-ultimate myapp` installs whatever `latest` is; all 30 workspaces resolve at that one version, each published by [`release.yml`](https://github.com/developerz-ai/ultimate/blob/main/.github/workflows/release.yml) over OIDC with a provenance attestation, and [`registry-audit.yml`](https://github.com/developerz-ai/ultimate/blob/main/.github/workflows/registry-audit.yml) files an issue on the day that stops being true. Resolve it rather than believing this sentence: | Fact | Read it yourself | |---|---| | what `latest` is | `npm view @ultimat3/core version` | -| that a tarball is attested | `npm view @ultimat3/core@3.0.0 dist.attestations` | -| who published it | `npm view @ultimat3/core@3.0.0 _npmUser` | +| that a tarball is attested | `npm view @ultimat3/core dist.attestations` | +| who published it | `npm view @ultimat3/core _npmUser` | -**Fixed in 2.0.0** and **fixed in 3.0.0** both mean fixed in a release you can install; the "on 1.2.0, do X" column beside such a row is for readers still pinned to 1.x, not a current workaround. **No publication holes** — `@ultimat3/scraping` was the last never-published package, bootstrapped by hand at 2.0.0, so `bun add @ultimat3/scraping` resolves and browser automation no longer needs a checkout. 3.0.0 is the first release the workflow published since 1.2.0: 2.0.0 went out by hand and is the one release whose tarballs carry no attestation at all, and 1.0.0 was the manual bootstrap. Milestones 0–10 are ✅; milestone 11 is 🚧, still open on the two-platform deploy proof — 1.1.0 gave a scaffolded app a real deployable artifact, which is progress toward it, not the proof. +**Fixed in 2.0.0**, **in 3.0.0** and **in 4.0.0** all mean fixed in a release you can install; the "on 1.2.0, do X" column beside such a row is for readers still pinned to 1.x, not a current workaround. **No publication holes** — `@ultimat3/scraping` was the last never-published package, bootstrapped by hand at 2.0.0, so `bun add @ultimat3/scraping` resolves and browser automation no longer needs a checkout. 3.0.0 and 4.0.0 both went out through the workflow, each tarball attested; 2.0.0 went out by hand and is the one release whose tarballs carry no attestation at all, and 1.0.0 was the manual bootstrap. Milestones 0–10 are ✅; milestone 11 is 🚧, still open on the two-platform deploy proof — 1.1.0 gave a scaffolded app a real deployable artifact, which is progress toward it, not the proof. The realtime restart numbers are **measured and committed**, in two halves that answer different questions ([`scripts/bench/results/`](https://github.com/developerz-ai/ultimate/tree/main/scripts/bench/results)). **Reachability:** 50,000 real WebSocket clients against a **single** `sync` node over `InProcessTransport`, `SIGKILL`ed with no drain — all 50,000 reconnected, 49,981 received a channel patch inside the window, p50 **54.0s** / p90 **105.5s** / max **145.7s**, and 156,851 connect attempts shed by the `AcceptBudget` before any query path. That times the first patch on the reconnected socket and nothing after it; it was published as "time-to-consistent" until 2026-08 and could not see a lost patch, so the name changed and the timings did not. **Delivery:** 10,000 clients, same forced restart, a probe every 200ms — **1,666,882 channel patches received, 0 observed sequence gaps**, `As of 2026-08` the only run with delivery accounting. That counts holes between frames a connection actually received, so it is a lower bound: no client observed a lost frame, which is not the same claim as none was lost. Both are **per-node recovery**: neither crossed NATS, so neither is a multi-node result and neither is a throughput figure → [Realtime](Realtime). This wiki is the only public documentation surface; there is no separate site. diff --git a/wiki/Jobs-And-Workflows.md b/wiki/Jobs-And-Workflows.md index f0fc10b2..ddcb4406 100644 --- a/wiki/Jobs-And-Workflows.md +++ b/wiki/Jobs-And-Workflows.md @@ -174,14 +174,14 @@ export interface JobDriver { The three optional members degrade rather than refuse: no `introspect` is `x jobs ls` with nothing to list, no `backfills` is a `backfill()` pass that runs with no bookkeeping, and no `close` is a driver holding nothing to hand back. -Two implementations ship in 1.0.0. Two more are **not in 3.0.0** — interface-complete stubs, so an app typechecks against them, and every method throws `X_NOT_IMPLEMENTED` with a runnable `fix:` rather than silently dropping a job. +Two implementations ship in 1.0.0. Two more are **not in 4.0.0** — interface-complete stubs, so an app typechecks against them, and every method throws `X_NOT_IMPLEMENTED` with a runnable `fix:` rather than silently dropping a job. | Driver | Status `As of 2026-08` | When | Trade-off | |---|---|---|---| | `postgres` (default) | **shipped** | always, up to ~thousands of jobs/sec. `x dev` runs it too, against the embedded PGlite | outbox is free (same DB, same tx); `SELECT ... FOR UPDATE SKIP LOCKED` claiming; zero extra infra | | `memory` | **shipped**, not a `jobs.driver` value | tests and fixtures — reached through `createMemoryDriver()`, and as `x jobs drain --to memory` | in-process; nothing survives a restart | -| `redis` | **not in 3.0.0 — throws `X_NOT_IMPLEMENTED`** | high-throughput, short jobs | would need the outbox relay; loses "queue state in one backup" | -| `nats` | **not in 3.0.0 — throws `X_NOT_IMPLEMENTED`** | very high fanout, multi-region, JetStream retention | strongest delivery semantics, most operational surface | +| `redis` | **not in 4.0.0 — throws `X_NOT_IMPLEMENTED`** | high-throughput, short jobs | would need the outbox relay; loses "queue state in one backup" | +| `nats` | **not in 4.0.0 — throws `X_NOT_IMPLEMENTED`** | very high fanout, multi-region, JetStream retention | strongest delivery semantics, most operational surface | `jobs.driver` in `app.config.ts` accepts `'postgres' | 'redis' | 'nats'` — and only `'postgres'` runs. Setting it to `redis` or `nats` typechecks and boots, then throws on the first enqueue: deliberate, and why the stubs exist instead of an absent export. @@ -226,7 +226,7 @@ Every command supports `--json`. See [CLI reference](CLI-Reference). | `X_IDEMPOTENCY_CONFLICT` | same key, different payload, or still in flight | fresh key for a different payload; otherwise retry after the first settles | | `X_DRAINING` | claim attempted on a worker that received SIGTERM | none — the job stays queued and another worker claims it | | `X_FORBIDDEN` | the job's actor fails the originating action's policy | grant the permission, or enqueue as a system actor | -| `X_NOT_IMPLEMENTED` | the `redis` or `nats` driver was reached — neither is in 3.0.0 | set `jobs.driver: 'postgres'` in `app.config.ts` (it is already the default) | +| `X_NOT_IMPLEMENTED` | the `redis` or `nats` driver was reached — neither is in 4.0.0 | set `jobs.driver: 'postgres'` in `app.config.ts` (it is already the default) | Full index: [Error codes](Error-Codes). Verbatim error shapes live in each package's `src/errors.ts`. diff --git a/wiki/Known-Gaps.md b/wiki/Known-Gaps.md index d50940e9..1b774ce1 100644 --- a/wiki/Known-Gaps.md +++ b/wiki/Known-Gaps.md @@ -9,18 +9,19 @@ today. [Open by decision](#open-by-decision) will not be fixed and says why. [No is unfinished work, not a defect. [Closed](#closed) is history — every row there carries the release that fixed it and the workaround for anyone still pinned below it. -**The installable release is `3.0.0`.** Resolve it yourself rather than reading it here: +**Only the [footer](_Footer) stamps the installable version.** Resolve it yourself rather than reading it here: ```bash npm view @ultimat3/core version ``` -A row that says **fixed on `main`** is fixed in the repository and in **no published release**. If you -are on 3.0.0 from npm, treat those rows as open and take the workaround. The next release is a major — -[`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md)'s `[Unreleased]` -section is the source of truth for what is in it. +A row that says **fixed on `main`** is fixed in the repository and in **no published release**; if you +are on an earlier version, treat those rows as open and take the workaround. `As of 2026-08-20` there +are none — 4.0.0 published every one of them, and `[Unreleased]` in +[`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md) is empty. That +section, not this page, is the source of truth for what is in the next release. -**Publication is not a gap.** All 30 workspaces are on the registry `As of 2026-08-19`; +**Publication is not a gap.** All 30 workspaces are on the registry `As of 2026-08-20`, checked by `scripts/registry-audit.ts` daily; `@ultimat3/scraping` was the last never-published package and was bootstrapped by hand at 2.0.0 ([`PUBLISHING.md`](https://github.com/developerz-ai/ultimate/blob/main/PUBLISHING.md) step 1). `bun add @ultimat3/scraping` resolves — check it with `npm view @ultimat3/scraping version`. @@ -73,16 +74,16 @@ the release that fixed it. Upgrading is the fix, and the breaking entries are th | Gap | Fixed in | If you are pinned below that | |---|---|---| -| `on delete` was declared and reached no SQL | **`main`** | `references(() => orgs.id, { onDelete: 'cascade' })` type-checked and the rule was dropped one layer below the declaration, so no generated `add constraint` ever spelled one and a drift check had nothing truthful to compare. Now: `ColumnDescription`/`ReferenceDescription` carry `onDelete`, `addForeignKey` writes the clause, and a rule changed on either side is `changed-foreign-key` drift whose `fix:` is the drop/add pair. On 3.0.0, add the clause to the `add constraint` the generator emitted, **before** applying the migration: `… references "orgs" ("id") on delete cascade;`. Editing it after it applies moves the checksum → `X_MIGRATION_CONFLICT` | -| A `references()` **removed** from a column emitted nothing | **`main`** | the key stayed on the database, the snapshot beside it recorded `foreignKeys: []` — actively denying a constraint the catalog held — and `compareForeignKeys` judges the declared side, so no check could see it. Now `foreignKeyPlan` emits the `drop constraint` in `up` and the `add` in `down`, naming the constraint the **previous snapshot** recorded rather than the name the generator would have picked. On 3.0.0, write the drop into the next generated migration by hand before applying it: `alter table "posts" drop constraint "posts_org_id_fkey";` | -| Drift ignored an index's direction and its predicate's presence | **`main`** | a `desc` index rebuilt ascending served a feed's newest page off the wrong end, and a partial index recreated as a total one silently widened the constraint — both read `ok: true`. Both are compared now; `asc` is normalised to `null` first, since Postgres stores an ascending index as not-descending. The predicate **text** is still uncompared and always will be — see [Open by decision](#open-by-decision). On 3.0.0, `x db gen` compares all five fields, so regenerate and inspect the diff | -| An unknown flag was refused **before** a planned command could answer honestly | **`main`** | the parser read flags against the spec first, so `x logs tail --follow` reported `X_CLI_BAD_FLAG` instead of `X_NOT_IMPLEMENTED`. A planned command now answers with its own status whatever flags it was given; a **shipped** command still refuses an unknown flag, which is correct — `x env` is shipped and declares `--json`, `--help`, `--cwd` and `--verbose`, so anything else is `X_CLI_BAD_FLAG` for the ordinary reason that no such flag exists. On 3.0.0, run the flagless form of a planned command to see the real message | -| `x deploy --method helm` threw `X_NOT_IMPLEMENTED` in a scaffolded app | **`main`** | `x new` wrote no `docker/helm`, and the command carried a "does this build implement helm?" branch over a build that implemented it completely. `x new` now writes the chart — 8 files, `Chart.yaml` + `values.yaml` + 6 templates — and the lying branch is deleted, so a missing chart is helm's own error. The framework repo's own `docker/helm` carries two more templates the scaffold does not (`pdb.yaml`, `servicemonitor.yaml`). On 3.0.0, copy [`docker/helm`](https://github.com/developerz-ai/ultimate/tree/main/docker/helm) from the framework repo, or use `--method compose` | -| `.env.development` **and `.env.production`** shipped in the image | **`main`** | the scaffold's `.dockerignore` excluded `.env` and `.env.*.local` — neither pattern matches `.env.production`, which is the file `docker-compose.prod.yml`'s `env_file:` tells the operator to create. This page said the leak was "harmless as generated"; **that was false**, and a real `docker build` proved both files land in a layer. Now `**/.env` + `**/.env.*` + `!**/.env.example`, in the framework's file, both tracked apps' and the one `x new` writes. On 3.0.0, add those three lines to `docker/Dockerfile.dockerignore` yourself — and rebuild, because an image already built still carries them | -| `realtime.heartbeatMs` was read by nothing | **`main`, and BREAKING** | the key sat in `RealtimeConfig` with a default of 15 000 and no reader anywhere. It is **deleted**: `RealtimeConfig` is `{ enabled, tier, transport, urlEnv }`, the socket beat is the client's `new LiveClient({ heartbeatMs })` and the presence beat is derived (`PresenceRegistry.heartbeatMs` is `max(1000, floor(ttlMs / 3))`). **There is no runtime refusal** — `section()` copies every own key of the patch and `validate()` checks only named fields, so an app that keeps the key keeps it silently. The failure is at **typecheck**: `TS2353`, excess property on `Input` — and an app that builds its config object into a variable first loses excess-property checking and gets no error at all. On 3.0.0, delete the key; setting it changes no behaviour either way | +| `on delete` was declared and reached no SQL | **4.0.0** | `references(() => orgs.id, { onDelete: 'cascade' })` type-checked and the rule was dropped one layer below the declaration, so no generated `add constraint` ever spelled one and a drift check had nothing truthful to compare. Now: `ColumnDescription`/`ReferenceDescription` carry `onDelete`, `addForeignKey` writes the clause, and a rule changed on either side is `changed-foreign-key` drift whose `fix:` is the drop/add pair. On 3.0.0, add the clause to the `add constraint` the generator emitted, **before** applying the migration: `… references "orgs" ("id") on delete cascade;`. Editing it after it applies moves the checksum → `X_MIGRATION_CONFLICT` | +| A `references()` **removed** from a column emitted nothing | **4.0.0** | the key stayed on the database, the snapshot beside it recorded `foreignKeys: []` — actively denying a constraint the catalog held — and `compareForeignKeys` judges the declared side, so no check could see it. Now `foreignKeyPlan` emits the `drop constraint` in `up` and the `add` in `down`, naming the constraint the **previous snapshot** recorded rather than the name the generator would have picked. On 3.0.0, write the drop into the next generated migration by hand before applying it: `alter table "posts" drop constraint "posts_org_id_fkey";` | +| Drift ignored an index's direction and its predicate's presence | **4.0.0** | a `desc` index rebuilt ascending served a feed's newest page off the wrong end, and a partial index recreated as a total one silently widened the constraint — both read `ok: true`. Both are compared now; `asc` is normalised to `null` first, since Postgres stores an ascending index as not-descending. The predicate **text** is still uncompared and always will be — see [Open by decision](#open-by-decision). On 3.0.0, `x db gen` compares all five fields, so regenerate and inspect the diff | +| An unknown flag was refused **before** a planned command could answer honestly | **4.0.0** | the parser read flags against the spec first, so `x logs tail --follow` reported `X_CLI_BAD_FLAG` instead of `X_NOT_IMPLEMENTED`. A planned command now answers with its own status whatever flags it was given; a **shipped** command still refuses an unknown flag, which is correct — `x env` is shipped and declares `--json`, `--help`, `--cwd` and `--verbose`, so anything else is `X_CLI_BAD_FLAG` for the ordinary reason that no such flag exists. On 3.0.0, run the flagless form of a planned command to see the real message | +| `x deploy --method helm` threw `X_NOT_IMPLEMENTED` in a scaffolded app | **4.0.0** | `x new` wrote no `docker/helm`, and the command carried a "does this build implement helm?" branch over a build that implemented it completely. `x new` now writes the chart — 8 files, `Chart.yaml` + `values.yaml` + 6 templates — and the lying branch is deleted, so a missing chart is helm's own error. The framework repo's own `docker/helm` carries two more templates the scaffold does not (`pdb.yaml`, `servicemonitor.yaml`). On 3.0.0, copy [`docker/helm`](https://github.com/developerz-ai/ultimate/tree/main/docker/helm) from the framework repo, or use `--method compose` | +| `.env.development` **and `.env.production`** shipped in the image | **4.0.0** | the scaffold's `.dockerignore` excluded `.env` and `.env.*.local` — neither pattern matches `.env.production`, which is the file `docker-compose.prod.yml`'s `env_file:` tells the operator to create. This page said the leak was "harmless as generated"; **that was false**, and a real `docker build` proved both files land in a layer. Now `**/.env` + `**/.env.*` + `!**/.env.example`, in the framework's file, both tracked apps' and the one `x new` writes. On 3.0.0, add those three lines to `docker/Dockerfile.dockerignore` yourself — and rebuild, because an image already built still carries them | +| `realtime.heartbeatMs` was read by nothing | **4.0.0**, and BREAKING | the key sat in `RealtimeConfig` with a default of 15 000 and no reader anywhere. It is **deleted**: `RealtimeConfig` is `{ enabled, tier, transport, urlEnv }`, the socket beat is the client's `new LiveClient({ heartbeatMs })` and the presence beat is derived (`PresenceRegistry.heartbeatMs` is `max(1000, floor(ttlMs / 3))`). **There is no runtime refusal** — `section()` copies every own key of the patch and `validate()` checks only named fields, so an app that keeps the key keeps it silently. The failure is at **typecheck**: `TS2353`, excess property on `Input` — and an app that builds its config object into a variable first loses excess-property checking and gets no error at all. On 3.0.0, delete the key; setting it changes no behaviour either way | | `@ultimat3/action`'s `stableStringify` folded `-0`, `NaN` and `Infinity` together | **3.0.0** | this page asked for work that was already done. The hash canonicalizer was split out and moved **down**: `canonicalJson` and `fingerprint` live in `@ultimat3/core` (tier 0), are injective, and give `NaN`, `±Infinity` and `-0` bare tokens of their own plus tagged forms for `Date`, `Map` and `Set`. `requestHash` is `fingerprint(input)`, the job dedupe key is `action::`, and `@ultimat3/query`'s copy was **deleted** rather than fixed. `stableStringify` survives as the OpenAPI **document** serializer alone, where emitting valid JSON is the requirement | | `.job()` produced a handle nothing could enqueue | **3.0.0** | `agentJob()` in `@ultimat3/ai` is the shipped bridge — an agent as durable, resumable, budgeted background work, with the queue accepting the handle. Pinned by `packages/ai/src/agent-job.test.ts`, whose test is named for this gap: *"agentJob() produces a handle the queue accepts, where .job() never could"* | -| A branch reaper could drop **another app's** databases | **`main`** | `listBranches()` walks `pg_database` for the whole server, so two Ultimate apps on one Postgres plus one nightly sweep was the other app's branches dropped by a `DROP DATABASE` nobody asked for. The marker is now `:` — `BranchInfo` carries `base` — and `reapBranches` skips any branch whose base is not this database. A **pre-3.x** marker records no base, so it is skipped too, never dropped: self-healing with no migration, because the next `createBranch` writes the base down. On 3.0.0, run the reaper only against a server this app owns | +| A branch reaper could drop **another app's** databases | **4.0.0** | `listBranches()` walks `pg_database` for the whole server, so two Ultimate apps on one Postgres plus one nightly sweep was the other app's branches dropped by a `DROP DATABASE` nobody asked for. The marker is now `:` — `BranchInfo` carries `base` — and `reapBranches` skips any branch whose base is not this database. A **pre-3.x** marker records no base, so it is skipped too, never dropped: self-healing with no migration, because the next `createBranch` writes the base down. On 3.0.0, run the reaper only against a server this app owns | | `x db branch ls` could not see a branch made before the `psql` shell-out was removed | 2.0.0 | the old path issued `CREATE DATABASE … TEMPLATE` through `psql` and wrote no comment, so a branch made by a 1.2.0 or earlier CLI is absent from `ls` — and `drop` may only remove what `ls` shows. Nothing back-fills the comment. Drop it by hand from the string the refusal names: `psql "$DATABASE_URL" -c 'DROP DATABASE "_branch_"'`, where `` is the branch name with every character outside `[A-Za-z0-9_]` replaced by `_` | | MCP tool names were published in `snake_case` and served verbatim | 2.0.0, and BREAKING | `openapi.json` carried `"mcpTool": "publish_post"` while the only name `tools/call` accepts is the export name, `publishPost` — 15 of the 17 `mcpTool` values in the two tracked apps' committed specs named no served tool. `toToolName` is deleted from `@ultimat3/action` and `@ultimat3/query`, all three publishers spell the export name, and `packages/mcp/src/cross-surface.test.ts` drives a `tools/call` with the name OpenAPI published. On 1.2.0, ignore the published value and call the **export name** — `x actions list --json` prints it as `name` → [MCP and AI](MCP-And-AI) | | Shared cache tier invalidation `DEL`ed keys it never declared in `KEYS` | 2.0.0 | it failed on **Dragonfly** and on **Redis Cluster** — a cluster cannot route a key it was not told about. The script returns the member list and the tier deletes value keys client-side, one key per `DEL`, so every delete is slot-local. On 1.2.0, single-node Redis, or a cache tier that is not the shared one → [Caching and invalidation](Caching-And-Invalidation) | @@ -97,12 +98,12 @@ the release that fixed it. Upgrading is the fix, and the breaking entries are th | Composite indexes emitted one mangled column name | 2.0.0 | `indexes: [{ on: ['orgId','createdAt'] }]` emitted `on "todos" ("org_id_created_at")`, which will not apply. The description carries the column list, the predicate and the direction, and the generator spells all three. On 1.2.0, write the index by hand | | A migration with two statements failed to apply | 2.0.0 | *cannot insert multiple commands into a prepared statement* on the embedded database, and against a server the moment the text carried a bound value. `migrate()` and `rollback()` split with `statementsOf()` and send one statement at a time inside the same transaction. On 1.2.0, one statement per migration file | | Every generator but `x g resource` imported files only `x g resource` wrote | 2.0.0 | the templates opened with `import * as repo from '../repo'`, so the generated file did not load: `X_CLI_UNEXPECTED` from every registry command, `--feature ` included. Each generator composes the slice modules its own source imports now, and a module the slice already has is skipped rather than overwritten, `--force` included. On 1.2.0: `x g resource ` **first**, then the narrower generator with `--feature ` | -| Jobs and tasks registered anonymously | **`main`** | a fresh scaffold had no `apps/web/api/index.ts`, so they registered as `anonymous-job-2` / `anonymous-task-1`. `x new` writes it. On 3.0.0, add `defineApi` yourself → [4 · Jobs and realtime](Tutorial-04-Jobs-And-Realtime) | -| Generated tests landed in the wrong `x verify` step | **`main`** | `contractTest` / `liveTest` / `jobTest` inside a plain `*.test.ts` ran under `unit`, and `x test contract` answered `X_TEST_NO_FILES`. The generators emit the typed filename now — `.contract.test.ts` for `x g action`/`x g mutator`, `.live.test.ts` for `x g query --live`, `.job.test.ts` for `x g job`, `x g task` and `x g backfill`. On 3.0.0, rename by hand: the filename is the type | -| A disk registered under a name that is not its driver's 404'd its own signed URLs | **`main`** | `localDriver` minted `/_storage//` while the mounted route resolves the segment through the **registry**, so a disk registered as `uploads` signed a URL nothing served. `StorageDriver.registerAs(diskName)` is called by `defineStorage` at boot and the driver hangs its URLs off that; `signedUrlBase` is read off the driver by the verifying half, so the minter and the verifier cannot state it twice. On 3.0.0, register the local disk under the driver's own name (`local`) | -| The robots.txt read left from the worker's IP while every page load left through the proxy | **`main`** | two client identities presented to one origin — and an origin reachable **only** through the proxy answered nothing, which the gate reads as "no restrictions". `ScrapeSession.proxy` reports the exit the driver resolved, and `createRobotsGate` takes it as a **resolver** (`proxy: () => sessionProxy`) because the gate is an argument to `driver.open()` while the exit is decided inside it. The read also carries the run's deadline, its cancellation and a 500 KiB cap. On 3.0.0, use `robots: { ignore: '' }` on a proxied scrape, or accept that an unreadable robots.txt reads as allow | -| A malformed request body echoed itself into the 422 | **`main`** | the parser's own message quotes the bytes it choked on, and it reached the `cause:` and the log store through `String(error)`. The caller-facing half now names the format alone — *could not parse the body as JSON* — and the parser's message rides in `meta` through `renderThrowable`. On 3.0.0, do not log a 422's `cause` verbatim into anything a third party reads | -| `createRateLimiter({ now })` | **`main`, and BREAKING** | renamed to `({ clock })`, the same `Clock` shape `createRequestContext`'s `init.clock` takes — a second spelling of "what time is it" is a second way to set one number. **The edit:** `createRateLimiter({ config, now: () => t })` becomes `createRateLimiter({ config, clock: { now: () => new Date(t) } })` | +| Jobs and tasks registered anonymously | **4.0.0** | a fresh scaffold had no `apps/web/api/index.ts`, so they registered as `anonymous-job-2` / `anonymous-task-1`. `x new` writes it. On 3.0.0, add `defineApi` yourself → [4 · Jobs and realtime](Tutorial-04-Jobs-And-Realtime) | +| Generated tests landed in the wrong `x verify` step | **4.0.0** | `contractTest` / `liveTest` / `jobTest` inside a plain `*.test.ts` ran under `unit`, and `x test contract` answered `X_TEST_NO_FILES`. The generators emit the typed filename now — `.contract.test.ts` for `x g action`/`x g mutator`, `.live.test.ts` for `x g query --live`, `.job.test.ts` for `x g job`, `x g task` and `x g backfill`. On 3.0.0, rename by hand: the filename is the type | +| A disk registered under a name that is not its driver's 404'd its own signed URLs | **4.0.0** | `localDriver` minted `/_storage//` while the mounted route resolves the segment through the **registry**, so a disk registered as `uploads` signed a URL nothing served. `StorageDriver.registerAs(diskName)` is called by `defineStorage` at boot and the driver hangs its URLs off that; `signedUrlBase` is read off the driver by the verifying half, so the minter and the verifier cannot state it twice. On 3.0.0, register the local disk under the driver's own name (`local`) | +| The robots.txt read left from the worker's IP while every page load left through the proxy | **4.0.0** | two client identities presented to one origin — and an origin reachable **only** through the proxy answered nothing, which the gate reads as "no restrictions". `ScrapeSession.proxy` reports the exit the driver resolved, and `createRobotsGate` takes it as a **resolver** (`proxy: () => sessionProxy`) because the gate is an argument to `driver.open()` while the exit is decided inside it. The read also carries the run's deadline, its cancellation and a 500 KiB cap. On 3.0.0, use `robots: { ignore: '' }` on a proxied scrape, or accept that an unreadable robots.txt reads as allow | +| A malformed request body echoed itself into the 422 | **4.0.0** | the parser's own message quotes the bytes it choked on, and it reached the `cause:` and the log store through `String(error)`. The caller-facing half now names the format alone — *could not parse the body as JSON* — and the parser's message rides in `meta` through `renderThrowable`. On 3.0.0, do not log a 422's `cause` verbatim into anything a third party reads | +| `createRateLimiter({ now })` | **4.0.0**, and BREAKING | renamed to `({ clock })`, the same `Clock` shape `createRequestContext`'s `init.clock` takes — a second spelling of "what time is it" is a second way to set one number. **The edit:** `createRateLimiter({ config, now: () => t })` becomes `createRateLimiter({ config, clock: { now: () => new Date(t) } })` | Reserved-but-unthrown error codes are listed in full under [Error codes → Reserved codes](Error-Codes#reserved-codes). Symptom-first triage is [Troubleshooting](Troubleshooting). diff --git a/wiki/Migrations-And-Backfills.md b/wiki/Migrations-And-Backfills.md index dce171a3..ead32481 100644 --- a/wiki/Migrations-And-Backfills.md +++ b/wiki/Migrations-And-Backfills.md @@ -14,7 +14,7 @@ One engine per concern. A **migration** changes the shape of a table — schema, ## Migrations: one engine, one ledger -**This page documents the code at 2.0.0 and the current release is 3.0.0** — `npm view @ultimat3/core version`, `As of 2026-08-19`. Three things shipped in 3.0.0 that this page does **not** yet describe: `x db seed` (run `x db seed --help`), `rollback({ steps: -1 })` reverting every migration but the last, and a migration deleted from the tree being invisible to the audit. Read [`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md)'s `3.0.0` section beside this page until it is rewritten. On a pin still at 1.x, take [Known gaps → `x db gen` / `x db migrate`](Known-Gaps) and its 1.1.0/1.2.0 workarounds instead. +**This page documents the code at 2.0.0 and two majors have shipped since** — `npm view @ultimat3/core version`, `As of 2026-08-20`. Three things shipped in 3.0.0 that this page does **not** yet describe: `x db seed` (run `x db seed --help`), `rollback({ steps: -1 })` reverting every migration but the last, and a migration deleted from the tree being invisible to the audit. **And four in 4.0.0**, three of which change generated DDL: `on delete` now reaches the emitted SQL — so any app that ever declared `references(…, { onDelete })` generates a **different** migration and should read `x db gen`'s diff before applying it — a `references()` *removed* from a column now emits a `drop constraint`, drift reports `changed-foreign-key` for a key whose rule moved, and the `x_jobs` idempotency index gains `tenant_id`. Read [`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md)'s `3.0.0` and `4.0.0` sections beside this page until it is rewritten, and [Upgrading](Upgrading) for the edits. On a pin still at 1.x, take [Known gaps → `x db gen` / `x db migrate`](Known-Gaps) and its 1.1.0/1.2.0 workarounds instead. `x db gen` and the `ROLE=migrate` release-phase container run the **same** engine — `packages/db`'s `migrate()`/`generateMigration()` — not two. **In 1.1.0** they did not: `x db gen`'s subcommands shelled out to `bunx drizzle-kit`, a second schema engine with its own journal, declared in no `package.json` and fetched unpinned at run time, which is why a 1.1.0 scaffold's own `bin/setup` fails. That shelling-out is gone from current source — `cmd-db.ts` calls `generateAppMigration` and `runMigrations` from `@ultimat3/db`/`@ultimat3/cli` directly, and the only remaining mention of `drizzle-kit` anywhere is a file header comment recording the history. diff --git a/wiki/Queries-And-Live-Queries.md b/wiki/Queries-And-Live-Queries.md index 23ad51d6..3b182d7d 100644 --- a/wiki/Queries-And-Live-Queries.md +++ b/wiki/Queries-And-Live-Queries.md @@ -2,7 +2,7 @@ A `query` is a read. `live: true` makes it subscribable. Never writes, never enqueues, never sends mail. -`As of 2026-08`. Stable API — semver from here ([Upgrading](Upgrading)). Tiers 1–2 of [Realtime](Realtime) ship; `persist: true` (tier 3, local-first) is not in 3.0.0. +`As of 2026-08`. Stable API — semver from here ([Upgrading](Upgrading)). Tiers 1–2 of [Realtime](Realtime) ship; `persist: true` (tier 3, local-first) is not in 4.0.0. ## The canonical shape @@ -23,7 +23,7 @@ export const liveFeed = query({ | `input` | yes | Standard Schema; `t` re-exported from `@ultimat3/query`, so a query file imports one package. The shipped provider is `@ultimat3/schema`'s dependency-free builtin — ArkType, Zod and Valibot are optional swaps behind `configureSchemaProvider`, and no adapter ships. Parsed before `policy`, before `sql`. Becomes the GET query string, the client hook argument, and the MCP tool's JSON Schema | | `policy` | yes | `can('')`, optionally with a predicate over `{ input, actor }`. Evaluated at HTTP call, client hook, subscribe, **and per delivered row** | | `live` | no — default `false` | registers the query with the incremental matcher. Requires a deterministic, bounded `sql` | -| `persist` | no — default `false` | tier 3. Swaps the client result store from memory to IndexedDB and makes the mutator queue durable. Implies `live: true`. Not in 3.0.0 | +| `persist` | no — default `false` | tier 3. Swaps the client result store from memory to IndexedDB and makes the mutator queue durable. Implies `live: true`. Not in 4.0.0 | | `sql` | yes | `(input) => SqlSource`. `from()` (`@ultimat3/query`) wraps an already-resolved `@ultimat3/entity` repo call and restates `where`/`orderBy`/`limit` for the matcher to read back; `select`/`preload` happen inside that repo call, before `from()` ever sees a row. No ORM in the graph. SQL-transparent: `toSQL()` prints the statement verbatim so an agent can read it and self-correct | | `mcp` | no — default not exposed | `{ expose: true, description }` makes the read an MCP tool. Opt-in, unlike an action: a read hands rows to an agent, so silence exposes nothing | | `mcp.visibleTo` | no | roles that may see the projected tool; a caller whose role is not named gets ToolNotFound, never Forbidden — the policy still decides every call | diff --git a/wiki/Realtime.md b/wiki/Realtime.md index dda4c43f..ee28ef8c 100644 --- a/wiki/Realtime.md +++ b/wiki/Realtime.md @@ -2,7 +2,7 @@ Three tiers, one ladder. Same mutator shape at every rung — climbing is a config change, never a rewrite. -`As of 2026-08`. Stable API — semver from here ([Upgrading](Upgrading)). Tiers 1–2 ship. Tier 3 (local-first) is not in 3.0.0. +`As of 2026-08`. Stable API — semver from here ([Upgrading](Upgrading)). Tiers 1–2 ship. Tier 3 (local-first) is not in 4.0.0. ## The ladder diff --git a/wiki/Tutorial-04-Jobs-And-Realtime.md b/wiki/Tutorial-04-Jobs-And-Realtime.md index 3eadf014..d2b1e38a 100644 --- a/wiki/Tutorial-04-Jobs-And-Realtime.md +++ b/wiki/Tutorial-04-Jobs-And-Realtime.md @@ -139,7 +139,7 @@ A dead job is never filtered out of view. | `retry --from-step ` | drops that step so it re-executes; everything before it replays from storage | | `drain --to memory\|redis\|nats` | moves `ready`/`delayed`/`suspended` jobs to another driver; enqueues on the target **before** acking the source | -The Redis and NATS **job** drivers are not in 3.0.0 — each throws `X_NOT_IMPLEMENTED` behind an interface that already ships, rather than pretending to work. Postgres is the shipped driver, and it is the one `x dev` boots. +The Redis and NATS **job** drivers are not in 4.0.0 — each throws `X_NOT_IMPLEMENTED` behind an interface that already ships, rather than pretending to work. Postgres is the shipped driver, and it is the one `x dev` boots. ## A live query @@ -230,7 +230,7 @@ bunx x dev --once --port 3100 Both are per-node recovery — neither run crossed NATS, so neither is a multi-node result nor a throughput figure. Detail and limits: [Realtime](Realtime). -Realtime tier 3 (local-first, `persist: true`) is not in 3.0.0. +Realtime tier 3 (local-first, `persist: true`) is not in 4.0.0. ## Next diff --git a/wiki/Tutorial-06-Growing-Up.md b/wiki/Tutorial-06-Growing-Up.md index d2665bce..c15aa857 100644 --- a/wiki/Tutorial-06-Growing-Up.md +++ b/wiki/Tutorial-06-Growing-Up.md @@ -109,7 +109,7 @@ Point `OTEL_EXPORTER_OTLP_ENDPOINT` at your collector's **HTTP** receiver, `:431 | Concern | Interface | Decided by | |---|---|---| | rows | `@ultimat3/db` · `DbClient` | `DATABASE_URL` — unset is PGlite, and PGlite is `x dev` only | -| job queue | `@ultimat3/jobs` · `JobDriver` | `jobs.driver: 'postgres'`. Redis and NATS drivers are interface-complete stubs that throw `X_NOT_IMPLEMENTED` — not in 3.0.0 | +| job queue | `@ultimat3/jobs` · `JobDriver` | `jobs.driver: 'postgres'`. Redis and NATS drivers are interface-complete stubs that throw `X_NOT_IMPLEMENTED` — not in 4.0.0 | | realtime fanout | `@ultimat3/realtime` · `Transport` | `NATS_URL` | | change feed | `@ultimat3/realtime` · `ChangeFeed` | `REPLICATION_*` | | cache | `@ultimat3/cache` · `CacheTier` | `cache.tiers` + `REDIS_URL` | @@ -141,7 +141,7 @@ Named rather than left to be discovered: | a custom-metrics adapter, which the chart's HPAs need and the framework never ships | 3–4 | | `x logs` planned — `X_NOT_IMPLEMENTED`, with `x dev` → the `/_x` timeline panel as its fix | any | | Redis and NATS **job** drivers throw `X_NOT_IMPLEMENTED` | any | -| realtime tier 3 (local-first, `persist: true`), the plugin API, multi-region replication | not in 3.0.0 | +| realtime tier 3 (local-first, `persist: true`), the plugin API, multi-region replication | not in 4.0.0 | Each sits behind an interface that ships today and fails loudly, rather than pretending to work. The full list, with a workaround per row: [Known gaps](Known-Gaps). diff --git a/wiki/Upgrading.md b/wiki/Upgrading.md index 6b71f8c0..3ba4820d 100644 --- a/wiki/Upgrading.md +++ b/wiki/Upgrading.md @@ -2,25 +2,77 @@ **`As of 2026-08`. Semver applies from here.** A breaking change to a documented API needs a major. Every `@ultimat3/*` version is pinned exactly and moves in lockstep — never mix versions. -**There are two majors to cross.** [`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md) is the source; neither ships a codemod, so every entry is a manual edit the entry itself names. +**There are three majors to cross.** [`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md) is the source; none ships a codemod, so every entry is a manual edit the entry itself names. | From → to | Breaking entries | Read | |---|---|---| | 1.x → 2.0.0 | **33** | the `2.0.0` section, in order | | 2.0.0 → 3.0.0 | **10**, all from a five-agent bug sweep | the `3.0.0` section, in order | -| 1.x → 3.0.0 | **43** | both sections, oldest first | +| 3.0.0 → 4.0.0 | **25**, from a sweep that closed every known gap | the `4.0.0` section, in order | +| 1.x → 4.0.0 | **68** | all three sections, oldest first | Each entry changes a surface the table below covers. -> **The pin to move to is 3.0.0** `As of 2026-08-19`. All 30 workspaces resolve at it — 29 `@ultimat3/*` plus the unscoped `create-ultimate`, `@ultimat3/scraping` and `@ultimat3/flags` included — and every 3.0.0 tarball was published by the release workflow with a provenance attestation. Resolve before you pin, never take it from this page: +> **Move to whatever `latest` is** — only the [footer](_Footer) stamps the number, because a version written into a page goes stale on the next tag. All 30 workspaces resolve at one version — 29 `@ultimat3/*` plus the unscoped `create-ultimate`, `@ultimat3/scraping` and `@ultimat3/flags` included — and every tarball since 3.0.0 was published by the release workflow with a provenance attestation. Resolve before you pin, never take it from this page: | Check | Command | Answer that means "go" | |---|---|---| -| what `latest` is | `npm view @ultimat3/core version` | `3.0.0` | -| that a package resolves at it | `npm view @ultimat3/scraping@3.0.0 version` | `3.0.0`, not `E404` | -| that the tarball is attested | `npm view @ultimat3/core@3.0.0 dist.attestations` | a `provenance` object | +| what `latest` is | `npm view @ultimat3/core version` | the version you are pinning | +| that a package resolves at it | `npm view @ultimat3/scraping@ version` | that version, not `E404` | +| that the tarball is attested | `npm view @ultimat3/core dist.attestations` | a `provenance` object | | every name that must move together | `bun run scripts/release-workflow.ts --json` | the 30 derived names — check each | +## 3.0.0 → 4.0.0, entry by entry + +Twenty-five `BREAKING —` entries. Most are one of two shapes: a **declaration nothing read**, deleted rather than implemented, and a **surface that answered the wrong thing**, corrected. Full rationale per row in [`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md)'s `4.0.0` section. + +**Start here — these three change behaviour whether or not you edit anything:** + +| Surface | The edit | +|---|---| +| `on delete` now reaches the generated SQL. Any app that ever declared `references(…, { onDelete })` generates **different DDL** | run `x db gen` and read the diff before migrating. Every `add constraint` this framework had ever emitted dropped the rule, so the database has been refusing deletes under a declared `cascade`. Drift also gains `changed-foreign-key`, whose `fix:` hands over a `drop constraint` / `add constraint` pair — `add constraint` alone is `42710` on a name already taken | +| `llm()`'s `cache.semantic.scope` receives `{ input, ctx }` and **defaults to the calling actor**, not `'global'` | `scope: (input) => input.orgId` → `scope: ({ ctx }) => ctx.actor.orgId ?? 'none'`, or delete `scope` and take the default. A semantic lookup is a cosine nearest-neighbour with no tenant predicate, so the old shared store answered one tenant with another tenant's completion — reproduced at similarity 1.0. A deliberately shared cache must now say so | +| `reapBranches()` skips branches whose base is not `current_database()` | none, and re-read it if you run two Ultimate apps on one Postgres: `listBranches()` walks `pg_database` for the whole server, so one nightly sweep was dropping the *other* app's branches. A pre-4.0 marker records no base and is now skipped rather than dropped; the next `createBranch` writes it down, so it self-heals with no migration | + +**Deleted because nothing read them** — in every case the edit is "delete the option": + +| Surface | The edit | +|---|---| +| `CaptureOptions.timeoutMs` and `CaptureRequest.timeout` (`@ultimat3/scraping`) | delete them. The port required a timeout, `page-over-target.ts` threaded it, and **no driver honoured it** | +| `ScrapeTarget.click`'s `index` parameter | delete it. It was unreachable from the public vocabulary — `ScrapeFrame.click` takes `(selector, options?)` and has no index — and the two drivers disagreed on it | +| `PrecacheAsset.critical` (`@ultimat3/pwa`) | delete it. `buildPrecacheManifest` never copied it, and the documented promise ("critical assets are precached even if large") was vacuous — there is no size filter at all | +| `PERIODIC_SYNC_TAG`, `BackgroundSyncOptions.periodicMinIntervalMs` (`@ultimat3/pwa`) | delete them. Periodic Background Sync was never implemented in any sense: no listener, no registration, no capability flag | +| `realtime.heartbeatMs` (`RealtimeConfig`) | delete the key — `RealtimeConfig` is now `{ enabled, tier, transport, urlEnv }`. The socket beat is `new LiveClient({ heartbeatMs })` (browser code, which cannot read server config) and the presence beat is derived. **There is no runtime refusal**: `section()` copies unknown keys through, so a stale key is silently inert | +| `@ultimat3/seo` no longer exports `extensionOf` | delete the import; `parseImageQuery` reads the format off the query | +| `@ultimat3/realtime` no longer exports `qidOf` or `canonicalJson` | change the import: `queryHash` from `@ultimat3/query`, `canonicalJson`/`fingerprint` from `@ultimat3/core`. **No live subscription re-keys** — the two spellings differed only on values JSON cannot carry | + +**Corrected, because they answered the wrong thing:** + +| Surface | The edit | +|---|---| +| `adminResource` no longer pluralises an entity name | set `path:` explicitly if you relied on the doubled URL. Every entity in both tracked apps is already named plural, so `entity('orgs')` was served at `/admin/orgses`. Which plural a name takes is an app's convention, not a mechanism the framework can own (axiom 8) | +| A local disk's signed URLs carry the **registered disk name**, not the driver kind | none, if you use `defineStorage` — it calls `registerAs(diskName)` at boot. A disk registered as `uploads` used to 404 every signature it had just written | +| `ordinal(value)` takes no locale | delete the second argument. It picked the plural category with your locale and appended the **English** suffix regardless, so `ordinal(1, 'de')` was `'1th'` | +| `registerFrameworkCatalog()` and `registerMailCatalog()` take no `locale` | delete the argument. `defineCatalogs` called them once per locale, seating the English-only catalog under **every** locale an app declared — an app shipping only `es` served English chrome with `isMiss` reading `false`, which is a fallback locale chain the i18n package forbids by name | +| `t.date` refuses a date-time with no offset and no `Z` | send `2026-08-19T10:00:00Z`. `2026-08-19T10:00` resolved against the **host process's** zone, so one wire value meant a different instant on each pod — reachable from a request through `coerceQuery`, and published as `format: 'date-time'`, which RFC 3339 requires an offset for | +| `in` with a non-array operand matches **no** rows on both drivers | pass an array. It matched one row in Postgres (the scalar was wrapped) and none in memory; `in` with a NULL in the list disagreed in the other direction, and the SQL now emits `(col in (…) or col is null)` | +| `isValidCron` / `parseCron` refuse an unsatisfiable day/month pair (`'0 0 30 2 *'`) | fix the expression; the refusal names the pair. It used to parse clean and then burn ~184ms of blocking CPU per tick in the scheduler's leader loop before throwing | +| `createRateLimiter({ now })` → `createRateLimiter({ clock })` | `{ config, now: () => t }` → `{ config, clock: { now: () => new Date(t) } }`. Callers that passed neither are unaffected | +| `requiresApp` is enforced by the dispatcher | none, unless a script matched on the old message. Outside an app, `x secrets set` and its siblings now answer `X_NOT_IN_APP` | +| `NackOptions.countsAsAttempt: false` no longer files a job `suspended` | none. "Do not burn an attempt" and "this is a `step.sleep` suspension" were one flag, so the worker's limiter and `job.concurrency` sheds pushed rows out of `ready` — and `queue_depth` / `queue_oldest_ready_seconds` under-reported because of it | +| A read whose input carries a `Date`, `Map` or `Set` gets a new cache key and cursor scope, **once** | none. `Object.keys(date)` is `[]`, so every date rendered `{}` and one key answered for every date window a read ever served. Affected cursors answer `X_CURSOR_INVALID` once with "request the first page again"; ordinary inputs are byte-identical | + +**Type-level, for hand-built literals and exhaustive switches:** + +| Surface | The edit | +|---|---| +| `ColumnDescription` / `ReferenceDescription` gain `onDelete: OnDelete \| null` | add the field to hand-built description literals (a test fixture, a custom generator). `null` is Postgres' `no action` and is the old behaviour | +| `DriftKind` gains `changed-foreign-key` | a `switch` over `DriftKind` with no `default` no longer compiles | +| `BranchInfo` gains `base: string \| null` | re-type if you built the shape by hand | +| Five generators write **typed** test filenames | re-run the generator, or rename by hand. `x verify` selects a suite by filename, so a generated `contractTest(…)` inside a plain `*.test.ts` ran under `unit` while `x test contract` answered `X_TEST_NO_FILES` — a step that passed by having nothing to run. `x g action`/`x g mutator` now also write `.contract.test.ts`, `x g query --live` writes `.live.test.ts`, and `x g job`/`x g task`/`x g backfill` write `.job.test.ts` | + +**One migration to run:** the `x_jobs` idempotency index gains the tenant. It was `(name, idempotency_key)` while the row already carried `tenant_id`. `x db migrate` applies it. + ## 2.0.0 → 3.0.0, entry by entry Ten `BREAKING —` entries, all from one bug sweep. Each was a documented surface that did nothing, or did the wrong thing; the fix is the edit named beside it. Full rationale per row in [`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md)'s `3.0.0` section. @@ -64,11 +116,11 @@ Ten `BREAKING —` entries, all from one bug sweep. Each was a documented surfac |---|---| | Pinned exact versions | no `^`, no `~`, in the framework or in a generated app. A range is a silent upgrade | | Lockstep releases | one release bumps all 30 packages — 29 `@ultimat3/*` plus the unscoped `create-ultimate` — to the same version. One version, one commit, one tag. A mixed set is unsupported | -| Published with provenance | npm via OIDC trusted publishing. Every 3.0.0 tarball carries an attestation; **2.0.0's do not** — that release went out by hand. Per version: `npm view @ultimat3/core@ dist.attestations` | +| Published with provenance | npm via OIDC trusted publishing. Every 3.0.0 and 4.0.0 tarball carries an attestation; **2.0.0's do not** — that release went out by hand. Per version: `npm view @ultimat3/core@ dist.attestations` | | Breaking changes land with codemods | if `x upgrade` cannot codemod it, the changelog carries the manual step | | Dependency upgrades are framework work | Solid is pinned to **`1.9.14`, the stable line** — Solid 2 is still prerelease (`2.0.0-beta.N`, DOM renderer split into `@solidjs/web`) and every app inherits whatever core this repo pins. Bumping it is a framework release, never an app-level `bun update`. There is no ArkType or Drizzle pin to carry: `@ultimat3/schema` ships dependency-free builtin validators (ArkType is an optional provider you adapt yourself) and `@ultimat3/entity` ships its own `postgresDriver()` | | Bun floor | `>=1.3`, target 2.0. Below the floor → `X_BUN_VERSION` | -| Not in 3.0.0, behind the interfaces that ship today | realtime tier 3 (`persist: true`, local-first), the plugin API, multi-region replication, and the Redis/NATS **job** drivers — the last throw `X_NOT_IMPLEMENTED` with a runnable `fix:` rather than pretending to work | +| Not in 4.0.0, behind the interfaces that ship today | realtime tier 3 (`persist: true`, local-first), the plugin API, multi-region replication, and the Redis/NATS **job** drivers — the last throw `X_NOT_IMPLEMENTED` with a runnable `fix:` rather than pretending to work | Do not upgrade a transitive dependency of a `@ultimat3/*` package by hand. Open an issue instead — the pin is deliberate. @@ -137,7 +189,7 @@ Server behavior on a stale build ID: Full detail: [PWA and offline](PWA-And-Offline). -## Migrating jobs between drivers — **not in 3.0.0** +## Migrating jobs between drivers — **not in 4.0.0** `jobs.driver` accepts **`postgres` \| `redis` \| `nats`**, and `postgres` is the only one implemented — `redis` and `nats` are interface-complete stubs that throw `X_NOT_IMPLEMENTED`. So **there is no driver migration to perform** `As of 2026-08`: `x jobs drain --to redis` constructs the target and fails on its first enqueue. @@ -160,7 +212,7 @@ Job code never changes across a driver: `steps` is a driver member, so step pers | From → to | Change | Notes | |---|---|---| | tier 1 → tier 2 | `live: true` on the query | needs a `replicator` role and `orderBy` + `limit` on the `sql` | -| tier 2 → tier 3 | `persist: true` on the query | not in 3.0.0. No new mutators, no new authz, no new server code | +| tier 2 → tier 3 | `persist: true` on the query | not in 4.0.0. No new mutators, no new authz, no new server code | | `memory` → `nats` transport | `realtime.transport`, and **`realtime.urlEnv`** — the env *key name*, not a URL. There is no `realtime.url` field | roll `sync` and `replicator`; clients reconnect with server-directed backoff. What actually decides the transport at boot is **`NATS_URL` being set**: `selectTransport(env)` never reads `config.realtime.transport`, so the config field documents intent and the env var makes the switch ([Configuration](Configuration)) | ## Where the facts live From 073cbd5c281bb4106ce0a755cf9e4963c1dc190c Mon Sep 17 00:00:00 2001 From: sebi Date: Thu, 20 Aug 2026 07:12:00 -0500 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20address=20review=20=E2=80=94=20and?= =?UTF-8?q?=20one=20finding=20was=20a=20real=20gap,=20not=20a=20doc=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five review findings applied, one split to #223, two rejected. `git tag --list` cannot answer the question this PR asks it. It reads the LOCAL repository, so it answered `v4.0.0` throughout the exact window in which the tag had never been pushed — the trap this PR documents, cited with a command blind to it. Now `git ls-remote --tags origin 'refs/tags/v4.0.0*'`, requiring the peeled `^{}` line (which is what proves annotated AND on the remote), plus `gh release view`. `registry-audit` was described as checking "the whole row". It audits npm only and asks nothing about the tag or the Release. Reworded in three places to say which rows it covers and which it does not. llms.txt and Getting-Started.md still said "installs 3.0.0" — both are single very long lines and I fixed the front of each, leaving the continuation. Both now resolve through `latest`. The version badge hardcoded 4.0.0, which is a second hand-maintained copy of a release fact — axiom 2. It now reads from the registry, so it cannot disagree with npm and there is nothing to bump next tag. "one Deployment per role" is wrong about the scaffolded chart: `migrate` is a Job, `replicator` is `enabled: false`, and no `backfill` role exists. Corrected in all four places. `jobs.driver` selects nothing, and that is the real finding. `JobsConfig. driver` has no reader anywhere — boot always builds `createPgDriver`, and packages/jobs/src/driver.ts's own header says so. This wiki claimed `driver: 'redis'` "typechecks and boots, then throws on the first enqueue". It does not: it changes nothing and you silently get Postgres, which is the worse of the two behaviours because nothing reports it. Six shipped `fix:` lines name that field as the repair for X_NOT_IMPLEMENTED, so six error paths ship an instruction that is a no-op — the failure axiom 4 exists to prevent. Docs corrected here and a Known-Gaps Open row added; the code fix is #223, because deleting the field is breaking and rewriting the `fix:` lines should land on its own. Rejected: "carry bot" (the file says "carry both"), and month-precision dates (day precision is this repo's convention for release facts, and is load-bearing here — two of these claims changed within 2026-08). Gate: bun run verify — 14/18, 4 skipped. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 16 +++++++++------- README.md | 2 +- docs/idea/14-roadmap.md | 2 +- docs/idea/README.md | 4 ++-- llms.txt | 2 +- wiki/Configuration.md | 2 +- wiki/FAQ.md | 6 +++--- wiki/Getting-Started.md | 2 +- wiki/Jobs-And-Workflows.md | 6 ++++-- wiki/Known-Gaps.md | 1 + wiki/Tutorial-06-Growing-Up.md | 2 +- 11 files changed, 25 insertions(+), 20 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2dcb613b..c66f49b1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,9 +17,10 @@ CLI binary: `x`. npm scope: `@ultimat3`. Import paths: `@ultimat3/`. one commit, one tag, 30 tarballs. **Repository, tag and registry agree.** Never read a number here as the installable one; run the -command beside it — that is the only thing here that cannot go stale. One command checks the whole -row at once: `bun run scripts/registry-audit.ts --json`, which answers `30/30 publishable packages -are on npm at 4.0.0, every one attested` or names each gap with a runnable `fix:`. +command beside it — that is the only thing here that cannot go stale. `bun run scripts/registry-audit.ts --json` +covers the **npm** rows in one call — it answers `30/30 publishable packages are on npm at 4.0.0, +every one attested` or names each gap with a runnable `fix:` — and it asks nothing about the tag or +the Release, which are the two rows below it. | Fact | State, `As of 2026-08-20` | Read it yourself | |---|---|---| @@ -28,7 +29,7 @@ are on npm at 4.0.0, every one attested` or names each gap with a runnable `fix: | On the registry | **all 30 at 4.0.0**, no holes | `bun run scripts/registry-audit.ts --json` | | npm `latest` | **4.0.0** — `bunx create-ultimate myapp` installs it | `npm view @ultimat3/core version` | | Provenance | every 4.0.0 tarball attested, `_npmUser: GitHub Actions` | `npm view @ultimat3/core@4.0.0 dist.attestations _npmUser` | -| Tag and Release | `v4.0.0` pushed **annotated**, GitHub Release published — the Release is what triggers the workflow | `git tag --list 'v4.*'` | +| Tag and Release | `v4.0.0` pushed **annotated**, GitHub Release published — the Release is what triggers the workflow | `git ls-remote --tags origin 'refs/tags/v4.0.0*'` — both the ref **and** its peeled `^{}` line, which is what proves it is annotated and on the remote; then `gh release view v4.0.0 --json tagName,isDraft,publishedAt`. **Not** `git tag --list`, which reads the local repository and answered `v4.0.0` throughout the window in which the tag had never been pushed | | OIDC trusted publisher | attached to all 30, with `Environment: npm-publish` | `NPM_CONFIG_OTP= bun run scripts/trust-publishers.ts --check --json` — without a fresh OTP every package reads as missing | **A lightweight tag is not a release trigger, and `--follow-tags` will not push one.** `v4.0.0` was @@ -119,9 +120,10 @@ figure. [`scripts/bench/restart-bench.ts`](scripts/bench/restart-bench.ts), resu Open: roadmap milestone 11's two-platform deploy proof — 1.1.0 gave a scaffolded app a real deployable artifact (`packages/cli/src/serve.ts`; `x new` writes `apps/web/server.ts`, `prerender.ts`, a Dockerfile and `docker-compose.prod.yml`; `ROLE=migrate` runs release-phase -migrations), and **4.0.0 gave it a chart** — `x new` writes `docker/helm`, 8 files, one -`Deployment` per role, so `x deploy --method helm` runs `helm upgrade --install` against it with -nothing to copy in. What is still missing is the **proof**, which is the milestone: the demo app on +migrations), and **4.0.0 gave it a chart** — `x new` writes `docker/helm`, 8 files: a +`Deployment` for each of the four roles enabled by default (`web`, `sync`, `worker`, `scheduler`), +`replicator` behind `enabled: false`, and `migrate` as a `Job` rather than a Deployment. So +`x deploy --method helm` runs `helm upgrade --install` against it with nothing to copy in. What is still missing is the **proof**, which is the milestone: the demo app on Compose **and** K8s from one image, with an invisible rolling restart, has not been demonstrated. Two things had to be true before it could be, and each was false in turn — until 2.0.0 `sync`'s readiness probe polled a port the process never opened, and until 4.0.0 a scaffolded app had no diff --git a/README.md b/README.md index dbc6f4fa..281995fc 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![CI](https://github.com/developerz-ai/ultimate/actions/workflows/ci.yml/badge.svg)](https://github.com/developerz-ai/ultimate/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Bun](https://img.shields.io/badge/bun-%E2%89%A5%201.3-black.svg?logo=bun)](https://bun.sh) -[![Version](https://img.shields.io/badge/version-4.0.0-blue.svg)](CHANGELOG.md) +[![Version](https://img.shields.io/npm/v/%40ultimat3%2Fcore?label=version&color=blue)](https://www.npmjs.com/package/@ultimat3/core) diff --git a/docs/idea/14-roadmap.md b/docs/idea/14-roadmap.md index 764186a6..70e9ed9a 100644 --- a/docs/idea/14-roadmap.md +++ b/docs/idea/14-roadmap.md @@ -34,7 +34,7 @@ Milestones 12–14 exist as a **design, not a plan in progress** — see [Design | Open | Why it is not closed | |---|---| -| **Two-platform deploy proof** (milestone 11) | `x build --target docker\|binary\|static`, `docker/docker-compose.{dev,prod}.yml` and `docker/helm` all exist, and `As of 2026-08-20` **a scaffolded app gets its own chart** — `x new` writes `docker/helm`, 8 files, one `Deployment` per role, so `x deploy --method helm` no longer exits `X_NOT_IMPLEMENTED` (4.0.0). Every artifact the milestone names now exists. What is missing is the proof itself: running the demo app on Hetzner+Compose **and** a K8s cluster from one image, with an invisible rolling restart, needs real infrastructure and has not been done | +| **Two-platform deploy proof** (milestone 11) | `x build --target docker\|binary\|static`, `docker/docker-compose.{dev,prod}.yml` and `docker/helm` all exist, and `As of 2026-08-20` **a scaffolded app gets its own chart** — `x new` writes `docker/helm`, 8 files — a `Deployment` for each of the four roles enabled by default (`web`, `sync`, `worker`, `scheduler`), `replicator` behind `enabled: false`, and `migrate` as a `Job`, so `x deploy --method helm` no longer exits `X_NOT_IMPLEMENTED` (4.0.0). Every artifact the milestone names now exists. What is missing is the proof itself: running the demo app on Hetzner+Compose **and** a K8s cluster from one image, with an invisible rolling restart, needs real infrastructure and has not been done | **Closed since**: a scaffolded app now has a deployable artifact. [`packages/cli/src/serve.ts`](../../packages/cli/src/serve.ts) boots a role with no dev watcher and no `/_x`, `ROLE=migrate` applies migrations through the db ledger and exits — the release phase a PaaS asks for — and `x new` writes `apps/web/server.ts`, `apps/web/prerender.ts`, `docker/Dockerfile`, its `.dockerignore` and `docker/docker-compose.prod.yml` ([`templates/scaffold-app.ts`](../../packages/cli/src/templates/scaffold-app.ts), [`templates/scaffold-container.ts`](../../packages/cli/src/templates/scaffold-container.ts)). That was the missing half of "one command produces something you can run"; it is **not** the two-platform proof, which is a measurement on real infrastructure and remains open. diff --git a/docs/idea/README.md b/docs/idea/README.md index 16c0043a..fce990b5 100644 --- a/docs/idea/README.md +++ b/docs/idea/README.md @@ -65,9 +65,9 @@ Consequences of each in [`00-thesis.md`](./00-thesis.md); axiom 8 in full in [`1 | Fact | State, `As of 2026-08-20` | Read it yourself | |---|---|---| | Versioned | 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` — 30 in all — in lockstep, one commit. The current line is a major: 25 changelog entries marked `BREAKING —` from a sweep that closed every known gap, no codemod. 2.0.0 was the first major and carried 33; 3.0.0 carried 10 | `bun run scripts/release.ts --check ` | -| Tagged | the tag is on origin **annotated**, and its GitHub Release is published — that Release is what triggers the workflow. `git push --follow-tags` pushes annotated tags only, so a `git tag` with no `-a` never leaves the machine | `git tag --list 'v4.*'` | +| Tagged | the tag is on origin **annotated**, and its GitHub Release is published — that Release is what triggers the workflow. `git push --follow-tags` pushes annotated tags only, so a `git tag` with no `-a` never leaves the machine | `git ls-remote --tags origin 'refs/tags/v4.0.0*'` — expect the ref **and** its peeled `^{}` line; then `gh release view v4.0.0 --json tagName,isDraft,publishedAt` (`isDraft: false`, `publishedAt` non-null). `git tag --list` reads the **local** repository and cannot tell you either | | Published | `latest` is what `bunx create-ultimate myapp` installs | `npm view @ultimat3/core version` | -| Publication holes | **none.** All 30 workspaces are on the registry at the stamped version. `@ultimat3/scraping` was the last never-published package and was bootstrapped by hand at 2.0.0, exactly as `@ultimat3/flags` had been — the one-time step in [`PUBLISHING.md`](../../PUBLISHING.md) that every package needs before a trusted publisher can attach | `bun run scripts/registry-audit.ts --json` — it resolves the derived list against npm and names each gap with a runnable `fix:` | +| Publication holes | **none.** All 30 workspaces are on the registry at the stamped version. This row and the two npm rows are what the audit covers; the tag and Release rows it does not ask about at all. `@ultimat3/scraping` was the last never-published package and was bootstrapped by hand at 2.0.0, exactly as `@ultimat3/flags` had been — the one-time step in [`PUBLISHING.md`](../../PUBLISHING.md) that every package needs before a trusted publisher can attach | `bun run scripts/registry-audit.ts --json` — it resolves the derived list against npm and names each gap with a runnable `fix:` | | Provenance | every 3.0.0 and 4.0.0 tarball carries an attestation and `_npmUser: GitHub Actions`; **2.0.0 carries neither**, having gone out by hand | `npm view @ultimat3/core dist.attestations`, `npm view @ultimat3/core@2.0.0 dist.attestations` | | OIDC trusted publisher | attached to all 30 on 2026-08-19, with `Environment: npm-publish` — that attachment is what let the workflow publish 3.0.0 and then 4.0.0, and its absence is why 2.0.0 has no provenance. Not the first ever: 1.1.0 and 1.2.0 published under earlier publisher configurations (a different `oidcConfigId` per package) | `NPM_CONFIG_OTP= bun run scripts/trust-publishers.ts --check --json` — every package, and without a fresh code they all read as missing. Per version: `npm view @ultimat3/core@1.2.0 _npmUser.trustedPublisher` | | Release approval | the workflow stops at `waiting` on the `npm-publish` environment until a named reviewer approves the pending deployment — the last point an irreversible publish can be stopped | `gh run view --json status` | diff --git a/llms.txt b/llms.txt index bf5d5e60..0e85c16e 100644 --- a/llms.txt +++ b/llms.txt @@ -1,6 +1,6 @@ # Ultimate -> A full-stack, Bun-only, opinionated web framework: Rails' philosophy applied to Bun + Postgres + SolidJS, where the primary developer is an AI agent and the secondary developer is a tired senior engineer working through their own AI agent and AI reviewer. Eight primitives, one authz system, and errors that carry an exact fix command. **One version in the repository, on its annotated tag, and on npm** `As of 2026-08-20` (`npm view @ultimat3/core version`) — 29 `@ultimat3/*` packages plus the unscoped `create-ultimate`, 30 in all, versioned in lockstep at one version, one commit, one tag, and all 30 published at 3.0.0 by [`.github/workflows/release.yml`](.github/workflows/release.yml) over OIDC with a provenance attestation. `bunx create-ultimate myapp` installs 3.0.0. Resolve it, do not trust this line — `npm view @ultimat3/core version` for the version, `npm view @ultimat3/core@3.0.0 dist.attestations` for the attestation, `bun run scripts/release-workflow.ts --json` for the 30 names that must move together. **No publication holes**: `@ultimat3/scraping` was the last never-published package and was bootstrapped by hand at 2.0.0 (`npm publish --access public --provenance=false`), the one-time step every package needs before a trusted publisher can attach, exactly as `@ultimat3/flags` had been (issue #84). Semver applies from 1.0.0: a breaking change to a documented API needs a major, which is what 3.0.0 is — 10 entries in its changelog section are marked `BREAKING —`, from a five-agent bug sweep, and none ships a codemod; 2.0.0 was the first major and carried 33. 3.0.0 is the first release the workflow published since 1.2.0: 2.0.0 went out by hand, with no trusted publisher attached for the OIDC exchange to verify against, and is the one release whose tarballs carry no attestation. Install with `bunx create-ultimate myapp`. +> A full-stack, Bun-only, opinionated web framework: Rails' philosophy applied to Bun + Postgres + SolidJS, where the primary developer is an AI agent and the secondary developer is a tired senior engineer working through their own AI agent and AI reviewer. Eight primitives, one authz system, and errors that carry an exact fix command. **One version in the repository, on its annotated tag, and on npm** `As of 2026-08-20` (`npm view @ultimat3/core version`) — 29 `@ultimat3/*` packages plus the unscoped `create-ultimate`, 30 in all, versioned in lockstep at one version, one commit, one tag, and all 30 published at that version by [`.github/workflows/release.yml`](.github/workflows/release.yml) over OIDC with a provenance attestation. `bunx create-ultimate myapp` installs whatever `latest` is. Resolve it, do not trust this line — `npm view @ultimat3/core version` for the version, `npm view @ultimat3/core@3.0.0 dist.attestations` for the attestation, `bun run scripts/release-workflow.ts --json` for the 30 names that must move together. **No publication holes**: `@ultimat3/scraping` was the last never-published package and was bootstrapped by hand at 2.0.0 (`npm publish --access public --provenance=false`), the one-time step every package needs before a trusted publisher can attach, exactly as `@ultimat3/flags` had been (issue #84). Semver applies from 1.0.0: a breaking change to a documented API needs a major, which is what 3.0.0 is — 10 entries in its changelog section are marked `BREAKING —`, from a five-agent bug sweep, and none ships a codemod; 2.0.0 was the first major and carried 33. 3.0.0 is the first release the workflow published since 1.2.0: 2.0.0 went out by hand, with no trusted publisher attached for the OIDC exchange to verify against, and is the one release whose tarballs carry no attestation. Install with `bunx create-ultimate myapp`. Everything in the framework is one of eight primitives: `entity`, `policy`, `action`, `mutator`, `query`, `job`, `route`, `task`. One `action` declaration projects into six artifacts (HTTP route, OpenAPI operation, typed client function, job handle, MCP tool, test scaffold) that all share the same `policy` — there is never a second authorization system. The CLI binary is `x`; every command and every error has a `--json` form, and every framework error carries a stable `X_*` code, a concrete cause, and the exact command that fixes it. `x verify` is the single gate, 17 steps in cost order: typecheck, lint, boundaries, filesize, package-shape, errors, unit, contract, live, job, e2e, eval, drift, contract-diff, budgets, manifest, roadmap. The stack is locked (Bun >= 1.3, Postgres with no ORM dependency — `entity()` projects to hand-written SQL through `postgresDriver()`, Standard Schema behind `t` with a dependency-free builtin provider as the shipped default, Better Auth, SolidJS pinned at `1.9.14` on the stable line rather than the `2.0.0-beta.N` prerelease, SCSS modules + tokens, OpenTelemetry always on) and the exclusions are permanent (GraphQL, multi-runtime, multi-ORM, a second CSS system, RSC, vendor edge/KV primitives, a plugin API). An app extends the framework by **wrapping**, never by forking, patching or petitioning: `entity()`, `action()`, `mutator()`, `query()` and `job()` are plain functions returning values, registration happens inside the call, `isAction` is structural, and no `x verify` step matches source text for a primitive call — so an app's own factory (`tenantEntity`, `auditedMutator`) yields primitives the registry, the manifest, the five projections, admin and MCP treat identically. Axiom 8: Ultimate ships mechanism; your app ships convention — mechanisms and *structural* conventions (file naming, the four surfaces, the tier order, one flat i18n catalog per locale) ship as build errors, *business* conventions (an org model, a plan tier, what an audit row says) never do. Deployment is containers only: one image, six roles selected by `ROLE`. diff --git a/wiki/Configuration.md b/wiki/Configuration.md index de4fae04..25235254 100644 --- a/wiki/Configuration.md +++ b/wiki/Configuration.md @@ -73,7 +73,7 @@ Better Auth, wrapped. Sessions live in Postgres. Authorization is **not** here | field | type | default | notes | |---|---|---|---| -| `jobs.driver` | `'postgres' \| 'redis' \| 'nats'` | `'postgres'` | `postgres` needs no extra infra and is the only shipped production driver. **`redis` and `nats` are not in 4.0.0** — the stubs throw `X_NOT_IMPLEMENTED` ([Jobs and workflows](Jobs-And-Workflows)) | +| `jobs.driver` | `'postgres' \| 'redis' \| 'nats'` | `'postgres'` | **declared, and read by nothing** `As of 2026-08-20` — boot always builds `createPgDriver`, so this field selects no driver and editing it has no effect. `postgres` is the only shipped production driver; `redis` and `nats` are `X_NOT_IMPLEMENTED` stubs you reach through `setJobDriver()`, not through here. Removing the field is breaking and waits for the next major — [issue #223](https://github.com/developerz-ai/ultimate/issues/223) ([Jobs and workflows](Jobs-And-Workflows)) | | `jobs.queues` | `string[]` | `['default']` | a `worker` runs one pool per queue in `WORKER_QUEUES` | | `jobs.concurrency` | `number` | `8` | per pool, per process | | `jobs.retry.attempts` | `number` | `5` | per-job `retry` overrides | diff --git a/wiki/FAQ.md b/wiki/FAQ.md index e455be9d..b56975a9 100644 --- a/wiki/FAQ.md +++ b/wiki/FAQ.md @@ -8,18 +8,18 @@ Honest answers. Where something is not built yet, it says so. **`As of 2026-08`.** Stable API, semver from here. 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` — **30 in all** — are **versioned** in lockstep: one version, one commit, one tag. -**Publication is a separate step from versioning, and the current release's has run.** Repository, tag and registry are the same version; there are no publication holes — every one of the 30 is on npm. +**Publication is a separate step from versioning, and for the current release it has run.** Repository, tag and registry are the same version; there are no publication holes — every one of the 30 is on npm. | Fact | State `As of 2026-08-20` | Resolve it yourself | |---|---|---| | What you can install | whatever `latest` is — `bunx create-ultimate myapp` gives you it | `npm view @ultimat3/core version` | | Repository version | all 30 stamped in one commit | the top section of [`CHANGELOG.md`](https://github.com/developerz-ai/ultimate/blob/main/CHANGELOG.md) | -| Tagged | the tag is on origin, annotated, and its GitHub Release is published | [the repository's tags](https://github.com/developerz-ai/ultimate/tags) | +| Tagged | the tag is on origin, annotated, and its GitHub Release is published | `git ls-remote --tags origin 'refs/tags/v*'` — expect the ref and its peeled `^{}` line — then `gh release view v --json tagName,isDraft,publishedAt` | | On the registry | **all 30**, at that version | `npm view @ultimat3/scraping version` | | Provenance | 1.1.0, 1.2.0, 3.0.0 and 4.0.0 carry an attestation; **2.0.0 does not** | `npm view @ultimat3/core dist.attestations` | | Who published it | `GitHub Actions`, over OIDC, on all 30 | `npm view @ultimat3/core _npmUser` | -Only the [footer](_Footer) stamps the number, so this table names the command instead of a version that goes stale on the next tag. In the repository, `bun run scripts/registry-audit.ts --json` checks the whole table in one call, and `registry-audit.yml` runs it daily and opens an issue on any disagreement. +Only the [footer](_Footer) stamps the number, so this table names the command instead of a version that goes stale on the next tag. In the repository, `bun run scripts/registry-audit.ts --json` covers the **registry** rows in one call — not the tag or the Release, which it never asks about — and `registry-audit.yml` runs it daily and opens an issue on any disagreement. 3.0.0 and 4.0.0 both went out through the workflow. 2.0.0 is the exception in the line: it was hand-published, with no trusted publisher attached for the OIDC exchange to verify against, so its tarballs carry no attestation and `_npmUser: sebyx07`. 1.0.0 was the manual bootstrap. `@ultimat3/scraping` and `@ultimat3/flags` were the two never-published packages and both are closed — each by the one-time manual bootstrap every package needs before a trusted publisher can attach. diff --git a/wiki/Getting-Started.md b/wiki/Getting-Started.md index 2bac0b2f..f6243c0a 100644 --- a/wiki/Getting-Started.md +++ b/wiki/Getting-Started.md @@ -177,4 +177,4 @@ $ x verify ## Status -`As of 2026-08`. Stable API — semver from here ([Upgrading](Upgrading)). The repository holds 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` and versions all 30 in lockstep — the current line is a major, 25 entries marked `BREAKING —` from a sweep that closed every known gap and no codemod ([Upgrading](Upgrading)); 2.0.0 was the first major and carried 33, 3.0.0 carried 10. Only the [footer](_Footer) stamps the number. **What `bunx create-ultimate myapp` installs is 3.0.0** `As of 2026-08-19`: npm's `latest` is 3.0.0 for all 30 workspaces, each published by the release workflow over OIDC with a provenance attestation. Resolve it, do not trust this line — `npm view @ultimat3/core version`, and `npm view @ultimat3/core@3.0.0 dist.attestations` for the attestation. **No publication holes**: `@ultimat3/scraping` was the last never-published package, bootstrapped by hand at 2.0.0, so `bun add @ultimat3/scraping` resolves. Milestones 0–10 are ✅; milestone 11 is 🚧, open on its two-platform deploy proof. Realtime tiers 1–2 ship; tier 3 (local-first) is not in 3.0.0. The 50k-socket forced-restart benchmark **is measured and committed** — first patch on the reconnected socket at p50 54.0s / p90 105.5s, on one node; delivery is a second run, 10,000 clients, 1,666,882 patches, 0 observed sequence gaps ([Realtime](Realtime)). Status markers come from [`docs/idea/14-roadmap.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/idea/14-roadmap.md). See [FAQ](FAQ). +`As of 2026-08`. Stable API — semver from here ([Upgrading](Upgrading)). The repository holds 29 `@ultimat3/*` packages plus the unscoped `create-ultimate` and versions all 30 in lockstep — the current line is a major, 25 entries marked `BREAKING —` from a sweep that closed every known gap and no codemod ([Upgrading](Upgrading)); 2.0.0 was the first major and carried 33, 3.0.0 carried 10. Only the [footer](_Footer) stamps the number. **What `bunx create-ultimate myapp` installs is npm's `latest`** `As of 2026-08-20`, one version for all 30 workspaces, each published by the release workflow over OIDC with a provenance attestation. Resolve it, do not trust this line — `npm view @ultimat3/core version`, and `npm view @ultimat3/core@3.0.0 dist.attestations` for the attestation. **No publication holes**: `@ultimat3/scraping` was the last never-published package, bootstrapped by hand at 2.0.0, so `bun add @ultimat3/scraping` resolves. Milestones 0–10 are ✅; milestone 11 is 🚧, open on its two-platform deploy proof. Realtime tiers 1–2 ship; tier 3 (local-first) is not in 3.0.0. The 50k-socket forced-restart benchmark **is measured and committed** — first patch on the reconnected socket at p50 54.0s / p90 105.5s, on one node; delivery is a second run, 10,000 clients, 1,666,882 patches, 0 observed sequence gaps ([Realtime](Realtime)). Status markers come from [`docs/idea/14-roadmap.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/idea/14-roadmap.md). See [FAQ](FAQ). diff --git a/wiki/Jobs-And-Workflows.md b/wiki/Jobs-And-Workflows.md index ddcb4406..1df9d667 100644 --- a/wiki/Jobs-And-Workflows.md +++ b/wiki/Jobs-And-Workflows.md @@ -183,7 +183,9 @@ Two implementations ship in 1.0.0. Two more are **not in 4.0.0** — interface-c | `redis` | **not in 4.0.0 — throws `X_NOT_IMPLEMENTED`** | high-throughput, short jobs | would need the outbox relay; loses "queue state in one backup" | | `nats` | **not in 4.0.0 — throws `X_NOT_IMPLEMENTED`** | very high fanout, multi-region, JetStream retention | strongest delivery semantics, most operational surface | -`jobs.driver` in `app.config.ts` accepts `'postgres' | 'redis' | 'nats'` — and only `'postgres'` runs. Setting it to `redis` or `nats` typechecks and boots, then throws on the first enqueue: deliberate, and why the stubs exist instead of an absent export. +**`jobs.driver` selects nothing** `As of 2026-08-20`, and this page said it did. `JobsConfig.driver` accepts `'postgres' | 'redis' | 'nats'`, and it **has no reader anywhere** — boot always builds `createPgDriver`, stated in [`packages/jobs/src/driver.ts`](https://github.com/developerz-ai/ultimate/blob/main/packages/jobs/src/driver.ts)'s own header. So setting it to `redis` does not boot-and-then-throw, as this page claimed: it changes nothing at all and you silently get Postgres, which is the more dangerous of the two behaviours because nothing reports it. + +**The seam that does work is `setJobDriver(driver)`** — swap the driver, zero job-code change, which is what the interface buys. The `redis` and `nats` stubs are real and throw `X_NOT_IMPLEMENTED` on every method; you reach them by constructing one and passing it to `setJobDriver`, never through config. Tracked as [issue #223](https://github.com/developerz-ai/ultimate/issues/223): the field is a declaration nothing reads, the same shape 4.0.0 deleted for `realtime.heartbeatMs` and `PrecacheAsset.critical`, and removing it is breaking — so it waits for the next major. `x jobs drain --to ` moves in-flight rows between drivers, and `--to memory` is the only target that completes today: `--to redis` and `--to nats` construct the target and fail on the first enqueue with `X_NOT_IMPLEMENTED`. The cross-driver migration procedure is not in 3.0.0 — see [Upgrading](Upgrading). @@ -226,7 +228,7 @@ Every command supports `--json`. See [CLI reference](CLI-Reference). | `X_IDEMPOTENCY_CONFLICT` | same key, different payload, or still in flight | fresh key for a different payload; otherwise retry after the first settles | | `X_DRAINING` | claim attempted on a worker that received SIGTERM | none — the job stays queued and another worker claims it | | `X_FORBIDDEN` | the job's actor fails the originating action's policy | grant the permission, or enqueue as a system actor | -| `X_NOT_IMPLEMENTED` | the `redis` or `nats` driver was reached — neither is in 4.0.0 | set `jobs.driver: 'postgres'` in `app.config.ts` (it is already the default) | +| `X_NOT_IMPLEMENTED` | the `redis` or `nats` driver was reached — neither is in 4.0.0 | call `setJobDriver(createPgDriver({ executor }))`, or `setJobDriver(createMemoryDriver())` in a test. **Not** `jobs.driver` in `app.config.ts` — that field has no reader, so editing it cannot repair this. To move rows already queued: `x jobs drain --to memory --json` | Full index: [Error codes](Error-Codes). Verbatim error shapes live in each package's `src/errors.ts`. diff --git a/wiki/Known-Gaps.md b/wiki/Known-Gaps.md index 1b774ce1..6369e674 100644 --- a/wiki/Known-Gaps.md +++ b/wiki/Known-Gaps.md @@ -30,6 +30,7 @@ section, not this page, is the source of truth for what is in the next release. | Gap | Symptom | Work around it by | |---|---|---| +| `jobs.driver` selects no driver | [`JobsConfig.driver`](https://github.com/developerz-ai/ultimate/blob/main/packages/core/src/config.ts) accepts `'postgres' \| 'redis' \| 'nats'` and **has no reader anywhere** — boot always builds `createPgDriver` ([`dev-queue.ts:132`](https://github.com/developerz-ai/ultimate/blob/main/packages/cli/src/dev-queue.ts)), and [`packages/jobs/src/driver.ts`](https://github.com/developerz-ai/ultimate/blob/main/packages/jobs/src/driver.ts)'s own header says so. `jobs: { driver: 'redis' }` therefore does **not** boot-and-then-throw as this wiki claimed: it changes nothing and you silently get Postgres. Worse, five shipped `fix:` lines name it as the repair for `X_NOT_IMPLEMENTED` — a `fix:` that is a no-op, which is the failure axiom 4 exists to prevent. [#223](https://github.com/developerz-ai/ultimate/issues/223) | swap the driver with **`setJobDriver(driver)`**, the seam that works: `setJobDriver(createPgDriver({ executor }))`, or `setJobDriver(createMemoryDriver())` in a test. Never through `app.config.ts` | | `SessionInit.proxy` is declared on the scrape seam and read by **nothing** | [`packages/scraping/src/driver.ts:30`](https://github.com/developerz-ai/ultimate/blob/main/packages/scraping/src/driver.ts) declares `proxy?: string` with the comment "BOTH transports dial through it". Nothing dials through it: `runScrape` builds the whole `SessionInit` at [`scrape-run.ts:103`](https://github.com/developerz-ai/ultimate/blob/main/packages/scraping/src/scrape-run.ts) and passes no `proxy` key, the CDP driver reads `options.proxy` — its own launch option, never `init.proxy` — and the fake and fixture drivers read neither. A seam field that changes no behaviour is the same shape as the robots-read bug closed one file over | pass the exit as a **driver** option, which is the one that works: `localBrowser({ proxy })` / `remoteBrowser({ proxy })`. Read `ScrapeSession.proxy` to see which exit a session actually took — see [Scraping](Scraping#what-it-does-not-do) | | An **offline** scrape reads `/robots.txt` over the real network | every `fakeBrowser` / `fixtureBrowser` run takes the default `robots: 'obey'`, and the gate reads `https:///robots.txt` with the platform `fetch` before the first navigation. Measured `As of 2026-08-19` on `runScrape` against `fakeBrowser`: one `fetch` to `https://shop.test/robots.txt` leaves the process, and the run still reports `refused: 0`. Under `bun test` the sealed network **does** refuse it — and `robotsFetcher`'s `catch { return undefined }` swallows the refusal, which the gate reads as "no robots.txt", which is allow-everything. So the suite is green either way and neither the egress nor the seal is visible in it. Contradicts [`SECURITY.md`](https://github.com/developerz-ai/ultimate/blob/main/SECURITY.md)'s egress row and `packages/scraping/CLAUDE.md`'s "an offline driver that fell through to the network would make a green suite secretly live" | declare the policy in the definition under test — `robots: { ignore: 'fixture host, no live origin' }` — which returns a gate that reads nothing at all. There is no boolean and no ambient off switch, by design — see [Scraping](Scraping#testing-offline) | | `bun run error-render` cannot see a `catch (error)` binding | the gate matches a **parameter** annotated `unknown`/`any` (`UNKNOWN_BINDING` in [`scripts/error-render.ts:158`](https://github.com/developerz-ai/ultimate/blob/main/scripts/error-render.ts)). A `catch (error)` binding is `unknown` by **inference** under `useUnknownInCatchVariables` and carries no annotation, so it matches nothing — which is how `String(error)` echoing a caller's own request body into a 422 `cause:` and into the log store stayed green in `packages/http/src/request.ts` through 3.0.0. That instance is fixed; the blind spot is not | review every `catch` by hand: a caught value reaches a `cause:`/`fix:` only through `renderThrowable(error)` from `@ultimat3/core`. `bun run error-render --json` is a floor, never a proof — its own file header lists what it cannot see | diff --git a/wiki/Tutorial-06-Growing-Up.md b/wiki/Tutorial-06-Growing-Up.md index c15aa857..315d84fd 100644 --- a/wiki/Tutorial-06-Growing-Up.md +++ b/wiki/Tutorial-06-Growing-Up.md @@ -71,7 +71,7 @@ Start Postgres with `wal_level=logical`, `max_replication_slots=8` and `max_wal_ ## Rung 2 → 3: Kubernetes -**`x new` writes the chart**, `As of 2026-08-19` — `docker/helm`, 8 files, one `Deployment` per role — so `x deploy --method helm` runs `helm upgrade --install` against it with nothing to copy in. On 3.0.0 and below `x new` writes none and the command exits `X_NOT_IMPLEMENTED`: copy [`docker/helm`](https://github.com/developerz-ai/ultimate/tree/main/docker/helm) from the framework repo. +**`x new` writes the chart**, `As of 2026-08-19` — `docker/helm`, 8 files — a `Deployment` for each of the four roles enabled by default (`web`, `sync`, `worker`, `scheduler`), `replicator` behind `enabled: false`, and `migrate` as a `Job` — so `x deploy --method helm` runs `helm upgrade --install` against it with nothing to copy in. On 3.0.0 and below `x new` writes none and the command exits `X_NOT_IMPLEMENTED`: copy [`docker/helm`](https://github.com/developerz-ai/ultimate/tree/main/docker/helm) from the framework repo. The alternative is the flat manifests in [`docs/ops/01-kubernetes.md`](https://github.com/developerz-ai/ultimate/blob/main/docs/ops/01-kubernetes.md). Carry one, never both — a repo with a chart *and* a manifest tree has two sources of truth for the same pod. Deleting `docker/helm` is how you choose the manifests: the command then fails with helm's own error, not the framework's.