xtask/
provider_manifest.rs1use anyhow::{anyhow, bail, Context, Result};
2use jsonschema::Validator;
3use serde_json::Value;
4use std::{fs, path::Path};
5
6const EXPECTED_INVALID_CODES: &[(&str, &str)] = &[
7 ("duplicate-tool.invalid.json", "duplicate_tool_name"),
8 ("duplicate-rest-path.invalid.json", "duplicate_rest_route"),
9 ("duplicate-cli-alias.invalid.json", "duplicate_cli_command"),
10 ("reserved-cli-command.invalid.json", "reserved_cli_command"),
11 ("missing-env.invalid.json", "invalid_env_declaration"),
12 ("denied-capability.invalid.json", "empty_capability_scope"),
13 (
14 "duplicate-mcp-primitive.invalid.json",
15 "duplicate_mcp_primitive",
16 ),
17 (
18 "generated-doc-prompt-injection.invalid.json",
19 "generated_doc_prompt_injection",
20 ),
21];
22
23pub fn check() -> Result<()> {
24 let root = std::env::current_dir().context("failed to read cwd")?;
25 let schema_path = root.join("docs/contracts/provider-manifest.schema.json");
26 let schema = load_json(&schema_path)?;
27 let compiled = jsonschema::options()
28 .build(&schema)
29 .map_err(|error| anyhow!("failed to compile {}: {error}", schema_path.display()))?;
30 let fixtures = root.join("docs/contracts/examples/provider-manifests");
31
32 let valid = [
33 "static-rust.valid.json",
34 "mcp.valid.json",
35 "mcp-rest-opt-in.valid.json",
36 "openapi.valid.json",
37 "wasm.valid.json",
38 "ai-sdk.valid.json",
39 ];
40 for name in valid {
41 let path = fixtures.join(name);
42 let payload = load_json(&path)?;
43 validate_schema(&compiled, &payload, &path)?;
44 soma_domain::provider_validation::validate_provider_manifest_value(&payload)
45 .with_context(|| format!("semantic validation failed for {}", path.display()))?;
46 }
47
48 for (name, code) in EXPECTED_INVALID_CODES {
49 let path = fixtures.join(name);
50 let payload = load_json(&path)?;
51 validate_schema(&compiled, &payload, &path)?;
52 let error = soma_domain::provider_validation::validate_provider_manifest_value(&payload)
53 .expect_err("invalid fixture should fail semantic validation");
54 if error.code() != *code {
55 bail!(
56 "{} failed with {}, expected {}",
57 path.display(),
58 error.code(),
59 code
60 );
61 }
62 }
63
64 println!("provider manifest contract fixtures are valid");
65 Ok(())
66}
67
68fn load_json(path: &Path) -> Result<Value> {
69 let raw = fs::read(path).with_context(|| format!("failed to read {}", path.display()))?;
70 serde_json::from_slice(&raw).with_context(|| format!("failed to parse {}", path.display()))
71}
72
73fn validate_schema(compiled: &Validator, payload: &Value, path: &Path) -> Result<()> {
74 let errors = compiled
75 .iter_errors(payload)
76 .map(|error| format!("{}: {}", error.instance_path(), error))
77 .collect::<Vec<_>>();
78 if !errors.is_empty() {
79 let details = errors.join("; ");
80 bail!(
81 "{} failed JSON Schema validation: {}",
82 path.display(),
83 details
84 );
85 }
86 Ok(())
87}