Skip to content

Commit 06b0760

Browse files
committed
docs: update AI docs for embed.FS migration fix (ADR-030)
- status.md: add entry #27 documenting the embed migration fix - todo.md: add critical bug entry for plugin migrations in Docker - decisions.md: add ADR-030 (Embed Plugin Migrations via embed.FS) - conventions.md: update migration section with embed.FS pattern - architecture.md: add embed.go, migrations/, plugin_schema.go to structure https://claude.ai/code/session_01QJLkgjQDu5qohzJKGV4hj9
1 parent ba94a83 commit 06b0760

5 files changed

Lines changed: 61 additions & 4 deletions

File tree

.ai/architecture.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,11 @@ chronicle/
9494
│ ├── config/ # CORE: Configuration loading (env vars)
9595
│ │ └── config.go
9696
│ │
97-
│ ├── database/ # CORE: Database connections
97+
│ ├── database/ # CORE: Database connections + migrations
9898
│ │ ├── mariadb.go # MariaDB connection pool
99-
│ │ └── redis.go # Redis client
99+
│ │ ├── redis.go # Redis client
100+
│ │ ├── plugin_schema.go # Plugin migration runner (reads embed.FS)
101+
│ │ └── plugin_health.go # Plugin health registry
100102
│ │
101103
│ ├── middleware/ # CORE: HTTP middleware
102104
│ │ ├── auth.go # Session validation
@@ -220,6 +222,7 @@ Every plugin follows this exact structure. No exceptions.
220222
```
221223
internal/plugins/<name>/
222224
.ai.md # Plugin-level AI documentation
225+
embed.go # Embeds migrations/*.sql via Go embed.FS (ADR-030)
223226
handler.go # Echo handlers (thin: bind, call service, render)
224227
handler_test.go # Handler tests (HTTP-level, mock service)
225228
service.go # Business logic (never imports Echo types)
@@ -228,6 +231,9 @@ internal/plugins/<name>/
228231
repository_test.go # Repository tests (integration, real DB)
229232
model.go # Domain models, DTOs, request/response structs
230233
routes.go # Route registration function
234+
migrations/ # Plugin-specific schema migrations (embedded in binary)
235+
001_*.up.sql
236+
001_*.down.sql
231237
templates/ # Templ components for this plugin
232238
index.templ # List view
233239
show.templ # Detail view

.ai/conventions.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,11 @@ Chronicle uses a **plugin-isolated database schema architecture**:
253253
- **Core schema** (`db/migrations/`): Single baseline migration with all core tables.
254254
Runs via golang-migrate on startup. Failure is fatal.
255255
- **Plugin schema** (`internal/plugins/<name>/migrations/`): Each built-in plugin
256-
has its own numbered migration files. Runs via `RunPluginMigrations()` after core
257-
migrations. Failure disables that plugin; app continues serving.
256+
has its own numbered migration files **embedded in the binary** via Go's `embed.FS`
257+
(ADR-030). Each plugin has an `embed.go` exporting `MigrationsFS`. Runs via
258+
`RunPluginMigrations()` after core migrations. Failure disables that plugin; app
259+
continues serving. `RegisteredPlugins()` lives in `cmd/server/main.go` (not in
260+
the database package) to avoid import cycles.
258261

259262
```sql
260263
-- Core migration example: db/migrations/000001_baseline.up.sql
@@ -280,6 +283,9 @@ CREATE TABLE IF NOT EXISTS calendars ( ... );
280283
in migration SQL. Update the valid sets there when adding new ENUM values.
281284
6. **Plugin tables**: Plugin tables belong in `internal/plugins/<name>/migrations/`,
282285
not in `db/migrations/`. Plugin schema failures degrade gracefully (ADR-028).
286+
Migrations are embedded in the binary via `embed.FS` (ADR-030). When adding a
287+
new plugin with migrations, create an `embed.go` in the plugin package and
288+
register it in `registeredPlugins()` in `cmd/server/main.go`.
283289

284290
### Permission Model
285291

.ai/decisions.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,3 +1050,42 @@ capabilities. Non-owners could see features but couldn't tell which were enabled
10501050
- Single source of truth for feature management.
10511051
- Owners can manage features directly from the same page all members see.
10521052
- Future enhancements (per-addon entity usage, "offline" banners) have one target page.
1053+
1054+
---
1055+
1056+
## ADR-030: Embed Plugin Migrations via Go embed.FS
1057+
1058+
**Date:** 2026-03-11
1059+
**Status:** Accepted (amends ADR-028)
1060+
1061+
**Context:** ADR-028 introduced per-plugin migration directories at
1062+
`internal/plugins/<name>/migrations/`. The migration runner used `os.Stat` and
1063+
`os.ReadDir` with relative filesystem paths. This worked in development (CWD =
1064+
project root) but failed silently in Docker: the runtime image copies the binary
1065+
to `/app` but never copies plugin migration directories. Since `os.Stat` returned
1066+
`os.IsNotExist`, the runner treated each plugin as "healthy with 0 migrations"
1067+
— no tables were created, entity pages crashed, and the DB Explorer showed 0/0.
1068+
1069+
**Decision:** Embed plugin migration SQL files in the binary using Go's `embed.FS`:
1070+
- Each plugin package gets an `embed.go` that exports `MigrationsFS embed.FS`
1071+
with `//go:embed migrations/*.sql`.
1072+
- `PluginSchema.MigrationsDir` (string) replaced with `MigrationsFS` (`fs.FS`).
1073+
- `parsePluginMigrations` and `LatestMigrationVersion` read from `fs.FS` instead
1074+
of the real filesystem.
1075+
- `RegisteredPlugins()` moved from `database` package to `cmd/server/main.go`
1076+
to avoid import cycles (database can't import plugin packages). Uses `fs.Sub`
1077+
to strip the `migrations/` prefix from each embed.FS.
1078+
- `PluginSchemas` stored on `App` struct and passed to `DatabaseExplorer` for
1079+
on-demand re-migration from the admin panel.
1080+
1081+
**Alternatives considered:**
1082+
- Copy plugin migration dirs to Docker runtime image: fragile, requires syncing
1083+
Dockerfile whenever plugins are added/removed. Still fails if CWD changes.
1084+
- Centralise all plugin migrations in one directory: loses per-plugin isolation
1085+
that ADR-028 established.
1086+
1087+
**Consequences:**
1088+
- Migrations work in any environment regardless of working directory.
1089+
- No Dockerfile changes needed when adding new plugins with migrations.
1090+
- Each plugin must have an `embed.go` exporting its `MigrationsFS`.
1091+
- `RegisteredPlugins()` now lives in `cmd/server/main.go` instead of `database`.

.ai/status.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
## Last Updated
1111
2026-03-11 -- **Sprint W-0.5: Visual Customization + Admin DB Explorer (IN PROGRESS).**
1212

13+
27. **Fix: Embed plugin migrations in binary (ADR-030).** Root cause of entity page errors and DB Explorer showing 0/0: plugin migrations used relative filesystem paths (`internal/plugins/*/migrations/`) that only resolve when the binary's CWD is the project root. In Docker, the binary runs from `/app` so migration directories were never found, tables were never created, and entity pages crashed. Fix: each plugin now embeds its `migrations/*.sql` via Go's `embed.FS`. `PluginSchema.MigrationsDir` (string) replaced with `MigrationsFS` (`fs.FS`). `RegisteredPlugins()` moved from `database` package to `cmd/server/main.go` to avoid import cycles (database can't import plugin packages). `PluginSchemas` stored on `App` struct and passed to `DatabaseExplorer` for on-demand re-migration. New `embed.go` files in calendar, maps, sessions, timeline, syncapi plugins.
14+
15+
### Previous Update
16+
2026-03-11 -- **Sprint W-0.5: Visual Customization + Admin DB Explorer (IN PROGRESS).**
17+
1318
26. Admin Database Explorer: New `/admin/database` page with interactive D3.js schema diagram showing all tables, FK relationships, plugin grouping, and migration status. Table detail panel on click. "Apply Pending Migrations" button (fixes sessions table missing issue). Removed "Manual DB Record (Advanced)" form from Features page. New files: `database_service.go` (info_schema introspection), `database.templ`, `db_explorer.js` widget. Added `LatestMigrationVersion()` to database package. Dashboard card + sidebar link.
1419

1520
### Previous Update

.ai/todo.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Known broken or missing things, ordered by severity.
1616
### Critical
1717

1818
- [x] **Public campaign widget 403s** — Editor and attributes widgets return 403 for non-member visitors on public campaigns. Root cause: GET `/entry`, `/fields`, `/tags`, `/relations` only registered in authenticated route group, not in public-capable group. Fixed by adding routes to `pub` group in entities/routes.go, tags/routes.go, relations/routes.go.
19+
- [x] **Plugin migrations not applied in Docker (entity page errors, 0/0 in DB Explorer)** — Plugin migrations used relative filesystem paths (`internal/plugins/*/migrations/`) that only resolve when CWD is the project root. In Docker, binary runs from `/app` so dirs were never found, tables never created, entity pages crashed on missing tables, and DB Explorer showed 0/0 for all plugins. Fixed by embedding migrations in the binary via Go's `embed.FS` (ADR-030). Each plugin now has `embed.go` exporting `MigrationsFS`. `PluginSchema.MigrationsDir` replaced with `MigrationsFS` (`fs.FS`).
1920

2021
### High
2122

0 commit comments

Comments
 (0)