# hectic SQL bundle Single source of truth for every object created in the `hectic` PostgreSQL schema. Consumed by: - `package/migrator` — applies the bundle on `migrator init` (mandatory). - `package/db-tool` — applies the bundle in `database hydrate` (default; opt out with `--no-hook`). - External consumers (e.g. `proxydoe`) — invoke `psql -f` directly against the paths exposed via `self.lib.hectic.*.path`. ## Layout | File | Purpose | | --- | --- | | `HECTIC_VERSION` | Single version string for the whole bundle (e.g. `0.1.0`). Read via `lib.fileContents`. | | `hectic-version.sql` | Templated. Creates `hectic.version`, inserts the current `versionString`, raises on mismatch. | | `hectic-secret.sql` | Creates `hectic.secret`, `hectic.load_secrets_from_env(text)`, `hectic.get_secret(text)`. | | `hectic-migration.sql` | Creates the `hectic.migration` table and supporting domains/triggers used by `migrator`. | | `hectic-inheritance.sql` | Creates `hectic.created_at`, `hectic.updated_at`, `hectic.immutable` parent tables and the DDL event triggers that enforce inheritance, attach `BEFORE UPDATE` triggers, and block DML on immutable tables outside `migration_mode`. | `hectic-version.sql` is templated at Nix evaluation time: `@HECTIC_VERSION@` is substituted with the contents of `HECTIC_VERSION`. All other files are applied verbatim. ## Apply order The bundle MUST be applied in this order (enforced by `apply-hectic-bundle.sh`): 1. `hectic-version.sql` — version check first; aborts the rest on mismatch. 2. `hectic-secret.sql` 3. `hectic-migration.sql` 4. `hectic-inheritance.sql` Re-applying the bundle is idempotent — every CREATE uses `IF NOT EXISTS` / `CREATE OR REPLACE`, and the version check accepts a row that already matches. ## Nix API (`self.lib.hectic`) ```nix self.lib.hectic = { versionString; # e.g. "0.1.0" version = { sql; path; }; # templated secret = { sql; path; }; migration = { sql; path; }; inheritance = { sql; path; }; bundleFiles; # ordered bundle file paths applyBundleScript; # generated helper shell source with paths embedded }; ``` `.sql` is the file contents as a string. `.path` is the Nix store path of the materialized file to pass to `psql -f`. `version.path` is generated at Nix evaluation time from the templated SQL; the other `*.path` entries point at the verbatim source files in the store. ## Shell helper (`apply-hectic-bundle.sh`) `lib/hook/apply-hectic-bundle.sh` is a dash-compatible helper template. `self.lib.hectic.applyBundleScript` is the generated shell source with concrete SQL paths embedded at Nix evaluation time. `migrator` and `db-tool` splice that shell source directly into their generated scripts. Public entry point: ```sh apply_hectic_bundle [] ``` - `` — full PostgreSQL connection string. - `` — optional. When present, after applying the bundle the helper invokes `hectic.load_secrets_from_env()` inside a dollar-quoted (`$ps_env$`) string so secret values cannot terminate the literal. The SQL file paths are embedded into the helper at Nix evaluation time, so callers only need to source the generated script and call the function. External consumers that do not want to source the helper can still invoke `psql -f` against `self.lib.hectic.bundleFiles` or the individual `self.lib.hectic.*.path` entries directly. ## Adding a new SQL file 1. Add `lib/hook/sql/hectic-.sql`. 2. Wire it into `lib/default.nix` under `lib.hectic.`. 3. Add its `.path` to `lib.hectic.bundleFiles` in the correct order. 4. Add a matching placeholder/replacement in `lib.hectic.applyBundleScript` and update `lib/hook/apply-hectic-bundle.sh` to apply the file. 5. Bump `HECTIC_VERSION` if the new content changes existing semantics. 6. Update tests in `test/package/migrator/test/postgresql/init-hectic-bundle/` and `test/package/db-tool/test/postgresql/hydrate-hook/`. ## Versioning `HECTIC_VERSION` is a single global version for the bundle, not per-file. Bump it on any breaking change to the schema. `hectic-version.sql` raises an exception when the database row diverges from the bundle version, forcing a deliberate migration before the rest of the bundle runs.