In this section

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

  1. Structural validation (JSON Schema)
  2. Cap URN validation (CU1–CU3)
  3. Cap definition rules (RULE1–RULE14)
  4. Media def rules (MS1–MS3)
  5. 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 to media:)

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: declared in= must conform_to declared out=.
  • effect=patch: the coordinate delta out − in must be derivable, and applying it back to in= must yield a type conforming to out=.

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:

  1. Capability’s local media_defs table (inline definitions)
  2. Standalone media def files (global registry)

No Fallbacks: If a media URN cannot be resolved, validation FAILS. No auto-generation.

Checked Locations:

  • The in spec from the URN string (unless wildcard)
  • The out spec 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>'

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