@happyvertical/smrt-cli 0.40.69 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -7,12 +7,18 @@ Developer CLI with lazy-loaded commands, manifest discovery, and class introspec
7
7
  ```
8
8
  smrt introspect # Discover SMRT objects in project
9
9
  smrt doctor # Umbrella diagnostics; can verify a generation snapshot
10
+ smrt doctor --db # Add the live-schema parity section (see below)
10
11
  smrt db:status # Pending schema changes + failed migration classification
12
+ smrt db:status --parity # Same, plus live-schema parity (see below)
11
13
  smrt db:migrate # Apply migrations
14
+ smrt db:migrate --postgres-safe # PostgreSQL concurrent-index mode (see below)
12
15
  smrt db:migrate --force-migration <exact-id> [--force-migration <exact-id>...] # Force exact generated migrations in one atomic batch
13
16
  smrt db:migrate-uuid # Convert schema-declared UUID text columns after data remap
14
17
  smrt db:diff # Show schema differences without generating migration files
15
- smrt db:rollback # Rollback migrations
18
+ smrt db:rollback # Roll back migrations by executing their recorded DOWN
19
+ smrt db:rollback --mark-only # Record-only flip; schema deliberately untouched
20
+ smrt db:prune # Prune framework system tables to their retention windows
21
+ smrt db:prune --dry-run # Same predicates, counted rather than deleted
16
22
  smrt docs:agents # Generate .agents/smrt-framework.md
17
23
  smrt docs:claude # Deprecated alias writing .claude/smrt-framework.md
18
24
  smrt dev:knowledge-* # Deterministic agent knowledge index/check/diff
@@ -34,6 +40,127 @@ migrations are manifest-driven through registered objects and project manifests.
34
40
 
35
41
  `smrt test` is **deprecated** — use vitest plugin directly.
36
42
 
43
+ ## `db:migrate` on PostgreSQL
44
+
45
+ `db:migrate` always bounds a PostgreSQL batch with `SET LOCAL lock_timeout` and
46
+ `SET LOCAL statement_timeout` inside its transaction, from
47
+ `migrations.postgres.lockTimeout` / `.statementTimeout` (defaults `30s` / `60s`;
48
+ accepts `ms`/`s`/`min`/`h` suffixes, and `0` disables as PostgreSQL defines it).
49
+ A migration queued behind a long-running writer therefore fails fast and rolls
50
+ back instead of holding the locks it already took against every writer.
51
+
52
+ `--postgres-safe` selects **concurrent-index mode**: non-index DDL still commits
53
+ in one transaction, then each index statement runs
54
+ `CREATE INDEX CONCURRENTLY` / `DROP INDEX CONCURRENTLY` on one pinned session
55
+ after that commit. This is the mode for large index rollouts.
56
+
57
+ - **Concurrent mode is not atomic.** Committed column/table changes survive a
58
+ later index failure; unfinished index migrations are recorded `failed`, and
59
+ `db:migrate` (which reconciles) retries them on the next run. The retry
60
+ resumes at the index build — their `error_message` carries a
61
+ `[smrt: concurrent-index phase 1 committed]` marker, so the non-index
62
+ statements that already committed are not re-run.
63
+ - INVALID indexes — the stump a cancelled or timed-out
64
+ `CREATE INDEX CONCURRENTLY` leaves, which `pg_indexes` still reports as
65
+ present — are detected via `pg_index.indisvalid` and dropped before the
66
+ rebuild.
67
+ - `migrations.postgres.useConcurrently: false` vetoes the flag; index DDL then
68
+ runs inside the atomic transaction (still bounded by the timeouts).
69
+ - Without the flag, a batch containing explicit `CONCURRENTLY` DDL is rejected
70
+ before the transaction opens — PostgreSQL cannot run it there.
71
+
72
+ ## Live-schema parity (#2368)
73
+
74
+ `doctor --db` and `db:status --parity` share `src/commands/db-parity.ts`, which
75
+ runs core's `checkLiveSchemaParity()`. This answers a different question than
76
+ `db:status`/`db:diff`: those compare the live database to the **manifest**, i.e.
77
+ to the artifact that dropped the index in the first place, which is why a
78
+ database missing 164 tenant-column indexes reported "in sync" (#2356).
79
+
80
+ Expected shape comes from the manifest schemas, the hand-DDL `_smrt_*` system
81
+ tables (parsed by core's `system-table-shapes.ts` — they are in no manifest and
82
+ enter no diff), and an index policy that consults no manifest at all: every
83
+ foreign-key/cross-package-ref/tenant column leads an index, every registry
84
+ conflict target has a matching UNIQUE index, every `unique: true` column is
85
+ unique live. Conflict targets come from `ObjectRegistry.getConflictColumns()`,
86
+ not from the schema definition.
87
+
88
+ - Severity is the contract: `error` fails the command (missing table/column,
89
+ type drift, orphan NOT NULL, conflict target unindexed or non-unique,
90
+ PostgreSQL INVALID index), `warning` does not (index coverage), `info` is
91
+ hidden without `--verbose` (undeclared tables/columns/indexes).
92
+ - Both surfaces **fail closed**: an unreachable database, or an adapter with no
93
+ `getTableSchema`, is an error, never a silent pass. Where index metadata
94
+ cannot be read at all, index checks are skipped and the report says so
95
+ (`indexIntrospection: 'unavailable'`) rather than inventing missing indexes.
96
+ - The check is read-only and lives in a new core module; it does not share code
97
+ with `migrations/differ.ts`.
98
+
99
+ ## `db:migrate` on SQLite: type changes rebuild the table
100
+
101
+ SQLite has no `ALTER COLUMN ... TYPE`, so a type-bucket change (the common one
102
+ being a numeric default edited `0` → `0.0`) is applied as the documented table
103
+ rebuild — stage, copy, drop, rename, replay indexes and triggers — planned by
104
+ `smrt-core`'s `migrations/sqlite-rebuild.ts` and executed inside the same
105
+ atomic batch as everything else. It is no longer a "manual intervention" that
106
+ makes `db:migrate` exit 1 on every run (#2370). All drifted columns of one
107
+ table are fixed by one rebuild; `--dry-run` prints the whole statement list.
108
+
109
+ The rebuild refuses — and the column stays manual drift — when another table
110
+ declares a foreign key onto the target while `PRAGMA foreign_keys` is ON,
111
+ because `DROP TABLE` would fire those children's `ON DELETE` actions. Fix that
112
+ one by hand (or against a connection with enforcement disabled).
113
+
114
+ ## `db:rollback` is execute-or-refuse
115
+
116
+ Schema state is diff-driven: `db:migrate` derives every migration from the
117
+ manifest at run time and stores **no SQL** in `_smrt_schema_migrations`. So
118
+ `db:rollback` can only honour the one DOWN script that is reconstructible from
119
+ a tracking row — `create_table_<table>` → `DROP TABLE IF EXISTS "<table>"`,
120
+ the exact statement `db:migrate` records for `diff.added_tables`.
121
+
122
+ - Rows with a reconstructible DOWN are **executed** through
123
+ `MigrationTracker.rollback` (transactional), then marked `rolled_back`.
124
+ - Anything else is **refused**: non-zero exit, an error naming each migration
125
+ and why, and no row touched. Refusal is all-or-nothing across the selected
126
+ set — a partial revert would leave the chain in a state the remaining DOWN
127
+ scripts were not written against. This includes rows recorded
128
+ `is_reversible` under a caller-chosen name: reversible at apply time, but the
129
+ SQL was never persisted, so it cannot be replayed (#2378).
130
+ - `--mark-only` is the explicit opt-in for the record-only flip (for an
131
+ operator who already reverted the schema by hand). It says in its own output
132
+ that the schema was not changed, and it is the only path that moves a row
133
+ without running DDL.
134
+ - A failed DOWN stops the batch; the migrations behind it are reported
135
+ `Not attempted` and left `completed`.
136
+ - `--dry-run` previews the DOWN statements and still exits non-zero when the
137
+ real run would refuse. Both `--dry-run` and `--mark-only` are declared
138
+ kebab-cased, so handlers must read `options['dry-run']` / `options['mark-only']`
139
+ (`parseCliArgs` returns keys verbatim — the #1385 data-loss class).
140
+
141
+ Reverting a non-`create_table` change is a forward operation: update the
142
+ `@smrt` object definitions and run `db:migrate` again.
143
+
144
+ ## `db:prune` is the retention cron entry point (#2375)
145
+
146
+ Runs `runRetentionSweep()` from `@happyvertical/smrt-core` over every
147
+ framework-owned system table plus every task other installed packages
148
+ registered (`_smrt_jobs`/`_smrt_job_events` from `smrt-jobs`, expired
149
+ sessions/magic-link tokens/CLI-auth requests from `smrt-users`).
150
+
151
+ - Defaults are the framework's documented retention windows; `retention` in
152
+ `smrt.config` overrides them persistently, and `--changes-days`,
153
+ `--usage-days`, `--dispatch-days` override them for one run.
154
+ - `--skip` takes **task** names, not table names — the same names the report
155
+ and `--json` print, so a package-contributed task is skipped the same way a
156
+ built-in one is.
157
+ - `--dry-run` counts with the identical predicates instead of deleting; the
158
+ report says `would prune`.
159
+ - The exit code is non-zero when **any** task failed, so a partial sweep never
160
+ looks clean to cron. Individual task failures never abort the others.
161
+ - Deployments running a jobs `TaskRunner` already get the same sweep every six
162
+ hours; this command is for those that do not, and for one-off operator runs.
163
+
37
164
  ## Architecture
38
165
 
39
166
  - **Lazy command loading**: commands loaded on-demand via dynamic import (~100ms overhead on first use)
@@ -64,3 +191,7 @@ migrations are manifest-driven through registered objects and project manifests.
64
191
  global when it appears before or without a subcommand (#2279). `--help` is
65
192
  deliberately untouched.
66
193
  - **Schema history nuance**: `db:status` / `db:history` should distinguish active live drift from superseded failed generated schema repairs instead of treating all failed rows as current blockers
194
+ - **Decorator check follows the Vite major**: doctor requires `oxc.decorator` in
195
+ vite.config on Vite 8+ and accepts tsconfig `experimentalDecorators` only
196
+ below it. The old unconditional tsconfig check flagged correct Vite 8 projects
197
+ and passed broken ones (#2368)
package/README.md CHANGED
@@ -48,7 +48,8 @@ The four snapshot options are atomic: supplying any one requires all four.
48
48
  | `smrt db:migrate --force-migration <exact-id> [--force-migration <exact-id>...]` | Force one or more exact generated migrations in one atomic batch while preserving every other guard |
49
49
  | `smrt db:migrate-uuid` | Convert schema-declared UUID text columns to native PostgreSQL uuid after data has been remapped |
50
50
  | `smrt db:diff` | Show schema differences without generating migration files |
51
- | `smrt db:rollback` | Rollback last migration |
51
+ | `smrt db:rollback` | Roll back the last migration by executing its recorded DOWN script; refuses when no DOWN script exists |
52
+ | `smrt db:rollback --mark-only` | Record-only: mark migrations rolled back without running any DOWN script (schema untouched) |
52
53
  | `smrt db:history` | Show migration history with active-vs-superseded failure classification |
53
54
 
54
55
  File-backed SQL/TypeScript migration generation is not supported. s-m-r-t schema