docs: consolidate design documentation and standardize crate English
This commit is contained in:
+15
-56
@@ -1,66 +1,25 @@
|
||||
# Yoi documentation
|
||||
# 文書の維持管理
|
||||
|
||||
This directory contains maintained developer documentation for Yoi. Its job is to preserve design intent that is hard to recover from code alone.
|
||||
文書には、コードから読み取れない設計理由と、開発を継続するための規則を残す。利用ガイド、設定説明、実装一覧は維持しない。同じ説明を複数の場所に置かず、変更の責任を持つコードの近くで管理する。
|
||||
|
||||
It is not a dumping ground for external research, old plans, API inventories, or ticket history. Those belong in local notes, work item artifacts, code, or git history.
|
||||
## 保管場所と責務
|
||||
|
||||
## Reading order
|
||||
- `principle/`:複数の実装に共通する設計原則。判断が競合したときの基準と、その理由を書く。個別の処理手順やデータ構造は置かない。
|
||||
- `development/`:テスト設計、検証、レビューなど、開発を続けるための規則。製品の操作手順や環境設定の説明は置かない。
|
||||
- `report/`:問題発生時の報告。試みた操作、観測した事実、影響、原因の確度、未解決事項を記録する。過去の報告は現行仕様の根拠にせず、当時の記録として蓄積できる。
|
||||
- Rustのcrate README:責務と非責務を短く示し、主要moduleへの参照を置く。各crateの`lib.rs`は`#![doc = include_str!("../README.md")]`でREADMEをcrateドキュメントとして使う。
|
||||
- Rustのmodule・itemドキュメント:所有境界、状態遷移、失敗時の保証と限界、非自明な判断理由を書く。API一覧や実装の逐語説明は繰り返さない。
|
||||
|
||||
1. [`design/overview.md`](design/overview.md) — the system map.
|
||||
2. [`design/durable-operations.md`](design/durable-operations.md) — cross-domain operation identity, checkpoints, retries, child operations, and disposition, including durable Workdir removal.
|
||||
3. [`design/context-history.md`](design/context-history.md) — the highest-risk invariant: inputs that affect the model must be committed to history before they enter context.
|
||||
4. [`design/worker-session-state.md`](design/worker-session-state.md) — Worker identity, replayable session logs, current metadata, and live process hints.
|
||||
5. [`design/session-observation.md`](design/session-observation.md) — common session captures, `SessionEntryRef`, Memory evidence, and host-authorized Worker observation.
|
||||
6. [`design/flow-state-graph.md`](design/flow-state-graph.md) — Workspace Flow sources, frozen content-addressed definitions, transition attempts, and bounded internal verification.
|
||||
7. [`design/profiles-manifests-prompts.md`](design/profiles-manifests-prompts.md) — reusable Profiles, resolved Manifests, and prompt resources.
|
||||
8. [`design/tool-permissions-scope.md`](design/tool-permissions-scope.md) — tool policy and filesystem scope.
|
||||
9. [`design/plugin-packages.md`](design/plugin-packages.md) — the retained offline package format and the prohibition on ambient Plugin authority.
|
||||
10. [`development/plugin-development.md`](development/plugin-development.md) — explicit-path offline Plugin package authoring and inspection.
|
||||
11. [`design/memory-knowledge.md`](design/memory-knowledge.md) — deprecated single-document Workspace Memory authority and its separation from subject-scoped subjektiv Memory.
|
||||
12. [`design/workspace-kanban-orchestrator-runtime.md`](design/workspace-kanban-orchestrator-runtime.md) — how Kanban operations become durable orchestration events and backend-internal routing decisions.
|
||||
13. [`design/workspace-runtime-docker.md`](design/workspace-runtime-docker.md) — the WebUI / Backend / Runtime split, Docker image layout, worker launch path, and workdir materialization boundary.
|
||||
14. [`development/ui-ux/design-language.md`](development/ui-ux/design-language.md) — Workspace Webの視覚・interaction原則、文法、token、汎用component。
|
||||
15. [`development/ui-ux/product-ux.md`](development/ui-ux/product-ux.md) — Workspace resource、navigation IA、resource固有pattern。
|
||||
16. [`development/ui-ux/application-architecture.md`](development/ui-ux/application-architecture.md) — application shell、Header、Sidebar、nested override architecture。
|
||||
17. [`development/ui-ux/visual-review.md`](development/ui-ux/visual-review.md) — Web UI実装者がbefore/afterを目視し、完了前に残すvisual validation evidence。
|
||||
18. [`development/server-runtime-auth.md`](development/server-runtime-auth.md) — manual Workspace Server / Runtime public-key exchange and authenticated Runtime startup checks.
|
||||
19. [`development/work-items.md`](development/work-items.md) — how project work is recorded and reviewed.
|
||||
20. [`development/rust-testing-strategy.md`](development/rust-testing-strategy.md) — what Yoi Rust tests should prove, where they belong, and how to name them.
|
||||
21. [`development/validation.md`](development/validation.md) — how to check changes.
|
||||
22. [`development/workspace-schema-migrations.md`](development/workspace-schema-migrations.md) — how to preflight, apply, verify, and roll back control-plane SQLite schema changes.
|
||||
23. [`design/standalone-agent-host.md`](design/standalone-agent-host.md) — in-process standalone Worker host の依存方向、authority、lifecycle、非目標。
|
||||
24. [`design/server-feature-storage.md`](design/server-feature-storage.md) — Server管理のWorkspace/Feature専用SQLite、migration、transaction、backup/restore、削除・shutdown境界。
|
||||
25. [`design/subjektiv-store.md`](design/subjektiv-store.md) — subject-scoped Memoryのownership、authorization、Session attribution、変更履歴、candidate consolidation境界。
|
||||
26. [`design/subjektiv-surface.md`](design/subjektiv-surface.md) — confirmed Memoryから生成するresident surface、availability、append-only restore injection。
|
||||
27. [`development/subjektiv-product-cutover.md`](development/subjektiv-product-cutover.md) — legacy Workspace Memoryからsubjektivへのoperator cutover、explicit reset、backup/rollback runbook。
|
||||
28. [`design/worker-wip-mode.md`](design/worker-wip-mode.md) — explicit-opt-in Worker WIP mode, Worldspace projection, authority, cache, and retry contracts.
|
||||
29. [`design/objective-wip-projection.md`](design/objective-wip-projection.md) — Host-assigned Objective collection/item routes, operation mapping, Backend authority, bounds, and compatibility suppression.
|
||||
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.
|
||||
WebなどRust以外の実装に固有の理由は、そのソースのmodule・型・処理に付くコメントで管理する。無関係なRust crateへ移さず、実装説明の別冊も増やさない。複数の画面・部品の設計判断に使うDesign LanguageやUX規則は文書として維持し、個別実装の説明と区別する。
|
||||
|
||||
## What belongs here
|
||||
`docs/`直下のファイルはこのREADMEだけとする。このファイルは文書の管理規則であり、全ファイルの索引にはしない。
|
||||
|
||||
Keep documentation when it records a stable design boundary, a non-obvious rationale, or a workflow that future changes must respect.
|
||||
## 追加・変更・削除の判断
|
||||
|
||||
Examples that belong:
|
||||
文書を追加する前に、残す必要のある判断が既存の原則、moduleドキュメント、テストのどこにも説明されていないことを確認する。コマンド出力、設定項目、型やフィールド、モデル名、schema番号、テスト件数など、実装から得られる可変情報は転記しない。APIの契約を説明する最小の例や、開発規則を実行する検証コマンドは、その目的に必要な範囲で残せる。
|
||||
|
||||
- Why Worker metadata is not the session log.
|
||||
- Why `Profile` and resolved `Manifest` are different layers.
|
||||
- Why context-only event injection is forbidden.
|
||||
- Why child Worker notifications are hints rather than completion proof.
|
||||
設計変更と同じ差分で、その理由を所有するドキュメントを更新する。設計がなくなった場合は説明も削除する。移動・削除時にはリンク、`include_str!`、テスト、生成処理からの参照を確認し、古い場所に案内だけの文書を残さない。`report/`の過去のパスや引用は、当時の証拠として扱う。
|
||||
|
||||
## What does not belong here
|
||||
`crates/`以下のREADME・rustdoc・説明コメントは英語で統一し、それ以外の文書は日本語で書く。実行時の文言、テストデータ、引用は言語統一のために変更しない。コード識別子、API名、パスは原綴りを保つ。段落の最初に主張を置き、対象・条件・根拠を明記する。未確認の主張を断定せず、同じ内容の予告・言い換え・総括を重ねない。
|
||||
|
||||
Do not keep material only because it once helped a ticket.
|
||||
|
||||
Examples that should be removed or moved to `docs/.local/` / work item artifacts:
|
||||
|
||||
- External project comparisons.
|
||||
- Provider API snapshots, prices, or model tables.
|
||||
- Old implementation plans that are no longer the design authority.
|
||||
- Public type or method inventories that drift with code.
|
||||
- Debug notes that are useful only for one investigation.
|
||||
|
||||
`docs/.local/` is intentionally outside the maintained documentation surface.
|
||||
作業計画と進捗はTicket・Objective・セッションの記録へ置く。秘密情報、認証情報、非公開プロンプト、生ログは文書へ転載しない。無視対象のローカル調査資料は、製品が参照する正本や維持対象の文書にしない。
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# Guarded Worker Restore and Web observations
|
||||
|
||||
T-701 extends the [registry-bound operation reception](workspace-worker-operations.md), not the Worker creation, Resume or Restore state machines.
|
||||
|
||||
## Different tokens protect different things
|
||||
|
||||
| Operation | Observed identity / expected input | Atomic validation and execution owner | Conflict / uncertain outcome | Shared client behavior / deliberate difference |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Restore | Workspace + Runtime + Worker identity; Runtime-owned `restore_observation_token`; immutable `expected_observation_token` + `request_id` | Existing Runtime per-Worker lifecycle operation lock, durable admission and lifecycle commit. Preparation and execution follow admission, not a frontend GET comparison | Stale new intent is HTTP 409 (`restore_observation_conflict`); operation results remain accepted / rejected / rolled_back / reconciliation_required. A lost reply is unknown, not definite rollback | Both entrances use the same scoped POST/parser/notice classifiers and observation refresh. Never replace a rejected intent's token and automatically retry |
|
||||
| Stop | Identity and reason, **not** a catalog content CAS | Runtime per-Worker operation lock and existing backend Stop settlement; Workspace session finalization only after accepted | Existing flat accepted / unsupported / rejected result with diagnostics | Same encoded target paths and bounded HTTP-error messages; refresh observations instead of writing a guessed local stopped state. Stop does not gain Restore's observation-token admission guarantee |
|
||||
| Pause / Resume / Cancel / Compact | Connected execution identity and `WorkerCommandEnvelope.command_id` | Existing bound execution/controller command admission | Command acknowledgement: Accepted / StaleCommandId / Conflict / InvalidState, with authoritative state | Command IDs order commands; they are not catalog observation tokens. Resume is not Restore; connections do not silently move to a replacement execution |
|
||||
| Delete / cleanup | Runtime + selected target IDs; `expected_plan_digest` of the candidate set, dependencies and blockers | Existing cleanup validation, per-target live/retention revalidation, removal service and catalog commits | Existing cleanup-plan conflicts, per-target status and diagnostics | Preserve plan protection for candidate sets and dependencies. Never substitute a Worker observation token. Page plans invalidate at the beginning of every catalog refresh and are unavailable until fresh catalog and plan observations settle |
|
||||
| Pin / Unpin | Registry Worker identity and explicit boolean | Registry retention update and ordered projection publication | Existing retention result/errors | Preserve retention semantics and full catalog metadata; refresh shared observations and cleanup plan |
|
||||
| Submit / Notify | Authenticated source + durable submission / notification request ID | Existing controller/session durable receipt admission | Existing accepted/rejected receipts and unknown delivery diagnostics | These are not Restore requests; Restore never manufactures input, Resume or a new Worker |
|
||||
|
||||
There is no universal Worker command token or shared frontend action lock. Workspace reception resolves a stable target and rechecks caller authorization; Runtime/controller/store owners retain operation-specific atomic boundaries.
|
||||
|
||||
## Browser request and result
|
||||
|
||||
Normal explicit Restore is enabled only for `availability = observed`, catalog `stopped`, and an available backend observation token. Sidebar display state and foreground `worker_state` are not eligibility evidence: sidebar uses `lifecycleState`; the page uses the full catalog's `state`. Neither UI guesses snapshot, singleton, Workdir provider or authorization eligibility.
|
||||
|
||||
Both entrances send the generated two-field `WorkerRestoreRequest` to:
|
||||
|
||||
```text
|
||||
POST /api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/restore
|
||||
```
|
||||
|
||||
Each ID is independently encoded. No Ticket-assignment query, Submit, Resume or Worker-create call is used. The bounded parser validates wrapper identity, the four states, diagnostics and optional **Workspace WorkerSummary**. A Stop lifecycle result or launch summary is not a Restore response. Unknown states/fields/invalid identities cannot produce a success notice.
|
||||
|
||||
Accepted means the existing Restore operation committed, not that the Worker is necessarily Running or a later run succeeded. A returned summary validates result identity; display is updated from fresh shared observations, not an old POST snapshot. Rejected and rolled_back display diagnostics without success. Reconciliation_required and transport/malformed-response uncertainty retain the original immutable request for an explicit **Retry Restore** action. That action sends the same request; it does not create an operation, exchange its observation token, or automatically loop. A conflict clears this local recovery intent and asks the user to review fresh observations before a new explicit operation. A GET alone does not settle Runtime's pending lifecycle operation.
|
||||
|
||||
## Backend admission, preparation and recovery
|
||||
|
||||
The Workspace `WorkspaceWorker` reception still resolves target identity and rechecks caller authority. Its Restore coordinator first dispatches the public tuple to Runtime without candidate preparation. A terminal owner returns its historical receipt; an admitted pending owner supplies its stored preparation before renewed singleton/Workdir/External eligibility is evaluated. Historical result summaries are read from the current Runtime and are not written back as catalog observations.
|
||||
|
||||
For a new intent, Workspace builds read-only preparation references after the normal eligibility checks. Runtime atomically checks the observation token again under the existing per-Worker lock and persists the owner together with `pending_restore` and its existing lifecycle operation ID. Only this admitted operation can proceed to SSH resource issuance, binding/access preparation and execution. A race between the first observation check and admission therefore cannot issue credentials or launch a new Restore from the old intent.
|
||||
|
||||
Preparation completion is attached to that same pending operation, not a replacement operation. Retained repository-access snapshots and exact request identity allow interrupted preparation to resume without exchanging candidate bindings. Runtime retries use the original owner's payload; a freshly generated preparation cannot rewrite the admitted target or expected observation. Stop and startup reconciliation continue to use the existing pending lifecycle ownership. When startup/legacy persistence has a pending lifecycle operation but no public receipt owner, an explicit request with the current observation may bind recovery to that exact operation ID, without applying candidate preparation. Once bound, a different request cannot take over; terminal replay does not resurrect a later stopped Worker.
|
||||
|
||||
| Caller | Intent identity retained / validation boundary |
|
||||
| --- | --- |
|
||||
| Web sidebar and page | Immutable two-field request retained locally for explicit uncertain-result recovery; no shared in-flight lock |
|
||||
| Workspace alias, scoped and Worker-control HTTP | Mandatory public request; common target/auth reception and the same Runtime admission |
|
||||
| Embedded / remote Runtime providers and management HTTP | Typed Runtime request and coordination transport, including original observation and lifecycle operation owner |
|
||||
| `WorkerRestore` Tool | Observed token in Tool arguments and durable Tool-call-derived request identity |
|
||||
| Existing TUI Restore/resume path | Selected row's token and exact request retained through the existing flow. Unknown/pending results refresh Session observation and prompt for an explicit same-tuple retry; cancellation does not retry and reports the original tuple. Conflict exits for fresh selection, never exchanges the failed intent. No new TUI Restore entrance |
|
||||
| Internal Coder, Orchestrator, subject reuse and compensation | Domain intent journal pins the request/token before dispatch and retains it through unknown result and restart. A definitive observation conflict retires only that rejected intent without automatic retry; a later deliberate invocation may pin fresh observation with a fresh request ID. It does not own lifecycle status or bypass Runtime validation |
|
||||
| Runtime startup / pending reconciliation | Private recovery of the existing persisted Restore owner and operation; not an external unguarded new-intent endpoint |
|
||||
|
||||
The Workspace internal intent journal stores client retry identity only. Runtime remains the lifecycle authority; it is not a second Restore state machine, Worker ledger or cross-screen lock.
|
||||
|
||||
## Observation and cleanup-plan ordering
|
||||
|
||||
`workspaceWorkersStore` retains the existing sidebar stream/projection and also fetches the full Workspace Worker catalog for the page. Full catalog metadata preserves retention, attachments, implementation and targets that are not sidebar entries. Stream updates overlay lifecycle/availability/foreground snapshot/token without interpreting absence as a new page exclusion.
|
||||
|
||||
Every stream update or explicit refresh increments a local **observation scheduling version** and publishes `catalogRefreshing` before fetching. This local version fences asynchronous display work; it is **not** Restore admission authority and is never sent to Runtime. A newer event/GET supersedes older GET completion. Old POST summaries and page-loader data cannot overwrite the active catalog. On disposal, requests and subscription callbacks are fenced. Catalog fetch errors have their own stable alert episode rather than clearing transport/Workdir alerts.
|
||||
|
||||
The page invalidates cleanup plans immediately when observations refresh. It does not fetch or install plans while `catalogRefreshing` is true. Successful refresh completion triggers plan reads even when the resulting catalog contents did not change. Workspace/lifetime and cleanup epochs reject late previous plan responses. A failed refresh leaves old candidates unavailable instead of treating old page data as fresh.
|
||||
|
||||
Both components retain local busy state only. Requests pin their starting Workspace/target, and late replies after Workspace switch/disposal cannot update another Workspace's notices, list or busy state. Sidebar and page may issue competing requests: atomic Runtime admission—not a cross-component client lock—decides their outcomes. Console links and existing sidebar filtering remain unchanged.
|
||||
|
||||
## Validation surfaces
|
||||
|
||||
- Runtime admission tests prove stale/ABA and serialization, durable same-request replay and pending reconciliation.
|
||||
- Runtime management, embedded/remote providers, Workspace aliases/scoped/control routes, Tool and existing TUI callers exercise the guarded API rather than a legacy unguarded Restore.
|
||||
- `tests/runtime-workers.test.ts` and `test/sidebar/worker-actions.test.ts` prove response/request and notice contracts; the Runtime parser tests are included in the normal Web test task.
|
||||
- Rendered `worker-stop.browser.spec.ts` and `workers-page.browser.spec.ts` cover menu/action eligibility, busy behavior, four outcomes and errors, explicit retry, Workspace/disposal fences, ordinary Console/Pin/Delete, sidebar-origin updates and cleanup epochs.
|
||||
- `worker-restore-store.browser.spec.ts` tests the real store's full metadata, GET/event ordering, dependent-plan invalidation, no POST recovery loop, catalog-only target preservation and disposal.
|
||||
|
||||
Exact executed validation counts and immutable source/review evidence belong to the repository Merge Request and final approved Ticket handoff.
|
||||
@@ -1,140 +0,0 @@
|
||||
# Workspace Worker operations
|
||||
|
||||
T-702 documents the implemented Workspace operation-reception boundary. It does not move execution authority into the Workspace registry or make all Worker-related routes use one API. The common implementation is [`worker_operations.rs`](../../crates/workspace-server/src/server/worker_operations.rs); production adapters and service orchestration are in [`server.rs`](../../crates/workspace-server/src/server.rs).
|
||||
|
||||
## Identity reception is not execution authority
|
||||
|
||||
`WorkspaceWorker::resolve` resolves a Workspace registry reference to a `RuntimeWorkerRef` (Runtime ID and Worker ID). Resolution first tries that exact pair, then a Workspace resource reference constrained to the requested Runtime. It returns an identity-bound handle, not a live execution snapshot, authorization lease, or promise that the Worker is running. Each operation rereads the target registry record and authorizes its own context.
|
||||
|
||||
The owners have different responsibilities:
|
||||
|
||||
- **Workspace registry and domain services:** durable Workspace identity, retention/pinning, Workdir links and sessions, Ticket assignments, credentials/bindings, control grants, restore coordination, and removal plans/catalog publication. Registry projections and observation streams describe Workers; they do not admit controller commands.
|
||||
- **`RuntimeRegistry` and Runtime adapters:** select the Runtime owning the resolved pair and delegate execution. Remote and embedded adapters preserve their existing transport/error behavior. Runtime owns lifecycle operation settlement and checks Workspace scope at the scoped embedded execution boundary.
|
||||
- **Execution backend and Worker controller/session:** own the actual execution endpoint, command admission, authoritative Idle/Busy state, in-flight run transitions, Submit FIFO and durable receipts, Notify receipts, and compaction. A catalog status is not sufficient evidence for command admission.
|
||||
|
||||
The term *controller* in a `WorkerControl` context means the calling Runtime Worker holding a grant over a target. It is not the target's in-process execution controller.
|
||||
|
||||
## Production ingress → common method → execution owner
|
||||
|
||||
For the HTTP rows below, `R` abbreviates `/api/runtimes/{runtime_id}/workers/{worker_id}` and `W` abbreviates `/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}`. Aliases operate in the server's configured Workspace; scoped routes additionally validate the Workspace path. The declarations live in [`server-api`](../../crates/server-api/src/lib.rs), and the generated contract service implementations in `server.rs` are the production unary adapters.
|
||||
|
||||
| Ingress | Common reception / coordination | Execution or persistence owner |
|
||||
| --- | --- | --- |
|
||||
| `POST R/restore` and `POST W/restore` | `WorkerOperationContext::from_request` → `restore_runtime_worker_with_context` → `WorkspaceWorker::restore`; scoped route first uses `scoped_restore_runtime_worker_with_context` | `WorkspaceApi::restore_workspace_worker` retains the existing singleton, Workdir, credential/binding and pending-operation reconciliation flow; Runtime/backend owns restored execution |
|
||||
| `POST R/input` and `POST W/input` | Resolve → convert request → `WorkspaceWorker::input` | `RuntimeRegistry::send_input` → Runtime adapter → execution backend/controller/session |
|
||||
| `POST R/stop` and `POST W/stop` | `WorkspaceWorker::stop` | Runtime owns Stop; on an **Accepted** result Workspace closes the current Workdir session and synchronizes linked Workdir state. Rejected/Unsupported results do not perform that finalization |
|
||||
| `POST R/cancel` and `POST W/cancel` | `WorkspaceWorker::cancel` | `RuntimeRegistry::cancel_worker` → target execution controller; not registry removal |
|
||||
| `PUT W/pin` / `DELETE W/pin` | `WorkspaceWorker::set_pinned(true/false)` | Workspace store retention update, ordered through `worker_projection`; no execution command |
|
||||
| `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 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 |
|
||||
|
||||
Both WS adapters call `WorkspaceWorkerMethodSender::send`, which selects the following **named methods** rather than implementing separate command semantics in each transport:
|
||||
|
||||
| Public protocol method | Named common sender method | Distinct owner/receipt |
|
||||
| --- | --- | --- |
|
||||
| `Pause { command }` | `pause` | Controller command admission/acknowledgement; pause is not Stop |
|
||||
| `Resume { command }` | `resume` | Controller resumes the paused run; not Restore |
|
||||
| `Cancel { command }` | `cancel` | Controller command admission for cancelling the current run; distinct from HTTP lifecycle request/result |
|
||||
| `Compact { command }` | `compact` | Controller maintenance/compaction, with command acknowledgement and subsequent lifecycle events |
|
||||
| `Submit { submission_request_id, input }` | `submit` | Session durable Submit receipt/FIFO and later run execution |
|
||||
| `Notify { notification_request_id, message }` | `notify` | Session durable Notify receipt and advisory delivery, not the Submit FIFO |
|
||||
|
||||
All six methods funnel through `dispatch` / `send_checked`. Other browser-allowed protocol methods also use that checked dispatch; the six named methods are not an exhaustive allowlist. For example pending-submission operations, rewind, Shutdown, completions, ListWorkers, RestoreWorker and RegisterPeer are allowed by `authorize_browser_worker_method`. This forwarding does not replace their lower-layer semantics with Workspace registry operations.
|
||||
|
||||
`build_inner_router` installs the three production WS routes using typed `websocket_route` registrations and merges the generated Workspace contract router for unary routes. [`workspace_subscription.rs`](../../crates/workspace-server/src/workspace_subscription.rs) handles multiplexing. Its `WorkspaceWorkers` subscription is a separate catalog stream with no method sender; only a runtime-scoped `WorkerProtocol` subscription carries an operational sender.
|
||||
|
||||
## Context and authorization restrictions
|
||||
|
||||
`WorkerOperationContext` is crate-private. Clients cannot submit this enum to select their authority.
|
||||
|
||||
- **Request-derived Worker control:** `from_request` uses the middleware-verified Runtime Worker subject and current Workspace registry membership. The shared source-binding helper rejects Runtime-only proofs and conflicting identity hints; no hint-only request or Account fallback can impersonate a Worker. Known-worker adapters bind their source through the same verified request extension before constructing the operation context.
|
||||
- **Account/browser context:** despite the `Browser` name, this also represents account-authenticated unary requests. It retains request headers and the server-derived Account input source. On each common authorization call, the server re-resolves credentials, rejects revoked credentials or an Account change, requires the configured Origin for `BrowserSession` actors, and requires an active Workspace. It does not interpret the operation's permission string as a Worker-control grant check for an Account actor; request middleware remains part of the route's authorization boundary.
|
||||
- **Worker-control context:** the target and calling Worker must be registered. The server requires an active grant for this controller/target pair and the operation's permission, locks by grant ID, then rereads the target and rechecks permission and the exact grant ID after acquiring the lock. A replacement grant requires new admission under its own lock. A missing active grant is reported as UnknownWorker; a present grant lacking the permission is InvalidInput. Merely knowing an ID does not grant control.
|
||||
- **Test Backend context:** `WorkerOperationContext::Backend` exists only under `#[cfg(test)]`. It bypasses context authorization after the target-record check. There is no production Backend enum variant for a client, Worker Tool, or generic internal caller to select. Production Backend services continue to own their explicit domain flows.
|
||||
|
||||
Worker-control grants are Workspace-owned records, not inferred parentage or universal access to every Runtime Worker. The production spawn flow persists a controller/subject grant with `send_input`, `notify`, `cancel`, `stop`, `restore`, `remove`, and `observe`; that default does not include `pin`. Known-worker discovery follows active grants, while Internal children use their separate parent-owned registry. Other grant origins/permission sets must be read from their own authority rather than assumed from this spawn default.
|
||||
|
||||
Permissions are `restore`, `stop`, `cancel`, `pin`, and `remove` for those identity operations. HTTP input uses `notify` for Notify and `send_input` for all other accepted kinds. `connect_protocol` checks `observe`; every connected method dispatch checks `send_input`, requires an interactive target, then explicitly requires a **Browser** context. Thus an observe grant is not a mutation grant, and a Worker-control grant does not enable browser WS method forwarding. WS Notify also uses this browser-only `send_input` check, not HTTP input's `notify` grant mapping.
|
||||
|
||||
`input` and all connected protocol method sends reject Workers with a Backend Job projection: their immutable input is Backend-owned and their Console is read-only. Connecting for observation is not itself rejected by that interactive check.
|
||||
|
||||
For Worker-control user input, `input` converts `User` to `UserIfIdle`; admission belongs to the target controller and never queues behind an active run. Browser Submit may durably queue while Running/Paused. Notify is advisory input, not a queued Submit; it can be incorporated at a between-turn boundary or drive the notification flow while Idle. The known-worker input adapter turns definite non-Accepted input results into errors, but keeps uncertain outcomes as the existing typed result with a `worker_input_outcome_unknown` diagnostic, and supplies the Idle-only guidance for user input.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
There is no universal Worker command token or universal success result.
|
||||
|
||||
- `RuntimeWorkerRef` identifies the registry target; a Workspace resource reference is an ingress lookup form. Neither identifies a particular run or proves authority.
|
||||
- `WorkerCommandEnvelope.command_id` orders controller lifecycle commands. `WorkerCommandAcknowledgement` contains command kind, disposition (`Accepted`, `StaleCommandId`, `Conflict`, `InvalidState`) and authoritative state. These IDs belong to controller command admission, not durable Submit identity or Workspace removal planning.
|
||||
- `submission_request_id` is the caller's Submit retry key in an authenticated-source namespace. A durable `SubmissionAccepted` receipt also contains the Worker-allocated `submission_id` and `Started`/`Queued` disposition. `SubmissionRejected` is correlated by request ID. Acceptance is not a successful completed run; `last_finished_submission_request_id` is a teardown fence, not a result-success claim.
|
||||
- `notification_request_id` correlates `NotificationAccepted` / `NotificationRejected`. Notify has its own durable receipt and exact-payload retry behavior; it does not allocate a Submit FIFO entry. Browser Submit/Notify are rewritten to `SubmitTracked`/`NotifyTracked` with the server's Account source. Browser-supplied tracked forms, `SubmitIfIdle`, and `WorkerEvent` are rejected.
|
||||
- Runtime `WorkerLifecycleOperationId` is a durable lifecycle/reconciliation identity, separate from a controller command ID. Ticket `assignment_operation_id`, cleanup plan digest, and retention plan/operation IDs and fingerprints likewise belong to their own domain checks. A multiplexed `subscription_id` only routes frames to an already-bound sender.
|
||||
|
||||
The common sender returns `Result<()>` after authorization and enqueueing on its transport channel. This is **not** a controller acknowledgement, durable receipt, or proof of upstream delivery. Actual outcomes arrive on the event stream. The dedicated WS reports dispatch errors as protocol error events; the multiplexed adapter currently terminates the connection when sending a method fails. It also requires an initial session Snapshot before accepting the WorkerProtocol subscription.
|
||||
|
||||
Unary input/lifecycle responses preserve their existing API projections: `WorkerOperationState` is `Accepted`, `Unsupported`, or `Rejected`, with Runtime/Worker IDs and diagnostics. Restore instead has `Accepted`, `Rejected`, `RolledBack`, and `ReconciliationRequired`. Pin returns retention state; cleanup returns per-target actions/statuses; WorkerRemove has its own response/error body. These are not interchangeable acknowledgements.
|
||||
|
||||
A transport or store failure can leave an **unknown outcome** after effects have started. Lower-layer input results have `WorkerInputDisposition::{Accepted,Rejected,Unknown}` and an optional `runtime_run_id`, but `worker_input_result_to_api` projects only state, target IDs and diagnostics: the public unary input result does not expose those two fields. Unknown delivery is explicitly preserved as a `worker_input_outcome_unknown` diagnostic; neither HTTP adapter changes it into definite rejection or retries it. Runtime execution results also distinguish Busy/Rejected/Errored/Unsupported; error handling and durable restore/removal recovery remain lower-layer responsibilities. Do not infer that a rejected-looking projected response or a closed WS proves that nothing was accepted. The new reception layer does not add automatic replay or an exactly-once guarantee across every ingress. In particular, `worker_input_request_from_api` sets `submission_request_id` to `None`; the unary Workspace input contract does not provide the browser WS Submit retry-key contract.
|
||||
|
||||
## Connected execution lifetime
|
||||
|
||||
`WorkspaceWorkerMethodSender` stores both the resolved Worker handle and the sending channel privately. A WS caller cannot pair a different Worker handle with a subscription's raw sending capability. Registry identity can outlive an execution, but an operational connection does not follow it to a replacement execution.
|
||||
|
||||
`connect_workspace_worker_protocol` obtains an observation source from the Workspace proxy (falling back to the Runtime only for WorkerNotFound). It then connects to one remote execution WS or attaches one embedded `WorkerProtocolTransport`. The embedded path captures the real endpoint and its initial snapshot, not the WorkerRef-wide catalog observation bus.
|
||||
|
||||
Embedded dispatch uses `send_connected_protocol_method_scoped`. Runtime verifies that the transport target equals the authorized WorkerRef, checks scope and pending-operation settlement, and validates the captured endpoint before any durable mutation. Even a delayed terminal-close observation cannot make the old endpoint Stop or send to a new execution under the same identity.
|
||||
|
||||
Shutdown and a closed execution relay terminate the operational stream. Embedded lifecycle/store/transport uncertainty is terminal; a settled Busy/Rejected/Unsupported rejection may leave it usable only if the endpoint still validates. Remote bridge failure closes its channels. `send_checked` reports `worker_protocol_closed` when its channel is closed and explicitly states that no implicit Restore or retry occurred. No bridge in this path retargets, restores, or reconnects to the next execution. The caller must explicitly Restore when authorized and establish a new connection; this is different from catalog observation continuing to describe the same registry identity.
|
||||
|
||||
## Internal Workers are parent-owned
|
||||
|
||||
Internal SubWorkers and service Internal Workers are not Workspace registry targets or independently addressable Runtime protocol subjects. They remain separate, parent-owned sessions. Their visible identity is `InternalWorkerRef.session_id` (`name` is display-only), and snapshots/events are presented through the parent's protocol stream; service-private Internal Workers are excluded from the public child inventory.
|
||||
|
||||
Direct-child control/observation stays in the parent-owned Worker machinery. A Workspace Runtime Worker grant is not a grant to an arbitrary Internal Worker, and this registry handle does not create an Internal Worker Runtime record, independent transport, or implicit Restore path. The Workspace observation capture adapter explicitly rejects a non-RuntimeWorker subject; it does not resolve a SubWorker as a registry Worker.
|
||||
|
||||
## Regression helpers are not production routes
|
||||
|
||||
Lower-level test functions in `server.rs` must not be mistaken for installed unauthenticated endpoints:
|
||||
|
||||
- `set_worker_retention`, `send_runtime_worker_input` / `dispatch_runtime_worker_input`, and `execute_runtime_cleanup` / `scoped_execute_runtime_cleanup` are `#[cfg(test)]` wrappers using the test-only Backend context.
|
||||
- `worker_protocol_ws_session`, `remote_worker_protocol_ws_session`, and `embedded_worker_protocol_ws_session` are `#[cfg(test)]` transport regression helpers. Production dedicated WS uses `workspace_worker_protocol_session` with the common bound sender; production multiplexed WS uses `serve_workspace_subscription` with that same sender type.
|
||||
- Production `connect_workspace_worker_protocol`, `connect_remote_worker_protocol`, and `connect_embedded_worker_protocol` are real shared connection helpers, not those test-only sessions. The grant-checked observation capture service also calls the connection helper to read a committed Snapshot; that read-only capture is outside the six operational WS methods.
|
||||
|
||||
Source presence alone is not route wiring. Follow `build_inner_router`, generated contract implementations, and the typed route declarations when auditing production ingress.
|
||||
|
||||
## Minimum scope, limitations, and future guard
|
||||
|
||||
The boundary above covers the T-702 minimum operation set. It is not a claim that every session/file/completion API has been migrated:
|
||||
|
||||
- Session snapshot/history and retained session attachment reads remain their existing observation/retention paths. They are not controller command admission.
|
||||
- Attachment upload grants and upload cancellation keep their Account/target-bound grant checks; file upload and uploaded-file deletion still resolve the target and delegate through `api.runtime`, rather than calling a new `WorkspaceWorker` file method.
|
||||
- Unary completions still resolve the registry reference and call `api.runtime.worker_completions`. Protocol `ListCompletions` is also browser-allowed through checked WS dispatch. Those are different ingress paths.
|
||||
- Worker creation, Backend Job execution, rollback compensation, Workspace deletion, and other domain-owned orchestration are not universally rewritten to this handle. Legitimate lower-level Runtime calls still exist.
|
||||
|
||||
Known limitations worth follow-up, rather than undocumented guarantees:
|
||||
|
||||
1. Browser credentials/Account are rechecked for each common operation or WS method send, but not continuously for every outgoing observation event. This is not a promise of immediate revocation-driven stream teardown.
|
||||
2. Cleanup is sequential rather than an all-target atomic transaction. The existing cleanup plan digest and each removal service's live revalidation remain distinct protections.
|
||||
3. Unary input has its existing narrower wire contract, rather than the WS authenticated receipt envelope. Identity unification does not create a new retry contract or manufacture caller request IDs across transports.
|
||||
|
||||
T-701's [stale-observation Restore guard](worker-restore-observations.md) connects the caller's `expected_observation_token` and `request_id` to Runtime's existing operation lock, distinguishes a new Restore intent from pending reconciliation, and handles UI conflicts. This boundary leaves those Restore inputs/results and the pending-operation state machine intact rather than creating a duplicate guard.
|
||||
|
||||
### Implementation references
|
||||
|
||||
- [`server/worker_operations.rs`](../../crates/workspace-server/src/server/worker_operations.rs): identity handle, contexts, authorization, named methods and private bound sender.
|
||||
- [`server.rs`](../../crates/workspace-server/src/server.rs): contract adapters, `build_inner_router`, scoped Restore/cleanup, WorkerRemovalService, dedicated WS and shared transport bridges.
|
||||
- [`workspace_subscription.rs`](../../crates/workspace-server/src/workspace_subscription.rs): multiplexed WorkerProtocol binding and catalog subscriptions.
|
||||
- [`hosts.rs`](../../crates/workspace-server/src/hosts.rs): Runtime registry/adapters and distinct input/lifecycle result projections.
|
||||
- [`protocol`](../../crates/protocol/src/lib.rs): command envelopes/acknowledgements, Submit/Notify receipts and Internal Worker presentation.
|
||||
- [`worker-runtime/runtime.rs`](../../crates/worker-runtime/src/runtime.rs) and [`execution.rs`](../../crates/worker-runtime/src/execution.rs): scoped connected endpoint validation, lifecycle identities and execution results.
|
||||
@@ -1,54 +0,0 @@
|
||||
# Backend account model and login flows
|
||||
|
||||
This document records the first Backend identity/auth layer introduced for the multi-workspace Backend direction.
|
||||
|
||||
## Account and owner model
|
||||
|
||||
The Backend store now separates a human user from the owner namespace used by workspace ownership:
|
||||
|
||||
- `accounts`: canonical namespace records. `kind = "user"` is implemented now; `kind = "organization"` is reserved in the schema for a later organization model.
|
||||
- `users`: human user records. Each user owns exactly one `accounts` row.
|
||||
- `workspaces.owner_account_id`: optional account owner reference. Existing/local workspaces may keep it unset until account bootstrap/ownership migration code assigns it.
|
||||
|
||||
This avoids baking `owner_user_id` into workspace authority while keeping the current implementation small.
|
||||
|
||||
## Browser login
|
||||
|
||||
Human browser auth is modeled as Passkey/WebAuthn ceremony endpoints:
|
||||
|
||||
- `POST /api/auth/bootstrap-user`
|
||||
- `POST /api/auth/passkeys/registration/options`
|
||||
- `POST /api/auth/passkeys/registration/complete`
|
||||
- `POST /api/auth/passkeys/login/options`
|
||||
- `POST /api/auth/passkeys/login/complete`
|
||||
- `GET /api/auth/whoami`
|
||||
|
||||
The store persists registration/login challenges, serialized WebAuthn ceremony state, verified passkey credentials, browser sessions, and challenge consumption. Successful login mints an HttpOnly `SameSite=Lax` cookie session.
|
||||
|
||||
Completion endpoints require browser `PublicKeyCredential` responses and verify them with `webauthn-rs` before a passkey is stored or a browser session is issued. Fake or stale credential responses are rejected by the verifier, and consumed challenges cannot be reused.
|
||||
|
||||
## CLI/TUI device login
|
||||
|
||||
CLI/TUI clients use a device-login flow instead of browser cookies:
|
||||
|
||||
- `POST /api/auth/device-login/start` creates a `device_code` and human `user_code`.
|
||||
- The CLI prints `verification_uri_complete` and polls `POST /api/auth/device-login/poll`.
|
||||
- A browser-authenticated user approves with `POST /api/auth/device-login/approve`.
|
||||
- Approval mints a Backend API token and returns it exactly once to the polling client.
|
||||
|
||||
`yoi login [--backend <URL>] [--no-wait]` starts this flow and stores the received token in `$XDG_CONFIG_HOME/yoi/backend-tokens.json` or `$HOME/.config/yoi/backend-tokens.json`.
|
||||
|
||||
## Request actor extraction
|
||||
|
||||
`workspace-server::auth::resolve_request_actor` resolves a typed request actor from either:
|
||||
|
||||
- `Authorization: Bearer <token>` for CLI/TUI API tokens, or
|
||||
- the configured browser session cookie.
|
||||
|
||||
The resolved actor includes `user_id`, `account_id`, `handle`, `display_name`, and `auth_method`. This helper is intended to be used by mutating Backend APIs as authorization gets added route-by-route.
|
||||
|
||||
## Non-goals for this step
|
||||
|
||||
- Organization membership and RBAC.
|
||||
- Password login, OAuth login, or other human auth methods.
|
||||
- Token scoping beyond Backend API bearer authentication.
|
||||
@@ -1,304 +0,0 @@
|
||||
# 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; list, glob, grep, create_file
|
||||
|
||||
# Not indexable; resolve known paths directly or discover via Operations:
|
||||
/checkouts/<encoded alias>/README.md file; read, edit, write
|
||||
/checkouts/<encoded alias>/src directory; list, 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 Operations 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.
|
||||
|
||||
## Indexable boundary (T-714)
|
||||
|
||||
Only `/checkouts` and accessible immediate attachment entrances are indexable.
|
||||
No directory content List runs during tree/observe, even at deep depth or when a
|
||||
known deep directory is the observation root. Empty, small and large directories
|
||||
all have the same boundary. Positive-depth content observations return
|
||||
`children: []` (no **indexable** children), not an omitted field and not
|
||||
an assertion that the directory is empty. At depth zero children are omitted;
|
||||
`observe("/checkouts", 1)` omits entrance children, depth 2 returns [] there.
|
||||
The Client retains discovered/inspected paths without adding indexable edges.
|
||||
|
||||
Known authorized paths remain directly resolvable and operable. Use List/Glob/Grep
|
||||
for discovery, then Inspect only selected entry coordinates; no successful search
|
||||
is undone when an entry disappears before Inspect. Bash/COMMAND is unnecessary.
|
||||
|
||||
## 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 structured `(scope, name)`. Each entrance is its own
|
||||
scope; content Interfaces use that entrance as ancestor scope, irrespective of
|
||||
indexability. Local names bind target coordinates, projection incarnation and
|
||||
live attachment generation. The provider's target-qualified-name routing hint
|
||||
only selects a target; fetch re-resolves one coherent current publication and
|
||||
checks the exact Interface, Descriptor, validator and scope under the pinned
|
||||
router connection. It never turns a cached registration or name into authority.
|
||||
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. Result publication
|
||||
checks the captured attachment generation, not merely a
|
||||
reused alias. It maps typed provider paths without observing/statting every
|
||||
result. Provider awaits are covered by the operation deadline. Objects publish
|
||||
no scope_ref here; legitimate ref-less binding still checks membership,
|
||||
validators, current provider authority and attachment generation.
|
||||
|
||||
## 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 | `list` | optional `limit` (default 100, 1..1000), `after` (returned `{kind, entry}`) | direct-content records `{kind, size, entry}`, `truncated`, optional `after` |
|
||||
| directory | `glob` | `pattern`, optional `path` | normal bounded Glob text/summary plus Descriptor-typed same-checkout `entry` coordinates |
|
||||
| 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 Descriptor-typed `entry` coordinates |
|
||||
| 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 declare a typed record with `summary`, optional `content`
|
||||
and `items`. Each item contains `entry: entry` in its Descriptor (an absolute
|
||||
Worldspace coordinate, encoded as a string in WIP Value), never a guessed ordinary
|
||||
string or an OS path. List additionally declares `kind`, byte `size`, `truncated`
|
||||
and optional `after: {kind: string, entry: entry}`. File operations have empty
|
||||
items except Create, which returns the new coordinate.
|
||||
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. Descriptor-safe ignore
|
||||
loading preserves root/nested rules and prunes ignored directories, checking
|
||||
scope before opening each rule source. It cannot read global/ambient ignore
|
||||
configuration outside provider authority or chase linked-worktree administration
|
||||
pointers; source-class precedence, per-provider Git-marker/error behavior and
|
||||
text/aggregate limits are detailed in `crates/workdir/CHECKOUT_PROVIDER.md`.
|
||||
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 are Read/Edit/Write/Glob/Grep plus **List** for
|
||||
native directory listing; create_file uses Write. List publication uses provider
|
||||
Read capability and potential List allow policy; execution reconstructs the bound
|
||||
directory's logical path for the exact List policy. Allowing Glob or COMMAND does
|
||||
not override a List deny/ask rule, and Interface scope validity grants no file
|
||||
access. Normal Tools mode is unchanged (no new top-level List tool). 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.
|
||||
|
||||
## Directory List pagination
|
||||
|
||||
List uses the existing provider List engine, **not** glob("*") or shell ls. It
|
||||
includes direct visible empty directories, hidden and ignored names: unlike
|
||||
Glob/Grep, provider List does not apply search ignore files. Existing Read scope,
|
||||
readability and can_enumerate_directory checks still gate names; no ancestor or
|
||||
nonrecursive enumeration permission is inferred from a known descendant.
|
||||
|
||||
Order is directories first, then lexical canonical provider path, with every
|
||||
other kind in the second group. after is the last returned exclusive ordering
|
||||
key, rebased to a canonical Worldspace entry by the Host. Pass the returned record
|
||||
unchanged to continue on the same directory; a foreign checkout, non-direct child,
|
||||
absolute provider path or unknown field is rejected. No cursor stat is required:
|
||||
the key may have disappeared. This is **live pagination, not a snapshot**. New
|
||||
keys before the cursor are not revisited; later keys may appear, removals vanish,
|
||||
and renames/kind changes may omit or repeat an entry. Directory validators fence
|
||||
each call, not all pages; refresh Inspect after a stale rejection before issuing
|
||||
a new explicit call. Detach/reuse expires the old Interface and validator.
|
||||
|
||||
Each page scans the live directory under provider cancellation/deadline and the
|
||||
existing traversal-entry ceiling, retaining at most limit+1 ordering candidates
|
||||
and the aggregate path-byte budget. It never retains the whole unbounded listing.
|
||||
The next ordering key is explicit whenever truncated; no next key means complete.
|
||||
Provider resource failures stay explicit failures, not silent truncation or retries.
|
||||
|
||||
UTF-8 canonical names are retained verbatim; unrepresentable names are skipped by
|
||||
the existing provider. Native List may reject visible symlinks under the checked
|
||||
provider's no-follow policy. If a provider returns symlink/other kinds they remain
|
||||
classified path coordinates, not a promise of inspectable/operable Objects:
|
||||
direct checkout resolution only publishes authorized regular files/directories.
|
||||
|
||||
## 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 bounded result
|
||||
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. Use Tree on `/checkouts` with depth 1; Inspect `/checkouts` directly and Invoke `list`.
|
||||
2. Inspect an entrance or known deep directory directly; Invoke `list`/`glob`/`grep`.
|
||||
For List, pass returned `after` with the next limit to obtain another live page.
|
||||
3. Follow a returned file `entry`, Inspect that path and Invoke 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 test -p worker --lib checkout_wip
|
||||
cargo test -p workdir --test list_pagination
|
||||
cargo check
|
||||
cargo test -p fs-operation -p workdir -p tools -p worker
|
||||
# Workdir External/HTTP features and dependent server fixture:
|
||||
cargo test -p workdir --features http-client
|
||||
cargo test -p yoi-workspace-server --lib backend_workdir_session_proxy_executes_typed_operations
|
||||
node scripts/verify-wip-dependencies.mjs
|
||||
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 |
|
||||
| Root/nested ignore parity, source precedence/negation/anchoring, directory pruning, denied/symlink rule confinement and bounded loading | `fs-operation/src/ignore_tests.rs`, `workdir/src/checkout_search_tests.rs`, native WIP ignore/link regression |
|
||||
| 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.
|
||||
@@ -1,124 +0,0 @@
|
||||
# Compaction
|
||||
|
||||
Compaction exists because long-running Workers need durable continuity without sending the entire transcript forever.
|
||||
|
||||
## Pruning vs compaction
|
||||
|
||||
Pruning is request-local packing of existing history. It chooses what to include for one model request while preserving the underlying committed log.
|
||||
|
||||
Compaction is a durable transition. It creates a new summarized state and should be recorded so later turns can explain why older details are no longer directly present.
|
||||
|
||||
## Token-budget protection
|
||||
|
||||
Safety checks should use effective backend/context limits, including in-flight usage. Cached input tokens still occupy context even if upload displays subtract them.
|
||||
|
||||
When a provider returns an exact `input_total_tokens` measurement for the whole prompt shape, Yoi can treat it as authoritative and estimate only incremental growth after that measurement. It should not fake a system/history split or tune thresholds just to mask estimator bugs.
|
||||
|
||||
## After compaction
|
||||
|
||||
Compaction output is not automatically safe. The post-compact context must be revalidated before the next request.
|
||||
|
||||
A `just_compacted` flag must not bypass safety checks. It is easy for a compact summary, retained tail, or prompt resource change to still exceed a context limit.
|
||||
|
||||
`result_context_max_tokens` is an advisory warning threshold for the assembled replacement
|
||||
history, not a hard activation cap or a provider context limit. Its estimate includes the summary,
|
||||
auto-read files, references, retained tail, and TaskStore context. Exceeding it emits a Compactor
|
||||
warning and continues without discarding the generated summary or stripping auto-read/retained
|
||||
content. `0` disables this warning. Normal request-time safety checks still apply after activation.
|
||||
|
||||
## Large sessions
|
||||
|
||||
Large-session compaction should not send an entire prefix transcript as the summary input. Prefer bounded overview/index inputs plus exploration, then keep the retained tail small and explicit.
|
||||
|
||||
This keeps compaction cost predictable and avoids turning a context-recovery mechanism into the largest prompt in the session.
|
||||
|
||||
## History integrity
|
||||
|
||||
Compaction should preserve persisted reasoning history and avoid serializing unverified hidden reasoning context. Trace and metrics can count request shape and reasoning items without smuggling hidden provider state into model input.
|
||||
|
||||
The important property is explainability: after compaction, records should still show what summary replaced which older context and why future turns can rely on it.
|
||||
|
||||
## Binary tool details
|
||||
|
||||
Durable binary attachments are not text and must not be charged by applying the text
|
||||
`bytes / 4` fallback to their base64 persistence encoding. Provider usage measurements remain
|
||||
authoritative for requests that actually include an attachment.
|
||||
|
||||
An image returned by `ViewImage` is sent for the immediate follow-up model request. Later requests
|
||||
project the consumed image body out while retaining the tool summary, so the Worker can call
|
||||
`ViewImage` again when it needs the current file. Compaction likewise never carries image bodies
|
||||
into the replacement Segment: retained images become related-file references with their Workdir
|
||||
alias and path where available.
|
||||
|
||||
The compactor may auto-read only UTF-8 text with `mark_read_required`. Images and other binary
|
||||
files can be nominated only with `add_reference`; the next Worker fetches them on demand with
|
||||
`ViewImage` or the appropriate read tool.
|
||||
|
||||
## Activation boundary
|
||||
|
||||
A compacted Segment is staged completely before it can become authoritative. The
|
||||
activation boundary uses this lock order everywhere a live Worker changes Segment:
|
||||
|
||||
1. stop the compaction service and remove its registry record;
|
||||
2. acquire the Worker's append barrier;
|
||||
3. write the final pending-submission checkpoint and stage the replacement Segment;
|
||||
4. acquire and preflight the machine-wide Worker allocation lock;
|
||||
5. compare-and-swap `metadata.json` from the source Segment to the replacement;
|
||||
6. atomically replace the allocation table while still holding its separate lock;
|
||||
7. publish the append destination, session projection, sink, and in-memory history;
|
||||
8. release the append barrier.
|
||||
|
||||
Submit and Notify acceptance take the same append barrier. The controller persists
|
||||
requests received while manual compaction is running immediately rather than holding
|
||||
volatile deferred methods, so an overlapping Shutdown cannot discard a SubWorker
|
||||
completion. Consequently an accepted item is either durably included in the
|
||||
replacement checkpoint, or waits until every live authority points at the replacement
|
||||
and is then appended there. Restore admission takes the allocation lock, so it cannot
|
||||
observe the interval between the metadata CAS and allocation hand-off.
|
||||
|
||||
The metadata CAS is the commit point. Failures before it leave the source Segment
|
||||
active. A failure updating allocation after it marks the old in-memory writer
|
||||
unusable and retains the machine-wide allocation lock: further acceptance fails
|
||||
closed and restore admission cannot register a competing writer until process
|
||||
teardown, after which restore converges from the replacement named by metadata.
|
||||
The Worker must likewise remain non-idle while terminal compaction-service cleanup
|
||||
is pending. Cleanup authority is stored on the Worker (not only on one compaction
|
||||
future). A failed stop returns a typed cleanup-pending outcome without clearing that
|
||||
authority; the next compaction boundary retries it. The authority and lifecycle
|
||||
reference are cleared exactly once after the registry record is gone, before
|
||||
activation or a terminal idle-capable result. While either activation repair or
|
||||
cleanup attention is outstanding, the controller fences Submit/Notify dispatch and
|
||||
does not start queued work. Shutdown is itself a cleanup barrier: it retries the
|
||||
Worker-owned stop authority with backoff and does not emit its terminal event until
|
||||
the registry record is gone.
|
||||
|
||||
## Metrics and comparison procedure
|
||||
|
||||
Compaction measurements stay out of the ordinary transcript. They are appended as
|
||||
`metrics` extensions and are read only through the explicit
|
||||
`session-metrics` reader/export path. `read_session_metrics` attaches the durable
|
||||
`segment_id` and `SegmentStart.compacted_from` lineage to each record.
|
||||
|
||||
1. Compare runs with the same workload and model settings. Record manual versus
|
||||
automatic mode, `threshold_policy`, and `retained_token_budget`.
|
||||
2. Start with the `compact.start` correlation ID. Join it to `compact.finish` and
|
||||
the metric breakdown on the result Segment, then to the next normal request's
|
||||
`compact.post_request`. Use `compacted_from`, rather than timestamps, to prove
|
||||
the source-to-result Segment relationship.
|
||||
3. Compare `compact.retained_tokens`, `compact.overview_tokens`,
|
||||
`compact.summary_tokens`, `compact.auto_read_tokens`, and
|
||||
`compact.result_context_tokens` to explain the context-size change.
|
||||
4. Compare the Compactor's input/output/cache-read/cache-creation tokens, request,
|
||||
turn, tool-call, and duration metrics. The current provider `UsageEvent` has no
|
||||
pricing authority, so `compact.cost_usd` is valueless with
|
||||
`status=unavailable` and `reason=provider_usage_unpriced`; do not fabricate a
|
||||
zero cost. Record a numeric value only after a price authority exists.
|
||||
5. Aggregate failure and cancellation using the fixed `failure_category` values.
|
||||
Metrics must never contain provider error text, prompt/session content, host
|
||||
paths, or secrets.
|
||||
6. Report at least success rate, next-request token/cache changes, Compactor token
|
||||
use, and duration per workload before changing retention or thresholds.
|
||||
|
||||
Export ordering is deterministic through timestamp, phase, Segment, and
|
||||
`log_index`. Causality still comes from `correlation_id` and `compacted_from`, not
|
||||
from timestamp order alone.
|
||||
@@ -1,49 +0,0 @@
|
||||
# Context and history
|
||||
|
||||
Any input that can influence the model across a turn must be committed to history before it is placed in model context.
|
||||
|
||||
This rule protects both explainability and prompt-cache behavior. If the model reacts to a context-only insertion that is not in history, later turns cannot explain why the assistant made that decision, and cache anchors become harder to reason about.
|
||||
|
||||
## Allowed context transformations
|
||||
|
||||
A context transformation is acceptable when it is reproducible from durable Worker state and does not introduce new volatile facts.
|
||||
|
||||
Examples:
|
||||
|
||||
- Pruning old history according to persisted/session-derived state.
|
||||
- Truncating oversized tool result content while preserving the committed result boundary.
|
||||
- Adding prompt-cache anchors derived from stable context structure.
|
||||
- Rendering the same materialized system prompt string after compaction.
|
||||
|
||||
These transformations change how context is packed, not what happened.
|
||||
|
||||
## Forbidden context injection
|
||||
|
||||
Do not insert turn-crossing information directly into context without first appending it to `worker.history`.
|
||||
|
||||
Forbidden examples:
|
||||
|
||||
- Delivering a `Notify` or `WorkerEvent` only as a temporary context note.
|
||||
- Adding a system reminder that explains behavior but is not persisted.
|
||||
- Rewriting old messages to include new facts.
|
||||
- Letting UI/controller-only state become model-visible without a committed record.
|
||||
|
||||
If new information should affect the model, append it to history and commit it. `history.json` / session persistence follows from the Engine history path.
|
||||
|
||||
## Prompt cache implications
|
||||
|
||||
Yoi optimizes for predictable prompt shape, not only small requests. Hidden context insertions make cache behavior hard to reproduce because the model-visible input can change without a corresponding history record.
|
||||
|
||||
Ordinary new inputs are not the problem: user messages, committed events, compacted summaries, and tool results are expected additions to history. The avoidable problem is changing prior or side-channel context in a way that is neither durable nor explainable from records.
|
||||
|
||||
## Pruning and compaction
|
||||
|
||||
Pruning is a request-time packing operation that chooses which existing history to include under a token budget. Compaction is a durable history operation that creates a new summarized state and must be committed.
|
||||
|
||||
After compaction, context safety must be revalidated. A flag such as `just_compacted` must not suppress safety checks, because compact output can itself be too large or malformed for the next request.
|
||||
|
||||
## UI and orchestration consequences
|
||||
|
||||
The TUI should display committed events rather than inventing transcript blocks. Orchestration should treat child notifications as prompts to inspect state, not as state themselves.
|
||||
|
||||
This keeps future turns able to answer: "what did the agent know, when did it know it, and where is that recorded?"
|
||||
@@ -1,418 +0,0 @@
|
||||
# Durable operation design
|
||||
|
||||
Durable operation records make retries converge on the same authorized intent
|
||||
and preserve the facts needed to explain an externally visible result. They are
|
||||
not execution traces and must not mirror every Rust function or implementation
|
||||
step as persisted state.
|
||||
|
||||
This document defines the cross-domain rules for operation identity, state,
|
||||
checkpoints, child operations, failure evidence, and terminal disposition.
|
||||
Domain code may use different records where its atomicity boundary differs, but
|
||||
it should classify the operation before choosing a schema.
|
||||
|
||||
## Core rule
|
||||
|
||||
Persist authority and non-reconstructable facts, not control flow.
|
||||
|
||||
A value belongs in durable operation state only when at least one of the
|
||||
following is true:
|
||||
|
||||
- it identifies the caller's stable intent and detects conflicting reuse;
|
||||
- it freezes authority or configuration that an exact retry must continue to
|
||||
use;
|
||||
- it binds a preallocated or created resource to the operation;
|
||||
- it records an externally visible side effect that cannot be safely derived or
|
||||
repeated;
|
||||
- it records the final domain result or disposition;
|
||||
- it provides bounded evidence needed for retry, reconciliation, or audit.
|
||||
|
||||
A local step does not become durable merely because it occurs before or after
|
||||
another function call. If current authority can be reread or the step can be
|
||||
repeated safely, derive or repeat it instead of adding a stage.
|
||||
|
||||
## Resilience boundary
|
||||
|
||||
These rules cover normal product retry and recovery boundaries: duplicate
|
||||
requests, returned errors, timeouts, known partial-completion outcomes, process
|
||||
restart from the last committed facts, and retries of provider operations with
|
||||
an explicit idempotency or observation contract.
|
||||
|
||||
They do not require the system to survive an unexpected stop at every point
|
||||
while Rust code is executing. A panic, abort, process kill, machine loss, or
|
||||
power failure may occur between an external side effect and its next durable
|
||||
checkpoint. Yoi does not attempt to close every such instruction-level crash
|
||||
window by writing a stage before and after every `await`, function, or provider
|
||||
call.
|
||||
|
||||
Consequently:
|
||||
|
||||
- do not claim general exactly-once execution;
|
||||
- do not introduce write-ahead stages solely to model arbitrary Rust
|
||||
control-flow interruption;
|
||||
- rely on SQLite transaction atomicity for work inside one database transaction;
|
||||
- prefer provider idempotency, compare-and-swap, stable resource identity, and
|
||||
authoritative observation for external effects;
|
||||
- when an uncovered crash window cannot be reconciled automatically, retain the
|
||||
last committed facts and surface an `unknown` or attention-required
|
||||
disposition rather than guessing that the side effect did or did not happen.
|
||||
|
||||
A domain may require a stronger crash-consistency contract for a specific
|
||||
destructive or security-sensitive effect. That requirement must be explicit and
|
||||
must define the provider protocol, checkpoint ordering, replay behavior, and
|
||||
reconciliation evidence. It is not implied by calling a record a durable
|
||||
operation.
|
||||
|
||||
## Classify the record before adding state
|
||||
|
||||
Not every record containing an `operation_id` is a state machine. Use one of the
|
||||
following shapes.
|
||||
|
||||
### Atomic idempotency ledger
|
||||
|
||||
Use an idempotency ledger when all authoritative mutations and result recording
|
||||
commit in one database transaction.
|
||||
|
||||
The record normally contains:
|
||||
|
||||
- Workspace and operation identity;
|
||||
- a fingerprint of stable caller intent;
|
||||
- the created resource or result identity;
|
||||
- the committed content digest or immutable change ID and timestamp where relevant.
|
||||
|
||||
It does not need `pending`, `executing`, or intermediate stages. An exact retry
|
||||
returns the recorded result. Reusing the same operation identity with a
|
||||
different fingerprint fails.
|
||||
|
||||
Repository secret mutation results, Workspace resource creation results, and
|
||||
transactionally appended domain events are examples of this shape.
|
||||
|
||||
### Reservation
|
||||
|
||||
Use a reservation when an identity or exclusive right must exist before a later
|
||||
binding can complete.
|
||||
|
||||
Persist factual transitions such as:
|
||||
|
||||
- the reserved resource identity;
|
||||
- the immutable request fingerprint and authority snapshot;
|
||||
- the concrete resource or assignment bound to the reservation;
|
||||
- reservation expiry or release evidence when the contract requires it.
|
||||
|
||||
Do not model internal dispatch, validation, construction, or callback steps as
|
||||
reservation states. A nullable result binding or a small `reserved | created`
|
||||
state can be sufficient when those values correspond to real authority facts.
|
||||
|
||||
### Durable side-effect operation
|
||||
|
||||
Use a durable side-effect operation when work crosses a database/provider
|
||||
boundary and a retry needs durable intent or result evidence.
|
||||
|
||||
The default lifecycle is deliberately small:
|
||||
|
||||
```text
|
||||
pending -> completed
|
||||
pending -> failed
|
||||
failed -> pending # only when the domain explicitly permits retry
|
||||
```
|
||||
|
||||
Existing code may use `succeeded` for the successful terminal value; new naming
|
||||
should prefer `completed`. Do not rewrite applied migrations or historical audit
|
||||
text only to normalize that word.
|
||||
|
||||
The operation should contain:
|
||||
|
||||
- stable operation identity and request fingerprint;
|
||||
- immutable resolved authority needed by an exact retry;
|
||||
- preallocated resource identity where it prevents duplicate creation;
|
||||
- only the necessary irreversible checkpoints;
|
||||
- bounded failure evidence;
|
||||
- the final result and domain disposition.
|
||||
|
||||
`pending` means that the intent remains open and current authority must be
|
||||
reread before progress. It does not identify which Rust function should execute
|
||||
next. `failed` records the latest terminal attempt outcome; retryability is an
|
||||
explicit domain rule, not something inferred from the word. `completed` means
|
||||
the operation's required result and evidence are durably committed.
|
||||
|
||||
### Parent workflow
|
||||
|
||||
A parent workflow coordinates domain operations but does not duplicate their
|
||||
lifecycle.
|
||||
|
||||
Persist:
|
||||
|
||||
- the parent intent and fencing authority;
|
||||
- stable child operation identities;
|
||||
- the final workflow result or disposition;
|
||||
- bounded attention or decision evidence.
|
||||
|
||||
Read child state from the child authority. Do not copy child states, provider
|
||||
stages, Worker status, attachment status, or Workdir status into a second parent
|
||||
state machine. A parent cleanup workflow will often need only
|
||||
`pending | completed`; child failure remains on the child operation and appears
|
||||
in the parent as current attention metadata.
|
||||
|
||||
Creating or binding a child must itself be idempotent. Prefer a deterministic
|
||||
child operation identity or persist the child reference atomically with the
|
||||
parent decision so a retry cannot create siblings for one intent.
|
||||
|
||||
## Checkpoint rules
|
||||
|
||||
A checkpoint records a fact that changes retry semantics. It is not a progress
|
||||
notification.
|
||||
|
||||
Add a checkpoint only when all of the following hold:
|
||||
|
||||
1. A side effect may already have occurred outside the current transaction.
|
||||
2. Current authority cannot derive the fact reliably enough for safe retry, or
|
||||
repeating the effect is not safe under the provider contract.
|
||||
3. The retry algorithm changes after the fact is committed.
|
||||
4. Tests can exercise behavior before and after the checkpoint.
|
||||
|
||||
Prefer factual fields over stage names:
|
||||
|
||||
- `provider_deleted_at` is evidence that provider deletion succeeded;
|
||||
- `child_operation_id` binds delegated work;
|
||||
- `result_digest` identifies the committed result content;
|
||||
- `target_ref_after` records verified merge evidence.
|
||||
|
||||
Avoid fields such as `validating`, `closing_session`, `detaching`,
|
||||
`deleting_registry`, or `finalizing`. Those names describe code location, not
|
||||
durable authority. If those steps are safe to rerun or their result can be read
|
||||
from Worker, attachment, Workdir, repository, or provider authority, they are
|
||||
not checkpoints.
|
||||
|
||||
A checkpoint must never claim more than the authority that produced it. For
|
||||
example, sending a provider request is not proof that provider deletion
|
||||
completed, and receiving a Worker notification is not proof that a Ticket or
|
||||
cleanup workflow completed.
|
||||
|
||||
## State, failure, blockers, and disposition are separate
|
||||
|
||||
Do not overload one enum with unrelated dimensions.
|
||||
|
||||
- **Operation state** says whether the intent is open, completed, or has a
|
||||
recorded failed attempt.
|
||||
- **Failure evidence** records a bounded category, timestamp, and safe
|
||||
diagnostic detail for the latest failure.
|
||||
- **Blockers and eligibility** are normally derived by rereading current
|
||||
authority. Persist them only as audit or attention evidence, not as a
|
||||
substitute for live validation.
|
||||
- **Disposition** records what the domain decided to retain, delete, release,
|
||||
tombstone, abandon, or leave unknown.
|
||||
- **Attention metadata** explains why automated progress currently cannot
|
||||
continue and what authority must change.
|
||||
|
||||
Values such as `blocked`, `executing`, `stale`, `dirty`, `retained`, and
|
||||
`deleted` therefore do not all belong in one operation-state enum. Some are
|
||||
derived conditions, some describe transient execution, and some are domain
|
||||
results.
|
||||
|
||||
Before every retry or side effect, reread live authority and revalidate its
|
||||
fence. A previously recorded blocker does not prove that the operation remains
|
||||
blocked, and a previously unblocked operation does not retain permission after
|
||||
assignment, ownership, content, or attachment authority changes.
|
||||
|
||||
## Identity and fingerprinting
|
||||
|
||||
Every externally retryable operation has a stable identity in its owning
|
||||
Workspace or authority scope. The operation fingerprint represents stable caller
|
||||
intent, not generated results or mutable observations.
|
||||
|
||||
Include inputs whose change would mean a different requested operation. Exclude:
|
||||
|
||||
- generated resource IDs when the Server allocates and persists them as the
|
||||
result;
|
||||
- timestamps assigned by the Server;
|
||||
- retry counters and diagnostics;
|
||||
- current provider observations that are expected to change;
|
||||
- secret bytes and credential material.
|
||||
|
||||
Resolved authority snapshots may be stored separately from the caller
|
||||
fingerprint. An exact retry uses the persisted snapshot where replay convergence
|
||||
requires it; a new operation resolves current authority. Unknown, foreign, or
|
||||
conflicting operation identity fails closed.
|
||||
|
||||
## Transactions and external providers
|
||||
|
||||
Keep database work in one transaction whenever the owning authority and result
|
||||
live in the same database. Do not create a durable operation merely to split a
|
||||
transaction that can remain atomic.
|
||||
|
||||
When an external provider is involved:
|
||||
|
||||
1. reserve stable intent and identity if retry needs them;
|
||||
2. invoke the provider with the strongest available idempotency, expected-old
|
||||
content digest, or stable resource key;
|
||||
3. verify the provider result through authoritative response or observation;
|
||||
4. commit only the checkpoint or result evidence that changes retry behavior;
|
||||
5. on retry, reread both the operation and current domain/provider authority
|
||||
before acting.
|
||||
|
||||
Compensation is a domain operation, not an invisible `finally` block. If
|
||||
compensation has its own external side effects or retry lifecycle, give it a
|
||||
stable child operation identity rather than expanding the parent into a list of
|
||||
cleanup stages.
|
||||
|
||||
## Workdir removal application
|
||||
|
||||
Workdir removal is one durable side-effect operation in the Workspace Server
|
||||
DB. It binds the Workspace, Workdir, owning Runtime,
|
||||
Repository/materialization identity, source actor, stable intent fingerprint,
|
||||
lifecycle, retry metadata, and bounded result. Runtime URL, provider handle,
|
||||
host path, credentials, and caller-selected Runtime are not operation inputs.
|
||||
|
||||
A durable one-pending-operation constraint plus an atomic attempt claim prevents
|
||||
concurrent callers from entering the provider side effect for the same Workdir;
|
||||
the in-process resource lock is an additional serialization layer, not the sole
|
||||
authority. Each active attempt persists the Server process ID and process-start
|
||||
marker. Recovery reclaims only an owner proven missing or replaced; a live or
|
||||
unobservable owner is never stolen. The reclaim transaction compare-and-set
|
||||
checks the exact proved owner snapshot and attempt count so stale orphan proof
|
||||
cannot overwrite a newer live claim.
|
||||
|
||||
Each attempt:
|
||||
|
||||
1. resolves or revalidates the persisted same-Workspace Workdir, Runtime,
|
||||
Repository, and materialization identity;
|
||||
2. checks current attachments, attachment reservations, current assignment
|
||||
occupancy, retention/cleanup holds, and pending materialization authority; a
|
||||
failed Workdir-create retry must atomically return to `pending` before
|
||||
provider work and is rejected while removal is pending;
|
||||
3. retains dirty, occupied, blocked, or otherwise unknown Workdirs without
|
||||
detaching a Worker or forcing deletion;
|
||||
4. observes the owning Runtime/provider and calls its existing Workdir cleanup
|
||||
only for an eligible clean Workdir;
|
||||
5. treats only successful provider cleanup or exact
|
||||
`working_directory_not_found` as removal evidence;
|
||||
6. deletes the Backend Workdir registry row and commits the operation's
|
||||
`completed`/`removed` result in one SQLite transaction.
|
||||
|
||||
A provider error leaves the registry intact and records a bounded
|
||||
`attention_required` result with explicit retryability. Startup recovery lists
|
||||
`pending` and retryable `failed` operations, then executes this same path after
|
||||
rereading live authority. `WorkdirDelete`, Workspace REST removal, Runtime
|
||||
cleanup execution, and recovery must not maintain separate inline
|
||||
provider-delete paths.
|
||||
|
||||
The public request contains only `working_directory_id` plus a bounded reason.
|
||||
The public result contains only the Workdir ID,
|
||||
`removed | retained | attention_required`, retryability, and an optional bounded
|
||||
failure category. Internal operation identifiers, checkpoints, provider paths,
|
||||
and credentials are not public DTO fields.
|
||||
|
||||
## Backend-owned bounded Jobs
|
||||
|
||||
A Backend Job is durable side-effect intent whose model execution is delegated to
|
||||
one ordinary Runtime-managed Worker per attempt. The Workspace Server owns the
|
||||
Job; `source_worker` and `notification_target` are provenance and delivery
|
||||
addresses, not parent ownership. Stopping or losing either Worker does not revoke
|
||||
Job intent. Attempt records retain the bound Worker identity as historical
|
||||
evidence without foreign-keying Worker retention to Job retention; normal Worker
|
||||
stop, archive, pin/hold, and removal policy remains authoritative.
|
||||
|
||||
The durable split is deliberate:
|
||||
|
||||
- `backend_jobs` stores immutable bounded input, its fingerprint, the current
|
||||
attempt number, and the final structured result or terminal failure;
|
||||
- `backend_job_attempts` binds one attempt and input digest to one dedicated
|
||||
`(Workspace, Runtime, Worker)` identity, a precommitted tracked Runtime
|
||||
submission request identity, a deadline, and its bounded outcome;
|
||||
- `backend_job_deliveries` records best-effort notification separately from the
|
||||
result. Its fixed-size collision-resistant durable delivery ID is also the
|
||||
Runtime's tracked notification request ID. Runtime reports acceptance only
|
||||
after the Worker has persisted the correlated notification receipt, so restart
|
||||
recovery can replay an ambiguous `sending` claim without duplicating Worker
|
||||
context. A missing or stopped recipient never discards a result or reruns
|
||||
model work.
|
||||
|
||||
Exact dispatch retry reuses the deterministic attempt and Worker allocation key.
|
||||
Only explicit re-evaluation of a definitively failed attempt increments the
|
||||
bounded attempt number; unknown outcomes are not replayable. The runner
|
||||
uses the existing embedded Runtime spawn, Worker registry, Session input,
|
||||
observation, usage, and lifecycle paths with no Workdir attachment. Each client
|
||||
explicitly selects an existing registry Profile. The runner resolves the complete
|
||||
Builtin/Workspace project catalog through the normal virtual config bundle and
|
||||
prompt projection path; browser launch candidates remain a narrower UI policy.
|
||||
Raw Profile source and filesystem/path selectors are not accepted. A selected
|
||||
recipe's instruction and features are not replaced with `backend-job` defaults.
|
||||
Profiles requiring ungranted Workspace mutation, ordinary Memory, child/peer,
|
||||
Flow, Workdir or MCP authority fail explicitly; no caller scope is inherited.
|
||||
The Ticket checker continues to select `builtin:backend-job` itself.
|
||||
|
||||
Job identity/result capability comes from an immutable Host-authored create and
|
||||
restore binding, not the Profile name, tag, instruction, Worker display name or
|
||||
Runtime placement. Backend checks the exact live Job/attempt/input digest and
|
||||
authenticated Worker before accepting results or domain operations. Profile
|
||||
policy alone never confers a subject grant.
|
||||
|
||||
A common optional resource key serializes Jobs using their existing durable
|
||||
intent/attempt records (schema 81), not a second domain workflow table. Exact
|
||||
request replay is distinct from a resource collision: the latter returns the
|
||||
original active immutable request with `resource_reused`, and never claims that
|
||||
new input was processed. Pending/unknown Jobs and **all** unfinished attempt
|
||||
cleanups retain the fence. Retry requires definitive failure, remaining attempt
|
||||
budget, completed prior cleanups and no competing resource owner. Unknown intent
|
||||
remains attention-required even after its Worker is removed.
|
||||
|
||||
Success is accepted only by the structured result boundary. Before Runtime
|
||||
input can execute, the attempt is fenced by Workspace, Job ID, attempt ID, input
|
||||
digest, bound Runtime Worker identity, and a fixed-size collision-resistant
|
||||
tracked submission request identity derived from the attempt. Ordinary Worker
|
||||
input and Console mutation are rejected for
|
||||
Job-owned Workers, so no later run in the same Session can exercise the result
|
||||
capability. Result JSON bytes are bounded before persistence. Worker final prose,
|
||||
Idle, Stopped, or a successful model call are not success evidence. Identical
|
||||
structured submission is idempotent; conflicting replay, stale or late attempts,
|
||||
wrong input digests, and other Workers fail closed.
|
||||
|
||||
On restart, an unbound reservation is redispatched with its stable allocation.
|
||||
A dispatched attempt continues only when its bound Worker and precommitted
|
||||
tracked submission identity remain observable; otherwise it becomes `unknown`.
|
||||
Ambiguous dispatch outcomes are also `unknown` and cannot be retried as a new
|
||||
attempt. Durable deadlines fence acceptance before the runner requests Worker
|
||||
stop. Notification recovery is independent: `sending` or `unknown` delivery
|
||||
claims return to `pending` and replay the same tracked notification request ID.
|
||||
The design does not claim general exactly-once model execution: a crash between
|
||||
Runtime creation and durable binding converges through stable allocation where
|
||||
available, and unrecoverable external evidence is surfaced as `unknown` rather
|
||||
than guessed. Historical attempts preserve the reason for retry without
|
||||
duplicating the Runtime Worker lifecycle state machine.
|
||||
|
||||
Workspace Worker subscriptions expose an explicit Job binding (`job_id`,
|
||||
`attempt_id`, and `purpose`) in snapshot and live projections. Sidebar hiding is
|
||||
a Web View policy over that canonical metadata; the Worker remains registered,
|
||||
observable, and eligible for its canonical read-only Console route.
|
||||
|
||||
## Diagnostics and audit
|
||||
|
||||
Persist bounded error categories and identifiers needed to investigate or retry.
|
||||
Do not persist credentials, provider handles, raw command output, raw prompts,
|
||||
full session transcripts, or host paths in ordinary operation diagnostics.
|
||||
|
||||
Attempt counts, last-attempt timestamps, and safe provider categories may help
|
||||
operations, but they are telemetry and evidence rather than lifecycle authority.
|
||||
Logs may describe detailed execution stages; the durable record should remain
|
||||
centered on intent, checkpoints, result, and disposition.
|
||||
|
||||
## Applying this rule
|
||||
|
||||
For a new or materially changed operation:
|
||||
|
||||
1. identify the owning authority and transaction boundary;
|
||||
2. classify it as an atomic ledger, reservation, durable side-effect operation,
|
||||
or parent workflow;
|
||||
3. define stable identity, fingerprint, and exact-retry behavior;
|
||||
4. list external side effects and decide which are idempotent or authoritatively
|
||||
observable;
|
||||
5. add only checkpoints that change retry behavior;
|
||||
6. keep child operation state in the child authority;
|
||||
7. separate failure, blocker, attention, and disposition from lifecycle state;
|
||||
8. state the unsupported crash windows honestly;
|
||||
9. test fingerprint conflict, exact retry, authority revalidation, checkpoint
|
||||
replay, and result/disposition projection as applicable.
|
||||
|
||||
Existing operation schemas need not be rewritten solely for vocabulary
|
||||
consistency. When an operation is changed for functional reasons, use this
|
||||
classification to remove derived or control-flow stages rather than adding
|
||||
another special-case lifecycle.
|
||||
@@ -1,92 +0,0 @@
|
||||
# Declarative Feature chat invocations
|
||||
|
||||
Feature chat invocations are explicit structured user input. They let an enabled Feature contribute discoverable chat actions without teaching each client Feature-specific syntax or asking the LLM to infer an operation from text.
|
||||
|
||||
## Namespaces and identity
|
||||
|
||||
Composer sigils have separate meanings:
|
||||
|
||||
- `:` is a client control command for the whole draft.
|
||||
- `@` is a Worker-readable file reference.
|
||||
- `/` selects a Feature invocation and may occur among ordinary prose.
|
||||
- `#` has no invocation meaning.
|
||||
|
||||
A public name such as `attach` is presentation, not authority. The payload carries the installed source-qualified `FeatureInvocationIdentity` (for example `builtin:attachments/attach`). Installation rejects duplicate public names and aliases, so clients never choose an arbitrary winner. Disabled, failed-to-install, or unavailable Features are absent from completion and cannot be resolved at execution.
|
||||
|
||||
## Declarative grammar
|
||||
|
||||
The common spelling is:
|
||||
|
||||
```text
|
||||
/name(positional, named=value) following natural language
|
||||
```
|
||||
|
||||
The closing `)` is the explicit argument/prose boundary. String values use JSON double quoting and escaping (including Unicode escapes and escaped control characters). Bare values end at whitespace, comma, or `)`; comma separates arguments and a trailing comma is accepted. Integers are decimal signed 32-bit values (`-?[0-9]+`); booleans are `true` or `false`. Positional indices are contiguous; every argument can also be represented by its stable name in restored chip text. Mixed named/positional input advances the positional cursor only for positional values, and assigning the same argument twice is an error.
|
||||
|
||||
A descriptor is declarative data, for example:
|
||||
|
||||
```json
|
||||
{
|
||||
"identity": "builtin:prepare/prepare",
|
||||
"name": "prepare",
|
||||
"aliases": [],
|
||||
"display_name": "Prepare context",
|
||||
"description": "Prepare scoped context before processing the request",
|
||||
"syntax": "parenthesized",
|
||||
"arguments": [
|
||||
{"name": "path", "position": 0, "required": true, "value_type": {"kind": "worker_file"}, "completion": {"kind": "worker_file"}},
|
||||
{"name": "mode", "required": false, "value_type": {"kind": "enum", "values": ["safe", "fast"]}, "completion": {"kind": "static", "values": ["safe", "fast"]}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`/prepare("資料/a b", mode=safe) explain the result` produces a typed invocation and a separate following Text segment only when explicitly confirmed. Missing required arguments, unknown or duplicate arguments, invalid enum/primitive values, and incomplete quotations remain validation errors, not partial business execution.
|
||||
|
||||
Descriptors declare requiredness, value type, positional index, completion strategy, and optional description. Completion strategies are static values, Worker files, client files, or a host-owned provider. Metadata and completion are side-effect free. Arbitrary parser or provider code never crosses into a client.
|
||||
|
||||
Clients may parse this spelling only for input assistance. A call becomes executable only after the user selects a descriptor and the client emits `Segment::FeatureInvoke`. Free text, URLs, paths, pasted content, attachments, assistant output, and tool output are not scanned or promoted by the Host.
|
||||
|
||||
## Composer and completion lifecycle
|
||||
|
||||
TUI and Web composers retain selected invocations as atomic typed chips containing identity and structured arguments. Backspace/Delete and range deletion remove an unsent chip; removal is not an undo for an already executed operation. Draft history, transport retry, queueing, Session history, snapshot/live projections, and rewind restoration preserve the typed segment rather than flattening and reparsing it.
|
||||
|
||||
Completion requests carry a kind, fresh `request_id`, and source-qualified argument context. Host and Runtime echo the request ID unchanged. Clients correlate replies with that nonce plus their current draft identity/content and edit invalidation state, cursor, target Worker, authority snapshot, and connection generation; even identical-prefix/context ABA replies are discarded. Old uncorrelated replies cannot establish a selected invocation. Client-file arguments use a declared `InvocationClientAdapter`; they do not ask the Worker to resolve a client-local path.
|
||||
|
||||
## Attachment adapter
|
||||
|
||||
`/attach` is the built-in real use of the generic contract. Its descriptor declares a required client-file argument and the attachment client adapter.
|
||||
|
||||
- TUI may select a path visible to the TUI process, stage an upload, and replace the invocation intent with an `UploadedFile` chip.
|
||||
- Web opens the existing browser picker; it does not enumerate the browser host filesystem.
|
||||
- The local path is never sent to or resolved by the Worker.
|
||||
- Uploading is cancellable draft staging, not invocation execution. Removing an in-progress upload chip by Backspace/Delete, range replacement, or draft abandonment cancels its reservation and adapter upload; the removed upload must no longer block a text-only Submit. Late callbacks and Undo cannot restore that cancelled reservation as sendable input. A completed unsent uploaded-file chip may retain its staged resource while editor Undo can restore it, releasing abandoned resources at the draft boundary; a late completion must never turn a deleted pending reservation into such an Undo-owned resource.
|
||||
- Authoritative `SubmissionAccepted` (not socket-send success) transfers uploaded references to the durable input lifecycle. Draft removal cannot delete an in-flight reference whose admission may already have succeeded, or an accepted attachment. Rejection restores the exact typed intent for retry; retry retains request/invocation IDs. Upload completion alone is not submission acceptance.
|
||||
- TUI standalone currently has no client-local upload staging transport. It reports the unsupported adapter and preserves the draft; it does not reinterpret a local path as a Worker host path.
|
||||
|
||||
There is no `/clear-attachments` command. Normal composer editing is the removal mechanism.
|
||||
|
||||
## Installation and execution
|
||||
|
||||
A Feature declares invocation descriptors using `FeatureDescriptor::with_chat_invocation` and registers matching handlers with `context.chat_invocations().register` (or `register_with_completion`) during `FeatureModule::install`. Registration must match the declared descriptor and the installing Feature's source-qualified identity. The invocation registry is checkpointed and rolled back with other Feature contributions. Only successfully installed, currently available descriptors are exposed through completion.
|
||||
|
||||
Authority-sensitive handlers override the side-effect-free `is_available()` discovery hook and `validate()` semantic hook. They must still revalidate authorization and use scoped idempotency at their business commit boundary inside `invoke()`; defaults grant no extra domain authority. Availability is checked again after asynchronous provider/handler work. Reusing a successful preparation result or resuming its dependent model run also rechecks the side-effect-free semantic/argument-specific authority validation; general Feature availability alone is insufficient. Completion strategies come from installed metadata, never from a client-selected provider identifier. Enum and boolean candidates can derive directly from value types when no explicit completion strategy is supplied.
|
||||
|
||||
Completion output is bounded to 64 entries / 64 KiB encoded data, with per-field byte limits. Oversized candidate values are discarded instead of altered. Handler results allow 4 KiB messages and 16 KiB context; oversized results/errors after execution become `OutcomeUnknown`, withholding dependent model context rather than assuming the operation was unexecuted.
|
||||
|
||||
The Worker validates every structured payload again against the installed descriptor. It executes invocations in segment order only when the durable submission is activated, after committing the user input and before the dependent LLM request. A completion choice, draft edit, queued-but-cancelled submission, reconnect, history view, or restored snapshot never executes a handler.
|
||||
|
||||
A submission may contain at most eight invocations with distinct invocation IDs. The first failed or outcome-unknown result fences later invocations **and prevents the dependent LLM run**; no cross-invocation rollback is promised. Every result is committed as typed Session history and rendered as bounded model-visible context. `OutcomeUnknown` is distinct from failure and is never automatically retried.
|
||||
|
||||
Before calling a handler, the Worker appends a start receipt through the existing `worker.pending_activations.v1` extension checkpoint. The result and updated receipt are appended together with the annotated system item. An unresolved start (including handler completion followed by a failed result append) remains a fence across same-process retry, restart, Pause/Resume, compaction, and rewind. Reusing an ID with changed arguments is rejected; exact replay can reuse its recorded result but does not invoke again. Receipt capacity is bounded and refuses untracked execution rather than evicting recovery evidence. Restoring or displaying history never reexecutes a handler.
|
||||
|
||||
Rewind preserves invocation-only receipts and the recovery-blocked fence independently of submission/notification queues. The retained history prefix and recovery checkpoint replace the segment together through the existing atomic Store replacement contract. There is no truncate-then-checkpoint repair window in which a crash can erase a completed or ambiguous business operation's recovery evidence.
|
||||
|
||||
The recovery receipt retains the invocation ID, qualified identity, SHA256 typed-payload digest, and bounded result—not another copy of every argument. Invocation IDs are nonempty and at most 128 UTF-8 bytes; clients generate fresh UUIDs on explicit selection and preserve them through transport retry. The session retains at most 1,024 recovery receipts without eviction. Corrupt, duplicate, inconsistent, or oversized recovery evidence fails closed.
|
||||
|
||||
`submission_request_id` protects submission admission and retry, while `invocation_id` is the stable idempotency key passed to a side-effecting handler. A handler must scope that identity at its domain boundary. This design does not claim a new generic exactly-once substrate.
|
||||
|
||||
## Authority boundaries
|
||||
|
||||
Possession of syntax, metadata, identity, or a typed payload grants no permission. Runtime target, enabled Feature, argument schema, and captured domain authority are checked at execution. Completion providers cannot perform business mutations or expose credentials. Handlers may share an underlying domain operation with Worker tools, but chat discovery does not expose every tool automatically.
|
||||
|
||||
Text-only clients submit text. The Backend and Host never reinterpret text as an invocation. Unknown typed input is visibly unresolved in restoration and **rejected on submission**, not silently flattened or processed by the LLM. Notify remains text-only: extra typed wire fields or typed Runtime input cannot be discarded into an advisory message or executed as a side-effecting call.
|
||||
@@ -1,111 +0,0 @@
|
||||
# Flow state graph and verifier
|
||||
|
||||
Flow is a Workspace-scoped declarative state graph. A Worker operating under an active Flow can request evaluation of its current state's outgoing conditions, but cannot select or write a target state directly.
|
||||
|
||||
## Source authority
|
||||
|
||||
A Workspace-authored Flow source is one DCDL document stored under a virtual path such as `flows/coder-review.dcdl`. Built-in Flow sources are compiled resources under `resources/flows/*.dcdl` and use read-only virtual paths such as `builtin/flows/coder-review.dcdl`. The source document is the graph authority; states and transitions are not normalized into independently editable relational records.
|
||||
|
||||
Every invocation uses a source-qualified typed selector:
|
||||
|
||||
```text
|
||||
builtin:<slug>
|
||||
workspace:<slug>
|
||||
```
|
||||
|
||||
Unqualified selectors and implicit override precedence are rejected. `builtin:coder-review` resolves from the embedded resource catalog. `workspace:coder-review` resolves from the current Workspace DB source. Built-in and Workspace sources with the same slug coexist as distinct logical records.
|
||||
|
||||
The compiler pipeline is:
|
||||
|
||||
```text
|
||||
DCDL source
|
||||
-> decodal evaluation
|
||||
-> private Serde-compatible value
|
||||
-> typed Flow source schema
|
||||
-> graph validation
|
||||
-> CompiledFlowDefinition
|
||||
```
|
||||
|
||||
The Serde-compatible intermediate is private compiler infrastructure. Public APIs and persisted Flow runtime records use typed Flow domain values.
|
||||
|
||||
`flow_sources` stores one current logical Workspace-authored source per Workspace/slug. Every changed source retains the original content, content digest, and compiled definition in immutable source history. Built-ins are read-only embedded resources: resolving one compiles and returns its exact source snapshot without writing it into Workspace DB or maintaining a manual change counter. Runtime pins source identity, content digest, and compiled definition in Worker state, so editing a Workspace source or updating a built-in resource never changes an existing instance.
|
||||
|
||||
The compiler rejects unknown fields, unsupported schema versions, invalid/reserved identifiers, unknown transition targets, authored `$cancelled` state/targets, terminal states with outgoing transitions, non-terminal states without transitions, unreachable states, and reachable closed paths that cannot reach a user-declared terminal state. It injects the synthetic exceptional-cancellation transition and `$cancelled` terminal state after validation.
|
||||
|
||||
## Runtime-owned instance and event authority
|
||||
|
||||
Flow source authority and Flow execution authority are split at the immutable source snapshot boundary.
|
||||
|
||||
The Workspace Backend stores only current Flow sources and immutable source history. Resolving a source-qualified selector returns the Workspace id, Flow id, content digest, and compiled definition. Resolution is read-only with respect to Flow execution: it never creates an instance, attempt, or event.
|
||||
|
||||
One Runtime Worker durably owns:
|
||||
|
||||
- the pinned source snapshot and compiled definition;
|
||||
- its active Flow instance, current state, and lifecycle status;
|
||||
- its active transition attempt;
|
||||
- its ordered Flow events.
|
||||
|
||||
The complete `FlowRuntimeState` is persisted as a typed `flow.runtime.v1` Worker session extension. Initial Flow state is committed in the same `UserInput` log record as the entered-state instructions and remaining Submit segments. Backend therefore cannot contain an active instance that Worker history has never observed.
|
||||
|
||||
Transition mutations clone the current Runtime state, append ordered events, persist the new session extension, and only then replace the in-memory projection. A persisted verifying attempt survives Runtime restart and same-Worker restore; the next transition request recovers it instead of creating a competing attempt.
|
||||
|
||||
The executable checks live in `FlowRuntimeState::begin_or_recover_transition` and `resolve_active_transition`, with private `begin_transition` / `resolve_transition` helpers:
|
||||
|
||||
- Before starting, require an active instance, no other active attempt, and the same compiled content digest as the pinned source. Reject an attempt ID already present in `TransitionRequested` events, including cancelled/failed attempts.
|
||||
- Before consuming a result, require its exact instance and currently reserved attempt ID. Then compare the compiled and captured definition digests against the pinned digest.
|
||||
- Before applying a successful verdict, require the instance to remain active and its current state to equal the captured source state. Validate the complete outgoing-condition result set and accept exactly one met condition.
|
||||
- Completion or cancellation releases the active attempt. A delayed result cannot consume a newer attempt even if the graph returns to the same state. The persisted event log prevents reusing the old attempt ID after restore.
|
||||
|
||||
There is no independent state counter or manually incremented built-in source counter. Event `sequence` remains only the append order of the actual event log, not a substitute for any of these conditions.
|
||||
|
||||
Worker stop retains the Flow with the Worker session. Restoring the same Worker reconstructs the latest state from session extensions and the saved Profile still determines `feature.flow` eligibility. Worker deletion removes the owning Worker/session; Flow state is not implicitly handed to another Worker.
|
||||
|
||||
Workspace Server schema migration v26 removes the legacy `flow_instances`, `flow_transition_attempts`, and `flow_events` tables. Backend/Web visibility, when needed, is a bounded Runtime Worker projection rather than a second instance authority.
|
||||
|
||||
## Worker boundary
|
||||
|
||||
Flow invocation uses the normal Submit segment vector rather than a Worker-create field:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "submit",
|
||||
"submission_request_id": "018f4f15-5c41-7d3a-8a72-2e755bc71681",
|
||||
"input": [
|
||||
{ "kind": "flow", "selector": "builtin:coder-review" },
|
||||
{ "kind": "text", "content": "Ticket 00001... implementation" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Runtime accepts exactly one Flow segment only when the resolved Profile enables `feature.flow` and a Workspace client is available. The Worker asks Workspace authority only for an immutable source snapshot, creates the instance locally, replaces the Flow segment with the entered state's instructions, and commits that runtime state atomically with the remaining Submit segments before LLM execution. A Worker with an active Flow rejects the duplicate input without changing its local state or events.
|
||||
|
||||
The generic model-facing `WorkerSpawn` accepts `initial_submit: Vec<Segment>` and routes them unchanged through the shared Workspace spawn request into Runtime `CreateWorkerRequest.initial_input`. It does not have a parallel `initial_text` or a role-specific `SpawnCoder` wrapper. Backend derives the flat content projection from the canonical segment vector, validates Flow shape before spawn, and includes the segment vector in lifecycle idempotency fingerprints. Runtime does not commit Worker creation or report spawn success merely because the initial Submit request entered the Worker's in-memory channel: Runtime assigns the Submit an opaque id, the Worker commits that id as an extension on the same `UserInput` entry as any initial `FlowRuntimeState`, and the execution backend must return a matching typed input-commit acknowledgement. Restoring the same Worker never replays spawn initial segments.
|
||||
|
||||
When an Orchestrator supplies `ticket_id` to generic `WorkerSpawn`, the Worker tool derives the assignment operation id from the durable tool-call id rather than accepting lifecycle authority from model input. The shared Workspace worker-create route projects that request into a Coder Ticket-role intent and atomically applies the existing queued-Ticket assignment operation only after Runtime has returned the input-commit acknowledgement. A spawn or pre-commit input failure therefore leaves the Ticket queued and unassigned.
|
||||
|
||||
`RequestFlowTransition` accepts only:
|
||||
|
||||
```json
|
||||
{ "reason": "bounded explanation" }
|
||||
```
|
||||
|
||||
It does not accept Workspace, Flow, instance, Runtime, Worker, state, transition, or target identifiers. `RuntimeFlowCoordinatorClient` reads and persists only the current Worker's local `FlowRuntimeState`; no Workspace mutation client participates in transitions. Profile enablement makes the capability eligible but does not create or select an instance; without an active instance the transition request fails closed.
|
||||
|
||||
The transition tool begins or recovers a locally persisted attempt, runs one internal verifier, and resolves the typed verifier outcome into the Worker-owned state. The resulting state and next-state instructions are committed in the normal tool result history.
|
||||
|
||||
## Internal verifier authority
|
||||
|
||||
The internal verifier receives:
|
||||
|
||||
- one immutable snapshot of committed parent-session entries through `session-explore`;
|
||||
- the captured current state, request reason, and complete outgoing condition list;
|
||||
- `FinishFlowVerification`;
|
||||
- when the parent has a Workdir session, only `Read`, `Glob`, and `Grep` backed by a `ReadOnlyWorkdirSession` capability-reducing wrapper.
|
||||
|
||||
It does not receive Workspace, Ticket, Memory, Worker-management, write/edit, Bash, or command authority. The read-only wrapper reports only read capabilities, rejects mutation/command operations, and closing it does not close the parent's source session.
|
||||
|
||||
`FinishFlowVerification` accepts exactly one `met | not_met | indeterminate` result and a bounded rationale for every transition id in the attempt. Missing, unknown, or duplicate ids are rejected before a result is recorded. A prose-only internal Worker completion is a failed verifier outcome, not a successful transition.
|
||||
|
||||
## Separation from the role-owned loop
|
||||
|
||||
This state graph does not implement Coder/Reviewer lifecycle, state-entry side effects, deterministic condition providers, or arbitrary Flow state data. The downstream role-owned loop submits the built-in Coder Flow segment, follows entered-state instructions, and uses Flow events plus typed review/repository evidence for its higher-level completion decisions.
|
||||
@@ -1,23 +0,0 @@
|
||||
# Legacy Workspace Memory authority
|
||||
|
||||
The single-document Workspace Memory product is a **legacy/deprecated** compatibility surface. Its document, staging candidates, and staging resolutions are durable Workspace context owned by the Workspace Server control-plane database. It is separate from subject-scoped subjektiv Memory, whose persistence authority is the Workspace's Server-managed `subjektiv` Feature database.
|
||||
|
||||
Legacy Runtime Workers query and mutate the old document through the established typed `/api/w/{workspace_id}/memory...` operations and `MemoryQuery`, `MemoryReadDocument`, `MemoryUpdateDocument`, and legacy `MemoryStaging*` tools. While those surfaces remain available, their existing inputs keep their existing Workspace-document meaning. They must not be silently redirected to subjektiv, assigned an inferred subject, or reused with a new interpretation.
|
||||
|
||||
Worker-side `memory` code owns shared schemas and transport types, not persistence. Legacy `AppendAudit` is acknowledgement-only and has no dedicated durable Memory-audit rows. General Session logs and diagnostics remain independently durable under their own retention policies.
|
||||
|
||||
Repository or ancestor `.yoi/memory`, `.yoi/knowledge`, and malformed marker trees are ignored. There is no cwd/ancestor discovery, dual read, startup import, or automatic migration. The removed `yoi memory lint` command is intentionally not a compatibility surface; invoking `yoi memory ...` is an unknown command.
|
||||
|
||||
Memory remains supporting context rather than implementation authority. Use Tickets, Objectives, repository files, Git history, and append-only Session records for exact current facts.
|
||||
|
||||
## subjektiv cutover and reset
|
||||
|
||||
Enabling `feature.subjektiv` does not migrate or erase legacy rows. The only supported destructive boundary is the explicit, authorized legacy reset API described by the [subjektiv product cutover runbook](../development/subjektiv-product-cutover.md). It atomically deletes the old document, staging candidates, and staging resolutions while retaining Workspace Memory settings, Sessions and diagnostics, Tickets, Knowledge, Skills, the subjektiv Feature database, and unrelated Workspace data. The reset is operator-triggered and idempotent; it is never a startup migration or Profile side effect.
|
||||
|
||||
## Upgrade guidance
|
||||
|
||||
Old repository-local Memory data is not imported automatically. Before upgrading from a version that used `.yoi/memory`, explicitly export any data that must be retained with that old version and import it into the intended Workspace-backed product surface. Do not assume the legacy control-plane reset imports repository-local or legacy Workspace data into subjektiv. Stale files may be archived or deleted only after verification.
|
||||
|
||||
## Preserved filesystem boundaries
|
||||
|
||||
Repository-local Memory removal and the control-plane legacy reset do not affect repository checkouts, Git metadata, Runtime-owned Workdirs, Session logs or artifacts, Server data directories, XDG client/server configuration, or the subjektiv Feature database. They remove only their explicitly named legacy authority.
|
||||
@@ -1,72 +0,0 @@
|
||||
# Merge Request native WIP projection
|
||||
|
||||
## Purpose and authority
|
||||
|
||||
When a Worker explicitly runs in `wip` mode and its Merge Request Feature has at least one enabled operation, the Worker Host projects that exact operation set as native Worldspace objects. The projection is an adapter over the existing scoped Workspace Merge Request tools and Backend routes. It does not create a second Merge Request store, provider integration path, review grant, approval rule, or completion authority.
|
||||
|
||||
The Host assigns the Merge Request provider the root `merge-requests` namespace through `WipMountRegistry` and mounts:
|
||||
|
||||
```text
|
||||
/merge-requests
|
||||
/merge-requests/<merge-request-id>
|
||||
```
|
||||
|
||||
There is no Feature Object or `/features/merge-request` compatibility alias. Legacy paths remain historical display data only and fail as current input with rediscovery guidance.
|
||||
|
||||
An item identity is a single canonical path segment. Nested paths and percent-encoded target substitution do not resolve. Item operations remove `merge_request_id` from their descriptor and reconstruct it only from the selected object route. Discovery does not mint authority: every call still uses the Worker's scoped `WorkspaceClient`, the enabled Feature flags, ordinary Tool permission rules, and the Backend's Workspace, repository, assignment, provider-ref, review, and integration checks.
|
||||
|
||||
## Current Feature inventory
|
||||
|
||||
The native surface is assembled from `MergeRequestFeatureConfig`, not from a broader Backend catalog. Only an ordinary Merge Request tool that would be registered for the Worker is projected and claimed from `/tools`. The current Feature has no list/query Tool, so the native adapter does not invent one: callers can resolve an existing known MR ID as a direct item route, while a successful `open` result includes the newly created item path.
|
||||
|
||||
Collection interface `yoi.merge-request/collection/v1`:
|
||||
|
||||
| Native operation | Existing Tool | Preserved authority inputs |
|
||||
| --- | --- | --- |
|
||||
| `open` | `OpenMergeRequest` | Ticket, repository key, source selector, target selector, summary |
|
||||
|
||||
Item interface `yoi.merge-request/item/v1`:
|
||||
|
||||
| Native operation | Existing Tool | Preserved non-identity inputs or authority |
|
||||
| --- | --- | --- |
|
||||
| `read` | `ShowMergeRequest` | bounded thread `after` and `limit`; Backend resolves current source and target refs |
|
||||
| `check_readiness` | `CheckMergeRequestReadiness` | Backend resolves the current source subject and derives current approval/blockers |
|
||||
| `review` | `ReviewMergeRequest` | decision/body/findings plus the injected Reviewer capability, captured request event, Ticket content digest, linked result snapshot, and exact candidate |
|
||||
| `complete` | `CompleteMergeRequest` | operation ID, approval event, target-before/after refs, merge strategy and conflict resolution; Backend requires Orchestrator authority |
|
||||
|
||||
Typical role surfaces are therefore:
|
||||
|
||||
- Coder: collection `open`; item `read`.
|
||||
- Reviewer: item `read` and `review`.
|
||||
- Orchestrator: item `read`, `check_readiness`, and `complete`.
|
||||
|
||||
`CompleteTicket` belongs to the Ticket Feature and route-bound Ticket item `complete`, alongside state and close decisions. It takes current item/state CAS, a replay key, reason, and optional references; it requires neither an MR result set nor a Reviewer approval. The former MR collection `complete_ticket` operation is not published. The review descriptor never contains the injected capability token, and a review item route must exactly match the Merge Request captured by that capability before the ordinary review Tool can run.
|
||||
|
||||
## Preserved review and integration gates
|
||||
|
||||
The native projection delegates to the same ordinary tools and REST endpoints. In particular, it does not replace or weaken:
|
||||
|
||||
- Workspace and configured-repository isolation;
|
||||
- current Coder assignment and assigned-Workdir source-selector proof for opening/review requests;
|
||||
- provider resolution of the exact current source candidate;
|
||||
- ReviewRequested binding to the Ticket item `content_digest` and complete linked-MR/source snapshot;
|
||||
- injected Reviewer capability consumption and exact MR/candidate matching;
|
||||
- source movement invalidating source approval while target-only movement preserves unchanged-source review;
|
||||
- unresolved request-change and revoked/stale approval rejection;
|
||||
- current target observation, target-before/after proof, strategy, and conflict-resolution checks;
|
||||
- Orchestrator-only Merge Request integration;
|
||||
- immutable integration/result evidence and exact linked-result-set requirement attestation.
|
||||
|
||||
The Worldspace adapter performs no Git or provider integration. A visible `complete` operation remains unusable without the ordinary Backend's authenticated online Orchestrator source and integration evidence.
|
||||
|
||||
## Bounded reads, staleness, and outcomes
|
||||
|
||||
`read` exposes the existing bounded thread page with optional `after` and a `1..=200` limit. Responses keep linked Tickets as references/data, so Ticket or Objective native projections are not prerequisites. Descriptors never expand repository diffs, unbounded thread history, credentials, or capability material.
|
||||
|
||||
A successful item read records a response fingerprint. A successful item mutation advances a local mutation generation. Opening records the returned MR and adds its native path to the result. Ticket decisions advance the Ticket provider's object generation, not the MR integration state or proofs. Ending Ticket work can affect later live source observation, so clients reread MR authority before MR actions; Backend checks remain authoritative. The common WIP Host rejects calls made with stale object/interface validators; callers must rediscover before deciding whether to call again.
|
||||
|
||||
Optional fields preserve ordinary JSON-input semantics: omission remains omission and explicit WIP unit/null remains JSON `null` for schema and permission matching. The reconstructed object is validated against the original Tool JSON Schema before the Tool executes.
|
||||
|
||||
Backend 401/403 responses map to permission denial, 404 to missing objects, and deterministic 400/409/422 authority or workflow failures to invalid arguments. Cancellation and interruption remain distinct. Transport/server failures after dispatch, malformed successful mutation responses, and identity-mismatched mutation responses are reported as unknown outcomes and must not be retried automatically.
|
||||
|
||||
Normal Tool mode is unchanged. In WIP mode, only the enabled Merge Request compatibility tools represented by these native objects are suppressed, preventing duplicate native and `/tools` publication.
|
||||
@@ -1,57 +0,0 @@
|
||||
# Objective native WIP projection
|
||||
|
||||
## Purpose and authority
|
||||
|
||||
When a Worker explicitly runs in `wip` mode and the Objective Feature is enabled, the Worker Host projects Objectives as native Worldspace objects. The projection is an adapter over the existing scoped Workspace API. It does not introduce another Objective store, state machine, reference resolver, or audit path.
|
||||
|
||||
The Host assigns the Objective provider the root `objectives` namespace through `WipMountRegistry` and mounts:
|
||||
|
||||
```text
|
||||
/objectives
|
||||
/objectives/<objective-reference>
|
||||
```
|
||||
|
||||
There is no Feature Object or `/features/objective` compatibility alias. Legacy paths are retained only as historical text and fail as current inputs with rediscovery guidance.
|
||||
|
||||
`<objective-reference>` is either a canonical `O-<digits>` key or an existing canonical internal alphanumeric ID. Only one direct child segment is accepted. Nested suffixes, Ticket keys, and unrelated or hidden prefixes do not resolve as Objective objects.
|
||||
|
||||
The Backend remains authoritative for existence, `O-*`/internal-ID resolution, Workspace scope, validation, audit events, pagination, body/event bounds, and linked-Ticket validation. A discovered route is not independent authority; every operation still uses the Worker's current `WorkspaceClient`.
|
||||
|
||||
## Current operation inventory
|
||||
|
||||
The collection interface is `yoi.objective/collection/v1`:
|
||||
|
||||
| Native operation | Existing Tool permission identity | Parameters |
|
||||
| --- | --- | --- |
|
||||
| `query` | `QueryObjective` | `query`, `states`, `linked_ticket_id`, `updated_after`, `updated_before`, `sort`, `limit`, `cursor` |
|
||||
| `create` | `ObjectiveCreate` | `title`, `body_md`, `state`, `linked_tickets` |
|
||||
|
||||
The item interface is `yoi.objective/item/v1`:
|
||||
|
||||
| Native operation | Existing Tool permission identity | Parameters |
|
||||
| --- | --- | --- |
|
||||
| `read` | `ShowObjective` | `event_limit`, `event_cursor` |
|
||||
| `edit` | `ObjectiveEdit` | `title`, `old_string`, `new_string`, `replace_all` |
|
||||
| `set_state` | `ObjectiveSetState` | `state` |
|
||||
| `link_ticket` | `ObjectiveLinkTicket` | `ticket_id` |
|
||||
| `unlink_ticket` | `ObjectiveUnlinkTicket` | `ticket_id` |
|
||||
|
||||
The item operations intentionally do not accept an Objective `id`. The handler is created from the resolved item route and reconstructs the equivalent ordinary Tool permission arguments with that route-bound identity. Descriptor validation rejects a caller attempting to supply a second Objective target.
|
||||
|
||||
`deny` and `ask` permission results fail closed before Backend dispatch. Backend 401/403 map to permission denial, 404 to missing target, and deterministic client validation failures to invalid arguments. Transport, server, or response-projection failures after dispatch retain the common unknown-outcome contract; mutating operations must not be retried automatically when the outcome is unknown.
|
||||
|
||||
## Bounded reads and references
|
||||
|
||||
`query` retains the ordinary Worker limit of 1–100 and returns stable snippets, canonical `O-*` references, linked `T-*` references, `next_cursor`, and `has_more`. Each result also includes its canonical item path. It does not expand Objective bodies, complete events, or Ticket records.
|
||||
|
||||
`read` retains the ordinary Worker event limit of 1–50 and projects the Backend-bounded body, `body_truncated`, linked-Ticket summaries, bounded events, and event pagination. Internal Objective, Ticket, and event-storage IDs are removed from model-visible results.
|
||||
|
||||
`create` returns the authoritative Objective detail plus its canonical item path. Mutations return the authoritative post-operation detail. The Object publishes fixed operation signatures, not the Objective's mutable content: editing content does not invalidate an unchanged Object/Interface contract. The adapter therefore keeps no content-freshness cache or synthetic change identifier. Each invocation checks current permission and dispatches to current Backend authority; `edit` evaluates its explicit `old_string`/uniqueness conditions there. Reads always fetch current Backend data rather than replaying cached content. Host validators still protect route and Interface signatures before dispatch.
|
||||
|
||||
Linked Ticket references remain strings passed to the existing Objective Backend operation. Their presence does not publish a Ticket object, grant Ticket read or mutation authority, or require a Ticket-native WIP projection.
|
||||
|
||||
## Compatibility and Tool mode
|
||||
|
||||
The native collection/item pair claims all seven Objective compatibility capabilities in WIP mode. Therefore `/tools/QueryObjective`, `/tools/ShowObjective`, and `/tools/Objective*` are not published alongside the native routes. Unrelated enabled tools keep their compatibility projections.
|
||||
|
||||
Normal `tools` mode is unchanged: it still exposes the same seven Objective tools with their existing names and input schemas. Both surfaces share the same `WorkspaceHttpObjectiveBackend` and scoped Backend endpoints.
|
||||
@@ -1,32 +0,0 @@
|
||||
# Orchestrator worktree and implementation worktree layout
|
||||
|
||||
Yoi can run an Orchestrator from a dedicated orchestration worktree while implementation worktrees still belong under the original workspace root. The runtime must therefore distinguish role execution cwd/workspace from the target repository root used for implementation branches and merge-completion.
|
||||
|
||||
This decision records the layout policy for Ticket `00001KTTB479X`.
|
||||
|
||||
## Roots
|
||||
|
||||
- `role_workspace_root`: the launched role Runtime's explicit execution/workdir root. Ticket authority remains the Workspace Server control-plane database reached through the Workspace API.
|
||||
- `original_workspace_root`: the repository root from which implementation worktrees should be created.
|
||||
- `implementation_worktree_root`: `<original_workspace_root>/.worktree`.
|
||||
- `merge_target_workspace_root`: the workspace/branch where merge-completion validates and merges.
|
||||
|
||||
When these roots differ, implementation worktrees must be created under `implementation_worktree_root`, not under the Orchestrator process cwd. Merge-completion must run against `merge_target_workspace_root`, not whichever worktree happens to host the Orchestrator.
|
||||
|
||||
## Policy
|
||||
|
||||
- Workspace Server Ticket records remain authoritative; none of these filesystem roots selects or hosts a Ticket backend.
|
||||
- Main-workspace draft Tickets are not an implicit Orchestrator queue. Queue acceptance still requires explicit Ticket state and routing checks.
|
||||
- Implementation branches should not include unrelated Orchestrator bookkeeping. Orchestration progress stays in the Workspace Server Ticket/event record; child implementation worktrees contain implementation diffs and branch-local artifacts only when needed.
|
||||
- Prompt and workflow guidance must treat merge-ready dossier as a checkpoint. If reviewer approval, clean/safe target workspace, and standing/user merge authority are present, Orchestrator should continue through merge, validation, Ticket close, and cleanup instead of stopping solely because a dossier exists.
|
||||
|
||||
## Implemented in this branch
|
||||
|
||||
- Ticket role launch prompts now emit a `Workspace routing context` section for Orchestrator launches, and for any launch that explicitly provides original/target roots.
|
||||
- `TicketRoleLaunchPlan` carries `original_workspace_root`, `implementation_worktree_root`, and `target_workspace_root` as explicit plan fields for callers/tests.
|
||||
- Orchestrator role prompt resources now say to create implementation worktrees under the original workspace root and to run merge-completion against the recorded target workspace.
|
||||
|
||||
## Follow-up boundaries
|
||||
|
||||
- Panel/workspace orchestration can later populate `original_workspace_root` and `target_workspace_root` when launching Orchestrator from a dedicated worktree.
|
||||
- A future implementation can add a first-class orchestration-worktree launcher and cleanup policy, but it must keep the four-root distinction above visible in launch diagnostics and tests.
|
||||
@@ -1,35 +0,0 @@
|
||||
# Design overview
|
||||
|
||||
Yoi is organized around one rule: durable authority must live in explicit records, while runtime conveniences remain reconstructable hints.
|
||||
|
||||
That rule shapes the crate split. The runtime can restart, attach, compact, or delegate work without relying on hidden controller state, and developer tooling can inspect the records that explain why an agent acted.
|
||||
|
||||
## Core layers
|
||||
|
||||
- `yoi` owns the product CLI and top-level command shape. It is the façade that wires connection selection, product commands, and normal TUI launch.
|
||||
- `worker` turns a `Engine` into a named runtime entity with scope, session persistence, protocol handling, tools, and Worker metadata integration.
|
||||
- `agen` owns model-facing turns: history append, retries, continuation, pruning/compaction mechanics, tool loops, and provider-independent callbacks.
|
||||
- `session-store` owns replayable append-only conversation/session logs.
|
||||
- `pod-store` owns current Worker metadata keyed by Worker name.
|
||||
- `protocol` defines the socket message boundary between clients and Workers.
|
||||
- `standalone` owns the client-side, one-process Standalone host and its dedicated session store. It does not create Runtime Workers, PID/socket catalogs, or Workspace product authority.
|
||||
- `client` contains reusable Backend Workspace/Runtime clients plus the shared `Target` boundary. Its normal targets are exactly Standalone and Backend; it is not a subprocess launcher or repository-local product backend.
|
||||
- `manifest` resolves Profiles, Manifests, model/provider references, scopes, prompts, and tool permission policy into a runtime contract.
|
||||
- `tools` implements built-in tools with bounded output and policy-aware execution.
|
||||
- `memory` owns shared legacy Memory transport plus extraction/evidence schemas, not persistence. Legacy single-document Memory lives in the Workspace Server control-plane database; subject-scoped Memory lives in the Workspace's Server-managed `subjektiv` Feature database.
|
||||
- `workspace-server` is the local Workspace control-plane seam. It can project Tickets, Workers, lifecycle, usage, and orchestration events, but browser/API operations must stay on opaque backend identities instead of raw local paths, sockets, Worker names, or session files.
|
||||
- `tui` is a UI over either one in-process Standalone session or Backend Workspace/Runtime Worker authority; it should not invent a local Worker catalog or durable product state.
|
||||
|
||||
## Why these boundaries exist
|
||||
|
||||
The Engine should not know process identity, Worker names, live sockets, spawned children, or UI state. It should know how to run an LLM turn over committed history and tools.
|
||||
|
||||
The Worker should not make provider-specific wire decisions. It coordinates runtime identity, persistence, scope, and protocol delivery around a Engine.
|
||||
|
||||
The TUI should not be an alternate source of truth. It may queue local input, show optimistic affordances, and render snapshots, but durable state comes from Worker/session records.
|
||||
|
||||
The CLI should own product command shape. Other crates should expose library APIs and typed runtime commands rather than re-parsing product arguments.
|
||||
|
||||
## Documentation boundary
|
||||
|
||||
Design docs explain the constraints that code must preserve. They should not duplicate every public type, exact schema field, or command output; those are owned by code, tests, and work items.
|
||||
@@ -1,29 +0,0 @@
|
||||
# Plugin Component Model boundary
|
||||
|
||||
Dynamic Worker Plugin execution is not part of the current product. The `.yoi-plugin` component metadata retained in `manifest` is an offline package-format contract only.
|
||||
|
||||
## Current behavior
|
||||
|
||||
- Worker creation and restore install no dynamic Plugin modules.
|
||||
- Manifest/Profile input rejects `plugins` and `feature.plugins`.
|
||||
- Runtime and Server startup perform no repository, ancestor, cwd, or user-data Plugin discovery.
|
||||
- No persisted local `package_path` is execution authority.
|
||||
- Only statically compiled trusted built-in Features contribute Worker capabilities.
|
||||
- `yoi plugin check` parses an explicitly named directory or package without instantiating a component.
|
||||
|
||||
The package validator may reject legacy core-Wasm artifacts and require Component Model metadata, but passing validation does not make an artifact installable or executable.
|
||||
|
||||
## Future platform constraints
|
||||
|
||||
A future Server Plugin platform may execute Wasmtime Component Model packages only after the architecture is implemented as a coherent authority boundary:
|
||||
|
||||
1. an operator installs an immutable package into Server-owned artifact authority;
|
||||
2. a Workspace owner selects an installed package through an immutable Addon configuration snapshot bound to exact package/content digests;
|
||||
3. Backend authors a per-Worker execution plan containing exact identities, digests, configuration, and bounded grants;
|
||||
4. Runtime fetches only Server-authorized digests and verifies package bytes and execution-plan identity;
|
||||
5. Runtime instantiates a fresh bounded Wasmtime Store with no ambient WASI authority; and
|
||||
6. restore uses the persisted execution plan and exact artifact rather than current Workspace settings or a filesystem path.
|
||||
|
||||
Default components receive no filesystem, sockets, environment, clocks, randomness, subprocess, Workdir, broad Workspace client, credential, or network authority. Any host import must be narrow, typed, explicitly granted, live-revalidated where necessary, bounded, and audited.
|
||||
|
||||
This future platform must not restore `.yoi/plugins`, user-data catalogs, cwd/ancestor discovery, native dynamic libraries, downloaded Cargo manifests, or local paths as compatibility authority.
|
||||
@@ -1,31 +0,0 @@
|
||||
# Plugin packages
|
||||
|
||||
Yoi retains `.yoi-plugin` as an offline authoring and inspection format. The format is not a Worker capability source.
|
||||
|
||||
## Current product boundary
|
||||
|
||||
Normal Worker creation, restore, Profile/Manifest resolution, Server/Runtime startup, and CLI execution do not discover Plugin catalogs from:
|
||||
|
||||
- repository or ancestor `.yoi/plugins` directories;
|
||||
- user-data Plugin directories;
|
||||
- the current working directory; or
|
||||
- persisted local `package_path` values.
|
||||
|
||||
`plugins` and `feature.plugins` are rejected in Worker Manifest/Profile input. Dynamic Plugin Tools, Services, Ingress handlers, and WASM components are not installed or executed. Worker capabilities come only from statically compiled trusted built-in Features.
|
||||
|
||||
## Offline format operations
|
||||
|
||||
The CLI keeps only operations whose input or destination is explicit:
|
||||
|
||||
```sh
|
||||
yoi plugin new rust-component-tool ./example-plugin
|
||||
yoi plugin check ./example-plugin
|
||||
yoi plugin pack ./example-plugin --output ./example-plugin.yoi-plugin
|
||||
yoi plugin check ./example-plugin.yoi-plugin
|
||||
```
|
||||
|
||||
These commands parse, validate, or write the named local artifact. They do not scan a Workspace, mutate Profile/Manifest configuration, install a package, register Worker Tools, or execute Plugin code. `plugin list` and `plugin show` were removed because their catalog semantics depended on ambient repository and user-data stores.
|
||||
|
||||
## Future installation authority
|
||||
|
||||
Server-installed Plugin packages and Addons are future work. That platform must provide explicit immutable package identity and digest, Server-owned artifact delivery and Workspace selection, a Backend-authored per-Worker execution plan, Runtime verification, and sandboxed execution. It must not restore repository-local or user-data discovery as a compatibility fallback.
|
||||
@@ -1,133 +0,0 @@
|
||||
# Profiles, Manifests, and prompts
|
||||
|
||||
Profiles are reusable recipes. Resolved Manifests are runtime contracts. Prompt resources are managed assets. Keeping those layers separate prevents runtime state from leaking into reusable configuration.
|
||||
|
||||
## Profiles
|
||||
|
||||
A Profile describes how a Worker should normally be built: worker language, model/provider selectors, prompt choices, tool policy defaults, and other reusable preferences.
|
||||
|
||||
A Profile should not contain runtime-bound fields:
|
||||
|
||||
- `worker.name`
|
||||
- concrete delegated `scope.allow`
|
||||
- sockets or process identifiers
|
||||
- session pointers
|
||||
- restored spawned-child state
|
||||
- raw secret values
|
||||
|
||||
Those fields depend on one run, one parent, or one machine. Putting them in a reusable Profile makes reuse unsafe.
|
||||
|
||||
Yoi Profiles are data artifacts resolved from builtin profile definitions, `profiles.toml`, Workspace Config values, or explicit JSON/TOML artifacts. Decodal is the Workspace authoring syntax; JSON/TOML artifacts are the low-level resolver interchange format. Runtime launch does not depend on executable profile scripts.
|
||||
|
||||
### Workspace registration and migration
|
||||
|
||||
Register the Profile **value**, not a source pathname:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
profile = {
|
||||
default_profile = "project:my-companion";
|
||||
entries = [{
|
||||
selector = "project:my-companion";
|
||||
label = "My Companion";
|
||||
description = "My reusable Worker recipe";
|
||||
profile = import "./profiles/my-companion.dcdl";
|
||||
}];
|
||||
};
|
||||
} as WorkspaceConfigSchema
|
||||
```
|
||||
|
||||
The imported file can contain `{ worker = { mode = "wip"; }; model = { ref = "codex-oauth/gpt-5.6-sol"; }; feature = { task = { enabled = true; }; }; }`. That record can also be written directly at `entries[0].profile`. Composition uses ordinary Decodal expressions, for example `(import "./profiles/base.dcdl") // { worker = { mode = "wip"; }; }` for an explicit patch, or `&` for compatible refinement. No dedicated Profile file or `profiles/` directory is required. Imports are relative to the **containing virtual config file**, not a host filesystem path; missing imports and virtual-tree escapes are rejected.
|
||||
|
||||
`selector` must be a nonempty `project:*` selector, unique in the Workspace. `label` and `description` are optional display metadata. An omitted `default_profile` selects `builtin:companion`; an explicitly unknown default is an error, not a fallback. A project Profile is a partial recipe resolved against `WorkerManifestConfig::resolution_defaults()`, **not** implicitly inherited from a builtin role. Explicit values remain explicit and omitted Profile fields remain absent in the transported value. To inherit a builtin recipe, import its public value explicitly; there is no implicit builtin inheritance.
|
||||
|
||||
### Public builtin imports
|
||||
|
||||
Workspace Decodal can import embedded Profile values through the read-only `$builtin/` namespace:
|
||||
|
||||
```dcdl
|
||||
(import "$builtin/profiles/companion.dcdl") // {
|
||||
worker = { mode = "wip"; };
|
||||
feature = { task = { enabled = true; }; ticket = { enabled = false; }; };
|
||||
}
|
||||
```
|
||||
|
||||
Use this expression anywhere a Profile value is accepted, including `profile.entries[*].profile` or an imported Workspace recipe. `$builtin/profiles/companion.dcdl` is a source path, not the launch selector `builtin:companion`. Public paths under `$builtin/profiles/` are `base.dcdl`, `default.dcdl`, `standalone.dcdl`, `standalone-subjektiv.dcdl`, `standalone-subjektiv-consolidation.dcdl`, `coder.dcdl`, `companion.dcdl`, `intake.dcdl`, `reviewer.dcdl`, `orchestrator.dcdl`, `job.dcdl`, `backend-job.dcdl`, `memory-consolidation.dcdl`, and `subjektiv-memory-consolidation.dcdl`. This is an explicit embedded catalog, not arbitrary resource or filesystem access. Unknown paths, namespace escapes, host paths and network URLs are errors.
|
||||
|
||||
Relative imports **inside a builtin source** resolve against its builtin virtual directory: companion's `./base.dcdl` means `$builtin/profiles/base.dcdl`, never the Workspace's `profiles/base.dcdl`. Relative imports in Workspace files continue to use their containing Workspace virtual directory. Builtin values obey ordinary Decodal composition: `//` applies an explicit patch and `&` refines compatible values (conflicting explicit values remain errors). Importing a builtin does not add a special merge or fallback mechanism.
|
||||
|
||||
Builtin dependencies share the Workspace snapshot budgets: at most 256 entries across Workspace entries and distinct observed builtin sources, 256 KiB per source, and 4 MiB of total source bytes. Unused Workspace entries still count; repeated imports of the same builtin count once. The observed import graph must be acyclic and at most 32 edges deep, **including the synthetic evaluation wrapper's import of the entrypoint**. These guards apply before the loader returns sources to Decodal, including imports in lazy object fields; builtins do not bypass them. Launch archives retain their separate transport limits (64 sources, 256 KiB per source, 1 MiB total source bytes).
|
||||
|
||||
The former `entries = [{ selector = "project:name"; source = "profiles/name.dcdl"; }]` registration is no longer accepted. Migrate it explicitly to `profile = import "./profiles/name.dcdl";`, preserving the selector, label, description and default. For registrations in a nested config file, adjust the relative import to the original virtual-tree entry (for example `../profiles/name.dcdl`). Do not rename or recreate the Profile file merely for registration. Previously saved source-based saved content snapshots remain readable as config data and are not rewritten, deleted, or silently substituted with a builtin. Their Profile projection/launch reports `profile_source_registration_removed` with the replacement form; saves under the new schema reject the old field. Existing Workers continue using their saved Manifest while the author migrates and saves the config.
|
||||
|
||||
The schema contribution separates its materialized registration schema from an optional, fingerprint-bound `authoring_source`. The latter supplies editor field suggestions inside array elements and partial Profile records, plus a separate constraint-only analysis pass for supplied nested values (including imported recipes); that pass never materializes the shape, makes omitted fields required, or supplies defaults to the saved projection. Editor diagnostics point to the supplied value's virtual file/span. The authoring shape is never substituted for the persisted evaluation schema. Decodal structural diagnostics cover registration and nested Profile field types; the shared Backend save boundary additionally checks full Profile/Manifest semantics (including mode values, forbidden runtime fields, duplicate selectors and default references). UI and WIP saves use that same boundary; invalid candidates leave the active config content untouched.
|
||||
|
||||
`scope` and `delegation_scope` retain both supported forms: a string such as `"workspace_read"` / `"workspace_write"`, or a table such as `{ intent = "workspace_read"; deny_write = ["private"]; symlink_policy = "resolved"; }`. Decodal 0.4 has no union range, so these fields are opaque to the editor constraint pass; a non-materialized table hint supplies nested completion without rejecting the string form or rewriting it. The shared Backend save validator checks scope forms, intent values and table fields (including their types and unknown keys). Other structurally typed fields such as `worker.mode` and `feature.task.enabled` retain editor type diagnostics. Completion hints never become Profile defaults or saved values.
|
||||
|
||||
Extraction `reasoning` in both `feature.memory` and `feature.subjektiv`, like `engine.reasoning`, accepts either a string effort or a signed integer budget. These union values are also opaque to editor constraints and semantically checked on save. Compaction authoring covers the complete token settings and their supported `compact_*` aliases, plus the ratio helper's `request` / `worker` aliases; values are not renamed or defaulted during Workspace evaluation. The shared test corpus `resources/config-schema/profile-authoring-values.json` proves exact values in native/WASM analysis and effective settings through save/projection/Runtime consumption. Its canonical compaction case must cover every serialized `CompactionConfigPartial` field, making future contract drift visible in tests.
|
||||
|
||||
## Manifests
|
||||
|
||||
A resolved Manifest is the concrete contract used to create or restore a Worker. It carries defaults, resolved paths, permissions, scope, prompt references, provider/model decisions, and runtime identity.
|
||||
|
||||
Source/partial layers may omit fields. Resolved manifests should be explicit enough that Worker creation does not depend on ambient configuration later changing under it.
|
||||
|
||||
`--manifest <path>` exists as an explicit low-level escape hatch. Normal fresh startup selects a `builtin:*` or `project:*` Profile from the Backend-managed Workspace Config content snapshot rather than applying an ambient manifest cascade.
|
||||
|
||||
Project Profiles are evaluated as part of the content-addressed Virtual Config, including its imports and value composition. The Backend packages the **evaluated selected value** into a digest-bound JSON entry in the existing Profile archive transport, and binds the launch bundle to the Workspace identity, tree/projection digests and schema/toolchain fingerprints. Runtime verifies the archive and resolves that exact value without re-evaluating Decodal, scanning imports or reading Workspace files. When Workspace evaluation imports builtins, the archive additionally seals the exact observed transitive builtin closure for the Workspace contract: canonical `$builtin/profiles/...` source keys, original Decodal bytes, content types, sizes and source digests, all bound by the outer archive and launch bundle digests. These are provenance dependencies, not runtime entrypoints or runtime import instructions. Missing or corrupt provenance fails verification even though the entrypoint is JSON; no local file or current embedded builtin can rescue it. Builtin Profiles selected directly retain their embedded Decodal archive path. Changing an embedded builtin so that it changes the evaluated Workspace value requires an **explicit config save/re-evaluation before a fresh launch**. The Backend compares current evaluation with the saved projection digest and rejects a mismatch rather than silently adopting the new value. T-711 retains the existing version-2 toolchain contract/fingerprint tuple so existing stored contracts remain readable; builtin provenance is bound by the archive and bundle digests, not a new catalog fingerprint field. Neither a builtin update nor a config save rewrites a sealed archive or silently rebases an existing Worker. Saving config changes only future fresh launches; the Worker persists the resulting resolved Manifest and restore uses that saved snapshot rather than selecting the current recipe again. Files below the Workdir are not implicit Profile override layers.
|
||||
|
||||
## Local stdio MCP server declarations
|
||||
|
||||
Profiles and manifest layers may declare named local stdio MCP servers under `mcp.stdio_server`. This is a typed configuration surface only. Declaring a server does not start a subprocess, discover packages, negotiate MCP capabilities, or register tools/resources/prompts.
|
||||
|
||||
Example TOML artifact fragment:
|
||||
|
||||
```toml
|
||||
[[mcp.stdio_server]]
|
||||
name = "filesystem"
|
||||
command = "node"
|
||||
args = ["server.js", "--root", "."]
|
||||
|
||||
[mcp.stdio_server.cwd]
|
||||
kind = "path"
|
||||
path = "./mcp"
|
||||
|
||||
[mcp.stdio_server.env]
|
||||
inherit = ["PATH"]
|
||||
|
||||
[mcp.stdio_server.env.set.SAFE_MODE]
|
||||
kind = "literal"
|
||||
value = "1"
|
||||
|
||||
[mcp.stdio_server.env.set.API_TOKEN]
|
||||
kind = "secret_ref"
|
||||
ref = "providers/mcp-token"
|
||||
|
||||
[mcp.stdio_server.env.set.UPSTREAM_TOKEN]
|
||||
kind = "env_ref"
|
||||
name = "MCP_UPSTREAM_TOKEN"
|
||||
```
|
||||
|
||||
`command` is a direct executable name/path, not a shell string. `args` are passed as argv entries by future lifecycle code. `cwd.kind = "path"` is resolved relative to the Profile or manifest layer; omit `cwd` or use `{ kind = "inherit" }` when the lifecycle caller should choose. Environment handling is explicit: future spawn code should inherit only names listed in `env.inherit` and set only variables in `env.set`. `literal` values are for non-secret data; credentials should use `secret_ref` or explicit `env_ref`. Diagnostics and Debug output must redact env literal values and must not print secret plaintext.
|
||||
|
||||
Local stdio MCP servers are ordinary local executables running with the user's OS permissions. Yoi's feature flags, Plugin permissions, and MCP config validation are not an operating-system sandbox and cannot prevent filesystem/network/process side effects once a later lifecycle implementation chooses to spawn a configured server.
|
||||
|
||||
## SubWorkers
|
||||
|
||||
`SubWorkerSpawn.profile` is optional and resolves through defaults when omitted. The only concrete capability delegation in the tool call is `SubWorkerSpawn.scope`, and it must be a subset of the parent Worker's effective scope.
|
||||
|
||||
`inherit` derives reusable settings from the parent's resolved Manifest while replacing child identity and delegated scope. It should not blindly reuse the parent's original Profile source or runtime state.
|
||||
|
||||
## Prompt resources
|
||||
|
||||
Prompts live under `resources/prompts` so builtins, project overrides, and user overrides have one asset boundary.
|
||||
|
||||
The prompt layer should explain policy and behavior, but it should not smuggle volatile state into model context. Runtime facts that affect later turns must still go through history.
|
||||
|
||||
Builtin resources should be embedded at compile time. Project Profiles and prompt overlays belong to the Backend-managed content-addressed Workspace Config and travel as digest-bound source archives. User Profile registries, provider/model catalog overrides, and explicit low-level Manifests remain filesystem-based where those local resolution paths are used.
|
||||
|
||||
## Why this separation matters
|
||||
|
||||
Without this split, configuration becomes unreproducible: a Profile might accidentally depend on a parent Worker's socket, a prompt override might act like hidden state, or a restored Worker might observe different defaults than the run that created it.
|
||||
|
||||
The boundaries make it clear which information is reusable authoring, which is resolved runtime contract, and which is durable run history.
|
||||
@@ -1,45 +0,0 @@
|
||||
# Provider and model boundary
|
||||
|
||||
The Engine should be provider-independent. Provider-specific wire formats, auth mechanisms, model catalogs, reasoning knobs, and terminal error shapes belong below or beside it, not inside ordinary turn orchestration.
|
||||
|
||||
## Engine responsibility
|
||||
|
||||
`agen` owns turn lifecycle:
|
||||
|
||||
- committed history append
|
||||
- tool loops
|
||||
- retry before stream start
|
||||
- continuation after safe partial output
|
||||
- pruning/compaction decisions
|
||||
- provider-neutral callbacks and events
|
||||
|
||||
It should not know where a Codex OAuth token lives, how a provider encodes reasoning effort, or which environment variables a provider once supported.
|
||||
|
||||
## Provider responsibility
|
||||
|
||||
Provider/model layers own:
|
||||
|
||||
- resolving model aliases and builtin model metadata
|
||||
- provider-specific request/response conversion
|
||||
- auth integration
|
||||
- context/window metadata
|
||||
- terminal error classification
|
||||
- trace points around HTTP/auth/body/SSE lifecycle
|
||||
|
||||
External API facts change quickly. Repository docs should record Yoi's boundary decisions, not snapshots of vendor documentation.
|
||||
|
||||
## Secrets
|
||||
|
||||
Local credentials use explicit secret references. Secret values must not appear in diagnostics, Debug output, CLI/TUI output, work items, docs, session logs, model context, or persisted plaintext store files.
|
||||
|
||||
Codex OAuth remains a separate integration because its existing `auth.json` / `CODEX_HOME` shape is not the same as normal provider secret refs.
|
||||
|
||||
## Error and trace semantics
|
||||
|
||||
Provider terminal errors that are displayed live, such as context-length failures, must persist as errored runs rather than successful empty turns.
|
||||
|
||||
Event trace sidecars are optional parsed lifecycle traces. They are not complete raw SSE logs and should not become a hidden source of model context.
|
||||
|
||||
## Why this boundary exists
|
||||
|
||||
Provider APIs drift. If provider details leak into Engine logic, every new model behavior risks changing core orchestration. Keeping the boundary explicit lets Yoi adapt provider integrations while preserving stable turn semantics.
|
||||
@@ -1,138 +0,0 @@
|
||||
# Server-managed Feature storage
|
||||
|
||||
`yoi-workspace-server::feature_storage` provides the SQLite lifecycle boundary for trusted Server-side Features. It is not a Worker feature API and it is not a model-visible SQL tool. Subject-scoped Memory is persisted in the Workspace's `subjektiv` Feature database through this boundary; legacy single-document Workspace Memory remains in the control-plane database and is not owned, imported, or reset by `FeatureStorage`.
|
||||
|
||||
## Ownership boundary
|
||||
|
||||
The Server owns:
|
||||
|
||||
- the physical layout under `feature-storage/<workspace-id>/features/<feature-id>.sqlite`;
|
||||
- validated Workspace and Feature identifiers (no caller-provided database path);
|
||||
- connection reuse, `foreign_keys`, WAL, `busy_timeout`, and write serialization;
|
||||
- migration timing/history, shutdown admission, online backup/restore, and Workspace deletion fencing.
|
||||
|
||||
A Feature owns:
|
||||
|
||||
- its tables, migration bodies, queries, and domain transactions;
|
||||
- validation and typed repository/API methods exposed to its Server handler;
|
||||
- authorization before the repository is called.
|
||||
|
||||
A Feature database is an API/ownership isolation boundary inside the trusted Server process. It does not sandbox arbitrary native code and does not replace Workspace authorization. A `FeatureDatabase`, SQLite path, or general SQL endpoint must never be sent to a Worker/Runtime or added to a model tool contract.
|
||||
|
||||
## Registration and repository example
|
||||
|
||||
Registration is trusted startup code. Merely constructing or registering storage is lazy and creates no directory or database; the first `open` applies migrations. The simplified example below demonstrates storage ownership and transaction atomicity, not the production subjektiv schema or Memory-change API; those contracts are described in [subjektiv-store.md](subjektiv-store.md).
|
||||
|
||||
```rust
|
||||
use rusqlite::{Transaction, params};
|
||||
use yoi_workspace_server::{
|
||||
FeatureDatabase, FeatureMigration, FeatureRegistration, FeatureStorageError,
|
||||
WorkspaceApi,
|
||||
};
|
||||
|
||||
type StorageResult<T> =
|
||||
std::result::Result<T, FeatureStorageError>;
|
||||
|
||||
fn create_subject_memory(tx: &Transaction<'_>) -> StorageResult<()> {
|
||||
tx.execute_batch(
|
||||
"CREATE TABLE subjects (
|
||||
subject_id TEXT PRIMARY KEY,
|
||||
state TEXT NOT NULL CHECK (state IN ('active', 'retired'))
|
||||
);
|
||||
CREATE TABLE memories (
|
||||
subject_id TEXT NOT NULL,
|
||||
memory_id TEXT NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
PRIMARY KEY (subject_id, memory_id),
|
||||
FOREIGN KEY (subject_id) REFERENCES subjects(subject_id)
|
||||
);
|
||||
CREATE TABLE interventions (
|
||||
intervention_id TEXT PRIMARY KEY,
|
||||
subject_id TEXT NOT NULL,
|
||||
detail TEXT NOT NULL,
|
||||
FOREIGN KEY (subject_id) REFERENCES subjects(subject_id)
|
||||
);"
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
static MIGRATIONS: &[FeatureMigration] = &[
|
||||
FeatureMigration::new(1, "subject memory baseline", create_subject_memory),
|
||||
];
|
||||
|
||||
const REGISTRATION: FeatureRegistration =
|
||||
FeatureRegistration::new("subjektiv", MIGRATIONS);
|
||||
|
||||
struct SubjektivRepository {
|
||||
database: FeatureDatabase,
|
||||
}
|
||||
|
||||
impl SubjektivRepository {
|
||||
fn apply_candidate(
|
||||
&self,
|
||||
subject_id: &str,
|
||||
memory_id: &str,
|
||||
body: &str,
|
||||
intervention_id: &str,
|
||||
) -> StorageResult<()> {
|
||||
// Candidate content and intervention recording are
|
||||
// one atomic transaction in the subjektiv Feature database.
|
||||
self.database.transaction(|tx| {
|
||||
tx.execute(
|
||||
"INSERT INTO memories (subject_id, memory_id, body)
|
||||
VALUES (?1, ?2, ?3)",
|
||||
params![subject_id, memory_id, body],
|
||||
)?;
|
||||
tx.execute(
|
||||
"INSERT INTO interventions (intervention_id, subject_id, detail)
|
||||
VALUES (?1, ?2, 'candidate applied')",
|
||||
params![intervention_id, subject_id],
|
||||
)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_subjektiv_repository(api: &WorkspaceApi) -> StorageResult<SubjektivRepository> {
|
||||
let registered = api.feature_storage().register(REGISTRATION)?;
|
||||
let database = api.feature_storage().open(®istered)?;
|
||||
Ok(SubjektivRepository { database })
|
||||
}
|
||||
```
|
||||
|
||||
A production Feature should register once while constructing its Server service and retain the returned `RegisteredFeature`/typed repository. Duplicate registration is rejected. `FeatureDatabase` methods are synchronous because `rusqlite` is synchronous; async HTTP/background handlers must run nontrivial repository work on a blocking executor rather than blocking a Tokio core thread.
|
||||
|
||||
## Migration and transaction contract
|
||||
|
||||
- Versions are contiguous from 1. Migration schema changes and the corresponding history rows are committed in one `BEGIN IMMEDIATE` transaction.
|
||||
- Concurrent opens in one manager are single-flight. Independent Server managers/processes serialize migration writers through SQLite and reread history after acquiring the transaction.
|
||||
- A migration callback error rolls back the complete migration batch. A later open retries it; failure is never recorded as success.
|
||||
- Stored migration names are verified. A history mismatch is rejected, and a schema newer than the registered plan is never overwritten or downgraded.
|
||||
- `FeatureDatabase::transaction` covers multiple tables in one Feature database. No atomic transaction is promised with the Server control-plane database or another Feature database.
|
||||
- A domain rejection can return `FeatureStorageError::operation(...)`; the transaction then rolls back.
|
||||
|
||||
## Lifecycle and operations
|
||||
|
||||
### Disable and restart
|
||||
|
||||
Not registering/opening a disabled Feature creates nothing. Disabling a previously used Feature does not delete its database. On restart, registering and opening the same Feature/Workspace reuses the persisted file and verifies/applies its migrations.
|
||||
|
||||
### Backup and restore
|
||||
|
||||
`WorkspaceFeatureStorage::backup` uses SQLite's online backup API for each discovered Feature database, so a live WAL is not ignored or copied as a bare database file. Each image passes `integrity_check` and `foreign_key_check`, and a manifest binds the snapshot to its Workspace and Feature identifiers. The snapshot directory is published with an atomic rename only after all images succeed.
|
||||
|
||||
Each Feature image is internally consistent. There is deliberately no cross-database snapshot instant or distributed transaction between Feature databases or the Server DB. A caller that needs a higher-level coordinated backup must first stop relevant Feature writers. In particular, the [subjektiv product cutover](../development/subjektiv-product-cutover.md) stops legacy and subjektiv activity before pairing a control-plane backup with the subjektiv Feature backup; the explicit legacy reset never deletes or recreates the Feature database.
|
||||
|
||||
`restore` rejects the wrong Workspace manifest, corrupt/foreign-key-invalid images, an existing destination, or any live connection. It builds validated databases in a temporary directory and atomically publishes the Workspace storage directory. Restore is an operator/Server lifecycle operation, not a model API.
|
||||
|
||||
### Workspace deletion
|
||||
|
||||
Workspace deletion calls `WorkspaceFeatureStorage::delete` after Worker/Workdir cleanup and before finalizing removal from the Server DB. It fences new opens, waits for connection-locked operations, closes handles, and removes only the validated Workspace subtree. The admission fence remains until the cached Workspace API is discarded, preventing a background task from recreating storage between filesystem deletion and control-plane finalization. Retrying deletion is idempotent.
|
||||
|
||||
### Shutdown
|
||||
|
||||
After HTTP graceful drain, Server serve paths explicitly call `shutdown`. Shutdown rejects new registration/open, waits for in-progress connection operations to release their locks, closes all managed connections, and makes retained handles fail with `DatabaseClosed`. Operations must not treat a shutdown error as success.
|
||||
|
||||
## Failure handling
|
||||
|
||||
Identifier, registration, migration-history, unsupported-schema, maintenance, shutdown, snapshot, I/O, and SQLite failures are typed as `FeatureStorageError`. Server handlers should map them to a bounded domain/API error without exposing paths, SQL, or unrestricted database access. Migration/restore/delete failures must stop the corresponding lifecycle transition; they are not warnings to ignore.
|
||||
@@ -1,47 +0,0 @@
|
||||
# Session capture and Worker observation
|
||||
|
||||
Yoi uses one session-entry exploration domain for host-provided snapshots, Memory extraction evidence, and authorized observation of active Workers. The domain is implemented in `worker::session_capture` and has no Workspace or Memory mutation authority.
|
||||
|
||||
## Common capture contract
|
||||
|
||||
A host constructs an immutable ordered `SessionCapture` from committed session items. The capture:
|
||||
|
||||
- excludes reasoning before overview, search, read, or evidence projection;
|
||||
- does not include the session system-prompt field;
|
||||
- assigns an append-stable `SessionEntryRef` (`E...`) from the committed source position;
|
||||
- uses the same reference in sparse overview anchors, search results, bounded reads, and Memory evidence conversion;
|
||||
- pages sparse real user/assistant anchors and reports the number of non-reasoning entries between anchors;
|
||||
- supports bounded range search and compact range listing when no filter is supplied;
|
||||
- bounds read item count and bytes.
|
||||
|
||||
`SessionEntryRef` is local to the selected session subject. Runtime peers use the canonical `{ kind: "runtime_worker", runtime_id, worker_id }` reference; parent-owned children use `{ kind: "sub_worker", name }`. A model must first select a host-projected session and must reuse both the structured subject and entry references returned for that session.
|
||||
|
||||
## Independent features
|
||||
|
||||
The three feature modules share only the capture domain:
|
||||
|
||||
- `session-explore` installs `ShowOverview`, `SearchEntries`, and `ReadEntry` for one immutable host snapshot. It has no Workspace client or Memory state.
|
||||
- `memory-extract` installs `StageMemoryCandidate` and `FinishMemoryExtraction`. It validates every staged `entry_ref` against its co-installed capture before converting it to typed Memory evidence.
|
||||
- `worker-observation` installs `ListWorkerSessions`, `ViewSessionOverview`, `SearchSessionEntries`, and `ReadSessionEntry`. It captures the selected Worker again on every operation, so newly committed entries become visible while existing append-only references remain stable.
|
||||
|
||||
The features do not enable or mutate each other. Feature-registry collision checks remain authoritative for tool names.
|
||||
|
||||
## Observation authority
|
||||
|
||||
`WorkerObservationProvider` is a host-injected authority boundary. The Worker receives only an `Arc<dyn WorkerObservationProvider>`; model input never supplies grants, Workspace credentials, Runtime URLs, session handles, repository paths, or provider clients.
|
||||
|
||||
The provider must:
|
||||
|
||||
1. list only active subjects already granted to the current Worker;
|
||||
2. reauthorize every capture instead of trusting a previous list result;
|
||||
3. return the same not-found result for missing and unauthorized subjects;
|
||||
4. return only committed session items;
|
||||
5. keep subject identifiers opaque and bounded.
|
||||
|
||||
Runtime/Backend integrations enable the feature through the Backend-only `WorkerSpawnRequest.resolved_worker_observation_enabled` field, forwarded as `CreateWorkerRequest.worker_observation_enabled`. Canonical same-Runtime peers may also be supplied through `resolved_worker_observation_grants`; Runtime revalidates those against live weak handles and Workspace scope. For cross-Runtime peers and dynamically added Workers, `WorkspaceClientWorkerObservationProvider` calls the Workspace-scoped Server projection on every list/capture. Server authorizes that route against the current Workspace Orchestrator identity, recomputes the active Workspace Worker set, and reads the selected Worker’s committed protocol snapshot. Runtime binds these providers through `Worker::bind_worker_observation_provider` on spawn and restore. Parent-owned SubWorkers use the same provider contract through `SpawnedSubWorkerObservationProvider`; their subjects use the tagged `sub_worker` variant.
|
||||
|
||||
Observation is read-only evidence access. It does not authorize Ticket, Memory, Worker, or Workdir mutations and is not completion or approval authority.
|
||||
|
||||
## SubWorker output
|
||||
|
||||
SubWorkers no longer expose a separate output cursor or lifecycle tool family. `WorkerList`, `WorkerSendInput`, and `WorkerStop` retain parent-owned lifecycle control through a `{ kind: "sub_worker", name }` subject, while committed child output is read through `worker-observation`. Turn-completion notifications carry no transcript and only tell the parent to inspect the authoritative committed session at a natural boundary.
|
||||
@@ -1,48 +0,0 @@
|
||||
# Standalone Agent Host
|
||||
|
||||
`crates/standalone` は、既存の Yoi Worker を同一プロセス内で起動する最小の host 境界である。
|
||||
新しい Agent 実行系ではなく、`manifest`、`worker`、`session-store`、`workdir` の既存契約を固定構成で組み立てる。
|
||||
|
||||
## 依存方向
|
||||
|
||||
```text
|
||||
standalone
|
||||
├─ manifest
|
||||
├─ worker
|
||||
├─ session-store
|
||||
└─ protocol
|
||||
|
||||
worker
|
||||
└─ WorkerBootstrap / start_worker_controller
|
||||
```
|
||||
|
||||
`standalone` は `tui`、`worker-runtime`、`yoi-workspace-server` に依存しない。
|
||||
`worker` の direct entrypoint、`worker-runtime` の fresh/restore factory、`standalone` は同じ controller lifecycle を使う。fresh runtime/standalone construction は `WorkerBootstrap` を共有し、Runtime は `prepare()` 後かつ Feature install/controller exposure 前に Workdir、observation、Flow の live binding を追加する。restore は replay 済み Worker を `PreparedWorker` へ渡して同じ pre-exposure lifecycle を通す。
|
||||
|
||||
## authority と lifecycle
|
||||
|
||||
- launch は `ProfileExecutionTarget::Standalone` で built-in/XDG Profile を resolve する。repository-local Profile と path selector は authority にしない。
|
||||
- canonical cwd を `WorkerFilesystemAuthority::local` の root/cwd とし、host ごとに top-level Worker と process-owned `WorkdirSession` を一つ作る。
|
||||
- Controller transport は `InProcess` に固定する。通常起動で Worker subprocess、HTTP/WS server、Unix socket を作らない。
|
||||
- model provider は通常の resolved Manifest から構築する。埋め込み host と deterministic test は `start_with_model_client` で同じ bootstrap に process-owned client を注入できる。
|
||||
- feature plan/install は既存 `WorkerController` が行う。Task や optional direct SubWorker を standalone 側で再実装しない。
|
||||
- `shutdown()` は Host-owned Job service の scheduling を閉じ、Internal Worker の取消・cleanup とローカル SQLite connection の close を確認してから、既存 `Method::Shutdown` を送る。controller が active run、SubWorker registry、Workdir session、MachineScope allocation を順に片付けた後の confirmation を待つ。cleanup を確認できない drop/error では lease を保持する。
|
||||
- `jobs()` は Feature へ明示的に渡す汎用 Job 実行 capability。対話 Session・モデル作成 SubWorker とは別で、Profile/result authority と永続化の契約は [Standalone Jobs](standalone-jobs.md) に記録する。
|
||||
- startup error は category のみを公開し、credential、prompt 本文、session metadata、内部 path を error text に含めない。
|
||||
|
||||
## CLI / TUI routing
|
||||
|
||||
- `yoi` の connection-aware command は `TargetKind::Standalone | Backend` の二択で dispatch する。`--local` は Standalone を、明示的な CLI selector、または `$XDG_CONFIG_HOME/yoi/client.toml` の `default_connection = "backend"` と `default_backend` は Backend を選ぶ。repository-local `.yoi/workspace.toml` / `.yoi/client.config.toml` は connection や Workspace selection の authority ではない。
|
||||
- `yoi init --display-name <NAME> --repository-key <KEY>` は現在の Git repository root を Server DB に登録し、`$XDG_CONFIG_HOME/yoi/client.toml` に Backend routing のみを保存する。repository-local `.yoi/workspace.toml` は作成しない。
|
||||
- Backend workflow で Workspace selector が省略された場合、client は現在の Git repository の canonical local source または `origin` URI を Server DB の Repository catalog と照合する。exactly one の Workspace だけを選択し、zero/multiple match や Backend offline は明示的に失敗して repository-local file へ fallback しない。
|
||||
- Standalone の通常起動は `StandaloneHost`、restore は専用 `StandaloneStore` の session picker を使う。Workspace Worker list、PID、Unix socket、subprocess は探索しない。
|
||||
- `workers`、Backend Worker restore、Workspace panel、Ticket、Objective は Backend authority を要求する。Standalone から repository-local filesystem backend へ fallback しない。
|
||||
- `yoi worker` は Runtime や明示的な process-owned integration が使う direct Worker entrypoint として残るが、通常の `yoi` / TUI 起動経路からは呼び出さない。
|
||||
|
||||
## 非目標
|
||||
|
||||
- TUI/CLI routing や画面実装
|
||||
- Runtime / Workspace Server / Orchestrator / Ticket authority の内包
|
||||
- standalone 独自の HTTP/WS API
|
||||
- Worker/Tool/Task/SubWorker protocol の fork
|
||||
- subprocess Worker launcher や runtime-owned Worker catalog
|
||||
@@ -1,152 +0,0 @@
|
||||
# Host-owned standalone Jobs (T-708)
|
||||
|
||||
## Implementation design recorded before code changes
|
||||
|
||||
Base: `3c424fd17d1a5e8bb5542de2d0ad88f55338e0ea` (current origin/develop; T-704 integrated). T-709's subjektiv consumer is not part of this change.
|
||||
|
||||
The reusable `job` crate owns the transport-neutral immutable request (registry Profile selector, input snapshot/reference and SHA-256 digest, caller Job ID, instruction, serialization key and bounded limits), result envelope/digest and validation. Backend retains its existing SQLite state machine, Runtime binding, domain grants, authorization, unknown-outcome and cleanup implementation; its adapter uses the shared request validation and result contract, not a second runner.
|
||||
|
||||
Standalone owns a separate adapter, not a WorkspaceClient impersonation. `StandaloneHost` owns a Job service and its connection lifecycle. The concrete local placement is `<standalone state directory>/<WorkerId>/jobs.sqlite3` (using the existing Worker directory layout; alongside, not inside, interactive sessions). The `standalone` crate owns the versioned local schema: durable intents, immutable attempts, execution state, accepted result/digest, cancellation and consumer acknowledgement. A single Host connection is protected by the existing Worker lease. SQLite transactions serialize reserve, dispatch, result acceptance and terminal transitions. No subjektiv tables or fake Workspace/Runtime registry are created.
|
||||
|
||||
Local adapter states follow the existing intent/attempt/result distinctions. Exact immutable request replay returns the same Job, never starts another attempt. Explicit retry creates a bounded new attempt only for known failure; unknown outcomes require caller reconciliation and never auto-replay. Unstarted pending attempts remain pending on shutdown/reopen. Interrupted dispatch/running attempts without an accepted result become unknown; accepted results survive restart and remain available until consumer acknowledgement. Cancellation is durable and never auto-resumed. Consumer acknowledgement/delivery failure is separate from model success and never reruns the model.
|
||||
|
||||
Execution uses the existing private Internal Worker substrate with a new Host-facing bounded Job entry point. It gets a distinct ephemeral session, explicitly resolved selected Profile/system prompt/model, no ambient parent tools, filesystem, Workspace or Feature connections, and only a Job/attempt/input-digest-bound structured result submission capability. Profiles requiring unavailable tools/features are explicitly rejected rather than silently stripped. Result authority is independent of Profile name; final prose, Idle and Worker termination are never success evidence. This first generic adapter grants no domain operations; future consumers must provide explicit domain capabilities rather than gaining them through a profile tag.
|
||||
|
||||
The Host limits concurrent active resources independently of caller limits, enforces timeout/turn/output limits, routes cancellation into the Internal Worker lifecycle and waits for resource cleanup. Accepted results are committed before resource release; required domain postprocessing must finish before submission (or be independently durable in its consumer). No daemon, pool, subprocess, HTTP/WS listener or Server dependency is introduced.
|
||||
|
||||
Validation will distinguish scripted in-process Internal Worker executions and SQLite reopen/recovery fixtures from any real-provider/process tests. It will cover alternative Profiles, immutable retry, invalid/stale results, resource limits, failure/cancellation/timeout, shutdown and restart. Backend focused regression tests must still prove its existing fences and cleanup.
|
||||
|
||||
## API and ownership
|
||||
|
||||
`job::{JobRequest, JobLimits, JobResultSubmission, JobOutcome}` are the common
|
||||
Feature-facing data contract. `JobOutcome` distinguishes intent state from attempt
|
||||
state and omits Workspace/Runtime identities. These enums are read vocabulary,
|
||||
not another transition engine. Backend converts neutral requests with
|
||||
`BackendJobRequest::from_job_request` and projects reservation/acceptance with
|
||||
`.outcome()`; Backend domain grants, provenance, delivery and cleanup remain on
|
||||
its own adapter and retain their existing durable/wire format and fingerprints.
|
||||
|
||||
Standalone consumers receive `StandaloneHost::jobs()` explicitly, not a fake
|
||||
Workspace client and not a tool on the interactive Worker. They can persist
|
||||
`request`, read `get`/`pending`, explicitly `start` or `retry`, `cancel`, and
|
||||
`acknowledge`. `.wait()` observes execution cleanup; `.outcome()` on the returned
|
||||
snapshot is the common read view. `Completed` with its accepted structured result
|
||||
is success authority; merely returning from `wait` is not. The injected-client
|
||||
entry point resolves/validates the same selected Profile and model configuration;
|
||||
it replaces only the model transport for embedders/tests.
|
||||
|
||||
A typical generic consumer supplies its own stable Job ID/reference and immutable input,
|
||||
immutable JSON input, instruction and registry Profile:
|
||||
|
||||
```rust,ignore
|
||||
let jobs = host.jobs();
|
||||
let snapshot = jobs.request(request)?;
|
||||
if snapshot.state == standalone::jobs::JobState::Pending {
|
||||
jobs.start(&snapshot.request.job_id).await?;
|
||||
}
|
||||
let outcome = jobs.wait(&snapshot.request.job_id).await?.outcome();
|
||||
// Validate/apply the consumer's domain result here. A failed notification or
|
||||
// delivery leaves the successful result unacknowledged; do not retry the model.
|
||||
if outcome.state == job::JobState::Completed {
|
||||
deliver_idempotently(&outcome)?;
|
||||
jobs.acknowledge(&outcome.job_id)?;
|
||||
}
|
||||
```
|
||||
|
||||
The Host retains tasks/cancellation even while a consumer waits; dropping a wait
|
||||
future does not detach cancellation ownership or release a concurrency slot.
|
||||
Active resources are bounded to eight by the Host and by the minimum of all live
|
||||
requests' ceilings, including after result acceptance and before cleanup.
|
||||
Serialization keys likewise fence live resources, not just pending result state.
|
||||
Busy `start` returns a typed capacity error and leaves the reserved attempt
|
||||
unchanged; callers decide when to start/resume pending work. There is no pool,
|
||||
background daemon, implicit retry or independent Feature-owned Internal Worker.
|
||||
|
||||
## Profile and capability policy
|
||||
|
||||
The caller must select an existing built-in or user registry Profile. Path/raw
|
||||
selectors and `inherit` are not accepted. Registry existence, selected instruction
|
||||
and model configuration are checked before dispatch. The ordinary user settings
|
||||
registry and Prompt catalog are used; repository-local recipes are not discovered
|
||||
by the standalone Host, and default never silently maps to a different Profile.
|
||||
|
||||
`builtin:job` is an **optional**, compatible result-only policy. It is not a
|
||||
runner-selected default or authority token. User registry Profiles with different
|
||||
instructions/models/bounds execute the same way, as demonstrated by the scripted
|
||||
Worker tests. This adapter deliberately rejects active unsupported Feature,
|
||||
filesystem/delegation scope, MCP, skills, compaction and durable event-trace
|
||||
requirements. Existing `builtin:backend-job` has such requirements and is rejected
|
||||
by this standalone adapter, while its Backend behavior is unchanged. A future
|
||||
consumer requiring domain operations must arrange explicit Host capabilities;
|
||||
choosing `builtin:subjektiv-memory-consolidation` alone grants nothing.
|
||||
|
||||
Every run uses the existing private Internal Worker substrate, a distinct identity
|
||||
and ephemeral Session. Only `SubmitJobResult` is installed by a FeatureModule.
|
||||
Its model input is `{result: ...}`; Job/attempt/input-digest bindings come from
|
||||
the Host capability, not model arguments. The sink checks those bindings, the
|
||||
current persisted attempt, deadline, byte limit and result digest. Exact result
|
||||
replay is idempotent; changed/foreign/stale results are rejected. Submission is
|
||||
sealed before acceptance, so uncertain responses permit only identical retries.
|
||||
An ambiguous sink failure is reconciled against SQLite; absent accepted evidence
|
||||
it is `Unknown`, not a known retryable model failure. No domain logic is embedded
|
||||
in this sink. Required consumer/domain postprocessing must complete before
|
||||
submission or have its own durable ownership outside the ephemeral Worker.
|
||||
|
||||
Selected instructions are retained. The Host execution contract is rendered from
|
||||
`resources/prompts/internal/job_system.md` before the run's initial history is
|
||||
created, not injected into a previous/transient turn. Turn/output policy is
|
||||
attenuated to at most 32 turns and 8192 output tokens per model request, preserving
|
||||
tighter selected Profile bounds. Structured results retain the shared 16 KiB
|
||||
default / 64 KiB absolute byte limits, not a larger standalone exception.
|
||||
|
||||
## Persistence and termination
|
||||
|
||||
Local schema version **1** is owned only by `standalone::job_store`: `job_intents`
|
||||
contains immutable serialized intent/fingerprint, serialization key, current
|
||||
attempt and acknowledgement; `job_attempts` contains immutable identity/input digest,
|
||||
execution state, failure and accepted result/digest. Foreign keys, immutable
|
||||
identity/result triggers, transactions, `WAL` and `synchronous=FULL` protect the
|
||||
boundary. No Server schema or subjektiv/Memory schema is copied or opened.
|
||||
The Host opens and recovers the store only under its existing top-level Worker
|
||||
lease; clones cannot open or recover it themselves.
|
||||
|
||||
- **Exact request replay:** same immutable intent returns its current snapshot.
|
||||
Changing any intent field under the same ID is a conflict, not a new attempt.
|
||||
- **Known failure:** only explicit `retry` creates a new immutable attempt, up to
|
||||
the intent's bounded maximum. Prior attempts/results remain in SQLite.
|
||||
- **Timeout/shutdown before acceptance:** fence `Unknown` before requesting Engine
|
||||
cancellation; late result submissions cannot create success. Await normal
|
||||
cleanup; retain the Host lease and report an error if confirmation is lost.
|
||||
- **Normal Host shutdown:** seal scheduling/mutations, cancel active runs, await
|
||||
resource cleanup, close SQLite, then stop the interactive controller and release
|
||||
its lease. Reserved/unstarted requests remain resumable. Old cloned handles
|
||||
return `Closed`, rather than mutating a store reopened by another Host.
|
||||
- **Unexpected Host drop:** request cancellation and retain the lease because
|
||||
synchronous drop cannot attest cleanup. No daemon continues after process exit.
|
||||
- **Process loss/reopen:** reserved attempts stay pending; unaccepted dispatched
|
||||
attempts become unknown; accepted results/acknowledgements remain intact.
|
||||
SQLite result evidence, never final prose or an ephemeral Session, decides success.
|
||||
- **Cancellation:** durable and never automatically resumed. Cancelling already
|
||||
unknown intent does not erase its attempt's uncertain execution evidence or
|
||||
release its serialization fence. `retry` still refuses it. There is no generic
|
||||
operation that declares uncertain domain side effects unexecuted; callers must
|
||||
reconcile domain evidence rather than bypassing the fence with an implicit replay.
|
||||
- **After acceptance:** model error, cancellation or consumer delivery failure
|
||||
cannot erase success or rerun the model. Acknowledgement is independently durable
|
||||
and idempotent. Execution resources are reclaimed independently of result lifetime.
|
||||
|
||||
Emergency cancellation has a bounded grace (one second for cancellation setup,
|
||||
two seconds for normal cleanup). Expiry is **not** cleanup confirmation: it returns
|
||||
an error, keeps unknown evidence/accepted results, and the Host retains its lease.
|
||||
The result-only Feature owns no async/background tasks or domain connections;
|
||||
the dropped engine stream/tool pumps request abort. Synchronous SQLite submission
|
||||
is bounded by its five-second busy timeout and cannot be preempted mid-transaction.
|
||||
No resource or side effect is claimed rolled back merely because a future dropped.
|
||||
|
||||
## Validation evidence
|
||||
|
||||
See [T-708 validation ledger](../report/2026-10-06-t708-validation.md). It separates
|
||||
scripted in-process Worker/Host runs, direct SQLite/reopen/rollback fixtures and
|
||||
an actual test-owned child-process loss/Host restore test from live-provider or
|
||||
live dogfood validation. T-709's subjektiv consumer is intentionally not migrated
|
||||
here; Backend T-704 Profile selection and T-690 cleanup remain regression-tested.
|
||||
@@ -1,43 +0,0 @@
|
||||
# Standalone subjektiv (T-709)
|
||||
|
||||
## Implementation design, recorded before implementation
|
||||
|
||||
Base: `13055d7ee18f9d29edc641d0bd0e57c3d034dfa0`, including T-704 and T-708. No Backend database, fake Workspace, local fallback, new daemon, or subjektiv-specific Internal Worker runner is introduced.
|
||||
|
||||
The operator-managed standalone state directory (existing CLI/XDG standalone state setting, not cwd or a repository file) is the local sharing boundary. `<state_dir>/subjektiv/` is a private directory. A persisted random storage-scope identity is issued here; Subject IDs use the common domain UUID issuer, never cwd/Profile/Worker name. The common Feature SQLite manager opens the subjektiv database under this root; it owns connection options, migration execution, close/reopen and transaction locking. The common subjektiv crate owns the existing schema/migrations/domain/CAS/receipt/surface rules. Existing Backend layout and physical schema remain unchanged. Job domain state is not embedded into Memory tables or Session text.
|
||||
|
||||
Subjects are explicitly created/listed in that state scope and selected at normal launch; enabled policy alone does not create or attach one. Each selected Subject has a separate OS advisory lock file under `<state_dir>/subjektiv/leases/`. The Host holds the exclusive lock for body execution and all delegated Jobs until confirmed shutdown. A second process cannot acquire the same lock. Process death releases the kernel lock, not a PID/Profile guess; uncertain in-process cleanup retains ownership. A separate Subject can run concurrently, with SQLite serializing common database transactions. Symlinks and non-private state nodes must be rejected at this boundary. Knowledge of an ID/path is not a model capability.
|
||||
|
||||
The selected Subject and local storage-scope identity are persisted in the standalone Worker record. Restore verifies them and reacquires the original Subject lock; resume never silently changes Subjects. Subject-free and disabled-policy launches retain the old path with no subjektiv persistence side effects. Ordinary SubWorkers/Reviewers receive no independent Subject/extraction connection.
|
||||
|
||||
The Feature receives an explicit Host-scoped subjektiv capability, separate from WorkspaceClient and Profile policy. Backend adapts its existing authenticated API operations; standalone binds one local store, Subject, Worker and committed Session authority. Session attribution is established by the Host, including Sessions without candidates. Reads use the existing committed public Session projection and standalone Session store, not a copy in the Memory database. No system prompt, hidden reasoning, uncommitted history or arbitrary filesystem/foreign-Subject access is granted.
|
||||
|
||||
Consolidation uses the existing generic standalone Jobs service and common request/result contracts. For a selected Subject the Host owns `<state_dir>/subjektiv/jobs/<SubjectId>/jobs.sqlite3` under that Subject lease, allowing durable intents/unknown outcomes/results to follow it across Workers. Subject-free Hosts retain the T-708 Worker-local location. Subjektiv selects the consolidation Profile. Job capabilities are explicitly attenuated to the immutable candidate batch, recall/decision/surface operations and public Session references; they never confer the Subject body's execution lease or ambient parent filesystem/Workspace tools. Common receipt/CAS rules handle partial apply and response loss; unknown execution is never automatically replayed. Surface failure leaves confirmed Memory intact. The generic runner owns cancellation, timeout, result binding, resource cleanup and shutdown; processing stops with the standalone process and pending durable work is recovered only on a subsequent explicit Host connection.
|
||||
|
||||
Validation must distinguish scripted model/Host and real test-owned process/lock runs from live-provider model quality. Root `cargo check`, changed-crate tests and narrow Backend/Feature/Job regression tests are required, plus format/diff checks. Concrete CLI/UI usage and recovery are documented in [standalone.md](../standalone.md).
|
||||
|
||||
## Implemented boundaries and validation
|
||||
|
||||
The validation counts and former API terminology below record T-709 at the cited source, not new executions. Current Memory changes and Job input-digest bindings follow [the store contract](subjektiv-store.md) and [the Job contract](standalone-jobs.md).
|
||||
|
||||
- `feature-storage` owns the shared SQLite connection policy/Feature manager; `subjektiv` owns the unchanged domain schema/migrations, Host-neutral API dispatch, and T-704 consolidation request/result/surface validation. Server modules remain thin adapters; Worker/standalone do not depend on Workspace Server.
|
||||
- `worker::subjektiv` exposes a nonserializable live Host capability plus a separate attenuated consolidation capability. Backend creation still requires the actual authenticated client and bound snapshot. Local creation is explicit Host injection; unavailable Backend never falls back locally.
|
||||
- The local catalog keeps private scope/language settings and common Feature storage under `subjektiv/`, separately from generic Subject Job ledgers. The common attribution table's existing `runtime_id` lookup key is the local storage namespace for local rows, not a running Runtime identity; public local evidence leaves `runtime_id`, Workspace, and Account fields absent. Worker IDs, Subject binding, and operator-managed scope must all match before Session content is read.
|
||||
- The shared strict FsStore public index is also used for committed evidence admission. Stable references are intersected with visible committed entries; numeric hints accept the two existing capture producers. Open-Run evidence is deferred only before a request, never exposed by Session tools or recorded before commit. Persisted local human provenance is distinct from Backend Account provenance and maps to the existing public `HumanInput` class.
|
||||
- Standalone integration tests use real Hosts/SQLite, scripted extraction/consolidation/surface models, and test-owned second processes. Nine cases cover inert/disabled policy, reopen, saved-binding restore, cross-Worker recall/source discovery, exact explicit commit staging, normal extract/pointer → generic Job → CAS apply → resident surface, same-Subject cross-process exclusion/abnormal-exit recovery, and transport loss after apply followed by surface-only repair without duplicate immutable Memory change.
|
||||
- Generic Job tests additionally cover immutable grant persistence, missing grants, foreign grants, mismatched input bindings and wrong-consumer grants, per-attempt/deadline revocation, cancellation, timeout, cleanup failure, and stale capabilities. Session adapter tests cover no-candidate discovery, foreign Subject/scope/Worker denial, bounded UTF-8 and JSON output, filter/generation-bound cursors, missing/corrupt storage, and hidden/uncommitted/partial-tail exclusion without observation writes.
|
||||
|
||||
Verified commands:
|
||||
|
||||
```sh
|
||||
cargo test -p standalone --test subjektiv -- --test-threads=1
|
||||
cargo test -p protocol -p feature-storage -p subjektiv -p manifest -p session-store -p standalone -p worker -p tui -p yoi -p yoi-workspace-server
|
||||
cargo test -p worker-runtime --lib protocol_
|
||||
cargo check
|
||||
cargo check -p protocol --features typescript,json-schema
|
||||
cargo check -p tui --features e2e-test
|
||||
cargo fmt --all -- --check
|
||||
git diff --check HEAD
|
||||
```
|
||||
|
||||
The changed-crate run passed, including 836 Worker unit tests, 603 Workspace Server unit tests (authorization/singleton/T-704 regression), 48 standalone unit tests, and nine standalone Subject integration tests. Existing ignored tests remain ignored. TypeScript generation check reports existing unsupported serde-attribute warnings but succeeds. These commands prove mechanism/authorization/storage contracts, not live-model quality or full interactive terminal E2E behavior.
|
||||
@@ -1,103 +0,0 @@
|
||||
# Backend subjektiv consolidation Jobs (T-704)
|
||||
|
||||
subjektiv requests the common Backend Job runner with an explicit
|
||||
`builtin:subjektiv-memory-consolidation` Profile. The runner has no purpose-based
|
||||
Profile switch. Subject-body Workers and the independent Workspace Memory feature
|
||||
are not Jobs and are not migrated or reset here. The shared/standalone adapters
|
||||
planned in T-708/T-709 are outside this Backend change.
|
||||
|
||||
## Input, grants and concurrency
|
||||
|
||||
The existing backlog/byte thresholds and missing/stale/failed surface policy
|
||||
remain the launch decision. A Job captures an immutable bounded batch (up to 100
|
||||
candidate IDs) plus the Subject `memory_fingerprint`. Selection also reserves the Job's
|
||||
16 KiB result budget, accounting for JSON-escaped IDs appearing in both the batch
|
||||
claim and compact verified outcomes. Excess candidates remain staged for a later
|
||||
batch. An empty batch is valid for a
|
||||
surface-only rebuild. The Backend explicitly binds the Subject and candidate
|
||||
batch grant in durable Job intent before Worker execution. No subject-body
|
||||
singleton ownership or Session attribution is transferred to the Job.
|
||||
|
||||
Authenticated `(Workspace, Runtime, Worker)` registry binding and the exact
|
||||
current dispatched attempt authorize each operation. Names, Profile tags and
|
||||
model-provided `subject_id` are not authority. Candidate listing exposes only
|
||||
unresolved IDs in the captured batch; reads/decisions outside it are denied.
|
||||
Surface prepare requires all batch candidates to have durable dispositions.
|
||||
Surface generation records preserve immutable Job/attempt provenance (subjektiv
|
||||
schema migration 6); publication, failure and completion reject another attempt's
|
||||
generation, including pre-Job/legacy generations.
|
||||
|
||||
The common resource key `subjektiv-consolidation:<subject>` prevents parallel
|
||||
Jobs for a Subject. Concurrent requests return the original active intent and
|
||||
report `skipped_existing_job`; they do not expand its grants. New candidates and
|
||||
items beyond the bounded batch stay in staging. A subsequent consolidation
|
||||
request snapshots remaining work; it is not counted as processed by the first
|
||||
Job. There is no domain-specific status ledger, pool or persistent idle Worker.
|
||||
Definitive failures use the common bounded retry API with the original intent;
|
||||
unknown execution stays fenced for attention. A later explicitly requested Job
|
||||
may snapshot remaining candidates once all previous Workers are safely cleaned.
|
||||
|
||||
## Memory and surface completion
|
||||
|
||||
`MemoryApplyCandidate` still uses the existing `expected_change_id` checks, atomic
|
||||
apply/close and immutable decision receipts. Partial application survives model
|
||||
failure, timeout, lost response, retry, Worker deletion and Backend recovery.
|
||||
Job replay is not a second memory apply: resolved candidates retain their
|
||||
existing dispositions, and exact candidate decisions converge on their receipts.
|
||||
|
||||
`SubmitBackendJobResult` for a bound consolidation Job receives only the Subject
|
||||
and captured candidate IDs. Its Host capability awaits the existing bounded
|
||||
clean-context surface editor and publish-or-recorded-failure, then adds the actual
|
||||
structured surface outcome. It does **not** schedule a Worker-owned post-Run task.
|
||||
The result call serializes and caches the exact generated outcome for ambiguous
|
||||
submission replay. The Backend verifies every batch disposition and the current
|
||||
Memory-input fingerprint, generation-run ownership and persisted ready/failed surface fields;
|
||||
it enriches the durable result with actual immutable candidate disposition
|
||||
summaries (`candidate_id` and `action`). Full reasons, affected change references and
|
||||
source edges remain in the existing immutable receipt store; they are not copied
|
||||
into a bounded Job result. Exact replay uses the same verified compact projection.
|
||||
Thus confirmed-memory consolidation and surface readiness are separate outcomes.
|
||||
A recorded surface failure does not undo Memory. A stale/unfinished/fabricated
|
||||
surface cannot be reported completed. Final prose or Idle is never success.
|
||||
|
||||
Only after durable structured result acceptance does the existing terminal Job
|
||||
cleanup run. Its tracked Run teardown fence lets the result tool finish before
|
||||
stop/remove. Timeout/unknown paths cancel unfinished Worker execution through
|
||||
that same cleanup; later operations/results are rejected. The next request can
|
||||
regenerate failed/stale surfaces without touching confirmed memory.
|
||||
|
||||
## Legacy cutover and recovery
|
||||
|
||||
The subject-specific direct spawn and Idle resubmission paths are removed.
|
||||
On a consolidation request the Backend checks the **persisted singleton owner**
|
||||
for the old `subjektiv-consolidation:<subject>` key. It revalidates Workspace,
|
||||
key and old dedicated recipe provenance, not the display name. A running/paused
|
||||
legacy foreground operation is left alone and returns `skipped_legacy_inflight`;
|
||||
no Job is launched alongside it. Legacy owned operations can finish during this
|
||||
bounded cutover.
|
||||
|
||||
After foreground Idle (or Stopped) is confirmed, Backend stop is confirmed before
|
||||
canonical WorkerRemovalService removal. Any legacy surface background task is
|
||||
cancelled at stop; the subsequent Job rebuilds from confirmed Memory and does not
|
||||
infer successful surface completion from Idle. Busy/unobservable/mismatched
|
||||
owners fail closed. Empty/ready Subjects also retire their idle legacy owner.
|
||||
Removal failures remain diagnosable through existing durable removal recovery;
|
||||
a subsequent request rechecks ownership instead of deleting storage directly.
|
||||
Subject-body/ordinary/Workspace-Memory Workers are never selected by this key.
|
||||
|
||||
The Job runner uses its existing durable dispatch allocation, tracked input,
|
||||
T-701 registry-bound restore/recovery, deadlines, unknown outcomes, result replay,
|
||||
delivery and T-690 cleanup. Job results, candidate receipts, Memory changes,
|
||||
provenance and surfaces remain in their existing Backend stores after Worker
|
||||
removal. No live dogfood Worker or environment is stopped/updated by development
|
||||
validation.
|
||||
|
||||
## Validation boundary
|
||||
|
||||
Server recording-backend fixtures prove reservation/binding/auth/result/cleanup
|
||||
contracts, and existing store tests prove candidate CAS/atomic receipts and
|
||||
surface freshness. Worker scripted-client tests prove the clean-context editor
|
||||
completion-before-result boundary and result replay. They are not real-provider
|
||||
LLM or live dogfood process evidence. See the T-704 validation report for exact
|
||||
commands/results and inherited intermittent failures; later passes do not erase
|
||||
those failures.
|
||||
@@ -1,324 +0,0 @@
|
||||
# subjektiv subject Memory store
|
||||
|
||||
The trusted Server-side `subjektiv` repository in
|
||||
`yoi_workspace_server::subjektiv` is the persistence authority for subject-scoped
|
||||
Memory. It is a domain store, not a Worker tool. Legacy single-document Workspace
|
||||
Memory remains a separate, deprecated control-plane authority until an operator
|
||||
performs the explicit reset defined by the
|
||||
[subjektiv product cutover runbook](../development/subjektiv-product-cutover.md);
|
||||
legacy rows are never imported, dual-written, or silently reinterpreted as subject
|
||||
Memory.
|
||||
|
||||
## Ownership and scope
|
||||
|
||||
- The Server-managed `FeatureStorage` owns the SQLite path, connection, common
|
||||
pragmas, migration lifecycle, backup/restore, shutdown, and Workspace deletion.
|
||||
- subjektiv owns its migration contents, tables, issued subject/Memory/snapshot
|
||||
identifiers, validation, queries, and domain transactions.
|
||||
- Each repository is bound to the Workspace of its `FeatureDatabase`. A
|
||||
`store_scope` row verifies that binding on every open. Subject IDs select data
|
||||
inside that boundary; knowing an ID grants no authorization.
|
||||
- Authorization must happen before calling the repository. Neither a subject ID
|
||||
nor Memory text can confer Workspace permissions or assignments.
|
||||
- No SQLite path, connection, or SQL surface is sent to a Worker or Runtime.
|
||||
Async callers must run these synchronous repository calls on a blocking
|
||||
executor.
|
||||
- The store contains no `current_worker_id` or equivalent current link. Yoi's
|
||||
keyed singleton remains the sole authority for the current Worker. T-668
|
||||
connects Workers using the opaque `subjektiv:<subject-id>` key and records
|
||||
immutable historical Worker/Session attribution as provenance; that history is
|
||||
never consulted as a current Worker link.
|
||||
|
||||
Registration is deliberately separate from open. Trusted Server construction
|
||||
registers `REGISTRATION` once per `FeatureStorage` manager, then reuses its
|
||||
`RegisteredFeature` for each Workspace. Merely adding this module does not open
|
||||
or create a subjektiv database. T-668 opens it lazily for explicit subject
|
||||
operations or an authenticated subject extraction write; leaving the Feature
|
||||
disabled has no subject-store side effect.
|
||||
|
||||
## Immutable changes and schema contracts
|
||||
|
||||
All new persisted JSON records use `schema_version: 1`. Subject staging retains
|
||||
existing extract `StagingRecord` schema version 2 and its six kinds:
|
||||
`preference`, `working_assumption`, `constraint`, `decision`, `open_question`,
|
||||
and `lesson`. The host adds only `subject_id` and `created_at`; the model does
|
||||
not provide subject scope or provenance.
|
||||
|
||||
A subject record has a store-issued random ID, an explicit role, lifecycle state,
|
||||
and lifecycle timestamps. It starts `active` with no confirmed Memory. Retirement is terminal
|
||||
in this baseline and is distinct from stopping a Worker. Retirement prevents new
|
||||
writes but retains all reads and history.
|
||||
|
||||
User-managed `behavior_md` is separate from generated Memory context. Updating it compares the exact previously read text through `expected_behavior_md`, not a behavior counter. Conflicts require rereading the actual current text; changing behavior neither changes confirmed Memory nor rewrites Worker history.
|
||||
|
||||
An immutable Memory change contains:
|
||||
|
||||
- a stable store-issued Memory ID, opaque `change_id: String`, and optional `previous_change_id`;
|
||||
- subject ID, existing six-way kind, state, claim, optional Markdown body,
|
||||
`why_useful`, and optional staleness condition;
|
||||
- exact source candidate IDs and exact `{memory_id, change_id}` derivations;
|
||||
- a required change reason and host-issued creation/update times.
|
||||
|
||||
The first change must start `active`. For every kind, `active` may remain active or
|
||||
become `resolved`/`retracted`; `resolved` may remain resolved, reopen to active,
|
||||
or be retracted. `retracted` is terminal. A corrected experience after
|
||||
retraction receives a separate Memory ID and may cite the old fixed change.
|
||||
`active` means eligible for ordinary recall, not proven true and not authorized.
|
||||
|
||||
Refinement/correction of the same experience appends a new immutable change under the
|
||||
same Memory ID. A distinct experience or lesson gets a new ID. Every write uses
|
||||
`expected_change_id` equal to the current change ID; stale callers receive a typed `MemoryChangeConflict`.
|
||||
Old rows, evidence edges, and derivations are immutable and retained.
|
||||
|
||||
`memory_fingerprint` is SHA-256 over the sorted, length-framed `(memory_id, current_change_id)` set, including all Memory states. Each committed content/state/provenance change gets a fresh change ID, preventing ABA reuse; staging alone does not change this fingerprint.
|
||||
|
||||
A surface snapshot stores only its body, exact Memory change references, the
|
||||
subject's `built_from_memory_fingerprint`, and host-issued ID/time. Creation requires
|
||||
the captured fingerprint to equal the current `memory_fingerprint` and permits at
|
||||
most one fixed change of each Memory. The snapshot may deliberately select a
|
||||
bounded subset; selection, generation, bounds, and injection policy are later
|
||||
work.
|
||||
|
||||
## Relational invariants and retention
|
||||
|
||||
The feature database contains `subjects`, `staging_records`,
|
||||
`staging_resolutions`, `memory_records`, immutable `memory_changes`, normalized
|
||||
candidate/derivation edges, and immutable `surface_snapshots` with normalized
|
||||
references. Composite foreign keys always include subject scope. As the physical
|
||||
database is already Workspace-scoped, Workspace identity is stored once in
|
||||
`store_scope` rather than copied into every row.
|
||||
|
||||
`memory_records` is the `current_change_id` pointer and current JSON projection;
|
||||
`memory_changes` is the immutable historical authority. Candidate JSON is
|
||||
immutable. A resolution stores both the resolution JSON and the exact staged JSON
|
||||
bytes that were resolved. Aggregate seal rows prevent evidence, derivations,
|
||||
resolution targets, or snapshot references from being appended after their
|
||||
parent JSON is finalized. Foreign keys plus retention triggers prevent dangling
|
||||
candidate, derivation, resolution-target, and snapshot references. Ordinary
|
||||
subject operations expose no record deletion API; the legacy reset contract does
|
||||
not touch this Feature database.
|
||||
|
||||
Search columns and JSON projections are written together by the typed repository.
|
||||
Raw SQL remains private trusted implementation detail. Existing Workspace Memory
|
||||
rows are neither imported nor dual-written.
|
||||
|
||||
## Transactions
|
||||
|
||||
`FeatureDatabase::try_transaction` preserves typed Feature-domain errors while
|
||||
retaining the same `BEGIN IMMEDIATE`, rollback, lifecycle fencing, and connection
|
||||
ownership as `transaction`.
|
||||
|
||||
`SubjektivStore::apply_candidate` and `apply_candidates` atomically:
|
||||
|
||||
1. verify the active subject and every unresolved candidate in repository scope;
|
||||
2. check the expected Memory change ID and every exact candidate/derivation ref;
|
||||
3. derive the change's candidate provenance from that same candidate set;
|
||||
4. insert and seal the immutable Memory change and evidence edges;
|
||||
5. update the current Memory projection and recompute the fingerprint of current Memory identities;
|
||||
6. insert and seal each immutable candidate resolution and exact target change.
|
||||
|
||||
Generic candidate resolution cannot emit `applied`, and direct Memory writes
|
||||
cannot attach candidate provenance; those relationships must go through this
|
||||
atomic path.
|
||||
|
||||
Any error rolls the complete operation back. The transaction is confined to the
|
||||
subjektiv Feature database; it does not promise atomicity with the Server
|
||||
control-plane database or another Feature database.
|
||||
|
||||
## Operational contract
|
||||
|
||||
Reopening the same Server-managed feature database verifies migrations and the
|
||||
stored Workspace scope before serving reads/writes. `WorkspaceFeatureStorage`
|
||||
online backup/restore includes the entire subjektiv database, including immutable
|
||||
history and provenance. Workspace deletion and shutdown use the common lifecycle
|
||||
fences and close retained repository handles. Disabling subjektiv does not erase
|
||||
its database.
|
||||
|
||||
## Extraction connection (T-668)
|
||||
|
||||
`feature.subjektiv` is an explicit Worker profile boundary. It reuses the
|
||||
committed-run threshold, restricted Internal extraction Worker,
|
||||
`SessionEntryRef` evidence resolution, explicit finish, generation fence, and
|
||||
success-only pointer progression from the established Memory lifecycle. Its
|
||||
pointer domain is separate from legacy Workspace Memory, and a Worker is rejected
|
||||
at installation when both automatic extraction destinations are enabled. Ordinary
|
||||
SubWorkers and Reviewer SubWorkers have lifecycle Features disabled by their host;
|
||||
only evidence actually committed into the parent Session can enter the parent's
|
||||
capture.
|
||||
|
||||
The staging request contains the existing model contract (`kind`, `claim`,
|
||||
`why_useful`, optional `staleness`, and entry references after host resolution)
|
||||
plus the committed parent `session_id`. It contains no subject id. The Server
|
||||
derives subject scope from authenticated Runtime/Worker source proof and the
|
||||
current keyed-singleton lease, then attaches the session id to every source
|
||||
reference and records immutable historical `{subject, runtime, worker, session}`
|
||||
provenance atomically with staging. A Session already attributed to another
|
||||
subject is rejected. Preference candidates still require exclusively HumanInput
|
||||
evidence.
|
||||
|
||||
Subject retirement does not stop a Worker, and stopping/replacing a Worker does
|
||||
not retire the subject. A replacement uses the same `subjektiv:<subject-id>` key;
|
||||
retries therefore recover through Yoi's singleton authority without a second
|
||||
current-worker mapping in this database.
|
||||
|
||||
Candidates are immutable writes that occur before `FinishMemoryExtraction`. If a
|
||||
run stages one or more candidates and then fails, is cancelled, or loses its
|
||||
generation fence, those candidates remain recorded and the extraction pointer
|
||||
does not advance. A retry may therefore produce semantically duplicate candidates;
|
||||
subsequent consolidation must compare them using the existing candidate
|
||||
identity/content boundary. The system must not claim that the partial writes were
|
||||
rolled back or silently delete them. Subject recall, consolidation, and legacy
|
||||
Memory migration remain separate work.
|
||||
|
||||
## Recall and explicit proposal tools (T-669)
|
||||
|
||||
An enabled `feature.subjektiv` installs five names that do not overlap the legacy
|
||||
single-Markdown Memory tools: `SubjektivMemoryQuery`, `SubjektivMemoryRead`,
|
||||
`SubjektivMemoryListChanges`, `SubjektivMemoryRemember`, and
|
||||
`SubjektivMemoryProposeChange`. Runtime-signed Worker source plus current
|
||||
`subjektiv:<subject-id>` singleton ownership is re-evaluated for every operation;
|
||||
none of the model-visible inputs contains a subject, Runtime, Worker, Session,
|
||||
origin, or raw provenance object.
|
||||
|
||||
Query searches only current projections. Omitted `states` means `active`; explicit
|
||||
empty `states` or `kinds` is invalid. Output defaults to 20 and is capped at 100,
|
||||
ordered by `updated_at DESC, memory_id ASC`. Its opaque cursor binds the subject,
|
||||
canonical filters, offset, and subject `memory_fingerprint`; any confirmed-Memory
|
||||
change makes it a typed stale-cursor conflict rather than silently mixing
|
||||
snapshots. Change-ID conflicts and stale cursors retain their diagnostic code as a
|
||||
structured `{status: "error", error: {code, message}}` tool result instead of
|
||||
requiring models to parse prose. Change history follows the immutable `previous_change_id` chain, newest first. Its first page fixes `head_change_id`, so later appended changes neither duplicate nor displace old page members. IDs are not counters and are not ordered numerically.
|
||||
|
||||
Read accepts an exact nonempty `change_id` or resolves the current change once. A
|
||||
missing historical change never falls back to current. Markdown pagination is
|
||||
line-based (default 200, maximum 1000) with a preliminary 16 KiB UTF-8 body
|
||||
cap and a global 56 KiB budget over the exact pretty-serialized model-visible
|
||||
response. JSON escaping, provenance, and metadata therefore reduce the current
|
||||
body page before the Worker content ceiling is reached. It reports
|
||||
`body_truncated`, `body_next_offset`, and `body_next_byte_offset`; an oversized
|
||||
or escape-heavy single line resumes from the returned UTF-8 byte boundary within
|
||||
that same line. Every continuation offset must also supply the exact `change_id`
|
||||
returned by the first page, so a current change cannot mix body contents.
|
||||
Provenance uses a separate immutable-change-bound cursor and returns one
|
||||
candidate/derivation reference per page. New staged candidates contain at most 10
|
||||
evidence records and 10 source references; read pages project at most two of each,
|
||||
with displayed anchor text capped at 64 UTF-8 bytes and nested offsets carried by
|
||||
the ordinary evidence cursor. Compatible candidates persisted before the admission
|
||||
cap remain readable across as many evidence pages as needed, while every current
|
||||
staging path rejects new larger anchor sets. Candidate evidence is the bounded
|
||||
host-resolved anchor saved in staging; raw Session bodies are not copied
|
||||
into Memory responses. Resolved, retracted, and historical changes remain
|
||||
addressable by ID.
|
||||
|
||||
Remember and ProposeChange only stage candidates. Entry references are resolved
|
||||
against the host's committed Session capture, and preference candidates continue
|
||||
to require exclusively `HumanInput` evidence. When no entry is supplied, a
|
||||
non-preference request returns `pending_commit`; after the run commits, the host
|
||||
reconstructs pending receipts from the durable tool-call/tool-result history and
|
||||
uses the committed tool-call entry itself as model-origin evidence. Receipt lookup
|
||||
reports `pending_commit`, `staged`, or `missing`; the post-commit hook retries
|
||||
immediately, the pre-request hook replays durable pending receipts after Worker
|
||||
restore, and the fail-closed pre-rewrite hook prevents compaction from dropping an
|
||||
unstaged receipt. Hook failure and process restart therefore retain an idempotent
|
||||
retry path, while an operation that never commits is not staged. Receipt identity is derived from Session and tool-call identity,
|
||||
and exact backend retries return the first candidate rather than creating another
|
||||
one. Neither path changes `memory_records`, `memory_changes`, the confirmed-Memory fingerprint,
|
||||
or a surface snapshot.
|
||||
|
||||
|
||||
## Historical Session discovery (T-673)
|
||||
|
||||
An enabled `feature.subjektiv` also installs exactly three read-only tools:
|
||||
`SubjektivSessionList`, `SubjektivSessionSearch`, and `SubjektivSessionRead`.
|
||||
The Server binds subject, Workspace, Runtime, Worker, retention catalogue, and
|
||||
archive authority from the authenticated caller. Model input contains only
|
||||
Session/segment/entry selectors, search filters, limits, and opaque cursors;
|
||||
knowing any identifier does not grant access. Reads use Yoi's retained or
|
||||
committed-archive Session storage and a shared public projection. They do not
|
||||
copy Session bodies into the subjektiv database, restore Workers, migrate
|
||||
Session storage, create candidates or Memories, revise a surface, or promote a
|
||||
re-read tool result to HumanInput.
|
||||
|
||||
The public projection covers committed records in every persisted segment,
|
||||
including non-active branches and pre-compaction entries, while excluding
|
||||
system prompts, hidden reasoning, traces/diagnostics, unfinished run tails, and
|
||||
attachment or pasted-artifact bodies. Results preserve exact Session, segment,
|
||||
entry, provenance, and lineage identity so inherited entries are not presented
|
||||
as a current decision. Full entry reads are UTF-8-boundary paged and remain
|
||||
subject to the same projection as snippets and search.
|
||||
|
||||
The subject's first committed primary-Worker Session is attributed independently
|
||||
of extraction thresholds or candidate creation. Recording is idempotent and is
|
||||
retried on later committed-run and pre-request lifecycle points after transient
|
||||
failure; no attribution is attempted before a committed capture exists. This is
|
||||
not a distributed transaction with Session persistence. In particular, the
|
||||
system does **not** backfill older unattributed Sessions from their text, display
|
||||
name, current singleton ownership, Memory references, or other inference.
|
||||
Historical Sessions without trustworthy Host-recorded attribution remain outside
|
||||
subject discovery until a separately authorized provenance mechanism exists.
|
||||
|
||||
Pagination cursors bind scope, operation, filters, stable ordering, and the
|
||||
public storage generation and are never authorization credentials. Every page
|
||||
rechecks current subject and observation authority. Deleted-Worker archives are
|
||||
eligible only when both the committed Server catalogue and Runtime manifest
|
||||
agree and the archived observation grant remains valid; incomplete, expired,
|
||||
corrupt, unavailable, or changed sources are reported explicitly rather than as
|
||||
an empty successful search.
|
||||
|
||||
Change proposals add optional `change_proposal` metadata to the existing v2
|
||||
`SubjectStagingRecord` envelope; automatic extraction remains proposal-free and
|
||||
its model schema is unchanged. The metadata is serialized atomically with the
|
||||
candidate and therefore remains present in candidate reads and immutable
|
||||
resolution copies:
|
||||
|
||||
```json
|
||||
{
|
||||
"intent": "revise",
|
||||
"memory_id": "memory-…",
|
||||
"expected_change_id": "change-…",
|
||||
"change_reason": "The committed evidence corrects the prior condition"
|
||||
}
|
||||
```
|
||||
|
||||
This is the T-670 handoff fixture. Consolidation must re-read this typed metadata,
|
||||
re-check subject ownership, `expected_change_id`, and the requested state
|
||||
transition in the same transaction that applies the candidate, and leave a stale
|
||||
proposal unresolved/conflicted. It must not parse target information from the
|
||||
claim, retarget to the latest change, or revive a retracted Memory. Valid
|
||||
transitions at staging are revise while active/resolved, active→resolved,
|
||||
active/resolved→retracted, and resolved→active. Retraction remains terminal.
|
||||
|
||||
## Candidate consolidation and corrections (T-670)
|
||||
|
||||
subjektiv requests a bounded Backend-owned Job with the explicit
|
||||
`builtin:subjektiv-memory-consolidation` Profile and an immutable Subject/candidate
|
||||
batch grant. The common runner resolves the recipe; Profile names do not grant
|
||||
permissions. The Host derives authority from the authenticated current
|
||||
Job/attempt/Worker binding; model-visible inputs cannot select a Workspace,
|
||||
subject, Runtime, Worker, or Session. Its tools list/read only pending candidates
|
||||
in that batch, query/read confirmed changes, and make one candidate decision.
|
||||
Candidate text is evidence rather than authority. No subject-specific idle Worker
|
||||
is resubmitted; see [Job execution and cutover](subjektiv-consolidation-jobs.md).
|
||||
|
||||
Every decision carries a stable request ID. An exact retry returns the committed
|
||||
receipt, while reuse with different input is a conflict. Applying a candidate
|
||||
atomically writes one new confirmed change, its exact candidate and derivation
|
||||
edges, the immutable resolution and affected change references, and updates
|
||||
the subject Memory fingerprint. A non-applied decision explicitly records
|
||||
`discarded`, `invalid`, `duplicate`, or `already_covered`; an empty model response,
|
||||
transport failure, or aborted consolidation records no disposition and leaves the
|
||||
candidate pending. A successful applied response is the only evidence that
|
||||
confirmed Memory changed.
|
||||
|
||||
A typed change proposal must be applied to its exact `memory_id` and
|
||||
`expected_change_id`, preserving its intent and change reason. The transaction
|
||||
rechecks the current change ID and state transition. It never rebases or retargets
|
||||
a stale proposal, revives retracted Memory, or extracts a target from claim prose.
|
||||
Correction/refinement of the same experience appends under the same Memory ID;
|
||||
a corrected experience after terminal retraction receives a new Memory ID and may
|
||||
cite the retracted fixed change as a derivation. Surface generation is a
|
||||
separate post-consolidation lifecycle: its failure cannot roll back a committed
|
||||
candidate decision.
|
||||
|
||||
For the product activation, legacy reset, and operator recovery boundary, follow
|
||||
the [subjektiv product cutover runbook](../development/subjektiv-product-cutover.md).
|
||||
@@ -1,85 +0,0 @@
|
||||
# Subject Memory surface generation
|
||||
|
||||
The subject Memory surface is a bounded start-time context derived from confirmed Memory with immutable change history. It is not another Memory authority and is never used as extraction or consolidation input.
|
||||
|
||||
## Fixed policy
|
||||
|
||||
The Backend captures one bounded input snapshot from current `active` records for exactly one subject. Staging records, Session history, historical changes, and prior surfaces are not queried.
|
||||
|
||||
Selection is deterministic:
|
||||
|
||||
1. categories use the fixed order `preference`, `constraint`, `decision`, `working_assumption`, `open_question`, `lesson`;
|
||||
2. each category is ordered by `updated_at DESC, memory_id ASC`;
|
||||
3. at most 8 records are retained per category;
|
||||
4. records are allocated round-robin by category, up to 24 records total;
|
||||
5. the 10,000-token material budget is initially divided equally across categories as a fairness reservation; unused shares are then reclaimed in the same canonical round-robin order, so one large category cannot exclude another non-empty category or strand usable capacity;
|
||||
6. both category and aggregate material bounds use the provider-independent estimate `ceil(UTF-8 bytes / 4)`;
|
||||
7. before invoking the editor, the Worker measures the actual normalized Agen request fields containing the rendered system prompt, conversation items (including the fixed question and material JSON), and the typed `SubmitMemorySurface` tool definition/schema against the 12,000-token complete-input budget. It reapplies equal per-category fairness reservations under that final estimator, reclaims unused shares in canonical order, fails rather than publishing a false empty surface if no grounded material fits, and guards every subsequent correction request before it reaches the provider. Provider-specific HTTP envelope bytes are outside this normalized request budget.
|
||||
|
||||
The clean-context editor uses the effective `builtin:subjektiv-memory-consolidation` profile model (`codex-oauth/gpt-5.6-luna`, medium reasoning). A generation permits three model turns (the initial attempt plus at most two correction turns). A Memory-input fingerprint conflict causes at most one complete re-read and regeneration. The published Markdown is capped at 1,024 tokens by the same estimate.
|
||||
|
||||
Every editor point carries one or more exact `{memory_id, change_id}` references. The Host checks the output shape, non-empty grounding, budget, subject scope, membership in the captured materials, and current `memory_fingerprint`. These checks prove reference existence and freshness only; they do not claim semantic correctness.
|
||||
|
||||
Publication is idempotent for identical output for one exact Memory-input fingerprint. A different concurrent output cannot replace the first published surface. Confirmed Memory writes atomically mark the prior surface stale. Failed surface work never rolls back confirmed Memory or candidate disposition.
|
||||
|
||||
Resident injection uses five distinct product states:
|
||||
|
||||
- `ready`: inject the non-empty snapshot only when its `built_from_memory_fingerprint`
|
||||
equals the subject's exact current `memory_fingerprint`;
|
||||
- `ready-empty`: a successful current generation selected no Markdown; inject no
|
||||
prose, but preserve the explicit successful-empty state;
|
||||
- `ungenerated`: no successful current generation exists; inject nothing;
|
||||
- `stale`: confirmed Memory changed after publication (or the snapshot predates
|
||||
this generation policy); inject nothing until regeneration succeeds;
|
||||
- `failed`: the generation attempt failed; inject nothing and preserve the typed
|
||||
failure for diagnostics/retry rather than representing it as empty Memory.
|
||||
|
||||
The resident Backend transports availability separately from optional Markdown.
|
||||
In storage, `ready-empty` is a current `ready` snapshot with empty Markdown; it is
|
||||
never conflated with `ungenerated`, `stale`, `failed`, request failure, or
|
||||
omission. Publication is append-only: generation inputs/runs, failures, snapshots,
|
||||
and exact references remain immutable history, while current state points at the
|
||||
applicable result.
|
||||
|
||||
Runtime-owned new Workers install Features before durably materializing the
|
||||
initial Session head. Initial resident prompt construction uses the host's shared
|
||||
prompt-contribution source contract, so an enabled Feature can contribute its
|
||||
current surface without owning Session persistence. Restore refresh is different:
|
||||
it is a subjektiv Feature lifecycle hook installed only for an execution-enabled,
|
||||
subject-attached Worker. The generic Worker host announces the restore boundary
|
||||
and durably commits typed system items requested by installed Features; it does
|
||||
not infer refresh behavior from the presence of a resident source. Consequently,
|
||||
legacy Memory-only Workers, policy-only unattached Workers, and lifecycle-suppressed
|
||||
Internal Workers/Reviewers do not read a subjektiv surface, render the restore
|
||||
template, or append a refresh.
|
||||
|
||||
The subjektiv restore hook preserves the persisted prompt and all prior history
|
||||
verbatim. It requests one durable restore-boundary system item that supersedes
|
||||
earlier resident summaries with the latest `ready` surface, an explicit
|
||||
`ready-empty` state, or a no-current-surface tombstone for `ungenerated`, `stale`,
|
||||
or `failed`; subsequent turns in that restored process do not append the boundary
|
||||
again. No regeneration, correction, failure, or cutover rewrites a committed
|
||||
Session entry.
|
||||
|
||||
## Semantic fixture and verification
|
||||
|
||||
Use a test subject with these active materials:
|
||||
|
||||
- constraint: “Never send credentials to tools.” (`staleness`: “Until credential brokering is redesigned.”)
|
||||
- constraint: “A trusted broker may deliver short-lived credentials to the Git provider.”
|
||||
- decision: “Publish only the Ticket work branch; never force-push.”
|
||||
- working assumption: “The provider resolves selectors after a normal push.”
|
||||
- open question: “Does the provider preserve approval after target-only movement?”
|
||||
- lesson: “Checking only the local branch previously produced non-reviewable evidence.”
|
||||
- duplicate lesson: “Local HEAD alone is not immutable review evidence.”
|
||||
|
||||
A valid review fixture must:
|
||||
|
||||
- retain “never” and the credential-redesign expiry condition;
|
||||
- present the apparently conflicting broker constraint without silently declaring one side invalid;
|
||||
- preserve the target-only-movement item as an open question rather than a decision;
|
||||
- combine the two review-evidence lessons only if both meanings remain;
|
||||
- cite only exact refs supplied with those materials;
|
||||
- distinguish mechanical citation validation from a human/model semantic review of the wording.
|
||||
|
||||
The repository tests exercise deterministic bounds and fair budget reclamation, subject isolation, active-current-only selection, complete normalized editor-request accounting, output/reference rejection, empty/ungenerated/failed/stale states, legacy regeneration, generation races, bounded conflict retry, terminal failure recording, parallel publication, correction/retraction staleness, and publication idempotency. The prompt fixture above is the manual semantic check for condition, negation, reservation, conflict, expiry, and duplicate editing behavior.
|
||||
@@ -1,25 +0,0 @@
|
||||
# Ticket evidence and immutable Merge Request results
|
||||
|
||||
Ticket lifecycle, repository integration, and current requirement attestation are separate facts. An incomplete read-side evidence summary does **not** undo a recorded `done` state or a `CompleteTicket` event.
|
||||
|
||||
## Evidence subjects
|
||||
|
||||
- An open MR uses the Repository/Runtime authority's provider-resolved current source. No Server-local checkout is assumed. Failure is a tagged `source_ref_observation: { status: "unavailable", code }`, not proof that review is stale. Ticket review status is `unknown`; `missing` includes `source_ref_unavailable`, not `review_after_rescope` merely because observation failed.
|
||||
- A merged MR uses its stored MergeResult's `approved_source_ref` and `approval_event_id`. Branch deletion/movement, checkout absence, and provider outage do not trigger source observation. The summary's `current_subject_ref` is the immutable merged source and observation is `not_required`.
|
||||
- Integration approval must exist, approve that exact source, predate the merge, have matching ReviewRequested evidence, and not have been revoked. `integration_evidence_error` is a bounded machine code for invalid stored evidence. `merged` alone never creates approval.
|
||||
|
||||
A merged MR's review status/timestamps describe **integration approval**, not a later requirement approval. Paged MR detail reads evaluate the full stored result before paging; UI must not infer approval from a merge event appearing in one page.
|
||||
|
||||
## Current requirements
|
||||
|
||||
`merge_request::requirement_approval` evaluates Ticket read-side MR evidence; it is not a Ticket completion gate. It validates an effective Reviewer approval of the current item content digest and the **exact complete linked MR/source snapshot**, including the hosting MR's own source and corresponding ReviewRequested event. There is no primary-MR fallback, comment-derived approval, or timestamp-only freshness rule. A post-integration review may attest new item content/a linked-result snapshot without replacing MergeResult or its integration approval.
|
||||
|
||||
`approved_current_subject` describes valid source/integration approvals. `review_after_rescope` retains its wire name but now describes exact current requirement attestation, not ordering of wall-clock timestamps. `approved_review` query filtering requires that current attestation. `stale_after_rescope` requires substantive item-edit history and a known source-approved snapshot lacking current attestation; unknown observations are only `missing_evidence`.
|
||||
|
||||
`complete_for_integration` means a nonempty linked MR set has valid source approvals and current requirement evidence, including for partially or fully integrated sets. It is not a claim that open repositories have been integrated, that target integration readiness has been checked, or that Ticket completion is authorized. An MRless Ticket has no applicable MR evidence: `missing` is empty, `review_status` is null, and approval/integration flags remain false. It does not match `missing_evidence` or `approved_review` merely because its state is done.
|
||||
|
||||
CompleteTicket belongs to the Ticket domain. It records the authenticated actor's reason/result and optional references using current item `content_digest`/state CAS and an operation receipt. It needs no MR or Reviewer approval, and does not create or modify those proofs. State setting, completion, close, and reopen share the same transactional update boundary. Exact replay returns current Ticket authority without reapplying a historical decision or appending an event. Historical MR-based `ticket_completion` records remain readable and distinct from new judgment receipts.
|
||||
|
||||
Writable targets authorize deliverables, not mandatory MRs for every target. Every linked MR remains part of the exact requirement snapshot; no arbitrary latest-review or single-repository shortcut is used.
|
||||
|
||||
Query candidate SQL narrows Ticket metadata, not live evidence. Evidence predicates are evaluated from the same full MR projection as ShowTicket, after source observation. Bounded scans can return an empty page with `has_more` and a cursor after the last scanned candidate; callers must follow that cursor rather than treat an empty page as exhaustion. This prevents stale SQL/report heuristics or early nonmatching candidates from hiding later evidence matches.
|
||||
@@ -1,39 +0,0 @@
|
||||
# Asynchronous Ticket item checker
|
||||
|
||||
Worker-originated `TicketCreate` and `TicketEditItem` mutations are checked after the authoritative SQLite save. The checker is advisory: its scheduling, model execution, result delivery, or failure never rolls back a Ticket mutation and never changes Ticket state, Queue eligibility, assignments, or implementation authority.
|
||||
|
||||
## Applied paths
|
||||
|
||||
The trigger is in the Workspace Server's authenticated Worker Ticket REST path. A complete, verified Runtime/Worker source header pair is required before a check is scheduled. Every successful Worker `Create` and `EditItem` operation schedules the persisted item content digest; failed saves and thread/state events do not schedule work.
|
||||
|
||||
The browser's direct item-edit handler currently does **not** schedule this check because it has no originating Runtime Worker advisory destination. Browser edits must not be presented as checked. Adding that path later requires an explicit notification/audit destination rather than borrowing an unrelated Worker identity.
|
||||
|
||||
## Immutable input and content identity
|
||||
|
||||
The Job input contains only:
|
||||
|
||||
- canonical and user-facing Ticket IDs;
|
||||
- the exact saved item `content_digest`;
|
||||
- exact saved title and body;
|
||||
- whether the operation created or edited the item;
|
||||
- changed-field flags and the necessary previous title/body for edited fields.
|
||||
|
||||
The digest is SHA-256 of editable Ticket item content, matching `TicketDetail.content_digest`; it is not a `create`/`item_edit` event ID or an edit counter. Conversation history, user requests, agreements, Worker transcripts, and external facts are not captured. The generic Backend Job input limit is 128 KiB; an oversized exact snapshot is not truncated or misrepresented as checked and produces a server diagnostic after the Ticket remains saved.
|
||||
|
||||
## Execution and bounds
|
||||
|
||||
The trigger reserves durable intent synchronously after save, then dispatches without waiting for model completion. Concurrency pressure never rejects that intent: attempts above the four-Job cap remain durably `reserved`, and terminal attempt transitions plus restart recovery drain queued attempts through an atomic `reserved` → `dispatching` slot claim. It uses T-674's Backend-owned Job runner and an ordinary registered embedded Runtime Worker with `builtin:backend-job`. That profile selects the existing lightweight `codex-oauth/gpt-5.6-luna` model through normal model configuration and authentication. The checker has only `SubmitBackendJobResult`; it has no Ticket mutation, Queue, Worker management, conversation discovery, Web, Memory, or Workdir tools.
|
||||
|
||||
Each check is bounded to 4 concurrent Backend Jobs, 45 seconds, one attempt, an 8 KiB result, at most 8 findings, bounded finding fields, and the Backend Job runner's fixed input/instruction/delivery limits. The deterministic Job ID hashes the complete checker intent, including the saved content, edit context, instruction and source/notification identity; exact retries reuse a Job, while a different edit or source producing the same Ticket content does not collide with it. Generic Job and attempt records retain completed results and categorized dispatch, timeout, invalid-output, and other failures.
|
||||
|
||||
## Result and advisory behavior
|
||||
|
||||
`internal.ticket_item_checker` defines the text-only categories and exact structured output. Server validation denies unknown fields, rejects duplicate or excessive findings, bounds every string, and requires each quote to be an exact substring of the inspected saved title or body. Invalid output terminalizes the attempt with `invalid_result` rather than treating it as a clean check.
|
||||
|
||||
Before advisory delivery, the server reloads the Ticket and compares the current item content digest with the immutable checked digest. Findings for the current content are sent only to the authenticated source Worker over the existing tracked `Notify` path. A notification identifies itself as a post-save checker advisory, includes Ticket/content digest, exact quote, reason, and correction/confirmation direction, and explicitly states that it is not a user request, approval gate, workflow blocker, or implementation instruction.
|
||||
|
||||
Clean results and stale results send no Worker input. Their durable delivery records are terminalized with distinct suppression categories so restart recovery cannot later emit or repeatedly reclaim them. Notification failures remain separate from the completed checker result. A stopped or missing destination is never recreated or started.
|
||||
|
||||
## Authoring responsibility
|
||||
|
||||
`common.tickets` separately instructs Ticket authors not to turn their own current-turn scope into durable requirements and not to invent user constraints. Those rules use the author's request context. The independent checker deliberately does not infer user intent or constraint provenance and does not certify overall specification correctness when it returns no findings.
|
||||
@@ -1,82 +0,0 @@
|
||||
# Ticket native WIP projection
|
||||
|
||||
## Purpose and authority
|
||||
|
||||
When a Worker explicitly runs in `wip` mode and its Ticket Feature is enabled, the Worker Host projects the enabled Ticket surface as native Worldspace objects. The projection is an adapter over the existing scoped Workspace Ticket API and typed Ticket tools. It does not add a Ticket store, resolver, audit path, state machine, readiness rule, queue path, or relation implementation.
|
||||
|
||||
The Host assigns the Ticket provider the root `tickets` namespace through `WipMountRegistry` and mounts:
|
||||
|
||||
```text
|
||||
/tickets
|
||||
/tickets/<ticket-reference>
|
||||
```
|
||||
|
||||
There is no Feature Object or `/features/ticket` compatibility alias. A legacy path supplied as current input fails with rediscovery guidance; saved history containing that text is left unchanged.
|
||||
|
||||
`<ticket-reference>` is either a canonical `T-<digits>` key or an existing canonical internal alphanumeric ID. Only a direct child is accepted. Nested suffixes, Objective keys, unrelated prefixes, and hidden routes do not resolve.
|
||||
|
||||
Every call retains the Worker's scoped `WorkspaceClient`, current Ticket Feature access, and ordinary Tool permission policy. Discovery is not mutation authority. A direct call to an unpublished operation fails even when the caller knows its name. `deny` and interactive `ask` permission outcomes fail closed before Backend dispatch.
|
||||
|
||||
## Current operation inventory
|
||||
|
||||
The native surface is assembled from the tools actually enabled by `TicketFeatureAccess`; it is not a second broad Ticket catalog. The native operation delegates to the named ordinary Tool and validates the reconstructed input against that Tool's exact JSON Schema.
|
||||
|
||||
Collection interface `yoi.ticket/collection/v1`:
|
||||
|
||||
| Native operation | Existing Tool identity | Subject identity |
|
||||
| --- | --- | --- |
|
||||
| `query` | `QueryTicket` | Collection; preserves bounded filters, limit and cursor |
|
||||
| `create` | `TicketCreate` | Collection; Backend assigns identity |
|
||||
|
||||
Item interface `yoi.ticket/item/v1`:
|
||||
|
||||
| Native operation | Existing Tool identity | Non-subject inputs |
|
||||
| --- | --- | --- |
|
||||
| `read` | `ShowTicket` | `event_limit`, `event_cursor` |
|
||||
| `edit` | `TicketEditItem` | title/body replacement and targets edit |
|
||||
| `comment` | `TicketComment` | comment body |
|
||||
| `mark_ready` | `TicketMarkReady` | optional reason |
|
||||
| `intake_ready` | `TicketIntakeReady` | intake summary and optional reason |
|
||||
| `queue` | `TicketQueue` | none |
|
||||
| `transition` | `TicketWorkflowState` | expected/target state, reason and body |
|
||||
| `close` | `TicketClose` | resolution |
|
||||
| `dependency_check` | `TicketDependencyCheck` | none |
|
||||
| `record_relation` | `TicketRelationRecord` | relation kind, **target Ticket**, optional note |
|
||||
| `remove_relation` | `TicketRelationRemove` | relation kind and **target Ticket** |
|
||||
| `record_orchestration_plan` | `TicketOrchestrationPlanRecord` | kind, related Ticket, note and accepted-plan data |
|
||||
| `query_orchestration_plans` | `TicketOrchestrationPlanQuery` | relation kind and bounded limit |
|
||||
|
||||
The subject `ticket`/`id` parameter is removed from every item descriptor and reconstructed exclusively from the resolved route. A caller cannot replace it through arguments. A relation target or related Ticket remains an explicit, distinct input and is resolved by the existing typed Backend.
|
||||
|
||||
Access profiles therefore expose different native interfaces:
|
||||
|
||||
- read-only/review: collection `query`; item `read`;
|
||||
- workspace authoring: query/create plus read/edit/comment/ready/queue/close/relation operations;
|
||||
- intake: the authoring surface plus `intake_ready`;
|
||||
- workflow/orchestration: query/read/comment, workflow transition/close/dependency checks, relation operations and orchestration-plan operations;
|
||||
- work-report: query/read/comment only.
|
||||
|
||||
No native operation is inferred for a Tool that the Feature did not register. Enabled Ticket tools claimed by the native projection are removed from `/tools`; normal Tool mode is unchanged.
|
||||
|
||||
## Bounded reads and references
|
||||
|
||||
`query` uses the authoritative Workspace bounded query and cursor contract. Results include canonical `T-*` references and native item paths without embedding unbounded thread or evidence history. `read` uses the authoritative bounded detail projection for thread events, relations, Objective references, assignments, Merge Requests and evidence. Objective and Merge Request values remain references/data; this projection neither assumes their native routes nor grants their authority.
|
||||
|
||||
Backend 401/403 failures map to permission denial, 404 to missing objects, and deterministic 400/409/422 validation or workflow failures to invalid arguments when the typed operation fails before any successful mutation. Cancellation and interruption remain distinct. Post-mutation canonical-reference lookups are explicitly marked as unknown-outcome stages, so even a later deterministic HTTP status cannot misreport an already-committed write as a rejection. Transport, server, malformed response, and other post-dispatch failures retain unknown outcome, and mutating operations are not retried automatically.
|
||||
|
||||
Successful reads observe the authoritative item `content_digest`. Successful mutations advance a local observation generation even when the first call uses an internal ID whose canonical alias has not yet been learned. Once a read or mutation reveals the canonical key, the canonical/internal alias groups are merged and invalidated together. Relation mutations also invalidate the reported target Ticket, while queue results invalidate every reported queued Ticket, because those objects' incoming relations or workflow states changed. A stale validator is rejected by the common WIP Host before another Backend dispatch; the caller must rediscover/reinspect and explicitly decide whether to call again.
|
||||
|
||||
## Preserved Ticket contracts
|
||||
|
||||
The projection invokes the existing Tool and scoped Backend routes, so the following remain authoritative and are not reimplemented by WIP:
|
||||
|
||||
- item content digest and audited item/thread/state events;
|
||||
- title/body/targets input validation;
|
||||
- planning-to-ready target validation and write-target requirement;
|
||||
- queue dependency traversal, planning-dependency rejection and cycle detection;
|
||||
- expected-state workflow transitions and close semantics;
|
||||
- canonical `T-*`/internal-ID resolution and Workspace isolation;
|
||||
- exact forward relation ownership, inverse views and relation target validation;
|
||||
- orchestration-plan validation and bounded query behavior.
|
||||
|
||||
The ordinary JSON Schema is retained as a second validation boundary behind the WIP descriptor. Optional fields omitted by the caller stay omitted, while an explicitly supplied `null` stays present as JSON `null`, so permission matching sees the same input shape as normal Tool mode. Defaults are applied only by the same ordinary typed Tool that applies them in Tool mode.
|
||||
@@ -1,37 +0,0 @@
|
||||
# Tool permissions and scope
|
||||
|
||||
Yoi treats tools as explicit capabilities. Model-visible tool names are not permission by themselves; the resolved Manifest and Worker scope decide whether a call is allowed.
|
||||
|
||||
## Permission policy
|
||||
|
||||
Tool permissions are built into PreToolCall policy.
|
||||
|
||||
- `allow` permits the call to proceed.
|
||||
- `deny` rejects only that call with a synthetic error result.
|
||||
- `ask` fails closed until a real approval/resume protocol exists.
|
||||
|
||||
Failing closed matters because an unresolved approval state is not the same as permission. A future approval flow must be able to pause and resume the same unresolved call, not merely ask the model to retry later.
|
||||
|
||||
## Filesystem scope
|
||||
|
||||
Filesystem scope is separate from tool allow/deny. A file tool may be registered and permitted, but the concrete path still must be inside readable or writable scope.
|
||||
|
||||
Symlinks do not grant extra authority. Access decisions should be made on the canonical target when it exists, and broken or out-of-scope links should produce diagnostics rather than escaping scope.
|
||||
|
||||
Directory traversal tools should not follow symlink directories as a way around scope. If an external checkout is needed, add its real path to read scope.
|
||||
|
||||
## Delegation
|
||||
|
||||
Child Workers receive an explicit subset of the parent's scope. Delegation is a capability loan, not a copy of all parent authority.
|
||||
|
||||
When a child stops, shuts down, or is pruned as unreachable, delegated write permissions must be reclaimed. Explicit base denies remain in force.
|
||||
|
||||
## Tool output
|
||||
|
||||
Tool output should be bounded before it enters history/model context. The system may truncate or summarize mechanical output boundaries, but it should not hide the fact that a tool call happened or fabricate successful results.
|
||||
|
||||
Network tools such as WebSearch/WebFetch are disabled/unconfigured by default, fail closed, and need explicit manifest/profile configuration. Their outputs are untrusted content and must remain bounded.
|
||||
|
||||
## Why this design exists
|
||||
|
||||
LLM tool calls are suggestions from an untrusted planner. Yoi can let the model propose operations while keeping final authority in manifest policy, scope checks, and durable records.
|
||||
@@ -1,186 +0,0 @@
|
||||
# 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 pages sort the existing complete Repository catalog by stable
|
||||
key. Workdir pages use the signed caller-bound Backend
|
||||
`workers/self/workdir-catalog` operation, with SQL keyset paging over the existing
|
||||
Workdir registry in stable ID order. This is not the legacy bounded
|
||||
`working-directories` browser/Tool snapshot: Workdirs older than its 200-record
|
||||
cap remain reachable. `next_cursor` advances over scanned rows even when the
|
||||
current read policy or unsupported legacy source makes a page sparse or empty;
|
||||
`empty` describes that page, and `has_more` remains authoritative for progress.
|
||||
Attachment pages use the Backend's SQL-bounded, alias-ordered limit/offset query
|
||||
and opaque `offset:<n>` cursors. Both Workdir and attachment responses supply an
|
||||
opaque complete-set `digest` derived from existing authoritative records in
|
||||
the same database snapshot as the page, not by hashing the first page. The Host
|
||||
uses this complete-set digest for the collection Object validator. A lifetime
|
||||
or capability change beyond row 50 therefore invalidates old collection
|
||||
observations without adding a ledger or edit counter. 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. `Tree(path="/", depth=1)` finds `repositories`, `workdirs` and
|
||||
`workdir-attachments` when permitted.
|
||||
2. Inspect the Repository collection path and Invoke `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. Inspect the returned item `path` directly and Invoke `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`, Inspect directly and Invoke `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,95 +0,0 @@
|
||||
# Worker aggregate, Session, and run authority
|
||||
|
||||
A Runtime-managed Worker is the canonical durable aggregate. One Worker owns exactly one Session for its lifetime; compaction and forks create Segments inside that Session rather than replacing the Session identity.
|
||||
|
||||
This identity rule separates durable conversation history from execution attempts:
|
||||
|
||||
- **Worker ID** identifies the Runtime catalog aggregate and remains stable across stop/restore.
|
||||
- **Session ID** identifies that Worker's sole replayable history and remains stable across compaction.
|
||||
- **Segment ID** identifies a branch or compacted history projection inside the Session.
|
||||
- **run identity** identifies one process/controller execution attempt. Fresh spawn uses a unique run ID; Restore uses the durable lifecycle operation ID so reconciliation addresses the same attempt rather than allocating another controller.
|
||||
|
||||
## Canonical Runtime layout
|
||||
|
||||
Filesystem Runtime stores materialize one aggregate under `workers/<worker_id>/`:
|
||||
|
||||
```text
|
||||
workers/<worker_id>/
|
||||
worker.json
|
||||
metadata.json
|
||||
session/
|
||||
session.json
|
||||
segments/
|
||||
<segment_id>.jsonl
|
||||
<segment_id>.trace.jsonl
|
||||
runs/
|
||||
<spawn-run-id>/
|
||||
worker.sock
|
||||
worker.out.log
|
||||
worker.err.log
|
||||
artifacts/
|
||||
spawned/
|
||||
restore-<lifecycle-operation-id>/
|
||||
worker.sock
|
||||
worker.out.log
|
||||
worker.err.log
|
||||
artifacts/
|
||||
spawned/
|
||||
```
|
||||
|
||||
`worker.json` is the atomic Runtime catalog authority: Worker identity and Workspace attribution, the settled lifecycle status, the create/restore request and execution binding, plus any pending Restore or Stop reconciliation operation. `metadata.json` is the current Worker projection used to restore active/pending Segment pointers, resolved manifest state, delegation metadata, and child/peer visibility. It is not a second transcript.
|
||||
|
||||
`session/session.json` fixes the single Session ID for the aggregate. The Worker-specific Session store rejects attempts to address another Session ID. Session JSONL is the append-oriented replay authority for committed user inputs, assistant items, tool results, system/runtime events, Segment lineage, and effective snapshots required to explain later behavior.
|
||||
|
||||
The normal execution path resolves all three stores from the trusted `WorkerRef`. It does not fall back to process-global Session or Worker-metadata roots. Those roots are legacy migration inputs only.
|
||||
|
||||
## Segment lifecycle
|
||||
|
||||
A new Worker materializes its Session when the initial Segment is created. The Session ID then remains stable.
|
||||
|
||||
Compaction writes a new Segment in the same Session with `compacted_from` lineage. Forking likewise writes a sibling Segment in the same Session with `forked_from` lineage. Allocation, UI, and event surfaces therefore report the new **Segment ID**, never a "new Session". A different Session requires a different Worker aggregate.
|
||||
|
||||
This keeps existing Worker IDs, Session IDs, Segment references, observation entry references, and UI routes meaningful across compaction and restore.
|
||||
|
||||
## Run lifecycle
|
||||
|
||||
A lifecycle transition has a stable operation ID distinct from settled Worker status. Runtime commits a pending operation into `worker.json` before invoking a side-effecting Backend call:
|
||||
|
||||
- fresh spawn uses a unique run ID and commits the connected aggregate only after Backend startup succeeds;
|
||||
- Restore records `ReconciliationRequired::Restore(operation_id, mode, last_settled_status, restore request)` after preflight and before controller startup;
|
||||
- Stop records `ReconciliationRequired::Stop(operation_id, last_settled_status, execution evidence)` before requesting controller shutdown.
|
||||
|
||||
Restore run directories are named `restore-<operation_id>` and use `create_new` semantics. Retry and startup reconciliation reuse that operation ID. An in-process Backend returns an already registered controller for the same operation; after Backend restart, the existing operation-scoped directory prevents the same operation from allocating a second run generation. A different Restore operation receives a different operation ID and directory only after the previous operation has reached a proved terminal outcome.
|
||||
|
||||
Startup reconciles pending Stop operations before considering automatic Restore. A proved Stop commits `Stopped` atomically and is therefore never selected as an active restore candidate. Pending or uncertain operations remain orthogonal to the last settled status and block conflicting input, lifecycle, deletion, and retention operations.
|
||||
|
||||
Restore candidates keep protocol events and observation registration isolated until the Active aggregate commit succeeds. After that commit, Runtime activates the Backend observation registration and flushes buffered protocol events in order. A rollback discards the candidate context, so external observers cannot see a live candidate beside a settled `Stopped` catalog record.
|
||||
|
||||
Stopping a Worker waits for controller shutdown completion and removes `worker.sock`. Run directories and diagnostic files remain evidence. How old runs are retained, archived, or purged is a separate policy; aggregate creation and migration do not invent that disposition.
|
||||
|
||||
Live sockets and provider sessions are execution hints, not durable identity. Restore reconstructs Workspace client attribution, observation registration, and Workdir/provider bindings from Runtime/Backend authority while keeping the Worker and Session identities unchanged.
|
||||
|
||||
## Legacy migration
|
||||
|
||||
Startup migration recognizes the versioned `worker-aggregate-v1` format and treats legacy process-global metadata/Session directories as read-only sources.
|
||||
|
||||
The migration is serialized by an OS file lock and writes a versioned manifest with per-Worker checkpoints and diagnostics. For each unambiguous catalog Worker it:
|
||||
|
||||
1. validates Worker metadata identity and the referenced Session;
|
||||
2. parses Segment and trace JSONL, tolerating only the existing crash-truncated final-line rule;
|
||||
3. stages `session.json` plus all Segment files under the target Worker aggregate;
|
||||
4. fsyncs files and directories, atomically renames the staged Session directory, and fsyncs its parent;
|
||||
5. atomically writes and fsyncs `metadata.json`;
|
||||
6. atomically updates the migration checkpoint.
|
||||
|
||||
Reruns validate exact Session identity, the complete source/target filename set, and file bytes before accepting an existing target. Mixed old/new stores and a crash between Session rename, metadata copy, and checkpoint update therefore converge without replacing divergent data.
|
||||
|
||||
Migration fails closed on target collisions, corrupt complete JSONL records, or ambiguous ownership. If multiple legacy metadata sources reference one Session—including metadata with no catalog Worker—the Session is not assigned to either aggregate. The manifest records the shared reference. Legacy metadata or Session directories with no catalog-backed owner are also recorded as orphans and left untouched; they do not become normal authority and are not silently deleted.
|
||||
|
||||
Legacy sources remain available for audit and recovery after a successful copy. Archive/retention disposition and Orchestrator-driven Worker removal are intentionally outside this migration contract.
|
||||
|
||||
## Child, peer, and notification state
|
||||
|
||||
Parent-visible children and peer registrations are current Worker metadata, distinct from Session history and run liveness. Restoring a parent reconstructs reachable children where possible and retains stopped-but-restorable visibility when metadata supports it. Delegated write scope is a capability loan; stopping or pruning a child must reclaim the parent's effective permissions.
|
||||
|
||||
Peer registration does not grant filesystem authority, imply parent ownership, or make notifications completion proof. Notifications remain UX hints committed through the normal Worker history path. Completion decisions must reread durable Ticket, repository, review, and test evidence rather than relying on a socket event or final assistant message.
|
||||
@@ -1,169 +0,0 @@
|
||||
# Worker WIP mode
|
||||
|
||||
## Status and dependencies
|
||||
|
||||
Workers support two explicit model-facing modes:
|
||||
|
||||
```toml
|
||||
[worker]
|
||||
name = "example"
|
||||
mode = "tools" # default
|
||||
```
|
||||
|
||||
```toml
|
||||
[worker]
|
||||
name = "example"
|
||||
mode = "wip"
|
||||
```
|
||||
|
||||
Profiles select the same policy without owning the runtime Worker name:
|
||||
|
||||
```toml
|
||||
worker = { mode = "wip" }
|
||||
```
|
||||
|
||||
Omission resolves to `tools`, so existing manifests, profiles, persisted snapshots, and restored Workers retain the ordinary individual-tool surface. A direct Internal SubWorker selected with `profile = "inherit"` inherits the parent mode as reusable behavior. `default` and named profile selections use that profile's own mode and do not implicitly inherit WIP.
|
||||
|
||||
The implementation uses the published **WIP 0.2.0** Client, HTTP, Protocol and official Text View packages from **crates.io**. Exact workspace version constraints and Cargo.lock registry checksums reproduce one Protocol type source; no local attachment, path override, Git snapshot or old registry 0.1.0 substitutes for the release. Canonical scope/Text View contracts were checked against wip-reference **6086f3c5ef10aa1464ce9c824750d7f667217bfd**. See [validation and resolution checks](../development/wip-integration-validation.md). CI/Nix dependency acquisition requires no upstream Git credentials. Yoi retains its async provider/transport and existing execution boundaries; upstream owns Client cache/lifecycle, codecs and signatures.
|
||||
|
||||
## LLM surface and route contract
|
||||
|
||||
A WIP Worker exposes exactly these generic LLM tools instead of exposing every enabled ordinary tool twice:
|
||||
|
||||
- `Tree(path, depth)`: retrieve the full indexable range within depth 0..8, or report incomplete/loading/failed/limited coverage as an error. Children at the requested boundary remain unobserved (null), not empty. Cached nonindexable paths are never included by prefix scanning Known Space.
|
||||
- `Inspect(path)`: acquire and display exactly the selected entity. An Object path returns only its official Object signature and ordered Interface references (each with an explicit inspection path); it does not fetch Interface descriptors. An absolute `scope::name` Interface address returns only that Interface's complete official signature. Neither form requires a prior Tree or Inspect. The path is explicit JSON envelope context outside the path-free signature. Summaries are complete; runtime metadata and documentation details are not appended to signatures.
|
||||
- `Invoke(path, interface, operation, arguments)`: use a structured `{scope, name}` reference and named argument record. The Client owns validators and observed scope_ref. Missing/stale/failed observations are recovered with at most one pre-dispatch retrieval per subject. A dispatched operation is never automatically retried.
|
||||
|
||||
Tree and Inspect accept explicit `refresh: true` for blocked observations. Inspect refreshes only the selected Object or Interface, not referenced entities. An ensure API returning None is never itself readiness evidence: freshness, complete edge coverage and retained data are checked. Loading and capacity pressure are reported rather than superseded or busy-looped.
|
||||
|
||||
The Yoi binding uses the Text View compact reference notation as an **input address**, not a parser for rendered signatures. For example:
|
||||
|
||||
```json
|
||||
{"path":"/tickets"}
|
||||
{"path":"/::\"yoi.tool/Read/v1\""}
|
||||
```
|
||||
|
||||
The first inspects an Object; the second inspects one root-scoped Interface. Object results contain `object_signature` and `interfaces: [{reference: {scope, name}, path}]`, without Interface signatures. Pass an entry's `path` directly to Inspect to obtain `{path, reference, interface_signature}`. This explicit per-entity display selection is a Yoi binding policy; it replaces the earlier automatic expansion of every Interface. Invoke continues to acquire missing observations automatically and accepts structured references, never a compact string.
|
||||
|
||||
Reference components follow Text View quoting: scope allows unquoted `[A-Za-z0-9_./-]+`, name allows `[A-Za-z0-9_.-]+`; other components use JSON string quoting/escapes. There is no scope inference, normalization, relative resolution, or operation suffix. A literal Object path containing `::` must be JSON-quoted inside the `path` value (for example `{"path":"\"/literal::name\""}`) to distinguish it from an Interface address. Ordinary Object paths retain Protocol path syntax, including Unicode. Invalid or unavailable Interface addresses fail rather than falling back to Object lookup.
|
||||
|
||||
### Model usage guidance
|
||||
|
||||
The three tool descriptions are the recurring model-facing usage instructions, including after compaction. Use a shallow Tree only when needed to locate entrances, inspect only relevant Objects and unknown Interface contracts, and then work through Invoke. Reuse known paths and understood contracts for the **same exact scope/name reference**; do not inspect every node or repeat Inspect as a prerequisite for every operation. Same local names under different scopes are not evidence of the same contract. Reinspect only when a reference/contract is new, changed, or no longer available in the conversation context. Cache maintenance is the Client's responsibility, not a reason for extra model calls. Use domain read/list/search/query Operations for content and large/nonindexable collections. Current authorization checks and the prohibition on blindly retrying dispatched operations remain unchanged.
|
||||
|
||||
The compatibility projection mounts enabled ordinary tools at:
|
||||
|
||||
```text
|
||||
/tools/<exact-tool-registration-name>
|
||||
```
|
||||
|
||||
Each compatibility Object publishes an explicit root-scoped reference:
|
||||
|
||||
```text
|
||||
{"scope":"/", "name":"yoi.tool/<exact-tool-registration-name>/v1"}
|
||||
```
|
||||
|
||||
Its single operation is `call`. For this compatibility route, `Invoke.arguments` is `{ "input": <original ordinary tool argument object> }`, matching the descriptor's named `input` parameter. Native projections instead receive a JSON object keyed by the descriptor's declared parameter names. Native input is decoded with the published `wip-http` descriptor-bound codec, so named and composite types are resolved, an integral JSON number remains a WIP `Number` when declared as such, and WIP `Bytes` use the codec's canonical RFC 4648 base64 JSON representation. Tool argument IDs remain JSON values; the adapter never infers a domain object or Worldspace route from them.
|
||||
|
||||
Compatibility input is projected conservatively into JSON-shape-preserving WIP types: primitives, closed records with required/optional fields, homogeneous lists, and string enums. Local JSON Pointer references are expanded within bounded depth/node budgets; cycles and unsupported constructs stay `Json`. Open records, nullable/mixed unions, composition, and tuple elements are not falsely modeled as closed records or tagged WIP unions. The original input JSON Schema, including constraints, descriptions, defaults, and local definitions, is included in the **visible parameter summary** as well as retained documentation details. Thus a single Interface Inspect reveals the usable input contract even where WIP's type system cannot express it or an expansion budget is reached; it is never just an opaque `input: json` with hidden instructions. Full Inspect output still obeys response limits and is not silently truncated.
|
||||
|
||||
The call envelope remains `{ "input": <original JSON value> }`; no tagged union wrapper, field renaming, or default insertion is introduced. Before execution, the Host validates the reconstructed JSON value with the exact original schema and then delegates to the original async `Tool`. Structural projection never replaces original-schema constraints or permission checks. A schema that cannot be compiled prevents WIP startup instead of creating a weaker projection.
|
||||
|
||||
## Native projection extension and collisions
|
||||
|
||||
`worker::wip::WipMountRegistry` is the Host-owned namespace, Object, and operation registry. A provider requests a root namespace with `allocate_namespace(owner, namespace)`; the owner is retained as Host metadata and is not inserted into the public path. It then mounts the collection Object directly at `/<namespace>` and may install a bounded direct-child resolver. Features do not appear as intermediate Objects.
|
||||
|
||||
Object resolution and operations are separate contracts. One mounted `WipProjection` owns a static Object route and its initial operations. Another Feature may add operations with `contribute_operations` without replacing that Object. Dynamic families likewise keep one `WipDynamicItemResolver`, while `contribute_dynamic_operations` supplies disjoint handlers for every resolved item without a second resolver. The Host merges only contributions with the same interface format, documentation, and type declarations; duplicate operation names, inconsistent interfaces/validators, missing targets, duplicate namespace owners, and unrelated route claims fail deterministically at registration. The merged interface validator changes with the effective operation set, so stale Known Space cannot authorize a newly contributed operation.
|
||||
|
||||
- Routes must be canonical absolute WIP paths, and allocated namespaces are one lowercase root segment.
|
||||
- Object names must match the final route segment.
|
||||
- Object existence and resolution have one owner; operation contributors cannot remount or substitute the Object.
|
||||
- Multiple Features may contribute disjoint operations to the same Object interface.
|
||||
- Unrelated duplicate routes, duplicate operation names, and conflicting interface shapes fail startup.
|
||||
- A bounded dynamic mount may resolve exactly one item segment below an already mounted native collection; deeper paths never fall through to the resolver.
|
||||
- A native projection replaces a compatibility projection either by the original same-route semantic capability or by an explicit registry capability claim owned by a mounted native route.
|
||||
- Conflicting native claims fail startup. When native is selected, the claimed compatibility tool is hidden rather than exposed through a second entry. Normal Tool mode is unaffected because it does not install the WIP registry.
|
||||
|
||||
`Inspect` delegates to official `wip-text-view::render_object` and `render_interface`; it renders the complete effective descriptor, including contributed operations, descriptor-local named declarations, and recursive record, list, enum, and union shapes with required flags and declaration documentation. The tests `host_namespace_object_resolution_and_operation_contributions_are_independent`, `operation_contributions_reject_interface_and_target_conflicts`, and `native_projection_preserves_descriptor_and_decodes_typed_arguments_end_to_end` demonstrate root allocation, unique resolution, contribution merging, collision rejection, current call-time authorization, complete descriptor inspection, and descriptor-typed invocation.
|
||||
|
||||
The built-in native route migration is:
|
||||
|
||||
| Historical route | Canonical route |
|
||||
| --- | --- |
|
||||
| `/features/ticket/tickets[/<reference>]` | `/tickets[/<reference>]` |
|
||||
| `/features/objective/objectives[/<reference>]` | `/objectives[/<reference>]` |
|
||||
| `/features/merge-request/merge-requests[/<reference>]` | `/merge-requests[/<reference>]` |
|
||||
|
||||
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
|
||||
|
||||
1. Allocate `allocate_namespace("repository", "repositories")`. The returned `WipNamespaceRoute::root()` is `/repositories`; the owner is independent of any Feature registration name.
|
||||
2. Mount a native collection `WipProjection` at that root with capability `repository:collection`, matching Object name and one declared interface. Static Objects underneath the root use the same capability owner prefix. A mounted collection retains its own identity, interfaces, and validator even when it has static children.
|
||||
3. For dynamic items, register one `WipDynamicMount` on the collection with capability `repository:item`. A foreign capability owner is rejected. Its `WipDynamicItemResolver` owns reference acceptance, Object identity, and current Object validator. It returns only a bounded direct child; it must not reinterpret a reference as another namespace or grant permission from existence.
|
||||
4. The reference provider supplies its read operations and handlers independently of management Feature enablement. An Object-only provider may use an empty operation descriptor. Enabled management Features register `WipOperationContribution` for static Objects or `WipDynamicOperationContribution` for a dynamic family; they do not allocate the namespace again or replace its resolver.
|
||||
5. Contributions use the existing interface reference and identical format, documentation, and type declarations, supplying only their new operations. Interface references are exact (scope, name) keys: extending a descriptor that is also mounted elsewhere must not create different descriptors/validators under the same reference (registration fails). Use separate references for collection and item contracts. Failed contributions leave the registry unchanged.
|
||||
6. Handlers are responsible for checking current target capability and current subject authorization before dispatch. Registration/discovery is metadata, not a grant. A resolver/handler must retain the original permission identity and domain audit/failure contracts. Revocation after discovery must fail before provider dispatch. Compatibility mounts are restricted to `/tools/<tool-name>` and cannot bypass namespace ownership or republish legacy routes.
|
||||
|
||||
Tests `management_enablement_is_independent_of_read_and_operation_permissions` and `contributed_interface_invalidates_observations_and_restore_requires_discovery` cover all eight enablement/read/manage combinations, direct-call rejection, effective descriptor changes, fresh runtime isolation under the same Worker identity, reset, and post-observation revocation.
|
||||
|
||||
## Authority and execution
|
||||
|
||||
Discovery is not authority. Object publication, operation implementation, target capability, and subject permission are independent checks. An Object may remain discoverable through an enabled read provider when a management Feature contributes no operations; conversely, publishing an operation never bypasses the target's current capability or the subject's current permission. Every handler re-evaluates permission at call time, so revocation after discovery is rejected before provider dispatch. Feature enablement selects implementations and does not itself grant read or mutation authority.
|
||||
|
||||
The Worldspace contains only the operations registered from enabled Features and the Worker's current execution context. Calling a compatibility operation:
|
||||
|
||||
1. resolves the exact mounted route;
|
||||
2. verifies object and interface membership and validators;
|
||||
3. validates the WIP descriptor call;
|
||||
4. validates the original JSON Schema;
|
||||
5. evaluates manifest tool permission rules against the **original tool name and original arguments**;
|
||||
6. invokes the original async `Tool` without blocking the Tokio runtime.
|
||||
|
||||
Filesystem scope, provider Workdir capability checks, Backend Workspace authority, Feature enablement, and input-specific restrictions stay in the original tool implementation and are checked again at invocation. `ask` permissions remain denied fail-closed because the runtime has no approval protocol. The gateway does not mint authority or expose authentication material.
|
||||
|
||||
Pre/post Engine history still records one bounded `Invoke` result, while the compatibility result retains the original `ToolOutput`, including attachments and normal Engine output pruning. WIP call audit records retain the route, operation, request identity, and terminal classification.
|
||||
|
||||
## Client lifetime, restoration, and compaction
|
||||
|
||||
The stateful `wip-client::Client` is owned by one Worker WIP runtime. Sessions are keyed by the canonical endpoint and an opaque security-context identity derived by the Host from Workspace, Worker, and session identity. Authentication credentials are not included in that identity and are never emitted to descriptors, tool output, or history.
|
||||
|
||||
Known Space and interface observations are therefore not shared across Workers, endpoints, or security contexts. A restored Worker creates a new runtime/client and must explore again. `refresh = true` explicitly supersedes requested retrievals. Old string references in saved history remain historical text, not cache/wire authority; observations are runtime-only and are not persisted or restored. Resume by inspecting the target path, not by migrating an opaque display string. A normal compaction keeps the live runtime but the three gateway schemas are supplied again on every LLM request, including direct Inspect and indexable Tree semantics; the compacted transcript is not treated as cache authority.
|
||||
|
||||
Saved Session/history entries remain append-only evidence and are never rewritten or replayed as calls. In particular, historical `/features/<feature>/...` text stays displayable but is not a current route alias. Passing such a legacy path to discovery or call returns `NotFound` with guidance to rediscover from `/`; the Host does not translate it, resolve it under another namespace, or expose old and new routes in parallel.
|
||||
|
||||
## Errors, cancellation, and retries
|
||||
|
||||
The adapter keeps these outcomes distinct:
|
||||
|
||||
- **rejected/not dispatched**: route, membership, validator, descriptor, schema, or permission failure; the original operation does not run;
|
||||
- **stale**: object/interface validator mismatch; refresh before reconstructing a call;
|
||||
- **cancelled**: the original provider confirms cooperative cancellation and its bounded cancelled output is retained;
|
||||
- **interrupted**: provider interruption is retained separately from cancellation;
|
||||
- **disconnected/timeout after dispatch**: `wip-client` records an unknown outcome;
|
||||
- **operation outcome unknown**: an executing provider fails without a rollback/non-execution guarantee, a response cannot be completed after dispatch, or the call future is force-closed.
|
||||
|
||||
Cooperative cancellation is scoped to the active execution's selected operation and exact resolved handler instance. Static/dynamic dispatchers forward `cancel_operation` only to that operation's provider, once per cancellation request, never to unrelated contributors or every alias backed by a shared handler. The runtime pins the dispatcher used for execution rather than resolving the Object again on cancellation, since dynamic resolvers may return stateful instances. Providers may implement `cancel_operation` for operation-specific control or the shared `cancel` default. Terminal completion or a dropped execution future releases the active binding; unknown/finished execution IDs do not invoke any provider. A cancellation request is not itself terminal confirmation.
|
||||
|
||||
An unknown mutating outcome must never be retried automatically. The model must inspect external state or ask for operator guidance. Retrieval failures may be explicitly refreshed.
|
||||
|
||||
## Measurement
|
||||
|
||||
The runtime tracks bounded counters for:
|
||||
|
||||
- ordinary individual-tool schema bytes hidden by WIP mode;
|
||||
- the three WIP gateway schema bytes;
|
||||
- discover, descriptor-inspection, and operation round trips;
|
||||
- successful operations.
|
||||
|
||||
These values are returned with discovery/inspection output and are intended for comparing prompt input volume, exploration round trips, and representative operation success with ordinary Tool mode. No fixed reduction percentage is promised.
|
||||
|
||||
## Scoped publication and display boundaries
|
||||
|
||||
References are structured everywhere; wire requests reject legacy/display strings. Root and self scopes are valid; ancestry uses canonical path segments, not string prefixes. Contextual descriptors use the resolved Object path as their explicit self scope, not an encoded suffix on the local name. Fetching a self-scoped Interface freezes descriptor/validator and optional scope Object ref from the same live projection. A scope without a ref is valid; the adapter does not invent one. Calls check target existence, Object preconditions, scope/membership/ref, then Interface preconditions and arguments, with the selected descriptor and handler pinned for execution/result validation. Existing providers repeat current authorization and their captured Workdir/Workspace metadata checks at their execution/commit boundary. The registered artifacts alone are not call authority.
|
||||
|
||||
Separate Interface namespaces on one static Object can be registered with `mount_interface`; the owner/identity cannot be replaced and duplicate Interface keys fail. Inspect preserves each Interface reference, even with identical operation names, and Invoke selects the exact namespace. Contribution merging retains one Interface and disjoint operation names.
|
||||
|
||||
Official signature quoting, escapes and summary layout are preserved verbatim. Host documentation is untrusted data in Tool output, not instructions. RenderError categories and the failed path/reference are reported as display failures. Library input limits and Yoi envelope limits reject the entire display rather than truncating it or reporting no operations. No signature parser, shorthand-input parser, independent lexer, or custom escape engine is installed.
|
||||
|
||||
Typed Entry results remain values; no eager result acquisition, guessed string-path conversion, or inferred indexable edges occur. A later Inspect failure never reclassifies a completed successful operation. FS indexable/list policy is a separate provider concern (T-714), not Client-side search/ranking.
|
||||
@@ -1,252 +0,0 @@
|
||||
# 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, content-addressed virtual tree:
|
||||
`workspace_config_trees`, `workspace_config_entries`, and append-only
|
||||
`workspace_config_tree_history`. `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_digest` of the complete source tree, changes with expected entry digests, and the `main.dcdl`
|
||||
entrypoint. Candidate evaluation applies changes, formats/normalizes Decodal,
|
||||
and evaluates the composed schema. Commit rechecks both the tree digest and the
|
||||
base toolchain/projection identity in an SQLite Immediate transaction before
|
||||
replacing active entries. Source content remains immutable and digest-addressed;
|
||||
evaluation provenance is retained by source, toolchain and projection identity,
|
||||
so reevaluating unchanged source does not erase earlier schema/compiler evidence.
|
||||
A candidate prepared before a schema-only refresh cannot reactivate its old
|
||||
contract. 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 digest-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.
|
||||
|
||||
Explicit grants do not replace the UI editor's user authentication. Authored
|
||||
source-tree read/entry/history/commit routes and the UI Profile settings catalog
|
||||
reject Runtime/Worker source proofs in both single-Workspace and server-scoped
|
||||
middleware, including workers with a read-write grant. `workspace.request` is
|
||||
not a configuration-editor capability: granted Workers use the WIP adapter,
|
||||
never the UI editor as an alternate access path. User/session/API-token editing
|
||||
continues to share the canonical validation and CAS boundary with WIP saves.
|
||||
|
||||
The audit found no separate Profile/Prompt/Runtime/Skill source-save endpoint;
|
||||
authored writes converge on source-tree commit. Existing runtime consumption of
|
||||
Prompt projections, Skill catalog/activation and resolved config bundles remains
|
||||
a separate read-only/activation contract needed for Worker startup and normal
|
||||
operation. Those evaluated projections are not authored source-tree bodies,
|
||||
content history or a second editor. Do not fence all `/config` routes as one
|
||||
capability: that would break the existing Prompt reader's runtime contract.
|
||||
|
||||
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 digest and path validators from the same snapshot.
|
||||
Opaque validators bind Workspace, Worker, grant, connection lifetime, logical
|
||||
path, whole-tree digest and active/applied evaluation contract. Identical source
|
||||
paths, content types and bytes retain the same digest; saving them again under
|
||||
the same contract does not create an edit counter or invalidate a content
|
||||
observation by itself. A schema/toolchain refresh invalidates observations even
|
||||
when source bytes are unchanged. 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`, tree digest,
|
||||
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 commit 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.
|
||||
@@ -1,37 +0,0 @@
|
||||
# Workspace Web Drive
|
||||
|
||||
The Web Drive is a latest-only shared Workspace resource, independent of Git, repositories, Worker sessions and Workdir occupancy. The Server's DB node tree, last committed request, receipt and current authorization are authoritative. The UI never uses physical blob keys/paths or stores a second persistent hierarchy.
|
||||
|
||||
## References and authorization
|
||||
|
||||
`/w/<workspace>/drive/<node-id>` is a human-facing stable latest URL. Its node ID remains a canonical positive decimal **string** (including values above JavaScript's safe-integer range). Rename/move retain the URL; deleting and recreating a name does not revive the deleted ID. Copy latest URL emits this Web route, not the API's safe binary download URL. Ticket/Objective/comment Markdown can link to this route. Possessing any URL/ID grants no access: metadata, content, mutations, download and receipt queries all use the current authenticated API.
|
||||
|
||||
Ordinary Drive usage retains the existing member-facing API policy, not owner-only settings policy. Worker grants are owner-controlled on the existing Workers page, using its current authoritative Worker catalog. Read-only/read-write are explicit Backend grants, not consequences of enabling a Profile tool. The UI does not invent a Web read-only ACL: Backend denial is displayed and suppresses mutations. Worker read-only denial and revoke are separately exercised against the real API.
|
||||
|
||||
## Drafts, observed changes and delayed operations
|
||||
|
||||
The optional node route reuses one controller across Workspace/entry switches. Drafts and mutation receipts are keyed by Workspace/node identity in this page session. Reads/writes/status/list/search/ancestor/image completion are generation fenced, including A→B→A. No delayed response can select a new target or change its draft/save destination. Leaving the Drive page prompts before destroying a dirty draft or unresolved receipt; they are not claimed to survive a browser reload.
|
||||
|
||||
Text edits retain the `last_mutation_id` returned with the read and send it as `expected_mutation_id`. A conflict preserves the draft and that original observation, displays an explicit conflict and never silently rebases, automerges or resubmits. Refresh can show the latest saved text alongside the draft. Discard/load-latest is an explicit destructive user decision. Partial previews cannot be saved. Save acknowledgments retain edits made while saving.
|
||||
|
||||
## Transfer and media policy
|
||||
|
||||
- Markdown/plain text: 64 KiB UTF-8 read/edit limit. Markdown uses the existing raw HTML-rejecting safe document renderer. Other active/binary media are download-only.
|
||||
- Raster image preview: PNG/JPEG/GIF/WebP, authenticated committed-request-bound fetch, 256 KiB bounded bytes/Blob; decode/load failure is explicit. Object URLs are released on switching/destruction. Larger images remain downloadable.
|
||||
- Download: native same-origin authenticated binary endpoint, bound to the observed committed request; Server's attachment/nosniff/sandbox/no-store headers remain authoritative.
|
||||
- Markdown inline images never automatically fetch external/cross-Workspace/API resources. They display alternative text and require explicit navigation to an authorized Drive URL. Same-Workspace resource links navigate normally; external HTTP(S) links have no userinfo, open a new tab with `noreferrer`; script/data/file, protocol-relative, arbitrary API/relative paths are not links.
|
||||
- Upload/replacement: at most 16 MiB raw Blob request, not JSON/base64 or DOM bytes. SHA-256 needs one bounded ArrayBuffer; there is no constant-memory browser hashing claim. Transfer/pending is distinct from the DB-published acknowledgment.
|
||||
- A request ID is allocated before a mutation. Transport/response loss or abort after transmission is conservatively unknown, not success/failure. Its current authorized receipt endpoint is queried; no new request or automatic reupload is generated. `uncommitted` means no commit observed yet, not proof that an in-flight publication cannot still finish. Unknown requests block new writes to that entry.
|
||||
|
||||
Name conflict, changed-content conflict, limit, denied, missing node, storage/I/O failure and outcome unknown are not translated into empty contents/empty Drive/success. Initial folder deletion is empty-only with explicit confirmation; no recursive operation or blob-copy rename is introduced.
|
||||
|
||||
## Validation
|
||||
|
||||
Browser API fixtures and real authority evidence are intentionally separate:
|
||||
|
||||
- `web/workspace/src/lib/workspace/drive/*test.ts`: malformed payload/ID and transport/state races at the client/controller boundary.
|
||||
- `drive-ui`/`drive-grants` component tests: actual safe rendering/interaction.
|
||||
- `web/workspace/test/drive-browser`: production static shell + synthetic API fixtures, pinned Chromium interactions and visual evidence.
|
||||
- `crates/workspace-server/src/server/tests/drive_web_roundtrip_tests.rs` and `web/workspace/test/drive-real-api`: hermetic real Server HTTP, SQLite, FeatureStorage and LocalFileSystem, repositoryless bootstrap and production Web adapter. Only the Runtime process boundary is fake; no live dogfood data is used.
|
||||
|
||||
See the test READMEs and T-724 validation record for exact commands/results, artifact paths, source commits and any deliberate failing fixture state.
|
||||
@@ -1,391 +0,0 @@
|
||||
# Workspace Drive storage
|
||||
|
||||
## Scope and ownership
|
||||
|
||||
Drive is a Workspace-owned **latest-version document tree**, not a mounted or
|
||||
POSIX-compatible filesystem. `workspace-drive` owns storage, hierarchy, conflicting
|
||||
changes, mutation receipts, bounded reads/search, and garbage collection.
|
||||
`WorkspaceApi::drive` is the trusted Server adapter. The authorized HTTP API and
|
||||
Backend-managed grants are documented below. Worker Tools/WIP, Web UI, and full
|
||||
end-to-end routing belong to T-723–725.
|
||||
Session attachments remain Session-owned and are not Drive authority. Worker,
|
||||
Session, and Workdir cleanup do not delete Drive.
|
||||
|
||||
Only a single Server Host with persistent local disks is supported initially.
|
||||
Multiple processes must use **the same metadata DB and blob root**. Separate
|
||||
Hosts/independent DB copies may not write the same Drive. Workers on other Hosts
|
||||
use the authorized API, not direct storage access. S3 deployment/migration,
|
||||
POSIX/inode semantics, histories, mounts, and distributed quotas are not included.
|
||||
|
||||
## Configuration and persistent volumes
|
||||
|
||||
Server startup uses the existing resolved Yoi data directory. `ServerConfig` keeps
|
||||
that resolved path in `data_root`; Drive code does not read environment variables
|
||||
or introduce another default. At the default layout:
|
||||
|
||||
```
|
||||
$YOI_DATA/drives/<internal-workspace-id>/blobs/<opaque-UUID>
|
||||
$YOI_DATA/server/server.db
|
||||
$YOI_DATA/server/feature-storage/<internal-workspace-id>/features/workspace-drive.sqlite
|
||||
```
|
||||
|
||||
The FeatureStorage root is derived from the authority database path using the
|
||||
existing Host convention; it is **not** placed below `drives`. Custom database
|
||||
placements remain possible: configure the trusted `ServerConfig.data_root`
|
||||
explicitly and persist both locations. Bind `$YOI_DATA` (and any custom database
|
||||
root) to persistent volumes, not container writable layers, Worker checkouts,
|
||||
or temporary directories. The storage identifier is the immutable internal
|
||||
Workspace ID, never its display name or a user-supplied path. No blob key,
|
||||
physical path, credentials, or SQL surface is included in node/read DTOs. Internal
|
||||
storage errors contain diagnostics for Host logs; the authorized API adapter must
|
||||
map them to sanitized failure responses, not expose raw error strings.
|
||||
|
||||
The directories and every ancestor are Host-owned and must not be writable by
|
||||
untrusted actors. Existing symlink components, symlink blobs, non-directory
|
||||
ancestors, nested directories, and path traversal are rejected. This is not a
|
||||
sandbox against a hostile local administrator replacing paths concurrently:
|
||||
`LocalFileSystem` uses path-based operations, not descriptor-relative no-follow
|
||||
operations. External edits of the store are outside the Drive operation contract.
|
||||
|
||||
## Metadata and tree contract
|
||||
|
||||
Migration 1 registers `workspace-drive` through FeatureStorage. Each scoped DB
|
||||
contains its Workspace binding, nodes, deletion tombstone, and mutation receipts.
|
||||
SQLite `AUTOINCREMENT` node IDs are stable, scoped to a Workspace and never
|
||||
reused after deletion. They are opaque application IDs, not inodes. Node IDs serialize as canonical positive **decimal strings**, preserving signed
|
||||
64-bit precision across JavaScript DTO boundaries. `last_mutation_id` is the
|
||||
existing request ID of the last committed mutation, not a counter or timestamp.
|
||||
The immutable root has an empty value. Restore preserves `sqlite_sequence`,
|
||||
node IDs, request IDs and receipts.
|
||||
|
||||
The DB is the only tree/latest-reference authority. Each node has one parent;
|
||||
root is the sole parentless directory, has empty name, and cannot be renamed,
|
||||
moved, deleted, or written. All other names are 1–255 UTF-8 bytes, compared
|
||||
case-sensitively with SQLite binary equality. No Unicode normalization or case
|
||||
folding is performed: canonically equivalent strings are distinct. Japanese is
|
||||
supported. Control characters, `.`, `..`, slash and backslash are rejected. Names
|
||||
never become blob paths. Same-parent names are protected by a unique DB index.
|
||||
Parents must be directories in this scoped Workspace. Self/descendant moves are
|
||||
rejected. Ordinary folder deletion is **empty-only**, never implicitly recursive.
|
||||
Workspace destruction is the separate fenced purge operation.
|
||||
|
||||
Every update/move/delete requires `expected_mutation_id`. The same IMMEDIATE
|
||||
transaction compares it with the node’s last successful request ID before writing.
|
||||
A different committed request or
|
||||
same-name/request-identity competitor returns `Conflict`; it never falls back to
|
||||
unconditional writes. Root mutations and malformed requests are `Invalid`;
|
||||
missing/deleted IDs are `NotFound`. No update counter is allocated or incremented.
|
||||
Actor identity is trusted caller input (the authorization adapter supplies it),
|
||||
and successful mutations record actor and UTC epoch milliseconds.
|
||||
|
||||
`BEGIN IMMEDIATE` is held for validation, blob operations, metadata mutation and
|
||||
receipt insertion. It serializes the whole Workspace hierarchy and all blob
|
||||
read/upload/GC activity across independent processes. A→B/B→A, parent deletion
|
||||
versus child creation, and updates based on the same observation therefore have one valid winner.
|
||||
FeatureStorage configures foreign keys, WAL, `synchronous=FULL`, and a 5-second
|
||||
busy timeout. Busy/SQL/I/O failures remain errors; they do not mean an empty Drive.
|
||||
Before opening SQLite or admitting a Feature handle, FeatureStorage creates and
|
||||
syncs **every metadata directory ancestor link**, including configured path and
|
||||
resolved target chains for trusted Host symlink paths. It re-syncs existing
|
||||
directories on retry after any prior flush failure. Directory sync failure aborts
|
||||
admission before DB initialization/migration; no usable connection is cached.
|
||||
SQLite's DB/WAL and immediate-directory synchronization alone would not protect
|
||||
new FeatureStorage/Workspace ancestors. Both this metadata link durability and the
|
||||
separate blob link durability below are required for acknowledged Drive success.
|
||||
|
||||
The Server uses `Drive::open_with_workspace_authority` to attach its trusted
|
||||
`server.db` to the Feature DB connection. An IMMEDIATE transaction also locks that
|
||||
attached authority, and checks that the Workspace remains `active`. Consequently
|
||||
a cached Drive handle cannot mutate after the deletion reservation commits, even
|
||||
before the separate Drive tombstone is persisted. Drive never writes the
|
||||
attached authority; only its metadata DB is changed. This conservative initial
|
||||
implementation also serializes against other Server DB writes during blob I/O.
|
||||
There is no cross-DB atomic write promise or distributed transaction: only the
|
||||
Drive DB is written. Long uploads/searches can increase Server write contention;
|
||||
all calls are blocking and async callers must use `spawn_blocking`.
|
||||
|
||||
## Blob publication and durability
|
||||
|
||||
Content creates/updates choose a fresh canonical UUID blob key unrelated to names
|
||||
or parents. `object_store` **0.13.2**, with the `fs` feature and without cloud
|
||||
features, supplies `local::LocalFileSystem`. `PutMode::Create` forbids overwrites.
|
||||
Rename/move change metadata only; no blob move/copy/stat is needed. List/metadata
|
||||
queries are SQL-only and still work if a referenced blob is missing, whereas
|
||||
content reads diagnose the missing reference.
|
||||
|
||||
The pinned local implementation writes a `create_new` staging file, publishes
|
||||
Create with `hard_link`, and best-effort removes staging. Inspection of 0.13.2's
|
||||
local implementation found **no `with_fsync` option and no fsync** ([versioned
|
||||
source](https://docs.rs/object_store/0.13.2/src/object_store/local.rs.html)).
|
||||
The dependency is pinned to 0.13.2 because staging cleanup also depends on its
|
||||
local filename protocol; reinspect publication/staging/durability before upgrading.
|
||||
Atomic
|
||||
publication is not power-loss durability. The small adapter compensates:
|
||||
|
||||
1. Create/validate Host directories; `sync_all` their entries and parents.
|
||||
2. Use LocalFileSystem to atomically create the immutable blob.
|
||||
3. `sync_all` the published file, then its containing directory.
|
||||
4. Only after both succeed, update the DB reference/last committed request and insert the
|
||||
receipt in the **same SQL transaction**.
|
||||
5. SQLite commit is the sole success/publication point.
|
||||
|
||||
Delete also syncs the directory, including on a missing-file retry. Sync, disk
|
||||
full, permissions, and other errors propagate; metadata is not committed if blob
|
||||
persistence fails. A failed sync may leave an unreferenced blob for GC. If DB
|
||||
commit fails, the old reference remains and the new blob is likewise reclaimable.
|
||||
There is no external-store/DB two-phase commit or bespoke object-store engine.
|
||||
|
||||
Supported durability target: **Linux, local persistent filesystem with working
|
||||
file and directory fsync and SQLite WAL locking**. Filesystems/drivers that do not
|
||||
support these calls fail rather than silently downgrade. Windows, network
|
||||
filesystems, and other OS/storage combinations have not been qualified. Disk
|
||||
controllers and devices must honor flushes. Unit tests verify ordering/failure
|
||||
semantics with injected errors and actual isolated files/SQLite; they are **not a
|
||||
power-cut experiment** and do not prove hardware power-loss behavior.
|
||||
|
||||
## Request result reconciliation
|
||||
|
||||
Every mutation includes a bounded request identity (1–128 bytes) and trusted
|
||||
actor (1–256 bytes). A SHA-256 fingerprint covers the actor and typed mutation,
|
||||
including content. Its committed result is stored with metadata in the same
|
||||
transaction. Replaying the exact same request returns the recorded result;
|
||||
reusing the ID with different input/actor is `Conflict`. Results are retained for
|
||||
the Workspace lifetime, including results referring to later deleted nodes.
|
||||
There is no clock-based deduplication window or partial FS-recovery engine.
|
||||
|
||||
After response loss or uncertain SQL/I/O errors, query `request_status`:
|
||||
`Committed` includes the original result; `Uncommitted` means no committed row in
|
||||
that authority snapshot. It is **not proof that an in-flight concurrent request
|
||||
can never commit**. Retry with the same identity/payload, never manufacture a new
|
||||
ID to resolve an unknown outcome. Failed/uncommitted operations have no success
|
||||
receipt. A committed result can still be queried/replayed after the Drive is
|
||||
fenced; it does not authorize new mutation.
|
||||
|
||||
## Bounds, pagination, reads and search
|
||||
|
||||
Hard limits are exported constants, not additional environment variables:
|
||||
|
||||
| Operation | Limit |
|
||||
|---|---:|
|
||||
| File create/update | 16 MiB (empty files supported) |
|
||||
| Binary/text read chunk | 1 MiB, positive requested length |
|
||||
| List page / GC page | 200 nodes / 200 blob keys + up to 200 staging files |
|
||||
| Search work per page | 128 nodes |
|
||||
| Text inspected per candidate | 64 KiB |
|
||||
| Search query | 256 UTF-8 bytes |
|
||||
| Name | 255 UTF-8 bytes |
|
||||
| Content type | 128 ASCII bytes, media-type separator required |
|
||||
|
||||
No offset writes or shared write handles exist; whole content is replaced. Size,
|
||||
invalid bounds and invalid UTF-8 errors are explicit `Invalid`; storage/capacity
|
||||
errors remain failures for the API to present. There is no quota reservation.
|
||||
Request and node/receipt volume remain bounded by available Host disk capacity;
|
||||
operators must monitor free space for both blobs and DB/WAL. Files/receipts can
|
||||
consume space until GC/Workspace deletion; there is no distributed quota system.
|
||||
|
||||
List uses ascending stable ID keyset pagination. Metadata is not a multi-call
|
||||
snapshot: concurrent nodes may appear/disappear and relocation can change page
|
||||
membership. Continuation is the last examined ID, not an offset or name. Search
|
||||
uses the same non-snapshot ID semantics, examining at most its work budget per
|
||||
page; an empty matching page can still have continuation. Name search is exact
|
||||
substring matching. Optional bounded text search inspects only UTF-8 `text/*`
|
||||
files no larger than 64 KiB, never images/large files. No index/embedding exists.
|
||||
|
||||
Each read supplies node ID **and the observed committed mutation**, and is locked to that
|
||||
blob for the entire chunk. The SQLite lock excludes upload/GC while reading.
|
||||
After an update the previous observation expires: the next chunk returns `Conflict` (or
|
||||
`NotFound` after deletion), not the new generation. No long-lived read lease or
|
||||
full-history retention is promised. Binary ranges are byte-based and exact;
|
||||
text reads require UTF-8 character-aligned offsets/ranges and reject split or
|
||||
invalid characters. Missing referenced blobs and length inconsistencies are
|
||||
errors, including missing zero-byte files, never successful empty content.
|
||||
|
||||
## Garbage collection and restart
|
||||
|
||||
GC takes the **same SQL IMMEDIATE lock** as readers/uploads. Thus there is no
|
||||
in-flight upload while sweeping; every currently referenced key is checked in
|
||||
metadata before deletion. A grace period or process-local mutex is not a safety
|
||||
proof. After a process crash SQLite releases/rolls back the transaction; an
|
||||
unfinished staging file or fully saved but uncommitted blob can be reclaimed on
|
||||
a later sweep. Recognized 0.13.2 staging names are cleaned under that lock; foreign
|
||||
files are not interpreted as blobs. Deleted/replaced blobs are reclaimed too.
|
||||
|
||||
`collect` is explicit Host maintenance, not a background task tied to a Worker.
|
||||
Repeat its cursor pages, then start future sweeps at `None` because new UUID keys
|
||||
can sort before the preceding cursor. Local object listing necessarily traverses
|
||||
the flat blob directory; response/deletion batches and candidate memory are
|
||||
bounded, unlike SQL list which does no blob traversal. Holding the write lock for
|
||||
local listing is a conservative safety/throughput tradeoff. A corrupt or redirected
|
||||
storage layout fails closed. GC never deletes a key referenced by the DB.
|
||||
|
||||
## Workspace deletion
|
||||
|
||||
Deletion reservation in Server authority changes the Workspace to `deleting`.
|
||||
The attached authority fence immediately rejects new mutations from cached Drive
|
||||
handles. Deletion start also persists the Drive tombstone before responding or
|
||||
scheduling cleanup; API restoration reapplies it for blocked/failed deletions.
|
||||
Purge commits that fence first, removes only this Workspace's blobs/staging,
|
||||
clears its nodes/receipts, then Host removes the blob directory and calls
|
||||
FeatureStorage's scoped delete. The feature scope's admission fence prevents
|
||||
cached tasks recreating DB files during finalization. Failures retain a retryable
|
||||
Server deletion operation; repeating purge/fence/delete is safe. Other Workspace
|
||||
roots are never traversed or removed. Root/tombstone remain in the domain DB until
|
||||
the Host removes the scope; there is no normal client recursive folder deletion.
|
||||
|
||||
## Consistent backup and restore (offline initial procedure)
|
||||
|
||||
A FeatureStorage DB snapshot **alone is not a Drive backup**. No online
|
||||
DB-plus-object snapshot is implemented. Use this administrative procedure:
|
||||
|
||||
1. Stop request admission, **all** Server processes sharing the authority,
|
||||
uploads/readers, maintenance/GC and deletion. Do not merely stop Workers.
|
||||
Complete or reconcile in-flight requests before backup.
|
||||
2. Checkpoint/close the metadata DB using SQLite/FeatureStorage shutdown or use
|
||||
its SQLite backup API while all Drive writers and GC remain stopped. Never
|
||||
copy an active DB and omit its WAL. Capture the existing FeatureStorage scoped
|
||||
metadata tree (or its snapshot manifest + databases), Server authority and
|
||||
other deployment-required secrets/config using their respective procedures.
|
||||
3. Copy the **same stopped generation's blob tree** with the metadata. Include
|
||||
all referenced immutable objects; copying unreferenced blobs/staging is safe
|
||||
and later GC can reclaim them. Preserve the internal Workspace ID. Record
|
||||
both roots, object_store/schema version and checksums in an operator manifest;
|
||||
restrict backup permissions because it contains user documents.
|
||||
4. Verify each file node's blob exists and length matches `size`; missing content
|
||||
is a corrupt backup, not an empty file. Flush/sync the backup using the backup
|
||||
destination's own durability facilities before treating it as complete.
|
||||
5. Restore only with admission/writers/GC still stopped, into absent FeatureStorage
|
||||
scope and Host blob roots. Use `ScopedFeatureStorage::restore` for its snapshot
|
||||
format, or restore the entire stopped deployment consistently. Never restore
|
||||
just `server.db` or create a new empty Drive DB over surviving blobs.
|
||||
6. Restore blobs from the matching generation; restore internal IDs, committed request IDs,
|
||||
receipts and `sqlite_sequence` unchanged. Validate references/lengths and
|
||||
sanitized metadata/text/binary reads before reopening admission. Resume GC
|
||||
only after validation. A deletion tombstone must not be cleared on restore.
|
||||
|
||||
The hermetic backup test stops writers, snapshots actual FeatureStorage SQLite,
|
||||
copies actual LocalFileSystem blobs, restores into absent roots, and checks stable
|
||||
IDs/latest content/receipts and non-reused ID allocation. It does not validate a
|
||||
production backup scheduler, volume snapshot, or physical power cut.
|
||||
|
||||
## Authorized typed HTTP API
|
||||
|
||||
The Rust `server-api` contract owns `/api/w/{workspace_id}/drive/` operations:
|
||||
|
||||
- `GET root`, `metadata`, `list`, `search`, `read-text`, `read-chunk`, `download`;
|
||||
- `POST mutate` (create folder/bounded UTF-8 text, replace text, relocate, delete);
|
||||
- `PUT upload` (binary body with flat query metadata);
|
||||
- `GET requests/{request_id}` for the committed receipt snapshot;
|
||||
- owner-controlled `POST/GET grants` and `DELETE grants/{grant_id}`.
|
||||
|
||||
Ordinary usage follows the existing member-facing authenticated Server-user
|
||||
policy. Non-ownership alone does not deny use. Grant management follows the
|
||||
existing Workspace-owner account boundary. No new general membership ACL is
|
||||
introduced. Browser/API-token identity is supplied by authentication middleware;
|
||||
user/account existence and active Workspace state are checked again in the
|
||||
operation transaction. Cookie mutations require the configured application
|
||||
Origin through the existing middleware. No ordinary DTO accepts an actor.
|
||||
|
||||
Runtime-forwarded requests require the existing signed source proof and live
|
||||
Runtime/Worker catalog membership. Workers receive an explicit whole-Drive
|
||||
`read_only` or `read_write` grant, independently of Profile, Subject, Ticket,
|
||||
Workdir or commands. Incomplete/mismatched Runtime or Worker identity cannot fall
|
||||
back to a browser actor. Grant creation rechecks owner, active Workspace, and
|
||||
existing live Worker in the Server DB transaction; active access changes require
|
||||
revoking the previous grant first (no silent downgrade). Migration 84 stores
|
||||
creation/revocation actor and time. Removal fences and catalog deletion revoke
|
||||
access and retain audit; grant rows cascade with Workspace deletion. No alternate
|
||||
Worker registry or Drive Workdir attachment is created.
|
||||
|
||||
A clone-bound Drive authorizer queries the attached Server authority under every
|
||||
client-operation `BEGIN IMMEDIATE`, including receipt replay/result queries.
|
||||
The lock spans validation, blob persistence and DB publication. Grant revocation
|
||||
and Worker removal use the same Server DB write authority: whichever transaction
|
||||
wins first is ordered first. A mutation committing before revocation is valid;
|
||||
a revoked cached handle cannot commit later. Upload admission also checks access
|
||||
before consuming the body, but that admission is not a lease: publication always
|
||||
rechecks current access. Download checks authorization separately for every
|
||||
chunk, and stops on revocation, deletion, a changed committed request or storage failure.
|
||||
|
||||
References contain **both** Workspace ID and stable decimal node ID. JSON uses
|
||||
`DriveEntryRef`; flat GET/binary queries require `entry_workspace_id` matching the
|
||||
route Workspace before resolving `id`/`parent_id`. Node IDs are canonical
|
||||
positive decimal strings within signed 64-bit range. Mutation IDs are request
|
||||
strings (1–128 UTF-8 bytes, no control characters); file sizes/offsets are
|
||||
bounded `u32`. List/search cursors carry Workspace identity and the last node ID;
|
||||
they are continuation hints, not capabilities or snapshots. Returned `latest_url`
|
||||
is a relative authenticated URL bound to Workspace and ID, independent of logical
|
||||
name/parent. It confers neither public sharing nor approval. File URLs download;
|
||||
folder URLs return metadata. Deletion/recreation never reuses the old reference.
|
||||
|
||||
Web upload consumes binary HTTP frames with an observed **16 MiB** ceiling and
|
||||
checks declared size and lowercase SHA-256 before publication. Oversized announced
|
||||
or received data, mismatches, aborted bodies and I/O errors never return success.
|
||||
The storage boundary currently requires a whole-file Vec, so uploads retain at
|
||||
most one bounded binary file in memory before calling T-721; they are not
|
||||
constant-memory object-store multipart uploads and are never expanded to
|
||||
JSON/base64. Runtime source-proof verification separately buffers a bounded body
|
||||
before granting trusted ingress, as in other signed Server operations. Web
|
||||
downloads stream **64 KiB** committed-request-bound chunks; they do not materialize the
|
||||
whole file. Generated Rust clients retain their explicit bounded-response policy
|
||||
and return exact `BinaryBody` bytes. Worker JSON-facing adapters can request
|
||||
bounded text or binary chunks through this same contract, but Tools/WIP and
|
||||
model-context/image presentation belong to T-723; this API does not emit large
|
||||
base64 JSON blobs.
|
||||
|
||||
Downloads validate bare MIME types and always use `Content-Disposition:
|
||||
attachment` with percent-encoded UTF-8 names, `nosniff`, a sandbox/default-none
|
||||
CSP, `private, no-store`, and a SHA-256 ETag over Workspace, node ID and committed request ID. Arbitrary HTML/SVG
|
||||
bytes are storable, not safe app-origin inline previews. Content length is exact;
|
||||
if a later chunk loses authority or expires, the response stream fails rather
|
||||
than returning another generation or a successful truncated file. Even an empty
|
||||
file validates its referenced blob before HTTP success. Conditional 304/range
|
||||
HTTP semantics are not added; observation-bound chunk reads are explicit.
|
||||
|
||||
Errors use fixed, path-free typed codes: `denied`, `not_found`, `conflict`,
|
||||
`invalid`, `limit`, `storage_unavailable`, `outcome_unknown`. SQL/commit/task
|
||||
uncertainty at a mutation boundary is `outcome_unknown`/`unknown`; known rejection
|
||||
or blob persistence failure is `not_committed`. A successful DB commit alone
|
||||
returns a mutation response. After a lost response, query the same request ID;
|
||||
`uncommitted` only means no committed receipt in that snapshot and must not be
|
||||
interpreted as proof that an in-flight request can never commit. Exact replay uses
|
||||
the same identity/payload/transport-bound actor and current permissions. Never
|
||||
blindly create a fresh request ID to resolve an unknown outcome. Physical paths,
|
||||
blob keys and raw provider errors are not public.
|
||||
|
||||
Contract regeneration/freshness:
|
||||
|
||||
```sh
|
||||
cargo run -q -p server-api --example export_openapi -- openapi/server-api.json
|
||||
cargo run -q -p server-api --example export_openapi -- --check openapi/server-api.json
|
||||
cargo run -q -p server-api --features typescript --example generate_drive_api_types > web/workspace/src/lib/generated/drive-api.ts
|
||||
deno fmt web/workspace/src/lib/generated/drive-api.ts
|
||||
cargo test -p server-api --features typescript generated_drive_api_contract_is_current
|
||||
# Existing OpenAPI-derived projections embed the whole-contract digest:
|
||||
for example in generate_repository_openapi_types generate_runtime_api_types generate_ticket_api_types generate_companion_api_types generate_worker_launch_api_types; do
|
||||
cargo run -q -p server-api --example "$example"
|
||||
cargo run -q -p server-api --example "$example" -- --check
|
||||
done
|
||||
```
|
||||
|
||||
The existing BinaryBody contract also supports binary **success responses**,
|
||||
including declared response headers, without changing binary request bytes or
|
||||
JSON/error behavior. It exports inline OpenAPI binary bodies, not JSON byte arrays.
|
||||
|
||||
## Upgrade to receipt-bound mutation checks
|
||||
|
||||
Migration 2 obtains each non-root node’s latest request ID from its existing
|
||||
committed receipt, transforms historical receipt payloads, and drops the old
|
||||
update counter. The current-state receipt must be unique; missing or ambiguous
|
||||
evidence aborts and rolls back the entire migration. Equal timestamps do not
|
||||
change the result. No content, node identity, request fingerprint or blob is
|
||||
regenerated. Deleted nodes’ receipts and AUTOINCREMENT history remain intact.
|
||||
|
||||
The frozen migration module retains old SQL/JSON names and proven predecessor
|
||||
correspondence only to verify existing receipts against their original request
|
||||
fingerprints. This evidence never authorizes a new mutation. Active node CAS and
|
||||
HTTP contracts do not accept the old numeric precondition. Exact pre-upgrade
|
||||
Update/Relocate/Delete replays are accepted only when the complete actor/payload
|
||||
and mapped predecessor reproduce the original fingerprint. If that evidence
|
||||
cannot be established, recover the committed result through `request_status`;
|
||||
changing the actor, payload or precondition and reusing its ID is a conflict,
|
||||
not permission to execute it again.
|
||||
@@ -1,285 +0,0 @@
|
||||
# Workspace Kanban to backend Orchestrator runtime
|
||||
|
||||
Workspace Kanban operations are control-plane requests. They may change Ticket state and request orchestration, but they must not directly execute shell, git, filesystem work, or send authority-bearing messages to raw local Worker sockets. The durable boundary is an orchestration event consumed by a backend-internal Orchestrator Worker.
|
||||
|
||||
This document records the design boundary for connecting Kanban operations, Tickets, the Workspace backend, `WorkerRuntimeRegistry`, and filesystem-capable Workers. It is intentionally a planning artifact: it does not require the Workspace backend to implement every table, API, remote protocol, or spawn adapter immediately.
|
||||
|
||||
## Core rule
|
||||
|
||||
A browser click or API request can create durable intent; it cannot be the authority to perform implementation side effects.
|
||||
|
||||
The minimum chain for implementation work is:
|
||||
|
||||
1. A user or authorized caller changes a Ticket through the Workspace/Ticket API.
|
||||
2. The state change and its orchestration event are committed durably in the same logical operation.
|
||||
3. A backend-internal Orchestrator Worker reads the event with domain-specific tools.
|
||||
4. The Orchestrator records a routing decision.
|
||||
5. If the Ticket is queued, unblocked, and accepted for implementation, the Orchestrator records `queued -> inprogress` plus the acceptance decision before any implementation side effect.
|
||||
6. The Orchestrator creates typed spawn intents for filesystem-capable Coder, Reviewer, or helper Workers.
|
||||
7. A local or remote runtime adapter resolves those intents into concrete worker launch/configuration using its own authority and capability policy.
|
||||
|
||||
`ready -> queued` is therefore a human gate for Orchestrator routing, not an unattended scheduler and not a lease that automatically starts code execution.
|
||||
|
||||
## Durable orchestration events
|
||||
|
||||
Orchestration events are immutable control-plane records derived from Ticket operations. They are not raw LLM messages, Worker notifications, or socket writes.
|
||||
|
||||
Initial event kinds:
|
||||
|
||||
- `ticket_queued`: emitted for `ready -> queued`; requests Orchestrator routing/start-if-unblocked checks.
|
||||
- `ticket_state_changed`: emitted for other lifecycle transitions that may affect orchestration state.
|
||||
- `ticket_returned_to_planning`: emitted when a Ticket is moved back to `planning` because concrete requirements, decisions, dependencies, or acceptance evidence are missing.
|
||||
- `ticket_done`: emitted when implementation/review flow records `done`; used for completion projection and close-readiness, not implicit close.
|
||||
|
||||
A stable event shape should include:
|
||||
|
||||
```text
|
||||
orchestration_event {
|
||||
event_id: opaque id, # durable event identity
|
||||
workspace_id: opaque workspace id,
|
||||
ticket_id: canonical Ticket id,
|
||||
kind: ticket_queued | ticket_state_changed | ticket_returned_to_planning | ticket_done,
|
||||
before_state: optional Ticket state,
|
||||
after_state: optional Ticket state,
|
||||
actor: { kind, key, display, source? }, # human, worker, api client, system
|
||||
source: { kind, surface, operation }, # kanban_api, ticket_api, orchestrator, import, ...
|
||||
request_id: opaque idempotency/correlation id,
|
||||
caused_by_event_id: optional event id,
|
||||
occurred_at: timestamp from the authoritative mutation,
|
||||
recorded_at: timestamp when stored,
|
||||
body: bounded structured reason/summary, # no raw transcript or raw local path authority
|
||||
}
|
||||
```
|
||||
|
||||
`request_id` is a correlation and idempotency key. For API-originated state changes, retrying the same request must return or reference the already-created Ticket transition and orchestration event instead of creating a second routing command. The event store should reject duplicate `(source, request_id)` pairs for mutation-producing requests, while allowing new events for distinct state changes.
|
||||
|
||||
The Ticket state mutation and event append must be atomic from the backend's perspective. If the Ticket changes but the event cannot be recorded, routing must be considered failed and visible; if an event exists, the Orchestrator must be able to recover it after backend restart.
|
||||
|
||||
## Event processing, retry, ack, defer, fail
|
||||
|
||||
Event delivery state is separate from the immutable event. A future implementation can store per-consumer delivery rows such as `(event_id, consumer_id, status, attempts, visible_after, last_error, updated_at)`. That delivery mechanism is only for reliable control-plane processing; it must not redefine `queued` as a scheduler state.
|
||||
|
||||
The backend-internal Orchestrator handles each event idempotently:
|
||||
|
||||
- Reload the current Ticket, relations, orchestration plan, worker links, and relevant runtime summaries before deciding.
|
||||
- Treat stale events as evidence, not commands. For example, a `ticket_queued` event for a Ticket that is now `planning` should be acknowledged with a stale/no-op decision rather than spawning work.
|
||||
- Record a decision, waiting reason, or failure summary before acknowledging a routing event.
|
||||
- Create spawn intents with stable `intent_id`s so dispatch retry can detect already-created intents.
|
||||
|
||||
Delivery outcomes:
|
||||
|
||||
- `ack`: the event has been interpreted and its durable outcome is recorded. The outcome may be `no_op_stale`, `routed_to_worker`, `blocked_waiting`, `returned_to_planning`, or `completion_projected`.
|
||||
- `defer`: the event is valid but cannot progress now. The Orchestrator records a waiting reason and optional retry condition/backoff. Defer is appropriate for dependency not done, target conflict, dirty workspace reported by a capable runtime, missing worker capacity, or runtime unavailable.
|
||||
- `fail`: processing cannot safely continue without operator or developer intervention. Fail is appropriate for invariant violations, malformed event payloads, missing Ticket authority, identity ambiguity, repeated dispatch inconsistency, or a request that would require forbidden authority.
|
||||
|
||||
Retries must be bounded and idempotent. A retry may re-read the Ticket and registry and either dispatch a previously recorded intent, update a waiting reason, or fail with escalation. It must not execute shell/git/filesystem work in the backend process to "check" whether progress is possible.
|
||||
|
||||
## Backend-internal Orchestrator Worker
|
||||
|
||||
The backend-internal Orchestrator is a routing/control-plane Worker. It can be hosted inside the Workspace backend runtime because its tools operate on domain records and runtime registry abstractions rather than the workspace filesystem.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- Read durable orchestration events and delivery state.
|
||||
- Inspect Tickets, Ticket relations, accepted orchestration plans, worker links, and bounded project-record projections.
|
||||
- Decide whether a queued Ticket is ready for implementation, should wait, should return to planning, or should request review/closure follow-up.
|
||||
- Record decisions, audit summaries, blocker/waiting reasons, and orchestration plan artifacts.
|
||||
- Select a runtime by required capabilities.
|
||||
- Create and dispatch typed spawn intents through `WorkerRuntimeRegistry`.
|
||||
- Append/read worker run overviews, lifecycle summaries, and usage aggregates.
|
||||
- Escalate when authority, requirements, or runtime capabilities are insufficient.
|
||||
|
||||
Non-responsibilities:
|
||||
|
||||
- No `Bash` authority.
|
||||
- No raw workspace `Read`/`Write`/`Edit` authority.
|
||||
- No direct git/worktree/build execution.
|
||||
- No raw local Worker socket or session path authority.
|
||||
- No use of browser-supplied local paths, executable paths, runtime registry paths, `display_ref`, `worker_name`, or runtime display names as operation authority.
|
||||
- No raw session transcript full ingest into the Workspace database.
|
||||
- No permission/auth, remote runtime protocol, Ticket DB migration, Kanban UI completion, or Coder/Reviewer spawn implementation completion in this design step.
|
||||
|
||||
If routing needs evidence that only filesystem access can provide, the Orchestrator records a helper spawn intent for a filesystem-capable runtime or records a waiting/escalation reason. It does not temporarily grant itself filesystem tools.
|
||||
|
||||
## Domain-specific tool surface
|
||||
|
||||
The internal Orchestrator should receive backend tools, not generic Worker tools. The tools should be narrow enough to enforce lifecycle and authority rules and broad enough to let future Orchestrator prompts reason without hidden context injection.
|
||||
|
||||
Required operation groups:
|
||||
|
||||
- Ticket operations:
|
||||
- list Tickets by state/risk/assignment bounds;
|
||||
- show Ticket details, thread summaries, and relevant artifacts;
|
||||
- append Ticket comments/implementation reports/review notes;
|
||||
- perform validated state transitions, including `queued -> inprogress`, return-to-planning, and `done` recording.
|
||||
- Relation and plan operations:
|
||||
- read typed Ticket relations and derived blockers;
|
||||
- read/write bounded orchestration plan artifacts;
|
||||
- record conflict/capacity/waiting/accepted-plan notes.
|
||||
- Event delivery operations:
|
||||
- read pending orchestration events;
|
||||
- ack, defer, or fail events with durable reason codes;
|
||||
- query retry/defer state by event, Ticket, or consumer.
|
||||
- Runtime registry operations:
|
||||
- list runtimes and capability summaries;
|
||||
- look up Workers by canonical `runtime_id` + `worker_id`;
|
||||
- query capability support such as backend-internal tools, filesystem, shell, git, worktrees, bounded transcript read, stream availability, and worker spawn support.
|
||||
- Spawn intent operations:
|
||||
- create spawn intents;
|
||||
- dispatch intents to a selected runtime;
|
||||
- read intent state and acceptance evidence;
|
||||
- associate intent/Worker summaries with Tickets.
|
||||
- Worker overview and usage operations:
|
||||
- append/read run overview entries;
|
||||
- append/read lifecycle summaries;
|
||||
- read usage aggregates for dashboard/control-plane display.
|
||||
- Audit and decision operations:
|
||||
- append routing decisions with actor/source/request id;
|
||||
- append authority-boundary failures and escalation requests;
|
||||
- query bounded decision history for a Ticket.
|
||||
|
||||
Forbidden operation groups for the internal Orchestrator:
|
||||
|
||||
- shell execution;
|
||||
- raw filesystem read/write/edit over repository paths;
|
||||
- raw Unix socket connects or socket path notification;
|
||||
- raw session full transcript ingest;
|
||||
- local Worker metadata path or session path access as authority;
|
||||
- browser-provided display labels, paths, or executable strings as authority.
|
||||
|
||||
## WorkerRuntime registry and spawn intents
|
||||
|
||||
`WorkerRuntimeRegistry` is the boundary between backend control-plane decisions and runtime-specific worker launch. The Orchestrator asks for capabilities and submits typed intents; it does not construct low-level process commands.
|
||||
|
||||
A spawn intent should describe policy and purpose rather than launch mechanics:
|
||||
|
||||
```text
|
||||
worker_spawn_intent {
|
||||
intent_id: opaque id,
|
||||
parent_event_id: orchestration event id,
|
||||
request_id: idempotency/correlation id,
|
||||
ticket_id: canonical Ticket id,
|
||||
role: intake | orchestrator | coder | reviewer | helper,
|
||||
purpose: route | implement | review | inspect | validate | summarize,
|
||||
required_capabilities: [backend_internal_tools | workspace_fs | shell | git | worktrees | build | bounded_transcript],
|
||||
workspace: { workspace_id, repository_targets? },
|
||||
cwd_semantics: role_default | ticket_worktree | target_repository | runtime_resolved,
|
||||
profile_intent: builtin role/profile selector intent,
|
||||
workflow_intent: optional workflow slug/phase intent,
|
||||
input_packet_ref: durable bounded context reference,
|
||||
acceptance_requirement: socket_ready | run_accepted(expected_segments) | decision_recorded,
|
||||
}
|
||||
```
|
||||
|
||||
The browser must not provide raw workspace roots, child cwd, executable paths, raw profile files, socket paths, local Worker names, or runtime display names in this intent. API callers can request high-level operations such as "queue this Ticket" or "open this canonical Worker"; the backend and runtime adapters resolve launch details from trusted workspace records, runtime configuration, and capability policy.
|
||||
|
||||
Runtime adapters are responsible for translating an accepted intent:
|
||||
|
||||
- A backend-internal runtime may create routing-only/intake/dashboard-assistant Workers with backend tools and no filesystem scope.
|
||||
- A local Worker runtime may resolve a Coder/Reviewer intent into Worker launch arguments, scope, delegated filesystem paths, branch/worktree policy, prompt/profile/workflow selection, and acceptance evidence.
|
||||
- A remote runtime may perform the same adaptation on a different machine without exposing local paths to the browser or storing them as API authority.
|
||||
|
||||
Dispatch success means the runtime accepted the typed intent and returned durable acceptance evidence. It does not by itself prove the Ticket is done. Worker progress is projected through lifecycle, overview, review, and Ticket state records.
|
||||
|
||||
## Runtime selection by capability
|
||||
|
||||
Runtime choice is capability-driven:
|
||||
|
||||
- Backend-internal runtime is suitable for routing-only Orchestrator work, intake refinement, dashboard assistant behavior, event processing, decision recording, and registry lookups.
|
||||
- Filesystem-capable local or remote runtimes are required for Coder, Reviewer, worktree creation, git operations, builds, tests, repository inspection, and helper checks that need repository files.
|
||||
- Bounded transcript read is a debug/support capability. It is not a substitute for overview/decision/lifecycle projections.
|
||||
|
||||
If no runtime satisfies the required capabilities, the Orchestrator records `runtime_unavailable` as a waiting reason and defers or escalates. It must not silently downgrade to the backend-internal runtime for work that requires filesystem, shell, git, or worktree authority.
|
||||
|
||||
## Worker identity, API, and database projection
|
||||
|
||||
External API identity is runtime-scoped and opaque:
|
||||
|
||||
- Worker detail: `GET /api/runtimes/{runtime_id}/workers/{worker_id}`.
|
||||
- Cross-runtime list: `GET /api/workers`, with each item carrying `runtime_id`, `worker_id`, and display fields.
|
||||
|
||||
`worker-name@runtime-name` is a display label (`display_ref`) only. It is not unique enough for authority and must not be accepted as the target of mutating operations. Similarly, local Worker `worker_name`, runtime display names, raw runtime registry paths, and socket/session paths are implementation diagnostics, not API authority.
|
||||
|
||||
A browser-safe Worker summary can expose:
|
||||
|
||||
```text
|
||||
worker_summary {
|
||||
runtime_id,
|
||||
worker_id,
|
||||
display_name,
|
||||
runtime_display_name,
|
||||
display_ref,
|
||||
role,
|
||||
state,
|
||||
capabilities,
|
||||
implementation: {
|
||||
kind,
|
||||
display_hint,
|
||||
worker_name? # local Worker runtime only; diagnostic/display hint, not authority
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For database projection, prefer a surrogate worker record id plus a uniqueness constraint on runtime-scoped identity:
|
||||
|
||||
```text
|
||||
workers (
|
||||
id INTEGER PRIMARY KEY,
|
||||
runtime_id TEXT NOT NULL,
|
||||
worker_id TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
runtime_display_name TEXT NOT NULL,
|
||||
display_ref TEXT NOT NULL,
|
||||
implementation_kind TEXT NOT NULL,
|
||||
implementation_display_hint TEXT,
|
||||
observed_at TEXT NOT NULL,
|
||||
UNIQUE(runtime_id, worker_id)
|
||||
)
|
||||
```
|
||||
|
||||
Run overviews, lifecycle events, Ticket worker links, and usage aggregates may reference the surrogate `workers.id` internally for stable joins. External APIs should continue to expose and accept only `runtime_id` + `worker_id` for Worker identity.
|
||||
|
||||
## Session, overview, lifecycle, and usage boundary
|
||||
|
||||
The Workspace backend durable projection should center on:
|
||||
|
||||
- orchestration events and delivery outcomes;
|
||||
- routing decisions and audit records;
|
||||
- worker lifecycle summaries;
|
||||
- worker run overviews;
|
||||
- Ticket state/relation/plan projections;
|
||||
- usage aggregates.
|
||||
|
||||
Raw session JSONL, provider traces, verbose event streams, local sockets, and local Worker metadata files remain runtime-local source/debug logs. The backend may expose bounded debug reads later, but that surface must be explicit, purpose-limited, permissioned, size-limited, and never treated as the normal Kanban/Orchestration UI data model.
|
||||
|
||||
This keeps dashboard views stable across local/remote runtimes and prevents raw transcript contents from becoming hidden durable authority for why a control-plane decision happened. If a decision matters, it must be written as a decision/audit/overview record.
|
||||
|
||||
## Failure, blocker, and waiting reason semantics
|
||||
|
||||
The Orchestrator records why it did not dispatch work as carefully as why it did dispatch work. Initial reason categories:
|
||||
|
||||
- `dependency_blocked`: required upstream Tickets or relations are unresolved.
|
||||
- `conflict_blocked`: target paths, repositories, branches, or worker assignments conflict with active work.
|
||||
- `dirty_workspace`: a filesystem-capable runtime reports that the relevant checkout/worktree is dirty or unsafe. The backend-internal Orchestrator does not inspect the filesystem itself.
|
||||
- `missing_requirement`: the Ticket lacks a concrete decision, acceptance criterion, permission, or scope needed to start; the Orchestrator may return it to `planning` with a reason.
|
||||
- `runtime_unavailable`: no registered runtime satisfies required capabilities or capacity.
|
||||
- `identity_ambiguous`: the requested Worker/runtime cannot be resolved by canonical `runtime_id` + `worker_id`.
|
||||
- `forbidden_authority`: completing the request would require raw shell/filesystem/socket/session/path authority in the backend process.
|
||||
- `dispatch_inconsistent`: a spawn intent retry observed inconsistent runtime acceptance evidence.
|
||||
|
||||
Waiting records should include ticket id, event id, reason code, human-readable summary, observed evidence, retry/unblock condition if any, and timestamp. A waiting reason may be cleared by a new Ticket event, relation change, runtime capability change, worker completion, or explicit operator action.
|
||||
|
||||
Returning a Ticket to `planning` requires a concrete missing-decision or missing-information reason. Risk flags, unknown implementation details, or a need for reviewer focus are not sufficient by themselves.
|
||||
|
||||
## Implementation sequence for future Tickets
|
||||
|
||||
This design suggests the following order without making any of it part of this Ticket:
|
||||
|
||||
1. Persist orchestration events for Kanban/Ticket state mutations, including idempotency by request id.
|
||||
2. Add event delivery tools and decision/audit append tools for a backend-internal Orchestrator Worker.
|
||||
3. Add runtime-scoped Worker detail APIs and backend worker projection records with surrogate ids and `UNIQUE(runtime_id, worker_id)`.
|
||||
4. Add spawn intent persistence and registry dispatch stubs that preserve authority boundaries.
|
||||
5. Implement local Worker runtime adaptation for Coder/Reviewer/helper intents.
|
||||
6. Add remote runtime protocol only after the local typed-intent boundary is stable.
|
||||
|
||||
At every step, keep the invariant that durable control-plane records explain why the system acted, while runtime-specific sockets, sessions, paths, and process launch details remain adapter-local implementation details.
|
||||
@@ -1,56 +0,0 @@
|
||||
# Workspace creation with optional Repository intent
|
||||
|
||||
A Workspace is usable without a Repository. Its initial Git Repository is an
|
||||
optional asset, not an identity or a prerequisite for connection. Creation does
|
||||
not initialize a Git repository or infer a replacement asset.
|
||||
|
||||
## Creation and replay contract
|
||||
|
||||
`POST /api/workspaces` accepts `operation_key`, `display_name`, and an optional
|
||||
`repository` intent. Omitted and explicit `null` both mean no initial Repository.
|
||||
An object is validated normally: a partial object, invalid URI, or invalid key is
|
||||
not treated as omission. Ownership comes from the authenticated request actor,
|
||||
not a caller-supplied owner field.
|
||||
|
||||
The bootstrap transaction persists the Workspace, optional Repository, initial
|
||||
config, memory settings, Ticket/Objective/Worker resource-key counters, active
|
||||
signing identity, and create receipt together. Signing-material provisioning
|
||||
retains its existing durable reservation/recovery protocol; a failed bootstrap
|
||||
leaves no partially created Workspace or Repository.
|
||||
|
||||
The fingerprint includes normalized creation intent and owner identity. No
|
||||
Repository differs from any Repository intent; repository-present fingerprints
|
||||
keep the existing encoding so saved create receipts remain replayable. The same
|
||||
operation key and input converge, including concurrent calls; changed input is a
|
||||
conflict. No schema migration is needed for the existing fingerprint/Workspace
|
||||
receipts.
|
||||
|
||||
A replay is not a historical snapshot. It returns the current Workspace and
|
||||
config content snapshot. If creation supplied a Repository, it returns the current
|
||||
record **at the originally requested key**. Removal or renaming away from that
|
||||
key produces `repository: null`; registration at that key later returns that
|
||||
current record. Replay never re-registers or resets an asset. If creation omitted
|
||||
a Repository, replay always returns `null`, even after other assets are added.
|
||||
|
||||
## Entry points and zero-Repository use
|
||||
|
||||
- Web creation requires only a display name. Optional Repository fields are
|
||||
disclosed explicitly; Settings retains the later-registration entry point.
|
||||
- `yoi [--backend URL] workspace create --display-name NAME` creates without Git
|
||||
discovery. Select the returned identity with `yoi --workspace-id ID`.
|
||||
- `yoi init` remains Git-oriented and still requires an explicit Repository key.
|
||||
- The TUI creation picker allows Enter at the optional URI prompt to skip Git
|
||||
registration. Workspace selection and ordinary embedded Worker startup do not
|
||||
require a Repository or Workdir. Operations that use Git/Workdirs retain their
|
||||
own prerequisites.
|
||||
- Empty Repository lists are valid, without a missing-configuration warning.
|
||||
Reopening and deletion use the usual Workspace authority and lifecycle guards;
|
||||
in particular, the last-accessible-Workspace deletion guard is unchanged.
|
||||
|
||||
Optional creation preserves the existing owner/member responsibility split:
|
||||
settings are owner-controlled, while ordinary viewing and use are member-facing.
|
||||
Non-ownership alone is not a reason to deny ordinary viewing. Existing
|
||||
member-facing non-owner reads, owner-gated settings mutations, owner-scoped
|
||||
catalog discovery, authentication and operation-permission checks are unchanged.
|
||||
This creation change does not introduce a membership authority or an owner-only
|
||||
viewing filter.
|
||||
@@ -1,164 +0,0 @@
|
||||
# Workspace / Runtime / Docker launch boundary
|
||||
|
||||
Yoi's browser-facing deployment is a three-process boundary: WebUI, Workspace Backend, and Worker Runtime. Docker Compose currently exercises that boundary with locally loaded Nix-built images; it is not a separate authority model.
|
||||
|
||||
This document records the current design state for the split launch path, Docker image layout, and working-directory ownership rules. It is intentionally about boundaries and invariants rather than a complete operations manual.
|
||||
|
||||
## Process boundary
|
||||
|
||||
The browser launch path is:
|
||||
|
||||
```text
|
||||
Browser WebUI
|
||||
-> Workspace Backend Server
|
||||
-> Worker Runtime
|
||||
-> Worker process
|
||||
```
|
||||
|
||||
Responsibilities are split as follows:
|
||||
|
||||
- WebUI renders the workspace and submits browser-safe requests. It does not hold filesystem authority, raw runtime paths, raw socket paths, or local Worker names as operation authority.
|
||||
- Workspace Backend Server is the workspace control plane. It owns workspace-scoped API identity, Runtime registry projection, Worker registry projection, Ticket/workspace records, memory backend access, and browser-safe launch options.
|
||||
- Worker Runtime owns runtime-specific process spawning, Worker controller state, workdir materialization, runtime-local workdir cleanup, and Worker protocol connectivity.
|
||||
- Worker process owns model turns, tool execution inside its granted scope, session history append, and Worker protocol handling.
|
||||
|
||||
The Backend can project Runtime and Worker state, but it should not become a hidden filesystem/runtime implementation. Runtime observations should be reconstructable from Runtime APIs and committed Backend records.
|
||||
|
||||
Remote Runtime authentication follows the same Workspace boundary. The Server signs each Runtime request with the target Workspace signing identity, and the Runtime verifies it against the installed Workspace issuer bundle. Runtime-to-Server source proof is signed by the Runtime identity and uses the bundle's Backend URL as audience. Server-global signing identities, Runtime-side global Server trust, and static bearer fallback are not Remote Workspace authority. Provisioning and rotation are described in [Workspace ↔ Runtime authentication](../development/server-runtime-auth.md).
|
||||
|
||||
## Docker image layout
|
||||
|
||||
Docker images are built through Nix `dockerTools.buildImage`, not through a root Dockerfile.
|
||||
|
||||
Current image roles:
|
||||
|
||||
```text
|
||||
yoi-runtime:latest
|
||||
yoi-runtime
|
||||
|
||||
yoi-server:latest
|
||||
yoi-server
|
||||
|
||||
yoi-webui:latest
|
||||
nginx serving built WebUI assets and proxying API requests
|
||||
```
|
||||
|
||||
The Compose setup assumes those images have already been loaded into the local Docker daemon. Compose is a local wiring layer, not the builder.
|
||||
|
||||
Typical development flow:
|
||||
|
||||
```text
|
||||
nix build .#docker-runtime
|
||||
nix build .#docker-server
|
||||
nix build .#docker-webui
|
||||
docker load < result
|
||||
# repeat load for each built image result
|
||||
docker compose up
|
||||
```
|
||||
|
||||
The Compose files live at:
|
||||
|
||||
```text
|
||||
compose.yaml
|
||||
```
|
||||
|
||||
The WebUI container serves static assets and proxies `/api` to the Backend Server. The Backend Server registers the Runtime container as a remote Runtime such as `docker-runtime`. The Runtime container runs `yoi-runtime` and owns Worker spawning/materialization for that runtime.
|
||||
|
||||
The operator-owned `/etc/yoi/server.toml` is mounted read-only at `/server-config/server.toml`. Its `browser.public_url` is the single browser-facing setting used for WebAuthn, device-login URLs, cookie policy, and cookie-authenticated mutation origin checks:
|
||||
|
||||
```toml
|
||||
[browser]
|
||||
public_url = "https://yoi.example.com"
|
||||
```
|
||||
|
||||
Create this host file before starting Compose and set it to the exact Nginx-facing origin. It is not an API/backend URL and must not include a path, query, or fragment. It is deployment topology outside the source and Workspace repositories, not Workspace DB state or repository-local configuration.
|
||||
|
||||
Container user and writable data directories matter: runtime/server images must be able to write their configured data directories and named volumes. The current local-image Compose setup avoids an image-level `User` override and sets data-directory permissions accordingly.
|
||||
|
||||
## Worker launch path
|
||||
|
||||
A browser-created Worker follows this control flow:
|
||||
|
||||
```text
|
||||
WebUI new-worker form
|
||||
runtime_id / profile / initial input / optional workdir selection
|
||||
|
||||
Workspace Backend create-worker API
|
||||
validate workspace scope
|
||||
resolve Runtime by canonical runtime_id
|
||||
resolve profile/manifest using Backend authority
|
||||
convert browser workdir selection into a Runtime working-directory claim
|
||||
forward a typed Worker creation request to the selected Runtime
|
||||
|
||||
Worker Runtime create_worker
|
||||
reject a requested primary workdir if an existing live/registered Worker already owns it
|
||||
spawn the Worker through the runtime execution backend
|
||||
return Worker detail, run state, and workdir binding
|
||||
|
||||
Workspace Backend post-spawn projection
|
||||
sync Worker registry
|
||||
sync workdir registry from Runtime summary
|
||||
sync Worker/workdir link
|
||||
return browser-safe Worker detail
|
||||
```
|
||||
|
||||
The Runtime guard against assigning the same primary workdir to two active Workers is required even when the Backend has a registry projection. It is the last authority that sees runtime-local Worker requests and execution bindings.
|
||||
|
||||
Worker interaction uses the unified Worker protocol WebSocket path. The older event-only stream is not an operation authority. Normal interaction should go through protocol transport and runtime/backend forwarding rather than raw local sockets.
|
||||
|
||||
## Working-directory model
|
||||
|
||||
A working directory is a materialized repository for a workspace on a concrete Runtime.
|
||||
|
||||
The normal invariant is:
|
||||
|
||||
```text
|
||||
workdir {
|
||||
workspace_id # Backend workspace scope
|
||||
runtime_id # concrete Runtime that owns the materialization
|
||||
repository_id # repository being materialized
|
||||
working_directory_id
|
||||
selector / resolved commit
|
||||
materialization status
|
||||
cleanliness
|
||||
}
|
||||
```
|
||||
|
||||
`repository_id` is part of the normal model. A workdir without a repository is not a normal reusable workspace workdir; it should be treated as diagnostic/orphan/corrupted state rather than as a first-class launch candidate.
|
||||
|
||||
The removed `management_kind` state must not be reintroduced. Whether a row was first authored by Backend creation or later observed from Runtime is provenance, not current workdir authority. Reuse eligibility must be derived from current state and ownership:
|
||||
|
||||
- Runtime reports the workdir as active/present;
|
||||
- cleanliness is clean;
|
||||
- no active Runtime Worker owns it as a primary workdir;
|
||||
- Backend worker/workdir links do not show an existing owner;
|
||||
- `primary_worker_id` is absent in the browser-safe summary.
|
||||
|
||||
Runtime-observed workdirs that satisfy those conditions are valid reusable candidates for the selected Runtime. Hiding them because Backend did not originally author the registry row creates a stale provenance bug.
|
||||
|
||||
## Workspace and Runtime sharing assumption
|
||||
|
||||
The current Docker and browser launch path treats a configured Runtime as serving the Workspace that registered it. Runtime workdir summaries do not carry independent workspace authority sufficient for arbitrary multi-workspace sharing.
|
||||
|
||||
If one Runtime is later shared by multiple Workspace Backends, the Runtime workdir materialization record/API must carry enough workspace identity to let a Backend decide whether a workdir belongs to its workspace. That should be modeled explicitly as workspace identity on materialization records, not through a vague managed/unmanaged kind.
|
||||
|
||||
Until that exists, do not infer cross-workspace ownership from display names, local paths, Runtime labels, or Backend registry provenance.
|
||||
|
||||
## Memory backend launch pitfall
|
||||
|
||||
Workspace memory backend HTTP operations run inside async Worker/runtime paths. They must use async HTTP clients. Blocking HTTP clients such as `reqwest::blocking` can panic when created/dropped inside a Tokio async context and can kill Worker controller tasks.
|
||||
|
||||
The current memory backend HTTP path uses async `reqwest::Client`. New Backend/Runtime launch code should preserve that boundary.
|
||||
|
||||
## Current validation surface
|
||||
|
||||
The local-image Compose path has been checked by building/loading the Nix Docker images, running `docker compose config`, starting the Compose stack, loading WebUI `/`, and confirming `/api/runtimes` sees the remote Runtime.
|
||||
|
||||
Code-level changes around Runtime/Backend/WebUI boundaries should generally be checked with the relevant Rust crates, WebUI check, and whitespace validation, for example:
|
||||
|
||||
```text
|
||||
nix develop -c cargo check -p yoi-workspace-server -p worker-runtime -p client
|
||||
nix develop -c cargo test -p yoi-workspace-server --lib
|
||||
cd web/workspace && deno task check
|
||||
git diff --check
|
||||
```
|
||||
@@ -1,40 +1,17 @@
|
||||
# Dogfooding Yoi
|
||||
# ドッグフーディング中の安全規則
|
||||
|
||||
This repository is developed with Yoi itself. Dogfooding is valuable because it exposes orchestration, memory, TUI, and workflow problems under real use.
|
||||
YoiでYoiを開発するときも、稼働中のServer・Runtimeを新しいbinaryの最初の起動試験に使わない。自身を実行しているプロセスは停止せず、マージ後の稼働環境の更新は利用者が行う。
|
||||
|
||||
## Pre-restart gate
|
||||
## 更新前の検証
|
||||
|
||||
Never use the live dogfood Server or Runtime as the first startup test for a new binary. A dogfood restart is allowed only after this sequence succeeds:
|
||||
製品のServer・Runtimeエントリーポイントをビルドし、変更した契約と依存箇所のテスト、root check、format、差分検査を行う。別のDB、Runtimeデータ、ポートを使った隔離環境で起動と実際の署名要求を確認する。過去の接続成功を保存して認証済みと見なすfixtureでは、本番の要求単位の認証を検証できない。
|
||||
|
||||
1. Build the production entrypoints: `cargo build -p worker-runtime --bin yoi-runtime -p yoi-workspace-server --bin yoi-server`.
|
||||
2. Run the focused and dependent tests for the changed contracts, followed by workspace-root `cargo check`, `cargo fmt --all -- --check`, and `git diff --check HEAD`.
|
||||
3. Exercise the provisioning and operational checks in [Workspace ↔ Runtime authentication](server-runtime-auth.md) against isolated Server DB, Runtime data, and ports. The Workspace owner must configure the Runtime endpoint and pinned Runtime key, the Runtime operator must install the Workspace issuer bundle, and actual signed requests must authenticate. A connection-test result describes only that attempt; no stored verification state is a readiness prerequisite.
|
||||
4. Have an external supervisor or operator restart Server and Runtime from the same validated source commit/build artifacts. A Worker hosted by the target Runtime must never terminate its own Runtime.
|
||||
5. Verify post-restart readiness through the Workspace Runtime projection, ping, Worker list/create, protocol subscription, and a Runtime-to-Server source-proof operation before treating the environment as healthy.
|
||||
稼働環境を更新する際は、検証したsourceとartifactを特定できるようにする。更新後は、疎通だけでなく、Workerの一覧・起動・protocol購読など変更に関係する操作を確認する。正常なHTTP応答だけで、画面、console、APIに残るエラーを無視しない。
|
||||
|
||||
The former isolated startup shell harness depended on removed Server-global trust commands and is intentionally not a fallback smoke path. New automated startup coverage must configure the same Workspace-scoped connection and explicit Runtime-side Workspace trust used by production, then exercise per-request authentication rather than recreating global trust or seeding a past verification result.
|
||||
検証用のWeb buildや生成処理が、稼働中の開発サーバーの生成物を書き換えないようにする。別のsource treeを検証する場合も、生成物やbuild cacheの混用によって結果を誤認しないように隔離する。
|
||||
|
||||
## What to record
|
||||
## 問題の記録
|
||||
|
||||
When a tool limitation, workflow obstacle, or model-facing policy problem blocks work, record it under `docs/report/` or a work item artifact. Do not turn every minor annoyance into a maintained design doc.
|
||||
作業を妨げるツール制限、誤解を招く指示、危険な復旧手順を確認した場合は、`docs/report/`または作業記録に報告する。試みたこと、観測した事実、影響、原因の確度、必要な設計判断を区別する。小さな不便ごとに維持対象の設計文書を増やさない。
|
||||
|
||||
A report is useful when it explains:
|
||||
|
||||
- what the agent/user tried to do
|
||||
- what made the work unsafe, confusing, or slow
|
||||
- what design boundary was missing
|
||||
- what evidence was observed
|
||||
|
||||
For a Remote Runtime rollout, also record the source commit, binary artifact digest, Server schema version, Runtime `binding_id` and pinned key fingerprint, the current Workspace `key_id` and public fingerprint, the Runtime-side trust registration/revocation, and typed HTTP/WebSocket outcomes. Do not copy a Runtime trust ID into the Server connection configuration or treat a successful connection test as persisted authentication. A successful document response does not outweigh visible UI, console, or API errors.
|
||||
|
||||
## Runtime command caveat
|
||||
|
||||
After rebuilding and restarting during dogfooding, `current_exe()` can point at a deleted binary path. Use typed runtime-command configuration and the development-only `YOI_POD_RUNTIME_COMMAND` executable override rather than reviving shell-command overrides.
|
||||
|
||||
## Multi-Worker work
|
||||
|
||||
Use child Workers for scoped tasks and reviews, but keep orchestration decisions in visible project records. Do not merge, close, or clean up merely because a child notification arrived.
|
||||
|
||||
## Secrets and logs
|
||||
|
||||
Do not put secrets, private prompts, or ignored secret-like file contents into diagnostics, work items, docs, session logs, or model context. During broad audits, existence/path checks are enough unless the user explicitly asks to inspect content.
|
||||
記録には調査に必要なsource・artifact・操作結果の識別情報だけを残す。秘密情報、認証情報、非公開プロンプト、秘密を含むファイルの内容を文書・診断・セッションへ転載しない。広範な監査では、明示的な内容確認が必要でない限り、存在とパスの確認にとどめる。
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# Environment boundary
|
||||
|
||||
Environment variables are a minimized runtime boundary. Prefer explicit profile/manifest configuration and secret references over ambient process state.
|
||||
|
||||
## Why minimize environment variables
|
||||
|
||||
Ambient environment is hard to audit: it can differ between shells, services, spawned Workers, tests, and restored processes. If important runtime behavior depends on it, reproducing a session becomes harder.
|
||||
|
||||
Yoi keeps environment variables for narrow bootstrap and development cases, while normal provider credentials and runtime configuration should be explicit records.
|
||||
|
||||
## Principles
|
||||
|
||||
- Distinguish data/runtime paths from resource/config paths.
|
||||
- Prefer embedded builtin resources over installed runtime resource directories.
|
||||
- Use explicit secret refs for provider and WebSearch credentials.
|
||||
- Keep dev-only executable overrides clearly named and documented.
|
||||
- Avoid shell-command parser overrides for runtime Worker launch.
|
||||
- Tests should prefer typed fixtures/injection and mutate process environment only around thin env-reader behavior.
|
||||
|
||||
## Current surface
|
||||
|
||||
Use `YOI_*` for current environment variables. Old project prefixes should not be reintroduced.
|
||||
|
||||
`YOI_POD_RUNTIME_COMMAND` is a legacy-named development-only executable-path override for typed `yoi worker` launch. It is not a general shell-command override.
|
||||
@@ -1,57 +0,0 @@
|
||||
# External Workdir sharing permissions
|
||||
|
||||
`yoi workdir share` exposes a client-hosted directory through an authenticated,
|
||||
outbound Backend connection. The operator selects exactly one permission level:
|
||||
|
||||
- **READ** (capability bits `25`): file reads and read-only discovery operations
|
||||
such as Stat, List, ViewImage, Glob, and Grep.
|
||||
- **WRITE** (capability bits `31`): READ plus Write and Edit. Including READ
|
||||
preserves the read-before-write content-hash contract. WRITE never enables
|
||||
COMMAND.
|
||||
- **COMMAND** (capability bits `63`): WRITE plus READ plus command start, status,
|
||||
bounded output, and cancellation. It executes on the sharing host with the OS
|
||||
authority of the user running the CLI; External Workdir command execution is
|
||||
**not a sandbox** and does not confine the process to the shared directory.
|
||||
|
||||
In short, `COMMAND ⇒ WRITE ⇒ READ`. READ is the default. Interactive mode asks
|
||||
for one level and shows its included permissions before creating the grant. For
|
||||
automation, pass `--permission` once:
|
||||
|
||||
```sh
|
||||
yoi --backend https://example.invalid workdir share ./project \
|
||||
--workspace-id workspace-a \
|
||||
--permission command \
|
||||
--non-interactive
|
||||
```
|
||||
|
||||
`--read-only` remains an alias for the READ level. `--read-write` remains an
|
||||
alias for the WRITE level and never enables COMMAND. These legacy flags conflict
|
||||
with `--permission`, and multiple `--permission` arguments are rejected so
|
||||
ambiguous automation fails closed.
|
||||
|
||||
The grant API retains the boolean projection for compatibility, but accepts only
|
||||
these combinations when creating new grants:
|
||||
|
||||
| Level | `read` | `write` | `command` |
|
||||
|---|---:|---:|---:|
|
||||
| READ | `true` | `false` | `false` |
|
||||
| WRITE | `true` | `true` | `false` |
|
||||
| COMMAND | `true` | `true` | `true` |
|
||||
|
||||
Older non-hierarchical grants such as command-only or READ+COMMAND are not
|
||||
silently widened. The Backend refuses their projection, provider registration,
|
||||
and subsequent restore/reconnect with guidance to revoke and re-share the
|
||||
directory at one of the supported levels. General Workdir attachment/session
|
||||
capability intersections may still produce partial capability sets; that
|
||||
restriction applies only to External grant authority.
|
||||
|
||||
Omitting `--ttl` creates no automatic expiry. An explicit TTL must be between
|
||||
60 seconds and 24 hours. Ctrl-C, revoke, expiry, provider disconnect, attachment
|
||||
detach, and provider-generation replacement terminate and reap active commands.
|
||||
A reconnect creates a fresh command namespace; old handles and results are not
|
||||
accepted by the new generation.
|
||||
|
||||
Command output uses the existing bounded inline contract. Worker-local spill
|
||||
paths are not sent to the sharing provider, and provider-local paths are never
|
||||
returned to the Backend or model. Truncated External command output is therefore
|
||||
reported without a full-output host path.
|
||||
@@ -1,49 +0,0 @@
|
||||
# Shared text file operations
|
||||
|
||||
`fs-operation::text` owns the provider-neutral read/edit/write argument contracts,
|
||||
JSON decoding, argument validation, pure text outcomes and byte-bound calculations.
|
||||
The argument schemas also supply WIP parameter declarations. Ordinary Tools flatten
|
||||
these arguments alongside their own Workdir routing fields; checkout WIP uses the
|
||||
same Tool execution functions, not a third editing implementation.
|
||||
|
||||
Edit requires a nonempty `old_string`, a different `new_string`, and a unique
|
||||
non-overlapping match unless `replace_all` is true. Limits count UTF-8 bytes, are
|
||||
supplied by each provider, and are checked (including arithmetic/capacity overflow)
|
||||
before allocating a replacement result. Outcomes carry replacement count and byte
|
||||
size independently of persistence metadata.
|
||||
|
||||
## Provider-owned differences
|
||||
|
||||
- Workdir read exposes optional line offset/limit and numbered, bounded output.
|
||||
Workspace settings read remains a whole-text read with no arguments, preserving
|
||||
content type and digest. Line range defaults and numbered rendering live in the
|
||||
core but are only selected by the Workdir adapter.
|
||||
- Workdir routing, attachment generation, Scope and permission identity remain in
|
||||
Worker/Tools/Workdir. Read history and content hashes stay in the existing
|
||||
attachment-scoped tracker. Descriptor-protected reads and checked edits/writes
|
||||
retain their existing validator, identity and save boundary in the provider.
|
||||
- Workspace settings grants, access mode and connection lifetime remain in Backend.
|
||||
The settings adapter binds the read to captured entry metadata and generates a
|
||||
canonical commit using the same tree digest. Decodal validation,
|
||||
formatting and atomic multi-file persistence remain in the canonical Backend
|
||||
commit. Content type, create/delete/rename/apply_changes and attach are settings
|
||||
operations, not ordinary OS file operations.
|
||||
|
||||
The core does not perform I/O, authorize anything, or own content/existence
|
||||
mirrors. Providers invoke pure processing inside their existing consistency
|
||||
boundary; it must not be used to turn a checked edit into an unchecked
|
||||
Read–Write sequence.
|
||||
|
||||
## External contracts
|
||||
|
||||
Operation names, argument shapes and successful responses remain unchanged.
|
||||
Workspace settings Edit now rejects identical old/new strings before reading or
|
||||
committing (the intended behavior change). Pure text failures are explicitly
|
||||
mapped to Tool argument errors or WIP `InvalidArguments`/`ResourceLimitExceeded`
|
||||
by the adapters. A mutation's post-dispatch transport or response failure is still
|
||||
`OutcomeUnknown`; the core does not retry it or reinterpret it as a validation
|
||||
rejection.
|
||||
|
||||
Pure policy matrices belong in `fs-operation::text` tests. Provider tests cover
|
||||
entrypoint wiring and the existing authorization, observation, checked-save,
|
||||
configuration CAS and unknown-outcome boundaries rather than copying that matrix.
|
||||
@@ -1,45 +0,0 @@
|
||||
# Plugin package authoring
|
||||
|
||||
Dynamic Worker Plugins are not currently installable or executable. This guide covers only the retained offline `.yoi-plugin` authoring format.
|
||||
|
||||
## Commands
|
||||
|
||||
Every retained command uses an explicit local input or destination:
|
||||
|
||||
```sh
|
||||
yoi plugin new rust-component-tool ./example-plugin
|
||||
yoi plugin new rust-component-service ./example-service
|
||||
yoi plugin check ./example-plugin
|
||||
yoi plugin pack ./example-plugin --output ./example-plugin.yoi-plugin
|
||||
yoi plugin check ./example-plugin.yoi-plugin
|
||||
```
|
||||
|
||||
- `new` writes an embedded starter template to the named destination and refuses unsafe or non-empty destinations.
|
||||
- `check` parses and validates the named directory or package without executing Plugin code.
|
||||
- `pack` validates the named directory and writes a deterministic constrained archive.
|
||||
|
||||
`list`, `show`, `--workspace`, and `--profile` are intentionally unavailable. They previously implied ambient Workspace/user catalog discovery.
|
||||
|
||||
## Safety and authority
|
||||
|
||||
Offline package commands do not:
|
||||
|
||||
- inspect repository or ancestor `.yoi/plugins` directories;
|
||||
- inspect a user-data Plugin store;
|
||||
- enable or install a package;
|
||||
- mutate Profile or Manifest configuration;
|
||||
- register Worker Tools, Services, or Ingress handlers;
|
||||
- instantiate or execute a WASM component; or
|
||||
- grant filesystem, network, secret, Ticket, or Workspace authority.
|
||||
|
||||
`plugins` and `feature.plugins` are rejected by current Worker Manifest/Profile resolution. Statically compiled built-in Features are the only current Worker capability source.
|
||||
|
||||
## Package format
|
||||
|
||||
A package directory contains `plugin.toml` plus the files named by that manifest. A packed `.yoi-plugin` uses the constrained deterministic archive format documented in [`../design/plugin-packages.md`](../design/plugin-packages.md). Validation rejects malformed metadata, unsafe paths, links, unsupported entries, bounds violations, digest inconsistencies, and legacy raw core-Wasm runtime declarations.
|
||||
|
||||
Generated templates include a placeholder `plugin.component.wasm`. Replace it with a real built component before `check` can report the package as verified. A verified package is still only an offline artifact; verification does not install or authorize it.
|
||||
|
||||
## Future Server Plugin platform
|
||||
|
||||
Do not copy packages into repository or user-data catalogs. Future installation must go through Server-owned package/artifact authority, immutable identity and digest selection, Backend-authored Worker execution plans, Runtime verification, and sandboxed execution. That platform is separate work and must not reintroduce filesystem catalog fallback.
|
||||
@@ -1,83 +0,0 @@
|
||||
# Runtime request proof rejection diagnostics
|
||||
|
||||
`runtime_request_proof_rejected` (`yoi::auth`, WARN) is an internal operational
|
||||
record. HTTP responses are unchanged: 401 with no proof details (repository
|
||||
routes retain their existing generic API error envelope). It does **not** establish
|
||||
why the reported same-machine intermittent 401 occurred. No reproduction or
|
||||
causal measurement of that incident has been obtained.
|
||||
|
||||
## Verification and correlation
|
||||
|
||||
- `request_id`: Server-generated UUID identifying this rejection event. It is not
|
||||
a caller-supplied ID or a response header, and is not a cross-service trace ID.
|
||||
- `claimed_runtime_id_hash`, `claimed_worker_id_hash`, `claimed_token_id_hash`:
|
||||
lowercase 64-character hex SHA-256 of
|
||||
`b"yoi-runtime-proof-diagnostic-id-v1\0" + id.as_bytes()`.
|
||||
`worker_source::diagnostic_id_hash` exposes this correlation function. Hash a
|
||||
known Runtime/Worker ID to find its failures; token ID hashes correlate replay
|
||||
attempts without logging the proof or JTI itself. These are fingerprints, not
|
||||
encryption: low-entropy IDs can be guessed. Do not place secrets in IDs.
|
||||
- All claims-derived fields are prefixed `claimed_`, even after signature checks.
|
||||
`proof_verification` describes check progress, **not an authorized principal**:
|
||||
- `unverified`: decoding may have succeeded, but signature verification has not.
|
||||
- `signature_verified`: signature passed; request binding or time failed.
|
||||
- `request_verified`: signature, request bindings and time passed, but replay,
|
||||
membership or authority failed.
|
||||
- When decoding fails, there are no claims fingerprints. Do not infer a Runtime
|
||||
from a malformed proof. No raw claims, proof, key, header, body or raw error is
|
||||
logged. The body digest and claim mismatch values are also deliberately absent.
|
||||
- Method, query-free path, and requested workspace are request metadata, not
|
||||
authenticated claims. They are bounded to 32, 512 and 128 UTF-8 bytes, with
|
||||
control characters removed. Truncated paths are not unique identifiers. Query
|
||||
names and values are never included. Paths are operational metadata, not a
|
||||
place to put secrets.
|
||||
|
||||
## Reasons and their limits
|
||||
|
||||
Decoding distinguishes `invalid_format`, `invalid_encoding`, and
|
||||
`malformed_claims`; no parser error text is included. Signature checks distinguish
|
||||
`invalid_runtime_public_key` from `invalid_signature` (which cannot tell whether
|
||||
an attacker, wrong key, corruption, or configuration mismatch caused the failure).
|
||||
|
||||
Request binding reports the first mismatch, in the existing check order:
|
||||
`issuer_mismatch`, `audience_mismatch`, `workspace_mismatch`, `worker_mismatch`,
|
||||
`permission_mismatch`, `method_mismatch`, `path_mismatch`, `body_digest_mismatch`.
|
||||
The server selects the trusted key by the unverified issuer and expects that
|
||||
same issuer/worker; issuer and worker mismatches can be diagnosed by the lower
|
||||
verifier but are not independently detectable by the current server caller.
|
||||
These diagnostics do not add new identity checks or change authorization.
|
||||
|
||||
`backend_audience_unavailable` denotes missing Backend public URL configuration.
|
||||
`runtime_trust_missing_or_revoked` deliberately covers both absence and revocation,
|
||||
as the existing store lookup/filter does not preserve that distinction.
|
||||
`replay` means JTI consumption returned false. `worker_catalog_membership` means
|
||||
neither registry membership nor an active create reservation was found.
|
||||
|
||||
Store failures identify their check stage without exposing raw errors:
|
||||
`runtime_trust_authority_unavailable`, `replay_authority_unavailable`,
|
||||
`worker_catalog_authority_unavailable`, `worker_reservation_authority_unavailable`,
|
||||
`worker_singleton_authority_denied`. The last can represent either an owner
|
||||
conflict or an authority failure: the store currently returns a shared error,
|
||||
so the diagnostic does not guess which. `verification_time_out_of_range` means
|
||||
that conversion to the JTI consumption timestamp failed; it is not proof expiry.
|
||||
`invalid` is a fallback for lower verifier errors not classified for this path.
|
||||
|
||||
## Time contract and T-728 coordination
|
||||
|
||||
This change preserves strict integer-second checks, inclusive bounds, and
|
||||
verification order: signature, request binding, then `iat > now`, then `exp < now`.
|
||||
No skew tolerance is added. If both time predicates fail, future issuance wins.
|
||||
|
||||
- `issued_in_future`: `time_delta_seconds = iat - now` (positive).
|
||||
- `expired`: `time_delta_seconds = now - exp` (positive).
|
||||
- Only these reasons include `issued_at_unix`, `expires_at_unix`,
|
||||
`verified_at_unix` and `time_tolerance_seconds` (currently zero).
|
||||
- Deltas use `i64::abs_diff`, preserving the full positive `u64` range without
|
||||
overflow. All times come from the actual signature-verified rejection and
|
||||
the exact clock sample used for that decision, not a second log-time sample.
|
||||
|
||||
T-728 owns any change to tolerance/validity policy. If it changes these checks,
|
||||
the error time data, logged effective tolerance, and boundary tests must change
|
||||
together. Do not label future issuance as expiry or independently recompute the
|
||||
rejection using a different clock sample. This is instrumentation for the next
|
||||
incident, not evidence that the original incident has been resolved.
|
||||
@@ -1,252 +1,81 @@
|
||||
# Rust testing strategy for Yoi
|
||||
# Rustテストの設計規則
|
||||
|
||||
Yoi tests protect behavior that Rust's type system cannot prove and deliberate compile-time API constraints that must not regress. A test must name the contract it protects and fail for one understandable reason.
|
||||
テストは、Rustの型だけでは保証できない製品の振舞いと、維持すべきコンパイル時のAPI制約を検証する。名前から守る規則が分かり、一つの失敗から原因となる契約を絞れるようにする。件数、配置場所、smoke testの成功を網羅性の根拠にしない。
|
||||
|
||||
This document defines how to choose test scope for Yoi's architecture: `yoi` CLI, Workspace Server, Worker Runtime, Worker tools/WIP adapters, SQLite authorities, Web/API DTOs, and local/runtime process boundaries. Cargo target classification and architectural test scope are separate concepts. Test the product logic and the connections that carry it; neither a directory name nor a full-stack fixture is evidence of coverage.
|
||||
## 検証範囲の選択
|
||||
|
||||
References used while writing this policy:
|
||||
利用者が観測する振舞いと、起きた場合に問題となる失敗を先に定める。その失敗を検出できる最小の範囲を、影響・発生可能性・保守コストから選ぶ。「最小」は、mockに置き換えやすい範囲ではなく、必要な接続まで含む範囲である。
|
||||
|
||||
- Rust Book, "Test Organization": unit tests are focused and may test module-private behavior; integration tests use public APIs the way external code does.
|
||||
- Cargo Book, `cargo test`: package, target, and feature selection determine which tests Cargo builds and runs.
|
||||
|
||||
## Test scope is not Cargo target classification
|
||||
|
||||
Cargo integration-test targets, normally under `tests/`, compile as separate crates and exercise the library's public API. In-crate tests can access private implementation details. This distinction determines compilation and visibility, not how many production components a test connects.
|
||||
|
||||
Use the following scopes when explaining what a test proves:
|
||||
|
||||
| Scope | What it proves |
|
||||
| 範囲 | 検証する内容 |
|
||||
| --- | --- |
|
||||
| Unit / module-local | One local rule without composing the surrounding subsystem |
|
||||
| Component | One named subsystem's behavior through its boundary, with external dependencies substituted where appropriate |
|
||||
| Integration / composition | Agreement between connected production components, including propagation of data, authority, and effects |
|
||||
| E2E | An explicitly selected product journey through the deployed interfaces and execution boundaries relevant to that journey |
|
||||
| 単体 | 周辺の仕組みを組み立てずに確認できる局所的な規則 |
|
||||
| コンポーネント | 対象のサブシステムを、その境界から操作した振舞い |
|
||||
| 結合 | 本番の構成要素を接続した際のデータ・権限・副作用の一致 |
|
||||
| E2E | 選定した利用場面を、必要な製品インターフェースと実行境界を通して確認した結果 |
|
||||
|
||||
These scopes are relative to the named subject. Describe the production path and substitutions rather than relying on the label. A public-API test against a fake dependency does not prove agreement with that dependency's production implementation. Yoi's placement and execution rules below make these scopes operational rather than leaving each author to choose a layout.
|
||||
Cargoのintegration-test targetは別crateとしてコンパイルされ、公開APIを使う。この区分は可視性とコンパイル単位を決めるものであり、上の検証範囲とは別である。実装内のテストでも本番の構成要素を接続すれば結合テストとなる。
|
||||
|
||||
Process boundaries are an execution concern, not a separate test level. An isolated child process can be part of an ordinary integration test when the connection under test requires it. It does not require an E2E exception merely because it is a real product binary. Assembling the product to exercise a user journey belongs to the E2E suite, not a crate-local process harness.
|
||||
プロセス境界も検証レベルとは別である。接続の検証に必要な隔離子プロセスは通常の結合テストに含める。利用場面を試すために製品全体を組み立てる場合は、crate内に別の起動基盤を作らず`tests/e2e`を使う。
|
||||
|
||||
## Choose scope from risk and observable behavior
|
||||
## 本番の接続と代替実装
|
||||
|
||||
Start with the observable behavior and the failure that matters to the user. Select the smallest scope that can expose that failure, considering its likelihood, impact, and the cost of maintaining the test. Smallest means sufficient, not merely easiest to mock.
|
||||
結合テストには、入口、本番の構成要素、代替した境界、観測する結果を明示する。主張する接続は本番実装のままにし、その外側だけを代替する。双方を別々のfakeでテストしても、実際の組合せが動くことは証明できない。
|
||||
|
||||
- Keep local policy matrices in unit/component tests. Use integration tests when the risk lies in agreement or handoff between components.
|
||||
- Test domain logic directly. UI interaction and rendering tests protect UI behavior; they do not replace proof of state transitions, authority decisions, or durable effects.
|
||||
- Select retry, concurrency, cancellation, and recovery cases from the product's guarantees and failure impact. Do not expand every change into all failure modes or all component combinations.
|
||||
- Prefer deterministic boundary failures for logical recovery tests. Use process termination only when process death itself is material to the guarantee and narrower tests cannot prove it. Logical recovery tests do not establish arbitrary OS-crash durability.
|
||||
- Reserve E2E for behavior that requires the wider product path. Explain the additional risk it covers beyond component and integration tests.
|
||||
永続化・トランザクションの保証には一時DBなどの実ストレージを使い、正本からの再読取りで結果を確認する。呼出し記録だけでは永続効果を証明できない。認証・権限を主張するテストでは、その境界をfixtureで迂回しない。障害注入でも、検証対象の復旧処理を代替しない。
|
||||
|
||||
## Production connections and test doubles
|
||||
再試行、競合、取消、復旧は製品の保証と失敗時の影響から選ぶ。全変更へ全障害の組合せを追加しない。通常は境界で決定的に失敗させ、プロセス死そのものが保証に関わる場合だけ終了を使う。論理的な復旧テストを、任意のOSクラッシュへの耐性の証拠にしない。
|
||||
|
||||
For integration tests, make the entry point, real components, substituted boundaries, and observable result clear in the test or a short fixture comment.
|
||||
## 型の保証と外部契約
|
||||
|
||||
1. Keep the connection under test real. Independently testing both components against fakes does not prove they work together.
|
||||
2. Substitute dependencies outside the behavior being claimed. Do not reproduce the product rule in a fake and then assert that it succeeds.
|
||||
3. Exercise the production operation and observe its result through the boundary that owns the guarantee. Use real temporary storage for persistence and transaction claims; captured calls alone do not prove durable effects.
|
||||
4. Preserve the identity and authority context relevant to the claim. A fixture that bypasses an authorization boundary cannot prove that boundary.
|
||||
5. Inject failures without replacing the recovery logic being tested. Synchronize through observable events or conditions, not timing guesses.
|
||||
構築済みの型について、enumの値域、必須フィールドの存在、所有権、`Option`の有無、網羅的な`match`、通常の引数・戻り値・trait適合を実行時に再検証するテストは追加しない。
|
||||
|
||||
## Coverage responsibility and maintenance
|
||||
JSON、TOML、DB行、HTTP、WebSocket、CLI引数、環境、パス、LLMのツール入力から型へ変換する境界は別である。Serde表現、schema、経路、範囲制限、拒否条件は外部契約として検証する。コンパイルの受理・拒否自体が公開APIの契約なら、macro展開、typestate、trait boundなどの最小のcompile-pass/compile-failテストを置く。
|
||||
|
||||
Before adding tests, inspect existing coverage for the changed contract. Identify which tests own the local rules and which prove the relevant connections. Keep this explanation proportional to the change; it does not require a new coverage registry or a centralized integration suite.
|
||||
## 配置と所有者
|
||||
|
||||
Give each invariant a primary test at the boundary that owns it. Integration tests should prove connections rather than repeat every local input matrix. Strengthen existing tests where possible; if they already prove the contract, report that evidence instead of adding redundant tests. Test counts, directory placement, and a passing smoke test are not proof of coverage.
|
||||
|
||||
When a product contract changes, update affected fixtures and assertions, including opt-in suites. Preserve obsolete behavior only for an explicitly supported compatibility boundary. A skipped or unexecuted test is not evidence that the current behavior works.
|
||||
|
||||
## What Rust types already guarantee
|
||||
|
||||
Do not write tests that only re-prove these facts for values already constructed as Rust types:
|
||||
|
||||
- enum values are one of the declared variants;
|
||||
- required struct fields exist;
|
||||
- references obey Rust ownership and lifetime rules;
|
||||
- `Option<T>` is present or absent explicitly;
|
||||
- exhaustive `match` expressions handle every variant;
|
||||
- ordinary function argument and return types agree;
|
||||
- a `Box<dyn Target>` or trait object satisfies the trait at compile time;
|
||||
- a typed DTO has fields of the declared Rust type after successful construction.
|
||||
|
||||
A typed value still needs a **boundary** test when it is converted from untrusted JSON, TOML, files, SQLite rows, WebSocket frames, HTTP requests, CLI argv, environment variables, model tool input, external process output, or platform paths. Serde representation, path containment, HTTP route shape, SQLite schema, and prompt/tool JSON schemas are external contracts, not compiler guarantees.
|
||||
|
||||
Compile-time behavior may itself be a public API contract. Compile-pass or compile-fail tests are appropriate when downstream code must continue to compile or be rejected, including macro expansion, typestate transitions, public trait bounds, and unavailable methods in a restricted state. Such a test protects the shape of the public API; it does not re-run a compiler guarantee as a runtime assertion.
|
||||
|
||||
Yoi currently uses `trybuild` for compile-fail API-shape tests in `agen`. Add new `trybuild` tests only when the compile-time accept/reject behavior is the product contract being protected. Keep fixtures minimal and name them after the API rule, not after the implementation helper that happens to trigger the compiler error.
|
||||
|
||||
## Test names
|
||||
|
||||
A test name must describe an observable rule. Prefer names of the form `<condition>_<expected_behavior>` or `<operation>_<expected_result>`. Include the input class or state when omitting it would make the rule ambiguous.
|
||||
|
||||
Good names:
|
||||
|
||||
- `backend_target_rejects_local_worker_operations`
|
||||
- `cli_backend_url_overrides_config_file_value`
|
||||
- `repository_path_rejects_symlink_escape`
|
||||
- `second_enter_does_not_submit_rewind_twice`
|
||||
- `workspace_server_returns_not_found_for_unknown_worker`
|
||||
|
||||
Bad names:
|
||||
|
||||
- `test_target`, `test_parse`, or `test_create`: names the subject but not the rule.
|
||||
- `works`, `basic`, `happy_path`, or `success`: does not say what outcome is protected.
|
||||
- `regression_1234`: records provenance but loses the lasting product behavior.
|
||||
- `ticket_test` or `server_test_2`: adds no information beyond the module or file.
|
||||
- a private helper name alone, such as `normalize_path`: couples the name to implementation without stating the externally relevant result.
|
||||
|
||||
No prefix is required. `contract_` is normally redundant because every test in this policy protects a contract. The following narrower labels are optional:
|
||||
|
||||
- `boundary_` when distinguishing external input handling from typed internal behavior is useful in that test target;
|
||||
- `regression_` when the original failure symptom is itself the clearest durable rule;
|
||||
- `smoke_` when the deliberately limited contract is successful wiring to a minimum state.
|
||||
|
||||
Examples such as `boundary_repository_path_rejects_symlink_escape`, `regression_second_enter_does_not_submit_rewind_twice`, and `smoke_workspace_server_reaches_health_route` remain meaningful after removing the prefix. A label does not rescue a vague name such as `boundary_path`, `regression_1234`, or `smoke_works`.
|
||||
|
||||
## Yoi contract map
|
||||
|
||||
This map identifies responsibility areas, not an exhaustive scenario checklist or a snapshot of implementation details. Select tests from the current product contract and the risks of the change; do not turn an old fixture or state name into a new product requirement.
|
||||
|
||||
| Area | Guaranteed by types | Must be tested |
|
||||
| 範囲 | 配置 | 実行区分 |
|
||||
| --- | --- | --- |
|
||||
| CLI parser and Target selection | parsed `Mode`, `LaunchMode`, `TargetKind`, and command enum variants exist | top-level target option precedence, mutually exclusive options, default config merge, local/backend-only rejection, help/usage text for public command surface |
|
||||
| Client config | typed config structs after TOML/environment parse | data-dir and cwd config locations, environment snapshot to startup config, property-wise overlay, missing/empty backend URL diagnostics, workspace-to-backend resolution, explicit CLI override over config |
|
||||
| Workspace Server HTTP API | handler function signatures and response DTO field types | route shape, scoped workspace authority, status-code mapping, request validation, bounded list/detail bodies, mutation round trips, compatibility routes only where explicitly adopted as product contracts |
|
||||
| Runtime HTTP/WebSocket protocol | protocol enum variants and typed frames after decode | JSON wire compatibility, frame ordering, snapshot/backlog/live semantics, bounded output, cancellation/restore lifecycle, unknown id/status errors |
|
||||
| Client crate Target operations | trait method signatures and result DTO types | unsupported operation errors, Local/Backend behavior parity where promised, stopped-worker semantics, URL/workspace/routing normalization |
|
||||
| Worker tool definitions | `Tool` trait implementation and JSON value construction compile | tool name/schema stability, input validation, Backend authority requirement, bounded output, no direct local authority fallback, side effects/audit records |
|
||||
| Tickets | typed Ticket inputs, state values, and assignment references after parse | authority mutation/readback, lifecycle and resolution semantics, assignment identity and authority, relations and audit history, separation of lifecycle, work progress, and delivery |
|
||||
| Objectives | typed Objective summaries/details after authority read | SQLite authority mutation round trip, canonical id allocation, state/title/body validation, ticket link/unlink, audit/event rows, scoped API/tool behavior |
|
||||
| Memory | typed memory operation inputs after parse | single-document edit semantics, old_string uniqueness, staging close lifecycle, bounded reads, Backend Workspace authority use, no filesystem fallback in Worker tools |
|
||||
| Worker lifecycle and workdirs | Rust structs for worker/workdir records | restore/dry-restore decisions, stopped/corrupted state transitions, occupancy projection, repository path containment, cleanup idempotence |
|
||||
| Workspace authority and SQLite stores | Rust records and trait methods compile | schema migrations, foreign-key/unique constraints, transaction rollback behavior, authority boundary selection, import-only legacy paths |
|
||||
| Nix/Docker/dev scripts | Nix attr names and shell strings are strings | binary names, package/bin target selectors, image entrypoints, fixed-output hashes, smoke help checks |
|
||||
| Web frontend TypeScript/Rust DTO boundary | generated/declared TS types after decode | API representation and route agreement, projection logic, validation and error handling; source-text assertions do not prove runtime or rendered behavior |
|
||||
| Prompts and tool schema resources | resource files exist as strings | prompt inclusion policy, feature-driven instruction contribution, schema names/descriptions visible to the model, no stale command/tool references |
|
||||
| 単体 | 所有module内の`#[cfg(test)] mod tests`、または`src/`配下の子テストmodule | 通常のcrateテスト |
|
||||
| コンポーネント | 所有crateの`src/<component>/tests.rs`など。公開APIを外部利用することが目的なら`tests/` | 通常のcrateテスト |
|
||||
| 結合 | 接続を所有するcrateの`tests/`。内部アクセスが必要なら結合範囲を明記したcrate内module | 対象の製品featureを含む通常のcrateテスト |
|
||||
| E2E | Workspaceの`tests/e2e` packageと共有harness | 専用の明示選択target/job |
|
||||
|
||||
## Test design rules
|
||||
Cargo test-target間の補助処理は`tests/common/mod.rs`、crate内ではテスト支援moduleにまとめる。関連する結合ケースは同じtargetのmoduleに分け、ケースごとに実行ファイルを増やさない。公開範囲を広げてまで内部テストを`tests/`へ動かさない。
|
||||
|
||||
1. Arrange only the state relevant to the named contract.
|
||||
2. Exercise public or module-boundary behavior where possible. Test a helper directly only when that helper is the policy boundary.
|
||||
3. Assert outcomes, rejected inputs, API responses, durable records, or state transitions rather than private call order and incidental ids.
|
||||
4. Use a table in one test when several inputs describe one policy matrix. Use separate tests when failures would represent different product rules.
|
||||
5. Boundary tests must include the boundary input in the fixture: argv slice, JSON body, TOML text, explicit environment snapshot, SQLite row/migration, path, HTTP request, protocol frame, or tool input JSON.
|
||||
6. Filesystem tests use an RAII temporary directory and leave no files behind, including after panic. Do not write tests against the user's real Yoi data dir.
|
||||
7. SQLite tests use temporary databases or in-memory stores and explicitly seed the authority records they need. Do not depend on the developer's live `server.db`.
|
||||
8. Tool tests assert the model-visible contract: tool name, schema, validation, bounded content, authority errors, and durable side effects. Do not only assert that a Rust helper was called.
|
||||
9. API tests should assert status codes and response bodies for both success and rejection. If a helper hides response bodies, include the body in assertion failure output.
|
||||
10. Serde/DTO tests should exist only for external representation contracts: renamed fields, wire enums, bounded truncation, or explicitly adopted compatibility with existing persisted data. Do not add aliases, legacy variants, routes, or tests for them without an explicit compatibility decision.
|
||||
11. Snapshot-like assertions are allowed only when the exact text/wire format is the contract. Prefer semantic assertions for large JSON/API responses.
|
||||
12. Built-in resources and prompts should be tested by deriving expected references from manifests or feature config, not by duplicating mutable inventory counts in Rust assertions.
|
||||
13. Do not test private helpers just to improve coverage numbers. If a private helper carries a policy boundary, name the contract in the test and keep the fixture minimal.
|
||||
14. Every bug fix either strengthens an existing test or adds one focused test whose name states the lasting rule or the original failure mode. A `regression_` prefix is not required.
|
||||
15. When a test needs a production resource, state which compatibility contract that resource represents. Generic fixtures should use minimal generated definitions.
|
||||
16. Do not introduce environment variables whose purpose is test control, test fixtures, or test observation.
|
||||
17. Code that consumes environment variables must snapshot them into typed startup configuration once. Test the environment-to-configuration mapping as a boundary over an explicit snapshot; all downstream logic tests pass typed configuration and do not read, set, remove, or depend on process-global environment variables.
|
||||
18. Tests must not depend on execution order. Ordinary tests must be safe under Cargo's default parallel execution and must not coordinate through process-global state.
|
||||
19. Synchronize on observable events or conditions. Do not use a fixed sleep to guess that asynchronous work has completed; use a timeout only as an upper bound that turns a hang into a diagnosable failure.
|
||||
20. Tests must not depend on live external services or unrestricted network access. Use in-process components or loopback transport; substitute only dependencies outside the connection being proved.
|
||||
21. Keep boundary integration in the ordinary test suite, including isolated process boundaries where needed. Add or extend E2E only for an explicitly selected product journey that narrower tests cannot prove, using the shared `tests/e2e` harness rather than assembling the product independently in a crate-local suite.
|
||||
複数crateの結合も、接続の契約を所有するcrateに置く。専用packageは依存制約または実際の共有需要がある場合に限る。通常の結合テストを配置の都合でE2Eへ移さない。parser・ツール方針は所有module、保存制約はstore、製品の権限判断はauthority、routerとauthorityの接続はAPIの結合suiteが担当する。
|
||||
|
||||
## Placement, fixtures, and execution
|
||||
## 共有fixtureと隔離
|
||||
|
||||
The following are Yoi's default operating rules, not interchangeable layout suggestions. The crate that owns a boundary also owns its test suite. Moving a test into another file does not change its scope.
|
||||
各結合・E2E suiteは、検証範囲、本番の構成要素、代替境界、実行target、前提条件を短いmoduleドキュメントに記す。資源の作成・解放を各ケースで重複させず、suiteの共有支援処理が次を所有する。
|
||||
|
||||
| Scope | Placement | Allowed dependencies | Execution class |
|
||||
| --- | --- | --- | --- |
|
||||
| Unit | Owning module's inline `#[cfg(test)] mod tests` or extracted child test module under `src/` | Local logic and minimal fixtures | Ordinary crate tests |
|
||||
| Component | Owning crate's `src/<component>/tests.rs` or equivalent named test module | Component implementation, temporary resources, doubles outside the component boundary | Ordinary crate tests |
|
||||
| Integration | A named test target under the owning crate's `tests/`; a named in-crate integration module when crate-internal access is required | Connected production implementations, temporary databases, loopback/IPC, isolated child processes when relevant to the boundary | Ordinary crate tests, with applicable product features |
|
||||
| E2E | Workspace `tests/e2e` package, with journey tests using its shared harness | Product assembly and actual execution interfaces needed by the selected journey, with isolated data and resources | Dedicated opt-in E2E target/job |
|
||||
- RAIIで管理する一時ディレクトリ・DB、隔離した識別子・通信endpoint。開発者の稼働環境や実データを借りない。
|
||||
- 明示的な実行ファイル、子のcwd・環境。親テストプロセスの環境を変更しない。
|
||||
- 起動から終了・回収までの子プロセス管理。assert失敗やtimeoutでも後始末する。
|
||||
- 観測可能なreadiness・イベントによる同期、待機の上限、障害診断の採取。
|
||||
|
||||
### Placement details and exceptions
|
||||
固定sleepで処理完了を推測しない。timeoutはハングを失敗へ変える上限として使う。通常テストは順序非依存・並列安全とし、live外部サービスや制限のないネットワークへ依存させない。通信には必要に応じてloopbackやIPCを使う。
|
||||
|
||||
- A component test whose purpose is external public-API use belongs under the owning crate's `tests/`. State its component scope in the target/module documentation; being a Cargo integration-test target does not expand its guarantee.
|
||||
- In-crate integration modules must be identified as such and document the production connection they cover. Do not expose private implementation solely to move tests under `tests/`.
|
||||
- Share Cargo test-target helpers under `tests/common/mod.rs`; use a crate-local test-support module for in-crate suites. Group related integration cases into modules of one target instead of creating a separate test executable for every case.
|
||||
- Cross-crate integration belongs to the crate that owns the composed boundary. Introduce a separate test-support or integration package only when dependency constraints or genuinely shared use justify it; do not place ordinary composition tests in `tests/e2e` for convenience.
|
||||
- Parser/help policy tests and Worker tool policy tests stay with their owning modules. Store tests prove persistence constraints; authority tests prove product policy. Route tests that connect router and authority implementations belong to the owning API's integration suite.
|
||||
- Keep product-assembly fixtures in `tests/e2e`. Do not create parallel E2E launch stacks under individual crates.
|
||||
環境変数は起動境界で一度だけsnapshotから型付き設定へ解決する。その変換を明示的snapshotでテストし、後続のロジックは型付き設定を受け取る。テスト制御・fixture・観測用の環境変数を追加せず、テストでプロセス全体の環境を読み書きしない。
|
||||
|
||||
### Shared fixture responsibilities
|
||||
## 名前とassertion
|
||||
|
||||
Each integration or E2E suite must use shared support for the resources it owns, rather than repeating setup and cleanup in individual cases. A short suite/module description must identify its scope, production components, substitutions, execution target, and prerequisites.
|
||||
名前は`<condition>_<expected_behavior>`または`<operation>_<expected_result>`を基本とし、`repository_path_rejects_symlink_escape`のように観測可能な規則を示す。`works`、`test_parse`、Ticket番号、private helper名だけでは規則を特定できない。`boundary_`、`regression_`、`smoke_`は意味がある場合だけ使い、必須接頭辞にはしない。
|
||||
|
||||
- Allocate isolated directories, databases, identities, and communication endpoints. Never borrow a running developer instance or live user data.
|
||||
- Resolve binaries explicitly and construct each child's working directory and environment without modifying the parent test process environment. Keep product environment inputs separate from test-control hooks.
|
||||
- Own child processes from launch through termination and reaping, including assertion failure and timeout. Synchronize startup and work completion on observable readiness/events, not fixed sleeps.
|
||||
- Bound waits and collect useful failure diagnostics. Reuse the suite's cleanup and artifact policy rather than making each case invent one.
|
||||
- fixtureには契約に必要な状態だけを用意する。境界テストには実際のargv、JSON、DB行、パスなどを含める。
|
||||
- 結果、拒否、応答、永続記録、状態遷移をassertする。privateな呼出し順や偶然のIDに依存させない。
|
||||
- 一つの入力規則は表形式にまとめ、別の製品規則は別のテストにする。
|
||||
- APIは成功と拒否のstatus・body、ツールはLLMに見える名前・schema・検証・出力上限・権限・副作用を確認する。
|
||||
- migrationは番号だけでなく、保存値、制約、変換、rollbackなど変更した保証を確認する。
|
||||
- 完全文字列やsnapshot比較は、その表現自体が契約の場合に限る。大きなJSONでは意味をassertする。
|
||||
- promptや組込み資源は所有者と包含条件を検証する。可変な一覧・件数・長い文言をassertionへ複写しない。
|
||||
- 互換処理は明示的な製品判断がある場合だけ維持し、古いfixtureを通すためにaliasや旧経路を増やさない。
|
||||
|
||||
Ordinary integration tests must remain deterministic and parallel-safe. A need for a database, a socket, or a child process alone is not a reason to make a test opt-in. Tests needing special tools, privileges, or platform support require an explicit prerequisite and execution selection; that exception does not automatically make them E2E.
|
||||
## 保守と実行責任
|
||||
|
||||
Execution commands and CI selection belong in [validation.md](validation.md). A suite's default/feature/opt-in classification must be reflected in its Cargo target and runner/CI configuration, not just its prose label.
|
||||
追加前に既存テストを確認し、各規則の主たるテストと接続を証明するテストを特定する。結合テストで局所的な全入力表を繰り返さない。バグ修正では既存テストを強化するか、継続的に守る規則を示す回帰テストを追加する。
|
||||
|
||||
## Boundary-specific guidance
|
||||
製品契約が変わったら、opt-in suiteを含め、影響するfixtureとassertionを更新する。製品feature限定の契約は同じfeatureで選択する。通常のテストを無関係なテスト専用featureや環境変数で隠さない。
|
||||
|
||||
### CLI
|
||||
DB・socket・子プロセスが必要なだけではopt-inにしない。特殊なtool・権限・platformが必要なら、その前提と実行選択を定義する。この例外も自動的にE2Eを意味しない。target、CIの選択、timeout、cleanup、診断を共有harnessとrunnerの設定に反映する。
|
||||
|
||||
Parser tests must focus on user-visible command contracts: accepted argv shapes, target precedence, config defaults, and clear rejection. Do not assert that a parser took a particular private branch.
|
||||
|
||||
Help tests should prevent stale public surface: command names, binary names, config locations, and removed shims. They should not duplicate the entire help text unless the exact text is the compatibility contract.
|
||||
|
||||
### Environment-backed startup configuration
|
||||
|
||||
Environment lookup belongs at startup, before operational logic begins. Materialize an explicit environment snapshot, resolve it into typed configuration once, and pass that configuration inward.
|
||||
|
||||
Test parsing, precedence, missing values, and invalid values against the explicit snapshot. Do not repeatedly exercise the same environment lookup through unrelated logic tests, and do not mutate the test process environment.
|
||||
|
||||
### Workspace Server and Runtime APIs
|
||||
|
||||
Route tests should use HTTP-like requests against the router and assert status plus JSON body. A direct handler test is appropriate only when route wiring is irrelevant and the handler is the boundary.
|
||||
|
||||
Protocol tests should assert observable event/order/state semantics, not internal channel choreography.
|
||||
|
||||
### SQLite authority
|
||||
|
||||
Migration tests must assert the current schema version and the persisted behavior introduced by the migration. A schema version bump without a behavior test is insufficient when the migration adds a table, index, constraint, or compatibility transform.
|
||||
|
||||
Authority mutation tests should prove durable state after re-read and audit/event side effects where the authority promises them.
|
||||
|
||||
### Worker tools
|
||||
|
||||
A Worker tool exposes an LLM-facing contract. Tests should cover:
|
||||
|
||||
- exact tool name when the name is model-visible;
|
||||
- required/optional schema fields and bounds;
|
||||
- rejected malformed input;
|
||||
- authority errors when Backend Workspace authority is required;
|
||||
- bounded and useful output content;
|
||||
- durable side effects for mutation tools.
|
||||
|
||||
Do not expose local filesystem fallback in a tool test unless that fallback is the intended product contract.
|
||||
|
||||
### Prompts and resources
|
||||
|
||||
Prompt tests should assert inclusion/exclusion and feature ownership, not incidental whitespace. If exact wording is a product policy, keep the asserted snippet short and name the policy.
|
||||
|
||||
## Execution assumptions for test authors
|
||||
|
||||
An ordinary test is part of the normal Cargo test suite. It must be deterministic, order-independent, parallel-safe, hermetic, and fast enough for repeated local execution.
|
||||
|
||||
If a contract exists only under a Cargo feature, gate the test with the same feature and place it in a target that Cargo actually builds for that feature. Do not hide ordinary product tests behind an unrelated test-only feature or environment variable.
|
||||
|
||||
Opt-in, ignored, privileged, platform-specific, and E2E execution must be explicit design decisions. Their targets, selection conditions, prerequisites, timeouts, and diagnostics must be documented and configured. Ordinary process-spawning integration tests use the same shared-fixture isolation and cleanup rules; process spawning alone is not an opt-in condition.
|
||||
|
||||
Commands and validation scope used while developing are repository workflow rules and belong in `AGENTS.md` or `development/validation.md`, not in this test-authoring policy.
|
||||
|
||||
## Review checklist
|
||||
|
||||
When reviewing or adding a test, ask:
|
||||
|
||||
1. What product contract does this test protect?
|
||||
2. Could Rust types alone already prove this?
|
||||
3. Is this the right boundary: parser/API/store/tool/protocol/public library?
|
||||
4. Would one failure point to one understandable rule?
|
||||
5. Does the test avoid live user data, real data dirs, and incidental ids?
|
||||
6. If this is a bug fix, did we encode the lasting rule or the original regression symptom?
|
||||
7. Does the test avoid test-only environment variables and process-global environment access?
|
||||
8. Is the test order-independent, parallel-safe, and synchronized without guessed sleeps?
|
||||
9. Is any compatibility behavior backed by an explicit product decision?
|
||||
10. Does the test use the prescribed owning suite and shared fixtures? If it assembles the product, is it in `tests/e2e` with a selected journey that narrower tests cannot prove?
|
||||
11. Are the connection under test and its production implementations real, with substitutions outside the claim?
|
||||
12. Does the test scope match the failure's likelihood and impact, including any recovery or fault-injection cases?
|
||||
13. Does existing coverage already prove this rule, and have affected fixtures and assertions been updated for the current contract?
|
||||
14. Are prerequisites, process/resource cleanup, execution selection, and CI responsibility explicit rather than inferred from a test label?
|
||||
実行方法と結果の扱いは[検証規則](validation.md)に従う。未実行、skip、未配線のCIを成功の証拠にしない。
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
# Workspace ↔ Runtime 認証
|
||||
|
||||
Yoi の Remote Runtime 認証は Workspace ごとの署名 identity を authority とする。
|
||||
認証の成否は実際の HTTP / WebSocket request ごとに決まり、保存済みの「verified」状態や過去の接続テスト結果を前提にしない。
|
||||
Server-global な署名鍵や Runtime 側の trusted-Server catalog は使わない。
|
||||
|
||||
## Authority
|
||||
|
||||
- Server DB は Workspace ごとの signing identity と Runtime 接続設定(binding)を保持する。
|
||||
- binding は endpoint、固定した Runtime public key/fingerprint、`binding_id`、authentication mode、管理者による失効を保持する。Workspace key や Runtime 側の trust ID のコピー、認証成功状態は保持しない。
|
||||
- Runtime は管理者が `trust-workspace` で受理した `WorkspacePublicIdentityBundle` を保持する。
|
||||
- bundle は `workspace_id`、Workspace `key_id`、Workspace public key/fingerprint、Backend URL を固定する。
|
||||
- Server → Runtime の各 HTTP / WebSocket request は、その時点の Workspace signing identity で短命な capability token を発行する。署名時には claims の Workspace・key ID・fingerprint が選択した identity と一致することを検証する。秘密鍵materialがない場合は fallback せず拒否する。
|
||||
- Runtime は現在の明示的な Workspace trust、署名、request method、`path_and_query`、body digest、permission、Workspace、Runtime、必要に応じ Worker、key ID/fingerprint、expiry、JTI を検証する。過去の handshake や verification record は不要である。
|
||||
- Runtime → Server の source proof は Runtime identity で署名し、対象 Workspace と bundle の Backend URL を audience に固定する。この proof の契約は変わらない。
|
||||
- Server は現在の Workspace Runtime binding、Runtime public key、Backend public URL、request target、body digest、permission、expiry、replay state を検証する。
|
||||
|
||||
旧 Server identity/trust 管理 command と旧 Runtime-side Server trust command、旧 Runtime auth key flags は廃止済みである。これらに相当する Server-global trust を fallback として使ってはならない。
|
||||
|
||||
## Provisioning
|
||||
|
||||
Runtime 側の Workspace trust 登録と Server 側の Runtime 接続設定は独立した管理操作である。Runtime が発行する `trust_id` を Server にコピーする必要はない。
|
||||
|
||||
1. Runtime identity を初期化する。
|
||||
|
||||
```sh
|
||||
yoi-runtime identity init --runtime-id <runtime-id>
|
||||
yoi-runtime identity show
|
||||
```
|
||||
|
||||
2. Workspace owner が Workspace identity settings から現在の Workspace issuer public bundle を export する。
|
||||
3. Runtime 管理者が bundle を確認し、Runtime に明示的に登録する。
|
||||
|
||||
```sh
|
||||
yoi-runtime trust-workspace add --bundle <workspace-issuer-bundle.json>
|
||||
yoi-runtime trust-workspace show --workspace-id <workspace-id>
|
||||
```
|
||||
|
||||
4. Workspace owner が Settings > Runtimes に Runtime public bundle と endpoint を登録する。接続設定には Runtime key を固定するが、Workspace key や Runtime の trust ID は登録しない。
|
||||
5. 接続テストまたは通常操作を実行する。実際の Workspace-signed request が Runtime の現在の trust と照合される。接続テスト結果はその試行の診断であり、永続的な認証状態を作らず、以後の request の成功を保証しない。
|
||||
|
||||
同じ Runtime identity は異なる Workspace から独立して信頼できる。trust record、replay protection、binding、失効はすべて Workspace scope で評価する。
|
||||
|
||||
## Runtime auth file
|
||||
|
||||
`runtime-auth.toml` は Runtime identity と Workspace issuer records のみを authority とする。
|
||||
旧 Server trust entry は読み飛ばされ、以後の identity / `trust-workspace` 更新時に書き戻されない。旧 entry を残しても認証には使用されない。
|
||||
|
||||
`trust-workspace` の file store は次を fail closed で検証する。
|
||||
|
||||
- 最大 8 MiB
|
||||
- 最大 4,096 records
|
||||
- exact Workspace / Runtime identity
|
||||
- `key_id` と public key fingerprint、管理 record の opaque `trust_id`
|
||||
- normalized Backend URL
|
||||
- 同じ `key_id` に異なる鍵materialを割り当てないこと。実際の置換・失効は新しい `trust_id` を発行するが、この ID は request token や Server binding に含めない
|
||||
- list は `offset` / `limit` 必須で、1 page 最大 100 records
|
||||
|
||||
## Local token
|
||||
|
||||
`--local-token` は明示的な local Runtime 呼び出し専用であり、Remote Workspace binding の代替ではない。Workspace issuer auth が有効な Remote Runtime request は Workspace capability token を使う。
|
||||
|
||||
## Key 変更と失効
|
||||
|
||||
Workspace signing identity は各 request の発行時に現在の authority から選択する。Workspace public key が変更された場合、Runtime 管理者が現在の public bundle を明示的に `trust-workspace replace` で受理する必要がある。Workspace key のコピーを更新するためだけに Server の Runtime 接続設定を置き換える操作は不要である。これは Workspace key rotation の新しい production API を定義するものではない。
|
||||
|
||||
Runtime 管理者は `trust-workspace revoke` で Workspace trust を失効できる。Runtime は現在の trust と一致しない key、失効済み trust、expired token、replayed JTI を拒否する。同じ key の再登録も管理者の明示的な操作で行い、request の自己申告による trust 登録は行わない。
|
||||
|
||||
Runtime key や endpoint の変更は Server の接続設定を明示的に置き換える。Server の Runtime cache と authorizer は現在の接続設定(endpoint、Runtime public key/fingerprint、`binding_id`、authentication mode、管理者による失効)と照合し、replaced/revoked binding を使う stale client を拒否する。接続設定の一致は送信先の選択であって、認証成功の証拠ではない。
|
||||
|
||||
接続先が到達不能・認証失敗でも、owner は観測した `binding_id` を指定して接続設定を修復できる。取得できた active Worker は置換を遮断するが、Worker 一覧の取得失敗を「idle の証明」とは扱わず、認証成功を修復の前提にもしない。接続設定の置換は Worker の停止・削除ではなく、Runtime 削除の資源確認とは別の操作である。
|
||||
|
||||
## 保存形式の移行
|
||||
|
||||
Server schema90 は既存 binding の endpoint、Runtime key、binding ID、明示的失効を保持し、古い認証状態と検証記録を履歴専用 archive に移す。履歴は要求の認可には参照しない。既存 Workspace identity や接続設定の作り直しは不要である。
|
||||
|
||||
Runtime の旧形式の Workspace trust は、検証済みの公開鍵と管理者の Active/Revoked 設定を保持して読み込む。形式の移行だけでは失効させず、すでに保存されている Revoked 設定を自動で解除することもしない。旧 verification file は認可に使用しない。消費済み token の replay 記録は引き継ぎ、同じ鍵の再登録でリセットしない。
|
||||
|
||||
API と capability claims の旧 handshake 契約は維持しないため、Backend・Runtime・Web は対応する版へ更新する。接続テストは更新後の実認証を確認するものであり、登録を有効化する手順ではない。
|
||||
|
||||
## 運用確認
|
||||
|
||||
Remote Runtime を設定した後は次を確認する。
|
||||
|
||||
1. `yoi-runtime trust-workspace show --workspace-id <workspace-id>` が期待する active bundle を表示する。
|
||||
2. Workspace Settings の Runtime 接続設定が期待する endpoint と固定 Runtime key を示し、失効していない。
|
||||
3. 接続テストの今回の結果を確認し、Runtime ping、Worker list/create、`worker.protocol` subscription が実際の Workspace-signed request で成功することを確認する。保存済み「verified」表示や verification evidence は確認項目ではない。
|
||||
4. wrong Workspace、wrong Runtime、wrong target/body、expired token、revoked Runtime trust、replaced/revoked binding、replayed JTI が拒否される。
|
||||
5. Runtime → Server source proof が configured Backend public URL audience と一致し、spoofed headers だけでは認証されない。
|
||||
|
||||
Server / Runtime の再起動は live reload ではない authority 変更を反映するときだけ、通常の運用権限と migration gate に従って行う。実行中プロセスを開発 Worker が無断で停止してはならない。
|
||||
@@ -1,98 +0,0 @@
|
||||
# subjektiv product cutover runbook
|
||||
|
||||
This runbook defines the product and operator boundary for moving a Workspace from legacy single-document Memory to subject-scoped subjektiv Memory. The cutover changes which product path is active; it does not reinterpret legacy tool or API inputs, migrate legacy records into subjektiv, or make historical Worker attribution into current authority.
|
||||
|
||||
T-672 documents and validates the contract only. It does **not** authorize or perform a reset in any real environment.
|
||||
|
||||
## Authorities and persistence
|
||||
|
||||
Two independent Memory systems exist during the transition:
|
||||
|
||||
| Product path | Persistence owner | Durable content | Status |
|
||||
| --- | --- | --- | --- |
|
||||
| Legacy Workspace Memory | Workspace Server control-plane database | one Workspace document, staging candidates, and staging resolutions | legacy/deprecated; retained only for compatibility and explicit reset |
|
||||
| Subject Memory | Server-managed `FeatureStorage`, in the Workspace-scoped `subjektiv` Feature database | subjects, immutable candidate and resolution records, confirmed Memory changes, Session attribution, surface generation records, and immutable surface snapshots | current product path |
|
||||
|
||||
The shared `memory` crate and Worker Features do not own either database. `FeatureStorage` owns the subjektiv SQLite lifecycle (path, connection, migration, backup/restore, shutdown, and Workspace deletion), while the subjektiv repository owns its schema and domain transactions. The legacy control-plane tables are not imported or dual-written into subjektiv.
|
||||
|
||||
Every subject operation is bounded first by the authenticated Workspace and then by an explicitly authorized subject. A subject ID is a selector, not a credential. The Host derives subject scope from authenticated Runtime/Worker source proof and current ownership of the opaque `subjektiv:<subject-id>` Worker key; model-visible requests do not supply or expand that authority.
|
||||
|
||||
The current Worker remains Yoi's keyed-singleton responsibility. The subjektiv database records immutable historical Worker and Session attribution, but it contains no second `current_worker_id` mapping and historical attribution is never used to choose or authorize the current Worker. Stopping or replacing a Worker does not retire its subject, and retiring a subject does not stop a Worker.
|
||||
|
||||
Workspace Memory settings remain Workspace-owned configuration. `feature.subjektiv` in a Profile is reusable policy, not subject authority: it remains inert for ordinary Ticket/Orchestrator Workers and Internal SubWorkers. Only a Backend-attested subject Worker (`subjektiv:<subject-id>`) or dedicated subject consolidator receives the trusted settings snapshot that activates subjektiv tools, resident load, and extraction. The snapshot is not subject identity and cannot be authored by a Profile, Browser, or model; Reviewer children never inherit the parent's attachment or run independent extraction.
|
||||
|
||||
## Session, Memory, and correction semantics
|
||||
|
||||
- **Session attribution and recall:** only a committed primary-Worker Session with Host-recorded subject attribution enters subject Session discovery. Attribution is immutable and idempotent. Recall reads the retained or committed-archive Session projection under current Workspace, subject, observation, and retention authorization; it does not copy Session bodies into subjektiv, infer attribution for older Sessions, restore a Worker, or turn recalled tool output into `HumanInput`.
|
||||
- **`SubjektivMemoryRemember`:** stages a new candidate; it does not directly create confirmed Memory. Explicit committed evidence is resolved against the Session capture. A non-preference request without an entry is staged only after its tool call commits, using the committed call as model-origin evidence. An uncommitted operation creates no candidate.
|
||||
- **`SubjektivMemoryProposeChange`:** stages a candidate with typed target metadata: exact Memory ID, `expected_change_id`, intent, and change reason. It does not mutate the target or silently retarget to a newer change.
|
||||
- **Candidate consolidation:** a restricted, subject-bound consolidator compares an immutable pending candidate with confirmed Memory and makes one idempotent atomic decision. An applied decision writes the new confirmed change, exact provenance and affected change references, and the immutable candidate resolution in one subjektiv transaction. Non-applied dispositions are explicit (`discarded`, `invalid`, `duplicate`, or `already_covered`). A failed or empty model run is not a disposition, and surface generation failure cannot undo an already committed decision.
|
||||
- **Corrections:** refinement or correction of the same experience appends the new immutable change under the same Memory ID using an `expected_change_id` check. Stale proposals remain unresolved/conflicted and are never rebased automatically. Retraction is terminal; a later corrected experience receives a new Memory ID and may derive from the old fixed change. Old changes, candidates, resolutions, provenance, and surface snapshots remain immutable history.
|
||||
|
||||
Confirmed Memory is useful subject context, not authorization and not exact implementation authority. Tickets, Objectives, repository state, Git history, and append-only Session records remain the exact sources for implementation state.
|
||||
|
||||
## Resident surface and history
|
||||
|
||||
A subject surface is a bounded rendering of current active confirmed records, not another Memory authority. The injection contract is:
|
||||
|
||||
- **ready:** inject the current non-empty snapshot only when it was built from the subject's exact current `memory_fingerprint`;
|
||||
- **ready-empty:** explicitly record that a successful current generation produced no Markdown and inject no Memory prose; do not conflate this with missing or failed generation;
|
||||
- **ungenerated:** inject nothing and report no current surface;
|
||||
- **stale:** inject nothing until regeneration publishes a snapshot for the current Memory-input fingerprint;
|
||||
- **failed:** inject nothing; retain the failure for diagnosis and retry rather than presenting it as an empty Memory.
|
||||
|
||||
A new Worker receives the current ready or ready-empty result in its initial durable prompt construction. A restored Worker does not rewrite its persisted prompt or prior Session history. It appends one durable restore-boundary system item that supersedes prior resident summaries with the latest ready surface, explicit ready-empty state, or a no-current-surface tombstone. Later turns in the same restored process do not append the boundary again. This preserves append-only history: no stale summary, failed generation, correction, or cutover operation edits previously committed Session entries or immutable subjektiv records.
|
||||
|
||||
## Legacy/deprecated compatibility boundary
|
||||
|
||||
The existing legacy tools (`MemoryQuery`, `MemoryReadDocument`, `MemoryUpdateDocument`, and the legacy `MemoryStaging*` operations) and `/api/w/{workspace_id}/memory...` APIs retain their established Workspace-document meanings while they exist. They are legacy/deprecated surfaces, not aliases for subject Memory. Do not silently route an old request to subjektiv, infer a subject from old input, or change an old input field's meaning.
|
||||
|
||||
New product work uses the explicit subject/session/confirmed-Memory/change-history/source and surface operations. Old clients must be stopped or upgraded before reset; a post-reset legacy read may observe the legacy empty/default state, but it must never expose subjektiv records through the old schema.
|
||||
|
||||
## Explicit legacy reset contract
|
||||
|
||||
The legacy reset is a destructive, operator-triggered API operation scoped to one authorized Workspace. Startup, Profile resolution, enabling `feature.subjektiv`, Worker restore, consolidation, and ordinary reads must never trigger it implicitly.
|
||||
|
||||
In one control-plane database transaction, the reset deletes all of the following legacy rows for the Workspace:
|
||||
|
||||
1. the legacy Memory document;
|
||||
2. all legacy Memory staging candidates;
|
||||
3. all legacy Memory staging resolutions.
|
||||
|
||||
The operation is all-or-nothing. A failure before commit leaves all three legacy sets unchanged. A retry after success is idempotent: absence of those rows is success and must not delete anything outside the legacy set.
|
||||
|
||||
Legacy `AppendAudit` was acknowledgement-only and created no dedicated durable Memory-audit rows, so there is no fourth legacy audit table to erase. General Session logs and diagnostics remain retained under their own policies.
|
||||
|
||||
The reset must retain:
|
||||
|
||||
- Workspace Memory settings content;
|
||||
- Sessions, Session logs, archives, and diagnostics;
|
||||
- Tickets, Objectives, and their audit/history;
|
||||
- Knowledge and Skills;
|
||||
- the entire Workspace-scoped subjektiv Feature database, including subjects, candidate/resolution history, confirmed changes, attribution, generation runs, failures, and surface snapshots;
|
||||
- all other new-product and unrelated Workspace data.
|
||||
|
||||
The reset is not a subject deletion API, a Workspace deletion API, a settings reset, a retention override, or a data migration.
|
||||
|
||||
## Operator procedure
|
||||
|
||||
Perform the cutover in this order:
|
||||
|
||||
1. **Stop legacy activity.** Disable legacy `feature.memory` Profiles and automatic extraction/consolidation, stop or drain old Workers, and block Browser/API clients from writing `/memory` routes. Wait for in-flight legacy writes to finish. Do not run legacy and new automatic extraction for the same Worker.
|
||||
2. **Back up while writers are stopped.** Capture the control-plane database containing legacy Memory and a `FeatureStorage` backup containing subjektiv. Record the Workspace and backup identities and verify the backup artifacts. Feature backup is internally consistent, but there is no cross-database snapshot instant; stopping both writer sets is what makes the pair a usable cutover checkpoint.
|
||||
3. **Trigger reset explicitly.** An authenticated operator invokes the legacy reset API for the intended Workspace. Do not automate this as a startup migration or infer consent from `feature.subjektiv` enablement.
|
||||
4. **Verify both sides.** Confirm the legacy document, staging, and resolutions are absent (or return their defined empty/default projection); confirm Workspace Memory settings and unrelated control-plane data remain; reopen/read the subjektiv store and verify the expected subject, confirmed changes, attribution, and current surface state are unchanged. Verify a subject Worker resolves through the `subjektiv:<subject-id>` singleton and has only the intended new tools.
|
||||
5. **Resume only the new path.** Start subject Workers with `feature.subjektiv`, keep legacy write surfaces disabled, and monitor typed surface/Session diagnostics. Do not treat an ungenerated, stale, or failed surface as ready-empty.
|
||||
|
||||
### Failure and recovery
|
||||
|
||||
- If reset returns a definite failure, the transaction rollback means legacy document, staging, and resolutions remain together. Correct the cause and rerun the same explicit operation.
|
||||
- If communication is lost and the result is ambiguous, keep legacy writers stopped, inspect all three legacy sets, and rerun. Idempotency makes a committed first attempt safe to repeat and atomicity prevents a valid partial state.
|
||||
- If verification of retained data or the new path fails, keep both write paths stopped. Restore the coordinated pre-reset backups according to the normal control-plane and `FeatureStorage` restore procedures, verify them, and only then resume. Never "recover" by deleting or recreating the subjektiv database.
|
||||
- Preserve Session logs and diagnostics throughout; they are recovery evidence, not reset targets.
|
||||
|
||||
## Validation boundary
|
||||
|
||||
The validation supporting this cutover proves substrate-level store shutdown/reopen behavior and restoration of a Worker object with its persisted history. It does **not** claim a restart of the Server or Runtime process, behavior during communication loss, real-model consolidation or surface quality, or a full-process end-to-end cutover. Those require separate operational validation before a production reset.
|
||||
|
||||
No real environment reset is performed or authorized by T-672.
|
||||
@@ -1,104 +0,0 @@
|
||||
# Workspace Web Application Architecture
|
||||
|
||||
この文書は、Workspace Webのapplication shell、Header、Sidebar、nested
|
||||
layoutのcomposition authorityを定義する。Navigation
|
||||
IAは[`product-ux.md`](product-ux.md)、視覚とinteractionの共通規則は[`design-language.md`](design-language.md)、具体的なsource
|
||||
pathとCSS
|
||||
ownershipは[`../../../web/workspace/README.md`](../../../web/workspace/README.md)を参照する。
|
||||
|
||||
## Application shell
|
||||
|
||||
root layoutが唯一のapplication shellを所有する。
|
||||
|
||||
- Sidebar frame
|
||||
- global fallback navigation
|
||||
- Header frame
|
||||
- main content
|
||||
- SidebarとHeaderのoverride context
|
||||
|
||||
page routeはapplication shell、global Header、Sidebar、fold
|
||||
controlを再実装しない。Pageのmarkupは標準shellのmainへ配置するcontentから始める。
|
||||
|
||||
## Header composition
|
||||
|
||||
Headerはroot shellが一度だけrenderする。Nested
|
||||
layoutは`HeaderOverride`を通じて現在scopeのlocation contentを登録する。
|
||||
|
||||
- page componentはHeader frameを作らない。
|
||||
- 最も深いactive overrideを表示する。
|
||||
- layoutが破棄されたら直前のoverrideへ戻る。
|
||||
- overrideがなければroot fallbackを表示する。
|
||||
- route titleをmain contentへ複製しない。
|
||||
|
||||
## Sidebar composition
|
||||
|
||||
### Root
|
||||
|
||||
root shellは`SidebarFrame`と`GlobalSidebar`を一度だけrenderし、root
|
||||
`SidebarController`を提供する。Overrideがない場合、`GlobalNavSections`がfallbackになる。
|
||||
|
||||
### Workspace
|
||||
|
||||
Workspace layoutは、親のroot
|
||||
slotへ`WorkspaceSidebar`を登録する。同時に、さらに深いlayoutが使う新しい`SidebarController`をcontextへ設定する。
|
||||
|
||||
`WorkspaceSidebar`はWorkspace shortcut headerを保持し、その下へchild
|
||||
slotをrenderする。Child overrideがない場合だけ、Workspace navigation
|
||||
fallbackを表示する。
|
||||
|
||||
### Settings
|
||||
|
||||
Settings layoutは、Workspace layoutが提供したchild
|
||||
slotへ`SettingsSidebar`を登録する。Workspace shortcut headerは残り、Workspace
|
||||
navigation fallbackだけがSettings navigationへ置き換わる。
|
||||
|
||||
さらに深いscopeも同じcontractを使う。
|
||||
|
||||
## Override stack
|
||||
|
||||
`HeaderOverride`と`SidebarOverride`は、layout lifecycleにbindしたLIFO
|
||||
stackとして扱う。
|
||||
|
||||
1. Layout mount時にparent controllerへregisterする。
|
||||
2. Controllerは最も新しいactive registrationを表示する。
|
||||
3. Nested layoutはchild controllerを新しく提供する。
|
||||
4. Layout destroy時に自分のregistrationをremoveする。
|
||||
5. 一つ前のregistrationがあれば復元する。
|
||||
6. Stackが空ならfallbackへ戻る。
|
||||
|
||||
同じregistrationを複数回removeしても結果を変えない。古いcleanupが新しいactive
|
||||
registrationを削除してはならない。
|
||||
|
||||
## Ownership
|
||||
|
||||
- `SidebarFrame`だけが`aside`、外枠、scroll領域、fold controlを所有する。
|
||||
- `GlobalSidebar`、`WorkspaceSidebar`、`SettingsSidebar`は各scopeのcontentを所有する。
|
||||
- `SidebarOverride`を登録できるのはscopeを定義するlayoutだけとする。
|
||||
- page component、一時的なwidget、dialogはSidebarを差し替えない。
|
||||
- child layoutは親navigationをcopyせず、自分のsidebar layerだけをregisterする。
|
||||
- Global、Workspace、Settingsを一つのsnippetへ平坦化しない。
|
||||
|
||||
## Data authority
|
||||
|
||||
Sidebar compositionはnavigation
|
||||
structureを決めるが、Workspace、permission、Worker
|
||||
stateのauthorityにはならない。
|
||||
|
||||
- Workspace identityとpermissionはBackend projectionを使う。
|
||||
- Worker listとstateはWorkspace protocol projectionを使う。
|
||||
- route pathやdisplay labelからauthorityを推測しない。
|
||||
- unavailable dataを架空の正常stateで置き換えない。
|
||||
- permissionにより利用できないnavigationは、権威あるpermission
|
||||
projectionに基づいて除外する。
|
||||
|
||||
## Design-lab
|
||||
|
||||
認証不要のstatic design-labはroot shellを再利用し、実際と同じnested
|
||||
controllerとoverride stackを構成する。
|
||||
|
||||
Live Workspace protocolへbindするresource listだけは、同じscope layer内のstatic
|
||||
fixtureへ置き換えてよい。Static fixtureへ別scopeのnavigationを混ぜず、production
|
||||
componentと同じ順序、state表現を使う。
|
||||
|
||||
Design-lab固有のswitch controlでSidebarを差し替えず、route
|
||||
hierarchyによってlayout lifecycleを発生させる。
|
||||
@@ -1,156 +0,0 @@
|
||||
# Workspace Web Product UX
|
||||
|
||||
この文書は、Workspace Webで扱うresource、navigationのinformation
|
||||
architecture、resourceごとの主要taskを定義する。見せ方の共通文法は[`design-language.md`](design-language.md)、application
|
||||
shellとSidebar
|
||||
slotは[`application-architecture.md`](application-architecture.md)を参照する。
|
||||
|
||||
## Product position
|
||||
|
||||
Workspace
|
||||
Webは、Workspace内の権威ある状態を確認し、注意が必要な箇所を判断し、一度に一つの操作を行うための作業面である。Marketing
|
||||
dashboard、analytics画面、Backend DTOのraw viewerにはしない。
|
||||
|
||||
基本的な操作の流れは次とする。
|
||||
|
||||
```text
|
||||
scopeを選ぶ
|
||||
→ resourceを選ぶ
|
||||
→ current stateを確認する
|
||||
→ 次の有効なactionを行う
|
||||
→ 結果を確認する
|
||||
```
|
||||
|
||||
## Resource
|
||||
|
||||
主要resourceは次のとおり。
|
||||
|
||||
- Workspace
|
||||
- Ticket
|
||||
- Objective
|
||||
- Merge Request
|
||||
- Memory
|
||||
- Worker
|
||||
- Workdir
|
||||
- Runtime
|
||||
- Repository
|
||||
|
||||
通常の表示とURLにはcanonical resource keyを使う。内部UUIDをlabelやfallback
|
||||
linkとして露出しない。
|
||||
|
||||
## Navigation IA
|
||||
|
||||
### Global
|
||||
|
||||
Workspaceを選ぶ前のscopeを扱う。
|
||||
|
||||
- Workspace catalog
|
||||
- Account
|
||||
- Device Login
|
||||
- Workspace作成
|
||||
|
||||
### Workspace
|
||||
|
||||
一つのWorkspace内で日常的に扱うresourceを置く。
|
||||
|
||||
1. Tickets
|
||||
2. Objectives
|
||||
3. Merge Requests
|
||||
4. Memory
|
||||
- Subjects
|
||||
- Surface
|
||||
- Committed Memory
|
||||
- Changes
|
||||
- Sources
|
||||
5. Workers
|
||||
|
||||
### Settings
|
||||
|
||||
Workspaceの管理と構成を扱う。
|
||||
|
||||
- Runtimes
|
||||
- Configuration Sources
|
||||
- Repositories
|
||||
- Repository Access
|
||||
- Profile Sources
|
||||
- Workspace Identity
|
||||
|
||||
`Runtimes`、`Repositories`、`Repository Access`をWorkspace primary
|
||||
navigationへ混ぜない。permissionにより利用できないSettings itemは表示しない。
|
||||
|
||||
## Page model
|
||||
|
||||
各routeは、主要な目的を一つだけ持つ。その目的は「Ticketを選ぶ」「このWorkerを確認する」「Repository
|
||||
accessを設定する」のように、動詞と対象を一つずつ使って表現できなければならない。
|
||||
|
||||
Workspace pageは、次のいずれかを基本形とする。
|
||||
|
||||
1. **一覧またはboard** — resourceを探し、注意が必要な項目を見つける。
|
||||
2. **詳細** — 一つのresourceを理解し、現在実行できる次の操作を行う。
|
||||
3. **formまたはeditor** — 範囲の明確な設定変更を行う。
|
||||
4. **Console** — 一つのWorkerを操作しながら、履歴とcurrent controlを確認する。
|
||||
|
||||
同じ目的を支える場合に限り、一つのrouteで複数の基本形を組み合わせてよい。
|
||||
|
||||
## Product pattern
|
||||
|
||||
### Tickets
|
||||
|
||||
- boardを一つの横方向の作業面として扱う。
|
||||
- laneはworkflow stateを表し、隣接laneとの関係を維持する。
|
||||
- Ticketはlane内でDesign Languageの`Resource row` patternを使う。
|
||||
- Ticket key、title、優先判断を変えるmetadataだけを表示する。
|
||||
- blockerとattentionはtextでも示す。
|
||||
- narrow viewportでは、bounded horizontal scrollでlane比較を維持する。
|
||||
|
||||
### Resource index
|
||||
|
||||
- 同じfieldをresource間で比較する場合、flat listまたはtableを使う。
|
||||
- 人が読むnameとcanonical keyを最初のcolumnに置く。
|
||||
- columnは判断に必要な値だけに絞る。
|
||||
- provider detail、変更ID、digestはdetailまたはtechnical disclosureへ移す。
|
||||
|
||||
### Resource detail
|
||||
|
||||
- identity、current state、attentionを最初に置く。
|
||||
- narrativeまたはhistoryをprimary領域に置く。
|
||||
- relation、target、review、workflowが別のauthorityを持つ場合、それぞれを独立したsectionにする。
|
||||
- raw metadataはtechnical disclosureへ置く。
|
||||
|
||||
### Settingsとform
|
||||
|
||||
- 一つのSettings routeは一つの設定関心事を所有する。
|
||||
- edit actionの前または同時に、現在有効な値を示す。
|
||||
- 一つの判断に必要なfieldをgroup化する。
|
||||
- narrow viewportでは一列にする。
|
||||
- form内でfilled actionにするのはsave actionだけとする。
|
||||
|
||||
### Memory
|
||||
|
||||
- Memoryの入口はWorkspace全体のlegacy `Document` / `Staging`ではなく、明示的に選択したsubjectとする。
|
||||
- subject detailではcurrent Workerをkeyed-singleton projectionとして表示し、historical Worker/Session attributionをcurrent authorityとして扱わない。
|
||||
- `Surface`はresident contextのcurrent stateを表示する。`ready`、`ready-empty`、`ungenerated`、`stale`、`failed`を区別し、空表示やrequest failureへまとめない。
|
||||
- `Committed Memory`はcurrent confirmed Memoryの一覧とdetailを持つ。変更履歴はappend-only viewとして分離し、過去の変更内容をcurrentへ暗黙fallbackしない。
|
||||
- `Sources`は候補、resolution、exact `{memory_id, change_id}` refs、Host-recorded Session attribution、閲覧可能なSession sourceをたどるviewとする。source IDをpermissionとして扱わず、現在のWorkspace/subject authorizationを毎回適用する。
|
||||
- legacy `/memory` の`Document` / `Staging` viewはdeprecated compatibility surfaceとして明示し、新しいsubject viewへ同じinputを別の意味で転送しない。
|
||||
- Markdown本文とdetail presentationはT-661の共有rendererを再利用する。Memory route独自のrenderer、application shell、Header、Sidebarを再実装せず、既存shellのmain contentとnested navigationを保つ。
|
||||
|
||||
### Worker Console
|
||||
|
||||
- transcriptをprimary surfaceとする。
|
||||
- direct SubWorkerを表示している場合も、Composerとrun
|
||||
controlは親Workerへbindしたままにする。
|
||||
- OverviewとNormalはpresentationだけを変え、authorityを変えない。
|
||||
- command outputはterminalに近いcompactな表示とし、prose messageから分離する。
|
||||
- live stateは権威あるWorker snapshotから導出する。
|
||||
- connection stateやtransient eventでcurrent stateを置き換えない。
|
||||
- hidden reasoningとraw system promptはdebug modeでも表示しない。
|
||||
|
||||
## Stateとpermission
|
||||
|
||||
- 存在しない、閲覧できない、読み込みに失敗したWorkspaceを別の状態として扱う。
|
||||
- 別のWorkspaceへ暗黙に切り替えない。
|
||||
- owner-only mutation controlをnon-ownerへ表示しない。
|
||||
- read-onlyの場合は、有用なread viewを残す。
|
||||
- Workerのdomain stateとWebSocket connection stateを混同しない。
|
||||
- Mergeなど不可逆または重要なoperationは、対象と結果を明示してから実行する。
|
||||
@@ -1,135 +0,0 @@
|
||||
# Subject / Memory information audit (T-692)
|
||||
|
||||
This document records T-692's UI decisions and validation at the commits cited below. Its counter values describe that historical implementation, not the current API. Obsolete UI labels are paraphrased by purpose below, not quoted as current copy. Current Memory uses immutable `change_id` references, `memory_fingerprint` and `built_from_memory_fingerprint`; see [the current store contract](../../design/subjektiv-store.md). Historical validation results are not new executions.
|
||||
|
||||
## Decision and scope
|
||||
|
||||
The previous Subjects index exposed a committed-Memory update counter alongside identity, state and
|
||||
update time; Subject detail led with technical metadata and raw surface states.
|
||||
These numbers require implementation knowledge before they help a user choose a
|
||||
Subject or understand what is available. The revised hierarchy starts with
|
||||
identity, connection, committed records and generated-context availability.
|
||||
|
||||
This is a presentation change, not a change to storage, freshness comparisons,
|
||||
permissions, generation, or Worker history. T-688's ordinary Worker launch and
|
||||
T-689's independent Subject creation remain the actual operation paths. T-693
|
||||
owns editable user-managed behavior: that content must remain distinct from this
|
||||
page's context generated from committed Memories. A successful read or a current
|
||||
surface does not attest what is already in any running Worker's history.
|
||||
|
||||
## Field classification and before/after priorities
|
||||
|
||||
| Information | Classification and reason | Presentation decision |
|
||||
| --- | --- | --- |
|
||||
| Subject role and ID | Normal identification. Similar or identical roles need a stable identifier for selection and comparison. | Keep role as heading and the complete wrapping ID visible in index and detail. |
|
||||
| Subject active/retired state | Normal operation decision: whether a new Worker connection can use the Subject. | Replace raw `active` with `Available`; keep `Retired`. Detail explains connection eligibility, not an assertion that a Worker is running. |
|
||||
| Current Worker | Normal connection decision, derived from the existing Backend singleton projection. | Promote `Connected` / `Not connected` and display name. Do not infer ownership from historical store counter or claim all Workers received current Memory. Worker start remains the ordinary launch form. |
|
||||
| Committed-Memory presence | Normal reading decision, independent from surface generation or candidates. | Show `Available` / `None` only on the successful first bounded page. On later pages say `N on this page`; errors say `Unavailable`. Never infer counts from an update counter or scan all pages. |
|
||||
| List counts | Normal navigation context, not a store-wide statistic. | Label `N shown` and `more available`, preserving existing bounded cursor pagination. |
|
||||
| Resident availability | Normal context decision. A missing surface is not missing committed Memory. | `Ready to use`, `Ready, no context`, `Not generated`, `Needs refresh`, `Generation failed`, and request `Unavailable` are distinct. These describe the current generated store artifact, not content truth or Worker-history propagation. |
|
||||
| Resident body and generated time | Normal content/read freshness context. | Keep the shared Markdown renderer, generated date and exact snapshot reference count. Ready-empty explains that a valid snapshot has no body; stale/failed do not display a partial or old snapshot. |
|
||||
| Historical Subject store counter | Diagnostic only: internal committed-Memory change number, not record count, schema version or quality score. | Remove it from the index's normal columns. Retain the exact value under detail's `Technical details`, with its meaning explained. Backend comparisons are untouched. |
|
||||
| Subject update time | Secondary metadata; not a context-injection or generation completion timestamp. | Label `Last changed`; on detail put it with Subject diagnostics rather than competing with the availability summary. |
|
||||
| Snapshot ID and historical built-from counter | Diagnostic correlation, distinct from individual Memory history entries. | Retain under `Surface sources and diagnostics`, labelled the historical generated-from counter label and explained. |
|
||||
| Surface Memory references | Detailed audit/reachability. | Preserve reference IDs, immutable history references and Memory links in the disclosure. |
|
||||
| Memory claim, kind, lifecycle state, excerpt and body | Normal content decision. Active, resolved and retracted are not interchangeable. | Retain list scanning and body reading. Keep the Markdown renderer and current/historical distinction. |
|
||||
| Individual Memory history entries | Detailed content comparison, also useful for identifying a selected version. | The historical UI labelled each Memory history entry separately from Subject counters. Preserve immutable history, selected history-entry links and history pagination. |
|
||||
| Candidate provenance, evidence, origin, source refs and derivations | Detailed audit, required to understand why a record exists. | Retain IDs, ranges, summaries, excerpts, origins and derived-history-entry links. Counts say `shown` except where the API provides exact per-candidate totals. Preserve body and evidence continuation links. |
|
||||
| Raw field names / duplicated general update-counter terminology | Unnecessary in the normal hierarchy; multiple unrelated counters are misleading. | Replace bare counter labels and raw surface enums with purpose-specific labels. Do not delete API fields, stored information, diagnostics or their detailed access. |
|
||||
|
||||
No diagnostic datum was removed from Subject detail, surface detail or Memory
|
||||
provenance/history. The index no longer shows a historical store counter, but the exact
|
||||
value remains one click away in Subject detail. No invented regeneration,
|
||||
editing or recovery action was added; `Needs refresh` does not mean an update job
|
||||
is running. Creation still creates only a Subject, and selection still uses the
|
||||
existing ordinary launch contract.
|
||||
|
||||
## Candidate-only verification
|
||||
|
||||
Candidates are not committed records and are not an input to this page. The
|
||||
Subject loader performs just three bounded reads: Subject, resident surface and
|
||||
`/memories?limit=100[&cursor=...]`. It does not read or count staging records.
|
||||
|
||||
The existing real-store regression
|
||||
`subjektiv::tests::candidate_decisions_reject_conflicts_and_roll_back_partial_writes`
|
||||
stages a candidate and attempts an invalid apply. It asserts the candidate has no
|
||||
resolution and the committed Memory list remains empty. The component regression
|
||||
`candidate-only Subjects show no committed Memories, not an inferred candidate count`
|
||||
uses that read projection (historical store counter 0, ungenerated surface, empty successful
|
||||
committed list) and checks `None`, `No committed Memories yet.`, `Not connected`
|
||||
and `Not generated`, with no resident body. Thus a staged candidate does not turn
|
||||
into a committed-Memory count or a claim that context has been generated.
|
||||
|
||||
The store test proves the persistence boundary; the component test proves the
|
||||
rendering of its public projection. These are separate boundary tests, **not** a
|
||||
live browser-to-Backend candidate-creation E2E. The existing `ungenerated` capture
|
||||
represents the same visible projection, but does not itself stage a candidate.
|
||||
|
||||
## Visual review and validation evidence
|
||||
|
||||
The restored implementation is in `fbb09e65` and `4cf6c130`; this report/test
|
||||
supplement does not change rendered markup, styling, copy or data contracts.
|
||||
|
||||
- Scenarios: `tools/web-ux/scenarios/memory-subjects-{light,dark}.json`.
|
||||
- Before: `target/web-ux/t-692-before-{light,dark}` at `cc979874`.
|
||||
- After: `target/web-ux/t-692-final2-{light,dark}` at clean `4cf6c130`.
|
||||
- Comparisons: `target/web-ux/t-692-final2-{light,dark}-comparison`.
|
||||
- Browser interactions: `target/web-ux/t-692-browser-interactions`.
|
||||
- Persona: synthetic Workspace fixture; permitted creation and intentional
|
||||
permission-rejection responses. No real account or private store data used.
|
||||
- Viewports: 1440, 768, 390 and 320px; light and dark themes.
|
||||
- States: connected/unconnected, active/retired, successful empty lists, ready
|
||||
long Markdown, ready-empty, ungenerated, stale, failed, unavailable HTTP503,
|
||||
create form and create permission error; expanded technical disclosures.
|
||||
- The takeover implementer personally inspected individual before/after images
|
||||
for index at 768px, representative detail at desktop/mobile and Memory detail,
|
||||
then ready-empty mobile, ungenerated narrow, stale desktop, failed mobile,
|
||||
endpoint error at 320px, mobile surface sources, compact Subject diagnostics
|
||||
and mobile permission error. Oversized contact sheets were rejected by the
|
||||
image provider; individual viewport PNGs were inspected instead.
|
||||
- Findings/result: primary state groups replace counters without losing IDs or
|
||||
document access; long IDs wrap, status is textual rather than color-only,
|
||||
mobile groups stack, diagnostics remain readable, and error state regions stay
|
||||
separate. No new visual defect identified. **Pass for the audited scope.**
|
||||
- Final bundles contain 64 captures each. `completed-with-errors` is expected:
|
||||
intentional creation HTTP403, endpoint HTTP503/associated aborted requests,
|
||||
and the deliberately failed surface's alert are retained as evidence. Healthy
|
||||
capture states have empty error arrays; this is not a blanket claim that all
|
||||
captures were error-free. Review-context diagnostics are empty.
|
||||
- The focused production-shell browser test proves main-owned vertical scrolling
|
||||
with wheel/keyboard/touch, 320px/mobile shell behavior, no page-wide overflow,
|
||||
local table/code scroll, Markdown safety, creation/launch separation, bounded
|
||||
pagination and Memory/source/history-entry reachability.
|
||||
|
||||
Independent review at source `4cf6c130` ran root `cargo check`,
|
||||
`cargo fmt --all -- --check`, diff hygiene, Web check (0 errors/warnings), 12
|
||||
Memory API tests, 452 full Web unit tests, 134 component tests (8 Memory tests),
|
||||
production build, the focused production-shell browser test (1), and Web-UX unit
|
||||
tests (16), all passing.
|
||||
|
||||
The takeover implementer then validated this report/test supplement with:
|
||||
|
||||
- `cargo test -p yoi-workspace-server --lib candidate_decisions_reject_conflicts_and_roll_back_partial_writes`: 1 passed.
|
||||
- Root `cargo check`, `cargo fmt --all -- --check`, and `git diff --check HEAD`: passed.
|
||||
- Targeted Memory component tests: 9 passed, including the added candidate-only projection test.
|
||||
- Web `deno task check`: 0 errors/warnings; `deno task test`: 452 passed;
|
||||
`deno task test:component`: 135 passed.
|
||||
- Scoped `deno fmt --check` on the changed TypeScript test: passed.
|
||||
- JSON audit of both final2 bundles: 64 captures each, exact clean source
|
||||
`4cf6c130`, and no errors outside the deliberate permission/error/failed routes.
|
||||
|
||||
The supplement changes only a document and a test, so it does not require new
|
||||
rendered-UI captures. Rereview records and the final approved Ticket handoff link
|
||||
this report to the new exact source.
|
||||
|
||||
Known non-blocking validation diagnostics: component runs emitted incidental
|
||||
localhost:3000 connection-refused messages while passing; the production build
|
||||
warned about a large chunk. A broader Web-UX typecheck fails in unchanged
|
||||
`tools/web-ux/src/processes.ts:44` on a `TextDecoderStream` generic incompatibility;
|
||||
this report does not claim that broad check passed.
|
||||
|
||||
No live-Backend browser interactions, real Worker model-request propagation,
|
||||
regeneration, or server/runtime process E2E were performed. No Rust production
|
||||
crate changed in T-692; the supplemental store test exercises an existing
|
||||
contract without changing that contract. Backend counters, comparison logic,
|
||||
storage schema and feature behavior remain intact.
|
||||
@@ -1,272 +1,32 @@
|
||||
# Web visual review
|
||||
# Web UI変更の目視確認
|
||||
|
||||
この文書は、Web UIを変更する実装者が完了前に行うvisual
|
||||
reviewの必須手順を定義する。Visual reviewは任意のpolish工程ではなく、type
|
||||
check、linter、testと並ぶvalidationである。
|
||||
描画結果が変わる変更は、実装者自身が変更前後を目視する。type check、linter、testと別の検証であり、後段Reviewerや利用者に最初の発見を委ねない。画像生成やHTTP成功だけでは確認済みにならない。
|
||||
|
||||
[`design-language.md`](design-language.md)はWorkspace
|
||||
Webが満たす視覚とinteractionの共通規則を定義する。Product固有の構成は[`product-ux.md`](product-ux.md)を参照する。[`tools/web-ux/README.md`](../../../tools/web-ux/README.md)はcapture
|
||||
toolの操作方法を定義する。この文書は、誰が、いつ、何を目視し、どの証拠を残さなければ実装完了とみなせないかを定義する。
|
||||
## 対象と条件
|
||||
|
||||
## 実装者の責任
|
||||
page、layout、navigation、componentのmarkup・style、色・書体・余白、文言・状態表示、可変幅、keyboard操作、表示に影響するAPI projectionを変えた場合に行う。描画に影響しない文書・型・Backend変更だけなら省略できるが、影響しない理由を検証記録に残す。
|
||||
|
||||
Visual
|
||||
reviewは、後段のReviewerやユーザーへ最初の発見を委ねない。UIを変更した実装者自身が、同じ変更の中で次を行う。
|
||||
変更箇所に関係する条件を選び、都合のよい一画面だけで済ませない。
|
||||
|
||||
1. 変更前をcaptureする。
|
||||
2. 変更後を同じ条件でcaptureする。
|
||||
3. screenshotとbrowser evidenceを自分で確認する。
|
||||
4. 発見した問題を修正する。
|
||||
5. 修正後を再captureして確認する。
|
||||
6. 変更と無関係な既存問題は、根拠を付けて分離する。
|
||||
- 権限やデータが表示を変える場合は、owner、non-owner、anonymous、read-onlyなど該当する利用主体を分ける。
|
||||
- 幅はdesktop `1440px`、狭い幅`768px`、mobile `390px`を基準とする。`320px`対応やbreakpoint付近が変更対象なら追加し、Sidebarなどを除いた実内容幅も確認する。
|
||||
- 色・背景・線・focus・状態・code・terminalを変える場合はlightとdarkを確認する。
|
||||
- 通常、空、読込み中、失敗、権限不足、長文・長い識別子、少数・多数の一覧など、該当するデータ状態を用意する。
|
||||
|
||||
Agentが実装者の場合、そのAgent自身が`ViewImage`または同等のVLM入力で実画面を確認する。captureを生成しただけ、別のAgentへ画像を渡しただけ、HTTP
|
||||
200を確認しただけでは完了しない。
|
||||
## 実施と確認項目
|
||||
|
||||
## 適用範囲
|
||||
1. route、利用主体、幅、theme、データ状態、撮影点を固定する。既存scenarioを再利用し、不足分は同じ変更で更新する。
|
||||
2. 変更前を撮影して画像を開き、既存問題と変更対象を把握する。
|
||||
3. [Design Language](../../principle/design-language.md)と[UX設計](../../principle/ux-design.md)に沿って実装する。撮影専用のCSS・shellや、本番と異なる権限判定で見栄えを整えない。
|
||||
4. 同条件で変更後を撮影し、実装者自身が画像と`review-context.json`を確認する。Agentも`ViewImage`などで実画像を見る。
|
||||
5. 発見した新規問題を修正し、再撮影して確認する。既存の無関係な問題は根拠を付けて分離する。
|
||||
|
||||
次の変更ではvisual reviewを必須とする。
|
||||
画像では、情報のまとまり、区切り線の役割、余白の大小、重複見出し、文字の強弱、折返し、欠落、overflow、主要操作の位置を確認する。DOM・表示・keyboardの順、focusの維持、色以外の状態表現、必須情報への到達も確認する。画素差の大小だけで合否を決めない。
|
||||
|
||||
- page、layout、navigation、Sidebar、Header、Console、dialog
|
||||
- componentのmarkupまたはstyle
|
||||
- color、typography、spacing、border、radius、shadow、theme token
|
||||
- copy、label、status、empty/loading/error/permission表示
|
||||
- responsive breakpoint、container、overflow、scroll
|
||||
- 表示dataの追加、削除、並び替え、progressive disclosure
|
||||
- icon、focus、keyboard interaction、motion
|
||||
- UIへ影響するgenerated typeまたはAPI projection
|
||||
`review-context.json`では、document status、可視エラー、console・page・requestエラー、accessibility snapshot、撮影点と画像hashを確認する。HTTP 200でも画面が権限エラーなら成功ではない。意図的な障害fixtureは予期したエラーと予期しないエラーを分けて判定する。
|
||||
|
||||
型、Backend、CLI、文書だけの変更でrendered Web
|
||||
UIが変わらない場合は省略できる。省略する場合は、UIへ影響しない理由をvalidation
|
||||
evidenceへ一文で残す。
|
||||
## 証拠と合否
|
||||
|
||||
## 必須の確認条件
|
||||
引継ぎにはscenario、変更前後のartifact、source、確認した条件、修正した問題、根拠付きの既存問題、最終の`pass | fail`を残す。必要な条件を自分で確認し、新規問題を修正し、意図しない描画回帰がない場合だけ合格とする。自動テストと目視確認のどちらも、他方の代替にはしない。
|
||||
|
||||
変更したsurfaceに関係する条件だけを選ぶが、都合のよい一画面だけに限定しない。
|
||||
|
||||
### Persona
|
||||
|
||||
権限やdataが表示を変える場合は、該当するpersonaを分ける。
|
||||
|
||||
- owner
|
||||
- non-owner
|
||||
- anonymous
|
||||
- read-onlyまたは操作権限なし
|
||||
|
||||
owner表示だけでpermission-aware UIを承認しない。
|
||||
|
||||
### Viewport
|
||||
|
||||
最低限、次を確認する。
|
||||
|
||||
- desktop: `1440px`幅を基準
|
||||
- narrow: Sidebarとmainが競合する幅。現在は`768px`を基準
|
||||
- mobile: `390px`幅を基準
|
||||
|
||||
`320px`対応、table、long form、Console、Sidebar
|
||||
foldなどが変更対象なら、その境界も追加する。media
|
||||
queryのviewport幅だけでなく、Sidebarやpanelを差し引いた実際のcontent幅を確認する。
|
||||
|
||||
### Theme
|
||||
|
||||
color、border、background、focus、status、code、terminalを変更した場合はlightとdarkの両方を確認する。片方のthemeで読めることを、もう片方のcontrastの証拠にしない。
|
||||
|
||||
### Data state
|
||||
|
||||
該当するstateを明示的に用意する。
|
||||
|
||||
- representative data
|
||||
- empty
|
||||
- loading
|
||||
- error
|
||||
- permission deniedまたはread-only
|
||||
- long title、long key、long error、複数行content
|
||||
- collectionの最小件数と、実際に起こりうる多い件数
|
||||
|
||||
happy pathだけでcomponentを承認しない。
|
||||
|
||||
## 目視確認の観点
|
||||
|
||||
### 情報のまとまり
|
||||
|
||||
- 同じ判断に使う情報がproximity、alignment、typographyによって一つのgroupに見えるか。
|
||||
- 別の意味を持つ情報が、十分な距離または一つの明確なboundaryで分離されているか。
|
||||
- 親子、peer、metadata、actionの関係が、DOMを読まなくても判別できるか。
|
||||
- card、background、border、shadow、余白を重ねて同じgroupingを重複表現していないか。
|
||||
- page title、Header、breadcrumb、section
|
||||
heading、説明文が同じ意味を繰り返していないか。
|
||||
|
||||
### Separatorとborder
|
||||
|
||||
画面上のすべての線について、何と何を区切る線か説明できなければならない。
|
||||
|
||||
- Headerの終端、peer row間、section開始、table
|
||||
cellなど、線の役割が一つに定まっているか。
|
||||
- 近接した二本の線が同じboundaryを重複表現していないか。
|
||||
- 線の上下の余白から、その線が前後どちらのgroupに属するか分かるか。
|
||||
- すべてのsectionへ機械的に同じ線を置き、hierarchyを平坦化していないか。
|
||||
- borderがなくてもspacingだけで十分な箇所へ線を追加していないか。
|
||||
|
||||
### Spacingとdensity
|
||||
|
||||
- 余白がtoken scaleに沿い、近い関係ほど狭く、別groupほど広くなっているか。
|
||||
- labelとvalue、headingとcontent、rowとrow、sectionとsectionの間に一貫した大小関係があるか。
|
||||
- 線の前後へ同程度の大きな余白を置き、boundaryの所属を曖昧にしていないか。
|
||||
- 初期viewportにprimary contentまたは主要操作が存在するか。
|
||||
- 空きすぎた領域が情報不足やhierarchyの弱さを隠していないか。
|
||||
- 密度を上げるためにfont、touch target、line heightを過度に縮めていないか。
|
||||
|
||||
### Wrapping、overflow、可変長content
|
||||
|
||||
- 実際に起こりうる最長のlabel、title、key、ref、error、翻訳で確認したか。
|
||||
- 一行を前提とするcontrolが意図せず二行にならないか。
|
||||
- button labelが折り返されたとき、row heightとalignmentが壊れないか。
|
||||
- title、metadata、code、URLが隣接actionを押し出さないか。
|
||||
- truncateする場合、完全な値へaccessできるか。
|
||||
- horizontal
|
||||
scrollはtable、board、terminalなど、その意味を所有する領域だけに閉じているか。
|
||||
- page全体へhorizontal overflowが発生していないか。
|
||||
- `overflow-wrap`だけで問題を隠さず、可読性と比較可能性を維持しているか。
|
||||
|
||||
### Responsive layout
|
||||
|
||||
- DOM順、視覚順、keyboard順が一致しているか。
|
||||
- Sidebar、Header、primary contentの順序がmobileでも目的に合うか。
|
||||
- Sidebarだけで初期viewportを占有していないか。
|
||||
- breakpoint直前と直後の両方で、content幅とcontrol配置が成立するか。
|
||||
- viewport
|
||||
queryだけでなく、containerの実幅に応じて一列化またはscrollを選べているか。
|
||||
- desktopの比較関係を、理由なくmobile cardへ変換していないか。
|
||||
|
||||
### Typographyとcopy
|
||||
|
||||
- route titleが画面上で一度だけ表示されているか。
|
||||
- page purpose、componentの存在理由、設計意図を説明するmeta
|
||||
copyが残っていないか。
|
||||
- proseが判断、warning、constraint、permission、recoveryのいずれかに実際に必要か。
|
||||
- heading level、font size、weight、colorが同じhierarchyで揃っているか。
|
||||
- metadataがbodyより強く見えていないか。
|
||||
- uppercase、letter spacing、monospaceを装飾目的で多用していないか。
|
||||
|
||||
### State、permission、action
|
||||
|
||||
- loading、empty、error、permission denied、unavailableが見分けられるか。
|
||||
- statusがcolorだけに依存していないか。
|
||||
- 利用できないowner-only actionをdisabledで並べるのではなく、page
|
||||
compositionから除外できているか。
|
||||
- filled primary actionが同じscopeに複数ないか。
|
||||
- destructive actionが通常のprimary pathと誤認されないか。
|
||||
- errorとrecovery actionが、失敗したsurfaceの近くにあるか。
|
||||
|
||||
### Interactionとaccessibility
|
||||
|
||||
- keyboard focusが全controlで見えるか。
|
||||
- focus順が画面の作業順と一致するか。
|
||||
- fold、disclosure、dialog、menuを操作した後にfocusが失われないか。
|
||||
- hoverでしか得られない必須情報がないか。
|
||||
- scroll region、icon-only control、status markerにaccessible
|
||||
nameまたはtextがあるか。
|
||||
- reduced motionで意味が失われないか。
|
||||
|
||||
## 実行手順
|
||||
|
||||
### 1. Scenarioを決める
|
||||
|
||||
変更対象route、persona、viewport、theme、data state、capture
|
||||
pointを列挙する。既存scenarioが目的を満たす場合は再利用し、満たさない場合は同じ変更内で追加または更新する。
|
||||
|
||||
### 2. 変更前をcaptureする
|
||||
|
||||
`tools/web-ux`を使い、変更前のsourceとscenarioを固定する。出力はrepositoryのproduction
|
||||
sourceへ混ぜず、`target/web-ux`などの開発artifact領域へ置く。
|
||||
|
||||
### 3. 変更前を目視する
|
||||
|
||||
既存問題も含め、変更対象付近のgrouping、separator、spacing、wrapping、responsive、stateを確認する。変更前を見ずに、変更後の印象だけで改善を主張しない。
|
||||
|
||||
### 4. 実装する
|
||||
|
||||
発見した問題と、適用するUX規則を対応付けて修正する。screenshotだけを整えるfixture固有hackや、productionと異なるshell、CSS、data
|
||||
authorityを作らない。
|
||||
|
||||
### 5. 同条件で再captureする
|
||||
|
||||
persona、route、viewport、theme、data
|
||||
stateを揃える。変更後だけ別の好条件へ変えない。
|
||||
|
||||
### 6. Screenshotとreview contextを確認する
|
||||
|
||||
実装者自身が画像を開き、`review-context.json`も読む。次を確認する。
|
||||
|
||||
- document status
|
||||
- visible UI error
|
||||
- console error
|
||||
- page error
|
||||
- failed request
|
||||
- accessibility snapshot
|
||||
- screenshot hashとcapture point
|
||||
|
||||
HTTP 200でも画面に`401 Unauthorized`などが表示されていれば失敗とする。
|
||||
|
||||
### 7. Compareする
|
||||
|
||||
before/afterを比較し、差分が意図した領域に限定されているか確認する。pixel
|
||||
differenceの大小だけで合否を決めない。意図しない移動、折返し、欠落、色変化を目視する。
|
||||
|
||||
### 8. Testとevidenceを揃える
|
||||
|
||||
source test、component test、E2E、type checkを実行し、visual
|
||||
evidenceと一緒に記録する。test成功はvisual reviewの代替ではなく、visual
|
||||
review成功もtestの代替ではない。
|
||||
|
||||
## 合格条件
|
||||
|
||||
次をすべて満たした場合だけvisual reviewを合格とする。
|
||||
|
||||
- 実装者自身がbefore/afterを目視した。
|
||||
- 必要なpersona、viewport、theme、data stateを確認した。
|
||||
- grouping、separator、spacing、wrapping、responsiveを確認した。
|
||||
- visible、console、page、request errorが解決または明確にdispositionされた。
|
||||
- 発見した新規問題を修正し、修正後を再captureした。
|
||||
- 意図しないvisual regressionがない。
|
||||
- artifact path、scenario、確認結果をhandoffへ記録した。
|
||||
|
||||
次は不合格である。
|
||||
|
||||
- capture commandが成功しただけ。
|
||||
- screenshotを生成したが開いていない。
|
||||
- desktopだけ、ownerだけ、happy pathだけを見た。
|
||||
- 後段Reviewerまたはユーザーが目視する前提でhandoffした。
|
||||
- 「好みの問題」として、説明できないseparator、spacing、overflowを未確認のまま残した。
|
||||
- testまたはlinterが成功したため、visual reviewを省略した。
|
||||
|
||||
## Evidenceの記録
|
||||
|
||||
handoffには最低限、次を記録する。
|
||||
|
||||
```text
|
||||
Visual review
|
||||
- Scenario: <scenario path>
|
||||
- Before: <run id or artifact path>
|
||||
- After: <run id or artifact path>
|
||||
- Personas: <list>
|
||||
- Viewports: <list>
|
||||
- Themes: <list>
|
||||
- Data states: <list>
|
||||
- Findings fixed: <bounded list>
|
||||
- Known issues: <bounded list with disposition>
|
||||
- Result: pass | fail
|
||||
```
|
||||
|
||||
画像をTicketやGitへ大量にcommitしない。artifact pathとsource
|
||||
commitを結び付け、必要なreviewerが同じ条件を再現できるようにする。認証profile、cookie、token、secret、private
|
||||
response bodyをartifactやhandoffへ含めない。
|
||||
|
||||
## Toolの位置付け
|
||||
|
||||
`tools/web-ux`はbrowser起動、scenario再現、capture、review
|
||||
context、compareを提供する。Toolは視覚的な良否を自動決定しない。
|
||||
|
||||
Linterが構文規則を検査し、testが特定のbehaviorを検査するのと同様に、visual
|
||||
reviewはrendered
|
||||
resultの関係性を検査する。三者は補完関係であり、どれか一つで他を代替しない。
|
||||
成果物は`target/web-ux`などの開発artifact領域へ置き、画像を大量にGitやTicketへ保存しない。認証profile、cookie、token、秘密情報、非公開response bodyを含めない。確認支援ツールは[tools/web-ux](../../../tools/web-ux/README.md)が所有し、合否判断は実装者が行う。
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
# Validation
|
||||
# 変更の検証
|
||||
|
||||
Validation should match the change. Do not run expensive broad checks just to look thorough, but do not skip checks that prove the changed boundary still works.
|
||||
変更した契約を確認できる検証を選び、実行した範囲と未確認の範囲を区別する。広いcheckの成功で、変更した境界のテストを代替しない。
|
||||
|
||||
## Docs-only changes
|
||||
## 文書とrustdoc
|
||||
|
||||
Minimum checks:
|
||||
文書だけの変更では`git diff --check HEAD`に加え、移動・削除したファイルへのリンク、ソースのinclude、テストや生成処理の参照を確認する。crate READMEやrustdocを変えた場合は、対象crateのドキュメント生成とdoctestも確認する。通常無効のfeatureでだけ存在するitemへのリンクは、その構成でも検証する。
|
||||
|
||||
```sh
|
||||
git diff --check HEAD
|
||||
```
|
||||
## Rustコード
|
||||
|
||||
Useful stale-surface sweeps:
|
||||
|
||||
```sh
|
||||
STALE_DOC_PATTERN='TODO[.]md|obsolete doc dirs|old ticket surface'
|
||||
rg -n "$STALE_DOC_PATTERN" README.md docs crates/*/README.md --glob '!docs/report/**'
|
||||
```
|
||||
|
||||
## Code changes
|
||||
|
||||
Follow `AGENTS.md` and the [test strategy](rust-testing-strategy.md). Start with the smallest target/filter that proves the changed contract:
|
||||
[テスト設計規則](rust-testing-strategy.md)に従い、開発中は変更を証明する最小のtarget・filterを使う。
|
||||
|
||||
```sh
|
||||
cargo test -p <crate> --lib <test-or-module-filter>
|
||||
cargo test -p <crate> --test <test-target> <test-filter>
|
||||
```
|
||||
|
||||
Before completion, run the root compile check and the changed crate's full tests, plus formatting and diff checks:
|
||||
完了前は[AGENTS.md](../../AGENTS.md)に従ってrootのcompile closureと変更crate全体を確認する。
|
||||
|
||||
```sh
|
||||
cargo check
|
||||
@@ -35,49 +24,26 @@ cargo fmt --all -- --check
|
||||
git diff --check HEAD
|
||||
```
|
||||
|
||||
Unit, component, and integration suites are ordinary tests. Do not exclude a named integration target merely because it uses temporary SQLite, loopback/IPC, or an isolated child process. Include affected product features and cross-crate integration suites beyond the changed crate when they own the connection being changed. `cargo check` does not execute tests, and `--lib` alone does not run external integration-test targets.
|
||||
単体・コンポーネント・結合は通常のテストに含める。SQLite、loopback、隔離子プロセスを使うtargetも省略しない。影響する製品feature、test-only target、別crateが所有する結合suiteを追加する。`cargo check`はテストを実行せず、`--lib`だけでは外部integration-test targetを実行しない。
|
||||
|
||||
Workspace-wide tests and heavyweight builds are selected when the changed boundary requires them, not as a substitute for identifying the relevant suites. Avoid repository-wide formatting churn when a validation failure is caused by pre-existing unrelated formatting.
|
||||
Workspace全体、`--all-targets`、Nix/Dockerなどの重い検証は、変更した境界を狭い検証では確認できない場合か、明示的に求められた場合に選ぶ。既存の無関係なformat差分を混ぜない。公開APIや生成元を変えた場合は、所有する生成処理と差分検査で生成物との一致を確認する。
|
||||
|
||||
## Web UI changes
|
||||
## Webとレビュー
|
||||
|
||||
Rendered Web UI changes require implementer-owned visual review in addition to type check, linter, and test. Follow [`ui-ux/visual-review.md`](ui-ux/visual-review.md).
|
||||
描画結果が変わるWeb UI変更には、type check・testに加えて実装者自身の[目視確認](ui-ux/visual-review.md)を必要とする。画像の生成、HTTP成功、後段Reviewerへの委託だけでは代替できない。
|
||||
|
||||
Minimum evidence includes:
|
||||
Ticketに対するレビューでは、差分のもっともらしさだけでなく、現行の要求と受入条件を満たすかを確認する。レビュー判断と実行可能な検証は互いの代替にしない。
|
||||
|
||||
- before/after capture under the relevant persona, viewport, theme, and data state;
|
||||
- the implementer’s own inspection of grouping, separator meaning, spacing, wrapping, overflow, responsive order, and visible errors;
|
||||
- `review-context.json` inspection;
|
||||
- disposition of every observed problem;
|
||||
- artifact paths and a final `pass | fail` result.
|
||||
## E2EとCI
|
||||
|
||||
A successful capture command, HTTP 200, screenshot generation, or later Reviewer inspection does not replace this validation.
|
||||
|
||||
## Work item checks
|
||||
|
||||
Use the authorized Ticket tools and boundaries described in `AGENTS.md`. Work item review verifies that the implementation satisfies the Ticket, not only that the diff looks plausible; it does not replace executable tests.
|
||||
|
||||
## E2E validation
|
||||
|
||||
Product-assembly journeys belong to the workspace `tests/e2e` package and use its shared harness. Add or extend these tests for an explicitly selected journey that ordinary component/integration tests cannot prove. A crate-local test using an isolated child process is not E2E merely because it crosses a process boundary.
|
||||
|
||||
E2E has a dedicated opt-in entry point:
|
||||
製品を組み立てる利用場面の検証は、`tests/e2e`の共有harnessを使う。必要なbinary・製品feature、隔離・回収、timeout、診断をsuiteに明記する。稼働中の開発環境を借りたり、crateごとに別の製品起動基盤を作ったりしない。
|
||||
|
||||
```sh
|
||||
cargo test -p yoi-e2e --features e2e --test <target> <test-filter>
|
||||
```
|
||||
|
||||
Select the target for the affected journey. Its suite documentation must describe required binaries/build features, runtime prerequisites, isolation and cleanup, timeouts, and diagnostic artifacts. Do not substitute a live developer deployment or create a second product-launch harness in an individual crate.
|
||||
このpackageは`default-members`に含まれず、対象には`e2e` featureが必要である。通常のroot実行やfeatureなしのWorkspace実行をE2E成功として報告しない。選んだ利用場面と実結果、または未実行を記録する。
|
||||
|
||||
The package is outside workspace `default-members`, and its test targets require the `e2e` feature. An ordinary root test run does not execute these targets; even a workspace-wide test run without the feature does not prove E2E behavior. Record the selected target and actual result, or state that E2E was not run.
|
||||
通常suiteのCI jobは、主張する結合targetと製品featureを実際に選択する。E2Eは専用jobとし、必須になる変更、手動・定期実行の条件を定める。特殊なplatform・tool・権限が必要なsuiteも適切なjobを持ち、可搬なテストまで例外扱いしない。
|
||||
|
||||
## CI execution policy
|
||||
|
||||
Suite ownership includes execution selection, not only source placement:
|
||||
|
||||
- Ordinary unit/component/integration suites belong in normal validation jobs. Jobs must include named integration targets and the product-feature configurations they claim to validate; a `--lib` job is not whole-crate coverage.
|
||||
- E2E belongs in a separate job that explicitly selects the package, feature, and journey targets. State its trigger policy: which changes require it and whether it also runs manually or on a schedule. Opt-in locally must not mean an unowned suite that is never exercised.
|
||||
- Suites with special platform, tool, or privilege requirements need a suitable job and an explicit selection rule. Keep the portable portion in normal validation rather than hiding an entire suite behind the exceptional case.
|
||||
- Timeouts, concurrency limits, resource cleanup, and failure artifacts belong to the shared harness/runner configuration. Do not rely on undocumented local setup.
|
||||
|
||||
When adding or changing a suite, identify the job that executes it and update its selection if needed. If execution is not yet wired, record that gap; a policy statement or a successful local command is not evidence that CI runs the suite.
|
||||
suiteの追加・変更時は実行するjobを確認し、選択条件を更新する。未配線なら不足として記録する。CI方針の記載やローカルの成功だけから、CIで実行済みと判断しない。
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# Web state checks and retained identities
|
||||
|
||||
The Web client uses purpose-specific content and operation identities, not an independent update counter. The checks below describe current boundaries, not a claim that the full cleanup or final generated-contract validation is complete.
|
||||
|
||||
| Boundary | What is compared, how, and when | Why order or time alone is insufficient |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Config source commit | The editor sends its saved `base_digest` and staged changes; update/rename/delete carry each observed entry's `expected_digest`. Local WASM checks entry conditions before replacing local content. The Server owns persistence admission. Success installs the returned snapshot as the new base; rejection retains the draft until explicit reload/reapply. | A later request can be authored from old content. Digests identify the actual source tree/entry, including a return to identical content; wall-clock age is not the condition. |
|
||||
| Config diagnostics | Before publication the editor requires a non-aborted request and filters diagnostics by the analyzed tree digest. | A late result is not evidence about currently displayed source. Content identity and asynchronous invalidation are different checks. |
|
||||
| Drive update/relocate/delete | The controller pins `expectedMutationId` from the user's observed node. The adapter sends it unchanged. `workspace-drive::mutate` checks current `last_mutation_id` via `changeable` in the same SQLite transaction before mutation. Publication stores the actual successful request ID and a durable receipt. | This identifies a real mutation, not a separately allocated state number. Content-only equality misses move/rename or ABA return to identical bytes. Last-arriving writes can overwrite another operation's result; timestamps do not prove operation identity. |
|
||||
| Drive read/preview | Each chunk checks the current node's `last_mutation_id` against the observed operation inside the store transaction. Conflict does not silently switch to latest bytes. | Chunks from different writes must not be combined, regardless of arrival order. |
|
||||
| Drive response-loss recovery | Before sending, the controller records one pending request ID. Pending/unknown receipts block another write for that selection. Recovery queries the same request. The store checks actor and typed payload against the existing receipt fingerprint before returning its result. | Unknown outcome does not mean the write failed. A fresh ID can duplicate a committed side effect; ordering cannot identify which payload committed. |
|
||||
| UI asynchronous publication | Selection and write/page invalidation fences are checked before publishing returned data. These are local request-lifetime fences, not persisted resource-state conditions. | The projection follows the latest selected request, not the last response to arrive. No server-visible opaque state number is needed here. |
|
||||
| Subject behavior save | The client sends exact `expected_behavior_md`, including empty text, with the replacement. A successful response must describe the requested text; mismatch is an unknown outcome and is not automatically retried. | Observed text already supplies the content condition; no extra counter is needed. Application to Worker history is a separate operation. |
|
||||
| Memory history display | `change_id` and `current_change_id` identify immutable entries. The parser preserves Server order; current/historical is exact identity equality. | Stable links need immutable identities, but IDs do not establish chronology and must not be parsed or sorted as numbers. |
|
||||
| Runtime trust display | The parser requires verification `binding_id` to equal the displayed binding. Mutations use the observed enrollment identity; revoke confirmation also compares the exact displayed key fingerprint before sending. | Re-enrolling the same public key creates a different binding lifetime. Matching key bytes or a recent response cannot make old verification current. |
|
||||
| Merge Request review freshness | Review/request `subject_ref` must match current Git source ref. Revoked verdicts are excluded; effective events follow supplied thread order. Merged results use validated immutable integration evidence, not a new live observation. | Source equality identifies what was reviewed. Arrival order or target-only movement cannot confer approval on another source commit. |
|
||||
|
||||
`ConfigProjectionValidator` is a typed static-template-catalog validation policy (`kind`, namespace, key aliases), not an opaque freshness token. Source tree, projection and toolchain digests describe different inputs/results. Key fingerprints identify key bytes; binding IDs identify enrollment lifetimes; mutation IDs identify actual committed requests. These are not interchangeable and are not increasing numbers.
|
||||
|
||||
WIP remains `0.2.0`; naming cleanup alone does not justify a new package/protocol version or generic validator number. Final API/OpenAPI/protocol freshness must be checked after authoritative Rust changes are integrated. Earlier Web build or test success does not prove that final generation check.
|
||||
@@ -1,212 +0,0 @@
|
||||
# Scoped WIP integration validation
|
||||
|
||||
## Released dependency and resolution
|
||||
|
||||
The adopted Client, HTTP, Protocol and official Text View are **crates.io 0.2.0**,
|
||||
with exact `=0.2.0` workspace declarations and registry checksums in Cargo.lock.
|
||||
`cargo info <package>@0.2.0` confirmed all four published packages (Rust 1.88).
|
||||
`node scripts/verify-wip-dependencies.mjs` checks actual locked/offline Cargo
|
||||
resolution: all WIP packages use registry 0.2.0, each required package occurs
|
||||
once, and Protocol has one source. No path/Git override or attached checkout is
|
||||
used. The earlier unreleased 0.1.0 source snapshot and vendor verifier were
|
||||
removed when T-713 thread sequence 26 specified 0.2.0.
|
||||
|
||||
Canonical contract reference: wip-reference
|
||||
`6086f3c5ef10aa1464ce9c824750d7f667217bfd`. This reference documents the contract;
|
||||
Cargo resolves the release, not that checkout or an unreleased Rust source commit.
|
||||
|
||||
## Executed checks on registry 0.2.0
|
||||
|
||||
- `cargo test -p worker --locked --offline`: **894 unit + 110 integration tests
|
||||
passed**, no ignored/failed tests. This includes ordinary Tools mode, Feature
|
||||
permission/revocation, restore, session/history and controller integration.
|
||||
- Narrow development checks: `wip::binding` (25), `wip::provider_tests` (12), existing
|
||||
`wip::tests` and workdir projection tests (43), checkout transports/providers
|
||||
(20), workspace-config production/authority tests (19). These are included
|
||||
in the complete worker run.
|
||||
- Published SDK tests via `cargo test -p wip-client -p wip-http -p wip-protocol
|
||||
-p wip-text-view --locked --offline`: Text View **16 public signature/token/golden
|
||||
tests + 4 doctests**; Client **63 adapter/runtime tests**; HTTP **38 tests + 1
|
||||
doctest**; Protocol **22 tests + 1 doctest**. Tests run from the registry packages,
|
||||
not substituted source or removed vendor directories.
|
||||
- Root `cargo check --locked --offline` passed, including normal TUI, CLI,
|
||||
Runtime and Workspace Server compile closure.
|
||||
- `cargo fmt --all -- --check` and `git diff --check HEAD` passed.
|
||||
- `nix build --no-link .#yoi.cargoDeps` passed with the updated registry dependency
|
||||
hash. This validates credential-free dependency acquisition, not a full Nix
|
||||
binary/image build or deployment update.
|
||||
|
||||
## Current target compatibility (preview, not integration)
|
||||
|
||||
Source runtime at `23cf6c4f882ce78985ba4bd673751cebe64e2d87` and current develop
|
||||
`b5ea162f838fc36e364edec9ab6689292b59cffd` produce the conflict-free `git merge-tree`
|
||||
preview tree `7ef3545b635cbdfc5e24658f6d1532e0f8b0947c`. An archive of this tree,
|
||||
without a branch/ref change, passed root locked/offline Cargo check, all **91**
|
||||
Server API TypeScript tests (including OpenAPI/checked-in generator freshness),
|
||||
registry WIP resolution checks, and Nix dependency acquisition with the same hash.
|
||||
T-722's API macros, Server API/Drive/Server source, OpenAPI and generated TS blobs
|
||||
are unchanged from develop in the combined tree; no artifact overwrite or
|
||||
regeneration is necessary for this source change. The later added same-name scope
|
||||
regression changes tests only, not the validated runtime or shared contracts.
|
||||
|
||||
The initial archive check reused a Cargo target directory; returning to the older
|
||||
main checkout exposed stale preview Server API artifacts. All local workspace
|
||||
artifacts were invalidated, and a fresh main root check and complete **894 + 110**
|
||||
Worker suite passed. Use an isolated target directory for future cross-tree checks.
|
||||
No target push, integration branch, deployment, or live dogfood update was made.
|
||||
Orchestrator readiness/integration must still resolve the then-current target.
|
||||
|
||||
## Contract evidence
|
||||
|
||||
`crates/worker/src/wip/binding.rs` tests exercise fresh/loading/stale/failed and
|
||||
capacity-blocked acquisition, explicit refresh, superseded responses, retrieval
|
||||
ownership cleanup, output limits, direct unseen-path Inspect, official unmodified
|
||||
signature/path envelopes, structured Interface input and complete edge-only trees.
|
||||
They invoke the registered three-tool surface for a search returning typed Entry
|
||||
paths, inspect only a selected nonindexable path, dispatch the selected operation,
|
||||
and verify that a later Inspect failure neither fails nor replays an earlier
|
||||
successful operation. Interface acquisition failures are not empty operation lists.
|
||||
|
||||
`provider_tests.rs` exercises the actual Yoi adapter for root/self scope, optional
|
||||
refs, copied scope_ref, different scopes with identical names/operations, shape vs
|
||||
membership errors, validator/ref absence/mismatch and protocol failure precedence.
|
||||
It replaces a dynamic scope Object identity, observes both stale sides after
|
||||
InterfaceMismatch, reacquires without replay, and confirms direct NotFound ends
|
||||
safe Interface reuse. An in-flight operation survives descriptor refresh using
|
||||
its exact frozen Host and Client descriptors rather than the newer return type.
|
||||
|
||||
`ancestor_tests.rs` covers the independent review finding P1_CURRENT_ANCESTOR_SCOPE:
|
||||
valid live ancestor refs that differ from registration placeholders, root `/` and
|
||||
intermediate provider-owned ancestors, ref-less and ref-bearing scope deletion
|
||||
without a prior scope Inspect, rejected cached dispatch and orphan observation,
|
||||
replacement/republication without restoring old Interface registrations,
|
||||
same-name scope replacement or loss of public ref rejecting copied old metadata
|
||||
without replay (and later explicit Inspect/Invoke copying only the new optional ref),
|
||||
shape/target/Object-validator/Interface-mismatch precedence, and frozen in-flight
|
||||
ancestor Descriptor/handler/result validation after replacement. Valid Invoke
|
||||
makes one provider publication call for the target, not a separate scope lookup.
|
||||
|
||||
Existing checkout tests retain commit-time captured Workdir/connection/validator
|
||||
checks, alias reuse/restore fencing, inode replacement rejection, authorization,
|
||||
shared prior-read tracking, exact post-operation validators, cooperative
|
||||
cancellation and unknown/lost/malformed responses without retries. Existing native
|
||||
Ticket/Objective/Merge Request tests retain scoped Workspace authority and Feature
|
||||
permission separation; compatibility Invoke retains original schema validation,
|
||||
permission identity and ToolOutput/attachments.
|
||||
|
||||
## Downstream provider seams
|
||||
|
||||
- Public projection/contribution Interface fields use
|
||||
`wip_protocol::InterfaceReference { scope, name }` from registry 0.2.0.
|
||||
- `WipSubtreeProvider::publication` returns a `WipPublication` that captures target,
|
||||
current scope Object, Descriptor/validator and handler at one provider boundary.
|
||||
Mutable ancestors must not be resolved independently after selecting a target.
|
||||
`scope: None` ends usable publication, even without a public ref; it is not a
|
||||
ref-less published Object. Host checks and observation never substitute the
|
||||
root registration placeholder, and Interface fetch never falls back to retained
|
||||
provider registrations. Old registrations must end when their scope lifetime
|
||||
ends. Published ancestor scopes outside the subtree remain Host-owned.
|
||||
- Contextual/self-scope publishers use `WipPublication::self_scoped` on their exact
|
||||
provider snapshot; references are explicit paths, not an encoded `/@/` suffix.
|
||||
Checkout and Workspace Config retain their captured provider/Backend execution
|
||||
and commit fences. Ref-less publication remains legitimate.
|
||||
- `WipRuntime::{tree, inspect, invoke}` is the object-centered integration seam.
|
||||
Inspect uses official summary-only signatures and typed rendering failures.
|
||||
- `WipMountRegistry::mount_interface` adds a separate static Interface namespace
|
||||
without replacing Object ownership or flattening same-named operations. It does
|
||||
not overlay stale registration metadata onto a live subtree provider.
|
||||
- FS indexable boundaries and directory.list are provider policy. T-714 now
|
||||
implements entrance-only trees and bounded typed discovery (details below).
|
||||
Transport/commit tests are `checkout_wip_tests.rs`, `checkout_http_tests.rs`,
|
||||
and `checkout_race_tests.rs`.
|
||||
- Worldspace state is runtime-only. Historical opaque references in append-only
|
||||
session/tool history are not migrated into cache or parsed for a guessed scope;
|
||||
a restored Client starts with fresh observations and direct path Inspect.
|
||||
|
||||
## T-714 checkout discovery validation
|
||||
|
||||
Based on integrated T-713 at `f9d4c97f`, still using the exact released crates.io
|
||||
0.2.0 SDK and official renderer. No alternate wire, renderer or upstream version
|
||||
was introduced. The checkout projection design documents provider live pagination,
|
||||
its ordering/cursor/byte ceilings, special-entry policy and the distinction between
|
||||
indexable emptiness and empty filesystem directories.
|
||||
|
||||
Focused regressions:
|
||||
|
||||
```sh
|
||||
cargo test -p worker --lib checkout_wip
|
||||
cargo test -p worker --lib checkout::race_tests
|
||||
cargo test -p worker --lib checkout_http_tests
|
||||
cargo test -p worker --lib ancestor_tests
|
||||
cargo test -p tools --lib checkout_list
|
||||
cargo test -p fs-operation --test list_pagination
|
||||
cargo test -p workdir --test list_pagination
|
||||
```
|
||||
|
||||
Host depth-zero omission / positive-depth `children: []`, empty/small/1100-entry
|
||||
folders, direct deep roots and zero provider content-List calls during observation
|
||||
are tested independently of Client output. Registered model tools exercise entrance
|
||||
self-scope and deep target ancestor-scope, paginated List/Glob/Grep typed entries,
|
||||
lazy selected Inspect with official signatures/path context, and Read without
|
||||
COMMAND. Acquired paths never become tree edges; a stalled result observation is
|
||||
never awaited and disappearance only fails a later explicit Inspect. Remote HTTP
|
||||
checks verify List continuation on the wire with no ordinary List fallback or
|
||||
response credentials/host-path leakage. Provider tests cover scoped External and
|
||||
nested cwd/output_root rebasing, nonrecursive enumeration/read authority, ignore
|
||||
policy, live mutations, byte bounds, cancellation and malicious cursor/result DTOs.
|
||||
|
||||
`WipSubtreeProvider::interface_target` is a routing hint for target-qualified local
|
||||
names under ancestor scopes. The default retains self/shared-scope behavior. Host
|
||||
checks provider ownership and resolves **one** coherent current target/scope
|
||||
publication; it never treats the hint as authorization or fetches a scope in an
|
||||
independent mutable lookup. Focused Host tests reject forged/outside/prefix/missing
|
||||
hints, ref-less deletion and absent membership while preserving existing ancestor
|
||||
ref/validator and in-flight snapshots. Actual checkout tests reject sibling/prefix
|
||||
scopes, supplied refs when current publication has none and stale alias generations;
|
||||
ordinary content updates do not terminate the entrance Interface lifetime.
|
||||
|
||||
Completion validation:
|
||||
|
||||
- Root `cargo check` passed.
|
||||
- `cargo test -p fs-operation -p workdir -p tools -p worker` passed: FS **58 unit +
|
||||
6 pagination**; Workdir **125 unit + 5 pagination**; Tools **84 unit + 39
|
||||
integration + 1 doctest**; Worker **901 unit + 110 integration**.
|
||||
- `cargo test -p workdir --features http-client` passed (**125 + 5**).
|
||||
- `cargo test -p yoi-workspace-server` passed (**691 unit + 11 CLI**).
|
||||
- `cargo test -p server-api` passed (**83**, including checked-in OpenAPI/TypeScript
|
||||
freshness). No generated API artifact changes were needed.
|
||||
- `node scripts/verify-wip-dependencies.mjs` confirmed one crates.io 0.2.0 Protocol
|
||||
source and all four exact SDK packages. `cargo fmt --all -- --check` and
|
||||
`git diff --check HEAD` passed.
|
||||
|
||||
The first dependent Server test build exposed predecessor fixtures still using
|
||||
removed `discover`/opaque strings/private `call`. Those tests now select structured
|
||||
references from actual path Inspect and use public Tree/Inspect/Invoke; the complete
|
||||
Server suite above includes configuration integration and the typed Workdir proxy.
|
||||
Its dev-only Protocol dependency reuses the same locked SDK source; the resolved
|
||||
package/version set is unchanged.
|
||||
|
||||
No full workspace test, full Nix/image build, deployment, target integration or
|
||||
live dogfood update is claimed. Target movement remains Orchestrator authority.
|
||||
|
||||
### Post-success connection-loss review regression
|
||||
|
||||
The first independent source review identified `POST_COMMIT_CONNECTION_FENCE`:
|
||||
lazy result publication had propagated a connection mismatch as a pre-effect
|
||||
rejection even after successful Write/Edit/Create. The production result
|
||||
publication boundary now maps post-success connection loss for mutations to
|
||||
`OutcomeUnknown`, while Read/List/Glob/Grep keep their validator mismatch. Lazy
|
||||
entry mapping is unchanged; there is still no result-wide acquisition.
|
||||
|
||||
`committed_checkout_mutation_losing_connection_is_unknown_not_rejected_or_replayed`
|
||||
executes real checked Tools/provider mutations, deterministically detaches after
|
||||
the routed execution guard finishes but before the same production publication
|
||||
boundary, then passes the error through WIP Invoke/Client. It checks committed
|
||||
file contents, unknown audit (not rejection), and exactly one dispatch for each
|
||||
Write/Edit/Create. `readonly_checkout_result_losing_connection_keeps_validator_mismatch`
|
||||
protects the non-mutating error policy. No sleep or production test hook is used.
|
||||
|
||||
After this fix, all **9** checkout race tests and the complete Worker suite
|
||||
(**903 unit + 110 integration**) passed, as did root Cargo check, formatting,
|
||||
diff checks and released SDK resolution verification. The earlier **901** Worker
|
||||
count above describes the pre-review combined-crate run.
|
||||
@@ -1,95 +1,25 @@
|
||||
# Tickets and work outcomes
|
||||
# 作業記録と判断の責任
|
||||
|
||||
A Ticket is a durable record of natural-language user intent and a concrete work outcome, not a mandatory code/review/merge pipeline. Research, analysis, planning, documentation, operations, and code can all be Ticket work. Requirements, acceptance criteria, and binding decisions govern what must be done; the requested return path governs whether to continue, complete, close, or return results without concluding the Ticket.
|
||||
Ticketには、利用者の意図と、それに対して判定できる成果を記録する。調査、設計、文書、運用も成果となる。コード、レビュー、マージをすべてのTicketに必須とせず、要求と受入条件に従って結果の返却・完了・終了を判断する。
|
||||
|
||||
Workspace Server control-plane records, append-only Ticket events, and any linked Merge Request evidence are authoritative. Ticket and Objective records live in the Workspace Server database. Repository-local `.yoi/tickets`, `.yoi/objectives`, and config files are ignored legacy input, not a debugging or compatibility backend. Notifications, chat summaries, and Memory are hints to inspect current authority, not proof of completion.
|
||||
## 記録の粒度
|
||||
|
||||
## Concepts
|
||||
一つのTicketは、単独で価値と達成を判断できる成果にする。中期的な目標はObjective、一時的な進捗はセッションのTaskで扱う。進捗を束ねるだけのTicketや、その代替となる階層関係を増やさない。
|
||||
|
||||
- `Ticket`: durable work record containing intent, requirements, decisions, useful results/evidence, and resolution history.
|
||||
- `Objective`: a first-class medium-term goal with background, strategy, success criteria, and canonical Ticket context links. It neither schedules nor authorizes Ticket work.
|
||||
- `Task`: session-local progress tracking, not the project record.
|
||||
- `Assignment`: an authorized delegation of Ticket work to a Worker, independent from its Profile or Flow.
|
||||
- `IntentPacket`: a bounded natural-language handoff of intent, requirements, binding decisions, latitude, escalation conditions, and validation.
|
||||
- `Ticket relation`: non-hierarchical project metadata (`depends_on`, `blocks`, `related`, `supersedes`, `duplicate_of`). Inverse views are derived. Relations do not grant resources or start Workers.
|
||||
作成・大幅な変更の前に、関連記録と重複を確認する。背景、要求、受入条件、決定事項、実装の裁量、未解決の問いを区別して書く。確認済みの事実、利用者の説明、仮説を混ぜない。担当者の役割制限、そのターンの計画、文書作成後に停止する判断を製品の禁止事項へ転記しない。
|
||||
|
||||
One Ticket should describe an independently useful outcome that can be judged on its own terms. Do not convert temporary execution steps or a broad effort's progress container into mandatory product deliverables.
|
||||
実装の承認待ちは、利用者が求めた場合にだけ作業の条件として記録する。未確認事項は必要な調査で絞り、実装手段を早期に必須要件へ変えない。依存関係は成果間の関係を示すものであり、資源所有や実行順の記録の代わりにはしない。
|
||||
|
||||
## Entry points and authority
|
||||
## 正本の確認と成果の報告
|
||||
|
||||
Use the highest-level interface matching the request: Workspace Dashboard/Ticket UI, authenticated Workspace APIs, product `yoi ticket ...` or `yoi objective ...` commands, and typed Worker tools. Inside Workers, use the supplied typed Ticket tools; do not substitute a CLI or direct storage access for missing tools. Maintainers inspect authority through the Workspace API, Server administration surfaces, and database diagnostics, not repository-local Ticket files.
|
||||
実装・routing・レビュー・終了の判断前には、現行Ticketと必要な関連記録を読む。通知、要約、過去の実装報告を現行の要求や承認の証拠にしない。Worker内の操作は、提供されたtyped toolsとsystem instructionsに従う。CLIや保存先の直接編集で欠けた権限を代替しない。
|
||||
|
||||
The Server resolves Workspace access, active configuration, and immutable Profile launch material; Runtime does not infer identity or authority from a checkout's `.yoi` files, cwd, or launch prose. The first committed user request supplies bounded action context, not actor identity or new grants. Profile/Flow selection is configuration, not review, merge, or resource authority. Availability of a tool is not authorization to use it for unrelated work.
|
||||
成果には、実際の変更、検証結果、未確認事項、判断に必要な証拠を残す。すべての作業にコメントやartifactを義務付けず、利用者が必要とする返却方法を選ぶ。長い調査結果、生ログ、秘密情報はTicket本文へ転載しない。
|
||||
|
||||
## Starting and returning Ticket work
|
||||
## コードの公開とレビュー
|
||||
|
||||
`SpawnTicketWorker` starts and atomically assigns a generic Worker with an `initial_request`. Its registered `profile` is optional (omitted/null selects `builtin:ticket-worker`); `flow` and alias-keyed `workdir_attachments` are optional. No repository or Flow is required for non-code work. Choose an explicit Profile when its tools and instructions fit the task; assignment does not depend on choosing the Coder role. Backend validates claims, derives repository attachment access from declared Ticket targets, and preserves explicit ExternalGrant ceilings. Callers cannot request capabilities through attachment selection.
|
||||
公開が作業範囲なら、まとまりのあるcommitを作り、正確なsourceを通常の非force更新で公開する。レビューでは要求、変更全体、検証証拠を独立に確認する。口頭の承認、Profile、Flow遷移を権限の代用にしない。
|
||||
|
||||
The guarded start records acceptance only after spawn, initial input, assignment, and attachment finalization. Do not pre-write `inprogress` or mistake a state update for a launch. On a failed or unknown outcome, reread durable operation evidence before retrying or spawning a replacement. After acceptance, reread Ticket state and current Worker responsibility.
|
||||
source変更後は変更後のsourceを再レビューする。targetだけの変化では、未変更sourceへの承認と統合可否の再確認を区別する。通常の修正・再レビュー証拠はMerge Requestに置き、Ticketには調整が必要な阻害要因、決定、最終引継ぎを残す。
|
||||
|
||||
Results can be returned in conversation, optionally recorded in a `TicketComment`, optionally published as a Drive document/artifact when that surface is available, or written/published to an authorized repository. Neither comments nor Drive artifacts are universally required. Preserve concise evidence useful to the user without fabricating work, tests, or conclusions. `implementation_report` is optional historical/audit context, not integration readiness or completion authority.
|
||||
|
||||
The return choice is independent of result format. Examples:
|
||||
|
||||
- Research with review not required: answer the question and optionally record a useful summary; no MR or approval is required for an authorized completion decision.
|
||||
- Code requiring independent review before merge: use the trusted review path and guarded MR integration. A Profile, Flow transition, tool invocation, or prose approval is not the review judgment.
|
||||
- Return without conclusion: report the result/limitations and leave the Ticket unconcluded. A finished turn, an artifact, or an approved MR does not itself decide completion.
|
||||
|
||||
These are instruction/tool contracts, not a claim that a mechanism proves real agent judgment. Backend authenticates actors and enforces typed boundaries; the responsible Worker/human must actually judge satisfaction against the natural-language request.
|
||||
|
||||
## Ticket tools and decisions
|
||||
|
||||
`QueryTicket` provides bounded discovery/filtering; `ShowTicket` provides authoritative item content and `content_digest`, paged thread, relations, Objectives, and any current MR context. Read the relevant Ticket before implementation, routing, review, state, or conclusion decisions. Check potential duplicates before creation or material rescope. Use `QueryObjective` and `ShowObjective` for broader context without replacing the Ticket read.
|
||||
|
||||
Useful mutation surfaces include `TicketCreate`, `TicketComment`, relation/plan tools, and authorized `TicketWorkflowState`, `CompleteTicket`, and `TicketClose` decisions. Exposure depends on the effective Profile/Feature configuration. Decision tools use current `content_digest` as `expected_content_digest`, expected state, a reason/resolution, stable operation key, and optional supporting references. Backend checks actor authority, CAS, replay, and resource boundaries. It does not enforce a universal state sequence, MR/approval gate, or natural-language satisfaction.
|
||||
|
||||
`CompleteTicket` records completion; `TicketClose` records closure. Neither integrates an MR or expands resource grants. Ticket progress/conclusion can occur without commits, MRs, or approvals when consistent with the request. Conversely, code's default publication/review practices must not be erased by confusing an independent Ticket decision with merge authority. Do not claim an unmerged result was integrated. Reread after conflicts and replay a recorded operation only with the same fingerprint.
|
||||
|
||||
Relations remain metadata, not a scheduling mechanism: record actual project dependencies, but do not encode capacity, Worker/worktree ownership, or execution ordering as relations. Use orchestration/session records for those concerns and inspect current Backend claim rules rather than inferring launch eligibility from a relation alone.
|
||||
|
||||
## Optional code Git/MR recipe
|
||||
|
||||
For code work using the default publication/review recipe, select `builtin:coder` and optionally `builtin:coder-review`. The Flow guides implement, review, fix, and handoff; it is not a Backend Ticket completion requirement and grants no authority. Read current repository state, use coherent commits and normal non-force publication only when authorized, validate the changed contracts, and report changed files, validation, and limitations. No commits/push should be inferred from a request explicitly prohibiting them.
|
||||
|
||||
Use one open repository-scoped MR per Ticket/repository. `OpenMergeRequest` creates selectors; once an MR exists, explicitly address its ID with `ShowMergeRequest` and preserve its selectors. Advance only the existing source selector with a normal non-force push. Do not invent an operation to append another source candidate, replacement MR, or fresh integration branch for every fix. Publish the exact committed source and verify provider resolution before requesting review.
|
||||
|
||||
The assigned Coder launches the Reviewer as an actual direct-child `builtin:reviewer` SubWorker, with explicit command grant and appropriate Workdir scope for inspection/validation, plus the structured Ticket/MR handoff. Server authority verifies assignment, Runtime-owned child identity, effective Profile, one-shot attempt, and captured subject. Review capability is injected by the trusted layer, not supplied by the model. The Reviewer makes an independent judgment against intent, acceptance criteria, the complete captured result snapshot, and validation, then records `ReviewMergeRequest`; prose and observation are not approval authority.
|
||||
|
||||
Source movement requires fresh review for the exact new source. Target-only movement preserves unchanged-source approval but requires refreshed integration evidence. Keep routine review/fix/rereview evidence on the MR rather than repeating each iteration in Ticket comments.
|
||||
|
||||
MR integration remains separate Orchestrator authority. Immediately before integration, reread Ticket/MR authority and `CheckMergeRequestReadiness`. Verify exact approved source, current target, strategy, and approval event. Apply and validate integration through ordinary source control in the bound Workdir, push normally, and verify provider resolution. `CompleteMergeRequest` records that already-applied repository result; it does not move a branch, complete the Ticket, or release assignment. If push succeeded but recording failed, reread/replay the same operation rather than pushing again. A Ticket request saying review is not required does not bypass MR integration guards.
|
||||
|
||||
## Responsibility, unfinished work, and cleanup (T-715)
|
||||
|
||||
Terminal current responsibility is retained for display after unfinished work ends. It is not the same as an unfinished assignment and does not require an invented unassignment operation before cleanup. A stopped or idle Worker may still own unfinished work; a completed/closed Ticket does not automatically stop/remove the Worker, release its attachments, or delete a Workdir. A result report or Flow terminal state is neither conclusion nor cleanup authority.
|
||||
|
||||
Worker stop, retention/removal, attachment release, and Workdir deletion are independent guarded decisions. Do not predeclare `delete_on_completion` or `retain_on_completion` at launch. Retain Workers while a requested review/fix or other handoff can still return; do not remove them just because one turn ended.
|
||||
|
||||
When authorized cleanup is in scope:
|
||||
|
||||
1. Reread Ticket responsibility, active unfinished work, Worker state, handoffs/notifications, and current retention constraints. Use the exact Worker subject from `WorkerList`.
|
||||
2. Stop the Worker if needed and confirm terminal state. Stop alone does not end unfinished work. `UnfinishedWork` (`unfinished_work`) requires explicit authorized work end or reassignment and a fresh authority read.
|
||||
3. Call `WorkerRemove` only with no unfinished work, running/restoring state, pin/legal hold, pending notification, or handoff. Backend revalidates guards; removal releases attachments but preserves Workdir materialization.
|
||||
4. Reread actual Workdir attachment release, occupancy, cleanliness, ownership, provider availability, and other use. Retain still-needed/existing resources. Only then use `WorkdirDelete` for a proven clean, unoccupied, no-longer-needed Ticket-dedicated Workdir owned/selected for this work.
|
||||
|
||||
Never delete before attachment release, force removal, discard changes, or advance on a partial/unknown failure. Reread authority before a bounded retry. Cleanup failures do not roll back recorded Ticket or MR outcomes; report concrete blockers rather than adding routine success comments.
|
||||
|
||||
## Authoring and granularity
|
||||
|
||||
Intake clarifies ambiguous intent, checks duplicates and relevant context, and records agreed work. Create/update only after user agreement or explicit instruction to record the draft. A minimum investigation gate is an authoring responsibility, not a universal implementation approval gate. Intake is not scheduling, implementation, review, merge, or cleanup authority.
|
||||
|
||||
A useful Ticket records background/motivation, requirements, acceptance criteria, binding decisions/invariants, implementation latitude, and relevant open questions/risks. Include useful result evidence and final resolution when appropriate. Separate confirmed facts, user claims, hypotheses, and undecided questions. Do not copy Worker role boundaries, a temporary plan, or a decision to stop after authoring into the Ticket as a product prohibition. Record an approval gate only when the user asks for it; do not prematurely prescribe implementation tactics.
|
||||
|
||||
Risk flags prompt context checks and reviewer focus, not automatic stop gates. Return to planning only for a named missing decision/information after bounded checks; escalate beyond-scope decisions without inventing new constraints.
|
||||
|
||||
Do not create new umbrella/progress-container Tickets. Split broad work into concrete useful outcomes, record the split in Ticket/Objective context, and use Objectives for enduring medium-term goals. Do not replace umbrellas with parent/child, sub-ticket, part-of, contains, or other hierarchy relations. A concrete planning/design/investigation Ticket is valid when requested. Existing umbrellas can be retired as superseded/decomposed without rewriting history; distinguish container retirement from completion of future work.
|
||||
|
||||
Keep long research dumps outside the item body and summarize necessary bounded artifacts. Never store secrets, credentials, private prompt contents, or secret-bearing logs in Ticket/MR/Drive records or model-visible evidence.
|
||||
|
||||
## Configuration and validation
|
||||
|
||||
Workspace Ticket data and authority live in Server SQLite and active Workspace configuration. `yoi init --display-name <NAME> --repository-key <KEY>` registers a repository through Backend and writes global client routing only. Repository-local `.yoi/workspace.toml` and `.yoi/ticket.config.toml` do not grant Workspace identity, Backend connection, role selection, or Ticket authority. Legacy `ticket init` and `ticket import-local` are unsupported; no automatic migration reads local Ticket trees.
|
||||
|
||||
Dashboard actions are explicit user actions, not scheduler/auto-maintainer authority. Launch success without a visible result calls for attaching to or observing the Worker and rereading authority, not assuming conclusion. Select an accessible Workspace and current registered Profile when a launch reports missing configuration.
|
||||
|
||||
Validate the actual result with checks appropriate to its acceptance criteria. Code changes follow `AGENTS.md` and [Rust testing strategy](rust-testing-strategy.md): begin with the smallest contract-proving target/filter, then the required compile/test/format checks. Record only validation actually run. Narrow effective instruction/tool fixtures protect inclusion, schema, and policy wording; they do not prove real Worker judgment or end-to-end behavior.
|
||||
統合、Ticketの結論、Worker停止、接続解除、資源削除は別の判断である。終了通知や最終回答だけから後続操作を開始せず、依頼範囲と現在の正本を確認する。未反映の変更を統合済みと報告しない。
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
# Workdir refusal diagnostics
|
||||
|
||||
This change improves the internal `api_error` record for
|
||||
`POST /api/w/{workspace_id}/workers/self/workdir-session/operations`.
|
||||
It does **not** identify or resolve the reported 2026-10-09 403: the running
|
||||
binary source commit and actual request are still unverified. Fixture success is
|
||||
not evidence about that incident. No deployment, live asset cleanup, privilege
|
||||
change, retry policy, or WIP dependency/version change is included (WIP remains
|
||||
0.2.0).
|
||||
|
||||
## Diagnostic path and public contract
|
||||
|
||||
Original refusing branches attach a closed `WorkdirDenialReason`, rather than
|
||||
recovering meaning from an error string. `WorkdirError::Denied` carries local
|
||||
message plus optional typed reason. `DenialContext` retains an existing error
|
||||
classification while enriching its diagnostics (notably checkout's historical
|
||||
permission-to-out-of-scope mapping). Typed provider markers survive the
|
||||
`FsAccessPolicy`/`io::Error` → `FsError` → `WorkdirError` boundaries.
|
||||
|
||||
`WorkdirTransportError.denial_reason` and
|
||||
`ExternalWorkdirOperationError.denial_reason` carry only an optional snake-case
|
||||
enum. Remote HTTP and External errors preserve that reason when converted back
|
||||
to `WorkdirError` and then forwarded again. Provider text is not the reason.
|
||||
The External codec continues dropping provider-authored message text. A missing
|
||||
reason stays unknown for generic `denied`; existing code-level path/symlink/
|
||||
read-only classifications can still identify their original category. Unknown
|
||||
or oversized reason strings fail deserialization rather than becoming log data.
|
||||
|
||||
Runtime registry session-open failures now retain a safe transport error **before**
|
||||
the former raw error-to-string conversion. Their existing Workspace HTTP meaning
|
||||
is still 502 (`workdir_session_open_failed`); operation-level permission failures
|
||||
remain 403. Session-open messages are normalized to the same safe provider
|
||||
message vocabulary instead of forwarding raw local errors. No authorization,
|
||||
scope, capability, session ownership, command ownership, or retry conditions
|
||||
are relaxed.
|
||||
|
||||
The self handler attaches internal context to its error. Both the handwritten
|
||||
response adapter and the generated route preserve it through the typed error
|
||||
response extension to `ApiErrorLog` and the logging middleware. Server API
|
||||
`workdir_log` and `denial_reason` are `serde(skip)` and omitted from schemas:
|
||||
the external error envelope remains `{code, message}` for provider failures or
|
||||
the existing Repository-style envelope for API/domain failures. Workspace
|
||||
responses do not expose the new internal diagnostics. Authorized Runtime/provider
|
||||
transport carries the enum to its consumer; it contains no free text or paths.
|
||||
|
||||
## Fields in `yoi::api` / `api_error`
|
||||
|
||||
- `operation`: fixed label from the typed operation (read, read_bytes, write,
|
||||
edit, command_start/status/output/cancel, authorize_scope, scope_rules_overlap,
|
||||
checkout_search/observe/execute, stat/list/glob/grep). No operation Debug dump.
|
||||
- `workdir_stage`: workspace_scope, worker_identity, attachment_validation,
|
||||
session_open, command_lookup, provider_dispatch, or command_registration. API
|
||||
failures identify the encountered stage without copying raw alias/handle/error
|
||||
text into the log. Registration failures can follow successful command start;
|
||||
they are not evidence of a pre-dispatch refusal or absence of effects.
|
||||
- `denial_reason`: optional closed reason reported by the provider/error chain.
|
||||
It describes the encountered refusing branch, not a permission grant and not
|
||||
an independent attestation of an external provider's explanation.
|
||||
- `workspace_id_hash`, `runtime_id_hash`, `worker_id_hash`, `workdir_id_hash`,
|
||||
`attachment_alias_hash`: 64-character lowercase SHA-256 fingerprints via
|
||||
`worker_source::diagnostic_id_hash`, using the existing
|
||||
`yoi-runtime-proof-diagnostic-id-v1\0` domain prefix. Hash a known catalog ID
|
||||
or attachment alias to locate its logs. Workspace and alias represent route/
|
||||
request selection; Runtime/Worker are added only after current identity checks,
|
||||
and Workdir only after a validated attachment is found. Missing information is
|
||||
omitted, never inferred from a failed lookup or command handle.
|
||||
- `request_token_id_hash`: fingerprint of the **existing verified Runtime proof
|
||||
JTI**, reusing the T-729 hash algorithm. It is not the proof, raw token ID,
|
||||
caller-supplied trace header, or a newly invented cross-service trace ID.
|
||||
In-process/test callers without that proof correlation omit it. Authentication
|
||||
still consumes the JTI using the existing policy; logging does not retry it.
|
||||
- Existing `method`, query-free `path`, `status`, `kind`, `message`, `diagnostics`
|
||||
remain. Workdir-context method/path are bounded to 32/512 UTF-8 bytes with
|
||||
controls removed. Provider operation messages remain normalized safe text.
|
||||
Contextual API failures use a fixed internal message and status-based kind,
|
||||
with no raw domain diagnostics copied into this log. Empty `diagnostics` does
|
||||
**not** mean the optional typed reason/context is absent.
|
||||
|
||||
Fingerprints are bounded correlation, not encryption: low-entropy identifiers
|
||||
can be guessed. Do not put credentials in IDs. Neither hashes nor stage labels
|
||||
prove the reported incident's cause. The request JTI identifies the incoming
|
||||
request; External operation IDs/generation/audit records remain their existing
|
||||
provider-side mechanisms, not a cross-service trace automatically joined here.
|
||||
|
||||
## Refusal vocabulary and limits
|
||||
|
||||
Reasons distinguish scope resolution/comparison unavailable, attenuated
|
||||
capability refusal, logical/parent/attachment/delegation scope exceeded,
|
||||
empty scope, invalid scoped path, writable-scope requirements for commands,
|
||||
child write-lease conflicts, unreadable cwd, read-only session, external root
|
||||
symlink/change/move, external scope symlink, checkout output-root escape,
|
||||
provider root escape, checkout search/enumeration refusal, provider symlink/
|
||||
checked-target refusal, path out of scope, symlink target out of scope, and
|
||||
read-only path. Original local messages remain local; arbitrary message text
|
||||
is never parsed into a reason.
|
||||
|
||||
- `os_permission_denied`: PermissionDenied with an actual OS error code, without
|
||||
a recognized policy marker. It does not determine whether modes, ACLs,
|
||||
sandboxing, mount policy, or another OS mechanism caused the refusal.
|
||||
- `unclassified_permission_denied`: custom PermissionDenied with neither typed
|
||||
policy origin nor raw OS code. Do not label an arbitrary
|
||||
`Error::new(PermissionDenied, "...")` as an OS refusal by guessing from text.
|
||||
- External/remote providers that do not send a reason cannot supply distinctions
|
||||
erased in their own binary. A registered provider's typed explanation is
|
||||
reported data, not proof of what happened inside that provider.
|
||||
- Authentication/JSON ingress failures before a typed Workdir operation and
|
||||
target can be obtained continue using their existing rejection diagnostics;
|
||||
no operation/body is guessed or decoded from rejected proof data.
|
||||
|
||||
## Regression proof boundaries
|
||||
|
||||
- Workdir provider fixtures exercise actual local authorization, broker scope/
|
||||
capability/read-only/delegation/lease refusals and confinement. Permission
|
||||
fixtures distinguish typed policy origins, OS-coded PermissionDenied, and
|
||||
origin-unknown custom errors, including checkout's existing public mapping.
|
||||
- Loopback HTTP fake exercises remote open/operation decoding and typed reason
|
||||
forwarding, including missing-reason providers and hostile provider text.
|
||||
External JSON/frame codecs exercise the bounded error envelope and reject
|
||||
arbitrary reason strings; the Server's External adapter drives the real self
|
||||
generated route through command-session dispatch and response/log conversion.
|
||||
- Server router tests use signed fixture requests and capture actual tracing
|
||||
events. They cover read/write/command_start/authorize_scope, session-open
|
||||
failures, External command status failures, no-identity rejection, oversized
|
||||
UTF-8 alias, oversized command/file/tool-call inputs, query/header/proof/key/
|
||||
credential markers, and raw provider/host paths. Identity hashes and the
|
||||
existing verified JTI fingerprint are checked against fixture identities.
|
||||
- The Server API boundary proves internal fields are absent from body/schema.
|
||||
Worker HTTP→WIP coverage verifies diagnostic wrappers do not change refusal
|
||||
classification or induce retries; tools retain established checkout codes.
|
||||
Normal-tool JSON→transport→WorkdirError→ToolsError→ToolError coverage checks all
|
||||
18 codes with no reason, a typed reason, and nested diagnostic wrappers. In
|
||||
particular, OutcomeUnknown/Conflict remain execution failures with unchanged
|
||||
display and inspection-before-retry guidance, not argument errors.
|
||||
Operational consumers share `WorkdirError::classification_source` to ignore
|
||||
only diagnostic wrappers while retaining the original error for diagnostics.
|
||||
Public Write tests cross JSON stat errors into the tool and the checked local
|
||||
provider: missing files are created identically without/with/nested reasons;
|
||||
denied/read-only/conflict/unknown-outcome stat failures never write or retry.
|
||||
This protects actual create control flow in addition to error conversion.
|
||||
- Real Remote HTTP and channel-backed External session dispatch tests cover
|
||||
Read/Create/Write/Edit crossed with missing/typed reasons and five error codes.
|
||||
After a dispatched mutation, Unavailable/Transport still become OutcomeUnknown
|
||||
with the original reason preserved: forwarded 500 and inspect-before-retry
|
||||
guidance are unchanged. Read and definite refusal/conflict controls retain
|
||||
their existing codes. The External path also checks actual HTTP error body/
|
||||
status conversion and absence of a new dispatch after the failure.
|
||||
- Fake-command JSON fixtures cover attachment detach and scoped-command close
|
||||
without launching processes. Missing/typed/nested UnknownCommand all retain
|
||||
disappearance/lease-release semantics. Unavailable remains a busy detach or
|
||||
failed scope cleanup; scope close revokes new work immediately but retains the
|
||||
write lease while cleanup is unresolved. Diagnostic data never grants cleanup.
|
||||
- A signed real self-handler test observes command dispatch, then removes only
|
||||
its fixture session-registration entry before returning the start handle.
|
||||
The 502 remains a post-start `command_registration` failure with a neutral
|
||||
safe log message; no process is launched and no retry is performed.
|
||||
|
||||
These are hermetic Rust/API/protocol fixtures, not a full product-process E2E,
|
||||
not a deployed binary test, and not reproduction of the user's asset/request.
|
||||
Validation commands and concrete counts are recorded on the implementation MR.
|
||||
@@ -1,91 +0,0 @@
|
||||
# Workdir removal and ordinary retry
|
||||
|
||||
Use the Runtime **Workdirs** page or the normal typed Workdir removal tool to
|
||||
remove an unused repository Workdir. Deletion is authorized by the Backend's
|
||||
existing durable removal operation; it is not a registry-only hide operation.
|
||||
|
||||
A failed attempt does not require a new recovery state or a manual edit of
|
||||
`materialization.json`. Retry the same removal request after resolving the
|
||||
reported cause. Each attempt rechecks current attachments, attachment
|
||||
reservations, retention holds, materialization identity and current file changes.
|
||||
A previous clean observation is not permission to discard new changes. A dirty
|
||||
Workdir must be preserved or its changes resolved by its owner before deletion.
|
||||
When the same repository checkout is still usable, an authorized Worker may
|
||||
reattach it through normal `WorkdirAttach` to preserve or resolve changes, then
|
||||
detach and retry removal. An executing removal fences new attachments; a failed
|
||||
attempt does not permanently prevent reconnection. Missing or replaced checkout
|
||||
identity is not made usable by this permission.
|
||||
|
||||
## Interpreting a retained Workdir
|
||||
|
||||
- **Mount remains:** Git status being clean does not mean mounted resources are
|
||||
released. Confirm the mount's owner, users and data destination. Have that
|
||||
owner safely release the resource, then retry normal deletion. Yoi does not
|
||||
recursively delete mounted data or implicitly unmount external/unknown
|
||||
resources. Do not use force-clean, recursive chmod or raw recursive deletion
|
||||
to override this boundary.
|
||||
- **Permission denied / resource busy / storage unavailable:** have the owning
|
||||
operator resolve access, release the resource or restore storage. Retry the
|
||||
same normal action; registry retention is intentional until cleanup is proven.
|
||||
- **Changes present / changes cannot be verified:** preserve current files and
|
||||
restore repository inspection where possible. Do not treat a previous cleanup
|
||||
failure or missing checkout metadata as proof that all remaining files are
|
||||
expendable.
|
||||
- **Identity or ownership unavailable:** verify the Workdir target with its
|
||||
owner. Do not rewrite registry or materialization identity to bypass checks.
|
||||
- **Runtime unavailable / outcome unknown:** reconnect the Runtime and retry.
|
||||
Absence of a provider response is not authoritative not-found evidence.
|
||||
- **Attachment, reservation or hold:** release the current owning authority
|
||||
through its normal API before retrying. A stale failure does not freeze these
|
||||
conditions; newly attached Workers remain protected.
|
||||
|
||||
The Web page refreshes authoritative inventory after each attempt. Successful
|
||||
removal commits registry deletion only after Runtime confirms physical deletion
|
||||
or authoritative not-found. An unknown result leaves the registry in place so
|
||||
retry can reconcile the provider's current state.
|
||||
|
||||
A retained partial-removal Workdir is listed as `cleanup_pending` with cleanliness
|
||||
`unknown`. List/detail observation checks its saved identity but does not read or
|
||||
hash surviving checkout content. The saved removal witness is not a current
|
||||
cleanliness assertion; only an actual removal retry runs the full content checks.
|
||||
|
||||
## Runtime safety boundary
|
||||
|
||||
The Runtime keeps removal content/identity evidence outside the tree being
|
||||
removed. This is a factual witness for partial deletion, not a new recovery
|
||||
state or cleanup stage. Surviving files are rechecked against it; an intact,
|
||||
currently clean checkout can renew obsolete evidence without discarding newly
|
||||
ignored/untracked content. Do not edit this witness manually.
|
||||
|
||||
Mount-safe removal currently requires Linux `openat2` and `/proc` inspection.
|
||||
Unavailable inspection fails closed with `mount_check_unavailable`; there is no
|
||||
fallback to recursive deletion across an unchecked filesystem boundary.
|
||||
Runtime HTTP list, detail and removal requests run synchronous provider work on
|
||||
blocking workers rather than the HTTP executor. Removal reserves only the target
|
||||
Workdir under the Runtime state mutex, then releases that mutex before content
|
||||
inspection and unlink. Unrelated Worker list/detail, session observation and
|
||||
subscription snapshots remain available while removal runs. Conflicting use,
|
||||
attachment, creation or removal of the same Workdir is rejected promptly rather
|
||||
than waiting under the global mutex.
|
||||
|
||||
Provider leases protect in-flight creation/access and live session resources,
|
||||
including their command tasks. Cloned sessions retain protection until safe close
|
||||
or final resource release; passive bindings alone do not prevent removal. Removed
|
||||
bindings cannot activate against a recreated Workdir with the same ID. Creation
|
||||
owns a fresh root exclusively so failed creation cannot roll back another
|
||||
creator's checkout or adopt an existing partial-removal directory. Reservations
|
||||
are transient and released on provider success, error or unwind; they do not
|
||||
replace the durable cleanup witness or attachment authority.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
Public responses expose a bounded cause category and safe guidance, not arbitrary
|
||||
provider errors or host paths. For an OS failure, correlate Backend removal logs
|
||||
(`operation_id`, `workdir_id`, Runtime) with Runtime cleanup logs for that Workdir
|
||||
and cleanup action. Runtime logs retain the concrete OS error/errno for the
|
||||
owning operator. These private diagnostics must not be copied unredacted into
|
||||
public responses or Tickets.
|
||||
|
||||
This procedure is not authorization to manipulate a live mount or another
|
||||
Worker's storage. If ownership or required operator authority is unclear, stop
|
||||
and request that specific owner/operator's decision.
|
||||
@@ -1,171 +0,0 @@
|
||||
# Worker Drive tools and WIP
|
||||
|
||||
Drive is Workspace-owned DB hierarchy and local blob storage exposed through the
|
||||
authorized Server API. It is **not** a Workdir, mount, Git checkout, or a Session
|
||||
attachment store. Worker code never accesses the Server's physical blob root.
|
||||
|
||||
## Enablement and authority
|
||||
|
||||
Set `feature.drive = { enabled = true; };` in a DCDL Profile (or
|
||||
`[feature.drive] enabled = true` in TOML). Only activation is accepted; Profile
|
||||
fields cannot specify Workspace, grant, actor, access, or host paths. Default,
|
||||
Coder, Companion, Intake, Orchestrator and Ticket Worker builtin Profiles select
|
||||
the feature. Standalone, Reviewer and Job defaults do not. Host must inject an
|
||||
available Workspace-bound `WorkspaceClient`; standalone activation is inert.
|
||||
|
||||
An authorized Workspace owner must independently grant the **registered Runtime
|
||||
Worker** whole-Drive `read_only` or `read_write` access through the T-722 grant API.
|
||||
Every read, metadata resolution, mutation, chunk and receipt query passes current
|
||||
Backend authorization. An enabled feature, Subject, Ticket assignment, path, ID,
|
||||
or observed validator is not permission. A read-only grant is never upgraded.
|
||||
The current API has no read-only capability descriptor endpoint: an authorized
|
||||
WIP inspect describes the operations for the entry kind, while Backend rejects
|
||||
writes without a write grant. Tool permission configuration also applies to
|
||||
native Invoke, including fail-closed `ask` where approval cannot be obtained.
|
||||
|
||||
Internal SubWorkers (including Reviewer children) have no separate Backend Drive
|
||||
identity/grant contract today. Spawn suppresses Drive activation **after every
|
||||
selector resolution**, including `inherit` and explicit builtin Profiles, and
|
||||
wraps the Workspace client to deny Drive JSON/binary operations. They cannot use
|
||||
the parent's shared client to inherit Drive read or write. Perform such work in
|
||||
the authorized parent, or a separately registered Runtime Worker with its own
|
||||
current Backend grant. Any future internal delegation must introduce an explicit
|
||||
parent-bounded scope and Backend reauthorization, not remove this fence based on
|
||||
Profile activation. The wrapper preserves non-Drive Workspace operations and
|
||||
Reviewer attestation.
|
||||
|
||||
## Normal Tools
|
||||
|
||||
`DriveRoot {}` discovers the authorized root and returns `{path, metadata}`.
|
||||
Public entry inputs are **Workspace-bound** references:
|
||||
|
||||
```json
|
||||
{"workspace_id":"workspace-id","node_id":"9007199254740993"}
|
||||
```
|
||||
|
||||
Node IDs remain canonical positive decimal **strings**, including
|
||||
values above JavaScript's safe-integer range. Renaming/moving preserves IDs and
|
||||
authenticated latest URLs. Deleting/recreating the same name allocates a new ID.
|
||||
|
||||
| Tool | Input/use |
|
||||
| --- | --- |
|
||||
| `DriveMetadata` | `entry`: one live entry and its last committed request ID |
|
||||
| `DriveList` | optional `parent` (default root), `limit` 1–200, opaque `after` |
|
||||
| `DriveSearch` | `query`, optional `include_text`, `limit` 1–128, opaque `after` |
|
||||
| `DriveRead` | `entry`, optional `max_bytes` 1–65,536; reports `truncated` |
|
||||
| `DriveCreateFolder` | `parent`, logical `name` |
|
||||
| `DriveCreateText` | `parent`, `name`, `content` <=64 KiB, optional `content_type` (Markdown by default) |
|
||||
| `DriveWrite` | observed `entry`, complete replacement `content` <=64 KiB |
|
||||
| `DriveEdit` | observed `entry`, `old_string`, `new_string`, optional `replace_all` |
|
||||
| `DriveRelocate` | observed `entry`, destination `parent`, new/current `name` |
|
||||
| `DriveDelete` | observed `entry`; folder deletion follows Backend rules |
|
||||
| `DriveViewImage` | `entry`; bounded committed-request-bound image bytes, not a URL |
|
||||
| `DriveSaveWorkdir` | explicit `target_workdir` alias, relative `path`, destination `parent`, `name`, optional `content_type` (octet-stream by default) |
|
||||
| `DriveRequestStatus` | `request_id` from an uncertain mutation |
|
||||
|
||||
List/search are paged discovery only, not implicit mutation observations. Omitting
|
||||
`limit` uses the Backend maximum (200 for list, 128 for search) in both Tools and
|
||||
native WIP; use the returned `next_after` to continue. Read or
|
||||
metadata-inspect an entry before replacing, editing, moving or deleting it. The
|
||||
Tools adapter retains at most 256 observations, scoped to its injected Workspace.
|
||||
Eviction requires a new explicit observation; it never substitutes another entry.
|
||||
A successful mutation removes the prior Tools observation. This is a bounded
|
||||
precondition cache, not a grant cache, second hierarchy DB, or durable history of
|
||||
all Drive versions. Restored workers observe again before mutating.
|
||||
|
||||
`DriveEdit` uses the same pure `fs_operation::text` rules as Workdir and Workspace
|
||||
config: nonempty `old_string`, different replacement, exactly one nonoverlapping
|
||||
match unless `replace_all=true`, and bounded resulting text. It fetches a full
|
||||
<=64 KiB preimage and verifies the **previously observed** committed request ID. It cannot edit
|
||||
truncated content. The Backend's atomic CAS still decides the first winner.
|
||||
|
||||
## Native WIP 0.2.0
|
||||
|
||||
Start with `Inspect {"path":"/drive"}`. `/drive` offers root/metadata, list/search,
|
||||
creation, Workdir save and receipt query. Invoke selects the structured Interface
|
||||
reference shown by Inspect:
|
||||
|
||||
```json
|
||||
{
|
||||
"path":"/drive",
|
||||
"interface":{"scope":"/drive","name":"yoi.drive/entry/v1"},
|
||||
"operation":"list",
|
||||
"arguments":{"limit":20}
|
||||
}
|
||||
```
|
||||
|
||||
Direct entry paths returned by discovery are:
|
||||
|
||||
```text
|
||||
/drive/<hex-encoded UTF-8 Workspace ID>/<canonical decimal node ID>
|
||||
```
|
||||
|
||||
Use the returned path rather than hand-constructing it. A file exposes metadata,
|
||||
read, write/edit, relocate/delete and view_image; a folder exposes metadata,
|
||||
list, create_folder/create_text, save_workdir and relocate/delete where valid.
|
||||
A native target binds its entry or creation parent: no ID or mutation-precondition argument is
|
||||
needed. Relocate's `parent` is a WIP typed entry path. Each entry is self-scoped;
|
||||
Host captures target, scope object, descriptor, validator and handler together.
|
||||
Client retains and supplies metadata automatically. Do not copy validators or
|
||||
`scope_ref` through model arguments.
|
||||
|
||||
Drive has **no indexable entry children**, regardless of the size of the Drive or
|
||||
the Client's already-inspected entries. Tree shows the Drive entrance, not a file
|
||||
inventory. List/search return bounded metadata and paths; only inspect the
|
||||
selected path. No Worker index/embedding or eager body injection is added.
|
||||
|
||||
## Conflicts, unknown outcomes and publication
|
||||
|
||||
Mutations use a Host-generated request identity, observed node ID and last
|
||||
committed request ID. Conflict never causes an adapter to replace that observation with
|
||||
latest and retry. Observe explicitly, reconsider the edit and submit a deliberate
|
||||
new operation. Native Client stale handling likewise does not replay dispatch.
|
||||
|
||||
Timeout, lost response, oversized/invalid mutation response or invalid completion
|
||||
may mean **OutcomeUnknown**, including a completion with a mismatched request ID
|
||||
or invalid entry metadata. The adapter queries the *same* request receipt once;
|
||||
only a committed receipt with matching IDs and valid metadata returns confirmed
|
||||
DB success. Otherwise it preserves unknown and the `request_id`. Query
|
||||
`DriveRequestStatus`; an `uncommitted` snapshot is not
|
||||
proof that an in-flight request cannot later commit. Do not retry blindly with a
|
||||
fresh request ID. Backend grants also protect receipt lookup/replay. Safe typed
|
||||
Denied/NotFound/Conflict/Invalid/Limit/StorageUnavailable/OutcomeUnknown errors are
|
||||
classified without reflecting provider paths, SQL, transport URLs or secrets.
|
||||
|
||||
Saving a Workdir artifact reads through the selected alias's scoped session
|
||||
`read_bytes`, checks path/offset/size/hash on every bounded chunk and verifies the
|
||||
assembled SHA256. Source read and destination write are independent. Unknown
|
||||
aliases, outside-scope paths, traversal/absolute host paths, changed sources,
|
||||
source read denial, oversized bodies and destination denial cannot publish a
|
||||
success. Upload transfers raw bytes internally with size/SHA256 metadata; the
|
||||
model never has to use Bash, a raw host path, or generate base64. Success is the
|
||||
Backend's **DB-publication commit**, not merely blob transfer completion.
|
||||
|
||||
## Images and historical observations
|
||||
|
||||
ViewImage reads <=10 MiB PNG/JPEG/GIF/WebP bytes in <=64 KiB Drive chunks at the
|
||||
metadata’s last committed request ID. Every chunk is authorized and committed-request-bound. An intervening
|
||||
update expires the read; no mixed-generation image is attached. Media is detected
|
||||
from bytes using the existing image helper, not the filename or a download URL.
|
||||
|
||||
Tools and native Invoke return `ImageAttachment` through `ToolOutput`. Native
|
||||
attachments accompany—not replace or bypass validation of—the WIP return value.
|
||||
Engine history, durable Session capture/log replay and normal ToolResult pruning
|
||||
own those bytes. Later Drive updates/deletion/revocation do not rewrite the text
|
||||
or image already recorded in a Session; restore reproduces that observation until
|
||||
normal pruning/correction removes details append-only. This is not full Drive
|
||||
version history and does not use unrecorded per-request message insertion.
|
||||
|
||||
## Validation seams
|
||||
|
||||
- `cargo test -p worker --lib feature::builtin::drive`
|
||||
- `cargo test -p worker --lib native_invoke`
|
||||
- `cargo test -p worker --lib native_attachments_do_not_bypass`
|
||||
- `cargo test -p worker-runtime --lib binary`
|
||||
- `cargo test -p yoi-workspace-server --lib worker_drive_tests`
|
||||
|
||||
Server adapter tests use isolated DB + LocalFileSystem + production typed HTTP
|
||||
router and fresh Runtime proofs/current Worker grants. Worker tests cover
|
||||
bounded observations, exact CAS inputs, no blind resend, Tools/WIP schemas,
|
||||
conditional registration, child denial and native image capture/replay. See
|
||||
`AGENTS.md` for root check, changed-crate tests, format and diff requirements.
|
||||
@@ -1,34 +0,0 @@
|
||||
# Workspace request admission and deletion
|
||||
|
||||
Workspace HTTP dispatch uses `WorkspaceAdmission` for **lifecycle admission**, not a Workspace-wide operation mutex. Each admitted handler owns a drop-safe lease. Independent requests (including read-oriented POSTs) may execute concurrently; proof validation occurs before any business/resource wait in the inner router.
|
||||
|
||||
## Ordering and authority
|
||||
|
||||
1. Acquire an immediate lifecycle lease. While deletion drains, new ordinary requests fail closed instead of queueing behind the deletion operation.
|
||||
2. Validate the bounded request body, Runtime signature, audience/Workspace/permission/method/target/body binding, expiry, transactional JTI consumption, current Runtime trust, Worker membership/reservation and singleton ownership. Browser actor/origin checks are unchanged. No TTL extension or 401 retry policy is introduced.
|
||||
3. Check the persisted Workspace state. Scoped requests, including GETs, reject a deleting Workspace, including after Server reconstruction. An absent Workspace remains 404. Server-owned deletion status/polling is outside scoped dispatch.
|
||||
4. Execute the inner router. Resource owners retain their existing exclusion: per-Worker session/control/removal locks; transactional Workdir attachment exclusivity and removal fences; SQLite domain transactions and content/identity precondition checks; the resource broker's one-shot handle consumption. Legacy memory document read/replace/write uses a short, resource-specific mutex because it spans multiple store calls.
|
||||
5. Drop the lifecycle lease on response, error, or cancellation. An admitted long operation is not rechecked for proof expiry merely because its business execution lasts past the proof TTL. This does not grant subsequent requests or disable provider/attachment authority checks.
|
||||
|
||||
The proof verifier's clock is injected into the Server instance for deterministic route tests, not obtained from test environment variables. Production uses the same wall-clock seconds and existing proof rules. Clock-skew policy remains a separate scope (T-728).
|
||||
|
||||
## Deletion and callbacks
|
||||
|
||||
Deletion start serializes only with other deletion starts. Before awaiting active requests it closes ordinary admission, so ongoing arrivals cannot starve the drain. The fence itself is RAII: a cancelled wait or a rejected preflight reopens admission. Successful reservation persists the deleting state before cleanup is scheduled; the persisted state remains the fence after the temporary drain lease is released.
|
||||
|
||||
While at least one ordinary handler remains admitted, these exact callback paths may obtain their own tracked leases:
|
||||
|
||||
- POST `/api/w/{workspace_id}/subjektiv/sessions` (create/restore session attribution)
|
||||
- POST `/api/runtime/v1/workspaces/{workspace_id}/resources/fetch` (bound Runtime resource handles)
|
||||
- GET `/api/w/{workspace_id}/runtime-config`
|
||||
- GET `/api/w/{workspace_id}/worker-discovery/workers`
|
||||
|
||||
They do not bypass authentication or authorization. Callback admission closes atomically when the last ordinary lease leaves; deletion then waits for already-admitted callbacks too. A callback stream cannot keep adding work once all parents have finished. There is no deletion write-lock queue in front of callbacks required by a parent.
|
||||
|
||||
## Evidence boundary
|
||||
|
||||
`server/tests/workspace_admission_tests.rs` exercises the real outer dispatcher and generated inner routes with isolated SQLite stores, authenticated Runtime proofs and controlled external-provider fixtures. The long-operation fixture stops A at its provider response and proves B's ShowTicket/QueryTicket POSTs and separate Workdir read finish before releasing A. It also covers same-session exclusion, conflicting Workdir attachments, memory edit and JTI atomicity, drain/fence/cancellation, real create/restore with session callbacks overlapping deletion start, resource/config/discovery callbacks, injected-clock proof rejections and provider failures/disconnect/timeout recovery.
|
||||
|
||||
Timeout uses Tokio's test clock; proof tests advance an independent injected clock by 120 seconds. Bounds detect hangs, not guessed sleeps. Dispatch task cancellation represents cancellation of the HTTP handler future; provider disconnect is a dropped transport response. These are in-process boundary tests, not a real-socket disconnect or live browser/Runtime E2E test. Background command and websocket lifetimes retain their existing domain-specific cleanup; request admission covers the handler until its response, not the entire upgraded connection.
|
||||
|
||||
Past production `401 expired` incidents have **not** been correlated with live binary-source/request traces. The structural failure path and deterministic regression evidence do not establish that all prior 401s had this cause. T-731 and its retained session/MR/source/Workdir are unrelated and untouched. WIP remains 0.2.0; no generated DTO/schema/route artifacts change.
|
||||
@@ -1,59 +1,9 @@
|
||||
# Workspace schema migrations
|
||||
# 保存schema変更の開発規則
|
||||
|
||||
Workspace schema authority belongs to `crates/workspace-server/src/store.rs`. The canonical
|
||||
schema, ordered migrations, migration-history validation, startup upgrade path, and explicit
|
||||
`yoi-server migrate` command must remain one contract.
|
||||
公開またはドッグフーディングで使用したmigrationは、対応する保存データを更新できる限り保持する。新しいmigrationを追加したことだけを理由に、直前のmigrationを削除しない。
|
||||
|
||||
## Retained migration chain
|
||||
schema変更では、既存の順序付きmigrationへ新しい処理を追加し、新規DBの作成結果も同じschemaへ揃える。適用済みmigrationの名前や内容を、語彙を統一するだけの目的で書き換えない。schema一覧はコードを正本とし、文書へ複製しない。
|
||||
|
||||
Released or dogfooded schema migrations are retained and composed in version order. Adding a new
|
||||
schema version does not authorize deleting the preceding migration. A migration may be removed
|
||||
only as an explicit baseline-retirement operation after the supported installations that depend on
|
||||
it have been migrated or intentionally discarded.
|
||||
検証には、最古の対応schemaからの更新、保存値と制約の維持、新規作成結果との一致、失敗時のrollbackを含める。途中まで完了した更新の再開や、元DBを変更しないdry-runを提供する場合は、その保証も実装経路で確認する。schema番号だけのassertionでは不足する。
|
||||
|
||||
The current retained Workspace chain is:
|
||||
|
||||
1. schema 50: `workspace schema baseline`
|
||||
2. schema 51: `workspace runtime bindings`
|
||||
3. schema 52: Runtime-binding counter and audit (historical migration purpose; the persisted migration name remains frozen).
|
||||
4. schema 53: `durable Workspace deletion operations`
|
||||
|
||||
A database may begin at any retained baseline. Its following history rows must be the exact prefix
|
||||
of the ordered migration chain from that baseline. This allows both a freshly created current
|
||||
database and a database upgraded across several releases while rejecting edited, reordered, or
|
||||
unknown histories.
|
||||
|
||||
## Runtime behavior
|
||||
|
||||
`SqliteWorkspaceStore::open` computes all pending retained migrations and applies them in order.
|
||||
Each migration is transactional and restartable: if a later step fails, completed steps remain a
|
||||
valid canonical prefix and the next run resumes from that version.
|
||||
|
||||
`yoi-server migrate` invokes the same store migration path without starting the Server:
|
||||
|
||||
```sh
|
||||
# Copy the DB into memory and validate the complete pending path without changing the source.
|
||||
yoi-server migrate --dry-run
|
||||
|
||||
# Preflight the complete path, then apply it to the source DB.
|
||||
yoi-server migrate
|
||||
```
|
||||
|
||||
Use `--database <PATH>` for a non-default Server DB. Stop `yoi-server` before applying migrations
|
||||
and make an external backup before an operational upgrade.
|
||||
|
||||
## Development workflow
|
||||
|
||||
When changing the Workspace schema:
|
||||
|
||||
1. increment `LATEST_SCHEMA_VERSION`;
|
||||
2. append one `Migration` entry with the new version, stable name, and apply function;
|
||||
3. preserve all migrations at or above `OLDEST_SCHEMA_VERSION`;
|
||||
4. update the canonical latest-schema creator for fresh databases;
|
||||
5. add a fixture at the oldest retained version and prove migration through every retained step;
|
||||
6. prove that `--dry-run` leaves the source DB unchanged;
|
||||
7. keep DDL validation and cross-schema foreign-key checks in the shared store preparation path.
|
||||
|
||||
A deliberate baseline retirement must be a separately reviewed change. It must identify the oldest
|
||||
remaining version, provide an operational migration/discard plan for older databases, update tests
|
||||
and this document, and must not be inferred merely because a new migration was added.
|
||||
古いbaselineの対応終了は、対象となる保存データを移行または明示的に破棄する判断を含め、独立にレビューする。残す最古のschema、移行・破棄の責任、影響するテストを明記する。所有storeのmigration経路と検証を共用し、起動・管理操作ごとに別のDDL判定を増やさない。
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
# ============================================================================
|
||||
# Worker Manifest リファレンス
|
||||
# ============================================================================
|
||||
# Worker の宣言的設定 (`WorkerManifest` / `WorkerManifestConfig`)。
|
||||
#
|
||||
# このファイル形式は低レベル runtime manifest。通常起動は profile discovery/default
|
||||
# (`profiles.toml` と bundled builtin profile) から manifest を生成する。
|
||||
# `yoi worker --manifest <path>` の one-file compatibility/debug mode では、
|
||||
# 指定した TOML 1 枚に builtin defaults を merge し、required validation を行う。
|
||||
# user/project `manifest.toml` を暗黙に merge する通常起動 cascade は使わない。
|
||||
#
|
||||
# `WorkerManifestConfig` の merge 規則: 上の層が同名フィールドを上書き、scope rule と
|
||||
# skills.directories は累積マージ、tool_output.per_tool は key 単位でマージ。
|
||||
#
|
||||
# パス解決: `--manifest <path>` では相対パスはその manifest ファイルの親ディレクトリ
|
||||
# を base に絶対パスへ解決される。profile artifact でも同じ validation 境界を通る。
|
||||
#
|
||||
# 凡例:
|
||||
# - 必須 … 値が無いと resolve エラー
|
||||
# - 任意 … 省略可
|
||||
# - デフォルト … 省略時に採用される値 (None なら "なし")
|
||||
# - 値 … enum 等で取り得る値
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
# ===== [worker] ================================================================
|
||||
# Worker メタデータ。
|
||||
[worker]
|
||||
|
||||
# 必須。Worker の表示名 (ResolveError::MissingField("worker.name") の対象)。
|
||||
name = "example-agent"
|
||||
|
||||
|
||||
# ===== [model] ==============================================================
|
||||
# LLM モデル設定。次の 3 形態を受ける:
|
||||
# (a) `ref` 単独 — カタログから全部解決
|
||||
# (b) `ref` + 一部 override — auth など個別差し替え
|
||||
# (c) `scheme` + `model_id` 直書き — カタログを使わない inline 指定
|
||||
# (b) / (c) では `ref` 未指定なら `scheme` / `model_id` / `auth` が必須。
|
||||
# (実際の必須判定は manifest::model_catalog の resolve 側で行う)
|
||||
[model]
|
||||
|
||||
# 任意。形式: "<provider_id>/<model_id_in_ref>"。
|
||||
# 最初の `/` だけで split されるので、`openrouter/anthropic/claude-sonnet-4`
|
||||
# のように内部 model_id に `/` が含まれる場合もそのまま書ける。
|
||||
ref = "anthropic/claude-sonnet-4-6"
|
||||
|
||||
# 任意 (ただし ref 未指定時は実質必須)。デフォルト: なし。
|
||||
# 値: "anthropic" | "openai_chat" | "openai_responses" | "gemini"
|
||||
# scheme = "anthropic"
|
||||
|
||||
# 任意 (ref 未指定時は実質必須)。デフォルト: なし。
|
||||
# プロバイダが受け付けるモデル ID 文字列。
|
||||
# model_id = "claude-sonnet-4-20250514"
|
||||
|
||||
# 任意。デフォルト: scheme ごとの組み込み既定 URL。
|
||||
# base_url = "https://api.anthropic.com"
|
||||
|
||||
# 任意 (ref 未指定時は実質必須)。デフォルト: なし。
|
||||
# kind の値: "none" | "secret_ref" | "api_key" | "codex_oauth"
|
||||
# - "none" … 認証不要 (ローカル Ollama 等)
|
||||
# - "secret_ref" … `yoi keys` の local secret store から key を読む。
|
||||
# `ref` はユーザー設定が明示的に選ぶ論理 secret id。
|
||||
# store は id -> value のみを持ち、provider 種別を解釈しない。
|
||||
# - "api_key" … 明示ファイルから key を読む低レベル形式。通常は
|
||||
# `secret_ref` を使う。file 指定時、相対パスは manifest base 起点で解決。
|
||||
# - "codex_oauth" … ChatGPT OAuth (`~/.codex/auth.json`)。追加フィールドなし。
|
||||
# auth = { kind = "none" }
|
||||
# auth = { kind = "secret_ref", ref = "providers/anthropic/default" }
|
||||
# auth = { kind = "api_key", file = "./sk-ant.local" }
|
||||
# auth = { kind = "codex_oauth" }
|
||||
|
||||
# 任意。デフォルト: モデルカタログ → provider.default_capability → scheme 既定
|
||||
# の順で解決される。明示 override したいときだけ書く。
|
||||
# [model.capability]
|
||||
# # 値: "none" | "sequential" | "parallel"
|
||||
# tool_calling = "parallel"
|
||||
# # 値: "none" | "json_object" | "json_schema"
|
||||
# structured_output = "json_schema"
|
||||
# # 任意。値: 省略 (= None) | "effort" | "budget_tokens" | "both"
|
||||
# reasoning = "both"
|
||||
# # 任意。デフォルト: false
|
||||
# vision = false
|
||||
# # 値: { kind = "explicit", max_breakpoints = <u8> } | { kind = "auto" }
|
||||
# prompt_caching = { kind = "explicit", max_breakpoints = 4 }
|
||||
|
||||
|
||||
# ===== [engine] =============================================================
|
||||
# ワーカーの生成パラメータ等。セクション自体省略可 (全フィールド任意)。
|
||||
[engine]
|
||||
|
||||
# 任意。デフォルト: "default" (`defaults::DEFAULT_INSTRUCTION`)。
|
||||
# effective Prompt catalog の exact dotted name を選択する。
|
||||
# instruction = "default"
|
||||
|
||||
# 任意。デフォルト: なし (プロバイダ任せ)。
|
||||
# 1 レスポンスあたりの出力 token 上限。
|
||||
# max_tokens = 4096
|
||||
|
||||
# 任意。デフォルト: なし (無制限)。
|
||||
# 1 セッションの最大ターン数。NonZeroU32 — 0 は parse エラー。
|
||||
# max_turns = 50
|
||||
|
||||
# 任意。デフォルト: なし (プロバイダ既定)。
|
||||
# temperature = 0.3
|
||||
|
||||
# 任意。デフォルト: なし (プロバイダ既定)。
|
||||
# top_p = 0.9
|
||||
|
||||
# 任意。デフォルト: なし (プロバイダ既定)。
|
||||
# top_k = 40
|
||||
|
||||
# 任意。デフォルト: 空配列。
|
||||
# stop_sequences = ["\n\n", "</stop>"]
|
||||
|
||||
# 任意。デフォルト: なし。
|
||||
# 値:
|
||||
# - 文字列 effort: "minimal" | "low" | "medium" | "high" | "xhigh"
|
||||
# あるいは provider-native な任意ラベル文字列
|
||||
# - 整数: Anthropic 系 thinking.budget_tokens (-1 で dynamic)
|
||||
# reasoning = "medium"
|
||||
# reasoning = -1
|
||||
|
||||
# 任意。デフォルト: "on_tool_call_complete"。
|
||||
# provider が個別 ToolCall の完成を保証する場合、そのブロック完成後に実行開始を
|
||||
# 前倒しする。安全な完成境界がない provider、または応答全体を見て実行前 Pause を
|
||||
# 判断する turn-end hook がある場合は、応答完了まで警告付きで明示的に遅延する。
|
||||
# これは直列実行モードではなく、先行 call の完了を待たず後続 call も開始するため、
|
||||
# 完了順・DB 反映順・採番順は保証しない。早期開始後に stream が切断された場合は
|
||||
# 自動 continuation せず、安全に terminalize して停止する。
|
||||
# 従来どおり応答全体の完了後に実行を開始するには "after_response" を明示する。
|
||||
# tool_call_dispatch = "after_response"
|
||||
|
||||
# 任意。tool 実行 content の byte 長キャップ。
|
||||
# セクション省略時は default_max_bytes = 64 * 1024、per_tool 空。
|
||||
# [worker.tool_output]
|
||||
# # 任意。デフォルト: 65536 (`defaults::TOOL_OUTPUT_MAX_BYTES` = 64 KiB)。
|
||||
# default_max_bytes = 65536
|
||||
#
|
||||
# # 任意。デフォルト: 空マップ。tool 名キーで個別キャップ上書き。
|
||||
# # キーは tool の登録名 ("Read", "Grep", "Glob", ...)。
|
||||
# [worker.tool_output.per_tool]
|
||||
# Read = 131072
|
||||
# Grep = 8192
|
||||
|
||||
# 任意。submit 時の FileRef (`@<path>`) upload / attachment byte 長キャップ。
|
||||
# Tool Output の truncation とは独立している。
|
||||
# [worker.file_upload]
|
||||
# # 任意。デフォルト: 262144 (`defaults::FILE_UPLOAD_MAX_BYTES` = 256 KiB)。
|
||||
# max_bytes = 262144
|
||||
|
||||
|
||||
# ===== [scope] ==============================================================
|
||||
# Worker がアクセスできるディレクトリ/ファイル範囲。
|
||||
# - allow: 最低 1 件必要 (空だと ResolveError / ScopeError::EmptyAllow)。
|
||||
# 複数 allow がマッチした場合は最大の permission が採用される。
|
||||
# - deny : 任意。マッチした deny の最小 permission *未満* に effective を
|
||||
# 押し下げる (deny.read で完全遮断、deny.write で Read 止まり)。
|
||||
# `target` は最終的に絶対パスでなければならない。manifest 内では相対 OK
|
||||
# (manifest base 起点で resolve)。
|
||||
|
||||
# 必須: 最低 1 件の allow ルール。
|
||||
[[scope.allow]]
|
||||
# 必須。manifest 内では相対 OK (base 起点で絶対化)。
|
||||
target = "./"
|
||||
# 必須。値: "read" | "write"
|
||||
permission = "write"
|
||||
# 任意。デフォルト: true (再帰的にマッチ)。
|
||||
# false の場合、ルール自身および直下の child のみマッチ。
|
||||
# recursive = true
|
||||
|
||||
# allow は何件でも書ける。
|
||||
# [[scope.allow]]
|
||||
# target = "/abs/docs"
|
||||
# permission = "read"
|
||||
# recursive = false
|
||||
|
||||
# 任意。アクセスを *ルール内 permission 未満* に押し下げる。
|
||||
# [[scope.deny]]
|
||||
# target = "./secrets"
|
||||
# permission = "write" # write を禁止 → 該当パスは Read までに降格
|
||||
# recursive = true
|
||||
#
|
||||
# [[scope.deny]]
|
||||
# target = "./secrets/key"
|
||||
# permission = "read" # read 自体を禁止 → アクセス完全遮断
|
||||
# recursive = true
|
||||
|
||||
|
||||
# ===== [compaction] =========================================================
|
||||
# コンテキスト圧縮 (Prune / Compact)。セクション省略で両方無効。
|
||||
# `prune_enabled`でPruneを制御する。Compactは閾値がNoneなら無効。
|
||||
# [compaction]
|
||||
#
|
||||
# # 任意。デフォルト: false。Compactの設定を保ったままPruneだけ切替できる。
|
||||
# prune_enabled = true
|
||||
#
|
||||
# # 任意。デフォルト: 8000 (`defaults::PRUNE_PROTECTED_TOKENS`)。
|
||||
# # pruning から保護する末尾 token budget。turn 数ではなく usage estimate で境界を引く。
|
||||
# prune_protected_tokens = 8000
|
||||
#
|
||||
# # 任意。デフォルト: 4096 (`defaults::PRUNE_MIN_SAVINGS`)。
|
||||
# # prune が発火するための最低節約 token 推定値。
|
||||
# prune_min_savings = 4096
|
||||
#
|
||||
# # 任意。デフォルト: なし (proactive compact 無効)。
|
||||
# # ターン間チェック (Controller post-run)。占有 token > これ で次ターン前に compact。
|
||||
# compact_threshold = 80000
|
||||
#
|
||||
# # 任意。デフォルト: なし (safety-net compact 無効)。
|
||||
# # ターン中チェック (WorkerInterceptor::pre_llm_request)。期待される関係:
|
||||
# # compact_threshold < compact_request_threshold (proactive を先に発火)。
|
||||
# # 逆順設定は許容するが warn ログを出す。
|
||||
# compact_request_threshold = 90000
|
||||
#
|
||||
# # 任意。デフォルト: 8000 (`defaults::COMPACT_RETAINED_TOKENS`)。
|
||||
# # compaction 後の history 末尾に verbatim で残す token budget。
|
||||
# compact_retained_tokens = 8000
|
||||
#
|
||||
# # 任意。デフォルト: 8000 (`defaults::COMPACT_AUTO_READ_BUDGET`)。
|
||||
# # compact worker が `mark_read_required` で取り込める累計 token。
|
||||
# compact_auto_read_budget = 8000
|
||||
#
|
||||
# # 任意。デフォルト: 50000 (`defaults::COMPACT_WORKER_MAX_INPUT_TOKENS`)。
|
||||
# # compact worker 自身の現在占有 token cap。超過で abort (circuit breaker)。
|
||||
# compact_worker_max_input_tokens = 50000
|
||||
#
|
||||
# # 任意。デフォルト: 20 (`defaults::COMPACT_WORKER_MAX_TURNS`)。
|
||||
# # compact worker 自身の tool loop 上限。Rust config で None の場合のみ無制限。
|
||||
# compact_worker_max_turns = 20
|
||||
#
|
||||
# # 任意。デフォルト: メインモデルを `clone_boxed()` で複製。
|
||||
# # compact 専用モデルを使う場合のみ書く ([model] と同じ形式)。
|
||||
# # [compaction.model]
|
||||
# # ref = "anthropic/claude-haiku-4-5"
|
||||
|
||||
|
||||
# ===== [feature.memory] (legacy/deprecated) ================================
|
||||
# Legacy single-document Workspace Memory compatibility設定。新規product pathは
|
||||
# `[feature.subjektiv]` を使う。旧`MemoryQuery` / `MemoryReadDocument` /
|
||||
# `MemoryUpdateDocument` / `MemoryStaging*` toolsと`/memory` APIのinput意味は
|
||||
# 変更せず、subject Memoryへ暗黙転送しない。
|
||||
#
|
||||
# resolved Worker ManifestではProfile由来の設定を`profile`に、Backend由来の
|
||||
# Workspace設定snapshotを`workspace_settings`に分離して保存する。source TOMLの
|
||||
# `[feature.memory]` fieldsはresolve後に`profile`へ入り、`workspace_settings`は
|
||||
# Backendだけがbindする信頼済み入力で、Profile・Browser・
|
||||
# model入力から指定できない。source `enabled = false`(resolvedでは
|
||||
# `profile.enabled = false`)の場合、legacy Memory tools、
|
||||
# resident injection、extract、consolidation requestをすべて無効にし、snapshotも保持しない。
|
||||
# `[feature.subjektiv]`の有効化やWorker restoreはlegacy dataをreset/migrateしない。
|
||||
# legacy resetは認証済みoperatorが明示APIで別途実行する。
|
||||
#
|
||||
# [feature.memory]
|
||||
# enabled = true
|
||||
# staging_tools = false
|
||||
#
|
||||
# [feature.memory.resident]
|
||||
# inject_summary = true
|
||||
#
|
||||
# [feature.memory.extraction]
|
||||
# enabled = true
|
||||
# reasoning = "medium"
|
||||
# threshold = 30000
|
||||
# worker_max_turns = 8
|
||||
#
|
||||
# # modelは任意。省略時はmain modelをcloneする。
|
||||
# # [feature.memory.extraction.model]
|
||||
# # ref = "codex-oauth/gpt-5.6-luna"
|
||||
#
|
||||
# [feature.memory.consolidation]
|
||||
# request_enabled = true
|
||||
#
|
||||
# # Backendがresolved Manifestへbindする。手書き/Profile入力では指定しない。
|
||||
# # [feature.memory.workspace_settings]
|
||||
# # workspace_id = "workspace-id"
|
||||
# # language = "日本語"
|
||||
#
|
||||
# Query結果/抜粋の上限とconsolidation eligibility/thresholdはoperation/Backend
|
||||
# policyが所有し、通常Worker Manifestには含めない。legacy `[memory]` は拒否する。
|
||||
|
||||
|
||||
# ===== [feature.subjektiv] ================================================
|
||||
# Current subject-scoped Memory product。ProfileはFeature activationと自動extract
|
||||
# policyだけを所有する。subject idはManifest fieldではない。Hostが認証済み
|
||||
# Runtime/Worker sourceとcurrent `subjektiv:<subject-id>` keyed-singleton ownershipから
|
||||
# operationごとにbindし、明示Workspace/subject authorizationを再評価する。
|
||||
#
|
||||
# `enabled = true`はsubject tools、Session attribution/recall、confirmed Memory、
|
||||
# candidate consolidation、resident surface lifecycleを有効にする。legacy
|
||||
# `[feature.memory]` dataのimport/reset/dual-writeは行わない。同じWorkerでlegacyと
|
||||
# subjektivの自動extract destinationを同時に有効化する構成は拒否される。
|
||||
#
|
||||
# [feature.subjektiv]
|
||||
# enabled = true
|
||||
#
|
||||
# [feature.subjektiv.extraction]
|
||||
# enabled = true
|
||||
# reasoning = "medium"
|
||||
# threshold = 50000
|
||||
# worker_max_turns = 8
|
||||
#
|
||||
# # modelは任意。省略時はmain modelをcloneする。
|
||||
# # [feature.subjektiv.extraction.model]
|
||||
# # ref = "codex-oauth/gpt-5.6-luna"
|
||||
#
|
||||
# # Backendがresolved Manifestへbindする。Profile・Browser・model入力では指定しない。
|
||||
# # Workspace identity/language設定のtrusted snapshotであり、subject identityではない。
|
||||
# # [feature.subjektiv.workspace_settings]
|
||||
# # workspace_id = "workspace-id"
|
||||
# # language = "日本語"
|
||||
#
|
||||
# Surface selection/generation/injection、candidate consolidation eligibility、recall
|
||||
# limitsはHost/Backend policyが所有し、通常Worker Manifestには含めない。current Worker
|
||||
# authorityはYoi keyed singletonに残り、historical Session attributionを代用しない。
|
||||
|
||||
|
||||
# ===== [skills] =============================================================
|
||||
# 外部 Agent Skills (`SKILL.md`) の候補ディレクトリ群。
|
||||
# セクション省略 = 何も使用しない (implicit な `$config_dir/skills/` 検索や
|
||||
# builtin probe は存在しない)。
|
||||
# [skills]
|
||||
#
|
||||
# # 任意。デフォルト: 空配列。
|
||||
# # 各エントリは skills *root* (root の child 各々が `<name>/SKILL.md` を持つ
|
||||
# # skill バンドル)。root 自身は skill ではない。
|
||||
# # 相対パスは manifest base 起点で解決。マージ時は層を跨いで concat される。
|
||||
# directories = [".claude/skills", ".cursor/skills"]
|
||||
@@ -0,0 +1,31 @@
|
||||
# 正本・権限・実行の分離
|
||||
|
||||
## 正本と観測
|
||||
|
||||
永続的な判断の根拠は、その判断を所有する明示的な記録に置く。UIの表示、キャッシュ、通知、プロセスの生存情報は、正本を再確認するための観測として扱う。これらだけから操作の成功、作業の完了、権限の継続を推定しない。
|
||||
|
||||
永続化する情報と、現在の状態から再構成できる情報を分ける。別の所有者が管理する状態を複写して第二の状態機械を作ると、更新漏れと復旧時の不一致が生じる。親の処理は子の識別子を保持し、必要な時点で子の正本を読む。
|
||||
|
||||
## 権限と入力
|
||||
|
||||
操作権限は認証された主体、明示的な付与、対象資源の現在の制約から決定する。プロンプト、Profile名、表示名、cwd、ファイルの存在、ツールが見えることを権限の根拠にしない。別の操作経路を設けても、同じ所有者の検証を通す。
|
||||
|
||||
委譲では親が実際に委譲できる範囲を上限にする。読取り、書込み、コマンド実行、観測、レビュー、統合を混同しない。探索で対象を発見できても操作権限を得たことにはならない。
|
||||
|
||||
## 履歴とLLM入力
|
||||
|
||||
LLMの生成に影響する入力は、リクエストへ加える前に履歴へ確定し、永続化形式からリクエストを再構成できるようにする。揮発的な入力の差込みでは、後のターンから発言やツール呼出しの根拠を追えなくなる。
|
||||
|
||||
履歴は追記を基本とする。圧縮や保持範囲の変更は、明示的な境界と再構成規則を持つ処理に限定する。過去の記録を黙って書き換えず、キャッシュとトークン消費への影響をセッションの実測に基づいて判断する。
|
||||
|
||||
## 初期設定と実行処理
|
||||
|
||||
環境変数などの暗黙的なプロセス状態は、起動境界で明示的な入力へ変換する。通常の処理は解決済みの型付き設定を受け取り、実行中に環境を読み直して判断を変えない。秘密情報は明示的な参照で扱い、設定の説明や診断へ値を複製しない。
|
||||
|
||||
## 状態と操作の独立性
|
||||
|
||||
作業の結論、レビュー判断、コード統合、Worker停止、記録の保持、接続解除、資源削除は別の判断である。一つの成功を別の操作の許可として流用しない。自動的に連動させる場合は、その責任を持つ操作として条件と結果を定義する。
|
||||
|
||||
競合確認では、何を、どの観測と比較し、どの操作の前後で検査するかを名前と処理で示す。到着順や時刻で足りる処理に不透明な更新番号を加えない。内容の一致、同じ操作の再送、鍵の一致、接続の生存期間を確認する場合は、それぞれを識別する根拠を使う。
|
||||
|
||||
個別の永続操作と再試行の判断は[永続操作の原則](durable-operations.md)に従う。具体的な認証、保存、復旧の仕組みは所有moduleのドキュメントに置く。
|
||||
@@ -1,6 +1,6 @@
|
||||
# Workspace Web Design Language
|
||||
|
||||
この文書は、Workspace Webのrendered resultが満たす設計規則を定義する。Product固有のresource構成は[`product-ux.md`](product-ux.md)、application shellとSidebar slotの実装構造は[`application-architecture.md`](application-architecture.md)、CSSとsource配置は[`../../../web/workspace/README.md`](../../../web/workspace/README.md)、visual review手順は[`visual-review.md`](visual-review.md)をauthorityとする。
|
||||
この文書は、Workspace Webのrendered resultが満たす設計規則を定義する。
|
||||
|
||||
一つの規則は最も具体的な一箇所だけをauthorityとする。具体的な二案の一方を棄却できない文は規則として残さない。
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# 永続操作と再試行
|
||||
|
||||
永続操作には、同じ依頼を識別する情報と、安全な再試行に必要な事実を保存する。Rustの関数呼出しや待機位置を、そのまま永続状態として記録しない。再読取りで分かる情報や安全に繰り返せる処理は、再構成または再実行する。
|
||||
|
||||
## 記録の分類
|
||||
|
||||
| 種類 | 保存する理由 |
|
||||
| --- | --- |
|
||||
| 原子的な冪等性記録 | 正本の更新と結果が一つのDBトランザクションで確定する。同一依頼の再送には保存済み結果を返し、異なる依頼による操作IDの再利用は拒否する。途中状態は不要。 |
|
||||
| 予約 | 後続の結付けに先立ち、資源IDや排他的な権利を確保する。予約先、依頼、権限、確定した結付けを記録する。 |
|
||||
| 外部副作用を伴う操作 | DB外で起きた効果を再試行で重複させないため、安定した依頼ID、必要なチェックポイント、結果を残す。 |
|
||||
| 親ワークフロー | 複数の操作を調整する。親の依頼、権限、安定した子操作ID、最終結果を持ち、子の状態は子の正本から読む。 |
|
||||
|
||||
同じDB内で原子的に確定できる処理を、永続操作を設けるためだけに分割しない。補償にも独立した副作用や再試行がある場合は、暗黙的な後始末ではなく、識別可能な子操作として扱う。
|
||||
|
||||
## 操作の識別
|
||||
|
||||
操作IDは所有するWorkspaceなどの権限範囲で識別する。依頼のfingerprintには、変わると別の依頼になる入力を含める。Serverが採番した結果ID、時刻、試行回数、変動する観測値、秘密の値は含めない。
|
||||
|
||||
厳密な再送で固定する権限・設定と、実行時に再検証する制約を分ける。固定した入力があっても、現在の割当、所有、接続、削除制約の検証を省略しない。子の作成も安定したIDまたは原子的な結付けで冪等にし、再送で別の子を増やさない。
|
||||
|
||||
## チェックポイントの条件
|
||||
|
||||
チェックポイントは、再試行の方法が変わる事実を記録する。次の条件をすべて満たす場合だけ追加する。
|
||||
|
||||
1. 現在のトランザクション外で副作用が発生し得る。
|
||||
2. その事実を十分確実に再観測できない、または安全に繰り返せない。
|
||||
3. 記録の前後で再試行の処理が変わる。
|
||||
4. 記録の前後それぞれの挙動をテストできる。
|
||||
|
||||
「検証中」「終了処理中」などコード位置を表す段階名ではなく、削除確認、子操作との結付け、確定結果の識別子などの事実を保存する。要求を送信した事実と、副作用の完了を確認した事実は区別する。
|
||||
|
||||
## 状態・失敗・処置
|
||||
|
||||
操作が未完か完了か、直近の失敗、現在の阻害条件、保持・削除などの処置結果は別の情報である。阻害条件は実行前に現在の正本から再判定する。過去の失敗から再試行可能性を推測せず、所有するdomainの規則に従う。
|
||||
|
||||
外部providerには、利用できる冪等性キー、期待する内容、安定した資源IDを渡し、権威ある応答または観測で結果を確認する。応答喪失を失敗と決めつけて、新しい操作IDで副作用を繰り返さない。確認できなければ最後に確定した事実を保持し、結果不明または要確認として扱う。
|
||||
|
||||
## 保証の限界
|
||||
|
||||
対象は重複要求、返却エラー、timeout、既知の部分成功、最後に確定した記録からの再起動復旧である。任意の命令位置でのpanic、強制終了、マシン喪失、電源断に対して、一度だけ実行されることを一般的には保証しない。すべての`await`の前後に状態を書いて、その保証を装わない。
|
||||
|
||||
破壊的・機密性の高い操作に強い耐障害性が必要なら、providerの契約、記録順、再送規則、照合根拠を個別に定義する。診断には調査と再試行に必要な識別子・分類だけを残し、秘密、生のプロンプト、セッション全文、内部パスを複製しない。
|
||||
@@ -0,0 +1,61 @@
|
||||
# Workspace WebのUX設計
|
||||
|
||||
Workspace Webの画面構成と、リソースごとの表示・操作を決める規則を定める。視覚表現と操作の共通規則は[Design Language](design-language.md)に従い、この文書では製品固有の判断を扱う。
|
||||
|
||||
## 作業面の目的
|
||||
|
||||
Workspace Webは、Workspaceの確定した状態を確認し、注意が必要な箇所を判断し、次の操作を行うための作業面とする。マーケティング用のdashboard、分析画面、Backend DTOをそのまま並べる画面にはしない。
|
||||
|
||||
操作の流れは、対象のWorkspaceとリソースを選び、現在の状態を確認し、操作を行い、その結果を確認する順とする。通常の表示とURLには各リソースのcanonical keyを使い、内部UUIDをlabelや代替リンクとして露出しない。
|
||||
|
||||
## ナビゲーションの区分
|
||||
|
||||
- Globalでは、Workspaceの選択・作成、Account、Device Loginなど、Workspaceを選ぶ前の操作を扱う。
|
||||
- Workspaceでは、Tickets、Objectives、Merge Requests、Drive、Memory、Workersなど、選択中のWorkspaceで日常的に扱う対象へ移動する。
|
||||
- Settingsでは、Runtime、Repositoryとそのアクセス設定、Configuration Sources、Profile Sources、Workspace Identityなど、Workspaceの管理と構成を扱う。これらを日常作業のナビゲーションへ混ぜない。
|
||||
|
||||
存在しないWorkspace、閲覧できないWorkspace、読込みに失敗したWorkspaceは区別し、別のWorkspaceへ暗黙に切り替えない。読取り専用の場合も、利用できる閲覧機能を残す。
|
||||
|
||||
## ページの目的
|
||||
|
||||
各routeの主要目的は、「Ticketを選ぶ」「Workerを確認する」「Repositoryのアクセスを設定する」のように、動詞と対象を一つずつ使って表現できるものにする。
|
||||
|
||||
- 一覧・boardは、対象を探し、注意が必要な項目を見つけるために使う。
|
||||
- 詳細は、一つの対象を理解し、現在実行できる操作を行うために使う。
|
||||
- form・editorは、範囲の明確な変更を行うために使う。
|
||||
- Consoleは、一つのWorkerを操作しながら履歴を確認するために使う。
|
||||
|
||||
一つのrouteで複数の形式を組み合わせるのは、同じ目的を支える場合に限る。
|
||||
|
||||
## 一覧と詳細
|
||||
|
||||
同じ項目をリソース間で比較する一覧には、平坦なlistまたはtableを使う。人が読む名前とcanonical keyを最初の列に置く。providerの詳細、変更ID、digestは比較用の列へ常設せず、詳細表示や診断用の開閉領域へ置く。
|
||||
|
||||
詳細では、対象の識別情報、現在の状態、注意が必要な事項を最初に示し、本文や履歴を主領域に置く。関連、target、review、workflowの状態をそれぞれ別の主体が確定する場合は、独立したsectionに分ける。
|
||||
|
||||
## Ticket board
|
||||
|
||||
boardは横方向にlaneを比較する作業面とする。laneはworkflow stateを表し、隣接laneとの関係を維持する。狭い画面でもboard内の横スクロールで比較できるようにする。
|
||||
|
||||
各Ticketにはkey、title、優先判断を変える情報を表示する。blockerとattentionは文字でも示す。
|
||||
|
||||
## Settingsと編集
|
||||
|
||||
一つのSettings routeは一つの設定関心事を扱う。編集操作の前または同時に現在有効な値を示し、一つの判断に必要な入力項目をまとめる。form内で塗りつぶしによって強調する操作は保存だけとする。
|
||||
|
||||
## Memory
|
||||
|
||||
Memoryは、明示的に選択したsubjectを起点に閲覧する。subjectの詳細では現在接続しているWorkerを示し、過去のWorkerやSessionの帰属情報を現在の接続として表示しない。
|
||||
|
||||
- `Surface`は生成されたresident contextの現在の状態を示す。生成済み、生成済みだが空、未生成、古くなった状態、生成失敗を区別する。取得要求の失敗を生成状態として扱わない。
|
||||
- `Committed Memory`は現在の確定Memoryを一覧と詳細で示す。変更履歴は現在の内容とは別に閲覧できるようにし、過去の変更内容を現在値の代わりに表示しない。
|
||||
- `Sources`では、候補とその処理結果、根拠となったMemoryと変更、Hostが記録したSessionへの帰属をたどれるようにする。Sessionの内容を表示するのは閲覧権限がある場合に限る。
|
||||
|
||||
## Worker Console
|
||||
|
||||
Consoleは会話と実行の履歴を主領域とする。コマンド出力はterminalに近い簡潔な表示にし、通常のメッセージ本文と分ける。
|
||||
|
||||
- 直接のSubWorkerの履歴を表示している間も、Composerと実行制御の対象は親Workerのままとする。
|
||||
- OverviewとNormalの切替では見せ方だけを変え、操作対象や権限を変えない。
|
||||
- 現在のWorker状態はWorkerの確定したsnapshotから表示し、WebSocketの接続状態や一時的なeventで置き換えない。
|
||||
- 非公開の推論と生のsystem promptは、debug表示でも公開しない。
|
||||
@@ -1,52 +0,0 @@
|
||||
# Standalone local Subjects
|
||||
|
||||
Standalone runs one client-owned Worker in the current process, without a Backend or Workspace. Local Subjects are explicitly created and selected; enabling a subjektiv Profile alone neither creates nor attaches a Subject.
|
||||
|
||||
## Create and list
|
||||
|
||||
```sh
|
||||
yoi --local subject create assistant --behavior-md 'Keep durable decisions and preferences.'
|
||||
yoi --local subject list
|
||||
```
|
||||
|
||||
`create <ROLE>` prints the created Subject record as JSON. `--behavior-md <TEXT>` is optional inline text, not a file path. `list` prints a JSON array of records in the selected standalone state scope. Both commands require explicit `--local` and reject Backend/Workspace selectors.
|
||||
|
||||
The state directory is obtained from the same local connection target as normal standalone launch. Its base follows the existing `YOI_DATA_DIR`, `YOI_HOME`, `XDG_DATA_HOME`, or `HOME` resolution, with `client/standalone/workers` appended. Subjects live under `<state_dir>/subjektiv/`; cwd, repository files, Worker names, and Profiles are not Subject identity or sharing boundaries. Use the returned common-domain Subject ID (`subject-<UUID>`), not the role, for selection.
|
||||
|
||||
The user-managed state root must be operator-owned and private (mode **0700**). A missing root is created with mode 0700; an existing root is validated, not automatically chmodded. An older nonprivate root is rejected. The operator can explicitly run `chmod 700 <state_dir>` on their own state root before retrying; do not substitute a repository directory or assume the CLI repairs permissions. Symlink and nonprivate Subject storage nodes are also rejected.
|
||||
|
||||
## Select for a new Worker
|
||||
|
||||
```sh
|
||||
yoi --local --subject <ID> --profile builtin:standalone-subjektiv
|
||||
```
|
||||
|
||||
`--subject=<ID>` is also accepted. The selector must be nonempty and may only be supplied once. It applies only to fresh local spawn: Backend selection, Runtime attach, socket/session attach, and resume combinations are rejected. A selected Subject requires a compatible subjektiv policy; launch validation rejects incompatible policy before Worker startup side effects.
|
||||
|
||||
The interactive local spawn form has an optional `subject` field, initially `(none)`. Press **F2** to switch between editing the Worker name and Subject ID; **Tab/Up/Down** continue to cycle Profiles. Leave the Subject field empty for the existing Subject-free launch path. The form does not create Subjects or open Subject storage just to display the field. The optional built-in `builtin:standalone-subjektiv` Profile enables local Subject behavior; its consolidation Profile is `builtin:standalone-subjektiv-consolidation` and does not require Backend resources.
|
||||
|
||||
## Resume
|
||||
|
||||
```sh
|
||||
yoi --local resume
|
||||
```
|
||||
|
||||
Resume restores the original persisted Subject/storage-scope binding and reacquires its execution lease. It does not accept a new `--subject` selector or silently change Subjects. The Host holds an exclusive per-Subject kernel lease for body execution and delegated Jobs until confirmed shutdown; one Subject cannot be executed concurrently by two processes. Process death releases the kernel lease, while uncertain in-process cleanup retains ownership. Separate Subjects may run concurrently in the same state scope.
|
||||
|
||||
## Subject Jobs and recovery
|
||||
|
||||
A selected Subject uses `<state_dir>/subjektiv/jobs/<SubjectId>/jobs.sqlite3`, shared across Worker IDs for that Subject and protected by its exclusive execution lease. Subject-free Hosts retain the existing Worker-local Job location. There is no separate Subject runner or background daemon.
|
||||
|
||||
On explicit selected-Subject Host startup, reserved Jobs resume automatically. An interrupted Job whose execution outcome is unknown is **never automatically replayed**. A successful result that has not yet been acknowledged is delivered from durable storage without retrying the model. Recovery is driven by an explicit Host startup, not an automatic process restart or independent scheduler.
|
||||
|
||||
## Memory and historical Sessions
|
||||
|
||||
The normal body Profile has recall, exact Memory change reads, explicit candidate tools, and bounded historical Session exploration. It cannot directly apply Memory. Explicit Remember/Revise without `entry_refs` returns a pending-commit receipt; the Host stages its exact tool-call evidence only after the logical Run is append-only committed. Pre-request retry may defer an open Run, but commit/rewrite failures are not silently treated as success. A candidate remains unconfirmed until the restricted consolidation Job applies or rejects it using the shared CAS/receipt rules.
|
||||
|
||||
Normal extraction uses the existing committed-capture lifecycle, restricted extraction tools, and append-only `subjektiv.extract.v1` pointer. The selected Profile owns extraction thresholds/model policy. An explicit extraction model override chooses that provider, not an injected dialog transport. Local Memory language defaults to English and is stored in operator-managed `subjektiv/language.json`, never loaded from repository policy files.
|
||||
|
||||
The Host stamps human input on its owned in-process client channel as persisted `local_human_input`, projected as the existing `HumanInput` provenance. It neither invents a Backend account nor trusts serialized source claims. Ordinary SubWorker/Job input retains its own nonhuman origin. Local evidence has no Workspace/account/Runtime identity; local storage-scope identity and Worker binding authorize discovery separately. Historical Sessions are not copied into SQLite. Reads/search exclude system prompts, hidden reasoning, incomplete logical Runs, partial JSONL tails, and foreign bindings; cursor continuations revalidate scope/Subject/Worker attribution and committed generation.
|
||||
|
||||
## Validation boundary
|
||||
|
||||
Host injection tests pass a clone of the injected model transport into delegated Jobs, so scripted body and Job calls use the same test-owned transport. These are local contract tests, not live-provider validation or evidence of model/consolidation quality. Generic Job finalizer and Host integration proofs are validated separately from the CLI/parser and spawn-form tests.
|
||||
Reference in New Issue
Block a user