From 1c8aa157494d1fd9c641799733bdb1d4db25e24e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 11:35:00 +0000 Subject: [PATCH] Version Packages --- .changeset/agent-channel-not-transport.md | 38 - .changeset/agent-provenance-executed-by.md | 11 - .changeset/binary-release-automation.md | 6 - .changeset/bold-wolves-call.md | 10 - .changeset/brave-foxes-modify.md | 8 - .changeset/brave-wolves-rest.md | 5 - .changeset/bright-foxes-glow.md | 16 - .changeset/build-rerun-after-skip.md | 7 - .changeset/bundle-fix.md | 8 - .changeset/calm-rivers-flow.md | 7 - .changeset/change-dry-run-cli.md | 8 - .changeset/change-dry-run-sdk.md | 6 - .changeset/change-interactive-prompts.md | 19 - .changeset/change-reapply-and-rewind-fixes.md | 16 - .changeset/citty-cli-migration.md | 16 - .changeset/citty-sdk-next.md | 7 - .changeset/cjs-shim-fix.md | 8 - .changeset/crisp-taxis-pick.md | 30 - .changeset/dry-run-error-display.md | 7 - .changeset/fix-binary-bun-pin.md | 6 - .changeset/fix-bundle-version.md | 5 - .changeset/fix-headless-error-output.md | 15 - .changeset/fix-helpers-loading.md | 12 - .changeset/fix-mssql-connection-hang.md | 11 - .changeset/fix-mssql-dialect-support-sdk.md | 10 - .changeset/fix-mssql-dialect-support.md | 19 - .../fix-mssql-mysql-schema-migration.md | 11 - .../fix-mssql-tarn-bundle-interop-cli.md | 11 - .changeset/fix-mssql-tarn-bundle-interop.md | 11 - .changeset/fix-sdk-bundle-deps.md | 15 - .changeset/fix-shutdown-hang.md | 8 - .changeset/fix-teardown-schema-qualify-cli.md | 7 - .changeset/fix-teardown-schema-qualify.md | 7 - .changeset/fix-teardown-tvp-ordering-cli.md | 7 - .changeset/fix-teardown-tvp-ordering.md | 7 - .changeset/generic-tvp-value.md | 6 - .changeset/gentle-birds-impersonate.md | 9 - .changeset/gold-items-feel.md | 35 - .../headless-envelope-and-exit-codes.md | 29 - .changeset/hip-pigs-knock.md | 34 - .changeset/humble-emus-jam.md | 167 ---- .../identity-ci-settings-edit-secret.md | 9 - .changeset/init-nested-projects.md | 10 - .changeset/keen-moons-glow.md | 7 - .changeset/kind-dogs-cheer.md | 6 - .changeset/mcp-default-viewer.md | 15 - .changeset/mcp-server.md | 8 - .changeset/mssql-connect-by-ip.md | 11 - .changeset/mssql-go-splitter-sdk.md | 11 - .changeset/mssql-teardown-sdk.md | 8 - .changeset/noorm-ci-namespace.md | 28 - .changeset/noorm-init-sql-repl.md | 8 - .changeset/pre.json | 83 -- .changeset/quiet-pandas-sleep.md | 7 - .changeset/rebuild-fix.md | 5 - .../reset-ignores-preserve-tables-cli.md | 13 - .changeset/reset-ignores-preserve-tables.md | 13 - .changeset/rich-errors-templates-headless.md | 18 - .changeset/runner-observability-cli.md | 9 - .changeset/sdk-protected-config-hardblock.md | 37 - .changeset/sharp-foxes-run.md | 5 - .changeset/swift-clouds-drift.md | 7 - ...teardown-mssql-check-constraint-udf-cli.md | 13 - .../teardown-mssql-check-constraint-udf.md | 13 - .changeset/tender-lions-enter.md | 32 - .changeset/tiny-dogs-yawn.md | 5 - .changeset/transfer-negation-flags.md | 6 - .changeset/tty-yes-flag-cli.md | 8 - .changeset/tui-change-parity.md | 8 - .changeset/tvp-support.md | 8 - .changeset/typed-tuples-sdk.md | 6 - .changeset/unmatched-include-warning.md | 7 - .changeset/update-progress-stall.md | 5 - .changeset/update-resumable-download.md | 5 - .changeset/v1-authorization-seam.md | 22 - .changeset/v1-crypto-identity.md | 15 - .changeset/v1-data-integrity.md | 21 - .changeset/v1-explore-correctness.md | 14 - .changeset/v1-mysql-dialect.md | 12 - .changeset/v1-tui-parity.md | 14 - .changeset/v1-update-logging.md | 13 - .changeset/vault-init-idempotent-sdk.md | 8 - .changeset/version-command.md | 5 - .changeset/version-debug.md | 5 - .changeset/warm-apples-march.md | 13 - .changeset/warm-tables-stay.md | 9 - .changeset/wise-owls-guard.md | 17 - .changeset/worker-bridge.md | 14 - examples/llm-memory-db-mssql/CHANGELOG.md | 48 ++ examples/llm-memory-db-mssql/package.json | 2 +- examples/llm-memory-db-pg/CHANGELOG.md | 48 ++ examples/llm-memory-db-pg/package.json | 2 +- examples/todo-db/CHANGELOG.md | 48 ++ examples/todo-db/package.json | 2 +- packages/cli/CHANGELOG.md | 765 ++++++++++++++++++ packages/cli/package.json | 2 +- packages/sdk/CHANGELOG.md | 627 ++++++++++++++ packages/sdk/package.json | 2 +- 98 files changed, 1541 insertions(+), 1296 deletions(-) delete mode 100644 .changeset/agent-channel-not-transport.md delete mode 100644 .changeset/agent-provenance-executed-by.md delete mode 100644 .changeset/binary-release-automation.md delete mode 100644 .changeset/bold-wolves-call.md delete mode 100644 .changeset/brave-foxes-modify.md delete mode 100644 .changeset/brave-wolves-rest.md delete mode 100644 .changeset/bright-foxes-glow.md delete mode 100644 .changeset/build-rerun-after-skip.md delete mode 100644 .changeset/bundle-fix.md delete mode 100644 .changeset/calm-rivers-flow.md delete mode 100644 .changeset/change-dry-run-cli.md delete mode 100644 .changeset/change-dry-run-sdk.md delete mode 100644 .changeset/change-interactive-prompts.md delete mode 100644 .changeset/change-reapply-and-rewind-fixes.md delete mode 100644 .changeset/citty-cli-migration.md delete mode 100644 .changeset/citty-sdk-next.md delete mode 100644 .changeset/cjs-shim-fix.md delete mode 100644 .changeset/crisp-taxis-pick.md delete mode 100644 .changeset/dry-run-error-display.md delete mode 100644 .changeset/fix-binary-bun-pin.md delete mode 100644 .changeset/fix-bundle-version.md delete mode 100644 .changeset/fix-headless-error-output.md delete mode 100644 .changeset/fix-helpers-loading.md delete mode 100644 .changeset/fix-mssql-connection-hang.md delete mode 100644 .changeset/fix-mssql-dialect-support-sdk.md delete mode 100644 .changeset/fix-mssql-dialect-support.md delete mode 100644 .changeset/fix-mssql-mysql-schema-migration.md delete mode 100644 .changeset/fix-mssql-tarn-bundle-interop-cli.md delete mode 100644 .changeset/fix-mssql-tarn-bundle-interop.md delete mode 100644 .changeset/fix-sdk-bundle-deps.md delete mode 100644 .changeset/fix-shutdown-hang.md delete mode 100644 .changeset/fix-teardown-schema-qualify-cli.md delete mode 100644 .changeset/fix-teardown-schema-qualify.md delete mode 100644 .changeset/fix-teardown-tvp-ordering-cli.md delete mode 100644 .changeset/fix-teardown-tvp-ordering.md delete mode 100644 .changeset/generic-tvp-value.md delete mode 100644 .changeset/gentle-birds-impersonate.md delete mode 100644 .changeset/gold-items-feel.md delete mode 100644 .changeset/headless-envelope-and-exit-codes.md delete mode 100644 .changeset/hip-pigs-knock.md delete mode 100644 .changeset/humble-emus-jam.md delete mode 100644 .changeset/identity-ci-settings-edit-secret.md delete mode 100644 .changeset/init-nested-projects.md delete mode 100644 .changeset/keen-moons-glow.md delete mode 100644 .changeset/kind-dogs-cheer.md delete mode 100644 .changeset/mcp-default-viewer.md delete mode 100644 .changeset/mcp-server.md delete mode 100644 .changeset/mssql-connect-by-ip.md delete mode 100644 .changeset/mssql-go-splitter-sdk.md delete mode 100644 .changeset/mssql-teardown-sdk.md delete mode 100644 .changeset/noorm-ci-namespace.md delete mode 100644 .changeset/noorm-init-sql-repl.md delete mode 100644 .changeset/pre.json delete mode 100644 .changeset/quiet-pandas-sleep.md delete mode 100644 .changeset/rebuild-fix.md delete mode 100644 .changeset/reset-ignores-preserve-tables-cli.md delete mode 100644 .changeset/reset-ignores-preserve-tables.md delete mode 100644 .changeset/rich-errors-templates-headless.md delete mode 100644 .changeset/runner-observability-cli.md delete mode 100644 .changeset/sdk-protected-config-hardblock.md delete mode 100644 .changeset/sharp-foxes-run.md delete mode 100644 .changeset/swift-clouds-drift.md delete mode 100644 .changeset/teardown-mssql-check-constraint-udf-cli.md delete mode 100644 .changeset/teardown-mssql-check-constraint-udf.md delete mode 100644 .changeset/tender-lions-enter.md delete mode 100644 .changeset/tiny-dogs-yawn.md delete mode 100644 .changeset/transfer-negation-flags.md delete mode 100644 .changeset/tty-yes-flag-cli.md delete mode 100644 .changeset/tui-change-parity.md delete mode 100644 .changeset/tvp-support.md delete mode 100644 .changeset/typed-tuples-sdk.md delete mode 100644 .changeset/unmatched-include-warning.md delete mode 100644 .changeset/update-progress-stall.md delete mode 100644 .changeset/update-resumable-download.md delete mode 100644 .changeset/v1-authorization-seam.md delete mode 100644 .changeset/v1-crypto-identity.md delete mode 100644 .changeset/v1-data-integrity.md delete mode 100644 .changeset/v1-explore-correctness.md delete mode 100644 .changeset/v1-mysql-dialect.md delete mode 100644 .changeset/v1-tui-parity.md delete mode 100644 .changeset/v1-update-logging.md delete mode 100644 .changeset/vault-init-idempotent-sdk.md delete mode 100644 .changeset/version-command.md delete mode 100644 .changeset/version-debug.md delete mode 100644 .changeset/warm-apples-march.md delete mode 100644 .changeset/warm-tables-stay.md delete mode 100644 .changeset/wise-owls-guard.md delete mode 100644 .changeset/worker-bridge.md diff --git a/.changeset/agent-channel-not-transport.md b/.changeset/agent-channel-not-transport.md deleted file mode 100644 index 4e8a6d34..00000000 --- a/.changeset/agent-channel-not-transport.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -"@noormdev/cli": major -"@noormdev/sdk": major ---- - -Resolve the access channel from who is driving, not which binary was invoked - -`Channel` used to name the transport: `user` meant the CLI/TUI/SDK and `mcp` -meant the MCP server. Those only coincide when a human is at the keyboard. An -agent refused a write over MCP could see `noorm` on the PATH and shell out, -and because the CLI hardcoded `user` at every policy call site, that second -attempt ran with the human's role. On a stock config that turned deny into -allow for `sql:write`, `sql:ddl`, `db:create`, `run:build` and `vault:read`, -and turned `db:destroy` into a confirm that `--yes` satisfied. - -Two breaking changes: - -**The config fields are renamed.** `Channel` is now `'user' | 'agent'`, and -`ConfigAccess` is `{ user, agent }` instead of `{ user, mcp }`. `agent: false` -hides a config from agents on *both* transports, not just over MCP. Stored -state migrates automatically (state schema v3) and carries every value over -verbatim — `mcp: 'operator'` becomes `agent: 'operator'`, `mcp: false` becomes -`agent: false`. SDK callers passing `channel: 'mcp'` to `createContext` must -pass `'agent'`, and anything reading or writing `config.access.mcp` must use -`config.access.agent`. In the TUI, the "MCP Role" field is now "Agent Role". - -**Agents shelling out to the CLI now get the agent role.** The CLI resolves -its channel from provenance via `resolveChannel()`: an allowlist of variables -the agent harnesses (Claude Code, Codex, Cursor, Gemini CLI) set for their own -child processes. A stock config gives agents `viewer`, so commands that used -to succeed inside an agent session are now refused — that is the fix, not a -regression. `TERM_PROGRAM`, `CI` and TTY state are deliberately not consulted; -they describe the terminal or the pipeline, not the caller. - -Set `NOORM_CHANNEL=user` to opt out when a human is scripting from inside an -agent session, or `NOORM_CHANNEL=agent` to opt in with no harness present. An -agent can set that variable too; this defends against one routing around a -refusal, not one deliberately evading the check. diff --git a/.changeset/agent-provenance-executed-by.md b/.changeset/agent-provenance-executed-by.md deleted file mode 100644 index d78a4e6c..00000000 --- a/.changeset/agent-provenance-executed-by.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@noormdev/cli": minor -"@noormdev/sdk": minor ---- - -## Added - -* `feat(identity):` Record the detected agent harness in operation provenance. When a session runs under a recognised agent harness, `executed_by` is suffixed with `(via )` — so a change applied by an agent is distinguishable from one a human applied. Stamped at the shared insert seam, so change operations, resets and run operations all carry it on every dialect. -* `feat(cli):` `noorm info` reports the detected harness and the environment variables that identified it, so an agent-driven session is visible rather than silent. - -Provenance is folded into the existing identity string rather than a new column: the audit question is binary, and a suffix answers it without a four-dialect schema migration. It is not an attestation — `executed_by` is unauthenticated free text and harness detection reads caller-controlled environment variables, so the suffix records what noorm observed, not a proven claim. diff --git a/.changeset/binary-release-automation.md b/.changeset/binary-release-automation.md deleted file mode 100644 index 36ebad25..00000000 --- a/.changeset/binary-release-automation.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Added -* `feat(ci):` Automated binary builds on release with install script diff --git a/.changeset/bold-wolves-call.md b/.changeset/bold-wolves-call.md deleted file mode 100644 index 68f057bf..00000000 --- a/.changeset/bold-wolves-call.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@noormdev/sdk": minor ---- - -### Added -* `feat(sdk):` Add `ctx.tvf()` method for calling table-valued functions on MSSQL and PostgreSQL -* `feat(sdk):` Add `Tvfs` generic parameter to `createContext()`, `Context`, and `ImpersonatedScope` for type-safe TVF signatures - -### Changed -* `refactor(sdk):` Replace `as any` casts in impersonation scope with proper generic flow through `buildProcCall`, `buildFuncCall`, and `buildTvfCall` diff --git a/.changeset/brave-foxes-modify.md b/.changeset/brave-foxes-modify.md deleted file mode 100644 index b0b95699..00000000 --- a/.changeset/brave-foxes-modify.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -## Added - -* `feat(dt):` DT file modifier — drop, add, or rename columns and filter rows from exported `.dt`/`.dtz`/`.dtzx` files without re-exporting from the source database -* `feat(cli):` "Modify .dt file" option in the Data Transfer screen with interactive recipe builder, schema preview, and streaming output diff --git a/.changeset/brave-wolves-rest.md b/.changeset/brave-wolves-rest.md deleted file mode 100644 index ec3e5b1d..00000000 --- a/.changeset/brave-wolves-rest.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -Add graceful shutdown screen with phase progress display on exit. Migrate test runner from vitest to bun test. Extract shared CLI hooks and utilities to reduce duplication across screens. diff --git a/.changeset/bright-foxes-glow.md b/.changeset/bright-foxes-glow.md deleted file mode 100644 index 204eae01..00000000 --- a/.changeset/bright-foxes-glow.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"@noormdev/sdk": minor ---- - -## DT Format - -### Added - -* `feat(dt):` Add `text` encoded type for large TEXT columns with smart gz64 compression -* `feat(dt):` Map MSSQL `nvarchar(max)` and `varchar(max)` to `text` encoded type -* `feat(dt):` Detect `(max)` suffix in MSSQL schema introspection via `max_length = -1` - -### Changed - -* `refactor(dt):` MySQL `text`, `mediumtext`, `longtext` now map to `text` (was `string`); `tinytext` stays `string` -* `refactor(dt):` MSSQL `text`, `ntext` now map to `text` (was `string`) diff --git a/.changeset/build-rerun-after-skip.md b/.changeset/build-rerun-after-skip.md deleted file mode 100644 index 85c9088f..00000000 --- a/.changeset/build-rerun-after-skip.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/cli": patch -"@noormdev/sdk": patch ---- - -## Fixed -* `fix(runner):` a third consecutive `run build` no longer re-executes a file the previous build correctly skipped — `skipped` was treated as "never ran" regardless of why, so an `unchanged` skip forced a re-run and failed on any DDL that is not idempotent diff --git a/.changeset/bundle-fix.md b/.changeset/bundle-fix.md deleted file mode 100644 index f5e92fa9..00000000 --- a/.changeset/bundle-fix.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## CLI - -### Fixed -* `fix(build):` Bundle all pure JS dependencies (meow, ink, react, pg, mysql2, tedious) - only better-sqlite3 remains external due to native bindings diff --git a/.changeset/calm-rivers-flow.md b/.changeset/calm-rivers-flow.md deleted file mode 100644 index 98cb017b..00000000 --- a/.changeset/calm-rivers-flow.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -### Fixed - -* `fix(settings):` NOORM_* environment variables now override any settings.yml field (e.g. `NOORM_PATHS_SQL` overrides `paths.sql`) diff --git a/.changeset/change-dry-run-cli.md b/.changeset/change-dry-run-cli.md deleted file mode 100644 index 24e81ccd..00000000 --- a/.changeset/change-dry-run-cli.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Fixed -* `fix(cli):` Wire `noorm change ff --dry-run` (and `next` / `run` / `revert`) through to the SDK — the flag previously parsed but did nothing -* `fix(cli):` Drop the unreachable positional `query` arg from `noorm sql` and add an argv rewriter so `noorm sql "SELECT 1"` is rewritten to `noorm sql query "SELECT 1"` before citty dispatch -* `fix(cli):` Show `(dry-run)` markers in human output and `dryRun: true` in `--json` envelopes for all four change commands diff --git a/.changeset/change-dry-run-sdk.md b/.changeset/change-dry-run-sdk.md deleted file mode 100644 index 0d52f4dd..00000000 --- a/.changeset/change-dry-run-sdk.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -## Changed -* `feat(sdk):` `changes.ff(options?)` and `changes.next(count, options?)` now accept `BatchChangeOptions` so callers can pass `dryRun` / `force`. Previously the options were silently dropped before reaching the manager. diff --git a/.changeset/change-interactive-prompts.md b/.changeset/change-interactive-prompts.md deleted file mode 100644 index 8baf3c7b..00000000 --- a/.changeset/change-interactive-prompts.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -Rework `noorm change` for interactive-first ergonomics and drop the surprising bare-invocation side effect. - -- Bare `noorm change` now renders citty's help output and **does not connect to the database**. The status listing that used to live there moved to a new explicit leaf: `noorm change list`. This matches every other root command (`config`, `settings`, `identity`, `db`, `vault`, `secret`, `run`) and prevents accidental connection attempts when users are just exploring the CLI. -- `change list` — new. Lists every known change with its status; accepts `--config`, `--json`. -- `change edit [name]` — new. Resolves the change folder from settings and spawns `$EDITOR` (then `$VISUAL`, then `code`) with stdio inherited so terminal editors work in-place; exits with the editor's own exit code. Surfaces spawn failures (e.g. editor binary not found) instead of silently no-oping. -- `change add [description]` — prompts for a description via `p.text` when omitted on a TTY. -- `change rm [name]` — prompts for a change to delete, then confirms with `p.confirm`. `--yes` is no longer required on a TTY (it now just skips the confirm). On a non-TTY, both the name and `--yes` are still required so CI never deletes silently. -- `change run [name]` — prompts from pending/reverted changes (filters `ctx.noorm.changes.status()` by `!orphaned && status in { pending, reverted }`). -- `change revert [name]` — prompts from successfully applied changes. -- `change rewind [name]` — prompts from successfully applied changes. -- `change history-detail [name]` — prompts from changes with execution history (`status !== 'pending'`). - -In every case, non-TTY invocations without a name exit 1 with a uniform `Change name required…` error. User cancellation from any picker exits 1 with `Cancelled.`. - -**Migration:** replace `noorm change` with `noorm change list` in any scripts or CI jobs that relied on the status listing. The `--json` shape is unchanged. diff --git a/.changeset/change-reapply-and-rewind-fixes.md b/.changeset/change-reapply-and-rewind-fixes.md deleted file mode 100644 index 9d7b57fe..00000000 --- a/.changeset/change-reapply-and-rewind-fixes.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@noormdev/cli': patch -'@noormdev/sdk': patch ---- - -Fix change apply/revert recovery, rewind flag handling, and MySQL support - -- Changes now run on MySQL at all. `ChangeHistory` retrieved insert ids with a `RETURNING` clause MySQL does not support, so no operation record was ever created and `change run`, `change ff`, `change revert` and `db teardown` were all inoperable. -- A reverted or torn-down change can be applied again. Every file was previously skipped against a prior success, so `apply -> revert -> apply` and `db teardown -> change ff` reported success over an untouched database. -- `ff` and `next` now treat `stale` changes as pending work, so teardown has a supported recovery path. -- `change rewind` honours `--dry-run` and `--force`, and accepts the documented count form (`change rewind 3`). `changes.rewind()` takes an options argument. -- A revert whose history cannot be read fails instead of reporting success over zero files. -- `.sql.tmpl` files inside a change now receive `$.config` and `$.secrets`. -- `.txt` manifests execute in the order they list files, instead of being sorted. -- `ff`/`next` warn when the changes directory is missing rather than reporting a clean run. -- `change list` marks orphaned changes and no longer lists the internal `__reset__` teardown marker. diff --git a/.changeset/citty-cli-migration.md b/.changeset/citty-cli-migration.md deleted file mode 100644 index 95d7ae97..00000000 --- a/.changeset/citty-cli-migration.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -## Added - -* `feat(cli):` Migrate CLI from meow to citty with structured subcommands, built-in `--help` examples, and `noorm ui` launcher -* `feat(cli):` Add shell completion via `@bomb.sh/tab` - -## Fixed - -* `fix(change):` Populate `cli_version` in change history rows -* `fix(cli):` Suppress `logger.info` inside `fn()` callbacks when `--json` is active -* `fix(cli):` Restore vault cp dry-run preview and 3-positional CLI surface -* `fix(cli):` Wire JSON output for change run/revert and unify history JSON path -* `fix(cli):` Restore human-readable db transfer output diff --git a/.changeset/citty-sdk-next.md b/.changeset/citty-sdk-next.md deleted file mode 100644 index 5f1ccb4a..00000000 --- a/.changeset/citty-sdk-next.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/sdk": minor ---- - -## Added - -* `feat(change):` Expose `change next` on the SDK for programmatic access to the next pending change diff --git a/.changeset/cjs-shim-fix.md b/.changeset/cjs-shim-fix.md deleted file mode 100644 index bcc0cdf2..00000000 --- a/.changeset/cjs-shim-fix.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## CLI - -### Fixed -* `fix(build):` Add CJS compatibility shim for dynamic require in ESM bundle diff --git a/.changeset/crisp-taxis-pick.md b/.changeset/crisp-taxis-pick.md deleted file mode 100644 index 37168143..00000000 --- a/.changeset/crisp-taxis-pick.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -"@noormdev/cli": major -"@noormdev/sdk": major ---- - -## Template Inspector & Execution Control - -### CLI - -- **Template Inspector Screen** (`run/inspect`, `[i]` shortcut): New dedicated screen for debugging SQL templates - - Categorized context view (data files, helpers, built-ins, config, secrets, environment) - - Array shape detection to debug property access failures - - Template preview with render error display - - Refresh support for iterative debugging - -- **Rerun Confirmation**: Pre-execution file status check with dialog - - Shows count of new, previously-run, changed, and failed files - - Confirmation prompt before re-running previously-executed files - - `[r]` retry shortcut in all run screens (respects `--force` flag) - -- **Execution Cancellation**: `[c]` to abort long-running operations - - Destroys connection to cleanly stop execution - - Shows "Execution cancelled" error state - -- **Unified KeyHandler Pattern**: Replaced per-screen focus handlers with flexible KeyHandler component - -### SDK - -- `checkFilesStatus()` function for pre-execution file status categorization -- New types: `FileStatusCategory`, `FileStatusResult`, `FilesStatusResult` diff --git a/.changeset/dry-run-error-display.md b/.changeset/dry-run-error-display.md deleted file mode 100644 index 43bd620c..00000000 --- a/.changeset/dry-run-error-display.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -Display template errors during dry-run in UI feedback - -Template rendering errors during dry-run were silently captured in results but never emitted via the observer event system, making them invisible in the UI. Now `file:dry-run` events include status and error fields, and the progress hook properly tracks failed dry-runs. diff --git a/.changeset/fix-binary-bun-pin.md b/.changeset/fix-binary-bun-pin.md deleted file mode 100644 index 86cda349..00000000 --- a/.changeset/fix-binary-bun-pin.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Fixed -* `fix(ci):` Pin bun to 1.3.11 in release binary workflow — bun 1.3.12 produces binaries that crash on startup (OOM kill, exit 137) diff --git a/.changeset/fix-bundle-version.md b/.changeset/fix-bundle-version.md deleted file mode 100644 index d8fa0951..00000000 --- a/.changeset/fix-bundle-version.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -Fix bundle: inject version at build time instead of requiring package.json diff --git a/.changeset/fix-headless-error-output.md b/.changeset/fix-headless-error-output.md deleted file mode 100644 index b84029ef..00000000 --- a/.changeset/fix-headless-error-output.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -### Fixed - -- `fix(headless):` Produce structured JSON error output (`{ success, error }`) when `--json` is set — previously errors were only logged as text, leaving CI pipelines with no parseable output on failure -- `fix(headless):` Enrich SQL error messages with dialect-aware diagnostics (line numbers, error codes, procedure names, severity) via `getSqlErrorMessage` in all headless command error paths -- `fix(headless):` Standardize `run build` exit code from `2` to `1` to match the `0`/`1` convention used by all other headless commands -- `fix(headless):` Replace stale `.sql.eta` file extension references with `.sql.tmpl` across CLI argument parsing, help text, and documentation - -### Added - -- `feat(headless):` Add `sql` command to the home help commands list -- `feat(headless):` Document `.sql.tmpl` template file support in `run` help text diff --git a/.changeset/fix-helpers-loading.md b/.changeset/fix-helpers-loading.md deleted file mode 100644 index 002c0462..00000000 --- a/.changeset/fix-helpers-loading.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -### Fixed - -- `fix(template):` Resolve `$helpers` loading in compiled binaries — bare specifier resolution now uses `Bun.build()` to bundle helper files with all dependencies, fixing `Cannot find package` errors in pnpm projects -- `fix(inspect):` Show `$helpers` exports in Inspect Template screen — categorization now uses source-based tracking instead of type-guessing, and load errors are surfaced instead of silently swallowed - -### Added - -- `feat(cli):` Add `dev/test-helpers` diagnostic command for verifying `$helpers` loading from any execution context diff --git a/.changeset/fix-mssql-connection-hang.md b/.changeset/fix-mssql-connection-hang.md deleted file mode 100644 index 0664c451..00000000 --- a/.changeset/fix-mssql-connection-hang.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Connection - -### Fixed -* `fix(mssql):` Connection hangs when target database does not exist — now verifies via `sys.databases` on `master` first -* `fix(mssql):` `ECONNRESET` on MSSQL Server 2022+ due to `encrypt: false` — now defaults to `encrypt: true` -* `fix(mssql):` Tarn pool silently retries failed connections — enabled `propagateCreateError` for fast failure -* `fix(connection):` Retry logic retried non-transient errors like `login failed` and `access denied` diff --git a/.changeset/fix-mssql-dialect-support-sdk.md b/.changeset/fix-mssql-dialect-support-sdk.md deleted file mode 100644 index 35153825..00000000 --- a/.changeset/fix-mssql-dialect-support-sdk.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -## Fixed -* `fix(mssql):` Resolve SQL Server failures in runner and change tracking operations -* `fix(mssql):` Add `OUTPUT inserted.id` support for MSSQL insert operations -* `fix(mssql):` Translate `.limit()` to `TOP` via MssqlLimitPlugin -* `fix(schema):` Make v2 migration fully idempotent with partial-state recovery -* `fix(runner):` Handle `AggregateError` and non-standard error objects from tedious driver diff --git a/.changeset/fix-mssql-dialect-support.md b/.changeset/fix-mssql-dialect-support.md deleted file mode 100644 index 04a32e44..00000000 --- a/.changeset/fix-mssql-dialect-support.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Fixed -* `fix(mssql):` Resolve SQL Server build failures caused by Kysely emitting unsupported `LIMIT` and `RETURNING` syntax -* `fix(mssql):` Add MssqlLimitPlugin to translate `.limit()` to `TOP` for all MSSQL queries -* `fix(mssql):` Use `OUTPUT inserted.id` instead of `RETURNING` for insert-and-get-id operations -* `fix(mssql):` Pass dialect from connection through all run screens to runner context -* `fix(schema):` Make v2 migration fully idempotent — handles partial migration states, orphaned tables, and interrupted runs -* `fix(schema):` Handle interrupted v1 migrations where tables exist but version record is missing -* `fix(schema):` Run `ensureSchemaVersion` before identity sync to prevent queries against unmigrated tables -* `fix(schema):` Move `waitForIdentityToLoad` out of connection factory into schema migration lifecycle -* `fix(logger):` Preserve Error objects in log redaction filter instead of spreading into empty `{}` -* `fix(logger):` Surface `.cause` chain in error log messages for wrapped errors -* `fix(runner):` Handle non-standard error objects from tedious driver including `AggregateError` -* `fix(runner):` Propagate batch-level errors to TUI when build fails before file execution -* `fix(runner):` Truncate `skip_reason` to prevent column overflow on MSSQL -* `fix(tui):` Show relative file paths in failed files list instead of basename only diff --git a/.changeset/fix-mssql-mysql-schema-migration.md b/.changeset/fix-mssql-mysql-schema-migration.md deleted file mode 100644 index e4179f7e..00000000 --- a/.changeset/fix-mssql-mysql-schema-migration.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Schema - -### Fixed -* `fix(schema):` MSSQL schema migration fails with `auto_increment` syntax error — use `identity(1,1)` instead -* `fix(schema):` MSSQL schema migration fails when multiple `timestamp` columns exist — use `datetime2` for MSSQL -* `fix(schema):` MySQL schema migration fails on `TEXT` columns with default values — use `varchar(2000)` for error messages -* `fix(schema):` MySQL and MSSQL `DROP INDEX` requires `ON table_name` — made `down()` dialect-aware diff --git a/.changeset/fix-mssql-tarn-bundle-interop-cli.md b/.changeset/fix-mssql-tarn-bundle-interop-cli.md deleted file mode 100644 index 9fde98a1..00000000 --- a/.changeset/fix-mssql-tarn-bundle-interop-cli.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -fix(mssql): noorm db create / run no longer crash with "Pool is not a constructor" - -In the bundled CLI, `await import('tarn')` / `await import('tedious')` expose -their exports under `.default`, so kysely's MSSQL dialect received an undefined -`Pool` and every MSSQL command (`noorm db create`, `run`, `change`, etc.) failed -with `Cannot connect to server: Pool is not a constructor`. Normalize the CJS -interop, mirroring the postgres dialect. diff --git a/.changeset/fix-mssql-tarn-bundle-interop.md b/.changeset/fix-mssql-tarn-bundle-interop.md deleted file mode 100644 index 74128d9a..00000000 --- a/.changeset/fix-mssql-tarn-bundle-interop.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -fix(mssql): construct tarn/tedious through CJS-interop guard in bundles - -When the SDK is bundled (tsup), `await import('tarn')` / `await import('tedious')` -expose their exports under `.default`, so spreading the namespace left -`tarn.Pool` undefined and kysely threw `Pool is not a constructor` on every -MSSQL connection. Normalize both with `module.default ?? module`, mirroring the -postgres dialect's existing guard. diff --git a/.changeset/fix-sdk-bundle-deps.md b/.changeset/fix-sdk-bundle-deps.md deleted file mode 100644 index 576ad2f9..00000000 --- a/.changeset/fix-sdk-bundle-deps.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -### Fixed - -- `fix(sdk):` Bundle all runtime dependencies — resolves `Cannot find package 'json5'` and similar errors when importing the SDK -- `fix(sdk):` Add `createRequire` banner for CJS packages that use `require('process')` in ESM bundles -- `fix(template):` Resolve `$helpers` loading in compiled binaries via `Bun.build()` bundling - -### Changed - -- `perf(sdk):` Lazy-load template data parsers (JSON5, YAML, CSV) — heavy parser libraries are now deferred until first use, reducing SDK startup time -- `perf(sdk):` Replace `voca` dependency with inline `camelCase` implementation (~1500 lines removed from bundle) -- `perf(sdk):` Stub `ansis` terminal color library — SDK consumers don't need ANSI output diff --git a/.changeset/fix-shutdown-hang.md b/.changeset/fix-shutdown-hang.md deleted file mode 100644 index 97d51eca..00000000 --- a/.changeset/fix-shutdown-hang.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Fixed -* `fix(shutdown):` Force `process.exit()` after graceful shutdown to prevent process hanging from lingering connection pool handles -* `fix(shutdown):` Remove duplicate `app:exit` emission that caused `unmount()` to fire twice -* `fix(shutdown):` Clear timeout timer in connection close race to prevent 5-second event loop leak diff --git a/.changeset/fix-teardown-schema-qualify-cli.md b/.changeset/fix-teardown-schema-qualify-cli.md deleted file mode 100644 index 2751bcf4..00000000 --- a/.changeset/fix-teardown-schema-qualify-cli.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -### Fixed - -* `fix(teardown):` Schema-qualify all DROP statements in teardown to prevent failures when the connection user's default schema differs from `dbo` (MSSQL) or `public` (PostgreSQL) diff --git a/.changeset/fix-teardown-schema-qualify.md b/.changeset/fix-teardown-schema-qualify.md deleted file mode 100644 index 2c147567..00000000 --- a/.changeset/fix-teardown-schema-qualify.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -### Fixed - -* `fix(teardown):` Schema-qualify all DROP statements in `db.teardown()` to prevent failures when the connection user's default schema differs from `dbo` (MSSQL) or `public` (PostgreSQL) diff --git a/.changeset/fix-teardown-tvp-ordering-cli.md b/.changeset/fix-teardown-tvp-ordering-cli.md deleted file mode 100644 index ea6a198d..00000000 --- a/.changeset/fix-teardown-tvp-ordering-cli.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -### Fixed - -* `fix(teardown):` Sort composite types (TVPs) before domain types during teardown to prevent dependency failures on MSSQL, which lacks `DROP TYPE ... CASCADE` diff --git a/.changeset/fix-teardown-tvp-ordering.md b/.changeset/fix-teardown-tvp-ordering.md deleted file mode 100644 index 2e36d8b2..00000000 --- a/.changeset/fix-teardown-tvp-ordering.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -### Fixed - -* `fix(teardown):` Sort composite types (TVPs) before domain types during teardown to prevent dependency failures on MSSQL, which lacks `DROP TYPE ... CASCADE` diff --git a/.changeset/generic-tvp-value.md b/.changeset/generic-tvp-value.md deleted file mode 100644 index 6d96f6a0..00000000 --- a/.changeset/generic-tvp-value.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@noormdev/sdk": minor ---- - -## Added -* `feat(tvp):` Make `TvpValue` and `tvp()` generic — `TvpValue` preserves row types through proc, func, and tvf signatures so the compiler catches column mismatches at call sites diff --git a/.changeset/gentle-birds-impersonate.md b/.changeset/gentle-birds-impersonate.md deleted file mode 100644 index 37cded97..00000000 --- a/.changeset/gentle-birds-impersonate.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@noormdev/sdk": minor ---- - -## Added - -* `feat(sdk):` Per-request user impersonation via `ctx.impersonate()` — borrow a dedicated pool connection, switch database identity, and run queries as a specific principal with guaranteed revert -* `feat(sdk):` Callback mode (auto-reverts on completion or throw) and explicit mode (caller-managed lifecycle for cross-boundary use cases like Hapi request hooks) -* `feat(sdk):` MSSQL (`EXECUTE AS USER` / `REVERT`) and PostgreSQL (`SET ROLE` / `RESET ROLE`) dialect support with SQL injection prevention via username validation and dialect-specific quoting diff --git a/.changeset/gold-items-feel.md b/.changeset/gold-items-feel.md deleted file mode 100644 index 54a73408..00000000 --- a/.changeset/gold-items-feel.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -"@noormdev/cli": major -"@noormdev/sdk": major ---- - -Initial release of noorm - Database Schema & Change Manager - -## @noormdev/cli - -### Features - -- **Terminal UI** - Full-featured TUI for managing database schemas -- **Headless Mode** - CLI commands with JSON output for CI/CD pipelines -- **Multi-Dialect Support** - PostgreSQL, MySQL, SQLite, MSSQL -- **Change Detection** - Checksum-based tracking, only changed files re-execute -- **SQL Templates** - Dynamic SQL with Eta templating engine -- **Change Management** - Versioned migrations with forward/revert support -- **Schema Explorer** - Browse tables, views, indexes, functions from terminal -- **SQL Terminal** - Built-in REPL with query history -- **Config Management** - Multiple database configs with encrypted storage -- **Secrets** - Encrypted secret storage with template injection -- **Stages** - Environment templates for teams -- **Protected Configs** - Safety guards for production databases -- **Locking** - Concurrent operation control -- **Identity** - Audit trail with git-based identity - -## @noormdev/sdk - -### Features - -- **Programmatic API** - Full access to noorm functionality -- **Context-based** - Single entry point via `createContext()` -- **Type-safe** - Full TypeScript support -- **Observable** - Event-based architecture with `@logosdx/observer` -- **Test Integration** - `requireTest` guard for safe test database usage diff --git a/.changeset/headless-envelope-and-exit-codes.md b/.changeset/headless-envelope-and-exit-codes.md deleted file mode 100644 index d06c51ac..00000000 --- a/.changeset/headless-envelope-and-exit-codes.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -"@noormdev/cli": major -"@noormdev/sdk": major ---- - -## Headless contract: one `--json` envelope, four exit codes - -### Changed - -* `fix(cli)!:` every `--json` payload is now a JSON **object** carrying a top-level boolean `success`, and is never a bare array. `success` is `true` if and only if the process exits `0`, so `jq -e '.success'` is a failure check that works against every command. - * **Commands that returned a top-level array now return a named object.** `change list` → `.changes` (plus `.pending`), `change history` → `.history`, and every `db explore` list — `tables`, `views`, `indexes`, `fks` (→ `.foreignKeys`), `functions`, `procedures`, `types`, `triggers` — under the matching key. A script doing `jq '.[]'` on any of those must now name the key. - * **Every other payload gained `success` and lost nothing.** Existing key reads keep working. - * `config export --json` now emits the envelope (`{success, name, config}`). The bare artifact — the shape `config import` reads — is still what `noorm config export dev > dev.json` produces, unchanged. - * `noorm update --json` no longer prints two JSON documents for one error, and no longer reports `success: true` on a failed install while exiting non-zero. -* `fix(cli)!:` exit codes now distinguish four outcomes: `0` success, `1` total failure, `2` usage error (bad or missing flag, a TTY-only command run non-interactively, or a named target that does not exist), `3` partial failure. - * **`2` no longer means "partial".** It previously meant partial failure on `run build` / `run dir` / `run files` / `run exec` and the five `change` execution commands — but the same `2` was also returned for a *total* failure on those commands, so the two were indistinguishable. Partial now has its own code. **A pipeline testing `[ $? -eq 2 ]` for "partially applied" must now test `-eq 3`.** Checks of the form `if ! noorm …` are unaffected. - * `ci secrets` partial loads move from `2` to `3`. `db transfer`'s total-failure exit moves from `2` to `1` (its partial exit stays `3`). `vault propagate` and `vault rm`, `dev test-workers` and `dev test-helpers` now report partial and not-found instead of collapsing everything to `1`. - * A "you named something that isn't there" failure — missing file, directory, config, change, secret key, table, or a glob matching nothing — moves from `1` to `2` across the CLI. Confirmation and `--force` refusals deliberately stay at `1`. - -### Fixed - -* `noorm run inspect ` reported a fully-populated context and exit `0` for a file that was never on disk — it only ever read the template's *directory*. It now fails with exit `2`. `run preview` gained the same up-front check so both surfaces agree. -* `noorm run dir` reported success over a directory that contained no SQL files, and reported a missing directory as a SQL failure. Both now exit `2` with a message naming the path. -* `noorm run exec` died with `Bun is not defined` under Node — the documented dev entry point — before doing any work. Glob expansion falls back to `node:fs/promises` when the `Bun` global is absent; the compiled binary keeps Bun's own matcher. -* `noorm run preview --json` put a full stack trace, including absolute filesystem paths, into the machine-readable `error` field. `--json` now carries the message only; the stack still reaches an operator on stderr. -* `noorm lock acquire` gained `--timeout` and `--reason`, which the TUI has always collected — CI-acquired locks were permanently default-timeout and reasonless, so whoever they blocked had no way to see what they were waiting on. -* `withVaultContext` never passed `yes` to the SDK context, so `--yes` / `NOORM_YES` was inert for every vault command. -* `db transfer` declared a `--force` flag that nothing read. -* `run exec`, `db reset`, `vault cp`, and `dev test-helpers` wrote some errors straight to stderr, leaving `--json` callers with an empty stdout and no envelope to parse. diff --git a/.changeset/hip-pigs-knock.md b/.changeset/hip-pigs-knock.md deleted file mode 100644 index abb5a7ac..00000000 --- a/.changeset/hip-pigs-knock.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -"@noormdev/cli": major -"@noormdev/sdk": major ---- - -**BREAKING:** Reorganize flat `NoormOps` into domain-aligned sub-namespaces - -The flat `ctx.noorm.*` API has been replaced with sub-namespaces that mirror the TUI home screen: - -- `ctx.noorm.changes.*` — scaffold, discover, validate, apply, revert, ff, status, pending, history -- `ctx.noorm.run.*` — discover, preview, file, files, dir, build -- `ctx.noorm.db.*` — listTables, describeTable, overview, previewTeardown, truncate, teardown, reset -- `ctx.noorm.lock.*` — acquire, release, status, withLock, forceRelease -- `ctx.noorm.vault.*` — init, status, set, get, getAll, list, delete, exists, propagate, copy -- `ctx.noorm.secrets.*` — get -- `ctx.noorm.templates.*` — render -- `ctx.noorm.transfer.*` — to, plan -- `ctx.noorm.dt.*` — exportTable, importFile -- `ctx.noorm.utils.*` — checksum, testConnection - -**Migration examples:** - -| Before | After | -|--------|-------| -| `ctx.noorm.build()` | `ctx.noorm.run.build()` | -| `ctx.noorm.fastForward()` | `ctx.noorm.changes.ff()` | -| `ctx.noorm.applyChange(name)` | `ctx.noorm.changes.apply(name)` | -| `ctx.noorm.listTables()` | `ctx.noorm.db.listTables()` | -| `ctx.noorm.acquireLock()` | `ctx.noorm.lock.acquire()` | -| `ctx.noorm.truncate()` | `ctx.noorm.db.truncate()` | -| `ctx.noorm.runFile(f)` | `ctx.noorm.run.file(f)` | -| `ctx.noorm.transferTo(c)` | `ctx.noorm.transfer.to(c)` | - -**New capabilities:** change authoring/scaffolding, dry-run previews, file discovery, vault operations, teardown preview diff --git a/.changeset/humble-emus-jam.md b/.changeset/humble-emus-jam.md deleted file mode 100644 index 4c54e127..00000000 --- a/.changeset/humble-emus-jam.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -"@noormdev/cli": major -"@noormdev/sdk": major ---- - -### Cross-Database Data Transfer - -Add a complete data transfer system for copying data between databases, including cross-dialect transfers (PostgreSQL ↔ MySQL ↔ MSSQL). - -**Planning & Execution** - -- Schema introspection via dialect-specific system catalogs to discover tables, columns, primary keys, identity columns, and row estimates -- Foreign key dependency graph with topological sort ensures parent tables transfer before children; graceful fallback on circular dependencies -- Same-server detection with localhost normalization (`127.0.0.1`, `::1`, `localhost.localdomain`) enables direct `INSERT INTO ... SELECT` optimization, bypassing application-level marshalling -- Cross-server path uses configurable batch streaming (default 1000 rows) with per-batch progress events -- Automatic exclusion of internal `__noorm_*` tables - -**Conflict Resolution** - -Four strategies for handling primary key conflicts: -- `fail` (default) — abort on first conflict -- `skip` — skip conflicting rows (`INSERT IGNORE`, `ON CONFLICT DO NOTHING`, `MERGE ... WHEN MATCHED THEN skip`) -- `update` — upsert existing rows (`ON DUPLICATE KEY UPDATE`, `ON CONFLICT DO UPDATE`, `MERGE ... THEN UPDATE`) -- `replace` — delete and re-insert (`REPLACE INTO`, delete+insert for PG/MSSQL) - -**Dialect-Specific Handling** - -- **PostgreSQL**: `session_replication_role = replica` for FK bypass, `OVERRIDING SYSTEM VALUE` for identity insert, `setval(pg_get_serial_sequence(...))` for sequence reset, `TRUNCATE ... CASCADE` -- **MySQL**: `SET FOREIGN_KEY_CHECKS = 0`, backtick quoting, cross-database `db.table` notation, `ALTER TABLE ... AUTO_INCREMENT` reset -- **MSSQL**: Per-table `NOCHECK CONSTRAINT ALL`, `SET IDENTITY_INSERT ON/OFF`, `DBCC CHECKIDENT` for reseed, `MERGE` statements for all conflict strategies, `[bracket]` quoting, four-part `[db].[schema].[table]` naming - -**Options**: `--tables`, `--on-conflict`, `--batch-size`, `--truncate`, `--no-fk`, `--no-identity`, `--dry-run` - -**UI** - -- 7-phase TUI wizard: destination selection → table picker (multi-select with "all" toggle) → options → plan preview with dependency visualization → confirm → live progress bars → completion summary -- Headless mode with `--to ` and full JSON output support -- Observer events for real-time progress: `transfer:planning`, `transfer:plan:ready`, `transfer:table:progress`, `transfer:complete` - ---- - -### Portable .dt Data Format - -Add a portable data format (`.dt`) for file-based export/import and cross-dialect database transfers. - -**Format** - -- JSON5-based line format: schema header line followed by data rows -- Three extensions: `.dt` (plain), `.dtz` (gzip compressed), `.dtzx` (encrypted + compressed) -- Schema header captures source dialect, version, table name, and column types -- Universal type system maps dialect-specific types to portable intermediates - -**Type System** - -- Simple types: `string`, `int`, `bigint`, `float`, `decimal`, `bool`, `timestamp`, `date`, `uuid` -- Encoded types: `json`, `binary`, `vector`, `array`, `custom` -- Version-aware mappings: MySQL VECTOR (9.0+), MSSQL JSON/VECTOR (2025+), PostgreSQL array notation -- Smart encoding: raw for small values, base64 for binary, gzip+base64 when compression saves ≥15% - -**Cross-Dialect Transfers** - -- In-memory `DtStreamer` converts source rows → universal intermediates → target rows -- No file I/O or serialization overhead — pure type conversion -- Soft-limit batching: flush at row count OR 1GB memory threshold to prevent OOM on large BLOBs -- Version detection via `queryDatabaseVersion()` enables version-aware type mapping - -**File Export/Import** - -- `DtWriter`: streaming writer with extension-based format selection -- `DtReader`: streaming reader with async row iteration -- Passphrase-based encryption: PBKDF2 key derivation + AES-256-GCM -- Schema validation: target table structure checked before import begins - -**CLI Integration** - -- `--export ` with `--tables`: single table → file path, multiple tables → directory with `.dt` per table -- `--import `: load `.dt`/`.dtz`/`.dtzx` into active config -- `--compress`: produce `.dtz` output -- `--passphrase`: produce/consume encrypted `.dtzx` -- Mutually exclusive with `--to` (file export vs db-to-db transfer) - -**TUI Integration** - -- Export/import options in destination selection phase -- Export flow: destination → tables → export options (path, compress, encrypt) → confirm → progress → complete -- Import flow: file path → passphrase (if `.dtzx`) → schema preview with validation → options → confirm → progress → complete - -**Template Loader** - -- `.dt` and `.dtz` files usable as seed data in templates -- Registered in template loader system alongside SQL and JSON loaders -- No `.dtzx` support in templates (no way to provide passphrase) - -**Observer Events**: `dt:export:start`, `dt:export:progress`, `dt:export:complete`, `dt:import:start`, `dt:import:schema`, `dt:import:progress`, `dt:import:complete`, `dt:stream:start`, `dt:stream:progress`, `dt:stream:complete`, `dt:validate:result` - ---- - -### Encrypted Secrets Vault - -Add a team-shared encrypted secrets vault backed by the database with dual-layer encryption. - -**Encryption Architecture** - -- Dual-layer model: vault key encrypted per-user via ephemeral X25519 ECDH + HKDF-SHA256 + AES-256-GCM, secret values encrypted with the shared vault key via AES-256-GCM -- Each user receives their own encrypted copy of the vault key using their identity public key -- Access determined by presence of `encrypted_vault_key` on identity row — no separate permissions table - -**Secret Resolution Hierarchy** - -Three-tier priority system for secret lookups: -1. Config-specific local secrets (highest, from `.noorm/state/state.enc`) -2. Global local secrets (shared across configs) -3. Vault secrets (lowest, database-backed) - -Secrets available in template context as `<%= secrets.KEY_NAME %>` via `buildSecretsContext()`. - -**Operations** - -- `vault init` — generate vault key, encrypt for initializer's identity -- `vault set ` — encrypt and upsert secret with `setBy` audit trail -- `vault rm ` — remove secret by key -- `vault list` — list all secrets with metadata (who set them, timestamps) -- `vault cp` — copy secrets between configs with `--all`, `--force`, `--dry-run`; auto-initializes destination vault if needed -- `vault propagate` — grant vault access to all pending team members by encrypting the vault key for their public keys - -**UI** - -- TUI screen with secret list, defined-but-unset indicators (from settings), pending user count, and keyboard shortcuts for all operations (`[a]`dd, `[d]`elete, `[p]`ropagate, `[i]`nit) -- Headless commands with JSON output for all operations -- Observer events: `vault:initialized`, `vault:secret:created`, `vault:secret:updated`, `vault:secret:deleted`, `vault:propagated`, `vault:copy:completed` - -**Database Schema**: New `__noorm_vault__` table (`secret_key`, `encrypted_value`, `set_by`, timestamps) and `encrypted_vault_key` column on `__noorm_identities__` - ---- - -### SDK: Move noorm operations to `ctx.noorm` namespace - -**Breaking:** Refactor the SDK Context to separate SQL-focused operations from noorm management operations. All schema, change, lock, runner, explore, transfer, and DT methods move from the top-level `ctx` to a new `ctx.noorm` namespace via a lazy-initialized `NoormOps` class. - -Top-level Context retains only SQL-focused API: `kysely`, `dialect`, `connected`, `connect()`, `disconnect()`, `transaction()`, `proc()`, `func()`. - -**Before:** -```ts -await ctx.build() -await ctx.fastForward() -const tables = await ctx.listTables() -await ctx.acquireLock() -``` - -**After:** -```ts -await ctx.noorm.build() -await ctx.noorm.fastForward() -const tables = await ctx.noorm.listTables() -await ctx.noorm.acquireLock() -``` - -Properties `config`, `settings`, `identity`, `observer` also move to `ctx.noorm`. - -**Migration:** Add `.noorm` between `ctx` and any management method call. SQL operations (`kysely`, `transaction`, `proc`, `func`) and lifecycle (`connect`, `disconnect`) are unchanged. - ---- - -### Run - -- Fix form retry on failure: reset internal `submitting` state after `onSubmit()` completes so the form is no longer permanently locked after a failed submission; parent `busy` prop still guards against double-submit during async operations -- Apply settings exclude rules to build: pass pre-filtered file list from `RunBuildScreen` to `runBuild()` instead of letting the runner re-discover all files from disk, which ignored `include`/`exclude` rules diff --git a/.changeset/identity-ci-settings-edit-secret.md b/.changeset/identity-ci-settings-edit-secret.md deleted file mode 100644 index fc3544e2..00000000 --- a/.changeset/identity-ci-settings-edit-secret.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -Add CI identity bootstrap and two interactive settings editors: - -- `noorm identity ci` — diagnostic command for CI environments. Bootstraps the process from `NOORM_IDENTITY_PRIVATE_KEY`, `NOORM_IDENTITY_NAME`, and `NOORM_IDENTITY_EMAIL` env vars, deriving the public key from the private key and computing a deterministic identity hash across runners. In-memory overrides for both private key and metadata replace `~/.noorm/` filesystem reads, so CI runners can decrypt vault/state without writing key files. -- `noorm settings edit` — interactive editor covering all 7 settings sections: paths, build, strict, logging, stages, rules, teardown. Stages and rules use add/edit/remove sub-loops. Esc inside a sub-editor returns to the section picker; only Esc at the top level exits. Adds `setTeardown()` to `SettingsManager` and a `settings:teardown-updated` event. -- `noorm settings secret` — interactive editor for universal and stage-scoped secret **requirements** (declarations, not values). Supports add, edit, remove, and list actions. diff --git a/.changeset/init-nested-projects.md b/.changeset/init-nested-projects.md deleted file mode 100644 index 66d04020..00000000 --- a/.changeset/init-nested-projects.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -### Added -* `feat(cli):` Add `noorm init --here` to initialize a project in the original cwd, ignoring any parent `.noorm/` discovered while walking up -* `feat(cli):` Add global `-c ` / `--cwd ` flag (like `git -C`) that runs the subcommand inside `` and skips the walk-up. Must precede the subcommand; after the subcommand `-c` keeps its per-command `--config` meaning. - -### Changed -* `refactor(cli):` `noorm init` now reports an existing `.noorm/` *before* the TTY gate, so scripted invocations get the more actionable error. diff --git a/.changeset/keen-moons-glow.md b/.changeset/keen-moons-glow.md deleted file mode 100644 index 1f1f0046..00000000 --- a/.changeset/keen-moons-glow.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -## Added -* `feat(cli):` Add `info` command for project and database status display (`noorm info`, `noorm --json info`) -* `feat(cli):` Show CLI version, schema/state/settings versions, install/upgrade dates, and DB object stats on Home screen diff --git a/.changeset/kind-dogs-cheer.md b/.changeset/kind-dogs-cheer.md deleted file mode 100644 index 61c3316c..00000000 --- a/.changeset/kind-dogs-cheer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -### Fixed -* `fix(sdk):` Add integration and unit tests verifying `db.truncate()` and `db.teardown()` respect `settings.teardown.preserveTables` diff --git a/.changeset/mcp-default-viewer.md b/.changeset/mcp-default-viewer.md deleted file mode 100644 index 91003fcd..00000000 --- a/.changeset/mcp-default-viewer.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@noormdev/cli": major -"@noormdev/sdk": major ---- - -## Access default: agents no longer get admin - -### Changed - -* `fix(policy)!:` a config with no explicit `access` now resolves to `{ user: 'admin', mcp: 'viewer' }` instead of `{ user: 'admin', mcp: 'admin' }`. A stock project writes no `access`, so every config was handing full admin to any connected MCP client — writes, DDL, `change_run`, `run_build`, database drops, and vault/secret reads all went through unchecked. - * **The `user` channel is unchanged.** Nothing about the CLI, TUI, or SDK behaves differently. - * **MCP agents keep read access.** `explore` and read-only SQL (`SELECT`, `EXPLAIN`, `SHOW`, `DESCRIBE`) still work with no setup. - * **MCP agents lose write access by default.** If an agent needs to write, run changes, or run builds, set that config's `access.mcp` to `operator` or `admin` explicitly — via `noorm ui` → Config → Edit, or in the JSON before `config import`. - * **Configs that already store an explicit `access` are untouched**, including ones holding `mcp: 'admin'`. If you created configs on an earlier build, that migration already wrote `admin/admin` to disk and it is preserved — run `noorm config list` and lower any `mcp:admin` you did not intend. -* `noorm config list` now prints the access tag for any config that differs from the default, so an `mcp: admin` escalation is visible. Previously the tag was hidden whenever the user channel was `admin`, which concealed exactly that case. diff --git a/.changeset/mcp-server.md b/.changeset/mcp-server.md deleted file mode 100644 index 144fa5d6..00000000 --- a/.changeset/mcp-server.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -## Added -* `feat(mcp):` Add MCP server for coding agent integration — `noorm mcp serve` starts a stdio JSON-RPC server, `noorm mcp init` generates `.mcp.json` config files -* `feat(rpc):` Add transport-agnostic RPC command registry with Zod-validated commands: `connect`, `disconnect`, `list_configs`, `overview`, `list`, `detail`, `sql`, `change_history`, `change_run`, `change_ff`, `change_revert`, `run_build`, `run_file` -* `feat(rpc):` Add SQL protection for read-only enforcement on protected configs using `sql-parser-cst` with keyword fallback diff --git a/.changeset/mssql-connect-by-ip.md b/.changeset/mssql-connect-by-ip.md deleted file mode 100644 index 79d6803b..00000000 --- a/.changeset/mssql-connect-by-ip.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@noormdev/cli": patch -"@noormdev/sdk": patch ---- - -## MSSQL connects by IP address - -### Fixed - -* `fix(connection):` connecting to MSSQL by IP address works again. Tedious derived the TLS SNI ServerName from the host, and Node rejects an IP literal there (RFC 6066), so every IP connection failed with `Setting the TLS ServerName to an IP address is not permitted`. Hostname connections are unchanged. Encryption stays on — the connection is never silently downgraded to plaintext to work around it. -* `feat(connection):` new optional `connection.tlsServerName` — the hostname the server's TLS certificate is issued for. Required when connecting to an IP address with certificate validation enabled (`ssl` set), since a certificate cannot be validated against an IP. Without it, that combination now fails with an error naming the field instead of an opaque TLS error. diff --git a/.changeset/mssql-go-splitter-sdk.md b/.changeset/mssql-go-splitter-sdk.md deleted file mode 100644 index fabc6048..00000000 --- a/.changeset/mssql-go-splitter-sdk.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@noormdev/sdk": minor ---- - -## Added -* `feat(runner):` Split MSSQL SQL files on the `GO` batch separator. Multi-statement DDL files (multiple `CREATE PROCEDURE` / `CREATE FUNCTION` / `CREATE TRIGGER` / `CREATE VIEW` / `CREATE TYPE` in one file) now run correctly instead of failing with `Incorrect syntax near 'GO'`. -* `feat(runner):` Batch failures report the failed batch index in `FileResult.error` (e.g. `[batch 3 of 5] `) and short-circuit the remaining batches. - -## Known limitations -* `GO` inside string literals or block comments is still treated as a separator — matches `sqlcmd` behavior. Document accordingly when authoring T-SQL files. -* `GO ` repetition is not implemented. diff --git a/.changeset/mssql-teardown-sdk.md b/.changeset/mssql-teardown-sdk.md deleted file mode 100644 index 79662b60..00000000 --- a/.changeset/mssql-teardown-sdk.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -## Fixed -* `fix(teardown):` Reorder schema teardown to drop procedures and functions before tables. MSSQL schema-bound UDFs (`WITH SCHEMABINDING`) previously blocked the table drop with `Cannot DROP TABLE because it is being referenced by object 'fn_X'`. New order: FK constraints → Procedures → Functions → Views → Tables → Types. -* `fix(teardown):` Replace `sp_MSforeachtable` with per-table sequential `ALTER TABLE [name] NOCHECK CONSTRAINT ALL` (and inverse) in MSSQL `db.truncate()`. The previous implementation spawned parallel workers that deadlocked on schema locks against non-trivial schemas. -* `fix(teardown):` `TeardownDialectOperations.disableForeignKeyChecks` / `enableForeignKeyChecks` now accept an optional `tables?: string[]` and may return `string | string[]`. PostgreSQL / MySQL / SQLite implementations ignore the argument — no behavior change for those dialects. diff --git a/.changeset/noorm-ci-namespace.md b/.changeset/noorm-ci-namespace.md deleted file mode 100644 index 3e22e845..00000000 --- a/.changeset/noorm-ci-namespace.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -Introduce the `noorm ci` command namespace and retire the standalone `noorm identity ci` diagnostic. - -Four new commands cover the full CI lifecycle — mint a keypair, enroll it in a real database, bootstrap ephemeral state inside a job, and batch-load secrets: - -- `noorm ci identity new --name --email ` — generate a test-CI keypair locally. No database contact, no state written. Prints the private key once plus a copy-pasteable `NOORM_IDENTITY_*` env block. Designed for stateless/ephemeral CI (`isTest` configs, throwaway databases). Accepts `--json`. -- `noorm ci identity enroll --config --name --email [--public-key ]` — register a CI identity in the target database and propagate vault access to it. Run once by a developer with existing vault access. Decrypts the caller's vault key, inserts a new identity row (`machine='ci'`, `os='env'`), and re-encrypts the vault key for the new identity. Idempotent on identityHash — safe to re-run. When `--public-key` is omitted, mints a new keypair and returns the private key; when provided, only the public half is enrolled (air-gapped flow). -- `noorm ci init [--name ] [--force]` — bootstrap ephemeral `state.enc` from `NOORM_IDENTITY_*` + `NOORM_CONNECTION_*` env vars. Runs inside the CI job. Creates a config (default: `ci`, override via `--name` or `NOORM_CI_CONFIG_NAME`), marks it active, sets `isTest: true`. Absorbs the former `noorm identity ci` precheck — fails fast with exit 1 if any required env var is missing or malformed, or if `state.enc` already exists without `--force`. -- `noorm ci secrets --file [--config ] [--overwrite]` — batch-load secrets from a dotenv-style file into the active (or `--config`-named) vault. Parser ignores blank lines and `#` comments, splits on the first `=`, and strips a single matched pair of surrounding quotes. Existing keys are skipped unless `--overwrite` is set (so reruns are safe). Exit codes: `0` clean, `1` precondition failure, `2` partial (some set, some errored). - -**Removed:** `noorm identity ci`. Its precheck behavior is now built into `noorm ci init`. Callers that used `identity ci` only for validation should replace it with `noorm ci init`, which does the validation plus the state bootstrap. - -**Migration:** replace any pipeline that set `NOORM_IDENTITY_*` + `NOORM_CONNECTION_*` and ran `noorm identity ci` followed by `noorm change ff` with: - -```bash -noorm ci init --name prod -noorm change ff -``` - -For vault-aware pipelines, provision the CI identity once from a developer machine: - -```bash -noorm ci identity enroll --config prod --name "CI Bot" --email ci@example.com -# copy the printed NOORM_IDENTITY_* block into your CI secrets store -``` diff --git a/.changeset/noorm-init-sql-repl.md b/.changeset/noorm-init-sql-repl.md deleted file mode 100644 index 171144fa..00000000 --- a/.changeset/noorm-init-sql-repl.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -Add two new CLI commands: - -- `noorm init` — interactive project bootstrap. Creates identity (if missing), project structure, and settings. Requires an interactive TTY. -- `noorm sql repl` — launches the TUI directly on the SQL Terminal screen. Supports `--config ` to switch active config before launching. Requires an interactive TTY. diff --git a/.changeset/pre.json b/.changeset/pre.json deleted file mode 100644 index efb475b6..00000000 --- a/.changeset/pre.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "mode": "exit", - "tag": "alpha", - "initialVersions": { - "@noormdev/cli": "0.0.1", - "@noormdev/sdk": "0.0.1", - "@noormdev/example-todo-db": "0.0.0", - "@noormdev/example-llm-memory-db-mssql": "0.0.1-alpha.0", - "@noormdev/example-llm-memory-db-pg": "0.0.1-alpha.0" - }, - "changesets": [ - "binary-release-automation", - "bold-wolves-call", - "brave-foxes-modify", - "brave-wolves-rest", - "bright-foxes-glow", - "bundle-fix", - "calm-rivers-flow", - "change-dry-run-cli", - "change-dry-run-sdk", - "change-interactive-prompts", - "citty-cli-migration", - "citty-sdk-next", - "cjs-shim-fix", - "crisp-taxis-pick", - "dry-run-error-display", - "fix-binary-bun-pin", - "fix-bundle-version", - "fix-headless-error-output", - "fix-helpers-loading", - "fix-mssql-connection-hang", - "fix-mssql-dialect-support-sdk", - "fix-mssql-dialect-support", - "fix-mssql-mysql-schema-migration", - "fix-mssql-tarn-bundle-interop-cli", - "fix-mssql-tarn-bundle-interop", - "fix-sdk-bundle-deps", - "fix-shutdown-hang", - "fix-teardown-schema-qualify-cli", - "fix-teardown-schema-qualify", - "fix-teardown-tvp-ordering-cli", - "fix-teardown-tvp-ordering", - "generic-tvp-value", - "gentle-birds-impersonate", - "gold-items-feel", - "hip-pigs-knock", - "humble-emus-jam", - "identity-ci-settings-edit-secret", - "init-nested-projects", - "keen-moons-glow", - "kind-dogs-cheer", - "mcp-server", - "mssql-go-splitter-sdk", - "mssql-teardown-sdk", - "noorm-ci-namespace", - "noorm-init-sql-repl", - "quiet-pandas-sleep", - "rebuild-fix", - "reset-ignores-preserve-tables-cli", - "reset-ignores-preserve-tables", - "rich-errors-templates-headless", - "runner-observability-cli", - "sdk-protected-config-hardblock", - "sharp-foxes-run", - "swift-clouds-drift", - "teardown-mssql-check-constraint-udf-cli", - "teardown-mssql-check-constraint-udf", - "tender-lions-enter", - "tiny-dogs-yawn", - "tty-yes-flag-cli", - "tvp-support", - "typed-tuples-sdk", - "update-progress-stall", - "update-resumable-download", - "vault-init-idempotent-sdk", - "version-command", - "version-debug", - "warm-apples-march", - "warm-tables-stay", - "wise-owls-guard", - "worker-bridge" - ] -} diff --git a/.changeset/quiet-pandas-sleep.md b/.changeset/quiet-pandas-sleep.md deleted file mode 100644 index 842641f0..00000000 --- a/.changeset/quiet-pandas-sleep.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Fixed - -* `fix(change):` Use `settings.paths` from `settings.yml` for change directory resolution instead of per-config `activeConfig.paths`, matching the pattern used by runner screens diff --git a/.changeset/rebuild-fix.md b/.changeset/rebuild-fix.md deleted file mode 100644 index 7c58d635..00000000 --- a/.changeset/rebuild-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -Rebuild with complete bundling fixes diff --git a/.changeset/reset-ignores-preserve-tables-cli.md b/.changeset/reset-ignores-preserve-tables-cli.md deleted file mode 100644 index 18d4a856..00000000 --- a/.changeset/reset-ignores-preserve-tables-cli.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -fix(cli): noorm db reset rebuilds cleanly regardless of preserveTables - -`noorm db reset` (teardown + build) honored `settings.teardown.preserveTables` -during the teardown phase, so preserved tables (e.g. reference vocabulary kept -for the `noorm db truncate` workflow) survived and then collided with the -build's `CREATE TABLE`, aborting the rebuild and leaving a partial schema. -`noorm db reset` now performs a full teardown that ignores `preserveTables` — -a full rebuild starts from nothing. `noorm db teardown` and `noorm db truncate` -still honor the setting. diff --git a/.changeset/reset-ignores-preserve-tables.md b/.changeset/reset-ignores-preserve-tables.md deleted file mode 100644 index 89bd51c7..00000000 --- a/.changeset/reset-ignores-preserve-tables.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -fix(sdk): db.reset() no longer preserves tables before rebuilding - -`db.reset()` (teardown + build) honored `settings.teardown.preserveTables`, -so any preserved table (e.g. reference vocabulary kept for the per-test -`truncate()` workflow) survived the teardown and then collided with the -build's `CREATE TABLE`, aborting the rebuild and leaving a partial schema. -reset() now performs a full teardown that ignores `preserveTables` — a full -rebuild starts from nothing. `preserveTables` still applies to standalone -`teardown()` and `truncate()`. diff --git a/.changeset/rich-errors-templates-headless.md b/.changeset/rich-errors-templates-headless.md deleted file mode 100644 index 37265891..00000000 --- a/.changeset/rich-errors-templates-headless.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -### Added - -- `feat(headless):` Add `run inspect` command — inspect template context (data files, helpers, builtins, config, secrets) without executing, with `--json` support -- `feat(headless):` Add `run preview` command — render .sql.tmpl files and output raw SQL to stdout, pipeable to files or other tools - -### Fixed - -- `fix(errors):` Propagate SQL Server TDS diagnostic info (line numbers, error codes, procedure names, severity) through to TUI — errors now show e.g. `[Line 42, Err 207] Invalid column name` instead of just the message text -- `fix(errors):` Propagate PostgreSQL and MySQL diagnostic info (error codes, SQLSTATE, severity) through to TUI -- `fix(errors):` Handle Kysely-unpacked `AggregateError` arrays from TDS with multi-line display -- `fix(template):` Eta `autoTrim` left-trim was eating newlines after interpolation tags, joining SQL lines (e.g. `ENDAS`, `ENDIF NOT EXISTS`) — disabled autoTrim and implemented directive-line stripping for `-- {% %}` convention -- `fix(db):` Disconnect shared TUI connection before `DROP DATABASE` to prevent ECONNRESET errors -- `fix(db):` Show friendly "Not Created" notice instead of aggressive ERROR badge when database does not exist -- `fix(tui):` Show full multi-line SQL errors in all run/change screens instead of truncating to 60 characters diff --git a/.changeset/runner-observability-cli.md b/.changeset/runner-observability-cli.md deleted file mode 100644 index 84aaa32e..00000000 --- a/.changeset/runner-observability-cli.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Fixed -* `fix(cli):` Surface SQL errors and skip reasons in `run` and `change` output -* `fix(cli):` `noorm run build` / `dir` / `files` / `exec` now print each failed file's driver error inline and route the summary through `logger.error` on non-success -* `fix(cli):` `noorm run file` and the skip path now report `(skipped: )` so callers know whether the file was unchanged or already-run -* `fix(cli):` `noorm change ff` / `run` / `revert` / `rewind` print per-change error detail on failure instead of just `(failed)` diff --git a/.changeset/sdk-protected-config-hardblock.md b/.changeset/sdk-protected-config-hardblock.md deleted file mode 100644 index e87f632f..00000000 --- a/.changeset/sdk-protected-config-hardblock.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -"@noormdev/sdk": major ---- - -## Breaking Changes - -### `allowProtected` option removed - -The `allowProtected` option has been removed from `CreateContextOptions`. Passing it no longer has any effect — protected configs unconditionally block all destructive operations with no override. - -**Before:** -```typescript -// This no longer works — allowProtected is not a valid option -const ctx = await createContext({ config: 'staging', allowProtected: true }) -await ctx.noorm.db.truncate() // would proceed -``` - -**After:** -```typescript -// Protected configs always block destructive ops — no override possible -const ctx = await createContext({ config: 'staging' }) -await ctx.noorm.db.truncate() // throws ProtectedConfigError -``` - -To run a destructive operation against a protected config, set `config.protected = false` manually before running the operation, then restore it. - -### `checkProtectedConfig` signature changed - -The exported `checkProtectedConfig` guard function signature changed from `(config, operation, options)` to `(config, operation)`. If you call this function directly, remove the third argument. - -## New Behavior - -The following operations are now blocked on protected configs (in addition to `truncate`, `teardown`, and `reset`): - -- `ctx.noorm.dt.importFile()` — bulk data import is destructive -- `ctx.noorm.changes.revert()` — schema rollbacks are destructive in production -- `ctx.noorm.changes.rewind()` — batch schema rollbacks are destructive in production diff --git a/.changeset/sharp-foxes-run.md b/.changeset/sharp-foxes-run.md deleted file mode 100644 index 9acb5a1e..00000000 --- a/.changeset/sharp-foxes-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -Add curl|sh installer and npm binary wrapper for streamlined installation. The published npm package now acts as a thin shim that downloads and executes the platform-specific compiled binary. diff --git a/.changeset/swift-clouds-drift.md b/.changeset/swift-clouds-drift.md deleted file mode 100644 index fc85d94c..00000000 --- a/.changeset/swift-clouds-drift.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -### Fixed - -* `fix(settings):` NOORM_* environment variables now override any settings.yml field (e.g. `NOORM_PATHS_SQL` overrides `paths.sql`) diff --git a/.changeset/teardown-mssql-check-constraint-udf-cli.md b/.changeset/teardown-mssql-check-constraint-udf-cli.md deleted file mode 100644 index 3277a4cd..00000000 --- a/.changeset/teardown-mssql-check-constraint-udf-cli.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -fix(cli): noorm db teardown drops MSSQL CHECK constraints before functions - -`noorm db teardown` (and `noorm db reset`) aborted with MSSQL error 3729 on -any schema where a scalar UDF is referenced by a CHECK constraint (the -canonical base/subtype "IsType" pattern). Functions are dropped before tables -to satisfy schema-bound dependents, which left the CHECK-constraint dependency -intact. Teardown now severs it first by dropping all user-schema CHECK -constraints (excluding the `noorm` schema) ahead of the function drops, so -both schema-bound functions and CHECK-backed functions tear down cleanly. diff --git a/.changeset/teardown-mssql-check-constraint-udf.md b/.changeset/teardown-mssql-check-constraint-udf.md deleted file mode 100644 index bab2334e..00000000 --- a/.changeset/teardown-mssql-check-constraint-udf.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -fix(teardown): drop MSSQL CHECK constraints before functions - -`teardown()` aborted with MSSQL error 3729 on any schema where a scalar UDF -is referenced by a CHECK constraint (the canonical base/subtype "IsType" -pattern). Functions are dropped before tables to satisfy schema-bound -dependents, but that left the CHECK-constraint dependency intact. Teardown -now severs it first by dropping all user-schema CHECK constraints (excluding -the `noorm` schema) ahead of the function drops, so both schema-bound -functions and CHECK-backed functions tear down cleanly. diff --git a/.changeset/tender-lions-enter.md b/.changeset/tender-lions-enter.md deleted file mode 100644 index 2a330f98..00000000 --- a/.changeset/tender-lions-enter.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -## Auto-Update Notifications - -### CLI - -- **Background Update Checking**: Checks npm registry on TUI launch - - Toast notification for available minor/patch updates - - Warning toast for major version updates - - Respects user preferences in `~/.noorm/settings.yml` - -- **Global Settings**: User-level preferences at `~/.noorm/settings.yml` - - `checkUpdates`: Enable/disable update checking (default: true) - - `autoUpdate`: Auto-install non-major updates (default: false) - - `dismissable`: Per-alert "don't ask again" preferences - -- **DismissableAlert Component**: Reusable confirmation dialogs - - Auto-resolves based on stored preference ('always'/'never'/'ask') - - Keyboard navigation with arrow keys and number shortcuts - - Optional "Don't ask again" checkbox with persistence - -### SDK - -- New `src/core/update/` module with: - - `checkForUpdate()`: Version comparison with prerelease channel support - - `installUpdate()`: Background npm install via child process - - `loadGlobalSettings()` / `saveGlobalSettings()`: User preferences - - `getDismissablePreference()` / `updateDismissablePreference()`: Alert state - -- New observer events: `update:checking`, `update:available`, `update:complete`, etc. diff --git a/.changeset/tiny-dogs-yawn.md b/.changeset/tiny-dogs-yawn.md deleted file mode 100644 index 648d0d8c..00000000 --- a/.changeset/tiny-dogs-yawn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -Fix display of created-at timestamps and file sizes in the change list and change run screens. diff --git a/.changeset/transfer-negation-flags.md b/.changeset/transfer-negation-flags.md deleted file mode 100644 index bf9398df..00000000 --- a/.changeset/transfer-negation-flags.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Fixed -* `fix(db):` `db transfer --no-fk` and `--no-identity` are honoured instead of being silently ignored — citty strips the `--no-` prefix and negates a flag of the remaining name, so the `noFk`/`noIdentity` args they were declared as could never receive a value diff --git a/.changeset/tty-yes-flag-cli.md b/.changeset/tty-yes-flag-cli.md deleted file mode 100644 index 9c540b57..00000000 --- a/.changeset/tty-yes-flag-cli.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -## Added -* `feat(cli):` Universal `--yes` / `-y` flag and `NOORM_YES=1` environment variable for TTY-gated commands. Unblocks CI, scripted bootstrap, and subagent flows. -* `feat(cli):` `noorm init --yes` now succeeds in non-TTY environments when an identity already exists at `~/.noorm/identity.{key,pub,json}`. Without an identity, it errors with a hint pointing at `noorm identity init --name "X" --email "Y"`. -* `feat(cli):` `noorm sql repl --yes`, `noorm settings edit --yes`, and `noorm settings secret --yes` print a documented redirect-hint error instead of refusing silently, telling the user which non-interactive alternative to use (`sql query` / direct YAML edit / `secret set`). diff --git a/.changeset/tui-change-parity.md b/.changeset/tui-change-parity.md deleted file mode 100644 index bcb8feb4..00000000 --- a/.changeset/tui-change-parity.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Fixed -* `fix(tui):` change operations now receive the active config's dialect — on sqlite and mysql the TUI queried postgres-shaped tracking tables, and the failed read surfaced as a successful no-op -* `fix(tui):` the global dry-run and force toggles are now honoured by `ff`, `next`, `run`, `revert`, and `rewind` — dry-run was displayed as active while changes applied for real -* `fix(tui):` core errors are surfaced as a toast instead of being written only to the log file diff --git a/.changeset/tvp-support.md b/.changeset/tvp-support.md deleted file mode 100644 index 9a9e558f..00000000 --- a/.changeset/tvp-support.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/sdk": minor ---- - -### Added - -* `feat(sdk):` Add TVP (table-valued parameter) support for MSSQL via `tvp()` helper — pass structured tabular data to `ctx.proc()`, `ctx.func()`, and `ctx.tvf()` calls -* `feat(sdk):` Validate TVP row key consistency and enforce MSSQL's 2,100 parameter limit with clear error messages diff --git a/.changeset/typed-tuples-sdk.md b/.changeset/typed-tuples-sdk.md deleted file mode 100644 index aa1726eb..00000000 --- a/.changeset/typed-tuples-sdk.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@noormdev/sdk": minor ---- - -## Added -* `feat(sdk):` Support `[Args, ReturnType]` tuple definitions for procs, functions, and TVFs — return types are inferred automatically, with explicit override still available diff --git a/.changeset/unmatched-include-warning.md b/.changeset/unmatched-include-warning.md deleted file mode 100644 index e81ac0ea..00000000 --- a/.changeset/unmatched-include-warning.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@noormdev/cli": patch -"@noormdev/sdk": patch ---- - -## Fixed -* `fix(run):` `run build` now names `build.include` entries that matched no files instead of reporting a plain success over zero files — include paths are relative to `paths.sql`, and the common `sql/01_tables` form silently matched nothing. `BatchResult` gains an optional `unmatchedInclude`. diff --git a/.changeset/update-progress-stall.md b/.changeset/update-progress-stall.md deleted file mode 100644 index c6524367..00000000 --- a/.changeset/update-progress-stall.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -`fix(update):` the binary self-update no longer hangs indefinitely and now shows download progress. `noorm update` streamed the ~70MB release binary with a bare `fetch()` — no timeout, so a stalled connection hung forever with no error and no feedback, indistinguishable from a freeze. It now streams to disk with a live `Downloading X / Y MB (Z%)` readout, aborts with a clear error if the transfer stalls (no bytes for 30s), and stages the replacement in the target's own directory so the atomic swap can't fail with a cross-filesystem `EXDEV` (e.g. `os.tmpdir()` on a different volume than `~/.local/bin`). diff --git a/.changeset/update-resumable-download.md b/.changeset/update-resumable-download.md deleted file mode 100644 index 00d1434c..00000000 --- a/.changeset/update-resumable-download.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -`feat(update):` the binary self-update now resumes instead of restarting. When a download stalls or the connection drops, `noorm update` retries (up to 5 attempts with backoff) and resumes from the bytes already on disk via an HTTP range request — validated with `If-Range`/`ETag` so a changed asset restarts cleanly rather than stitching a stale prefix. A flaky connection now retries the tail, not the whole ~70MB. Permanent failures (e.g. `404`) still fail fast without retrying, and the CLI prints a `resuming (attempt N/M)` notice between attempts. diff --git a/.changeset/v1-authorization-seam.md b/.changeset/v1-authorization-seam.md deleted file mode 100644 index 1afd8e61..00000000 --- a/.changeset/v1-authorization-seam.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"@noormdev/cli": major -"@noormdev/sdk": major ---- - -## Authorization is now enforced at the core seam - -Policy was re-implemented per surface, and several cells were simply empty — the CLI would perform operations the TUI correctly refused, and two whole domains had no permission to check against. - -### Fixed - -* `fix(policy)!:` the SQL classifier no longer routes writes as reads. `EXPLAIN (ANALYZE) DELETE FROM t` classified as `read` and deleted rows under a `viewer` role — through the CLI and over a live MCP session. Two independent causes are fixed: the keyword fallback treated `EXPLAIN` as terminally read-only, and on the parser path `EXPLAIN` inherited nothing from the statement it wraps while the write-signal scan had no DDL node types. An `EXPLAIN` now inherits its wrapped statement's classification. -* `fix(policy)!:` statement splitting is dialect-aware. It tracked only `'`, so an MSSQL bracket-quoted identifier (`SELECT 1 AS [a'b]; DROP TABLE x`) split wrong and the tail ran unclassified. `"`, backticks, `[…]`, `$tag$…$tag$` and all three comment forms are now modelled. -* `fix(policy)!:` a statement with no leading word character used to classify as `read`, so a leading NUL byte carried `DROP TABLE` past the gate. It now fails closed. -* `feat(policy)!:` added the permissions that did not exist — `vault:*`, `secret:*`, `config:write`, `db:truncate`, `db:teardown`, `transfer:plan`, `lock:force`, `debug:*`. Operations in those domains had nothing to gate against and shipped ungated. -* `fix(vault)!:` vault and secret operations are gated at the core seam, so SDK, CLI and TUI inherit one check. A `viewer` config that was denied `run build` could still run `vault set`, `vault rm`, `vault propagate` and `secret set`. -* `fix(db)!:` `db create` is gated. The matrix denied `db:create` to `viewer` and the TUI enforced it; the CLI created the database anyway. -* `fix(db)!:` `db truncate` and `db teardown` require confirmation. `--yes`/`--force` were declared arguments that neither command read, so `noorm db teardown < /dev/null` dropped 63 objects and exited 0 — against documentation promising the CLI refuses. -* `fix(debug)!:` the debug screens' delete paths are gated. They removed rows from `noorm.vault` and `noorm.identities` with no authorization check at all. -* `fix(lock)!:` `lock force` is gated and requires `--yes`, names the holder it evicts, and returns a distinct exit code when there was nothing to release. -* `fix(config)!:` `config import --force` is gated on `config:write`. It rewrote a config's `access` with no check, escalating a `viewer` config to full delete rights in one command. -* `fix(transfer):` `getTransferPlan` is gated, matching the invariant its own comment claimed. `--dry-run` leaked destination table names, row estimates and the FK graph to a denied caller. diff --git a/.changeset/v1-crypto-identity.md b/.changeset/v1-crypto-identity.md deleted file mode 100644 index f24677aa..00000000 --- a/.changeset/v1-crypto-identity.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@noormdev/cli": major -"@noormdev/sdk": major ---- - -## Identity and state key handling fail closed - -### Fixed - -* `fix(identity)!:` malformed key material is rejected instead of deriving a publicly computable key. `Buffer.from(key, 'hex')` never throws, so any non-hex private key collapsed to a zero-length HKDF input and produced a **constant** AES key — an attacker holding no key material could decrypt a real `state.enc`. `isValidKeyHex` existed but was only called on the CI path; it now guards every entry point. -* `fix(ci)!:` `ci identity enroll` verifies the stored public key matches the one presented. It checked only that a row existed, so anyone able to INSERT into `noorm.identities` — no vault access required — could pre-plant their own key and receive the vault key, while the operator's enrollment reported success and echoed back the expected key. -* `fix(identity)!:` `identity init --force` requires `--yes`, backs up the keypair, and names what it destroys. It silently regenerated the keypair and permanently destroyed every project's `state.enc`. -* `fix(ci):` `ci init --force` backs up `state.enc` and refuses on a TTY without confirmation; it destroyed all configs and secrets unprompted. It also now routes through `parseConfig`, so a numeric-looking database name persists as a string. -* `fix(vault)!:` `vault propagate` names every recipient and withholds until confirmed, and reports partial failures instead of returning success with the failed identity silently omitted. -* `fix(sdk):` an explicit identity scope whose revert fails is no longer marked reverted, and `disconnect()` no longer hangs when scopes are still held. diff --git a/.changeset/v1-data-integrity.md b/.changeset/v1-data-integrity.md deleted file mode 100644 index 7e0ad0f4..00000000 --- a/.changeset/v1-data-integrity.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -"@noormdev/cli": major -"@noormdev/sdk": major ---- - -## Operations no longer report success over lost, duplicated or unwritten data - -### Fixed - -* `fix(transfer)!:` export and db-to-db transfer paginate by primary key instead of bare `LIMIT/OFFSET`. With no `ORDER BY`, a source being written to during a 50,000-row export produced 14,149 missing and 13,387 duplicated rows while reporting `rowsExported: 50000` and exiting 0. Tables with no primary key now take a single-statement snapshot rather than corrupting silently. -* `fix(dt)!:` a malformed row or a dead worker no longer hangs the pipeline forever. The error branch never decremented the in-flight count, `request()` never settled on worker death, and `worker:exit` had no subscribers — a four-byte bad payload wedged `db transfer --import` indefinitely. Three busy-wait loops are replaced with real settlement. -* `fix(transfer)!:` the postgres same-server path no longer copies the destination into itself. It ignored the source database entirely and could only ever emit `INSERT INTO t SELECT ... FROM t`. -* `fix(dt):` decompression is bounded on import. An unbounded `gunzip` inflated 398 KB to 400 MB (1029:1), and because it runs in a compute worker the resulting OOM triggered the hang above. -* `fix(transfer):` reported counts reflect reality — `onConflict: 'skip'` no longer counts no-ops as inserted, and the same-server path reports affected rows instead of a postgres `reltuples` estimate. -* `fix(transfer):` `db transfer --export` with no `--tables` exports every table, as its own flag description promised. It exported nothing, printed `success: true` and exited 0. -* `fix(transfer):` cross-dialect transfer works. The planner probed the destination with the *source* dialect, so every cross-dialect transfer aborted and roughly 400 lines of streaming conversion were unreachable — while the docs claimed the feature worked. -* `fix(teardown)!:` `truncate` qualifies statements with the table's schema. It discarded the schema, emptied 12 tables and then aborted — partial irreversible loss, reported as a failure. -* `fix(state)!:` `state.enc` writes are atomic and reconciled. Ten concurrent `noorm secret set` runs left five secrets, all exiting 0. Writes now stage-and-rename with an exclusive lock, three-way reconcile against a changed file, and keep a `.bak` generation. -* `fix(state):` reads stopped rewriting state. `needsMigration` tested a field the migration never writes, so it was always true and every command — including `config list` — re-encrypted and rewrote `state.enc`, turning reads into writes and amplifying the loss above. -* `fix(runner):` `.sql.tmpl` dedup works. The stored checksum was the raw file hash while the comparison used the rendered hash, so templates re-executed on every build and failed on non-idempotent DDL. -* `fix(runner):` SQLite executes every statement in a multi-statement file instead of silently running the first and reporting success. diff --git a/.changeset/v1-explore-correctness.md b/.changeset/v1-explore-correctness.md deleted file mode 100644 index 2bd967d9..00000000 --- a/.changeset/v1-explore-correctness.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@noormdev/cli": minor -"@noormdev/sdk": minor ---- - -## Schema exploration answers about the object you asked for - -### Fixed - -* `fix(explore):` procedure detail returns parameters. It filtered `information_schema.parameters` on a bare name where the column holds `name_oid`, so the list view reported a parameter count and the detail view always showed none — on every surface. -* `fix(explore):` SQLite quotes identifiers at all raw-SQL sites. A single table named with an embedded quote broke listing, overview and detail for *unrelated* tables. -* `fix(explore):` MySQL table detail reads indexes and foreign keys from the requested schema rather than the connected database, which produced self-contradictory output with the real index missing. -* `fix(explore):` `--schema` is honoured on the list commands, which declared it and ignored it. -* `fix(explore):` the overview counts from the same listings the detail views use, inside one guarded call that surfaces errors, instead of a second implementation that disagreed and hardcoded several counters to zero. diff --git a/.changeset/v1-mysql-dialect.md b/.changeset/v1-mysql-dialect.md deleted file mode 100644 index 0edf525d..00000000 --- a/.changeset/v1-mysql-dialect.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@noormdev/cli": patch -"@noormdev/sdk": patch ---- - -## MySQL works - -### Fixed - -* `fix(runner):` `run build` executes on MySQL. `createOperation` emitted a `RETURNING` clause MySQL has no support for, so the operation record was never created and **zero SQL files ran** — the command failed before touching a single file. -* `fix(change):` `recordReset` no longer fails silently on MySQL. It swallowed the same error and returned `0`, so `db teardown` recorded nothing and reported no error at all. -* `refactor(shared):` operation-record insertion is one dialect-aware helper. It existed as three independent copies, which is why fixing the runner left the change module broken and the same defect had to be found twice. diff --git a/.changeset/v1-tui-parity.md b/.changeset/v1-tui-parity.md deleted file mode 100644 index 71c886d0..00000000 --- a/.changeset/v1-tui-parity.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@noormdev/cli": minor ---- - -## TUI matches the CLI on gating and global modes - -### Fixed - -* `fix(tui):` `db transfer` routes through the gated path with a typed confirmation. It called core directly, skipping the confirmation tier the SDK enforces. -* `fix(tui):` the global dry-run toggle is honoured by transfer, truncate and teardown. The indicator showed dry-run as active while all three ran for real. -* `fix(tui):` `truncateFirst` is a working control instead of state the UI could display but never change. -* `fix(tui):` `vault propagate` is no longer a bare `p` keypress — it checks policy and names each recipient before granting access to every enrolled identity. -* `fix(tui):` fixed 15 stale-closure dependency omissions across hooks and screens. `react-hooks/exhaustive-deps` is not enabled in this repo, so callbacks silently captured first-render values. -* `fix(tui):` `InitScreen` no longer overwrites an existing `.noorm/.gitignore` on re-init. diff --git a/.changeset/v1-update-logging.md b/.changeset/v1-update-logging.md deleted file mode 100644 index 7572e32f..00000000 --- a/.changeset/v1-update-logging.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@noormdev/cli": minor -"@noormdev/sdk": patch ---- - -## Self-update input validation, log rotation and redaction - -### Fixed - -* `fix(update)!:` the version string from the npm registry is validated as semver before it reaches a URL or a shell. It was interpolated verbatim, and because `fetch` normalises `..`, a poisoned dist-tag could relocate **both the binary and its `checksums.txt`** to an attacker-controlled repository — so checksum verification passed against the attacker's own file. -* `fix(logger):` rotation reopens its write stream. It renamed the file out from under the open descriptor, so every subsequent write landed in the rotated file, `noorm.log` never reappeared, and rotation fired exactly once before growing unbounded. -* `fix(logger):` `settings.logging.enabled`, `.file`, `.maxSize` and `.maxFiles` are honoured. The CLI hardcoded all four, so every logging setting was inert across every command — and the log viewer read a different path than the CLI wrote. -* `fix(logger):` redaction covers this project's own variables. `NOORM_CONNECTION_PASSWORD` and `NOORM_IDENTITY_PRIVATE_KEY` were not masked, credential-bearing URIs passed through verbatim because values were never inspected, and `Error` objects were skipped wholesale. Log files are created `0600` rather than world-readable. diff --git a/.changeset/vault-init-idempotent-sdk.md b/.changeset/vault-init-idempotent-sdk.md deleted file mode 100644 index bd91a747..00000000 --- a/.changeset/vault-init-idempotent-sdk.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@noormdev/sdk": patch ---- - -## Changed -* `fix(vault):` `vault.init()` is now idempotent. A repeat call against an already-initialized vault returns `[null, null]` instead of `[null, Error('Vault already initialized')]`. The `vault:initialized` observer event still fires only on first init. - -This is a behavior change for callers that were special-casing the `'Vault already initialized'` error string — they now see no error on repeat and must check whether the returned key is `null` (already initialized) or a `Buffer` (newly generated). diff --git a/.changeset/version-command.md b/.changeset/version-command.md deleted file mode 100644 index 3329a28a..00000000 --- a/.changeset/version-command.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -Add `noorm version` command for diagnostic output showing CLI version, identity paths, and project status diff --git a/.changeset/version-debug.md b/.changeset/version-debug.md deleted file mode 100644 index 8108b479..00000000 --- a/.changeset/version-debug.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -Add state loading error output to version command for debugging diff --git a/.changeset/warm-apples-march.md b/.changeset/warm-apples-march.md deleted file mode 100644 index a86c5c4a..00000000 --- a/.changeset/warm-apples-march.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@noormdev/cli": patch ---- - -## Fixed - -* `fix(change):` Resolve change paths using `projectRoot` instead of bare relative paths, matching the SDK pattern -* `fix(form):` Add Shift+Tab support for backward field navigation -* `fix(config):` Guard ConfigImportScreen `useInput` handler with `isActive` to prevent focus interference -* `fix(config):` Constrain ConfigEditScreen form height to terminal bounds with `overflowY="hidden"` -* `fix(transfer):` Update aggregate `rowsTransferred` in real-time during dt:import and db-to-db transfers -* `fix(transfer):` Prevent `dt:import:complete` from setting phase to `complete` prematurely during multi-file imports -* `fix(transfer):` Show spinner instead of misleading 0% progress bar for same-server transfers diff --git a/.changeset/warm-tables-stay.md b/.changeset/warm-tables-stay.md deleted file mode 100644 index 780de768..00000000 --- a/.changeset/warm-tables-stay.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@noormdev/sdk": minor ---- - -## API/Services - -### Fixed -* `fix(sdk):` `db.truncate()` and `db.teardown()` now respect `settings.teardown.preserveTables` and `postScript` from settings.yml -* `fix(sdk):` `db.truncate()` accepts optional `TruncateOptions` — user-provided `preserve`/`only` take priority over settings fallback diff --git a/.changeset/wise-owls-guard.md b/.changeset/wise-owls-guard.md deleted file mode 100644 index 0f16dd54..00000000 --- a/.changeset/wise-owls-guard.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -"@noormdev/cli": minor -"@noormdev/sdk": minor ---- - -## Access Roles - -Replace the per-config `protected: boolean` flag with per-channel access roles. - -**Breaking:** `Config.protected` is removed from the exported types and `access: ConfigAccess` is now required on `Config`/`ConfigSummary`. TypeScript consumers that read `.protected` or construct `Config` literals must move to `access`. Stored state auto-migrates on load (see below), so runtime configs are unaffected. (Released as `minor` under the `1.0.0-alpha` pre-release line, where `^` ranges pin within the alpha series; it is a breaking type change and will be treated as such at the 1.0 boundary.) - -* `feat(policy):` Configs now carry `access: { user, mcp }`, each `'viewer' | 'operator' | 'admin'` (or `mcp: false` to hide the config entirely). Roles are enforced by one policy matrix instead of scattered `protected` checks — `viewer` reads only, `operator` writes and confirms destructive operations, `admin` is frictionless. Enforcement runs at the core seam, so the SDK, TUI, and CLI all inherit it: `run`, `changes`, `transfer`, and the SQL terminal are gated, not just MCP. -* `feat(mcp):` Every MCP command is now gated on `access.mcp` before it runs, closing the gap where `change_run`/`change_ff`/`change_revert`/`run_file`/`run_build` reached the database unchecked. `access.mcp: false` makes a config invisible to agents — absent from `list_configs`, and `connect` fails with the same error an unknown config produces. `confirm`-tier permissions collapse to a hard deny on the MCP channel (no human to type a confirmation phrase); give a config `mcp: 'admin'` if an agent legitimately needs to run changes there. -* `feat(sql):` Raw SQL (`noorm sql`, MCP `sql`, the TUI SQL terminal) is now gated by what the statement actually does — classified `read`/`write`/`ddl` — instead of a blanket read-only check. Data-modifying CTEs (`WITH … AS (DELETE …) SELECT …`) and a denylist of side-effecting functions (`pg_terminate_backend`, `dblink_exec`, `query_to_xml`, … — bare or schema-qualified) classify as writes so a `viewer` cannot mutate through the read path. Multi-statement input takes the highest class present; unparseable input or `EXEC`/`CALL` classify as `ddl` (fail closed). The classifier guards against mutation, not disclosure — back hard confidentiality with database `GRANT`s. -* `feat(sdk):` `createContext({ channel })` — defaults to `'user'`; set `'mcp'` when embedding the SDK behind an MCP-like surface. New exported types `Channel`, `ConfigAccess`, `Role`. `ProtectedConfigError` is now raised from `checkPolicy` denials/unconfirmable actions rather than a bare `protected` check; match on `err.name`/`instanceof`, not the message string (the message text changed). -* **Behavior change:** on the `user` channel, `NOORM_YES=1` resolves an operator `confirm` to allow — including in the SDK guards. A migrated `protected: true` config becomes `operator`, so an SDK/CI caller with `NOORM_YES=1` set now *executes* `truncate`/`teardown`/`reset`/`dt.import`/`changes.*`/`run.*` where the previous `protected` hard-block threw unconditionally. Unset `NOORM_YES` (or use `viewer`/`mcp:false`) where the block must hold. -* `fix(config):` A legacy `protected: true` migrates automatically to `{ user: 'operator', mcp: 'viewer' }`, and `protected: false`/absent to `{ user: 'admin', mcp: 'admin' }`. The `protected` field is still accepted on `config import`/settings input for this release, then removed. Note: downgrading to a prior binary after this migration silently unprotects configs (older binaries have no access concept) — avoid rolling back once state is migrated. diff --git a/.changeset/worker-bridge.md b/.changeset/worker-bridge.md deleted file mode 100644 index ade9a00b..00000000 --- a/.changeset/worker-bridge.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@noormdev/cli": minor -"@noormdev/sdk": minor ---- - -### Added - -* `feat(worker-bridge):` Worker thread infrastructure for parallel DT export/import. WorkerBridge class (ObserverRelay subclass), WorkerPool with round-robin dispatch, OrderBuffer for index-ordered reassembly. -* `feat(workers):` Persistent Connection Worker (Kysely-backed DB operations) and stateless Compute Worker (serialize/deserialize) as standalone entry points. -* `feat(dt):` Export and import pipelines now run through worker threads — Connection Worker handles DB queries, Compute Pool parallelizes CPU-bound serialization across N cores. -* `feat(dt):` Three-tier progress events (`loaded`/`processed`/`saved`) for both export and import, enabling granular TUI progress display. -* `feat(connection):` ConnectionManager tracks WorkerBridge instances alongside direct Kysely connections for coordinated shutdown. -* `feat(cli):` `noorm dev/test-workers` diagnostic command for verifying worker thread infrastructure across execution contexts. -* `build:` Worker entry points included in `bun build --compile` for single binary support. diff --git a/examples/llm-memory-db-mssql/CHANGELOG.md b/examples/llm-memory-db-mssql/CHANGELOG.md index 0df92460..6f5957ff 100644 --- a/examples/llm-memory-db-mssql/CHANGELOG.md +++ b/examples/llm-memory-db-mssql/CHANGELOG.md @@ -1,5 +1,53 @@ # @noormdev/example-llm-memory-db-mssql +## 0.0.1 + +### Patch Changes + +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [8749127] +- Updated dependencies [06d799b] +- Updated dependencies [dd7e387] +- Updated dependencies [8797eb4] +- Updated dependencies [01208ec] +- Updated dependencies [ba22246] +- Updated dependencies [850b2d3] +- Updated dependencies [72fb64f] +- Updated dependencies [18e36b6] +- Updated dependencies [3ef0007] +- Updated dependencies [8b20702] +- Updated dependencies [715cddc] +- Updated dependencies [66e6fb8] +- Updated dependencies [a4d7308] +- Updated dependencies [1dc22b3] +- Updated dependencies [6b3cce1] +- Updated dependencies [ba22246] +- Updated dependencies [6753ebd] +- Updated dependencies [8e47888] +- Updated dependencies [fff443a] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [01208ec] +- Updated dependencies [01208ec] +- Updated dependencies [3ef0007] +- Updated dependencies [0951b7e] +- Updated dependencies [3ef0007] +- Updated dependencies [66e6fb8] +- Updated dependencies [df24197] +- Updated dependencies [f4fec5b] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [01208ec] +- Updated dependencies [4f4967c] +- Updated dependencies [4751921] +- Updated dependencies [a18bfbe] + - @noormdev/sdk@1.0.0 + ## 0.0.1-alpha.5 ### Patch Changes diff --git a/examples/llm-memory-db-mssql/package.json b/examples/llm-memory-db-mssql/package.json index d9653c95..cf726be7 100644 --- a/examples/llm-memory-db-mssql/package.json +++ b/examples/llm-memory-db-mssql/package.json @@ -1,6 +1,6 @@ { "name": "@noormdev/example-llm-memory-db-mssql", - "version": "0.0.1-alpha.5", + "version": "0.0.1", "private": true, "type": "module", "description": "LLM-memory schema + SDK example exercising NoORM against MSSQL with TVPs", diff --git a/examples/llm-memory-db-pg/CHANGELOG.md b/examples/llm-memory-db-pg/CHANGELOG.md index 0e738ccf..8ff8d105 100644 --- a/examples/llm-memory-db-pg/CHANGELOG.md +++ b/examples/llm-memory-db-pg/CHANGELOG.md @@ -1,5 +1,53 @@ # @noormdev/example-llm-memory-db-pg +## 0.0.1 + +### Patch Changes + +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [8749127] +- Updated dependencies [06d799b] +- Updated dependencies [dd7e387] +- Updated dependencies [8797eb4] +- Updated dependencies [01208ec] +- Updated dependencies [ba22246] +- Updated dependencies [850b2d3] +- Updated dependencies [72fb64f] +- Updated dependencies [18e36b6] +- Updated dependencies [3ef0007] +- Updated dependencies [8b20702] +- Updated dependencies [715cddc] +- Updated dependencies [66e6fb8] +- Updated dependencies [a4d7308] +- Updated dependencies [1dc22b3] +- Updated dependencies [6b3cce1] +- Updated dependencies [ba22246] +- Updated dependencies [6753ebd] +- Updated dependencies [8e47888] +- Updated dependencies [fff443a] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [01208ec] +- Updated dependencies [01208ec] +- Updated dependencies [3ef0007] +- Updated dependencies [0951b7e] +- Updated dependencies [3ef0007] +- Updated dependencies [66e6fb8] +- Updated dependencies [df24197] +- Updated dependencies [f4fec5b] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [01208ec] +- Updated dependencies [4f4967c] +- Updated dependencies [4751921] +- Updated dependencies [a18bfbe] + - @noormdev/sdk@1.0.0 + ## 0.0.1-alpha.5 ### Patch Changes diff --git a/examples/llm-memory-db-pg/package.json b/examples/llm-memory-db-pg/package.json index e49207d8..c407cde5 100644 --- a/examples/llm-memory-db-pg/package.json +++ b/examples/llm-memory-db-pg/package.json @@ -1,6 +1,6 @@ { "name": "@noormdev/example-llm-memory-db-pg", - "version": "0.0.1-alpha.5", + "version": "0.0.1", "private": true, "type": "module", "description": "Full-surface noorm example: LLM memory + task tracker schema, SDK, and tests against PostgreSQL", diff --git a/examples/todo-db/CHANGELOG.md b/examples/todo-db/CHANGELOG.md index e3663372..1e108e97 100644 --- a/examples/todo-db/CHANGELOG.md +++ b/examples/todo-db/CHANGELOG.md @@ -1,5 +1,53 @@ # @noormdev/example-todo-db +## 0.0.1 + +### Patch Changes + +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [8749127] +- Updated dependencies [06d799b] +- Updated dependencies [dd7e387] +- Updated dependencies [8797eb4] +- Updated dependencies [01208ec] +- Updated dependencies [ba22246] +- Updated dependencies [850b2d3] +- Updated dependencies [72fb64f] +- Updated dependencies [18e36b6] +- Updated dependencies [3ef0007] +- Updated dependencies [8b20702] +- Updated dependencies [715cddc] +- Updated dependencies [66e6fb8] +- Updated dependencies [a4d7308] +- Updated dependencies [1dc22b3] +- Updated dependencies [6b3cce1] +- Updated dependencies [ba22246] +- Updated dependencies [6753ebd] +- Updated dependencies [8e47888] +- Updated dependencies [fff443a] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [01208ec] +- Updated dependencies [01208ec] +- Updated dependencies [3ef0007] +- Updated dependencies [0951b7e] +- Updated dependencies [3ef0007] +- Updated dependencies [66e6fb8] +- Updated dependencies [df24197] +- Updated dependencies [f4fec5b] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [ba22246] +- Updated dependencies [01208ec] +- Updated dependencies [4f4967c] +- Updated dependencies [4751921] +- Updated dependencies [a18bfbe] + - @noormdev/sdk@1.0.0 + ## 0.0.1-alpha.5 ### Patch Changes diff --git a/examples/todo-db/package.json b/examples/todo-db/package.json index af217183..1922a3d4 100644 --- a/examples/todo-db/package.json +++ b/examples/todo-db/package.json @@ -1,6 +1,6 @@ { "name": "@noormdev/example-todo-db", - "version": "0.0.1-alpha.5", + "version": "0.0.1", "private": true, "type": "module", "description": "SDK integration tests exercising the todo-db example schema", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 80fa624d..a18c58a5 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,770 @@ # @noormdev/cli +## 1.0.0 + +### Major Changes + +- ba22246: Resolve the access channel from who is driving, not which binary was invoked + + `Channel` used to name the transport: `user` meant the CLI/TUI/SDK and `mcp` + meant the MCP server. Those only coincide when a human is at the keyboard. An + agent refused a write over MCP could see `noorm` on the PATH and shell out, + and because the CLI hardcoded `user` at every policy call site, that second + attempt ran with the human's role. On a stock config that turned deny into + allow for `sql:write`, `sql:ddl`, `db:create`, `run:build` and `vault:read`, + and turned `db:destroy` into a confirm that `--yes` satisfied. + + Two breaking changes: + + **The config fields are renamed.** `Channel` is now `'user' | 'agent'`, and + `ConfigAccess` is `{ user, agent }` instead of `{ user, mcp }`. `agent: false` + hides a config from agents on _both_ transports, not just over MCP. Stored + state migrates automatically (state schema v3) and carries every value over + verbatim — `mcp: 'operator'` becomes `agent: 'operator'`, `mcp: false` becomes + `agent: false`. SDK callers passing `channel: 'mcp'` to `createContext` must + pass `'agent'`, and anything reading or writing `config.access.mcp` must use + `config.access.agent`. In the TUI, the "MCP Role" field is now "Agent Role". + + **Agents shelling out to the CLI now get the agent role.** The CLI resolves + its channel from provenance via `resolveChannel()`: an allowlist of variables + the agent harnesses (Claude Code, Codex, Cursor, Gemini CLI) set for their own + child processes. A stock config gives agents `viewer`, so commands that used + to succeed inside an agent session are now refused — that is the fix, not a + regression. `TERM_PROGRAM`, `CI` and TTY state are deliberately not consulted; + they describe the terminal or the pipeline, not the caller. + + Set `NOORM_CHANNEL=user` to opt out when a human is scripting from inside an + agent session, or `NOORM_CHANNEL=agent` to opt in with no harness present. An + agent can set that variable too; this defends against one routing around a + refusal, not one deliberately evading the check. + +- 72fb64f: ## Template Inspector & Execution Control + + ### CLI + + - **Template Inspector Screen** (`run/inspect`, `[i]` shortcut): New dedicated screen for debugging SQL templates + + - Categorized context view (data files, helpers, built-ins, config, secrets, environment) + - Array shape detection to debug property access failures + - Template preview with render error display + - Refresh support for iterative debugging + + - **Rerun Confirmation**: Pre-execution file status check with dialog + + - Shows count of new, previously-run, changed, and failed files + - Confirmation prompt before re-running previously-executed files + - `[r]` retry shortcut in all run screens (respects `--force` flag) + + - **Execution Cancellation**: `[c]` to abort long-running operations + + - Destroys connection to cleanly stop execution + - Shows "Execution cancelled" error state + + - **Unified KeyHandler Pattern**: Replaced per-screen focus handlers with flexible KeyHandler component + + ### SDK + + - `checkFilesStatus()` function for pre-execution file status categorization + - New types: `FileStatusCategory`, `FileStatusResult`, `FilesStatusResult` + +- 6b3cce1: Initial release of noorm - Database Schema & Change Manager + + ## @noormdev/cli + + ### Features + + - **Terminal UI** - Full-featured TUI for managing database schemas + - **Headless Mode** - CLI commands with JSON output for CI/CD pipelines + - **Multi-Dialect Support** - PostgreSQL, MySQL, SQLite, MSSQL + - **Change Detection** - Checksum-based tracking, only changed files re-execute + - **SQL Templates** - Dynamic SQL with Eta templating engine + - **Change Management** - Versioned migrations with forward/revert support + - **Schema Explorer** - Browse tables, views, indexes, functions from terminal + - **SQL Terminal** - Built-in REPL with query history + - **Config Management** - Multiple database configs with encrypted storage + - **Secrets** - Encrypted secret storage with template injection + - **Stages** - Environment templates for teams + - **Protected Configs** - Safety guards for production databases + - **Locking** - Concurrent operation control + - **Identity** - Audit trail with git-based identity + + ## @noormdev/sdk + + ### Features + + - **Programmatic API** - Full access to noorm functionality + - **Context-based** - Single entry point via `createContext()` + - **Type-safe** - Full TypeScript support + - **Observable** - Event-based architecture with `@logosdx/observer` + - **Test Integration** - `requireTest` guard for safe test database usage + +- ba22246: ## Headless contract: one `--json` envelope, four exit codes + + ### Changed + + - `fix(cli)!:` every `--json` payload is now a JSON **object** carrying a top-level boolean `success`, and is never a bare array. `success` is `true` if and only if the process exits `0`, so `jq -e '.success'` is a failure check that works against every command. + - **Commands that returned a top-level array now return a named object.** `change list` → `.changes` (plus `.pending`), `change history` → `.history`, and every `db explore` list — `tables`, `views`, `indexes`, `fks` (→ `.foreignKeys`), `functions`, `procedures`, `types`, `triggers` — under the matching key. A script doing `jq '.[]'` on any of those must now name the key. + - **Every other payload gained `success` and lost nothing.** Existing key reads keep working. + - `config export --json` now emits the envelope (`{success, name, config}`). The bare artifact — the shape `config import` reads — is still what `noorm config export dev > dev.json` produces, unchanged. + - `noorm update --json` no longer prints two JSON documents for one error, and no longer reports `success: true` on a failed install while exiting non-zero. + - `fix(cli)!:` exit codes now distinguish four outcomes: `0` success, `1` total failure, `2` usage error (bad or missing flag, a TTY-only command run non-interactively, or a named target that does not exist), `3` partial failure. + - **`2` no longer means "partial".** It previously meant partial failure on `run build` / `run dir` / `run files` / `run exec` and the five `change` execution commands — but the same `2` was also returned for a _total_ failure on those commands, so the two were indistinguishable. Partial now has its own code. **A pipeline testing `[ $? -eq 2 ]` for "partially applied" must now test `-eq 3`.** Checks of the form `if ! noorm …` are unaffected. + - `ci secrets` partial loads move from `2` to `3`. `db transfer`'s total-failure exit moves from `2` to `1` (its partial exit stays `3`). `vault propagate` and `vault rm`, `dev test-workers` and `dev test-helpers` now report partial and not-found instead of collapsing everything to `1`. + - A "you named something that isn't there" failure — missing file, directory, config, change, secret key, table, or a glob matching nothing — moves from `1` to `2` across the CLI. Confirmation and `--force` refusals deliberately stay at `1`. + + ### Fixed + + - `noorm run inspect ` reported a fully-populated context and exit `0` for a file that was never on disk — it only ever read the template's _directory_. It now fails with exit `2`. `run preview` gained the same up-front check so both surfaces agree. + - `noorm run dir` reported success over a directory that contained no SQL files, and reported a missing directory as a SQL failure. Both now exit `2` with a message naming the path. + - `noorm run exec` died with `Bun is not defined` under Node — the documented dev entry point — before doing any work. Glob expansion falls back to `node:fs/promises` when the `Bun` global is absent; the compiled binary keeps Bun's own matcher. + - `noorm run preview --json` put a full stack trace, including absolute filesystem paths, into the machine-readable `error` field. `--json` now carries the message only; the stack still reaches an operator on stderr. + - `noorm lock acquire` gained `--timeout` and `--reason`, which the TUI has always collected — CI-acquired locks were permanently default-timeout and reasonless, so whoever they blocked had no way to see what they were waiting on. + - `withVaultContext` never passed `yes` to the SDK context, so `--yes` / `NOORM_YES` was inert for every vault command. + - `db transfer` declared a `--force` flag that nothing read. + - `run exec`, `db reset`, `vault cp`, and `dev test-helpers` wrote some errors straight to stderr, leaving `--json` callers with an empty stdout and no envelope to parse. + +- 6753ebd: **BREAKING:** Reorganize flat `NoormOps` into domain-aligned sub-namespaces + + The flat `ctx.noorm.*` API has been replaced with sub-namespaces that mirror the TUI home screen: + + - `ctx.noorm.changes.*` — scaffold, discover, validate, apply, revert, ff, status, pending, history + - `ctx.noorm.run.*` — discover, preview, file, files, dir, build + - `ctx.noorm.db.*` — listTables, describeTable, overview, previewTeardown, truncate, teardown, reset + - `ctx.noorm.lock.*` — acquire, release, status, withLock, forceRelease + - `ctx.noorm.vault.*` — init, status, set, get, getAll, list, delete, exists, propagate, copy + - `ctx.noorm.secrets.*` — get + - `ctx.noorm.templates.*` — render + - `ctx.noorm.transfer.*` — to, plan + - `ctx.noorm.dt.*` — exportTable, importFile + - `ctx.noorm.utils.*` — checksum, testConnection + + **Migration examples:** + + | Before | After | + | ----------------------------- | ------------------------------- | + | `ctx.noorm.build()` | `ctx.noorm.run.build()` | + | `ctx.noorm.fastForward()` | `ctx.noorm.changes.ff()` | + | `ctx.noorm.applyChange(name)` | `ctx.noorm.changes.apply(name)` | + | `ctx.noorm.listTables()` | `ctx.noorm.db.listTables()` | + | `ctx.noorm.acquireLock()` | `ctx.noorm.lock.acquire()` | + | `ctx.noorm.truncate()` | `ctx.noorm.db.truncate()` | + | `ctx.noorm.runFile(f)` | `ctx.noorm.run.file(f)` | + | `ctx.noorm.transferTo(c)` | `ctx.noorm.transfer.to(c)` | + + **New capabilities:** change authoring/scaffolding, dry-run previews, file discovery, vault operations, teardown preview + +- 8e47888: ### Cross-Database Data Transfer + + Add a complete data transfer system for copying data between databases, including cross-dialect transfers (PostgreSQL ↔ MySQL ↔ MSSQL). + + **Planning & Execution** + + - Schema introspection via dialect-specific system catalogs to discover tables, columns, primary keys, identity columns, and row estimates + - Foreign key dependency graph with topological sort ensures parent tables transfer before children; graceful fallback on circular dependencies + - Same-server detection with localhost normalization (`127.0.0.1`, `::1`, `localhost.localdomain`) enables direct `INSERT INTO ... SELECT` optimization, bypassing application-level marshalling + - Cross-server path uses configurable batch streaming (default 1000 rows) with per-batch progress events + - Automatic exclusion of internal `__noorm_*` tables + + **Conflict Resolution** + + Four strategies for handling primary key conflicts: + + - `fail` (default) — abort on first conflict + - `skip` — skip conflicting rows (`INSERT IGNORE`, `ON CONFLICT DO NOTHING`, `MERGE ... WHEN MATCHED THEN skip`) + - `update` — upsert existing rows (`ON DUPLICATE KEY UPDATE`, `ON CONFLICT DO UPDATE`, `MERGE ... THEN UPDATE`) + - `replace` — delete and re-insert (`REPLACE INTO`, delete+insert for PG/MSSQL) + + **Dialect-Specific Handling** + + - **PostgreSQL**: `session_replication_role = replica` for FK bypass, `OVERRIDING SYSTEM VALUE` for identity insert, `setval(pg_get_serial_sequence(...))` for sequence reset, `TRUNCATE ... CASCADE` + - **MySQL**: `SET FOREIGN_KEY_CHECKS = 0`, backtick quoting, cross-database `db.table` notation, `ALTER TABLE ... AUTO_INCREMENT` reset + - **MSSQL**: Per-table `NOCHECK CONSTRAINT ALL`, `SET IDENTITY_INSERT ON/OFF`, `DBCC CHECKIDENT` for reseed, `MERGE` statements for all conflict strategies, `[bracket]` quoting, four-part `[db].[schema].[table]` naming + + **Options**: `--tables`, `--on-conflict`, `--batch-size`, `--truncate`, `--no-fk`, `--no-identity`, `--dry-run` + + **UI** + + - 7-phase TUI wizard: destination selection → table picker (multi-select with "all" toggle) → options → plan preview with dependency visualization → confirm → live progress bars → completion summary + - Headless mode with `--to ` and full JSON output support + - Observer events for real-time progress: `transfer:planning`, `transfer:plan:ready`, `transfer:table:progress`, `transfer:complete` + + *** + + ### Portable .dt Data Format + + Add a portable data format (`.dt`) for file-based export/import and cross-dialect database transfers. + + **Format** + + - JSON5-based line format: schema header line followed by data rows + - Three extensions: `.dt` (plain), `.dtz` (gzip compressed), `.dtzx` (encrypted + compressed) + - Schema header captures source dialect, version, table name, and column types + - Universal type system maps dialect-specific types to portable intermediates + + **Type System** + + - Simple types: `string`, `int`, `bigint`, `float`, `decimal`, `bool`, `timestamp`, `date`, `uuid` + - Encoded types: `json`, `binary`, `vector`, `array`, `custom` + - Version-aware mappings: MySQL VECTOR (9.0+), MSSQL JSON/VECTOR (2025+), PostgreSQL array notation + - Smart encoding: raw for small values, base64 for binary, gzip+base64 when compression saves ≥15% + + **Cross-Dialect Transfers** + + - In-memory `DtStreamer` converts source rows → universal intermediates → target rows + - No file I/O or serialization overhead — pure type conversion + - Soft-limit batching: flush at row count OR 1GB memory threshold to prevent OOM on large BLOBs + - Version detection via `queryDatabaseVersion()` enables version-aware type mapping + + **File Export/Import** + + - `DtWriter`: streaming writer with extension-based format selection + - `DtReader`: streaming reader with async row iteration + - Passphrase-based encryption: PBKDF2 key derivation + AES-256-GCM + - Schema validation: target table structure checked before import begins + + **CLI Integration** + + - `--export ` with `--tables`: single table → file path, multiple tables → directory with `
.dt` per table + - `--import `: load `.dt`/`.dtz`/`.dtzx` into active config + - `--compress`: produce `.dtz` output + - `--passphrase`: produce/consume encrypted `.dtzx` + - Mutually exclusive with `--to` (file export vs db-to-db transfer) + + **TUI Integration** + + - Export/import options in destination selection phase + - Export flow: destination → tables → export options (path, compress, encrypt) → confirm → progress → complete + - Import flow: file path → passphrase (if `.dtzx`) → schema preview with validation → options → confirm → progress → complete + + **Template Loader** + + - `.dt` and `.dtz` files usable as seed data in templates + - Registered in template loader system alongside SQL and JSON loaders + - No `.dtzx` support in templates (no way to provide passphrase) + + **Observer Events**: `dt:export:start`, `dt:export:progress`, `dt:export:complete`, `dt:import:start`, `dt:import:schema`, `dt:import:progress`, `dt:import:complete`, `dt:stream:start`, `dt:stream:progress`, `dt:stream:complete`, `dt:validate:result` + + *** + + ### Encrypted Secrets Vault + + Add a team-shared encrypted secrets vault backed by the database with dual-layer encryption. + + **Encryption Architecture** + + - Dual-layer model: vault key encrypted per-user via ephemeral X25519 ECDH + HKDF-SHA256 + AES-256-GCM, secret values encrypted with the shared vault key via AES-256-GCM + - Each user receives their own encrypted copy of the vault key using their identity public key + - Access determined by presence of `encrypted_vault_key` on identity row — no separate permissions table + + **Secret Resolution Hierarchy** + + Three-tier priority system for secret lookups: + + 1. Config-specific local secrets (highest, from `.noorm/state/state.enc`) + 2. Global local secrets (shared across configs) + 3. Vault secrets (lowest, database-backed) + + Secrets available in template context as `<%= secrets.KEY_NAME %>` via `buildSecretsContext()`. + + **Operations** + + - `vault init` — generate vault key, encrypt for initializer's identity + - `vault set ` — encrypt and upsert secret with `setBy` audit trail + - `vault rm ` — remove secret by key + - `vault list` — list all secrets with metadata (who set them, timestamps) + - `vault cp` — copy secrets between configs with `--all`, `--force`, `--dry-run`; auto-initializes destination vault if needed + - `vault propagate` — grant vault access to all pending team members by encrypting the vault key for their public keys + + **UI** + + - TUI screen with secret list, defined-but-unset indicators (from settings), pending user count, and keyboard shortcuts for all operations (`[a]`dd, `[d]`elete, `[p]`ropagate, `[i]`nit) + - Headless commands with JSON output for all operations + - Observer events: `vault:initialized`, `vault:secret:created`, `vault:secret:updated`, `vault:secret:deleted`, `vault:propagated`, `vault:copy:completed` + + **Database Schema**: New `__noorm_vault__` table (`secret_key`, `encrypted_value`, `set_by`, timestamps) and `encrypted_vault_key` column on `__noorm_identities__` + + *** + + ### SDK: Move noorm operations to `ctx.noorm` namespace + + **Breaking:** Refactor the SDK Context to separate SQL-focused operations from noorm management operations. All schema, change, lock, runner, explore, transfer, and DT methods move from the top-level `ctx` to a new `ctx.noorm` namespace via a lazy-initialized `NoormOps` class. + + Top-level Context retains only SQL-focused API: `kysely`, `dialect`, `connected`, `connect()`, `disconnect()`, `transaction()`, `proc()`, `func()`. + + **Before:** + + ```ts + await ctx.build(); + await ctx.fastForward(); + const tables = await ctx.listTables(); + await ctx.acquireLock(); + ``` + + **After:** + + ```ts + await ctx.noorm.build(); + await ctx.noorm.fastForward(); + const tables = await ctx.noorm.listTables(); + await ctx.noorm.acquireLock(); + ``` + + Properties `config`, `settings`, `identity`, `observer` also move to `ctx.noorm`. + + **Migration:** Add `.noorm` between `ctx` and any management method call. SQL operations (`kysely`, `transaction`, `proc`, `func`) and lifecycle (`connect`, `disconnect`) are unchanged. + + *** + + ### Run + + - Fix form retry on failure: reset internal `submitting` state after `onSubmit()` completes so the form is no longer permanently locked after a failed submission; parent `busy` prop still guards against double-submit during async operations + - Apply settings exclude rules to build: pass pre-filtered file list from `RunBuildScreen` to `runBuild()` instead of letting the runner re-discover all files from disk, which ignored `include`/`exclude` rules + +- ba22246: ## Access default: agents no longer get admin + + ### Changed + + - `fix(policy)!:` a config with no explicit `access` now resolves to `{ user: 'admin', mcp: 'viewer' }` instead of `{ user: 'admin', mcp: 'admin' }`. A stock project writes no `access`, so every config was handing full admin to any connected MCP client — writes, DDL, `change_run`, `run_build`, database drops, and vault/secret reads all went through unchecked. + - **The `user` channel is unchanged.** Nothing about the CLI, TUI, or SDK behaves differently. + - **MCP agents keep read access.** `explore` and read-only SQL (`SELECT`, `EXPLAIN`, `SHOW`, `DESCRIBE`) still work with no setup. + - **MCP agents lose write access by default.** If an agent needs to write, run changes, or run builds, set that config's `access.mcp` to `operator` or `admin` explicitly — via `noorm ui` → Config → Edit, or in the JSON before `config import`. + - **Configs that already store an explicit `access` are untouched**, including ones holding `mcp: 'admin'`. If you created configs on an earlier build, that migration already wrote `admin/admin` to disk and it is preserved — run `noorm config list` and lower any `mcp:admin` you did not intend. + - `noorm config list` now prints the access tag for any config that differs from the default, so an `mcp: admin` escalation is visible. Previously the tag was hidden whenever the user channel was `admin`, which concealed exactly that case. + +- ba22246: ## Authorization is now enforced at the core seam + + Policy was re-implemented per surface, and several cells were simply empty — the CLI would perform operations the TUI correctly refused, and two whole domains had no permission to check against. + + ### Fixed + + - `fix(policy)!:` the SQL classifier no longer routes writes as reads. `EXPLAIN (ANALYZE) DELETE FROM t` classified as `read` and deleted rows under a `viewer` role — through the CLI and over a live MCP session. Two independent causes are fixed: the keyword fallback treated `EXPLAIN` as terminally read-only, and on the parser path `EXPLAIN` inherited nothing from the statement it wraps while the write-signal scan had no DDL node types. An `EXPLAIN` now inherits its wrapped statement's classification. + - `fix(policy)!:` statement splitting is dialect-aware. It tracked only `'`, so an MSSQL bracket-quoted identifier (`SELECT 1 AS [a'b]; DROP TABLE x`) split wrong and the tail ran unclassified. `"`, backticks, `[…]`, `$tag$…$tag$` and all three comment forms are now modelled. + - `fix(policy)!:` a statement with no leading word character used to classify as `read`, so a leading NUL byte carried `DROP TABLE` past the gate. It now fails closed. + - `feat(policy)!:` added the permissions that did not exist — `vault:*`, `secret:*`, `config:write`, `db:truncate`, `db:teardown`, `transfer:plan`, `lock:force`, `debug:*`. Operations in those domains had nothing to gate against and shipped ungated. + - `fix(vault)!:` vault and secret operations are gated at the core seam, so SDK, CLI and TUI inherit one check. A `viewer` config that was denied `run build` could still run `vault set`, `vault rm`, `vault propagate` and `secret set`. + - `fix(db)!:` `db create` is gated. The matrix denied `db:create` to `viewer` and the TUI enforced it; the CLI created the database anyway. + - `fix(db)!:` `db truncate` and `db teardown` require confirmation. `--yes`/`--force` were declared arguments that neither command read, so `noorm db teardown < /dev/null` dropped 63 objects and exited 0 — against documentation promising the CLI refuses. + - `fix(debug)!:` the debug screens' delete paths are gated. They removed rows from `noorm.vault` and `noorm.identities` with no authorization check at all. + - `fix(lock)!:` `lock force` is gated and requires `--yes`, names the holder it evicts, and returns a distinct exit code when there was nothing to release. + - `fix(config)!:` `config import --force` is gated on `config:write`. It rewrote a config's `access` with no check, escalating a `viewer` config to full delete rights in one command. + - `fix(transfer):` `getTransferPlan` is gated, matching the invariant its own comment claimed. `--dry-run` leaked destination table names, row estimates and the FK graph to a denied caller. + +- ba22246: ## Identity and state key handling fail closed + + ### Fixed + + - `fix(identity)!:` malformed key material is rejected instead of deriving a publicly computable key. `Buffer.from(key, 'hex')` never throws, so any non-hex private key collapsed to a zero-length HKDF input and produced a **constant** AES key — an attacker holding no key material could decrypt a real `state.enc`. `isValidKeyHex` existed but was only called on the CI path; it now guards every entry point. + - `fix(ci)!:` `ci identity enroll` verifies the stored public key matches the one presented. It checked only that a row existed, so anyone able to INSERT into `noorm.identities` — no vault access required — could pre-plant their own key and receive the vault key, while the operator's enrollment reported success and echoed back the expected key. + - `fix(identity)!:` `identity init --force` requires `--yes`, backs up the keypair, and names what it destroys. It silently regenerated the keypair and permanently destroyed every project's `state.enc`. + - `fix(ci):` `ci init --force` backs up `state.enc` and refuses on a TTY without confirmation; it destroyed all configs and secrets unprompted. It also now routes through `parseConfig`, so a numeric-looking database name persists as a string. + - `fix(vault)!:` `vault propagate` names every recipient and withholds until confirmed, and reports partial failures instead of returning success with the failed identity silently omitted. + - `fix(sdk):` an explicit identity scope whose revert fails is no longer marked reverted, and `disconnect()` no longer hangs when scopes are still held. + +- ba22246: ## Operations no longer report success over lost, duplicated or unwritten data + + ### Fixed + + - `fix(transfer)!:` export and db-to-db transfer paginate by primary key instead of bare `LIMIT/OFFSET`. With no `ORDER BY`, a source being written to during a 50,000-row export produced 14,149 missing and 13,387 duplicated rows while reporting `rowsExported: 50000` and exiting 0. Tables with no primary key now take a single-statement snapshot rather than corrupting silently. + - `fix(dt)!:` a malformed row or a dead worker no longer hangs the pipeline forever. The error branch never decremented the in-flight count, `request()` never settled on worker death, and `worker:exit` had no subscribers — a four-byte bad payload wedged `db transfer --import` indefinitely. Three busy-wait loops are replaced with real settlement. + - `fix(transfer)!:` the postgres same-server path no longer copies the destination into itself. It ignored the source database entirely and could only ever emit `INSERT INTO t SELECT ... FROM t`. + - `fix(dt):` decompression is bounded on import. An unbounded `gunzip` inflated 398 KB to 400 MB (1029:1), and because it runs in a compute worker the resulting OOM triggered the hang above. + - `fix(transfer):` reported counts reflect reality — `onConflict: 'skip'` no longer counts no-ops as inserted, and the same-server path reports affected rows instead of a postgres `reltuples` estimate. + - `fix(transfer):` `db transfer --export` with no `--tables` exports every table, as its own flag description promised. It exported nothing, printed `success: true` and exited 0. + - `fix(transfer):` cross-dialect transfer works. The planner probed the destination with the _source_ dialect, so every cross-dialect transfer aborted and roughly 400 lines of streaming conversion were unreachable — while the docs claimed the feature worked. + - `fix(teardown)!:` `truncate` qualifies statements with the table's schema. It discarded the schema, emptied 12 tables and then aborted — partial irreversible loss, reported as a failure. + - `fix(state)!:` `state.enc` writes are atomic and reconciled. Ten concurrent `noorm secret set` runs left five secrets, all exiting 0. Writes now stage-and-rename with an exclusive lock, three-way reconcile against a changed file, and keep a `.bak` generation. + - `fix(state):` reads stopped rewriting state. `needsMigration` tested a field the migration never writes, so it was always true and every command — including `config list` — re-encrypted and rewrote `state.enc`, turning reads into writes and amplifying the loss above. + - `fix(runner):` `.sql.tmpl` dedup works. The stored checksum was the raw file hash while the comparison used the rendered hash, so templates re-executed on every build and failed on non-idempotent DDL. + - `fix(runner):` SQLite executes every statement in a multi-statement file instead of silently running the first and reporting success. + +### Minor Changes + +- ba22246: ## Added + + - `feat(identity):` Record the detected agent harness in operation provenance. When a session runs under a recognised agent harness, `executed_by` is suffixed with `(via )` — so a change applied by an agent is distinguishable from one a human applied. Stamped at the shared insert seam, so change operations, resets and run operations all carry it on every dialect. + - `feat(cli):` `noorm info` reports the detected harness and the environment variables that identified it, so an agent-driven session is visible rather than silent. + + Provenance is folded into the existing identity string rather than a new column: the audit question is binary, and a suffix answers it without a four-dialect schema migration. It is not an attestation — `executed_by` is unauthenticated free text and harness detection reads caller-controlled environment variables, so the suffix records what noorm observed, not a proven claim. + +- 88b33ce: ## Added + + - `feat(dt):` DT file modifier — drop, add, or rename columns and filter rows from exported `.dt`/`.dtz`/`.dtzx` files without re-exporting from the source database + - `feat(cli):` "Modify .dt file" option in the Data Transfer screen with interactive recipe builder, schema preview, and streaming output + +- 6753ebd: Add graceful shutdown screen with phase progress display on exit. Migrate test runner from vitest to bun test. Extract shared CLI hooks and utilities to reduce duplication across screens. +- e87c435: Rework `noorm change` for interactive-first ergonomics and drop the surprising bare-invocation side effect. + + - Bare `noorm change` now renders citty's help output and **does not connect to the database**. The status listing that used to live there moved to a new explicit leaf: `noorm change list`. This matches every other root command (`config`, `settings`, `identity`, `db`, `vault`, `secret`, `run`) and prevents accidental connection attempts when users are just exploring the CLI. + - `change list` — new. Lists every known change with its status; accepts `--config`, `--json`. + - `change edit [name]` — new. Resolves the change folder from settings and spawns `$EDITOR` (then `$VISUAL`, then `code`) with stdio inherited so terminal editors work in-place; exits with the editor's own exit code. Surfaces spawn failures (e.g. editor binary not found) instead of silently no-oping. + - `change add [description]` — prompts for a description via `p.text` when omitted on a TTY. + - `change rm [name]` — prompts for a change to delete, then confirms with `p.confirm`. `--yes` is no longer required on a TTY (it now just skips the confirm). On a non-TTY, both the name and `--yes` are still required so CI never deletes silently. + - `change run [name]` — prompts from pending/reverted changes (filters `ctx.noorm.changes.status()` by `!orphaned && status in { pending, reverted }`). + - `change revert [name]` — prompts from successfully applied changes. + - `change rewind [name]` — prompts from successfully applied changes. + - `change history-detail [name]` — prompts from changes with execution history (`status !== 'pending'`). + + In every case, non-TTY invocations without a name exit 1 with a uniform `Change name required…` error. User cancellation from any picker exits 1 with `Cancelled.`. + + **Migration:** replace `noorm change` with `noorm change list` in any scripts or CI jobs that relied on the status listing. The `--json` shape is unchanged. + +- 850b2d3: ## Added + + - `feat(cli):` Migrate CLI from meow to citty with structured subcommands, built-in `--help` examples, and `noorm ui` launcher + - `feat(cli):` Add shell completion via `@bomb.sh/tab` + + ## Fixed + + - `fix(change):` Populate `cli_version` in change history rows + - `fix(cli):` Suppress `logger.info` inside `fn()` callbacks when `--json` is active + - `fix(cli):` Restore vault cp dry-run preview and 3-positional CLI surface + - `fix(cli):` Wire JSON output for change run/revert and unify history JSON path + - `fix(cli):` Restore human-readable db transfer output + +- f946820: Add CI identity bootstrap and two interactive settings editors: + + - `noorm identity ci` — diagnostic command for CI environments. Bootstraps the process from `NOORM_IDENTITY_PRIVATE_KEY`, `NOORM_IDENTITY_NAME`, and `NOORM_IDENTITY_EMAIL` env vars, deriving the public key from the private key and computing a deterministic identity hash across runners. In-memory overrides for both private key and metadata replace `~/.noorm/` filesystem reads, so CI runners can decrypt vault/state without writing key files. + - `noorm settings edit` — interactive editor covering all 7 settings sections: paths, build, strict, logging, stages, rules, teardown. Stages and rules use add/edit/remove sub-loops. Esc inside a sub-editor returns to the section picker; only Esc at the top level exits. Adds `setTeardown()` to `SettingsManager` and a `settings:teardown-updated` event. + - `noorm settings secret` — interactive editor for universal and stage-scoped secret **requirements** (declarations, not values). Supports add, edit, remove, and list actions. + +- f522adc: ### Added + + - `feat(cli):` Add `noorm init --here` to initialize a project in the original cwd, ignoring any parent `.noorm/` discovered while walking up + - `feat(cli):` Add global `-c ` / `--cwd ` flag (like `git -C`) that runs the subcommand inside `` and skips the walk-up. Must precede the subcommand; after the subcommand `-c` keeps its per-command `--config` meaning. + + ### Changed + + - `refactor(cli):` `noorm init` now reports an existing `.noorm/` _before_ the TTY gate, so scripted invocations get the more actionable error. + +- 2174274: ## Added + - `feat(cli):` Add `info` command for project and database status display (`noorm info`, `noorm --json info`) + - `feat(cli):` Show CLI version, schema/state/settings versions, install/upgrade dates, and DB object stats on Home screen +- 4373083: ## Added + - `feat(mcp):` Add MCP server for coding agent integration — `noorm mcp serve` starts a stdio JSON-RPC server, `noorm mcp init` generates `.mcp.json` config files + - `feat(rpc):` Add transport-agnostic RPC command registry with Zod-validated commands: `connect`, `disconnect`, `list_configs`, `overview`, `list`, `detail`, `sql`, `change_history`, `change_run`, `change_ff`, `change_revert`, `run_build`, `run_file` + - `feat(rpc):` Add SQL protection for read-only enforcement on protected configs using `sql-parser-cst` with keyword fallback +- 7b907b3: Introduce the `noorm ci` command namespace and retire the standalone `noorm identity ci` diagnostic. + + Four new commands cover the full CI lifecycle — mint a keypair, enroll it in a real database, bootstrap ephemeral state inside a job, and batch-load secrets: + + - `noorm ci identity new --name --email ` — generate a test-CI keypair locally. No database contact, no state written. Prints the private key once plus a copy-pasteable `NOORM_IDENTITY_*` env block. Designed for stateless/ephemeral CI (`isTest` configs, throwaway databases). Accepts `--json`. + - `noorm ci identity enroll --config --name --email [--public-key ]` — register a CI identity in the target database and propagate vault access to it. Run once by a developer with existing vault access. Decrypts the caller's vault key, inserts a new identity row (`machine='ci'`, `os='env'`), and re-encrypts the vault key for the new identity. Idempotent on identityHash — safe to re-run. When `--public-key` is omitted, mints a new keypair and returns the private key; when provided, only the public half is enrolled (air-gapped flow). + - `noorm ci init [--name ] [--force]` — bootstrap ephemeral `state.enc` from `NOORM_IDENTITY_*` + `NOORM_CONNECTION_*` env vars. Runs inside the CI job. Creates a config (default: `ci`, override via `--name` or `NOORM_CI_CONFIG_NAME`), marks it active, sets `isTest: true`. Absorbs the former `noorm identity ci` precheck — fails fast with exit 1 if any required env var is missing or malformed, or if `state.enc` already exists without `--force`. + - `noorm ci secrets --file [--config ] [--overwrite]` — batch-load secrets from a dotenv-style file into the active (or `--config`-named) vault. Parser ignores blank lines and `#` comments, splits on the first `=`, and strips a single matched pair of surrounding quotes. Existing keys are skipped unless `--overwrite` is set (so reruns are safe). Exit codes: `0` clean, `1` precondition failure, `2` partial (some set, some errored). + + **Removed:** `noorm identity ci`. Its precheck behavior is now built into `noorm ci init`. Callers that used `identity ci` only for validation should replace it with `noorm ci init`, which does the validation plus the state bootstrap. + + **Migration:** replace any pipeline that set `NOORM_IDENTITY_*` + `NOORM_CONNECTION_*` and ran `noorm identity ci` followed by `noorm change ff` with: + + ```bash + noorm ci init --name prod + noorm change ff + ``` + + For vault-aware pipelines, provision the CI identity once from a developer machine: + + ```bash + noorm ci identity enroll --config prod --name "CI Bot" --email ci@example.com + # copy the printed NOORM_IDENTITY_* block into your CI secrets store + ``` + +- e10b7a4: Add two new CLI commands: + + - `noorm init` — interactive project bootstrap. Creates identity (if missing), project structure, and settings. Requires an interactive TTY. + - `noorm sql repl` — launches the TUI directly on the SQL Terminal screen. Supports `--config ` to switch active config before launching. Requires an interactive TTY. + +- 2955758: ### Added + + - `feat(headless):` Add `run inspect` command — inspect template context (data files, helpers, builtins, config, secrets) without executing, with `--json` support + - `feat(headless):` Add `run preview` command — render .sql.tmpl files and output raw SQL to stdout, pipeable to files or other tools + + ### Fixed + + - `fix(errors):` Propagate SQL Server TDS diagnostic info (line numbers, error codes, procedure names, severity) through to TUI — errors now show e.g. `[Line 42, Err 207] Invalid column name` instead of just the message text + - `fix(errors):` Propagate PostgreSQL and MySQL diagnostic info (error codes, SQLSTATE, severity) through to TUI + - `fix(errors):` Handle Kysely-unpacked `AggregateError` arrays from TDS with multi-line display + - `fix(template):` Eta `autoTrim` left-trim was eating newlines after interpolation tags, joining SQL lines (e.g. `ENDAS`, `ENDIF NOT EXISTS`) — disabled autoTrim and implemented directive-line stripping for `-- {% %}` convention + - `fix(db):` Disconnect shared TUI connection before `DROP DATABASE` to prevent ECONNRESET errors + - `fix(db):` Show friendly "Not Created" notice instead of aggressive ERROR badge when database does not exist + - `fix(tui):` Show full multi-line SQL errors in all run/change screens instead of truncating to 60 characters + +- 6753ebd: Add curl|sh installer and npm binary wrapper for streamlined installation. The published npm package now acts as a thin shim that downloads and executes the platform-specific compiled binary. +- c8da002: ## Auto-Update Notifications + + ### CLI + + - **Background Update Checking**: Checks npm registry on TUI launch + + - Toast notification for available minor/patch updates + - Warning toast for major version updates + - Respects user preferences in `~/.noorm/settings.yml` + + - **Global Settings**: User-level preferences at `~/.noorm/settings.yml` + + - `checkUpdates`: Enable/disable update checking (default: true) + - `autoUpdate`: Auto-install non-major updates (default: false) + - `dismissable`: Per-alert "don't ask again" preferences + + - **DismissableAlert Component**: Reusable confirmation dialogs + - Auto-resolves based on stored preference ('always'/'never'/'ask') + - Keyboard navigation with arrow keys and number shortcuts + - Optional "Don't ask again" checkbox with persistence + + ### SDK + + - New `src/core/update/` module with: + + - `checkForUpdate()`: Version comparison with prerelease channel support + - `installUpdate()`: Background npm install via child process + - `loadGlobalSettings()` / `saveGlobalSettings()`: User preferences + - `getDismissablePreference()` / `updateDismissablePreference()`: Alert state + + - New observer events: `update:checking`, `update:available`, `update:complete`, etc. + +- 01208ec: ## Added + - `feat(cli):` Universal `--yes` / `-y` flag and `NOORM_YES=1` environment variable for TTY-gated commands. Unblocks CI, scripted bootstrap, and subagent flows. + - `feat(cli):` `noorm init --yes` now succeeds in non-TTY environments when an identity already exists at `~/.noorm/identity.{key,pub,json}`. Without an identity, it errors with a hint pointing at `noorm identity init --name "X" --email "Y"`. + - `feat(cli):` `noorm sql repl --yes`, `noorm settings edit --yes`, and `noorm settings secret --yes` print a documented redirect-hint error instead of refusing silently, telling the user which non-interactive alternative to use (`sql query` / direct YAML edit / `secret set`). +- ba22246: ## Schema exploration answers about the object you asked for + + ### Fixed + + - `fix(explore):` procedure detail returns parameters. It filtered `information_schema.parameters` on a bare name where the column holds `name_oid`, so the list view reported a parameter count and the detail view always showed none — on every surface. + - `fix(explore):` SQLite quotes identifiers at all raw-SQL sites. A single table named with an embedded quote broke listing, overview and detail for _unrelated_ tables. + - `fix(explore):` MySQL table detail reads indexes and foreign keys from the requested schema rather than the connected database, which produced self-contradictory output with the real index missing. + - `fix(explore):` `--schema` is honoured on the list commands, which declared it and ignored it. + - `fix(explore):` the overview counts from the same listings the detail views use, inside one guarded call that surfaces errors, instead of a second implementation that disagreed and hardcoded several counters to zero. + +- ba22246: ## TUI matches the CLI on gating and global modes + + ### Fixed + + - `fix(tui):` `db transfer` routes through the gated path with a typed confirmation. It called core directly, skipping the confirmation tier the SDK enforces. + - `fix(tui):` the global dry-run toggle is honoured by transfer, truncate and teardown. The indicator showed dry-run as active while all three ran for real. + - `fix(tui):` `truncateFirst` is a working control instead of state the UI could display but never change. + - `fix(tui):` `vault propagate` is no longer a bare `p` keypress — it checks policy and names each recipient before granting access to every enrolled identity. + - `fix(tui):` fixed 15 stale-closure dependency omissions across hooks and screens. `react-hooks/exhaustive-deps` is not enabled in this repo, so callbacks silently captured first-render values. + - `fix(tui):` `InitScreen` no longer overwrites an existing `.noorm/.gitignore` on re-init. + +- ba22246: ## Self-update input validation, log rotation and redaction + + ### Fixed + + - `fix(update)!:` the version string from the npm registry is validated as semver before it reaches a URL or a shell. It was interpolated verbatim, and because `fetch` normalises `..`, a poisoned dist-tag could relocate **both the binary and its `checksums.txt`** to an attacker-controlled repository — so checksum verification passed against the attacker's own file. + - `fix(logger):` rotation reopens its write stream. It renamed the file out from under the open descriptor, so every subsequent write landed in the rotated file, `noorm.log` never reappeared, and rotation fired exactly once before growing unbounded. + - `fix(logger):` `settings.logging.enabled`, `.file`, `.maxSize` and `.maxFiles` are honoured. The CLI hardcoded all four, so every logging setting was inert across every command — and the log viewer read a different path than the CLI wrote. + - `fix(logger):` redaction covers this project's own variables. `NOORM_CONNECTION_PASSWORD` and `NOORM_IDENTITY_PRIVATE_KEY` were not masked, credential-bearing URIs passed through verbatim because values were never inspected, and `Error` objects were skipped wholesale. Log files are created `0600` rather than world-readable. + +- 4751921: ## Access Roles + + Replace the per-config `protected: boolean` flag with per-channel access roles. + + **Breaking:** `Config.protected` is removed from the exported types and `access: ConfigAccess` is now required on `Config`/`ConfigSummary`. TypeScript consumers that read `.protected` or construct `Config` literals must move to `access`. Stored state auto-migrates on load (see below), so runtime configs are unaffected. (Released as `minor` under the `1.0.0-alpha` pre-release line, where `^` ranges pin within the alpha series; it is a breaking type change and will be treated as such at the 1.0 boundary.) + + - `feat(policy):` Configs now carry `access: { user, mcp }`, each `'viewer' | 'operator' | 'admin'` (or `mcp: false` to hide the config entirely). Roles are enforced by one policy matrix instead of scattered `protected` checks — `viewer` reads only, `operator` writes and confirms destructive operations, `admin` is frictionless. Enforcement runs at the core seam, so the SDK, TUI, and CLI all inherit it: `run`, `changes`, `transfer`, and the SQL terminal are gated, not just MCP. + - `feat(mcp):` Every MCP command is now gated on `access.mcp` before it runs, closing the gap where `change_run`/`change_ff`/`change_revert`/`run_file`/`run_build` reached the database unchecked. `access.mcp: false` makes a config invisible to agents — absent from `list_configs`, and `connect` fails with the same error an unknown config produces. `confirm`-tier permissions collapse to a hard deny on the MCP channel (no human to type a confirmation phrase); give a config `mcp: 'admin'` if an agent legitimately needs to run changes there. + - `feat(sql):` Raw SQL (`noorm sql`, MCP `sql`, the TUI SQL terminal) is now gated by what the statement actually does — classified `read`/`write`/`ddl` — instead of a blanket read-only check. Data-modifying CTEs (`WITH … AS (DELETE …) SELECT …`) and a denylist of side-effecting functions (`pg_terminate_backend`, `dblink_exec`, `query_to_xml`, … — bare or schema-qualified) classify as writes so a `viewer` cannot mutate through the read path. Multi-statement input takes the highest class present; unparseable input or `EXEC`/`CALL` classify as `ddl` (fail closed). The classifier guards against mutation, not disclosure — back hard confidentiality with database `GRANT`s. + - `feat(sdk):` `createContext({ channel })` — defaults to `'user'`; set `'mcp'` when embedding the SDK behind an MCP-like surface. New exported types `Channel`, `ConfigAccess`, `Role`. `ProtectedConfigError` is now raised from `checkPolicy` denials/unconfirmable actions rather than a bare `protected` check; match on `err.name`/`instanceof`, not the message string (the message text changed). + - **Behavior change:** on the `user` channel, `NOORM_YES=1` resolves an operator `confirm` to allow — including in the SDK guards. A migrated `protected: true` config becomes `operator`, so an SDK/CI caller with `NOORM_YES=1` set now _executes_ `truncate`/`teardown`/`reset`/`dt.import`/`changes.*`/`run.*` where the previous `protected` hard-block threw unconditionally. Unset `NOORM_YES` (or use `viewer`/`mcp:false`) where the block must hold. + - `fix(config):` A legacy `protected: true` migrates automatically to `{ user: 'operator', mcp: 'viewer' }`, and `protected: false`/absent to `{ user: 'admin', mcp: 'admin' }`. The `protected` field is still accepted on `config import`/settings input for this release, then removed. Note: downgrading to a prior binary after this migration silently unprotects configs (older binaries have no access concept) — avoid rolling back once state is migrated. + +- a18bfbe: ### Added + + - `feat(worker-bridge):` Worker thread infrastructure for parallel DT export/import. WorkerBridge class (ObserverRelay subclass), WorkerPool with round-robin dispatch, OrderBuffer for index-ordered reassembly. + - `feat(workers):` Persistent Connection Worker (Kysely-backed DB operations) and stateless Compute Worker (serialize/deserialize) as standalone entry points. + - `feat(dt):` Export and import pipelines now run through worker threads — Connection Worker handles DB queries, Compute Pool parallelizes CPU-bound serialization across N cores. + - `feat(dt):` Three-tier progress events (`loaded`/`processed`/`saved`) for both export and import, enabling granular TUI progress display. + - `feat(connection):` ConnectionManager tracks WorkerBridge instances alongside direct Kysely connections for coordinated shutdown. + - `feat(cli):` `noorm dev/test-workers` diagnostic command for verifying worker thread infrastructure across execution contexts. + - `build:` Worker entry points included in `bun build --compile` for single binary support. + +### Patch Changes + +- eb95caf: ## Added + - `feat(ci):` Automated binary builds on release with install script +- dd7e387: ## Fixed + - `fix(runner):` a third consecutive `run build` no longer re-executes a file the previous build correctly skipped — `skipped` was treated as "never ran" regardless of why, so an `unchanged` skip forced a re-run and failed on any DDL that is not idempotent +- 9673054: ## CLI + + ### Fixed + + - `fix(build):` Bundle all pure JS dependencies (meow, ink, react, pg, mysql2, tedious) - only better-sqlite3 remains external due to native bindings + +- 01208ec: ## Fixed + - `fix(cli):` Wire `noorm change ff --dry-run` (and `next` / `run` / `revert`) through to the SDK — the flag previously parsed but did nothing + - `fix(cli):` Drop the unreachable positional `query` arg from `noorm sql` and add an argv rewriter so `noorm sql "SELECT 1"` is rewritten to `noorm sql query "SELECT 1"` before citty dispatch + - `fix(cli):` Show `(dry-run)` markers in human output and `dryRun: true` in `--json` envelopes for all four change commands +- ba22246: Fix change apply/revert recovery, rewind flag handling, and MySQL support + + - Changes now run on MySQL at all. `ChangeHistory` retrieved insert ids with a `RETURNING` clause MySQL does not support, so no operation record was ever created and `change run`, `change ff`, `change revert` and `db teardown` were all inoperable. + - A reverted or torn-down change can be applied again. Every file was previously skipped against a prior success, so `apply -> revert -> apply` and `db teardown -> change ff` reported success over an untouched database. + - `ff` and `next` now treat `stale` changes as pending work, so teardown has a supported recovery path. + - `change rewind` honours `--dry-run` and `--force`, and accepts the documented count form (`change rewind 3`). `changes.rewind()` takes an options argument. + - A revert whose history cannot be read fails instead of reporting success over zero files. + - `.sql.tmpl` files inside a change now receive `$.config` and `$.secrets`. + - `.txt` manifests execute in the order they list files, instead of being sorted. + - `ff`/`next` warn when the changes directory is missing rather than reporting a clean run. + - `change list` marks orphaned changes and no longer lists the internal `__reset__` teardown marker. + +- b22c1ec: ## CLI + + ### Fixed + + - `fix(build):` Add CJS compatibility shim for dynamic require in ESM bundle + +- cb9f9c2: Display template errors during dry-run in UI feedback + + Template rendering errors during dry-run were silently captured in results but never emitted via the observer event system, making them invisible in the UI. Now `file:dry-run` events include status and error fields, and the progress hook properly tracks failed dry-runs. + +- 0951b7e: ## Fixed + - `fix(ci):` Pin bun to 1.3.11 in release binary workflow — bun 1.3.12 produces binaries that crash on startup (OOM kill, exit 137) +- ba9b358: Fix bundle: inject version at build time instead of requiring package.json +- 2328fa2: ### Fixed + + - `fix(headless):` Produce structured JSON error output (`{ success, error }`) when `--json` is set — previously errors were only logged as text, leaving CI pipelines with no parseable output on failure + - `fix(headless):` Enrich SQL error messages with dialect-aware diagnostics (line numbers, error codes, procedure names, severity) via `getSqlErrorMessage` in all headless command error paths + - `fix(headless):` Standardize `run build` exit code from `2` to `1` to match the `0`/`1` convention used by all other headless commands + - `fix(headless):` Replace stale `.sql.eta` file extension references with `.sql.tmpl` across CLI argument parsing, help text, and documentation + + ### Added + + - `feat(headless):` Add `sql` command to the home help commands list + - `feat(headless):` Document `.sql.tmpl` template file support in `run` help text + +- 3ab86b8: ### Fixed + + - `fix(template):` Resolve `$helpers` loading in compiled binaries — bare specifier resolution now uses `Bun.build()` to bundle helper files with all dependencies, fixing `Cannot find package` errors in pnpm projects + - `fix(inspect):` Show `$helpers` exports in Inspect Template screen — categorization now uses source-based tracking instead of type-guessing, and load errors are surfaced instead of silently swallowed + + ### Added + + - `feat(cli):` Add `dev/test-helpers` diagnostic command for verifying `$helpers` loading from any execution context + +- 365f437: ## Connection + + ### Fixed + + - `fix(mssql):` Connection hangs when target database does not exist — now verifies via `sys.databases` on `master` first + - `fix(mssql):` `ECONNRESET` on MSSQL Server 2022+ due to `encrypt: false` — now defaults to `encrypt: true` + - `fix(mssql):` Tarn pool silently retries failed connections — enabled `propagateCreateError` for fast failure + - `fix(connection):` Retry logic retried non-transient errors like `login failed` and `access denied` + +- 18e36b6: ## Fixed + - `fix(mssql):` Resolve SQL Server build failures caused by Kysely emitting unsupported `LIMIT` and `RETURNING` syntax + - `fix(mssql):` Add MssqlLimitPlugin to translate `.limit()` to `TOP` for all MSSQL queries + - `fix(mssql):` Use `OUTPUT inserted.id` instead of `RETURNING` for insert-and-get-id operations + - `fix(mssql):` Pass dialect from connection through all run screens to runner context + - `fix(schema):` Make v2 migration fully idempotent — handles partial migration states, orphaned tables, and interrupted runs + - `fix(schema):` Handle interrupted v1 migrations where tables exist but version record is missing + - `fix(schema):` Run `ensureSchemaVersion` before identity sync to prevent queries against unmigrated tables + - `fix(schema):` Move `waitForIdentityToLoad` out of connection factory into schema migration lifecycle + - `fix(logger):` Preserve Error objects in log redaction filter instead of spreading into empty `{}` + - `fix(logger):` Surface `.cause` chain in error log messages for wrapped errors + - `fix(runner):` Handle non-standard error objects from tedious driver including `AggregateError` + - `fix(runner):` Propagate batch-level errors to TUI when build fails before file execution + - `fix(runner):` Truncate `skip_reason` to prevent column overflow on MSSQL + - `fix(tui):` Show relative file paths in failed files list instead of basename only +- ef88aeb: ## Schema + + ### Fixed + + - `fix(schema):` MSSQL schema migration fails with `auto_increment` syntax error — use `identity(1,1)` instead + - `fix(schema):` MSSQL schema migration fails when multiple `timestamp` columns exist — use `datetime2` for MSSQL + - `fix(schema):` MySQL schema migration fails on `TEXT` columns with default values — use `varchar(2000)` for error messages + - `fix(schema):` MySQL and MSSQL `DROP INDEX` requires `ON table_name` — made `down()` dialect-aware + +- 3ef0007: fix(mssql): noorm db create / run no longer crash with "Pool is not a constructor" + + In the bundled CLI, `await import('tarn')` / `await import('tedious')` expose + their exports under `.default`, so kysely's MSSQL dialect received an undefined + `Pool` and every MSSQL command (`noorm db create`, `run`, `change`, etc.) failed + with `Cannot connect to server: Pool is not a constructor`. Normalize the CJS + interop, mirroring the postgres dialect. + +- 431f9b9: ## Fixed + - `fix(shutdown):` Force `process.exit()` after graceful shutdown to prevent process hanging from lingering connection pool handles + - `fix(shutdown):` Remove duplicate `app:exit` emission that caused `unmount()` to fire twice + - `fix(shutdown):` Clear timeout timer in connection close race to prevent 5-second event loop leak +- 715cddc: ### Fixed + + - `fix(teardown):` Schema-qualify all DROP statements in teardown to prevent failures when the connection user's default schema differs from `dbo` (MSSQL) or `public` (PostgreSQL) + +- 66e6fb8: ### Fixed + + - `fix(teardown):` Sort composite types (TVPs) before domain types during teardown to prevent dependency failures on MSSQL, which lacks `DROP TYPE ... CASCADE` + +- ba22246: ## MSSQL connects by IP address + + ### Fixed + + - `fix(connection):` connecting to MSSQL by IP address works again. Tedious derived the TLS SNI ServerName from the host, and Node rejects an IP literal there (RFC 6066), so every IP connection failed with `Setting the TLS ServerName to an IP address is not permitted`. Hostname connections are unchanged. Encryption stays on — the connection is never silently downgraded to plaintext to work around it. + - `feat(connection):` new optional `connection.tlsServerName` — the hostname the server's TLS certificate is issued for. Required when connecting to an IP address with certificate validation enabled (`ssl` set), since a certificate cannot be validated against an IP. Without it, that combination now fails with an error naming the field instead of an opaque TLS error. + +- a22973c: ## Fixed + + - `fix(change):` Use `settings.paths` from `settings.yml` for change directory resolution instead of per-config `activeConfig.paths`, matching the pattern used by runner screens + +- ac0b3c1: Rebuild with complete bundling fixes +- 3ef0007: fix(cli): noorm db reset rebuilds cleanly regardless of preserveTables + + `noorm db reset` (teardown + build) honored `settings.teardown.preserveTables` + during the teardown phase, so preserved tables (e.g. reference vocabulary kept + for the `noorm db truncate` workflow) survived and then collided with the + build's `CREATE TABLE`, aborting the rebuild and leaving a partial schema. + `noorm db reset` now performs a full teardown that ignores `preserveTables` — + a full rebuild starts from nothing. `noorm db teardown` and `noorm db truncate` + still honor the setting. + +- 01208ec: ## Fixed + - `fix(cli):` Surface SQL errors and skip reasons in `run` and `change` output + - `fix(cli):` `noorm run build` / `dir` / `files` / `exec` now print each failed file's driver error inline and route the summary through `logger.error` on non-success + - `fix(cli):` `noorm run file` and the skip path now report `(skipped: )` so callers know whether the file was unchanged or already-run + - `fix(cli):` `noorm change ff` / `run` / `revert` / `rewind` print per-change error detail on failure instead of just `(failed)` +- 8797eb4: ### Fixed + + - `fix(settings):` NOORM\_\* environment variables now override any settings.yml field (e.g. `NOORM_PATHS_SQL` overrides `paths.sql`) + +- 3ef0007: fix(cli): noorm db teardown drops MSSQL CHECK constraints before functions + + `noorm db teardown` (and `noorm db reset`) aborted with MSSQL error 3729 on + any schema where a scalar UDF is referenced by a CHECK constraint (the + canonical base/subtype "IsType" pattern). Functions are dropped before tables + to satisfy schema-bound dependents, which left the CHECK-constraint dependency + intact. Teardown now severs it first by dropping all user-schema CHECK + constraints (excluding the `noorm` schema) ahead of the function drops, so + both schema-bound functions and CHECK-backed functions tear down cleanly. + +- 6753ebd: Fix display of created-at timestamps and file sizes in the change list and change run screens. +- fbe7f24: ## Fixed + - `fix(db):` `db transfer --no-fk` and `--no-identity` are honoured instead of being silently ignored — citty strips the `--no-` prefix and negates a flag of the remaining name, so the `noFk`/`noIdentity` args they were declared as could never receive a value +- 6782afd: ## Fixed + - `fix(tui):` change operations now receive the active config's dialect — on sqlite and mysql the TUI queried postgres-shaped tracking tables, and the failed read surfaced as a successful no-op + - `fix(tui):` the global dry-run and force toggles are now honoured by `ff`, `next`, `run`, `revert`, and `rewind` — dry-run was displayed as active while changes applied for real + - `fix(tui):` core errors are surfaced as a toast instead of being written only to the log file +- f4fec5b: ## Fixed + - `fix(run):` `run build` now names `build.include` entries that matched no files instead of reporting a plain success over zero files — include paths are relative to `paths.sql`, and the common `sql/01_tables` form silently matched nothing. `BatchResult` gains an optional `unmatchedInclude`. +- fdcbd6f: `fix(update):` the binary self-update no longer hangs indefinitely and now shows download progress. `noorm update` streamed the ~70MB release binary with a bare `fetch()` — no timeout, so a stalled connection hung forever with no error and no feedback, indistinguishable from a freeze. It now streams to disk with a live `Downloading X / Y MB (Z%)` readout, aborts with a clear error if the transfer stalls (no bytes for 30s), and stages the replacement in the target's own directory so the atomic swap can't fail with a cross-filesystem `EXDEV` (e.g. `os.tmpdir()` on a different volume than `~/.local/bin`). +- 8e3526f: `feat(update):` the binary self-update now resumes instead of restarting. When a download stalls or the connection drops, `noorm update` retries (up to 5 attempts with backoff) and resumes from the bytes already on disk via an HTTP range request — validated with `If-Range`/`ETag` so a changed asset restarts cleanly rather than stitching a stale prefix. A flaky connection now retries the tail, not the whole ~70MB. Permanent failures (e.g. `404`) still fail fast without retrying, and the CLI prints a `resuming (attempt N/M)` notice between attempts. +- ba22246: ## MySQL works + + ### Fixed + + - `fix(runner):` `run build` executes on MySQL. `createOperation` emitted a `RETURNING` clause MySQL has no support for, so the operation record was never created and **zero SQL files ran** — the command failed before touching a single file. + - `fix(change):` `recordReset` no longer fails silently on MySQL. It swallowed the same error and returned `0`, so `db teardown` recorded nothing and reported no error at all. + - `refactor(shared):` operation-record insertion is one dialect-aware helper. It existed as three independent copies, which is why fixing the runner left the change module broken and the same defect had to be found twice. + +- 7a99a11: Add `noorm version` command for diagnostic output showing CLI version, identity paths, and project status +- b676799: Add state loading error output to version command for debugging +- d279232: ## Fixed + + - `fix(change):` Resolve change paths using `projectRoot` instead of bare relative paths, matching the SDK pattern + - `fix(form):` Add Shift+Tab support for backward field navigation + - `fix(config):` Guard ConfigImportScreen `useInput` handler with `isActive` to prevent focus interference + - `fix(config):` Constrain ConfigEditScreen form height to terminal bounds with `overflowY="hidden"` + - `fix(transfer):` Update aggregate `rowsTransferred` in real-time during dt:import and db-to-db transfers + - `fix(transfer):` Prevent `dt:import:complete` from setting phase to `complete` prematurely during multi-file imports + - `fix(transfer):` Show spinner instead of misleading 0% progress bar for same-server transfers + ## 1.0.0-alpha.39 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 8ead9600..2236ed04 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@noormdev/cli", - "version": "1.0.0-alpha.39", + "version": "1.0.0", "description": "Database schema & changeset manager CLI", "type": "module", "bin": { diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index 3ef53ae1..7a328439 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -1,5 +1,632 @@ # @noormdev/sdk +## 1.0.0 + +### Major Changes + +- ba22246: Resolve the access channel from who is driving, not which binary was invoked + + `Channel` used to name the transport: `user` meant the CLI/TUI/SDK and `mcp` + meant the MCP server. Those only coincide when a human is at the keyboard. An + agent refused a write over MCP could see `noorm` on the PATH and shell out, + and because the CLI hardcoded `user` at every policy call site, that second + attempt ran with the human's role. On a stock config that turned deny into + allow for `sql:write`, `sql:ddl`, `db:create`, `run:build` and `vault:read`, + and turned `db:destroy` into a confirm that `--yes` satisfied. + + Two breaking changes: + + **The config fields are renamed.** `Channel` is now `'user' | 'agent'`, and + `ConfigAccess` is `{ user, agent }` instead of `{ user, mcp }`. `agent: false` + hides a config from agents on _both_ transports, not just over MCP. Stored + state migrates automatically (state schema v3) and carries every value over + verbatim — `mcp: 'operator'` becomes `agent: 'operator'`, `mcp: false` becomes + `agent: false`. SDK callers passing `channel: 'mcp'` to `createContext` must + pass `'agent'`, and anything reading or writing `config.access.mcp` must use + `config.access.agent`. In the TUI, the "MCP Role" field is now "Agent Role". + + **Agents shelling out to the CLI now get the agent role.** The CLI resolves + its channel from provenance via `resolveChannel()`: an allowlist of variables + the agent harnesses (Claude Code, Codex, Cursor, Gemini CLI) set for their own + child processes. A stock config gives agents `viewer`, so commands that used + to succeed inside an agent session are now refused — that is the fix, not a + regression. `TERM_PROGRAM`, `CI` and TTY state are deliberately not consulted; + they describe the terminal or the pipeline, not the caller. + + Set `NOORM_CHANNEL=user` to opt out when a human is scripting from inside an + agent session, or `NOORM_CHANNEL=agent` to opt in with no harness present. An + agent can set that variable too; this defends against one routing around a + refusal, not one deliberately evading the check. + +- 72fb64f: ## Template Inspector & Execution Control + + ### CLI + + - **Template Inspector Screen** (`run/inspect`, `[i]` shortcut): New dedicated screen for debugging SQL templates + + - Categorized context view (data files, helpers, built-ins, config, secrets, environment) + - Array shape detection to debug property access failures + - Template preview with render error display + - Refresh support for iterative debugging + + - **Rerun Confirmation**: Pre-execution file status check with dialog + + - Shows count of new, previously-run, changed, and failed files + - Confirmation prompt before re-running previously-executed files + - `[r]` retry shortcut in all run screens (respects `--force` flag) + + - **Execution Cancellation**: `[c]` to abort long-running operations + + - Destroys connection to cleanly stop execution + - Shows "Execution cancelled" error state + + - **Unified KeyHandler Pattern**: Replaced per-screen focus handlers with flexible KeyHandler component + + ### SDK + + - `checkFilesStatus()` function for pre-execution file status categorization + - New types: `FileStatusCategory`, `FileStatusResult`, `FilesStatusResult` + +- 6b3cce1: Initial release of noorm - Database Schema & Change Manager + + ## @noormdev/cli + + ### Features + + - **Terminal UI** - Full-featured TUI for managing database schemas + - **Headless Mode** - CLI commands with JSON output for CI/CD pipelines + - **Multi-Dialect Support** - PostgreSQL, MySQL, SQLite, MSSQL + - **Change Detection** - Checksum-based tracking, only changed files re-execute + - **SQL Templates** - Dynamic SQL with Eta templating engine + - **Change Management** - Versioned migrations with forward/revert support + - **Schema Explorer** - Browse tables, views, indexes, functions from terminal + - **SQL Terminal** - Built-in REPL with query history + - **Config Management** - Multiple database configs with encrypted storage + - **Secrets** - Encrypted secret storage with template injection + - **Stages** - Environment templates for teams + - **Protected Configs** - Safety guards for production databases + - **Locking** - Concurrent operation control + - **Identity** - Audit trail with git-based identity + + ## @noormdev/sdk + + ### Features + + - **Programmatic API** - Full access to noorm functionality + - **Context-based** - Single entry point via `createContext()` + - **Type-safe** - Full TypeScript support + - **Observable** - Event-based architecture with `@logosdx/observer` + - **Test Integration** - `requireTest` guard for safe test database usage + +- ba22246: ## Headless contract: one `--json` envelope, four exit codes + + ### Changed + + - `fix(cli)!:` every `--json` payload is now a JSON **object** carrying a top-level boolean `success`, and is never a bare array. `success` is `true` if and only if the process exits `0`, so `jq -e '.success'` is a failure check that works against every command. + - **Commands that returned a top-level array now return a named object.** `change list` → `.changes` (plus `.pending`), `change history` → `.history`, and every `db explore` list — `tables`, `views`, `indexes`, `fks` (→ `.foreignKeys`), `functions`, `procedures`, `types`, `triggers` — under the matching key. A script doing `jq '.[]'` on any of those must now name the key. + - **Every other payload gained `success` and lost nothing.** Existing key reads keep working. + - `config export --json` now emits the envelope (`{success, name, config}`). The bare artifact — the shape `config import` reads — is still what `noorm config export dev > dev.json` produces, unchanged. + - `noorm update --json` no longer prints two JSON documents for one error, and no longer reports `success: true` on a failed install while exiting non-zero. + - `fix(cli)!:` exit codes now distinguish four outcomes: `0` success, `1` total failure, `2` usage error (bad or missing flag, a TTY-only command run non-interactively, or a named target that does not exist), `3` partial failure. + - **`2` no longer means "partial".** It previously meant partial failure on `run build` / `run dir` / `run files` / `run exec` and the five `change` execution commands — but the same `2` was also returned for a _total_ failure on those commands, so the two were indistinguishable. Partial now has its own code. **A pipeline testing `[ $? -eq 2 ]` for "partially applied" must now test `-eq 3`.** Checks of the form `if ! noorm …` are unaffected. + - `ci secrets` partial loads move from `2` to `3`. `db transfer`'s total-failure exit moves from `2` to `1` (its partial exit stays `3`). `vault propagate` and `vault rm`, `dev test-workers` and `dev test-helpers` now report partial and not-found instead of collapsing everything to `1`. + - A "you named something that isn't there" failure — missing file, directory, config, change, secret key, table, or a glob matching nothing — moves from `1` to `2` across the CLI. Confirmation and `--force` refusals deliberately stay at `1`. + + ### Fixed + + - `noorm run inspect ` reported a fully-populated context and exit `0` for a file that was never on disk — it only ever read the template's _directory_. It now fails with exit `2`. `run preview` gained the same up-front check so both surfaces agree. + - `noorm run dir` reported success over a directory that contained no SQL files, and reported a missing directory as a SQL failure. Both now exit `2` with a message naming the path. + - `noorm run exec` died with `Bun is not defined` under Node — the documented dev entry point — before doing any work. Glob expansion falls back to `node:fs/promises` when the `Bun` global is absent; the compiled binary keeps Bun's own matcher. + - `noorm run preview --json` put a full stack trace, including absolute filesystem paths, into the machine-readable `error` field. `--json` now carries the message only; the stack still reaches an operator on stderr. + - `noorm lock acquire` gained `--timeout` and `--reason`, which the TUI has always collected — CI-acquired locks were permanently default-timeout and reasonless, so whoever they blocked had no way to see what they were waiting on. + - `withVaultContext` never passed `yes` to the SDK context, so `--yes` / `NOORM_YES` was inert for every vault command. + - `db transfer` declared a `--force` flag that nothing read. + - `run exec`, `db reset`, `vault cp`, and `dev test-helpers` wrote some errors straight to stderr, leaving `--json` callers with an empty stdout and no envelope to parse. + +- 6753ebd: **BREAKING:** Reorganize flat `NoormOps` into domain-aligned sub-namespaces + + The flat `ctx.noorm.*` API has been replaced with sub-namespaces that mirror the TUI home screen: + + - `ctx.noorm.changes.*` — scaffold, discover, validate, apply, revert, ff, status, pending, history + - `ctx.noorm.run.*` — discover, preview, file, files, dir, build + - `ctx.noorm.db.*` — listTables, describeTable, overview, previewTeardown, truncate, teardown, reset + - `ctx.noorm.lock.*` — acquire, release, status, withLock, forceRelease + - `ctx.noorm.vault.*` — init, status, set, get, getAll, list, delete, exists, propagate, copy + - `ctx.noorm.secrets.*` — get + - `ctx.noorm.templates.*` — render + - `ctx.noorm.transfer.*` — to, plan + - `ctx.noorm.dt.*` — exportTable, importFile + - `ctx.noorm.utils.*` — checksum, testConnection + + **Migration examples:** + + | Before | After | + | ----------------------------- | ------------------------------- | + | `ctx.noorm.build()` | `ctx.noorm.run.build()` | + | `ctx.noorm.fastForward()` | `ctx.noorm.changes.ff()` | + | `ctx.noorm.applyChange(name)` | `ctx.noorm.changes.apply(name)` | + | `ctx.noorm.listTables()` | `ctx.noorm.db.listTables()` | + | `ctx.noorm.acquireLock()` | `ctx.noorm.lock.acquire()` | + | `ctx.noorm.truncate()` | `ctx.noorm.db.truncate()` | + | `ctx.noorm.runFile(f)` | `ctx.noorm.run.file(f)` | + | `ctx.noorm.transferTo(c)` | `ctx.noorm.transfer.to(c)` | + + **New capabilities:** change authoring/scaffolding, dry-run previews, file discovery, vault operations, teardown preview + +- 8e47888: ### Cross-Database Data Transfer + + Add a complete data transfer system for copying data between databases, including cross-dialect transfers (PostgreSQL ↔ MySQL ↔ MSSQL). + + **Planning & Execution** + + - Schema introspection via dialect-specific system catalogs to discover tables, columns, primary keys, identity columns, and row estimates + - Foreign key dependency graph with topological sort ensures parent tables transfer before children; graceful fallback on circular dependencies + - Same-server detection with localhost normalization (`127.0.0.1`, `::1`, `localhost.localdomain`) enables direct `INSERT INTO ... SELECT` optimization, bypassing application-level marshalling + - Cross-server path uses configurable batch streaming (default 1000 rows) with per-batch progress events + - Automatic exclusion of internal `__noorm_*` tables + + **Conflict Resolution** + + Four strategies for handling primary key conflicts: + + - `fail` (default) — abort on first conflict + - `skip` — skip conflicting rows (`INSERT IGNORE`, `ON CONFLICT DO NOTHING`, `MERGE ... WHEN MATCHED THEN skip`) + - `update` — upsert existing rows (`ON DUPLICATE KEY UPDATE`, `ON CONFLICT DO UPDATE`, `MERGE ... THEN UPDATE`) + - `replace` — delete and re-insert (`REPLACE INTO`, delete+insert for PG/MSSQL) + + **Dialect-Specific Handling** + + - **PostgreSQL**: `session_replication_role = replica` for FK bypass, `OVERRIDING SYSTEM VALUE` for identity insert, `setval(pg_get_serial_sequence(...))` for sequence reset, `TRUNCATE ... CASCADE` + - **MySQL**: `SET FOREIGN_KEY_CHECKS = 0`, backtick quoting, cross-database `db.table` notation, `ALTER TABLE ... AUTO_INCREMENT` reset + - **MSSQL**: Per-table `NOCHECK CONSTRAINT ALL`, `SET IDENTITY_INSERT ON/OFF`, `DBCC CHECKIDENT` for reseed, `MERGE` statements for all conflict strategies, `[bracket]` quoting, four-part `[db].[schema].[table]` naming + + **Options**: `--tables`, `--on-conflict`, `--batch-size`, `--truncate`, `--no-fk`, `--no-identity`, `--dry-run` + + **UI** + + - 7-phase TUI wizard: destination selection → table picker (multi-select with "all" toggle) → options → plan preview with dependency visualization → confirm → live progress bars → completion summary + - Headless mode with `--to ` and full JSON output support + - Observer events for real-time progress: `transfer:planning`, `transfer:plan:ready`, `transfer:table:progress`, `transfer:complete` + + *** + + ### Portable .dt Data Format + + Add a portable data format (`.dt`) for file-based export/import and cross-dialect database transfers. + + **Format** + + - JSON5-based line format: schema header line followed by data rows + - Three extensions: `.dt` (plain), `.dtz` (gzip compressed), `.dtzx` (encrypted + compressed) + - Schema header captures source dialect, version, table name, and column types + - Universal type system maps dialect-specific types to portable intermediates + + **Type System** + + - Simple types: `string`, `int`, `bigint`, `float`, `decimal`, `bool`, `timestamp`, `date`, `uuid` + - Encoded types: `json`, `binary`, `vector`, `array`, `custom` + - Version-aware mappings: MySQL VECTOR (9.0+), MSSQL JSON/VECTOR (2025+), PostgreSQL array notation + - Smart encoding: raw for small values, base64 for binary, gzip+base64 when compression saves ≥15% + + **Cross-Dialect Transfers** + + - In-memory `DtStreamer` converts source rows → universal intermediates → target rows + - No file I/O or serialization overhead — pure type conversion + - Soft-limit batching: flush at row count OR 1GB memory threshold to prevent OOM on large BLOBs + - Version detection via `queryDatabaseVersion()` enables version-aware type mapping + + **File Export/Import** + + - `DtWriter`: streaming writer with extension-based format selection + - `DtReader`: streaming reader with async row iteration + - Passphrase-based encryption: PBKDF2 key derivation + AES-256-GCM + - Schema validation: target table structure checked before import begins + + **CLI Integration** + + - `--export ` with `--tables`: single table → file path, multiple tables → directory with `
.dt` per table + - `--import `: load `.dt`/`.dtz`/`.dtzx` into active config + - `--compress`: produce `.dtz` output + - `--passphrase`: produce/consume encrypted `.dtzx` + - Mutually exclusive with `--to` (file export vs db-to-db transfer) + + **TUI Integration** + + - Export/import options in destination selection phase + - Export flow: destination → tables → export options (path, compress, encrypt) → confirm → progress → complete + - Import flow: file path → passphrase (if `.dtzx`) → schema preview with validation → options → confirm → progress → complete + + **Template Loader** + + - `.dt` and `.dtz` files usable as seed data in templates + - Registered in template loader system alongside SQL and JSON loaders + - No `.dtzx` support in templates (no way to provide passphrase) + + **Observer Events**: `dt:export:start`, `dt:export:progress`, `dt:export:complete`, `dt:import:start`, `dt:import:schema`, `dt:import:progress`, `dt:import:complete`, `dt:stream:start`, `dt:stream:progress`, `dt:stream:complete`, `dt:validate:result` + + *** + + ### Encrypted Secrets Vault + + Add a team-shared encrypted secrets vault backed by the database with dual-layer encryption. + + **Encryption Architecture** + + - Dual-layer model: vault key encrypted per-user via ephemeral X25519 ECDH + HKDF-SHA256 + AES-256-GCM, secret values encrypted with the shared vault key via AES-256-GCM + - Each user receives their own encrypted copy of the vault key using their identity public key + - Access determined by presence of `encrypted_vault_key` on identity row — no separate permissions table + + **Secret Resolution Hierarchy** + + Three-tier priority system for secret lookups: + + 1. Config-specific local secrets (highest, from `.noorm/state/state.enc`) + 2. Global local secrets (shared across configs) + 3. Vault secrets (lowest, database-backed) + + Secrets available in template context as `<%= secrets.KEY_NAME %>` via `buildSecretsContext()`. + + **Operations** + + - `vault init` — generate vault key, encrypt for initializer's identity + - `vault set ` — encrypt and upsert secret with `setBy` audit trail + - `vault rm ` — remove secret by key + - `vault list` — list all secrets with metadata (who set them, timestamps) + - `vault cp` — copy secrets between configs with `--all`, `--force`, `--dry-run`; auto-initializes destination vault if needed + - `vault propagate` — grant vault access to all pending team members by encrypting the vault key for their public keys + + **UI** + + - TUI screen with secret list, defined-but-unset indicators (from settings), pending user count, and keyboard shortcuts for all operations (`[a]`dd, `[d]`elete, `[p]`ropagate, `[i]`nit) + - Headless commands with JSON output for all operations + - Observer events: `vault:initialized`, `vault:secret:created`, `vault:secret:updated`, `vault:secret:deleted`, `vault:propagated`, `vault:copy:completed` + + **Database Schema**: New `__noorm_vault__` table (`secret_key`, `encrypted_value`, `set_by`, timestamps) and `encrypted_vault_key` column on `__noorm_identities__` + + *** + + ### SDK: Move noorm operations to `ctx.noorm` namespace + + **Breaking:** Refactor the SDK Context to separate SQL-focused operations from noorm management operations. All schema, change, lock, runner, explore, transfer, and DT methods move from the top-level `ctx` to a new `ctx.noorm` namespace via a lazy-initialized `NoormOps` class. + + Top-level Context retains only SQL-focused API: `kysely`, `dialect`, `connected`, `connect()`, `disconnect()`, `transaction()`, `proc()`, `func()`. + + **Before:** + + ```ts + await ctx.build(); + await ctx.fastForward(); + const tables = await ctx.listTables(); + await ctx.acquireLock(); + ``` + + **After:** + + ```ts + await ctx.noorm.build(); + await ctx.noorm.fastForward(); + const tables = await ctx.noorm.listTables(); + await ctx.noorm.acquireLock(); + ``` + + Properties `config`, `settings`, `identity`, `observer` also move to `ctx.noorm`. + + **Migration:** Add `.noorm` between `ctx` and any management method call. SQL operations (`kysely`, `transaction`, `proc`, `func`) and lifecycle (`connect`, `disconnect`) are unchanged. + + *** + + ### Run + + - Fix form retry on failure: reset internal `submitting` state after `onSubmit()` completes so the form is no longer permanently locked after a failed submission; parent `busy` prop still guards against double-submit during async operations + - Apply settings exclude rules to build: pass pre-filtered file list from `RunBuildScreen` to `runBuild()` instead of letting the runner re-discover all files from disk, which ignored `include`/`exclude` rules + +- ba22246: ## Access default: agents no longer get admin + + ### Changed + + - `fix(policy)!:` a config with no explicit `access` now resolves to `{ user: 'admin', mcp: 'viewer' }` instead of `{ user: 'admin', mcp: 'admin' }`. A stock project writes no `access`, so every config was handing full admin to any connected MCP client — writes, DDL, `change_run`, `run_build`, database drops, and vault/secret reads all went through unchecked. + - **The `user` channel is unchanged.** Nothing about the CLI, TUI, or SDK behaves differently. + - **MCP agents keep read access.** `explore` and read-only SQL (`SELECT`, `EXPLAIN`, `SHOW`, `DESCRIBE`) still work with no setup. + - **MCP agents lose write access by default.** If an agent needs to write, run changes, or run builds, set that config's `access.mcp` to `operator` or `admin` explicitly — via `noorm ui` → Config → Edit, or in the JSON before `config import`. + - **Configs that already store an explicit `access` are untouched**, including ones holding `mcp: 'admin'`. If you created configs on an earlier build, that migration already wrote `admin/admin` to disk and it is preserved — run `noorm config list` and lower any `mcp:admin` you did not intend. + - `noorm config list` now prints the access tag for any config that differs from the default, so an `mcp: admin` escalation is visible. Previously the tag was hidden whenever the user channel was `admin`, which concealed exactly that case. + +- 0951b7e: ## Breaking Changes + + ### `allowProtected` option removed + + The `allowProtected` option has been removed from `CreateContextOptions`. Passing it no longer has any effect — protected configs unconditionally block all destructive operations with no override. + + **Before:** + + ```typescript + // This no longer works — allowProtected is not a valid option + const ctx = await createContext({ config: "staging", allowProtected: true }); + await ctx.noorm.db.truncate(); // would proceed + ``` + + **After:** + + ```typescript + // Protected configs always block destructive ops — no override possible + const ctx = await createContext({ config: "staging" }); + await ctx.noorm.db.truncate(); // throws ProtectedConfigError + ``` + + To run a destructive operation against a protected config, set `config.protected = false` manually before running the operation, then restore it. + + ### `checkProtectedConfig` signature changed + + The exported `checkProtectedConfig` guard function signature changed from `(config, operation, options)` to `(config, operation)`. If you call this function directly, remove the third argument. + + ## New Behavior + + The following operations are now blocked on protected configs (in addition to `truncate`, `teardown`, and `reset`): + + - `ctx.noorm.dt.importFile()` — bulk data import is destructive + - `ctx.noorm.changes.revert()` — schema rollbacks are destructive in production + - `ctx.noorm.changes.rewind()` — batch schema rollbacks are destructive in production + +- ba22246: ## Authorization is now enforced at the core seam + + Policy was re-implemented per surface, and several cells were simply empty — the CLI would perform operations the TUI correctly refused, and two whole domains had no permission to check against. + + ### Fixed + + - `fix(policy)!:` the SQL classifier no longer routes writes as reads. `EXPLAIN (ANALYZE) DELETE FROM t` classified as `read` and deleted rows under a `viewer` role — through the CLI and over a live MCP session. Two independent causes are fixed: the keyword fallback treated `EXPLAIN` as terminally read-only, and on the parser path `EXPLAIN` inherited nothing from the statement it wraps while the write-signal scan had no DDL node types. An `EXPLAIN` now inherits its wrapped statement's classification. + - `fix(policy)!:` statement splitting is dialect-aware. It tracked only `'`, so an MSSQL bracket-quoted identifier (`SELECT 1 AS [a'b]; DROP TABLE x`) split wrong and the tail ran unclassified. `"`, backticks, `[…]`, `$tag$…$tag$` and all three comment forms are now modelled. + - `fix(policy)!:` a statement with no leading word character used to classify as `read`, so a leading NUL byte carried `DROP TABLE` past the gate. It now fails closed. + - `feat(policy)!:` added the permissions that did not exist — `vault:*`, `secret:*`, `config:write`, `db:truncate`, `db:teardown`, `transfer:plan`, `lock:force`, `debug:*`. Operations in those domains had nothing to gate against and shipped ungated. + - `fix(vault)!:` vault and secret operations are gated at the core seam, so SDK, CLI and TUI inherit one check. A `viewer` config that was denied `run build` could still run `vault set`, `vault rm`, `vault propagate` and `secret set`. + - `fix(db)!:` `db create` is gated. The matrix denied `db:create` to `viewer` and the TUI enforced it; the CLI created the database anyway. + - `fix(db)!:` `db truncate` and `db teardown` require confirmation. `--yes`/`--force` were declared arguments that neither command read, so `noorm db teardown < /dev/null` dropped 63 objects and exited 0 — against documentation promising the CLI refuses. + - `fix(debug)!:` the debug screens' delete paths are gated. They removed rows from `noorm.vault` and `noorm.identities` with no authorization check at all. + - `fix(lock)!:` `lock force` is gated and requires `--yes`, names the holder it evicts, and returns a distinct exit code when there was nothing to release. + - `fix(config)!:` `config import --force` is gated on `config:write`. It rewrote a config's `access` with no check, escalating a `viewer` config to full delete rights in one command. + - `fix(transfer):` `getTransferPlan` is gated, matching the invariant its own comment claimed. `--dry-run` leaked destination table names, row estimates and the FK graph to a denied caller. + +- ba22246: ## Identity and state key handling fail closed + + ### Fixed + + - `fix(identity)!:` malformed key material is rejected instead of deriving a publicly computable key. `Buffer.from(key, 'hex')` never throws, so any non-hex private key collapsed to a zero-length HKDF input and produced a **constant** AES key — an attacker holding no key material could decrypt a real `state.enc`. `isValidKeyHex` existed but was only called on the CI path; it now guards every entry point. + - `fix(ci)!:` `ci identity enroll` verifies the stored public key matches the one presented. It checked only that a row existed, so anyone able to INSERT into `noorm.identities` — no vault access required — could pre-plant their own key and receive the vault key, while the operator's enrollment reported success and echoed back the expected key. + - `fix(identity)!:` `identity init --force` requires `--yes`, backs up the keypair, and names what it destroys. It silently regenerated the keypair and permanently destroyed every project's `state.enc`. + - `fix(ci):` `ci init --force` backs up `state.enc` and refuses on a TTY without confirmation; it destroyed all configs and secrets unprompted. It also now routes through `parseConfig`, so a numeric-looking database name persists as a string. + - `fix(vault)!:` `vault propagate` names every recipient and withholds until confirmed, and reports partial failures instead of returning success with the failed identity silently omitted. + - `fix(sdk):` an explicit identity scope whose revert fails is no longer marked reverted, and `disconnect()` no longer hangs when scopes are still held. + +- ba22246: ## Operations no longer report success over lost, duplicated or unwritten data + + ### Fixed + + - `fix(transfer)!:` export and db-to-db transfer paginate by primary key instead of bare `LIMIT/OFFSET`. With no `ORDER BY`, a source being written to during a 50,000-row export produced 14,149 missing and 13,387 duplicated rows while reporting `rowsExported: 50000` and exiting 0. Tables with no primary key now take a single-statement snapshot rather than corrupting silently. + - `fix(dt)!:` a malformed row or a dead worker no longer hangs the pipeline forever. The error branch never decremented the in-flight count, `request()` never settled on worker death, and `worker:exit` had no subscribers — a four-byte bad payload wedged `db transfer --import` indefinitely. Three busy-wait loops are replaced with real settlement. + - `fix(transfer)!:` the postgres same-server path no longer copies the destination into itself. It ignored the source database entirely and could only ever emit `INSERT INTO t SELECT ... FROM t`. + - `fix(dt):` decompression is bounded on import. An unbounded `gunzip` inflated 398 KB to 400 MB (1029:1), and because it runs in a compute worker the resulting OOM triggered the hang above. + - `fix(transfer):` reported counts reflect reality — `onConflict: 'skip'` no longer counts no-ops as inserted, and the same-server path reports affected rows instead of a postgres `reltuples` estimate. + - `fix(transfer):` `db transfer --export` with no `--tables` exports every table, as its own flag description promised. It exported nothing, printed `success: true` and exited 0. + - `fix(transfer):` cross-dialect transfer works. The planner probed the destination with the _source_ dialect, so every cross-dialect transfer aborted and roughly 400 lines of streaming conversion were unreachable — while the docs claimed the feature worked. + - `fix(teardown)!:` `truncate` qualifies statements with the table's schema. It discarded the schema, emptied 12 tables and then aborted — partial irreversible loss, reported as a failure. + - `fix(state)!:` `state.enc` writes are atomic and reconciled. Ten concurrent `noorm secret set` runs left five secrets, all exiting 0. Writes now stage-and-rename with an exclusive lock, three-way reconcile against a changed file, and keep a `.bak` generation. + - `fix(state):` reads stopped rewriting state. `needsMigration` tested a field the migration never writes, so it was always true and every command — including `config list` — re-encrypted and rewrote `state.enc`, turning reads into writes and amplifying the loss above. + - `fix(runner):` `.sql.tmpl` dedup works. The stored checksum was the raw file hash while the comparison used the rendered hash, so templates re-executed on every build and failed on non-idempotent DDL. + - `fix(runner):` SQLite executes every statement in a multi-statement file instead of silently running the first and reporting success. + +### Minor Changes + +- ba22246: ## Added + + - `feat(identity):` Record the detected agent harness in operation provenance. When a session runs under a recognised agent harness, `executed_by` is suffixed with `(via )` — so a change applied by an agent is distinguishable from one a human applied. Stamped at the shared insert seam, so change operations, resets and run operations all carry it on every dialect. + - `feat(cli):` `noorm info` reports the detected harness and the environment variables that identified it, so an agent-driven session is visible rather than silent. + + Provenance is folded into the existing identity string rather than a new column: the audit question is binary, and a suffix answers it without a four-dialect schema migration. It is not an attestation — `executed_by` is unauthenticated free text and harness detection reads caller-controlled environment variables, so the suffix records what noorm observed, not a proven claim. + +- 8749127: ### Added + + - `feat(sdk):` Add `ctx.tvf()` method for calling table-valued functions on MSSQL and PostgreSQL + - `feat(sdk):` Add `Tvfs` generic parameter to `createContext()`, `Context`, and `ImpersonatedScope` for type-safe TVF signatures + + ### Changed + + - `refactor(sdk):` Replace `as any` casts in impersonation scope with proper generic flow through `buildProcCall`, `buildFuncCall`, and `buildTvfCall` + +- 06d799b: ## DT Format + + ### Added + + - `feat(dt):` Add `text` encoded type for large TEXT columns with smart gz64 compression + - `feat(dt):` Map MSSQL `nvarchar(max)` and `varchar(max)` to `text` encoded type + - `feat(dt):` Detect `(max)` suffix in MSSQL schema introspection via `max_length = -1` + + ### Changed + + - `refactor(dt):` MySQL `text`, `mediumtext`, `longtext` now map to `text` (was `string`); `tinytext` stays `string` + - `refactor(dt):` MSSQL `text`, `ntext` now map to `text` (was `string`) + +- 850b2d3: ## Added + + - `feat(change):` Expose `change next` on the SDK for programmatic access to the next pending change + +- a4d7308: ## Added + - `feat(tvp):` Make `TvpValue` and `tvp()` generic — `TvpValue` preserves row types through proc, func, and tvf signatures so the compiler catches column mismatches at call sites +- 1dc22b3: ## Added + + - `feat(sdk):` Per-request user impersonation via `ctx.impersonate()` — borrow a dedicated pool connection, switch database identity, and run queries as a specific principal with guaranteed revert + - `feat(sdk):` Callback mode (auto-reverts on completion or throw) and explicit mode (caller-managed lifecycle for cross-boundary use cases like Hapi request hooks) + - `feat(sdk):` MSSQL (`EXECUTE AS USER` / `REVERT`) and PostgreSQL (`SET ROLE` / `RESET ROLE`) dialect support with SQL injection prevention via username validation and dialect-specific quoting + +- 01208ec: ## Added + + - `feat(runner):` Split MSSQL SQL files on the `GO` batch separator. Multi-statement DDL files (multiple `CREATE PROCEDURE` / `CREATE FUNCTION` / `CREATE TRIGGER` / `CREATE VIEW` / `CREATE TYPE` in one file) now run correctly instead of failing with `Incorrect syntax near 'GO'`. + - `feat(runner):` Batch failures report the failed batch index in `FileResult.error` (e.g. `[batch 3 of 5] `) and short-circuit the remaining batches. + + ## Known limitations + + - `GO` inside string literals or block comments is still treated as a separator — matches `sqlcmd` behavior. Document accordingly when authoring T-SQL files. + - `GO ` repetition is not implemented. + +- 66e6fb8: ### Added + + - `feat(sdk):` Add TVP (table-valued parameter) support for MSSQL via `tvp()` helper — pass structured tabular data to `ctx.proc()`, `ctx.func()`, and `ctx.tvf()` calls + - `feat(sdk):` Validate TVP row key consistency and enforce MSSQL's 2,100 parameter limit with clear error messages + +- df24197: ## Added + - `feat(sdk):` Support `[Args, ReturnType]` tuple definitions for procs, functions, and TVFs — return types are inferred automatically, with explicit override still available +- ba22246: ## Schema exploration answers about the object you asked for + + ### Fixed + + - `fix(explore):` procedure detail returns parameters. It filtered `information_schema.parameters` on a bare name where the column holds `name_oid`, so the list view reported a parameter count and the detail view always showed none — on every surface. + - `fix(explore):` SQLite quotes identifiers at all raw-SQL sites. A single table named with an embedded quote broke listing, overview and detail for _unrelated_ tables. + - `fix(explore):` MySQL table detail reads indexes and foreign keys from the requested schema rather than the connected database, which produced self-contradictory output with the real index missing. + - `fix(explore):` `--schema` is honoured on the list commands, which declared it and ignored it. + - `fix(explore):` the overview counts from the same listings the detail views use, inside one guarded call that surfaces errors, instead of a second implementation that disagreed and hardcoded several counters to zero. + +- 4f4967c: ## API/Services + + ### Fixed + + - `fix(sdk):` `db.truncate()` and `db.teardown()` now respect `settings.teardown.preserveTables` and `postScript` from settings.yml + - `fix(sdk):` `db.truncate()` accepts optional `TruncateOptions` — user-provided `preserve`/`only` take priority over settings fallback + +- 4751921: ## Access Roles + + Replace the per-config `protected: boolean` flag with per-channel access roles. + + **Breaking:** `Config.protected` is removed from the exported types and `access: ConfigAccess` is now required on `Config`/`ConfigSummary`. TypeScript consumers that read `.protected` or construct `Config` literals must move to `access`. Stored state auto-migrates on load (see below), so runtime configs are unaffected. (Released as `minor` under the `1.0.0-alpha` pre-release line, where `^` ranges pin within the alpha series; it is a breaking type change and will be treated as such at the 1.0 boundary.) + + - `feat(policy):` Configs now carry `access: { user, mcp }`, each `'viewer' | 'operator' | 'admin'` (or `mcp: false` to hide the config entirely). Roles are enforced by one policy matrix instead of scattered `protected` checks — `viewer` reads only, `operator` writes and confirms destructive operations, `admin` is frictionless. Enforcement runs at the core seam, so the SDK, TUI, and CLI all inherit it: `run`, `changes`, `transfer`, and the SQL terminal are gated, not just MCP. + - `feat(mcp):` Every MCP command is now gated on `access.mcp` before it runs, closing the gap where `change_run`/`change_ff`/`change_revert`/`run_file`/`run_build` reached the database unchecked. `access.mcp: false` makes a config invisible to agents — absent from `list_configs`, and `connect` fails with the same error an unknown config produces. `confirm`-tier permissions collapse to a hard deny on the MCP channel (no human to type a confirmation phrase); give a config `mcp: 'admin'` if an agent legitimately needs to run changes there. + - `feat(sql):` Raw SQL (`noorm sql`, MCP `sql`, the TUI SQL terminal) is now gated by what the statement actually does — classified `read`/`write`/`ddl` — instead of a blanket read-only check. Data-modifying CTEs (`WITH … AS (DELETE …) SELECT …`) and a denylist of side-effecting functions (`pg_terminate_backend`, `dblink_exec`, `query_to_xml`, … — bare or schema-qualified) classify as writes so a `viewer` cannot mutate through the read path. Multi-statement input takes the highest class present; unparseable input or `EXEC`/`CALL` classify as `ddl` (fail closed). The classifier guards against mutation, not disclosure — back hard confidentiality with database `GRANT`s. + - `feat(sdk):` `createContext({ channel })` — defaults to `'user'`; set `'mcp'` when embedding the SDK behind an MCP-like surface. New exported types `Channel`, `ConfigAccess`, `Role`. `ProtectedConfigError` is now raised from `checkPolicy` denials/unconfirmable actions rather than a bare `protected` check; match on `err.name`/`instanceof`, not the message string (the message text changed). + - **Behavior change:** on the `user` channel, `NOORM_YES=1` resolves an operator `confirm` to allow — including in the SDK guards. A migrated `protected: true` config becomes `operator`, so an SDK/CI caller with `NOORM_YES=1` set now _executes_ `truncate`/`teardown`/`reset`/`dt.import`/`changes.*`/`run.*` where the previous `protected` hard-block threw unconditionally. Unset `NOORM_YES` (or use `viewer`/`mcp:false`) where the block must hold. + - `fix(config):` A legacy `protected: true` migrates automatically to `{ user: 'operator', mcp: 'viewer' }`, and `protected: false`/absent to `{ user: 'admin', mcp: 'admin' }`. The `protected` field is still accepted on `config import`/settings input for this release, then removed. Note: downgrading to a prior binary after this migration silently unprotects configs (older binaries have no access concept) — avoid rolling back once state is migrated. + +- a18bfbe: ### Added + + - `feat(worker-bridge):` Worker thread infrastructure for parallel DT export/import. WorkerBridge class (ObserverRelay subclass), WorkerPool with round-robin dispatch, OrderBuffer for index-ordered reassembly. + - `feat(workers):` Persistent Connection Worker (Kysely-backed DB operations) and stateless Compute Worker (serialize/deserialize) as standalone entry points. + - `feat(dt):` Export and import pipelines now run through worker threads — Connection Worker handles DB queries, Compute Pool parallelizes CPU-bound serialization across N cores. + - `feat(dt):` Three-tier progress events (`loaded`/`processed`/`saved`) for both export and import, enabling granular TUI progress display. + - `feat(connection):` ConnectionManager tracks WorkerBridge instances alongside direct Kysely connections for coordinated shutdown. + - `feat(cli):` `noorm dev/test-workers` diagnostic command for verifying worker thread infrastructure across execution contexts. + - `build:` Worker entry points included in `bun build --compile` for single binary support. + +### Patch Changes + +- dd7e387: ## Fixed + - `fix(runner):` a third consecutive `run build` no longer re-executes a file the previous build correctly skipped — `skipped` was treated as "never ran" regardless of why, so an `unchanged` skip forced a re-run and failed on any DDL that is not idempotent +- 8797eb4: ### Fixed + + - `fix(settings):` NOORM\_\* environment variables now override any settings.yml field (e.g. `NOORM_PATHS_SQL` overrides `paths.sql`) + +- 01208ec: ## Changed + - `feat(sdk):` `changes.ff(options?)` and `changes.next(count, options?)` now accept `BatchChangeOptions` so callers can pass `dryRun` / `force`. Previously the options were silently dropped before reaching the manager. +- ba22246: Fix change apply/revert recovery, rewind flag handling, and MySQL support + + - Changes now run on MySQL at all. `ChangeHistory` retrieved insert ids with a `RETURNING` clause MySQL does not support, so no operation record was ever created and `change run`, `change ff`, `change revert` and `db teardown` were all inoperable. + - A reverted or torn-down change can be applied again. Every file was previously skipped against a prior success, so `apply -> revert -> apply` and `db teardown -> change ff` reported success over an untouched database. + - `ff` and `next` now treat `stale` changes as pending work, so teardown has a supported recovery path. + - `change rewind` honours `--dry-run` and `--force`, and accepts the documented count form (`change rewind 3`). `changes.rewind()` takes an options argument. + - A revert whose history cannot be read fails instead of reporting success over zero files. + - `.sql.tmpl` files inside a change now receive `$.config` and `$.secrets`. + - `.txt` manifests execute in the order they list files, instead of being sorted. + - `ff`/`next` warn when the changes directory is missing rather than reporting a clean run. + - `change list` marks orphaned changes and no longer lists the internal `__reset__` teardown marker. + +- 18e36b6: ## Fixed + - `fix(mssql):` Resolve SQL Server failures in runner and change tracking operations + - `fix(mssql):` Add `OUTPUT inserted.id` support for MSSQL insert operations + - `fix(mssql):` Translate `.limit()` to `TOP` via MssqlLimitPlugin + - `fix(schema):` Make v2 migration fully idempotent with partial-state recovery + - `fix(runner):` Handle `AggregateError` and non-standard error objects from tedious driver +- 3ef0007: fix(mssql): construct tarn/tedious through CJS-interop guard in bundles + + When the SDK is bundled (tsup), `await import('tarn')` / `await import('tedious')` + expose their exports under `.default`, so spreading the namespace left + `tarn.Pool` undefined and kysely threw `Pool is not a constructor` on every + MSSQL connection. Normalize both with `module.default ?? module`, mirroring the + postgres dialect's existing guard. + +- 8b20702: ### Fixed + + - `fix(sdk):` Bundle all runtime dependencies — resolves `Cannot find package 'json5'` and similar errors when importing the SDK + - `fix(sdk):` Add `createRequire` banner for CJS packages that use `require('process')` in ESM bundles + - `fix(template):` Resolve `$helpers` loading in compiled binaries via `Bun.build()` bundling + + ### Changed + + - `perf(sdk):` Lazy-load template data parsers (JSON5, YAML, CSV) — heavy parser libraries are now deferred until first use, reducing SDK startup time + - `perf(sdk):` Replace `voca` dependency with inline `camelCase` implementation (~1500 lines removed from bundle) + - `perf(sdk):` Stub `ansis` terminal color library — SDK consumers don't need ANSI output + +- 715cddc: ### Fixed + + - `fix(teardown):` Schema-qualify all DROP statements in `db.teardown()` to prevent failures when the connection user's default schema differs from `dbo` (MSSQL) or `public` (PostgreSQL) + +- 66e6fb8: ### Fixed + + - `fix(teardown):` Sort composite types (TVPs) before domain types during teardown to prevent dependency failures on MSSQL, which lacks `DROP TYPE ... CASCADE` + +- fff443a: ### Fixed + - `fix(sdk):` Add integration and unit tests verifying `db.truncate()` and `db.teardown()` respect `settings.teardown.preserveTables` +- ba22246: ## MSSQL connects by IP address + + ### Fixed + + - `fix(connection):` connecting to MSSQL by IP address works again. Tedious derived the TLS SNI ServerName from the host, and Node rejects an IP literal there (RFC 6066), so every IP connection failed with `Setting the TLS ServerName to an IP address is not permitted`. Hostname connections are unchanged. Encryption stays on — the connection is never silently downgraded to plaintext to work around it. + - `feat(connection):` new optional `connection.tlsServerName` — the hostname the server's TLS certificate is issued for. Required when connecting to an IP address with certificate validation enabled (`ssl` set), since a certificate cannot be validated against an IP. Without it, that combination now fails with an error naming the field instead of an opaque TLS error. + +- 01208ec: ## Fixed + - `fix(teardown):` Reorder schema teardown to drop procedures and functions before tables. MSSQL schema-bound UDFs (`WITH SCHEMABINDING`) previously blocked the table drop with `Cannot DROP TABLE because it is being referenced by object 'fn_X'`. New order: FK constraints → Procedures → Functions → Views → Tables → Types. + - `fix(teardown):` Replace `sp_MSforeachtable` with per-table sequential `ALTER TABLE [name] NOCHECK CONSTRAINT ALL` (and inverse) in MSSQL `db.truncate()`. The previous implementation spawned parallel workers that deadlocked on schema locks against non-trivial schemas. + - `fix(teardown):` `TeardownDialectOperations.disableForeignKeyChecks` / `enableForeignKeyChecks` now accept an optional `tables?: string[]` and may return `string | string[]`. PostgreSQL / MySQL / SQLite implementations ignore the argument — no behavior change for those dialects. +- 3ef0007: fix(sdk): db.reset() no longer preserves tables before rebuilding + + `db.reset()` (teardown + build) honored `settings.teardown.preserveTables`, + so any preserved table (e.g. reference vocabulary kept for the per-test + `truncate()` workflow) survived the teardown and then collided with the + build's `CREATE TABLE`, aborting the rebuild and leaving a partial schema. + reset() now performs a full teardown that ignores `preserveTables` — a full + rebuild starts from nothing. `preserveTables` still applies to standalone + `teardown()` and `truncate()`. + +- 3ef0007: fix(teardown): drop MSSQL CHECK constraints before functions + + `teardown()` aborted with MSSQL error 3729 on any schema where a scalar UDF + is referenced by a CHECK constraint (the canonical base/subtype "IsType" + pattern). Functions are dropped before tables to satisfy schema-bound + dependents, but that left the CHECK-constraint dependency intact. Teardown + now severs it first by dropping all user-schema CHECK constraints (excluding + the `noorm` schema) ahead of the function drops, so both schema-bound + functions and CHECK-backed functions tear down cleanly. + +- f4fec5b: ## Fixed + - `fix(run):` `run build` now names `build.include` entries that matched no files instead of reporting a plain success over zero files — include paths are relative to `paths.sql`, and the common `sql/01_tables` form silently matched nothing. `BatchResult` gains an optional `unmatchedInclude`. +- ba22246: ## MySQL works + + ### Fixed + + - `fix(runner):` `run build` executes on MySQL. `createOperation` emitted a `RETURNING` clause MySQL has no support for, so the operation record was never created and **zero SQL files ran** — the command failed before touching a single file. + - `fix(change):` `recordReset` no longer fails silently on MySQL. It swallowed the same error and returned `0`, so `db teardown` recorded nothing and reported no error at all. + - `refactor(shared):` operation-record insertion is one dialect-aware helper. It existed as three independent copies, which is why fixing the runner left the change module broken and the same defect had to be found twice. + +- ba22246: ## Self-update input validation, log rotation and redaction + + ### Fixed + + - `fix(update)!:` the version string from the npm registry is validated as semver before it reaches a URL or a shell. It was interpolated verbatim, and because `fetch` normalises `..`, a poisoned dist-tag could relocate **both the binary and its `checksums.txt`** to an attacker-controlled repository — so checksum verification passed against the attacker's own file. + - `fix(logger):` rotation reopens its write stream. It renamed the file out from under the open descriptor, so every subsequent write landed in the rotated file, `noorm.log` never reappeared, and rotation fired exactly once before growing unbounded. + - `fix(logger):` `settings.logging.enabled`, `.file`, `.maxSize` and `.maxFiles` are honoured. The CLI hardcoded all four, so every logging setting was inert across every command — and the log viewer read a different path than the CLI wrote. + - `fix(logger):` redaction covers this project's own variables. `NOORM_CONNECTION_PASSWORD` and `NOORM_IDENTITY_PRIVATE_KEY` were not masked, credential-bearing URIs passed through verbatim because values were never inspected, and `Error` objects were skipped wholesale. Log files are created `0600` rather than world-readable. + +- 01208ec: ## Changed + + - `fix(vault):` `vault.init()` is now idempotent. A repeat call against an already-initialized vault returns `[null, null]` instead of `[null, Error('Vault already initialized')]`. The `vault:initialized` observer event still fires only on first init. + + This is a behavior change for callers that were special-casing the `'Vault already initialized'` error string — they now see no error on repeat and must check whether the returned key is `null` (already initialized) or a `Buffer` (newly generated). + ## 1.0.0-alpha.39 ## 1.0.0-alpha.38 diff --git a/packages/sdk/package.json b/packages/sdk/package.json index a2672a78..460ee814 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@noormdev/sdk", - "version": "1.0.0-alpha.39", + "version": "1.0.0", "description": "Database schema & changeset manager SDK", "type": "module", "main": "./dist/index.js",