1use std::collections::{BTreeMap, BTreeSet};
10use std::sync::OnceLock;
11
12use serde::{Deserialize, Serialize};
13
14use crate::core::ocla::OclaCapabilityKind;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum Plan {
21 #[serde(alias = "free", alias = "supporter", alias = "sponsor")]
22 Community,
23 Pro,
24 #[serde(alias = "business", alias = "biz")]
25 Team,
26 #[serde(alias = "ent")]
27 Enterprise,
28}
29
30impl Plan {
31 pub const ALL: [Self; 4] = [Self::Community, Self::Pro, Self::Team, Self::Enterprise];
32
33 #[must_use]
34 pub const fn all() -> &'static [Self] {
35 &Self::ALL
36 }
37
38 #[must_use]
39 pub const fn rank(self) -> usize {
40 match self {
41 Self::Community => 0,
42 Self::Pro => 1,
43 Self::Team => 2,
44 Self::Enterprise => 3,
45 }
46 }
47
48 #[must_use]
49 pub const fn as_str(self) -> &'static str {
50 match self {
51 Self::Community => "community",
52 Self::Pro => "pro",
53 Self::Team => "team",
54 Self::Enterprise => "enterprise",
55 }
56 }
57}
58
59pub const REGISTRY_SOURCE: &str = include_str!("../../data/product-capabilities.toml");
62
63#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
64#[serde(rename_all = "snake_case")]
65pub enum UserPriceClass {
66 Free,
67 Hive,
70 Retired,
72 CloudPaid,
75 ProPaid,
78 TeamPaid,
80 EnterprisePaid,
82 SdkOemCommercial,
83}
84
85#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
87#[serde(rename_all = "snake_case")]
88pub enum HivePlan {
89 Personal,
90 Team,
91 Enterprise,
92}
93
94impl HivePlan {
95 #[must_use]
96 pub const fn plan(self) -> Plan {
97 match self {
98 Self::Personal => Plan::Pro,
99 Self::Team => Plan::Team,
100 Self::Enterprise => Plan::Enterprise,
101 }
102 }
103}
104
105pub const REGISTRY_SCHEMA_VERSION: u16 = 3;
107
108#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
109#[serde(rename_all = "snake_case")]
110pub enum SourceVisibility {
111 PublicApache,
112 PublicEnterpriseSourceAvailable,
113 PrivateSource,
114}
115
116#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
117#[serde(rename_all = "snake_case")]
118pub enum RuntimeDelivery {
119 SourceBuild,
120 SignedBinary,
121 LocalService,
122 ManagedCloudService,
123 EnterprisePrivateImage,
124 HiveService,
127}
128
129#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
130#[serde(rename_all = "snake_case")]
131pub enum AhaCriticality {
132 None,
133 Supporting,
134 IndividualAha,
135 TeamAha,
136 Undisclosed,
138}
139
140#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
141#[serde(rename_all = "snake_case")]
142pub enum MoatSensitivity {
143 Low,
144 Medium,
145 CrownJewel,
146 Undisclosed,
148}
149
150pub const PUBLIC_PROJECTION: &str = "public";
152pub const UNDISCLOSED_SOURCE: &str = "undisclosed";
154
155#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
156#[serde(rename_all = "snake_case")]
157pub enum CommercialUse {
158 ApacheRights,
159 FreeSmallTeamInternal,
160 ServiceAgreement,
161 EnterpriseAgreement,
162 SdkOemAgreement,
163 ThirdPartyTerms,
164}
165
166#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
167#[serde(rename_all = "snake_case")]
168pub enum RedistributionRights {
169 ApacheRights,
170 SeparateAgreementRequired,
171 ThirdPartyTerms,
172}
173
174#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
175#[serde(rename_all = "snake_case")]
176pub enum TelemetryEventClass {
177 None,
178 ProductUsage,
179 BillingUsage,
180 OperationalAggregate,
181 Audit,
182}
183
184#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
185#[serde(rename_all = "snake_case")]
186pub enum PublicStatus {
187 Available,
188 Preview,
189 Internal,
190 Deprecated,
191 Research,
192 Private,
193}
194
195#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
196#[serde(rename_all = "snake_case")]
197pub enum LicenseClass {
198 ApacheTrustCore,
199 CommercialSource,
200 PrivateService,
201 FreeRuntime,
202 SdkOem,
203 ThirdParty,
204}
205
206#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
207#[serde(deny_unknown_fields)]
208pub struct ProductCapability {
209 pub id: String,
210 pub name: String,
211 pub price_class: UserPriceClass,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub hive_plan: Option<HivePlan>,
215 pub source_visibility: SourceVisibility,
216 pub runtime_delivery: RuntimeDelivery,
217 pub aha_criticality: AhaCriticality,
218 pub moat_sensitivity: MoatSensitivity,
219 pub license_class: LicenseClass,
220 pub self_host_available: bool,
221 pub managed_cloud_available: bool,
222 pub enterprise_entitlement: String,
224 pub account_required: bool,
225 pub allowed_commercial_use: CommercialUse,
226 pub redistribution_oem_rights: RedistributionRights,
227 pub data_boundary: String,
228 pub telemetry_event_class: TelemetryEventClass,
229 pub public_maturity: PublicStatus,
230 pub source_path: String,
231}
232
233impl ProductCapability {
234 #[must_use]
236 pub const fn minimum_plan(&self) -> Plan {
237 match self.price_class {
238 UserPriceClass::Free => Plan::Community,
239 UserPriceClass::Hive => match self.hive_plan {
240 Some(plan) => plan.plan(),
241 None => Plan::Enterprise,
243 },
244 UserPriceClass::CloudPaid | UserPriceClass::ProPaid => Plan::Pro,
245 UserPriceClass::TeamPaid => Plan::Team,
246 UserPriceClass::EnterprisePaid
249 | UserPriceClass::SdkOemCommercial
250 | UserPriceClass::Retired => Plan::Enterprise,
251 }
252 }
253
254 #[must_use]
257 pub const fn is_enterprise(&self) -> bool {
258 matches!(self.price_class, UserPriceClass::EnterprisePaid)
259 || matches!(
260 (self.price_class, self.hive_plan),
261 (UserPriceClass::Hive, Some(HivePlan::Enterprise))
262 )
263 }
264}
265
266#[derive(Clone, Debug, Deserialize)]
267#[serde(deny_unknown_fields)]
268struct RegistryDocument {
269 schema_version: u16,
270 #[serde(default)]
273 projection: Option<String>,
274 capability: Vec<ProductCapability>,
275 aliases: BTreeMap<String, String>,
277}
278
279#[derive(Debug)]
280pub struct ProductCapabilityRegistry {
281 entries: Vec<ProductCapability>,
282 lookup: BTreeMap<String, usize>,
283 projected: bool,
284}
285
286impl ProductCapabilityRegistry {
287 pub fn parse(source: &str) -> Result<Self, String> {
288 let document: RegistryDocument = toml::from_str(source)
289 .map_err(|error| format!("invalid capability registry: {error}"))?;
290 if document.schema_version != REGISTRY_SCHEMA_VERSION {
291 return Err(format!(
292 "unsupported capability registry schema {}",
293 document.schema_version
294 ));
295 }
296 if document.capability.is_empty() {
297 return Err("capability registry is empty".to_string());
298 }
299
300 let projected = match document.projection.as_deref() {
301 None => false,
302 Some(PUBLIC_PROJECTION) => true,
303 Some(other) => return Err(format!("unknown capability registry projection '{other}'")),
304 };
305
306 let mut lookup = BTreeMap::new();
307 let mut ids = BTreeSet::new();
308 for (index, capability) in document.capability.iter().enumerate() {
309 validate_record(capability)?;
310 validate_projection(capability, projected)?;
311 if !ids.insert(capability.id.as_str()) {
312 return Err(format!("duplicate capability id '{}'", capability.id));
313 }
314 lookup.insert(capability.id.clone(), index);
315 }
316 for (alias, target) in document.aliases {
317 if alias.trim().is_empty() || !ids.contains(target.as_str()) {
318 return Err(format!("invalid capability alias '{alias}' -> '{target}'"));
319 }
320 let index = lookup[&target];
321 if lookup.insert(alias.clone(), index).is_some() {
322 return Err(format!("duplicate capability lookup key '{alias}'"));
323 }
324 }
325 Ok(Self {
326 entries: document.capability,
327 lookup,
328 projected,
329 })
330 }
331
332 #[must_use]
335 pub const fn is_public_projection(&self) -> bool {
336 self.projected
337 }
338
339 #[must_use]
340 pub fn entries(&self) -> &[ProductCapability] {
341 &self.entries
342 }
343
344 #[must_use]
345 pub fn find(&self, id_or_key: &str) -> Option<&ProductCapability> {
346 self.lookup
347 .get(id_or_key)
348 .and_then(|index| self.entries.get(*index))
349 }
350
351 #[must_use]
352 pub fn allows(&self, plan: Plan, id_or_key: &str) -> bool {
353 self.find(id_or_key).is_some_and(|capability| {
354 !matches!(
357 capability.price_class,
358 UserPriceClass::SdkOemCommercial | UserPriceClass::Retired
359 ) && plan.rank() >= capability.minimum_plan().rank()
360 })
361 }
362
363 #[must_use]
364 pub fn render_markdown(&self) -> String {
365 use std::fmt::Write as _;
366
367 let mut entries: Vec<&ProductCapability> = self.entries.iter().collect();
368 entries.sort_unstable_by_key(|capability| capability.id.as_str());
369 let mut output = String::from(
370 "<!-- Generated from product/capabilities.toml; do not edit by hand. -->\n\n\
371 # Product capabilities\n\n\
372 | ID | Name | Price | Hive plan | Source visibility | Delivery | Aha | Moat | License | Self-host | Managed cloud | Enterprise entitlement | Account | Commercial use | Redistribution/OEM | Data boundary | Telemetry | Maturity | Source path |\n\
373 |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n",
374 );
375 for capability in entries {
376 writeln!(
377 output,
378 "| {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} |",
379 capability.id,
380 capability.name.replace('|', "\\|"),
381 enum_name(&capability.price_class),
382 capability
383 .hive_plan
384 .as_ref()
385 .map_or_else(|| "-".to_string(), enum_name),
386 enum_name(&capability.source_visibility),
387 enum_name(&capability.runtime_delivery),
388 enum_name(&capability.aha_criticality),
389 enum_name(&capability.moat_sensitivity),
390 enum_name(&capability.license_class),
391 capability.self_host_available,
392 capability.managed_cloud_available,
393 capability.enterprise_entitlement,
394 capability.account_required,
395 enum_name(&capability.allowed_commercial_use),
396 enum_name(&capability.redistribution_oem_rights),
397 capability.data_boundary.replace('|', "\\|"),
398 enum_name(&capability.telemetry_event_class),
399 enum_name(&capability.public_maturity),
400 capability.source_path.replace('|', "\\|"),
401 )
402 .expect("writing to String cannot fail");
403 }
404 output
405 }
406}
407
408fn enum_name<T: Serialize>(value: &T) -> String {
409 serde_json::to_value(value)
410 .expect("serializable registry enum")
411 .as_str()
412 .expect("registry enum serializes as string")
413 .to_string()
414}
415
416fn validate_record(capability: &ProductCapability) -> Result<(), String> {
417 for (field, value) in [
418 ("id", capability.id.as_str()),
419 ("name", capability.name.as_str()),
420 (
421 "enterprise_entitlement",
422 capability.enterprise_entitlement.as_str(),
423 ),
424 ("source_path", capability.source_path.as_str()),
425 ("data_boundary", capability.data_boundary.as_str()),
426 ] {
427 if value.trim().is_empty() {
428 return Err(format!("capability '{}' has empty {field}", capability.id));
429 }
430 }
431 let invalid = if matches!(
432 capability.aha_criticality,
433 AhaCriticality::IndividualAha | AhaCriticality::TeamAha
434 ) && capability.price_class != UserPriceClass::Free
435 {
436 Some("Aha-critical capability has no free delivery path")
437 } else if capability.moat_sensitivity == MoatSensitivity::CrownJewel
438 && capability.source_visibility != SourceVisibility::PrivateSource
439 {
440 Some("crown-jewel implementation must not enter public source")
441 } else if capability.moat_sensitivity == MoatSensitivity::CrownJewel
442 && capability.redistribution_oem_rights != RedistributionRights::SeparateAgreementRequired
443 {
444 Some("crown-jewel redistribution requires a separate agreement")
445 } else if capability.source_visibility == SourceVisibility::PublicApache
446 && (capability.license_class != LicenseClass::ApacheTrustCore
447 || capability.allowed_commercial_use != CommercialUse::ApacheRights
448 || capability.redistribution_oem_rights != RedistributionRights::ApacheRights
449 || capability.price_class != UserPriceClass::Free
450 || capability.account_required)
451 {
452 Some("public Apache reference rights must remain free and accountless")
453 } else if capability.license_class == LicenseClass::ApacheTrustCore
454 && capability.source_visibility != SourceVisibility::PublicApache
455 {
456 Some("Apache source classification mismatch")
457 } else if capability.source_visibility != SourceVisibility::PublicApache
458 && (capability.allowed_commercial_use == CommercialUse::ApacheRights
459 || capability.redistribution_oem_rights == RedistributionRights::ApacheRights)
460 {
461 Some("non-Apache implementations cannot grant Apache redistribution rights")
462 } else if capability.price_class == UserPriceClass::SdkOemCommercial
463 && (capability.license_class != LicenseClass::SdkOem
464 || capability.allowed_commercial_use != CommercialUse::SdkOemAgreement)
465 {
466 Some("SDK/OEM capability requires a separate commercial license")
467 } else if capability.runtime_delivery == RuntimeDelivery::SourceBuild
468 && (!capability.self_host_available
469 || capability.source_visibility == SourceVisibility::PrivateSource)
470 {
471 Some("source builds require available source and a self-host path")
472 } else if capability.runtime_delivery == RuntimeDelivery::ManagedCloudService
473 && !capability.managed_cloud_available
474 {
475 Some("managed service requires managed-cloud availability")
476 } else if matches!(
477 capability.runtime_delivery,
478 RuntimeDelivery::SignedBinary
479 | RuntimeDelivery::LocalService
480 | RuntimeDelivery::EnterprisePrivateImage
481 ) && !capability.self_host_available
482 {
483 Some("local artifact requires a self-host path")
484 } else if capability.aha_criticality == AhaCriticality::IndividualAha
485 && (capability.account_required || !capability.self_host_available)
486 {
487 Some("individual Aha must work locally without an account")
488 } else if capability.source_visibility == SourceVisibility::PublicEnterpriseSourceAvailable
489 && capability.license_class != LicenseClass::CommercialSource
490 {
491 Some("source-available Enterprise requires its own license")
492 } else if capability.license_class == LicenseClass::FreeRuntime
493 && (capability.source_visibility != SourceVisibility::PrivateSource
494 || capability.price_class != UserPriceClass::Free
495 || capability.allowed_commercial_use != CommercialUse::FreeSmallTeamInternal
496 || capability.redistribution_oem_rights
497 != RedistributionRights::SeparateAgreementRequired)
498 {
499 Some("Free Runtime rights must not grant source/OEM/redistribution rights")
500 } else if capability.is_enterprise() != (capability.enterprise_entitlement == capability.id) {
501 Some("Enterprise capability requires its explicit canonical entitlement")
502 } else if !capability.is_enterprise() && capability.enterprise_entitlement != "none" {
503 Some("non-Enterprise capability must not require governance entitlement")
504 } else if !capability.self_host_available && !capability.managed_cloud_available {
505 Some("capability has no delivery path")
506 } else if (capability.price_class == UserPriceClass::Hive) != capability.hive_plan.is_some() {
507 Some("Hive plan is required exactly on hive capabilities")
508 } else if capability.price_class == UserPriceClass::Hive
509 && (capability.source_visibility != SourceVisibility::PrivateSource
510 || !capability.account_required
511 || !matches!(
512 capability.runtime_delivery,
513 RuntimeDelivery::HiveService | RuntimeDelivery::SignedBinary
514 ))
515 {
516 Some("Hive capability needs private source, an account and the Hive or signed path")
517 } else if capability.runtime_delivery == RuntimeDelivery::HiveService
518 && (capability.price_class != UserPriceClass::Hive || !capability.managed_cloud_available)
519 {
520 Some("Hive service delivery requires a Hive plan and the managed service")
521 } else if matches!(
522 capability.price_class,
523 UserPriceClass::CloudPaid
524 | UserPriceClass::ProPaid
525 | UserPriceClass::TeamPaid
526 | UserPriceClass::EnterprisePaid
527 ) {
528 Some("legacy price class is not valid in schema 3; use free, hive or retired")
529 } else {
530 None
531 };
532 if let Some(reason) = invalid {
533 return Err(format!("capability '{}': {reason}", capability.id));
534 }
535 Ok(())
536}
537
538const WITHHELD_FIELDS: [(&str, &str); 4] = [
542 ("aha_criticality", "undisclosed"),
543 ("moat_sensitivity", "undisclosed"),
544 ("public_maturity", "private"),
545 ("source_path", UNDISCLOSED_SOURCE),
546];
547
548fn validate_projection(capability: &ProductCapability, projected: bool) -> Result<(), String> {
552 let withheld = [
553 capability.aha_criticality == AhaCriticality::Undisclosed,
554 capability.moat_sensitivity == MoatSensitivity::Undisclosed,
555 capability.public_maturity == PublicStatus::Private,
556 capability.source_path == UNDISCLOSED_SOURCE,
557 ];
558 let undisclosed = withheld[0] || withheld[1] || withheld[3];
559 let public = capability.source_visibility == SourceVisibility::PublicApache;
560 let problem = if !projected && undisclosed {
561 Some("undisclosed classification outside the public projection")
562 } else if projected && public && undisclosed {
563 Some("public capability must stay fully classified")
564 } else if projected && !public && !withheld.iter().all(|field| *field) {
565 Some("public projection discloses a strategic classification")
566 } else {
567 None
568 };
569 match problem {
570 Some(reason) => Err(format!("capability '{}': {reason}", capability.id)),
571 None => Ok(()),
572 }
573}
574
575pub fn public_projection(source: &str) -> Result<String, String> {
582 use std::fmt::Write as _;
583
584 let registry = ProductCapabilityRegistry::parse(source)?;
585 if registry.is_public_projection() {
586 return Ok(source.to_string());
587 }
588 let hidden: BTreeSet<&str> = registry
589 .entries()
590 .iter()
591 .filter(|capability| capability.source_visibility != SourceVisibility::PublicApache)
592 .map(|capability| capability.id.as_str())
593 .collect();
594
595 let mut output = String::with_capacity(source.len());
596 let mut current_id: Option<String> = None;
597 for line in source.split_inclusive('\n') {
598 let content = line.trim_end_matches(['\n', '\r']);
599 let newline = &line[content.len()..];
600 if content.trim() == "[[capability]]" {
601 current_id = None;
602 }
603 let Some((key, value)) = content.split_once(" = ") else {
604 output.push_str(line);
605 continue;
606 };
607 if key == "id" {
608 current_id = Some(value.trim_matches('"').to_string());
609 }
610 let withheld = current_id
611 .as_deref()
612 .filter(|id| hidden.contains(id))
613 .and_then(|_| WITHHELD_FIELDS.iter().find(|(field, _)| *field == key));
614 match withheld {
615 Some((field, replacement)) => write!(output, "{field} = \"{replacement}\"{newline}")
616 .expect("writing to String cannot fail"),
617 None => output.push_str(line),
618 }
619 if key == "schema_version" {
620 write!(output, "projection = \"{PUBLIC_PROJECTION}\"{newline}")
621 .expect("writing to String cannot fail");
622 }
623 }
624
625 let projected = ProductCapabilityRegistry::parse(&output)?;
626 if !projected.is_public_projection() {
627 return Err("capability registry has no schema_version line to mark".to_string());
628 }
629 Ok(output)
630}
631
632static REGISTRY: OnceLock<ProductCapabilityRegistry> = OnceLock::new();
633
634#[must_use]
635pub fn registry() -> &'static ProductCapabilityRegistry {
636 REGISTRY.get_or_init(|| {
637 ProductCapabilityRegistry::parse(REGISTRY_SOURCE)
638 .unwrap_or_else(|error| panic!("embedded product capability registry failed: {error}"))
639 })
640}
641
642#[must_use]
645pub const fn ocla_product_capability_id(kind: OclaCapabilityKind) -> &'static str {
646 match kind {
647 OclaCapabilityKind::ObservationHook => "trust.ocla.observation_hook",
648 OclaCapabilityKind::UsageSink => "enterprise.ocla.usage_sink",
649 OclaCapabilityKind::MetricsExporter => "team.ocla.metrics_exporter",
650 OclaCapabilityKind::SavingsLedger => "trust.ocla.savings_ledger",
651 OclaCapabilityKind::IntentClassifier => "pro.ocla.intent_classifier",
652 OclaCapabilityKind::OutcomeTracker => "pro.ocla.outcome_tracker",
653 OclaCapabilityKind::CompressionProvider => "trust.ocla.compression_provider",
654 OclaCapabilityKind::ResponseOptimizer => "pro.ocla.response_optimizer",
655 OclaCapabilityKind::EfficiencyAnalyzer => "pro.ocla.efficiency_analyzer",
656 OclaCapabilityKind::ConfigTuner => "pro.ocla.config_tuner",
657 OclaCapabilityKind::ExperimentRunner => "pro.ocla.experiment_runner",
658 OclaCapabilityKind::ConnectorScheduler => "pro.ocla.connector_scheduler",
659 OclaCapabilityKind::AgentGateway => "pro.ocla.agent_gateway",
660 OclaCapabilityKind::DeliveryRegistry => "pro.ocla.delivery_registry",
661 }
662}
663
664pub const EXPERIMENT_EXECUTOR_CAPABILITY_ID: &str = "pro.ocla.experiment_executor";
665
666#[cfg(test)]
667mod tests {
668 use super::*;
669
670 #[test]
671 fn embedded_registry_is_valid() {
672 assert!(registry().entries().len() >= 40);
673 let source = include_str!("../../../product/capabilities.toml");
674 if REGISTRY_SOURCE == source {
675 return;
676 }
677 assert!(
682 REGISTRY_SOURCE.contains("\nprojection = \"public\"\n"),
683 "packaged capability registry drifted from repository source"
684 );
685 let ids = |text: &str| -> Vec<String> {
686 ProductCapabilityRegistry::parse(text)
687 .expect("registry parses")
688 .entries()
689 .iter()
690 .map(|entry| entry.id.clone())
691 .collect()
692 };
693 assert_eq!(ids(REGISTRY_SOURCE), ids(source));
694 assert!(!REGISTRY_SOURCE.contains("\"ee/"));
695 }
696
697 #[test]
698 fn malformed_or_duplicate_records_are_rejected() {
699 assert!(ProductCapabilityRegistry::parse("schema_version = 99").is_err());
700 let first = ®istry().entries()[0].id;
701 let second = ®istry().entries()[1].id;
702 let duplicate = REGISTRY_SOURCE.replacen(
703 &format!("id = \"{second}\""),
704 &format!("id = \"{first}\""),
705 1,
706 );
707 assert!(ProductCapabilityRegistry::parse(&duplicate).is_err());
708 }
709
710 #[test]
711 fn unknown_capabilities_fail_closed() {
712 for plan in Plan::all() {
713 assert!(!registry().allows(*plan, "unclassified.future.feature"));
714 }
715 }
716
717 fn mutated_first(field: &str, value: toml::Value) -> String {
718 let mut document: toml::Value = toml::from_str(REGISTRY_SOURCE).unwrap();
719 document["capability"][0][field] = value;
720 toml::to_string(&document).unwrap()
721 }
722
723 #[test]
724 fn all_eighteen_classifications_are_required_without_defaults() {
725 let document: toml::Value = toml::from_str(REGISTRY_SOURCE).unwrap();
726 let records = document["capability"].as_array().unwrap();
727 for record in records {
728 let hive = record["price_class"].as_str() == Some("hive");
729 assert_eq!(
730 record.as_table().unwrap().len(),
731 18 + usize::from(hive),
732 "{record}"
733 );
734 for field in record.as_table().unwrap().keys() {
735 let mut incomplete = record.clone();
736 incomplete.as_table_mut().unwrap().remove(field);
737 if field == "hive_plan" {
738 let parsed: ProductCapability = incomplete.try_into().unwrap();
740 assert!(validate_record(&parsed).is_err(), "missing {field}");
741 continue;
742 }
743 assert!(
744 incomplete.try_into::<ProductCapability>().is_err(),
745 "missing {field}"
746 );
747 }
748 }
749 }
750
751 #[test]
755 fn registry_source_paths_enforce_the_open_core_boundary() {
756 let repository = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
757 for entry in registry().entries() {
758 for source in entry.source_path.split(';').map(str::trim) {
759 let in_repository = source.starts_with("rust/");
760 if entry.source_visibility == SourceVisibility::PublicApache {
761 assert!(in_repository, "{}: public source outside rust/", entry.id);
762 assert!(
763 repository.join(source).exists(),
764 "{}: public source {source} is missing",
765 entry.id
766 );
767 } else {
768 assert!(
769 !in_repository,
770 "{}: private capability points into the public tree",
771 entry.id
772 );
773 }
774 if entry.price_class != UserPriceClass::Free {
775 assert_ne!(entry.source_visibility, SourceVisibility::PublicApache);
776 }
777 }
778 }
779 }
780
781 #[test]
782 fn shared_team_capabilities_need_team_and_enterprise_self_hosts_them() {
783 let registry = registry();
784 let entry = registry.find("team.context.shared").unwrap();
785 assert_eq!(entry.price_class, UserPriceClass::Hive);
786 assert_eq!(entry.hive_plan, Some(HivePlan::Team));
787 assert!(entry.managed_cloud_available && entry.self_host_available);
788 assert!(!registry.allows(Plan::Pro, &entry.id));
789 assert!(registry.allows(Plan::Team, &entry.id));
790 assert!(registry.allows(Plan::Enterprise, &entry.id));
791 let mut public = entry.clone();
792 public.source_visibility = SourceVisibility::PublicApache;
793 assert!(validate_record(&public).is_err());
794 }
795
796 #[test]
797 fn team_runtime_capabilities_ship_in_the_signed_self_hosted_runtime() {
798 let registry = registry();
799 for id in [
800 "team.runtime.artifact_reader",
801 "team.runtime.artifact_author",
802 ] {
803 let entry = registry.find(id).unwrap();
804 assert_eq!(entry.price_class, UserPriceClass::Hive);
805 assert_eq!(entry.hive_plan, Some(HivePlan::Team));
806 assert_eq!(entry.runtime_delivery, RuntimeDelivery::SignedBinary);
807 assert!(entry.self_host_available && !entry.managed_cloud_available);
808 assert!(!registry.allows(Plan::Pro, id));
809 assert!(registry.allows(Plan::Team, id));
810 let breaks: [fn(&mut ProductCapability); 3] = [
812 |record| {
813 record.source_visibility = SourceVisibility::PublicEnterpriseSourceAvailable;
814 },
815 |record| record.account_required = false,
816 |record| record.runtime_delivery = RuntimeDelivery::LocalService,
817 ];
818 for change in breaks {
819 let mut broken = entry.clone();
820 change(&mut broken);
821 let refusal = validate_record(&broken).unwrap_err();
822 assert!(refusal.contains("Hive capability"), "{refusal}");
823 }
824 }
825 }
826
827 #[test]
828 fn hosted_seat_authority_is_classified_for_team_and_enterprise_only() {
829 let registry = registry();
830 let entry = registry.find("team.seat_management").unwrap();
831 assert_eq!(entry.price_class, UserPriceClass::Hive);
832 assert_eq!(entry.hive_plan, Some(HivePlan::Team));
833 assert_eq!(entry.runtime_delivery, RuntimeDelivery::HiveService);
834 assert!(entry.account_required && entry.managed_cloud_available);
835 assert_eq!(entry.enterprise_entitlement, "none");
836 assert!(!registry.allows(Plan::Community, &entry.id));
837 assert!(!registry.allows(Plan::Pro, &entry.id));
838 assert!(registry.allows(Plan::Team, &entry.id));
839 assert!(registry.allows(Plan::Enterprise, &entry.id));
840 }
841
842 #[test]
843 fn aha_paywalls_public_crown_jewels_and_unclassified_telemetry_fail_closed() {
844 for (field, value) in [
845 ("price_class", "cloud_paid"),
846 ("moat_sensitivity", "crown_jewel"),
847 ("license_class", "free_runtime"),
848 ("source_visibility", "private_source"),
849 ("telemetry_event_class", "arbitrary_runtime_text"),
850 ("enterprise_entitlement", "enterprise.unrelated"),
851 ] {
852 let malformed = mutated_first(field, value.into());
853 assert!(
854 ProductCapabilityRegistry::parse(&malformed).is_err(),
855 "accepted {field}={value}"
856 );
857 }
858 assert!(
859 ProductCapabilityRegistry::parse(&mutated_first("account_required", true.into()))
860 .is_err()
861 );
862 }
863
864 #[test]
865 fn free_private_intelligence_does_not_grant_oem_or_source_rights() {
866 let mut entry = registry()
869 .find("pro.runtime.adaptive_context")
870 .unwrap()
871 .clone();
872 entry.price_class = UserPriceClass::Free;
873 entry.license_class = LicenseClass::FreeRuntime;
874 entry.allowed_commercial_use = CommercialUse::FreeSmallTeamInternal;
875 entry.aha_criticality = AhaCriticality::IndividualAha;
876 entry.account_required = false;
877 assert!(validate_record(&entry).is_ok());
878 assert_eq!(entry.minimum_plan(), Plan::Community);
879 let mut leaking = entry.clone();
880 leaking.redistribution_oem_rights = RedistributionRights::ApacheRights;
881 assert!(validate_record(&leaking).is_err());
882 leaking = entry.clone();
883 leaking.allowed_commercial_use = CommercialUse::ApacheRights;
884 assert!(validate_record(&leaking).is_err());
885 let disclosed = !registry().is_public_projection();
889 if disclosed {
890 leaking = entry.clone();
891 leaking.license_class = LicenseClass::ThirdParty;
892 leaking.redistribution_oem_rights = RedistributionRights::ThirdPartyTerms;
893 assert!(validate_record(&leaking).is_err());
894 }
895 for license in [
896 LicenseClass::PrivateService,
897 LicenseClass::CommercialSource,
898 LicenseClass::SdkOem,
899 ] {
900 leaking = entry.clone();
901 leaking.license_class = license;
902 leaking.redistribution_oem_rights = RedistributionRights::ApacheRights;
903 assert!(validate_record(&leaking).is_err());
904 }
905 leaking = entry.clone();
906 leaking.runtime_delivery = RuntimeDelivery::SourceBuild;
907 assert!(validate_record(&leaking).is_err());
908 if disclosed {
909 leaking = entry.clone();
910 leaking.account_required = true;
911 assert!(validate_record(&leaking).is_err());
912 }
913 }
914
915 #[test]
916 fn legacy_ids_do_not_paywall_local_or_small_team_aha() {
917 for entry in registry().entries() {
918 if matches!(
919 entry.aha_criticality,
920 AhaCriticality::IndividualAha | AhaCriticality::TeamAha
921 ) {
922 assert!(
923 registry().allows(Plan::Community, &entry.id),
924 "{}",
925 entry.id
926 );
927 }
928 }
929 for id in [
930 "enterprise.execution_policy",
931 "enterprise.sso_scim",
932 "team.sso_oidc",
933 ] {
934 assert!(!registry().allows(Plan::Community, id));
935 assert!(!registry().allows(Plan::Team, id));
936 }
937 }
938
939 #[test]
942 fn public_projection_withholds_strategy_but_keeps_every_access_decision() {
943 let canonical = ProductCapabilityRegistry::parse(REGISTRY_SOURCE).unwrap();
944 if canonical.is_public_projection() {
945 assert!(!REGISTRY_SOURCE.contains("crown_jewel"));
948 return;
949 }
950 let source = public_projection(REGISTRY_SOURCE).unwrap();
951 let projected = ProductCapabilityRegistry::parse(&source).unwrap();
952 assert!(projected.is_public_projection());
953 assert_eq!(public_projection(&source).unwrap(), source);
954 assert!(!source.contains("crown_jewel"));
955 assert_eq!(projected.entries().len(), canonical.entries().len());
956 for (before, after) in canonical.entries().iter().zip(projected.entries()) {
957 if before.source_visibility == SourceVisibility::PublicApache {
958 assert_eq!(before, after);
959 } else {
960 assert_eq!(after.moat_sensitivity, MoatSensitivity::Undisclosed);
961 assert_eq!(after.aha_criticality, AhaCriticality::Undisclosed);
962 assert_eq!(after.public_maturity, PublicStatus::Private);
963 assert_eq!(after.source_path, UNDISCLOSED_SOURCE);
964 let mut restored = after.clone();
965 restored.aha_criticality = before.aha_criticality;
966 restored.moat_sensitivity = before.moat_sensitivity;
967 restored.public_maturity = before.public_maturity;
968 restored.source_path.clone_from(&before.source_path);
969 assert_eq!(
970 before, &restored,
971 "projection changed rights of {}",
972 before.id
973 );
974 }
975 for plan in Plan::all() {
976 assert_eq!(
977 canonical.allows(*plan, &before.id),
978 projected.allows(*plan, &before.id)
979 );
980 }
981 }
982
983 let hidden = canonical
984 .entries()
985 .iter()
986 .find(|entry| entry.moat_sensitivity == MoatSensitivity::CrownJewel)
987 .unwrap();
988 let block = format!("id = \"{}\"", hidden.id);
989 let (head, tail) = source.split_once(&block).unwrap();
990 let partial = format!(
991 "{head}{block}{}",
992 tail.replacen(
993 "moat_sensitivity = \"undisclosed\"",
994 "moat_sensitivity = \"crown_jewel\"",
995 1
996 )
997 );
998 for (registry, case) in [
999 (
1000 mutated_first("moat_sensitivity", "undisclosed".into()),
1001 "canonical",
1002 ),
1003 (partial, "partial projection"),
1004 (
1005 source.replacen("projection = \"public\"", "projection = \"other\"", 1),
1006 "unknown",
1007 ),
1008 (
1009 source.replacen(
1010 &format!("source_path = \"{}\"", registry().entries()[0].source_path),
1011 &format!("source_path = \"{UNDISCLOSED_SOURCE}\""),
1012 1,
1013 ),
1014 "public record withheld",
1015 ),
1016 ] {
1017 assert!(
1018 ProductCapabilityRegistry::parse(®istry).is_err(),
1019 "accepted {case}"
1020 );
1021 }
1022 }
1023
1024 #[test]
1028 fn hive_classification_keeps_local_free_and_retires_personal_cloud() {
1029 let registry = registry();
1030 for entry in registry.entries() {
1031 assert!(
1032 matches!(
1033 entry.price_class,
1034 UserPriceClass::Free
1035 | UserPriceClass::Hive
1036 | UserPriceClass::Retired
1037 | UserPriceClass::SdkOemCommercial
1038 ),
1039 "{} keeps a legacy price class",
1040 entry.id
1041 );
1042 if entry.price_class == UserPriceClass::Free {
1043 assert!(!entry.account_required, "{}", entry.id);
1044 }
1045 }
1046 for id in [
1047 "pro.dashboard.knowledge_workflows",
1048 "pro.runtime.adaptive_context",
1049 "pro.runtime.memory_curation",
1050 "pro.runtime.context_policy",
1051 ] {
1052 let entry = registry.find(id).unwrap();
1053 assert_eq!(entry.price_class, UserPriceClass::Free, "{id}");
1054 assert!(registry.allows(Plan::Community, id), "{id}");
1055 }
1056 for (id, plan) in [
1057 ("pro.runtime.semantic_detectors", HivePlan::Personal),
1058 ("pro.runtime.code_security", HivePlan::Personal),
1059 ("pro.runtime.personal_protection", HivePlan::Personal),
1060 ("team.private_registry", HivePlan::Team),
1061 ("team.managed_connectors", HivePlan::Team),
1062 ("team.sso_oidc", HivePlan::Enterprise),
1063 ("team.audit_retention", HivePlan::Enterprise),
1064 (
1065 "enterprise.runtime.org_policy_verifier",
1066 HivePlan::Enterprise,
1067 ),
1068 ] {
1069 let entry = registry.find(id).unwrap();
1070 assert_eq!(entry.price_class, UserPriceClass::Hive, "{id}");
1071 assert_eq!(entry.hive_plan, Some(plan), "{id}");
1072 assert_eq!(entry.minimum_plan(), plan.plan(), "{id}");
1073 assert!(!registry.allows(Plan::Community, id), "{id}");
1074 assert!(registry.allows(Plan::Enterprise, id), "{id}");
1075 }
1076 for id in ["pro.cloud_sync", "pro.hosted_index", "team.revenue_share"] {
1077 let entry = registry.find(id).unwrap();
1078 assert_eq!(entry.price_class, UserPriceClass::Retired, "{id}");
1079 assert_ne!(entry.minimum_plan(), Plan::Community, "{id}");
1080 for plan in Plan::all() {
1081 assert!(!registry.allows(*plan, id), "{id} granted to {plan:?}");
1082 }
1083 }
1084 assert_eq!(registry.find("cloud_sync").unwrap().id, "pro.cloud_sync");
1086 assert_eq!(registry.find("sso_oidc").unwrap().id, "team.sso_oidc");
1087
1088 let hive = registry.find("team.context.shared").unwrap().clone();
1089 for legacy in [
1090 UserPriceClass::CloudPaid,
1091 UserPriceClass::ProPaid,
1092 UserPriceClass::TeamPaid,
1093 ] {
1094 let mut broken = hive.clone();
1095 broken.price_class = legacy;
1096 broken.hive_plan = None;
1097 broken.runtime_delivery = RuntimeDelivery::ManagedCloudService;
1098 assert!(validate_record(&broken).is_err(), "{legacy:?} accepted");
1099 }
1100 let mut planless = hive.clone();
1101 planless.hive_plan = None;
1102 assert!(validate_record(&planless).is_err());
1103 let mut stray = registry.find("free.cockpit.ui").unwrap().clone();
1104 stray.hive_plan = Some(HivePlan::Personal);
1105 assert!(validate_record(&stray).is_err());
1106 let mut local = hive;
1107 local.managed_cloud_available = false;
1108 assert!(validate_record(&local).is_err());
1109 }
1110
1111 #[test]
1112 fn legacy_aliases_cannot_shadow_ids_or_point_to_unclassified_capabilities() {
1113 for target in ["missing.capability", "compression"] {
1114 let mut document: toml::Value = toml::from_str(REGISTRY_SOURCE).unwrap();
1115 document["aliases"]
1116 .as_table_mut()
1117 .unwrap()
1118 .insert("bad_alias".into(), target.into());
1119 assert!(
1120 ProductCapabilityRegistry::parse(&toml::to_string(&document).unwrap()).is_err()
1121 );
1122 }
1123 let mut document: toml::Value = toml::from_str(REGISTRY_SOURCE).unwrap();
1124 document["aliases"].as_table_mut().unwrap().insert(
1125 "trust.runtime.compression".into(),
1126 "trust.runtime.caching".into(),
1127 );
1128 assert!(ProductCapabilityRegistry::parse(&toml::to_string(&document).unwrap()).is_err());
1129 }
1130}