Customer Database · API Governance Rules

Customer Database API Rules

Spectral linting rules defining API design standards and conventions for Customer Database.

15 Rules error 9 warn 6
View Rules File View on GitHub

Rule Categories

consent contact customer identity multi no properties schema soft

Rules

error
customer-record-has-stable-identifier
A customer record must declare a stable, system-assigned identifier. Without one there is nothing for a merge, a consent record, or an identity link to point at. SCIM calls this "id"; vCard calls it "UID".
$
warn
customer-record-declares-external-id
Records that sync with other systems need an identifier assigned elsewhere, or every integration invents its own correlation key. SCIM "externalId".
$
warn
contact-points-are-multi-valued
Every source standard models contact channels as multi-valued and typed. A single scalar "email" field cannot represent a customer with a work and a personal address, and forces lossy merges.
$.properties[?(@property.match(/^(email|phone|telephone|address)$/i))]
warn
multi-valued-attribute-has-primary
A multi-valued contact attribute needs a way to name the preferred entry, or consumers pick arbitrarily. SCIM uses a "primary" boolean; vCard uses PREF.
$..properties[?(@ && @.type == 'array')].items.properties
warn
soft-delete-flag-present
A customer database needs to distinguish a deactivated record from a deleted one, or referential integrity and audit history are lost on every deactivation. SCIM "active".
$
warn
consent-is-modeled
None of SCIM, vCard, or Schema.org models consent, so a customer schema that holds personal data for EU or California residents must model it explicitly or the legal basis for processing is unrecorded and unprovable.
$
error
consent-records-legal-basis
A consent record that does not name its lawful ground cannot be defended. GDPR Article 6(1) enumerates six.
$.properties.consent.items.properties
error
consent-records-purpose
Purpose limitation means consent captured for one purpose does not authorize another. A consent record without a purpose authorizes nothing specific.
$.properties.consent.items.properties
error
consent-records-capture-timestamp
The obligation is to prove what was true at the moment of processing, which requires knowing when permission was given — not when the row was written.
$.properties.consent.items.properties
error
identity-link-declares-method
A link asserted by exact key match and one inferred from weak-attribute similarity carry completely different weight. Without a method, downstream systems cannot tell a fact from a guess.
$.properties.identityLinks.items.properties
error
identity-link-declares-namespace
An identifier without a namespace is not resolvable and cannot be safely compared.
$.properties.identityLinks.items.properties
warn
properties-are-described
Every property needs a description. This is what makes a schema usable by an agent rather than merely valid — a field named "status" with no description is unactionable.
$..properties[*]
error
schema-declares-id
A schema without an $id cannot be referenced, cached, or reused across specs.
$
error
schema-declares-title
$
error
no-plaintext-credential-fields
SCIM defines "password" as write-only and never returned. A customer record schema that exposes a readable credential field is a defect, not a feature.
$.properties[?(@property.match(/^(password|secret|apiKey|token)$/i))]

Spectral Ruleset

customer-database-jsonschema-spectral-rules.yml Raw ↑
# generated: '2026-08-13'
# method: generated
# source: >-
#   Authored for this topic profile against the schemas in json-schema/ and the
#   obligations described in data-model/customer-database-data-model.yml. Not published
#   by any vendor. Run with Spectral against a customer-record JSON Schema:
#     spectral lint --ruleset rules/customer-database-jsonschema-spectral-rules.yml <schema.json>
#
# Intent: catch the modeling mistakes that make a customer database non-portable,
# non-auditable, or unsafe to hand to an agent. Every rule below maps to a documented
# obligation or to a named gap in the data model.

rules:

  customer-record-has-stable-identifier:
    description: >-
      A customer record must declare a stable, system-assigned identifier. Without one
      there is nothing for a merge, a consent record, or an identity link to point at.
      SCIM calls this "id"; vCard calls it "UID".
    message: 'Customer record must define an "id" property.'
    severity: error
    given: $
    then:
      field: properties.id
      function: truthy

  customer-record-declares-external-id:
    description: >-
      Records that sync with other systems need an identifier assigned elsewhere, or every
      integration invents its own correlation key. SCIM "externalId".
    message: 'Consider declaring "externalId" so other systems can correlate without internal keys.'
    severity: warn
    given: $
    then:
      field: properties.externalId
      function: truthy

  contact-points-are-multi-valued:
    description: >-
      Every source standard models contact channels as multi-valued and typed. A single
      scalar "email" field cannot represent a customer with a work and a personal address,
      and forces lossy merges.
    message: 'Contact channels should be arrays of typed objects, not scalar strings.'
    severity: warn
    given: $.properties[?(@property.match(/^(email|phone|telephone|address)$/i))]
    then:
      field: type
      function: pattern
      functionOptions:
        notMatch: '^string$'

  multi-valued-attribute-has-primary:
    description: >-
      A multi-valued contact attribute needs a way to name the preferred entry, or
      consumers pick arbitrarily. SCIM uses a "primary" boolean; vCard uses PREF.
    message: 'Multi-valued contact attributes should carry a "primary" flag.'
    severity: warn
    given: $..properties[?(@ && @.type == 'array')].items.properties
    then:
      field: primary
      function: truthy

  soft-delete-flag-present:
    description: >-
      A customer database needs to distinguish a deactivated record from a deleted one, or
      referential integrity and audit history are lost on every deactivation. SCIM "active".
    message: 'Declare an "active" boolean for soft delete rather than removing records.'
    severity: warn
    given: $
    then:
      field: properties.active
      function: truthy

  consent-is-modeled:
    description: >-
      None of SCIM, vCard, or Schema.org models consent, so a customer schema that holds
      personal data for EU or California residents must model it explicitly or the legal
      basis for processing is unrecorded and unprovable.
    message: 'No consent structure found. Consent is the largest interoperability gap in customer data — model it explicitly.'
    severity: warn
    given: $
    then:
      field: properties.consent
      function: truthy

  consent-records-legal-basis:
    description: >-
      A consent record that does not name its lawful ground cannot be defended. GDPR
      Article 6(1) enumerates six.
    message: 'Consent records must carry a "legalBasis".'
    severity: error
    given: $.properties.consent.items.properties
    then:
      field: legalBasis
      function: truthy

  consent-records-purpose:
    description: >-
      Purpose limitation means consent captured for one purpose does not authorize another.
      A consent record without a purpose authorizes nothing specific.
    message: 'Consent records must carry a "purpose".'
    severity: error
    given: $.properties.consent.items.properties
    then:
      field: purpose
      function: truthy

  consent-records-capture-timestamp:
    description: >-
      The obligation is to prove what was true at the moment of processing, which requires
      knowing when permission was given — not when the row was written.
    message: 'Consent records must carry a "capturedAt" timestamp.'
    severity: error
    given: $.properties.consent.items.properties
    then:
      field: capturedAt
      function: truthy

  identity-link-declares-method:
    description: >-
      A link asserted by exact key match and one inferred from weak-attribute similarity
      carry completely different weight. Without a method, downstream systems cannot tell
      a fact from a guess.
    message: 'Identity links must declare a "method" (deterministic, probabilistic, or declared).'
    severity: error
    given: $.properties.identityLinks.items.properties
    then:
      field: method
      function: truthy

  identity-link-declares-namespace:
    description: An identifier without a namespace is not resolvable and cannot be safely compared.
    message: 'Identity links must declare a "namespace".'
    severity: error
    given: $.properties.identityLinks.items.properties
    then:
      field: namespace
      function: truthy

  properties-are-described:
    description: >-
      Every property needs a description. This is what makes a schema usable by an agent
      rather than merely valid — a field named "status" with no description is unactionable.
    message: 'Property is missing a description.'
    severity: warn
    given: $..properties[*]
    then:
      field: description
      function: truthy

  schema-declares-id:
    description: A schema without an $id cannot be referenced, cached, or reused across specs.
    message: 'Schema must declare "$id".'
    severity: error
    given: $
    then:
      field: $id
      function: truthy

  schema-declares-title:
    message: 'Schema must declare a "title".'
    severity: error
    given: $
    then:
      field: title
      function: truthy

  no-plaintext-credential-fields:
    description: >-
      SCIM defines "password" as write-only and never returned. A customer record schema
      that exposes a readable credential field is a defect, not a feature.
    message: 'Credential field detected. Credentials must be write-only and never returned on read.'
    severity: error
    given: $.properties[?(@property.match(/^(password|secret|apiKey|token)$/i))]
    then:
      field: writeOnly
      function: truthy