DevOps open source pour bases de données

Des migrations de bases de données sans surprises.

Ptah compare le schéma souhaité à une base existante, puis génère des migrations versionnées ou applique un plan approuvé. Il migre aussi l’état d’inférence persistant, par exemple pour recalculer les embeddings d’une table pgvector. PostgreSQL, MySQL, MariaDB, SQLite et d’autres moteurs. Open source, licence MIT, sans chaîne de compilation à installer.

$ curl -fsSL https://ptah.run/install.sh | sh

v0.13.0 · pre-GA · MITWindows : irm https://ptah.run/install.ps1 | iex

Modification
sh · ptah-quick-start 49 / 49
# Écrire le schéma de la base dans un fichier.$ ptah db read --db-url sqlite://app.db | tee schema.sqlCREATE TABLE "users" (  "id" INTEGER PRIMARY KEY,  "email" TEXT NOT NULL); # Le contrôle de dérive compare ce fichier à la base dont il provient.$ ptah schema drift --schema-file schema.sql \    --db-url sqlite://app.dbNo schema drift detected. # Ajouter une colonne au schéma souhaité.$ cat > schema.sql <<'SQL'CREATE TABLE "users" (  "id" INTEGER PRIMARY KEY,  "email" TEXT NOT NULL,  "created_at" TEXT);SQL # Le même contrôle a maintenant une différence à signaler.$ ptah schema drift --schema-file schema.sql \    --db-url sqlite://app.dbSchema drift detected (highest severity: warning).Failure threshold: all. Failing: true.Database: sqlite://app.db Findings:- columns_added: 1 (warning) # Déterminer les changements nécessaires. Une simulation n’exécute rien.$ ptah schema apply --schema-file schema.sql \    --db-url sqlite://app.db --dry-runPlanned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TEXT; # Exécuter le plan revu. --auto-approve convient à un fichier jetable.$ ptah schema apply --schema-file schema.sql \    --db-url sqlite://app.db --auto-approvePlanned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TEXT;Auto-approval enabled; applying schema changes.Schema apply completed successfully. # Exécuter le même contrôle une troisième fois.$ ptah schema drift --schema-file schema.sql \    --db-url sqlite://app.dbNo schema drift detected.
schema.sql correspond à la base+1 ligne−1 ligne+7 lignes
CREATE TABLE users (  id INTEGER PRIMARY KEY,  email TEXT NOT NULL);
CREATE TABLE users (  id INTEGER PRIMARY KEY,  email TEXT NOT NULL,+  created_at TEXT);
CREATE TABLE users (  id INTEGER PRIMARY KEY−  email TEXT NOT NULL);
CREATE TABLE users (  id INTEGER PRIMARY KEY,  email TEXT NOT NULL);+ +CREATE TABLE sessions (+  id INTEGER PRIMARY KEY,+  user_id INTEGER NOT NULL,+  token TEXT NOT NULL,+  FOREIGN KEY (user_id) REFERENCES users(id)+);
sh
--db-url
$ ptah schema drift --schema-file schema.sql \    --db-url sqlite://app.dbNo schema drift detected.exit 0 $ ptah schema apply --schema-file schema.sql \    --db-url sqlite://app.db --dry-runSchema is synced, no changes to be made.exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url sqlite://app.dbSchema drift detected (highest severity: warning).Failure threshold: all. Failing: true.Database: sqlite://app.db Findings:- columns_added: 1 (warning)exit 1 $ ptah schema apply --schema-file schema.sql \    --db-url sqlite://app.db --dry-runPlanned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TEXT;exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url sqlite://app.dbSchema drift detected (highest severity: destructive).Failure threshold: all. Failing: true.Database: sqlite://app.db Findings:- columns_removed: 1 (destructive)exit 1 $ ptah schema apply --schema-file schema.sql \    --db-url sqlite://app.db --dry-runPlanned schema changes:-- Disable foreign-key enforcement for the table rebuild belowPRAGMA foreign_keys = off;-- SQLite table rebuild for changes ALTER TABLE cannot express on usersCREATE TABLE "__ptah_rebuild_users" (  "id" INTEGER PRIMARY KEY);INSERT INTO "__ptah_rebuild_users" ("id") SELECT "id" FROM "users";DROP TABLE "users";ALTER TABLE "__ptah_rebuild_users" RENAME TO "users";-- Restore foreign-key enforcement after the table rebuildPRAGMA foreign_keys = on;exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url sqlite://app.dbSchema drift detected (highest severity: warning).Failure threshold: all. Failing: true.Database: sqlite://app.db Findings:- constraints_added: 1 (warning)- tables_added: 1 (safe)exit 1 $ ptah schema apply --schema-file schema.sql \    --db-url sqlite://app.db --dry-runPlanned schema changes:CREATE TABLE "sessions" (  "id" INTEGER PRIMARY KEY,  "user_id" INTEGER NOT NULL,  "token" TEXT NOT NULL,  CONSTRAINT "fk_sessions_user_id" FOREIGN KEY ("user_id") REFERENCES "users" ("id"));exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url postgres://app@db/appNo schema drift detected.exit 0 $ ptah schema apply --schema-file schema.sql \    --db-url postgres://app@db/app --dry-runSchema is synced, no changes to be made.exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url postgres://app@db/appSchema drift detected (highest severity: warning).Failure threshold: all. Failing: true.Database: postgres://app@db/app Findings:- columns_added: 1 (warning)exit 1 $ ptah schema apply --schema-file schema.sql \    --db-url postgres://app@db/app --dry-runPlanned schema changes:-- Add/modify columns for table: users ---- ALTER statements: --ALTER TABLE "users" ADD COLUMN "created_at" TEXT;exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url postgres://app@db/appSchema drift detected (highest severity: destructive).Failure threshold: all. Failing: true.Database: postgres://app@db/app Findings:- columns_removed: 1 (destructive)exit 1 $ ptah schema apply --schema-file schema.sql \    --db-url postgres://app@db/app --dry-runPlanned schema changes:-- Remove columns from table: users ---- ALTER statements: --ALTER TABLE "users" DROP COLUMN "email" CASCADE;-- WARNING: Dropping column users.email with CASCADE - This will delete data and dependent objects! --;exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url postgres://app@db/appSchema drift detected (highest severity: warning).Failure threshold: all. Failing: true.Database: postgres://app@db/app Findings:- constraints_added: 1 (warning)- tables_added: 1 (safe)exit 1 $ ptah schema apply --schema-file schema.sql \    --db-url postgres://app@db/app --dry-runPlanned schema changes:-- POSTGRES TABLE: sessions --CREATE TABLE "sessions" (  "id" INTEGER PRIMARY KEY NOT NULL,  "user_id" INTEGER NOT NULL,  "token" TEXT NOT NULL);-- ALTER statements: --ALTER TABLE "sessions" ADD CONSTRAINT "fk_sessions_user_id" FOREIGN KEY ("user_id") REFERENCES "users"("id");exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url mysql://app@db/appNo schema drift detected.exit 0 $ ptah schema apply --schema-file schema.sql \    --db-url mysql://app@db/app --dry-runSchema is synced, no changes to be made.exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url mysql://app@db/appSchema drift detected (highest severity: warning).Failure threshold: all. Failing: true.Database: mysql://app@db/app Findings:- columns_added: 1 (warning)exit 1 $ ptah schema apply --schema-file schema.sql \    --db-url mysql://app@db/app --dry-runPlanned schema changes:-- Modify table: users ---- ALTER statements: --ALTER TABLE `users` ADD COLUMN `created_at` TEXT;exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url mysql://app@db/appSchema drift detected (highest severity: destructive).Failure threshold: all. Failing: true.Database: mysql://app@db/app Findings:- columns_removed: 1 (destructive)exit 1 $ ptah schema apply --schema-file schema.sql \    --db-url mysql://app@db/app --dry-runPlanned schema changes:-- Modify table: users ---- ALTER statements: --ALTER TABLE `users` DROP COLUMN `email`;-- WARNING: Dropping column users.email - This will delete data! --;exit 0
$ ptah schema drift --schema-file schema.sql \    --db-url mysql://app@db/appSchema drift detected (highest severity: warning).Failure threshold: all. Failing: true.Database: mysql://app@db/app Findings:- constraints_added: 1 (warning)- tables_added: 1 (safe)exit 1 $ ptah schema apply --schema-file schema.sql \    --db-url mysql://app@db/app --dry-runPlanned schema changes:-- MYSQL TABLE: sessions --CREATE TABLE `sessions` (  `id` INTEGER PRIMARY KEY,  `user_id` INTEGER NOT NULL,  `token` TEXT NOT NULL);-- ALTER statements: --ALTER TABLE `sessions` ADD CONSTRAINT `fk_sessions_user_id` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`);exit 0
Vous décrivez le schéma souhaité. Ptah indique les changements nécessaires avant l’exécution. Le contrôle de dérive vérifie ensuite que la base correspond au schéma. Autres exécutions enregistrées →
Fonctionnement

Une seule comparaison. Chaque flux de travail s’appuie sur elle.

Quel que soit le format de votre schéma, Ptah le convertit en un seul schéma souhaité et le compare à la base. Dérive, plans, analyse statique et application sont quatre lectures de ce même diff. Chaque contrôle se termine avec le code 0, 1 ou 2 : la CI ne prend donc jamais une connexion en échec pour un schéma conforme.

SOURCES · COMBINABLESschema.sqlschema.yamlschema.hclschema.dbmlmodels/*.goorm loader · stdoutschéma souhaitéBASE EXISTANTEmoteurs compatibles →comparerun seul diffCE QUI EN SORTschema driftla preuve012schema plan · --dry-runl’objet de la revuemigrations lintle garde-fouschema apply · migrations upaprès approbation
  1. schema.sqlschema.yamlschema.hclschema.dbmlmodels/*.goorm loader · stdout
  2. schéma souhaitébase existante
  3. comparer
  4. schema driftla preuve 0 1 2
  5. schema plan · --dry-runl’objet de la revue
  6. migrations lintle garde-fou
  7. schema apply · migrations upaprès approbation → base existante

Des tâches courantes au fil de la semaine.

De vraies commandes et leurs sorties réelles. Les codes de retour sont constants : 0 en cas de succès, 1 pour un contrôle négatif, 2 pour une erreur d’utilisation ou de connexion.

Tenir les migrations risquées et destructives hors de main.

$ dans la CI.github/workflows/ptah.yml
- uses: stokaro/ptah-action@v1
  with:
    dir: ./internal/models
    db-url: ${{ secrets.PTAH_DATABASE_URL }}
    dialect: postgres
    migration-dir: ./migrations

$ ptah migrations lint --dir ./migrations \
    --dialect postgres
migrations/1790114595_drop_bio.up.sql:1 [warning] BC104: dropping a column retires a name application versions already deployed against the old schema still select and insert, so each of them starts failing the moment this migration commits, whether or not the column held any rows; deploy readers that no longer use the column first, then drop it in a later release (dropped column breaks deployed code)
migrations/1790114595_drop_bio.up.sql:1 [error] DS102: DROP COLUMN permanently deletes the column's data; deploy readers that no longer use the column first, then drop it in a later release (column dropped)

2 finding(s).
Contrôles · pull requeststokaro/ptah-action@v1
Ptah destructive-change verdictfailure
Ptah found destructive migration statements
Safety verdict: destructive.
Destructive changes: yes.
Commentaire publié par ptah-action
## Ptah migration plan

-- Remove columns from table: users --
-- ALTER statements: --
ALTER TABLE "users" DROP COLUMN "bio" CASCADE;

L’Action planifie la migration sur la base cible, publie le plan en commentaire et crée un contrôle qui échoue si une instruction devait détruire des données. ptah migrations lint se termine avec le code 1 quand il trouve un problème et 2 quand il n’a pas pu s’exécuter : une connexion en échec ne passe donc jamais pour un répertoire sans problème.

Tenir les migrations non relues à l’écart de la base.

shsceller
$ ptah migrations hash --dir ./migrations
Wrote ./migrations/ptah.sum
4 migration file(s) hashed
$ cat ./migrations/ptah.sum
h1:wmq3WUjBVQT+Ll4SPPan7wJGghKMtqi2yDc2Tc4AKnM=
1790114505_create_users.down.sql h1:3hAVcH5B1mghhsNEDFiyIlGOdernvs+6dDRkaO79/F0=
1790114505_create_users.up.sql h1:WJmUNv9ET7Qaypt2hpFNHHrqSQ5XGu0Yi0gMV0gLEMo=
1790114510_add_created_at.down.sql h1:3E0vyUoTX+11C374nPdXruUtJycoXTRoGvl8XsHLLIk=
1790114510_add_created_at.up.sql h1:9wID/+fArtJnyBz/JvM/NDDd1Ovm5SvkaJYcygIXLc4=
shplus tard, après modification d’un fichier déjà appliqué
$ ptah migrations up \
    --db-url sqlite://app.db \
    --migrations-dir ./migrations
error: migration sum verification failed:
migration directory does not match ptah.sum:
  changed: 1790114505_create_users.up.sql
$ echo $?
2

ptah.sum conserve une somme de contrôle par fichier. up, validate et lint la vérifient avant toute action, et status le fait avec --verify-sum. Ce que la CI a relu est exactement ce que la production exécute.

Changer de modèle d’embeddings sans interrompre la recherche.

g1 · ptah-docs-stub · la génération lue par les requêtesrun g1: cut_over, running
g2 · ptah-docs-stub-v2 · le nouveau modèleerror: not found: run g2
g1 · ptah-docs-stub · la génération lue par les requêtesrun g1: cut_over, running
g2 · ptah-docs-stub-v2 · le nouveau modèlerun g2: boundary_captured, running
g1 · ptah-docs-stub · la génération lue par les requêtesrun g1: cut_over, running
g2 · ptah-docs-stub-v2 · le nouveau modèlerun g2: backfilled, running
g1 · ptah-docs-stub · la génération lue par les requêtesrun g1: cut_over, running
g2 · ptah-docs-stub-v2 · le nouveau modèlerun g2: caught_up, running
g1 · ptah-docs-stub · la génération lue par les requêtesrun g1: cut_over, running
g2 · ptah-docs-stub-v2 · le nouveau modèlerun g2: indexed, running
g1 · ptah-docs-stub · la génération lue par les requêtesrun g1: cut_over, running
g2 · ptah-docs-stub-v2 · le nouveau modèlerun g2: verified, running
g1 · ptah-docs-stub · la génération lue par les requêtesrun g1: cut_over, running
g2 · ptah-docs-stub-v2 · le nouveau modèlerun g2: cut_over, running
$ ptah inference plan --spec /work/spec-g2.yaml --db-url 'postgres://ptah:ptah@postgres:5432/ptah?sslmode=disable'
generation 4f8a8e77ec3904675efb9678ec1b3de138a135b21d267621d93decf47e581d85
  source.table = docs (configured: the specification)
  model.identifier = ptah-docs-stub-v2 (configured: the specification)
  model.revision = 1 (configured: the specification)
  provider.credential =  (configured: the specification, as a reference rather than a value)
  generation.reproducibility = full (inferred: the specification pins an immutable model revision)
  source.estimated_rows = 3 (measured)
  run.consistency_mode = outbox (configured: the migration specification)
  target.exists = false (measured)
  target.capability.vector_type = true (measured)
  target.capability.vector_index = true (measured)
  [prepare] create the vector column and metadata for generation 4f8a8e77ec3904675efb9678ec1b3de138a135b21d267621d93decf47e581d85
  [capture] install the outbox mechanism and record its starting position
  [backfill] embed 3 in-scope source rows
  [catch-up] process every source change recorded after the snapshot began
  [index] build the vector index and wait for it to be valid
  [verify] run every verification layer against the exact target generation
  [cutover] point queries at generation 4f8a8e77ec3904675efb9678ec1b3de138a135b21d267621d93decf47e581d85, which nothing currently reads

What is not established:
  - generation.reproducibility = full (inferred: the specification pins an immutable model revision)

What leaves the database:
  - ptah-docs-stub-v2 at http://embeddings:8080/v1, declared local
  - the text of title, body
  - for 3 rows

Consistency mode: outbox
  - the outbox event and the source change are one transaction, so a change that committed has an event
$ ptah inference prepare --spec /work/spec-g2.yaml --db-url 'postgres://ptah:ptah@postgres:5432/ptah?sslmode=disable' --run-id g2
prepared run g2 for generation 4f8a8e77ec39
  - snapshot boundary: 788
$ ptah inference backfill --spec /work/spec-g2.yaml --db-url 'postgres://ptah:ptah@postgres:5432/ptah?sslmode=disable' --run-id g2 --batch-rows 10
backfill finished: 3 scanned, 3 embedded, 0 skipped
$ ptah inference catchup --spec /work/spec-g2.yaml --db-url 'postgres://ptah:ptah@postgres:5432/ptah?sslmode=disable' --run-id g2 --batch-rows 10
caught up to transaction 796: 0 changed rows, 0 tombstoned
  - the outbox keeps events this run has processed: floor 773 for public.docs is held by run g1 (generation b7cc9eb5c7d2ee13ad46ba3d5da8a479ec57b128d771e4151fab5bf6d1da0866)
$ ptah inference index --spec /work/spec-g2.yaml --db-url 'postgres://ptah:ptah@postgres:5432/ptah?sslmode=disable' --run-id g2
generation 4f8a8e77ec39 has a valid index
$ ptah inference verify --spec /work/spec-g2.yaml --db-url 'postgres://ptah:ptah@postgres:5432/ptah?sslmode=disable' --run-id g2
generation 4f8a8e77ec3904675efb9678ec1b3de138a135b21d267621d93decf47e581d85: 3 source rows, 3 target rows
  - every deterministic layer passed
  - not measured: the stored vectors were not read back, so their dimension was checked and their values were not; the target refuses a non-finite component on write, so nothing here could have carried one
$ ptah inference cutover --spec /work/spec-g2.yaml --db-url 'postgres://ptah:ptah@postgres:5432/ptah?sslmode=disable' --run-id g2
plan 949aa4c67b4b
cutover refused:
  - this policy requires an approval and none was given
error: cutover refused

$ ptah inference cutover --spec /work/spec-g2.yaml --db-url 'postgres://ptah:ptah@postgres:5432/ptah?sslmode=disable' --run-id g2 --approve 949aa4c67b4b --approver 'site check'
queries now read generation 4f8a8e77ec3904675efb9678ec1b3de138a135b21d267621d93decf47e581d85 (plan 949aa4c67b4b)
  - no stabilization window was asked for, so nothing is keeping the previous generation current and there is no rollback to it

Ptah construit la nouvelle génération à côté de celle que lisent les requêtes, appelle votre service d’embeddings, vérifie le résultat et fait basculer les requêtes quand une approbation désigne le plan. Il ne calcule jamais de vecteur lui-même, et plan indique ce qui sort de la base avant toute exécution.

Schéma en tant que code

Travaille avec vos schémas existants.

Décrivez le schéma souhaité, puis relisez les changements nécessaires pour y parvenir. Chaque source est convertie dans le même schéma interne avant la planification : fichiers, annotations Go, base existante ou tout programme qui affiche un schéma, ORM compris.

schema.sqlschema.yamlschema.hclschema.dbmlmodels/user.go
CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  email TEXT NOT NULL,
  created_at TEXT
);
tables:
  users:
    columns:
      id:
        type: INTEGER
        primary: true
      email:
        type: TEXT
        not_null: true
      created_at:
        type: TEXT
table "users" {
  column "id" {
    type = integer
    null = true
  }

  column "email" {
    type = text
    null = false
  }

  column "created_at" {
    type = text
    null = true
  }

  primary_key {
    columns = [column.id]
  }
}
Table users {
  id integer [pk]
  email text [not null]
  created_at text
}
package models

//ptah:schema:table name="users"
type User struct {
	//ptah:schema:field name="id" type="INTEGER" primary="true"
	ID int64

	//ptah:schema:field name="email" type="TEXT" not_null="true"
	Email string

	//ptah:schema:field name="created_at" type="TEXT"
	CreatedAt *string
}
même plan, quelle que soit la sourceexit 0exit 0exit 0exit 0exit 0
$ ptah schema apply --schema-file schema.sql \
    --db-url sqlite://app.db --dry-run
Planned schema changes:
ALTER TABLE "users" ADD COLUMN "created_at" TEXT;
$ ptah schema apply --schema-file schema.yaml \
    --db-url sqlite://app.db --dry-run
Planned schema changes:
ALTER TABLE "users" ADD COLUMN "created_at" TEXT;
$ ptah schema apply --schema-file schema.hcl \
    --db-url sqlite://app.db --dry-run
Planned schema changes:
ALTER TABLE "users" ADD COLUMN "created_at" TEXT;
$ ptah schema apply --schema-file schema.dbml \
    --db-url sqlite://app.db --dry-run
Planned schema changes:
ALTER TABLE "users" ADD COLUMN "created_at" TEXT;
$ ptah schema apply --root-dir ./models \
    --db-url sqlite://app.db --dry-run
Planned schema changes:
ALTER TABLE "users" ADD COLUMN "created_at" TEXT;
Ou laissez l’ORM définir le schéma

Tout programme qui écrit le schéma complet en SQL, HCL ou YAML sur stdout est une source. Ptah le lance sans shell, en borne l’exécution et refuse une sortie vide.

  • GORM
  • SQLAlchemy
  • Django
  • TypeORM
  • Sequelize
  • Hibernate
  • votre script

une recette enregistrée dans le dépôt, versions figées · les autres affichent du DDL SQL et peuvent suivre le même contrat

$ ptah schema drift \
    --schema-cmd ./scripts/export-schema \
    --db-url sqlite://app.db
No schema drift detected.
Données de référence en tant que code

Des lignes de référence, déclarées à côté des tables.

Ptah compare les lignes déclarées à la table existante et écrit une migration de données réversible. Le contrôle de dérive et les plans directs lisent les mêmes lignes. Une migration de données qui modifie ou supprime des lignes exige --allow-destructive.

  1. //ptah:schema:data · countries.yaml
  2. comparaison par clé avec la table existante
  3. INSERT · UPDATE · DELETE
  4. .up.sql / .down.sql · ptah.sum
Accès à la base en tant que code

Rôles, privilèges et politiques au niveau des lignes, déclarés avec les tables.

ptah schema security lit la base existante et signale une table sans sécurité au niveau des lignes sur laquelle un rôle a reçu des privilèges. ptah viz --security marque le même constat sur le schéma déclaré. Avec --fail-on any, ce constat bloque une pull request.

  1. GRANT … TO role
  2. ptah schema security
  3. PRV01 · pas de RLS sur une table avec privilèges
  4. déclarer une politique
Export du schéma

Un seul schéma, exporté vers tout ce qui en a besoin.

Sept cibles. Les annotations Go les atteignent toutes ; un fichier de schéma, toutes sauf HCL. Des contrats pour les machines, des documents pour les humains, deux autres formats de schéma. Aucun export ne se connecte à une base de données, et chaque export est une proposition à relire.

$ ptah schema export --to openapi-v3 --root-dir ./models
$ ptah schema export --to graphql --root-dir ./models \
    --graphql-operations list,by-id
$ ptah schema export --to protobuf --root-dir ./models \
    --proto-package shop.v1 --out shop.proto
$ ptah schema export --to markdown --root-dir ./models
$ ptah schema export --to html --root-dir ./models --out schema.html
$ ptah schema export --to dbml --root-dir ./models
$ ptah schema export --to hcl --root-dir ./models --out schema.hcl
openapi: 3.0.3
info:
  title: Ptah Exported Schema
  version: 1.0.0
servers:
  - url: /
paths: {}
components:
  schemas:
    products:
      type: object
      required:
        - id
        - name
        - price
        - status
      properties:
        id:
          type: integer
          format: int32
        name:
          type: string
          maxLength: 255
        price:
          type: number
        status:
          type: string
          enum:
            - active
            - inactive
# Code generated by ptah; DO NOT EDIT.

# Operation shapes below were generated on request. They are type declarations
# only: Ptah generates no resolvers, no data access, no authorization or tenant
# isolation, and no filtering, ordering, pagination, or transaction behavior.

enum ProductStatus {
  active
  inactive
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

type Query {
  products(first: Int, after: String): ProductConnection
  product(id: ID!): Product
}

type Product {
  id: ID!
  name: String!
  price: Float!
  status: ProductStatus!
}

type ProductEdge {
  node: Product!
  cursor: String!
}

type ProductConnection {
  edges: [ProductEdge!]!
  pageInfo: PageInfo!
}
Exported Protobuf schema to /tmp/app/shop.proto
Exported 1 message(s), 4 field(s), 1 enum(s)
bootstrapped new compatibility history
2 export warning(s) reported

$ cat shop.proto
edition = "2023";

package shop.v1;

message Product {
  int32 id = 1;
  string name = 2;
  string price = 3;
  ProductStatus status = 4;
}

enum ProductStatus {
  PRODUCT_STATUS_UNSPECIFIED = 0;
  PRODUCT_STATUS_ACTIVE = 1;
  PRODUCT_STATUS_INACTIVE = 2;
}

// Code generated by ptah schema export --to protobuf; DO NOT EDIT.
// ptah:protobuf-export-version=2
// ptah:content-sha256=4b1c2203313b7090dd40822f26eda0ee8d0b1df0ffb61c3691aa34d06de3c81b
# Schema reference

- [products](#products)

## products

| Column | Type | Null | Default | Key | Comment |
| --- | --- | --- | --- | --- | --- |
| id | SERIAL | no | — | PK | — |
| name | VARCHAR(255) | no | — | — | — |
| price | DECIMAL(10,2) | no | — | — | — |
| status | product_status | no | — | — | — |

## Enums

- `product_status` — active, inactive
Exported schema documentation to /tmp/app/schema.html
Found 1 table(s), 4 field(s), 1 enum(s)
Enum "product_status" {
  "active"
  "inactive"
}

Table "products" {
  "id" SERIAL [pk, not null]
  "name" VARCHAR(255) [not null]
  "price" DECIMAL(10,2) [not null]
  "status" product_status [not null]
}
Exported HCL schema to /tmp/app/schema.hcl
Found 1 table(s), 4 field(s), 1 enum(s)

$ cat schema.hcl
// Code generated by ptah; DO NOT EDIT.

enum "product_status" {
  values = ["active", "inactive"]
}

table "products" {
  column "id" {
    type = SERIAL
  }
  column "name" {
    type = VARCHAR(255)
  }
  column "price" {
    type = DECIMAL(10,2)
  }
  column "status" {
    type = enum.product_status
  }
  primary_key {
    columns = [column.id]
  }
}

api_name, api_type et api_expose façonnent le contrat publié sans toucher au moteur de migration : un DECIMAL peut être publié sous forme de chaîne, et une colonne peut être exclue du contrat. Les numéros de champs Protobuf sont conservés dans le fichier que vous versionnez.

Développement agentique

Deux façons de faire travailler un modèle sur le projet. Un seul jeu d’outils. Jamais la base de données.

Choisissez qui gère la connexion au modèle. Tout le reste est le même Ptah : onze outils avec un espace de travail, chaque écriture prévisualisée et vérifiée par rapport à un digest, une ligne d’audit pour chaque décision.

ptah assistPtah gère la connexion au modèle, via un profil que vous configurez : l’API compatible OpenAI ou l’API Anthropic.
vousterminal
ptah assistun modèle sur votre machine, une passerelle ou un fournisseur
votre projetschéma · migrations · une base de développement
ptah mcpLe client gère le modèle. Claude, Cursor, VS Code, Zed ou tout client MCP qui exécute un serveur local via stdio.
vousdans le client
votre client MCPClaude, Cursor, VS Code ou Zed
votre projetschéma · migrations · une base de développement
  • lire → prévisualiser → approuver → appliquerL’approbation couvre un seul patch, désigné par son digest. Si les fichiers ont changé après la prévisualisation, l’application est refusée.
  • Allow once · for this session · NoVous répondez dans le terminal ou dans le client. La politique du dépôt ne peut que restreindre les autorisations.
  • N’écrit jamais dans la base de donnéesAucun outil n’exécute de SQL, ne modifie une base ou n’applique une migration, et aucun réglage ne peut en ajouter un. ptah migrations up reste une étape exécutée par une personne.
  • Audit et nombre d’octetsAvec un espace de travail, chaque décision d’autorisation est écrite dans .ptah/agent-audit.jsonl. Dans ptah assist, chaque réponse se termine par le nombre d’octets du projet transmis au fournisseur.
ptah assist · le moment de l’approbationenregistré en direct
┃ Ptah is asking for artifact.write:schema.
┃
┃ apply a 1-file patch to the schema artifact
┃   artifact: schema
┃   directory: /home/developer/projects/ptah-assist-lab/workspace/schema
┃   base digest: sha256:c2faa21a30324714af7e7de289200af7f5a84908223ba1cba094b4a9
┃   result digest: sha256:fcb3f414e6936591b9510583b77fdead27f6419602161183a89a00
┃   update: schema.yaml
┃
┃ This approval covers exactly sha256:6e2a256e07315164caa524dd82d73a922ede320cd1
┃ > Allow once
┃   Allow for this session
┃   No

↑ up • ↓ down • / filter • enter submit
ptah assist · le modèle peut aussi refuserenregistré en direct
> now write the migration for that column into the migrations directory in Ptah's native format, and
 call apply_patch for it. be brief

  Held — not written. Two blockers, both mine, not yours:

  • Format unknown: no tool result in this session has shown me Ptah's native migration file
  format, and I won't invent one.
  • No preview to apply:  apply_patch  needs a preview token/digest from a fresh  preview_patch
  against the current migrations artifact (last listed: 0 files, digest  sha256:70f6b4… ). I have
  none, and I can't claim a write or a verification pass without them.

  If the migration tool surface is enabled here, I'll: preview a migration adding  tasks.active
  (matching the schema change already applied), show you the diff, then apply it with its token.

-- AcmeQuant/Qwen3.8-27B via router, 1 turn(s), 0 tool call(s), answer
-- No Ptah tool answered, so nothing above was checked against this project.

Entièrement open source. Aucune télémétrie. Fonctionne là où le réseau ne va pas.

Conçu pour les données qui doivent rester où elles sont : réglementées, souveraines, classifiées. Chaque composant peut être auto-hébergé, et aucun ne transmet de données à l’extérieur.

  • MITEntièrement open source

    La CLI, ptah-compat, ptah-ls, l’opérateur Kubernetes, la GitHub Action et ce site. Quatre dépôts, une seule licence, et aucun contrôle de sécurité n’est réservé à une offre payante.

  • PrivéAucune télémétrie

    Ptah se connecte uniquement aux adresses que vous lui donnez : votre base de données, le service d’embeddings, le fournisseur de modèle de ptah assist et le registre où vous publiez ou d’où vous récupérez les artefacts. Aucune statistique d’usage, aucune vérification de mise à jour, aucun compte.

  • À vousSouverain par défaut

    Schémas et répertoires de migrations sous forme d’artefacts OCI dans votre propre registre : ECR, GAR, Harbor, GHCR, Docker Hub ou un simple registry:2. L’opérateur s’exécute dans votre cluster. Les identifiants sont des références, jamais des valeurs : une spec est donc un fichier que vous pouvez versionner.

  • IsoléAucun réseau requis

    Les archives des versions contiennent ptah, ptah-compat et ptah-ls, compilés sans CGO. Vérifiez une archive avec checksums.txt sans accès au réseau. Pour l’inférence, --release accepte un répertoire oci-layout que vous avez transféré sur disque.

Une preuve, pas une promesse

Avant qu’une exécution d’inférence ne contacte un fournisseur, plan affiche exactement ce qui sort : quel modèle, quel point de terminaison et sa classe déclarée, quelles colonnes, combien de lignes. C’est la liste même que demande une revue de protection des données.

$ ptah inference planaffiché avant toute exécution
What leaves the database:
  - ptah-docs-stub-v2 at http://embeddings:8080/v1, declared local
  - the text of title, body
  - for 3 rows

Consistency mode: outbox
  - the outbox event and the source change are one transaction, so a change that committed has an event
GitOps pour bases de données

Réconcilier une base de données avec un schéma publié comme artefact OCI.

L’opérateur Ptah fige le tag sur un digest et n’utilise que ce digest. Il vérifie l’artefact, observe la base de données et publie un plan. Par défaut, il n’applique le plan qu’après une approbation qui le désigne, puis observe à nouveau la base au lieu de se fier au code de retour du job. Les plans destructifs sont désactivés par défaut ; les activer lie toujours l’approbation aux octets exacts du plan.

PtahSchema · boucle de réconciliationoperator.ptah.run
RÉCONCILIATIONà intervalles · sur changementaprès application01résoudre le tag → digest02vérifier l’artefact03observer la base04publier le plan05approuver06appliquer07observer à nouveauDÉBUT
  1. résoudre le tag → digest
  2. vérifier l’artefact
  3. observer la base
  4. publier le plan
  5. approuver
  6. appliquer
  7. observer à nouveau
05
les plans destructifs sont désactivés par défaut ; les activer lie l’approbation aux octets exacts du plan
07
la boucle se fie à ce qu’elle observe dans la base, pas au code de retour du job d’application
Bases de données

Sachez ce qui fonctionne sur votre base de données.

Dix moteurs. Choisissez le vôtre pour voir ce que Ptah y gère, la limite à connaître et les branches de version testées.

PostgreSQLCible principale

Gère les tables, vues, fonctions, extensions, types personnalisés, rôles et la sécurité au niveau des lignes.

Le modèle d’objets le plus large. Les types énumérés, domaines, composites et intervalles, les déclencheurs, les séquences et les vues matérialisées sont aussi gérés ici.
Voir la prise en charge de PostgreSQL →
Branches de version testées
14 – 18certifiée par Ptah
13testée après la fin du support
Testé sur un serveur réel dans la CI.
SQLiteEmbarqué

Modifications de schéma, y compris la reconstruction automatique des tables pour les changements que SQLite ne sait pas exprimer en ALTER TABLE.

Les reconstructions sont planifiées, pas cachées. Une reconstruction copie la table, et le plan l’indique comme une reconstruction de table SQLite.
Voir la prise en charge de SQLite →
Branches de version testées
3certifiée par Ptah
Bibliothèque embarquée, testée dans le processus, sans serveur.
MySQLPris en charge

Une planification qui respecte les règles DDL de MySQL : index, contraintes, clés étrangères et le SQL que MySQL accepte réellement.

Pas interchangeable avec MariaDB. Dialecte et capacités distincts, même si la famille de planificateurs est commune.
Voir la prise en charge de MySQL →
Branches de version testées
8.4 · 9.7 · 26.7certifiée par Ptah
Testé sur un serveur réel dans la CI.
MariaDBPris en charge

DDL, index et contraintes propres à MariaDB, planifiés selon les règles de son dialecte.

Des capacités propres. Un serveur MariaDB est reconnu à sa propre chaîne de version, même derrière mysql://, et planifié avec le preset MariaDB, jamais celui de MySQL.
Voir la prise en charge de MariaDB →
Branches de version testées
10.11 · 11.4 · 11.8 · 12.3certifiée par Ptah
Testé sur un serveur réel dans la CI.
CockroachDBProtocole PostgreSQL

Modifications de schéma et politiques TTL au niveau des lignes, planifiées par la voie PostgreSQL avec les règles de CockroachDB.

Ce n’est pas PostgreSQL. Les opérations que CockroachDB ne connaît pas sont refusées ou réécrites avant que le SQL ne soit produit, jamais envoyées telles quelles.
Voir la prise en charge de CockroachDB →
Branches de version testées
25.4 · 26.2 · 26.3certifiée par Ptah
Testé sur un serveur réel dans la CI.
YugabyteDBProtocole PostgreSQL

Modifications de schéma, rôles et sécurité au niveau des lignes par la voie PostgreSQL.

Les opérations d’index diffèrent. La création concurrente d’index est prise en charge ; DROP INDEX CONCURRENTLY est exclu par le preset YugabyteDB.
Voir la prise en charge de YugabyteDB →
Branches de version testées
2024.2 · 2025.2 · 2026.1certifiée par Ptah
Testé sur un serveur réel dans la CI.
SQL ServerSous-ensemble pris en charge

Tables T-SQL, index filtrés et lecture d’un catalogue existant.

Un sous-ensemble prudent de T-SQL. Les changements hors de ce sous-ensemble demandent une migration manuelle.
Voir la prise en charge de SQL Server →
Branches de version testées
2019 · 2022 · 2025certifiée par Ptah
Testé sur un serveur réel dans la CI.
OraclePris en charge

Lecture du schéma, planification et migrations versionnées.

La convergence déclarative a des limites connues. La comparaison d’un fichier déclaré avec le catalogue d’une base Oracle existante ne converge pas encore, et les migrations ne sont pas sérialisées par un verrou consultatif.
Voir la prise en charge de Oracle →
Branches de version testées
21 · 23certifiée par Ptah
Testé sur un serveur réel dans la CI.
ClickHouseAnalytique

Tables MergeTree avec paramètres de moteur et clés de tri, vues matérialisées et politiques TTL.

Limité par les capacités. Pas de clés étrangères ni de transactions à modéliser ; le plan travaille avec ce que le moteur propose.
Voir la prise en charge de ClickHouse →
Branches de version testées
26.3 · 26.7 · 26.8 · 26.9certifiée par Ptah
24.10testée après la fin du support
Testé sur un serveur réel dans la CI.
SpannerInterface PostgreSQL

Workflows de schéma par l’interface PostgreSQL, testés sur l’émulateur Spanner.

Limite des preuves. Testé sur l’émulateur ; la compatibilité avec le service géré n’est pas certifiée.
Voir la prise en charge de Spanner →
Limite des preuves
émulateurtesté dans la CI
gérénon certifié
L’émulateur ne prouve rien sur le service géré.

Vérifiez les capacités de votre serveur.

Affiche la version détectée, son niveau de prise en charge et le profil de capacités avec lequel Ptah planifie.

$ ptah db capabilities --db-url "$DATABASE_URL"
Comment lire la sortie →

Des vérifications de compatibilité appuyées par la CI.

Des sondes automatiques s’exécutent à chaque push sur master et chaque nuit : 28 branches de version certifiées par Ptah, celles que Ptah teste dans la CI et sur lesquelles il s’engage dans les limites des capacités sondées. SQLite embarqué et l’émulateur Spanner sont déclarés comme tels, pas comme des serveurs réels.

Voir les preuves de prise en charge →
Ptah dans votre workflow

ptah en ligne de commande. ptah-ls dans l’éditeur pour les annotations Go. La GitHub Action dans la CI, l’opérateur Kubernetes dans le cluster, des paquets Go sur ptah.run dans votre propre code.

Vous venez d’Atlas ?

ptah-compat accepte des commandes au format Atlas et exécute derrière elles le comportement de Ptah. Implémentation indépendante en salle blanche, sans affiliation à Ariga. Compatibilité Atlas →