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 (theintag value, a Media URN)o ∈ U: Output dimension (theouttag 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-widecapdagCLI and the direct cartridge CLI.aliasesis required and non-empty.abstract: an optional boolean (defaultfalse). 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:
- Missing or wildcard direction tags resolve to
media:internally. - When
inresolves to the top media URN (media:), the segment is omitted in canonical form. Same forout. The internal value is stillmedia:; the rendered form just doesn’t show it. - Missing
effectresolves todeclaredinternally 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:
inandoutare 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 URNconforms_tothis one. A side typed asmedia:reads as “any A”: there is no constraint on what data flows there.media:voidis the unit type, the nullary value. A side typed asmedia:voidreads 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=nonemeans 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:
- 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 declaredout=;none→ the declaredin=;patch→ the patchedin=. Ops do not pick output labels. - 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/patchrequire tag-equivalence with the inferred output (the effect fully determines the type, so a more specific emission still violates the declaration);declaredrequires conformance to the declaredout=(more specific is legal, more generic fails). A violation is a hardinternalfailure 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:voidando ≠ 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:voidando = 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
inandouttags (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.