In this section

Product structure

A Cap URN is a quadruple over the Tagged URN domain:

C = U × U × U × E

For a Cap URN c ∈ C:

c = (i, o, y, e)

Where:

  • i ∈ U: Input dimension (the in tag value, a Media URN)
  • o ∈ U: Output dimension (the out tag value, a Media URN)
  • y ∈ U: Non-direction cap-tags (op, ext, model, language, etc.)
  • e ∈ E: Effect on runtime media/type identity

Cap definition: aliases and abstract

The Cap URN is the cap’s structural identity. Beyond the URN, a cap definition carries two non-URN fields:

  • aliases: a non-empty list of globally-unique, lowercase ([a-z0-9._-]+, colon-free) names. Aliases are the DNS layer for the cap: they are the single source of a cap’s human-facing names, used by both the fabric-wide capdag CLI and the direct cartridge CLI. aliases is required and non-empty.
  • abstract: an optional boolean (default false). An abstract cap is a generic-input dispatch umbrella: it is never backed by a cartridge and never appears as a runnable graph edge. Its resolve → detect → narrow → dispatch mechanics are specified in Dispatch §”Abstract caps”.

Both fields are governed by publish-time invariants (global alias uniqueness and abstract-cap coverage) specified in Validation rules.


String representation

Canonical form

A Cap URN serializes as:

cap:in="<media-urn>";out="<media-urn>";effect=<effect>;<cap-tags>

Examples:

cap:effect=none
cap:extract;in="media:document;ext=pdf";out="media:record"
cap:in="media:enc=utf-8";out="media:fmt=json;record";prompt

Direction tags

The in, out, and effect coordinates are structural:

Tag Purpose Default
in Input media type media: (any)
out Output media type media: (any)
effect Runtime media/type inference rule declared

Non-direction tags

All other tags form the y dimension. No tag in y has functional meaning to the protocol. Only in, out, and effect participate in dispatch, conformance, and runtime output inference. Cap-tags are arbitrary descriptive metadata: they refine the cap’s definition (so two caps with different y are different caps), but no tag key is privileged. Common descriptive tags include operation names (extract, generate), language codes, model identifiers, and hints. All are equal under the protocol.


Parsing and normalization

Cap URN processing distinguishes three forms:

Form Description
Surface syntax What users may write (may omit in/out/effect)
Canonical form Normalized representation (structural defaults applied, default effect omitted)
Validation target Post-normalization structure that rules check

Surface syntax (accepted input)

Users may omit direction tags or write the trivial wildcard explicitly. These are all valid surface syntax:

cap:test
cap:in=media:;out=media:;test
cap:in=*;test;out=*
cap:effect=none

Normalization to canonical form

Parsing produces a unique canonical representative per cap. Two rules govern the directional axes:

  1. Missing or wildcard direction tags resolve to media: internally.
  2. When in resolves to the top media URN (media:), the segment is omitted in canonical form. Same for out. The internal value is still media:; the rendered form just doesn’t show it.
  3. Missing effect resolves to declared internally and is omitted in canonical form. Non-default effects are preserved.
Surface Syntax Canonical Form
cap:test cap:test
cap:in=media:;test;out=media: cap:test
cap:in=*;test;out=* cap:test
cap:in=media:pdf;extract;out=media:record cap:extract;in=media:pdf;out=media:record
cap:in=media:;out=media:;effect=none cap:effect=none

The value * in direction tags expands to media::

in=*  →  in=media:
out=* →  out=media:

This ensures media: is the unique top of the directional order, and the canonical form is byte-equal across every way of writing it.

Validation target

Validation rules (CU1, CU2 in Validation rules) apply to the canonical form, not surface syntax. After normalization:

  • in and out are always present
  • Their values are valid Media URNs

Quoting

Direction spec values containing ; must be quoted:

cap:in="media:bytes;document;ext=pdf";extract;out="media:record"

Without quotes, media:bytes;document;ext=pdf would parse incorrectly.


Cap kinds

The (i, o, y, e) structure admits a five-way classification by inspecting the directional axes. The classification is logical only: the dispatch protocol does not branch on kind. Tools, UIs, planners, and human readers use it to talk about what a cap does without re-deriving the rules each time.

Two anchor types make the taxonomy fall out:

  • media: is the top type, the universal wildcard over the media URN order. Every other media URN conforms_to this one. A side typed as media: reads as “any A”: there is no constraint on what data flows there.
  • media:void is the unit type, the nullary value. A side typed as media:void reads as “()”: no meaningful data flows there. It is not “invalid” or “absent”; it is the type with exactly one value.

The five kinds

Kind i o e y Reads as
Identity media: media: none empty A → A
Source media:void not void any any () → B
Sink not void media:void any any A → ()
Effect media:void media:void any any () → ()
Transform anything else anything else anything legal any A → B

Each implementation exposes this classification via a kind() method on CapUrn (or its language-port equivalent), returning a CapKind enum value.

Identity is explicit

Identity is not the same thing as the fully generic top-to-top cap.

  • cap:effect=none means true categorical identity

So:

URN Kind Reading
cap:effect=none Identity A → A for any A
cap:passthrough Transform generic top-to-top transform with y-axis refinement

The all-default declared top form is illegal:

URN Status
cap: inadmissible / illegal
cap:in=media:;out=media: inadmissible / illegal

The identity cap is:

pub const CAP_IDENTITY: &str = "cap:effect=none";

Every capset must include the identity cap (see CU1 in Validation rules).

Effect admissibility

A non-default effect is a structural promise about the relation between the declared in= and out=, and an incoherent promise is rejected at construction (validate_admissible, enforced by every CapUrn parser in every language, so the fabric publisher rejects it too, since publishing parses every cap URN):

Effect Admissibility condition
none declared in= must conform_to declared out=, because the output is the input’s type and the declaration must be consistent with that
patch the coordinate delta out − in must be derivable, and applying it back to in= must produce a type conforming to out= (the witness check)
declared always admissible (subject to the bare-top rule above)

cap:in="media:document;ext=pdf";out="media:enc=utf-8";effect=none is therefore illegal: it promises the output type is the input type while declaring a contradictory output.

The effect contract at runtime

The effect is not advisory. Two mechanisms hold every execution to it:

  1. Derived response labels. A cartridge runtime derives the media URN of a cap’s response STREAM_START from the declared effect (derive_response_media): declared → the declared out=; none → the declared in=; patch → the patched in=. Ops do not pick output labels.
  2. The engine audit. At receipt of a cap’s output STREAM_START (before forwarding, persisting, or collecting), the engine checks the emitted media URN with CapUrn::is_conformant_runtime_output, the single effect-conformance predicate: none/patch require tag-equivalence with the inferred output (the effect fully determines the type, so a more specific emission still violates the declaration); declared requires conformance to the declared out= (more specific is legal, more generic fails). A violation is a hard internal failure naming the cap, the effect, and expected vs actual.

The planner’s downstream type refinement (CapUrn::infer_runtime_output_media, see Planner) relies on exactly this contract; the audit is what makes the reliance sound. See Input and output for the runtime mechanics.

Source, sink, effect: void as unit

media:void lets the (i, o, y, e) structure express caps that are not data transformers in the conventional sense.

  • A Source has i = media:void and o ≠ media:void. It produces a value with no meaningful input. Examples: warming a model (cap:in=media:void;out=media:model-artifact;warm), search-models, list-compatible-models, generators driven by configuration alone.
  • A Sink has i ≠ media:void and o = media:void. It absorbs a value with no meaningful output. Examples: discard caps, log-to- telemetry, append-to-index.
  • An Effect has both sides media:void. Reads as () → (). A nullary side-effect cap: warm-cache, ping, health-check, initialize-index, sync-registry, log-heartbeat. Valid in the type theory; useful in practice for command-style operations whose whole purpose is the side effect.

In all three cases the y dimension may carry any tags. media:void on a side is a directional decision; y continues to refine the definition of the cap.

Transform: the default

Transform is the catch-all: at least one side is a non-void media URN, and the cap is neither the explicit identity nor the generic top-to-top cap. Transform covers most caps in practice: the data processors (extract, render, generate-text, embed, convert).

Why the distinction is logical only

Dispatch (the accepts / conforms_to predicates) operates on the (i, o, y, effect) cap structure uniformly. It does not consult CapKind. A Source and a Transform whose in happens to specialize a pattern’s media:void are matched by the same rules; the kind is a description of the result, not a routing dimension.

This separation matters because:

  • The protocol stays simple (one matching rule over four structural coordinates).
  • Tools and humans can still reason about caps in plain terms (“this is a Source: it doesn’t take input”).
  • The kind cannot drift: it is always derivable from the URN. There is no separate field to keep in sync, and no flag a cartridge could set wrongly.

Dimension semantics

The four structural coordinates (i, o, y, e) correspond to four independent questions. The kind taxonomy from Cap kinds is what falls out when those questions are answered.

Input dimension (i)

in answers: what data does this cap consume?

Value Meaning
media:document;ext=pdf “Requires a PDF.”
media: “Accepts any input.” (top; Identity / generic)
media:void “Takes no data input.” (unit; Source / Effect)

media: and media:void are not interchangeable: one says “anything goes here,” the other says “nothing flows here.”

Output dimension (o)

out answers: what data does this cap produce?

Value Meaning
media:json “Produces JSON.”
media: “May produce any output.” (top)
media:void “Produces no data.” (unit; Sink / Effect)

Cap-tags dimension (y)

y answers: what specifies, refines, or labels this cap beyond its data signature?

cap:...;extract;target=metadata

y is itself a Tagged URN (without prefix), with the same matching semantics as any other Tagged URN. Tags in y are arbitrary. No key has functional meaning to the protocol. They distinguish caps with the same data signature (e.g. an extract cap and a summarize cap can both have media:document;ext=pdf → media:enc=utf-8 and remain distinct because their y differs).

A non-empty y is also what distinguishes cap:passthrough (Transform) from the rejected bare top form, even though the directional axes match.


Accessing components

Extracting dimensions

Given a Cap URN string, extract:

let cap = CapUrn::from_string("cap:extract;in=media:pdf;out=media:record")?;

let input: &str = cap.in_spec();    // "media:document;ext=pdf"
let output: &str = cap.out_spec();  // "media:record"
let has_extract: bool = cap.has_marker_tag("extract"); // true
let kind: CapKind = cap.kind()?;    // CapKind::Transform

kind() derives the CapKind classification from the parsed (i, o, y, e) structure. It returns an error only on internally inconsistent state (which CapUrn construction prevents), which is a hard signal that something upstream is broken.

Component types

Component Type Access
Input Media URN string in_spec()
Output Media URN string out_spec()
Cap-tags Key-value map tags, tag(key)
Kind CapKind enum kind()

Specificity

Cap URN specificity is defined in Specificity. Cap URNs have four structural coordinates, but the numeric specificity score only ranks the three tag-bearing weighted coordinates out, in, and y. Those three use the same six-form per-tag ladder (?x:0, x?=v:1, x (=x=*):2, x!=v:3, x=v:4, !x:5), with axis weights:

spec_C(c) = 10_000 * spec_U(c.out)
          +    100 * spec_U(c.in)
          +          spec_U(c.y)

The lexicographic priority (out, in, y) reflects routing intent: producing different things is the largest semantic difference between two caps; consuming different things is next; descriptive y-axis metadata is last. Two orders of magnitude separate each axis so per-axis sums up to ~99 stay in their own digit slot, making the integer both totally ordered and visually decodable (40205 reads as out=4, in=2, y=5).

The effect coordinate is structural but unscored. It changes dispatch/runtime behavior, but it is not treated as a graded refinement dimension for ranking.

Examples by kind, showing the three weighted-coordinate sums (out, in, y) and the weighted total:

URN Kind (out, in, y) spec_C
cap:?effect Transform (0, 0, 0) 0
cap:extract Transform (0, 0, 2) 2
cap:extract;in=media:pdf;out=media:record Transform (2, 2, 2) 20202
cap:in=media:void;out=media:void;ping Effect (2, 2, 2) 20202
cap:extract;target=metadata Transform (0, 0, 6) 6

The fully unconstrained explicit request cap:?effect sits at specificity 0. Identity is the explicit cap:effect=none, not the bare top form.


Partial order structure

Cap URNs form a partial order (specialization order) in the product space. The fully unconstrained explicit request cap:?effect is the top:

                         cap:?effect                   (top request)
                             |
                        cap:extract                       (Transform)
                       /            \
       cap:extract;in=media:document;ext=pdf       cap:extract;out=media:record
                       \            /
            cap:extract;in=media:pdf;out=media:record
                             |
   cap:extract;in=media:pdf;out=media:record;target=metadata     (more specific)

The ordering follows from the dispatch relation (see Dispatch). Note that the kind can change as you move down the lattice: cap:effect=none is Identity, while other refinements are Transform/Source/Sink/Effect as their structure dictates.


Relationship to media URNs

Direction values are media URNs

The in and out tag values are themselves Media URNs:

cap:in="media:bytes;document;ext=pdf";out="media:record"
        ↑                     ↑
    Media URN             Media URN

Matching uses media URN semantics

When matching direction specs, use Media URN matching:

let candidate_in = MediaUrn::from_string("media:bytes")?;
let request_in = MediaUrn::from_string("media:bytes;document;ext=pdf")?;

// For dispatch: request_in must conform to candidate_in
request_in.conforms_to(&candidate_in)  // true

Validation rules

Cap URNs must satisfy (from Validation rules):

  • CU1: Must have in and out tags (enforced via normalization)
  • CU2: Direction values must be valid Media URNs or *

Common patterns by kind

This section walks the five kinds with concrete, idiomatic examples.

Identity

cap:effect=none

The identity morphism. Fully generic on in, out, and y, with an explicit effect=none promise. Required in all capsets.

Transform: typed data processor

cap:extract;in=media:pdf;out=media:record
cap:generate;constrained;in=media:enc=utf-8;language=en;out=media:json
cap:render-page-image;in=media:pdf;out=media:image

This is the most common kind: real data flows in and out, and the y dimension labels the operation and any modifiers.

Source: generator

cap:in=media:void;out=media:model-artifact;warm
cap:in=media:void;out=media:model-list;list-compatible-models
cap:in=media:void;out=media:embeddings-dim;numeric;target=embeddings-dim

media:void on the input side: the cap produces a value driven by its y (configuration tags, args, peer state) rather than a piped input.

Sink: consumer

cap:discard;out=media:void
cap:in=media:json;log;out=media:void

media:void on the output side: the cap absorbs a value but contributes no data to the downstream flow. Useful as a graph terminator.

Effect: nullary side-effect

cap:in=media:void;out=media:void;ping
cap:in=media:void;out=media:void;warm-cache
cap:in=media:void;out=media:void;health-check

Both sides media:void. The whole point of the cap is the side effect: the protocol carries no data either way, only the invocation. Read as () → ().


Summary

Concept Definition
Structure C = U × U × U × E
Components (in, out, y, effect)
Top type media:, the universal wildcard for either direction
Unit type media:void, the nullary value (no data flows on this side)
Identity cap:effect=none
Illegal bare form cap: and cap:in=media:;out=media: are inadmissible
Direction defaults Missing or * → media:; canonical drops in/out when both are media: on admissible caps
Functional axes in, out, and effect participate in dispatch/runtime typing; y is arbitrary metadata
Kinds Identity, Source, Sink, Effect, Transform (logical only)

Cap URNs extend Tagged URNs with four-dimensional structure. The dispatch relation (next document) defines how these dimensions interact for routing. Once dispatch is in place, multiple Cap URNs can be wired into a data-flow graph and serialized via Machine notation.