{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "additionalProperties": false,
  "required": ["id", "name", "lastUpdated", "registrar", "entries"],
  "description": "The Community Activation Register. The registered thing is the community, not the specification: each entry is a Community Activation Programme (CAP), and the extension it owns, if any, is a sub-resource beneath it with a stable IRI of its own so that other CAPs can reference it without duplicating it. Entries hold pointers, hashes and dated observations, never definitions — every technical artefact belongs to whoever published it, and the register's job is to say where it is, that it has not changed, and what was found when it was last checked. Admission is a judgement about the community made once by the UN secretariat; it is independent of which forums list the CAP, or whether any do.",
  "properties": {
    "@context": {
      "description": "JSON-LD context. Production deployments use the published register vocabulary context at vocabulary.uncefact.org so that register.json is processable as Linked Data.",
      "oneOf": [
        { "type": "string", "format": "uri" },
        { "type": "array" },
        { "type": "object" }
      ]
    },
    "@type": {
      "oneOf": [
        { "type": "string" },
        { "type": "array", "items": { "type": "string" } }
      ]
    },
    "id": { "type": "string", "format": "uri" },
    "name": { "type": "string" },
    "description": { "type": "string" },
    "lastUpdated": { "type": "string", "format": "date" },
    "registrar": { "$ref": "#/$defs/Registrar" },
    "entries": { "type": "array", "items": { "$ref": "#/$defs/CommunityActivationEntry" } }
  },
  "$defs": {
    "Registrar": {
      "type": "object",
      "additionalProperties": false,
      "description": "The body accountable for the register and for the agent that maintains it.",
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "id": { "type": "string", "format": "uri" },
        "name": { "type": "string" },
        "agent": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
            "id": { "type": "string" },
            "name": { "type": "string" },
            "description": { "type": "string" }
          },
          "required": ["id", "name"]
        }
      },
      "required": ["id", "name"]
    },

    "CommunityActivationEntry": {
      "type": "object",
      "additionalProperties": false,
      "description": "A Community Activation Programme admitted to the register. Thin on the community itself, since the detail lives on the CAP's own site and is reached through profileURL.",
      "required": ["@type", "id", "name", "owner", "status"],
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "id": {
          "type": "string",
          "format": "uri",
          "description": "Forum-independent identifier minted by the register, https://registers.uncefact.org/untp/cap/{slug}. A CAP joining or leaving a forum never changes it."
        },
        "did": {
          "type": "string",
          "description": "The CAP's acting identity, used to sign the credentials it issues. Lives on the CAP's own domain and is distinct from the register identifier above."
        },
        "name": { "type": "string" },
        "shortName": { "type": "string" },
        "description": { "type": "string", "description": "A one-line summary for listings. Shorter and more neutral than statement, which is the community's own claim about itself." },
        "statement": {
          "type": "string",
          "description": "The community's own statement of what it is activating and why. Self-declared and not verified by the registrar."
        },
        "website": { "type": "string", "format": "uri" },
        "owner": { "$ref": "#/$defs/CommunityOwner" },
        "industrySector": { "$ref": "#/$defs/IndustrySector" },
        "geographicScope": { "$ref": "#/$defs/GeographicScope" },
        "convenesAt": {
          "type": "array",
          "items": { "type": "string", "format": "uri" },
          "description": "Forums where the CAP actively participates, zero or more. Discovery by sector or region is derived from industrySector and geographicScope instead, so an empty list does not make a CAP undiscoverable."
        },
        "profileURL": {
          "type": "string",
          "format": "uri",
          "description": "The machine-readable profile the CAP publishes on its own domain, which the registrar crawls. Required at admission, because without one the entry is frozen at its registration date."
        },
        "admission": { "$ref": "#/$defs/Admission" },
        "status": {
          "$ref": "#/$defs/LifecycleStatus",
          "description": "Lifecycle state of the community. A CAP that folds becomes dormant rather than being deleted, because credentials issued under its extension stay in circulation. Staleness is not a status: it is derived from the crawl observations below."
        },
        "ownedExtension": { "$ref": "#/$defs/Extension" },
        "uses": { "$ref": "#/$defs/Uses" },
        "observations": { "$ref": "#/$defs/CrawlObservation" }
      }
    },

    "CommunityOwner": {
      "type": "object",
      "additionalProperties": false,
      "description": "The member association operating the CAP. A single corporate is not an eligible owner.",
      "required": ["id", "name"],
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "id": { "type": "string" },
        "name": { "type": "string" },
        "website": { "type": "string", "format": "uri" },
        "memberOrganisationType": {
          "type": "string",
          "enum": ["industry-association", "standards-body", "intergovernmental-body", "consortium", "research-centre", "government-agency"]
        }
      }
    },

    "Admission": {
      "type": "object",
      "additionalProperties": false,
      "description": "What the community declared and when it was admitted. Admission gates the register, not any forum listing, and is checked once at initial registration rather than on every update.",
      "required": ["declarationURL", "decidedOn"],
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "declarationURL": {
          "type": "string",
          "format": "uri",
          "description": "The CAP's published self-declaration against the admission criteria."
        },
        "criteriaVersion": { "type": "string" },
        "decidedOn": { "type": "string", "format": "date" },
        "decidedBy": { "type": "string" }
      }
    },

    "Extension": {
      "type": "object",
      "additionalProperties": false,
      "description": "The extension specification this community owns. Exactly one CAP owns an extension; other CAPs reference it through uses.extensions rather than registering it again.",
      "required": ["id", "name"],
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "id": {
          "type": "string",
          "format": "uri",
          "description": "Stable IRI beneath the owning CAP, so another entry can link to it without copying it."
        },
        "name": { "type": "string" },
        "shortName": { "type": "string" },
        "specificationURL": { "type": "string", "format": "uri" },
        "licence": {
          "type": "string",
          "format": "uri",
          "description": "The licence the extension is published under. Must be permissive or Creative Commons, so that an extension others are expected to implement is not one they must negotiate access to."
        },
        "versions": { "type": "array", "items": { "$ref": "#/$defs/ExtensionVersion" } }
      }
    },

    "ExtensionVersion": {
      "type": "object",
      "additionalProperties": false,
      "description": "An extension releases, and is conformance tested, as a whole. Versioning sits here rather than on each credential type so that a verdict can attach to a release.",
      "required": ["versionLabel", "extendsUntpVersion"],
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "versionLabel": { "type": "string" },
        "releasedDate": { "type": "string", "format": "date" },
        "extendsUntpVersion": { "type": "string" },
        "credentialTypes": { "type": "array", "items": { "$ref": "#/$defs/ExtensionCredentialType" } },
        "artefacts": { "$ref": "#/$defs/Artefacts" },
        "conformance": { "$ref": "#/$defs/ExtensionConformanceObservation" }
      }
    },

    "ExtensionCredentialType": {
      "type": "object",
      "additionalProperties": false,
      "required": ["credentialType"],
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "credentialType": { "type": "string", "description": "The type name as it appears in a credential's type array." },
        "extends": { "type": "string", "format": "uri", "description": "The core credential type this extends." },
        "specificationURL": { "type": "string", "format": "uri", "description": "Where this credential type is specified, when the extension publishes a page per type. Absent for a type that is planned rather than written." },
        "purpose": { "type": "string" },
        "status": { "$ref": "#/$defs/LifecycleStatus", "description": "How far this credential type has got. An extension is conformance tested as a whole, but its credential types mature at different rates." }
      }
    },

    "Artefacts": {
      "type": "object",
      "additionalProperties": false,
      "description": "Where the tested artefacts are and that they have not changed. Pointers and hashes, never copies: a hash proves integrity, and the wind-down commitment is what keeps the artefacts reachable if the CAP ceases.",
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "schema": { "$ref": "#/$defs/HashedReference" },
        "context": { "$ref": "#/$defs/HashedReference" },
        "testSuite": { "type": "string", "format": "uri" }
      }
    },

    "HashedReference": {
      "type": "object",
      "additionalProperties": false,
      "required": ["uri"],
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "uri": { "type": "string", "format": "uri" },
        "hashAlgorithm": { "type": "string" },
        "hashValue": { "type": "string" }
      }
    },

    "ExtensionConformanceObservation": {
      "type": "object",
      "additionalProperties": false,
      "description": "UNECE's verdict that this extension version is a conformant UNTP extension. The only statement in the entry that the registrar originates rather than crawls.",
      "required": ["testedOn", "result"],
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "testedOn": { "type": "string", "format": "date" },
        "testedBy": { "type": "string", "format": "uri" },
        "untpVersion": { "type": "string" },
        "result": { "type": "string", "enum": ["pass", "fail", "not-tested"] },
        "checks": {
          "type": "object",
          "additionalProperties": false,
          "description": "The runnable extension conformance suite, as described on the Extension Vocabularies page.",
          "properties": {
            "samplesValidateAgainstExtensionSchema": { "$ref": "#/$defs/CheckOutcome" },
            "samplesValidateAgainstCoreSchema": { "$ref": "#/$defs/CheckOutcome" },
            "safeModeExpansionLosesNothing": { "$ref": "#/$defs/CheckOutcome" },
            "noProtectedTermRedefined": { "$ref": "#/$defs/CheckOutcome" },
            "allMintedIrisResolve": { "$ref": "#/$defs/CheckOutcome" }
          }
        }
      }
    },

    "CheckOutcome": { "type": "string", "enum": ["pass", "fail", "not-tested"] },

    "RegistrationState": {
      "type": "string",
      "enum": ["candidate", "in-progress", "registered", "grid", "not-applicable"],
      "description": "Where the thing stands in relation to a UNTP register, which is a separate fact from how far this community has got with adopting it. candidate and in-progress mean it is heading for a UNTP register; registered means it has an entry, so ref is populated; grid means it is an authoritative register that belongs on GRID rather than in a UNTP register; not-applicable means neither applies."
    },
    "LifecycleStatus": {
      "type": "string",
      "enum": ["proposed", "planned", "development", "pilot", "active", "implemented", "dormant", "withdrawn"],
      "description": "The shared register lifecycle vocabulary, used for a community, a credential type an extension defines, and a community's adoption of something it builds on. Forums display their own labels for these values rather than keeping a second vocabulary."
    },

    "Uses": {
      "type": "object",
      "additionalProperties": false,
      "description": "What this CAP has chosen to build on. Declared by the CAP in its own profile and crawled into the register, so the declaration stays with the declarer while the aggregate stays queryable. Counting these links across entries gives declared uptake; observed uptake comes from the registrar detecting extensions in credentials in the wild and is a different number.",
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "extensions": { "type": "array", "items": { "$ref": "#/$defs/UsesLink" } },
        "identifierSchemes": { "type": "array", "items": { "$ref": "#/$defs/UsesLink" } },
        "conformitySchemes": { "type": "array", "items": { "$ref": "#/$defs/UsesLink" } },
        "software": { "type": "array", "items": { "$ref": "#/$defs/SoftwareUse" } }
      }
    },

    "UsesLink": {
      "type": "object",
      "additionalProperties": false,
      "anyOf": [{ "required": ["ref"] }, { "required": ["name"] }],
      "description": "Something a community builds on. Much of what a CAP depends on is not registered yet — a national livestock identifier, a domestic assurance scheme — so a link may name a thing instead of pointing at an entry, and gains a ref once that entry exists.",
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "ref": { "type": "string", "format": "uri", "description": "The register entry being used, once there is one." },
        "name": { "type": "string", "description": "What it is called, for something not yet registered. Properties of a registered target, such as its scope or system type, resolve from its own entry and are not copied here." },
        "website": { "type": "string", "format": "uri", "description": "The authoritative home of the thing itself, for something with no register entry. Distinct from ref, which is always an entry in one of the UNTP registers." },
        "status": { "$ref": "#/$defs/LifecycleStatus", "description": "How far this community has got with adopting it. Describes the relationship, not the target." },
        "registration": { "$ref": "#/$defs/RegistrationState", "description": "Where it stands in relation to a UNTP register. Separate from adoption: a scheme can be in production use by the community and still have no UNTP entry." },
        "declared": { "type": "string", "format": "date", "description": "When the CAP first declared this choice." },
        "lastSeen": { "type": "string", "format": "date", "description": "When the registrar last found it in the CAP's profile. Staleness is visible from this rather than from a status." }
      }
    },

    "SoftwareUse": {
      "type": "object",
      "additionalProperties": false,
      "anyOf": [{ "required": ["ref"] }, { "required": ["name"] }],
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "ref": { "type": "string", "format": "uri" },
        "name": { "type": "string", "description": "For a system not yet in the software implementers register." },
        "website": { "type": "string", "format": "uri", "description": "The system's own home, for one with no register entry." },
        "status": { "$ref": "#/$defs/LifecycleStatus", "description": "How far this community has got with the system: testing, in production, and so on." },
        "registration": { "$ref": "#/$defs/RegistrationState", "description": "Where it stands in relation to a UNTP register. Separate from adoption: a scheme can be in production use by the community and still have no UNTP entry." },
        "declared": { "type": "string", "format": "date" },
        "lastSeen": { "type": "string", "format": "date" },
        "extensionConformance": { "$ref": "#/$defs/ExtensionConformanceAssertion" }
      }
    },

    "ExtensionConformanceAssertion": {
      "type": "object",
      "additionalProperties": false,
      "description": "The CAP's assertion that a software product conforms to its extension. Asserted by the CAP, which did the testing, never by UNECE and never by the vendor. Cites the core registration it depends on, so the requirement to pass core tests first is visible in the data rather than enforced by procedure.",
      "required": ["assertedBy", "credential"],
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "assertedBy": { "type": "string", "description": "The CAP's DID." },
        "credential": { "type": "string", "format": "uri", "description": "The conformity credential the CAP issued and hosts." },
        "extensionVersion": { "type": "string" },
        "coreRegistration": { "type": "string", "format": "uri", "description": "The software's entry in the UNTP software implementers register." },
        "assertedOn": { "type": "string", "format": "date" }
      }
    },

    "CrawlObservation": {
      "type": "object",
      "additionalProperties": false,
      "description": "What the registrar found when it last fetched the CAP's profile. Staleness is derived from this, and is deliberately not a status: a site that is down for a fortnight is not a community that has folded.",
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "lastCrawled": { "type": "string", "format": "date" },
        "crawlStatus": { "type": "string", "enum": ["ok", "unreachable", "changed", "never"] }
      }
    },

    "IndustrySector": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "label": { "type": "string" },
        "isicCodes": { "type": "array", "items": { "type": "string" } }
      }
    },

    "GeographicScope": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "@type": { "type": "array", "items": { "type": "string" }, "description": "JSON-LD type. Maps to a class in the register vocabulary." },
        "label": { "type": "string" },
        "countries": { "type": "array", "items": { "type": "string" } },
        "isGlobal": { "type": "boolean" }
      }
    }
  }
}
