Chio/Docs

Signed Tool Manifests

Every tool server in chio publishes a signed manifest declaring what tools it provides, the schemas for their inputs and outputs, and the permissions it needs from its host. The manifest format lives in chio-manifest; the schema identifier is chio.manifest.v1. The runtime kernel verifies the signature against a registered public key before the server is admitted, so a compromised server cannot quietly advertise tools it should not expose or change prices after registration.

Why Manifests Are Signed

Two failure modes motivate the signature.

  • Unauthorized tool advertisement: a compromised server might add a tool entry that looks benign and route invocations to a malicious backend. The kernel admits only the manifest a known key signed, so the attacker would need both the binary and the signing key to succeed.
  • Post-hoc price changes: pricing lives inside the signed manifest. An operator who set a budget against a published quote knows that quote cannot change without a fresh signature. Detecting drift is a single key check.

The signature is not authorization

A valid signature confirms the manifest was produced by the named key. It does not authorize tool calls. Authorization flows through capability tokens at runtime; the manifest is the catalog the kernel registers against, not a grant of access.

Top-Level Fields

The struct lives at chio-manifest/src/lib.rs:23-58 and serializes with #[serde(deny_unknown_fields)] so unknown top-level fields fail deserialization closed:

crates/chio-manifest/src/lib.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ToolManifest {
    pub schema: String,
    pub server_id: chio_core::ServerId,
    pub name: String,
    pub description: Option<String>,
    pub version: String,
    pub tools: Vec<ToolDefinition>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub server_tools: Vec<ServerTool>,
    pub required_permissions: Option<RequiredPermissions>,
    pub public_key: String,
}
FieldTypeMeaning
schemaStringMUST equal the constant TOOL_MANIFEST_SCHEMA = "chio.manifest.v1" (lib.rs:20).
server_idchio_core::ServerIdStable unique identifier; the registration path uses it to look up the registered key.
nameStringHuman-readable server name.
descriptionOption<String>Optional server description.
versionStringSemantic version of this tool server.
toolsVec<ToolDefinition>The tools this server provides. validate_manifest rejects an empty vec with EmptyManifest (lib.rs:240-242).
server_toolsVec<ServerTool>Provider-native server tools allowlisted (Anthropic computer_use, bash, text_editor). Defaults to empty (#[serde(default, skip_serializing_if = "Vec::is_empty")]); absent entries default to deny.
required_permissionsOption<RequiredPermissions>Optional. Filesystem, network, and environment-variable declarations. The struct is descriptive only; the host sandbox does the enforcing.
public_keyStringHex-encoded Ed25519 public key of this tool server. Cross-checked against the verifying key during admission.

ToolDefinition

Each entry in tools is a ToolDefinition. It captures the contract the kernel admits.

FieldTypeMeaning
nameStringTool name. Unique within the server.
descriptionStringHuman-readable description.
input_schemaJSON valueJSON Schema for the tool's input arguments.
output_schemaOption<JSON value>Optional JSON Schema for the tool's output.
pricingOption<ToolPricing>Optional advertised pricing metadata. See Pricing Models & SLAs.
has_side_effectsboolWhether this tool writes files, sends network requests, or modifies state. Read-only tools can be cached.
latency_hintOption<LatencyHint>Estimated latency category: instant, fast, moderate, slow.

Latency hints are advisory

Latency hints feed scheduling heuristics and operator dashboards. They are not SLAs. Real latency commitments live on the marketplace listing's ListingSla (see Capability Discovery).

ToolPricing

The pricing block on a ToolDefinition lives at chio-manifest/src/lib.rs:142-152. It carries four fields, all of them honoring deny_unknown_fields:

crates/chio-manifest/src/lib.rs
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ToolPricing {
    pub pricing_model: PricingModel,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub base_price: Option<MonetaryAmount>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub unit_price: Option<MonetaryAmount>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub billing_unit: Option<String>,
}

The currency travels inside MonetaryAmount { units, currency }: ToolPricing itself has no top-level currency field. It also has no max_cost_per_invocation (that field belongs to ToolGrant at chio-core-types/src/capability.rs:1749) and no sla_guarantees (those belong to ListingSla at chio-listing/src/discovery.rs:118-138). See the dedicated section below.

PricingModel enum

The four variants at chio-manifest/src/lib.rs:154-161:

crates/chio-manifest/src/lib.rs
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PricingModel {
    Flat,
    PerInvocation,
    PerUnit,
    Hybrid,
}
VariantWire formUse when
Flat"flat"A single fixed amount per call. Set base_price; leave unit_price and billing_unit unset.
PerInvocation"per_invocation"Charge per call with a per-call unit_price and billing_unit = "invocation". The metering pipeline emits one ledger entry per receipt.
PerUnit"per_unit"Charge by an output-derived count (tokens, rows, bytes). Set unit_price and a meaningful billing_unit string.
Hybrid"hybrid"A flat connection fee plus per-unit charge. Set base_price, unit_price, and billing_unit. Settlement adds them.

Where SLA Guarantees and Per-Call Caps Live

Operators commonly look on ToolPricing for two adjacent concepts that do not live there:

  • SLA commitments are carried by chio_listing::ListingSla (chio-listing/src/discovery.rs:118-138), paired into a ListingPricingHint:
    crates/chio-listing/src/discovery.rs
    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
    #[serde(rename_all = "camelCase")]
    pub struct ListingSla {
        pub max_latency_ms: u64,
        /// Availability SLA expressed in basis points. 10_000 means 100.00%.
        pub availability_bps: u32,
        pub throughput_rps: u64,
    }
  • Per-call cost caps live on the issued capability, not on the manifest. ToolGrant::max_cost_per_invocation at chio-core-types/src/capability.rs:1747-1749 is an Option<MonetaryAmount> checked against parent grants in ToolGrant::is_subset_of (capability.rs:1805-1813).

Signing

Manifests are signed with Ed25519 over the canonical JSON encoding of the manifest body. The signed envelope is SignedManifest:

rust
pub struct SignedManifest {
    /// The tool manifest.
    pub manifest: ToolManifest,
    /// Ed25519 signature over the canonical JSON encoding of manifest.
    pub signature: Signature,
    /// The signing key (for verification without out-of-band lookup).
    pub signer_key: PublicKey,
}

Signing and verification are exposed through two free functions:

rust
pub fn sign_manifest(
    manifest: &ToolManifest,
    keypair: &Keypair,
) -> Result<SignedManifest, ManifestError>;

pub fn verify_manifest(
    signed: &SignedManifest,
    public_key: &PublicKey,
) -> Result<(), ManifestError>;

Both functions call validate_manifest first. Validation enforces three structural invariants:

  • schema equals chio.manifest.v1. Any other value returns UnsupportedSchema.
  • tools is non-empty. An empty list returns EmptyManifest.
  • Tool names are unique. Duplicates return DuplicateToolName.
  • Server-tool allowlist entries are unique.

required_permissions

A tool server can declare what it needs from its sandbox so the host operator sees the surface up front. RequiredPermissions is optional, with these fields:

FieldTypeMeaning
read_pathsOption<Vec<String>>Filesystem paths the server reads.
write_pathsOption<Vec<String>>Filesystem paths the server writes.
network_hostsOption<Vec<String>>Network hosts the server reaches.
environment_variablesOption<Vec<String>>Environment variables the server reads.

Permission overreach is a deployment-time decision: the operator either grants the declared permissions or refuses to admit the manifest. required_permissions is descriptive, not enforced inside the manifest itself; the sandbox or host that runs the tool server is the enforcement point.


Provider-Native Server Tools

Some upstream model providers offer server-side tools beyond the regular client-hosted tool surface (Anthropic computer_use_*, bash_*, text_editor_*). These carry a larger trust boundary than ordinary tools, so the manifest must explicitly allowlist them. The ServerTool enum names them; the server_tools field on the manifest is the allowlist; absent entries default to deny.

rust
#[serde(rename_all = "snake_case")]
pub enum ServerTool {
    ComputerUse,
    Bash,
    TextEditor,
}

// Anthropic versions server-tool names with a trailing date,
// e.g. bash_20241022. Treat known categories as server tools so
// version bumps stay fail-closed behind the same allowlist entry.
ServerTool::from_anthropic_wire_name("bash_20241022")
    == Some(ServerTool::Bash);

x-chio-* Extensions for OpenAPI Sources

When chio-openapi generates a manifest from an OpenAPI document, five vendor extensions on each operation feed the manifest and the chio api protect proxy. The vocabulary lives in spec/OPENAPI-INTEGRATION.md sections 2.1 through 2.5; sections 3.x define precedence. The five shipped extensions are sensitivity, side-effects, approval-required, budget-limit, and publish. There is no x-chio-pricing or x-chio-sla; pricing on OpenAPI-derived manifests is set by the operator at signing time.

ExtensionTypeEffectSpec
x-chio-sensitivitystringTag for log granularity: public, internal, sensitive, restricted. Default internal.OPENAPI-INTEGRATION.md §2.1
x-chio-side-effectsbooleanOverride the method-based default. true forces deny-by-default; false forces session-scoped allow.§2.2
x-chio-approval-requiredbooleanForces deny-by-default regardless of method or side-effects. Sets annotations.requires_approval on the generated ToolDefinition.§2.3
x-chio-budget-limitu64Per-invocation cost cap in minor currency units, consumed by the budget guard at request time.§2.4
x-chio-publishbooleanWhen false, the operation is excluded from the generated manifest. Honored only when respect_publish_flag is true.§2.5

Precedence rules from OPENAPI-INTEGRATION.md §3: x-chio-approval-required: true beats both the HTTP method default and any x-chio-side-effects value, so a GET operation marked approval-required still produces deny-by-default. See OpenAPI integration in the spec for the full precedence matrix.


Verification Flow

When a tool server registers with a kernel, the kernel walks the same path on every admission.

  1. Read the SignedManifest from the registration payload.
  2. Run validate_manifest: schema check, non-empty tools, unique names, unique server-tool allowlist.
  3. Verify the Ed25519 signature against the registered public key for that server_id.
  4. Cross-check that the manifest's public_key field matches the verifying key.
  5. Register tools, schemas, pricing, and side-effect flags internally.
rendering…
Tool server signs the manifest, sends it on registration, kernel validates the structure, verifies the Ed25519 signature, and registers tools only when every check passes.

Failure Modes

FailureErrorBehavior
Wrong schema idUnsupportedSchemaReject. Operator updates the producer to emit chio.manifest.v1.
Empty tool listEmptyManifestReject. A server with zero tools has nothing to register.
Duplicate tool nameDuplicateToolName(name)Reject. Producer must collapse duplicates before signing.
Bad signatureVerificationFailedReject. Indicates either tampering or signing with the wrong key.
Unknown top-level fieldSerde deserialization errorReject before validation runs.
Permission overreachOperator decisionOut of band: the manifest declares what it needs; the host decides whether to admit.

Worked Example: Sign and Register

A small native tool server publishes one tool, greet, with per-invocation pricing.

hello-tool/src/main.rs
use chio_core::capability::MonetaryAmount;
use chio_core::crypto::Keypair;
use chio_manifest::{
    sign_manifest, LatencyHint, PricingModel, ToolDefinition,
    ToolManifest, ToolPricing, TOOL_MANIFEST_SCHEMA,
};

fn main() -> anyhow::Result<()> {
    let keypair = Keypair::generate();

    let manifest = ToolManifest {
        schema: TOOL_MANIFEST_SCHEMA.into(),
        server_id: "srv-hello".into(),
        name: "Hello Tool Server".into(),
        description: Some("A demo tool server".into()),
        version: "0.1.0".into(),
        tools: vec![ToolDefinition {
            name: "greet".into(),
            description: "Returns a greeting".into(),
            input_schema: serde_json::json!({
                "type": "object",
                "properties": { "name": { "type": "string" } },
                "required": ["name"]
            }),
            output_schema: None,
            pricing: Some(ToolPricing {
                pricing_model: PricingModel::PerInvocation,
                base_price: None,
                unit_price: Some(MonetaryAmount {
                    units: 50,
                    currency: "USD".to_string(),
                }),
                billing_unit: Some("invocation".into()),
            }),
            has_side_effects: false,
            latency_hint: Some(LatencyHint::Instant),
        }],
        server_tools: Vec::new(),
        required_permissions: None,
        public_key: keypair.public_key().to_hex(),
    };

    let signed = sign_manifest(&manifest, &keypair)?;
    let json = serde_json::to_string_pretty(&signed)?;
    println!("{json}");
    Ok(())
}

The kernel-side admission code calls verify_manifest with the registered public key. On success the tool is registered with its schemas, pricing, and side-effect flag intact.

rust
use chio_manifest::verify_manifest;

let signed = read_signed_manifest_from_registration()?;
let registered_key = lookup_registered_key(&signed.manifest.server_id)?;
verify_manifest(&signed, &registered_key)?;

// Manifest is now admissible. Register tools with the kernel.
for tool in &signed.manifest.tools {
    kernel.register_tool(&signed.manifest.server_id, tool)?;
}

Re-signing on every change

Any change to the manifest, including adding or removing a tool, changing a price, or bumping a version, requires a fresh signature. The kernel will reject a manifest whose body does not exactly match the bytes that were signed. There is no partial update; the unit of signing is the whole manifest.

In the Procurement Tour

This is station 2 of the Procurement Tour: provider publishes the signed manifest the buyer's kernel reads.

Picks up from previous station. The buyer's capability has been issued; the buyer kernel now needs to learn what Vanguard offers and at what rate.

Vanguard Security signs and publishes a ToolManifest declaring its soc2-review tool with per-row metered pricing. The advertised unit_price is denominated in cents per 1000-row batch, so a single billing unit at this rate is $0.001 per evidence row:

vanguard-soc2-review.signed-manifest.json
{
  "manifest": {
    "schema": "chio.manifest.v1",
    "server_id": "vanguard-security",
    "name": "Vanguard SOC 2 Review Tool",
    "description": "Per-row evidence inspection for SOC 2 type II reviews",
    "version": "1.4.0",
    "tools": [
      {
        "name": "soc2-review",
        "description": "Inspect a control evidence row and emit a finding",
        "input_schema": {
          "type": "object",
          "properties": {
            "evidence_uri": { "type": "string" },
            "control_id": { "type": "string" }
          },
          "required": ["evidence_uri", "control_id"]
        },
        "output_schema": null,
        "pricing": {
          "pricing_model": "per_unit",
          "unit_price": { "units": 100, "currency": "USD" },
          "billing_unit": "1000-evidence-rows"
        },
        "has_side_effects": false,
        "latency_hint": "fast"
      }
    ],
    "required_permissions": null,
    "public_key": "c87a..."
  },
  "signature": "ed25519:...",
  "signer_key": "did:chio:c87a..."
}

The buyer kernel runs verify_manifest against Vanguard's registered key, then carries the soc2-review entry plus its ToolPricing forward as the catalog input for the next station's quote request.

Continue at next station, where the buyer assembles a GovernedTransactionIntent that pins the quoted units and cost.

Signed Tool Manifests · Chio Docs