Saltar al contenido

Cabuya publisher manifest (v0.1)

Ver el Markdown

Identificador del esquema: https://cabuya.org/schemas/0.1/manifest.schema.json

Campos

CampoTipoPerfilEjemploVerificaciones
protocolObligatorioobjectNúcleo
nameObligatorio

El nombre del protocolo: cabuya (decidido el 2026-08-16; dominios cabuya.org y cabuyaprotocol.org).

stringNúcleoDSC005
spec_versionObligatoriostringNúcleoDSC005
publisherObligatorioobjectNúcleo
publisher_idObligatorio

Asignado por el registro, legible por personas, asignado una sola vez y nunca reasignado (R12).

stringNúcleoDSC005
canonical_urlObligatorio

La clave del registro es la URL canónica más los alias, nunca un slug.

stringuriNúcleoDSC005
aliasesarrayExtendido
namestringExtendido
contact

Solo contacto institucional (dirección de rol). MUST NOT ser una dirección personal.

stringExtendido
conformance_targetObligatorio

L0L1L2L3L4

stringNúcleoDSC005
feedsarrayExtendidoDSC005
nameObligatoriostringNúcleo
urlObligatoriostringuriNúcleo
entityObligatorio

En 0.1: solo place. Las entidades a nivel de persona MUST NOT aparecer (§7.1).

place

stringNúcleo
profile

coreextended

stringExtendido
municipality_code

DIVIPOLA, cuando el feed es un fragmento por municipio.

stringExtendido
apiobjectExtendido
base_url

Forma RECOMMENDED: {origin}/api/public/v1/ (convención emergente del ecosistema).

stringuriExtendido
writeobjectExtendido
enabledbooleanExtendido
auth

nonebearer

stringExtendido
mcpobjectExtendido
endpointstringuriExtendido
tools_language

Etiqueta BCP 47 de los identificadores de las herramientas (el registro no debe asumir inglés).

stringExtendido
licenseObligatorio

Identificador SPDX de los datos publicados. REQUIRED — hoy 1 de 20 aplicaciones declara uno, y su ausencia bloquea la revisión legal de cualquier consumidor.

stringNúcleo"ODbL-1.0"ENV001ENV003ENV004
permitted_use

El consentimiento de reutilización va en el sobre, no en dialectos de robots.txt.

arrayExtendido[…3]ENV005
crawl_policy_urlstringuriExtendido
events

Identificadores de eventos del registro que se sirven, por ejemplo sismos-co-2026. Registro acotado por evento, registros con evento opcional (Q10).

arrayExtendido
languages

BCP 47. es es la base requerida del ecosistema.

arrayExtendido
sunset_at

Declaración de cierre ordenado (§7.4).

stringdate-timeExtendido

Ejemplos

Dos que cumplen y tres que no. Los que no cumplen declaran, en su propio archivo, qué están demostrando — y las pruebas del validador se comparan contra esas cadenas exactas.

Cumplevalid-minimal-core.json

Qué demuestra este ejemplo: VALID — minimal Core feed. The L2 floor: what an afternoon of work produces. Note last_confirmed_at: null on the second record — 'never confirmed' stated honestly (the silence test: a publisher with zero freshness data can still conform).

valid-minimal-core.json
{
  "$comment": "VALID — minimal Core feed. The L2 floor: what an afternoon of work produces. Note last_confirmed_at: null on the second record — 'never confirmed' stated honestly (the silence test: a publisher with zero freshness data can still conform).",
  "last_updated": "2026-08-16T04:00:00Z",
  "ttl": 300,
  "version": "0.1.0",
  "publisher_id": "example-app",
  "license": "CC-BY-4.0",
  "permitted_use": ["display", "aggregate", "ai_answer"],
  "data": {
    "places": [
      {
        "id": "184",
        "publisher_id": "example-app",
        "name": "Coliseo Mayor",
        "place_kind": "shelter",
        "municipality_code": "66001",
        "address_text": "Av. Las Américas con Calle 37, Villa Olímpica",
        "lifecycle_status": "active",
        "service_status": "full",
        "last_confirmed_at": "2026-08-15T21:00:00Z",
        "confirmation_method": "in_person",
        "source": { "source_id": "example-app", "source_kind": "first_party" },
        "public_url": "https://example-app.invalid/lugares/184"
      },
      {
        "id": "b3f2c9e1-7a44-4a1c-9d02-1f6e8a5c0d11",
        "publisher_id": "example-app",
        "name": "Punto de acopio Kennedy",
        "place_kind": "collection_center",
        "municipality_code": "66001",
        "address_text": "Parque principal de Kennedy",
        "lifecycle_status": "active",
        "last_confirmed_at": null,
        "source": { "source_id": "example-app", "source_kind": "user_report" },
        "public_url": "https://example-app.invalid/lugares/b3f2c9e1-7a44-4a1c-9d02-1f6e8a5c0d11"
      }
    ]
  }
}

Cumplevalid-rich-extended.json

Qué demuestra este ejemplo: VALID — Extended feed exercising the worked duplicate cases. Record 1 = the Coliseo Mayor cluster (Manizales test: municipality_code disambiguates; same_as claims the two peers' records, one-hop, no authority). Record 2 = the Colegio María Auxiliadora case: this publisher says 'paused' while a peer says active — the record carries its OWN status with its OWN age plus a same_as claim; discrepancies are preserved, not resolved. Record 3 shows negative confirmation and merged_into.

valid-rich-extended.json
{
  "$comment": "VALID — Extended feed exercising the worked duplicate cases. Record 1 = the Coliseo Mayor cluster (Manizales test: municipality_code disambiguates; same_as claims the two peers' records, one-hop, no authority). Record 2 = the Colegio María Auxiliadora case: this publisher says 'paused' while a peer says active — the record carries its OWN status with its OWN age plus a same_as claim; discrepancies are preserved, not resolved. Record 3 shows negative confirmation and merged_into.",
  "last_updated": "2026-08-16T04:05:00Z",
  "ttl": 300,
  "version": "0.1.0",
  "publisher_id": "acopios-demo",
  "license": "ODbL-1.0",
  "permitted_use": ["display", "aggregate", "redistribute"],
  "data": {
    "places": [
      {
        "id": "shel-0007",
        "publisher_id": "acopios-demo",
        "name": [{ "text": "Coliseo Mayor", "language": "es" }, { "text": "Main Coliseum shelter", "language": "en" }],
        "place_kind": "shelter",
        "place_kind_secondary": ["collection_center"],
        "origin_category": "albergue_temporal",
        "municipality_code": "66001",
        "address_text": "Av. Las Américas con Calle 37, Villa Olímpica",
        "lat": 4.815091,
        "lon": -75.735,
        "geo_precision": "exact",
        "lifecycle_status": "active",
        "service_status": "full",
        "last_confirmed_at": "2026-08-16T02:30:00Z",
        "confirmed_by": "volunteer",
        "confirmation_method": "in_person",
        "confirmations_24h": 3,
        "contradictions_active": 0,
        "updated_at": "2026-08-16T02:31:12Z",
        "source": { "source_id": "acopios-demo", "source_kind": "first_party" },
        "source_authority": "community",
        "attribution_required": true,
        "public_url": "https://acopios-demo.invalid/p/shel-0007",
        "contact_available": true,
        "same_as": ["pereira-ayuda:coliseo-mayor", "unidos-demo:c32653bdd8"]
      },
      {
        "id": "acop-0119",
        "publisher_id": "acopios-demo",
        "name": "IE María Auxiliadora",
        "place_kind": "collection_center",
        "origin_category": "acopio",
        "municipality_code": "66170",
        "address_text": "Cl. 43 #13-74, Dosquebradas",
        "lifecycle_status": "active",
        "service_status": "paused",
        "last_confirmed_at": "2026-08-15T18:00:00Z",
        "confirmation_method": "phone",
        "source": { "source_id": "acopios-demo", "source_kind": "first_party" },
        "public_url": "https://acopios-demo.invalid/p/acop-0119",
        "same_as": ["unidos-demo:76f30c68a4"]
      },
      {
        "id": "acop-0042",
        "publisher_id": "acopios-demo",
        "name": "Acopio UTP — Parque del Estudiante",
        "place_kind": "collection_center",
        "municipality_code": "66001",
        "address_text": "Carrera 27 #10-02, Álamos (frente al edificio Favi)",
        "lat": 4.79105,
        "lon": -75.690601,
        "geo_precision": "approximate",
        "lifecycle_status": "active",
        "last_confirmed_at": "2026-08-14T09:00:00Z",
        "contradictions_active": 2,
        "last_reported_absent_at": "2026-08-15T20:15:00Z",
        "merged_into": "acop-0040",
        "source": { "source_id": "partner-feed-demo", "source_url": "https://partner.invalid/feed.json", "retrieved_at": "2026-08-16T03:00:00Z", "source_kind": "partner_feed" },
        "public_url": "https://acopios-demo.invalid/p/acop-0042"
      }
    ]
  }
}

No cumpleinvalid-1-missing-confirmation-key.json

Qué demuestra este ejemplo: INVALID — WHY: the first record OMITS the last_confirmed_at key entirely. Omission is non-conforming; the honest form is last_confirmed_at: null ('never confirmed'). The distinction is the trust core's whole point: a consumer must always be able to render an age or 'sin confirmar' — silence is not an answer. VALIDATOR ERROR: data.places[0]: required property 'last_confirmed_at' is missing (did you mean to publish last_confirmed_at: null?).

invalid-1-missing-confirmation-key.json
{
  "$comment": "INVALID — WHY: the first record OMITS the last_confirmed_at key entirely. Omission is non-conforming; the honest form is last_confirmed_at: null ('never confirmed'). The distinction is the trust core's whole point: a consumer must always be able to render an age or 'sin confirmar' — silence is not an answer. VALIDATOR ERROR: data.places[0]: required property 'last_confirmed_at' is missing (did you mean to publish last_confirmed_at: null?).",
  "last_updated": "2026-08-16T04:00:00Z",
  "ttl": 300,
  "version": "0.1.0",
  "publisher_id": "example-app",
  "license": "CC-BY-4.0",
  "data": {
    "places": [
      {
        "id": "300",
        "publisher_id": "example-app",
        "name": "Punto de acopio El Remanso",
        "place_kind": "collection_center",
        "municipality_code": "66001",
        "address_text": "Av. principal El Remanso, junto al centro de salud",
        "lifecycle_status": "active",
        "source": { "source_id": "example-app" },
        "public_url": "https://example-app.invalid/lugares/300"
      }
    ]
  }
}

No cumpleinvalid-2-contact-and-personal-data.json

Qué demuestra este ejemplo: INVALID — WHY (three violations of §7): (1) a phone number travels in the feed ('x_example_phone' — namespaced extensions do NOT exempt contact data; contact is public_url + link-out, fetch-on-demand from origin); (2) confirmed_by carries a person's NAME instead of a role token; (3) description embeds a personal name and phone (free text is the third leak channel — publishers MUST strip before publishing). Schema alone passes some of these; the validator's deny-pattern pass catches them all. VALIDATOR ERRORS: data.places[0].x_example_phone: contact values MUST NOT travel in feeds (use contact_available + public_url) · data.places[0].confirmed_by: must be a role token (team|volunteer|official_source|partner:{id}), never a personal name · data.places[0].description: possible personal data detected (name+phone pattern) — strip before publishing.

invalid-2-contact-and-personal-data.json
{
  "$comment": "INVALID — WHY (three violations of §7): (1) a phone number travels in the feed ('x_example_phone' — namespaced extensions do NOT exempt contact data; contact is public_url + link-out, fetch-on-demand from origin); (2) confirmed_by carries a person's NAME instead of a role token; (3) description embeds a personal name and phone (free text is the third leak channel — publishers MUST strip before publishing). Schema alone passes some of these; the validator's deny-pattern pass catches them all. VALIDATOR ERRORS: data.places[0].x_example_phone: contact values MUST NOT travel in feeds (use contact_available + public_url) · data.places[0].confirmed_by: must be a role token (team|volunteer|official_source|partner:{id}), never a personal name · data.places[0].description: possible personal data detected (name+phone pattern) — strip before publishing.",
  "last_updated": "2026-08-16T04:00:00Z",
  "ttl": 300,
  "version": "0.1.0",
  "publisher_id": "example-app",
  "license": "CC-BY-4.0",
  "data": {
    "places": [
      {
        "id": "77",
        "publisher_id": "example-app",
        "name": "Acopio Consota",
        "place_kind": "collection_center",
        "municipality_code": "66001",
        "address_text": "Mz 7 y 8, Villa Consota, Cuba",
        "lifecycle_status": "active",
        "last_confirmed_at": "2026-08-15T10:00:00Z",
        "confirmed_by": "María Ejemplo Pérez",
        "description": "Recibe alimentos. Preguntar por María Ejemplo Pérez, cel 3000000000.",
        "x_example_phone": "+57 300 000 0000",
        "source": { "source_id": "example-app" },
        "public_url": "https://example-app.invalid/lugares/77"
      }
    ]
  }
}

No cumpleinvalid-3-status-in-name-and-always-now.json

Qué demuestra este ejemplo: INVALID — WHY (two production-observed anti-patterns): (1) CR-2 violation: the name encodes operational state ('(cerrado ahora)') — observed verbatim in the wild; state belongs in lifecycle_status/service_status so it can change without breaking name-based reconciliation, and so two publishers' opposite statuses are COMPARABLE fields, not prose. (2) The always-now anti-pattern: this feed's last_updated is regenerated per request (validator detects it by probing twice and seeing the value advance with the clock while content is unchanged) — worse than no signal, consumers can never detect change. Also note the record contradicts itself: name says closed, lifecycle_status says active. VALIDATOR ERRORS: data.places[0].name: operational state token detected in name ('cerrado') — move to service_status/lifecycle_status (CR-2) · envelope.last_updated: value advanced with the probe clock on identical content — generate at build/publish time, not per request.

invalid-3-status-in-name-and-always-now.json
{
  "$comment": "INVALID — WHY (two production-observed anti-patterns): (1) CR-2 violation: the name encodes operational state ('(cerrado ahora)') — observed verbatim in the wild; state belongs in lifecycle_status/service_status so it can change without breaking name-based reconciliation, and so two publishers' opposite statuses are COMPARABLE fields, not prose. (2) The always-now anti-pattern: this feed's last_updated is regenerated per request (validator detects it by probing twice and seeing the value advance with the clock while content is unchanged) — worse than no signal, consumers can never detect change. Also note the record contradicts itself: name says closed, lifecycle_status says active. VALIDATOR ERRORS: data.places[0].name: operational state token detected in name ('cerrado') — move to service_status/lifecycle_status (CR-2) · envelope.last_updated: value advanced with the probe clock on identical content — generate at build/publish time, not per request.",
  "last_updated": "2026-08-16T04:59:59Z",
  "ttl": 300,
  "version": "0.1.0",
  "publisher_id": "example-app",
  "license": "CC-BY-4.0",
  "data": {
    "places": [
      {
        "id": "912",
        "publisher_id": "example-app",
        "name": "Centro de acopio IE María Auxiliadora (cerrado ahora)",
        "place_kind": "collection_center",
        "municipality_code": "66170",
        "address_text": "Cl. 43 #13-74, Dosquebradas",
        "lifecycle_status": "active",
        "last_confirmed_at": "2026-08-15T18:00:00Z",
        "source": { "source_id": "example-app" },
        "public_url": "https://example-app.invalid/lugares/912"
      }
    ]
  }
}