@gallopsystems/agent-skills 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gallopsystems/agent-skills",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "description": "Gallop Systems agent skills, symlinked into .claude/skills (Claude Code) and .agents/skills (Codex) on install.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -10,7 +10,9 @@ doctl databases ca <db-id> # cluster CA certi
10
10
 
11
11
  - **The connection URI contains live credentials** — treat command output as a secret. Don't echo it into logs; pipe it directly to where it's needed.
12
12
  - **Check `Version`** and keep CI/local database versions in sync with production — a test suite running `postgres:15` against a pg-18 production cluster hides version-specific behavior.
13
- - Connections use port 25060 with `sslmode=require`. **TLS trap**: some clients (e.g. newer `pg-connection-string`) silently upgrade `require` to `verify-full`, which rejects DO's CA under the default trust store. Fix: supply the CA from `doctl databases ca <db-id>` explicitly, or configure ssl options in code rather than relying on the URI.
13
+ - Connections use port 25060 with `sslmode=require`. **TLS trap**: some clients (e.g. `pg-connection-string` as bundled with `pg` >= 8.16) silently treat `require` as `verify-full`, which rejects DO's self-signed CA under the default trust store (`SELF_SIGNED_CERT_IN_CHAIN`). Two non-obvious parts:
14
+ - **An ssl option set in code does NOT override an `sslmode` already in the URI.** Passing `ssl: { rejectUnauthorized: false }` while the connection string still ends in `?sslmode=require` keeps failing — the URI's `sslmode` wins. The fix has to land in the URI itself: drop/replace `sslmode`, append `uselibpqcompat=true` (restores libpq semantics: encrypt but don't verify), or supply the CA from `doctl databases ca <db-id>` explicitly.
15
+ - **When you can't edit the URI** — specifically, when you bind App Platform's generated `${db.DATABASE_URL}` straight into an env var in the app spec, it always carries `?sslmode=require` and you don't author the string — append the param to the bound value: `value: ${db.DATABASE_URL}&uselibpqcompat=true`. This only applies to that bound-pipethrough case; if you declare the connection-string env var yourself, just put the right params (or none) in from the start and an in-code ssl option is enough.
14
16
 
15
17
  ## Spaces
16
18
 
@@ -102,6 +102,29 @@ function isActiveUser() {
102
102
  }
103
103
  ```
104
104
 
105
+ A predicate helper can also return a callback that receives the query's
106
+ `eb`, so the same helper works in any `.where()`/`.having()` on that table:
107
+
108
+ ```typescript
109
+ import type { ExpressionBuilder } from "kysely";
110
+
111
+ // AVOID - raw sql means the column name is an unchecked string.
112
+ // A typo or renamed column only fails at runtime.
113
+ function nameMatches(name: string) {
114
+ return sql<boolean>`lower(name) = ${name.toLowerCase()}`;
115
+ }
116
+
117
+ // PREFER - eb.fn keeps the column reference type-checked against DB,
118
+ // and the value stays parameterized.
119
+ function nameMatches(name: string) {
120
+ return (eb: ExpressionBuilder<DB, "user">) =>
121
+ eb(eb.fn("lower", ["name"]), "=", name.toLowerCase());
122
+ }
123
+
124
+ // Both call sites stay identical:
125
+ db.selectFrom("user").where(nameMatches(input)).execute();
126
+ ```
127
+
105
128
  ### Conditional Expressions with Arrays
106
129
 
107
130
  Build dynamic filters by collecting expressions:
@@ -964,6 +987,53 @@ await db.schema.createIndex("idx_order_user_id").on("order").column("user_id").e
964
987
  .addColumn("price", sql`numeric(10, 2)`)
965
988
  ```
966
989
 
990
+ ### Migration Ordering Is Append-Only — Out-of-Order Timestamps Break Prod Deploys
991
+
992
+ Kysely's migrator enforces a **strict append-only ledger**: it refuses to run any
993
+ unexecuted migration whose timestamp sorts *before* the last-executed one
994
+ (throwing `Corrupted migrations: previously executed migration ... is missing`).
995
+ This bites when two branches each add a migration, and the one that merges *second*
996
+ carries the *earlier* timestamp:
997
+
998
+ ```
999
+ branch A merges first → 1700000200000_drop_thing (runs in prod)
1000
+ branch B merges second → 1700000100000_add_table (timestamp is EARLIER)
1001
+ → 1700000100001_add_column
1002
+ ```
1003
+
1004
+ Prod has already recorded `...200000_drop_thing` as executed. On the next deploy
1005
+ the `migrate` job sees two pending migrations that sort *before* it, throws
1006
+ "Corrupted migrations", and **exits non-zero**. If migrations run as a pre-deploy
1007
+ job (common on PaaS like DigitalOcean App Platform), the failed job fails the whole
1008
+ deploy and the platform **auto-rolls-back to the previous image** — so prod silently
1009
+ stays on stale code and every subsequent deploy fails the same way. A self-reinforcing
1010
+ loop that looks like a deploy/token problem but is really a migration-ledger problem.
1011
+
1012
+ **Prevent it:** before merging a long-lived branch, check whether `main` has merged
1013
+ any migration with a *later* timestamp than yours. If so, regenerate your migration's
1014
+ timestamp so it sorts last (`migrate:make` again, or rename the file) **before it
1015
+ merges** — only safe while the migration has not yet run in any shared DB. Never
1016
+ re-stamp a migration that prod has already executed; that forces it to re-run.
1017
+
1018
+ **Fix it once prod is wedged:** reconcile the ledger so the executed set is a clean
1019
+ *prefix* again, then let the normal strict migrate job run. Do **not** reach for
1020
+ `allowUnorderedMigrations: true` — it works (kysely-ctl spreads the `migrations`
1021
+ config into the `Migrator`), but it permanently weakens the ordering guard to paper
1022
+ over one bad state. Instead, surgically remove the prematurely-recorded row from the
1023
+ migration ledger table (default `kysely_migration`):
1024
+
1025
+ ```sql
1026
+ -- prod ledger has the later-timestamp migration recorded, blocking the two earlier ones
1027
+ DELETE FROM kysely_migration WHERE name = '1700000200000_drop_thing';
1028
+ ```
1029
+
1030
+ Now `add_table → add_column → drop_thing` are all pending in true timestamp order, and
1031
+ the next deploy's strict migrate job applies them cleanly. This only works when the
1032
+ removed migration is **idempotent to re-run** (e.g. a `dropTable`/`dropColumn` written
1033
+ with `ifExists`, so re-applying it after the others is a safe no-op). Verify the
1034
+ ledger is a contiguous prefix after the DELETE, and prefer letting the deploy's own
1035
+ migrate job re-apply rather than running migrations from a laptop against prod.
1036
+
967
1037
  ## Type Generation
968
1038
 
969
1039
  Use `kysely-codegen` to generate types from your database: