1use std::collections::{BTreeMap, BTreeSet};
4
5pub mod json;
6pub mod selection;
7
8pub const IMPLEMENTATION_STAGE: &str = "release";
11
12pub fn version() -> &'static str {
14 env!("CARGO_PKG_VERSION")
15}
16
17#[derive(Clone, Debug, Default, Eq, PartialEq)]
24pub struct ContractRegistry {
25 pub libraries: Vec<LibraryRegistration>,
26 pub constructs: Vec<ConstructRegistration>,
27 pub effect_contracts: Vec<EffectContract>,
28}
29
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct LibraryRegistration {
32 pub id: String,
33 pub version: String,
34 pub standard: bool,
35}
36
37#[derive(Clone, Debug, Eq, PartialEq)]
38pub struct ConstructRegistration {
39 pub id: String,
40 pub library_id: String,
41 pub version: String,
42 pub construct_family: String,
43 pub keyword: String,
44 pub scope: String,
45 pub grammar: Option<ConstructGrammar>,
51 pub fields: Vec<ConstructField>,
52 pub requires: Vec<ConstructInterface>,
53 pub provides: Vec<ConstructInterface>,
54 pub lowering_target: String,
55 pub target_capability: Option<String>,
56}
57
58#[derive(Clone, Debug, Eq, PartialEq)]
59pub struct ConstructField {
60 pub name: String,
61 pub kind: String,
62 pub required: bool,
63}
64
65#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct ConstructGrammar {
76 pub shape: String,
77 pub keyword: String,
78 pub slots: Vec<ConstructGrammarSlot>,
79 pub payload: Option<Vec<ConstructGrammarPayloadField>>,
82 pub binding: String,
84 pub target_capability: String,
85 pub clauses: Option<Vec<ConstructGrammarClause>>,
90}
91
92#[derive(Clone, Debug, Eq, PartialEq)]
96pub struct ConstructGrammarSlot {
97 pub name: String,
98 pub kind: String,
99 pub connective: Option<String>,
100}
101
102#[derive(Clone, Debug, Eq, PartialEq)]
105pub struct ConstructGrammarPayloadField {
106 pub name: String,
107 pub kind: String,
108 pub required: bool,
109}
110
111#[derive(Clone, Debug, Eq, PartialEq)]
118pub struct ConstructGrammarClause {
119 pub name: String,
120 pub kind: String,
121 pub required: bool,
122 pub list: bool,
123 pub connective: Option<String>,
124}
125
126pub const CONSTRUCT_GRAMMAR_SHAPE_EFFECT_OPERATION: &str = "effect_operation";
127pub const CONSTRUCT_GRAMMAR_SHAPE_DECLARATION_BLOCK: &str = "declaration_block";
128pub const CONSTRUCT_GRAMMAR_CONNECTIVES: &[&str] = &["from", "for", "into", "to", "via", "onto"];
129pub const CONSTRUCT_GRAMMAR_SLOT_KINDS: &[&str] = &["identifier", "expression"];
130pub const CONSTRUCT_GRAMMAR_BINDING_MODES: &[&str] = &["required", "optional", "none"];
131pub const CONSTRUCT_GRAMMAR_CLAUSE_KINDS: &[&str] = &[
134 "identifier",
135 "expression",
136 "duration",
137 "glob",
138 "schema",
139 "scalar",
140 "flag",
141];
142pub const CONSTRUCT_GRAMMAR_CLAUSE_CONNECTIVES: &[&str] =
145 &["from", "for", "into", "to", "via", "onto", "by"];
146
147pub const AGENT_FEATURE_CLASS_TAXONOMY: &[&str] = &[
153 "context.compact",
154 "context.auto_compact",
155 "session.resume",
156 "session.fork",
157 "session.clone",
158 "session.export",
159 "turn.cancel",
160 "turn.steer",
161 "turn.follow_up",
162 "subagent.spawn",
163 "subagent.observe",
164 "subagent.steer",
165 "skill.attach",
166 "plugin.load",
167 "hook.lifecycle",
168 "native.command.dispatch",
169 "permission.policy",
170 "model.select",
171 "reasoning.select",
172 "goal.track",
173 "command.list",
174 "feature.report",
175];
176
177impl ConstructGrammar {
178 pub fn derive_fields(&self) -> Vec<ConstructField> {
193 if let Some(clauses) = &self.clauses {
194 return clauses
195 .iter()
196 .map(|clause| {
197 let (kind, required) = if clause.kind == "flag" {
198 ("boolean".to_owned(), false)
199 } else if clause.list {
200 ("list".to_owned(), clause.required)
201 } else {
202 (
203 field_kind_for_clause_kind(&clause.kind).to_owned(),
204 clause.required,
205 )
206 };
207 ConstructField {
208 name: clause.name.clone(),
209 kind,
210 required,
211 }
212 })
213 .collect();
214 }
215 let mut fields = Vec::new();
216 for slot in &self.slots {
217 fields.push(ConstructField {
218 name: slot.name.clone(),
219 kind: slot.kind.clone(),
220 required: true,
221 });
222 }
223 for field in self.payload.iter().flatten() {
224 fields.push(ConstructField {
225 name: field.name.clone(),
226 kind: field.kind.clone(),
227 required: field.required,
228 });
229 }
230 if self.binding != "none" {
231 fields.push(ConstructField {
232 name: "binding".to_owned(),
233 kind: "identifier".to_owned(),
234 required: self.binding == "required",
235 });
236 }
237 fields
238 }
239}
240
241fn field_kind_for_clause_kind(kind: &str) -> &str {
249 match kind {
250 "glob" | "scalar" => "string",
251 "schema" => "type_ref",
252 other => other,
253 }
254}
255
256#[derive(Clone, Debug, Eq, PartialEq)]
257pub struct ConstructInterface {
258 pub kind: String,
259 pub name: Option<String>,
260 pub type_ref: Option<String>,
261 pub phase: String,
262 pub cardinality: String,
263}
264
265pub const CORE_CAPABILITY_CALL_CONSTRUCT_ID: &str = "core.capability.call";
266pub const CONSTRUCT_FAMILY_DECLARATION_BLOCK: &str = "declaration_block";
267pub const CONSTRUCT_FAMILY_EFFECT_OPERATION: &str = "effect_operation";
268pub const CONSTRUCT_FAMILY_EFFECT_CONTRACT: &str = "effect_contract";
269pub const CONSTRUCT_FAMILY_SOURCE_DECLARATION: &str = "source_declaration";
270pub const CONSTRUCT_FAMILY_ASSERTION: &str = "assertion";
271pub const CONSTRUCT_FAMILY_RULE: &str = "rule";
272pub const CONSTRUCT_FAMILY_PROJECTION_READ: &str = "projection_read";
273pub const CONSTRUCT_LOWERING_METADATA: &str = "metadata";
274pub const CONSTRUCT_LOWERING_METADATA_ONLY: &str = "metadata_only";
275pub const CONSTRUCT_LOWERING_CAPABILITY_CALL: &str = "capability_call";
276pub const CONSTRUCT_LOWERING_TYPED_EFFECT_CALL: &str = "typed_effect_call";
277pub const CONSTRUCT_LOWERING_RESOURCE_EFFECT: &str = "resource_effect";
278pub const CONSTRUCT_LOWERING_CORE_EFFECT: &str = "core_effect";
279pub const CONSTRUCT_LOWERING_SIGNAL_EMIT: &str = "signal_emit";
280pub const CONSTRUCT_LOWERING_SIGNAL_SOURCE: &str = "signal_source";
281pub const CONSTRUCT_LOWERING_CLOCK_SOURCE: &str = "clock_source";
282pub const CONSTRUCT_LOWERING_SCHEDULE_EMITTER: &str = "schedule_emitter";
283pub const CONSTRUCT_LOWERING_RULE_TEMPLATE: &str = "rule_template";
284pub const CONSTRUCT_LOWERING_PROJECTION_VIEW: &str = "projection_view";
285pub const CONSTRUCT_LOWERING_ASSERTION_CHECK: &str = "assertion_check";
286pub const CONSTRUCT_SCOPE_RULE_BODY: &str = "rule_body";
287pub const CONSTRUCT_INTERFACE_CAPABILITY: &str = "Capability";
288pub const CONSTRUCT_INTERFACE_EFFECT_HANDLE: &str = "EffectHandle";
289pub const CONSTRUCT_INTERFACE_PHASE_COMPILE_RUNTIME: &str = "compile/runtime";
290pub const CONSTRUCT_INTERFACE_CARDINALITY_EXACTLY_ONE: &str = "exactly-one";
291
292pub const CONSTRUCT_STATIC_DETERMINISTIC: &str = "deterministic";
293pub const CONSTRUCT_STATIC_CONTRACT_PINNED: &str = "contract_pinned";
294pub const CONSTRUCT_STATIC_NO_RUNTIME_INPUTS: &str = "no_runtime_inputs";
295pub const CONSTRUCT_STATIC_NO_HIDDEN_AUTHORITY: &str = "no_hidden_authority";
296pub const CONSTRUCT_STATIC_NO_PACKAGE_SCHEDULER: &str = "no_package_scheduler";
297pub const CONSTRUCT_STATIC_NO_PACKAGE_LIFECYCLE: &str = "no_package_lifecycle";
298pub const CONSTRUCT_STATIC_NO_DIRECT_FACT_WRITE: &str = "no_direct_fact_write";
299pub const CONSTRUCT_STATIC_NO_DIRECT_RULE_FIRE: &str = "no_direct_rule_fire";
300
301pub const CONSTRUCT_PLATFORM_STATIC_GUARANTEES: &[&str] = &[
302 CONSTRUCT_STATIC_DETERMINISTIC,
303 CONSTRUCT_STATIC_CONTRACT_PINNED,
304 CONSTRUCT_STATIC_NO_RUNTIME_INPUTS,
305 CONSTRUCT_STATIC_NO_HIDDEN_AUTHORITY,
306 CONSTRUCT_STATIC_NO_PACKAGE_SCHEDULER,
307 CONSTRUCT_STATIC_NO_PACKAGE_LIFECYCLE,
308 CONSTRUCT_STATIC_NO_DIRECT_FACT_WRITE,
309 CONSTRUCT_STATIC_NO_DIRECT_RULE_FIRE,
310];
311
312#[derive(Clone, Copy, Debug, Eq, PartialEq)]
313pub enum ConstructTargetCapabilityPolicy {
314 Forbidden,
315 RequiredCapabilityCallContract,
316}
317
318impl ConstructTargetCapabilityPolicy {
319 pub fn as_str(self) -> &'static str {
320 match self {
321 Self::Forbidden => "forbidden",
322 Self::RequiredCapabilityCallContract => "required_capability_call_contract",
323 }
324 }
325}
326
327#[derive(Clone, Copy, Debug, Eq, PartialEq)]
328pub enum ConstructLoweringAuthorityProfile {
329 None,
330 CapabilityScoped,
331 EventAdmission,
332 ProjectionSource,
333}
334
335impl ConstructLoweringAuthorityProfile {
336 pub fn as_str(self) -> &'static str {
337 match self {
338 Self::None => "none",
339 Self::CapabilityScoped => "capability_scoped",
340 Self::EventAdmission => "event_admission",
341 Self::ProjectionSource => "projection_source",
342 }
343 }
344}
345
346#[derive(Clone, Copy, Debug, Eq, PartialEq)]
352pub enum Severity {
353 Error,
354 Warning,
355 Info,
356 Hint,
357}
358
359impl Severity {
360 pub const ALL: [Severity; 4] = [Self::Error, Self::Warning, Self::Info, Self::Hint];
362
363 pub fn as_str(self) -> &'static str {
365 match self {
366 Self::Error => "error",
367 Self::Warning => "warning",
368 Self::Info => "info",
369 Self::Hint => "hint",
370 }
371 }
372
373 pub fn from_wire(value: &str) -> Option<Severity> {
376 match value {
377 "error" => Some(Self::Error),
378 "warning" => Some(Self::Warning),
379 "info" => Some(Self::Info),
380 "hint" => Some(Self::Hint),
381 _ => None,
382 }
383 }
384
385 pub fn lsp_code(self) -> i32 {
389 match self {
390 Self::Error => 1,
391 Self::Warning => 2,
392 Self::Info => 3,
393 Self::Hint => 4,
394 }
395 }
396}
397
398#[derive(Clone, Copy, Debug, Eq, PartialEq)]
399pub struct PlatformConstructFamily {
400 pub id: &'static str,
401 pub description: &'static str,
402}
403
404#[derive(Clone, Copy, Debug, Eq, PartialEq)]
405pub struct PlatformConstructLowering {
406 pub id: &'static str,
407 pub compatible_families: &'static [&'static str],
408 pub package_authorable: bool,
409 pub required_scope: Option<&'static str>,
410 pub target_capability: ConstructTargetCapabilityPolicy,
411 pub required_interfaces: &'static [&'static str],
412 pub provided_interfaces: &'static [&'static str],
413 pub lifecycle_profiles: &'static [&'static str],
414 pub authority_profile: ConstructLoweringAuthorityProfile,
415 pub static_guarantees: &'static [&'static str],
416}
417
418#[derive(Clone, Copy, Debug, Eq, PartialEq)]
419pub struct PlatformReservedKeywordPrivilege {
420 pub keyword: &'static str,
421 pub library_id: &'static str,
422 pub construct_family: &'static str,
423 pub scope: &'static str,
424 pub lowering_target: &'static str,
425}
426
427#[derive(Clone, Copy, Debug, Eq, PartialEq)]
428pub struct PlatformConstructCatalog {
429 pub families: &'static [PlatformConstructFamily],
430 pub lowerings: &'static [PlatformConstructLowering],
431 pub scopes: &'static [&'static str],
432 pub field_kinds: &'static [&'static str],
433 pub interface_kinds: &'static [&'static str],
434 pub interface_phases: &'static [&'static str],
435 pub interface_cardinalities: &'static [&'static str],
436 pub reserved_keywords: &'static [&'static str],
437 pub reserved_keyword_privileges: &'static [PlatformReservedKeywordPrivilege],
438}
439
440impl PlatformConstructCatalog {
441 pub fn family(&self, id: &str) -> Option<&'static PlatformConstructFamily> {
442 self.families.iter().find(|family| family.id == id)
443 }
444
445 pub fn lowering(&self, id: &str) -> Option<&'static PlatformConstructLowering> {
446 self.lowerings.iter().find(|lowering| lowering.id == id)
447 }
448
449 pub fn family_ids(&self) -> impl Iterator<Item = &'static str> + '_ {
450 self.families.iter().map(|family| family.id)
451 }
452
453 pub fn lowering_ids(&self) -> impl Iterator<Item = &'static str> + '_ {
454 self.lowerings.iter().map(|lowering| lowering.id)
455 }
456
457 pub fn lowerings_for_family<'a>(
458 &'a self,
459 family: &'a str,
460 ) -> impl Iterator<Item = &'a PlatformConstructLowering> + 'a {
461 self.lowerings
462 .iter()
463 .filter(move |lowering| lowering.compatible_families.contains(&family))
464 }
465
466 pub fn contains_scope(&self, scope: &str) -> bool {
467 self.scopes.contains(&scope)
468 }
469
470 pub fn contains_field_kind(&self, kind: &str) -> bool {
471 self.field_kinds.contains(&kind)
472 }
473
474 pub fn contains_interface_kind(&self, kind: &str) -> bool {
475 self.interface_kinds.contains(&kind)
476 }
477
478 pub fn contains_interface_phase(&self, phase: &str) -> bool {
479 self.interface_phases.contains(&phase)
480 }
481
482 pub fn contains_interface_cardinality(&self, cardinality: &str) -> bool {
483 self.interface_cardinalities.contains(&cardinality)
484 }
485
486 pub fn contains_reserved_keyword(&self, keyword: &str) -> bool {
487 self.reserved_keywords.contains(&keyword)
488 }
489
490 pub fn reserved_keyword_privilege(
491 &self,
492 library_id: &str,
493 keyword: &str,
494 construct_family: &str,
495 scope: &str,
496 lowering_target: &str,
497 ) -> Option<&'static PlatformReservedKeywordPrivilege> {
498 self.reserved_keyword_privileges.iter().find(|privilege| {
499 privilege.library_id == library_id
500 && privilege.keyword == keyword
501 && privilege.construct_family == construct_family
502 && privilege.scope == scope
503 && privilege.lowering_target == lowering_target
504 })
505 }
506}
507
508pub const PLATFORM_CONSTRUCT_CATALOG: PlatformConstructCatalog = PlatformConstructCatalog {
509 families: &[
510 PlatformConstructFamily {
511 id: CONSTRUCT_FAMILY_DECLARATION_BLOCK,
512 description: "package-declared block syntax that lowers to metadata",
513 },
514 PlatformConstructFamily {
515 id: CONSTRUCT_FAMILY_EFFECT_OPERATION,
516 description: "rule-body operation syntax that lowers to a core effect template",
517 },
518 PlatformConstructFamily {
519 id: CONSTRUCT_FAMILY_EFFECT_CONTRACT,
520 description: "package effect-contract metadata used by capability resolution",
521 },
522 PlatformConstructFamily {
523 id: CONSTRUCT_FAMILY_SOURCE_DECLARATION,
524 description: "top-level source blocks that lower to signal-source and clock-source admission templates",
525 },
526 PlatformConstructFamily {
527 id: CONSTRUCT_FAMILY_ASSERTION,
528 description: "assertions that lower to assertion checks",
529 },
530 PlatformConstructFamily {
531 id: CONSTRUCT_FAMILY_RULE,
532 description: "rules that lower to rule templates and fact writes",
533 },
534 PlatformConstructFamily {
535 id: CONSTRUCT_FAMILY_PROJECTION_READ,
536 description: "checker-owned projection reads used by rules and assertions",
537 },
538 ],
539 lowerings: &[
540 PlatformConstructLowering {
541 id: CONSTRUCT_LOWERING_METADATA,
542 compatible_families: &[
543 CONSTRUCT_FAMILY_EFFECT_CONTRACT,
544 CONSTRUCT_FAMILY_PROJECTION_READ,
545 ],
546 package_authorable: false,
547 required_scope: None,
548 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
549 required_interfaces: &[],
550 provided_interfaces: &[],
551 lifecycle_profiles: &["none"],
552 authority_profile: ConstructLoweringAuthorityProfile::None,
553 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
554 },
555 PlatformConstructLowering {
556 id: CONSTRUCT_LOWERING_METADATA_ONLY,
557 compatible_families: &[CONSTRUCT_FAMILY_DECLARATION_BLOCK],
558 package_authorable: true,
559 required_scope: None,
560 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
561 required_interfaces: &[],
562 provided_interfaces: &[],
563 lifecycle_profiles: &["none"],
564 authority_profile: ConstructLoweringAuthorityProfile::None,
565 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
566 },
567 PlatformConstructLowering {
568 id: CONSTRUCT_LOWERING_CAPABILITY_CALL,
569 compatible_families: &[CONSTRUCT_FAMILY_EFFECT_OPERATION],
570 package_authorable: true,
571 required_scope: Some(CONSTRUCT_SCOPE_RULE_BODY),
572 target_capability: ConstructTargetCapabilityPolicy::RequiredCapabilityCallContract,
573 required_interfaces: &[CONSTRUCT_INTERFACE_CAPABILITY],
574 provided_interfaces: &[CONSTRUCT_INTERFACE_EFFECT_HANDLE],
575 lifecycle_profiles: &["effect_graph", "typed_effect_graph"],
576 authority_profile: ConstructLoweringAuthorityProfile::CapabilityScoped,
577 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
578 },
579 PlatformConstructLowering {
580 id: CONSTRUCT_LOWERING_TYPED_EFFECT_CALL,
581 compatible_families: &[CONSTRUCT_FAMILY_EFFECT_OPERATION],
582 package_authorable: true,
587 required_scope: Some(CONSTRUCT_SCOPE_RULE_BODY),
588 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
589 required_interfaces: &[CONSTRUCT_INTERFACE_CAPABILITY],
590 provided_interfaces: &[CONSTRUCT_INTERFACE_EFFECT_HANDLE],
591 lifecycle_profiles: &["typed_effect_graph"],
592 authority_profile: ConstructLoweringAuthorityProfile::CapabilityScoped,
593 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
594 },
595 PlatformConstructLowering {
596 id: CONSTRUCT_LOWERING_RESOURCE_EFFECT,
597 compatible_families: &[CONSTRUCT_FAMILY_EFFECT_OPERATION],
598 package_authorable: false,
599 required_scope: Some(CONSTRUCT_SCOPE_RULE_BODY),
600 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
601 required_interfaces: &["Resource"],
602 provided_interfaces: &[CONSTRUCT_INTERFACE_EFFECT_HANDLE],
603 lifecycle_profiles: &["resource_effect_graph"],
604 authority_profile: ConstructLoweringAuthorityProfile::CapabilityScoped,
605 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
606 },
607 PlatformConstructLowering {
608 id: CONSTRUCT_LOWERING_CORE_EFFECT,
609 compatible_families: &[CONSTRUCT_FAMILY_EFFECT_OPERATION],
610 package_authorable: false,
611 required_scope: Some(CONSTRUCT_SCOPE_RULE_BODY),
612 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
613 required_interfaces: &[],
614 provided_interfaces: &[CONSTRUCT_INTERFACE_EFFECT_HANDLE],
615 lifecycle_profiles: &["effect_graph", "typed_effect_graph"],
616 authority_profile: ConstructLoweringAuthorityProfile::CapabilityScoped,
617 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
618 },
619 PlatformConstructLowering {
620 id: CONSTRUCT_LOWERING_SIGNAL_EMIT,
621 compatible_families: &[CONSTRUCT_FAMILY_EFFECT_OPERATION],
622 package_authorable: false,
623 required_scope: Some(CONSTRUCT_SCOPE_RULE_BODY),
624 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
625 required_interfaces: &["Event"],
626 provided_interfaces: &[],
627 lifecycle_profiles: &["event_record"],
628 authority_profile: ConstructLoweringAuthorityProfile::EventAdmission,
629 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
630 },
631 PlatformConstructLowering {
632 id: CONSTRUCT_LOWERING_SIGNAL_SOURCE,
633 compatible_families: &[CONSTRUCT_FAMILY_SOURCE_DECLARATION],
634 package_authorable: false,
635 required_scope: None,
636 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
637 required_interfaces: &[],
638 provided_interfaces: &[],
639 lifecycle_profiles: &["signal_source_template"],
640 authority_profile: ConstructLoweringAuthorityProfile::EventAdmission,
641 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
642 },
643 PlatformConstructLowering {
644 id: CONSTRUCT_LOWERING_CLOCK_SOURCE,
645 compatible_families: &[CONSTRUCT_FAMILY_SOURCE_DECLARATION],
646 package_authorable: false,
647 required_scope: None,
648 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
649 required_interfaces: &[],
650 provided_interfaces: &[],
651 lifecycle_profiles: &["clock_source_template"],
652 authority_profile: ConstructLoweringAuthorityProfile::EventAdmission,
653 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
654 },
655 PlatformConstructLowering {
656 id: CONSTRUCT_LOWERING_SCHEDULE_EMITTER,
657 compatible_families: &[CONSTRUCT_FAMILY_EFFECT_OPERATION],
658 package_authorable: false,
659 required_scope: Some(CONSTRUCT_SCOPE_RULE_BODY),
660 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
661 required_interfaces: &[],
662 provided_interfaces: &[CONSTRUCT_INTERFACE_EFFECT_HANDLE],
663 lifecycle_profiles: &["schedule_template"],
664 authority_profile: ConstructLoweringAuthorityProfile::EventAdmission,
665 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
666 },
667 PlatformConstructLowering {
668 id: CONSTRUCT_LOWERING_RULE_TEMPLATE,
669 compatible_families: &[CONSTRUCT_FAMILY_RULE],
670 package_authorable: false,
671 required_scope: None,
672 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
673 required_interfaces: &[],
674 provided_interfaces: &[],
675 lifecycle_profiles: &["rule_template"],
676 authority_profile: ConstructLoweringAuthorityProfile::None,
677 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
678 },
679 PlatformConstructLowering {
680 id: CONSTRUCT_LOWERING_PROJECTION_VIEW,
681 compatible_families: &[CONSTRUCT_FAMILY_PROJECTION_READ],
682 package_authorable: false,
683 required_scope: None,
684 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
685 required_interfaces: &["Projection"],
686 provided_interfaces: &[],
687 lifecycle_profiles: &["event_projection"],
688 authority_profile: ConstructLoweringAuthorityProfile::ProjectionSource,
689 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
690 },
691 PlatformConstructLowering {
692 id: CONSTRUCT_LOWERING_ASSERTION_CHECK,
693 compatible_families: &[CONSTRUCT_FAMILY_ASSERTION],
694 package_authorable: false,
695 required_scope: None,
696 target_capability: ConstructTargetCapabilityPolicy::Forbidden,
697 required_interfaces: &[],
698 provided_interfaces: &[],
699 lifecycle_profiles: &["assertion_check"],
700 authority_profile: ConstructLoweringAuthorityProfile::None,
701 static_guarantees: CONSTRUCT_PLATFORM_STATIC_GUARANTEES,
702 },
703 ],
704 scopes: &[
705 "top_level",
706 CONSTRUCT_SCOPE_RULE_BODY,
707 "workflow_body",
708 "expression",
709 ],
710 field_kinds: &[
711 "identifier",
712 "string",
713 "number",
714 "boolean",
715 "duration",
716 "type_ref",
717 "provider_ref",
718 "capability_ref",
719 "event_ref",
720 "effect_ref",
721 "expression",
722 "predicate",
723 "list",
724 "record",
725 "enum",
726 ],
727 interface_kinds: &[
728 "Resource",
729 "Projection",
730 "Event",
731 "SignalSource",
732 "EffectContract",
733 "Operation",
734 CONSTRUCT_INTERFACE_CAPABILITY,
735 "ProviderKind",
736 "Profile",
737 "Binding",
738 CONSTRUCT_INTERFACE_EFFECT_HANDLE,
739 "TerminalOutput",
740 "Value",
741 "ContextArtifact",
742 "Diagnostic",
743 ],
744 interface_phases: &[
745 "compile",
746 "runtime",
747 CONSTRUCT_INTERFACE_PHASE_COMPILE_RUNTIME,
748 ],
749 interface_cardinalities: &[
750 CONSTRUCT_INTERFACE_CARDINALITY_EXACTLY_ONE,
751 "optional-one",
752 "many",
753 "named-many",
754 ],
755 reserved_keywords: &[
756 "acquire", "agent", "append", "ask", "call", "cancel", "case", "claim", "class", "coerce",
761 "complete", "consume", "counter", "credential", "decide", "effect", "else", "emit", "enum",
762 "event", "fail", "flow", "from", "harness", "if", "ledger", "lease", "let", "match",
763 "release", "renew", "rule", "tracker", "signal", "tell", "then", "use", "when", "workflow",
764 ],
765 reserved_keyword_privileges: &[
766 PlatformReservedKeywordPrivilege {
767 keyword: "claim",
768 library_id: "std.tracker",
769 construct_family: CONSTRUCT_FAMILY_EFFECT_OPERATION,
770 scope: CONSTRUCT_SCOPE_RULE_BODY,
771 lowering_target: CONSTRUCT_LOWERING_TYPED_EFFECT_CALL,
772 },
773 PlatformReservedKeywordPrivilege {
774 keyword: "renew",
775 library_id: "std.tracker",
776 construct_family: CONSTRUCT_FAMILY_EFFECT_OPERATION,
777 scope: CONSTRUCT_SCOPE_RULE_BODY,
778 lowering_target: CONSTRUCT_LOWERING_TYPED_EFFECT_CALL,
779 },
780 PlatformReservedKeywordPrivilege {
781 keyword: "release",
782 library_id: "std.tracker",
783 construct_family: CONSTRUCT_FAMILY_EFFECT_OPERATION,
784 scope: CONSTRUCT_SCOPE_RULE_BODY,
785 lowering_target: CONSTRUCT_LOWERING_TYPED_EFFECT_CALL,
786 },
787 PlatformReservedKeywordPrivilege {
793 keyword: "tracker",
794 library_id: "std.tracker",
795 construct_family: CONSTRUCT_FAMILY_DECLARATION_BLOCK,
796 scope: "top_level",
797 lowering_target: CONSTRUCT_LOWERING_METADATA_ONLY,
798 },
799 PlatformReservedKeywordPrivilege {
800 keyword: "counter",
801 library_id: "std.coord",
802 construct_family: CONSTRUCT_FAMILY_DECLARATION_BLOCK,
803 scope: "top_level",
804 lowering_target: CONSTRUCT_LOWERING_METADATA_ONLY,
805 },
806 PlatformReservedKeywordPrivilege {
807 keyword: "lease",
808 library_id: "std.coord",
809 construct_family: CONSTRUCT_FAMILY_DECLARATION_BLOCK,
810 scope: "top_level",
811 lowering_target: CONSTRUCT_LOWERING_METADATA_ONLY,
812 },
813 PlatformReservedKeywordPrivilege {
814 keyword: "ledger",
815 library_id: "std.coord",
816 construct_family: CONSTRUCT_FAMILY_DECLARATION_BLOCK,
817 scope: "top_level",
818 lowering_target: CONSTRUCT_LOWERING_METADATA_ONLY,
819 },
820 PlatformReservedKeywordPrivilege {
825 keyword: "credential",
826 library_id: "std.custody",
827 construct_family: CONSTRUCT_FAMILY_DECLARATION_BLOCK,
828 scope: "top_level",
829 lowering_target: CONSTRUCT_LOWERING_METADATA_ONLY,
830 },
831 PlatformReservedKeywordPrivilege {
841 keyword: "acquire",
842 library_id: "std.coord",
843 construct_family: CONSTRUCT_FAMILY_EFFECT_OPERATION,
844 scope: CONSTRUCT_SCOPE_RULE_BODY,
845 lowering_target: CONSTRUCT_LOWERING_RESOURCE_EFFECT,
846 },
847 PlatformReservedKeywordPrivilege {
848 keyword: "append",
849 library_id: "std.coord",
850 construct_family: CONSTRUCT_FAMILY_EFFECT_OPERATION,
851 scope: CONSTRUCT_SCOPE_RULE_BODY,
852 lowering_target: CONSTRUCT_LOWERING_RESOURCE_EFFECT,
853 },
854 PlatformReservedKeywordPrivilege {
855 keyword: "consume",
856 library_id: "std.coord",
857 construct_family: CONSTRUCT_FAMILY_EFFECT_OPERATION,
858 scope: CONSTRUCT_SCOPE_RULE_BODY,
859 lowering_target: CONSTRUCT_LOWERING_RESOURCE_EFFECT,
860 },
861 PlatformReservedKeywordPrivilege {
862 keyword: "release",
863 library_id: "std.coord",
864 construct_family: CONSTRUCT_FAMILY_EFFECT_OPERATION,
865 scope: CONSTRUCT_SCOPE_RULE_BODY,
866 lowering_target: CONSTRUCT_LOWERING_RESOURCE_EFFECT,
867 },
868 PlatformReservedKeywordPrivilege {
881 keyword: "signal",
882 library_id: "std.ingress",
883 construct_family: CONSTRUCT_FAMILY_DECLARATION_BLOCK,
884 scope: "top_level",
885 lowering_target: CONSTRUCT_LOWERING_METADATA_ONLY,
886 },
887 PlatformReservedKeywordPrivilege {
888 keyword: "emit",
889 library_id: "std.ingress",
890 construct_family: CONSTRUCT_FAMILY_EFFECT_OPERATION,
891 scope: CONSTRUCT_SCOPE_RULE_BODY,
892 lowering_target: CONSTRUCT_LOWERING_SIGNAL_EMIT,
893 },
894 ],
895};
896
897#[derive(Clone, Debug, Eq, PartialEq)]
898pub struct EffectContract {
899 pub id: String,
900 pub library_id: String,
901 pub version: String,
902 pub effect_kind: String,
903 pub source_forms: Vec<String>,
904 pub input_schema: Option<String>,
905 pub output_schema: Option<String>,
906 pub required_capabilities: Vec<String>,
907 pub provider_kinds: Vec<String>,
908 pub projected_facts: Vec<String>,
909 pub validation: TypedOutputValidation,
910}
911
912#[derive(Clone, Copy, Debug, Eq, PartialEq)]
913pub enum TypedOutputValidation {
914 None,
915 RuntimeBoundary,
916}
917
918impl TypedOutputValidation {
919 pub fn as_str(self) -> &'static str {
920 match self {
921 Self::None => "none",
922 Self::RuntimeBoundary => "runtime_boundary",
923 }
924 }
925}
926
927#[derive(Clone, Debug, Eq, PartialEq)]
928pub struct ContractRegistryDiagnostic {
929 pub code: String,
930 pub message: String,
931}
932
933pub const MESSAGING_SEND_CAPABILITY: &str = "messaging.send";
947
948pub fn std_messaging_send_construct() -> ConstructRegistration {
950 ConstructRegistration {
951 id: MESSAGING_SEND_CAPABILITY.to_owned(),
952 library_id: "std.messaging".to_owned(),
953 version: "0.1.0".to_owned(),
954 construct_family: CONSTRUCT_FAMILY_EFFECT_OPERATION.to_owned(),
955 keyword: "send".to_owned(),
956 scope: CONSTRUCT_SCOPE_RULE_BODY.to_owned(),
957 grammar: Some(ConstructGrammar {
961 shape: CONSTRUCT_GRAMMAR_SHAPE_EFFECT_OPERATION.to_owned(),
962 keyword: "send".to_owned(),
963 slots: vec![ConstructGrammarSlot {
964 name: "channel".to_owned(),
965 kind: "identifier".to_owned(),
966 connective: Some("via".to_owned()),
967 }],
968 payload: Some(vec![
969 ConstructGrammarPayloadField {
970 name: "text".to_owned(),
971 kind: "expression".to_owned(),
972 required: true,
973 },
974 ConstructGrammarPayloadField {
975 name: "markdown".to_owned(),
976 kind: "expression".to_owned(),
977 required: false,
978 },
979 ConstructGrammarPayloadField {
980 name: "thread_id".to_owned(),
981 kind: "expression".to_owned(),
982 required: false,
983 },
984 ]),
985 binding: "required".to_owned(),
986 target_capability: MESSAGING_SEND_CAPABILITY.to_owned(),
987 clauses: None,
988 }),
989 fields: vec![
993 ConstructField {
994 name: "channel".to_owned(),
995 kind: "identifier".to_owned(),
996 required: true,
997 },
998 ConstructField {
999 name: "text".to_owned(),
1000 kind: "expression".to_owned(),
1001 required: true,
1002 },
1003 ConstructField {
1004 name: "markdown".to_owned(),
1005 kind: "expression".to_owned(),
1006 required: false,
1007 },
1008 ConstructField {
1009 name: "thread_id".to_owned(),
1010 kind: "expression".to_owned(),
1011 required: false,
1012 },
1013 ConstructField {
1014 name: "binding".to_owned(),
1015 kind: "identifier".to_owned(),
1016 required: true,
1017 },
1018 ],
1019 requires: vec![ConstructInterface {
1020 kind: CONSTRUCT_INTERFACE_CAPABILITY.to_owned(),
1021 name: Some(MESSAGING_SEND_CAPABILITY.to_owned()),
1022 type_ref: None,
1023 phase: CONSTRUCT_INTERFACE_PHASE_COMPILE_RUNTIME.to_owned(),
1024 cardinality: CONSTRUCT_INTERFACE_CARDINALITY_EXACTLY_ONE.to_owned(),
1025 }],
1026 provides: vec![ConstructInterface {
1029 kind: CONSTRUCT_INTERFACE_EFFECT_HANDLE.to_owned(),
1030 name: None,
1031 type_ref: Some("MessageSendReceipt".to_owned()),
1032 phase: CONSTRUCT_INTERFACE_PHASE_COMPILE_RUNTIME.to_owned(),
1033 cardinality: CONSTRUCT_INTERFACE_CARDINALITY_EXACTLY_ONE.to_owned(),
1034 }],
1035 lowering_target: CONSTRUCT_LOWERING_CAPABILITY_CALL.to_owned(),
1036 target_capability: Some(MESSAGING_SEND_CAPABILITY.to_owned()),
1037 }
1038}
1039
1040pub fn std_messaging_send_effect_contract() -> EffectContract {
1043 EffectContract {
1044 id: MESSAGING_SEND_CAPABILITY.to_owned(),
1045 library_id: "std.messaging".to_owned(),
1046 version: "0.1.0".to_owned(),
1047 effect_kind: "capability.call".to_owned(),
1048 source_forms: vec!["send".to_owned()],
1049 input_schema: Some(r#"{"channel":"string","text":"string"}"#.to_owned()),
1054 output_schema: Some(
1060 r#"{"accepted_at":"string","channel":"string","destination":"string","message_id":"string","provider":"string","provider_message_id":"string","status":"string","thread_id":"string"}"#.to_owned(),
1061 ),
1062 required_capabilities: vec![MESSAGING_SEND_CAPABILITY.to_owned()],
1063 provider_kinds: vec!["messaging".to_owned()],
1064 projected_facts: vec!["effect.output".to_owned()],
1065 validation: TypedOutputValidation::RuntimeBoundary,
1066 }
1067}
1068
1069impl ContractRegistry {
1070 pub fn merge(&mut self, other: ContractRegistry) {
1071 for library in other.libraries {
1072 self.upsert_library(library);
1073 }
1074 for form in other.constructs {
1075 self.upsert_construct(form);
1076 }
1077 for contract in other.effect_contracts {
1078 self.upsert_effect_contract(contract);
1079 }
1080 self.libraries.sort_by(|left, right| left.id.cmp(&right.id));
1081 self.constructs.sort_by(|left, right| {
1082 left.id
1083 .cmp(&right.id)
1084 .then_with(|| left.version.cmp(&right.version))
1085 });
1086 self.effect_contracts.sort_by(|left, right| {
1087 left.id
1088 .cmp(&right.id)
1089 .then_with(|| left.version.cmp(&right.version))
1090 });
1091 }
1092
1093 pub fn upsert_library(&mut self, library: LibraryRegistration) {
1094 if let Some(existing) = self
1095 .libraries
1096 .iter_mut()
1097 .find(|existing| existing.id == library.id)
1098 {
1099 if existing.version == "unlocked" && library.version != "unlocked" {
1100 *existing = library;
1101 } else if existing.version == library.version {
1102 existing.standard |= library.standard;
1103 } else if library.version != "unlocked" {
1104 self.libraries.push(library);
1105 }
1106 return;
1107 }
1108 self.libraries.push(library);
1109 }
1110
1111 pub fn upsert_construct(&mut self, form: ConstructRegistration) {
1112 if self.constructs.iter().any(|existing| {
1113 existing.id == form.id && existing.version == form.version && existing == &form
1114 }) {
1115 return;
1116 }
1117 self.constructs.push(form);
1118 }
1119
1120 pub fn upsert_effect_contract(&mut self, contract: EffectContract) {
1121 if let Some(existing) = self
1122 .effect_contracts
1123 .iter_mut()
1124 .find(|existing| existing.id == contract.id && existing.version == contract.version)
1125 {
1126 if existing.library_id == contract.library_id
1127 && existing.effect_kind == contract.effect_kind
1128 && existing.input_schema == contract.input_schema
1129 && existing.output_schema == contract.output_schema
1130 && existing.validation == contract.validation
1131 {
1132 merge_unique_list(&mut existing.source_forms, &contract.source_forms);
1133 merge_unique_list(
1134 &mut existing.required_capabilities,
1135 &contract.required_capabilities,
1136 );
1137 merge_unique_list(&mut existing.provider_kinds, &contract.provider_kinds);
1138 merge_unique_list(&mut existing.projected_facts, &contract.projected_facts);
1139 } else {
1140 self.effect_contracts.push(contract);
1141 }
1142 return;
1143 }
1144 self.effect_contracts.push(contract);
1145 }
1146
1147 pub fn validate(&self) -> Vec<ContractRegistryDiagnostic> {
1148 let mut diagnostics = Vec::new();
1149 let mut libraries = BTreeSet::new();
1150
1151 for library in &self.libraries {
1152 if library.id.trim().is_empty() {
1153 diagnostics.push(registry_diagnostic(
1154 "library_id_empty",
1155 "library registration has an empty id",
1156 ));
1157 }
1158 if library.version.trim().is_empty() {
1159 diagnostics.push(registry_diagnostic(
1160 "library_version_empty",
1161 format!("library `{}` has an empty version", library.id),
1162 ));
1163 }
1164 if !libraries.insert(library.id.clone()) {
1165 diagnostics.push(registry_diagnostic(
1166 "library_duplicate",
1167 format!("library `{}` is registered more than once", library.id),
1168 ));
1169 }
1170 }
1171
1172 let library_ids = self
1173 .libraries
1174 .iter()
1175 .map(|library| library.id.as_str())
1176 .collect::<BTreeSet<_>>();
1177 let mut constructs = BTreeSet::new();
1178 let mut construct_keywords = BTreeSet::new();
1179
1180 for form in &self.constructs {
1181 if form.id.trim().is_empty() {
1182 diagnostics.push(registry_diagnostic(
1183 "construct_id_empty",
1184 "construct has an empty id",
1185 ));
1186 }
1187 if form.version.trim().is_empty() {
1188 diagnostics.push(registry_diagnostic(
1189 "construct_version_empty",
1190 format!("construct `{}` has an empty version", form.id),
1191 ));
1192 }
1193 if form.keyword.trim().is_empty() {
1194 diagnostics.push(registry_diagnostic(
1195 "construct_keyword_empty",
1196 format!("construct `{}` has an empty keyword", form.id),
1197 ));
1198 }
1199 if form.construct_family.trim().is_empty() {
1200 diagnostics.push(registry_diagnostic(
1201 "construct_family_empty",
1202 format!("construct `{}` has an empty construct family", form.id),
1203 ));
1204 }
1205 if form.scope.trim().is_empty() {
1206 diagnostics.push(registry_diagnostic(
1207 "construct_scope_empty",
1208 format!("construct `{}` has an empty scope", form.id),
1209 ));
1210 }
1211 if form.lowering_target.trim().is_empty() {
1212 diagnostics.push(registry_diagnostic(
1213 "construct_lowering_target_empty",
1214 format!("construct `{}` has an empty lowering target", form.id),
1215 ));
1216 }
1217 if form
1218 .target_capability
1219 .as_deref()
1220 .is_some_and(|target| target.trim().is_empty())
1221 {
1222 diagnostics.push(registry_diagnostic(
1223 "construct_target_capability_empty",
1224 format!("construct `{}` has an empty target capability", form.id),
1225 ));
1226 }
1227 if !library_ids.contains(form.library_id.as_str()) {
1228 diagnostics.push(registry_diagnostic(
1229 "construct_unknown_library",
1230 format!(
1231 "construct `{}` references unknown library `{}`",
1232 form.id, form.library_id
1233 ),
1234 ));
1235 }
1236 if !constructs.insert((form.id.clone(), form.version.clone())) {
1237 diagnostics.push(registry_diagnostic(
1238 "construct_duplicate",
1239 format!(
1240 "construct `{}` version `{}` is registered more than once",
1241 form.id, form.version
1242 ),
1243 ));
1244 }
1245 if !construct_keywords.insert((form.scope.clone(), form.keyword.clone())) {
1246 diagnostics.push(registry_diagnostic(
1247 "construct_keyword_duplicate",
1248 format!(
1249 "construct keyword `{}` is registered more than once for `{}`",
1250 form.keyword, form.scope
1251 ),
1252 ));
1253 }
1254 validate_construct_fields(form, &mut diagnostics);
1255 validate_construct_interfaces(form, "requires", &form.requires, &mut diagnostics);
1256 validate_construct_interfaces(form, "provides", &form.provides, &mut diagnostics);
1257 }
1258
1259 let mut contracts = BTreeSet::new();
1260
1261 for contract in &self.effect_contracts {
1262 if contract.id.trim().is_empty() {
1263 diagnostics.push(registry_diagnostic(
1264 "effect_contract_id_empty",
1265 "effect contract has an empty id",
1266 ));
1267 }
1268 if contract.version.trim().is_empty() {
1269 diagnostics.push(registry_diagnostic(
1270 "effect_contract_version_empty",
1271 format!("effect contract `{}` has an empty version", contract.id),
1272 ));
1273 }
1274 if contract.effect_kind.trim().is_empty() {
1275 diagnostics.push(registry_diagnostic(
1276 "effect_kind_empty",
1277 format!("effect contract `{}` has an empty effect kind", contract.id),
1278 ));
1279 }
1280 if contract.source_forms.is_empty() {
1281 diagnostics.push(registry_diagnostic(
1282 "source_forms_empty",
1283 format!("effect contract `{}` declares no source forms", contract.id),
1284 ));
1285 }
1286 if !library_ids.contains(contract.library_id.as_str()) {
1287 diagnostics.push(registry_diagnostic(
1288 "effect_contract_unknown_library",
1289 format!(
1290 "effect contract `{}` references unknown library `{}`",
1291 contract.id, contract.library_id
1292 ),
1293 ));
1294 }
1295 if !contracts.insert((contract.id.clone(), contract.version.clone())) {
1296 diagnostics.push(registry_diagnostic(
1297 "effect_contract_duplicate",
1298 format!(
1299 "effect contract `{}` version `{}` is registered more than once",
1300 contract.id, contract.version
1301 ),
1302 ));
1303 }
1304 validate_unique_list(
1305 "required_capability_duplicate",
1306 &format!("effect contract `{}`", contract.id),
1307 "required capability",
1308 &contract.required_capabilities,
1309 &mut diagnostics,
1310 );
1311 validate_unique_list(
1312 "provider_kind_duplicate",
1313 &format!("effect contract `{}`", contract.id),
1314 "provider kind",
1315 &contract.provider_kinds,
1316 &mut diagnostics,
1317 );
1318 validate_unique_list(
1319 "projected_fact_duplicate",
1320 &format!("effect contract `{}`", contract.id),
1321 "projected fact",
1322 &contract.projected_facts,
1323 &mut diagnostics,
1324 );
1325 if contract.validation == TypedOutputValidation::RuntimeBoundary
1326 && contract.output_schema.is_none()
1327 {
1328 diagnostics.push(registry_diagnostic(
1329 "runtime_validation_without_output_schema",
1330 format!(
1331 "effect contract `{}` uses runtime validation without an output schema",
1332 contract.id
1333 ),
1334 ));
1335 }
1336 if !contract.projected_facts.is_empty() && contract.output_schema.is_none() {
1337 diagnostics.push(registry_diagnostic(
1338 "projection_without_output_schema",
1339 format!(
1340 "effect contract `{}` projects facts without an output schema",
1341 contract.id
1342 ),
1343 ));
1344 }
1345 }
1346
1347 diagnostics
1348 }
1349}
1350
1351fn validate_construct_fields(
1352 form: &ConstructRegistration,
1353 diagnostics: &mut Vec<ContractRegistryDiagnostic>,
1354) {
1355 let mut fields = BTreeMap::new();
1356 for field in &form.fields {
1357 if field.name.trim().is_empty() {
1358 diagnostics.push(registry_diagnostic(
1359 "construct_field_name_empty",
1360 format!("construct `{}` has a field with an empty name", form.id),
1361 ));
1362 }
1363 if field.kind.trim().is_empty() {
1364 diagnostics.push(registry_diagnostic(
1365 "construct_field_kind_empty",
1366 format!(
1367 "construct `{}` field `{}` has an empty kind",
1368 form.id, field.name
1369 ),
1370 ));
1371 }
1372 let count = fields.entry(field.name.clone()).or_insert(0usize);
1373 *count += 1;
1374 }
1375 for (field, count) in fields {
1376 if count > 1 {
1377 diagnostics.push(registry_diagnostic(
1378 "construct_field_duplicate",
1379 format!(
1380 "construct `{}` declares field `{field}` more than once",
1381 form.id
1382 ),
1383 ));
1384 }
1385 }
1386}
1387
1388fn validate_construct_interfaces(
1389 form: &ConstructRegistration,
1390 direction: &str,
1391 interfaces: &[ConstructInterface],
1392 diagnostics: &mut Vec<ContractRegistryDiagnostic>,
1393) {
1394 for interface in interfaces {
1395 if interface.kind.trim().is_empty() {
1396 diagnostics.push(registry_diagnostic(
1397 "construct_interface_kind_empty",
1398 format!(
1399 "construct `{}` {direction} interface has an empty kind",
1400 form.id
1401 ),
1402 ));
1403 }
1404 if interface
1405 .name
1406 .as_deref()
1407 .is_some_and(|name| name.trim().is_empty())
1408 {
1409 diagnostics.push(registry_diagnostic(
1410 "construct_interface_name_empty",
1411 format!(
1412 "construct `{}` {direction} interface `{}` has an empty name",
1413 form.id, interface.kind
1414 ),
1415 ));
1416 }
1417 if interface
1418 .type_ref
1419 .as_deref()
1420 .is_some_and(|type_ref| type_ref.trim().is_empty())
1421 {
1422 diagnostics.push(registry_diagnostic(
1423 "construct_interface_type_empty",
1424 format!(
1425 "construct `{}` {direction} interface `{}` has an empty type",
1426 form.id, interface.kind
1427 ),
1428 ));
1429 }
1430 if interface.phase.trim().is_empty() {
1431 diagnostics.push(registry_diagnostic(
1432 "construct_interface_phase_empty",
1433 format!(
1434 "construct `{}` {direction} interface `{}` has an empty phase",
1435 form.id, interface.kind
1436 ),
1437 ));
1438 }
1439 if interface.cardinality.trim().is_empty() {
1440 diagnostics.push(registry_diagnostic(
1441 "construct_interface_cardinality_empty",
1442 format!(
1443 "construct `{}` {direction} interface `{}` has an empty cardinality",
1444 form.id, interface.kind
1445 ),
1446 ));
1447 }
1448 }
1449}
1450
1451fn merge_unique_list(target: &mut Vec<String>, values: &[String]) {
1452 for value in values {
1453 if !target.contains(value) {
1454 target.push(value.clone());
1455 }
1456 }
1457 target.sort();
1458}
1459
1460fn registry_diagnostic(
1461 code: impl Into<String>,
1462 message: impl Into<String>,
1463) -> ContractRegistryDiagnostic {
1464 ContractRegistryDiagnostic {
1465 code: code.into(),
1466 message: message.into(),
1467 }
1468}
1469
1470fn validate_unique_list(
1471 code: &str,
1472 owner: &str,
1473 label: &str,
1474 values: &[String],
1475 diagnostics: &mut Vec<ContractRegistryDiagnostic>,
1476) {
1477 let mut seen = BTreeMap::new();
1478 for value in values {
1479 let count = seen.entry(value).or_insert(0usize);
1480 *count += 1;
1481 }
1482 for (value, count) in seen {
1483 if count > 1 {
1484 diagnostics.push(registry_diagnostic(
1485 code,
1486 format!("{owner} declares {label} `{value}` more than once"),
1487 ));
1488 }
1489 }
1490}
1491
1492#[cfg(test)]
1493mod tests {
1494 use super::*;
1495
1496 #[test]
1497 fn exposes_stage_marker() {
1498 assert_eq!(IMPLEMENTATION_STAGE, "release");
1499 }
1500
1501 #[test]
1502 fn exposes_version() {
1503 assert!(!version().is_empty());
1504 }
1505
1506 #[test]
1507 fn derive_fields_maps_declaration_block_clauses() {
1508 let grammar = ConstructGrammar {
1512 shape: CONSTRUCT_GRAMMAR_SHAPE_DECLARATION_BLOCK.to_owned(),
1513 keyword: "ledger".to_owned(),
1514 slots: Vec::new(),
1515 payload: None,
1516 binding: "none".to_owned(),
1517 target_capability: String::new(),
1518 clauses: Some(vec![
1519 ConstructGrammarClause {
1520 name: "shared".to_owned(),
1521 kind: "flag".to_owned(),
1522 required: false,
1523 list: false,
1524 connective: None,
1525 },
1526 ConstructGrammarClause {
1527 name: "partition".to_owned(),
1528 kind: "identifier".to_owned(),
1529 required: true,
1530 list: false,
1531 connective: Some("by".to_owned()),
1532 },
1533 ConstructGrammarClause {
1534 name: "allow read".to_owned(),
1535 kind: "glob".to_owned(),
1536 required: false,
1537 list: true,
1538 connective: None,
1539 },
1540 ]),
1541 };
1542
1543 let fields = grammar.derive_fields();
1544 assert_eq!(
1545 fields,
1546 vec![
1547 ConstructField {
1549 name: "shared".to_owned(),
1550 kind: "boolean".to_owned(),
1551 required: false,
1552 },
1553 ConstructField {
1556 name: "partition".to_owned(),
1557 kind: "identifier".to_owned(),
1558 required: true,
1559 },
1560 ConstructField {
1562 name: "allow read".to_owned(),
1563 kind: "list".to_owned(),
1564 required: false,
1565 },
1566 ]
1567 );
1568
1569 for field in &fields {
1572 assert!(
1573 PLATFORM_CONSTRUCT_CATALOG.contains_field_kind(&field.kind),
1574 "derived field kind `{}` must be a platform field kind",
1575 field.kind
1576 );
1577 }
1578 }
1579
1580 #[test]
1581 fn severity_round_trips_the_canonical_set() {
1582 assert_eq!(Severity::ALL.len(), 4);
1583 for severity in Severity::ALL {
1584 assert_eq!(Severity::from_wire(severity.as_str()), Some(severity));
1585 }
1586 assert_eq!(
1587 Severity::ALL.map(Severity::as_str),
1588 ["error", "warning", "info", "hint"]
1589 );
1590 assert_eq!(Severity::from_wire("note"), None);
1592 assert_eq!(Severity::from_wire("normal"), None);
1593 }
1594
1595 #[test]
1596 fn severity_lsp_codes_align_one_to_one() {
1597 assert_eq!(
1599 Severity::ALL.map(Severity::lsp_code),
1600 [1, 2, 3, 4],
1601 "error/warning/info/hint must map to LSP 1/2/3/4"
1602 );
1603 }
1604
1605 #[test]
1606 fn platform_construct_catalog_defines_current_executable_slice() {
1607 assert!(PLATFORM_CONSTRUCT_CATALOG
1608 .family(CONSTRUCT_FAMILY_DECLARATION_BLOCK)
1609 .is_some());
1610 assert!(PLATFORM_CONSTRUCT_CATALOG
1611 .family(CONSTRUCT_FAMILY_EFFECT_OPERATION)
1612 .is_some());
1613
1614 let capability_call = PLATFORM_CONSTRUCT_CATALOG
1615 .lowering(CONSTRUCT_LOWERING_CAPABILITY_CALL)
1616 .expect("capability_call lowering");
1617 assert_eq!(
1618 capability_call.compatible_families,
1619 &[CONSTRUCT_FAMILY_EFFECT_OPERATION]
1620 );
1621 assert!(capability_call.package_authorable);
1622 assert_eq!(
1623 capability_call.required_scope,
1624 Some(CONSTRUCT_SCOPE_RULE_BODY)
1625 );
1626 assert_eq!(
1627 capability_call.target_capability,
1628 ConstructTargetCapabilityPolicy::RequiredCapabilityCallContract
1629 );
1630 assert_eq!(
1631 capability_call.required_interfaces,
1632 &[CONSTRUCT_INTERFACE_CAPABILITY]
1633 );
1634 assert_eq!(
1635 capability_call.provided_interfaces,
1636 &[CONSTRUCT_INTERFACE_EFFECT_HANDLE]
1637 );
1638 assert_eq!(
1639 capability_call.lifecycle_profiles,
1640 &["effect_graph", "typed_effect_graph"]
1641 );
1642 assert_eq!(
1643 capability_call.authority_profile,
1644 ConstructLoweringAuthorityProfile::CapabilityScoped
1645 );
1646 assert_eq!(
1647 capability_call.static_guarantees,
1648 CONSTRUCT_PLATFORM_STATIC_GUARANTEES
1649 );
1650
1651 let metadata_only = PLATFORM_CONSTRUCT_CATALOG
1652 .lowering(CONSTRUCT_LOWERING_METADATA_ONLY)
1653 .expect("metadata_only lowering");
1654 assert_eq!(
1655 metadata_only.compatible_families,
1656 &[CONSTRUCT_FAMILY_DECLARATION_BLOCK]
1657 );
1658 assert!(metadata_only.package_authorable);
1659 assert_eq!(
1660 metadata_only.target_capability,
1661 ConstructTargetCapabilityPolicy::Forbidden
1662 );
1663
1664 assert!(PLATFORM_CONSTRUCT_CATALOG
1665 .family(CONSTRUCT_FAMILY_SOURCE_DECLARATION)
1666 .is_some());
1667
1668 let signal_source = PLATFORM_CONSTRUCT_CATALOG
1669 .lowering(CONSTRUCT_LOWERING_SIGNAL_SOURCE)
1670 .expect("signal_source lowering");
1671 assert!(!signal_source.package_authorable);
1672 assert_eq!(
1673 signal_source.compatible_families,
1674 &[CONSTRUCT_FAMILY_SOURCE_DECLARATION]
1675 );
1676 assert_eq!(
1677 signal_source.lifecycle_profiles,
1678 &["signal_source_template"]
1679 );
1680
1681 let clock_source = PLATFORM_CONSTRUCT_CATALOG
1682 .lowering(CONSTRUCT_LOWERING_CLOCK_SOURCE)
1683 .expect("clock_source lowering");
1684 assert!(!clock_source.package_authorable);
1685 assert_eq!(
1686 clock_source.compatible_families,
1687 &[CONSTRUCT_FAMILY_SOURCE_DECLARATION]
1688 );
1689 assert_eq!(clock_source.lifecycle_profiles, &["clock_source_template"]);
1690 assert_eq!(
1691 clock_source.authority_profile,
1692 ConstructLoweringAuthorityProfile::EventAdmission
1693 );
1694
1695 assert!(PLATFORM_CONSTRUCT_CATALOG.contains_reserved_keyword("claim"));
1696 assert!(PLATFORM_CONSTRUCT_CATALOG.contains_reserved_keyword("lease"));
1697 assert!(PLATFORM_CONSTRUCT_CATALOG
1701 .reserved_keyword_privilege(
1702 "std.tracker",
1703 "claim",
1704 CONSTRUCT_FAMILY_EFFECT_OPERATION,
1705 CONSTRUCT_SCOPE_RULE_BODY,
1706 CONSTRUCT_LOWERING_TYPED_EFFECT_CALL,
1707 )
1708 .is_some());
1709 assert!(PLATFORM_CONSTRUCT_CATALOG
1710 .reserved_keyword_privilege(
1711 "std.tracker",
1712 "claim",
1713 CONSTRUCT_FAMILY_EFFECT_OPERATION,
1714 CONSTRUCT_SCOPE_RULE_BODY,
1715 CONSTRUCT_LOWERING_CAPABILITY_CALL,
1716 )
1717 .is_none());
1718 assert!(PLATFORM_CONSTRUCT_CATALOG
1719 .reserved_keyword_privilege(
1720 "memory",
1721 "claim",
1722 CONSTRUCT_FAMILY_EFFECT_OPERATION,
1723 CONSTRUCT_SCOPE_RULE_BODY,
1724 CONSTRUCT_LOWERING_TYPED_EFFECT_CALL,
1725 )
1726 .is_none());
1727 }
1728
1729 #[test]
1730 fn validates_duplicate_and_malformed_contracts() {
1731 let registry = ContractRegistry {
1732 libraries: vec![LibraryRegistration {
1733 id: "std.coercion".to_owned(),
1734 version: "v0".to_owned(),
1735 standard: true,
1736 }],
1737 constructs: vec![
1738 ConstructRegistration {
1739 id: "coerce.form".to_owned(),
1740 library_id: "std.coercion".to_owned(),
1741 version: "v0".to_owned(),
1742 construct_family: "declaration_block".to_owned(),
1743 keyword: "coerce".to_owned(),
1744 scope: "top_level".to_owned(),
1745 grammar: None,
1746 fields: vec![ConstructField {
1747 name: "name".to_owned(),
1748 kind: "identifier".to_owned(),
1749 required: true,
1750 }],
1751 requires: Vec::new(),
1752 provides: Vec::new(),
1753 lowering_target: "metadata_only".to_owned(),
1754 target_capability: None,
1755 },
1756 ConstructRegistration {
1757 id: "coerce.form".to_owned(),
1758 library_id: "missing".to_owned(),
1759 version: "v0".to_owned(),
1760 construct_family: String::new(),
1761 keyword: "coerce".to_owned(),
1762 scope: "top_level".to_owned(),
1763 grammar: None,
1764 fields: vec![
1765 ConstructField {
1766 name: "name".to_owned(),
1767 kind: "identifier".to_owned(),
1768 required: true,
1769 },
1770 ConstructField {
1771 name: "name".to_owned(),
1772 kind: String::new(),
1773 required: false,
1774 },
1775 ],
1776 requires: vec![ConstructInterface {
1777 kind: String::new(),
1778 name: Some(String::new()),
1779 type_ref: Some(String::new()),
1780 phase: String::new(),
1781 cardinality: String::new(),
1782 }],
1783 provides: Vec::new(),
1784 lowering_target: String::new(),
1785 target_capability: Some(String::new()),
1786 },
1787 ],
1788 effect_contracts: vec![
1789 EffectContract {
1790 id: "schema.coerce".to_owned(),
1791 library_id: "std.coercion".to_owned(),
1792 version: "v0".to_owned(),
1793 effect_kind: "schema.coerce".to_owned(),
1794 source_forms: vec!["coerce".to_owned()],
1795 input_schema: Some("schema.coerce.input".to_owned()),
1796 output_schema: Some("typed-provider-output".to_owned()),
1797 required_capabilities: vec!["model.invoke".to_owned()],
1798 provider_kinds: vec!["model".to_owned()],
1799 projected_facts: vec!["effect.output".to_owned()],
1800 validation: TypedOutputValidation::RuntimeBoundary,
1801 },
1802 EffectContract {
1803 id: "schema.coerce".to_owned(),
1804 library_id: "missing".to_owned(),
1805 version: "v0".to_owned(),
1806 effect_kind: "schema.coerce".to_owned(),
1807 source_forms: Vec::new(),
1808 input_schema: None,
1809 output_schema: None,
1810 required_capabilities: vec![
1811 "model.invoke".to_owned(),
1812 "model.invoke".to_owned(),
1813 ],
1814 provider_kinds: Vec::new(),
1815 projected_facts: vec!["effect.output".to_owned()],
1816 validation: TypedOutputValidation::RuntimeBoundary,
1817 },
1818 ],
1819 };
1820
1821 let codes = registry
1822 .validate()
1823 .into_iter()
1824 .map(|diagnostic| diagnostic.code)
1825 .collect::<BTreeSet<_>>();
1826
1827 assert!(codes.contains("effect_contract_unknown_library"));
1828 assert!(codes.contains("effect_contract_duplicate"));
1829 assert!(codes.contains("construct_unknown_library"));
1830 assert!(codes.contains("construct_duplicate"));
1831 assert!(codes.contains("construct_keyword_duplicate"));
1832 assert!(codes.contains("construct_family_empty"));
1833 assert!(codes.contains("construct_field_duplicate"));
1834 assert!(codes.contains("construct_field_kind_empty"));
1835 assert!(codes.contains("construct_interface_kind_empty"));
1836 assert!(codes.contains("construct_interface_name_empty"));
1837 assert!(codes.contains("construct_interface_type_empty"));
1838 assert!(codes.contains("construct_interface_phase_empty"));
1839 assert!(codes.contains("construct_interface_cardinality_empty"));
1840 assert!(codes.contains("construct_lowering_target_empty"));
1841 assert!(codes.contains("source_forms_empty"));
1842 assert!(codes.contains("required_capability_duplicate"));
1843 assert!(codes.contains("runtime_validation_without_output_schema"));
1844 assert!(codes.contains("projection_without_output_schema"));
1845 }
1846
1847 #[test]
1848 fn merge_replaces_unlocked_import_with_locked_library() {
1849 let mut registry = ContractRegistry {
1850 libraries: vec![LibraryRegistration {
1851 id: "memory".to_owned(),
1852 version: "unlocked".to_owned(),
1853 standard: false,
1854 }],
1855 constructs: Vec::new(),
1856 effect_contracts: Vec::new(),
1857 };
1858
1859 registry.merge(ContractRegistry {
1860 libraries: vec![LibraryRegistration {
1861 id: "memory".to_owned(),
1862 version: "0.1.0".to_owned(),
1863 standard: false,
1864 }],
1865 constructs: Vec::new(),
1866 effect_contracts: Vec::new(),
1867 });
1868
1869 assert_eq!(
1870 registry.libraries,
1871 vec![LibraryRegistration {
1872 id: "memory".to_owned(),
1873 version: "0.1.0".to_owned(),
1874 standard: false,
1875 }]
1876 );
1877 assert_eq!(registry.validate(), Vec::new());
1878 }
1879}
1880
1881#[cfg(test)]
1882mod registry_wellformedness_refusal_tests {
1883 use super::*;
1901
1902 fn library() -> LibraryRegistration {
1903 LibraryRegistration {
1904 id: "acme.widgets".to_owned(),
1905 version: "0.1.0".to_owned(),
1906 standard: false,
1907 }
1908 }
1909
1910 fn construct() -> ConstructRegistration {
1911 ConstructRegistration {
1912 id: "acme.widgets.block".to_owned(),
1913 library_id: "acme.widgets".to_owned(),
1914 version: "0.1.0".to_owned(),
1915 construct_family: "declaration_block".to_owned(),
1916 keyword: "widget".to_owned(),
1917 scope: "top_level".to_owned(),
1918 grammar: None,
1919 fields: vec![ConstructField {
1920 name: "path".to_owned(),
1921 kind: "expression".to_owned(),
1922 required: true,
1923 }],
1924 requires: Vec::new(),
1925 provides: Vec::new(),
1926 lowering_target: "metadata_only".to_owned(),
1927 target_capability: None,
1928 }
1929 }
1930
1931 fn effect_contract() -> EffectContract {
1932 EffectContract {
1933 id: "acme.widgets.call".to_owned(),
1934 library_id: "acme.widgets".to_owned(),
1935 version: "0.1.0".to_owned(),
1936 effect_kind: "capability.call".to_owned(),
1937 source_forms: vec!["acme.widgets.block".to_owned()],
1938 input_schema: None,
1939 output_schema: None,
1940 required_capabilities: Vec::new(),
1941 provider_kinds: Vec::new(),
1942 projected_facts: Vec::new(),
1943 validation: TypedOutputValidation::None,
1944 }
1945 }
1946
1947 fn registry() -> ContractRegistry {
1948 ContractRegistry {
1949 libraries: vec![library()],
1950 constructs: vec![construct()],
1951 effect_contracts: vec![effect_contract()],
1952 }
1953 }
1954
1955 fn codes(registry: &ContractRegistry) -> BTreeSet<String> {
1956 registry
1957 .validate()
1958 .into_iter()
1959 .map(|diagnostic| diagnostic.code)
1960 .collect()
1961 }
1962
1963 #[test]
1964 fn a_well_formed_registry_validates_silently() {
1965 assert_eq!(
1966 registry().validate(),
1967 Vec::new(),
1968 "the baseline every case below perturbs must itself be clean"
1969 );
1970 }
1971
1972 #[test]
1974 fn an_empty_identifier_is_refused_wherever_it_appears() {
1975 let cases: Vec<(&str, ContractRegistry)> = vec![
1976 ("library_id_empty", {
1977 let mut r = registry();
1978 r.libraries[0].id = String::new();
1979 r.constructs[0].library_id = String::new();
1983 r.effect_contracts[0].library_id = String::new();
1984 r
1985 }),
1986 ("library_version_empty", {
1987 let mut r = registry();
1988 r.libraries[0].version = String::new();
1989 r
1990 }),
1991 ("library_duplicate", {
1992 let mut r = registry();
1993 r.libraries.push(library());
1994 r
1995 }),
1996 ("construct_id_empty", {
1997 let mut r = registry();
1998 r.constructs[0].id = String::new();
1999 r
2000 }),
2001 ("construct_version_empty", {
2002 let mut r = registry();
2003 r.constructs[0].version = String::new();
2004 r
2005 }),
2006 ("construct_keyword_empty", {
2007 let mut r = registry();
2008 r.constructs[0].keyword = String::new();
2009 r
2010 }),
2011 ("construct_scope_empty", {
2012 let mut r = registry();
2013 r.constructs[0].scope = String::new();
2014 r
2015 }),
2016 ("construct_target_capability_empty", {
2019 let mut r = registry();
2020 r.constructs[0].target_capability = Some(" ".to_owned());
2021 r
2022 }),
2023 ("construct_field_name_empty", {
2024 let mut r = registry();
2025 r.constructs[0].fields[0].name = String::new();
2026 r
2027 }),
2028 ("effect_contract_id_empty", {
2029 let mut r = registry();
2030 r.effect_contracts[0].id = String::new();
2031 r
2032 }),
2033 ("effect_contract_version_empty", {
2034 let mut r = registry();
2035 r.effect_contracts[0].version = String::new();
2036 r
2037 }),
2038 ("effect_kind_empty", {
2039 let mut r = registry();
2040 r.effect_contracts[0].effect_kind = String::new();
2041 r
2042 }),
2043 ];
2044
2045 let mut missing = Vec::new();
2048 for (expected, perturbed) in &cases {
2049 let found = codes(perturbed);
2050 if !found.contains(*expected) {
2051 missing.push(format!("{expected} (got {found:?})"));
2052 }
2053 }
2054 assert!(
2055 missing.is_empty(),
2056 "{} of {} registry refusals did not fire:\n {}",
2057 missing.len(),
2058 cases.len(),
2059 missing.join("\n ")
2060 );
2061 }
2062
2063 #[test]
2068 fn a_whitespace_only_identifier_is_empty() {
2069 let mut r = registry();
2070 r.constructs[0].keyword = " \t ".to_owned();
2071 assert!(codes(&r).contains("construct_keyword_empty"));
2072 }
2073}