docs+runner+parser: SSOT+AST using finalized (legacy text inlining removed); provider verify reads nyash.toml; preflight warn hook; method-body guard removed; CURRENT_TASK updated for next JSON work
This commit is contained in:
@ -17,6 +17,8 @@ pub mod plugin_loader_v2;
|
||||
pub mod scheduler;
|
||||
pub mod semantics;
|
||||
pub mod unified_registry;
|
||||
pub mod provider_lock;
|
||||
pub mod provider_verify;
|
||||
// pub mod plugin_box; // legacy - 古いPluginBox
|
||||
// pub mod plugin_loader; // legacy - Host VTable使用
|
||||
pub mod extern_registry; // ExternCall (env.*) 登録・診断用レジストリ
|
||||
|
||||
38
src/runtime/provider_lock.rs
Normal file
38
src/runtime/provider_lock.rs
Normal file
@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* Provider Lock (skeleton)
|
||||
*
|
||||
* Phase 15.5 受け口: 型→Provider のロック状態を保持するための最小スケルトン。
|
||||
* 既定では挙動を変えず、環境変数により警告/エラー化のみ可能にする。
|
||||
*/
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static LOCKED: AtomicBool = AtomicBool::new(false);
|
||||
static WARN_ONCE: OnceLock<()> = OnceLock::new();
|
||||
|
||||
/// Return true when providers are locked
|
||||
pub fn is_locked() -> bool { LOCKED.load(Ordering::Relaxed) }
|
||||
|
||||
/// Lock providers (idempotent)
|
||||
pub fn lock_providers() { LOCKED.store(true, Ordering::Relaxed); }
|
||||
|
||||
/// Guard called before creating a new box instance.
|
||||
/// Default: no-op. When NYASH_PROVIDER_LOCK_STRICT=1, returns Err if not locked.
|
||||
/// When NYASH_PROVIDER_LOCK_WARN=1, prints a warning once.
|
||||
pub fn guard_before_new_box(box_type: &str) -> Result<(), String> {
|
||||
if is_locked() { return Ok(()); }
|
||||
let strict = std::env::var("NYASH_PROVIDER_LOCK_STRICT").ok().as_deref() == Some("1");
|
||||
let warn = std::env::var("NYASH_PROVIDER_LOCK_WARN").ok().as_deref() == Some("1");
|
||||
if strict {
|
||||
return Err(format!("E_PROVIDER_NOT_LOCKED: attempted to create '{}' before Provider Lock", box_type));
|
||||
}
|
||||
if warn {
|
||||
// Print once per process
|
||||
let _ = WARN_ONCE.get_or_init(|| {
|
||||
eprintln!("[provider-lock][warn] NewBox emitted before Provider Lock. Set NYASH_PROVIDER_LOCK_STRICT=1 to error.");
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
117
src/runtime/provider_verify.rs
Normal file
117
src/runtime/provider_verify.rs
Normal file
@ -0,0 +1,117 @@
|
||||
/*!
|
||||
* Provider Verify (skeleton)
|
||||
*
|
||||
* Phase 15.5 受け口: 起動時に最小の必須メソッドを検証するための軽量フック。
|
||||
* 既定はOFF。環境変数で warn/strict に切替える。
|
||||
*
|
||||
* Env:
|
||||
* - NYASH_PROVIDER_VERIFY=warn|strict
|
||||
* - NYASH_VERIFY_REQUIRED_METHODS="StringBox:length,concat;ArrayBox:push,get"
|
||||
* (optional; merged with nyash.toml definitions when present)
|
||||
*
|
||||
* nyash.toml (optional; merged when present):
|
||||
* - [verify.required_methods]
|
||||
* StringBox = ["length","concat"]
|
||||
* ArrayBox = ["push","get"]
|
||||
* or
|
||||
* - [verify.required_methods.StringBox]
|
||||
* methods = ["length","concat"]
|
||||
* - [types.StringBox]
|
||||
* required_methods = ["length","concat"]
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn parse_required_methods(spec: &str) -> HashMap<String, Vec<String>> {
|
||||
let mut map = HashMap::new();
|
||||
for part in spec.split(';') {
|
||||
let p = part.trim();
|
||||
if p.is_empty() { continue; }
|
||||
if let Some((ty, rest)) = p.split_once(':') {
|
||||
let methods: Vec<String> = rest
|
||||
.split(',')
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
if !methods.is_empty() {
|
||||
map.insert(ty.trim().to_string(), methods);
|
||||
}
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
fn load_required_methods_from_toml() -> HashMap<String, Vec<String>> {
|
||||
let mut map: HashMap<String, Vec<String>> = HashMap::new();
|
||||
let text = match std::fs::read_to_string("nyash.toml") { Ok(s) => s, Err(_) => return map };
|
||||
let doc: toml::Value = match toml::from_str(&text) { Ok(v) => v, Err(_) => return map };
|
||||
|
||||
// Helper: add entry if array-of-strings
|
||||
let mut add_arr = |ty: &str, arr: &toml::Value| {
|
||||
if let Some(a) = arr.as_array() {
|
||||
let mut v: Vec<String> = Vec::new();
|
||||
for e in a { if let Some(s) = e.as_str() { let s = s.trim(); if !s.is_empty() { v.push(s.to_string()); } } }
|
||||
if !v.is_empty() { map.insert(ty.to_string(), v); }
|
||||
}
|
||||
};
|
||||
|
||||
// 1) [verify.required_methods]
|
||||
if let Some(vrfy) = doc.get("verify") {
|
||||
if let Some(req) = vrfy.get("required_methods") {
|
||||
if let Some(tbl) = req.as_table() {
|
||||
for (k, v) in tbl.iter() {
|
||||
if v.is_array() { add_arr(k, v); continue; }
|
||||
if let Some(t) = v.as_table() { if let Some(m) = t.get("methods") { add_arr(k, m); } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2) [types.<TypeName>].required_methods
|
||||
if let Some(types) = doc.get("types") {
|
||||
if let Some(tbl) = types.as_table() {
|
||||
for (k, v) in tbl.iter() {
|
||||
if let Some(t) = v.as_table() { if let Some(m) = t.get("required_methods") { add_arr(k, m); } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
pub fn verify_from_env() -> Result<(), String> {
|
||||
let mode = std::env::var("NYASH_PROVIDER_VERIFY").unwrap_or_default();
|
||||
let mode = mode.to_ascii_lowercase();
|
||||
if !(mode == "warn" || mode == "strict") { return Ok(()); }
|
||||
|
||||
// Merge: nyash.toml + env override
|
||||
let mut req = load_required_methods_from_toml();
|
||||
let spec = std::env::var("NYASH_VERIFY_REQUIRED_METHODS").unwrap_or_default();
|
||||
if !spec.trim().is_empty() {
|
||||
let env_map = parse_required_methods(&spec);
|
||||
for (k, v) in env_map { req.entry(k).or_default().extend(v); }
|
||||
}
|
||||
if req.is_empty() { return Ok(()); }
|
||||
|
||||
let host = crate::runtime::plugin_loader_unified::get_global_plugin_host();
|
||||
let host = host.read().unwrap();
|
||||
|
||||
let mut failures: Vec<String> = Vec::new();
|
||||
for (ty, methods) in req.iter() {
|
||||
for m in methods {
|
||||
match host.resolve_method(ty, m) {
|
||||
Ok(_) => { /* ok */ }
|
||||
Err(_e) => failures.push(format!("{}.{}", ty, m)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if failures.is_empty() { return Ok(()); }
|
||||
let msg = format!(
|
||||
"Provider verify failed ({}): missing methods: {}",
|
||||
mode,
|
||||
failures.join(", ")
|
||||
);
|
||||
if mode == "warn" { eprintln!("[provider-verify][warn] {}", msg); Ok(()) } else { Err(msg) }
|
||||
}
|
||||
Reference in New Issue
Block a user