
PART 3成果を保存・共有する3.3
Project Database — 構造化した成果を保存・検索する
schemaの作成から行の保存・検索、WebUIでの確認(総行数・ページ送り)、Session外からの操作、SQL制限、migration競合、書き込み結果不明時の復旧までを説明します。
Project Databaseは、顧客記録、調査結果、在庫など、agentと人間が再利用する構造化データの保存先です。行の背景や判断はShared Documentsに残します。通常Projectごとにserver管理のCloudflare D1 databaseを持ち、最初のmigrationを適用した時点で作成されます。schemaを読むだけでは作成されません。ローカルSQLiteファイルや、HTMLに自動接続されるbackendではありません。
前提と権限
| 操作する人 | 入口 | 条件 |
|---|---|---|
| Session内のagent | chat db schema / query / migrate | active Projectをcoverageに持つrunning Session。schema/SELECTは現在の横断閲覧権限でも利用可能 |
| Session外の人間・外部agent | aachat db schema / query / migrate | 本人のログインと、そのProjectのactive membership |
| 人間のWebUI | ProjectのDatabaseタブ(閲覧のみ) | Project member |
Project roleがViewerならschemaとSELECTを読めます。CollaboratorとAdminはmigration、INSERT、UPDATE、DELETEも実行できます。これは被覆Projectと人間向け操作のroleです。横断閲覧を許可されたSessionは対象のmembershipなしでschemaとSELECTを読めますが、変更SQLとmigrationはCROSS_PROJECT_READ_ONLYで拒否されます。人間向けAPIではTeam ownerでもProject membershipがなければ操作できません。
Projectがactiveでない場合、DatabaseのCLI操作は受け付けません。被覆Projectと人間向け操作ではArchivedは凍結扱いのエラー、Done(completed)とPlanningはproject_not_active、deleting中は見つからない扱いになります。再開するにはProjectをIn progressへ戻します(Projects)。WebUIのDatabaseタブも、active以外ではDatabase unavailable(Database browsing is available only for active projects.)と表示して行を表示しません。
agentへD1のcredentialを渡す必要はありません。
tableを作り、成果を1件保存する(Session内)
chat dbはSession内のコマンドです。Team、Project、Database ID、Sessionを選ぶflagはありません。schemaとqueryは現在のディレクトリ、migrateはmigration fileのpathからProjectを解決します。Project rootの外や曖昧なpathでは失敗します。
acme/customer-researchをSessionのcoverageまたは横断閲覧先にある実在Projectへ置き換え、まず現在のschemaを読みます。
cd aachat/teams/acme/projects/customer-research
chat db schema応答のdataにはschema_sql、schema_version、migration_count、head_migrationが入ります。被覆Projectではread-onlyのdb/schema.sql投影も同じschemaで、空のDBなら-- aachat-schema-version: root、migration後ならsha256:...のversionで始まります。
横断閲覧では$AA_WS_DIR/aachat/teams/<team>/projects/<target>へ移動します。AA_WS_DIRはworkspace rootです。対象のdb/やschema.sql投影は不要です。schemaを読み、既存の表・列にSELECTを送ります。以下のmigration・変更SQLの例は書き込み可能なProject用です。
db/schema.sqlは編集しません。編集すると同期されず、db/_errors.mdに診断が書かれて元の内容へ戻ります。schemaを変えるときはdb/migrations/<id>.sqlを作ります。以下は空のDBであることを確認してから、db/migrations/add_customer.sqlへ保存する例です。既存schemaがある場合は、parent headerへ現在のversion全体を正確にコピーし、既存tableに合わせてSQLを直します。rootを再利用しません。
-- aachat-parent: root
CREATE TABLE customer (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
status TEXT NOT NULL
);
CREATE INDEX customer_status ON customer(status);parent headerはbyte 0から始めます。BOMやNULを含まないUTF-8の通常ファイルを使い、symlinkは使いません。migration IDは英小文字・数字・_・-で、先頭は英小文字または数字、最大128文字です。ファイルの同期はSQL sourceの保存であり、DBへの適用ではありません。同期が済んだら明示的に適用し、行を書いて読み戻します。
chat db migrate db/migrations/add_customer.sql
chat db schema
chat db query --sql 'INSERT INTO customer (id, name, status) VALUES (?, ?, ?)' --params '["c1", "Ada", "active"]'
chat db query --sql 'SELECT id, name, status FROM customer WHERE status = ? ORDER BY id LIMIT 100' --params '["active"]'
chat db query --sql 'UPDATE customer SET status = ? WHERE id = ?' --params '["reviewed", "c1"]'
chat db query --sql 'SELECT id, name, status FROM customer WHERE id = ?' --params '["c1"]'期待結果は次のとおりです。
migrateはdata.dispositionがapplied(すでに適用済みならreconciled)で、新しいschema_versionとmigration_countを返します。続くschemaにtableとindexが現れます。queryはdata.result_set.columnsとdata.result_set.items(行の配列)、変更件数のchanges、last_row_idを返します。最初のSELECTはc1、Ada、active、UPDATE後のSELECTはc1、Ada、reviewedを返します。
各段階の実際のJSON応答を読んでから次へ進みます。同じ主キーでINSERTを繰り返しても新しい記録にはなりません。捨ててよい練習用の行ならchat db query --sql 'DELETE FROM customer WHERE id = ?' --params '["c1"]'で削除し、同じキーのSELECTで不在を確認します。
長いSQLやparameterは、--sqlの代わりに--file <path>または--stdin、--paramsの代わりに--params-file <path>(stringのJSON配列)で渡せます。queryは読み取り専用ではなく、変更SQLもそのまま実行します。
Session外から操作する
aachatにログインした人間や外部のcoding agentは、Sessionを使わずに同じDatabaseを操作できます。Session内ではこのコマンドは使えず、chat dbを使います。接続repoの外ではTeamを--teamで明示します。
aachat db schema customer-research --team acme
echo '{"sql":"SELECT id, name FROM customer WHERE status = ?","params":["active"]}' \
| aachat db query customer-research --team acme --stdin
aachat db migrate customer-research ~/aachat/teams/acme/projects/customer-research/db/migrations/add_customer.sql --team acmequeryは--stdinで{"sql": "...", "params": [...]}のJSONを受け取ります。migrateのFILEは、共通root(~/aachat/teams/<team>/projects/<project>/、AACHAT_HOMEで変更可)または接続repoにある、対象Projectのdb/migrations/<id>.sqlに限ります。source全体のdigestを送るため、未同期や同期後に編集したsourceは失敗します。- 権限は本人のProject roleです(ViewerはschemaとSELECTのみ)。
aachat project database <project> --team <team>はschemaのcatalogを読むだけで、SQLは実行しません。
queryとmigrationのSQL規則は異なる
両方ともSQLは最大100 KiBです。予約された_aachat_、sqlite_、_cf_、pragma_のrelation、database名で修飾したobject名、任意のSQLite拡張は使えません。
| 操作 | 許可する入力 | 主な制限 |
|---|---|---|
| query | SELECT、INSERT、UPDATE、DELETEのいずれか1文。RETURNINGとOUTPUTは不可 | placeholderは位置指定?のみ。parameterは最大100個のstringのJSON配列で、placeholder数と一致させる |
| migrate | 下記allowlistの1〜100文 | parameter bindingは不可。現在のparent versionが必要 |
queryでDDLやPRAGMAは実行できません。SELECT結果は最大1,000行かつdecoded 1 MiBで、超過は部分結果ではなくエラーです。必要なcolumn、WHERE、小さなLIMITを指定します。LIMITを指定するなら静的な非負整数を使い、placeholderや式にしません。queryのUPDATEとDELETEではORDER BYやLIMITを使えません。
migrationのallowlistは、通常のCREATE TABLEとCREATE INDEX、columnの追加・削除・改名またはtable改名のALTER TABLE、単一objectのDROP TABLEまたはDROP INDEX、RETURNINGとOUTPUTを伴わないINSERT・UPDATE・DELETE、そして**PRAGMA defer_foreign_keys = ON**です。このPRAGMAはmigrationだけの例外です。SELECT、任意PRAGMA、view、trigger、明示的なtransaction制御はmigrationで使えません。allowlistに含まれていてもSQLiteの全方言を許可するわけではありません。
WebUIで結果を見る
ProjectのDatabaseタブを開き、画面下のバーでtableを選んでcolumnと行を確認します。閲覧専用で、SQL editorや更新操作はありません。
- column見出しをクリックすると昇順・降順のsortが切り替わります。
- 1ページの行数は25・50・100行(既定100行)から選び、Previous / Nextで移動します。行数の選択は狭い画面では表示されません。
- 選択中tableの総行数を表示します。広い画面は
Rows 1–100 of 1,234のような行範囲と総数、狭い画面はTotal 1,234です。空のtableは0件、件数を取得できない・失敗したときはTotal unavailableと表示します。 - table、page、行数、sortはURLに保持されます。tableが9個以上あるときはChoose tableから検索して選べます。
- データが変わるとUpdates availableが表示され、押すと新しい行を読み込みます。Refreshでcatalogと行を読み直します。schema変更時はcatalogを再取得します。
空文字(Empty)、NULL、値の入ったセルは区別して表示されます。NULL以外のセルを押すと値の全文を開け、Copyでコピーできます。NULLのセルは詳細を開けません。
agentへtableを指定するときは、下部バーの選択中table名の横にあるCopy table referenceを押し、依頼文へ貼り付けます。Databaseのpathと正確なtable名をまとめてコピーします。
Database: aachat/teams/<team>/projects/<project>/db/
Table: "customers"Table行はJSON文字列です。tableは個別ファイルではありません。空のtableや行の取得失敗時も、tableを選択できていれば参照をコピーできます。参照にページやsort条件は含まれず、アクセス権も付与しません。
| 表示 | 意味と次の操作 |
|---|---|
| No database yet | 最初のmigrationを適用していません。agentにmigrationの作成と適用を依頼します。schemaを読むだけでは初期化されません |
| No tables yet | DBは初期化済みですがuser tableがありません。migrationでtableを作り、Refreshします |
| Table no longer exists | URLのtableが削除・改名されました。下のバーから現在のtableを選びます |
| This page is too large to display | 1ページが1 MiBを超えました。Show 25 rowsで25行に減らします |
| These rows exceed the Database viewer limit | 25行でも1 MiBを超えます。agentへcolumnや値を絞ったSELECTを依頼します |
書き込みを重複させずに復旧する
失敗時もstdoutに1つのJSON {"ok":false,"error":{"code","message",...},"data":...}が返ります。error.message、あればerror.hint、dataを読みます。chat dbの失敗ではdata.mutation_stateがrejected(受け付けられていない)かunknown(結果不明)かを示します。aachat dbではdata.change.stateのnot_appliedかunknownです。timeoutだけでは書き込み失敗と判断できません。
| 症状 | 次の操作 |
|---|---|
| Projectを解決できない・権限がない | Project rootのcwd、Session coverageまたは現在の横断閲覧権限、Projectのactive状態、roleを確認します。ファイルを移しても権限は増えません |
SELECTがDATABASE_NOT_INITIALIZED | 書き込み可能なProjectでは最初のmigrationを作成・適用します。横断閲覧先では自分のProject経由で人間または対象ProjectのLeadへ初期化を引き継ぎます。空schemaはD1を初期化しません |
| migration sourceが拒否された・見つからない | db/_errors.mdを読み、path、header、encoding、SQLを直し、同期が受理されてから適用します |
| sourceが同期と一致しない | 通常の同期が同じファイルを送り終えるのを待ち、同じmigrateを再実行します |
| 受理済みmigrationと別内容を同じIDで保存した | migration SQLは自動mergeされません。ローカルの内容はそのまま残ります。下書きを新しいmigration IDへ移し、元のpathを同期し直してから、現在のschemaから新しいmigrationを作ります |
| parent versionが古い | chat db schemaで現在のversionを読み、そのversion向けにmigrationを作り直します。1つのparentから進める子は1つだけで、force操作はありません |
| migrationの結果が不明・reconciling | 照合(reconciling)中はschemaの読み取りと次のmigrationが待たされますが、SELECTとDMLは続けられます。照合を待ってから、同じmigration fileでmigrateを再実行します。適用済みなら再適用せずreconciledを返します |
queryの結果がunknown | 同じINSERT・UPDATE・DELETEを再送しません。既知の主キーと現在値をSELECTで読んで、追加の書き込みが必要か判断します |
| DBの準備中・混雑 | 少し待って同じコマンドを再実行します。並列書き込みで回避しません |
| 結果が大きすぎる | columnと行を絞り、区切って読みます。途中までのデータを全件と見なしません |
| provisioningの競合 | 運用側の修復が必要です。SQLやcredentialを含めずにエラーを報告します |
ローカルのmigrationやschemaファイルを消しても適用済みデータは戻りません。即時rollbackやbackupの仕組みはありません。Project削除は受理済みqueryと未解決migrationの終了、物理DBの削除を待ちます。信頼境界とProjectsも確認してください。