Files
yoi/docs/report/t725-drive-connections.md

76 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Drive共有の接続不足と検証(T-725、2026-10-19)
## 判断の基準
基準 `adfa42297352c5a64fe8100969aad6fda4df8a81` と着手HEAD `cea0225a6e2d9e23c59a7d4a453f99bc2e1eba97` を比較した。差分81 filesはSession保存、Journal、Ticket公開操作、復元親の子操作等であり、以下のDrive保存/API/Worker/Webテストに差分はない。Cargo.lockの `wip-{client,http,protocol,text-view}` はすべてcrates.ioの0.2.0で、ローカル差替えはしていない。
検証したテスト実装は専用branch `work/T-725-drive-connections` の `ec6c5bed`。以下の結果はこの実装に対する証拠であり、対象developへの統合判断とは分ける。
T-721〜724の保存・公開操作を再実装せず、**別入口の返却値が次の操作へ渡る接続と、cleanupの所有境界**だけを追加した。製品修正は不要だった。Ticket一巡は実装・採用済みT-744の `ticket-regression-test-design.md`、通常suiteの判断はT-761の `regression-test-design.md` と分担する。関連Ticketの変更・再開はしていない。
## 優先順位と追加した契約
配置は `crates/workspace-server/src/server/tests/worker_drive_tests.rs`。以下はすべて通常のServer lib targetであり、ignoreやテスト専用featureは追加しない。
| 優先 | 利用結果/具体故障・コード根拠 | 既存証拠の限界/採否 |
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P1 | ユーザーuploadの返却node/mutationをWorkdirなしTools/WIPが読み更新し、owner/別Workerが同一資料を取得する。`server_drive::upload/read_text/public_node`、Worker `drive/backend.rs::read/mutate` とWIPの投影がずれると、個別成功でも本文や観測が別物になる。 | `workdirless_workers_share_markdown_through_current_read_and_write_grants` はWorker作成から別Worker読取り。`image_attachment_retains_original_bytes_after_drive_update_and_revocation` はuser uploadから画像添付。`native_create_and_write_use_client_managed_committed_requests_and_reject_stale_worker` はWIP作成から更新。この間の**user Markdown binary upload→Tools/WIP更新**を新規接続。 |
| P2 | Workerを止め、保持規則に従い削除・交換してもDrive文書の参照と内容が残る。`server/worker_operations.rs::stop/delete_from_plan`、`WorkerRemovalService` と `store::commit_worker_catalog_removal` はWorker権限を終え、Drive資料を削除してはならない。 | `cleanup_service_deletes_stopped_singleton_worker` は実Runtime停止・削除・交換、storeの `drive_grants_migrate_and_preserve_audit_through_revoke_worker_delete_and_restart` はgrant取消監査を確認するが、Drive資料の再取得はない。**元参照→交換Workerの明示grant後読取り**を新規接続。停止だけでgrantが失効するという別契約は追加しない。 |
| P2 | source Workdirを本当に削除した後も保存した資料を使える。`drive.rs` のSaveWorkdirはbytesをAPIへ渡し、`execute_workdir_removal`→Runtime→`working_directory/cleanup.rs` はmaterializationだけを削除する。誤ったroot削除やsource依存を検出する。 | `scoped_workdir_import_publishes_document_and_image_bytes_with_bounded_reads` は独立metadata、完全download bytes、画像添付を確認するがsourceが残る。**実provider削除の後に同node/mutation/bytes/receiptを読む**接続を追加。保存crateのbackup/GC行列は増やさない。 |
### 入力・fixture・観測点
- `user_uploaded_markdown_roundtrips_through_workdirless_tools_and_native_wip_at_one_identity`:既存Fixtureの実SQLite/FeatureStorage/LocalFileSystem、2 Workerの空Workdir links、明示read_write/read_only grantを使用。owner binary PUTはsize/SHA256と日本語Markdown bytesを渡す。Toolsとnative WIPの2経路で読取り・writeを行う。返却Workspace/node、uploadのlast_mutation_id、更新request ID=last_mutation_id、別Worker readのentry/本文、owner metadata、committed receiptのentry、mutation固定downloadの完全bytesを照合する。WIPは本番install/Inspect/Invoke、外部LLMは呼ぶとpanicする。Runtime起動とHTTP transportはfixtureだが、毎要求の署名・router・現grant・storeは本番。
- `worker_removal_preserves_drive_reference_for_explicitly_granted_replacement`:real embedded Runtime+既存deterministic controller backendでRepository/WorkdirなしWorkerが文書を作る。本番WorkspaceWorker stop→Runtime stopped→cleanup plan→delete_from_planを通し、Runtime/catalog除去、旧Workerの拒否を確認。別IDの交換Workerはgrant前拒否、read_only付与後に**元参照**で完全entry(latest_url、last_mutation_idを含む)と本文が一致し、元receiptもcommitted。保持対象Sessionは既存fixtureで準備する。lifecycle admissionは内部Backend test contextであり、browser/Worker-control認証の新証拠ではない。実controllerのshutdown/モデル挙動は代替されている。
- `source_workdir_removal_keeps_published_drive_bytes_and_receipt`:`ticket_worker_launch_tests::LaunchFixture` の既存認証付きloopback Runtimeを共有する。通常のServer Workdir createでlocal Git sourceからcleanなREADME.mdをmaterializeする。source sessionはLocalWorkdirSessionでread scopeと**実materializer lease**を渡す(Runtime session transportは代替)。DriveSaveWorkdirはalias `source`+論理path `README.md`、元bytes `clean\n`。session close/drop後、Server removal→認証付きRuntime HTTP→実materializer unlinkを通し、Removed/nonretryable・物理root不存在・registry不存在をassert。その後、新しいWorkdirなしDriveFeatureから元entry/bytes/receiptを照合する。`on_api`のcloneは同じstore/drive_host/Runtimeを共有し、別DBを作らない。
Workdir用に新しい起動基盤は作らず、既存Launch backendにcleanupの実materializerへの委譲を足し、materializer配置をfixtureのTempDir配下へ閉じた。listenerは既存Dropでabort、通信は既存有界timeoutを利用する。embedded Server adapterは仕様上Workdir create/status/cleanup非対応(`hosts.rs`)。最初のembedded試行はAttentionRequiredとなったため不採用とし、成功fakeへ変えず既存HTTP harnessへ切り替えた。
## 追加しなかった保証と既存assertion
パスは、断りがなければServer `src/server/tests/` 配下。
| 保証/採否 | 主な実在テストと観測、代替境界 |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| stale先勝ち:維持 | `simultaneous_worker_edits_publish_exactly_one_last_mutation_id_and_preserve_the_loser_conflict` は成功1件・敗者StructuredConflict・勝者本文/metadata、`stale_worker_observations_cannot_write_edit_relocate_or_delete_newer_last_mutation_id` はwrite/edit/move/delete拒否と本文/名前保持。nativeは前掲create/write testでvalidator conflictと勝者bytes。Tools/WIP→実署名router、Runtimeのみfixture。 |
| 現grant取消:維持 | `revocation_invalidates_cached_metadata_reads_writes_and_receipt_queries` はread/write/list/search/status等のdeniedと別Worker本文保持。`native_tree_does_not_index_entries_but_direct_inspect_and_invoke_use_current_grants` はcached invoke/refreshed inspect拒否。`drive_api_tests::drive_verified_upload_revoked_during_reception_cannot_commit_or_replay` はbody frameで取消、403/denied、uncommitted、list空。最後の注入はverified-source Extensionを使用し、署名ingress自体の証拠とは分ける。 |
| commit後応答喪失:維持 | `lost_mutation_response_is_resolved_by_receipt_without_resending_effect` と `lost_binary_upload_reply_reconciles_same_request_without_duplicate_publication` は本番router成功後の返答だけをtransportで落とす。committed/元node、POSTまたはPUT数1、元requestのGET照会、別Worker一覧1件とmutation一致をassert。単なる失敗応答fakeではない。 |
| Web接続:維持/明示実行 | `drive_web_roundtrip_tests::repositoryless_drive_shares_worker_bytes_and_enforces_current_read_only_grants_over_http` はRepositoryなしbootstrap、実HTTP、Worker grant/revokeの403、一覧entryと実blob bytesを確認。`repositoryless_drive_real_web_adapter_preserves_bytes_cas_and_latest_reference_lifecycle` は実HTTPと本番Deno adapter(parser/operation/grant)を接続する既存ignored test。Worker起動はfixture、browser DOM/Passkey loginは対象外。画面draft/取消等は別のsynthetic API browser suiteを重複しない。 |
| 保存/reopen/backup/GC:所有crateを維持 | `workspace-drive/tests/storage.rs::markdown_image_empty_and_folders_survive_restart_with_stable_identity` はDrive再open後のmetadata/本文/画像。`receipts_survive_response_loss_and_restart_without_double_mutation` はreceipt/replay一致・別actor拒否・list1件。`stopped_writer_backup_restores_ids_last_mutation_ids_receipts_and_sequence_with_blobs` は一致するDB+blobsの復元、元metadata/bytes/receiptと新ID採番。`src/tests.rs::saved_blob_before_metadata_failure_is_not_published_and_restart_collects_it` は未公開・uncommitted・再open回収、`commit_failure_rolls_back_metadata_and_receipt_together` はmetadata/receipt同時rollback。`collection_cannot_remove_in_flight_upload_or_selected_read` はin-flight保護。これらをServer全体再起動や実電源断保証に拡張せず、新たな保存障害行列は不採用。 |
## 故障検出試行と復元
以下は意図的なmutantであり、製品不具合の発見ではない。patchとログは `target/t725/` に保持し、製品sourceは復元した。
| 試行 | 注入と検出 | 実行結果(外側wall/libtest) |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `mutant-read` | `server_drive::read_text` の返却本文を `String::new()` にする。P1 filterの元upload本文比較が空文字≠日本語Markdownで失敗。 | exit101、64.156秒/0.41秒(build込み) |
| `mutant-cleanup` | `execute_workdir_removal` に誤ったDrive blob rootのremove_dir_allを挿入。実source削除後のfresh DriveReadが公開error「Drive storage or transport unavailable」で失敗。 | exit101、88.103秒/0.59秒(build込み) |
| 復元 | `server.rs` と `server_drive.rs` に差分がないことを検査後、Server全crateと3つの個別filterを実行。 | 下表の全成功。故障をテストから避ける製品修正はない。 |
開発中にはsource fixtureの未定義git helperによるcompile error、準備用Repository FK不足、embedded adapter非対応の試行失敗もあった。helperを増やさず既存clean READMEを使用し、最終fixtureは本番Server create+既存HTTP Runtimeを利用した。これらを製品regressionや故障検出成功には数えない。いずれも `cargo test --locked -p yoi-workspace-server --lib source_workdir_removal_keeps` の試行で、`source-removal-first` はcompile exit101/wall6.875秒、`source-removal` はFK失敗exit101/wall84.053秒/libtest1.64秒、`source-removal-fixed` は非対応判定exit101/wall56.519秒/libtest2.14秒、HTTP経路への修正後 `source-removal-http` は1 pass/wall83.709秒/libtest0.61秒。時間採取は `/usr/bin/time` がないためBash `TIMEFORMAT` を使用し、出力のなかった背景起動の試行はbuild成功に数えなかった。
## 実行区分・費用とT-761への接続
すべて専用checkoutで実行した単発実測であり、CIや全通常suiteの速度ではない。`wall` はCargo起動/build込み、libtest時間は実行のみ。既存dead_code warningsは継続。
| command(root) | 結果 | wall/実行秒 |
| -------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ---------------------- |
| `cargo test --locked -p yoi-workspace-server --lib worker_drive_tests --no-run` | 初回build成功 | 234.981/実行なし |
| `cargo test --locked -p yoi-workspace-server --lib worker_drive_tests -- --test-threads=4` | P1+Worker寿命追加時20 pass | 71.131/9.69 |
| `cargo test --locked -p yoi-workspace-server -- --test-threads=4` | 最終実装1210 lib+12 bin pass、既存3 ignored、doctest対象0 | 400.021/291.85(lib) |
| `cargo test --locked -p yoi-workspace-server --lib user_uploaded_markdown_roundtrips` | 復元後1 pass(Tools/WIP両方) | 0.994/0.72 |
| `cargo test --locked -p yoi-workspace-server --lib worker_removal_preserves_drive_reference` | 復元後1 pass | 4.458/4.20 |
| `cargo test --locked -p yoi-workspace-server --lib source_workdir_removal_keeps` | 復元後1 pass | 0.872/0.62 |
| `cargo test --locked -p workspace-drive` | 40 unit+13 storage pass、doctest対象0 | 4.934/0.21+0.09 |
| `cargo check --locked` | root通常compile closure成功 | 75.368 |
| `cargo test --locked -p yoi-workspace-server --lib drive_web_roundtrip_tests -- --ignored` | **明示**Web adapter1 pass | 1.051/0.75 |
最終module境界コメントの補正後にも同じ `worker_drive_tests -- --test-threads=4` を実行し21 pass(wall71.296秒、libtest9.75秒)を確認した。`cargo doc --locked -p yoi-workspace-server --no-deps` は成功(wall70.312秒)。`cargo fmt --all -- --check`(3.824秒)、`git diff --check HEAD`(0.004秒)、`env -u LD_LIBRARY_PATH deno run --allow-read --allow-run=git tools/check-state-contract-names.ts`(0.189秒、0 unapproved/0 stale exceptions)、2文書の `deno fmt --check` も成功した。文書の相対参照先は実在する。
T-761の通常runnerはCargo通常lib targetを列挙するため、追加3件は同targetのままで選択される。3件の逐次libtest実測合計5.54秒(単独Cargo wall合計6.324秒)。既存runnerのshard/並列負荷やcold build増分は未測定であり、全体時間へ単純加算しない。最も重いWorker寿命4.20秒は実永続Runtime停止・保持・再作成を検出する費用で、既存DB単体や共有2Workerの成功では代替できない。通常suiteへの追加はこの3件だけとし、Web adapterの既存ignoreは変更しない。
全Workspace `deno task test`、browser、製品全体E2E、実モデル判断、物理電源断、Server/Runtime全体再起動、CI実行は未実施。新規CI jobは追加しておらず、CIへの自動配線・実行済みとは扱わない。実モデルの調査・待機・完了判断を固定LLM応答で証明せず、追加したのは機構の接続保証である。
証拠は `target/t725/*.log`・`*.time` と2つのmutant patch。報告書へ生ログ/秘密は転載しない。稼働環境・対象develop・Ticket caseは変更していない。