Overview
This document specifies all validation rules for CapDAG. All implementations MUST enforce these rules identically.
Rule categories
| Category | Rules | Scope |
|---|---|---|
| Cap URN Rules | CU1–CU3 | URN structure |
| Cap Definition Rules | RULE1–RULE22 | Capability arguments, outputs and identity |
| Media Def Rules | MS1–MS3 | Media definitions |
| Cross-Validation Rules | XV1–XV9 | Reference integrity |
Validation order
- Structural validation (JSON Schema)
- Cap URN validation (CU1–CU3)
- Cap definition rules (RULE1–RULE14)
- Media def rules (MS1–MS3)
- Cross-validation (XV1–XV7)
Cap URN rules
CU1: Required direction specifiers
Rule: Cap URNs in canonical form MUST include in and out tags.
Surface syntax vs canonical form: Users may omit in/out in surface syntax. During normalization, missing tags default to media:. Validation applies to the canonical form.
Canonical Form:
cap:in="<media-urn>";out="<media-urn>";<other-tags>
Examples:
# Surface syntax → Canonical form
cap:in=media:;out=media:;transform → cap:in=media:;transform;out=media: ✓ (normalized)
# Already canonical
cap:in="media:document;ext=pdf";extract;out="media:record" ✓
cap:in=media:;generate;out="media:enc=utf-8" ✓
Error: Cap URN requires 'in' tag / Cap URN requires 'out' tag
This error occurs only for malformed inputs that bypass normalization (e.g., programmatic construction without validation).
CU2: Valid media URN references
Rule: Direction specifier values (in and out) MUST be:
- A valid Media URN (starting with
media:) - Or the wildcard
*(which normalizes tomedia:)
Examples:
cap:in="media:bytes;document;ext=pdf";extract;out="media:record" ✓
cap:in=*;convert;out=* ✓ (normalizes to media:)
cap:in="invalid";test;out="media:enc=utf-8" ✗
Error: Invalid 'in' media URN: <value>. Must start with 'media:' or be '*'
CU3: Effect admissibility
Rule: A cap URN’s declared effect must be coherent with its declared
in=/out= (see Cap URN structure: effect admissibility):
- The all-default bare top form (
cap:/cap:in=media:;out=media:with default effect and no tags) is inadmissible. effect=none: declaredin=mustconform_todeclaredout=.effect=patch: the coordinate deltaout − inmust be derivable, and applying it back toin=must yield a type conforming toout=.
Enforcement: At CapUrn construction (validate_admissible) in every
implementation. Every parse of a cap URN enforces it, including the
fabric publisher, so an incoherent effect declaration cannot be
published.
Examples:
cap:effect=none ✓ (identity)
cap:decimate-sequence;effect=none ✓ (in=media: ⊑ out=media:)
cap:in="media:document;ext=pdf";out="media:enc=utf-8";effect=none ✗ (in ⋢ out)
cap: ✗ (bare top)
Cap definition rules (RULE1–RULE22)
These rules validate the args and outputs arrays in capability definitions. Every
implementation and the fabric publisher raise each rule under the number it has here: a number
names one rule.
Where they are enforced. A definition is held to these rules wherever it crosses from one program to another, by the side that receives it as well as the side that sends it:
- the fabric publisher, before a definition is served;
- a library reading a definition from a registry, before it is cached or returned. A cached definition that breaks a rule was written under other rules: its cache file is dropped and the definition is fetched again;
- a cartridge, over its own manifest, before it advertises;
- a host reading the manifest a cartridge advertises. A manifest that parses and carries a definition breaking a rule is refused as incompatible, naming the cap and the rule, and the cartridge is not attached.
Two rules are checked elsewhere because of what they need. RULE13 is checked when a definition is parsed: a definition without aliases does not deserialize. RULE16 needs the media definitions an argument’s default is validated against, which a host reading a manifest may not hold, so it is the publisher’s rule.
RULE1: No duplicate media URNs
Rule: No two arguments may have the same media_urn.
Rationale: Each argument is uniquely identified by its media URN.
Error: RULE1: Duplicate media_urn '<urn>'
RULE2: Sources must not be empty
Rule: Every argument MUST have a non-empty sources array.
Rationale: An argument without sources cannot receive input.
Error: RULE2: Argument '<media_urn>' has empty sources
RULE3: At most one stdin argument
Rule: At most one argument may declare a stdin source.
Rationale: stdin is a command line’s one piped input, and the argument that declares it is the cap’s main input (RULE15). It says nothing about how many arguments a cap takes data on: every argument is delivered as a stream of its own, and any of them may be fed by another cap’s output (Machine notation).
Error: RULE3: Arguments '<urn1>' and '<urn2>' both declare a stdin source; a cap has one main input
RULE4: No duplicate source types per argument
Rule: No argument may specify the same source type (stdin, position, cli_flag) more than once.
Rationale: Each source type represents a single input channel per argument.
Error: RULE4: Argument '<media_urn>' has duplicate source type '<type>'
RULE5: No duplicate positions
Rule: No two arguments may have the same positional index.
Rationale: Positional arguments must be unambiguous.
Error: RULE5: Duplicate position <n> in argument '<media_urn>'
RULE6: Sequential positions
Rule: Positions must be sequential starting from 0 with no gaps.
Rationale: Ensures predictable positional argument ordering.
Error: RULE6: Position gap - expected <n> but found <m>
RULE7: No position and CLI flag combination
Rule: No argument may have both a position source and a cli_flag source.
Rationale: An argument is either positional or named, not both.
Error: RULE7: Argument '<media_urn>' has both position and cli_flag sources
RULE8: No unknown source keys
Rule: Source objects may only contain recognized keys: stdin, position, or cli_flag.
Rationale: Prevents typos and invalid configurations.
Enforcement: Validated by JSON Schema with deny_unknown_fields.
Error: RULE8: Argument '<media_urn>' has source with unknown keys
RULE9: No duplicate CLI flags
Rule: No two arguments may have the same cli_flag value.
Rationale: CLI flags must uniquely identify arguments.
Error: RULE9: Duplicate cli_flag '<flag>' in argument '<media_urn>'
RULE10: Reserved CLI flags
Rule: These CLI flags are reserved and cannot be used:
manifest--help--version-v-h
Rationale: Reserved for system use.
Error: RULE10: Argument '<media_urn>' uses reserved cli_flag '<flag>'
RULE11: CLI flag verbatim usage
Rule: CLI flags are used exactly as specified (no automatic prefixing with --).
Enforcement: By design: implementations use the flag string verbatim.
RULE12: Media URN as key
Rule: An argument is identified by its media_urn, and an output by its media_urn. A name (RULE17, RULE19) is a label by which a machine may select one; it identifies nothing.
Enforcement: Bindings, stream delivery, equivalence and every lookup are keyed by the media URN. No implementation derives anything about a URN from a name or about a name from a URN.
RULE13: Aliases required
Rule: Every capability definition MUST carry a non-empty aliases
list. Each alias is lowercase, matches [a-z0-9._-]+, and contains no
colon.
Rationale: aliases is the single source of a cap’s human-facing
names (the DNS layer, used by both the fabric-wide capdag CLI and the
direct cartridge CLI). A cap
with no aliases has no name and is inadmissible.
Error: RULE13: Capability '<urn>' has empty aliases
RULE14: Only the main input may stream
Rule: An argument may declare streaming: true only if it is the cap’s
main input, the argument whose stdin source is tag-equivalent to the URN’s
in= (CapArg::is_main_input). A streaming declaration on any other
argument is an invalid cap schema.
Rationale: streaming declares that an argument is consumed without a
length promise: a feed (15.2 §Streaming contracts). Side arguments
(options, model specs, prompts alongside the main input) are values the
runtime demuxes whole; a feed there has no meaning and would let an
unbounded stream reach a collector that must refuse it (12.4 L16). Keeping
streaming on the main input alone keeps the executor’s hop rule
one-dimensional: a hop is a boundary iff the producer’s output streams and
the consumer’s main input does not. streaming is orthogonal to
is_sequence (cardinality) and is not a URN axis.
Error: RULE14: Argument '<media_urn>' declares streaming=true but is
not the main input (no stdin source equivalent to in='<in>'); only the main
input may be consumed without a length promise
RULE15: The main input matches in=
Rule: If the cap URN’s in= is media:void, no argument may declare a stdin source. Otherwise exactly one argument declares one, and its URN is tag-equivalent to in=. That argument is the cap’s main input.
Rationale: The cap URN states what the cap takes; the definition must be able to receive it, and must not claim an input the URN denies.
Error: RULE15: in= is 'media:void' but <n> arg(s) declare a stdin source, or RULE15: in= is '<in>' but no arg declares a stdin source equivalent to it
RULE16: A default satisfies its argument’s validation
Rule: An argument’s default_value must satisfy the validation its media definition declares.
Error: RULE16: default_value <value> for '<media_urn>' <reason>
RULE17: Argument names
Rule: Every argument has a name: non-empty, of the form (ALPHA | "_") (ALNUM | "_" | "-")*, not true, false or null, and unique among the cap’s arguments.
Rationale: A machine selects an argument by this name where types cannot decide (Machine notation). It is a label local to the definition.
Error: RULE17: Argument '<media_urn>' has no name, RULE17: Argument name '<name>' is not a valid name, or RULE17: Arguments '<urn1>' and '<urn2>' share the name '<name>'
RULE18: accepts only without stdin
Rule: An argument may declare accepts, the media URN its stream must conform to, only if it declares no stdin source.
Rationale: An argument says what it accepts in one place. The main input says it with its stdin source; any other argument accepts its own media_urn unless accepts says otherwise. accepts is what lets two arguments take values of one type while keeping distinct URNs (RULE1).
Error: RULE18: Argument '<media_urn>' declares both a stdin source and accepts
RULE19: Output names
Rule: Every output has a name, under the same form and reservation as RULE17, unique among the cap’s outputs.
Error: RULE19: Output '<media_urn>' has no name, RULE19: Output name '<name>' is not a valid name, or RULE19: Outputs '<urn1>' and '<urn2>' share the name '<name>'
RULE20: No duplicate output URNs
Rule: No two outputs may have the same media_urn.
Error: RULE20: Duplicate output media_urn '<urn>'
RULE21: Exactly one main output
Rule: If the cap URN’s out= is media:void, the cap declares no outputs. Otherwise exactly one output’s media_urn is tag-equivalent to out=. That output is the cap’s main output; its runtime type follows the cap URN’s effect.
Error: RULE21: out= is 'media:void' but <n> output(s) are declared, or RULE21: out= is '<out>' but <n> outputs are equivalent to it; exactly one must be
RULE22: What an output carries
Rule: An output other than the main one carries either its own media_urn or, when it declares carries = "input", the main input’s runtime type unchanged. carries = "input" requires a cap whose in= is not media:void. The main output declares no carries: the cap URN’s effect decides it.
Error: RULE22: Output '<media_urn>' carries the input but the cap takes none, or RULE22: The main output '<media_urn>' declares carries
Media def rules (MS1–MS3)
MS1: Title required
Rule: Every media def MUST have a title field.
Rationale: Titles provide human-readable identification.
Error: Media def '<urn>' has no title
MS2: Valid URN format
Rule: Media URNs MUST start with media: prefix.
Rationale: Distinguishes media defs from other URN types.
Error: Invalid media URN: expected 'media:' prefix
MS3: Media type required
Rule: Every media def MUST have a media_type field (MIME type).
Rationale: Specifies the content type for proper handling.
Error: Media def '<urn>' has no media_type
Cross-validation rules (XV1–XV5)
XV1: No duplicate cap URNs
Rule: No two capability definitions may have the same canonical Cap URN.
Rationale: Cap URNs uniquely identify capabilities.
Error: Duplicate cap URN: <urn>
XV2: No duplicate media URNs (global)
Rule: No two standalone media definitions may have the same URN.
Rationale: Media URNs uniquely identify media defs in the global registry.
Error: Duplicate media URN: <urn>
XV3: Media URN resolution required
Rule: All media URNs referenced in capabilities MUST resolve to a defined media def.
Resolution Order:
- Capability’s local
media_defstable (inline definitions) - Standalone media def files (global registry)
No Fallbacks: If a media URN cannot be resolved, validation FAILS. No auto-generation.
Checked Locations:
- The
inspec from the URN string (unless wildcard) - The
outspec from the URN string (unless wildcard) - Every
args[].media_urn output.media_urn- Every
args[].sources[].stdin(media URN in stdin source)
Error: Unresolved media URN '<urn>' referenced in <location>
XV4: Inline media def title required
Rule: Media defs defined inline in capability media_defs tables MUST have a title field.
Rationale: Same as MS1: all media defs need titles.
Error: Inline media def '<urn>' in <file> has no title
XV5: No redefinition of registry media defs
Rule: Inline media defs MUST NOT redefine media defs that exist in the global registry.
Rationale: The global registry is the canonical source. Redefining creates conflicts.
Enforcement:
- With network: Strictly enforce. Any conflict with registry = FAIL.
- Without network: Check cached specs only. Log warning if cannot verify.
Error: XV5: Inline media def '<urn>' redefines existing registry spec
Warning (offline): XV5: Could not verify inline spec '<urn>' against online registry (offline mode)
XV6: Global alias uniqueness
Rule: Alias names are unique across the entire snapshot. No two caps may share an alias, and no cap alias may collide with a media alias. The union of cap aliases and media aliases contains no duplicates.
Rationale: Aliases are the DNS layer: a name resolves to exactly one cap or media. A collision makes resolution ambiguous.
Enforcement: The publisher hard-fails on any collision.
Error: XV6: Duplicate alias '<alias>' (defined by <a> and <b>)
XV7: Abstract-cap coverage
Rule: Every abstract cap MUST have at least one concrete
specialization in the same snapshot: a concrete (non-abstract) cap that
is is_dispatchable from the abstract cap’s URN specialized on a
concrete input. An abstract cap with no concrete specialization is dead.
Rationale: An abstract cap is a dispatch umbrella (see Dispatch §”Abstract caps”). Without a concrete specialization it can never dispatch to anything.
Enforcement: The publisher hard-fails on a dead abstract cap.
Error: XV7: Abstract cap '<urn>' has no concrete specialization
XV8: An abstract cap names the medium its specializations share
Rule: Where every concrete specialization of an abstract cap takes
input of one medium (media:image, media:audio, media:video,
media:document), the abstract cap’s input MUST conform to that medium;
likewise its output. A side on which the specializations share no
medium is unconstrained by this rule.
Rationale: A type is a claim. cap:convert-image declared
media: → media: says it converts anything, while every converter
under it takes and makes an image. The umbrella says what its
specializations have in common, no more.
Enforcement: The publisher hard-fails on an abstract cap declared wider than its specializations.
Error: XV8: Abstract cap '<urn>' declares its <input|output> as '<declared>', but all of its specializations take part as '<medium>'
XV9: Documentation links resolve
Rule: Every link from a cap’s or a media def’s documentation or
description into the registry browser
(https://capdag.com/browse/<urn>) MUST name a cap or media URN that
exists in the same snapshot. The linked URN is compared canonically, so
tag order and quoting do not matter; a URN that does not parse is an
error.
Rationale: A type that is respelled, or a cap whose input changes, otherwise leaves links behind that open on “not found”.
Enforcement: The publisher hard-fails on a link that does not resolve.
Error: XV9: <cap|media> '<urn>' links to '<target>', which is not in the catalogue
Implementation requirements
Fail hard
All validation errors MUST cause immediate failure with a clear error message.
- No fallbacks
- No silent recovery
- No default values for required fields
Consistent behavior
All implementations (Rust, JavaScript, server functions) MUST enforce identical rules with identical error messages.
Error codes
| Code Range | Category |
|---|---|
| CU1–CU3 | Cap URN Validation |
| RULE1–RULE22 | Cap Args, Outputs and Identity Validation |
| MS1–MS3 | Media Def Validation |
| XV1–XV9 | Cross-Validation |
Summary table
| Rule | Description | Scope |
|---|---|---|
| CU1 | Required in/out tags | URN |
| CU2 | Valid media URN values | URN |
| CU3 | Effect admissibility (none: in ⊑ out; patch: derivable delta) | URN |
| RULE1 | No duplicate media_urns | Args |
| RULE2 | Non-empty sources | Args |
| RULE3 | At most one stdin argument | Args |
| RULE4 | No duplicate source types | Args |
| RULE5 | No duplicate positions | Args |
| RULE6 | Sequential positions | Args |
| RULE7 | No position + cli_flag combo | Args |
| RULE8 | No unknown source keys | Args |
| RULE9 | No duplicate cli_flags | Args |
| RULE10 | Reserved cli_flags forbidden | Args |
| RULE11 | CLI flags verbatim | Args |
| RULE12 | media_urn as identifier; a name is a label | Args, outputs |
| RULE13 | Non-empty aliases required | Identity |
| RULE14 | Only the main input may stream | Args |
| RULE15 | The main input matches in= |
Args |
| RULE16 | A default satisfies its argument’s validation | Args |
| RULE17 | Argument names: required, well-formed, unique | Args |
| RULE18 | accepts only without stdin |
Args |
| RULE19 | Output names: required, well-formed, unique | Outputs |
| RULE20 | No duplicate output media_urns | Outputs |
| RULE21 | Exactly one main output, matching out= |
Outputs |
| RULE22 | What an output carries | Outputs |
| MS1 | Title required | Media |
| MS2 | media: prefix required | Media |
| MS3 | media_type required | Media |
| XV1 | No duplicate cap URNs | Cross |
| XV2 | No duplicate media URNs | Cross |
| XV3 | All media URNs resolve | Cross |
| XV4 | Inline specs need title | Cross |
| XV5 | No registry redefinition | Cross |
| XV6 | Global alias uniqueness (caps ∪ media) | Cross |
| XV7 | Abstract-cap coverage (≥1 concrete specialization) | Cross |
| XV8 | An abstract cap names the medium its specializations share | Cross |
| XV9 | Documentation links resolve | Cross |