feat: publish Repository and Workdir WIP objects

Use Host root namespace ownership and disjoint Feature operation contributions. Separate read-only workdir_catalog from manage_workdir, encode all existing identities canonically, publish safe bounded inventory and lifetime-bound attachment Objects, and reuse normal lifecycle/session router/lock authority.

Contextual interfaces resolve the current permission/capability intersection; object and interface validators reject stale cached access. Cover fresh workspace WIP-only discovery/create/attach/read/detach, readonly and unsupported targets, collision/revocation/alias reuse, active cleanup exclusion and terminal unknown outcomes without retries.

Validation: WIP 40 tests; worker 730 unit + 96 integration tests serial; manifest 182 tests; workspace-root cargo check; cargo fmt --all -- --check; git diff --check HEAD.
This commit is contained in:
2026-10-05 18:10:01 +09:00
parent bbfbf92264
commit 42e4ce5550
11 changed files with 3319 additions and 78 deletions
+175
View File
@@ -0,0 +1,175 @@
# Repository, Workdir and attachment WIP projection
## Objects and operations
The Host owns these root namespaces, using the same `WipMountRegistry`
namespace/resolver/contribution contracts as Tickets, Objectives and Merge Requests:
```text
/
├── repositories list
│ └── <encoded repository key> read
├── workdirs list, create
│ └── <encoded Workdir ID> read, attach, delete (when supported)
└── workdir-attachments list
└── <connection lifetime ID> read, detach
```
Operations are Interface declarations, **not child files**. There is no
`/features/manage-workdir`, no writable Repository catalog and no Repository
content tree here. `/checkouts` content projection is a separate provider (T-699),
as is Workspace configuration (T-695). Management Objects do not grant access to
unattached contents. A future content provider should return its entrance from
an authorized, live attachment; it must not infer content/command capability
from catalog or lifecycle access.
`path` in inventory results is an absolute WIP Object path. It is a reference,
not an OS path, a capability grant, or an instruction to reinterpret another
string argument as a route. Source Repository references use `repository_path`;
attachment references use `workdir_path`. Attach results include
`attachments_path` to discover the current connection and its lifetime ID.
### Identity and route encoding
Repository identity is the formal Backend `repository_key`, never its display
name or a guessed `main`. Workdir identity is `working_directory_id`, never a
Worker-local alias. A segment containing only ASCII letters, digits, `-`, `_`
is unchanged. All other UTF-8 identities are encoded as `~` followed by lowercase
hexadecimal UTF-8 bytes. The `~` marker is itself always encoded. Only the
canonical encoding is accepted; no alternate spellings, recursive URL decoding,
case folding, slash traversal or namespace fallback are accepted. For example,
`repo/name` is `/repositories/~7265706f2f6e616d65`. This is injective and accepts
existing valid keys, including Unicode and slash-containing identities.
Attachment identity is a fresh opaque Backend connection ID for each lifetime,
including detach/reattach to the **same** alias and Workdir. Read exposes alias,
Workdir ID and effective attachment capabilities as attributes. Workdir existence
and Worker connection are separate Objects. Detaching a connection does not
remove the Workdir or delete a logical settings space.
## Feature and authority separation
`feature.workdir_catalog.enabled` independently supplies read-only Repository,
Workdir and attachment reference operations. It defaults enabled so a WIP Worker
with Workspace authority and appropriate permissions can find registered keys
without Ticket or conversation hints. It does not add ordinary Tools or require
a filesystem/Bash location. Standalone Workers without Workspace authority have
no Workspace catalog mounted.
`feature.manage_workdir.enabled` supplies management operation implementations
on the **same Objects** using `contribute_operations` and
`contribute_dynamic_operations`. It remains default disabled. It neither owns
another resolver nor confers permission. The object provider is unique; duplicate
namespace, route, operation name or inconsistent Interface contributions fail at
registration through the common registry.
Native permission identities are:
| Operation | Manifest permission identity | Bound identity in permission input |
| --- | --- | --- |
| repositories.list | RepositoryList | list arguments |
| repository.read | RepositoryRead | repository_key |
| workdirs.list | WorkdirList | list arguments |
| workdir.read | WorkdirRead | working_directory_id |
| workdirs.create | WorkdirCreate | original create arguments |
| workdir.attach | WorkdirAttach | working_directory_id + alias |
| workdir.delete | WorkdirDelete | working_directory_id + reason |
| attachments.list | WorkdirAttachmentList | list arguments |
| attachment.read | WorkdirAttachmentRead | connection_id |
| attachment.detach | WorkdirDetach | current bound alias |
Read permissions gate direct item resolution as well as list item visibility.
The manifest's existing default policy is retained; `ask` is denied fail-closed.
An argument-dependent policy may advertise an operation that could be allowed,
but every call checks its exact reconstructed input before execution. Mutation
arguments cannot override path-bound Workdir or connection identity. Having
RepositoryRead is not WorkdirCreate; inventory/management permissions are not
file access, delegation or command execution grants. Backend caller/Workspace
identity, source proof, attachment capability ceilings and lifecycle policy are
checked by the existing paths on every request.
## Contextual Interfaces and stale observations
Providers opt into common Host contextual Interfaces. Operation publication is
the intersection of registered implementation, current target support and current
subject policy. The collection reference feature can remain available while the
management feature is disabled. External Workdirs without cleanup authority do
not expose delete. Only currently supported lifecycle operations are advertised;
no delete/command is added to logical attachments solely because they appear as
workspaces.
A contextual Interface reference is opaque and path-qualified as
`<base>/@/<hex UTF-8 Object path>`. Clients must use the returned reference, not
construct it. Fetch and call freshly resolve the same Object and filtered
Interface; base references cannot fetch an unfiltered descriptor. Descriptor and
Object validators invalidate old observations after target/connection/permission
changes. Validators are managed by the WIP Client, not manually by the LLM.
Cached Interface possession and known direct paths never bypass current checks.
Historic session text remains append-only and is not rewritten or replayed.
## Backend lifecycle and safe output
Catalogs are request-time projections of existing Backend authority, not another
store or synchronization job. WIP list pages default to 50 and accept 1–100
items. Repository/Workdir pages sort by stable identity and use the last identity
as `next_cursor`; they project the existing Backend inventory response rather
than maintaining a catalog copy. Attachment pages use the Backend's SQL-bounded,
alias-ordered limit/offset query and opaque `offset:<n>` cursors. A cursor is not
a snapshot or authority grant: restart listing after concurrent collection
changes. Filtering by exact `connection_id` is applied in the caller-scoped
Backend ledger before paging, so direct item lookup also works beyond the first
page. Unknown/expired IDs yield no item, never a current alias substitute.
Repository projection uses an explicit field
allowlist: formal key, kind/provider, any existing display metadata and explicit
nullable default selector. Source URI/path, credentials, remotes, Runtime
connection information and internal fingerprints are not model-visible.
Workdir projection includes stable ID, optional display name, public source kind
and Repository key, existing selector/ref/tree, status, cleanliness and occupancy
when available. Cleanup handles and External grant internals are not forwarded.
No missing attributes or implicit selectors are fabricated. Backend errors are
sanitized rather than forwarding raw diagnostic bodies with possible host paths.
Create and attach reuse the ordinary Workdir backend, request identity,
Worker-session router, mutation lock and Internal SubWorker lifecycle hooks.
Creation does **not** attach. Attach installs the Backend-attenuated session
capabilities, never a fabricated `ALL` capability for External Workdirs. Detach
uses the same begin/finish/cancel-detach path and command/operation exclusion as
Tools; the Backend checks expected connection identity under its existing
current-Worker lifecycle lock before closing sessions, then conditionally
updates that exact row in the existing connection ledger. Alias reuse cannot
retarget an old connection operation. Compensation retains the original
connection identity. Existing removal dispositions `removed`, `retained`, and
`attention_required`, plus retryability, remain intact.
Stale identity/validator rejection is distinct from unknown operation outcome.
Transport failure, invalid post-dispatch response or uncertain lifecycle failure
is **not** proof that no effect occurred; it must not be automatically retried.
Inspect current state or request operator guidance. No new retry or attachment
ledger is introduced by WIP.
When native management is mounted, `WorkdirList/Create/Attach/Detach/Delete` are
claimed as represented native capabilities, so `/tools/Workdir*` is not also
published. Ordinary Tools mode keeps its names and inputs. Existing native
Ticket/Objective/Merge Request routes retain their T-698 contracts.
## First-Workspace example
1. `WipDiscover(path="/", depth=1)` finds `repositories`, `workdirs` and
`workdir-attachments` when permitted.
2. Inspect the returned Repository collection Interface and call `list` with a
bounded limit. An empty registered/visible catalog explicitly returns an empty
list; do not invent a key. Follow `next_cursor` when supplied.
3. Discover the returned item `path`, inspect its Interface and call `read`.
`default_selector: null` means no configured default, not `main`.
4. Inspect the Workdir collection and `create` using the discovered formal key,
with optional selector, display name and Runtime under the existing contract.
5. Follow created `item.path`, discover/inspect and `attach` with a chosen
Worker-local alias. This is separate from creation.
6. Follow `attachments_path`, `list`, then discover/read the returned connection
path to confirm alias, Workdir and effective capabilities.
7. Call that connection's `detach` with no identity override. The observed
lifetime ID guards alias reuse; the Workdir remains in its inventory.
Each step uses current WIP observations and no Ticket-derived key, host path,
Runtime endpoint or manual validator. Explicitly refresh/reset discovery after
external changes and before reconstructing a rejected stale call.
+1 -1
View File
@@ -75,7 +75,7 @@ The built-in native route migration is:
| `/features/objective/objectives[/<reference>]` | `/objectives[/<reference>]` |
| `/features/merge-request/merge-requests[/<reference>]` | `/merge-requests[/<reference>]` |
`/repositories` and `/workdirs` can be allocated by a later provider using the same registration contract; this layer does not implement or pre-allocate those domains. `/checkouts` content projection is likewise outside this contract.
The built-in Repository/Workdir reference provider allocates `/repositories`, `/workdirs`, and `/workdir-attachments` using this contract; `manage-workdir` contributes operations rather than owning another Object. See [Repository, Workdir and attachment WIP projection](workdir-wip-projection.md) for identity encoding, reference/management permissions, attachment lifetime safety, and first-Workspace discovery. `/checkouts` content projection remains a separate provider.
### Registering a subsequent provider