harn_parser/builtin_signatures/
lookup.rs1use crate::ast::TypeExpr;
10use harn_builtin_meta::{BuiltinExposure, CapabilityId};
11
12use super::signatures;
13use super::{BuiltinMetadata, BuiltinSignature, Ty, TyExt};
14
15pub fn lookup(name: &str) -> Option<&'static BuiltinSignature> {
21 lookup_with_privileged_wire(name, false)
22}
23
24pub fn lookup_with_privileged_wire(
28 name: &str,
29 allow_privileged_wire: bool,
30) -> Option<&'static BuiltinSignature> {
31 if let Some(entry) = harn_builtin_registry::builtin_entry(name) {
32 return matches!(
33 entry.contract.exposure,
34 BuiltinExposure::PureGlobal | BuiltinExposure::CapabilityFunction { .. }
35 )
36 .then_some(entry.signature)
37 .or_else(|| {
38 (allow_privileged_wire && entry.contract.exposure == BuiltinExposure::PrivilegedWire)
39 .then_some(entry.signature)
40 })
41 .or_else(|| {
42 (crate::legacy_ambient_capabilities_enabled()
43 && matches!(
44 entry.contract.exposure,
45 BuiltinExposure::HarnessMethod { .. }
46 | BuiltinExposure::PrivilegedWire
47 | BuiltinExposure::StdlibInternal
48 | BuiltinExposure::RuntimeInternal
49 ))
50 .then_some(entry.signature)
51 });
52 }
53 if crate::legacy_ambient_capabilities_enabled() {
54 if let Some(entry) = legacy_capability_method_entry(name) {
55 return Some(entry.signature);
56 }
57 if let Some(entry) = legacy_ambient_cap_global_entry(name) {
58 return Some(entry.signature);
59 }
60 if let Some(canonical) = crate::legacy_builtin_alias_target(name) {
61 return lookup(canonical);
62 }
63 }
64 static_signature_index().get(name).copied()
65}
66
67fn static_signature_index(
75) -> &'static std::collections::HashMap<&'static str, &'static BuiltinSignature> {
76 static INDEX: std::sync::OnceLock<
77 std::collections::HashMap<&'static str, &'static BuiltinSignature>,
78 > = std::sync::OnceLock::new();
79 INDEX.get_or_init(|| {
80 let mut index = std::collections::HashMap::new();
81 for group in signatures::groups() {
82 for sig in group {
83 index.entry(sig.name).or_insert(sig);
85 }
86 }
87 index
88 })
89}
90
91pub fn legacy_capability_method_entry(
95 name: &str,
96) -> Option<&'static harn_builtin_registry::BuiltinManifestEntry> {
97 let mut matches = ambient_harness_method_entries()
98 .into_iter()
99 .filter(|entry| {
100 matches!(
101 entry.contract.exposure,
102 BuiltinExposure::HarnessMethod { method, .. } if method == name
103 )
104 });
105 let entry = matches.next()?;
106 matches.next().is_none().then_some(entry)
107}
108
109pub fn legacy_ambient_cap_global_entry(
113 name: &str,
114) -> Option<&'static harn_builtin_registry::BuiltinManifestEntry> {
115 ambient_harness_method_entries()
116 .into_iter()
117 .find(|entry| entry.name.strip_prefix("__cap_") == Some(name))
118}
119
120pub fn legacy_privileged_wire_entry(
123 name: &str,
124) -> Option<&'static harn_builtin_registry::BuiltinManifestEntry> {
125 harn_builtin_registry::installed_manifest()
126 .into_iter()
127 .find(|entry| {
128 matches!(entry.contract.exposure, BuiltinExposure::PrivilegedWire)
129 && entry.name.strip_prefix("__") == Some(name)
130 })
131}
132
133pub fn legacy_ambient_runtime_name(name: &str) -> Option<&'static str> {
141 if let Some(target) = crate::legacy_builtin_alias_target(name) {
142 return Some(target);
143 }
144 legacy_privileged_wire_entry(name).map(|entry| entry.name)
145}
146
147fn ambient_harness_method_entries() -> Vec<&'static harn_builtin_registry::BuiltinManifestEntry> {
148 let installed = harn_builtin_registry::installed_manifest();
153 if installed.is_empty() {
154 harn_capability_contracts::manifest().to_vec()
155 } else {
156 installed
157 }
158}
159
160pub fn lookup_capability_method(
162 capability: CapabilityId,
163 method: &str,
164) -> Option<&'static BuiltinSignature> {
165 capability_method_entry(capability.field_name(), method).map(|entry| entry.signature)
166}
167
168pub fn capability_method_entry(
172 field: &str,
173 method: &str,
174) -> Option<&'static harn_builtin_registry::BuiltinManifestEntry> {
175 let capability = CapabilityId::from_field_name(field)?;
176 harn_builtin_registry::installed_manifest()
177 .iter()
178 .copied()
179 .find(|entry| {
180 matches!(
181 entry.contract.exposure,
182 BuiltinExposure::HarnessMethod {
183 capability: candidate,
184 method: candidate_method,
185 } if candidate == capability && candidate_method == method
186 )
187 })
188 .or_else(|| harn_capability_contracts::capability_method_entry(field, method))
189}
190
191pub fn is_builtin(name: &str) -> bool {
193 lookup(name).is_some()
194 || (crate::legacy_ambient_capabilities_enabled()
195 && crate::is_registered_legacy_hostlib_name(name))
196}
197
198pub fn is_builtin_with_privileged_wire(name: &str, allow_privileged_wire: bool) -> bool {
199 lookup_with_privileged_wire(name, allow_privileged_wire).is_some()
200 || (crate::legacy_ambient_capabilities_enabled()
201 && crate::is_registered_legacy_hostlib_name(name))
202}
203
204pub fn iter_builtin_names() -> impl Iterator<Item = &'static str> {
208 let installed: Vec<_> = harn_builtin_registry::installed_manifest()
209 .into_iter()
210 .filter(|entry| {
211 matches!(
212 entry.contract.exposure,
213 BuiltinExposure::PureGlobal | BuiltinExposure::CapabilityFunction { .. }
214 )
215 })
216 .collect();
217 let installed_names: std::collections::HashSet<&'static str> =
218 harn_builtin_registry::installed_manifest()
219 .into_iter()
220 .map(|entry| entry.name)
221 .collect();
222 installed.into_iter().map(|entry| entry.name).chain(
223 signatures::groups()
224 .into_iter()
225 .flat_map(|g| g.iter())
226 .filter(move |s| !installed_names.contains(s.name))
227 .map(|s| s.name),
228 )
229}
230
231pub fn static_signature_names() -> impl Iterator<Item = &'static str> {
240 signatures::groups()
241 .into_iter()
242 .flat_map(|g| g.iter())
243 .map(|s| s.name)
244}
245
246pub fn iter_builtin_metadata() -> impl Iterator<Item = BuiltinMetadata> {
251 let installed: Vec<_> = harn_builtin_registry::installed_manifest()
252 .into_iter()
253 .filter(|entry| {
254 matches!(
255 entry.contract.exposure,
256 BuiltinExposure::PureGlobal | BuiltinExposure::CapabilityFunction { .. }
257 )
258 })
259 .collect();
260 let installed_names: std::collections::HashSet<&'static str> =
261 harn_builtin_registry::installed_manifest()
262 .into_iter()
263 .map(|entry| entry.name)
264 .collect();
265 installed
266 .into_iter()
267 .map(|entry| BuiltinMetadata {
268 name: entry.name,
269 return_types: builtin_return_type_names(entry.signature),
270 })
271 .chain(
272 signatures::groups()
273 .into_iter()
274 .flat_map(|g| g.iter())
275 .filter(move |s| !installed_names.contains(s.name))
276 .map(|sig| BuiltinMetadata {
277 name: sig.name,
278 return_types: builtin_return_type_names(sig),
279 }),
280 )
281}
282
283pub fn builtin_return_type(name: &str) -> Option<TypeExpr> {
287 let sig = lookup(name)?;
288 if sig.returns.is_any() {
289 return None;
290 }
291 Some(sig.returns.to_type_expr())
292}
293
294pub const UNTYPED_BOUNDARY_SOURCES: &[&str] = &[
305 "json_parse",
306 "json_extract",
307 "yaml_parse",
308 "toml_parse",
309 "llm_call",
310 "llm_call_safe",
311 "llm_completion",
312 "http_get",
313 "http_post",
314 "http_put",
315 "http_patch",
316 "http_delete",
317 "http_download",
318 "http_request",
319 "http_session_request",
320 "http_stream_info",
321 "sse_receive",
322 "sse_server_mock_receive",
323 "sse_server_response",
324 "sse_server_status",
325 "websocket_accept",
326 "websocket_receive",
327 "host_call",
328 "connector_call",
329 "host_tool_call",
330 "mcp_call",
331];
332
333pub fn is_untyped_boundary_source(name: &str) -> bool {
336 UNTYPED_BOUNDARY_SOURCES.contains(&name)
337}
338
339pub fn is_untyped_boundary_capability_method(field: &str, method: &str) -> bool {
347 UNTYPED_BOUNDARY_SOURCES
348 .iter()
349 .filter_map(|name| harness_method_for_ambient_name(name))
350 .any(|(candidate_field, candidate_method)| {
351 candidate_field == field && candidate_method == method
352 })
353}
354
355fn entry_publishes_ambient_name(entry_name: &str, capability_field: &str, ambient: &str) -> bool {
365 if entry_name == ambient {
366 return true;
367 }
368 let Some(rest) = entry_name.strip_prefix("__cap_") else {
369 return false;
370 };
371 rest == ambient
372 || rest
373 .strip_prefix(capability_field)
374 .and_then(|rest| rest.strip_prefix('_'))
375 == Some(ambient)
376}
377
378fn harness_method_for_ambient_name(name: &str) -> Option<(&'static str, &'static str)> {
387 let contract_owned = ambient_harness_method_entries()
394 .into_iter()
395 .find_map(|entry| {
396 let BuiltinExposure::HarnessMethod { capability, method } = entry.contract.exposure
397 else {
398 return None;
399 };
400 entry_publishes_ambient_name(entry.name, capability.field_name(), name)
401 .then_some((capability.field_name(), method))
402 });
403 if contract_owned.is_some() {
404 return contract_owned;
405 }
406 let path = crate::diagnostic::harness_net_replacement(name)
407 .or_else(|| crate::diagnostic::harness_fs_replacement(name))
408 .or_else(|| crate::diagnostic::harness_env_replacement(name))
409 .or_else(|| crate::diagnostic::harness_stdio_replacement(name))
410 .or_else(|| crate::diagnostic::harness_clock_replacement(name))
411 .or_else(|| crate::diagnostic::harness_random_replacement(name))?;
412 path.strip_prefix("harness.")?.split_once('.')
413}
414
415fn builtin_return_type_names(sig: &BuiltinSignature) -> &'static [&'static str] {
421 match &sig.returns {
422 Ty::Named(name) => match *name {
423 "bool" => &["bool"],
424 "bytes" => &["bytes"],
425 "dict" => &["dict"],
426 "float" => &["float"],
427 "int" => &["int"],
428 "list" => &["list"],
429 "nil" => &["nil"],
430 "string" => &["string"],
431 _ => &[],
432 },
433 Ty::Union(members) => match *members {
434 [Ty::Named("string"), Ty::Named("nil")] => &["string", "nil"],
435 [Ty::Named("nil"), Ty::Named("string")] => &["string", "nil"],
436 [Ty::Named("int"), Ty::Named("nil")] => &["int", "nil"],
437 [Ty::Named("nil"), Ty::Named("int")] => &["int", "nil"],
438 [Ty::Named("dict"), Ty::Named("nil")] => &["dict", "nil"],
439 [Ty::Named("nil"), Ty::Named("dict")] => &["dict", "nil"],
440 [Ty::Named("bytes"), Ty::Named("nil")] => &["bytes", "nil"],
441 [Ty::Named("nil"), Ty::Named("bytes")] => &["bytes", "nil"],
442 _ => &[],
443 },
444 Ty::Never => &["never"],
445 _ => &[],
446 }
447}
448
449#[cfg(test)]
450mod ambient_install_regression {
451 use super::*;
452
453 #[test]
454 fn installed_manifest_does_not_shadow_ambient_capability_methods() {
455 let previous = std::env::var_os("HARN_LEGACY_AMBIENT_CAPABILITIES");
459 std::env::set_var("HARN_LEGACY_AMBIENT_CAPABILITIES", "1");
460 crate::refresh_legacy_ambient_capabilities();
461 assert!(
462 is_builtin("store_set"),
463 "capability-contracts fallback must resolve ambient store_set"
464 );
465
466 let entries: &'static [&'static harn_builtin_registry::BuiltinManifestEntry] = Box::leak(
468 harn_capability_contracts::manifest()
469 .to_vec()
470 .into_boxed_slice(),
471 );
472 harn_builtin_registry::install_builtin_manifest(entries);
473
474 assert!(
475 is_builtin("store_set"),
476 "after manifest install, ambient store_set must still resolve uniquely"
477 );
478 assert!(
479 legacy_capability_method_entry("store_set").is_some(),
480 "legacy_capability_method_entry must stay unique after install"
481 );
482
483 match previous {
484 Some(value) => std::env::set_var("HARN_LEGACY_AMBIENT_CAPABILITIES", value),
485 None => std::env::remove_var("HARN_LEGACY_AMBIENT_CAPABILITIES"),
486 }
487 crate::refresh_legacy_ambient_capabilities();
488 }
489}