Phase 0 — M0 close-out: seam fence (0.F), CI (0.G), docs (0.H), @relavium/db (0.I)#3
Conversation
…client Stand up @relavium/db (Phase 0 · 0.I): the Drizzle SQLite schema for the nine Phase-1 local tables (llm_providers, model_catalog, agents, workflows, runs, step_executions, messages, run_events, run_costs), faithful to the canonical DDL — TEXT UUIDs, JSON-as-TEXT, epoch-ms INTEGER timestamps, integer micro-cents, CHECK-string enums, integer-bool (0/1) defaults, soft-delete partial indexes, and ON DELETE CASCADE on the run children. The runs.status / runs.execution_mode CHECK value sets are imported from @relavium/shared so the persisted enums cannot drift from the contract. Adds the drizzle-kit migration set, a better-sqlite3 client factory (WAL + foreign_keys = ON) and migration runner, and a Vitest smoke test (6 tests) that applies every migration to a fresh DB, round-trips a runs + run_events row, proves the run -> step_executions -> messages and run -> run_events cascades, and asserts the status/execution_mode CHECKs fire by constraint name (FK satisfied first, so only the CHECK can reject). The Node-side SQLite driver (ADR-0005 left it open) is pinned to better-sqlite3 in ADR-0021. @types/node is pinned to the engines floor (20.11) so a Node-22-only API is a type error, not a runtime break. Also broadens @relavium/shared's exports with a `default` condition so CJS-condition tooling (drizzle-kit) resolves it. Refs: ADR-0005, ADR-0021 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0.F)
Wire the @relavium/llm seam fence before Phase 1's first adapter exists, applied to
BOTH TS and JS source: two complementary ESLint rules forbid every provider-SDK
(@anthropic-ai/sdk, openai, @google/genai) import outside packages/llm/src/adapters/*.
no-restricted-imports covers the static forms (bare, subpath, `import type`, and
`export … from` re-exports); no-restricted-syntax closes the rest — dynamic
`import()`, ANY non-literal computed `import(expr)`, the `import('…').T` type query,
and `require()`. The config-file ignore is scoped to repo/package/app-root tooling
configs so a source file named `*.config.ts` cannot escape the fence. The fence is
lifted only inside the adapter zone.
A quarantined fixture exercises all nine forms and assert-fence.mjs asserts each rule
family fires its EXACT count (plus a config-named source file stays fenced) — so a
partial regression fails the check rather than passing on the remaining errors.
Wired as `pnpm lint:fence-check`.
Refs: ADR-0011
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add ci.yml running `pnpm turbo run lint typecheck test` (+ build, format:check, and the seam fence-check) on every push and PR with a frozen lockfile and the Turborepo cache, plus a CI-only strict-peer-dependency gate. The GitHub Actions .turbo cache is the always-on layer (no-change re-runs hit); the Vercel-style remote cache is opt-in via secrets. Reserves the Phase-1 conformance and nightly live-API lanes as commented anchors. The `ci` job is the required check; branch protection is set in repo settings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewer's GuideImplements Phase 0 close-out for three workstreams: a strict ESLint-based LLM provider seam fence (TS+JS) with self-checking fixtures, a new @relavium/db package (Drizzle schema, migrations, better-sqlite3 client, smoke tests, docs, and ADR), and a CI workflow that runs lint/typecheck/test/build/format plus the seam-fence check and a strict peer-dependency gate. Entity relationship diagram for @relavium/db Phase-1 schemaerDiagram
llm_providers ||--o{ model_catalog : provides
model_catalog ||--o{ agents : backs
model_catalog ||--o{ run_costs : priced_by
workflows ||--o{ runs : instance_of
runs ||--o{ step_executions : has_steps
runs ||--o{ run_events : has_events
runs ||--o{ run_costs : has_costs
step_executions ||--o{ messages : emits
agents ||--o{ step_executions : used_by
llm_providers {
text id PK
text name
}
model_catalog {
text id PK
text provider_id FK
text model_id
}
workflows {
text id PK
text slug
}
runs {
text id PK
text workflow_id FK
text status
text execution_mode
}
step_executions {
text id PK
text run_id FK
text agent_id FK
}
messages {
text id PK
text step_execution_id FK
text run_id
}
run_events {
text id PK
text run_id FK
}
run_costs {
text id PK
text run_id FK
text model_id FK
}
agents {
text id PK
text model_id FK
}
Flow diagram for CI pipeline and seam-fence checkflowchart LR
Dev[push / pull_request]
CI[GitHub Actions CI workflow]
Install[pnpm install --frozen-lockfile]
Turbo[turbo run lint typecheck test]
Build[pnpm turbo run build]
Format[pnpm format:check]
FenceCheck[pnpm lint:fence-check]
ESLint[ESLint with seam rules]
Fixtures[lint fixtures + assert-fence.mjs]
PeerGate[peer-dep-gate job]
Dev --> CI
CI --> Install --> Turbo --> Build --> Format --> FenceCheck
FenceCheck --> Fixtures --> ESLint
CI --> PeerGate
PeerGate --> Install
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughAdds a Phase‑1 SQLite persistence package (Drizzle schema, migrations, better‑sqlite3 client, tests), an ESLint seam fence with fixtures and validation, workspace/tooling updates, and a GitHub Actions CI workflow that runs lint/typecheck/test/build and enforces the seam fence. ChangesSQLite Database Layer & Seam Fence Enforcement
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="tools/lint-fixtures/assert-fence.mjs" line_range="27-28" />
<code_context>
+
+const eslint = new ESLint();
+const results = await eslint.lintFiles([MAIN, CONFIG_NAMED]);
+const resultFor = (suffix) => results.find((r) => r.filePath.endsWith(suffix));
+const seamErrors = (res, ruleId) =>
+ (res?.messages ?? []).filter((m) => m.ruleId === ruleId && m.severity === 2).length;
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Path-suffix matching can fail on Windows due to hard-coded '/' separators.
Using `r.filePath.endsWith('/…')` assumes POSIX separators; on Windows it will be `'\'`, so `resultFor('/forbidden-vendor-import.ts')` and `resultFor('/forbidden-in-name.config.ts')` may return `undefined`, breaking the regression detection. Please normalize paths (e.g. `path.normalize` + normalized suffix) or use `path.basename` for filename-only checks so this works cross‑platform.
</issue_to_address>
### Comment 2
<location path="packages/db/README.md" line_range="21" />
<code_context>
+ `better-sqlite3` ([ADR-0021](../../docs/decisions/0021-node-sqlite-driver-better-sqlite3.md)).
+- A **smoke test** that applies every migration to a fresh DB and round-trips a row.
+
+It depends only on `@relavium/shared` (the contract enums its CHECKs reuse) and Drizzle.
+
+## Conventions (from the canonical DDL)
</code_context>
<issue_to_address>
**suggestion (typo):** Clarify wording in parenthetical about contract enums and CHECKs.
The phrase "the contract enums its CHECKs reuse" is ungrammatical. Please change it to something like "the contract enums that its CHECKs reuse" for clarity.
```suggestion
It depends only on `@relavium/shared` (the contract enums that its CHECKs reuse) and Drizzle.
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
This pull request scaffolds the @relavium/db package, introducing a Drizzle SQLite schema for Phase-1 tables, a better-sqlite3 client factory, and migration smoke tests, aligned with ADR-0021. It also implements an airtight ESLint import fence to prevent provider SDKs from crossing the @relavium/llm seam outside of designated adapters, backed by quarantined lint fixtures and an assertion script. The review feedback suggests simplifying the boolean default value helper in the schema by passing the boolean directly to Drizzle, and recommends adding a CHECK constraint on the messages.role column to prevent invalid roles.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const boolFlag = (name: string, def: boolean) => | ||
| integer(name, { mode: 'boolean' }) | ||
| .notNull() | ||
| .default(def ? sql`1` : sql`0`); |
There was a problem hiding this comment.
Since mode: 'boolean' is configured on the integer column, Drizzle ORM's .default() method natively accepts a boolean value (which it automatically serializes to 1 or 0 for SQLite). Passing raw SQL sql'1' or sql'0' is redundant and bypasses TypeScript's type safety for the default value. We can simplify this by passing the boolean def directly.
| const boolFlag = (name: string, def: boolean) => | |
| integer(name, { mode: 'boolean' }) | |
| .notNull() | |
| .default(def ? sql`1` : sql`0`); | |
| const boolFlag = (name: string, def: boolean) => | |
| integer(name, { mode: 'boolean' }) | |
| .notNull() | |
| .default(def); |
| // role values (system|user|assistant|tool) are documented literals; the reference | ||
| // DDL declares no CHECK on role, so none is added here. | ||
| role: text('role').notNull(), | ||
| content: text('content'), | ||
| contentParts: jsonText('content_parts'), | ||
| toolCalls: jsonText('tool_calls'), | ||
| toolCallId: text('tool_call_id'), | ||
| name: text('name'), | ||
| finishReason: text('finish_reason'), | ||
| createdAt: epochMs('created_at').notNull(), | ||
| }, | ||
| (t) => [ | ||
| index('idx_messages_step').on(t.stepExecutionId, t.sequenceNumber), | ||
| index('idx_messages_run').on(t.runId, t.createdAt), | ||
| ], | ||
| ); |
There was a problem hiding this comment.
To prevent database corruption and ensure defensive programming, it is highly recommended to enforce a CHECK constraint on the role column of the messages table, restricting it to the allowed values ('system', 'user', 'assistant', 'tool'). This aligns with the check constraints defined on other enum-like columns in the schema (such as runs.status and step_executions.status).
| // role values (system|user|assistant|tool) are documented literals; the reference | |
| // DDL declares no CHECK on role, so none is added here. | |
| role: text('role').notNull(), | |
| content: text('content'), | |
| contentParts: jsonText('content_parts'), | |
| toolCalls: jsonText('tool_calls'), | |
| toolCallId: text('tool_call_id'), | |
| name: text('name'), | |
| finishReason: text('finish_reason'), | |
| createdAt: epochMs('created_at').notNull(), | |
| }, | |
| (t) => [ | |
| index('idx_messages_step').on(t.stepExecutionId, t.sequenceNumber), | |
| index('idx_messages_run').on(t.runId, t.createdAt), | |
| ], | |
| ); | |
| // role values (system|user|assistant|tool) are documented literals; the reference | |
| // DDL declares no CHECK on role, so none is added here. | |
| role: text('role').notNull(), | |
| content: text('content'), | |
| contentParts: jsonText('content_parts'), | |
| toolCalls: jsonText('tool_calls'), | |
| toolCallId: text('tool_call_id'), | |
| name: text('name'), | |
| finishReason: text('finish_reason'), | |
| createdAt: epochMs('created_at').notNull(), | |
| }, | |
| (t) => [ | |
| check('messages_role_check', sql`${t.role} in (${inList(['system', 'user', 'assistant', 'tool'])})`), | |
| index('idx_messages_step').on(t.stepExecutionId, t.sequenceNumber), | |
| index('idx_messages_run').on(t.runId, t.createdAt), | |
| ], | |
| ); |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/db/src/schema.ts (1)
57-58: 💤 Low valueConsider using
replaceAll()for clarity.The global regex works correctly, but
replaceAll()(ES2021) is slightly more readable. Since Node ≥ 20 is required per ADR-0021, this is safe.Suggested change
const inList = (values: readonly string[]) => - sql.raw(values.map((v) => `'${v.replace(/'/g, "''")}'`).join(', ')); + sql.raw(values.map((v) => `'${v.replaceAll("'", "''")}'`).join(', '));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/db/src/schema.ts` around lines 57 - 58, The inList helper builds a raw SQL list by replacing single quotes via a regex; change the replacement to use String.prototype.replaceAll for clarity by updating the implementation of inList (the function that calls sql.raw) to use v.replaceAll("'", "''") instead of v.replace(/'/g, "''"), preserving the existing behavior and keeping the values: readonly string[] handling and the sql.raw call intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci.yml:
- Line 39: Replace floating tags with commit-pinned SHAs for GitHub Actions used
(e.g., actions/checkout, pnpm/action-setup, actions/setup-node, actions/cache)
and add the checkout option to disable credential persistence: for every
checkout step (including the peer-dep-gate job) change uses: actions/checkout@v4
to the corresponding commit SHA (actions/checkout@<sha>) and add with:
persist-credentials: false; apply the same commit-pinning approach to
pnpm/action-setup, actions/setup-node and actions/cache usages so all action
references use exact SHAs instead of tags.
---
Nitpick comments:
In `@packages/db/src/schema.ts`:
- Around line 57-58: The inList helper builds a raw SQL list by replacing single
quotes via a regex; change the replacement to use String.prototype.replaceAll
for clarity by updating the implementation of inList (the function that calls
sql.raw) to use v.replaceAll("'", "''") instead of v.replace(/'/g, "''"),
preserving the existing behavior and keeping the values: readonly string[]
handling and the sql.raw call intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 18b6cf35-f8df-41f6-9788-bfa2bd109606
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (25)
.github/workflows/ci.yml.prettierignoredocs/decisions/0005-sqlite-drizzle-local-postgres-cloud.mddocs/decisions/0021-node-sqlite-driver-better-sqlite3.mddocs/decisions/README.mddocs/tech-stack.mdeslint.config.mjspackage.jsonpackages/db/README.mdpackages/db/drizzle.config.tspackages/db/drizzle/0000_cuddly_war_machine.sqlpackages/db/drizzle/meta/0000_snapshot.jsonpackages/db/drizzle/meta/_journal.jsonpackages/db/package.jsonpackages/db/src/client.test.tspackages/db/src/client.tspackages/db/src/index.tspackages/db/src/schema.tspackages/db/tsconfig.build.jsonpackages/db/tsconfig.jsonpackages/shared/package.jsonpnpm-workspace.yamltools/lint-fixtures/assert-fence.mjstools/lint-fixtures/forbidden-in-name.config.tstools/lint-fixtures/forbidden-vendor-import.ts
…e fence-check
- ci: pin every third-party action to a full commit SHA (with a `# vX.Y.Z` tracking
comment) and set `persist-credentials: false` on checkout — closes the SonarCloud
security hotspot, so a moved tag can no longer inject unreviewed code.
- tools/lint-fixtures/assert-fence.mjs: match ESLint results by `path.basename`
instead of an `endsWith('/…')` suffix, so the fence-check works on Windows (`\`).
- packages/db: `inList` uses `String.prototype.replaceAll` for clarity (output is
identical — verified no migration drift); fix a README grammar nit.
Skipped, with reason:
- boolean defaults keep `sql`1`/`sql`0``: drizzle's `.default(true)` emits
`DEFAULT true` in the DDL, which diverges from the canonical `DEFAULT 1/0`
(database-schema.md). The explicit literal is intentional for byte-fidelity, and
the `boolFlag(name, def: boolean)` wrapper keeps the call site type-safe.
- no CHECK added on `messages.role`: the canonical DDL declares none; adding one
here would drift from the single-canonical-home reference (it belongs in
database-schema.md first, as a deliberate contract change).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
…ings Phase 0 — Foundations is done: all workstreams 0.A–0.I merged (PR #1–#3), achieving milestone M0. Reflect that across the roadmap and entry docs: - phase-0-foundations.md: status -> Complete; 0.F–0.I marked done; the 0.M3/M0 milestone done; exit criterion #7 lists better-sqlite3 (ADR-0021). - current.md: Where-we-are / What-is-active / next-steps now point at Phase 1 (@relavium/llm seam + @relavium/core engine); M1 is the next checkpoint. - roadmap/README.md milestone spine: M0 done; root README + CLAUDE.md status updated. Adds docs/roadmap/deferred-tasks.md: every confirmed-but-deferred finding from the 97-agent comprehensive review, as discrete checkable tasks (decisions, schema/test depth, tooling, docs, packaging) so none get lost. None blocked M0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gress-test/doc-drift)
13-dimension, double-verified end-to-end review of 1.AF (engine media plumbing).
This lands the two confirmed HIGH items, the behavioral mediums, and the doc-drift
cluster; the 1.AH-latent host-wiring items stay recorded in deferred-tasks.md.
H1 (I3): redact inline media from agent:tool_call.toolInput before it rides the
event/IPC/log stream. A model can emit a base64 data: URI or a {kind:'base64',data}
object as a tool argument; the result side (outputSummary) was redacted this
workstream but the symmetric input side was not. Reuse the same redactInlineMedia
walk at sanitizeInput — display-only (the dispatch already ran on the real args).
H2: unify the load-time output_modalities check (validate-catalog) and the runtime
FallbackChain pre-skip (capabilities) on ONE exact-membership predicate
(isOutputCombinationSupported, @relavium/llm). The runtime did text-strip + subset,
which wrongly ADMITTED wire-invalid combinations (the closed set exists to reject)
and diverged from the load-check + ADR-0031 decision #3. Both now share the
predicate; text-only stays always-emittable (no regression for []-combo models).
GC grace (db): bump media_objects.last_referenced_at on addReference AND the
terminal sweep (removeRunReferences), so the grace window measures from
drop-to-zero, not production time (ADR-0042 section 4). A long-lived handle losing
its last reference no longer gets zero effective grace.
Egress test (db): mock node:https to exercise the concrete nodeMediaEgressDeps
(the un-injectable pin/SNI/port/family wiring) + the default-deps range-block — the
one part of the SSRF-critical path with no executable proof (E43-7).
Docs: sse-event-schema (cost:updated.mediaUnits deferred — the layering forbids
shared importing the llm seam type), tool-registry (thirteen built-ins; add
media_scope_denied to the deny union), built-in-tools (read_media row); record the
keychain no-raw-key IPC test + the mediaUnits-surface deferrals in deferred-tasks.md.
Validation: lint/typecheck/test/build 16/16 green, prettier clean, Leakwatch 0.
Refs: ADR-0042, ADR-0043, ADR-0044
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…xt pickup 2.I 2.G (interactive human-gate prompt + `relavium gate` cross-process resume) merged via PR #47, fully closing 2.K's deferred gate-resume half. Flip the status surfaces: - phase-2-cli.md: §2.G heading → Done; status header + Remaining-build-order status line → add 2.G; the build-order table drops the 2.G row and renumbers (2.I now #1, 2.L #2, 2.S #3, …); the gate-closing backbone shrinks to `2.I → 2.L`; the §2.K deferred gate-resume note → landed. - current.md: 2.G added to Landed; next pickup → 2.I. - CLAUDE.md + README.md: 2.G landed; next pickup 2.I. The structural views (dependency matrix, ordered waves, Mermaid graphs) are the from-scratch plan, not a live tracker, so they keep 2.G as a graph node unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… nitpick)
Verify-each-against-current-code pass; all 6 were still valid, fixed minimally.
- agent-run.test.ts: replace an unsafe inline `as { error?: { code?: string } }`
cast with a local `isRecord` type guard (strict-TS).
- chat.ts: `onAbort` handler → block body so it never forwards `session.abort()`'s
return value (void-style).
- chat-ink.tsx: the ChatApp 'abort' case now mirrors home-controller — when
`onAbort` is absent (made OPTIONAL; the no-op default dropped), Esc rejects a
PENDING approval directly instead of a no-op that would hang the decision. The
prop is `(() => void) | undefined` so the createElement passthrough forwards an
absent `ctx.onAbort` under exactOptionalPropertyTypes.
- persister.ts: derive the session title inside the COMPLETED-exchange branch
(from `pendingUserText`), not in `beginUserTurn` — so an aborted/errored first
turn (whose rows are rolled back) never labels the row with a phantom title.
- fs.ts: `listOne` recursive walk now checks `isSensitiveReadPath` on the entry's
REALPATH (falling back to lexical on a broken link), mirroring collectGlobFiles
— a symlink named innocuously but pointing into a .ssh/.relavium/credential
store is no longer surfaced in a listing. Displayed `rel` name unchanged.
- docs/roadmap/current.md: bump the stale "Last updated" stamp 2026-06-29 →
2026-06-30 (aligns with the newest body entry).
Tests: persister aborted-title pin; fs symlink-to-sensitive-store list-skip pin
(itPosix); agent-run guard is itself the fix. #3 relies on the tested
home-controller parity (ChatApp has no unit harness). Full turbo typecheck/test/
lint green + fence/engine-deps/bundle-closure + Leakwatch clean; changed files
prettier-clean.
Refs: ADR-0057
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>



Closes the M0 — Foundations green workstreams (0.F–0.I). Three commits, one per workstream — the multi-review hardening is folded into each workstream's commit (not a separate fix commit), so each commit is self-consistent and green.
Commits
3520718@relavium/dbdatabase-schema.md),drizzle-kitmigration set,better-sqlite3client (WAL +foreign_keys=ON) + migration runner, 6-test smoke suite. ADR-0021 (driver) + ADR-0005 amendment +defaultexport on@relavium/shared.1/0;@types/nodepinned to the20.11floor (sonode:sqliteis a type error);inListquote-escaping; CHECK test now inserts a valid workflow first + asserts the constraint name (was masked by the FK error);run→step_executions→messagescascade test7a855dc@relavium/llmfence on TS + JS, lifted only insidepackages/llm/src/adapters/*; quarantined fixture +pnpm lint:fence-check.*.config.tssource file no longer escapes); computedimport(expr)banned; JS source now fenced;export … fromforms added;assert-fence.mjsasserts exact per-rule counts + a config-named regression fixture671beb6ci.yml: lint/typecheck/test/build + format + fence-check on push/PR, frozen lockfile, Turbo cache, strict-peer gate, reserved Phase-1 lanes.Review trail
Three independent reviews ran against the branch. Review 1 probed the seam fence adversarially and found real bypasses (config-named files, computed dynamic imports, JS source) that Reviews 2 & 3 missed — all reproduced locally, then closed. The fence now covers 9 import syntaxes with self-checking fixtures so it can't silently regress. Three findings were refuted with reasons (roadmap-not-updated = intentional; CI commit
Refs:= no ADR to ref; amendment heading = the> Amendedblockquote is the documented pattern).Validation
pnpm install --frozen-lockfile→pnpm turbo run lint typecheck test build(8/8) →pnpm format:check→pnpm lint:fence-check(5/5 static, 4/4 syntax, config-named fenced) → strict-peer gate — all green. Thenode:sqlitefloor guard verified (TS2307).🤖 Generated with Claude Code
Summary by Sourcery
Finalize Phase 0 foundations by adding a typed SQLite persistence layer, enforcing a strict LLM provider SDK import fence, and wiring a CI workflow that gates pushes and PRs on lint, typecheck, tests, build, formatting, and fence integrity.
New Features:
Enhancements:
CI:
Documentation:
Tests:
Summary by CodeRabbit
New Features
New Features
Documentation
Infrastructure