# Checks

Every check the Cabuya validator runs: its id, severity, level, the rule it enforces, and how to fix a document that fails it. Anchors are stable forever.

Canonical: https://cabuya.org/developers/validator/checks
Language: en

## Checks

Every check the validator can report, with a stable anchor. Error messages link here, so these URLs do not move.

62 checks catalogued · 42 Implemented.

A check id is permanent. Renaming one would break the fix loop of every agent that cached the old id, so retired checks keep their id and are marked rather than removed.

## Discovery

### DSC001 — Manifest reachable over HTTPS as JSON

- Severity: error · Level: L1 · Catalogued, not yet implemented (Task 15 (behavioral probes))
- Rule: The manifest MUST be served over HTTPS with Content-Type: application/json.
- Fix: Serve the manifest over HTTPS with a JSON content type, and check it from outside your network.
- Specification: https://cabuya.org/developers/spec/0.1/2-discovery#2-1

### DSC002 — Soft-404: a discovery path answering 200 + text/html is absent

- Severity: error · Level: L1 · Implemented
- Rule: A manifest is JSON. HTML at a discovery path means an SPA catch-all is answering — the manifest is absent, not present.
- Fix: Exclude the discovery path from your catch-all so the file is served instead of your app shell.
- Specification: https://cabuya.org/developers/spec/0.1/1-architecture#1-2

### DSC003 — robots.txt returns 200 text/plain

- Severity: warning · Level: L1 · Catalogued, not yet implemented (Task 15)
- Rule: L2+ preconditions include a real robots.txt.
- Fix: Serve a real robots.txt at the root with a text/plain content type.
- Specification: https://cabuya.org/developers/spec/0.1/1-architecture#1-2

### DSC004 — Manifest at the RECOMMENDED path, or a <link rel> advertisement

- Severity: warning · Level: L1 · Catalogued, not yet implemented (Task 15)
- Rule: The manifest SHOULD live at /.well-known/cabuya.json, or be advertised with <link rel="cabuya">.
- Fix: Move the manifest to /.well-known/cabuya.json, or advertise its location with a <link rel="cabuya-manifest">.
- Specification: https://cabuya.org/developers/spec/0.1/2-discovery#2-2

### DSC005 — Manifest validates against manifest.schema.json

- Severity: error · Level: L1 · Implemented
- Rule: The manifest MUST conform to its schema.
- Fix: Correct the manifest against the published schema; the finding names the failing property.
- Specification: https://cabuya.org/developers/spec/0.1/2-discovery#2-1

### DSC006 — publisher.canonical_url matches the registry entry

- Severity: error · Level: L1 · Catalogued, not yet implemented (Task 15)
- Rule: A registered publisher’s manifest MUST agree with its registry entry about its canonical URL.
- Fix: Make the canonical URL in the manifest match the one in your registry entry, or open a pull request to change the entry.
- Specification: https://cabuya.org/developers/spec/0.1/2-discovery#2-4

### DSC007 — Every feeds[].url is absolute HTTPS and reachable

- Severity: error · Level: L1 · Catalogued, not yet implemented (Task 15)
- Rule: Declared feeds MUST resolve.
- Fix: Give every feed an absolute https URL that resolves from outside your network.
- Specification: https://cabuya.org/developers/spec/0.1/2-discovery#2-3

### DSC008 — crawl_policy_url resolves

- Severity: warning · Level: L1 · Catalogued, not yet implemented (Task 15)
- Rule: The declared crawl/reuse policy SHOULD be fetchable — consumers must honor it.
- Fix: Point crawl_policy_url at a page that exists, or remove the field.
- Specification: https://cabuya.org/developers/spec/0.1/2-discovery#2-4

### DSC009 — conformance_target does not exceed the measured level

- Severity: error · Level: L1 · Catalogued, not yet implemented (Task 13 (content side) + Task 15)
- Rule: conformance_target is a declaration; it MUST NOT claim more than this run measures. Reported as a mismatch, never as the level.
- Fix: Lower conformance_target to the level you actually reach, or fix what is blocking the level you claim. The target is an intention, not a claim.
- Specification: https://cabuya.org/developers/spec/0.1/8-versioning-and-conformance#8-3


## Envelope

### SCH001 — The document does not conform to its published JSON Schema

- Severity: error · Level: L2 · Implemented
- Rule: Every feed and manifest MUST validate against its versioned schema.
- Fix: Correct the value so it satisfies the schema. The finding gives the pointer and the expected shape.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### ENV001 — Envelope required fields present and well-typed

- Severity: error · Level: L2 · Implemented
- Rule: last_updated, ttl, version, publisher_id and license are REQUIRED on every feed envelope.
- Fix: Add the missing envelope field. All five are required on every feed.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### ENV002 — last_updated is RFC 3339 with a UTC offset

- Severity: error · Level: L2 · Implemented
- Rule: The generation timestamp MUST be unambiguous.
- Fix: Write last_updated as an RFC 3339 timestamp with an explicit UTC offset, e.g. 2026-08-17T14:02:00Z.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### ENV003 — license present

- Severity: error · Level: L2 · Implemented
- Rule: An unlicensed feed does not conform — absence blocks every consumer’s legal review.
- Fix: Declare a licence. An unlicensed feed blocks every consumer’s legal review, which is a harder problem than a missing field.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### ENV004 — license is an SPDX id, or license_url accompanies it

- Severity: warning · Level: L2 · Implemented
- Rule: A machine-resolvable licence SHOULD be used.
- Fix: Use an SPDX identifier, or keep the custom string and add license_url pointing at the terms.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### ENV005 — permitted_use present, values within the closed enum

- Severity: warning · Level: L2 · Implemented
- Rule: Consent-to-reuse travels in the envelope: display | aggregate | redistribute | ai_answer | ai_train.
- Fix: Add permitted_use with values from the closed enum: display, aggregate, redistribute.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### ENV006 — version is a supported spec version

- Severity: error · Level: L2 · Implemented
- Rule: Supported versions span at most two MAJORs.
- Fix: Set version to a supported spec version. Consumers use it to decide how to read the rest.
- Specification: https://cabuya.org/developers/spec/0.1/8-versioning-and-conformance#8-1

### ENV007 — Access-Control-Allow-Origin: * present

- Severity: error · Level: L2 · Implemented
- Rule: The one non-obvious MUST: without it every browser-based consumer needs a proxy.
- Fix: Send Access-Control-Allow-Origin: * on the feed. Without it a browser client cannot read you at all.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### ENV008 — ttl is a positive, plausible integer (1–86400)

- Severity: warning · Level: L2 · Implemented
- Rule: ttl is the caching contract; implausible values break polling consumers.
- Fix: Set ttl to a positive number of seconds between 1 and 86400. It is the caching contract, not a hint.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### ENV009 — Feed ≤ 5 MB and ≤ 10 000 records, or shards declared

- Severity: warning · Level: L2 · Implemented
- Rule: Beyond the size guidance, publishers SHOULD shard by municipality and declare shards in the manifest.
- Fix: Shard the feed and declare the shards, or reduce it below 5 MB and 10 000 records.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### ENV010 — Content-Type: application/json, UTF-8

- Severity: info · Level: L2 · Implemented
- Rule: Transport hygiene.
- Fix: Serve the feed as application/json with UTF-8. A text/plain feed is one many clients will refuse.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1


## Record

### REC001 — last_confirmed_at KEY present on every record (null is legal)

- Severity: error · Level: L2 · Implemented
- Rule: The confirmation key is REQUIRED; null is the honest "never confirmed". Omission is not.
- Fix: Include last_confirmed_at on every record. If nobody has confirmed it, publish null — that is the honest answer and consumers know what to do with it. Never invent one.
- Specification: https://cabuya.org/developers/spec/0.1/6-trust-and-verification#6-1

### REC002 — id matches the {publisher_id}:{local_id} shape

- Severity: error · Level: L2 · Implemented
- Rule: Record identity is {publisher_id}:{local_id} — globally unique with zero coordination.
- Fix: Use the {publisher_id}:{local_id} form for the record id, with your own publisher id.
- Specification: https://cabuya.org/developers/spec/0.1/5-identifiers#5-1

### REC003 — No minting in another publisher’s namespace

- Severity: error · Level: L2 · Implemented
- Rule: A publisher MUST NOT mint ids in another publisher’s namespace.
- Fix: Mint ids only in your own namespace. Reference another publisher’s record with same_as instead.
- Specification: https://cabuya.org/developers/spec/0.1/5-identifiers#5-1

### REC004 — Locator rule: address_text OR lat+lon present

- Severity: error · Level: L2 · Implemented
- Rule: A place you cannot locate directs no one.
- Fix: Give the record an address_text, or a lat and lon pair, or both. A place nobody can find is not a place.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### REC005 — Both locators present (RECOMMENDED)

- Severity: warning · Level: L2 · Implemented
- Rule: Address and coordinates together survive more consumer contexts than either alone.
- Fix: Add the second locator. Coordinates and an address answer different questions for a person on the ground.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### REC006 — public_url present and absolute

- Severity: error · Level: L2 · Implemented
- Rule: Link-out is the contact mechanism — contact values never travel.
- Fix: Add an absolute public_url pointing at your own page for this place, so a consumer can hand the reader back to you.
- Specification: https://cabuya.org/developers/spec/0.1/7-normative-exclusions#7-2

### REC007 — place_kind within the enum

- Severity: error · Level: L2 · Implemented
- Rule: The shared vocabulary is what makes crosswalks possible; unknown kinds use other + a namespaced extension.
- Fix: Use a place_kind from the enum, and put your own vocabulary in place_kind_ext alongside it.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### REC008 — municipality_code is a valid DIVIPOLA code

- Severity: warning · Level: L2 · Implemented
- Rule: Territorial coding is DIVIPOLA; publishers keep their raw string in municipality_text.
- Fix: Use the official DIVIPOLA code for the municipality. The shape is five digits.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### REC009 — source{} present with source_id

- Severity: error · Level: L2 · Implemented
- Rule: Provenance is structured, never prose — attribution and chains depend on it.
- Fix: Add source with a source_id, so attribution survives every hop the record makes.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-3

### REC010 — CR-2: name contains no operational-state token

- Severity: error · Level: L2 · Implemented
- Rule: Names MUST NOT encode operational state — state belongs in lifecycle_status / service_status.
- Fix: Take the operational state out of the name and put it in service_status. A name that says CLOSED is a name that is wrong the moment it reopens.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### REC011 — name and status fields do not contradict each other

- Severity: warning · Level: L2 · Implemented
- Rule: A record that says two things about its own state is a record a consumer cannot render honestly.
- Fix: Reconcile the name and the status fields. When they disagree a consumer has to guess, and it will guess wrong half the time.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### REC012 — CR-1: updated_at is not a reused last_confirmed_at

- Severity: error · Level: L2 · Implemented
- Rule: An edit is not a confirmation; the two timestamps do not interconvert.
- Fix: Set updated_at to when the row changed and last_confirmed_at to when a human verified it. Reusing one for the other tells consumers a place was checked when it was only edited.
- Specification: https://cabuya.org/developers/spec/0.1/6-trust-and-verification#6-1

### REC013 — expires_at set on inherently temporary place kinds

- Severity: warning · Level: L2 · Implemented
- Rule: Temporary places SHOULD declare when they stop being true.
- Fix: Set expires_at on a place that is temporary by nature, so consumers can stop showing it without asking you.
- Specification: https://cabuya.org/developers/spec/0.1/6-trust-and-verification#6-2

### REC014 — same_as entries are fully-qualified and one-hop

- Severity: warning · Level: L2 · Implemented
- Rule: same_as is a one-hop, non-transitive claim — never a transitive chain.
- Fix: Write same_as entries as fully-qualified {publisher_id}:{id} references, one hop only, and never as an authority claim.
- Specification: https://cabuya.org/developers/spec/0.1/5-identifiers#5-2

### REC015 — Unknown members are preserved, never rejected

- Severity: error · Level: L2 · Implemented
- Rule: Extensibility applies to the validator first: an unknown member MUST NOT fail validation.
- Fix: Preserve unknown members rather than dropping them. A field you do not understand may be the one another consumer needs.
- Specification: https://cabuya.org/developers/spec/0.1/8-versioning-and-conformance#8-4

### REC016 — x_ extensions are namespaced x_{publisher}_{field}

- Severity: warning · Level: L2 · Implemented
- Rule: Namespaced extensions prevent two publishers colliding on one private field name.
- Fix: Namespace the extension as x_{publisher}_{field}, so two publishers can extend the same record without colliding.
- Specification: https://cabuya.org/developers/spec/0.1/8-versioning-and-conformance#8-4

### REC017 — es baseline present for localized strings

- Severity: warning · Level: L2 · Implemented
- Rule: es is the REQUIRED baseline for human-readable strings; en is RECOMMENDED.
- Fix: Provide the es baseline for the localized string. Spanish is the floor every consumer can rely on.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### REC018 — No duplicate id within one feed

- Severity: error · Level: L2 · Implemented
- Rule: Two records with one id make every downstream dedupe wrong.
- Fix: Remove the duplicate id. Two records with one id make every consumer’s merge non-deterministic.
- Specification: https://cabuya.org/developers/spec/0.1/5-identifiers#5-1


## Person-level data

### PII001 — A contact value appears in any field, extensions included

- Severity: error · Level: L2 · Implemented
- Rule: Contact values MUST NOT travel in feeds — namespaced extensions do not exempt them.
- Fix: Remove the contact value. Use contact_available to say that contact exists, and public_url to send the reader to you for it.
- Specification: https://cabuya.org/developers/spec/0.1/7-normative-exclusions#7-2

### PII002 — confirmed_by is a role token, never a personal name

- Severity: error · Level: L2 · Implemented
- Rule: confirmed_by ∈ team | volunteer | official_source | partner:{publisher_id}.
- Fix: Replace the personal name in confirmed_by with a role token: team, volunteer, official_source, or partner:{publisher_id}.
- Specification: https://cabuya.org/developers/spec/0.1/6-trust-and-verification#6-1

### PII003 — Free text matches a personal-data pattern

- Severity: error · Level: L2 · Implemented
- Rule: Free text is the third leak channel: publishers MUST strip personal data from description / warning_text.
- Fix: Remove the personal data from the free text. The finding names the field and the pattern class, never the value it matched.
- Specification: https://cabuya.org/developers/spec/0.1/7-normative-exclusions#7-1

### PII004 — A person-level entity appears

- Severity: error · Level: L2 · Implemented
- Rule: Person-level data never federates — this is a join prohibition, not a field omission.
- Fix: Remove the person-level entity. Cabuya carries places; a person’s situation stays inside the application that owns it.
- Specification: https://cabuya.org/developers/spec/0.1/7-normative-exclusions#7-1

### PII005 — A field name matches the deny-list even if the value looks clean

- Severity: warning · Level: L2 · Implemented
- Rule: A field named for contact data will eventually carry it.
- Fix: Rename the field. A deny-listed name signals person-level data even when today’s values look clean, and the next row may not be.
- Specification: https://cabuya.org/developers/spec/0.1/7-normative-exclusions#7-2

### PII006 — A moderation verdict about a third party is republished

- Severity: error · Level: L2 · Implemented
- Rule: Moderation verdicts do not federate; suppressed records are omitted, never labelled downstream.
- Fix: Remove the moderation verdict. Republishing a judgement about a third party spreads it beyond anyone who can correct it.
- Specification: https://cabuya.org/developers/spec/0.1/7-normative-exclusions#7-3


## Behaviour

### BEH001 — Feed reachable on two probes; content-type stable

- Severity: error · Level: L2 · Implemented
- Rule: A feed that answers differently on two probes cannot be consumed reliably.
- Fix: Serve the feed consistently. Two probes returning different content types means a consumer cannot cache you at all.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### BEH002 — Always-now: last_updated advances with the probe clock on identical content

- Severity: error · Level: L2 · Implemented
- Rule: last_updated MUST be generated at build/publish time, never per request — a per-request timestamp is worse than no signal.
- Fix: Set last_updated to when the content actually changed. A timestamp that follows the clock makes every consumer refetch identical bytes forever.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### BEH003 — last_updated older than 7 × ttl (the stale badge state)

- Severity: warning · Level: L2 · Implemented
- Rule: Staleness is information, not failure — but it must be visible.
- Fix: Regenerate the feed, or raise ttl to match how often you really update. Stale beyond seven times your own ttl is a promise you are not keeping.
- Specification: https://cabuya.org/developers/spec/0.1/8-versioning-and-conformance#8-3

### BEH004 — Per-shard lastmod present (the incremental-sync pattern)

- Severity: warning · Level: L2 · Catalogued, not yet implemented (Task 15)
- Rule: A per-shard lastmod is working incremental sync at zero cost.
- Fix: Publish a lastmod per shard so consumers can fetch only what changed.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-4

### BEH005 — Declared shards reachable and envelope-consistent

- Severity: error · Level: L2 · Catalogued, not yet implemented (Task 15)
- Rule: A declared shard that disagrees with its siblings breaks every consumer that trusts the manifest.
- Fix: Make every declared shard reachable and consistent with the envelope that declares it.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1


## Read API

### API001 — Read API base reachable; envelope shape identical to the feed

- Severity: error · Level: L3 · Catalogued, not yet implemented (v0.2 (L3 checks — deferred from the walking skeleton))
- Rule: One schema, four transports.
- Fix: Serve the read API at the declared base with the same envelope shape as the static feed.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-1

### API002 — Static ≡ API: the same record is byte-compatible from both surfaces

- Severity: error · Level: L3 · Catalogued, not yet implemented (Task 15)
- Rule: The equivalence rule is what lets one schema serve four transports.
- Fix: Return byte-compatible records from the API and the feed. A consumer must not have to know which one it read.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-2

### API003 — cursor pagination ordered on a server sequence, not a timestamp

- Severity: error · Level: L3 · Catalogued, not yet implemented (v0.2)
- Rule: Timestamp cursors silently drop offline-composed records that arrive late.
- Fix: Paginate on a server-side sequence rather than a timestamp. Timestamps collide and records get skipped.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-1

### API004 — Documented query parameters accepted

- Severity: warning · Level: L3 · Catalogued, not yet implemented (v0.2)
- Rule: municipality, kind, bbox, updated_since, limit, cursor.
- Fix: Accept the documented query parameters, or remove them from the documentation.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-1

### API005 — CORS * on the API; no auth required for reads

- Severity: error · Level: L3 · Catalogued, not yet implemented (v0.2)
- Rule: Reads are public by design.
- Fix: Serve the API with CORS * and no authentication for reads. Public-interest data behind a key is not public.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-1

### API006 — Consumes ≥ 1 peer feed (partly self-declared)

- Severity: info · Level: L3 · Catalogued, not yet implemented (v0.2)
- Rule: L3 requires consuming as well as serving — the one requirement a probe cannot fully measure, so it is reported as info with the limitation stated.
- Fix: Consume at least one peer feed. The level is about interoperating, not only about publishing.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-3


## Write API

### WRT001 — POST accepts the {source, external_id, place} envelope

- Severity: error · Level: L4 · Catalogued, not yet implemented (v0.2)
- Rule: The write envelope is fixed.
- Fix: Accept the {source, external_id, place} envelope on POST.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-2

### WRT002 — Idempotency on (source, external_id): a replay does not duplicate

- Severity: error · Level: L4 · Catalogued, not yet implemented (v0.2)
- Rule: Re-sending is an upsert of the sender’s own contribution, never a duplicate.
- Fix: Make writes idempotent on (source, external_id). A replay after a timeout must not create a second record.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-2

### WRT003 — 409 on an id conflict outside the sender’s namespace

- Severity: error · Level: L4 · Catalogued, not yet implemented (v0.2)
- Rule: Namespace discipline is enforced at the write boundary.
- Fix: Return 409 when a sender tries to write an id outside its own namespace.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-2

### WRT004 — In auth:none mode, a moderation state is echoed and rate limiting is observable

- Severity: error · Level: L4 · Catalogued, not yet implemented (v0.2)
- Rule: Open writes REQUIRE mitigations: rate limiting, a moderation queue, and an echoed state.
- Fix: Echo the moderation state and make rate limiting observable, so an unauthenticated sender knows what happened to its write.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-2

### WRT005 — Republished records carry source.source_id = the original sender

- Severity: error · Level: L4 · Catalogued, not yet implemented (v0.2)
- Rule: The sender’s identity travels with the record forever.
- Fix: Keep source.source_id as the original sender when republishing. Attribution is the thing that must survive the hop.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-2


## Licensing

### LIC001 — Declared licence is not share-alike

- Severity: warning · Level: L2 · Implemented
- Rule: Share-alike licences poison aggregation for downstream consumers.
- Fix: Choose a licence that is not share-alike. A viral licence makes every aggregator’s whole dataset derivative, which is why most will not read you.
- Specification: https://cabuya.org/developers/spec/0.1/3-the-feed#3-1

### LIC002 — attribution string present for aggregators to display

- Severity: info · Level: L2 · Implemented
- Rule: Attribution is a consumption MUST; an explicit string makes it easy to honor.
- Fix: Add an attribution string for aggregators to display. Credit that travels with the data is the point of the network.
- Specification: https://cabuya.org/developers/spec/0.1/4-api-surface#4-3


## Site Navigation

- [Home](https://cabuya.org/)
- [Specification](https://cabuya.org/developers/spec)
- [Schemas](https://cabuya.org/developers/schemas)
- [RFCs](https://cabuya.org/rfcs)
- [Changelog](https://cabuya.org/changelog)
- [Developers](https://cabuya.org/developers)
- [Registry](https://cabuya.org/registry)
- [Governance](https://cabuya.org/governance)
- [Join](https://cabuya.org/join)
- [GitHub](https://github.com/Cabuya)
- [Agent skill repository](https://github.com/Cabuya/cabuya-skill)
- [Founding record](https://github.com/Cabuya/cabuya.org/tree/main/docs/context)
