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