fix: restore standalone logical workspace scope
This commit is contained in:
@@ -8,6 +8,7 @@ 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 {
|
||||
@@ -25,10 +26,15 @@ pub struct BuiltinProfileResource {
|
||||
}
|
||||
|
||||
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] = &[
|
||||
@@ -41,11 +47,18 @@ pub const BUILTIN_PROFILE_RESOURCES: &[BuiltinProfileResource] = &[
|
||||
},
|
||||
BuiltinProfileResource {
|
||||
selector: Some(BUILTIN_DEFAULT_PROFILE),
|
||||
path: "profiles/default.dcdl",
|
||||
path: DEFAULT_PATH,
|
||||
source: include_str!("../../../resources/profiles/default.dcdl"),
|
||||
description: "Standalone Yoi coding profile.",
|
||||
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:coder"),
|
||||
path: "profiles/coder.dcdl",
|
||||
@@ -281,7 +294,11 @@ mod tests {
|
||||
assert_eq!(catalog.entrypoints.len() + 1, catalog.sources.len());
|
||||
assert_eq!(
|
||||
catalog.entrypoints.get(BUILTIN_DEFAULT_PROFILE),
|
||||
Some(&"profiles/default.dcdl".to_owned())
|
||||
Some(&DEFAULT_PATH.to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
catalog.entrypoints.get(BUILTIN_STANDALONE_PROFILE),
|
||||
Some(&"profiles/standalone.dcdl".to_owned())
|
||||
);
|
||||
assert!(catalog.digest().starts_with("sha256:"));
|
||||
}
|
||||
@@ -300,12 +317,31 @@ mod tests {
|
||||
assert_eq!(value["feature"]["manage_workdir"]["enabled"], false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standalone_profile_inherits_default_and_overrides_workspace_policy() {
|
||||
let value = resolve_builtin_profile_artifact(BUILTIN_STANDALONE_PROFILE)
|
||||
.expect("evaluate built-in standalone")
|
||||
.expect("standalone exists");
|
||||
assert_eq!(value["slug"], "standalone");
|
||||
assert_eq!(value["model"]["ref"], "codex-oauth/gpt-5.6-sol");
|
||||
assert_eq!(value["scope"]["intent"], "workspace_write");
|
||||
assert_eq!(value["scope"]["symlink_policy"], "logical");
|
||||
assert_eq!(value["delegation_scope"]["intent"], "workspace_write");
|
||||
assert_eq!(value["delegation_scope"]["symlink_policy"], "logical");
|
||||
assert_eq!(value["feature"]["sub_worker"]["enabled"], true);
|
||||
assert_eq!(value["feature"]["memory"]["enabled"], false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_cannot_escape_the_builtin_resource_catalog() {
|
||||
assert_eq!(
|
||||
resolve_import_path("profiles/default.dcdl", "./base.dcdl").as_deref(),
|
||||
Some("profiles/base.dcdl")
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_import_path("profiles/standalone.dcdl", "./default.dcdl").as_deref(),
|
||||
Some(DEFAULT_PATH)
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_import_path("profiles/default.dcdl", "../outside.dcdl"),
|
||||
None
|
||||
|
||||
@@ -10,8 +10,8 @@ mod scope;
|
||||
|
||||
pub use builtin_profile::{
|
||||
BUILTIN_DEFAULT_PROFILE, BUILTIN_PROFILE_CATALOG_ID, BUILTIN_PROFILE_RESOURCES,
|
||||
BuiltinProfileCatalogSnapshot, BuiltinProfileImport, BuiltinProfileResource,
|
||||
builtin_profile_catalog_snapshot, builtin_profile_entrypoints,
|
||||
BUILTIN_STANDALONE_PROFILE, BuiltinProfileCatalogSnapshot, BuiltinProfileImport,
|
||||
BuiltinProfileResource, builtin_profile_catalog_snapshot, builtin_profile_entrypoints,
|
||||
};
|
||||
pub use config::{
|
||||
CompactionConfigPartial, EngineManifestConfig, FileUploadLimitsPartial,
|
||||
|
||||
@@ -20,8 +20,8 @@ use crate::config::{
|
||||
use crate::model::{AuthRef, ModelManifest};
|
||||
use crate::{
|
||||
EngineManifestConfig, McpConfig, McpStdioCwdPolicy, Permission, ResolveError, ScopeConfig,
|
||||
ScopeRule, SkillsConfig, WebConfig, WorkerManifest, WorkerManifestConfig, WorkerMetaConfig,
|
||||
paths,
|
||||
ScopeRule, SkillsConfig, SymlinkPolicy, WebConfig, WorkerManifest, WorkerManifestConfig,
|
||||
WorkerMetaConfig, paths,
|
||||
};
|
||||
|
||||
const PROFILE_FORMAT_V1: &str = "yoi.profile.v1";
|
||||
@@ -698,6 +698,8 @@ struct ProfileScopeTable {
|
||||
intent: ProfileScopeIntent,
|
||||
#[serde(default)]
|
||||
deny_write: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
symlink_policy: SymlinkPolicy,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -938,10 +940,14 @@ fn profile_scope_intent_to_config(
|
||||
default_intent: Option<ProfileScopeIntent>,
|
||||
field: &'static str,
|
||||
) -> Result<ScopeConfig, ProfileError> {
|
||||
let (intent, deny_write) = match scope {
|
||||
Some(ProfileScopeConfig::Table(table)) => (Some(table.intent), table.deny_write),
|
||||
Some(ProfileScopeConfig::String(intent)) => (Some(intent), Vec::new()),
|
||||
None => (default_intent, Vec::new()),
|
||||
let (intent, deny_write, symlink_policy) = match scope {
|
||||
Some(ProfileScopeConfig::Table(table)) => {
|
||||
(Some(table.intent), table.deny_write, table.symlink_policy)
|
||||
}
|
||||
Some(ProfileScopeConfig::String(intent)) => {
|
||||
(Some(intent), Vec::new(), SymlinkPolicy::default())
|
||||
}
|
||||
None => (default_intent, Vec::new(), SymlinkPolicy::default()),
|
||||
};
|
||||
let Some(intent) = intent else {
|
||||
return Ok(ScopeConfig::default());
|
||||
@@ -969,7 +975,7 @@ fn profile_scope_intent_to_config(
|
||||
target: workspace_base.to_path_buf(),
|
||||
permission,
|
||||
recursive: true,
|
||||
symlink_policy: Default::default(),
|
||||
symlink_policy,
|
||||
}],
|
||||
deny,
|
||||
})
|
||||
@@ -1247,6 +1253,7 @@ mod tests {
|
||||
|
||||
for name in [
|
||||
"default",
|
||||
"standalone",
|
||||
"intake",
|
||||
"orchestrator",
|
||||
"companion",
|
||||
@@ -1386,12 +1393,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_default_resolves_as_a_standalone_local_capability_profile() {
|
||||
fn builtin_standalone_resolves_as_a_local_capability_profile() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let resolved = ProfileResolver::new()
|
||||
.with_workspace_base(tmp.path())
|
||||
.resolve_for_target(
|
||||
&ProfileSelector::source_named(ProfileRegistrySource::Builtin, "default"),
|
||||
&ProfileSelector::source_named(ProfileRegistrySource::Builtin, "standalone"),
|
||||
ProfileResolveOptions::with_worker_name("standalone-worker"),
|
||||
ProfileExecutionTarget::Standalone,
|
||||
)
|
||||
@@ -1405,17 +1412,25 @@ mod tests {
|
||||
path: None,
|
||||
provenance: Some(provenance),
|
||||
..
|
||||
} if name == "default" && provenance.starts_with("profiles/default.dcdl#sha256:")
|
||||
} if name == "standalone" && provenance.starts_with("profiles/standalone.dcdl#sha256:")
|
||||
));
|
||||
assert_eq!(
|
||||
resolved.manifest.model.ref_.as_deref(),
|
||||
Some("codex-oauth/gpt-5.6-sol")
|
||||
);
|
||||
assert!(resolved.manifest.feature.task.enabled);
|
||||
assert!(resolved.manifest.feature.web.enabled);
|
||||
assert!(resolved.manifest.feature.image.enabled);
|
||||
assert!(resolved.manifest.feature.sub_worker.enabled);
|
||||
assert!(resolved.manifest.scope.allow.iter().any(|rule| {
|
||||
rule.permission == protocol::Permission::Write && rule.target == tmp.path()
|
||||
rule.permission == protocol::Permission::Write
|
||||
&& rule.target == tmp.path()
|
||||
&& rule.symlink_policy == protocol::SymlinkPolicy::Logical
|
||||
}));
|
||||
assert!(resolved.manifest.delegation_scope.allow.iter().any(|rule| {
|
||||
rule.permission == protocol::Permission::Write && rule.target == tmp.path()
|
||||
rule.permission == protocol::Permission::Write
|
||||
&& rule.target == tmp.path()
|
||||
&& rule.symlink_policy == protocol::SymlinkPolicy::Logical
|
||||
}));
|
||||
assert!(!resolved.manifest.feature.memory.profile.enabled);
|
||||
assert!(!resolved.manifest.feature.ticket.enabled);
|
||||
|
||||
Reference in New Issue
Block a user