fix(server): route embedded WorkerRemove through registry-bound reception

This commit is contained in:
2026-10-06 14:50:31 +09:00
parent 7d8e595270
commit 849a6b4c1c
4 changed files with 213 additions and 94 deletions
@@ -28,6 +28,7 @@ For the HTTP rows below, `R` abbreviates `/api/runtimes/{runtime_id}/workers/{wo
| `GET /api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-plan` | `build_runtime_cleanup_plan` (preview, not a Worker mutation) | Workspace cleanup planner/projections |
| `POST /api/w/{workspace_id}/runtimes/{runtime_id}/cleanup-executions` | `execute_runtime_cleanup_with_context` validates plan revision/digest and selected candidates → each selected Worker's `delete_from_plan` | `WorkerRemovalService::execute_cleanup_removal` → existing retention planning, Runtime retention execution, and Workspace catalog commit. Workdir-only actions remain in the cleanup service |
| `POST /api/w/{workspace_id}/workers/remove` (`WorkerRemove`, not a browser removal alias) | `worker_remove_contract` verifies its source proof against the requested target → `WorkspaceWorker::remove` | `WorkerRemovalService::execute_async` checks the `remove` grant and coordinates retention/removal. It rejects self-removal and requires a stopped target for a new removal; existing durable removal operations have their own recovery path |
| Embedded `WorkerRemove` Tool | Runtime-owned in-process target-bound proof → `EmbeddedServerWorkerMutationDispatcher` verifies/consumes proof → installed `EmbeddedWorkspaceWorkerRemoveExecutor` → `WorkspaceWorker::resolve_with_services` → the same `WorkspaceWorker::remove` | Same `WorkerRemovalService::execute_async`, grant-lock/recheck, self/live/retention guards, durable plan/recovery and catalog commit as HTTP; no Runtime-to-Server dependency |
| Dedicated WS `R/protocol/ws` and `W/protocol/ws` | `worker_protocol_ws` (scoped wrapper for `W`) → `WorkspaceWorker::connect_protocol` → `workspace_worker_protocol_session` → bound sender | One remote WS or embedded execution transport; controller/session owns protocol operation results |
| Multiplexed WS `/api/w/{workspace_id}/protocol/ws`, runtime-scoped `WorkerProtocol` selector | `scoped_workspace_protocol_ws` → `serve_workspace_subscription` → resolve + `connect_protocol`; subscription stores the bound sender | Same connected execution transport as dedicated WS; the subscription layer wraps its snapshot/events, not a different execution implementation |
| Worker-control `POST /api/w/{workspace_id}/worker-control/workers/{runtime_id}/{worker_id}/{input,cancel,stop,restore}` | `send_known_worker_input`, `cancel_known_worker`, `stop_known_worker`, `restore_known_worker` authenticate the Worker source, construct `WorkerControl`, and call the same identity methods/restore adapter | Same Runtime execution owners, under the applicable active target grant; known-worker input adds its existing non-Accepted-result error translation |
@@ -66,7 +67,9 @@ For Worker-control user input, `input` converts `User` to `UserIfIdle`; admissio
Scoped Restore may coordinate Ticket assignment only when both `ticket_id` and `assignment_operation_id` are supplied. A Worker-control context cannot use either assignment parameter. Aggregate cleanup rejects all Worker-control requests: Worker callers must retain the stronger target-bound WorkerRemove proof/retention path. A control grant is not authority over Ticket assignments or Workdir cleanup. The cleanup coordinator revalidates Account credentials, Origin, and active Workspace even for a Workdir-only request.
`WorkerRemove` is intentionally a different authorization entry point. It accepts a verified mutation source rather than `WorkerOperationContext`, verifies its target-bound proof before calling the identity method, and keeps source-specific and grant revalidation in the removal service. It is not permission-free just because it does not call `authorize_operation`.
The HTTP and embedded `WorkerRemove` entry points share the registry-bound removal method. `WorkerRemove` intentionally has a distinct authorization contract from the other operations. It accepts a verified mutation source rather than `WorkerOperationContext`, verifies its target-bound proof before calling the identity method, and keeps source-specific and grant revalidation in the removal service. It is not permission-free just because it does not call `authorize_operation`.
The embedded executor retains only the removal services with a **weak** RuntimeRegistry reference. Its removal-only `WorkspaceWorker<()>` specialization shares resolution, current-record checks and the removal method with the full `WorkspaceWorker<WorkspaceApi>` handle. This service projection avoids a strong `WorkspaceApi → Runtime → dispatcher → WorkspaceApi` ownership cycle; it is not another Worker ledger or an authorization fallback. It cannot call the full handle's restore/input/protocol methods. The existing independent Runtime-owned proof forwarder and verification/replay seam stay unchanged.
## Tokens and outcomes are operation-specific
@@ -0,0 +1,28 @@
# T-702 validation: intermittent stale Ticket checker assertion
The full `yoi-workspace-server` library suite intermittently fails
`server::tests::ticket_checker_suppresses_stale_and_clean_results_and_diagnoses_invalid_output`
at its post-stale-result `assert!(execution.take_inputs().is_empty())`. The same unchanged
assertion passed in some isolated/full runs and failed in others during T-702 validation.
To separate this from Worker operation reception, I archived original base
`b96bd110c623fafdd7e8df1b153e8c640c793abe` under the ignored `target/t702-baseline`
directory without moving/replacing refs or discarding the Ticket Workdir. A first
shared-target build failed with stale dependency-interface artifacts and was **not**
baseline evidence. I then used an isolated `CARGO_TARGET_DIR=target/t702-baseline-build`
with `CARGO_BUILD_JOBS=3`. The baseline compiled successfully; its isolated checker test
passed. Its full library run with `--test-threads=4` reproduced the same assertion:
**599 passed, 1 failed, 600 total** (79.19s), at baseline `server.rs:44331`.
The post-review T-702 fix's full library run reported **611 passed, 1 failed, 612 total**
(79.01s), at the corresponding `server.rs:44266` assertion. This demonstrates an inherited
intermittent validation failure, not a green suite or proof of its root cause.
A subsequent post-review-fix whole-crate run, with the same implementation and no skips,
passed **612 library + 11 binary tests and doc tests** (library 81.24s). That successful
run does not erase the failed runs or resolve the underlying checker race.
The Worker reception fix's 12 operation-boundary tests, 5 WorkerRemove tests (including
constructor-installed embedded dispatcher, target-bound proof, resource-key normalization,
running/pinned refusals, retention and catalog removal), root `cargo check`, formatting and
diff checks passed. The checker implementation/test was not changed or skipped to conceal
the failure. Follow-up should instrument competing checker-job dispatch and deterministic
execution-input consumption so this assertion has an explicit synchronization boundary.