{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://github.com/hasna/contracts/schema/hasna.service_contract.v1.json",
  "title": "Hasna Service Contract v1",
  "description": "Repo self-description (hasna.contract.json) for the Hasna Service Contract v1. Hosting story, product surfaces, and storage capabilities are separate declarations; the server data backend (sqlite | postgresql) is the only technical switch.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema",
    "name",
    "class",
    "contractVersion",
    "kitVersion"
  ],
  "allOf": [
    {
      "if": {
        "required": [
          "class"
        ],
        "properties": {
          "class": {
            "const": "saas"
          }
        }
      },
      "then": {
        "required": [
          "storage"
        ],
        "properties": {
          "storage": {
            "required": [
              "backend",
              "envPrefix"
            ],
            "properties": {
              "backend": {
                "const": "postgresql"
              }
            }
          }
        }
      }
    }
  ],
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Optional editor hint pointing at this JSON Schema."
    },
    "schema": {
      "const": "hasna.service_contract.v1"
    },
    "name": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
      "description": "Lowercase dashed app short-name, e.g. todos, mailery, loops."
    },
    "class": {
      "enum": [
        "library",
        "cli-with-store",
        "service",
        "saas"
      ]
    },
    "contractVersion": {
      "const": "v1"
    },
    "kitVersion": {
      "type": "string",
      "minLength": 1,
      "description": "Version of @hasna/contracts (the contract kit) the repo tracks."
    },
    "description": {
      "type": "string",
      "minLength": 1
    },
    "bins": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "description": "Declared bins. Allowlisted: <name>, <name>-cli, <name>-mcp, <name>-serve, <name>-worker, <name>-runner, <name>-daemon, <name>-migrate, <name>-doctor. The deployment app additionally supports its registered canonical operator entrypoint hasna-deploy."
    },
    "hosting": {
      "type": "array",
      "items": {
        "enum": [
          "user-hosted",
          "hasna-saas"
        ]
      },
      "minItems": 1,
      "uniqueItems": true,
      "description": "Customer-facing product stories. Public OSS cores include user-hosted; add hasna-saas only when a managed control plane exists."
    },
    "serviceSurfaces": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "status",
          "authMode"
        ],
        "allOf": [
          {
            "if": {
              "required": [
                "status"
              ],
              "properties": {
                "status": {
                  "const": "supported"
                },
                "kind": {
                  "const": "api"
                }
              }
            },
            "then": {
              "required": [
                "bin",
                "health",
                "readiness",
                "version"
              ]
            }
          }
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "kind": {
            "enum": [
              "api",
              "sdk",
              "mcp",
              "cli"
            ]
          },
          "status": {
            "enum": [
              "supported",
              "deferred",
              "unsupported"
            ]
          },
          "bin": {
            "type": "string",
            "minLength": 1
          },
          "mcpBin": {
            "type": "string",
            "minLength": 1
          },
          "authMode": {
            "enum": [
              "none",
              "local-only",
              "api-key",
              "session",
              "service-token",
              "custom"
            ]
          },
          "health": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "method",
              "path"
            ],
            "properties": {
              "method": {
                "const": "GET"
              },
              "path": {
                "type": "string",
                "pattern": "^/[A-Za-z0-9_./:*-]*$"
              },
              "public": {
                "type": "boolean"
              },
              "description": {
                "type": "string",
                "minLength": 1
              }
            }
          },
          "readiness": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "method",
              "path"
            ],
            "properties": {
              "method": {
                "const": "GET"
              },
              "path": {
                "type": "string",
                "pattern": "^/[A-Za-z0-9_./:*-]*$"
              },
              "public": {
                "type": "boolean"
              },
              "description": {
                "type": "string",
                "minLength": 1
              }
            }
          },
          "version": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "method",
              "path"
            ],
            "properties": {
              "method": {
                "const": "GET"
              },
              "path": {
                "type": "string",
                "pattern": "^/[A-Za-z0-9_./:*-]*$"
              },
              "public": {
                "type": "boolean"
              },
              "description": {
                "type": "string",
                "minLength": 1
              }
            }
          },
          "apiBasePath": {
            "type": "string",
            "pattern": "^/v[0-9]+$"
          },
          "openApiPath": {
            "type": "string",
            "pattern": "^/[A-Za-z0-9_./:-]*$"
          },
          "exportSubpath": {
            "type": "string",
            "pattern": "^\\.(?:\\/[A-Za-z0-9_.-]+(?:\\/[A-Za-z0-9_.-]+)*)?$",
            "description": "SDK package export key such as . or ./sdk."
          },
          "generatedFrom": {
            "type": "string",
            "pattern": "^/[A-Za-z0-9_./:-]*$",
            "description": "OpenAPI path used to generate the SDK."
          },
          "clientClassName": {
            "type": "string",
            "pattern": "^[A-Za-z_$][A-Za-z0-9_$]*$"
          },
          "deferReason": {
            "type": "string",
            "minLength": 1
          },
          "readinessGates": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "id",
                "kind"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "minLength": 1
                },
                "kind": {
                  "enum": [
                    "auth",
                    "storage",
                    "secret-ref",
                    "migration",
                    "health",
                    "readiness",
                    "redaction",
                    "smoke",
                    "operator",
                    "other"
                  ]
                },
                "required": {
                  "type": "boolean"
                },
                "command": {
                  "type": "string",
                  "minLength": 1
                },
                "evidenceRef": {
                  "type": "object"
                },
                "status": {
                  "enum": [
                    "pending",
                    "passed",
                    "failed",
                    "blocked",
                    "deferred"
                  ]
                },
                "summary": {
                  "type": "string",
                  "minLength": 1
                }
              }
            }
          }
        }
      },
      "description": "Declared API, SDK, MCP, and CLI product surfaces. Legacy entries without kind remain parseable; new manifests declare kind explicitly."
    },
    "storage": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "backend"
      ],
      "properties": {
        "backend": {
          "enum": [
            "sqlite",
            "postgresql"
          ],
          "description": "Active server data backend. sqlite|postgresql only."
        },
        "engines": {
          "type": "array",
          "items": {
            "enum": [
              "sqlite",
              "json",
              "postgresql"
            ]
          },
          "minItems": 1,
          "uniqueItems": true,
          "description": "Supported storage engines; capability metadata independent of the active backend."
        },
        "envPrefix": {
          "type": "string",
          "pattern": "^HASNA_[A-Z][A-Z0-9]*_$",
          "description": "Primary env prefix, e.g. HASNA_TODOS_."
        },
        "aliasEnvPrefix": {
          "type": "string",
          "pattern": "^[A-Z][A-Z0-9]*_$",
          "description": "Optional short alias env prefix, e.g. TODOS_."
        },
        "databaseUrlSecretRef": {
          "type": "string",
          "pattern": "^hasna/oss/[a-z0-9-]+/database-url$",
          "description": "Legacy/private-tier database secret ref. Public conformance rejects this field."
        },
        "sqlitePath": {
          "type": "string",
          "pattern": "\\.db$",
          "description": "Local sqlite path (~/.hasna/<name>/<name>.db)."
        },
        "pgTestGate": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "envVar",
            "command"
          ],
          "properties": {
            "envVar": {
              "type": "string",
              "pattern": "^[A-Z][A-Z0-9_]*_TEST_DATABASE_URL$"
            },
            "command": {
              "type": "string",
              "minLength": 1
            }
          },
          "description": "Environment-gated live PostgreSQL test command."
        }
      }
    },
    "publishing": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "status"
      ],
      "allOf": [
        {
          "if": {
            "required": [
              "status"
            ],
            "properties": {
              "status": {
                "const": "published"
              }
            }
          },
          "then": {
            "required": [
              "targets"
            ],
            "properties": {
              "targets": {
                "minItems": 1
              }
            }
          }
        },
        {
          "if": {
            "required": [
              "status"
            ],
            "properties": {
              "status": {
                "const": "unpublished"
              }
            }
          },
          "then": {
            "properties": {
              "targets": {
                "maxItems": 0
              }
            }
          }
        }
      ],
      "properties": {
        "status": {
          "enum": [
            "published",
            "unpublished"
          ],
          "description": "Whether the repo ships a published artifact. Declaring unpublished is a positive statement; omitting publishing entirely says only that the repo has not described how it ships."
        },
        "targets": {
          "type": "array",
          "uniqueItems": true,
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "package",
              "registry",
              "mechanism",
              "credential"
            ],
            "allOf": [
              {
                "if": {
                  "required": [
                    "mechanism"
                  ],
                  "properties": {
                    "mechanism": {
                      "const": "ci"
                    }
                  }
                },
                "then": {
                  "required": [
                    "workflow"
                  ]
                }
              },
              {
                "if": {
                  "required": [
                    "mechanism"
                  ],
                  "properties": {
                    "mechanism": {
                      "const": "manual"
                    }
                  }
                },
                "then": {
                  "not": {
                    "required": [
                      "workflow"
                    ]
                  }
                }
              },
              {
                "if": {
                  "required": [
                    "credential"
                  ],
                  "properties": {
                    "credential": {
                      "const": "trusted-publisher"
                    }
                  }
                },
                "then": {
                  "properties": {
                    "mechanism": {
                      "const": "ci"
                    }
                  }
                }
              }
            ],
            "properties": {
              "package": {
                "type": "string",
                "pattern": "^(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*$",
                "description": "Registry package name including any scope, e.g. @hasna/todos."
              },
              "registry": {
                "type": "string",
                "pattern": "^[a-z0-9][a-z0-9.-]*(?::[0-9]+)?(?:\\/[A-Za-z0-9._~-]+)*$",
                "description": "Registry host, optionally with port and path, and never a scheme or embedded credentials (registry.npmjs.org, npm.pkg.github.com). No registry is assumed by default."
              },
              "access": {
                "enum": [
                  "public",
                  "restricted"
                ],
                "description": "Registry visibility of the published artifact."
              },
              "mechanism": {
                "enum": [
                  "ci",
                  "manual"
                ],
                "description": "Where the publish is initiated and authorised from. Not a taxonomy of publish commands: a repo publishing through a bespoke script is still ci when a workflow drives it."
              },
              "credential": {
                "enum": [
                  "trusted-publisher",
                  "token"
                ],
                "description": "How the publish authenticates. trusted-publisher is workload identity exchanged at publish time; token is a long-lived registry credential."
              },
              "flow": {
                "enum": [
                  "direct",
                  "staged"
                ],
                "description": "Whether the artifact becomes installable in one step, or is uploaded first and promoted in a separate step."
              },
              "provenance": {
                "enum": [
                  "required",
                  "best-effort",
                  "none"
                ],
                "description": "Intent for build provenance/attestation. required means the release gate refuses a publish without it."
              },
              "workflow": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "provider",
                  "repository",
                  "file"
                ],
                "properties": {
                  "provider": {
                    "enum": [
                      "github-actions",
                      "gitlab-ci"
                    ],
                    "description": "CI provider the registry accepts as a trusted publisher."
                  },
                  "repository": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9._-]+\\/[A-Za-z0-9._-]+$",
                    "description": "owner/repo on the provider's forge."
                  },
                  "file": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9._-]+\\.ya?ml$",
                    "description": "Workflow file NAME, not a path; registries key the registration on the bare filename."
                  },
                  "environment": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Deployment environment gating the publish job. Absent means no environment gate, not unknown."
                  }
                },
                "description": "The exact triple a registry's trusted-publisher registration consumes."
              }
            }
          },
          "description": "One entry per published artifact per registry. A repo shipping several packages declares one target each."
        }
      },
      "description": "How the repo's artifacts reach consumers. Optional and additive; absence asserts nothing."
    },
    "metadata": {
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "conformance": {
          "type": "object",
          "additionalProperties": true,
          "properties": {
            "waiverProfile": {
              "const": "non-node-monorepo",
              "description": "Explicit surface-waiver eligibility for exceptional non-Node monorepos. Libraries are eligible for API/MCP waivers without this profile."
            },
            "waivedSurfaces": {
              "type": "array",
              "uniqueItems": true,
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "kind",
                  "reason"
                ],
                "properties": {
                  "kind": {
                    "enum": [
                      "api",
                      "sdk",
                      "mcp",
                      "cli"
                    ]
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            },
            "waivedStorageEngines": {
              "type": "array",
              "uniqueItems": true,
              "maxItems": 1,
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "engine",
                  "reason"
                ],
                "properties": {
                  "engine": {
                    "enum": [
                      "postgresql"
                    ]
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "allOf": [
                      {
                        "pattern": "\\S"
                      },
                      {
                        "pattern": "^[^\\u0000-\\u001f\\u007f]*$"
                      }
                    ]
                  },
                  "reviewedBy": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "allOf": [
                      {
                        "pattern": "\\S"
                      },
                      {
                        "pattern": "^[^\\u0000-\\u001f\\u007f]*$"
                      }
                    ]
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              },
              "description": "Explicit storage-engine exceptions, at most one per engine. Only a CLI-only cli-with-store repo (no <name>-serve bin, no supported api service surface, storage.backend sqlite, no hasna-saas story) may waive postgresql; sqlite is never waivable, expiresAt is a UTC RFC 3339 timestamp, and conformance stops honouring a waiver once it has passed."
            }
          }
        }
      }
    }
  }
}
