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
+6 -29
View File
@@ -1,33 +1,10 @@
# manifest
## Role
Validates reusable Worker Profiles and resolves them into Manifests with launch-time names, paths, and permissions. This crate owns configuration types and resolution boundaries, not Worker execution or credential retrieval.
`manifest` resolves reusable profile/configuration inputs into the concrete runtime Manifest used to create or restore Workers.
## Main entry points
## Boundaries
Owns:
- Profile and Manifest data structures
- source/partial/resolved configuration layering
- path and prompt-resource resolution
- tool permission and filesystem scope configuration types
- model/provider references as configuration records
Does not own:
- provider HTTP clients or secret lookup implementation (`provider`, `secrets`)
- Worker lifecycle (`worker`)
- product CLI parsing (`yoi`)
- generated memory records (`memory`)
## Design notes
Profiles are reusable recipes; resolved Manifests are runtime contracts. Keep runtime-bound fields such as Worker name, concrete delegated scope, sockets, session pointers, and raw secrets out of reusable Profiles.
`feature.workdir_catalog.enabled` defaults to `true` for read-only Repository/Workdir/attachment reference discovery in WIP mode. It is independent of `feature.manage_workdir` (which defaults disabled), registers no normal Tools, and does not grant filesystem, attachment, or Workspace authority. Permissions remain independent. Standalone Tools-mode profiles may retain this default; only WIP installation consumes the flag. Set it to `false` to opt out.
## See also
- [`../../docs/design/profiles-manifests-prompts.md`](../../docs/design/profiles-manifests-prompts.md)
- [`../../docs/design/tool-permissions-scope.md`](../../docs/design/tool-permissions-scope.md)
- [`ProfileResolver`] / [`ResolvedProfile`] — Profile selection and resolution results
- [`WorkerManifestConfig`] / [`WorkerManifest`] — Partial configuration merging and execution contracts
- [`Scope`] / [`DelegationScope`] / [`SharedScope`] — Direct access and child delegation checks
- [`plugin`] — Offline package inspection, independent of execution authority
+7
View File
@@ -1,3 +1,10 @@
//! Converts an explicit builtin source set into Decodal values.
//!
//! Selects a catalog entry point and loads only relative imports within that catalog.
//! Does not fall back to OS paths or Workspace sources, and distinguishes undefined
//! entry points from evaluation failures. Launching or restoring values already
//! evaluated by the Workspace is owned separately from this reevaluation.
use std::collections::BTreeMap;
use decodal::{Data, Engine, ImportLoader, LoadedImport};
+22 -22
View File
@@ -1,10 +1,4 @@
//! Partial-form of [`crate::WorkerManifest`] used as cascade layers.
//!
//! `WorkerManifestConfig` mirrors `WorkerManifest` but every field is optional
//! so individual layers (builtin defaults, user manifest, project
//! manifest, programmatic overlay) can be partial. Layers are combined
//! via [`WorkerManifestConfig::merge`] and the final config is converted to
//! a validated [`WorkerManifest`] via `TryFrom`.
//! Partial configuration merging and validation.
use std::collections::{BTreeSet, HashMap};
use std::num::NonZeroU32;
@@ -25,17 +19,21 @@ use crate::{
WorkerManifest, WorkerMeta,
};
/// Partial-form Worker manifest. Every field is optional; one or more
/// instances merge via [`WorkerManifestConfig::merge`] before being
/// converted to a validated [`WorkerManifest`] via `TryFrom`.
/// Merges partial configuration into a validated [`crate::WorkerManifest`].
///
/// [`WorkerManifestConfig`] preserves omitted values. Resolve each input's relative
/// paths with [`WorkerManifestConfig::resolve_paths`] before merging; do not later
/// reinterpret them against another input's base. [`WorkerManifestConfig::merge`]
/// handles explicit merging; final conversion validates required fields and
/// semantics. This type does not imply implicit discovery or merging of user/project
/// Manifest files during normal startup.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WorkerManifestConfig {
#[serde(default)]
pub worker: WorkerMetaConfig,
/// `[model]` セクションは partial でも完成形でも同じ
/// [`ModelManifest`] を使う。ref / inline の両形を受け入れるための
/// 全 Optional 構造なので、カスケード層と最終マニフェストで型を
/// 分ける必要がない。
/// Partial and final `[model]` sections share [`ModelManifest`]. All fields
/// are optional to support both ref and inline forms, so cascade layers and
/// final manifests need no separate types.
#[serde(default)]
pub model: ModelManifest,
#[serde(default)]
@@ -911,10 +909,12 @@ impl WorkerManifestConfig {
self
}
/// Merge `upper` into `self`. Fields present in `upper` override
/// fields from `self`. Map entries merge key-wise with `upper`
/// winning on conflict. Scope rules from both layers accumulate
/// (see [`ScopeConfig`] semantics).
/// Merges explicit upper-layer values, retaining lower-layer values when omitted.
///
/// Maps prefer upper values per key; scope rules and Skills directories accumulate.
/// Resolve each input's paths before merging so relative paths from different
/// inputs are not mistaken for sharing a base. This merge guarantees neither
/// authority containment checks nor persistence CAS.
pub fn merge(self, upper: WorkerManifestConfig) -> Self {
Self {
worker: self.worker.merge(upper.worker),
@@ -1135,16 +1135,16 @@ fn ensure_absolute(field: &'static str, path: &Path) -> Result<(), ResolveError>
}
}
/// `AuthRef::ApiKey { file }` が相対パスのとき `base` を前置する。
/// Prepends `base` to relative `AuthRef::ApiKey { file }` paths.
fn resolve_auth_file(auth: &mut Option<AuthRef>, base: &Path) {
if let Some(AuthRef::ApiKey { file: Some(p), .. }) = auth.as_mut() {
*p = join_if_relative(base, p);
}
}
/// モデル宣言に含まれる `auth.file` が絶対パスであることを検証する。
/// ref / scheme / model_id 等の論理的な有効性(ref があるか、inline が
/// 揃っているか)の検証は [`crate::model_catalog`] で行う。
/// Validates that model declarations use absolute `auth.file` paths.
/// [`crate::model_catalog`] validates logical ref / scheme / model_id requirements,
/// such as the presence of a ref or complete inline fields.
fn validate_model_paths(model: &ModelManifest, field: &'static str) -> Result<(), ResolveError> {
if let Some(AuthRef::ApiKey { file: Some(p), .. }) = &model.auth {
ensure_absolute(field, p)?;
+4 -6
View File
@@ -1,10 +1,8 @@
//! Single source of truth for manifest default values.
//! Centralizes Manifest defaults.
//!
//! Every default that would otherwise be duplicated between serde
//! `#[serde(default = "...")]` attributes (on [`crate::WorkerManifest`])
//! and the cascade resolution in [`crate::config`] lives here as a
//! `pub const`. Both paths read from this module, so changing a
//! default requires editing exactly one line.
//! Serde defaults in [`crate::WorkerManifest`] and resolution in
//! [`crate::WorkerManifestConfig`] share constants to keep decoding and partial
//! configuration merging consistent.
/// Read-only Repository/Workdir/attachment reference discovery in WIP mode.
/// This enables a reference provider, not normal Tools or any authority grant.
+15 -8
View File
@@ -1,3 +1,5 @@
#![doc = include_str!("../README.md")]
mod builtin_profile;
mod config;
pub mod defaults;
@@ -40,12 +42,14 @@ use std::path::PathBuf;
use serde::de::Error as _;
use serde::{Deserialize, Serialize};
/// Declarative configuration for a Worker.
/// Resolved execution contract for creating and restoring a Worker.
///
/// Parsed from a TOML manifest file. Describes the model, system prompt,
/// and directory scope (required). The Worker's working directory is **not**
/// part of the manifest — it is the process's `std::env::current_dir()`
/// at construction time.
/// Separate from reusable Profiles and partial configuration, this includes
/// launch-time identity, paths, permissions, and prompt references. Persisting
/// resolved values lets restoration reuse the original contract without resolving
/// current configuration or Profiles again. Workdir attachments and their operation
/// authority belong to a separate execution boundary; this value does not pin the
/// process cwd.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkerManifest {
@@ -890,10 +894,13 @@ pub struct SessionConfig {
pub record_event_trace: bool,
}
/// Manifest-level pattern-based tool permission policy.
/// Pattern-based permission policy for tool calls.
///
/// Presence of `[permissions]` enables this layer. Rules are evaluated
/// in declaration order; if none match, [`Self::default_action`] is used.
/// Evaluates rules in declaration order, using [`Self::default_action`] if none
/// match. This is separate from [`Scope`] path authority: allowing a tool does not
/// guarantee access to its target path. `Ask` defers permission; current callers
/// fail closed because there is no approval/resume protocol. The Worker owns
/// enforcement.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ToolPermissionConfig {
pub default_action: ToolPermissionAction,
+44 -47
View File
@@ -1,72 +1,69 @@
//! LLM モデル宣言型
//! LLM model declaration types.
//!
//! Worker マニフェストの `[model]` セクションで記述する型。`ref`(プロバイダ
//! とモデルを両方指し示す短縮形)と inline 指定(`scheme` / `model_id`
//! 直書き)の両方を受け入れるため、すべてのフィールドを `Option` として
//! 持つ 1 つの型 [`ModelManifest`] に統合している。実解決(ref をプロバイダ
//! カタログ / モデルカタログから引いて `scheme` や `model_id` を埋める)
//! は [`crate::model_catalog`] の責務で、本モジュールはデータ表現のみを提供する。
//! The Worker manifest's `[model]` section uses one [`ModelManifest`] with all
//! fields optional to support both `ref` (a provider/model shorthand) and inline
//! `scheme` / `model_id` declarations. [`crate::model_catalog`] resolves refs via
//! provider/model catalogs and fills in `scheme`, `model_id`, and other fields;
//! this module provides only the data representation.
//!
//! 同じ型を partial(カスケード層)と完成形(最終マニフェスト)の両方で
//! 使うことで、merge と最終変換の重複を避ける。
//! Sharing the type between partial cascade layers and final manifests avoids
//! duplicate merging and final conversion logic.
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
// `ModelCapability` は `agen` 側に定義される runtime 構造だが、
// マニフェストで任意に override できるよう型だけ再エクスポートする。
// `ModelCapability` is a runtime type defined in `agen`; re-export only the type
// to allow optional manifest overrides.
pub use agen::llm_client::capability::{ModelCapability, ReasoningControl, ReasoningEffort};
/// Worker マニフェストの `[model]` セクション。
/// The Worker manifest's `[model]` section.
///
/// - ref だけ書く: `[model] ref = "anthropic/claude-sonnet-4-6"`
/// - ref + 一部 override: ref で基底を引き、`auth` 等だけ書き換え
/// - 完全 inline: `ref` を省略して `scheme` / `model_id` / `auth` を直書き
/// - Ref only: `[model] ref = "anthropic/claude-sonnet-4-6"`
/// - Ref with overrides: use ref as a base and override fields such as `auth`.
/// - Fully inline: omit `ref` and specify `scheme` / `model_id` / `auth` directly.
///
/// どの形が有効かの判定は [`crate::model_catalog::resolve_model_manifest`] が担う。
/// 本クレートは「どこから取るか」を表現するだけで、未設定かどうかを
/// 理由にした hard error は出さない。
/// [`crate::model_catalog::resolve_model_manifest`] determines which forms are valid.
/// This crate only represents configuration sources; it does not raise hard errors
/// merely because fields are unset.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ModelManifest {
/// `<provider_id>/<model_id_in_ref>` 形式のカタログ参照。`/` の
/// 最初の 1 文字目で split し provider カタログを引く。
/// OpenRouter の `anthropic/claude-sonnet-4` のように `/` を含む
/// model_id は `openrouter/anthropic/claude-sonnet-4` と書く
/// (provider 側で最初の `/` のみ split するため)。
/// Catalog reference in `<provider_id>/<model_id_in_ref>` form. Splits at
/// the first `/` to look up the provider catalog. For model IDs containing
/// `/`, such as OpenRouter's `anthropic/claude-sonnet-4`, use
/// `openrouter/anthropic/claude-sonnet-4`; only the first `/` is split.
#[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")]
pub ref_: Option<String>,
/// wire format の明示指定。ref 未指定時は必須、ref 指定時は override。
/// Explicit wire format. Required without ref; an override with ref.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scheme: Option<SchemeKind>,
/// API のベース URL。scheme の既定値を override する。
/// API base URL, overriding the scheme default.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
/// プロバイダが受け付けるモデル ID。ref 未指定時は必須。
/// Provider-accepted model ID. Required without ref.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model_id: Option<String>,
/// 認証方式。ref 未指定時は必須、ref 指定時は override。
/// Authentication method. Required without ref; an override with ref.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auth: Option<AuthRef>,
/// モデル能力の明示指定。未指定時はモデルカタログ → provider
/// `default_capability` → scheme 既定の順で解決される。
/// Explicit model capabilities. If omitted, resolves in order: model catalog,
/// provider `default_capability`, then scheme default.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub capability: Option<ModelCapability>,
/// モデルの希望コンテキストウィンドウ(tokens)。カタログ未掲載 / inline
/// モデルでもここで明示 override できる。実効値は `max_context_window`
/// またはカタログ上の backend maximum で clamp される。
/// Desired model context window in tokens. Can explicitly override uncataloged
/// or inline models. The effective value is clamped by `max_context_window`
/// or the catalog's backend maximum.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_window: Option<u64>,
/// backend が実際に受け付けるコンテキストウィンドウ上限(tokens)。
/// 表示・安全判定に使う実効 context window は `context_window` とこの値の
/// 小さい方になる。
/// Maximum context window in tokens actually accepted by the backend.
/// The effective window used for display and safety checks is the smaller
/// of `context_window` and this value.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_context_window: Option<u64>,
}
impl ModelManifest {
/// `upper` を `self` に上書きマージする。マニフェスト cascade 向け
/// (builtin → user → project → overlay の優先順位で呼ばれる)。
/// Merges explicit `upper` values over `self`, retaining lower values when omitted.
pub fn merge(self, upper: Self) -> Self {
Self {
ref_: upper.ref_.or(self.ref_),
@@ -81,39 +78,39 @@ impl ModelManifest {
}
}
/// サポートする wire scheme の種類。
/// Supported wire scheme kinds.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SchemeKind {
/// Anthropic Messages API (`/v1/messages`)。Ollama `/v1/messages` もこれで扱う
/// Anthropic Messages API (`/v1/messages`), also used for Ollama `/v1/messages`.
Anthropic,
/// OpenAI Chat Completions (`/v1/chat/completions`)。OpenAI 互換ルーター共通枠
/// OpenAI Chat Completions (`/v1/chat/completions`), shared by compatible routers.
OpenaiChat,
/// OpenAI Responses API (`/v1/responses`)。別チケットで scheme 新設予定
/// OpenAI Responses API (`/v1/responses`).
OpenaiResponses,
/// Google Gemini (`/v1beta/models/...:streamGenerateContent`)
Gemini,
}
/// 認証の参照。
/// Authentication reference.
///
/// 実際のトークン値の解決(local secret store / file 読取、OAuth refresh 等)は
/// worker-side factory で行う。ここはあくまで「どこから取るか」の宣言。
/// A worker-side factory resolves actual tokens (local secret store / file reads,
/// OAuth refresh, etc.). This is only a declaration of where to obtain them.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum AuthRef {
/// 認証不要(ローカル Ollama 等)
/// No authentication required (e.g. local Ollama).
#[default]
None,
/// API key file reference. Prefer [`AuthRef::SecretRef`] for normal
/// provider credentials; this remains an explicit file source for low-level
/// manifests and tests.
ApiKey {
/// key を書き込んだファイル(絶対パス)
/// File containing the key (absolute path).
#[serde(default)]
file: Option<PathBuf>,
},
/// ChatGPT OAuth(`~/.codex/auth.json`)。
/// ChatGPT OAuth (`~/.codex/auth.json`).
#[serde(rename = "codex_oauth")]
CodexOAuth,
/// Typed local secret-store reference. The profile resolver preserves this
+72 -79
View File
@@ -1,17 +1,17 @@
//! プロバイダ / モデルカタログ。
//! Provider / model catalogs.
//!
//! - builtin プロバイダ: `resources/providers/builtin.toml`
//! - builtin モデル: `resources/models/builtin.toml`
//! - builtin providers: `resources/providers/builtin.toml`
//! - builtin models: `resources/models/builtin.toml`
//! - user override: `<config_dir>/{providers,models}.toml`
//!
//! `<config_dir>` の解決は [`crate::paths::config_dir`] を参照。
//! どちらの override も「あれば builtin を置換、無ければ builtin」と
//! いう一方向の差し替え(マージしない)。providers / models は独立に
//! 読み、片方だけ user override も可。
//! See [`crate::paths::config_dir`] for `<config_dir>` resolution. Each override
//! replaces its builtin catalog if present, otherwise the builtin is used; no
//! merging occurs. Provider and model catalogs load independently, so either
//! can be overridden alone.
//!
//! [`resolve_model_manifest`] が [`crate::ModelManifest`](ref / inline
//! 両形)を最終的な [`ModelConfig`] に解決する単一の入口で、wire 層
//! に渡す前のバリデーションもここで行う。
//! [`resolve_model_manifest`] is the single entry point for resolving either ref
//! or inline [`crate::ModelManifest`] declarations into a final [`ModelConfig`],
//! including validation before passing it to the wire layer.
use std::path::{Path, PathBuf};
@@ -45,9 +45,8 @@ pub enum CatalogError {
BuiltinParse(#[source] toml::de::Error),
}
/// マニフェスト解決時のエラー。`ModelManifest` がカタログ参照を満たせ
/// ない、あるいは inline フォームでも必須フィールドが揃わない場合に
/// 返す。
/// Manifest resolution error: `ModelManifest` cannot satisfy a catalog reference
/// or an inline declaration is missing required fields.
#[derive(Debug, thiserror::Error)]
pub enum ResolveError {
#[error("failed to load provider catalog: {0}")]
@@ -62,15 +61,14 @@ pub enum ResolveError {
InlineMissing(&'static str),
}
/// UI 向けの認証ヒント。
/// UI authentication hint.
///
/// 「何を表示・要求するか」のメタ情報で、ランタイムの [`AuthRef`]
/// とは責務が別。1:1 の対応関係にあり、[`auth_hint_to_ref`] / [`AuthHint`]
/// 解決経路で相互変換される。
/// Metadata describing what to display/request, separate from runtime [`AuthRef`].
/// Maps 1:1 to it via `auth_hint_to_ref` during resolution.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum AuthHint {
/// 認証不要(ローカル Ollama 等)
/// No authentication required (e.g. local Ollama).
None,
/// API key file reference. Normal credential configuration should prefer
/// [`AuthHint::SecretRef`] so plaintext credentials stay out of manifests.
@@ -82,12 +80,12 @@ pub enum AuthHint {
#[serde(rename = "ref")]
ref_: String,
},
/// ChatGPT OAuth(`~/.codex/auth.json`)
/// ChatGPT OAuth (`~/.codex/auth.json`)
#[serde(rename = "codex_oauth")]
CodexOAuth,
}
/// プロバイダカタログの 1 エントリ。
/// One provider catalog entry.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProviderEntry {
pub id: String,
@@ -96,43 +94,41 @@ pub struct ProviderEntry {
#[serde(default)]
pub base_url: Option<String>,
pub auth_hint: AuthHint,
/// モデルカタログ未登録モデルでこの provider が使われたとき
/// (ref で provider はあるが model 行は無い等)のフォールバック。
/// 省略時は `Scheme::default_capability()` を最終フォールバックに
/// 使う。
/// Fallback when this provider serves a model absent from the model catalog
/// (e.g. a ref has a provider but no model entry). If omitted,
/// `Scheme::default_capability()` is the final fallback.
#[serde(default)]
pub default_capability: Option<ModelCapability>,
/// モデルカタログ未登録モデルで使う既定の context window。省略時は
/// [`DEFAULT_CONTEXT_WINDOW`] を使う。
/// Default context window for models absent from the model catalog.
/// Uses [`DEFAULT_CONTEXT_WINDOW`] if omitted.
#[serde(default)]
pub default_context_window: Option<u64>,
}
/// モデルカタログの 1 エントリ。
/// One model catalog entry.
///
/// `id` は **provider 内ユニーク**。同じ `gpt-5` が異なる provider に
/// 存在するのは OK で、ref が必ず `<provider>/<model_id>` を含むため
/// 曖昧性が出ない。
/// `id` is **unique within a provider**. The same `gpt-5` may appear under different
/// providers without ambiguity, since refs always include `<provider>/<model_id>`.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ModelEntry {
pub id: String,
pub provider: String,
/// モデル単位の capability override。省略時は
/// `ProviderEntry::default_capability` にフォールバックする。
/// Per-model capability override, falling back to
/// `ProviderEntry::default_capability` if omitted.
#[serde(default)]
pub capability: Option<ModelCapability>,
/// モデル単位の context window。省略時は provider default → builtin
/// fallback にフォールバックする。実効値は `max_context_window` で clamp
/// される。
/// Per-model context window. If omitted, falls back to the provider default,
/// then the builtin fallback. The effective value is clamped by
/// `max_context_window`.
#[serde(default)]
pub context_window: Option<u64>,
/// backend が実際に受け付ける context window の上限。UI や pre-request
/// safety は希望値ではなく clamp 済みの実効値を使う。
/// Maximum context window actually accepted by the backend. UI and
/// pre-request safety checks use the clamped effective value, not the desired one.
#[serde(default)]
pub max_context_window: Option<u64>,
}
/// 解決済みモデル設定。`build_client` が消費する完成形。
/// Resolved model configuration, ready for `build_client`.
#[derive(Debug, Clone, PartialEq)]
pub struct ModelConfig {
pub scheme: SchemeKind,
@@ -158,7 +154,7 @@ struct ModelCatalogFile {
model: Vec<ModelEntry>,
}
/// `auth_hint` に対応する [`AuthRef`] のひな型を返す。
/// Returns an [`AuthRef`] template corresponding to `auth_hint`.
fn auth_hint_to_ref(hint: &AuthHint) -> AuthRef {
match hint {
AuthHint::None => AuthRef::None,
@@ -170,13 +166,11 @@ fn auth_hint_to_ref(hint: &AuthHint) -> AuthRef {
// --- providers ---------------------------------------------------------------
/// builtin + user override を解決して provider カタログを返す。
/// Returns the provider catalog after resolving builtin + user overrides.
///
/// user override (`<config_dir>/providers.toml`) が存在すれば builtin
/// を置き換える。存在しなければ builtin のみ。user override が存在
/// するが壊れている場合はエラーを返す(silent fallback はしない —
/// ユーザーが書いた設定が silent に無視されて builtin に戻る挙動は
/// 気付きにくいため)。
/// A user override (`<config_dir>/providers.toml`) replaces the builtin if present;
/// otherwise only the builtin is used. An invalid override returns an error rather
/// than silently falling back, since ignoring user configuration is hard to notice.
pub fn load_providers() -> Result<Vec<ProviderEntry>, CatalogError> {
if let Some(path) = crate::paths::user_catalog_override("providers.toml")
&& path.is_file()
@@ -186,14 +180,14 @@ pub fn load_providers() -> Result<Vec<ProviderEntry>, CatalogError> {
load_builtin_providers()
}
/// builtin provider カタログのみを返す。
/// Returns only the builtin provider catalog.
pub fn load_builtin_providers() -> Result<Vec<ProviderEntry>, CatalogError> {
let parsed: ProviderCatalogFile =
toml::from_str(BUILTIN_PROVIDERS).map_err(CatalogError::BuiltinParse)?;
Ok(parsed.provider)
}
/// 指定パスから provider カタログを読む。
/// Reads a provider catalog from the given path.
pub fn load_providers_from(path: &Path) -> Result<Vec<ProviderEntry>, CatalogError> {
let text = std::fs::read_to_string(path).map_err(|source| CatalogError::Io {
path: path.to_path_buf(),
@@ -209,7 +203,7 @@ pub fn load_providers_from(path: &Path) -> Result<Vec<ProviderEntry>, CatalogErr
// --- models ------------------------------------------------------------------
/// builtin + user override を解決してモデルカタログを返す。
/// Returns the model catalog after resolving builtin + user overrides.
pub fn load_models() -> Result<Vec<ModelEntry>, CatalogError> {
if let Some(path) = crate::paths::user_catalog_override("models.toml")
&& path.is_file()
@@ -219,14 +213,14 @@ pub fn load_models() -> Result<Vec<ModelEntry>, CatalogError> {
load_builtin_models()
}
/// builtin model カタログのみを返す。
/// Returns only the builtin model catalog.
pub fn load_builtin_models() -> Result<Vec<ModelEntry>, CatalogError> {
let parsed: ModelCatalogFile =
toml::from_str(BUILTIN_MODELS).map_err(CatalogError::BuiltinParse)?;
Ok(parsed.model)
}
/// 指定パスからモデルカタログを読む。
/// Reads a model catalog from the given path.
pub fn load_models_from(path: &Path) -> Result<Vec<ModelEntry>, CatalogError> {
let text = std::fs::read_to_string(path).map_err(|source| CatalogError::Io {
path: path.to_path_buf(),
@@ -239,12 +233,12 @@ pub fn load_models_from(path: &Path) -> Result<Vec<ModelEntry>, CatalogError> {
Ok(parsed.model)
}
// --- ref 解決 / マニフェスト → ModelConfig ---------------------------------
// --- ref resolution / manifest → ModelConfig ---------------------------------
/// `<provider_id>/<model_id>` の最初の `/` で 1 回だけ split する。
/// OpenRouter の `openrouter/anthropic/claude-sonnet-4.6` のように
/// model_id に `/` を含むケースは、provider=`openrouter`、
/// model_id=`anthropic/claude-sonnet-4.6` として通る。
/// Splits `<provider_id>/<model_id>` once, at the first `/`.
/// Model IDs containing `/`, such as OpenRouter's
/// `openrouter/anthropic/claude-sonnet-4.6`, resolve as provider=`openrouter`,
/// model_id=`anthropic/claude-sonnet-4.6`.
fn split_ref(s: &str) -> Option<(&str, &str)> {
let (provider, rest) = s.split_once('/')?;
if provider.is_empty() || rest.is_empty() {
@@ -253,30 +247,30 @@ fn split_ref(s: &str) -> Option<(&str, &str)> {
Some((provider, rest))
}
/// `ModelManifest` をカタログ込みで解決し、最終 [`ModelConfig`] を返す。
/// Resolves `ModelManifest` against catalogs into a final [`ModelConfig`].
///
/// - **`ref` あり** → provider カタログを引き、未登録なら hard error。
/// model カタログは未登録でも warn ログだけに留め、`provider.default_capability`
/// にフォールバック(タイポで動く可能性は API 側 `model not found` で
/// 結果的に検出されるため)。
/// - **`ref` なし** → `scheme` / `model_id` / `auth` の 3 つが揃って
/// いることを検証し、そのまま `ModelConfig` を組む。
/// - **With `ref`**: looks up the provider; an unknown provider is a hard error.
/// A missing model entry only logs a warning and falls back to
/// `provider.default_capability` (typos are eventually caught by the API's
/// `model not found` error).
/// - **Without `ref`**: requires `scheme` / `model_id` / `auth` and builds
/// `ModelConfig` directly.
///
/// 各フィールドの解決順は ticket の表に準拠:
/// scheme/base_url は manifest 明示 > provider、model_id は manifest 明示 > ref、
/// auth は manifest 明示 > provider.auth_hint 由来、capability は
/// manifest 明示 > model catalog > provider.default_capability >
/// (`build_client` 側で)`Scheme::default_capability()`。
/// context_window は manifest 明示 > model catalog > provider default >
/// [`DEFAULT_CONTEXT_WINDOW`]。実効 context_window は manifest/model の
/// max_context_window で clamp される。
/// Field precedence:
/// scheme/base_url: explicit manifest > provider; model_id: explicit manifest > ref;
/// auth: explicit manifest > provider.auth_hint; capability:
/// explicit manifest > model catalog > provider.default_capability >
/// `Scheme::default_capability()` (in `build_client`).
/// context_window: explicit manifest > model catalog > provider default >
/// [`DEFAULT_CONTEXT_WINDOW`]. The effective context_window is clamped by the
/// manifest/model max_context_window.
pub fn resolve_model_manifest(manifest: &ModelManifest) -> Result<ModelConfig, ResolveError> {
let providers = load_providers().map_err(ResolveError::LoadProviders)?;
let models = load_models().map_err(ResolveError::LoadModels)?;
resolve_with_catalogs(manifest, &providers, &models)
}
/// テスト等で in-memory カタログを差し込む解決経路。
/// Resolution path accepting in-memory catalogs, e.g. for tests.
pub fn resolve_with_catalogs(
manifest: &ModelManifest,
providers: &[ProviderEntry],
@@ -290,7 +284,7 @@ pub fn resolve_with_catalogs(
.find(|p| p.id == provider_id)
.ok_or_else(|| ResolveError::UnknownProvider(provider_id.to_string()))?;
// model 行は無くても続行可(warn ログ + provider.default_capability)。
// Missing model entries may proceed with a warning + provider.default_capability.
let model_entry = models
.iter()
.find(|m| m.provider == provider_id && m.id == ref_model_id);
@@ -637,10 +631,9 @@ auth_hint = { kind = "none" }
assert!(matches!(err, CatalogError::Parse { .. }));
}
/// `YOI_CONFIG_DIR` を tempdir に向けるテストガード。
/// `paths::config_dir` は他の env (YOI_HOME / XDG_CONFIG_HOME)
/// より高優先で `YOI_CONFIG_DIR` を尊重するため、これだけで
/// 開発機の env 設定に左右されないテストになる。
/// Test guard pointing `YOI_CONFIG_DIR` to a temporary directory.
/// `paths::config_dir` prioritizes it over YOI_HOME / XDG_CONFIG_HOME,
/// isolating tests from the development machine's environment settings.
struct ConfigDirGuard {
prev: Option<String>,
}
@@ -648,8 +641,8 @@ auth_hint = { kind = "none" }
impl ConfigDirGuard {
fn new(path: &Path) -> Self {
let prev = std::env::var("YOI_CONFIG_DIR").ok();
// SAFETY: serial_test の `#[serial]` 属性で env を弄るテスト
// 同士は直列化される。
// SAFETY: serial_test's `#[serial]` attribute serializes tests that
// modify the environment.
unsafe { std::env::set_var("YOI_CONFIG_DIR", path) };
Self { prev }
}
@@ -692,7 +685,7 @@ auth_hint = { kind = "none" }
#[serial]
fn load_falls_back_to_builtin_when_override_absent() {
let dir = tempfile::tempdir().unwrap();
// override ファイルは作らない
// Do not create an override file.
let _g = ConfigDirGuard::new(dir.path());
let entries = load_providers().unwrap();
assert!(!entries.is_empty());
+39 -40
View File
@@ -1,36 +1,36 @@
//! Yoi のホームディレクトリ配下のパス解決を一元化するモジュール。
//! Centralized path resolution under Yoi's home directory.
//!
//! 用途別に三つの base directory を持つ:
//! Separates base directories by purpose:
//!
//! - **`config_dir`** — 人が手で書く / 編集する設定。`profiles.toml`,
//! `providers.toml`, `models.toml` 等
//! - **`data_dir`** — プログラムが書く永続データ。`sessions/` 等
//! - **`secret_data_dir`** — local secret store の読み書き base。既存
//! secret store は path-derived key を使うため、通常 data とは別に
//! legacy `$HOME/.yoi` を既定にする
//! - **`runtime_dir`** — 再起動で消えてよいランタイム状態。socket,
//! `workers.json`, `pid` ファイル等
//! - **`config_dir`** — Handwritten/edited configuration: `profiles.toml`,
//! `providers.toml`, `models.toml`, etc.
//! - **`data_dir`** — Program-written persistent data, such as `sessions/`.
//! - **`secret_data_dir`** — Base for local secret-store reads/writes. The existing
//! store uses path-derived keys, so it defaults to legacy `$HOME/.yoi`, separate
//! from regular data.
//! - **`runtime_dir`** — Runtime state disposable on restart: sockets,
//! `workers.json`, `pid` files, etc.
//!
//! ## 解決順 (優先順位高 → 低)
//! ## Resolution order (highest to lowest priority)
//!
//! | base | 1. `YOI_<KIND>_DIR` | 2. `YOI_HOME` | 3. `XDG_*` | 4. 既定 |
//! | base | 1. `YOI_<KIND>_DIR` | 2. `YOI_HOME` | 3. `XDG_*` | 4. default |
//! |---|---|---|---|---|
//! | config | `YOI_CONFIG_DIR` | `$YOI_HOME/config` | `$XDG_CONFIG_HOME/yoi` | `$HOME/.config/yoi` |
//! | data | `YOI_DATA_DIR` | `$YOI_HOME` | `$XDG_DATA_HOME/yoi` | `$HOME/.local/share/yoi` |
//! | secrets | `YOI_DATA_DIR` | `$YOI_HOME` | — | `$HOME/.yoi` |
//! | runtime | `YOI_RUNTIME_DIR` | `$YOI_HOME/run` | `$XDG_RUNTIME_DIR/yoi` | `$HOME/.yoi/run` |
//!
//! `YOI_HOME=$X` のとき config は `$X/config`、data は `$X` 直下、
//! runtime は `$X/run` に集約される。テストや sandbox 利用ではこれ一本
//! で全部 tempdir に向けられる。
//! With `YOI_HOME=$X`, config uses `$X/config`, data uses `$X` directly, and
//! runtime uses `$X/run`. Tests and sandboxes can redirect all of them to a
//! temporary directory with this single variable.
//!
//! 解決された各 base が存在するか / ディレクトリかは保証しない —
//! 呼び出し側がファイル操作の前に作成 / 検査する。
//! Resolved bases are not guaranteed to exist or be directories; callers must
//! create/check them before file operations.
use std::path::PathBuf;
/// 設定ディレクトリ。`profiles.toml`, `providers.toml`, `models.toml`,
/// `prompts/` などが置かれる。
/// Configuration directory for `profiles.toml`, `providers.toml`, `models.toml`,
/// `prompts/`, etc.
pub fn config_dir() -> Option<PathBuf> {
resolve_config_dir_from_parts(
env_path("YOI_CONFIG_DIR"),
@@ -40,8 +40,7 @@ pub fn config_dir() -> Option<PathBuf> {
)
}
/// データディレクトリ。`sessions/` などプログラムが書く永続データの
/// 置き場。
/// Data directory for program-written persistent data, such as `sessions/`.
pub fn data_dir() -> Option<PathBuf> {
resolve_data_dir_from_parts(
env_path("YOI_DATA_DIR"),
@@ -51,11 +50,11 @@ pub fn data_dir() -> Option<PathBuf> {
)
}
/// Secret store 用の data directory。
/// Data directory for the secret store.
///
/// SecretStore は base path から復号 key を導出するため、通常 data dir の
/// XDG fallback には追従させず、明示 override が無い場合は legacy
/// `$HOME/.yoi` を読み書きする。
/// SecretStore derives decryption keys from the base path, so it does not follow
/// the regular data directory's XDG fallback. Without an explicit override, it
/// reads/writes legacy `$HOME/.yoi`.
pub fn secret_data_dir() -> Option<PathBuf> {
resolve_secret_data_dir_from_parts(
env_path("YOI_DATA_DIR"),
@@ -64,8 +63,8 @@ pub fn secret_data_dir() -> Option<PathBuf> {
)
}
/// ランタイムディレクトリ。socket, `workers.json`, Worker ごとの `pid` /
/// `status.json` 等が置かれる。再起動で消えて構わない。
/// Runtime directory for sockets, `workers.json`, per-Worker `pid` /
/// `status.json`, etc. Its contents may be discarded on restart.
pub fn runtime_dir() -> Option<PathBuf> {
resolve_runtime_dir_from_parts(
env_path("YOI_RUNTIME_DIR"),
@@ -85,33 +84,33 @@ pub fn user_profiles_path() -> Option<PathBuf> {
user_profiles_path_from_config_dir(config_dir())
}
/// `<config_dir>/<file_name>` — providers.toml / models.toml 等の
/// user override ファイル。
/// `<config_dir>/<file_name>` — user override files such as providers.toml /
/// models.toml.
pub fn user_catalog_override(file_name: &str) -> Option<PathBuf> {
user_catalog_override_from_config_dir(config_dir(), file_name)
}
/// `<data_dir>/sessions/` — session store のデフォルト位置。
/// `<data_dir>/sessions/` — default session store location.
pub fn sessions_dir() -> Option<PathBuf> {
sessions_dir_from_data_dir(data_dir())
}
/// `<runtime_dir>/workers.json` — machine-wide Worker allocation table。
/// `<runtime_dir>/workers.json` — machine-wide Worker allocation table.
pub fn worker_allocation_path() -> Option<PathBuf> {
worker_allocation_path_from_runtime_dir(runtime_dir())
}
/// `<runtime_dir>/<worker_name>/` — Worker ごとのランタイムディレクトリ。
/// `<runtime_dir>/<worker_name>/` — per-Worker runtime directory.
pub fn worker_runtime_dir(worker_name: &str) -> Option<PathBuf> {
worker_runtime_dir_from_runtime_dir(runtime_dir(), worker_name)
}
/// `<runtime_dir>/<worker_name>/sock` — Worker の Unix socket パス。
/// `<runtime_dir>/<worker_name>/sock` — Worker's Unix socket path.
///
/// Worker プロセス内で実際に socket を作成するのは `worker` crate の
/// `RuntimeDir::socket_path()` で、Worker 名が分かっている外部 (TUI の
/// attach フロー等) からの**予測**はこの関数で行う。両者は同じパス
/// を返すことが期待される。
/// The `worker` crate's `RuntimeDir::socket_path()` creates the socket inside the
/// Worker process. This function **predicts** the path for external callers that
/// know the Worker name, such as the TUI attach flow. Both should return the same
/// path.
pub fn worker_socket_path(worker_name: &str) -> Option<PathBuf> {
worker_socket_path_from_runtime_dir(runtime_dir(), worker_name)
}
@@ -219,9 +218,9 @@ fn worker_socket_path_from_runtime_dir(
Some(worker_runtime_dir_from_runtime_dir(runtime_dir, worker_name)?.join("sock"))
}
/// 空文字列の env は未設定として扱う。`std::env::var` は `Ok("")` と
/// `Err(NotPresent)` を区別するが、パス解決においては両者を未設定と
/// 同等に扱うのが直感的。
/// Treats empty environment values as unset. `std::env::var` distinguishes
/// `Ok("")` from `Err(NotPresent)`, but path resolution treats both as unset
/// for intuitive behavior.
fn env_path(name: &str) -> Option<PathBuf> {
let value = std::env::var(name).ok()?;
path_from_env_value(Some(value.as_str()))
+20 -5
View File
@@ -1,3 +1,19 @@
//! Offline reading, validation, and writing of the `.yoi-plugin` format.
//!
//! Inspects only explicitly supplied directories or archives; does not implicitly
//! discover catalogs, register with Workers, or instantiate components. Validating
//! the format grants no execution authority. Worker Manifests/Profiles reject dynamic
//! Plugin configuration; package declarations and local paths do not grant Worker
//! capabilities.
//!
//! Archives use a restricted stored-ZIP subset. Unsafe paths, links, unsupported
//! entries, size/count limit violations, and inconsistent headers are rejected;
//! the file set is digested in order. Directory inputs also reject internal links
//! and undergo the same validation, so distribution formats share the package
//! contract. Component Model metadata is required; legacy core-Wasm runtime
//! declarations are rejected. Validation covers the offline format, not safety
//! established by running components.
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::{BTreeMap, BTreeSet};
@@ -398,12 +414,11 @@ impl PluginPackageManifest {
const LEGACY_PLUGIN_RUNTIME_WASM_KIND: &str = "wasm";
const LEGACY_PLUGIN_RUNTIME_WASM_ABI: &str = "yoi-plugin-wasm-1";
/// Manifest runtime kind for current WebAssembly Component Model packages.
/// Component Model declaration kind accepted in offline packages.
///
/// Component runtime manifests must set `component` to the packaged component
/// artifact path and `world` to [`PLUGIN_COMPONENT_TOOL_WORLD`] or
/// [`PLUGIN_COMPONENT_INSTANCE_WORLD`]. Legacy raw core-Wasm manifests are
/// intentionally rejected by validation; `wasm-component` is the public runtime.
/// Validates component paths and corresponding worlds; rejects legacy core-Wasm
/// declarations. This constant implies neither runtime registration nor verified
/// component execution.
pub const PLUGIN_RUNTIME_COMPONENT_KIND: &str = "wasm-component";
pub const PLUGIN_COMPONENT_TOOL_WORLD: &str = "yoi:plugin/tool@1.0.0";
pub const PLUGIN_COMPONENT_INSTANCE_WORLD: &str = "yoi:plugin/instance@1.0.0";
+15 -6
View File
@@ -1,9 +1,4 @@
//! Profile discovery and resolution.
//!
//! Profiles are reusable, human-authored recipes. They are intentionally not
//! complete runtime manifests: runtime-bound and authority-bearing fields such
//! as `worker.name` and concrete `scope.allow` rules are supplied by the resolver
//! from launch context.
//! Profile selection and resolution.
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
@@ -461,6 +456,20 @@ impl ProfileResolveOptions {
}
}
/// Resolves a launch-time Manifest from a reusable Profile.
///
/// Names and concrete scope rules come from the launch context, not the Profile,
/// so reusing configuration for another Worker does not inherit the original
/// Worker's identity or path authority. Scope intent and configured secret references
/// are retained; input validation rejects runtime-bound fields and Manifest-style
/// authority rules.
///
/// Profiles resolve as values, not executable scripts. Omitted values use
/// [`crate::WorkerManifestConfig::resolution_defaults`], not an implicit builtin
/// parent role. Source-relative paths resolve against their own source before
/// Manifest conversion. [`ResolvedProfile`] keeps source values and the Manifest
/// snapshot separate, so restoration after launch need not reselect the current
/// Profile.
#[derive(Debug, Clone, Default)]
pub struct ProfileResolver {
workspace_base: Option<PathBuf>,
+19 -11
View File
@@ -1,11 +1,4 @@
//! Runtime representation of a Worker's access scope.
//!
//! Built from [`crate::ScopeConfig`] via [`Scope::from_config`]. Every
//! rule `target` must already be an absolute path — per-layer path
//! resolution runs earlier, inside [`crate::WorkerManifestConfig::resolve_paths`].
//! All rule targets retain both their lexically normalized logical identity and
//! their provider-resolved identity. Allow rules select one identity explicitly;
//! deny rules always inspect both so aliases cannot bypass a restriction.
//! Path authority and delegation scope checks.
use std::ffi::OsString;
use std::path::{Component, Path, PathBuf};
@@ -15,10 +8,25 @@ use arc_swap::{ArcSwap, Guard};
use crate::{Permission, ScopeConfig, ScopeRule, SymlinkPolicy};
/// Parsed, pwd-resolved set of allow/deny rules for a Worker.
/// Checks Worker path authority and authority delegable to children.
///
/// Read/write access decisions are pure functions of the path being
/// queried and these rules — see [`Scope::permission_at`].
/// Rule targets must already be absolute paths resolved by
/// [`crate::WorkerManifestConfig::resolve_paths`]. Each rule retains a lexically
/// normalized logical identity and a symlink-resolved identity. Allows default to
/// `resolved`; explicit `logical` rules allow logical paths even when link targets
/// lie outside the logical subtree. Denies check both identities, preventing aliases
/// or symlinks from bypassing restrictions.
///
/// [`Scope`] grants direct access; [`DelegationScope`] grants authority transferable
/// to children. Direct access alone does not permit delegation. Delegated rules must
/// fit existing allows, including permission, recursion, and symlink policy, and
/// must not overlap denies. A `resolved` allow cannot expand into a `logical` rule.
///
/// Parent write restrictions during delegation can be additional denies. On return,
/// remove added rules one at a time by exact match, without lifting preexisting
/// denies in the same region. [`SharedScope`] serializes mutations and exposes the
/// same latest scope to checks. File-operation path validation and reclaiming
/// authority when children stop remain with their respective owners.
#[derive(Debug, Clone)]
pub struct Scope {
allow: Vec<ResolvedRule>,