Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 51 additions & 54 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
# Runs lint + typecheck + test across every workspace in dependency order with the
# Turborepo cache, plus the formatting check and the no-vendor-type-across-the-seam fence.
#
# Branch protection (set in the GitHub repo settings, not here): the `ci` job below is the
# REQUIRED status check to merge into `main`. The `peer-dep-gate` job is advisory until the
# surface packages land their peers in Phase 1. The `coverage` job is advisory too — it enforces
# the testing.md >=90% line+branch engine floor (exit criterion #5) but stays non-required until the
# thin core-branch margin is confirmed stable under CI's Node 24; promote it to a required check then.
# Branch protection (set in the GitHub repo settings, not here): `ci` and `coverage` are the REQUIRED
# status checks to merge into `main`. Every other job self-labels `(advisory)` in its `name:`, which is
# what shows in the PR checks list — `floor-check`, `peer-dep-gate` and `windows-concurrency`.
#
# `coverage` enforces the testing.md >=90% line+branch floor for `@relavium/llm` and `@relavium/mcp` only.
# `packages/core` is measured and printed by the same run but does not fail it: its branch margin is +0.83
# (90.83 measured 2026-07-29) and Phase 2.5.5 Waves 1-3 edit `core` heavily, so blocking merges on a
# sub-1-point margin would red-CI real work for no defect. That is a scoped, dated ruling with a promotion
# trigger — Wave 3's test-coverage items — not an open-ended exemption. A local `pnpm coverage` still
# enforces all three, so the floor never silently relaxes for a developer.
#
# Caching: the always-on layer is the GitHub Actions `.turbo` cache (restored/saved below),
# which makes a no-change re-run a Turborepo cache hit — the M0 "demonstrably hitting"
Expand All @@ -16,6 +21,13 @@
#
# Third-party actions are pinned to a full commit SHA (the `# vX.Y.Z` comment tracks the
# human-readable release) so a moved tag can't inject unreviewed code; bumps are deliberate.
#
# Install-script posture: `pnpm install` runs WITHOUT `--ignore-scripts` deliberately. Supply-chain risk is
# handled more precisely one level up, by `pnpm.onlyBuiltDependencies` in the root package.json, which
# allowlists the ONLY two packages permitted to run lifecycle scripts (`better-sqlite3` for its native
# prebuild, `esbuild` for its platform binary). Every other transitive dependency is already blocked.
# Passing `--ignore-scripts` here would break both of those and gain nothing — it is a weaker, blunter form
# of a control the repo already applies.
name: CI

on:
Expand Down Expand Up @@ -118,6 +130,25 @@ jobs:
- name: Engine dependency allowlist
run: pnpm lint:engine-deps

# `tools/` is real code that gates real things (the seam fence, the bundle-closure guard, the
# models.dev sync). The root `ci` script has always linted it; this job never did, so a lint error in
# a guard script could merge while `pnpm run ci` was red locally (#312).
# NB: it is `pnpm run ci`, never `pnpm ci` — pnpm reserves `ci` as a builtin and answers
# `ERR_PNPM_CI_NOT_IMPLEMENTED`, so the script is unreachable by the name everyone types.
- name: Lint the tooling scripts
run: pnpm lint:tools

# RUN the artifact this job just built, through the SAME `pnpm smoke:cli` script the root `ci` script
# calls — a check that exists in only one of the two is exactly the #312 divergence this change closes.
# Until now nothing in the required gate executed
# `apps/cli/dist/index.js` — only the advisory Windows leg and the tag-gated release smoke did (#294) —
# so a bundle that builds but cannot boot merged green. The `run --json` leg additionally proves the
# migrations resolve beside the bundle, which is the failure `apps/cli/drizzle/**` becoming a declared
# turbo output (#315) exists to prevent: a cache-hit replay used to leave `dist/` fresh next to a
# missing `drizzle/`, crashing on first DB touch with nothing red anywhere.
- name: Smoke the compiled binary
run: pnpm smoke:cli

# Supported-floor gate (ADR-0067). TWO floors, deliberately different:
# * the PUBLISHED floor is `apps/cli` `engines.node` = `>=22` — the max constraint in the CLI's RUNTIME
# dependency closure (`ink@7` / `cli-truncate` / `slice-ansi` → `>=22`); nothing runtime needs more.
Expand All @@ -131,7 +162,7 @@ jobs:
# a property of the prod closure, verified at the ADR (see ADR-0067's amendment note) rather than here.
# A SEPARATE job (the required check stays the ubuntu `ci` job on Node 24); promote once confirmed stable.
floor-check:
name: node 22-line floor (22.13.0) · typecheck · test · build
name: node 22-line floor (22.13.0) · typecheck · test · build (advisory)
runs-on: ubuntu-latest
timeout-minutes: 15
# Fully isolate this job from the shared Turbo remote cache. Turbo's task hash does NOT include the
Expand Down Expand Up @@ -164,7 +195,7 @@ jobs:
# checkouts resolve while the surface packages' peers are not all in the tree yet). Catch
# peer drift here without breaking local dev.
peer-dep-gate:
name: strict peer-dependency check
name: strict peer-dependency check (advisory)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand All @@ -179,13 +210,13 @@ jobs:
- name: Install with strict peers
run: pnpm install --frozen-lockfile --config.strict-peer-dependencies=true

# Engine coverage floor (testing.md >=90% line+branch, exit criterion #5). Advisory for now — a
# SEPARATE job (not part of the required `ci` job) so it surfaces a regression without blocking merge
# while the core-package branch margin is thin. `pnpm coverage` is a repo-ROOT run, which is what makes
# the root-relative per-glob thresholds (packages/core, packages/llm) authoritative (vitest.config.ts).
# Promote to a required check once the margin is confirmed stable under CI's Node 24.
# Engine coverage floor (testing.md >=90% line+branch, exit criterion #5). A REQUIRED check named
# `engine coverage floor (llm, mcp)` — kept as a SEPARATE job from `ci` so a coverage regression is
# legible on its own line rather than buried in a 12-step job. It is a repo-ROOT vitest run, which is what
# makes the root-relative per-glob thresholds authoritative (vitest.config.ts). `packages/core` is measured
# but not enforced here; see the header for the ruling and its promotion trigger.
coverage:
name: engine coverage floor (advisory)
name: engine coverage floor (llm, mcp)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
Expand Down Expand Up @@ -214,8 +245,13 @@ jobs:
# each package still covers its OWN src via relative imports, so the floor stays src-accurate).
- name: Build workspaces (so coverage resolves the @relavium/* package entries)
run: pnpm turbo run build
- name: Engine coverage floor (>=90% line+branch)
run: pnpm coverage
# ENFORCED subset only: `llm` and `mcp` fail the build below 90% line+branch. `core` is measured and
# printed by the same run but does NOT fail it — its branch margin is +0.83 today and Phase 2.5.5
# Waves 1-3 edit it heavily, so blocking on a sub-1-point margin would red-CI real work for no
# defect (maintainer ruling 2026-07-29). A bare `pnpm coverage` still enforces all three locally.
# Promote `core` here once Wave 3's test-coverage items land.
- name: Engine coverage floor (>=90% line+branch — llm, mcp enforced; core measured)
run: pnpm coverage:enforced

# Cross-OS concurrency + headless gate (2.5.I S6). The DB write-path hardening (BEGIN IMMEDIATE + the
# SQLITE_BUSY retry's Atomics.wait sleep + WAL locking) and the two-process concurrency e2e (a child spawn +
Expand Down Expand Up @@ -270,42 +306,3 @@ jobs:
echo "::error::chat entered raw mode without a TTY (the driver-selection gate regressed)"; exit 1
fi
echo "✓ windows headless no-TTY smoke passed"

# --- Reserved Phase-1 lanes (TODO: enable with the first provider adapter) ------------
# The per-provider conformance suite and the nightly live-API lane land WITH the adapters
# in Phase 1 (testing.md); only their CI slots are reserved here so the testing standard
# maps cleanly onto lanes from day one. Do not enable until `packages/llm` exists — and
# pin each action to a commit SHA (as above) when uncommenting.
#
# conformance:
# name: provider conformance (fixtures)
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@<sha> # pin on enable
# - uses: pnpm/action-setup@<sha>
# - uses: actions/setup-node@<sha>
# with: { node-version-file: .nvmrc, cache: pnpm }
# - run: pnpm install --frozen-lockfile
# - run: pnpm turbo run test:conformance # fixture mode — no network, no keys
#
# Nightly live-API lane — runs the conformance suite against real providers using keys
# from CI secrets. Separate workflow trigger so it never gates a PR:
# # on:
# # schedule:
# # - cron: '0 7 * * *' # 07:00 UTC nightly
# live-api:
# name: provider conformance (live, nightly)
# if: github.event_name == 'schedule'
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@<sha> # pin on enable
# - uses: pnpm/action-setup@<sha>
# - uses: actions/setup-node@<sha>
# with: { node-version-file: .nvmrc, cache: pnpm }
# - run: pnpm install --frozen-lockfile
# - run: pnpm turbo run test:conformance:live
# env:
# ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
# GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
# --------------------------------------------------------------------------------------
11 changes: 11 additions & 0 deletions .github/workflows/models-catalog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ jobs:
# (sync.mjs says so in its own comments). New models "merge automatically" via the deferred auto-PR
# (deferred-tasks.md); until it lands they are simply picked up by the next local `pnpm sync:models`. The
# snapshot this job writes into the ephemeral CI checkout is discarded — only the exit code is the guard.
# Staleness signal, informational only — and it MUST run before the guard below. `sync:models` REWRITES
# `snapshot.ts` in the ephemeral checkout (sync.mjs's `writeFileSync`), so a `--check` placed after it
# would compare upstream against the file the previous step just regenerated and report "current" every
# time. `sync.mjs` documents `--check` as the CI-facing mode that fails when the COMMITTED snapshot has
# drifted, and a matching `sync:models:check` script has always existed — but no workflow ever called it
# (#317). `continue-on-error` keeps it a report: the guard below is what must stay red on a money change,
# while ordinary upstream churn (a new model, a renamed display name) should not page anyone.
- name: Snapshot freshness vs upstream (informational)
continue-on-error: true
run: pnpm sync:models:check

- name: No ALREADY-SHIPPED model's price moved or vanished
run: pnpm sync:models

Expand Down
1 change: 1 addition & 0 deletions apps/cli/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const THIRD_PARTY_EXTERNAL = [
'quickjs-emscripten-core',
'react',
'smol-toml',
'string-width',
'yaml',
'zod',
];
Expand Down
10 changes: 10 additions & 0 deletions apps/cli/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "https://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", "drizzle/**"]
}
}
}
32 changes: 19 additions & 13 deletions docs/roadmap/current.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@

> Status: Living
>
> Last updated: 2026-07-26
> Last updated: 2026-07-29

- **Related**: [README.md](README.md), [phases/phase-2.5-cli-consolidation.md](phases/phase-2.5-cli-consolidation.md), [phases/phase-2.5.5-hardening-and-remediation.md](phases/phase-2.5.5-hardening-and-remediation.md), [phases/phase-2-cli.md](phases/phase-2-cli.md), [deferred-tasks.md](deferred-tasks.md), [../project-structure.md](../project-structure.md), [../tech-stack.md](../tech-stack.md)

> **Authority (until Phase 2.5.5 and Phase 2.6 close):** This file is canonical for
> live progress and execution order; the phase documents stay canonical for scope and acceptance. Ordering
> differences between the two are deliberate. `current.md` may override recommended ordering — it may **not**
> redefine a security boundary, a scope line, or a milestone acceptance criterion.

This page tracks what is active **right now** and the immediate next concrete actions.
The full phase plan and the global milestone spine are in [README.md](README.md).
**Phase 2.5 (CLI Consolidation) is complete** (milestone **M2.5-4**, PR #69, 2026-07-08) — its
Expand All @@ -25,14 +30,14 @@ PR #75, 2026-07-13) — see [Active now](#what-is-active-now). **2.6.Q** (dynami
enrichment) is **no longer blocked**: [ADR-0071](../decisions/0071-models-dev-as-the-model-metadata-source.md)
and [ADR-0072](../decisions/0072-model-metadata-in-the-db-behind-a-generated-offline-floor.md) are both
**Accepted**, resolving the six open maintainer questions the phase file had cited, and the P1–P5
implementation steps have landed on `development` (PR #76, open, review-folded). P6 and the
`/settings` → `/models` visibility toggle remain open, and the bundled offline snapshot regen is
pending a Gemini pricing decision.
implementation steps are **merged to `main`** (PR #76, 2026-07-26). P6 and the `/settings` → `/models`
visibility toggle remain open; the bundled offline snapshot was **regenerated** in PR #79 after the D3
price ruling.

**The unified build order across both phases is in
[Execution order — Phase 2.5.5 + Phase 2.6](#execution-order--phase-255--phase-26-temporary) below** — a
temporary section, deleted when both phases close. Its baseline step is discharged (PRs #76 and #77 merged);
**the next unit of work is Wave 0's single `ci.yml`/`turbo.json`/`tsup.config.ts` PR.**
temporary section, deleted when both phases close. Its baseline step is discharged (PRs #76 and #77 merged)
and **Wave 0's CI-truth PR is in flight on `development` (PR #80)**.

## Execution order — Phase 2.5.5 + Phase 2.6 (temporary)

Expand All @@ -51,8 +56,8 @@ temporary section, deleted when both phases close. Its baseline step is discharg
The plan opened on a divergence neither phase file modelled: Phase 2.5.5's whole backlog is written against
`f88b0e8`, which existed only on `development` behind PR #76, so every `file:line` citation in it resolved
against a tree nobody was branching from. **PR #76 and #77 are both merged** — `f88b0e8` is on `origin/main`,
`origin/development` and `origin/main` hold identical trees, and there are no open PRs. Every citation in
this plan now resolves.
`origin/development` and `origin/main` held identical trees with no open PRs as of 2026-07-26. Every
citation in this plan now resolves. (PR #79 and PR #80 have opened since.)

One consequence survives and is worth keeping until the phases close:

Expand Down Expand Up @@ -110,7 +115,8 @@ checklists before ~30 security-gated PRs are reviewed against them.
`0015` = 2.6.H · `0016` = 2.6.G's pins · `0017` = 2.6.N lineage; **ADR-0073+** for the nine unwritten 2.6 ADRs.
3. **One `ci.yml`/`turbo.json`/`tsup.config.ts` PR** (a five-way collision file — do not split):
2.5.5.H · the required gate never runs the compiled binary + undeclared `drizzle` output (#294, #315) →
local `pnpm ci` vs `ci.yml` divergence (#312) → the coverage-floor **ruling and its implementation**
local `pnpm run ci` vs `ci.yml` divergence (#312 — and `pnpm ci` is shadowed by a pnpm builtin, so the
script was unreachable by the name everyone types) → the coverage-floor **ruling and its implementation**
(#296, #152) → the `(advisory)` labels (#320) → `THIRD_PARTY_EXTERNAL` (G27, #248) → bundle-closure
single-chunk assert (#314) → `sync:models:check` (#317). *(`release.yml`'s ancestry check, `G26`, already
landed in #77; only its tag-protection half remains, and that is now configured too.)*
Expand Down Expand Up @@ -241,7 +247,7 @@ The four Day-1-independent 2.6 workstreams everything downstream sits on — and
Then 2.5.5.C · per-row isolation (#117) as pure propagation of H's skip-and-report ruling.
5. **2.6.A** (package extraction, `validateAuthoredWorkflow` back-port, the run-path tool pre-flight
#37/G29, direct unit tests #6) and **2.6.D**. *2.6.A's `max_tokens` pre-flight moves to Wave 4b — it has
a hard dependency on 2.6.Q's `limit.output`.* **Closes M2.6-2 and M2.6-3.**
a hard dependency on 2.6.Q's `limit.output`.* **Closes M2.6-2.** (M2.6-3 also needs 2.6.B, which lands in Wave 6.)

### Wave 4b — Money floor and close-out

Expand All @@ -264,7 +270,7 @@ adapter/catalog money floor, cut the release 2.6.Q P6 is gated on.
### Wave 5a — Paint before you build

Restructure the render layer and settle the theme/config substrate **once**, before ~24 new screens and a
five-locale catalog land on it.
an `en`+`tr` string catalog land on it.

1. **ADR row 10 (i18n + theming) is this wave's first gate** — ahead of rows 7 and 8. 2.6.L's palette work
is a render-layer freeze and must not open against an undecided contract.
Expand Down Expand Up @@ -337,9 +343,9 @@ unanswered**; the remainder sit inline in their own phase-file bullet.
|---|------|----------|----------------|
| D1 | 0 | Mark the `ci` job a **required** check (open since Phase 0) | Yes — every CI item here is advisory until it flips |
| D2 | 0 | Coverage floor: promote to required, or soften `testing.md`? | Promote, **and implement in the same PR** — a checked-in run already shows 92–97% margin |
| D3 | 0 | Accept the upstream price changes the ADR-0071 §9 guard is refusing? | ✅ **Ruled 2026-07-26: take current prices.** Verified: `gemini-flash-latest` moved $0.30→$1.50 in / $2.50→$9.00 out the shipped floor under-prices by 5×. **Blocked on D3b below** |
| D3 | 0 | Accept the upstream price changes the ADR-0071 §9 guard is refusing? | ✅ **Ruled + executed 2026-07-26: take current prices.** Verified: `gemini-flash-latest` moved $0.30→$1.50 in / $2.50→$9.00 out (the shipped floor under-priced by 5×) and `gemini-flash-lite-latest` $0.10→$0.25 / $0.40→$1.50. The nine upstream retirements were accepted in the same ruling; snapshot regenerated and merged in **PR #79**. Nothing outstanding |
| D4 | 0 | Confirm the number reservation (`0013`–`0017`, ADR-0073+) | As listed — one item per number, in landing order |
| D5 | 0 | The binding locale bar | ✅ **Ruled 2026-07-26: ship `en` + `tr`**, catalog architected for n locales, `es`/`fr`/`de` staged. EXIT:6, 2.6.L and 2.5.5.F amended |
| D5 | 0 | The binding locale bar | ✅ **Ruled + propagated 2026-07-26: ship `en` + `tr`**, catalog architected for n locales, `es`/`fr`/`de` staged. Amended at all seven sites |
| D7 | 0 | Publish v0.1.1 as-is, or supersede with v0.2.0? | v0.2.0 — ADR-0067's Node `>=22` bump is breaking for 0.x |
| D8 | 1 | Do already-persisted approval previews need a scrub? | Yes — migration 0013, same PR. Deleting `history.db` also destroys provider registrations |
| D10 | 1 | `BudgetExceededError`/`BudgetPauseError`: adopt `.code`? | Adopt — must precede Wave 3's `RelaviumError` migration |
Expand Down
Loading
Loading