fix: simplify Ticket reporting and speed up browser lists

Remove model-supplied assignment and retry identities from implementation reports; retain Host idempotency, authentication and notification evidence.

Add a SQL-only browser projection and decouple auxiliary page loads. Preserve authoritative MR evidence queries. Include regression tests and component benchmarks, documenting the remaining no-match search cost.
This commit is contained in:
2026-10-16 01:58:15 +09:00
parent 9f1cbbd385
commit b67aa2de7d
42 changed files with 3197 additions and 152 deletions
+9 -3
View File
@@ -12,7 +12,7 @@ Ticket case/handling, MR approval, Job execution, or Worker lifecycle.
| Ticket Assign, Answer, item/content edits | Ticket transaction triggers → `notification_routing` | Original delegation; explicit request vs attention. No recipient, released/history, closed/done, unsupported recipient and self-notification are recorded separately. | Assignment reservations, pinned requests, authorized targets and ACKs; common recovery; `assignment_delivery_tests.rs` and `server_assignment_delivery_tests.rs`. |
| Ticket comments/post edits, tags, case, tracking, release | Same capture and routing ledger | They do not become new Orchestrator requests. Applicable implementation-Worker attention is distinct from new delegation. | Same evidence; `notification_routing_tests.rs` covers positive and negative routes. Unknown source kinds defer explicitly. |
| MR review requested/result/revoked/cancelled/merge and observed source changes | MR transaction/source-observation triggers → common routing | Ticket delegation and implementation recipient captured at the event. MR remains the freshness/approval authority. | Stable MR event identity; current MR must be reread before action. Ordinary review comments are not inferred handoffs. `implementation_result_tests.rs` and `server_implementation_result_tests.rs` cover capture, self-suppression, recovery and public diagnostics. |
| Explicit implementation result/handoff/blocker | Authenticated `TicketImplementationResult` transaction | Implementation assignment and its pinned Orchestrator delegation, with exact-replay identity. No substitution of AssignmentAnswer or self-reported handling. | Result and source persist together; same common delivery/receipt path. No MR is required. |
| Explicit implementation result/handoff/blocker | Authenticated `TicketImplementationResult` transaction | Authenticated Workspace Worker and Ticket, without assignment membership requirements. The current Orchestrator is captured at report-save time, or no-recipient is recorded. No substitution of AssignmentAnswer or self-reported handling. | Result and source persist together; same common delivery/receipt path. No MR is required. |
| Backend Job accepted result | Result transaction → `notification_routing::jobs` | Fixed recipient, never replacement by the current Orchestrator. No target, checker no-findings and stale content suppress without ACK. Domain validation/rendering remains in the adapter. | Common request/ACK/failure ledger, fixed bytes/recipient, startup and periodic recovery; `job_notification_tests.rs` and `server_job_notification_tests.rs`. |
| Explicit WorkerNotify/WorkerSendInput | Authenticated caller | Explicit communication, not automatic business routing. | Existing Worker control boundary; not inferred from Ticket activity. |
| Internal SubWorker → parent / Web subscriptions | Parent-owned execution / UI synchronization | Separate ownership and authorization. | Not migrated into business-notification routing. |
@@ -31,8 +31,14 @@ uses real Git publication and signed HTTP to cover the opening baseline, a later
source change before review, and result reporting against observation-only evidence.
Schema 96 migrates Job notifications; schema 97 installs MR/implementation-result
capture. Legacy Job ACKs and suppressions remain distinct. Ambiguous legacy
implementation ownership remains unknown; comments never synthesize a result.
capture. Schema 98 removes assignment ownership and caller-supplied assignment IDs
from result reporting, preserving prior result bytes and notification evidence.
Legacy Job ACKs and suppressions remain distinct; comments never synthesize a result.
The model supplies only Ticket, kind, body and optional MR evidence. Host-side tools
use the durable Tool call identity as the transport replay key. Retrying the same
invocation cannot duplicate a report; a new invocation is a new report, so an unknown
outcome must not be retried blindly with a fresh call. Stored notifications retain
their captured recipient rather than being reassigned during delivery recovery.
Preparation failures remain retryable and visible after recovery; durable fair
scheduling prevents repeated failures from starving healthy or already-pinned work.
@@ -0,0 +1,55 @@
# Tickets browse: component performance comparison
## Change
The Web Tickets list uses `POST /api/w/{workspace_id}/tickets/browse`, a deliberately narrow display contract. The existing `/tickets/query` and Ticket detail paths retain authoritative MR evidence observation; the browse result contains no evidence/approval fields and is not an integration-readiness attestation.
A nonempty browse page uses one SQLite read transaction with four SELECT statements: filtered page selection, active tags, current closure judgments, and current Orchestrator assignment tracking. No full Ticket/event reconstruction, dependency graph, provider Git observation, or per-row enrichment query is performed. All filters precede LIMIT; cursors bind Workspace, canonical filters, sort and the last row, including NULL timestamps and ID ties.
The Web loader waits only for the list (and tag-name resolution when requested). Orchestrator status loads independently after mount; repositories load on opening Add Ticket. Late responses are fenced against route/Workspace changes.
## Backend measurement
Run the retained benchmark:
```sh
cargo test -p yoi-workspace-server ticket_browse_component_performance --lib -- --ignored --nocapture
```
Optional fixture sizes: `BROWSE_BENCH_TICKETS` and `BROWSE_BENCH_EVENTS`.
- Real `SqliteWorkspaceAuthority` methods and file-backed disposable SQLite; no production DB, HTTP server, Runtime, provider network, or live Git.
- 1,000 generated open Tickets, each with 100 discussion events plus the initial post, roughly 1 KiB text per event, event attributes/references and one relation. One additional seed Ticket is used for valid references.
- Compare the former browser path (`query_tickets`, preserved for authoritative queries) with `query_ticket_browse` on the same fixture.
- Fixture construction excluded; both paths warmed, then three old/new pairs measured through JSON serialization. Assert identical displayed items/order and has-more before accepting results.
- Debug test build on a shared development host; these are component measurements, not production response-time estimates. Run-to-run variation is substantial; no flaky time threshold is asserted.
Adopted Unicode-aware implementation with borrowed scalar-function inputs, median milliseconds:
| Case | Former browser path | Browse path | Response bytes, old → new |
|---|---:|---:|---:|
| Initial 30-row list | 23,265.888 | 0.769 | 29,744 → 5,647 |
| Dense discussion-text match | 23,121.110 | 16.130 | 52,163 → 5,646 |
| Sparse title match (`Ticket 00099`, 10 rows) | 7,314.318 | 496.367 | 10,076 → 1,968 |
| No match (`absent-needle`) | 299.065 | 409.180 | 184 → 186 |
The ordinary list removes the unnecessary work rather than caching evidence. Dense matching stops after enough rows; sparse/no-match searches still scan text across candidates. **No-match search regresses by about 110 ms in this fixture.** The browse path fixes the old SQL/Rust Unicode case-fold mismatch using the same Rust lowercase operation for the needle and stored text, at callback cost. An ASCII SQL length-check fast path was measured and removed because it increased scan cost. A persistent search index is not introduced in this change; large sparse/no-match searches remain a limitation, not a claimed improvement.
## Web component measurement
```sh
cd web/workspace
deno task test:component 'src/routes/w/[workspaceId]/tickets/ticket-list.browser.spec.ts'
```
The real Svelte component and new loader run against controlled promises, with 20 ms list latency and 100 ms auxiliary-request latency. The comparison restores the former `Promise.all` auxiliary gate for the baseline; it does **not** simulate the old backend SQL. One focused run measured 103.17 ms with the gate versus 23.48 ms through row rendering without it. There are no live requests or hard timing thresholds. Separate assertions verify visible rows while status is unresolved and no repository fetch before Add Ticket.
## Regression evidence
- Workspace-server library: 1,088 passed, 2 ignored (including the separately run timing benchmark).
- Backend browse tests: keyset pagination, filter-before-limit, tag/current-assignment/closure parity, Workspace isolation, literal `%`/`_`, Unicode title/body/thread/reason search and embedded NUL.
- Actual HTTP route: response shape, unauthenticated rejection, invalid filters and foreign Workspace rejection.
- Counting source observer: browse makes zero calls; authoritative QueryTicket and show still observe open MR sources.
- Web unit tests: 644 passed. Svelte components: 401 passed. Svelte check: no errors or warnings.
No production migration or restart is needed for the schema itself (no schema change); serving the new endpoint and Web code still requires the normal software update.