_____ _ _ | __ \| |_ __ _| |__ | |__) | __| / _` | '_ \ | ___/| |_ | (_| | | | | |_| \__| \__,_|_| |_|
驚きのないデータベースマイグレーション
Ptah(プタハ)は、目標スキーマと稼働中のデータベースを比較し、バージョン管理型マイグレーションを書くか、承認済みの計画を適用する。pgvector テーブルの再埋め込みのような、永続的な推論状態の移行も扱う。PostgreSQL、MySQL、MariaDB、SQLite ほか。オープンソース、MIT ライセンス、導入すべきツールチェーンはない。
$ curl -fsSL https://ptah.run/install.sh | sh
- 完全なオープンソース、MIT
- テレメトリーなし
- エアギャップ環境で動く
- それが重要な理由 →
# データベース自身のスキーマをファイルに書き出す。$ ptah db read --db-url sqlite://app.db | tee schema.sqlCREATE TABLE "users" ( "id" INTEGER PRIMARY KEY, "email" TEXT NOT NULL); # ドリフト検査は、そのファイルと取得元のデータベースを比べる。$ ptah schema drift --schema-file schema.sql \ --db-url sqlite://app.dbNo schema drift detected. # 目標スキーマを書き換えて、列を 1 つ要求する。$ cat > schema.sql <<'SQL'CREATE TABLE "users" ( "id" INTEGER PRIMARY KEY, "email" TEXT NOT NULL, "created_at" TEXT);SQL # 今度は同じ検査が報告を返す。$ 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) # 差を埋めるのに何が要るかを尋ねる。ドライランは何も実行しない。$ ptah schema apply --schema-file schema.sql \ --db-url sqlite://app.db --dry-runPlanned schema changes:ALTER TABLE "users" ADD COLUMN "created_at" TEXT; # レビュー済みの計画を実行する。使い捨てのファイルなら --auto-approve でよい。$ 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. # そして同じ検査を、3 度目に。$ ptah schema drift --schema-file schema.sql \ --db-url sqlite://app.dbNo schema drift detected.
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)+);
$ 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
比較は 1 つ、どのワークフローもそれを読む
スキーマを何で書いても、Ptah はそれを 1 つの目標スキーマに解析し、データベースとの差分を取る。ドリフト、計画、lint、適用は、その 1 つの差分を 4 通りに読んだものだ。どの検査も 0、1、2 のいずれかで終了するので、CI が接続の失敗をクリーンなスキーマと取り違えることはない。
schema.sqlschema.yamlschema.hclschema.dbmlmodels/*.goorm loader · stdout- 目標スキーマ稼働中のデータベース
- 比較
schema drift裏づけ 0 1 2schema plan · --dry-runレビューの対象migrations lint関門schema apply · migrations up承認のあと → 稼働中のデータベース
ふつうの一週間にやること、3 つ
実際のコマンドと、実際の出力。終了コードはどれでも同じ意味を持つ。0 は成功、1 は検査結果が否定的だったこと、2 は使い方か接続の失敗。
安全でない、または破壊的なマイグレーションを main に入れない
- 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).
Ptah destructive-change verdictfailurePtah found destructive migration statements Safety verdict: destructive. Destructive changes: yes.
このアクションは対象データベースに対してマイグレーションを計画し、計画をコメントとして投稿し、データを破壊するステートメントがあれば失敗するチェック実行を 1 件書き込む。ptah migrations lint は指摘があれば 1 で、実行できなければ 2 で終了する。だから接続の失敗がクリーンなディレクトリに見えることはない。
レビューされていないマイグレーションをデータベースに近づけない
$ 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=
$ 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 はファイルごとにチェックサムを記録する。up、validate、lint は何かを行う前にそれを検査し、status も --verify-sum を付ければ検査する。CI がレビューしたものが、そのまま本番で実行される。
検索を止めずに埋め込みモデルを切り替える
run g1: cut_over, runningerror: not found: run g2run g1: cut_over, runningrun g2: boundary_captured, runningrun g1: cut_over, runningrun g2: backfilled, runningrun g1: cut_over, runningrun g2: caught_up, runningrun g1: cut_over, runningrun g2: indexed, runningrun g1: cut_over, runningrun g2: verified, runningrun g1: cut_over, runningrun 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 は、クエリが読んでいる世代の隣に新しい世代を作り、指定された埋め込みエンドポイントを呼び出し、検証し、承認がその計画を名指ししたところでクエリを切り替える。ベクトルそのものを Ptah が作ることはない。何かを実行する前に、データベースから何が出ていくかを plan が示す。
いま持っているスキーマがそのまま使える
目標スキーマを定義し、そこへ至るのに必要な変更をレビューする。どの入力元も、計画を立てる前に同じ内部スキーマへ解析される。ファイル、Go のアノテーション、稼働中のデータベース、スキーマを出力する任意のプログラム。ORM もその 1 つだ。
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 }
$ 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;
完全なスキーマを SQL、HCL、YAML のいずれかで標準出力に書き出すプログラムなら、どれでも入力元になる。Ptah はそれをシェルを介さず、制限をかけて実行し、空の出力は拒否する。
- GORM
- SQLAlchemy
- Django
- TypeORM
- Sequelize
- Hibernate
- 自作のスクリプト
リポジトリにコミット済みの、バージョン固定のレシピ · そのほかは SQL DDL を出力し、同じ契約を使える
$ ptah schema drift \ --schema-cmd ./scripts/export-schema \ --db-url sqlite://app.db No schema drift detected.
ルックアップ行を、テーブルの隣で宣言する
Ptah は宣言された行と稼働中のテーブルの差分を取り、元に戻せるデータマイグレーションを書く。ドリフト検査と直接適用の計画も同じ行を読む。行を更新または削除するデータマイグレーションには --allow-destructive が必要だ。
//ptah:schema:data · countries.yaml- 稼働中のテーブルとキーで差分を取る
INSERT · UPDATE · DELETE.up.sql / .down.sql · ptah.sum
ロール、権限付与、行レベルポリシーを、テーブルと一緒に宣言する
ptah schema security は稼働中のデータベースを読み、行レベルセキュリティのないまま、ロールに権限が付与されているテーブルを報告する。ptah viz --security は同じ指摘を宣言されたスキーマの上に示す。--fail-on any を付ければ、この指摘でプルリクエストを止められる。
GRANT … TO roleptah schema security- PRV01 · 権限付与されたテーブルに RLS がない
- ポリシーを宣言する
1 つのスキーマを、必要とするところへエクスポートする
出力先は 7 つ。Go のアノテーションからは 7 つすべてに、スキーマファイルからは HCL 以外のすべてに出力できる。機械向けの契約、人間向けのドキュメント、さらに 2 つのスキーマ形式。データベースには一切接続せず、エクスポート結果はどれもレビューを経る候補だ。
$ 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、api_expose は、マイグレーションエンジンには手を触れずに、公開する契約の形を決める。たとえば DECIMAL を文字列として出したり、列を契約から外したりできる。Protobuf のフィールド番号は、コミットするファイルの中に保持される。
モデルをプロジェクトにつなぐ方法は 2 つ。ツールは 1 組。データベースには触れない
モデルへの接続を誰が持つかを選ぶ。その先はどちらも同じ Ptah だ。ワークスペースがあれば 11 個のツールが使え、書き込みはすべてプレビューされてダイジェストと照合され、判断ごとに監査ログが 1 行残る。
- 読む → プレビュー → 承認 → 適用承認の対象は、ダイジェストで名指しされた 1 つのパッチだ。プレビューのあとでファイルが変わっていれば、適用は拒否される。
- Allow once · for this session · No答えるのは端末かクライアントの中。リポジトリのポリシーにできるのは、権限を取り除くことだけだ。
- データベースには書き込まないSQL を実行するツールも、データベースを変更するツールも、マイグレーションを適用するツールもなく、それを加える設定もない。
ptah migrations upは人が行う手順のままだ。 - 監査とバイト数ワークスペースがあれば、権限に関する判断はすべて
.ptah/agent-audit.jsonlに記録される。ptah assist では、各回答の最後に、プロジェクトの内容のうち何バイトがプロバイダーに届いたかが示される。
↑ up • ↓ down • / filter • enter submit
> 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.
完全なオープンソース。テレメトリーなし。ネットワークが届かない場所でも動く
その場から動かせないデータのために作った。規制対象、データ主権の対象、機密扱いのデータ。どのコンポーネントもセルフホストでき、開発元へ通信するものは 1 つもない。
- MIT完全なオープンソース
CLI、
ptah-compat、ptah-ls、Kubernetes Operator、GitHub Action、そしてこのサイト。リポジトリは 4 つ、ライセンスは 1 つ。有料プランに限定された安全性チェックはない。 - プライベートテレメトリーなし
Ptah が接続するのは、指定したアドレスだけだ。データベース、埋め込みエンドポイント、ptah assist が使うモデルプロバイダー、そして push または pull するレジストリ。利用状況の送信も、更新の確認も、アカウントもない。
- 自前デフォルトでデータ主権を守る
スキーマとマイグレーションディレクトリは、自前のレジストリに OCI アーティファクトとして置く。ECR、GAR、Harbor、GHCR、Docker Hub、あるいは素の
registry:2。Operator は自分のクラスター内で動く。認証情報は値ではなく参照で書くので、spec はそのままコミットできるファイルになる。 - エアギャップネットワーク不要
リリースアーカイブには ptah、ptah-compat、ptah-ls が入っており、どれも CGO なしでビルドされている。アーカイブは、ネットワークなしで
checksums.txtと照合できる。推論では、--releaseがディスクで持ち込んだ oci-layout ディレクトリを受け付ける。
推論の実行がプロバイダーに触れる前に、plan は何が外に出るかを正確に表示する。どのモデルか、どのエンドポイントとその宣言されたクラス、どの列、何行か。データ保護の審査が求めるのと同じ一覧だ。
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
OCI アーティファクトとして公開したスキーマに合わせて、データベースをリコンサイルする
Ptah Operator はタグをダイジェストに固定し、そのダイジェストだけを使う。アーティファクトを検証し、データベースを観測して、計画を公開する。デフォルトでは、その計画を名指しした承認があって初めて適用し、そのあとはジョブの終了コードを信用せず、データベースをもう一度観測する。破壊的な計画はデフォルトで無効だ。有効にしても、承認は計画のバイト列そのものに結びつけられる。
- タグをダイジェストに解決
- アーティファクトを検証
- データベースを観測
- 計画を公開
- 承認
- 適用
- 再び観測
- 05
- 破壊的な計画はデフォルトで無効。有効にすると、承認は計画のバイト列そのものに結びつく
- 07
- ループが信用するのはデータベースで観測したものであって、適用ジョブの終了コードではない
お使いのデータベースで何が使えるかを確かめる
エンジンは 10。選ぶと、Ptah がそこで何を管理するか、知っておくべき制限、どのリリースラインをテストしているかがわかる。
テーブル、ビュー、関数、拡張、カスタム型、ロール、行レベルセキュリティを管理する。
14 – 18Ptah 認定13サポート終了後もテストスキーマの変更。ALTER TABLE で表せない変更には、テーブルの再構築を自動で計画する。
3Ptah 認定MySQL の DDL の規則に沿った計画。インデックス、制約、外部キー、そして MySQL が実際に受け付ける SQL。
8.4 · 9.7 · 26.7Ptah 認定MariaDB 固有の DDL、インデックス、制約を、MariaDB 自身の方言の規則で計画する。
10.11 · 11.4 · 11.8 · 12.3Ptah 認定スキーマの変更と行レベルの TTL ポリシー。CockroachDB の規則を加えた PostgreSQL の経路で計画する。
25.4 · 26.2 · 26.3Ptah 認定スキーマの変更、ロール、行レベルセキュリティを PostgreSQL の経路で扱う。
2024.2 · 2025.2 · 2026.1Ptah 認定T-SQL のテーブル、フィルター付きインデックス、稼働中のカタログの読み取り。
2019 · 2022 · 2025Ptah 認定スキーマの読み取り、計画、バージョン管理されたマイグレーション。
21 · 23Ptah 認定エンジン設定とソートキーを持つ MergeTree テーブル、マテリアライズドビュー、TTL ポリシー。
26.3 · 26.7 · 26.8 · 26.9Ptah 認定24.10サポート終了後もテストPostgreSQL インターフェースを通したスキーマのワークフロー。Spanner のエミュレーターに対してテストしている。
サーバーの機能を確かめる
検出したバージョン、そのサポートレベル、Ptah が計画に使う機能プロファイルを表示する。
$ ptah db capabilities --db-url "$DATABASE_URL"
CI に裏付けられた互換性の検査
自動化された検査が master へのプッシュのたびと毎晩実行される。Ptah 認定のリリースラインは 28。CI でテストし、検査した機能の範囲で Ptah が責任を持つラインだ。組み込みの SQLite と Spanner のエミュレーターは、稼働中のサーバーとしてではなく、そのものとして宣言している。
サポートの根拠を見る →ptah はコマンドラインで。ptah-ls は Go のアノテーション向けにエディタで。GitHub Action は CI で、Kubernetes Operator はクラスターで。Go パッケージは ptah.run から自分のコードで使える。
ptah-compat は Atlas と同じ形のコマンドを受け付け、その裏で Ptah の挙動を実行する。Ariga とは無関係の、独立したクリーンルーム実装。Atlas 互換 →