feat(config-source): add confined read-only builtin Decodal imports

Share the explicit embedded Profile catalog with native and WASM evaluators. Preserve ordinary Decodal values, isolate builtin relative imports, reject reserved Workspace mutations, and capture the exact loaded builtin closure. Guard lazy import cycles and dependency depth before evaluation recurses.

Validation: config-source 48 tests, builtin-source 2 tests, manifest 185 tests; root cargo check, formatting and diff checks pass. Existing contract version/fingerprint stay unchanged; archive provenance binds builtin content instead of invalidating stored Workspace metadata.
This commit is contained in:
2026-10-07 03:35:59 +09:00
parent a64707ee69
commit 4343be86c1
9 changed files with 721 additions and 211 deletions
Generated
+9
View File
@@ -543,6 +543,13 @@ dependencies = [
"serde",
]
[[package]]
name = "builtin-source"
version = "0.1.0"
dependencies = [
"sha2 0.11.0",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
@@ -766,6 +773,7 @@ dependencies = [
name = "config-source"
version = "0.1.0"
dependencies = [
"builtin-source",
"decodal",
"decodal-language-service",
"decodal-language-tools",
@@ -2598,6 +2606,7 @@ version = "0.1.0"
dependencies = [
"agen",
"arc-swap",
"builtin-source",
"decodal",
"protocol",
"secrets",
+3
View File
@@ -25,6 +25,7 @@ members = [
"crates/tools",
"crates/fs-operation",
"crates/flow",
"crates/builtin-source",
"crates/config-source",
"crates/config-source-wasm",
"crates/workdir",
@@ -63,6 +64,7 @@ default-members = [
"crates/tools",
"crates/fs-operation",
"crates/flow",
"crates/builtin-source",
"crates/config-source",
"crates/config-source-wasm",
"crates/workdir",
@@ -116,6 +118,7 @@ runtime-api = { path = "crates/runtime-api" }
server-api = { path = "crates/server-api" }
secrets = { path = "crates/secrets" }
tools = { path = "crates/tools" }
builtin-source = { path = "crates/builtin-source" }
config-source = { path = "crates/config-source" }
fs-operation = { path = "crates/fs-operation" }
workdir = { path = "crates/workdir" }
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "builtin-source"
version = "0.1.0"
edition.workspace = true
license.workspace = true
publish = false
[dependencies]
sha2.workspace = true
+214
View File
@@ -0,0 +1,214 @@
//! Explicit compile-time public Decodal source catalog, shared by native and WASM hosts.
use sha2::{Digest, Sha256};
use std::collections::BTreeMap;
pub const BUILTIN_PROFILE_CATALOG_ID: &str = "builtin-profiles-v2";
pub const BUILTIN_DEFAULT_PROFILE: &str = "builtin:default";
pub const BUILTIN_STANDALONE_PROFILE: &str = "builtin:standalone";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinProfileImport {
pub specifier: &'static str,
pub resolved_path: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinProfileResource {
pub selector: Option<&'static str>,
pub path: &'static str,
pub source: &'static str,
pub description: &'static str,
pub imports: &'static [BuiltinProfileImport],
}
const BASE_PATH: &str = "profiles/base.dcdl";
pub const DEFAULT_PATH: &str = "profiles/default.dcdl";
const BASE_IMPORT: &[BuiltinProfileImport] = &[BuiltinProfileImport {
specifier: "./base.dcdl",
resolved_path: BASE_PATH,
}];
const DEFAULT_IMPORT: &[BuiltinProfileImport] = &[BuiltinProfileImport {
specifier: "./default.dcdl",
resolved_path: DEFAULT_PATH,
}];
const NO_IMPORTS: &[BuiltinProfileImport] = &[];
pub const BUILTIN_PROFILE_RESOURCES: &[BuiltinProfileResource] = &[
BuiltinProfileResource {
selector: None,
path: BASE_PATH,
source: include_str!("../../../resources/profiles/base.dcdl"),
description: "Shared built-in Profile defaults.",
imports: NO_IMPORTS,
},
BuiltinProfileResource {
selector: Some(BUILTIN_DEFAULT_PROFILE),
path: DEFAULT_PATH,
source: include_str!("../../../resources/profiles/default.dcdl"),
description: "Default Yoi coding profile.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some(BUILTIN_STANDALONE_PROFILE),
path: "profiles/standalone.dcdl",
source: include_str!("../../../resources/profiles/standalone.dcdl"),
description: "Standalone Yoi coding profile.",
imports: DEFAULT_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:standalone-subjektiv"),
path: "profiles/standalone-subjektiv.dcdl",
source: include_str!("../../../resources/profiles/standalone-subjektiv.dcdl"),
description: "Standalone with an explicitly connected local Subject.",
imports: &[BuiltinProfileImport {
specifier: "./standalone.dcdl",
resolved_path: "profiles/standalone.dcdl",
}],
},
BuiltinProfileResource {
selector: Some("builtin:standalone-subjektiv-consolidation"),
path: "profiles/standalone-subjektiv-consolidation.dcdl",
source: include_str!("../../../resources/profiles/standalone-subjektiv-consolidation.dcdl"),
description: "Delegated local Subject Job policy.",
imports: &[BuiltinProfileImport {
specifier: "./job.dcdl",
resolved_path: "profiles/job.dcdl",
}],
},
BuiltinProfileResource {
selector: Some("builtin:coder"),
path: "profiles/coder.dcdl",
source: include_str!("../../../resources/profiles/coder.dcdl"),
description: "Ticket implementation with direct Reviewer SubWorkers.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:companion"),
path: "profiles/companion.dcdl",
source: include_str!("../../../resources/profiles/companion.dcdl"),
description: "General assistance with Workspace tools.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:intake"),
path: "profiles/intake.dcdl",
source: include_str!("../../../resources/profiles/intake.dcdl"),
description: "Read-only intake and planning.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:reviewer"),
path: "profiles/reviewer.dcdl",
source: include_str!("../../../resources/profiles/reviewer.dcdl"),
description: "Independent review of a published Merge Request source.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:orchestrator"),
path: "profiles/orchestrator.dcdl",
source: include_str!("../../../resources/profiles/orchestrator.dcdl"),
description: "Workspace orchestration and Worker control.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:job"),
path: "profiles/job.dcdl",
source: include_str!("../../../resources/profiles/job.dcdl"),
description: "Result-only caller-selected Job policy.",
imports: NO_IMPORTS,
},
BuiltinProfileResource {
selector: Some("builtin:backend-job"),
path: "profiles/backend-job.dcdl",
source: include_str!("../../../resources/profiles/backend-job.dcdl"),
description: "Backend-owned bounded Job Worker.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:memory-consolidation"),
path: "profiles/memory-consolidation.dcdl",
source: include_str!("../../../resources/profiles/memory-consolidation.dcdl"),
description: "Internal Memory consolidation service.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:subjektiv-memory-consolidation"),
path: "profiles/subjektiv-memory-consolidation.dcdl",
source: include_str!("../../../resources/profiles/subjektiv-memory-consolidation.dcdl"),
description: "Delegated subject Memory consolidation service.",
imports: BASE_IMPORT,
},
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuiltinProfileCatalogSnapshot {
pub id: &'static str,
pub sources: BTreeMap<String, String>,
pub entrypoints: BTreeMap<String, String>,
pub imports: BTreeMap<String, String>,
}
impl BuiltinProfileCatalogSnapshot {
pub fn digest(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(self.id.as_bytes());
for (path, source) in &self.sources {
hasher.update((path.len() as u64).to_le_bytes());
hasher.update(path.as_bytes());
hasher.update((source.len() as u64).to_le_bytes());
hasher.update(source.as_bytes());
}
for (selector, path) in &self.entrypoints {
hasher.update((selector.len() as u64).to_le_bytes());
hasher.update(selector.as_bytes());
hasher.update((path.len() as u64).to_le_bytes());
hasher.update(path.as_bytes());
}
for (request, resolved_path) in &self.imports {
hasher.update((request.len() as u64).to_le_bytes());
hasher.update(request.as_bytes());
hasher.update((resolved_path.len() as u64).to_le_bytes());
hasher.update(resolved_path.as_bytes());
}
format!(
"sha256:{}",
hasher
.finalize()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>()
)
}
}
pub fn builtin_profile_catalog_snapshot() -> BuiltinProfileCatalogSnapshot {
let mut sources = BTreeMap::new();
let mut entrypoints = BTreeMap::new();
let mut imports = BTreeMap::new();
for resource in BUILTIN_PROFILE_RESOURCES {
sources.insert(resource.path.to_owned(), resource.source.to_owned());
for import in resource.imports {
imports.insert(
format!("{}\0{}", resource.path, import.specifier),
import.resolved_path.to_owned(),
);
}
if let Some(selector) = resource.selector {
entrypoints.insert(selector.to_owned(), resource.path.to_owned());
}
}
BuiltinProfileCatalogSnapshot {
id: BUILTIN_PROFILE_CATALOG_ID,
sources,
entrypoints,
imports,
}
}
pub fn builtin_profile_entrypoints() -> impl Iterator<Item = &'static BuiltinProfileResource> {
BUILTIN_PROFILE_RESOURCES
.iter()
.filter(|resource| resource.selector.is_some())
}
+41
View File
@@ -0,0 +1,41 @@
use builtin_source::{BUILTIN_PROFILE_RESOURCES, builtin_profile_catalog_snapshot};
use std::collections::BTreeSet;
#[test]
fn public_catalog_is_closed_and_explicit() {
let catalog = builtin_profile_catalog_snapshot();
assert_eq!(catalog.sources.len(), BUILTIN_PROFILE_RESOURCES.len());
assert!(catalog.digest().starts_with("sha256:"));
for resource in BUILTIN_PROFILE_RESOURCES {
assert!(resource.path.starts_with("profiles/"));
assert!(resource.path.ends_with(".dcdl"));
assert_eq!(catalog.sources[resource.path], resource.source);
for import in resource.imports {
assert!(catalog.sources.contains_key(import.resolved_path));
assert!(
resource
.source
.contains(&format!("import {:?}", import.specifier))
);
}
}
assert!(!catalog.sources.contains_key("prompts/default.md"));
}
#[test]
fn catalog_import_metadata_has_no_cycles() {
fn visit(path: &str, visiting: &mut BTreeSet<String>) {
assert!(visiting.insert(path.to_string()), "catalog cycle at {path}");
let resource = BUILTIN_PROFILE_RESOURCES
.iter()
.find(|resource| resource.path == path)
.unwrap();
for import in resource.imports {
visit(import.resolved_path, visiting);
}
visiting.remove(path);
}
for resource in BUILTIN_PROFILE_RESOURCES {
visit(resource.path, &mut BTreeSet::new());
}
}
+1
View File
@@ -6,6 +6,7 @@ license.workspace = true
publish = false
[dependencies]
builtin-source.workspace = true
decodal.workspace = true
decodal-language-service.workspace = true
decodal-language-tools.workspace = true
+442 -7
View File
@@ -1,5 +1,8 @@
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::sync::{Arc, Mutex};
use builtin_source::BUILTIN_PROFILE_RESOURCES;
use decodal::{
Data, Diagnostic, DiagnosticKind, Engine, HostEnvironment, ImportCandidate, ImportLoader,
@@ -22,6 +25,7 @@ pub const MAX_CHANGE_COUNT: usize = 256;
pub const MAX_ENTRY_BYTES: usize = 256 * 1024;
pub const MAX_TOTAL_BYTES: usize = 4 * 1024 * 1024;
pub const MAX_PATH_BYTES: usize = 512;
pub const MAX_IMPORT_DEPTH: usize = 32;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, ts_rs::TS)]
pub struct VirtualPath(String);
@@ -249,6 +253,7 @@ impl ConfigEntry {
content_type: ConfigContentType,
content: impl Into<String>,
) -> Result<Self, ConfigTreeError> {
validate_workspace_path(&path)?;
let content = content.into();
if content.len() > MAX_ENTRY_BYTES {
return Err(ConfigTreeError::LimitExceeded("entry bytes"));
@@ -282,6 +287,10 @@ impl ConfigTreeSnapshot {
let mut ordered = BTreeMap::new();
let mut total = 0usize;
for entry in entries {
validate_workspace_path(&entry.path)?;
if entry.content.len() > MAX_ENTRY_BYTES {
return Err(ConfigTreeError::LimitExceeded("entry bytes"));
}
total = total
.checked_add(entry.content.len())
.ok_or(ConfigTreeError::LimitExceeded("total bytes"))?;
@@ -363,6 +372,7 @@ impl ConfigTreeSnapshot {
let mut touched = BTreeSet::new();
for change in changes {
for path in change.paths() {
validate_workspace_path(path)?;
if !touched.insert(path.clone()) {
return Err(ConfigTreeError::PathChangedMoreThanOnce(path.clone()));
}
@@ -1086,6 +1096,26 @@ impl SnapshotEnvironment {
pub fn evaluate_contract(
&self,
contract: &ToolchainContract,
) -> Result<EvaluationResult, Vec<ConfigDiagnostic>> {
self.evaluate_with_builtin_sources(contract, Arc::default())
}
/// Capture only builtin sources actually loaded by evaluation, including transitive imports.
/// These immutable bytes are provenance for the evaluated value, not Runtime inputs.
pub fn builtin_import_sources(
&self,
contract: &ToolchainContract,
) -> Result<BTreeMap<String, String>, Vec<ConfigDiagnostic>> {
let sources = Arc::default();
self.evaluate_with_builtin_sources(contract, Arc::clone(&sources))?;
let captured = sources.lock().expect("builtin source capture lock").clone();
Ok(captured)
}
fn evaluate_with_builtin_sources(
&self,
contract: &ToolchainContract,
sources: Arc<Mutex<BTreeMap<String, String>>>,
) -> Result<EvaluationResult, Vec<ConfigDiagnostic>> {
if let Err(error) = contract.validate() {
return Err(vec![self.config_error(
@@ -1144,9 +1174,9 @@ impl SnapshotEnvironment {
"configured entrypoint is not Decodal source",
)]);
}
let mut engine = decodal::Engine::new(SnapshotImportLoader {
snapshot: self.snapshot.clone(),
});
let mut loader = SnapshotImportLoader::new(self.snapshot.clone());
loader.builtin_sources = sources;
let mut engine = decodal::Engine::new(loader);
let schema_module = engine
.add_root_source(
WORKSPACE_CONFIG_SCHEMA_SOURCE,
@@ -1424,9 +1454,7 @@ impl HostEnvironment for &SnapshotEnvironment {
type Loader = SnapshotImportLoader;
fn create_loader(&self) -> Self::Loader {
SnapshotImportLoader {
snapshot: self.snapshot.clone(),
}
SnapshotImportLoader::new(self.snapshot.clone())
}
fn configure_engine(&self, engine: &mut Engine<Self::Loader>) -> decodal::Result<()> {
@@ -1532,9 +1560,18 @@ impl HostEnvironment for ConfigAuthoringEnvironment<'_> {
#[derive(Debug, Clone)]
pub struct SnapshotImportLoader {
snapshot: ConfigTreeSnapshot,
builtin_sources: Arc<Mutex<BTreeMap<String, String>>>,
import_edges: BTreeMap<String, BTreeSet<String>>,
}
impl SnapshotImportLoader {
fn new(snapshot: ConfigTreeSnapshot) -> Self {
Self {
snapshot,
builtin_sources: Arc::default(),
import_edges: BTreeMap::new(),
}
}
pub fn resolve(
&self,
current_key: Option<&str>,
@@ -1555,6 +1592,70 @@ impl ImportLoader for SnapshotImportLoader {
specifier: &str,
) -> decodal::Result<LoadedImport> {
let path = self.resolve(current_key, specifier).map_err(import_error)?;
// Guard the import graph before returning a module to Decodal. In particular,
// cycles hidden inside lazy object fields must not recurse during materialization.
let current = current_key
.expect("resolve requires a source context")
.split("@sha256:")
.next()
.unwrap();
if self
.import_edges
.entry(current.to_string())
.or_default()
.insert(path.to_string())
{
let mut depths = BTreeMap::new();
let validation = self.import_edges.keys().try_for_each(|root| {
validate_import_graph(&self.import_edges, root, &mut BTreeSet::new(), &mut depths)
.map(|_| ())
});
if let Err(diagnostic) = validation {
// A language service can keep using its loader after a diagnostic.
// Only successful edges may be cached as already validated.
self.import_edges
.get_mut(current)
.unwrap()
.remove(path.as_str());
return Err(diagnostic);
}
}
if is_builtin_path(&path) {
let public_path = path.as_str().strip_prefix("$builtin/").unwrap_or("");
let resource = BUILTIN_PROFILE_RESOURCES
.iter()
.find(|resource| resource.path == public_path)
.ok_or_else(|| {
Diagnostic::new(
DiagnosticKind::Import,
Span::default(),
format!("unknown or non-public read-only builtin source: {path}"),
)
})?;
let mut captured = self
.builtin_sources
.lock()
.expect("builtin source capture lock");
captured.insert(path.to_string(), resource.source.to_string());
if captured.len() + self.snapshot.entries.len() > MAX_ENTRY_COUNT {
return Err(import_error(ConfigTreeError::LimitExceeded("entry count")));
}
let bytes: usize = captured.values().map(String::len).sum::<usize>()
+ self
.snapshot
.entries
.values()
.map(|entry| entry.content.len())
.sum::<usize>();
if resource.source.len() > MAX_ENTRY_BYTES || bytes > MAX_TOTAL_BYTES {
return Err(import_error(ConfigTreeError::LimitExceeded("total bytes")));
}
return Ok(LoadedImport::source(
format!("{path}@{}", digest_bytes(resource.source.as_bytes())),
path.as_str(),
resource.source,
));
}
let entry = self.snapshot.get(&path).ok_or_else(|| {
Diagnostic::new(
DiagnosticKind::Import,
@@ -1598,11 +1699,52 @@ impl ImportLoader for SnapshotImportLoader {
.map_err(import_error)?;
Ok(import_completions(&self.snapshot, current.as_ref(), prefix)
.into_iter()
.map(|specifier| ImportCandidate::new(specifier).with_detail("virtual config source"))
.map(|specifier| {
let builtin = specifier.starts_with("$builtin/")
|| current.as_ref().is_some_and(|p| is_builtin_path(p));
ImportCandidate::new(specifier).with_detail(if builtin {
"read-only builtin Decodal source"
} else {
"virtual config source"
})
})
.collect())
}
}
fn validate_import_graph(
edges: &BTreeMap<String, BTreeSet<String>>,
current: &str,
visiting: &mut BTreeSet<String>,
depths: &mut BTreeMap<String, usize>,
) -> decodal::Result<usize> {
if let Some(depth) = depths.get(current) {
return Ok(*depth);
}
if !visiting.insert(current.to_string()) {
return Err(Diagnostic::new(
DiagnosticKind::Cycle,
Span::default(),
format!("virtual config import cycle at {current}"),
));
}
if visiting.len() > MAX_IMPORT_DEPTH + 1 {
return Err(import_error(ConfigTreeError::LimitExceeded("import depth")));
}
let mut depth = 0;
if let Some(children) = edges.get(current) {
for child in children {
depth = depth.max(validate_import_graph(edges, child, visiting, depths)? + 1);
}
}
if depth > MAX_IMPORT_DEPTH {
return Err(import_error(ConfigTreeError::LimitExceeded("import depth")));
}
visiting.remove(current);
depths.insert(current.to_string(), depth);
Ok(depth)
}
fn snapshot_import_cache_key(entry: &ConfigEntry) -> String {
// The source id remains the virtual path for diagnostics and relative-import
// resolution. The cache identity also includes immutable source content so
@@ -1610,6 +1752,19 @@ fn snapshot_import_cache_key(entry: &ConfigEntry) -> String {
format!("{}@{}", entry.path, entry.content_digest)
}
fn is_builtin_path(path: &VirtualPath) -> bool {
path.as_str() == "$builtin" || path.as_str().starts_with("$builtin/")
}
fn validate_workspace_path(path: &VirtualPath) -> Result<(), ConfigTreeError> {
if is_builtin_path(path) {
return Err(ConfigTreeError::InvalidPath(format!(
"{path}: builtin namespace is read-only"
)));
}
Ok(())
}
pub fn resolve_import(
current: &VirtualPath,
specifier: &str,
@@ -1623,6 +1778,15 @@ pub fn resolve_import(
{
return Err(ConfigTreeError::InvalidImport(specifier.into()));
}
let current_builtin = is_builtin_path(current);
let explicit_builtin = specifier == "$builtin" || specifier.starts_with("$builtin/");
if current_builtin
&& !explicit_builtin
&& !specifier.starts_with("./")
&& !specifier.starts_with("../")
{
return Err(ConfigTreeError::InvalidImport(specifier.into()));
}
let mut components = if specifier.starts_with("./") || specifier.starts_with("../") {
current.parent_components()
} else {
@@ -1632,6 +1796,9 @@ pub fn resolve_import(
match component {
"" | "." => {}
".." => {
if (current_builtin || explicit_builtin) && components.len() <= 1 {
return Err(ConfigTreeError::ImportEscape(specifier.into()));
}
components
.pop()
.ok_or_else(|| ConfigTreeError::ImportEscape(specifier.into()))?;
@@ -1648,6 +1815,26 @@ pub fn import_completions(
prefix: &str,
) -> Vec<String> {
let mut candidates = BTreeSet::new();
for resource in BUILTIN_PROFILE_RESOURCES {
let specifier = format!("$builtin/{}", resource.path);
if specifier.starts_with(prefix) {
candidates.insert(specifier);
}
if let Some(current) = current.filter(|path| is_builtin_path(path)) {
let parent = current.parent_components().join("/");
if let Some(relative) =
format!("$builtin/{}", resource.path).strip_prefix(&format!("{parent}/"))
{
let relative = format!("./{relative}");
if relative.starts_with(prefix) {
candidates.insert(relative);
}
}
}
}
if current.is_some_and(is_builtin_path) {
return candidates.into_iter().collect();
}
for path in snapshot.entries.keys() {
if current == Some(path) {
continue;
@@ -3368,6 +3555,254 @@ mod tests {
assert!(diagnostics[0].message.contains("skills/broken/SKILL.md"));
}
#[test]
fn builtin_imports_are_ordinary_values_with_confined_transitive_sources() {
let snapshot = ConfigTreeSnapshot::from_entries(1, [
entry("main.dcdl", r#"import "$builtin/profiles/companion.dcdl" // { worker = { mode = "wip"; }; feature = { workspace_config = { enabled = true; }; }; }"#),
entry("profiles/base.dcdl", "{ slug = \"workspace-shadow\"; }"),
]).unwrap();
let environment = SnapshotEnvironment::new(snapshot);
let contract = ToolchainContract::new(1, vec![path("main.dcdl")], 1);
let result = environment.evaluate_contract(&contract).unwrap();
let value = &result.projections[0].data_json;
assert_eq!(value["slug"], "companion");
assert_eq!(value["worker"]["mode"], "wip");
assert_eq!(value["feature"]["workspace_config"]["enabled"], true);
assert_eq!(value["feature"]["ticket"]["enabled"], true);
let closure = environment.builtin_import_sources(&contract).unwrap();
assert_eq!(
closure.keys().cloned().collect::<Vec<_>>(),
vec![
"$builtin/profiles/base.dcdl",
"$builtin/profiles/companion.dcdl"
]
);
assert_eq!(
closure["$builtin/profiles/base.dcdl"],
BUILTIN_PROFILE_RESOURCES[0].source
);
// Intersections conflict on explicit builtin values, exactly like any other import.
let snapshot = ConfigTreeSnapshot::from_entries(
1,
[entry(
"main.dcdl",
r#"import "$builtin/profiles/companion.dcdl" & { slug = "different"; }"#,
)],
)
.unwrap();
assert!(
SnapshotEnvironment::new(snapshot)
.evaluate_contract(&contract)
.is_err()
);
}
#[test]
fn builtin_namespace_rejects_unknown_sources_escape_and_host_paths() {
let contract = ToolchainContract::new(1, vec![path("main.dcdl")], 1);
for specifier in [
"$builtin/profiles/private.dcdl",
"$builtin/prompts/default.md",
"$builtin/../profiles/base.dcdl",
"$builtin/profiles/../../profiles/base.dcdl",
"/etc/passwd",
"file:///etc/passwd",
"https://example.com/main.dcdl",
"builtin:companion",
] {
let source = format!("import {specifier:?}");
let snapshot = ConfigTreeSnapshot::from_entries(
1,
[
entry("main.dcdl", &source),
entry("profiles/base.dcdl", "{ spoof = true; }"),
],
)
.unwrap();
let diagnostics = SnapshotEnvironment::new(snapshot)
.evaluate_contract(&contract)
.unwrap_err();
assert_eq!(
diagnostics[0].kind, "import",
"{specifier}: {diagnostics:?}"
);
assert!(!diagnostics[0].message.is_empty());
}
let current = path("$builtin/profiles/companion.dcdl");
assert_eq!(
resolve_import(&current, "./base.dcdl").unwrap(),
path("$builtin/profiles/base.dcdl")
);
assert!(resolve_import(&current, "../../main.dcdl").is_err());
assert!(resolve_import(&current, "main.dcdl").is_err());
assert!(resolve_import(&current, "/main.dcdl").is_err());
}
#[test]
fn builtin_namespace_is_not_a_mutable_workspace_tree() {
let builtin = path("$builtin/profiles/companion.dcdl");
assert!(
ConfigEntry::new(builtin.clone(), ConfigContentType::Decodal, "{}")
.unwrap_err()
.to_string()
.contains("read-only")
);
for change in [
ConfigTreeChange::Create {
path: builtin.clone(),
content_type: ConfigContentType::Decodal,
content: "{}".into(),
},
ConfigTreeChange::Delete {
path: builtin.clone(),
expected_digest: "digest".into(),
},
ConfigTreeChange::Rename {
from: path("main.dcdl"),
to: builtin,
expected_digest: "digest".into(),
},
] {
assert!(
ConfigTreeSnapshot::empty()
.apply(&[change])
.unwrap_err()
.to_string()
.contains("read-only")
);
}
let environment = SnapshotEnvironment::new(ConfigTreeSnapshot::empty());
let completions = import_completions(
environment.snapshot(),
Some(&path("main.dcdl")),
"$builtin/profiles/",
);
assert_eq!(completions.len(), BUILTIN_PROFILE_RESOURCES.len());
assert!(environment.snapshot().list_prefix(None).is_empty());
}
#[test]
fn builtin_values_do_not_bypass_cycle_or_path_limits() {
let snapshot = ConfigTreeSnapshot::from_entries(1, [
entry("main.dcdl", r#"{ builtin = import "$builtin/profiles/companion.dcdl"; cycle = import "./cycle.dcdl"; }"#),
entry("cycle.dcdl", r#"import "./main.dcdl""#),
]).unwrap();
let contract = ToolchainContract::new(1, vec![path("main.dcdl")], 1);
let diagnostics = SnapshotEnvironment::new(snapshot)
.evaluate_contract(&contract)
.unwrap_err();
assert_eq!(diagnostics[0].kind, "cycle");
let mut loader = SnapshotImportLoader::new(ConfigTreeSnapshot::empty());
for index in 0..=MAX_IMPORT_DEPTH {
loader.import_edges.insert(
format!("{index}.dcdl"),
BTreeSet::from([format!("{}.dcdl", index + 1)]),
);
}
let diagnostic = validate_import_graph(
&loader.import_edges,
"0.dcdl",
&mut BTreeSet::new(),
&mut BTreeMap::new(),
)
.unwrap_err();
assert!(diagnostic.message.contains("import depth"));
let oversized = format!("$builtin/profiles/{}.dcdl", "x".repeat(MAX_PATH_BYTES));
assert!(matches!(
resolve_import(&path("main.dcdl"), &oversized),
Err(ConfigTreeError::LimitExceeded("path bytes"))
));
}
#[test]
fn rejected_import_graph_edges_stay_rejected_after_diagnostics() {
let mut loader = SnapshotImportLoader::new(
ConfigTreeSnapshot::from_entries(1, [entry("a.dcdl", "{}"), entry("b.dcdl", "{}")])
.unwrap(),
);
loader.load(Some("a.dcdl"), "./b.dcdl").unwrap();
for _ in 0..2 {
let diagnostic = loader.load(Some("b.dcdl"), "./a.dcdl").unwrap_err();
assert_eq!(diagnostic.kind, DiagnosticKind::Cycle);
}
}
#[test]
fn import_graph_shared_dependencies_are_memoized_without_losing_depth() {
let mut edges = BTreeMap::new();
for index in 0..=MAX_IMPORT_DEPTH {
edges.insert(
format!("{index}"),
BTreeSet::from([format!("{}", index + 1), format!("{}", index + 2)]),
);
}
let mut depths = BTreeMap::new();
assert!(validate_import_graph(&edges, "0", &mut BTreeSet::new(), &mut depths).is_err());
edges.remove(&MAX_IMPORT_DEPTH.to_string());
let mut depths = BTreeMap::new();
assert_eq!(
validate_import_graph(&edges, "0", &mut BTreeSet::new(), &mut depths).unwrap(),
MAX_IMPORT_DEPTH
);
assert_eq!(depths.len(), MAX_IMPORT_DEPTH + 2);
}
#[test]
fn snapshot_import_depth_is_checked_before_evaluation_recurses() {
let mut entries = Vec::new();
for index in 0..=MAX_IMPORT_DEPTH + 1 {
let source = if index == MAX_IMPORT_DEPTH + 1 {
"{}".to_string()
} else {
format!("import \"./{}.dcdl\"", index + 1)
};
entries.push(entry(&format!("{index}.dcdl"), &source));
}
let diagnostics =
SnapshotEnvironment::new(ConfigTreeSnapshot::from_entries(1, entries).unwrap())
.evaluate_contract(&ToolchainContract::new(1, vec![path("0.dcdl")], 1))
.unwrap_err();
assert!(
diagnostics
.iter()
.any(|diagnostic| diagnostic.message.contains("import depth"))
);
}
#[test]
fn builtin_dependencies_share_snapshot_count_and_byte_budgets() {
let contract = ToolchainContract::new(1, vec![path("main.dcdl")], 1);
let mut entries = vec![entry(
"main.dcdl",
r#"import "$builtin/profiles/companion.dcdl""#,
)];
for i in 1..MAX_ENTRY_COUNT {
entries.push(text_entry(&format!("unused/{i}.txt"), ""));
}
let environment =
SnapshotEnvironment::new(ConfigTreeSnapshot::from_entries(1, entries).unwrap());
let diagnostics = environment.evaluate_contract(&contract).unwrap_err();
assert!(diagnostics[0].message.contains("entry count"));
let mut entries = vec![entry(
"main.dcdl",
r#"import "$builtin/profiles/companion.dcdl""#,
)];
let source_len = entries[0].content.len();
for i in 0..16 {
let bytes = if i == 15 {
MAX_ENTRY_BYTES - source_len
} else {
MAX_ENTRY_BYTES
};
entries.push(text_entry(&format!("unused/{i}.txt"), &"a".repeat(bytes)));
}
let environment =
SnapshotEnvironment::new(ConfigTreeSnapshot::from_entries(1, entries).unwrap());
let diagnostics = environment.evaluate_contract(&contract).unwrap_err();
assert!(diagnostics[0].message.contains("total bytes"));
}
#[test]
fn host_environment_evaluation_uses_only_snapshot_imports() {
let snapshot = ConfigTreeSnapshot::from_entries(
+1
View File
@@ -6,6 +6,7 @@ license.workspace = true
rust-version.workspace = true
[dependencies]
builtin-source.workspace = true
arc-swap = "1"
agen = { workspace = true }
decodal.workspace = true
+1 -204
View File
@@ -2,213 +2,10 @@ use std::collections::BTreeMap;
use decodal::{Data, Engine, ImportLoader, LoadedImport};
use serde_json::{Map, Number, Value};
use sha2::{Digest, Sha256};
use crate::profile::ProfileError;
pub const BUILTIN_PROFILE_CATALOG_ID: &str = "builtin-profiles-v2";
pub const BUILTIN_DEFAULT_PROFILE: &str = "builtin:default";
pub const BUILTIN_STANDALONE_PROFILE: &str = "builtin:standalone";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinProfileImport {
pub specifier: &'static str,
pub resolved_path: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinProfileResource {
pub selector: Option<&'static str>,
pub path: &'static str,
pub source: &'static str,
pub description: &'static str,
pub imports: &'static [BuiltinProfileImport],
}
const BASE_PATH: &str = "profiles/base.dcdl";
const DEFAULT_PATH: &str = "profiles/default.dcdl";
const BASE_IMPORT: &[BuiltinProfileImport] = &[BuiltinProfileImport {
specifier: "./base.dcdl",
resolved_path: BASE_PATH,
}];
const DEFAULT_IMPORT: &[BuiltinProfileImport] = &[BuiltinProfileImport {
specifier: "./default.dcdl",
resolved_path: DEFAULT_PATH,
}];
const NO_IMPORTS: &[BuiltinProfileImport] = &[];
pub const BUILTIN_PROFILE_RESOURCES: &[BuiltinProfileResource] = &[
BuiltinProfileResource {
selector: None,
path: BASE_PATH,
source: include_str!("../../../resources/profiles/base.dcdl"),
description: "Shared built-in Profile defaults.",
imports: NO_IMPORTS,
},
BuiltinProfileResource {
selector: Some(BUILTIN_DEFAULT_PROFILE),
path: DEFAULT_PATH,
source: include_str!("../../../resources/profiles/default.dcdl"),
description: "Default Yoi coding profile.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some(BUILTIN_STANDALONE_PROFILE),
path: "profiles/standalone.dcdl",
source: include_str!("../../../resources/profiles/standalone.dcdl"),
description: "Standalone Yoi coding profile.",
imports: DEFAULT_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:standalone-subjektiv"),
path: "profiles/standalone-subjektiv.dcdl",
source: include_str!("../../../resources/profiles/standalone-subjektiv.dcdl"),
description: "Standalone with an explicitly connected local Subject.",
imports: &[BuiltinProfileImport {
specifier: "./standalone.dcdl",
resolved_path: "profiles/standalone.dcdl",
}],
},
BuiltinProfileResource {
selector: Some("builtin:standalone-subjektiv-consolidation"),
path: "profiles/standalone-subjektiv-consolidation.dcdl",
source: include_str!("../../../resources/profiles/standalone-subjektiv-consolidation.dcdl"),
description: "Delegated local Subject Job policy.",
imports: &[BuiltinProfileImport {
specifier: "./job.dcdl",
resolved_path: "profiles/job.dcdl",
}],
},
BuiltinProfileResource {
selector: Some("builtin:coder"),
path: "profiles/coder.dcdl",
source: include_str!("../../../resources/profiles/coder.dcdl"),
description: "Ticket implementation with direct Reviewer SubWorkers.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:companion"),
path: "profiles/companion.dcdl",
source: include_str!("../../../resources/profiles/companion.dcdl"),
description: "General assistance with Workspace tools.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:intake"),
path: "profiles/intake.dcdl",
source: include_str!("../../../resources/profiles/intake.dcdl"),
description: "Read-only intake and planning.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:reviewer"),
path: "profiles/reviewer.dcdl",
source: include_str!("../../../resources/profiles/reviewer.dcdl"),
description: "Independent review of a published Merge Request source.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:orchestrator"),
path: "profiles/orchestrator.dcdl",
source: include_str!("../../../resources/profiles/orchestrator.dcdl"),
description: "Workspace orchestration and Worker control.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:job"),
path: "profiles/job.dcdl",
source: include_str!("../../../resources/profiles/job.dcdl"),
description: "Result-only caller-selected Job policy.",
imports: NO_IMPORTS,
},
BuiltinProfileResource {
selector: Some("builtin:backend-job"),
path: "profiles/backend-job.dcdl",
source: include_str!("../../../resources/profiles/backend-job.dcdl"),
description: "Backend-owned bounded Job Worker.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:memory-consolidation"),
path: "profiles/memory-consolidation.dcdl",
source: include_str!("../../../resources/profiles/memory-consolidation.dcdl"),
description: "Internal Memory consolidation service.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
selector: Some("builtin:subjektiv-memory-consolidation"),
path: "profiles/subjektiv-memory-consolidation.dcdl",
source: include_str!("../../../resources/profiles/subjektiv-memory-consolidation.dcdl"),
description: "Delegated subject Memory consolidation service.",
imports: BASE_IMPORT,
},
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuiltinProfileCatalogSnapshot {
pub id: &'static str,
pub sources: BTreeMap<String, String>,
pub entrypoints: BTreeMap<String, String>,
pub imports: BTreeMap<String, String>,
}
impl BuiltinProfileCatalogSnapshot {
pub fn digest(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(self.id.as_bytes());
for (path, source) in &self.sources {
hasher.update((path.len() as u64).to_le_bytes());
hasher.update(path.as_bytes());
hasher.update((source.len() as u64).to_le_bytes());
hasher.update(source.as_bytes());
}
for (selector, path) in &self.entrypoints {
hasher.update((selector.len() as u64).to_le_bytes());
hasher.update(selector.as_bytes());
hasher.update((path.len() as u64).to_le_bytes());
hasher.update(path.as_bytes());
}
for (request, resolved_path) in &self.imports {
hasher.update((request.len() as u64).to_le_bytes());
hasher.update(request.as_bytes());
hasher.update((resolved_path.len() as u64).to_le_bytes());
hasher.update(resolved_path.as_bytes());
}
format!("sha256:{:x}", hasher.finalize())
}
}
pub fn builtin_profile_catalog_snapshot() -> BuiltinProfileCatalogSnapshot {
let mut sources = BTreeMap::new();
let mut entrypoints = BTreeMap::new();
let mut imports = BTreeMap::new();
for resource in BUILTIN_PROFILE_RESOURCES {
sources.insert(resource.path.to_owned(), resource.source.to_owned());
for import in resource.imports {
imports.insert(
format!("{}\0{}", resource.path, import.specifier),
import.resolved_path.to_owned(),
);
}
if let Some(selector) = resource.selector {
entrypoints.insert(selector.to_owned(), resource.path.to_owned());
}
}
BuiltinProfileCatalogSnapshot {
id: BUILTIN_PROFILE_CATALOG_ID,
sources,
entrypoints,
imports,
}
}
pub fn builtin_profile_entrypoints() -> impl Iterator<Item = &'static BuiltinProfileResource> {
BUILTIN_PROFILE_RESOURCES
.iter()
.filter(|resource| resource.selector.is_some())
}
pub use builtin_source::*;
pub(crate) fn resolve_builtin_profile_artifact(
selector: &str,