{
  "@context": [
    "http://www.w3.org/ns/anno.jsonld",
    {
      "@version": 1.1,
      "tap": "https://taprun.dev/ns/tap-v1#",
      "oa": "http://www.w3.org/ns/oa#",
      "owl": "http://www.w3.org/2002/07/owl#",
      "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
      "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
      "xsd": "http://www.w3.org/2001/XMLSchema#",
      "schema": "http://schema.org/",
      "dcterms": "http://purl.org/dc/terms/",
      "prov": "http://www.w3.org/ns/prov#"
    }
  ],

  "@id": "https://taprun.dev/ns/tap-v1",
  "@type": "owl:Ontology",
  "dcterms:title": "Tap v1 Web Annotation Extension Vocabulary",
  "dcterms:description": "Tap-namespaced extensions to the W3C Web Annotation Data Model. Scope: only terms that ship across a consumer boundary in a stored `.tap.json` envelope are defined here — internal implementation types (forge-time fingerprints, intermediate selector candidates) are TypeScript-only and intentionally excluded. Two surfaces ship terms on disk: a compiled Tap program stored as a `.tap.json` W3C Annotation (motivation `tap:executing`, body `tap:ExecutionPlan`), and a doctor assessment Annotation (motivation `oa:assessing`, body carrying `tap:verdict` and related diagnostic properties). The Tap runtime parses `tap:*` fields by literal key and does not require a remote-context fetch; this document exists as a stable identifier and as documentation for external tooling. Naming follows JSON-LD/RDF convention: classes are PascalCase, properties are camelCase, motivations are lowercase verbs (per W3C anno).",
  "dcterms:issued": "2026-04-15",
  "dcterms:modified": "2026-04-23",
  "dcterms:creator": {
    "@type": "schema:Organization",
    "schema:name": "TapRun",
    "schema:url": "https://taprun.dev/"
  },
  "dcterms:license": "https://creativecommons.org/publicdomain/zero/1.0/",
  "dcterms:isVersionOf": "https://taprun.dev/ns/tap-v1",
  "owl:versionInfo": "tap-v1",
  "rdfs:seeAlso": [
    "https://www.w3.org/TR/annotation-model/",
    "https://www.w3.org/TR/annotation-vocab/",
    "https://www.w3.org/TR/selectors-states/",
    "https://www.rfc-editor.org/rfc/rfc9535",
    "https://taprun.dev/"
  ],

  "@graph": [
    {
      "@id": "tap:executing",
      "@type": "oa:Motivation",
      "rdfs:label": "executing",
      "rdfs:comment": "Program-level execution. Declares that the annotation carries a deterministic `tap:ExecutionPlan` that replays the documented interaction against the target resource. The canonical motivation for a stored Tap program (`.tap.json`). A consumer that cannot execute the plan still sees a valid W3C Annotation with a target IRI and a structured body.",
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:ExecutionPlan",
      "@type": "rdfs:Class",
      "rdfs:label": "Execution Plan",
      "rdfs:comment": "Body type of a compiled Tap program. Carries `site`, `name`, `intent`, `description`, `health`, `args`, `columns`, and an ordered `ops` array of operations drawn from a closed 24-member union (the Tap op set). Serialized as bare JSON keys under the body object — JSON-LD consumers that follow the `tap:` vocabulary treat them as the `tap:*` properties defined below. Pure data: `ops[*].fn` is a source-level function string, never evaluated at parse time. See the Tap operation schema for the op union.",
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:site",
      "@type": "rdf:Property",
      "rdfs:label": "site",
      "rdfs:comment": "Site identifier — the logical namespace a tap belongs to (e.g. `github`, `reddit`). Part of the `{site}/{name}` coordinate used everywhere in Tap. Appears as a bare `site` key inside a `tap:ExecutionPlan` body.",
      "rdfs:domain": { "@id": "tap:ExecutionPlan" },
      "rdfs:range": { "@id": "xsd:string" },
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:name",
      "@type": "rdf:Property",
      "rdfs:label": "name",
      "rdfs:comment": "Tap identifier within a site namespace (e.g. `trending`, `publish`). Together with tap:site forms the unique `{site}/{name}` coordinate. Appears as a bare `name` key inside a `tap:ExecutionPlan` body.",
      "rdfs:domain": { "@id": "tap:ExecutionPlan" },
      "rdfs:range": { "@id": "xsd:string" },
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:intent",
      "@type": "rdf:Property",
      "rdfs:label": "intent",
      "rdfs:comment": "Tap direction — `read` (idempotent data retrieval; doctor runs automatically) or `write` (side-effecting action; doctor skips unless opted in). Drives whether verification may safely re-execute the tap. Appears as a bare `intent` key inside a `tap:ExecutionPlan` body.",
      "rdfs:domain": { "@id": "tap:ExecutionPlan" },
      "rdfs:range": { "@id": "xsd:string" },
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:health",
      "@type": "rdf:Property",
      "rdfs:label": "health",
      "rdfs:comment": "Health contract for a read tap — declarative gates (`min_rows`, `non_empty` fields, etc.) that doctor evaluates to decide HEALTHY vs BROKEN. Opaque JSON object. Appears as a bare `health` key inside a `tap:ExecutionPlan` body.",
      "rdfs:domain": { "@id": "tap:ExecutionPlan" },
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:args",
      "@type": "rdf:Property",
      "rdfs:label": "args",
      "rdfs:comment": "Argument schema for a tap — parameter name → { type, default, description }. Opaque JSON object; drives `tap.run` input validation and MCP tool schemas. Appears as a bare `args` key inside a `tap:ExecutionPlan` body.",
      "rdfs:domain": { "@id": "tap:ExecutionPlan" },
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:verdict",
      "@type": "rdf:Property",
      "rdfs:label": "verdict",
      "rdfs:comment": "Doctor verdict on an assessing annotation — one of `healthy`, `broken`, `stale`, `layer-mismatch`, `unreachable`, `unverified`. `unreachable` signals that the target resource could not be fetched (DNS failure, timeout, network error) and is distinct from `broken`, which signals that the tap ran against a fetched page but produced output that violates its health contract. `unverified` signals that the tap's output passed its own health contract but no independent authoritative source was available to cross-check it; pair with `tap:suggestAuthoritative` when a known-shape source for the target exists so consumers can close the verification gap.",
      "rdfs:range": { "@id": "xsd:string" },
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:compiledFromLayer",
      "@type": "rdf:Property",
      "rdfs:label": "compiledFromLayer",
      "rdfs:comment": "Trust layer (1-4) the tap was compiled from. 1 = explicit declarations (JSON-LD, Open Graph, agents.json). 2 = data contracts (API, SSR globals). 3 = semantic structure (ARIA, headings). 4 = implementation details (CSS classes). Derived from the tap's target address via `selectorLayer()`. Emitted in the body of an `oa:assessing` doctor annotation.",
      "rdfs:range": { "@id": "xsd:integer" },
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:recommendedLayer",
      "@type": "rdf:Property",
      "rdfs:label": "recommendedLayer",
      "rdfs:comment": "Layer doctor recommends re-forging from, when a higher-trust layer is available than the one the tap currently uses. Nullable. Emitted in the body of an `oa:assessing` doctor annotation.",
      "rdfs:range": { "@id": "xsd:integer" },
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:crossValidation",
      "@type": "rdf:Property",
      "rdfs:label": "crossValidation",
      "rdfs:comment": "Layer-cross-validation record — `{ layer1Value, observedValue, disagreement }`. Populated when Layer-1 (JSON-LD) says one thing and the tap's extraction returns another. Fires the `layer-mismatch` verdict. Emitted in the body of an `oa:assessing` doctor annotation.",
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:suggestions",
      "@type": "rdf:Property",
      "rdfs:label": "suggestions",
      "rdfs:comment": "Ordered list of actionable diagnostic suggestions attached to an assessing annotation — each suggestion names a specific fix (refresh fingerprint, re-forge from Layer N, check auth, etc.). Emitted in the body of an `oa:assessing` doctor annotation.",
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    },

    {
      "@id": "tap:suggestAuthoritative",
      "@type": "rdf:Property",
      "rdfs:label": "suggestAuthoritative",
      "rdfs:comment": "Optional ready-to-embed `AuthoritativeSpec` payload appended to a doctor `assessing` annotation when verdict is `unverified` AND the tap's target resolves to a known-shape source (e.g. an HN/Reddit/GitHub endpoint, or a feed advertised by the page). Lets agents/scripts close the verification gap by copying the spec into the tap's `body.authoritative` field rather than hand-writing V config. Absent otherwise — doctor never emits an empty or fabricated suggestion. The shape is one of three variants: single-step JSON fetch, two-step JSON fetch, or Atom feed; each carries `row_mapping`, `match_key`, and per-field `comparators`.",
      "rdfs:isDefinedBy": { "@id": "https://taprun.dev/ns/tap-v1" }
    }
  ]
}
