docs: consolidate design documentation and standardize crate English
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user