ticket: use boolean ticket capabilities
This commit is contained in:
@@ -19,8 +19,8 @@ use crate::plugin::PluginConfig;
|
||||
use crate::{
|
||||
CompactionConfig, EngineManifest, FeatureConfig, FeatureFlagConfig, FileUploadLimits,
|
||||
McpConfig, McpEnvValue, McpStdioCwdPolicy, MemoryConfig, ScopeConfig, SessionConfig,
|
||||
SkillsConfig, TicketFeatureAccessConfig, TicketFeatureConfig, ToolOutputLimits,
|
||||
ToolPermissionConfig, ToolPermissionRule, WebConfig, WorkerManifest, WorkerMeta,
|
||||
SkillsConfig, TicketFeatureConfig, ToolOutputLimits, ToolPermissionConfig, ToolPermissionRule,
|
||||
WebConfig, WorkerManifest, WorkerMeta,
|
||||
};
|
||||
|
||||
/// Partial-form Worker manifest. Every field is optional; one or more
|
||||
@@ -117,19 +117,24 @@ impl FeatureFlagConfigPartial {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct TicketFeatureConfigPartial {
|
||||
#[serde(default)]
|
||||
pub enabled: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub preset: Option<TicketFeatureAccessConfig>,
|
||||
pub authoring: Option<bool>,
|
||||
pub thread: Option<bool>,
|
||||
pub intake: Option<bool>,
|
||||
pub orchestration_control: Option<bool>,
|
||||
}
|
||||
|
||||
impl TicketFeatureConfigPartial {
|
||||
fn merge(self, other: Self) -> Self {
|
||||
Self {
|
||||
enabled: other.enabled.or(self.enabled),
|
||||
preset: other.preset.or(self.preset),
|
||||
authoring: other.authoring.or(self.authoring),
|
||||
thread: other.thread.or(self.thread),
|
||||
intake: other.intake.or(self.intake),
|
||||
orchestration_control: other.orchestration_control.or(self.orchestration_control),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +184,10 @@ impl From<TicketFeatureConfigPartial> for TicketFeatureConfig {
|
||||
fn from(value: TicketFeatureConfigPartial) -> Self {
|
||||
Self {
|
||||
enabled: value.enabled.unwrap_or_default(),
|
||||
preset: value.preset.unwrap_or_default(),
|
||||
authoring: value.authoring.unwrap_or_default(),
|
||||
thread: value.thread.unwrap_or_default(),
|
||||
intake: value.intake.unwrap_or_default(),
|
||||
orchestration_control: value.orchestration_control.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +196,10 @@ impl From<TicketFeatureConfig> for TicketFeatureConfigPartial {
|
||||
fn from(value: TicketFeatureConfig) -> Self {
|
||||
Self {
|
||||
enabled: Some(value.enabled),
|
||||
preset: Some(value.preset),
|
||||
authoring: Some(value.authoring),
|
||||
thread: Some(value.thread),
|
||||
intake: Some(value.intake),
|
||||
orchestration_control: Some(value.orchestration_control),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1757,7 +1768,10 @@ enabled = true
|
||||
|
||||
[feature.ticket]
|
||||
enabled = true
|
||||
preset = "read_only"
|
||||
authoring = false
|
||||
thread = false
|
||||
intake = false
|
||||
orchestration_control = false
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1787,10 +1801,10 @@ preset = "read_only"
|
||||
.unwrap();
|
||||
assert!(manifest.feature.task.enabled);
|
||||
assert!(manifest.feature.ticket.enabled);
|
||||
assert_eq!(
|
||||
manifest.feature.ticket.preset,
|
||||
TicketFeatureAccessConfig::ReadOnly
|
||||
);
|
||||
assert!(!manifest.feature.ticket.authoring);
|
||||
assert!(!manifest.feature.ticket.thread);
|
||||
assert!(!manifest.feature.ticket.intake);
|
||||
assert!(!manifest.feature.ticket.orchestration_control);
|
||||
assert!(!manifest.feature.memory.enabled);
|
||||
}
|
||||
|
||||
@@ -1803,14 +1817,18 @@ enabled = true
|
||||
|
||||
[feature.ticket]
|
||||
enabled = true
|
||||
preset = "read_only"
|
||||
authoring = false
|
||||
thread = false
|
||||
intake = false
|
||||
orchestration_control = false
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let upper = WorkerManifestConfig::from_toml(
|
||||
r#"
|
||||
[feature.ticket]
|
||||
preset = "orchestration_control"
|
||||
thread = true
|
||||
orchestration_control = true
|
||||
|
||||
[feature.web]
|
||||
enabled = true
|
||||
@@ -1844,10 +1862,10 @@ enabled = true
|
||||
.unwrap();
|
||||
assert!(manifest.feature.memory.enabled);
|
||||
assert!(manifest.feature.ticket.enabled);
|
||||
assert_eq!(
|
||||
manifest.feature.ticket.preset,
|
||||
TicketFeatureAccessConfig::OrchestrationControl
|
||||
);
|
||||
assert!(!manifest.feature.ticket.authoring);
|
||||
assert!(manifest.feature.ticket.thread);
|
||||
assert!(!manifest.feature.ticket.intake);
|
||||
assert!(manifest.feature.ticket.orchestration_control);
|
||||
assert!(manifest.feature.web.enabled);
|
||||
assert!(!manifest.feature.workers.enabled);
|
||||
}
|
||||
|
||||
@@ -153,38 +153,18 @@ impl Default for FeatureFlagConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
|
||||
pub struct TicketFeatureConfig {
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
#[serde(default)]
|
||||
pub preset: TicketFeatureAccessConfig,
|
||||
}
|
||||
|
||||
impl Default for TicketFeatureConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
preset: TicketFeatureAccessConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TicketFeatureAccessConfig {
|
||||
ReadOnly,
|
||||
WorkspaceAuthoring,
|
||||
Intake,
|
||||
OrchestrationControl,
|
||||
WorkReport,
|
||||
Review,
|
||||
}
|
||||
|
||||
impl Default for TicketFeatureAccessConfig {
|
||||
fn default() -> Self {
|
||||
Self::WorkspaceAuthoring
|
||||
}
|
||||
pub authoring: bool,
|
||||
#[serde(default)]
|
||||
pub thread: bool,
|
||||
#[serde(default)]
|
||||
pub intake: bool,
|
||||
#[serde(default)]
|
||||
pub orchestration_control: bool,
|
||||
}
|
||||
|
||||
/// External Agent Skills (`SKILL.md`) ingest configuration. Skills are
|
||||
|
||||
@@ -16,8 +16,8 @@ use crate::model::{AuthRef, ModelManifest};
|
||||
use crate::plugin::PluginConfig;
|
||||
use crate::{
|
||||
EngineManifestConfig, McpConfig, McpStdioCwdPolicy, MemoryConfig, Permission, ResolveError,
|
||||
ScopeConfig, ScopeRule, SkillsConfig, TicketFeatureAccessConfig, WebConfig, WorkerManifest,
|
||||
WorkerManifestConfig, WorkerMetaConfig, paths,
|
||||
ScopeConfig, ScopeRule, SkillsConfig, WebConfig, WorkerManifest, WorkerManifestConfig,
|
||||
WorkerMetaConfig, paths,
|
||||
};
|
||||
|
||||
const PROFILE_FORMAT_V1: &str = "yoi.profile.v1";
|
||||
@@ -971,7 +971,7 @@ fn builtin_default_profile_artifact() -> serde_json::Value {
|
||||
"memory": { "enabled": true },
|
||||
"web": { "enabled": true },
|
||||
"workers": { "enabled": true },
|
||||
"ticket": { "enabled": true, "preset": "workspace_authoring" }
|
||||
"ticket": { "enabled": true, "authoring": true, "thread": true }
|
||||
},
|
||||
"memory": {
|
||||
"extract_threshold": 50000,
|
||||
@@ -1005,18 +1005,19 @@ fn apply_role_profile(
|
||||
value["feature"]["memory"] = serde_json::json!({ "enabled": memory });
|
||||
value["feature"]["web"] = serde_json::json!({ "enabled": web });
|
||||
value["feature"]["workers"] = serde_json::json!({ "enabled": workers });
|
||||
let ticket_access = match slug {
|
||||
"companion" => TicketFeatureAccessConfig::WorkspaceAuthoring,
|
||||
"intake" => TicketFeatureAccessConfig::Intake,
|
||||
"orchestrator" => TicketFeatureAccessConfig::OrchestrationControl,
|
||||
"coder" => TicketFeatureAccessConfig::WorkReport,
|
||||
"reviewer" => TicketFeatureAccessConfig::Review,
|
||||
_ => TicketFeatureAccessConfig::WorkspaceAuthoring,
|
||||
let ticket = match slug {
|
||||
"companion" => serde_json::json!({ "enabled": true, "authoring": true, "thread": true }),
|
||||
"intake" => {
|
||||
serde_json::json!({ "enabled": true, "authoring": true, "thread": true, "intake": true })
|
||||
}
|
||||
"orchestrator" => {
|
||||
serde_json::json!({ "enabled": true, "thread": true, "orchestration_control": true })
|
||||
}
|
||||
"coder" => serde_json::json!({ "enabled": true, "thread": true }),
|
||||
"reviewer" => serde_json::json!({ "enabled": true, "thread": true }),
|
||||
_ => serde_json::json!({ "enabled": true, "authoring": true, "thread": true }),
|
||||
};
|
||||
value["feature"]["ticket"] = serde_json::json!({
|
||||
"enabled": true,
|
||||
"preset": ticket_access,
|
||||
});
|
||||
value["feature"]["ticket"] = ticket;
|
||||
}
|
||||
|
||||
fn reject_manifest_shaped_profile(value: &serde_json::Value) -> Result<(), ProfileError> {
|
||||
@@ -1442,10 +1443,10 @@ mod tests {
|
||||
assert_eq!(companion.model.ref_.as_deref(), Some("codex-oauth/gpt-5.5"));
|
||||
assert!(companion.web.is_some());
|
||||
assert!(companion.feature.ticket.enabled);
|
||||
assert_eq!(
|
||||
companion.feature.ticket.preset,
|
||||
TicketFeatureAccessConfig::WorkspaceAuthoring
|
||||
);
|
||||
assert!(companion.feature.ticket.authoring);
|
||||
assert!(companion.feature.ticket.thread);
|
||||
assert!(!companion.feature.ticket.intake);
|
||||
assert!(!companion.feature.ticket.orchestration_control);
|
||||
assert_eq!(
|
||||
companion.compaction.as_ref().unwrap().threshold,
|
||||
Some(240000)
|
||||
@@ -1467,10 +1468,11 @@ mod tests {
|
||||
assert!(intake.feature.task.enabled);
|
||||
assert!(!intake.feature.workers.enabled);
|
||||
assert!(intake.feature.ticket.enabled);
|
||||
assert_eq!(
|
||||
intake.feature.ticket.preset,
|
||||
TicketFeatureAccessConfig::Intake
|
||||
);
|
||||
assert!(intake.feature.ticket.enabled);
|
||||
assert!(intake.feature.ticket.authoring);
|
||||
assert!(intake.feature.ticket.thread);
|
||||
assert!(intake.feature.ticket.intake);
|
||||
assert!(!intake.feature.ticket.orchestration_control);
|
||||
assert!(intake.scope.allow.is_empty());
|
||||
assert!(intake.delegation_scope.allow.is_empty());
|
||||
assert_eq!(intake.model.ref_.as_deref(), Some("codex-oauth/gpt-5.5"));
|
||||
@@ -1481,10 +1483,11 @@ mod tests {
|
||||
assert!(orchestrator.feature.task.enabled);
|
||||
assert!(orchestrator.feature.workers.enabled);
|
||||
assert!(orchestrator.feature.ticket.enabled);
|
||||
assert_eq!(
|
||||
orchestrator.feature.ticket.preset,
|
||||
TicketFeatureAccessConfig::OrchestrationControl
|
||||
);
|
||||
assert!(orchestrator.feature.ticket.enabled);
|
||||
assert!(!orchestrator.feature.ticket.authoring);
|
||||
assert!(orchestrator.feature.ticket.thread);
|
||||
assert!(!orchestrator.feature.ticket.intake);
|
||||
assert!(orchestrator.feature.ticket.orchestration_control);
|
||||
assert!(orchestrator.scope.allow.is_empty());
|
||||
assert!(orchestrator.delegation_scope.allow.is_empty());
|
||||
assert_eq!(
|
||||
@@ -1503,19 +1506,20 @@ mod tests {
|
||||
assert!(coder.web.is_some());
|
||||
assert!(coder.compaction.is_some());
|
||||
assert!(coder.feature.ticket.enabled);
|
||||
assert_eq!(
|
||||
coder.feature.ticket.preset,
|
||||
TicketFeatureAccessConfig::WorkReport
|
||||
);
|
||||
|
||||
assert!(coder.feature.ticket.enabled);
|
||||
assert!(!coder.feature.ticket.authoring);
|
||||
assert!(coder.feature.ticket.thread);
|
||||
assert!(!coder.feature.ticket.intake);
|
||||
assert!(!coder.feature.ticket.orchestration_control);
|
||||
let reviewer = resolve("reviewer");
|
||||
assert!(reviewer.feature.task.enabled);
|
||||
assert!(!reviewer.feature.workers.enabled);
|
||||
assert!(reviewer.feature.ticket.enabled);
|
||||
assert_eq!(
|
||||
reviewer.feature.ticket.preset,
|
||||
TicketFeatureAccessConfig::Review
|
||||
);
|
||||
assert!(reviewer.feature.ticket.enabled);
|
||||
assert!(!reviewer.feature.ticket.authoring);
|
||||
assert!(reviewer.feature.ticket.thread);
|
||||
assert!(!reviewer.feature.ticket.intake);
|
||||
assert!(!reviewer.feature.ticket.orchestration_control);
|
||||
assert!(reviewer.scope.allow.is_empty());
|
||||
assert!(reviewer.delegation_scope.allow.is_empty());
|
||||
assert_eq!(reviewer.model.ref_.as_deref(), Some("codex-oauth/gpt-5.5"));
|
||||
@@ -1664,7 +1668,10 @@ enabled = true
|
||||
|
||||
[feature.ticket]
|
||||
enabled = true
|
||||
preset = "read_only"
|
||||
authoring = false
|
||||
thread = false
|
||||
intake = false
|
||||
orchestration_control = false
|
||||
"#,
|
||||
);
|
||||
let workspace = tmp.path().join("workspace");
|
||||
@@ -1682,10 +1689,10 @@ preset = "read_only"
|
||||
assert!(resolved.manifest.feature.web.enabled);
|
||||
assert!(resolved.manifest.feature.workers.enabled);
|
||||
assert!(resolved.manifest.feature.ticket.enabled);
|
||||
assert_eq!(
|
||||
resolved.manifest.feature.ticket.preset,
|
||||
crate::TicketFeatureAccessConfig::ReadOnly
|
||||
);
|
||||
assert!(!resolved.manifest.feature.ticket.authoring);
|
||||
assert!(!resolved.manifest.feature.ticket.thread);
|
||||
assert!(!resolved.manifest.feature.ticket.intake);
|
||||
assert!(!resolved.manifest.feature.ticket.orchestration_control);
|
||||
assert_eq!(
|
||||
resolved.manifest.delegation_scope.allow[0].target,
|
||||
workspace
|
||||
@@ -1768,14 +1775,11 @@ worker_context_max_tokens = 68000
|
||||
resolved.manifest.model.ref_.as_deref(),
|
||||
Some("codex-oauth/gpt-5.5")
|
||||
);
|
||||
assert!(resolved.manifest.scope.allow.is_empty());
|
||||
assert!(resolved.manifest.delegation_scope.allow.is_empty());
|
||||
assert!(resolved.manifest.session.record_event_trace);
|
||||
assert!(resolved.manifest.feature.ticket.enabled);
|
||||
assert_eq!(
|
||||
resolved.manifest.feature.ticket.preset,
|
||||
crate::TicketFeatureAccessConfig::WorkspaceAuthoring
|
||||
);
|
||||
assert!(resolved.manifest.feature.ticket.authoring);
|
||||
assert!(resolved.manifest.feature.ticket.thread);
|
||||
assert!(!resolved.manifest.feature.ticket.intake);
|
||||
assert!(!resolved.manifest.feature.ticket.orchestration_control);
|
||||
assert_eq!(
|
||||
resolved.profile.as_ref().unwrap().name.as_deref(),
|
||||
Some("default")
|
||||
|
||||
Reference in New Issue
Block a user