Authorize Feature effects and protected input at operation boundaries

Remove non-MCP activation gates without expanding legacy grants. Bind resource resolution, background Job attenuation, protected prompt provenance and repeated model supply to current Host authority. Align generated contracts and regression fixtures.
This commit is contained in:
2026-10-28 11:44:34 +09:00
parent 618880d38d
commit 960a1b4364
75 changed files with 13191 additions and 1143 deletions
+18
View File
@@ -12,6 +12,24 @@
委譲では親が実際に委譲できる範囲を上限にする。読取り、書込み、コマンド実行、観測、レビュー、統合を混同しない。探索で対象を発見できても操作権限を得たことにはならない。
### Featureの構成と実操作
Featureの有効化、Tool・service・hookの登録、セッション内状態の初期化は権限を発行せず、それ自体に一律の起動認可を要求しない。外部への作用、保護データの読取り・モデルへの供給、購読・背景処理の開始は、起動中でも通常操作と同じ操作名と実対象資源で認可する。認可の要否をFeature単位のboolや起動専用の許可一覧へ置き換えない。
操作名は完全一致で扱い、親の名前から配下操作への許可を推定しない。Workspaceのcollection操作の許可は、個別のTicket・Subject・Worker等への許可ではない。資源の種類を跨ぐ包含を認めず、パスの再帰範囲などの包含は所有する型の契約だけに従う。保存済みの旧Feature・Workspace grantは読取り互換のために保持しても、新しい操作や資源の包括許可へ変換せず、基礎権限を暗黙に追加しない。
MCPはT-787の移行対象外とする暫定例外であり、既存の `mcp` 起動認可と `PermissionResource::Feature` を保持する。discoveryは外部プロセスを起動するため、既存の拒否を共通コード整理で失わせない。この例外を他のFeatureへ拡張せず、MCPの各呼出し・restoreに新しい操作認可が整備されたとも扱わない。
### 認可境界と継続実行
認可はHostに束縛された主体から、アクセス・供給・作用の直前に行う。共通のFeature内部処理やHost/provider境界を使い、Tool・WIP・typed service・起動・restore・hook・背景処理の入口差で省略しない。providerの所有・scope・capability・CAS等の検査は操作権限と合成する別の制約であり、互いの代替ではない。信頼されたFeature実装が境界を呼ぶ責務を持ち、任意コードの自動捕捉やsandboxを保証するものではない。
起動への必須性は認可の要否から分離する。必須の保護処理が拒否された場合は起動を明確に失敗させる。任意処理は未開始・利用不可を診断し、空データや成功として隠さない。拒否されたデータを先に読んだりモデルへ渡したりせず、認可拒否と通信障害を区別する。購読・背景処理の次のアクセスや配送、保存済み入力の再供給でも現行権限を検査する。登録や履歴の復元は再認可ではなく、取消要求の受付は停止確認でもない。
Subjectの権限未設定は保護操作の拒否であり、ローカルな構成の拒否ではない。Subject未接続Workerの既存Backend/provider契約と、明示的なstandaloneを区別し、接続障害や失効した接続をstandaloneとして迂回しない。主体の本体所有、委譲元、再委譲上限、現行policy、失効をHostで再確認し、保存済みの認可結果や共有clientから継続権限を推定しない。
Subject Jobの実効権限は、対象Subjectに束縛されたJobの限定grantと現行Subject policyの積集合に限る。Job種別のgrant、凍結された起点、現行attempt・入力digest・期限・writer等の制約も維持する。Memory consolidationのgrantをJournalや一般Subject操作へ流用せず、権限未設定・旧Jobの起点欠落・grant不一致で一般Worker互換へfallbackしない。Job結果の保持や過去の入力の存在は、新しい読取り・供給・作用の許可ではない。
## 履歴とLLM入力
LLMの生成に影響する入力は、リクエストへ加える前に履歴へ確定し、永続化形式からリクエストを再構成できるようにする。揮発的な入力の差込みでは、後のターンから発言やツール呼出しの根拠を追えなくなる。
@@ -0,0 +1,79 @@
# T-787 操作認可境界の棚卸し
## 観測範囲
2026-10-28、HEAD `618880d38ded5e501d7c926006d1869282f06892` に対する共有作業ツリーの未commit差分を読んだ記録。T-787の本文とAGENTSの文書管理・検証規則を基準にした。実装とテストは並行して変更中であり、この記録は受入条件の充足、テスト成功、全入口の保護完了を示さない。将来の仕様正本や維持対象のAPI一覧ではなく、一律gate撤去で到達可能になる境界の調査証拠として残す。
共通の判断基準は [正本・権限・実行の原則](../principle/authority-and-history.md#featureの構成と実操作) に置く。下表の操作名は完全一致の例であり、`ticket` が `ticket.create` を含むという意味ではない。`Workspace` 資源はcollection操作用で、別kindの個別資源を含まない。操作名を列挙した行も、列挙以外を許可するものではない。
## Feature・経路別の境界
資源名は `crates/server-api/src/authorization.rs::PermissionResource` のvariantを指す。共通境界欄は読取り時点で確認した呼出し位置であり、provider欄の既存検査とは別の制約である。通常の登録はローカル処理であり、表の外部処理が起動に必須かどうかとは分離する。
以下、`WB/` は `crates/worker/src/feature/builtin/`、`WS/` は `crates/workspace-server/src/server/`、`W/` は `crates/worker/src/`。
| Feature・経路 | 操作例と実資源 | 共通境界・別入口 | 合成するprovider側の検査 | 起動必須性・拒否時の扱い |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| task | 保護された外部操作なし | `WB/task/mod.rs` のTaskStore・Tool登録・履歴replay | セッション内の入力検証・保存契約。Subject policyを要求しない | ローカル初期化。権限未設定でも起動・操作・restore可能であるべき |
| image・通常のファイル操作・Bash | `filesystem.read`、`filesystem.write`、`bash.execute` / `Workdir` のID・path・再帰範囲 | `W/authorized_workdir.rs` のsession wrapper。Toolごとの認可だけに頼らない | attachment選択、provider scope、解決済みpath・symlink、capability、明示command grant | Tool登録だけなら外部読取りなし。ViewImageの読取り・Workdir由来の起動入力は別途検査対象。画像bytesの再供給までの証明は未確認 |
| web | `web.search` / `Network` のprovider endpoint URL、`web.fetch` / 各取得・redirect先URL | `WB/web.rs::WorkspaceWebAuthorizer` → `crates/tools/src/web.rs`。検索のsecret取得・network前、fetchのDNS・network前にcallback | 公開HTTP(S)・private/local拒否、credential禁止、redirect・byte・timeout上限、secret provider | 任意Toolの登録でsecret読取り・通信をしない。要求時の拒否を通信や空の検索結果に置き換えない |
| sub_worker | `subworker.spawn`、`subworker.send_input`、`subworker.stop`、`subworker.observe` / Hostで束縛した親runtime/worker+子nameの `SubWorker` | `W/spawn/tool.rs`、`WB/manage_worker.rs`、`WB/worker_observation.rs` → bound-check | 親の委譲scope、明示command、子所有、name予約、Reviewer capability、現行委譲上限 | 登録はローカル。spawn・入力・観測・停止時は実操作。standaloneのローカル親所有child互換を他のWorker制御へ広げない |
| flow | ローカルなFlow状態遷移にFeature起動権限なし。verifierのWorkdir読取りは `filesystem.read` / `Workdir` | `WB/flow_transition.rs` のcoordinator・固定capture・read-only internal verifier | Flow graph・typed verdict、Session commit、read-only wrapper。verifierにはWorkspaceやBashを渡さない | 登録・復元はローカル。verifier実行やファイル読取りまで「ローカルだから無認可」と一般化しない。LLM/provider接続の独立検証はこの読取り調査では未確認 |
| worker・worker observation | `worker.list`、`worker.create`、`worker.observe.list` / `Workspace`。`worker.send_input`、`worker.notify`、`worker.cancel`、`worker.stop`、`worker.restore`、`worker.remove`、`worker.observe` / 正確な `Worker` | `WB/manage_worker.rs` のcontrol/lifecycle service、`WB/worker_observation.rs` | control/resource grant、Runtime/Worker所有・状態、観測範囲、削除・restore条件、Subject delegation専用経路 | 登録でWorker作成・復元を行わない。既存grantやWorker生存を操作認可の代わりにしない |
| workspace_worker_discovery | `worker.discover` / `Workspace` | `WB/workspace_worker_discovery.rs` のlist前 | bounded query、cursor、Workspace一覧のprovider契約。発見はcontrol/observeの付与ではない | 任意Tool。拒否時は下流一覧取得前に失敗 |
| manage_workdir・workdir_catalog | `repository.list`、`workdir.list`、`workdir.attachment.list` / `Workspace`。`repository.read`、`workdir.create` / `Repository`。`workdir.read`、`workdir.attach`、`workdir.delete` / `Workdir`。`workdir.attachment.read`、`workdir.detach` / `WorkdirAttachment` | `WB/manage_workdir.rs` と `manage_workdir/wip.rs`。detachはHostがaliasからconnection IDを束縛 | provider scope、attachment接続CAS・release fence、稼働占有・削除条件、repository materializer・既存資源grant | 登録はローカル。設定済attachmentの準備と任意の作成/解除Toolを区別。解除と削除を分離し、古いalias観測で別接続を解除しない |
| workspace_config | `workspace_config.attach`、`workspace_config.observe`、`workspace_config.read`、`workspace_config.commit` / `WorkspaceConfig` のpath・再帰範囲 | `WB/workspace_config/backend.rs::authorize_post` → 共通HTTP送信。commitはroot entrypoint変更と各path、renameの両側を検査 | config tree所有・path検証、observed content/CAS、commitのatomicity | WIP登録はローカル。attach/read/observe/commit要求が保護操作であり、FeatureをONにしただけでは許可しない |
| drive | `drive.capabilities`、`drive.root`、`drive.search` / `Workspace`。`drive.metadata`、`drive.list`、`drive.read_text`、`drive.read_chunk`、create/update/delete/relocate/uploadの各完全名 / `DriveEntry`。`drive.request_status` / `DriveRequest` | `WB/drive/backend.rs` のtyped backend。Tool・WIPは同backend、chunk/imageは各読取り、relocateは元entry・移動先parentを検査 | Drive grant、entry/version/hash、chunk条件、mutation CAS、request receipt・upload条件 | 登録でroot取得をしない。任意操作の拒否は下流取得/変更前。DriveはWorkdirでもfilesystem権限でもない |
| ticket | `ticket.create`、`ticket.query`、`ticket.list`、`ticket.tag.list` 等 / `Workspace`。`ticket.read`、`ticket.edit_item`、`ticket.case.update`、`ticket.assignment.answer`、`ticket.implementation_result.write` 等 / 個別 `Ticket` | `WB/ticket.rs::WorkspaceHttpTicketBackend`、`ticket/assignment_tracking.rs`、`assignment_public.rs`、`notification_diagnostics.rs`。Tool・WIP・typed serviceを照合 | authenticated actor、対象canonical化、assignment/event CAS、operation key・exact replay、対象資源条件。relationは両Ticketを検査 | 登録・service公開だけでは起動認可なし。createは `ticket.create` のみで旧 `ticket` の追加要求なし。起動prompt投影の読取りも保護操作 |
| objective | `objective.query`、`objective.create` / `Workspace`。`objective.read`、`objective.edit`、`objective.set_state`、`objective.link_ticket`、`objective.unlink_ticket` / `Objective`。リンク情報取得の `ticket.read` / `Ticket` | `WB/objective.rs::WorkspaceHttpObjectiveBackend`、native Tool/WIP adapter | Objective/Ticket対象検証、Backendの更新条件。関連Ticket本文の読取りをObjectiveの権限に含めない | 登録はローカル、明示操作時に認可。関連情報の追加読取りまで証拠を照合する必要あり |
| merge_request | `merge_request.open` / `Ticket`。`merge_request.read`、`merge_request.readiness`、`merge_request.review`、`merge_request.complete` / `MergeRequest` | `WB/merge_request.rs::MergeRequestTool` とnative WIP dispatch | repository selector、source・Ticket digest・snapshot、review capability、actor、integration evidence | 登録はローカル。操作認可は承認・統合の実証を代替しない |
| orchestration | 下位の `ticket.read` / `Ticket`、`worker.create` / `Workspace` 等を合成。独立したFeature起動権限なし | `WB/orchestration.rs` → TicketServiceとWorkerLifecycleService | 現行Orchestrator assignment、Ticket targetから導くattachment access、Profile/Flow解決、資源finalization | service依存の登録とspawnの実操作を分ける。Toolだけでなくserviceを直呼びする経路の作用0も照合対象 |
| memory(Workspace・Subject) | `memory.query`、`memory.document.read`、`memory.document.update` 等 / legacy `Workspace`。Subject側は `memory.query`、`memory.read`、`memory.changes.list`、`memory.resident.read`、`memory.resident.supply`、`memory.staging.write` 等 / Host束縛の `Subject` | `WB/memory.rs` のtyped dispatch、`WorkspaceSubjektivResidentSummarySource::fetch`、`load_initial_checked`・refresh/restore/pre-requestのsupply検査 | Subject本体所有・現行policy、Memory change/proposal条件、Job grant、履歴へのcommit・既存source provenance | Subject resident contextは必須処理、Workspace summaryは任意処理。必須拒否は起動失敗、任意拒否は未開始診断。読取り許可は再供給の許可ではない |
| memory extraction・surface lifecycle | `memory.extract.read`、`memory.extract.supply`、`memory.extract.commit`、`memory.staging.write`、`memory.consolidation.request`、`memory.surface.supply` 等 / 接続に応じた `Subject` またはlegacy `Workspace` | `WB/memory_lifecycle.rs::MemoryExtractionAuthority`、`memory_surface_lifecycle.rs`、Host側Job要求 | 凍結input範囲、抽出cursor、stage/commit条件、Subject/Workspaceの分離、attempt・writer fence | hook/task登録はローカル。任意背景処理でも開始・各読取り・モデル供給・commit・次の要求で再検査。取消受付と停止完了は別 |
| Subject Journal・Subject Job入力 | `journal.startup.read`、`journal.startup.supply`、`journal.compaction.read`、`journal.compaction.supply`、`journal.recording.request`、`journal.job.input.read`、`journal.job.input.supply` 等 / Host束縛の `Subject` | `WB/journal.rs`、`WB/backend_job_input.rs`、`W/subjektiv.rs` → `WS/subjektiv_journal.rs`、`subjektiv_job.rs`、`subjektiv_memory_journal.rs` | domain grant、現在の本体、Job immutable batch・origin・attempt・digest・期限・writer、receipt・journal位置 | startup grantがない構成は読取りを開始しない。構成されたstartup contextは必須、recording/compaction補助は各拒否・unavailableを診断。Job入力は各モデル要求とrestore後にも再検査 |
| MCP(暫定維持) | 既存の完全名 `mcp` / `Feature` | `W/feature.rs::authorize_requested_manifest`、controller discovery、既存activation guard | 既存stdio設定・プロセスspawn/終了・Tool登録・cleanup/retry・restore契約 | discovery前の既存拒否を保持。他Featureの新原則を用いて既存gateを削除しない。新しいserver/Tool/resource別認可や常時接続は対象外 |
## 旧grant・Job・文書の整合
差分の `ScopedPermission::covers` は操作名を完全一致で比較し、resource containmentはkindを跨がない。`Feature` variantと旧トップレベル名の保存形式は残るが、MCP以外の旧Feature grantは新しい操作の権限にならない。旧Workspace grantも個別Ticket/Subject/Worker等へ展開しない。schemaの自動書換えや全Subjectへの基礎権限付与を移行の説明にしない。
`WS/subject_authorization.rs::job_permissions` はJournal/consolidationの限定操作と同じSubject資源に絞り、現行policyとの積集合を取る。`active_grant`、凍結originの現在の本体所有も検査する。一般Jobは空のSubject権限であり、旧Jobのorigin欠落は拒否される。これはJob grantだけで許可する契約でも、Subject policy全体をJobへコピーする契約でもない。権限未設定・domain不一致・失効を非Subject Worker互換へfallbackさせない。
調査時点の維持文書 `docs/principle/`・`docs/development/` には、一律のFeature起動認可を要求する旧説明は見つからなかった。principleへ明示的な境界原則を追加した。`docs/report/` は過去の証拠であり、旧状態の記述を現行仕様に書き換えない。所有コードのコメントについては、`W/feature.rs` と `server-api/src/authorization.rs` の差分がMCP限定・操作境界へ更新されている。`worker-runtime/src/worker_backend.rs::saved_subjektiv_authorization_fixture_grants_only_exact_feature_activation` は依然 `subjektiv`・`workdir_catalog` の旧Feature名だけを許可するfixtureであり、新しいSubject resident操作・bound-checkの実接続証拠にはならない。
## 生成契約の所有とscope
実際の出力は `web/workspace/src/lib/generated/` と `openapi/server-api.json` である。付与された `apps/web/src/generated/` は既存出力先と一致しないため、新規folder作成・生成物コピー・手修正を行わなかった。所有者は `crates/server-api/examples/` と `src/repository_openapi_typescript.rs`、ts-rs系は `src/lib.rs` の対応関数である。
- OpenAPIの生成・照合は `cargo run -p server-api --example export_openapi` と `cargo run -p server-api --example export_openapi -- --check`。既定の書込み先は `openapi/server-api.json`。
- OpenAPI closureからの出力は `cargo run -q -p server-api --example generate_repository_openapi_types`、`generate_workdir_api_types`、`generate_runtime_api_types`、`generate_worker_launch_api_types`、`generate_ticket_api_types`、`generate_companion_api_types`。各exampleの `-- --check` は既存出力との照合。出力名はそれぞれのexampleが参照する定数を正本とし、一般的な `generated/` へredirectしない。
- Memoryの出力は `cargo run -q -p server-api --features typescript --example generate_memory_api_types > web/workspace/src/lib/generated/memory-api.ts`。同様のts-rs系は `generate_auth_api_types`、`generate_skill_api_types`、`generate_repository_access_types`、`generate_drive_api_types`、`generate_legacy_typescript`。stdout出力と直接書込み型のexampleを混同しない。
- `server-api/src/lib.rs` の `generated_worker_launch_api_contract_is_current` はOpenAPI closure出力を、`generated_memory_api_contract_is_current` はtypescript feature時のMemory出力を照合する。これらのCargo testはこの調査では実行していない。
- 追加された `authorization/bound-check` と既存 `authorization/check` は `openapi = false` の内部契約。`PermissionResource` のts-rs deriveがあることだけから、全てのenumが既存Web artifactへ出力されるとは判断しない。新variantの影響するschema closureと実際の生成差分は所有者で確認する必要がある。生成物が古いとも差分不要とも、この読取りだけでは断定しない。
## 未確認の接続・coverage
以下はコード読取りから必要と判断した確認範囲であり、テスト失敗を観測したという意味ではない。
- task-onlyの起動・direct install・restoreでFeature認可HTTPが0、Ticket Tool/WIP/serviceで未付与・範囲外・失効時に下流作用0、許可時に実作用完了を同じ接続で確認する。
- providerの拒否、delegation失効・policy縮小・Subject本体交代、attachmentの入替え競合を実Host接続で確認する。fake clientで認可callbackを数えた証拠だけでは代替できない。
- 初期resident入力の拒否時の読取り0・モデル供給0、許可時の取得・commit、optional Workspace summaryの未開始診断と通信障害分類を区別する。`load_initial_checked` が初期化で呼ばれる差分はあるが、その読取りだけでライフサイクル全体の成功を主張しない。
- 保存済みpromptのsourceが設定変更等で除去されたrestore、同じbodyのcache再供給、Journal compactionの凍結済み参照、Subject Jobの凍結入力にも現行権限が適用されるか確認する。source objectが残る場合のcallback成功だけでは除去後の経路を証明できない。
- Journal/consolidation Jobの限定grantと現行policy、origin欠落の旧Job、一般Job、current attempt・writer・期限拒否の組合せを確認する。Job入力のpre-request hook追加は、全Job操作のgrant整合の証明ではない。
- 継続処理について取消要求後の停止確認、途中失敗cleanup、停止後の追加配送・読取り・作用0を確認する。登録時の拒否テストで継続中の権限変更を代替しない。
- MCPのallow/deny、拒否時discovery spawn0、stdioの発見・各呼出し・restore・cleanup/retryを既存の粗い認可のまま回帰確認する。本Ticketの変更から新しい各呼出し認可を推論しない。
- 旧保存grantを読めること、新しい操作へ暗黙展開されないこと、明示standalone・非Subject互換・Workspace障害が区別されること、生成契約の整合は実接続/生成チェックが別途必要。
この調査でCargo build/testや生成処理は実行していない。文書のみの静的検査は実装の受入判定を代替しない。
## 同日・候補安定後の追補
上記は棚卸し時点の観測であり、以下はその後に実装担当が行った検証である。独立レビューの判断とは分ける。
- `worker-runtime/src/worker_backend.rs` の旧Feature名fixtureを、Subjectの実操作とbound targetを検査するfixtureに更新した。task-onlyのFIFO・receipt replayはHost呼出し0、pending head修復はresident/Journal取得と保護入力checkpointを検査する。既存headの再修復では保護入力を再取得しない。
- `worker.rs` の `protected_input_tests` はcached prompt、source除去後restore、fork・compaction・rewind、未知provenance拒否とhook後の再検査を扱う。Journalはstartup/compactionを区別し、空payloadは保護入力として偽装しない。追加の `backend_job_input.rs` とcontroller結合テストは、凍結Job入力を各モデル要求前に再検査し、拒否時にモデル要求・結果送信が発生しないことを確認した。
- `server/tests/subject_authorization_tests.rs` の署名Host接続テストで、unset task、MCPのdiscovery前拒否、Ticket作成の下流作用、alias正規化、provider scopeとの積集合、attachment再接続、本体交代を確認した。`subjektiv_jobs_tests.rs` は限定Job grantと現行policy、origin/attemptの失効を検査する。これは任意のFeatureコードを自動監視できるという主張ではない。
- 安定候補でroot `cargo check`、変更crateの通常suite(`server-api`、`tools`、`worker`、`worker-runtime`、`yoi-workspace-server`)、`server-api --features typescript` のsuite、変更crateのrustdoc生成、format・差分・状態契約名検査が成功した。restoreを含むsuiteにはcheckout内 `target/restore-test-environment/` 以下の専用config/data/runtimeを使用した。最初のServer再実行は隔離環境変数不足でpreflight失敗し、設定後の全suite再実行が成功した。preflightを削除して通してはいない。
- OpenAPIと影響する各OpenAPI closure出力は所有exampleの `--check` で一致した。Webの型検査と変更型を使う関連APIテストが成功した。描画変更はなく、browser/E2E・稼働環境更新は実施していない。通常suiteで元から明示選択のignoredテストを実行済みとは扱わない。
件数・生ログ・source識別はMRの検証記録に置く。これらの成功だけで受入条件の網羅、正式承認、統合、Ticketの完了を推論しない。