1pub const SOURCE_FINGERPRINT: &str = env!("HARN_SOURCE_FINGERPRINT");
18
19pub mod contracts;
20mod harness_kind;
21pub mod host_capabilities;
22pub mod llm_options;
23pub mod methods;
24pub mod predicate;
25pub mod runtime_type_tags;
26pub mod shapes;
27pub mod signatures;
28
29pub use contracts::{
30 wire_identifier_key, BuiltinContract, BuiltinExposure, CapabilityId, EffectAccess,
31 EffectAuthorization, EffectKind, EffectSpec, ResourceSelector,
32};
33pub use harness_kind::HarnessKind;
34pub use methods::{MethodDescriptor, MethodEffect, MethodMigration, MethodReceiver, RemovedMethod};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub struct BuiltinSignature {
41 pub name: &'static str,
43 pub params: &'static [Param],
47 pub returns: Ty,
50 pub type_params: &'static [&'static str],
53 pub has_rest: bool,
57 pub where_clauses: &'static [(&'static str, &'static str)],
60 pub projection: Option<RecordProjection>,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub enum RecordProjection {
67 Pick { source: usize, keys: usize },
68}
69
70impl BuiltinSignature {
71 pub const fn with_name(self, name: &'static str) -> Self {
73 Self { name, ..self }
74 }
75
76 pub const fn with_projection(self, projection: RecordProjection) -> Self {
77 Self {
78 projection: Some(projection),
79 ..self
80 }
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
86pub struct Param {
87 pub name: &'static str,
88 pub ty: Ty,
89 pub optional: bool,
92}
93
94impl Param {
95 pub const fn new(name: &'static str, ty: Ty) -> Self {
96 Self {
97 name,
98 ty,
99 optional: false,
100 }
101 }
102
103 pub const fn optional(name: &'static str, ty: Ty) -> Self {
104 Self {
105 name,
106 ty,
107 optional: true,
108 }
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub enum Ty {
118 Named(&'static str),
122 Generic(&'static str),
125 Any,
128 Optional(&'static Ty),
130 Apply(&'static str, &'static [Ty]),
134 Union(&'static [Ty]),
137 Fn(&'static [Ty], &'static Ty),
140 Shape(&'static [ShapeFieldDescriptor]),
143 OpenShape(&'static [ShapeFieldDescriptor], &'static [Ty]),
152 SchemaOf(&'static str),
156 Never,
158 LitInt(i64),
160 LitString(&'static str),
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
165pub struct ShapeFieldDescriptor {
166 pub name: &'static str,
167 pub ty: Ty,
168 pub optional: bool,
169}
170
171impl ShapeFieldDescriptor {
172 pub const fn new(name: &'static str, ty: Ty) -> Self {
173 Self {
174 name,
175 ty,
176 optional: false,
177 }
178 }
179
180 pub const fn optional(name: &'static str, ty: Ty) -> Self {
181 Self {
182 name,
183 ty,
184 optional: true,
185 }
186 }
187}
188
189impl Ty {
190 pub const fn is_any(&self) -> bool {
192 matches!(self, Ty::Any)
193 }
194}
195
196fn write_shape_members(
202 f: &mut core::fmt::Formatter<'_>,
203 fields: &[ShapeFieldDescriptor],
204 rests: &[Ty],
205) -> core::fmt::Result {
206 for (i, fld) in fields.iter().enumerate() {
207 if i > 0 {
208 f.write_str(", ")?;
209 }
210 let name = fld.name;
211 let ty = &fld.ty;
212 let optional = if fld.optional { "?" } else { "" };
213 write!(f, "{name}{optional}: {ty}")?;
214 }
215 for (i, rest) in rests.iter().enumerate() {
216 if i > 0 || !fields.is_empty() {
217 f.write_str(", ")?;
218 }
219 write!(f, "...{rest}")?;
220 }
221 Ok(())
222}
223
224impl core::fmt::Display for Ty {
225 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
231 match self {
232 Ty::Named(s) | Ty::Generic(s) => f.write_str(s),
233 Ty::Any => f.write_str("any"),
234 Ty::Never => f.write_str("never"),
235 Ty::Optional(inner) => write!(f, "{inner}?"),
236 Ty::Apply(name, args) => {
237 f.write_str(name)?;
238 f.write_str("<")?;
239 for (i, a) in args.iter().enumerate() {
240 if i > 0 {
241 f.write_str(", ")?;
242 }
243 write!(f, "{a}")?;
244 }
245 f.write_str(">")
246 }
247 Ty::Union(parts) => {
248 if let [inner, Ty::Named("nil")] = parts {
252 if !matches!(inner, Ty::Named("nil")) {
253 return write!(f, "{inner}?");
254 }
255 }
256 if let [Ty::Named("int"), Ty::Named("float")] = parts {
257 return f.write_str("number");
258 }
259 for (i, p) in parts.iter().enumerate() {
260 if i > 0 {
261 f.write_str(" | ")?;
262 }
263 write!(f, "{p}")?;
264 }
265 Ok(())
266 }
267 Ty::Fn(params, ret) => {
268 f.write_str("(")?;
269 for (i, p) in params.iter().enumerate() {
270 if i > 0 {
271 f.write_str(", ")?;
272 }
273 write!(f, "{p}")?;
274 }
275 write!(f, ") -> {ret}")
276 }
277 Ty::Shape(fields) => {
278 f.write_str("{")?;
279 write_shape_members(f, fields, &[])?;
280 f.write_str("}")
281 }
282 Ty::OpenShape(fields, rests) => {
283 f.write_str("{")?;
284 write_shape_members(f, fields, rests)?;
285 f.write_str("}")
286 }
287 Ty::SchemaOf(t) => write!(f, "Schema<{t}>"),
288 Ty::LitInt(n) => write!(f, "{n}"),
289 Ty::LitString(s) => write!(f, "\"{s}\""),
290 }
291 }
292}
293
294impl BuiltinSignature {
295 pub const fn simple(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
299 Self {
300 name,
301 params,
302 returns,
303 type_params: &[],
304 has_rest: false,
305 where_clauses: &[],
306 projection: None,
307 }
308 }
309
310 pub const fn variadic(name: &'static str, params: &'static [Param], returns: Ty) -> Self {
313 Self {
314 name,
315 params,
316 returns,
317 type_params: &[],
318 has_rest: true,
319 where_clauses: &[],
320 projection: None,
321 }
322 }
323
324 pub const fn generic(
328 name: &'static str,
329 type_params: &'static [&'static str],
330 params: &'static [Param],
331 returns: Ty,
332 ) -> Self {
333 Self {
334 name,
335 params,
336 returns,
337 type_params,
338 has_rest: false,
339 where_clauses: &[],
340 projection: None,
341 }
342 }
343
344 pub fn required_params(&self) -> usize {
346 self.params.iter().filter(|p| !p.optional).count()
347 }
348
349 pub fn is_type_param(&self, name: &str) -> bool {
352 self.type_params.contains(&name)
353 }
354
355 pub fn is_generic(&self) -> bool {
357 !self.type_params.is_empty()
358 }
359
360 pub fn type_param_names(&self) -> Vec<String> {
364 self.type_params.iter().map(|s| (*s).to_string()).collect()
365 }
366
367 pub fn where_clause_strings(&self) -> Vec<(String, String)> {
369 self.where_clauses
370 .iter()
371 .map(|(tp, iface)| ((*tp).to_string(), (*iface).to_string()))
372 .collect()
373 }
374}
375
376impl core::fmt::Display for BuiltinSignature {
377 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
382 if !self.type_params.is_empty() {
383 f.write_str("<")?;
384 for (i, tp) in self.type_params.iter().enumerate() {
385 if i > 0 {
386 f.write_str(", ")?;
387 }
388 f.write_str(tp)?;
389 }
390 if !self.where_clauses.is_empty() {
391 f.write_str(" where ")?;
392 for (i, (tp, iface)) in self.where_clauses.iter().enumerate() {
393 if i > 0 {
394 f.write_str(", ")?;
395 }
396 write!(f, "{tp}: {iface}")?;
397 }
398 }
399 f.write_str("> ")?;
400 }
401 f.write_str(self.name)?;
402 f.write_str("(")?;
403 let last_idx = self.params.len().saturating_sub(1);
404 for (i, p) in self.params.iter().enumerate() {
405 if i > 0 {
406 f.write_str(", ")?;
407 }
408 if self.has_rest && i == last_idx {
409 f.write_str("...")?;
410 }
411 f.write_str(p.name)?;
412 if p.optional {
413 f.write_str("?")?;
414 }
415 let ty = &p.ty;
416 write!(f, ": {ty}")?;
417 }
418 let ret = &self.returns;
419 write!(f, ") -> {ret}")
420 }
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq)]
426pub struct BuiltinMetadata {
427 pub name: &'static str,
428 pub return_types: &'static [&'static str],
429}
430
431pub const TY_ANY: Ty = Ty::Any;
438pub const TY_BOOL: Ty = Ty::Named("bool");
439pub const TY_BYTES: Ty = Ty::Named("bytes");
440pub const TY_CLOSURE: Ty = Ty::Named("closure");
441pub const TY_DECIMAL: Ty = Ty::Named("decimal");
442pub const TY_DICT: Ty = Ty::Named("dict");
443pub const TY_DURATION: Ty = Ty::Named("duration");
444pub const TY_FLOAT: Ty = Ty::Named("float");
445pub const TY_INT: Ty = Ty::Named("int");
446pub const TY_LIST: Ty = Ty::Named("list");
447pub const TY_NEVER: Ty = Ty::Never;
448pub const TY_NIL: Ty = Ty::Named("nil");
449pub const TY_RESOURCE: Ty = Ty::Named("resource");
450pub const TY_STRING: Ty = Ty::Named("string");
451
452pub const TY_STRING_OR_NIL: Ty = Ty::Union(&[TY_STRING, TY_NIL]);
454pub const TY_INT_OR_NIL: Ty = Ty::Union(&[TY_INT, TY_NIL]);
456pub const TY_DICT_OR_NIL: Ty = Ty::Union(&[TY_DICT, TY_NIL]);
458pub const TY_BYTES_OR_NIL: Ty = Ty::Union(&[TY_BYTES, TY_NIL]);
460pub const TY_NUMBER: Ty = Ty::Union(&[TY_INT, TY_FLOAT]);
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 const APPLY_ARGS: &[Ty] = &[TY_DICT];
468 const FN_PARAMS: &[Ty] = &[TY_INT, TY_STRING];
469 const SHAPE_FIELDS: &[ShapeFieldDescriptor] = &[
470 ShapeFieldDescriptor::new("name", TY_STRING),
471 ShapeFieldDescriptor::optional("age", TY_INT),
472 ];
473
474 const OPEN_SHAPE_RESTS: &[Ty] = &[TY_DICT];
475
476 #[test]
477 fn wire_identifier_keys_stay_unique_across_capabilities() {
478 let mut seen = std::collections::HashMap::new();
483 for capability in CapabilityId::ALL {
484 let key = wire_identifier_key(capability.field_name());
485 if let Some(other) = seen.insert(key.clone(), capability.field_name()) {
486 panic!(
487 "`{other}` and `{}` both normalize to `{key}`",
488 capability.field_name()
489 );
490 }
491 }
492 }
493
494 #[test]
495 fn host_namespace_resolves_the_separatorless_spelling() {
496 assert_eq!(
497 CapabilityId::from_host_namespace("prmonitor"),
498 Some(CapabilityId::PrMonitor)
499 );
500 assert_eq!(
501 CapabilityId::from_host_namespace("PrMonitor"),
502 Some(CapabilityId::PrMonitor)
503 );
504 assert_eq!(
505 CapabilityId::from_host_namespace("pr_monitor"),
506 Some(CapabilityId::PrMonitor)
507 );
508 assert_eq!(CapabilityId::from_host_namespace("not_a_capability"), None);
509 assert_eq!(CapabilityId::from_field_name("prmonitor"), None);
512 }
513
514 #[test]
515 fn ty_display_atomic_and_compound() {
516 assert_eq!(format!("{TY_INT}"), "int");
517 assert_eq!(format!("{TY_ANY}"), "any");
518 assert_eq!(format!("{TY_NEVER}"), "never");
519 assert_eq!(format!("{TY_STRING_OR_NIL}"), "string?");
522 let opt_int = Ty::Optional(&TY_INT);
523 assert_eq!(format!("{opt_int}"), "int?");
524 assert_eq!(format!("{TY_NUMBER}"), "number");
526 let list_dict = Ty::Apply("list", APPLY_ARGS);
527 assert_eq!(format!("{list_dict}"), "list<dict>");
528 let lit_int = Ty::LitInt(42);
529 assert_eq!(format!("{lit_int}"), "42");
530 let lit_str = Ty::LitString("pass");
531 assert_eq!(format!("{lit_str}"), "\"pass\"");
532 let schema_t = Ty::SchemaOf("T");
533 assert_eq!(format!("{schema_t}"), "Schema<T>");
534 let fn_ty = Ty::Fn(FN_PARAMS, &TY_BOOL);
535 assert_eq!(format!("{fn_ty}"), "(int, string) -> bool");
536 let shape = Ty::Shape(SHAPE_FIELDS);
537 assert_eq!(format!("{shape}"), "{name: string, age?: int}");
542 let open = Ty::OpenShape(SHAPE_FIELDS, OPEN_SHAPE_RESTS);
543 assert_eq!(format!("{open}"), "{name: string, age?: int, ...dict}");
544 let tail_only = Ty::OpenShape(&[], OPEN_SHAPE_RESTS);
545 assert_eq!(format!("{tail_only}"), "{...dict}");
546 }
547
548 const BASIC_PARAMS: &[Param] = &[Param::new("a", TY_DICT), Param::new("b", TY_DICT)];
549 const REST_PARAMS: &[Param] = &[Param::new("prefix", TY_STRING), Param::new("args", TY_ANY)];
550 const OPT_PARAMS: &[Param] = &[
551 Param::new("receipt", TY_DICT),
552 Param::optional("candidate", TY_ANY),
553 ];
554 const GENERIC_PARAMS: &[Param] = &[Param::new("schema", Ty::SchemaOf("T"))];
555
556 #[test]
557 fn signature_display_basic() {
558 let sig = BuiltinSignature::simple("deep_merge", BASIC_PARAMS, TY_DICT);
559 assert_eq!(format!("{sig}"), "deep_merge(a: dict, b: dict) -> dict");
560 }
561
562 #[test]
563 fn signature_display_with_optional_and_rest() {
564 let sig = BuiltinSignature {
565 name: "io_println",
566 params: REST_PARAMS,
567 returns: TY_NIL,
568 type_params: &[],
569 has_rest: true,
570 where_clauses: &[],
571 projection: None,
572 };
573 assert_eq!(
574 format!("{sig}"),
575 "io_println(prefix: string, ...args: any) -> nil"
576 );
577
578 let opt_sig =
579 BuiltinSignature::simple("lifecycle_replay_resume_input", OPT_PARAMS, TY_DICT);
580 assert_eq!(
581 format!("{opt_sig}"),
582 "lifecycle_replay_resume_input(receipt: dict, candidate?: any) -> dict"
583 );
584 }
585
586 #[test]
587 fn signature_display_with_generics_and_where() {
588 let sig = BuiltinSignature {
589 name: "schema_parse",
590 params: GENERIC_PARAMS,
591 returns: Ty::Generic("T"),
592 type_params: &["T"],
593 has_rest: false,
594 where_clauses: &[("T", "Decode")],
595 projection: None,
596 };
597 assert_eq!(
598 format!("{sig}"),
599 "<T where T: Decode> schema_parse(schema: Schema<T>) -> T"
600 );
601 }
602}