feat: project attached checkouts with scoped native AI operations

This commit is contained in:
2026-10-06 03:37:07 +09:00
parent 3deef49897
commit 149853bb0e
43 changed files with 8706 additions and 346 deletions
+225
View File
@@ -0,0 +1,225 @@
# Attached checkout WIP projection
T-699 publishes the contents of **current Worker attachments**, independently of
Repository/Workdir inventory and management. This is a Yoi implementation of
request-time projection, **not a dependency on `wip-fs-server`**. The upstream
read-only source/specification informed the design; its byte `read` and
truncate/write `replace` contracts are not substituted for Yoi's text/edit/save
contracts. No file-tree store, synchronization, or per-file resident registration
is created.
```text
/
├── repositories inventory/reference
├── workdirs identity/lifecycle
├── workdir-attachments Backend connection lifetimes
└── checkouts current accessible content entrances; list
└── <encoded alias> directory; glob, grep, create_file
├── README.md file; read, edit, write
└── src directory; glob, grep, create_file
```
Operations are Interface declarations, not children or `/features/...` routes.
File tools are native when the normal Worker WIP surface is installed. Bash stays
in compatibility mode. Ordinary Tools mode retains Read/Edit/Write/Glob/Grep.
Feature activation never grants READ, WRITE, EDIT, search, COMMAND, or delegation.
## Placement and ownership
`worker::checkout` owns Worldspace binding, contextual Interface declarations,
permission input reconstruction and safe output. It never opens a host pathname.
It resolves the live `WorkdirSessionRouter` on each request. The existing
`WorkdirSession` provider executes `checkout_observe` and `checkout_execute`;
Runtime HTTP and External transports carry typed operation/result envelopes at
their existing authorization, connection-generation and cancellation boundaries.
The Workspace-attached session forwards through the existing Backend broker.
Endpoint paths, credentials and materialized roots do not enter WIP descriptors,
Objects, arguments or results.
The common Host registry has an **explicit asynchronous subtree provider**
contract, separate from the existing one-segment dynamic item families. It
validates every resolved projection's path/owner/descriptor and every enumerated
child's immediate-parent relationship. Static and dynamic/subtree overlaps are
rejected. Observe resolves only the requested depth; successful observations are
complete through that depth, not partial trees masked as success. Directory
listing and native search use the shared List/Glob/Grep engines through typed
`CheckoutSearchRequest` forwarding. Scoped sessions append intersecting rule
layers, translate their cwd, and require provider-side visibility checks before
entry descent/content reads. Results retain session-logical coordinates, so a
SubWorker cwd does not duplicate a path prefix in its Object links. Unsupported
providers fail closed rather than falling back to a broader source search.
Direct resolution also uses current provider authority, not possession of a
cached Interface.
## Names and connection isolation
Aliases use T-697's canonical encoding: nonempty ASCII letters/digits/`-`/`_` are
unchanged; every other UTF-8 alias is `~` plus lowercase UTF-8 hex, with `~` itself
encoded. This is injective, not display-name slugification, Unicode normalization,
URL decoding or case folding. Only canonical spellings are accepted.
`checkouts.list` returns alias, slug, Workdir ID, Worker-local attachment generation
and `path`. Attachment inventory retains its Backend connection lifetime ID and
can return `checkout_path`/`checkout_slug` for the matching live router attachment.
Attach results point to the attachment catalog and checkout entrance. Workdir
existence alone does not expose its content. Following a reference still requires
current scope/capability and an accessible filesystem projection.
Objects have no shared inode `ref`: hard-linked names and different attachments
must not be mistaken for one path-independent representation. Interface references
are opaque, path-qualified and include a fresh projection incarnation and live
attachment generation. Validators combine that namespace with the provider's
opaque identity/state validator. Restoring a Worker creates a fresh incarnation;
detach/alias reuse changes the router generation. An old Interface, validator or
read-history entry cannot retarget a new connection. No separate connection ledger
is added. The existing router's active-operation exclusion and begin/finish/cancel
detach paths remain authoritative; detach does not remove the Workdir/files.
Collection enumeration rejects connection-set changes between sampling and
publication instead of pairing old items with a new validator. Search-link
publication checks the captured attachment generation, not merely a reused
alias; response-side provider awaits are covered by the operation deadline.
## AI Operation mapping
All operations bind their attachment and target from the WIP route. There is no
`target_workdir`, Workdir ID, host path or replacement `file_path` argument.
Arguments not declared by the descriptor are rejected before dispatch. Paths
below are relative to the **bound directory**, not the Worldspace or endpoint.
| Object | Operation | Arguments | Result |
| --- | --- | --- | --- |
| `/checkouts` | `list` | none | bounded `items` containing alias/slug/Workdir/generation/path |
| directory | `glob` | `pattern`, optional `path` | normal bounded Glob text/summary plus same-checkout Object links |
| directory | `grep` | `pattern`; optional `path`, `glob`, `type`, `case_insensitive`, `-A`, `-B`, `-C`, `multiline`, `output_mode`, `head_limit`, `offset` | normal Grep grouped/context/count text and typed provider search targets mapped to Object links |
| file | `read` | optional line `offset`, `limit` | line-numbered text and normal Read summary |
| file | `edit` | `old_string`, `new_string`, optional `replace_all` | replacement count/summary and preview |
| file | `write` | `content` | existing-file save summary; never creation |
| directory | `create_file` | `path`, `content` | create-new summary and created-file link; permitted missing parents are created |
Native operation results use a JSON record with `summary`, nullable `content` and
`items` (`path` is an absolute WIP Object navigation coordinate, not an OS path).
Search links are constructed from **typed provider paths**, not by parsing Grep's
rendered filenames/line delimiters. Provider scope remains authoritative for
search output as well as links. Glob/Grep share normal provider ignore/pattern,
context/output mode, offset and bounded result behavior. Search does not claim an
atomic snapshot of every descendant: it fences the bound directory before/after
search and each provider read/search retains its own consistency guarantees.
Read offsets are zero-based **lines**, limit defaults to 2000, displayed numbers
are one-based. The shared Read retains at most 256 KiB of source text and bounds
its numbered presentation to 256 KiB, with an explicit truncation marker when
numbering/Unicode expansion exceeds that budget. No byte-range
operation is published by this native surface. Partial Read records the provider's
full bounded-source hash and total line count, not the hash of only its displayed
slice. Native and ordinary Tool calls use the same original alias/generation
tracker, mutation coordinator, rendering and parameter validation.
Edit requires a nonempty, different `old_string`/`new_string`; `old_string` must
occur exactly once unless `replace_all` is true. Existing Write/Edit require a
prior successful Read through the same attachment. The read-history hash check
and the provider identity validator are **both** required: a same-content inode
replacement cannot be blessed merely by matching a hash. Failed/conflicting Read
must not record history; successful mutations update hash/change statistics only
after provider success. Create is strictly beneath the bound parent, creates only
an absent file, and must not overwrite an existing destination or symlink.
Manifest permission identities remain Read/Edit/Write/Glob/Grep; create_file uses
Write. The Host reconstructs the same route-bound input for policy evaluation;
provider read/write scope, capabilities and delegated leases are checked again.
`ask` fails closed. Native replacement claims remove `/tools/Read`, `/tools/Edit`,
`/tools/Write`, `/tools/Glob`, `/tools/Grep`, not unrelated Tools.
## Consistency, safety and resource policy
The provider resolves/pins the entry/parent, compares its opaque validator and
executes shared `fs-operation` processing within the same checked boundary. The
validator represents identity and protected metadata, independently of the Read
content hash. Read checks state across its stream and returns a validator for
that same state. The WIP response uses the **execution result's validator**, not a
later independent path stat that could bless a replacement after execution.
The native checked filesystem projection deliberately rejects symlink traversal
and unsupported/special entries. It does not silently broaden existing resolved
or logical scope. Ordinary Tools retain their existing symlink-policy behavior.
External sessions retain the root descriptor pinned **before approval/grant**;
renaming/replacing its ambient pathname must not change the approved root.
Absolute paths, traversal, alternate alias arguments and special-file mutation
are not native capabilities. UTF-8 names are byte-for-byte; non-UTF-8 names must
not be lossy-normalized into another Object name. Root/directory permissions are
not inferred from catalog visibility or from a descendant pathname supplied by
the model.
Saves retain Yoi's temporary-file publication rather than in-place truncation.
Thus saving one hard-link pathname replaces that name rather than changing all
aliases of the old inode. Checked preconditions detect known changes before
commit. Advisory locks cannot exclude arbitrary noncooperating external editors;
read-time/commit-time identity checks do not create a filesystem-wide transaction.
The supported race guarantees and remaining external namespace race window are
specified by the provider implementation and tests, not by a claim that a pinned
fd makes any rename-based update universally atomic.
A guaranteed pre-effect rejection is distinct from **OutcomeUnknown** after
mutation/parent creation may have started, commit succeeds but state cannot be
returned, response validation/encoding fails, or a transport/deadline loses the
result. Never automatically retry uncertain mutations. Inspect current state and
rediscover/reread. Do not report partially created parents as unchanged.
Host observations are bounded to depth 32 and 1024 nodes with a 30-second total
cooperative deadline. The checkout adapter permits at most 16 concurrent requests
and bounds admission waiting and the complete operation (including result-link
publication) to 30 seconds each; provider path/depth, source/response,
mutation/search and transport bounds apply too. No initial context expands an
entire tree. Resource-limit errors do not turn incomplete observation into a
successful truncated tree; bounded search results may explicitly report
truncation under the existing search contract.
## Typical sequence
1. Discover `/checkouts` with depth 1; inspect its Interface and call `list`.
2. Discover an entrance or known deep directory, inspect it, call `glob`/`grep`.
3. Follow a returned file `path`, discover/inspect, call line `read`.
4. Call `edit` or `write`; Client-managed validators protect the exact observed
object while the shared tracker enforces the prior Read.
5. Rediscover after external changes; a stale rejection is not permission to skip
Read. ReRead after successful save to verify text.
6. Inspect a directory and call `create_file` with a descendant relative path,
then discover/Read the returned new file.
7. Use `/workdir-attachments/<connection lifetime>` for detach. Old checkout
observations and history cannot operate a newly attached alias.
Workspace configuration (T-695) remains a separate logical provider; it is not
replaced by raw Linux filesystem writes. Bash native conversion, broad POSIX
operations, lifecycle duplication, upstream changes and deployment/restarts are
outside this feature.
## Reproducible validation matrix
Run from the main repository, on Linux with `openat2` and `/proc/self/fd`:
```sh
cargo check --workspace --all-targets
cargo test -p fs-operation -p workdir -p tools -p worker
cargo test -p worker-runtime -p yoi-workspace-server
cargo fmt --all -- --check
git diff --check
```
| Boundary | Focused regression locations |
| --- | --- |
| Deep mounts, multiple aliases, route-only arguments, exact permission identities, no duplicate compatibility file Tools | `worker/src/checkout_wip_tests.rs` and existing `wip.rs` registry/controller tests |
| Native search → numbered Read → unique/all Edit → Write → Create; returned validator cached chains | `checkout_wip_tests.rs`, `tools/src/checkout_tests.rs` |
| SubWorker write leases, sparse/nonrecursive/nested scopes, denied basename/content pruning, result-coordinate rebasing, revocation | `workdir/src/checkout_search_tests.rs`, `checkout.rs`, native scoped-child test |
| Approved External root replacement; same-content inode/name/parent substitution; hard-link save isolation; symlinks/special files; Unicode/non-UTF8 paths | `workdir/src/checkout.rs`, `fs-operation/src/checked.rs` |
| Mid-read updates; independent hash and validator fences; parent partial creation and post-save state failure | `checked.rs`, `checkout.rs`, Tools tests |
| Detach/alias reuse/restore references and read history; collection/search response generation fences | `checkout_wip_tests.rs`, `checkout_race_tests.rs`, existing router/scope/catalog lifecycle tests |
| Traversal depth/entry/cancellation bounds; stalled observation/link publication; numbered output expansion; post-dispatch encoding loss | `fs-operation/src/traversal_tests.rs`, `checkout_race_tests.rs`, `tools/src/read.rs`, `wip.rs` |
| Real prefixed HTTP provider → Remote session → router → native WIP; auth, read-only, lost/malformed post-commit response without retry | `worker/src/checkout_http_tests.rs` |
| Workspace proxy/External typed pairing and unknown-outcome propagation; Backend/Runtime Workspace, grant and generation isolation | checkout additions in `manage_workdir.rs` and Backend `server.rs`, plus full existing host/provider suites |
| Ordinary Tools text/hash/edit/write/search and provider symlink policy | full Tools, fs-operation and Workdir suites |
The HTTP fixture tests actual network/codec boundaries but is not a production
Backend grant-network end-to-end deployment. Production routing/authorization is
covered separately by the existing host suites. Fault tests are deterministic
boundary simulations, not power-loss tests. Metadata/pinned-handle tests do not
close the external-editor final-check/rename window documented above. No live
Worker or deployed environment needs updating/restarting to execute this matrix.