merge: compose classification tags with Ticket case migration

This commit is contained in:
2026-10-11 02:57:45 +09:00
41 changed files with 6752 additions and 229 deletions
+49
View File
@@ -0,0 +1,49 @@
# Workspace Ticket classification tags
T-740 adds classification independently of Ticket state, close judgments, Orchestrator tracking, Worker lifecycle, repository grants, and MR evidence. T-739 owns case-state/close migration; T-743 owns the final Web management UI.
## Identity and names
`WorkspaceTag` contains `workspace_id`, immutable `id`, display `name`, `last_operation_key`, and `deleted`. Every operation resolves the ID within the route Workspace, never by display name or an unscoped numeric identity. A rename preserves links; a deleted tag is a tombstone, and recreating its name creates a new identity.
Names are trimmed with Rust Unicode whitespace trimming, then compared by Unicode lowercase. Display casing is retained. No Unicode canonical/compatibility normalization or locale-specific case folding is implied: canonically equivalent but differently encoded strings can remain distinct. Empty names, control characters, and names longer than 128 UTF-8 bytes are rejected. Active normalized names are unique per Workspace; deleted names are available for reuse. `ready`, `closed`, and other apparent control words are ordinary classification names.
Legacy `TicketMeta.labels` is not the classification authority and is not reinterpreted or removed by this change.
## Mutation and observation
- `GET /api/w/{workspace_id}/ticket-tags`: active tags ordered by stable ID; optional `after` ID and `limit` (bounded to 200). Continue with the last returned ID. A full page is not a promise of another page; continue until exhausted.
- `POST /api/w/{workspace_id}/ticket-tags`: `WorkspaceTagMutation` with `operation_key` and `action` (`kind: create | rename | delete`). Rename/delete require `tag_id` and the observed `expected_last_operation_key`; create/rename require `name`.
- `PUT /api/w/{workspace_id}/tickets/{id}/tags`: `TicketTagSetRequest` with `operation_key`, `expected_tag_ids`, and desired `tag_ids`. Add/remove multiple IDs by computing a desired set from the current detail/list/query projection. This explicit full-set comparison prevents silently overwriting another user's unrelated membership change. IDs are unique sets, not ordered lists for membership comparison; duplicate IDs are rejected. Exact replay retains the original array order as part of the original input. At most 100 active tags may be attached to one Ticket.
The tag's observed operation key compares the last committed change of that exact identity, including rename-back, before rename/delete. Membership compares the actual current active ID set, not a generic update number, timestamp, body digest, or state. An intervening change which restores exactly the same set is intentionally equivalent: the requested operation depends only on present membership, not on unseen membership history. An observed set containing a subsequently deleted ID is stale and fails, rather than implicitly dropping references.
SQLite immediate transactions serialize deletion and attachment. Delete atomically tombstones the tag and removes current membership; attachment rejects missing/deleted/foreign-Workspace identities. Neither outcome leaves an active dangling link. Foreign keys preserve Workspace/Ticket/tag identity integrity.
Operation keys belong to the Workspace classification mutation namespace. Authenticated identity is compared separately from mutable assignment/role context: reassignment does not make an identical receipt into another request, while the original full context remains in the audit. Exact replay compares the operation input, target, and authenticated source identity and returns the original stored response before stale/live checks; it does not repeat audit/event writes. Reusing a key for different input or identity conflicts. A replay is a receipt of the original result, not a fresh observation (its response can contain a subsequently renamed/deleted tag); reread detail/list before editing. Retry an uncertain request with its original key and input.
## Authorization and history
Browser and Worker operations use the same Backend tag store and existing Ticket Workspace authorization. The current shared Ticket policy permits authenticated Server users and authorized Runtime-forwarded Workers; tag management does not introduce an owner-only repository/secrets policy or a new membership/grant system. Runtime-forwarded sources are verified using existing source-proof checks, not trusted from arbitrary headers. Authenticated source attribution is retained in audit records; caller-supplied names/roles cannot grant authority. No local filesystem fallback or separate Worker tag namespace is provided.
Workspace mutations preserve an append-only audit with tag identity and display-name snapshots. Ticket membership changes preserve typed thread events with before/after identity/name snapshots. Deletion preserves tombstones and prior snapshots, so history still means the names and identities observed at the time, not today's display names. Historical events are not rewritten on rename or delete. An unchanged membership set records a receipt without claiming a `tags_changed` thread event. Explicit deletion of the entire Workspace purges its tag catalog, memberships, and receipts through the existing domain purge inventory.
Classification does not edit the Ticket's editable item content digest, state, close reason, resolution, assignment, or grants; it does not enter routing notifications or start/stop/delegate Workers. Close reasons remain a separate judgment contract even if a UI presents both using similar visual chips.
## Projections, filtering, and consumers
Domain `Ticket`/`TicketSummary`, human list/detail/query projections, `ShowTicket`/`QueryTicket`, and typed client responses expose `tags: WorkspaceTag[]`. Deleted tags are absent from current projections but remain in receipts/audit/history.
Ticket query/search takes stable IDs:
- `tag_ids`: all IDs must be attached (AND).
- `tag_ids_any`: at least one ID must be attached (OR), unless empty.
- `tag_ids_none`: none may be attached (NOT).
Nonempty groups combine with AND and with existing text/state/objective/relation/evidence filters. Unknown/deleted/foreign IDs never resolve to another tag: required IDs yield no matches, excluded IDs exclude nothing. Filters are bounded to 100 IDs per group. SQL filtering occurs before bounded candidate selection, then authoritative projections recheck membership. Pagination is not a database snapshot across requests; query-bound cursors cannot be reused with changed filters.
Tools expose Workspace tag creation/list/rename/delete and Ticket tag-set editing. The WIP native operations use the same tool/client boundary and permissions. OpenAPI is generated from typed ServerApi/domain schemas; browser Ticket types are generated from the canonical OpenAPI document. T-743 should use these public operations and explicit stable IDs, not invent Web-only tag state or depend on tag names for execution policy.
## Shared integration with T-739
Both branches touch Ticket operation enums, summary/detail construction, ServerApi query/detail contracts, tool projections, generated OpenAPI/TypeScript, and Ticket SQLite schema validation. Merge must compose migrations and current schema, not merely pick a higher schema version or overwrite one branch's baseline. Preserve tag tables, receipts, audit rows, tombstones and memberships through case-state migration. Re-run migration/equivalence tests and regenerate public contracts after composition. T-740 itself is buildable on the existing case-state model and does not import T-739's state/close migration.
+4 -4
View File
@@ -31,7 +31,7 @@ closeは案件の実作業拘束を終える一方、T-715の終端担当責任
## 旧保存データの移行
T-739 sourceではTicket component schema7→9を使用する。Workspaceは現developのauth/runtime connection移行89→90をそのまま保持し、案件移行90→91を追加する。旧89からはauth→案件の順に進み、現90からは案件移行だけを行う。component6からの既存移行も維持する。
T-739/T-740合成sourceのTicket componentはschema10(タグと案件判断)とする。旧schema7(六状態)・8(六状態とタグ)を明示的な移行入力として扱い、番号だけで旧保存形式を推測しない。Workspaceはdevelopのauth/runtime connection移行89→90をそのまま保持し、案件移行90→91を追加する。旧89からはauth→案件の順に進み、現90からは案件移行だけを行う。component6からの既存移行も維持する。案件のみのcomponent9は、表・列・FKとimmutable triggerの保存形式を検証したうえでタグだけを追加し、既存の終了判断・receipt・migration actor/key・handoffを再生成しない。偽のversion markerや欠けたschema objectは拒否する。
| 旧workflow state | 現行案件状態 | 終了理由 |
| --- | --- | --- |
@@ -40,14 +40,14 @@ T-739 sourceではTicket component schema7→9を使用する。Workspaceは現d
旧doneを成功の証拠に昇格しない。移行による終了判断はmigration actorを明示し、旧resolutionの説明を保持する。旧thread、completion/close receipt、担当履歴、MR証跡を改ざんしない。旧phase、explicit flag、status、queued_by/queued_at、resolutionをimmutable `typed_ticket_legacy_workflow_snapshot` に保存し、`legacy_handoff` として後続追跡/routing移行へ渡す。これは現行案件状態の第二正本ではない。移行だけで過去のWorkerや終了済み作業を起動しない。
component schema8は並行T-740のタグ契約が所有するため、この独立sourceは8を推測して9へ変換せず安全に拒否する。T-740との統合時には、タグschemaと案件schemaの明示的な合成、7およびタグあり8からの移行、再実行、タグ/監査/終了履歴保持を検証する必要がある。番号を分けただけでは合成完了と扱わない。
T-740のタグschemaを案件schemaへ合成する際、タグのID・名前・tombstone・membership、operation receiptの入力・認証済みsource identity・応答、audit/threadの観測時snapshotを保持する。タグ変化からclose/reopen/訂正やeditable content digestの更新を起こさない。旧7およびタグあり8からの移行、再実行、タグ/監査/終了履歴、FKとauth archiveの保持を合成fixtureで検証する。番号を分けただけでは合成完了と扱わない。
## 検証境界
- `crates/ticket/tests/case.rs`: Repository/MRなしの理由、再開/訂正、actor/ref/history、CAS/ABA、exact replay/異入力拒否、並行close、Workspace境界、transaction rollback。
- `crates/ticket/tests/migration.rs`: 六状態、旧thread/receipt/担当/MR参照の保持、理由不明、再open、schema8の安全な拒否。
- `crates/ticket/tests/migration.rs`: 六状態、旧thread/receipt/担当/MR参照の保持、理由不明、再open、タグあり8との合成と再実行。
- Workspace Store: 六状態×MRあり/なし、終端担当、旧queue/handoff、migration resumption、Workerを起動しない移行。
- Workspace API/authority/lifecycle: 偽装actor・無権限・他Workspace・旧route拒否、作業releaseと表示責任、reopen時の新routing identity、独立削除guardとWorkspace purge rollback。
- Worker通常Tool/WIP、Client、CLI、Web parser: 同じcase入力、モデルに見えるnullable観測keyと履歴、Backend以外のfallback禁止、stale失敗の伝達。
実経路統合・Orchestrator追跡の置換は後続単位の責務であり、以上の単独source検証をT-740合成や実環境更新の検証と混同しない。
実経路統合・Orchestrator追跡の置換は後続単位の責務であり、以上の合成source検証を実環境のmigration・更新や全経路統合検証と混同しない。
+49
View File
@@ -0,0 +1,49 @@
# T-740: composition with develop 71ab4c06
## Source reconciliation
The existing `work/T-740-workspace-ticket-tags` source at `e9397fa0` was clean. The requested develop commit `71ab4c06b9ae24ca4daa88516d1e436c5a31c779` was fetched and merged normally into this same branch (no replacement MR or integration branch). Generated conflicts in OpenAPI and companion/repository/runtime/ticket/worker-launch types were resolved by regeneration, not by selecting one side's contract. Workdir types were regenerated as well because the shared contract digest changed.
The merged Rust contract retains both classification operations/projections and the develop Runtime binding/per-request authentication model. Workspace schema remains 90; Ticket classification schema remains 8. The Runtime migration archives historical verification metadata, does not make it a grant, and does not rebuild Ticket/tag tables. Shared server/store/schema-equivalence/purge inventory changes were inspected against develop: both Runtime archives and all three classification tables are included.
Official generators rerun:
- canonical `export_openapi`;
- repository/runtime/ticket/workdir/worker-launch/companion OpenAPI schema-closure generators;
- feature-gated memory/skill/auth/repository-access/Drive/legacy Server type catalogs. Their checked-in representation conventions are preserved (auth uses the raw normalized generator output, not Deno reformatting).
## Composition defect fixed
New signed-Runtime router tests found that classification handlers discarded the verified Runtime Worker identity when optional transport source-hint headers were absent. This produced empty attribution in durable tag receipts and allowed another authenticated Worker to replay the same operation key.
Classification now takes the verified Runtime source from `ServerRequestContext`, retains the existing source-hint verification path, rejects mismatched identities, and carries the actual Worker identity into event/audit and exact-replay input. Runtime-only claims without a Worker identity cannot impersonate a human classification actor. Request proof tokens are not operation identity; exact replay with a new valid signature from the same Worker is accepted, while another Worker conflicts. Other Ticket operation contracts are not respecified by this correction.
## Executed validation
All fixtures are hermetic, temporary, in-process boundaries following `docs/development/rust-testing-strategy.md`.
- `cargo test -p yoi-workspace-server --lib ticket_tags_tests`: 6 passed (original 3 plus signed Runtime lifecycle/replay, identity-bound replay, and rejection matrix).
- Rejection coverage: unsigned hints, forged signing key, unknown Worker, other Workspace claim, body tampering, stale pinned key, and administratively revoked binding, for both catalog mutation and Ticket membership endpoints; no rejected-operation writes or execution effects.
- `current_workspace90_without_classification_upgrades_ticket7_without_rewriting_ticket_history`: passed. A current Workspace-90/pre-classification Ticket-7 fixture upgrades to Ticket-8 without rewriting an existing closed Ticket, labels, decision/history, or Workspace auth schema; arbitrary `ready` classification preserves the decision.
- `workspace89_to90_runtime_rebuild_preserves_tag_links_tombstones_receipts_and_audit`: passed. Real retained migration plus tagged data preserves exact current links, tombstones/recreated identities, receipt/context/audit rows and thread snapshots; replay and FK/deleted-reference rejection remain intact.
- `fresh_and_upgraded_state_contract_schemas_enforce_the_same_constraints`: passed, including classification tables and Runtime archives.
- `cargo test -p ticket --lib tags`: 14 passed.
- `cargo test -p worker --lib feature::builtin::ticket::tags`: 7 passed, covering normal tools/native WIP, permission, route binding and durable mutation/replay contracts after composition.
- `cargo test -p worker-runtime --lib workspace_issuer`: 12 passed.
- Full `cargo test -p ticket -p server-api -p client -p worker -p yoi-workspace-server`: passed including integration/doc targets. Unit suites: client 57, server-api 92, ticket 120, worker 975, Server 830 passed/1 existing ignored. The compaction integration target's 25 tests also passed.
- `cargo test -p server-api --features typescript`: 101 passed; all feature-catalog artifact tests passed.
- Root `cargo check`: passed, including normal TUI/Server closure.
- Canonical OpenAPI and all six OpenAPI-based generator `--check` runs: passed.
- `cargo fmt --all -- --check`, `git diff --check HEAD`: passed.
Initial signed-source tests failed on attribution/replay and passed after the fix. An attempted Deno format of auth catalog output failed its exact generated-artifact test; regenerating it in the documented raw form restored the artifact and all 101 feature tests passed.
## Naming audit limitation inherited from target
The required state-contract-name command was executed. It reports **30 unapproved lines and 9 stale exception hashes** in develop's frozen SSH migration/fixtures, migration-entrypoint fixture and timestamp-mismatch report. Every affected file and the exception inventory are byte-for-byte unchanged from `71ab4c06` (`git diff --exit-code 71ab4c06 -- <affected paths>` passed). The T-740 integration additions introduce no such finding. The inventory was not expanded, hashes were not silently refreshed, and no upstream historical contracts were renamed merely to make the checker green. This audit is **not reported as passing** and remains an Orchestrator/user follow-up outside the classification correction.
## Limits / handoff
T-739 is not integrated: its future case-state/close/migration composition is **not verified** by these tests. T-743 final Web UI, live production DB migration and full end-to-end UI/Runtime execution are not claimed. The existing ignored Server Drive Web-adapter roundtrip was not executed. The current-DB upgrade test covers a seeded schema-90 fixture, not live user data.
Only the existing source branch is to be published non-force. Source movement requires fresh trusted review on the same explicit MR `01a126a0-b4b1-7a13-ba61-5b2c06a3bca4` and current Ticket digest. Target publication, MR integration and Ticket completion/close are not performed here.
+9
View File
@@ -0,0 +1,9 @@
# T-740: delegated cwd and attachment coordinates
During T-740, two implementation children were spawned with a read rule for `.` and narrow write rules, plus `cwd: crates/ticket` or `cwd: crates/server-api`. Their committed sessions reported only the narrow crate attachment accessible; `docs/development/rust-testing-strategy.md` and, for the API child, the delegated client/Worker files were inaccessible. The API child correctly stopped rather than trying to bypass the denial. Its partial API changes were inspected and retained.
The parent had already read the test-design policy and reviewed the delegated tests against it. Remaining client/Worker work was assigned to a new child with `cwd: .`; that child could read the policy and implement its delegated targets. Parent integration and tests verified the actual checkout, not only child summaries.
This observation does not establish the provider's internal cause. The public `SubWorkerSpawn` description says cwd selects tool working directory, not Workdir/Profile/memory/Ticket roots or additional authority. Narrow cwd plus wider read/delegation rules should have predictable attachment coordinates, or the tool should report that only a restricted attachment can be provided before work starts. An early capability/attachment summary would avoid partial implementations and make mandatory policy-document access verifiable.
No authority workaround, process stop of the running host, or change to the control-plane tooling is included in T-740.
+50
View File
@@ -0,0 +1,50 @@
# T-740 implementation and validation
## Result
Classification foundation is implemented independently of case-state migration and the final Web UI. Contract/design and T-739/T-743 integration notes: `docs/design/workspace-ticket-tags.md`.
Changed boundaries:
- `ticket`: stable Workspace tag identity, normalized display-name uniqueness, tag catalog mutation/list, transactional multi-membership replacement, observation comparisons, identity-bound exact receipts, historical snapshots, schema v8 migration/verification, and projections.
- `server-api` / `client`: typed scoped routes and client methods, list/detail/query tag fields, AND/OR/NOT ID filters, OpenAPI and browser type generation.
- `workspace-server`: generated route registration, authenticated source attribution, SQL filtering before bounded candidate scans, authoritative projection recheck, suppression of execution/routing notifications, retained pre-classification schema verification and fixture correction, Workspace deletion purge inventory.
- `worker`: normal classification tools, native collection/item WIP operations, query filters, current identities/observation keys, and historical before/after snapshots in model-visible tag-change events.
## Executed checks
Tests were designed against `docs/development/rust-testing-strategy.md`. Narrow development checks included:
- `cargo test -p ticket --lib tags`: 14 tag authority tests passed, including name policy, duplicate/foreign/deleted references, stale observation, exact/incorrect replay, changed actor, assignment-context changes, multi-attach/remove, rollback, concurrent delete/attach and replay, no-op audit semantics, bounded lists/sets, and control-word tags across all existing states.
- `cargo test -p server-api --lib ticket_tag`: 4 passed; with `--features typescript`, 5 passed.
- `cargo test -p worker --lib feature::builtin::ticket::tags`: 7 passed, covering model-visible schemas, permission/authority errors, scoped HTTP operations and WIP binding.
- `cargo test -p worker --lib ticket_tag_history_projection`: passed.
- `cargo test -p yoi-workspace-server --lib ticket_tags_tests`: 3 passed, covering real router mutation/projection/filter, authentication/forged source/other Workspace rejection, actor-bound receipts, no state/content/assignment/grant/execution projection changes, and query pagination/filter-bound cursors.
- `cargo test -p yoi-workspace-server --lib fresh_and_upgraded_state_contract_schemas`: passed, including tag tables/constraints.
- `cargo test -p yoi-workspace-server --lib schema_v65_migrates`: passed after distinguishing retained intermediate schema from current classification schema.
- `cargo test -p yoi-workspace-server --lib explicit_purge_inventory`: passed.
Full changed-crate command passed:
```sh
cargo test -p ticket -p server-api -p client -p worker -p yoi-workspace-server --quiet
```
Unit suites at that point: client 57, server-api 92, ticket 120, worker 968, workspace-server 814 passed / 1 ignored. All included integration and doc-test targets also passed. Worker was subsequently rerun after adding the historical tag projection test (969 unit tests); its included integration/doc tests passed.
The one ignored Server test is the existing repositoryless Drive real-Web-adapter roundtrip, unrelated to classification. It was not executed.
- Root `cargo check`: passed, including normal TUI/Server compile closure.
- `cargo test -p server-api --features typescript`: 101 passed; doc tests passed. Existing ts-rs warnings about unsupported serde attributes remain.
- Canonical `export_openapi -- --check`: passed.
- `generate_repository_openapi_types`, `generate_runtime_api_types`, `generate_ticket_api_types`, `generate_workdir_api_types`, `generate_worker_launch_api_types`, `generate_companion_api_types` with `-- --check`: all passed. Other feature-gated type catalogs are protected by the full server-api TypeScript tests.
- `cargo fmt --all -- --check`, `git diff --check HEAD`: passed.
- `env -u LD_LIBRARY_PATH deno run --allow-read --allow-run=git tools/check-state-contract-names.ts`: passed, 0 unapproved / 0 stale exceptions; exception inventory unchanged.
Initial development/full-suite failures concerned in-flight trait implementations, stale generated artifacts, status-code expectations, and a retained schema fixture/intermediate verifier. They were corrected and the commands above rerun; the final changed-crate run had no failures.
## Not claimed / follow-up
No final Web tag UI, full browser/TUI/runtime E2E, real-model judgment test, workspace-wide test run, Nix/Docker build, or live production-data migration was performed. T-739 branch integration is not tested here: schema composition and contract regeneration must preserve the catalog, memberships, tombstones, receipts and tag audit/thread events. This branch is independently buildable on the existing Ticket-state model.
MR publication and trusted independent review are separate handoff evidence. This implementation does not merge the MR or complete/close T-740.