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