docs: consolidate design documentation and standardize crate English

This commit is contained in:
2026-10-12 14:58:00 +09:00
parent a633a53274
commit 4efb0ace1f
404 changed files with 4556 additions and 11654 deletions
+9 -32
View File
@@ -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・操作結果の識別情報だけを残す。秘密情報、認証情報、非公開プロンプト、秘密を含むファイルの内容を文書・診断・セッションへ転載しない。広範な監査では、明示的な内容確認が必要でない限り、存在とパスの確認にとどめる。
-24
View File
@@ -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.
-49
View File
@@ -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.
-45
View File
@@ -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.
+49 -220
View File
@@ -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を成功の証拠にしない。
-91
View File
@@ -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を発生させる。
-196
View File
@@ -1,196 +0,0 @@
# 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とする。
一つの規則は最も具体的な一箇所だけをauthorityとする。具体的な二案の一方を棄却できない文は規則として残さない。
## 1. Principles
複数の規則や要求が競合した場合だけ、この章の優先原理を使う。
### Task before explanation
次の有効なactionへ到達する前の操作、scroll、常設説明が少ない案を選ぶ。ただし、説明を省くと対象、結果、permission、不可逆性を誤認する場合は説明をactionより前に置く。
### Meaning before decoration
情報の関係、重要度、操作可能性、stateのいずれも変えない視覚差を追加しない。
### Authority before convenience
Backend authorityが確定していないdata、state、permission、actionを、UIの都合で補完または推測する案を棄却する。
## 2. Semantics / Grammar
### Information structure
- pageと、page内で一つのtaskまたは判断を完結させる領域をscopeとする。
- 各scopeで最も強い視覚要素はheading、現在判断が必要なattention、filled actionのいずれか一つだけにする。同じ強さのheading、attention、filled actionを複数置かない。
- hierarchyをcolorだけで表現しない。DOMと画面上の配置順を一致させた上で、size、weight、contrastを使う。
- scopeを識別するprimary titleは、一つのscopeにつき一度だけ表示する。
- actionは、そのactionが影響するscope内に置く。
- DTOにfieldが存在するという理由だけで表示せず、current taskの判断またはactionを変える値だけを常設する。
**Disclosure**
- Raw dataやデバッグ情報など、通常は必要としない詳細な情報は折りたたみ領域に隠す。
- 作成または編集のformを表示用screenへ常時同居させず、明示的なactionで開く折りたたみ領域または別pageに置く。
### Interaction and state
- actionにはbutton、navigationにはlink、選択には対応するform controlを使う。
- interactive elementはhover、focus、active、disabledを視覚的に区別する。
- staticなstatus、key、metadataをbuttonやpillの形にしない。
- clickable rowは遷移先を一つだけ持ち、row全体で同じ遷移を実行する。
- permissionがないactionをdisabled controlとして表示しない。
**State and operation**
- stateの違いによってユーザーの判断または利用できるactionが変わる場合、その違いを一つの表示へ統合しない。
- UI stateはそのstateを所有するauthorityから取得し、別domainやtransportのsignalから推測しない。
- 非同期mutationでは楽観的更新か悲観的更新を選ぶ。楽観的更新は暫定stateを明示し、failure時のrollbackまたは再取得を定義できる場合だけ使う。それ以外は確定済みstateを維持し、operationのpending stateを別に表示する。
- loading、failure、unavailableの表示は利用不能になった最小のscopeだけを置き換え、利用可能な親scopeとsibling scopeを残す。
- destructive actionは対象と結果を明記し、復元できない場合だけconfirmationを要求する。
### Language / Copy
- title、navigation label、control labelで明らかな内容を説明文で繰り返さない。
- 常設proseは、対象またはactionの選択を変えるwarning、permission、validation constraint、error recoveryに必要な内容だけにする。
- button labelには具体的なactionを表す動詞を使い、`Submit`と`OK`を使わない。
- identifier、API名、type名を別の語へ言い換えない。
### Responsive behavior
- page全体にhorizontal scrollを発生させない。
- wrappingまたはstack化によって情報の対応関係が失われる場合だけ、その領域内にbounded horizontal scrollを持たせる。それ以外のside-by-side layoutは一列にする。
- primary actionを初期viewportから追い出さない。
- contentをtruncateする場合、完全な値をcopyまたはdetailで取得できるようにする。
### Accessibility
- action、navigation、form controlを非semantic elementだけで実装しない。
- すべてのcontrolにaccessible nameを与える。
- keyboard focusを不可視にしない。
- tab orderを画面上の順序と一致させる。
- stateをcolorだけで区別しない。
- horizontal scroll regionをkeyboard focus可能にし、accessible nameを与える。
## 3. Primitives / Tokens
### Color / Theme
colorはCSS custom propertyをauthorityとし、通常のWorkspace colorはOKLCHで定義する。lightとdarkは同じsemantic token名を使う。
```css
--bg
--bg-raised
--bg-subtle
--line
--line-strong
--text
--text-strong
--text-muted
--text-faint
--code
--accent
--accent-muted
--success
--warning
--danger
--interactive-hover
--interactive-selected
--shadow-overlay
```
- `--bg`、`--bg-raised`、`--bg-subtle`へhueを持たせない。
- accentはfocus、current navigation、current selectionだけに使い、通常本文や通常actionの背景へ使わない。
- success、warning、dangerを、それぞれ対応するstate以外へ使わない。
- componentへraw colorを追加せず、上記semantic tokenを使う。
- ConsoleとterminalのANSI paletteを通常のstatusやformへ流用しない。
### Typography
font sizeは、その領域でユーザーが行う読み方によって選ぶ。
- defaultは`14px / 20px`とし、個々のtextを読むこと自体がtaskの中心になる領域に使う。
- 同じ構造の反復を一覧として走査する領域と、位置、順序、形、選択状態を中心に識別するUI chromeには`12px / 16px`を使う。textを読まなければactionの意味や結果を判断できない場合は`14px / 20px`を使う。
- `24px / 32px`はページまたはdocument内で唯一かつ最大のheadingだけに使う。
- 収めるため、または情報を弱く見せるためにfont sizeを下げない。`12px`未満と`13px`を使わない。
- 機械的な値にはmonospace familyを使う。
```css
--font-sans
--font-mono
--font-size-title /* 24px */
--line-height-title /* 32px */
--font-size-body /* 14px */
--line-height-body /* 20px */
--font-size-compact /* 12px */
--line-height-compact /* 16px */
```
### Spacing / Shape
```css
--space-1 /* 4px */
--space-2 /* 8px */
--space-3 /* 12px */
--space-4 /* 16px */
--space-5 /* 24px */
--space-6 /* 32px */
--radius-soft
```
- 一つの境界にspacing、line、surface backgroundを重ねず、primary cueを一つだけ使う。
- 汎用的な`Card` primitiveを設けない。
- nested surfaceでparentと同じ境界表現を繰り返さない。
- shadowは通常flowから浮くmenu、popover、Tooltipだけに使う。
- radiusはinteractive controlだけに使う。
### Motion
- motion完了をactionの受付、state change、content理解の条件にしない。
- 繰り返しまたは連続animationを、`prefers-reduced-motion: reduce`で停止する。
## 4. Components / Patterns
### Border / Structural edge
- `Bevel`は1pxの単色borderを描くvisual wrapperであり、照明、raised/inset、ridge/grooveを表現しない。
- `Bevel`自体へbackgroundやsurface toneを与えず、必要なbackgroundは内側のsemantic childが持つtext/content areaへ適用する。
- 隣接するsurfaceでは、実際に境界となる辺だけを有効にする。
- standaloneな閉じた領域は全辺を`Bevel`で囲う。Desktop shellではHeaderのbottom edgeとSidebarのright edgeだけを有効にし、viewport外周のtop/left edgeを描かない。
- `BevelLine`は開いた領域内の1px separatorだけに使う。同じlayerではheading、本文、Lineの端を揃え、Lineだけに端方向のpaddingやmarginを追加しない。
- nested layerでは親layoutがそのlayer全体へinline方向の余白を与え、content、row、Lineを一緒に移動する。Lineだけを短くしない。
- control自身のborder、focus ring、status marker、table gridを`Bevel`または`BevelLine`で置き換えない。
### Sidebar
- Desktop幅は`clamp(220px, 20vw, 280px)`とする。
- navigation contentだけをscrollさせる。
- Desktopのfold controlはSidebar下部、Mobileのfold controlはHeaderに置く。
- MobileでSidebarを表示するときはHeader下の残りviewport全体を使い、main contentと同時表示しない。
- navigationとscope control以外を置かない。
- navigation linkはSidebar幅全体を使うflat rowとする。
- active routeは一つの視覚cueと`aria-current="page"`で示す。
- iconだけでnavigation labelを置き換えない。
- fold controlは一つだけ置き、current stateに対応するaccessible nameを使う。
### Tooltip / Contextual help
- operationやtable全体を説明する常設proseを置く前に、button labelとcolumn headingだけで識別できるようにする。
- labelだけでは表現できない補助説明だけを、対象へbindしたTooltipに入れる。
- pointer hoverとkeyboard focusの両方で開く。
- triggerとTooltipを`aria-describedby`で関連付ける。
- hoverはdelay後、focusは直ちに表示する。
- `Escape`で閉じ、triggerからfocusを移動しない。
- 複数paragraph、form、link、buttonを入れない。
- error、permission、validation constraint、不可逆操作の結果、current stateをTooltipへ入れない。
- touch環境ではTooltipだけを説明への到達手段にしない。
### Generic component patterns
- **Form field**: label、control、constraintまたはhelp、field errorの順に配置する。
- **Key-value**: 一つのresourceの属性には`dl`を使う。
Design-labではcomponent名と実際のsampleだけを表示する。規則とrationaleをUIへ書かない。
-156
View File
@@ -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.
+20 -260
View File
@@ -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)が所有し、合否判断は実装者が行う。
+17 -51
View File
@@ -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.
+13 -83
View File
@@ -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.
-91
View File
@@ -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.
-171
View File
@@ -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判定を増やさない。