feat(workspace-config): project grant-bound logical settings through WIP
This commit is contained in:
@@ -38,6 +38,7 @@ It is not a dumping ground for external research, old plans, API inventories, or
|
||||
30. [`design/ticket-wip-projection.md`](design/ticket-wip-projection.md) — Host-assigned Ticket collection/item routes, role-specific operations, route identity, and typed Backend preservation.
|
||||
31. [`design/merge-request-wip-projection.md`](design/merge-request-wip-projection.md) — Host-assigned Merge Request routes and preserved review/integration authority gates.
|
||||
32. [`design/feature-chat-invocations.md`](design/feature-chat-invocations.md) — declarative `/` invocation identity, grammar, completion, typed draft, attachment staging, execution, and authority boundaries.
|
||||
33. [`design/workspace-config-wip-projection.md`](design/workspace-config-wip-projection.md) — Backend-authored configuration as a logical WIP attachment, explicit grants, shared UI conflict domain, and activation boundaries.
|
||||
|
||||
## What belongs here
|
||||
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
# Workspace configuration as a WIP logical filesystem
|
||||
|
||||
## Authority and source mapping
|
||||
|
||||
Workspace configuration is not the Backend host's configuration directory. The
|
||||
canonical authored source is already a SQLite-backed, revisioned virtual tree:
|
||||
`workspace_config_trees`, `workspace_config_entries`, and append-only
|
||||
`workspace_config_tree_revisions`. `WorkspaceConfigState` combines its
|
||||
`ConfigTreeSnapshot`, toolchain/schema contract and projection digest. The
|
||||
`main.dcdl` entrypoint and its imports are authored files; evaluated profiles,
|
||||
prompts and other projections are not replacement source files.
|
||||
|
||||
The builtin Workspace schema composes profile, prompts, runtime,
|
||||
repository-access and Skills settings. Profile sources include model settings.
|
||||
Do not assume that all imported Profile sources live under `profiles/`:
|
||||
`VirtualPath` identifies authored entries, including shared imports. Skill
|
||||
Markdown and text resources are entries in the same virtual tree.
|
||||
|
||||
The WIP provider must resolve this authority at request time, not copy it to an
|
||||
OS directory, maintain a synchronized config cache/store, or register every
|
||||
entry as a permanent Object. A logical attachment selects a Workspace-bound
|
||||
configuration subject; it is not a Bash cwd or a host filesystem grant.
|
||||
|
||||
## Existing save and activation contract
|
||||
|
||||
The browser source-tree editor uses `ConfigCommitRequest`: a base revision and
|
||||
tree digest, changes with expected entry digests, and the `main.dcdl`
|
||||
entrypoint. Candidate evaluation applies changes, formats/normalizes Decodal,
|
||||
and evaluates the composed schema. Commit rechecks revision **and** digest in
|
||||
an SQLite Immediate transaction before replacing active entries and appending
|
||||
the revision manifest. Worker operations must share that conflict domain with
|
||||
UI and other Worker edits; a per-Worker mutex alone would not prevent lost
|
||||
updates.
|
||||
|
||||
The Backend commit orchestration validates the prompt projection and
|
||||
repository-access references, then commits and best-effort publishes the prompt
|
||||
projection to the embedded Runtime. Saving establishes new authoritative
|
||||
Workspace configuration, **not universal live application to existing Workers**.
|
||||
New Worker configuration bundles are revision-bound. In-flight consumers can
|
||||
retain immutable projections. Skill availability and activation are separate
|
||||
contracts. A transport failure after dispatch is not proof of rollback and is
|
||||
not authorization to retry a mutation automatically.
|
||||
|
||||
## Separate authorities and excluded objects
|
||||
|
||||
The following are not files of the authored Workspace source tree:
|
||||
|
||||
- Backend host `server.toml`, OS configuration paths and materializer state.
|
||||
- Workspace identity, signing material, runtime trust/registration,
|
||||
credential stores, authentication/session/passkey/token data and private keys.
|
||||
- Repository registrations and Flow records, which use separate typed APIs.
|
||||
- Workspace display name and Memory language, which have independent CAS
|
||||
domains; they must not be presented as one atomic source-tree transaction.
|
||||
- Worker retention/lifecycle policy and destructive administrative operations.
|
||||
|
||||
Repository-access **references** in authored config are not credential private
|
||||
material. Granting config access does not grant secret-management operations,
|
||||
filesystem access, command execution, or arbitrary Workspace access. Authored
|
||||
text can itself contain sensitive information; configuration access must be an
|
||||
explicit operator grant, not a consequence of possessing Read/Write Tools.
|
||||
Existing secret stores must never be serialized into observation, operation
|
||||
results, diagnostic bodies or history through this provider.
|
||||
|
||||
## Enablement and operator grants
|
||||
|
||||
Choose Worker WIP mode and the Feature implementation independently of authority:
|
||||
|
||||
```toml
|
||||
[worker]
|
||||
mode = "wip"
|
||||
|
||||
[feature.workspace_config]
|
||||
enabled = true
|
||||
```
|
||||
|
||||
The Feature defaults disabled; enabling it does not add a settings grant. An
|
||||
operator authenticated as the Workspace owner grants access to a specific
|
||||
registered Runtime/Worker through `workspace_config_grant_create`:
|
||||
|
||||
```text
|
||||
POST /api/w/<workspace-id>/workspace-config-grants
|
||||
{"runtime_id":"<registered-runtime>","worker_id":"<registered-worker>","access":"read_write"}
|
||||
```
|
||||
|
||||
Use `read_only` for observation/read-only access. Grant creation is a separate
|
||||
owner administration action, not an approval prompt for each edit. To revoke,
|
||||
use the owner operation `workspace_config_grant_revoke` on the returned grant
|
||||
ID. Worker requests carry authenticated source identity, never these IDs as a
|
||||
substitute for authorization. A grant from another Workspace or another Worker
|
||||
cannot be adopted by knowing a route or possessing a previous Interface.
|
||||
|
||||
The existing connection ledger remains authoritative. Each granted Worker has
|
||||
an independently identified logical registry target over the **same** authored
|
||||
configuration tree. This preserves the ordinary Workdir exclusive-owner
|
||||
invariant without making separately granted Workers maintain separate config
|
||||
stores. Every attachment has an opaque connection lifetime ID; alias reuse is
|
||||
not identity reuse. No Runtime filesystem session is created for this target.
|
||||
Detaching ends that connection's use but deletes neither configuration nor its
|
||||
registry target. Configuration is not a Workdir-cleanup or command target.
|
||||
|
||||
## User invocation and Worker access
|
||||
|
||||
Select `/workspace-config` using the common Feature completion menu. Complete
|
||||
its declared parenthesized argument form (close `)` to use the default access),
|
||||
then send the typed chip with the request, for example:
|
||||
|
||||
```text
|
||||
/workspace-config() このWorkspaceのモデル設定を変更して
|
||||
```
|
||||
|
||||
The composer preserves the selection as a typed invocation chip. Ordinary text
|
||||
that happens to contain this spelling is not a command. Feature preparation
|
||||
calls Backend attach before dependent LLM input and displays the selected
|
||||
logical subject and effective access. Failure/unknown preparation must not be
|
||||
reported as a successful attachment. Optional declarative `access` values are
|
||||
`effective` (default), `read_only`, and `read_write`. They restrict existing
|
||||
rights; explicit `read_write` fails rather than silently downgrading. A Worker
|
||||
can invoke the native attach operation at `/workspace-config` through the same
|
||||
Feature/Backend handler. Neither path creates a grant or a second ledger.
|
||||
|
||||
The logical content root is `/workspace-config`, not `/checkouts` or a host
|
||||
path. Observe exposes requested structure and available Interfaces; contents
|
||||
are retrieved through Operations. Implicit directories reflect current
|
||||
`VirtualPath` entries and are not independently stored directories.
|
||||
|
||||
## Backend transport and consistency
|
||||
|
||||
Worker operations use identity-bound routes under
|
||||
`/api/w/<workspace-id>/workers/self/workspace-config`:
|
||||
|
||||
| Interaction | Route | Contract |
|
||||
| --- | --- | --- |
|
||||
| Resolve connection | GET root | Current grant checked even if authorized but unattached |
|
||||
| Attach | POST root | Idempotent reuse of the active canonical `workspace-config` alias |
|
||||
| Observe | POST `observe` | Requested paths/depth; metadata only, no source bodies |
|
||||
| Read | POST `read` | Connection + path validator; canonical body/type/digest |
|
||||
| Save | POST `commit` | Connection + root validator + canonical config changes/CAS |
|
||||
|
||||
Observe returns root revision/digest and path validators from the same snapshot.
|
||||
Opaque validators bind Workspace, Worker, grant, connection lifetime, logical
|
||||
path and whole-tree revision/digest. Even replacing a file with identical bytes
|
||||
in a later revision invalidates older observations. Whole-tree CAS is
|
||||
intentionally conservative: an unrelated UI edit can require fresh observation.
|
||||
The Client holds validators; the LLM must not manage or fabricate them.
|
||||
|
||||
Authorization, active connection lookup and operation use share the per-Worker
|
||||
session lock with detach/revoke. The canonical Immediate-transaction CAS remains
|
||||
the final cross-Worker/UI commit boundary. Failed validation, stale state and
|
||||
permission rejection do not overwrite configuration. Potential auxiliary
|
||||
repository-secret effects and uncertain persistence are not described as a
|
||||
full rollback. Sanitized failures distinguish `not_committed` and `unknown`;
|
||||
a timeout/disconnect after dispatch never causes automatic mutation retry.
|
||||
|
||||
Structural observation is bounded to depth 0–8 and 256 returned nodes, paths to
|
||||
512 bytes and each text entry to 256 KiB. Oversized observations fail rather
|
||||
than presenting a truncated result as complete. Source-tree creation/deletion
|
||||
is expressed through canonical changes, including atomic multi-file changes
|
||||
needed to update a source together with its import/registry references. The
|
||||
`main.dcdl` entrypoint cannot be deleted/renamed. These are configuration
|
||||
operations, not a generic POSIX filesystem or an OS mount.
|
||||
|
||||
## Native operation mapping
|
||||
|
||||
| WIP target | Operations and model arguments | Canonical action |
|
||||
| --- | --- | --- |
|
||||
| Root | `attach(access?)` | Same Backend connection operation as the selected slash invocation |
|
||||
| Root | `apply_changes(changes)` | One atomic create/update/delete/rename batch; source and import/registry edits can be combined |
|
||||
| Directory | `create(path, content, content_type?)` | Create an entry below this directory; ancestor directories are implicit |
|
||||
| File | `read()` | Return canonical text and content type, not a byte-stream or OS descriptor |
|
||||
| File | `write(content)` | Update this exact entry, preserving its content type |
|
||||
| File | `edit(old_string, new_string, replace_all?)` | Unique string replacement unless `replace_all` is selected |
|
||||
| File | `delete()` | Canonical deletion subject to entrypoint/import validation |
|
||||
| Explicit missing path | `create(content, content_type?)` | Create that exact absence-observed entry, not a substituted path |
|
||||
|
||||
Content type is `decodal` (default for creation) or `text`. File operation
|
||||
targets come from the resolved WIP route. The root change batch uses canonical
|
||||
relative source paths; it does **not** accept `expected_digest`, revision,
|
||||
validator, Workspace, grant or connection overrides as model arguments. The
|
||||
adapter captures metadata and fills those preconditions automatically, requiring
|
||||
all paths in a batch to match one observed tree state. Unsupported/invalid
|
||||
configuration mutations fail instead of performing a partial multi-file save.
|
||||
|
||||
Example `apply_changes` argument:
|
||||
|
||||
```json
|
||||
{"changes":[{"kind":"create","path":"notes/readme.md","content_type":"text","content":"Configuration notes"}]}
|
||||
```
|
||||
|
||||
These are native Interfaces only. There is no configuration-specific
|
||||
compatibility Tool or fallback under `/tools`, no Bash location, and no
|
||||
`wip-fs-server`/Linux real-filesystem provider substituted for these operations.
|
||||
|
||||
## Validation scope
|
||||
|
||||
Start with the changed contracts:
|
||||
|
||||
```sh
|
||||
cargo test -p manifest --lib workspace_config
|
||||
cargo test -p worker --lib workspace_config
|
||||
cargo test -p worker --lib manage_workdir::wip
|
||||
cargo test -p yoi-workspace-server --lib workspace_config_integration
|
||||
cargo test -p yoi-workspace-server --lib config_source::tests
|
||||
cargo test -p server-api --lib workspace_config
|
||||
cargo test -p tui --lib backend_worker_picker
|
||||
```
|
||||
|
||||
The Backend integration tests use production Feature completion selection,
|
||||
structured `Worker::run` input, request preparation and the real Backend router
|
||||
and SQLite configuration store. At the recording LLM-client boundary they
|
||||
assert that attach and its durable context happened first, then use the actual
|
||||
WIP Client/Host to read/edit/write/create/delete and atomically change a source
|
||||
with its import. The existing Prompt reader confirms the saved projection.
|
||||
Signed-route, owner-grant, readonly/revoke, detach/lifetime, multi-Worker/UI CAS,
|
||||
same-content replacement, invalid configuration, storage-failure and dropped
|
||||
post-save-response cases complement that sequence.
|
||||
|
||||
Web tests exercise the production generic composer component's declaration
|
||||
completion, selection and typed submission, plus removal/literal-text behavior.
|
||||
Logical-source parsers and launch/sidebar/TUI tests ensure that a settings
|
||||
attachment is not treated as a repository revision or process cwd. The two
|
||||
layers are complementary; the Rust fixture uses a recording LLM client and
|
||||
in-process transport, not a live browser-to-provider deployment. No live
|
||||
Backend/Runtime/LLM browser or physical power-loss result is claimed. Bounds
|
||||
are finite input/tree and lock/request deadlines, not a promise that synchronous
|
||||
configuration evaluation is forcibly preemptible at a hard wall-clock limit.
|
||||
|
||||
Completion validation also runs the current-source root `cargo check`, complete
|
||||
changed-crate suites, the server-api TypeScript configuration and generated
|
||||
contract checks, formatting and diff checks. Updating this code does not update
|
||||
or restart the running dogfooding environment.
|
||||
@@ -0,0 +1,30 @@
|
||||
# SubWorker cwd and logical paths during T-695
|
||||
|
||||
Two writable child sessions initially reported missing Workdir/Ticket access.
|
||||
The concrete observations did not justify that conclusion:
|
||||
|
||||
- A default-profile child had no `ShowTicket` tool, while the `inherit` child
|
||||
could read the Ticket. Product requirements therefore had to be forwarded or
|
||||
the effective child profile chosen explicitly.
|
||||
- A child spawned with `cwd: crates/workspace-server` received
|
||||
`file not found: crates/workspace-server/crates/workspace-server/src/config_source.rs`
|
||||
for a root-relative `Read` path. `Read("src/config_source.rs")` succeeded.
|
||||
- `Read("../server-api/src/lib.rs")` was rejected as an invalid logical path,
|
||||
despite both crate directories being in the declared child scope.
|
||||
- The read-only investigation child successfully inspected repository sources;
|
||||
successful spawning was not evidence that every child had the expected tools,
|
||||
but one invalid absolute output-directory lookup was not evidence of an
|
||||
inaccessible repository either.
|
||||
|
||||
The parent inspected committed child sessions and preserved their changes.
|
||||
The server child was released at Idle, then continued under an explicitly
|
||||
`inherit`, root-cwd child with the same per-crate write grants and root read
|
||||
scope. No host-path or parent-traversal workaround was used to bypass the
|
||||
rejected path. The original prerequisite diff and test evidence were retained.
|
||||
|
||||
Improvement: tool descriptions and generated child startup context should agree
|
||||
on whether relative file paths are attachment-root-relative or cwd-relative.
|
||||
Expose effective attachment aliases, root/cwd coordinates and granted tool
|
||||
surface directly to the child, so a profile/tool mismatch is not misdiagnosed
|
||||
as a repository authority failure. Scope validation should distinguish invalid
|
||||
path syntax, missing file, missing attachment and actual permission denial.
|
||||
Reference in New Issue
Block a user