feat: deliver committed Ticket events to plugin services
This commit is contained in:
@@ -31,6 +31,7 @@ pub struct ArtifactIdentityProjection {
|
||||
pub world: String,
|
||||
pub required_imports: Vec<String>,
|
||||
pub tools: Vec<String>,
|
||||
pub services: Vec<String>,
|
||||
pub connections: Vec<String>,
|
||||
pub requested_grants: Vec<PluginGrant>,
|
||||
pub config_schema: PluginSchemaIdentity,
|
||||
@@ -89,6 +90,7 @@ pub struct BindingPolicyReference {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PluginSurface {
|
||||
Tool,
|
||||
Service,
|
||||
}
|
||||
|
||||
/// Exact grant identities; new operations are never implied by old pins.
|
||||
@@ -100,6 +102,7 @@ pub enum PluginGrant {
|
||||
slot: String,
|
||||
},
|
||||
TicketRead,
|
||||
TicketEvents,
|
||||
TicketDataRead {
|
||||
collection: String,
|
||||
schema_id: String,
|
||||
@@ -130,6 +133,8 @@ pub struct PluginPinProjection {
|
||||
pub config: Value,
|
||||
pub surfaces: Vec<PluginSurface>,
|
||||
pub tools: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub services: Vec<String>,
|
||||
pub grants: Vec<PluginGrant>,
|
||||
pub bindings: Vec<BindingPolicyReference>,
|
||||
}
|
||||
@@ -178,6 +183,7 @@ impl WorkerPluginPin {
|
||||
| plugin::PLUGIN_COMPONENT_LOGGED_TOOL_WORLD
|
||||
| plugin::PLUGIN_COMPONENT_HTTPS_LOGGED_TOOL_WORLD
|
||||
| plugin::PLUGIN_COMPONENT_TICKET_TOOL_WORLD
|
||||
| plugin::PLUGIN_COMPONENT_SERVICE_WORLD
|
||||
)
|
||||
|| !valid_digest(&artifact.config_schema.digest)
|
||||
|| !valid_identity(&artifact.package_id)
|
||||
@@ -193,6 +199,8 @@ impl WorkerPluginPin {
|
||||
projection.surfaces.dedup();
|
||||
projection.tools.sort();
|
||||
projection.tools.dedup();
|
||||
projection.services.sort();
|
||||
projection.services.dedup();
|
||||
projection.grants.sort();
|
||||
projection.grants.dedup();
|
||||
projection.bindings.sort();
|
||||
@@ -201,6 +209,16 @@ impl WorkerPluginPin {
|
||||
|| projection.tools.iter().any(|name| {
|
||||
!valid_identity(name) || !artifact.tools.iter().any(|tool| *tool == *name)
|
||||
})
|
||||
|| (!projection.services.is_empty()
|
||||
&& !projection.surfaces.contains(&PluginSurface::Service))
|
||||
|| (projection.surfaces.contains(&PluginSurface::Service)
|
||||
&& (projection.services.is_empty()
|
||||
|| !projection.grants.contains(&PluginGrant::TicketEvents)))
|
||||
|| projection.services.iter().any(|name| {
|
||||
!valid_identity(name)
|
||||
|| !artifact.services.contains(name)
|
||||
|| artifact.world != plugin::PLUGIN_COMPONENT_SERVICE_WORLD
|
||||
})
|
||||
|| projection
|
||||
.bindings
|
||||
.windows(2)
|
||||
@@ -218,8 +236,11 @@ impl WorkerPluginPin {
|
||||
PluginGrant::TicketRead
|
||||
| PluginGrant::TicketDataRead { .. }
|
||||
| PluginGrant::TicketDataWrite { .. } => {
|
||||
artifact.world != plugin::PLUGIN_COMPONENT_TICKET_TOOL_WORLD
|
||||
|| !artifact.requested_grants.contains(grant)
|
||||
!matches!(
|
||||
artifact.world.as_str(),
|
||||
plugin::PLUGIN_COMPONENT_TICKET_TOOL_WORLD
|
||||
| plugin::PLUGIN_COMPONENT_SERVICE_WORLD
|
||||
) || !artifact.requested_grants.contains(grant)
|
||||
|| match grant {
|
||||
PluginGrant::TicketDataRead {
|
||||
collection,
|
||||
@@ -236,6 +257,11 @@ impl WorkerPluginPin {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
PluginGrant::TicketEvents => {
|
||||
artifact.world != plugin::PLUGIN_COMPONENT_SERVICE_WORLD
|
||||
|| !artifact.requested_grants.contains(grant)
|
||||
|| projection.services.is_empty()
|
||||
}
|
||||
PluginGrant::Logging => !artifact.requested_grants.contains(&PluginGrant::Logging),
|
||||
PluginGrant::Https { slot } => {
|
||||
!valid_identity(slot)
|
||||
|
||||
@@ -15,6 +15,7 @@ fn artifact() -> ArtifactIdentityProjection {
|
||||
schema_version: 2,
|
||||
world: plugin::PLUGIN_COMPONENT_HTTPS_LOGGED_TOOL_WORLD.into(),
|
||||
required_imports: vec!["logging".into(), "https".into(), "logging".into()],
|
||||
services: vec![],
|
||||
tools: vec!["other".into(), "echo".into()],
|
||||
connections: vec!["api".into()],
|
||||
requested_grants: vec![
|
||||
@@ -35,6 +36,7 @@ fn projection() -> PluginPinProjection {
|
||||
PluginPinProjection {
|
||||
config: serde_json::from_str(r#"{"z":{"b":2,"a":1},"a":[{"z":0,"a":false}]}"#).unwrap(),
|
||||
surfaces: vec![PluginSurface::Tool, PluginSurface::Tool],
|
||||
services: vec![],
|
||||
tools: vec!["other".into(), "echo".into(), "other".into()],
|
||||
grants: vec![
|
||||
PluginGrant::Https { slot: "api".into() },
|
||||
@@ -238,3 +240,47 @@ fn ticket_grants_require_exact_world_request_and_schema_namespace() {
|
||||
a.requested_grants.retain(|g| g != &write);
|
||||
assert_eq!(exact.matches_artifact(&a), Err(PlanError::Invalid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_selection_needs_exact_new_world_declared_name_and_explicit_event_grant() {
|
||||
let mut artifact = artifact();
|
||||
let mut selected = projection();
|
||||
selected.tools.clear();
|
||||
selected.bindings.clear();
|
||||
selected.grants = vec![PluginGrant::TicketEvents];
|
||||
selected.surfaces = vec![PluginSurface::Service];
|
||||
selected.services = vec!["watch".into()];
|
||||
artifact.services = vec!["watch".into()];
|
||||
artifact.requested_grants.push(PluginGrant::TicketEvents);
|
||||
assert_eq!(pin(&artifact, selected.clone()), Err(PlanError::Invalid));
|
||||
artifact.world = plugin::PLUGIN_COMPONENT_SERVICE_WORLD.into();
|
||||
let exact = pin(&artifact, selected.clone()).unwrap();
|
||||
selected.services.push("undeclared".into());
|
||||
assert_eq!(pin(&artifact, selected), Err(PlanError::Invalid));
|
||||
artifact.services.clear();
|
||||
assert_eq!(exact.matches_artifact(&artifact), Err(PlanError::Invalid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_serialized_pin_does_not_acquire_service_selection_or_event_grant() {
|
||||
let plan: WorkerPluginExecutionPlan =
|
||||
serde_json::from_str(include_str!("fixtures/canonical-plan.json")).unwrap();
|
||||
assert!(plan.plugins()[0].projection().services.is_empty());
|
||||
assert!(
|
||||
!plan.plugins()[0]
|
||||
.projection()
|
||||
.surfaces
|
||||
.contains(&PluginSurface::Service)
|
||||
);
|
||||
assert!(
|
||||
!plan.plugins()[0]
|
||||
.projection()
|
||||
.grants
|
||||
.contains(&PluginGrant::TicketEvents)
|
||||
);
|
||||
// Additive default-off fields cannot mutate the exact old plan fingerprint.
|
||||
assert_eq!(
|
||||
plan.fingerprint().unwrap(),
|
||||
"sha256:00ee15d79098f4afa137da5d4b0b9b78acde48a6157fdda0f3327d8c122bd889"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user