最終更新: 2026-08-31 設計ステータス: 実装仕様確定・未実装
本文書は、ロードマップ Phase 2のdeny-by-default capability modelを固定する先行規範仕様である。外部状態へ触れる操作を実行単位で明示grantし、process environment、argv、clock、stdio、filesystem、processからambient authorityを除去する。
次期言語挙動、CLI、canonical errorとscript catch可否は次期意味論・実装決定、Phase 3/4の公開API・budget・state・transactionは実行予算・協調実行仕様、Phase 5/6のaudit schemaとfail-closedは決定性・実行時監査仕様、Phase 0の保証境界は脅威モデルに従う。本書のPhase 2先行型はそれら最終契約の内部subsetとし、同じbuildへ旧型・旧event・旧adapter契約を併存させない。
| Phase | 本文書と後続正本の境界 |
|---|---|
| 2 | empty default、grant/freeze、operation分離、同期Ready adapter、事前deny、path-handle認可、snapshot、host registry metadata、CLI profile、ambient実装削除 |
| 3 | 実行予算・協調実行仕様の有限budget、reservation、deadline、実行中cancelをadapterへ伝播 |
| 4 | 同文書のcooperative adapter extension、Pending ticket/waker、backpressure。同期traitの意味は変更しない |
| 6 | 決定性・実行時監査仕様のcanonical event、sequence、redaction、fail-closed sink |
| 7 | capability matrix、race/stress、budget/audit完全性の継続gate |
原則:
CapabilitySet::empty()が唯一のlibrary既定値で、全外部操作を拒否する。CLI profileはこのempty setから必要なadapterを明示grantして構築する。- grantはhostだけが行う。scriptはcapabilityを生成、列挙、複製、grant、revokeできない。
- setはstart時freezeされ、実行中のgrant/revokeはない。取消はexecution全体をcancelし、新setで再実行する。
- denialは対象OS API、resolver、host callbackより前に成立する。script実行中のoperation denialは次期意味論・実装決定第3節のcatch可能なcanonical
capability/sandboxerrorで、未捕捉時はRuntimeErrorとなる。link/import等handler開始前のdenialだけがcatch不可terminalDeniedとなる。budget超過、deadline、cancel、audit fail-closed、InternalFailure等の制御・基盤terminalはscriptからcatchできない。 - Environment、Clock、Stdin、Stdout、Filesystem各操作、ProcessExit、ModuleResolver、HostFunctionを別authorityとし、一方から他方を導出しない。
- adapterは
Send + Syncである。Phase 2同期traitはcaller thread上で有限時間にReady相当の結果を返す。Phase 4 cooperative extensionだけがPendingを返し、同期traitを暗黙に別threadへ移さない。HTTP/DB/mail/queueはcoreへ入れずhost adapterとする。
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum CapabilityKind {
Environment,
Clock,
Stdin,
Stdout,
Filesystem,
ProcessExit,
ModuleResolver,
HostFunction,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct CapabilitySetId([u8; 32]);
impl CapabilitySetId {
pub const fn as_bytes(&self) -> &[u8; 32];
}
#[derive(Clone)]
pub struct CapabilitySet(Arc<FrozenCapabilities>);
impl CapabilitySet {
pub fn empty() -> Self;
pub fn builder() -> CapabilitySetBuilder;
pub fn id(&self) -> CapabilitySetId;
pub fn contains(&self, kind: CapabilityKind) -> bool;
}
pub struct CapabilitySetBuilder { /* mutable, !Clone */ }
impl CapabilitySetBuilder {
pub fn environment(self, value: EnvironmentSnapshot) -> Result<Self, ConfigError>;
pub fn clock(self, value: Arc<dyn Clock>) -> Result<Self, ConfigError>;
pub fn stdin(self, value: Arc<dyn Input>) -> Result<Self, ConfigError>;
pub fn stdout(self, value: Arc<dyn Output>) -> Result<Self, ConfigError>;
pub fn filesystem(self, value: FilesystemCapability) -> Result<Self, ConfigError>;
pub fn process_exit(self, value: ProcessExit) -> Result<Self, ConfigError>;
pub fn module_resolver(self, value: Arc<dyn ModuleResolver>) -> Result<Self, ConfigError>;
pub fn grant_host_function(self, id: HostFunctionId) -> Result<Self, ConfigError>;
pub fn build(self) -> CapabilitySet;
}同kind二重設定はConfigError::DuplicateCapabilityで、後勝ちにしない。host function grantだけは異なるIDを複数追加でき、同ID重複はconfiguration error。setからadapterを取り出すpublic getterはない。
clone()は同じimmutable authorityへのshallow cloneで、set IDと権限は同一。revoke APIは存在しない。
IDは認可tokenではなくpolicy相関IDである。SHA-256入力の全整数はunsigned big-endian、u128 policy/function IDは16 byte big-endian、文字列はu64(length) || UTF-8 bytesとする。
固定tag:
| 型 | tag |
|---|---|
| CapabilityKind | Environment=0x01, Clock=0x02, Stdin=0x03, Stdout=0x04, Filesystem=0x05, ProcessExit=0x06, ModuleResolver=0x07, HostFunction=0x08 |
| DataClassification | Public=0x00, Sensitive=0x01, Secret=0x02 |
| SymlinkPolicy | DenyAll=0x00, FollowWithinRoot=0x01, OperateOnFinalEntry=0x02 |
| FsOperation | Read=0x01, Write=0x02, Create=0x03, Delete=0x04, Metadata=0x05, List=0x06, Import=0x07 |
entry_countは存在するCapabilityKind group数で、host functionが複数でも1 groupと数える。groupはCapabilityKind tag昇順、environment key/mountはUTF-8 byte列昇順、host function IDは16 byte値昇順。empty setも同じdomainとentry_count=0でhashする。
"TSUMUGI-CAPSET-V1\0"
|| u64(entry_count)
|| each group:
capability_kind_tag
Environment: u64(key_count) || each str(key) || classification_tag
// value本文は含めない。policy IDでありinput identityではない
Clock: clock_policy_id[16]
Stdin: input_policy_id[16]
Stdout: output_policy_id[16]
Filesystem: filesystem policy encoding(第8.6節)
ProcessExit: process_exit_policy_id[16]
ModuleResolver: resolver_policy_id[16]
HostFunction: u64(count) || sorted host_function_id[16]
adapter policy IDはhostが構成内容ごとに変える非zero u128で、pointer addressを使わない。
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AdapterError {
Host(HostError),
Control(ControlStop),
SecureResolutionUnsupported,
}
pub struct CapabilityCallContext<'a> { /* field private */ }
impl CapabilityCallContext<'_> {
pub fn execution_id(&self) -> ExecutionId;
pub fn deadline(&self) -> MonotonicInstant;
pub fn cancellation(&self) -> &CancellationToken;
pub fn check_control(&mut self) -> Result<(), ControlStop>;
pub fn reserve(&mut self, request: BudgetRequest)
-> Result<BudgetReservation, ControlStop>;
}ControlStop、BudgetRequest、reservationのreserve/commit/refund、MonotonicInstantは実行予算・協調実行仕様第3・7節を唯一の正本とする。独立したBudgetFailure、optional deadline、未計測時に成功するpublic charge APIを作らない。adapterはcall前、bounded block中に可能な地点、return前にcheck_controlし、外部効果またはallocation前にatomic reservationを得る。
error projectionはvariantごとに分離する。script操作中のAdapterError::Hostだけをsanitized canonical host errorとしてcatch可能にし、SecureResolutionUnsupportedは第8.4節のfilesystem操作に限って同じchannelへ投影する。AdapterError::Controlはcatch可能なlanguage errorへ変換せず、ControlStopのvariantに応じてterminal BudgetExceeded / DeadlineExceeded / Cancelledへだけ投影する。link/control-plane中のhost failureはterminal HostErrorとする。
script操作中のnon-filesystem capability denialは、次期意味論・実装決定第3.4節の「host function capability拒否」を共通のcanonical projectionとして使う。adapter-backed builtinの{name}は閉じた集合env / now / input / print / exitから選び、登録host functionではcatalogに固定した公開名を使う。filesystem denialだけは同節のsandbox projectionを使う。これはadapter-backed builtinをnative host functionとみなす分類ではなく、公開error kind/messageを一意にする互換境界である。
Phase 2の各同期traitはmay_block = falseで、caller thread上で有限時間に完了する場合だけ登録できる。Phase 4では同文書第6.2節のCooperativeAdapter<Request, Response> / HostCallPoll::{Ready, Pending} / HostCallTicketを対応request/response型へ追加できる。Pendingはcooperative extensionだけが返し、同期traitのsignatureや意味を変更しない。両実装は同じcapability decision、budget、deadline、cancellation、call ID、audit lifecycleを共有する。
Phase 6のaudit emitterはprivate fieldとしてcontextへ追加するが、adapterから任意event名を発行するAPIは公開しない。core dispatcherだけがcanonical eventを生成する。
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum DataClassification { Public, Sensitive, Secret }
#[derive(Clone)]
pub struct EnvironmentValue {
value: String,
classification: DataClassification,
}
impl EnvironmentValue {
pub fn new(value: impl Into<String>, classification: DataClassification)
-> Result<Self, ConfigError>;
pub fn expose_to_script(&self) -> &str;
pub fn classification(&self) -> DataClassification;
}
#[derive(Clone)]
pub struct EnvironmentSnapshot(Arc<BTreeMap<String, EnvironmentValue>>);
impl EnvironmentSnapshot {
pub fn empty() -> Self;
pub fn from_entries(
entries: impl IntoIterator<Item = (String, EnvironmentValue)>,
) -> Result<Self, ConfigError>;
pub fn keys(&self) -> impl Iterator<Item = &str>;
pub(crate) fn get(&self, key: &str) -> Option<&EnvironmentValue>;
}key/valueはUTF-8、NULなし。keyは1..=256 bytes、重複keyはerror。snapshotはstart前に完成し、run中にprocess environmentを再読しない。
- Environment capabilityなしの
env()はenvironment adapter call 0で内部Denialを生成し、script実行中はsanitized canonicalcapabilityerrorとしてcatch可能にする。未捕捉時はRuntimeErrorであり、terminalDeniedにはしない。 - capabilityあり・keyなしは
null。 - safe profileはprotected runtime keyをsnapshotへ入れない。CLIの
--allow-envとlegacy profileにも同じ判定を使う。 - protected判定はWindowsではkeyをUnicode uppercase化して
TSUMUGI_prefixと比較し、その他OSではcase-sensitiveにTSUMUGI_prefixと比較する。これによりtsumugi_*とUnicode case aliasもWindowsで拒否する。 - custom embedding hostがprotected keyを明示投入する場合は
Secret必須であり、CLI互換profileの保護とは別のtrusted-host操作とする。 - argumentsはEnvironmentではなく
ExecutionRequest.arguments。空snapshotが既定でprocess argvへfallbackしない。
pub trait Clock: Send + Sync + 'static {
fn policy_id(&self) -> NonZeroU128;
fn now_utc(
&self,
context: &mut CapabilityCallContext<'_>,
) -> Result<SystemTime, AdapterError>;
}now()はこのtraitだけを使う。deadlineはscript用ClockでなくEngineのmonotonic clockを使う。test utilityとしてFixedClockを提供する。Clockなしのnow()はtrait call 0で内部Denialを生成し、script実行中はcatch可能なcanonical capability error、未捕捉時はRuntimeErrorとする。
pub trait Input: Send + Sync + 'static {
fn policy_id(&self) -> NonZeroU128;
fn read_line(
&self,
context: &mut CapabilityCallContext<'_>,
limit: ReadLimit,
) -> Result<InputLine, AdapterError>;
}
#[derive(Clone, Copy, Debug)]
pub struct ReadLimit { pub max_bytes: Option<NonZeroU64> }
pub enum InputLine { Line(String), Eof }
pub trait Output: Send + Sync + 'static {
fn policy_id(&self) -> NonZeroU128;
fn write_all(
&self,
context: &mut CapabilityCallContext<'_>,
bytes: &[u8],
) -> Result<(), AdapterError>;
fn flush(
&self,
context: &mut CapabilityCallContext<'_>,
) -> Result<(), AdapterError>;
}- Inputなしの
input()はInput adapter call 0で内部Denialを生成し、script実行中はcatch可能なcanonicalcapabilityerror、未捕捉時はRuntimeErrorとする。EOFはnull、AdapterError::Hostはcatch可能なcanonicalhosterrorで、nullへ潰さない。AdapterError::Controlは第4節どおりterminal outcomeへ投影する。 - Input adapterはincremental readし、finite limit NならN+1 byteを蓄積する前に
AdapterError::Control(ControlStop::BudgetExceeded(_))。改行なしも同じ。 - Outputなしの
printはOutput call 0で内部Denialを生成し、script実行中はcatch可能なcanonicalcapabilityerror、未捕捉時はRuntimeErrorとする。grant時はUTF-8 bytes+改行をlogical write前に一括chargeする。 - 残量NならN bytes成功、N+1は
Output::write_allcall 0でBudgetExceeded。 - Phase 2はtraitとdeny/error分離まで、有限meterとN境界はPhase 3。
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum FsOperation {
Read,
Write, // 既存file overwrite/append
Create, // 新規file/directory/destination entry
Delete,
Metadata,
List,
Import,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum SymlinkPolicy {
DenyAll,
FollowWithinRoot,
OperateOnFinalEntry,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct MountName(String);
impl MountName {
pub fn new(value: impl Into<String>) -> Result<Self, ConfigError>;
pub fn as_str(&self) -> &str;
}
#[derive(Clone)]
pub struct FilesystemRoot {
pub mount: MountName,
pub policy_id: NonZeroU128,
pub operations: BTreeSet<FsOperation>,
pub symlink_policy: SymlinkPolicy,
pub adapter: Arc<dyn DirectoryHandle>,
}
impl FilesystemRoot {
pub fn new(
mount: MountName,
policy_id: NonZeroU128,
operations: BTreeSet<FsOperation>,
symlink_policy: SymlinkPolicy,
adapter: Arc<dyn DirectoryHandle>,
) -> Result<Self, ConfigError>;
}
#[derive(Clone)]
pub struct FilesystemCapability { roots: Arc<[FilesystemRoot]> }
impl FilesystemCapability {
pub fn new(roots: impl IntoIterator<Item = FilesystemRoot>)
-> Result<Self, ConfigError>;
pub fn roots(&self) -> impl Iterator<Item = &FilesystemRoot>;
}MountNameはASCII [A-Za-z][A-Za-z0-9_-]{0,31}。mount重複と空operationsはconfiguration error。同じpolicy IDは、同じadapter Arc・operations・symlink policyへ別mount aliasを付ける場合だけ許可する。FilesystemRoot::newはadapter.policy_id()とadapter.symlink_policy()が指定値と一致することを検証する。script filesystem operationでmountまたはoperationが不足する場合はadapter/OS call 0で内部Denialを生成し、catch可能なcanonical sandbox errorへ変換する。未捕捉時はRuntimeErrorであり、handler開始前のlink/importだけがterminal Deniedとなる。
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RelativePath { components: Arc<[String]> }
impl RelativePath {
pub fn components(&self) -> impl Iterator<Item = &str>;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PathError {
Empty,
ContainsNul,
Absolute,
DotComponent,
ParentComponent,
EmptyComponent,
BackslashSeparator,
InvalidMountName,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FilesystemTarget {
pub mount: MountName,
pub path: RelativePath,
}
impl FilesystemTarget {
pub fn parse(script_path: &str) -> Result<Self, PathError>;
}規範syntaxは@MOUNT/component/component。unqualified component/componentはmount名defaultへparseする。mountは完全一致し、prefix一致や登録順fallbackをしない。
relative componentはUTF-8、1 byte以上、NULなし。/だけをseparatorとし、absolute /、.、..、空component、backslash、drive prefix、UNCを拒否する。host platform pathへ変換する前に検証する。PathErrorはcapabilityの有無を調べる前にcatch可能なRuntimeErrorCode::Argumentへ変換するため、malformed pathとauthority不足のchannelは混在しない。parse後に該当mountがない場合はadapter/OS call 0で内部Denial { code: ResourceNotGranted }を生成し、script filesystem operationならcatch可能なcanonical sandbox errorへ変換する。
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WriteMode { Truncate, Append }
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OpenFileRequest {
ReadExisting,
WriteExisting { mode: WriteMode },
CreateNew { mode: WriteMode },
Upsert { mode: WriteMode },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EntryKind { File, Directory, Symlink, Other }
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PublicMetadata {
pub kind: EntryKind,
pub size_bytes: u64,
pub readonly: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DirectoryEntry {
pub name: String, // 単一の検証済みcomponent。path separatorなし
pub kind: EntryKind,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RemoveKind { FileOrSymlink, EmptyDirectory }
pub trait DirectoryHandle: Send + Sync + 'static {
fn policy_id(&self) -> NonZeroU128;
fn symlink_policy(&self) -> SymlinkPolicy;
fn open_file(
&self,
context: &mut CapabilityCallContext<'_>,
path: &RelativePath,
request: OpenFileRequest,
) -> Result<Box<dyn FileHandle>, AdapterError>;
fn create_dir(
&self,
context: &mut CapabilityCallContext<'_>,
path: &RelativePath,
) -> Result<(), AdapterError>;
fn metadata(
&self,
context: &mut CapabilityCallContext<'_>,
path: &RelativePath,
follow_final: bool,
) -> Result<PublicMetadata, AdapterError>;
fn list(
&self,
context: &mut CapabilityCallContext<'_>,
path: &RelativePath,
max_entries: Option<NonZeroU64>,
) -> Result<Vec<DirectoryEntry>, AdapterError>;
fn remove(
&self,
context: &mut CapabilityCallContext<'_>,
path: &RelativePath,
kind: RemoveKind,
) -> Result<(), AdapterError>;
fn rename(
&self,
context: &mut CapabilityCallContext<'_>,
from: &RelativePath,
to_directory: &dyn DirectoryHandle,
to: &RelativePath,
replace: bool,
) -> Result<(), AdapterError>;
}
pub trait FileHandle: Send + 'static {
fn read_to_end(
&mut self,
context: &mut CapabilityCallContext<'_>,
max_bytes: Option<NonZeroU64>,
) -> Result<Vec<u8>, AdapterError>;
fn write_all(
&mut self,
context: &mut CapabilityCallContext<'_>,
bytes: &[u8],
) -> Result<(), AdapterError>;
fn metadata(
&self,
context: &mut CapabilityCallContext<'_>,
) -> Result<PublicMetadata, AdapterError>;
}adapter契約:
- root内判定と利用を同じdirectory/file handleへbindする。
canonicalizeでcheck後に元pathをstd::fsへ渡す実装は禁止。 - Unix
openatだけに依存しない。Windows handle、capability filesystem、in-memory adapter等で同じ契約を実現できる。 - platformがroot拘束とsymlink policyを保証できなければ
SecureResolutionUnsupported。文字列prefix checkへfallbackしない。script filesystem操作ではcanonicalhosterror(codesecure_resolution_unsupported)へ変換してcatch可能とし、未捕捉ならRuntimeErrorとなる。link中のresolverで発生してscript handlerが存在しない場合だけterminalHostErrorへ変換する。対象fileのread/write/deleteは行わない。 DenyAllは途中/final symlinkを拒否。FollowWithinRootは解決先を同じroot handleへ拘束。OperateOnFinalEntryは中間symlinkをroot内へ拘束し、delete/renameおよびfollow_final=falseのmetadataだけがfinal symlink entry自体を扱える。Read/Write/Create/Listはfinal symlinkを拒否する。- create/write/appendはdangling final symlinkを追従しない。targetをsecureにbindできなければ拒否。
- renameはsource
Delete、destinationCreate、replace時はdestinationDeleteも必要。両handle認可後、単一rename call。 - case/Unicode aliasはadapterがfilesystem規則でroot内拘束する。
| Script operation | Read | Write | Create | Delete | Metadata | List | Import |
|---|---|---|---|---|---|---|---|
read_file, read_lines |
✓ | ||||||
write_file, append_file |
✓ | ✓ | |||||
mkdir |
✓ | ||||||
remove, remove_dir |
✓ | ||||||
| exists/type/size | ✓ | ||||||
list_dir |
✓ | ✓ | |||||
rename no replace |
✓(to) | ✓(from) | |||||
rename replace |
✓(to) | ✓(from+to) | |||||
| Tsumugi import | ✓ |
builtin mappingは固定する。read_file/read_linesはReadExisting、write_fileはUpsert { Truncate }、append_fileはUpsert { Append }を使う。upsertはfileの存在を調べずWrite+Createを事前要求する。現行言語にWriteExisting/CreateNew専用builtinはなく、これらは将来またはhost adapter用である。removeはFileOrSymlink、remove_dirはEmptyDirectoryを使う。PublicMetadataは時刻、owner、absolute pathを公開せず、DirectoryEntry.nameが空、.、..、separator含有ならadapter contract violationとしてHostErrorにする。
Importはruntime Readと独立する。filesystem resolverが内部でImport rootを使う。Readだけでimportできず、Importだけでread_fileできない。
lexical path syntaxはcapability lookupより先に検証し、PathErrorをcatch可能なRuntimeErrorCode::Argumentへ変換する。syntaxが正しい場合だけcapability/mount/operationを調べ、不足時はadapter/OS call 0で内部Denialを生成する。script filesystem operationではsafe fieldだけからcatch可能なcanonical sandbox errorへ変換し、未捕捉ならRuntimeErrorとする。handler開始前のimport resolver denialだけはcatch不可terminal Deniedである。存在path/不存在pathを同じpublic denial code/messageにし、absolute host path、symlink、permissionを含めない。grant済みroot内のnot-found等だけがcatch可能なcanonical host errorまたは既存言語意味論のnull/falseになれる。denyをnull/falseへ変換しない。
CapabilitySetIdにはmount UTF-8 byte順で次をencodeする。
u64(root_count)
|| each root:
str(mount) || policy_id[16] || symlink_policy_u8
|| u64(operation_count) || sorted operation_u8
host OS pathやcredentialはencodeしない。policy内容が変わるとhostはpolicy IDを変えなければならない。
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProcessExit { policy_id: NonZeroU128 }
impl ProcessExit {
pub const fn new(policy_id: NonZeroU128) -> Self;
pub const fn policy_id(self) -> NonZeroU128;
}OS process終了権限ではなくexecutionをExecutionOutcome::Exited { code, usage }にする権限。grant済み0..=255はcatch不可terminal Exited、未grantはadapter/process/OS call 0で内部Denialを生成し、script実行中はcatch可能なcanonical capability error、未捕捉時はRuntimeErrorとする。library/callback/adapterはstd::process::exitを呼ばない。
pub trait ModuleSource: Send + 'static {
fn read_chunk(
&mut self,
context: &mut CapabilityCallContext<'_>,
max_bytes: NonZeroUsize,
) -> Result<ModuleChunk, AdapterError>;
}
pub enum ModuleChunk {
Bytes(Vec<u8>), // lenは1..=max_bytes
Eof,
}
pub trait ModuleResolver: Send + Sync + 'static {
fn policy_id(&self) -> NonZeroU128;
fn resolve(
&self,
context: &mut CapabilityCallContext<'_>,
request: ResolveRequest<'_>,
) -> Result<ResolvedModule, AdapterError>;
}
pub struct ResolveRequest<'a> {
pub importer: Option<&'a ModuleId>,
pub specifier: &'a str,
pub language_revision: LanguageRevision,
}
pub struct ResolvedModule {
pub id: ModuleId,
pub source: Box<dyn ModuleSource>,
pub classification: DataClassification,
}resolverはlink時だけ呼ぶ。Engineはsourceを64 KiB以下のchunkで読み、Phase 3のmodule/total limit NならN+1を蓄積する前に停止する。chunkがmaxを超えたadapterはHostErrorCode="resolver_contract_violation"。完了bytesをUTF-8検証し、invalidならmodule compile diagnostic。
ModuleIdは同じlogical moduleへ同じIDを返す。同ID/異hashはlink error。HTTP resolverをcoreへ内蔵しない。host adapterがnetwork/TLS/redirect/credential/timeout/size/cache/supply-chainを担う。
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct HostFunctionId(NonZeroU128);
impl HostFunctionId {
pub const fn new(value: NonZeroU128) -> Self;
pub const fn get(self) -> NonZeroU128;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Arity {
Exact(u16),
Range { min: u16, max: u16 },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct HostCost {
pub base_fuel: u64,
pub per_argument_fuel: u64,
pub per_value_unit_fuel: u64,
pub max_result_bytes: Option<NonZeroU64>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AuditValuePolicy { Omit, TypeOnly, LengthOnly }
#[derive(Clone, Debug)]
pub struct HostFunctionDescriptor {
pub id: HostFunctionId,
pub name: String,
pub arity: Arity,
pub cost: HostCost,
pub argument_audit: Vec<AuditValuePolicy>,
pub result_audit: AuditValuePolicy,
pub may_block: bool,
}
impl HostFunctionDescriptor {
pub fn validate(&self) -> Result<(), ConfigError>;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum HostCallError {
Host(HostError),
Control(ControlStop),
}
pub struct HostCallContext<'a> { inner: CapabilityCallContext<'a> }
impl HostCallContext<'_> {
pub fn check_control(&mut self) -> Result<(), HostCallError>;
pub fn charge_external(&mut self, units: u64) -> Result<(), HostCallError>;
}
pub trait HostFunction: Send + Sync + 'static {
fn descriptor(&self) -> &HostFunctionDescriptor;
fn call(
&self,
context: &mut HostCallContext<'_>,
arguments: &[Value],
) -> Result<Value, HostCallError>;
}
pub struct HostFunctionRegistryBuilder { /* private */ }
pub struct HostFunctionRegistry { /* immutable */ }
impl HostFunctionRegistry {
pub fn builder() -> HostFunctionRegistryBuilder;
pub fn get(&self, id: HostFunctionId) -> Option<&Arc<dyn HostFunction>>;
}
impl HostFunctionRegistryBuilder {
pub fn register(self, function: Arc<dyn HostFunction>) -> Result<Self, ConfigError>;
pub fn build(self) -> Result<HostFunctionRegistry, ConfigError>;
}nameは既存identifier grammarを満たす1..=64 UTF-8 bytesとする。host_ prefixは必須にしない。core keyword、print、core/context builtin、他host functionとの重複はbuild error。これによりhttp_requestのような承認済み業務名も登録できる。user bindingはcall siteでbuiltin/host fallbackより優先する。
validate()は次を強制する。Arity::Rangeはmin <= max。argument_auditが空なら全indexをOmitとする。空でない場合、Exact(n)では長さがn、Range { max, .. }では長さがmaxでなければerror。実callのindex i < actual_arityにはvector[i]を使い、actualより後のpolicyは無視する。maxは1024以下、nameは64 bytes以下、host function IDはregistry内で一意でなければならない。
registry登録とexecution grantは別。link時にname→IDを固定し、run中のname lookupやregistry差替えをしない。registered-but-not-grantedはcallback call 0で内部Denialを生成し、script call中はcatch可能なcanonical capability error、未捕捉時はRuntimeErrorとする。unknown nameは通常language name error。handler開始前に同じ判断が必要なcontrol-plane処理だけがterminal Deniedとなる。
- arityはargument式評価前に検証。不一致はcatch可能な通常argument error。
- Phase 2はcost metadataのvalidationとcatalog格納まで。Phase 3でgrant後・callback前にfuel/host-callを課金する。
- value unitは各Value nodeにつき1、Stringは加えてUTF-8 byte数、Listは要素、Dictはkey/valueを再帰加算する。checked u64加算しoverflowはBudgetExceeded。Function/Errorはnode 1だけ。現行非循環Valueを前提とし、循環導入時はvisited identityで二重計上しない。
- resultはPhase 3で
max_result_bytesとheap budgetをscriptへ渡す前に検査する。超過時、callback副作用はrollbackしないが全language-stateはterminal規則どおりrollbackし、auditのhost_effects_may_remainへ反映する。 - script call中の
HostCallError::Hostはcanonicalhostruntime errorへ変換してcatch可能とし、未捕捉ならRuntimeErrorとなる。HostCallError::Control(BudgetExceeded | DeadlineExceeded | Cancelled)はcatch不可terminalである。link/control-plane中でscript handlerが存在しないhost failureだけはterminalHostErrorとなる。業務not-found等は明示Valueまたは登録済みcanonical script errorで返す。
Phase 2はdescriptorにOmit、TypeOnly、LengthOnlyだけを許可し、runtime value本文を記録するpolicyを設けない。したがって完全taint trackingなしでもcore auditへargument/result本文は出ない。defaultはOmit。LengthOnlyは型と長さだけ、TypeOnlyは型だけで、内容hashも記録しない。
Phase 6のaudit sinkはこのmetadataを使う。secretを扱うfunctionはOmitを必須とし、host review事項とする。HostError.safe_message、panic eventへ本文、credential、absolute path、backtraceを入れない。
callback unwind panicはInternalFailure、context poison。callbackへEngine/ExecutionContext参照を渡さず同execution再入を禁止する。panic=abort、FFI UB、process exit/abort、無期限blockは捕捉不能。
| Script operation | Env | Clock | Stdin | Stdout | FS op | Exit | Resolver | HostFn grant |
|---|---|---|---|---|---|---|---|---|
args() |
— | — | — | — | — | — | — | — |
env() |
✓ | — | — | — | — | — | — | — |
now() |
— | ✓ | — | — | — | — | — | — |
input() |
— | — | ✓ | — | — | — | — | — |
print |
— | — | — | ✓ | — | — | — | — |
read_file |
— | — | — | — | Read | — | — | — |
write_file upsert |
— | — | — | — | Write+Create | — | — | — |
| metadata | — | — | — | — | Metadata | — | — | — |
list_dir |
— | — | — | — | Metadata+List | — | — | — |
| remove | — | — | — | — | Delete | — | — | — |
| import | — | — | — | — | optional resolver内部Import | — | ✓ | — |
exit() |
— | — | — | — | — | ✓ | — | — |
| registered host function | — | — | — | — | — | — | — | ✓ |
args()はrequest snapshotで、capabilityではない。
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum DenialCode {
CapabilityNotGranted,
OperationNotGranted,
ResourceNotGranted,
HostFunctionNotGranted,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct OperationId(String); // crate-defined fixed ASCII identifier
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct ResourceLabel(String); // hostがPublicと宣言したlabelだけ
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Denial {
pub code: DenialCode,
pub capability: CapabilityKind,
pub operation: OperationId,
pub public_resource: Option<ResourceLabel>,
}invalid pathはhost API misuseでなく、grantの有無を調べる前にcatch可能なcanonical argument errorとする。capability/mount/operation不足はOS/adapter call 0のDenialを生成する。script operationではそのsafe fieldだけからcanonical capability errorを作りcatch可能、link/control-planeではterminal Deniedにする。いずれも存在、absolute path、environment value、callback detailを含めない。
Phase 6 event schemaは決定性・実行時監査仕様第7節のExecutionStarted、CapabilityDecision、HostCallStarted、HostCallFinished、BudgetCharged、Yielded、Resumed、Terminalだけを使う。CapabilityAllowed、CapabilityDenied、ExecutionTerminated等の別event名を定義しない。host call denyも同じoperation ID/call IDでHostCallStarted→CapabilityDecision(Deny)→HostCallFinished(Denied)とし、adapter/OS call 0でpairを閉じる。Phase 2はdescriptor/redaction metadataとeventへの写像を固定するがeventを発行せず、Phase 6でbounded journal、sequence、ack、fail-closedを有効化する。
唯一のCLI文法は次期意味論・実装決定第6節である。本節はそのOPTIONSに属するprofile/capability optionの値検証とauthority構築だけを定める。
tsumugi [OPTIONS] [SCRIPT [ARGS...]]
OPTIONS:
--vm
--profile safe|legacy
--allow-env KEY
--allow-clock
--allow-script-stdin
--allow-exit
--deny-stdout
--fs-root NAME=PATH
--fs-op NAME=OP[,OP...]
--allow-import-root NAME=PATH
--help
--version
--
- option解析中の最初のpositionalをSCRIPTとし、それ以後の全tokenはoption風でも再解釈せずscript argumentへ順序どおり渡す。
--はoption terminatorであり、後続tokenがなければSCRIPTなしとしてREPLを起動する。 - SCRIPT
-はstdin source、SCRIPTなしはREPLである。capability optionは各REPL executionへ同じfrozen profileとして適用する。 --vmはidempotent。profile/boolean option重複、unknown option、option値欠落、同NAME・同KEY重複、profileとの不正組合せ、非UTF-8は副作用前にstderrへ診断しexit 1とする。CLI usage errorにexit 2を使わない。NAMEはMountName grammar。OPはread|write|create|delete|metadata|list。importは--fs-opでは受理せずresolver optionだけで付与する。--fs-root NAME=PATHと--fs-op NAME=...は1対1必須。CLIはroot directory handleをexecution作成前に開き、safe profileのsymlink policyを常にDenyAllとする。- scriptは
@NAME/...でrootを選ぶ。unqualified pathにはNAMEdefaultが必要である。 --allow-import-root NAME=PATHはspecifier@NAME/...だけを解決するfilesystemModuleResolverを作り、runtime filesystemをgrantしない。- capability optionはsafe profileだけで受理し、legacyとの同時指定はusage error 1とする。
--allow-env KEYはCLI execution作成時にprocess envのOS上のkey同一性で完全一致する値をsnapshotする。不存在はsnapshotへ入れずwarningなし。protected runtime keyは第5節のOS-aware判定でusage error 1とする。--allow-clockはsystem clock、--allow-script-stdinはscript Input、--allow-exitはProcessExitをprofile builderへ明示grantする。- safe file/stdin/REPL profileはstdout adapterを
CapabilitySetBuilder::stdoutで明示grantする既定構成で、ambient stdio accessではない。--deny-stdoutはそのgrantを除去する。REPL prompt出力はscript capability外のCLI I/Oである。 - 公式CLIはhost function registryを持たないためhost function grant optionを提供しない。embedding hostが登録とexecution grantを別々に行う。
--profile safeはRelease N以降の既定。root sourceはCLIがfileまたはstdinから明示読込みしてEngineへ渡し、script filesystem capabilityへ暗黙追加しない。profile builderはstdout adapterを明示grantし、arguments snapshotをrequestへ設定する。environment、clock、script stdin、filesystem、exit、resolver、host functionはoptionなしでは付与しない。このstdout既定はCapabilitySetに実在する明示authorityであり、coreがprocess stdoutへambient接続することを許さない。
--profile legacyだけが互換用authorityを明示構築する。
- process envをstart前snapshotし
TSUMUGI_を除外。 - system clock、script stdin/stdout、ProcessExitをgrant。
- CLI adapterだけが
TSUMUGI_SANDBOXとTSUMUGI_ENV_ALLOWを1回読む。core evaluator/builtinは読まない。 - sandbox設定あり: comma-separated host rootsをstart時CWD基準でabsolute化してdirectory handleとして開き、
legacy0,legacy1, ... mountへ順序固定する。rootが1つならdefaultaliasも同じhandle/policy IDへ付ける。旧absolute pathはOS lexical componentで各rootへのrelative candidateを作り、含むrootのうちcomponent数が最長のものを選ぶ。同じ長さの候補が複数ならprofile構築error。旧relative pathはsnapshot CWDを含む最長rootへrouteし、該当rootがなければadapter/OS call 0の内部Denialからcatch可能なcanonicalsandboxerrorへ変換する。候補選択後の解決とI/Oはroot handleだけを使う。 - sandbox未設定/空: legacy専用
LegacyFilesystemTranslatorへunrestricted filesystem namespace authorityを明示grantしsecurity warningをstderrへ1件出す。Unixは/とsnapshot CWD handle、Windowsは要求されたvolume rootとsnapshot CWD handleへ安全なhandle操作を使う。authority自体がunrestrictedなだけでcheck/use文字列fallbackは使わない。 - env allow設定あり: comma-separated exact keyまたは末尾
*prefixだけsnapshot。未設定/空はTSUMUGI_以外を全snapshotしwarning 1件。 - translatorは旧absolute pathをnamespace root handle、旧relative pathをstart時CWD handleへ変換する。symlink操作はhandle adapter契約に従う。secure handleを実装できないplatformではfilesystem capability構築を拒否。
- legacy authorityは
ExecutionStarted.capability_policy_hashと通常のcanonical decision/host-call eventで観測する。warning専用の別audit eventを追加しない。Phase 2完了条件はstderr warning、Phase 6では同じ8 event schemaとfail-closedを使う。
| Release | default | 旧環境変数 |
|---|---|---|
| N-1 | legacy | 読む。起動時deprecation warning。safeを選択可 |
| N | safe | safeでは無視。legacyは明示flag必須 |
| N+1 | safe | coreから直接参照削除済み。legacy adapter継続可否は別判断 |
OnceLock process-global policyをper-execution capabilityへ再利用しない。
- builderはbuildで消費、setはimmutable。
- cloneは同じset ID/adapter Arc。clone片方だけのrevokeなし。
- start後のbuilder、registry、process env、legacy env var変更は実行へ反映しない。
- 緊急取消はexecution全体の
CancellationToken。個別authorityを途中revokeしてresumeしない。 - contextは前回setを保存しない。次requestがemptyならdeny-by-default。
- scriptがenv/argumentsをglobal Valueへ保存した場合、保存したexecutionが
Completed/Exitedでcommitしたときだけ残り得る。他terminalではAUD-024により開始時点へrollbackする。別tenant再利用前にclear_user_stateまたは新contextを使う。
HTTP client、DB driver、SMTP、cloud SDKをcoreへ追加しない。hostは任意URLの汎用関数よりhost_orders_lookup(order_id)のような最小業務操作をhost functionとして公開する。
credentialはhost内部保持。destination allow-list、TLS、redirect、timeout、request/response size、transaction、rate limitはhost adapter責任。外部service failureはscript call中ならcatch可能canonical host error、handler開始前ならterminal HostError、業務not-foundは明示Valueとする。
core/context builtinは次期意味論・実装決定第13節の単一BuiltinSpec registryを正本とし、tree dispatch、VM/compiler認識、arity、context metadata、generated documentationをそこから導出する。builtin_core.rs、builtin.rs、compiler.rsへ独立した公開builtin名一覧を残さない。
HostFunctionRegistryはhostがruntimeに構築する別registryであり、BuiltinSpecへhost descriptorを混在させない。ただしEngine build時にkeyword、BuiltinSpec、既登録host functionとの名前衝突を一括検査し、link時にname→HostFunctionIdを固定する。tree/VM/compilerのcallable resolutionは「user binding → public builtin → host registry」の共通resolverを使い、host registryを第4の手書き名前一覧としてbackendごとに複製しない。
AUD-049のcontract testは、BuiltinSpec全entryのtree/VM/compiler/generated docs一致、HostFunctionRegistryの衝突検査、同じregistry snapshotからのhost name resolution、unknown/registered/ungranted/grantedの4組合せを検証する。
| Slice | Phase | 内容 | 完了条件 |
|---|---|---|---|
| C1 | 2 | CapabilitySet/Denial/dispatcher/empty default | adapter call前deny |
| C2 | 2 | CallableCatalog統合 | AUD-049 |
| C3 | 2 | Environment/args/Clock | ambient process read 0 |
| C4 | 2 | Input/Output trait | deny/EOF/HostError分離 |
| C5 | 2 | mount routing/DirectoryHandle/FileHandle | path/symlink/oracle |
| C6 | 2 | ModuleResolver/stream/link接続 | runtime resolver 0 |
| C7 | 2 | ProcessExit | structured Exited |
| C8 | 2 | HostFunctionRegistry metadata/grant | name/arity/redaction/panic |
| C9 | 2 | CLI safe/legacy | AUD-018、migration warning |
| C10 | 2 | ambient実装削除 | core直接OS access 0 |
| C11 | 3 | finite meter/deadline/runtime cancel/cost | N境界、reservation |
| C12 | 4 | cooperative adapter/ticket/waker | Ready/Pending、backpressure、cancel |
| C13 | 6 | canonical audit sink/event/sequence | 8 event、fail-closed、correlation |
C1→C2、C3/C4/C5/C7を並行、C6はstream後、C8はBuiltinSpec衝突検査後、C9/C10を最後に行う。C11〜C13をPhase 2完了条件へ含めないが、Phase 2のpublic signatureは後続最終型と競合させない。
| ID | Phase | 基準 |
|---|---|---|
| CAP-AT-01 | 2 | emptyでenv/clock/stdin/stdout/FS 7操作/exit/resolver/host functionを各1回試しadapter/callback/OS call 0。script操作はcatch可能capability error、link/control-planeはterminal Denied |
| CAP-AT-02 | 2 | grantした1操作だけ成功し隣接操作deny |
| CAP-AT-03 | 2 | clone ID/権限一致、変更/revoke APIなし、start後snapshot不変 |
| CAP-AT-04 | 2/3 | cancel後、新setで別実行可。Phase 3で旧実行だけCancelled |
| CAP-AT-05 | 2 | env snapshot後process env変更が不変、missing=null。capability不足はenvironment adapter call 0のcatch可能capability errorで、未捕捉時RuntimeError |
| CAP-AT-06 | 2 | fixed clockで同結果。Clockなしはtrait call 0のcatch可能capability errorで、未捕捉時RuntimeError |
| CAP-AT-07 | 3 | input N-1/N成功、N+1は蓄積前BudgetExceeded。改行なし同じ |
| CAP-AT-08 | 3 | output N-1/N成功、N+1はOutput call 0 |
| CAP-AT-09 | 2/3 | EOF / AdapterError::Host / authority不足 / AdapterError::Controlを、順にnull / catch可能host error / catch可能capability error / terminal control outcomeへ分離する。第4節のcanonical name全件をexact matchし、link前failureはterminal channelへ分離 |
| CAP-AT-10 | 2 | path absolute/dot/dotdot/NUL/empty/backslash/drive/UNC/prefix衝突を拒否 |
| CAP-AT-11 | 2/7 | symlink途中/final/dangling/renameを全policyで検証しroot外変更0。raceをstress gate化 |
| CAP-AT-12 | 2 | 許可外存在/不存在はadapter call 0、同denial。root内だけnot-found可 |
| CAP-AT-13 | 2 | FS operation matrix全組合せで1 authority欠落ごとI/O前に内部Denialを生成。script operationはcatch可能sandbox error、未捕捉時RuntimeError |
| CAP-AT-14 | 2 | secure resolution不能adapterはfail closed、文字列fallback 0。script operationはcatch可能host error、未捕捉時RuntimeError |
| CAP-AT-15 | 3 | file read/write N-1/N成功、N+1はallocation/write前BudgetExceeded |
| CAP-AT-16 | 2 | importありresolverなしはresolver call 0のterminal Denied。grant時linkだけ、run 0 |
| CAP-AT-17 | 3 | source N-1/N成功、N+1はchunk蓄積前拒否 |
| CAP-AT-18 | 2 | exit capabilityなしはprocess/OS call 0のcatch可能capability error(未捕捉時RuntimeError)、あり0/255 Exited、-1/256 RuntimeError、process継続 |
| CAP-AT-19 | 2/3 | Phase 2でregistered/granted 4組合せとarity。ungrantedはcallback 0のcatch可能capability error(未捕捉時RuntimeError)。Phase 3でfuel N-1/N/N+1 |
| CAP-AT-20 | 2 | catalog/tree/VM/compiler/generated docsの名前・arity・metadata完全一致、重複build error |
| CAP-AT-21 | 2/3 | callback success/catch可能host error/panicを分離。Phase 3でresult N+1 BudgetExceeded。link前host failureはterminal HostError |
| CAP-AT-22 | 2/6 | Omit/TypeOnly/LengthOnly serializerにfake secret本文0。Phase 6でsink eventも同じ |
| CAP-AT-23 | 2 | safe profileでroot source読込みとprofile builderが明示grantしたstdout以外ambient call 0。--deny-stdout時はstdout call 0で、全option mapping一致 |
| CAP-AT-24 | 2/6 | legacy互換、empty sandbox/env allowはstderr warning各1。Phase 6でも別eventを追加せずcanonical schemaとfail-closedを維持 |
| CAP-AT-25 | N-1/N | default profile差とwarningをgolden固定 |
| CAP-AT-26 | 2 | safeで旧環境変数変更がset ID/挙動へ影響0 |
| CAP-AT-27 | 2 | adapter/CLI以外のprocess env、runtime fs、stdio、process exit、ambient clock直接利用0 |
| CAP-AT-28 | 2 | CapabilitySetId/filesystem encodingのgolden bytes/hash固定 |
| CAP-AT-29 | 2 | 複数mount qualified/default/missing/duplicateのroutingを完全一致検証 |
| CAP-AT-30 | 6 | ExecutionStarted、CapabilityDecision、host call pair、budget/yield/resume、最後のTerminalに欠番・重複がなく、sink failureでfail-closed |
- Phase 0: security boundaryではないこと、OS責任、residual riskは脅威モデル。
- Phase 1:
CapabilitySetは実行予算・協調実行仕様のfiniteExecutionRequestへmoveし、terminal outcomeは組み込みAPI仕様、catch規則は次期意味論・実装決定に従う。 - Phase 2: C1〜C10とPhase 2のCAP-ATを完了条件とする。環境変数allow-listや現行sandboxだけでは完了でない。
- Phase 3/4: finite budget/controlとcooperative
Pendingは実行予算・協調実行仕様を正本とし、Phase 2同期traitを置換しない。 - Phase 6: event enum、redaction、bounded journal、sink failureは決定性・実行時監査仕様を正本とし、
FailClosed以外や別event名を追加しない。 - AUD-020: canonicalize/check/useをportable path-handleへ置換し、dangling symlink、TOCTOU、存在oracleをCAP-AT-10〜14で検証。
- AUD-018:
args()はrequest snapshotだけを読み、CLIが複数script引数を渡す。 - AUD-049: 単一catalogとCAP-AT-20を完了条件とする。