merge: profile override scope
This commit is contained in:
commit
a13868818c
|
|
@ -320,6 +320,21 @@ fn workspace_worktree_delegation(workspace_root: &Path) -> ScopeConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn append_missing_rules(target: &mut Vec<ScopeRule>, defaults: Vec<ScopeRule>) {
|
||||||
|
for rule in defaults {
|
||||||
|
if !target.contains(&rule) {
|
||||||
|
target.push(rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_scope_launch_defaults(scope: &mut ScopeConfig, defaults: ScopeConfig) {
|
||||||
|
// Profile resolution has already applied explicit profile/workspace override scope rules.
|
||||||
|
// Launch policy contributes runtime defaults on top rather than replacing those grants.
|
||||||
|
append_missing_rules(&mut scope.allow, defaults.allow);
|
||||||
|
append_missing_rules(&mut scope.deny, defaults.deny);
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_profile_launch_policy(
|
fn apply_profile_launch_policy(
|
||||||
manifest: &mut PodManifest,
|
manifest: &mut PodManifest,
|
||||||
workspace_root: &Path,
|
workspace_root: &Path,
|
||||||
|
|
@ -333,24 +348,28 @@ fn apply_profile_launch_policy(
|
||||||
};
|
};
|
||||||
match role {
|
match role {
|
||||||
Some(TicketRole::Orchestrator) => {
|
Some(TicketRole::Orchestrator) => {
|
||||||
manifest.scope = workspace_scope(workspace_root, Permission::Read, &[]);
|
let default_scope = workspace_scope(workspace_root, Permission::Read, &[]);
|
||||||
|
apply_scope_launch_defaults(&mut manifest.scope, default_scope);
|
||||||
manifest.delegation_scope = workspace_worktree_delegation(workspace_root);
|
manifest.delegation_scope = workspace_worktree_delegation(workspace_root);
|
||||||
}
|
}
|
||||||
Some(TicketRole::Intake) | Some(TicketRole::Reviewer) => {
|
Some(TicketRole::Intake) | Some(TicketRole::Reviewer) => {
|
||||||
manifest.scope = workspace_scope(workspace_root, Permission::Read, &[]);
|
let default_scope = workspace_scope(workspace_root, Permission::Read, &[]);
|
||||||
|
apply_scope_launch_defaults(&mut manifest.scope, default_scope);
|
||||||
manifest.delegation_scope = ScopeConfig::default();
|
manifest.delegation_scope = ScopeConfig::default();
|
||||||
}
|
}
|
||||||
Some(TicketRole::Coder) => {
|
Some(TicketRole::Coder) => {
|
||||||
manifest.scope = workspace_scope(workspace_root, Permission::Write, &[]);
|
let default_scope = workspace_scope(workspace_root, Permission::Write, &[]);
|
||||||
|
apply_scope_launch_defaults(&mut manifest.scope, default_scope);
|
||||||
manifest.delegation_scope = ScopeConfig::default();
|
manifest.delegation_scope = ScopeConfig::default();
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let worktree_root = workspace_root.join(".worktree");
|
let worktree_root = workspace_root.join(".worktree");
|
||||||
manifest.scope = workspace_scope(
|
let default_scope = workspace_scope(
|
||||||
workspace_root,
|
workspace_root,
|
||||||
Permission::Write,
|
Permission::Write,
|
||||||
std::slice::from_ref(&worktree_root),
|
std::slice::from_ref(&worktree_root),
|
||||||
);
|
);
|
||||||
|
apply_scope_launch_defaults(&mut manifest.scope, default_scope);
|
||||||
manifest.delegation_scope = workspace_worktree_delegation(workspace_root);
|
manifest.delegation_scope = workspace_worktree_delegation(workspace_root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -665,6 +684,22 @@ permission = "write"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scope_rule(target: &Path, permission: Permission) -> ScopeRule {
|
||||||
|
ScopeRule {
|
||||||
|
target: target.to_path_buf(),
|
||||||
|
permission,
|
||||||
|
recursive: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_scope_contains(rules: &[ScopeRule], target: &Path, permission: Permission) {
|
||||||
|
let expected = scope_rule(target, permission);
|
||||||
|
assert!(
|
||||||
|
rules.contains(&expected),
|
||||||
|
"expected scope rules to contain {expected:?}; got {rules:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn user_manifest_flag_is_not_accepted() {
|
fn user_manifest_flag_is_not_accepted() {
|
||||||
let err = Cli::try_parse_from(["yoi pod", "--user-manifest", "manifest.toml"]).unwrap_err();
|
let err = Cli::try_parse_from(["yoi pod", "--user-manifest", "manifest.toml"]).unwrap_err();
|
||||||
|
|
@ -754,6 +789,68 @@ permission = "write"
|
||||||
assert_eq!(manifest.worker.language, "manifest");
|
assert_eq!(manifest.worker.language, "manifest");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn profile_launch_preserves_workspace_override_scope_allow_in_final_manifest() {
|
||||||
|
let tmp = TempDir::new().unwrap();
|
||||||
|
let workspace = tmp.path().join("runtime-workspace");
|
||||||
|
let external = tmp.path().join("external-readable");
|
||||||
|
let yoi_dir = workspace.join(".yoi");
|
||||||
|
std::fs::create_dir_all(&workspace).unwrap();
|
||||||
|
std::fs::create_dir_all(&external).unwrap();
|
||||||
|
std::fs::create_dir_all(&yoi_dir).unwrap();
|
||||||
|
write(
|
||||||
|
&yoi_dir.join("override.local.toml"),
|
||||||
|
&format!(
|
||||||
|
r#"
|
||||||
|
[[scope.allow]]
|
||||||
|
target = "{}"
|
||||||
|
permission = "read"
|
||||||
|
recursive = true
|
||||||
|
"#,
|
||||||
|
external.display()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let profile = tmp.path().join("profile.lua");
|
||||||
|
write(
|
||||||
|
&profile,
|
||||||
|
r#"
|
||||||
|
local yoi = require("yoi")
|
||||||
|
return yoi.profile {
|
||||||
|
slug = "override-scope",
|
||||||
|
model = { scheme = "anthropic", model_id = "test-model" },
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let cli = Cli::try_parse_from([
|
||||||
|
"yoi pod",
|
||||||
|
"--workspace",
|
||||||
|
workspace.to_str().unwrap(),
|
||||||
|
"--profile",
|
||||||
|
profile.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (manifest, _loader) = resolve_manifest(&cli).unwrap();
|
||||||
|
let snapshot = serde_json::to_value(&manifest).unwrap();
|
||||||
|
let snapshot_scope: ScopeConfig =
|
||||||
|
serde_json::from_value(snapshot["scope"].clone()).unwrap();
|
||||||
|
|
||||||
|
assert_scope_contains(&manifest.scope.allow, &external, Permission::Read);
|
||||||
|
assert_scope_contains(&manifest.scope.allow, &workspace, Permission::Write);
|
||||||
|
assert_scope_contains(
|
||||||
|
&manifest.scope.deny,
|
||||||
|
&workspace.join(".worktree"),
|
||||||
|
Permission::Write,
|
||||||
|
);
|
||||||
|
assert_scope_contains(&snapshot_scope.allow, &external, Permission::Read);
|
||||||
|
assert_scope_contains(&snapshot_scope.allow, &workspace, Permission::Write);
|
||||||
|
assert_scope_contains(
|
||||||
|
&snapshot_scope.deny,
|
||||||
|
&workspace.join(".worktree"),
|
||||||
|
Permission::Write,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn profile_uses_selected_profile() {
|
fn profile_uses_selected_profile() {
|
||||||
let tmp = TempDir::new().unwrap();
|
let tmp = TempDir::new().unwrap();
|
||||||
|
|
@ -883,15 +980,15 @@ permission = "write"
|
||||||
|
|
||||||
assert!(called);
|
assert!(called);
|
||||||
assert_eq!(manifest.pod.name, "runtime-workspace");
|
assert_eq!(manifest.pod.name, "runtime-workspace");
|
||||||
assert_eq!(manifest.scope.allow.len(), 1);
|
assert_eq!(manifest.scope.allow.len(), 2);
|
||||||
assert_eq!(manifest.scope.allow[0].target, workspace);
|
assert_scope_contains(&manifest.scope.allow, tmp.path(), Permission::Write);
|
||||||
assert_eq!(manifest.scope.allow[0].permission, Permission::Write);
|
assert_scope_contains(&manifest.scope.allow, &workspace, Permission::Write);
|
||||||
assert_eq!(manifest.scope.deny.len(), 1);
|
assert_eq!(manifest.scope.deny.len(), 1);
|
||||||
assert_eq!(
|
assert_scope_contains(
|
||||||
manifest.scope.deny[0].target,
|
&manifest.scope.deny,
|
||||||
tmp.path().join("runtime-workspace/.worktree")
|
&tmp.path().join("runtime-workspace/.worktree"),
|
||||||
|
Permission::Write,
|
||||||
);
|
);
|
||||||
assert_eq!(manifest.scope.deny[0].permission, Permission::Write);
|
|
||||||
assert_eq!(manifest.delegation_scope.allow.len(), 2);
|
assert_eq!(manifest.delegation_scope.allow.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
manifest.delegation_scope.allow[0].target,
|
manifest.delegation_scope.allow[0].target,
|
||||||
|
|
@ -944,9 +1041,9 @@ permission = "write"
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(manifest.scope.allow.len(), 1);
|
assert_eq!(manifest.scope.allow.len(), 2);
|
||||||
assert_eq!(manifest.scope.allow[0].target, workspace);
|
assert_scope_contains(&manifest.scope.allow, tmp.path(), Permission::Write);
|
||||||
assert_eq!(manifest.scope.allow[0].permission, Permission::Read);
|
assert_scope_contains(&manifest.scope.allow, &workspace, Permission::Read);
|
||||||
assert!(manifest.scope.deny.is_empty());
|
assert!(manifest.scope.deny.is_empty());
|
||||||
assert_eq!(manifest.delegation_scope.allow.len(), 2);
|
assert_eq!(manifest.delegation_scope.allow.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user