{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://subprocessors.gooddaysoftware.com/schemas/subprocessor-list-v1.json",
  "title": "GoodDay published subprocessor list",
  "description": "The complete published subprocessor list. This exact document is (a) served verbatim as https://subprocessors.gooddaysoftware.com/subprocessors.json, (b) embedded as JSON-LD in the page head, (c) rendered as the HTML table, and (d) summarised in /llms.txt. All four are emitted by one build from one payload and are asserted field-for-field identical at build time.\n\nSAFETY INVARIANT: this schema has no representation for a draft entry, a removed entry, a change-log record, a subscription, or a merchant identifier. `additionalProperties: false` at every level makes that structural rather than a filter that could be forgotten. See spec §Agent-specific safety constraints — hidden states must be hidden in the payload, not just in the rendering.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema_version",
    "object",
    "scope_statement",
    "controller",
    "last_updated",
    "data"
  ],
  "properties": {
    "schema_version": {
      "type": "string",
      "const": "1",
      "description": "Payload schema version. Bumped only on a breaking reshape; new optional fields are additive and do not bump it. Lets an agent branch safely."
    },
    "object": {
      "type": "string",
      "const": "subprocessor_list"
    },
    "scope_statement": {
      "type": "string",
      "minLength": 1,
      "description": "Plain-language statement that the list covers only subprocessors that process, store, or transmit personal data — so a reviewer does not mistake it for a complete vendor inventory. Required by AC 2. Present in the payload as well as the rendered page so an agent reading only JSON gets the same caveat a human does.",
      "examples": [
        "This list names every third party that processes, stores, or transmits personal data on GoodDay's behalf. It is not a complete inventory of GoodDay's vendors — vendors that do not handle personal data are out of scope and are not listed here."
      ]
    },
    "controller": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "name",
        "contact"
      ],
      "description": "The responsible entity. Institutional only — AC 3 forbids naming any individual anywhere on this surface, and this schema gives no field in which a person could be named.",
      "properties": {
        "name": {
          "type": "string",
          "const": "GoodDay Software, Inc."
        },
        "contact": {
          "type": "string",
          "format": "email",
          "description": "Role mailbox, never a personal address.",
          "const": "integrations@gooddaysoftware.com"
        }
      }
    },
    "last_updated": {
      "type": "string",
      "format": "date",
      "description": "Calendar date on which this content was last published (AC 13). Sourced from `last_updated` in the version-controlled content file, which CI requires to move whenever `entries` changes. An unmerged edit must never move this, and cannot: it is not on the branch the site builds from.",
      "examples": [
        "2026-08-07"
      ]
    },
    "notice_period_days": {
      "type": "integer",
      "const": 10,
      "description": "Advance-notice window GoodDay commits to before a newly disclosed subprocessor takes effect, per the DPA. Published so an agent can reason about the `pending` entries without external context."
    },
    "data": {
      "type": "array",
      "minItems": 1,
      "description": "Every entry in the noticed or in-effect state, in display order. NEVER paginated — see contracts/README.md deviation D5: a paginated legal disclosure lets an agent read page one and conclude it has the complete list.",
      "items": {
        "$ref": "#/$defs/entry"
      }
    }
  },
  "$defs": {
    "entry": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "id",
        "object",
        "name",
        "url",
        "purpose",
        "state"
      ],
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
          "description": "Stable public identifier. Immutable for the life of the entry; survives removal and restoration. This is the handle a DPA objection or a future per-merchant view cites. REVISION 2: a kebab-case slug authored by hand in the content file. Revision 1 pinned this to `^spe_[a-z2-7]{24}$` because ids were generated by the database that revision 2 removed; the identifier is now written and reviewed by a human, so it is readable by one.",
          "examples": [
            "google-cloud-platform",
            "stripe"
          ]
        },
        "object": {
          "type": "string",
          "const": "subprocessor"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "description": "The subprocessor's legal entity name (AC 1).",
          "examples": [
            "Google Cloud Platform",
            "Stripe"
          ]
        },
        "url": {
          "type": "string",
          "format": "uri",
          "pattern": "^https://",
          "description": "Link to the subprocessor's website (AC 1).",
          "examples": [
            "https://cloud.google.com"
          ]
        },
        "purpose": {
          "type": "string",
          "minLength": 1,
          "maxLength": 1000,
          "description": "Plain-language description of what GoodDay uses this subprocessor for (AC 1).",
          "examples": [
            "Primary cloud infrastructure. Runs the GoodDay application and workers, the operational database, the data warehouse, log storage, and secret management."
          ]
        },
        "state": {
          "type": "string",
          "description": "Whether the subprocessor is live. This is a CLOSED enum in v1: only these two values are valid, and a payload carrying anything else fails validation. `draft` and `removed` have no representation here by construction — they are absent from the build, not filtered out of it. New states arrive by bumping `schema_version` and serving a v2 document alongside this one, so a consumer pinned to v1 keeps seeing only these two. Defensive advice for consumers reading the JSON without validating: if you encounter a value you do not recognise, surface it rather than dropping the entry — an unrecognised state must never make a disclosed subprocessor invisible.",
          "enum": [
            "in_effect",
            "pending"
          ]
        },
        "effective_date": {
          "type": "string",
          "format": "date",
          "description": "The calendar date on which this subprocessor takes effect. REQUIRED when `state` is `pending`; omitted when `state` is `in_effect`. Date-only rather than a timestamp — deliberate deviation from the ISO-8601-timestamp convention, because a DPA notice period is a calendar-day concept and a timestamp would make 'is it in effect yet' timezone-dependent. Evaluated in America/Chicago. See contracts/README.md deviation D1.",
          "examples": [
            "2026-11-01"
          ]
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "state": {
                "const": "pending"
              }
            },
            "required": [
              "state"
            ]
          },
          "then": {
            "required": [
              "effective_date"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "state": {
                "const": "in_effect"
              }
            },
            "required": [
              "state"
            ]
          },
          "then": {
            "not": {
              "required": [
                "effective_date"
              ]
            }
          }
        }
      ]
    }
  },
  "examples": [
    {
      "schema_version": "1",
      "object": "subprocessor_list",
      "scope_statement": "This list names every third party that processes, stores, or transmits personal data on GoodDay's behalf. It is not a complete inventory of GoodDay's vendors — vendors that do not handle personal data are out of scope and are not listed here.",
      "controller": {
        "name": "GoodDay Software, Inc.",
        "contact": "integrations@gooddaysoftware.com"
      },
      "last_updated": "2026-08-07",
      "notice_period_days": 10,
      "data": [
        {
          "id": "google-cloud-platform",
          "object": "subprocessor",
          "name": "Google Cloud Platform",
          "url": "https://cloud.google.com",
          "purpose": "Primary cloud infrastructure. Runs the GoodDay application and workers, the operational database, the data warehouse, log storage, and secret management. All other processing happens on top of it.",
          "state": "in_effect"
        },
        {
          "id": "stripe",
          "object": "subprocessor",
          "name": "Stripe",
          "url": "https://stripe.com",
          "purpose": "Payment processing for GoodDay's wholesale invoicing and payments features. Pay-links and the payment page run on the merchant's own Stripe account — buyers enter card or bank details directly with Stripe, and GoodDay receives payment status and reference data back by webhook.",
          "state": "pending",
          "effective_date": "2026-11-01"
        }
      ]
    }
  ]
}
